diff --git a/.dockerignore b/.dockerignore
new file mode 100644
index 0000000..1908512
--- /dev/null
+++ b/.dockerignore
@@ -0,0 +1,25 @@
+.git
+.gitignore
+
+node_modules
+venv/
+.venv/
+__pycache__/
+*.pyc
+vendor/
+target/
+dist/
+build/
+*.egg-info/
+
+.env
+.idea/
+.vscode/
+*.log
+tmp/
+.DS_Store
+
+Dockerfile
+.dockerignore
+
+outputs/
\ No newline at end of file
diff --git a/.github/workflows/deploy_docs.yml b/.github/workflows/deploy_docs.yml
index 579e77a..81c9f75 100644
--- a/.github/workflows/deploy_docs.yml
+++ b/.github/workflows/deploy_docs.yml
@@ -1,4 +1,8 @@
-name: Deploy MkDocs Documentation
+name: Deploy Site (landing + docs)
+
+# Publishes to gh-pages:
+# / -> docs/landing (the EmbodiedGen V2 project landing page)
+# /docs/ -> MkDocs documentation (built from docs/documentation)
on:
push:
@@ -33,7 +37,7 @@ jobs:
- name: Set PYTHONPATH
run: echo "PYTHONPATH=$(pwd)" >> $GITHUB_ENV
- - name: Build docs
+ - name: Build docs (validation)
run: mkdocs build
deploy:
@@ -59,5 +63,21 @@ jobs:
- name: Set PYTHONPATH
run: echo "PYTHONPATH=$(pwd)" >> $GITHUB_ENV
- - name: Deploy docs
- run: mkdocs gh-deploy --force
+ - name: Build docs -> site/
+ run: mkdocs build
+
+ - name: Assemble publish dir (landing at root, docs under /docs/)
+ run: |
+ rm -rf public
+ mkdir -p public/docs
+ cp -a docs/landing/. public/
+ rm -rf public/tools public/README.md # build tooling / readme are not part of the published site
+ cp -a site/. public/docs/
+
+ - name: Configure git identity
+ run: |
+ git config user.name "github-actions[bot]"
+ git config user.email "41898282+github-actions[bot]@users.noreply.github.com"
+
+ - name: Publish to gh-pages
+ run: ghp-import -n -p -f -m "Deploy landing + docs ${GITHUB_SHA}" public
diff --git a/.gitignore b/.gitignore
index ec1dc31..556bf65 100644
--- a/.gitignore
+++ b/.gitignore
@@ -41,6 +41,7 @@ wheels*/
*.bak
.arcconfig
.vscode/
+.cursor/
# files
*.pack
@@ -51,6 +52,10 @@ wheels*/
# node
node_modules
+# site build artifacts (mkdocs build + assembled publish dir)
+/site/
+/public/
+
# local files
build.sh
__pycache__/
@@ -60,7 +65,17 @@ scripts/tools/
weights
apps/sessions/
apps/assets/
+.gradio/*
+CLAUDE.md
+AGENTS.md
+.github/copilot-instructions.md
+apps/gradio_cache/
# Larger than 1MB
-docs/assets/real2sim_mujoco.gif
-docs/assets/scene3d.gif
\ No newline at end of file
+docs/documentation/assets/real2sim_mujoco.gif
+docs/documentation/assets/scene3d.gif
+
+# Project landing page (docs/landing) โ keep its otherwise-ignored site files tracked
+!docs/landing/**/*.html
+!docs/landing/**/*.mp4
+!docs/landing/**/*.json
diff --git a/.gitmodules b/.gitmodules
index d215578..5a3fe7a 100644
--- a/.gitmodules
+++ b/.gitmodules
@@ -13,3 +13,16 @@
url = https://github.com/HochCC/sam-3d-objects.git
branch = main
shallow = true
+[submodule "thirdparty/GraspGen"]
+ path = thirdparty/GraspGen
+ url = https://github.com/NVlabs/GraspGen.git
+ branch = main
+ shallow = true
+[submodule "thirdparty/Hunyuan3D-Part"]
+ path = thirdparty/Hunyuan3D-Part
+ url = https://github.com/Tencent-Hunyuan/Hunyuan3D-Part.git
+ branch = main
+ shallow = true
+[submodule "thirdparty/infinigen"]
+ path = thirdparty/infinigen
+ url = https://github.com/princeton-vl/infinigen.git
diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml
index e76d477..7810ce8 100644
--- a/.pre-commit-config.yaml
+++ b/.pre-commit-config.yaml
@@ -8,6 +8,7 @@ repos:
description: Prevent giant files from being committed
entry: check-added-large-files
language: python
+ exclude: ^docs/
args: ["--maxkb=1024"]
- repo: local
hooks:
@@ -18,18 +19,18 @@ repos:
language: system
exclude: (?x)(^tools/|^thirdparty/|^patch_files/)
files: \.(c|cc|cxx|cpp|cu|h|hpp)$
- args: [--project=asset_recons, --path]
+ args: [--project=embodied_gen, --path]
- repo: local
hooks:
- - id: pycodestyle-python
- name: pep8-exclude-docs
- description: Check python code style.
- entry: pycodestyle
+ - id: ruff-check-python
+ name: ruff-check
+ description: Check and fix python lint issues.
+ entry: ruff check --fix
language: system
exclude: (?x)(^docs/|^thirdparty/|^scripts/build/)
files: \.(py)$
types: [file, python]
- args: [--config=setup.cfg]
+ args: [--config=pyproject.toml]
# pre-commit install --hook-type commit-msg to enable it
@@ -44,35 +45,12 @@ repos:
- repo: local
hooks:
- - id: pydocstyle-python
- name: pydocstyle-change-exclude-docs
- description: Check python doc style.
- entry: pydocstyle
- language: system
- exclude: (?x)(^docs/|^thirdparty/)
- files: \.(py)$
- types: [file, python]
- args: [--config=pyproject.toml]
- - repo: local
- hooks:
- - id: black
- name: black-exclude-docs
- description: black format
- entry: black
+ - id: ruff-format-python
+ name: ruff-format
+ description: Format python code.
+ entry: ruff format
language: system
exclude: (?x)(^docs/|^thirdparty/)
files: \.(py)$
types: [file, python]
args: [--config=pyproject.toml]
-
- - repo: local
- hooks:
- - id: isort
- name: isort
- description: isort format
- entry: isort
- language: system
- exclude: (?x)(^thirdparty/)
- files: \.(py)$
- types: [file, python]
- args: [--settings-file=pyproject.toml]
\ No newline at end of file
diff --git a/README.md b/README.md
index dc05d23..5143ca5 100644
--- a/README.md
+++ b/README.md
@@ -1,338 +1,270 @@
-# *EmbodiedGen*: Towards a Generative 3D World Engine for Embodied Intelligence
+# *EmbodiedGen V2*: An Agentic, Simulation-Ready 3D World Engine for Embodied AI
-[](https://horizonrobotics.github.io/EmbodiedGen/)
+[](https://horizonrobotics.github.io/EmbodiedGen/)
+[](https://horizonrobotics.github.io/EmbodiedGen/docs/)
[](https://github.com/HorizonRobotics/EmbodiedGen)
[](https://arxiv.org/abs/2506.10600)
[](https://www.youtube.com/watch?v=rG4odybuJRk)
+[](https://huggingface.co/datasets/HorizonRobotics/EmbodiedGenData)
[](https://mp.weixin.qq.com/s/HH1cPBhK2xcDbyCK4BBTbw)
-
[](https://huggingface.co/spaces/HorizonRobotics/EmbodiedGen-Gallery-Explorer)
[](https://huggingface.co/spaces/HorizonRobotics/EmbodiedGen-Image-to-3D)
[](https://huggingface.co/spaces/HorizonRobotics/EmbodiedGen-Text-to-3D)
[](https://huggingface.co/spaces/HorizonRobotics/EmbodiedGen-Texture-Gen)
-> ***EmbodiedGen*** is a generative engine to create diverse and interactive 3D worlds composed of high-quality 3D assets(mesh & 3DGS) with plausible physics, leveraging generative AI to address the challenges of generalization in embodied intelligence related research.
-> It composed of six key modules: `Image-to-3D`, `Text-to-3D`, `Texture Generation`, `Articulated Object Generation`, `Scene Generation` and `Layout Generation`.
+> **From intent to executable 3D worlds.**
+> ***EmbodiedGen*** compiles language, images, and edit commands into **simulation-ready 3D worlds** โ physically plausible assets, large-scale scenes, and task-driven interactive environments, deployable across every major robotics simulator.
-
+
---
-## โจ Table of Contents of EmbodiedGen
-[](https://horizonrobotics.github.io/EmbodiedGen/) Follow the documentation to get started!
-
-- [๐ผ๏ธ Image-to-3D](#image-to-3d)
-- [๐ Text-to-3D](#text-to-3d)
-- [๐จ Texture Generation](#texture-generation)
-- [๐ 3D Scene Generation](#3d-scene-generation)
+## โจ What's New in V2
+
+- ๐ฌ **3D Vibe Coding** โ build and edit sim-ready scenes through natural-language dialogue via Claude Code slash commands (`/embodiedgen:*`), each edit a bounded, physics-validated skill call.
+- ๐ **Large-scale scene generation** โ multi-room, navigable, instance-editable houses at controllable complexity tiers (`minimalist` โ `detail`).
+- ๐ฆ **One world, every simulator** โ a standardized layout loads with consistent geometry, collision, and physics across **SAPIEN, Isaac Sim, Isaac Gym, MuJoCo, Genesis, and PyBullet**.
+- ๐งฉ **Pluggable 3D backends** โ switch between **SAM3D**, **TRELLIS**, and the **Hunyuan3D Pro** cloud API with a single flag.
+- ๐งฅ **Beyond rigid bodies** โ text-conditioned garments deploy as deformable meshes in Genesis.
+- ๐ฆพ **Part-level affordance** โ functional part segmentation, per-part semantics, and simulation-validated 6-DoF grasp poses for any generated asset.
+- ๐ค **Closed-loop robot learning** โ policies trained purely in EmbodiedGen-generated worlds transfer to real robots (task success **9.7 โ 79.8%** in sim, **21.7 โ 75.0%** on real robots, from a companion [sim-to-real RL study](https://arxiv.org/abs/2603.18532)).
+
+## ๐ Table of Contents
+
+- [๐ Quick Start](#quick-start)
+- [๐งฑ Generate โ Sim-Ready 3D Assets](#generate-assets)
+- [๐ Scale โ Large-Scale Scenes](#scale-scenes)
+- [๐ Compose โ Task-Driven Worlds](#compose-worlds)
+- [๐ฌ Edit โ 3D Vibe Coding](#vibe-coding)
+- [๐ฆ Export โ Any Simulators](#any-simulators)
+- [๐ค Train โ Robot Learning](#robot-learning)
- [โ๏ธ Articulated Object Generation](#articulated-object-generation)
-- [๐๏ธ Layout (Interactive 3D Worlds) Generation](#layout-generation)
-- [๐ฎ Any Simulators](#any-simulators)
-
-[๐ฌ Feedback Wanted: How Do You Use EmbodiedGen & Whatโs Missing?](https://github.com/HorizonRobotics/EmbodiedGen/issues/66)
+- [๐งฉ 3D Scene Completion](#3d-scene-completion)
-## ๐ Quick Start
+---
-[](https://horizonrobotics.github.io/EmbodiedGen/)
+
๐ Quick Start
-### โ
Setup Environment
```sh
git clone https://github.com/HorizonRobotics/EmbodiedGen.git
cd EmbodiedGen
-git checkout v0.1.7
-git submodule update --init --recursive --progress
-conda create -n embodiedgen python=3.10.13 -y # recommended to use a new env.
+git checkout v2.0.0
+bash install/init_submodules.sh
+conda create -n embodiedgen python=3.10.13 -y
conda activate embodiedgen
-bash install.sh basic # around 20 mins
-# Optional: `bash install.sh extra` for scene3d-cli
+# bash install.sh cu126 && conda deactivate && conda activate embodiedgen # Optional: if you don't have local cuda126.
+bash install.sh basic # around 10 mins
```
-### โ
Starting from Docker
-
-We provide a pre-built Docker image on [Docker Hub](https://hub.docker.com/repository/docker/wangxinjie/embodiedgen) with a configured environment for your convenience. For more details, please refer to [Docker documentation](https://github.com/HorizonRobotics/EmbodiedGen/tree/master/docker).
-
-> **Note:** Model checkpoints are not included in the image, they will be automatically downloaded on first run. You still need to set up the GPT Agent manually.
+Set up the GPT agent (required by most pipelines): update the API key in `embodied_gen/utils/gpt_config.yaml`. Then generate your first sim-ready asset:
```sh
-IMAGE=wangxinjie/embodiedgen:env_v0.1.x
-CONTAINER=EmbodiedGen-docker-${USER}
-docker pull ${IMAGE}
-docker run -itd --shm-size="64g" --gpus all --cap-add=SYS_PTRACE --security-opt seccomp=unconfined --privileged --net=host --name ${CONTAINER} ${IMAGE}
-docker exec -it ${CONTAINER} bash
+img3d-cli --image_path apps/assets/example_image/sample_00.jpg \
+ --n_retry 2 --output_root outputs/imageto3d
+# โ outputs/imageto3d/sample_00/result: URDF + mesh(.obj/.glb) + 3DGS(.ply) + video
```
-### โ
Setup GPT Agent
+A pre-built Docker image is also available on [Docker Hub](https://hub.docker.com/repository/docker/wangxinjie/embodiedgen).
-Update the API key in file: `embodied_gen/utils/gpt_config.yaml`.
+โก๏ธ Full guide: [Installation & Setup](https://horizonrobotics.github.io/EmbodiedGen/docs/install.html) ยท [Docker](docker/README.md)
-You can choose between two backends for the GPT agent:
-
-- **`gpt-4o`** (Recommended) โ Use this if you have access to **Azure OpenAI**.
-- **`qwen2.5-vl`** โ An alternative with free usage via OpenRouter, apply a free key [here](https://openrouter.ai/settings/keys) and update `api_key` in `embodied_gen/utils/gpt_config.yaml` (50 free requests per day)
+---
+๐งฑ Generate โ Sim-Ready 3D Assets
-### ๐ธ Directly use EmbodiedGen All-Simulators-Ready Assets
+Turn a **single image** or a **text prompt** into a simulation-ready asset: metric geometry, convex collision proxy, VLM-inferred physical properties (scale, mass, friction), quality-checked with automatic retries โ packaged as URDF + mesh + 3DGS.
-[](https://huggingface.co/spaces/HorizonRobotics/EmbodiedGen-Gallery-Explorer) Explore EmbodiedGen generated assets that are ready for simulation across any simulators (SAPIEN, Isaac Sim, MuJoCo, PyBullet, Genesis, Isaac Gym etc.). Details in chapter [any-simulators](#any-simulators).
+
----
+```sh
+# Image โ 3D (backends: SAM3D | TRELLIS | HUNYUAN3D, via --image3d_model)
+img3d-cli --image_path apps/assets/example_image/sample_01.jpg --output_root outputs/imageto3d
-๐ผ๏ธ Image-to-3D
+# Text โ 3D
+text3d-cli --prompts "small bronze figurine of a lion" --output_root outputs/textto3d
-[](https://huggingface.co/spaces/HorizonRobotics/EmbodiedGen-Image-to-3D) Generate physically plausible 3D asset URDF from single input image, offering high-quality support for digital twin systems.
-(HF space is a simplified demonstration. For the full functionality, please refer to `img3d-cli`.)
+# Re-texture an existing mesh (Chinese & English prompts)
+texture-cli --mesh_path apps/assets/example_texture/meshes/horse.obj \
+ --prompt "A gray horse head with flying mane and brown eyes" --output_root outputs/texture_gen
+```
-
+The same generate-and-export path reaches **soft bodies**: text-conditioned garments deploy as deformable meshes in Genesis.
-### โ๏ธ Service
-Run the image-to-3D generation service locally.
-Models downloaded automatically on first run, please be patient.
-```sh
-# Run in foreground
-python apps/image_to_3d.py
-# Or run in the background
-CUDA_VISIBLE_DEVICES=0 nohup python apps/image_to_3d.py > /dev/null 2>&1 &
-```
+Any generated URDF can be further annotated with **part-level affordances** โ functional part segmentation, per-part semantics, and simulation-validated 6-DoF grasps (requires `bash install.sh affordance`):
-### โก API
-Generate physically plausible 3D assets from image input via the command-line API.
```sh
-img3d-cli --image_path apps/assets/example_image/sample_00.jpg apps/assets/example_image/sample_01.jpg \
---n_retry 2 --output_root outputs/imageto3d
-
-# See result(.urdf/mesh.obj/mesh.glb/gs.ply) in ${output_root}/sample_xx/result
+affordance-cli --urdf-paths apps/assets/example_affordance/ear_hear/sample.urdf \
+ --output-dirs outputs/affordance_annotation/ear_hear
```
-Support the use of [SAM3D](https://github.com/facebookresearch/sam-3d-objects) or [TRELLIS](https://github.com/microsoft/TRELLIS) as 3D generation model, modify `IMAGE3D_MODEL` in `embodied_gen/scripts/imageto3d.py` to switch model.
+โก๏ธ Full guides: [Image-to-3D](https://horizonrobotics.github.io/EmbodiedGen/docs/tutorials/image_to_3d.html) ยท [Text-to-3D](https://horizonrobotics.github.io/EmbodiedGen/docs/tutorials/text_to_3d.html) ยท [Texture Generation](https://horizonrobotics.github.io/EmbodiedGen/docs/tutorials/texture_edit.html) ยท [Soft-Body Simulation](https://horizonrobotics.github.io/EmbodiedGen/docs/tutorials/soft_body.html) ยท [Affordance](https://horizonrobotics.github.io/EmbodiedGen/docs/tutorials/affordance.html)
---
+๐ Scale โ Large-Scale Scenes
-๐ Text-to-3D
-
-[](https://huggingface.co/spaces/HorizonRobotics/EmbodiedGen-Text-to-3D) Create 3D assets from text descriptions for a wide range of geometry and styles. (HF space is a simplified demonstration. For the full functionality, please refer to `text3d-cli`.)
+Go beyond tabletops: generate **multi-room, navigable, instance-editable houses** as sim-ready backgrounds at a controllable complexity tier, or create photo-realistic **3DGS background scenes** from a text prompt.
-
+
-### โ๏ธ Service
-Deploy the text-to-3D generation service locally.
-
-Text-to-image model based on the Kolors model, supporting Chinese and English prompts.
-Models downloaded automatically on first run, please be patient.
```sh
-python apps/text_to_3d.py
-```
-
-### โก API
-Text-to-image model based on SD3.5 Medium, English prompts only.
-Usage requires agreement to the [model license(click accept)](https://huggingface.co/stabilityai/stable-diffusion-3.5-medium), models downloaded automatically.
-
-For large-scale 3D asset generation, set `--n_image_retry=4` `--n_asset_retry=3` `--n_pipe_retry=2`, slower but better, via automatic checking and retries. For more diverse results, omit `--seed_img`.
+# Room / house from a natural-language description (requires `bash install.sh room`)
+room-cli -m embodied_gen.scripts.room_gen.gen_room \
+ --output-root outputs/rooms --prompt "Wipe the table in a simple dining room"
+# or specify the profile explicitly: --room-type Kitchen --seed 42 --complexity simple
-```sh
-text3d-cli --prompts "small bronze figurine of a lion" "A globe with wooden base" "wooden table with embroidery" \
- --n_image_retry 1 --n_asset_retry 1 --n_pipe_retry 1 --seed_img 0 \
- --output_root outputs/textto3d
+# 3DGS background scene from text (requires `bash install.sh scene3d`)
+scene3d-cli --prompts "Art studio with easel and canvas" --output_dir outputs/bg_scenes/ --seed 0
```
-Text-to-image model based on the Kolors model.
-```sh
-bash embodied_gen/scripts/textto3d.sh \
- --prompts "A globe with wooden base and latitude and longitude lines" "ๆฉ่ฒ็ตๅจๆ้ป๏ผๆ็ฃจๆ็ป่" \
- --output_root outputs/textto3d_k
-```
-ps: models with more permissive licenses found in `embodied_gen/models/image_comm_model.py`
+โก๏ธ Full guides: [Room Generation](https://horizonrobotics.github.io/EmbodiedGen/docs/tutorials/room_gen.html) ยท [3D Scene Generation](https://horizonrobotics.github.io/EmbodiedGen/docs/tutorials/scene_gen.html)
---
+๐ Compose โ Task-Driven Worlds
-๐จ Texture Generation
-
-[](https://huggingface.co/spaces/HorizonRobotics/EmbodiedGen-Texture-Gen) Generate visually rich textures for 3D mesh.
-
-
+From a natural-language task description, EmbodiedGen parses a **scene graph** and composes a physically stable, directly loadable **interactive 3D world** โ background, context objects, manipulated targets, distractors, and robot.
+
+
+
+
+
+
-### โ๏ธ Service
-Run the texture generation service locally.
-Models downloaded automatically on first run, see `download_kolors_weights`, `geo_cond_mv`.
```sh
-python apps/texture_edit.py
-```
+layout-cli --task_descs "Place the pen in the mug on the desk" \
+ --bg_list "outputs/example_gen_scenes/scene_part_list.txt" \
+ --output_root "outputs/layouts_gen" --insert_robot
-### โก API
-Support Chinese and English prompts.
-```sh
-texture-cli --mesh_path "apps/assets/example_texture/meshes/robot_text.obj" \
-"apps/assets/example_texture/meshes/horse.obj" \
---prompt "ไธพ็็ๅญ็ๅๅฎ้ฃๆ ผๆบๅจไบบ๏ผๅคง็ผ็๏ผ็ๅญไธๅ็โHelloโ็ๆๅญ" \
-"A gray horse head with flying mane and brown eyes" \
---output_root "outputs/texture_gen" \
---seed 0
+# Load the generated layout into SAPIEN simulation
+sim-cli --layout_path "outputs/layouts_gen/task_0000/layout.json" \
+ --output_dir "outputs/layouts_gen/task_0000/sapien_render" --insert_robot
```
----
+โก๏ธ Full guide: [Layout Generation](https://horizonrobotics.github.io/EmbodiedGen/docs/tutorials/layout_gen.html) (background download, batch generation, layout randomization)
-๐ 3D Scene Generation
+---
-
+๐ฌ Edit โ 3D Vibe Coding
-### โก API
-> Run `bash install.sh extra` to install additional requirements if you need to use `scene3d-cli`.
+Build and edit sim-ready 3D worlds **through dialogue**. EmbodiedGen ships a Claude Code plugin whose slash commands wrap the generation and spatial-computing skills โ each instruction is a bounded, physics-validated skill call that preserves a deployable world state.
-It takes ~30mins to generate a color mesh and 3DGS per scene.
+
```sh
-CUDA_VISIBLE_DEVICES=0 scene3d-cli \
---prompts "Art studio with easel and canvas" \
---output_dir outputs/bg_scenes/ \
---seed 0 \
---gs3d.max_steps 4000 \
---disable_pano_check
+bash install/install_agent_plugin.sh # register the plugin in Claude Code
```
----
-
-
-โ๏ธ Articulated Object Generation
-
-See our paper published in NeurIPS 2025.
-[[Arxiv Paper]](https://arxiv.org/abs/2505.20460) |
-[[Gradio Demo]](https://huggingface.co/spaces/HorizonRobotics/DIPO) |
-[[Code]](https://github.com/RQ-Wu/DIPO)
-
-
-
+| Command | What it does |
+|---------|--------------|
+| `/embodiedgen:gen_assets` | Generate 3D assets from images or text |
+| `/embodiedgen:gen_indoor` | Generate rooms or multi-room houses |
+| `/embodiedgen:gen_bg` | Generate 3DGS background scenes |
+| `/embodiedgen:gen_layout` | Compose task-driven interactive worlds |
+| `/embodiedgen:vibe3d` | Insert / remove / place objects in a scene via natural language |
+| `/embodiedgen:sim` | Render layouts in SAPIEN simulation |
+| `/embodiedgen:convert` | Export assets to USD / MJCF / URDF |
+| `/embodiedgen:process` | Scale or rotate existing assets |
+โก๏ธ Full guide: [3D Vibe Coding](https://horizonrobotics.github.io/EmbodiedGen/docs/tutorials/vibe_coding.html)
---
+๐ฆ Export โ Any Simulators
-๐๏ธ Layout(Interactive 3D Worlds) Generation
+One standardized asset, six engines, zero manual adaptation โ consistent geometry, collision, textures, and physical metadata everywhere.
-### ๐ฌ Generate Layout from task description
+| Simulator | How to use EmbodiedGen assets |
+|-----------|------------------------------|
+| [SAPIEN](https://github.com/haosulab/SAPIEN) / [IsaacGym](https://github.com/isaac-sim/IsaacGymEnvs) / [PyBullet](https://github.com/bulletphysics/bullet3) | Generated `.urdf` used **directly** |
+| [MuJoCo](https://github.com/google-deepmind/mujoco) / [Genesis](https://github.com/Genesis-Embodied-AI/Genesis) | `MeshtoMJCFConverter` โ MJCF |
+| [IsaacSim](https://github.com/isaac-sim/IsaacSim) | `MeshtoUSDConverter` โ USD |
-
+
-Text-to-image model based on SD3.5 Medium, usage requires agreement to the [model license](https://huggingface.co/stabilityai/stable-diffusion-3.5-medium). All models auto-downloaded at the first run.
+โก๏ธ Full guide: [Any Simulators](https://horizonrobotics.github.io/EmbodiedGen/docs/tutorials/any_simulators.html) (conversion API & examples)
-You can generate any desired room as background using `scene3d-cli`. As each scene takes approximately 30 minutes to generate, we recommend pre-generating them for efficiency and adding them to `outputs/example_gen_scenes/scene_part_list.txt`.
+---
-We provided some sample background assets created with `scene3d-cli`. Download them(~2G) using:
-```sh
-hf download HorizonRobotics/EmbodiedGenData \
- --repo-type dataset --local-dir outputs \
- --include "example_gen_scenes/scene_00[01][0-9]/**" \
- "example_gen_scenes/scene_part_list.txt"
-```
+๐ค Train โ Robot Learning
-Generating one interactive 3D scene from task description with `layout-cli` takes approximately 30 minutes.
-```sh
-layout-cli --task_descs "Place the pen in the mug on the desk" "Put the fruit on the table on the plate" \
---bg_list "outputs/example_gen_scenes/scene_part_list.txt" --output_root "outputs/layouts_gen" --insert_robot
-```
+Generated worlds are not just viewable โ they are **online training environments**. Spin up parallel `gym` environments from a generated layout, record sensor and trajectory data, and evaluate grasp quality of generated assets.
-Run multiple tasks defined in `task_list.txt` in the backend.
-Remove `--insert_robot` if you don't consider the robot pose in layout generation.
```sh
-CUDA_VISIBLE_DEVICES=0 nohup layout-cli \
---task_descs "apps/assets/example_layout/task_list.txt" \
---bg_list "outputs/example_gen_scenes/scene_part_list.txt" \
---n_image_retry 4 --n_asset_retry 3 --n_pipe_retry 3 \
---output_root "outputs/layouts_gens" --insert_robot > layouts_gens.log &
-```
+# Parallel simulation environments from a generated layout
+python embodied_gen/scripts/parallel_sim.py \
+ --layout_file "outputs/layouts_gen/task_0000/layout.json" \
+ --output_dir "outputs/parallel_sim/task_0000" --num_envs 16
-Using `compose_layout.py`, you can recompose the layout of the generated interactive 3D scenes.
-```sh
-python embodied_gen/scripts/compose_layout.py \
---layout_path "outputs/layouts_gens/task_0000/layout.json" \
---output_dir "outputs/layouts_gens/task_0000/recompose" \
---insert_robot
+# Grasp-quality evaluation of a generated URDF (ManiSkill + SAPIEN)
+python embodied_gen/scripts/eval_collision_success.py \
+ --urdf-path outputs/imageto3d/sample_00/result/sample_00.urdf --num-trials 4
```
-We provide `sim-cli`, that allows users to easily load generated layouts into an interactive 3D simulation using the SAPIEN engine (will support for more simulators in future updates).
-
-```sh
-sim-cli --layout_path "outputs/layouts_gen/task_0000/layout.json" \
---output_dir "outputs/layouts_gen/task_0000/sapien_render" --insert_robot
-```
+In a companion [sim-to-real RL study](https://arxiv.org/abs/2603.18532), policies trained purely in EmbodiedGen-generated worlds reached **79.8%** simulation and **75.0%** real-robot task success.
-Example: generate multiple parallel simulation envs with `gym.make` and record sensor and trajectory data.
+โก๏ธ Full guide: [Robot Learning](https://horizonrobotics.github.io/EmbodiedGen/docs/tutorials/robot_learning.html)
-
-
-
-
-
-
+---
-```sh
-python embodied_gen/scripts/parallel_sim.py \
---layout_file "outputs/layouts_gen/task_0000/layout.json" \
---output_dir "outputs/parallel_sim/task_0000" \
---num_envs 16
-```
+โ๏ธ Articulated Object Generation
-### ๐ผ๏ธ Real-to-Sim Digital Twin
+See our paper **DIPO** published in NeurIPS 2025:
+[[arXiv]](https://arxiv.org/abs/2505.20460) | [[Gradio Demo]](https://huggingface.co/spaces/HorizonRobotics/DIPO) | [[Code]](https://github.com/RQ-Wu/DIPO)
-
+
---
-๐ฎ Any Simulators
-
-Use EmbodiedGen-generated assets with correct physical collisions and consistent visual effects in any simulator
-([isaacsim](https://github.com/isaac-sim/IsaacSim), [mujoco](https://github.com/google-deepmind/mujoco), [genesis](https://github.com/Genesis-Embodied-AI/Genesis), [pybullet](https://github.com/bulletphysics/bullet3), [isaacgym](https://github.com/isaac-sim/IsaacGymEnvs), [sapien](https://github.com/haosulab/SAPIEN)).
-Example in `tests/test_examples/test_asset_converter.py`.
-
-| Simulator | Conversion Class |
-|-----------|------------------|
-| [isaacsim](https://github.com/isaac-sim/IsaacSim) | MeshtoUSDConverter |
-| [mujoco](https://github.com/google-deepmind/mujoco) / [genesis](https://github.com/Genesis-Embodied-AI/Genesis) | MeshtoMJCFConverter |
-| [sapien](https://github.com/haosulab/SAPIEN) / [isaacgym](https://github.com/isaac-sim/IsaacGymEnvs) / [pybullet](https://github.com/bulletphysics/bullet3) | EmbodiedGen generated .urdf can be used directly |
+๐งฉ 3D Scene Completion
+See our paper **3D-Fixer** published in CVPR 2026:
+[[arXiv]](https://arxiv.org/abs/2604.04406) | [[Project Page]](https://zx-yin.github.io/3dfixer/) | [[Online Demo]](https://huggingface.co/spaces/HorizonRobotics/3D-Fixer) | [[Code]](https://github.com/HorizonRobotics/3D-Fixer)
-
+
---
## For Developer
+
```sh
pip install -e .[dev] && pre-commit install
python -m pytest # Pass all unit-test are required.
-# mkdocs serve --dev-addr 0.0.0.0:8000
-# mkdocs gh-deploy --force
```
## ๐ Citation
If you use EmbodiedGen in your research or projects, please cite:
+
+```bibtex
+@article{wang2026embodiedgenv2,
+ title = {EmbodiedGen V2: An Agentic, Simulation-Ready 3D World Engine for Embodied AI},
+ author = {Xinjie Wang and Liu Liu and Taojun Ding and Andrew Choi and Chaodong Huang and
+ Mengao Zhao and Ziang Li and Jackson Jiang and Chunlei Yu and Shengxiang Liu and
+ Wei Xu and Zhizhong Su},
+ journal = {arXiv preprint arXiv:2506.10600},
+ year = {2026}
+}
+```
```bibtex
@misc{wang2025embodiedgengenerative3dworld,
- title={EmbodiedGen: Towards a Generative 3D World Engine for Embodied Intelligence},
- author={Xinjie Wang and Liu Liu and Yu Cao and Ruiqi Wu and Wenkang Qin and Dehui Wang and Wei Sui and Zhizhong Su},
- year={2025},
- eprint={2506.10600},
- archivePrefix={arXiv},
- primaryClass={cs.RO},
- url={https://arxiv.org/abs/2506.10600},
+ title = {EmbodiedGen: Towards a Generative 3D World Engine for Embodied Intelligence},
+ author = {Xinjie Wang and Liu Liu and Yu Cao and Ruiqi Wu and Wenkang Qin and
+ Dehui Wang and Wei Sui and Zhizhong Su},
+ year = {2025},
+ eprint = {2506.10600},
+ archivePrefix = {arXiv},
+ primaryClass = {cs.RO},
+ url = {https://arxiv.org/abs/2506.10600}
}
```
@@ -341,7 +273,8 @@ If you use EmbodiedGen in your research or projects, please cite:
## ๐ Acknowledgement
EmbodiedGen builds upon the following amazing projects and models:
-๐ [Trellis](https://github.com/microsoft/TRELLIS) | ๐ [Hunyuan-Delight](https://huggingface.co/tencent/Hunyuan3D-2/tree/main/hunyuan3d-delight-v2-0) | ๐ [Segment Anything](https://github.com/facebookresearch/segment-anything) | ๐ [Rembg](https://github.com/danielgatis/rembg) | ๐ [RMBG-1.4](https://huggingface.co/briaai/RMBG-1.4) | ๐ [Stable Diffusion x4](https://huggingface.co/stabilityai/stable-diffusion-x4-upscaler) | ๐ [Real-ESRGAN](https://github.com/xinntao/Real-ESRGAN) | ๐ [Kolors](https://github.com/Kwai-Kolors/Kolors) | ๐ [ChatGLM3](https://github.com/THUDM/ChatGLM3) | ๐ [Aesthetic Score](http://captions.christoph-schuhmann.de/aesthetic_viz_laion_sac+logos+ava1-l14-linearMSE-en-2.37B.html) | ๐ [Pano2Room](https://github.com/TrickyGo/Pano2Room) | ๐ [Diffusion360](https://github.com/ArcherFMY/SD-T2I-360PanoImage) | ๐ [Kaolin](https://github.com/NVIDIAGameWorks/kaolin) | ๐ [diffusers](https://github.com/huggingface/diffusers) | ๐ [gsplat](https://github.com/nerfstudio-project/gsplat) | ๐ [QWEN-2.5VL](https://github.com/QwenLM/Qwen2.5-VL) | ๐ [GPT4o](https://platform.openai.com/docs/models/gpt-4o) | ๐ [SD3.5](https://huggingface.co/stabilityai/stable-diffusion-3.5-medium) | ๐ [ManiSkill](https://github.com/haosulab/ManiSkill) | ๐ [SAM3D](https://github.com/facebookresearch/sam-3d-objects)
+๐ [Trellis](https://github.com/microsoft/TRELLIS) | ๐ [Hunyuan-Delight](https://huggingface.co/tencent/Hunyuan3D-2/tree/main/hunyuan3d-delight-v2-0) | ๐ [Segment Anything](https://github.com/facebookresearch/segment-anything) | ๐ [Rembg](https://github.com/danielgatis/rembg) | ๐ [RMBG-1.4](https://huggingface.co/briaai/RMBG-1.4) | ๐ [Real-ESRGAN](https://github.com/xinntao/Real-ESRGAN) | ๐ [Kolors](https://github.com/Kwai-Kolors/Kolors) | ๐ [Aesthetic Score](http://captions.christoph-schuhmann.de/aesthetic_viz_laion_sac+logos+ava1-l14-linearMSE-en-2.37B.html) | ๐ [Pano2Room](https://github.com/TrickyGo/Pano2Room) | ๐ [Diffusion360](https://github.com/ArcherFMY/SD-T2I-360PanoImage) | ๐ [Kaolin](https://github.com/NVIDIAGameWorks/kaolin) | ๐ [diffusers](https://github.com/huggingface/diffusers) | ๐ [gsplat](https://github.com/nerfstudio-project/gsplat) | ๐ [ChatGPT](https://platform.openai.com/docs/models/gpt-4o) | ๐ [SD3.5](https://huggingface.co/stabilityai/stable-diffusion-3.5-medium) | ๐ [ManiSkill](https://github.com/haosulab/ManiSkill) | ๐ [SAM3D](https://github.com/facebookresearch/sam-3d-objects) | ๐ [infinigen](https://github.com/princeton-vl/infinigen)
+๐ [Trellis](https://github.com/microsoft/TRELLIS) | ๐ [Hunyuan-Delight](https://huggingface.co/tencent/Hunyuan3D-2/tree/main/hunyuan3d-delight-v2-0) | ๐ [Hunyuan3D-Part](https://github.com/Tencent-Hunyuan/Hunyuan3D-Part) | ๐ [GraspGen](https://github.com/NVlabs/GraspGen) | ๐ [Segment Anything](https://github.com/facebookresearch/segment-anything) | ๐ [Rembg](https://github.com/danielgatis/rembg) | ๐ [RMBG-1.4](https://huggingface.co/briaai/RMBG-1.4) | ๐ [Stable Diffusion x4](https://huggingface.co/stabilityai/stable-diffusion-x4-upscaler) | ๐ [Real-ESRGAN](https://github.com/xinntao/Real-ESRGAN) | ๐ [Kolors](https://github.com/Kwai-Kolors/Kolors) | ๐ [ChatGLM3](https://github.com/THUDM/ChatGLM3) | ๐ [Aesthetic Score](http://captions.christoph-schuhmann.de/aesthetic_viz_laion_sac+logos+ava1-l14-linearMSE-en-2.37B.html) | ๐ [Pano2Room](https://github.com/TrickyGo/Pano2Room) | ๐ [Diffusion360](https://github.com/ArcherFMY/SD-T2I-360PanoImage) | ๐ [Kaolin](https://github.com/NVIDIAGameWorks/kaolin) | ๐ [diffusers](https://github.com/huggingface/diffusers) | ๐ [gsplat](https://github.com/nerfstudio-project/gsplat) | ๐ [QWEN-2.5VL](https://github.com/QwenLM/Qwen2.5-VL) | ๐ [GPT4o](https://platform.openai.com/docs/models/gpt-4o) | ๐ [SD3.5](https://huggingface.co/stabilityai/stable-diffusion-3.5-medium) | ๐ [ManiSkill](https://github.com/haosulab/ManiSkill) | ๐ [SAM3D](https://github.com/facebookresearch/sam-3d-objects) | ๐ [infinigen](https://github.com/princeton-vl/infinigen)
---
diff --git a/apps/assets/example_scene/text_prompts.txt b/apps/assets/example_scene/text_prompts.txt
index 727df54..eae0049 100644
--- a/apps/assets/example_scene/text_prompts.txt
+++ b/apps/assets/example_scene/text_prompts.txt
@@ -1,75 +1,57 @@
# Living Room
-Cozy living room with soft sofa and warm lighting
-Spacious living room with wooden floor and large windows
-Modern living room with neutral colors and minimalist decor
-Bright living room with plants and natural sunlight
-Classic living room with comfortable armchairs and carpet
+Rustic spacious living room with wooden beams and stone fireplace
+Minimalist spacious living room with neutral tones and plush throw blankets
# Kitchen
Clean kitchen with white cabinets and stainless steel appliances
-Kitchen with wooden countertops and hanging pots
Rustic kitchen with brick walls and vintage stove
-Bright kitchen with large island and bar stools
-Modern kitchen with sleek design and open shelves
+Farmhouse kitchen with open shelves and ceramic sink
# Bedroom
-Minimalist bedroom with white walls and cozy bed
-Warm bedroom with wooden furniture and soft rugs
-Bedroom with desk and natural light
-Classic bedroom with bedside lamps and curtains
-Bright bedroom with colorful pillows and plants
-
-# Dining Room
-Elegant dining room with wooden table and chandelier
-Casual dining room with bench seating and simple decor
-Bright dining room with large windows and natural light
-Rustic dining room with exposed beams and farmhouse table
-Modern dining room with glass table and sleek chairs
+Classic spacious bedroom with bedside lamps and curtains
+Minimalist bedroom with white linens and wooden bedframe
+Modern bedroom with sleek furniture and abstract wall art
# Office Room
Organized office with wooden desk and ergonomic chair
-Office with computer setup and bookshelves
-Bright office with large windows and plants
-Minimalist office with clean desk and laptop
-Cozy office with soft lighting and storage cabinets
+Industrial office with metal desk and exposed brick wall
+Bright office with large windows and potted plants
# Study Room
-Quiet study room with large desk and bookshelf
-Study room with comfortable chair and lamp
-Bright study room with natural light and workspace
Classic study room with wooden furniture and papers
-Modern study room with clean design and minimal clutter
+Rustic study room with wooden bookshelves and woven rug
# Gym Room
-Home gym with treadmill and free weights
-Gym with yoga mats and dumbbells
-Bright gym with large mirrors and exercise bike
Minimalist gym with clean floor and resistance bands
-Spacious gym with bench press and fitness equipment
+Home gym with yoga mats and natural light
# Workshop
-Woodworking workshop with tools and workbench
Workshop with organized tool storage
Bright workshop with large windows and materials
-Metalworking workshop with machines and safety gear
Creative workshop with paint and crafting supplies
-# Bathroom
-Bright modern bathroom with wooden accents
-Cozy tiled bathroom with warm lighting
-
-# Toilet Room
-Minimalist toilet room with white ceramic fixtures
-Compact toilet room with wall-mounted sink
-
# Storage Room
Organized storage room with labeled boxes
-Cluttered storage room with metal shelves
-
-# Meeting Room
-Bright meeting room with glass walls
-Classic meeting room with wooden table
+Minimalist storage room with stackable bins and clean floor
+Compact storage room with foldable shelves and bright lighting
# Studio Room
Art studio with easel and canvas
-Music studio with acoustic foam panels
\ No newline at end of file
+Dance studio with mirrored walls and wooden floor
+Creative studio with mixed media supplies and colorful decor
+
+# Bathroom
+Modern bathroom with white tiles and glass shower
+Cozy bathroom with clawfoot tub and soft towels
+Minimalist bathroom with clean lines and monochrome palette
+
+# Lab or Office Room
+Sleek, sterile modern laboratory with bright white surfaces
+Spotless laboratory with labeled shelves and tools
+Well-lit clean lab with uncluttered work surfaces
+Plain, clean laboratory with basic lab furniture
+Bright, organized laboratory with advanced digital displays
+Modern workspace mixing sterile lab benches and casual seating
+Immaculate lab filled with advanced scientific devices
+Simple sterile lab with white walls and clear counters
+Minimalist office laboratory with seamless white surfaces
\ No newline at end of file
diff --git a/apps/common.py b/apps/common.py
index 55e30da..449d026 100644
--- a/apps/common.py
+++ b/apps/common.py
@@ -14,11 +14,20 @@
# implied. See the License for the specific language governing
# permissions and limitations under the License.
-import spaces
-from embodied_gen.utils.monkey_patches import monkey_path_trellis
+import spaces # noqa: E402
+from embodied_gen.utils.monkey_patch.gradio import (
+ _disable_xformers_flash3,
+ _neutralize_warp_in_parent,
+ _patch_open3d_cuda_device_count_bug,
+)
+from embodied_gen.utils.monkey_patch.trellis import monkey_path_trellis
+_neutralize_warp_in_parent()
+_patch_open3d_cuda_device_count_bug()
+_disable_xformers_flash3()
monkey_path_trellis()
+
import gc
import logging
import os
@@ -45,7 +54,7 @@
RembgRemover,
SAMPredictor,
)
-from embodied_gen.models.sr_model import ImageRealESRGAN, ImageStableSR
+from embodied_gen.models.sr_model import ImageRealESRGAN
from embodied_gen.scripts.render_gs import entrypoint as render_gs_api
from embodied_gen.scripts.render_mv import build_texture_gen_pipe, infer_pipe
from embodied_gen.scripts.text2image import (
@@ -73,7 +82,6 @@
current_dir = os.path.dirname(current_file_path)
sys.path.append(os.path.join(current_dir, ".."))
from thirdparty.TRELLIS.trellis.pipelines import TrellisImageTo3DPipeline
-from thirdparty.TRELLIS.trellis.utils import postprocessing_utils
logging.basicConfig(
format="%(asctime)s - %(levelname)s - %(message)s", level=logging.INFO
@@ -81,18 +89,20 @@
logger = logging.getLogger(__name__)
os.environ["GRADIO_ANALYTICS_ENABLED"] = "false"
-
+os.environ.setdefault("OPENAI_API_KEY", "sk-placeholder")
MAX_SEED = 100000
+# Global variables for lazy initialization
+_RBG_REMOVER = None
+_RBG14_REMOVER = None
+
# DELIGHT = DelightingModel()
# IMAGESR_MODEL = ImageRealESRGAN(outscale=4)
# IMAGESR_MODEL = ImageStableSR()
if os.getenv("GRADIO_APP").startswith("imageto3d"):
- RBG_REMOVER = RembgRemover()
- RBG14_REMOVER = BMGG14Remover()
SAM_PREDICTOR = SAMPredictor(model_type="vit_h", device="cpu")
if "sam3d" in os.getenv("GRADIO_APP"):
- PIPELINE = Sam3dInference()
+ PIPELINE = Sam3dInference(device="cuda")
else:
PIPELINE = TrellisImageTo3DPipeline.from_pretrained(
"microsoft/TRELLIS-image-large"
@@ -107,10 +117,8 @@
)
os.makedirs(TMP_DIR, exist_ok=True)
elif os.getenv("GRADIO_APP").startswith("textto3d"):
- RBG_REMOVER = RembgRemover()
- RBG14_REMOVER = BMGG14Remover()
if "sam3d" in os.getenv("GRADIO_APP"):
- PIPELINE = Sam3dInference()
+ PIPELINE = Sam3dInference(device="cuda")
else:
PIPELINE = TrellisImageTo3DPipeline.from_pretrained(
"microsoft/TRELLIS-image-large"
@@ -130,11 +138,7 @@
elif os.getenv("GRADIO_APP") == "texture_edit":
DELIGHT = DelightingModel()
IMAGESR_MODEL = ImageRealESRGAN(outscale=4)
- PIPELINE_IP = build_texture_gen_pipe(
- base_ckpt_dir="./weights",
- ip_adapt_scale=0.7,
- device="cuda",
- )
+ PIPELINE_HAS_IP_ADAPTER = False
PIPELINE = build_texture_gen_pipe(
base_ckpt_dir="./weights",
ip_adapt_scale=0,
@@ -163,6 +167,9 @@ def preprocess_image_fn(
rmbg_tag: str = "rembg",
preprocess: bool = True,
) -> tuple[Image.Image, Image.Image]:
+ """Preprocess image with lazy model initialization to avoid CUDA init at import time."""
+ global _RBG_REMOVER, _RBG14_REMOVER
+
if isinstance(image, str):
image = Image.open(image)
elif isinstance(image, np.ndarray):
@@ -170,7 +177,16 @@ def preprocess_image_fn(
image_cache = image.copy() # resize_pil(image.copy(), 1024)
- bg_remover = RBG_REMOVER if rmbg_tag == "rembg" else RBG14_REMOVER
+ # Lazy initialization - models are created on first call within @spaces.GPU context
+ if rmbg_tag == "rembg":
+ if _RBG_REMOVER is None:
+ _RBG_REMOVER = RembgRemover()
+ bg_remover = _RBG_REMOVER
+ else:
+ if _RBG14_REMOVER is None:
+ _RBG14_REMOVER = BMGG14Remover()
+ bg_remover = _RBG14_REMOVER
+
image = bg_remover(image)
image = keep_largest_connected_component(image)
@@ -276,7 +292,7 @@ def image_to_3d(
sam_image: Image.Image = None,
is_sam_image: bool = False,
req: gr.Request = None,
-) -> tuple[dict, str]:
+) -> tuple[object, str]:
if is_sam_image:
seg_image = filter_image_small_connected_components(sam_image)
seg_image = Image.fromarray(seg_image, mode="RGBA")
@@ -286,6 +302,7 @@ def image_to_3d(
if isinstance(seg_image, np.ndarray):
seg_image = Image.fromarray(seg_image)
+ logger.info("Start generating 3D representation from image...")
if isinstance(PIPELINE, Sam3dInference):
outputs = PIPELINE.run(
seg_image,
@@ -334,7 +351,7 @@ def image_to_3d(
def extract_3d_representations_v2(
- state: dict,
+ state: object,
enable_delight: bool,
texture_size: int,
req: gr.Request,
@@ -401,7 +418,7 @@ def extract_3d_representations_v2(
def extract_3d_representations_v3(
- state: dict,
+ state: object,
enable_delight: bool,
texture_size: int,
req: gr.Request,
@@ -650,27 +667,62 @@ def generate_texture_mvimages(
sub_idxs: tuple[tuple[int]] = ((0, 1, 2), (3, 4, 5)),
req: gr.Request = None,
) -> list[str]:
+ global PIPELINE, PIPELINE_HAS_IP_ADAPTER
+
output_root = os.path.join(TMP_DIR, str(req.session_hash))
use_ip_adapter = True if ip_img_path and ip_adapt_scale > 0 else False
- PIPELINE_IP.set_ip_adapter_scale([ip_adapt_scale])
- img_save_paths = infer_pipe(
- index_file=f"{output_root}/condition/index.json",
- controlnet_cond_scale=controlnet_cond_scale,
- guidance_scale=guidance_scale,
- strength=strength,
- num_inference_steps=num_inference_steps,
- ip_adapt_scale=ip_adapt_scale,
- ip_img_path=ip_img_path,
- uid=uid,
- prompt=prompt,
- save_dir=f"{output_root}/multi_view",
- sub_idxs=sub_idxs,
- pipeline=PIPELINE_IP if use_ip_adapter else PIPELINE,
- seed=seed,
- )
- gc.collect()
- torch.cuda.empty_cache()
+ if PIPELINE is None:
+ PIPELINE = build_texture_gen_pipe(
+ base_ckpt_dir="./weights",
+ ip_adapt_scale=0,
+ device="cuda",
+ )
+
+ if use_ip_adapter and not PIPELINE_HAS_IP_ADAPTER:
+ logger.info("Load IP adapter into default texture pipeline")
+ if hasattr(PIPELINE.unet, "encoder_hid_proj"):
+ PIPELINE.unet.text_encoder_hid_proj = (
+ PIPELINE.unet.encoder_hid_proj
+ )
+ PIPELINE.load_ip_adapter(
+ "./weights/Kolors-IP-Adapter-Plus",
+ subfolder="",
+ weight_name=["ip_adapter_plus_general.bin"],
+ )
+ PIPELINE_HAS_IP_ADAPTER = True
+
+ if PIPELINE_HAS_IP_ADAPTER:
+ PIPELINE.set_ip_adapter_scale(
+ [ip_adapt_scale if use_ip_adapter else 0.0]
+ )
+
+ try:
+ img_save_paths = infer_pipe(
+ index_file=f"{output_root}/condition/index.json",
+ controlnet_cond_scale=controlnet_cond_scale,
+ guidance_scale=guidance_scale,
+ strength=strength,
+ num_inference_steps=num_inference_steps,
+ ip_adapt_scale=ip_adapt_scale if use_ip_adapter else 0.0,
+ ip_img_path=ip_img_path if use_ip_adapter else None,
+ uid=uid,
+ prompt=prompt,
+ save_dir=f"{output_root}/multi_view",
+ sub_idxs=sub_idxs,
+ pipeline=PIPELINE,
+ seed=seed,
+ )
+ finally:
+ if use_ip_adapter and PIPELINE_HAS_IP_ADAPTER:
+ logger.info("Unload IP adapter from default texture pipeline")
+ if hasattr(PIPELINE, "unload_ip_adapter"):
+ PIPELINE.unload_ip_adapter()
+ else:
+ PIPELINE = None
+ PIPELINE_HAS_IP_ADAPTER = False
+ gc.collect()
+ torch.cuda.empty_cache()
return img_save_paths + img_save_paths
diff --git a/apps/image_to_3d.py b/apps/image_to_3d.py
index 5bf5fc9..09158cb 100644
--- a/apps/image_to_3d.py
+++ b/apps/image_to_3d.py
@@ -41,14 +41,31 @@
app_name = os.getenv("GRADIO_APP")
if app_name == "imageto3d_sam3d":
- enable_pre_resize = False
+ _enable_pre_resize_default = False
sample_step = 25
bg_rm_model_name = "rembg" # "rembg", "rmbg14"
elif app_name == "imageto3d":
- enable_pre_resize = True
+ _enable_pre_resize_default = True
sample_step = 12
bg_rm_model_name = "rembg" # "rembg", "rmbg14"
+current_rmbg_tag = bg_rm_model_name
+
+
+def set_current_rmbg_tag(rmbg: str) -> None:
+ global current_rmbg_tag
+ current_rmbg_tag = rmbg
+
+
+def preprocess_example_image(
+ img: str,
+) -> tuple[object, object, gr.Button]:
+ image, image_cache = preprocess_image_fn(
+ img, current_rmbg_tag, _enable_pre_resize_default
+ )
+ return image, image_cache, gr.Button(interactive=True)
+
+
with gr.Blocks(delete_cache=(43200, 43200), theme=custom_theme) as demo:
gr.HTML(image_css, visible=False)
gr.HTML(lighting_css, visible=False)
@@ -72,12 +89,10 @@
๐ผ๏ธ Generate physically plausible 3D asset from single input image.
- """.format(
- VERSION=VERSION
- ),
+ """.format(VERSION=VERSION),
elem_classes=["header"],
)
-
+ enable_pre_resize = gr.State(_enable_pre_resize_default)
with gr.Row():
with gr.Column(scale=3):
with gr.Tabs() as input_tabs:
@@ -98,11 +113,9 @@
height=400,
elem_classes=["image_fit"],
)
- gr.Markdown(
- """
+ gr.Markdown("""
If you are not satisfied with the auto segmentation
- result, please switch to the `Image(SAM seg)` tab."""
- )
+ result, please switch to the `Image(SAM seg)` tab.""")
with gr.Tab(
label="Image(SAM seg)", id=1
) as samimage_input_tab:
@@ -159,11 +172,11 @@
)
with gr.Row():
randomize_seed = gr.Checkbox(
- label="Randomize Seed", value=False
+ label="Randomize Seed", value=True
)
project_delight = gr.Checkbox(
label="Back-project Delight",
- value=True,
+ value=False,
)
gr.Markdown("Geo Structure Generation")
with gr.Row():
@@ -262,7 +275,6 @@
has quality inspection, open with an editor to view details.
"""
)
- enable_pre_resize = gr.State(enable_pre_resize)
with gr.Row() as single_image_example:
examples = gr.Examples(
label="Image Gallery",
@@ -272,11 +284,12 @@
glob("apps/assets/example_image/*")
)
],
- inputs=[image_prompt, rmbg_tag, enable_pre_resize],
- fn=preprocess_image_fn,
- outputs=[image_prompt, raw_image_cache],
+ inputs=[image_prompt],
+ fn=preprocess_example_image,
+ outputs=[image_prompt, raw_image_cache, generate_btn],
run_on_click=True,
examples_per_page=10,
+ cache_examples=False,
)
with gr.Row(visible=False) as single_sam_image_example:
@@ -339,10 +352,24 @@
)
image_prompt.upload(
- preprocess_image_fn,
- inputs=[image_prompt, rmbg_tag, enable_pre_resize],
+ lambda img, rmbg: preprocess_image_fn(
+ img, rmbg, _enable_pre_resize_default
+ ),
+ inputs=[image_prompt, rmbg_tag],
outputs=[image_prompt, raw_image_cache],
+ queue=False,
+ ).success(
+ active_btn_by_content,
+ inputs=image_prompt,
+ outputs=generate_btn,
+ )
+
+ rmbg_tag.change(
+ set_current_rmbg_tag,
+ inputs=[rmbg_tag],
+ outputs=[],
)
+
image_prompt.change(
lambda: tuple(
[
@@ -381,10 +408,9 @@
est_mu_text,
],
)
- image_prompt.change(
- active_btn_by_content,
- inputs=image_prompt,
- outputs=generate_btn,
+ image_prompt.clear(
+ lambda: gr.Button(interactive=False),
+ outputs=[generate_btn],
)
image_prompt_sam.upload(
diff --git a/apps/text_to_3d.py b/apps/text_to_3d.py
index e5a176d..3f63456 100644
--- a/apps/text_to_3d.py
+++ b/apps/text_to_3d.py
@@ -69,9 +69,7 @@
๐ Create 3D assets from text descriptions for a wide range of geometry and styles.
- """.format(
- VERSION=VERSION
- ),
+ """.format(VERSION=VERSION),
elem_classes=["header"],
)
@@ -106,7 +104,7 @@
0, MAX_SEED, label="Seed", value=0, step=1
)
randomize_seed = gr.Checkbox(
- label="Randomize Seed", value=False
+ label="Randomize Seed", value=True
)
rmbg_tag = gr.Radio(
choices=["rembg", "rmbg14"],
@@ -159,7 +157,7 @@
label="Randomize Seed", value=False
)
project_delight = gr.Checkbox(
- label="Back-project Delight", value=True
+ label="Back-project Delight", value=False
)
gr.Markdown("Geo Structure Generation")
with gr.Row():
diff --git a/apps/visualize_asset.py b/apps/visualize_asset.py
index 233df4a..4161967 100644
--- a/apps/visualize_asset.py
+++ b/apps/visualize_asset.py
@@ -23,14 +23,18 @@
os.makedirs(gradio_tmp_dir, exist_ok=True)
os.environ["GRADIO_TEMP_DIR"] = gradio_tmp_dir
+import colorsys
import shutil
import uuid
import xml.etree.ElementTree as ET
+import zipfile
from pathlib import Path
import gradio as gr
+import numpy as np
import pandas as pd
-from app_style import custom_theme, lighting_css
+import trimesh
+from app_style import custom_theme
from embodied_gen.utils.tags import VERSION
try:
@@ -49,25 +53,65 @@
# --- Configuration & Data Loading ---
RUNNING_MODE = "local" # local or hf_remote
CSV_FILE = "dataset_index.csv"
+HF_REPO_ID = "HorizonRobotics/EmbodiedGenData"
+HF_LOCAL_DIR = "EmbodiedGenData"
+CAMERA_ZOOM = 3.2
+
+# Compatible with huggingface space zero GPU
+import spaces
+from huggingface_hub import snapshot_download
+
+
+@spaces.GPU
+def fake_gpu_init():
+ pass
+
+
+fake_gpu_init()
if RUNNING_MODE == "local":
- DATA_ROOT = "/horizon-bucket/robot_lab/datasets/embodiedgen/assets"
+ DATA_ROOT = "/horizon-bucket/robot_lab/datasets/embodiedgen/assets_v2"
elif RUNNING_MODE == "hf_remote":
- from huggingface_hub import snapshot_download
-
+ # Only fetch the index and preview videos up front; per-asset mesh/urdf/
+ # usd/mjcf files are pulled lazily on demand (see `ensure_hf_files`).
snapshot_download(
- repo_id="HorizonRobotics/EmbodiedGenData",
+ repo_id=HF_REPO_ID,
repo_type="dataset",
- allow_patterns=f"dataset/**",
- local_dir="EmbodiedGenData",
+ allow_patterns=[
+ f"dataset/{CSV_FILE}",
+ "dataset/**/*.mp4",
+ ],
+ local_dir=HF_LOCAL_DIR,
local_dir_use_symlinks=False,
)
- DATA_ROOT = "EmbodiedGenData/dataset"
+ DATA_ROOT = os.path.join(HF_LOCAL_DIR, "dataset")
else:
raise ValueError(
f"Unknown RUNNING_MODE: {RUNNING_MODE}, must be 'local' or 'hf_remote'."
)
+
+def ensure_hf_files(rel_patterns: list[str]) -> None:
+ """Lazily download files for one asset in hf_remote mode.
+
+ Args:
+ rel_patterns: Glob patterns relative to ``DATA_ROOT`` (the repo's
+ ``dataset/`` folder), e.g. ``"cat/sub/uid/mesh/**"``.
+
+ No-op when running locally, where all files already exist on disk.
+ Progress toasts are emitted by the calling action handlers.
+ """
+ if RUNNING_MODE != "hf_remote":
+ return
+ snapshot_download(
+ repo_id=HF_REPO_ID,
+ repo_type="dataset",
+ allow_patterns=[f"dataset/{p}" for p in rel_patterns],
+ local_dir=HF_LOCAL_DIR,
+ local_dir_use_symlinks=False,
+ )
+
+
csv_path = os.path.join(DATA_ROOT, CSV_FILE)
df = pd.read_csv(csv_path)
TMP_DIR = os.path.join(
@@ -120,6 +164,82 @@ def _unique_path(
return str(dst)
+def _bounding_radius(mesh: trimesh.Trimesh | trimesh.Scene) -> float:
+ """Radius of the mesh/scene bounding sphere (half the diagonal)."""
+ lo, hi = mesh.bounds
+ return float(np.linalg.norm(np.asarray(hi) - np.asarray(lo)) / 2)
+
+
+def _camera_position(radius: float | None) -> tuple:
+ """Initial camera `(alpha, beta, radius)`; only push the distance."""
+ if not radius or radius <= 0:
+ return (None, None, None)
+ return (None, None, CAMERA_ZOOM * radius)
+
+
+def _visual_radius(visual_path: str | None) -> float | None:
+ if not visual_path:
+ return None
+ return _bounding_radius(trimesh.load(visual_path, process=False))
+
+
+def _pastel_color(i: int) -> list[int]:
+ """Distinct pastel RGBA for the i-th convex piece (golden-ratio hues)."""
+ h = (i * 0.6180339887498949) % 1.0
+ r, g, b = colorsys.hsv_to_rgb(h, 0.45, 0.98)
+ return [int(r * 255), int(g * 255), int(b * 255), 255]
+
+
+def _colored_collision_path(
+ collision_path: str | None, session_hash: str
+) -> tuple[str | None, float | None]:
+ """Export the collision mesh as a GLB with one color per convex piece.
+
+ The convex-decomposition pieces are stored as separate objects inside the
+ single collision ``.obj``; they are recovered via connected-component split
+ and each is tinted a distinct pastel color for visualization.
+
+ Returns ``(glb_path, bounding_radius)`` for camera framing.
+ """
+ if not collision_path:
+ return None, None
+
+ tmp_root = (
+ Path(os.environ.get("GRADIO_TEMP_DIR", "/tmp"))
+ / "model3d-cache"
+ / session_hash
+ )
+ tmp_root.mkdir(parents=True, exist_ok=True)
+
+ # rolling cleanup for same kind
+ prev = _prev_temp.get(session_hash, {})
+ old = prev.get("collision")
+ if old and old.exists():
+ old.unlink()
+
+ loaded = trimesh.load(collision_path, process=False)
+ if isinstance(loaded, trimesh.Scene):
+ parts = list(loaded.geometry.values())
+ else:
+ parts = loaded.split(only_watertight=False)
+ if len(parts) == 0:
+ parts = [loaded]
+
+ scene = trimesh.Scene()
+ for i, part in enumerate(parts):
+ part.visual = trimesh.visual.ColorVisuals(
+ mesh=part, vertex_colors=_pastel_color(i)
+ )
+ scene.add_geometry(part)
+
+ dst = tmp_root / f"collision-{uuid.uuid4().hex}.glb"
+ scene.export(str(dst))
+
+ prev["collision"] = dst
+ _prev_temp[session_hash] = prev
+ return str(dst), _bounding_radius(scene)
+
+
# --- Helper Functions (data filtering) ---
def get_primary_categories():
return sorted(df["primary_category"].dropna().unique())
@@ -345,6 +465,20 @@ def show_asset_from_gallery(
)
row = subset.iloc[index]
+
+ # In hf_remote mode, pull only what the two mesh viewers need: the visual
+ # GLB + collision OBJ (mesh/), plus the tiny URDF used to locate them and
+ # show metadata. USD/MJCF/USD-textures are fetched only on button click.
+ gr.Info("โณ Loading 3D model, please wait...")
+ rel_dir = row["asset_dir"]
+ urdf_name = os.path.basename(row["urdf_path"])
+ ensure_hf_files(
+ [
+ f"{rel_dir}/{urdf_name}",
+ f"{rel_dir}/mesh/**",
+ ]
+ )
+
visual_path, collision_path, desc = _extract_mesh_paths(row)
urdf_path = os.path.join(DATA_ROOT, row["urdf_path"])
@@ -416,41 +550,217 @@ def render_meshes(
if visual_path
else None
)
- collision_unique = (
- _unique_path(collision_path, session_hash, "collision")
- if collision_path
- else None
- )
+ visual_cam = _camera_position(_visual_radius(visual_path))
+
+ if collision_path:
+ collision_unique, collision_r = _colored_collision_path(
+ collision_path, session_hash
+ )
+ else:
+ collision_unique, collision_r = None, None
+ collision_cam = _camera_position(collision_r)
+
+ if visual_unique or collision_unique:
+ gr.Info("โ
3D model loaded.")
if switch_viewer:
yield (
- gr.update(value=visual_unique),
+ gr.update(value=visual_unique, camera_position=visual_cam),
gr.update(value=None, visible=False),
- gr.update(value=collision_unique, visible=True),
+ gr.update(
+ value=collision_unique,
+ visible=True,
+ camera_position=collision_cam,
+ ),
False,
)
else:
yield (
- gr.update(value=visual_unique),
- gr.update(value=collision_unique, visible=True),
+ gr.update(value=visual_unique, camera_position=visual_cam),
+ gr.update(
+ value=collision_unique,
+ visible=True,
+ camera_position=collision_cam,
+ ),
gr.update(value=None, visible=False),
True,
)
-def create_asset_zip(asset_dir: str, req: gr.Request):
+def _rel_dir(asset_dir: str) -> str:
+ """Repo-relative asset dir (path under DATA_ROOT), for HF glob patterns."""
+ return os.path.relpath(asset_dir, DATA_ROOT)
+
+
+def _find_urdf_stem(asset_dir: str) -> str | None:
+ for f in os.listdir(asset_dir):
+ if f.lower().endswith(".urdf"):
+ return os.path.splitext(f)[0]
+ return None
+
+
+def _zip_items(
+ zip_path: str,
+ items: list[tuple[str, str]],
+ exclude_suffixes: tuple[str, ...] = (),
+) -> str:
+ """Write files/dirs into a zip.
+
+ Args:
+ zip_path: Output archive path.
+ items: ``(src_abspath, arcname)`` pairs. Directories are walked and
+ stored under ``arcname``.
+ exclude_suffixes: Lowercase filename suffixes to skip when walking
+ directories, e.g. ``(".glb",)``.
+ """
+ with zipfile.ZipFile(zip_path, "w", zipfile.ZIP_DEFLATED) as zf:
+ for src, arcname in items:
+ if os.path.isdir(src):
+ for root_d, _, files in os.walk(src):
+ for fn in files:
+ if exclude_suffixes and fn.lower().endswith(
+ exclude_suffixes
+ ):
+ continue
+ fp = os.path.join(root_d, fn)
+ rel = os.path.relpath(fp, src)
+ zf.write(fp, os.path.join(arcname, rel))
+ elif os.path.isfile(src):
+ zf.write(src, arcname)
+ return zip_path
+
+
+def _fmt_zip_path(
+ asset_dir: str, fmt: str, req: gr.Request
+) -> tuple[str, str, str]:
+ """Build the per-format output zip path named ``_.zip``.
+
+ Returns ``(zip_path, uid, zip_name)``.
+ """
+ uid = os.path.basename(os.path.normpath(asset_dir))
user_dir = os.path.join(TMP_DIR, str(req.session_hash))
os.makedirs(user_dir, exist_ok=True)
+ zip_name = f"{uid}_{fmt}.zip"
+ return os.path.join(user_dir, zip_name), uid, zip_name
- asset_folder_name = os.path.basename(os.path.normpath(asset_dir))
- zip_path_base = os.path.join(user_dir, asset_folder_name)
- archive_path = shutil.make_archive(
- base_name=zip_path_base, format='zip', root_dir=asset_dir
+def download_urdf(asset_dir: str, req: gr.Request) -> str | None:
+ """Package the ``.urdf`` file(s) and the ``mesh/`` folder (minus ``.glb``).
+
+ The visual/collision meshes referenced by the URDF live in ``mesh/`` as
+ ``.obj`` + material files; the ``.glb`` (a separate viewer asset) and all
+ other formats (USD/MJCF/video/...) are intentionally excluded.
+ """
+ if not asset_dir or not os.path.isdir(asset_dir):
+ gr.Warning("Please select an asset first.")
+ return None
+ gr.Info("โณ Preparing URDF asset for download, please wait...")
+ rel = _rel_dir(asset_dir)
+ ensure_hf_files([f"{rel}/*.urdf", f"{rel}/mesh/**"])
+
+ items: list[tuple[str, str]] = [
+ (os.path.join(asset_dir, f), f)
+ for f in os.listdir(asset_dir)
+ if f.lower().endswith(".urdf")
+ ]
+ mesh_dir = os.path.join(asset_dir, "mesh")
+ if os.path.isdir(mesh_dir):
+ items.append((mesh_dir, "mesh"))
+
+ zip_path, uid, zip_name = _fmt_zip_path(asset_dir, "urdf", req)
+ _zip_items(zip_path, items, exclude_suffixes=(".glb",))
+ gr.Info(f"โ
{zip_name} is ready.")
+ return zip_path
+
+
+def download_usd(asset_dir: str, req: gr.Request) -> str | None:
+ """Package the ``.usd`` file together with its ``textures/`` folder."""
+ if not asset_dir or not os.path.isdir(asset_dir):
+ gr.Warning("Please select an asset first.")
+ return None
+
+ gr.Info("โณ Preparing USD asset for download, please wait...")
+ rel = _rel_dir(asset_dir)
+ stem = _find_urdf_stem(asset_dir)
+ if stem is not None:
+ ensure_hf_files([f"{rel}/{stem}.usd", f"{rel}/textures/**"])
+ else:
+ ensure_hf_files([f"{rel}/*.usd", f"{rel}/textures/**"])
+
+ items: list[tuple[str, str]] = []
+ for f in os.listdir(asset_dir):
+ if f.lower().endswith(".usd"):
+ items.append((os.path.join(asset_dir, f), f))
+ tex_dir = os.path.join(asset_dir, "textures")
+ if os.path.isdir(tex_dir):
+ items.append((tex_dir, "textures"))
+
+ if not any(s.lower().endswith(".usd") for s, _ in items):
+ gr.Warning("No USD file available for this asset.")
+ return None
+
+ zip_path, uid, zip_name = _fmt_zip_path(asset_dir, "usd", req)
+ _zip_items(zip_path, items)
+ gr.Info(f"โ
{zip_name} is ready.")
+ return zip_path
+
+
+def download_mjcf(asset_dir: str, req: gr.Request) -> str | None:
+ """Package the ``mjcf/`` folder."""
+ if not asset_dir or not os.path.isdir(asset_dir):
+ gr.Warning("Please select an asset first.")
+ return None
+
+ gr.Info("โณ Preparing MJCF asset for download, please wait...")
+ ensure_hf_files([f"{_rel_dir(asset_dir)}/mjcf/**"])
+ mjcf_dir = os.path.join(asset_dir, "mjcf")
+ if not os.path.isdir(mjcf_dir):
+ gr.Warning("No MJCF folder available for this asset.")
+ return None
+
+ zip_path, uid, zip_name = _fmt_zip_path(asset_dir, "mjcf", req)
+ _zip_items(zip_path, [(mjcf_dir, "mjcf")])
+ gr.Info(f"โ
{zip_name} is ready.")
+ return zip_path
+
+
+# Download buttons keyed by format; order matches the outputs list used when
+# locking/unlocking them together.
+_DL_KEYS = ("urdf", "usd", "mjcf")
+_DL_LABELS = {
+ "urdf": "โฌ๏ธ Download URDF",
+ "usd": "โฌ๏ธ Download USD",
+ "mjcf": "โฌ๏ธ Download MJCF",
+}
+
+
+def _lock_downloads(active: str) -> tuple:
+ """Disable all download buttons; show a persistent hint on the clicked one.
+
+ Keeps the user from spamming clicks while the (possibly slow) zip is being
+ prepared, since a toast alone neither blocks clicks nor stays put.
+ """
+ return tuple(
+ gr.update(interactive=False, label=f"โณ Preparing {k.upper()}...")
+ if k == active
+ else gr.update(interactive=False)
+ for k in _DL_KEYS
)
- gr.Info(f"โ
{asset_folder_name}.zip is ready and can be downloaded.")
- return archive_path
+
+def _unlock_downloads() -> tuple:
+ """Re-enable buttons and restore labels; keep the freshly built file value."""
+ return tuple(
+ gr.update(interactive=True, label=_DL_LABELS[k]) for k in _DL_KEYS
+ )
+
+
+def _reset_downloads() -> tuple:
+ """Enable + restore labels + clear stale file value (used on asset switch)."""
+ return tuple(
+ gr.update(interactive=True, label=_DL_LABELS[k], value=None)
+ for k in _DL_KEYS
+ )
def start_session(req: gr.Request) -> None:
@@ -493,9 +803,7 @@ def end_session(req: gr.Request) -> None:
Browse and visualize the EmbodiedGen 3D asset database. Select categories to filter and click on a preview to load the model.
- """.format(
- VERSION=VERSION
- ),
+ """.format(VERSION=VERSION),
elem_classes=["header"],
)
@@ -573,21 +881,21 @@ def end_session(req: gr.Request) -> None:
with gr.TabItem("Visual Mesh") as t1:
viewer = gr.Model3D(
label="๐ง 3D Model Viewer",
- height=500,
+ height=380,
clear_color=[0.95, 0.95, 0.95],
elem_id="visual_mesh",
)
with gr.TabItem("Collision Mesh") as t2:
collision_viewer_a = gr.Model3D(
label="๐ง Collision Mesh",
- height=500,
+ height=380,
clear_color=[0.95, 0.95, 0.95],
elem_id="collision_mesh_a",
visible=True,
)
collision_viewer_b = gr.Model3D(
label="๐ง Collision Mesh",
- height=500,
+ height=380,
clear_color=[0.95, 0.95, 0.95],
elem_id="collision_mesh_b",
visible=False,
@@ -624,13 +932,23 @@ def end_session(req: gr.Request) -> None:
label="URDF File Path", interactive=False, lines=2
)
with gr.Row():
- extract_btn = gr.Button(
- "๐ฅ Extract Asset",
+ # DownloadButtons that build their zip into their own value
+ # on click; a chained JS handler then triggers the browser
+ # download from that value (one-click). If the JS ever
+ # fails, the value is still populated, so a second manual
+ # click downloads it via the button's native behavior.
+ urdf_dl_btn = gr.DownloadButton(
+ label="โฌ๏ธ Download URDF",
variant="primary",
interactive=False,
)
- download_btn = gr.DownloadButton(
- label="โฌ๏ธ Download Asset",
+ usd_dl_btn = gr.DownloadButton(
+ label="โฌ๏ธ Download USD",
+ variant="primary",
+ interactive=False,
+ )
+ mjcf_dl_btn = gr.DownloadButton(
+ label="โฌ๏ธ Download MJCF",
variant="primary",
interactive=False,
)
@@ -711,22 +1029,61 @@ def update_assets(p, s, c):
switch_viewer_state,
],
).success(
- lambda: (gr.Button(interactive=True), gr.Button(interactive=False)),
- outputs=[extract_btn, download_btn],
+ fn=_reset_downloads,
+ outputs=[urdf_dl_btn, usd_dl_btn, mjcf_dl_btn],
)
- extract_btn.click(
- fn=create_asset_zip, inputs=[asset_folder], outputs=[download_btn]
- ).success(fn=lambda: gr.update(interactive=True), outputs=download_btn)
+ # After the zip is built into the button's own value, pass that file value
+ # straight into the JS handler and trigger the browser download from its
+ # URL. Reading the value via `inputs` avoids DOM-timing/selector issues.
+ download_js = """
+ (f) => {
+ if (f && f.url) {
+ const a = document.createElement('a');
+ a.href = f.url;
+ a.download = (f.orig_name || 'asset.zip').split('/').pop();
+ document.body.appendChild(a);
+ a.click();
+ document.body.removeChild(a);
+ }
+ return [];
+ }
+ """
+
+ dl_btns = [urdf_dl_btn, usd_dl_btn, mjcf_dl_btn]
+
+ # Each flow: lock all buttons (prevent spam clicks) -> build the zip ->
+ # JS-trigger the browser download -> unlock. Selecting another asset
+ # resets the buttons independently (see `.success` above).
+ urdf_dl_btn.click(
+ fn=lambda: _lock_downloads("urdf"), outputs=dl_btns
+ ).then(
+ fn=download_urdf, inputs=[asset_folder], outputs=[urdf_dl_btn]
+ ).then(fn=lambda *a: None, inputs=[urdf_dl_btn], js=download_js).then(
+ fn=_unlock_downloads, outputs=dl_btns
+ )
+
+ usd_dl_btn.click(fn=lambda: _lock_downloads("usd"), outputs=dl_btns).then(
+ fn=download_usd, inputs=[asset_folder], outputs=[usd_dl_btn]
+ ).then(fn=lambda *a: None, inputs=[usd_dl_btn], js=download_js).then(
+ fn=_unlock_downloads, outputs=dl_btns
+ )
+
+ mjcf_dl_btn.click(
+ fn=lambda: _lock_downloads("mjcf"), outputs=dl_btns
+ ).then(
+ fn=download_mjcf, inputs=[asset_folder], outputs=[mjcf_dl_btn]
+ ).then(fn=lambda *a: None, inputs=[mjcf_dl_btn], js=download_js).then(
+ fn=_unlock_downloads, outputs=dl_btns
+ )
demo.load(start_session)
demo.unload(end_session)
if __name__ == "__main__":
+ # Serve gallery videos / meshes that live under DATA_ROOT (outside cwd).
demo.launch(
server_port=8088,
- allowed_paths=[
- "/horizon-bucket/robot_lab/datasets/embodiedgen/assets"
- ],
+ allowed_paths=[os.path.abspath(DATA_ROOT)],
)
diff --git a/docker/amd_rocm/kaolin_stub.py b/docker/amd_rocm/kaolin_stub.py
index 6852490..ed2dd09 100644
--- a/docker/amd_rocm/kaolin_stub.py
+++ b/docker/amd_rocm/kaolin_stub.py
@@ -16,6 +16,7 @@
This is the ROCm-unblock shim; the upstream-PR-appropriate fix is to make the kaolin
imports in `data/utils.py` lazy/optional (see docs/exp18.md).
"""
+
import importlib.abc
import importlib.machinery
import sys
diff --git a/docker/amd_rocm/spconv_rocm_compat.py b/docker/amd_rocm/spconv_rocm_compat.py
index 59893a5..b2a48e2 100644
--- a/docker/amd_rocm/spconv_rocm_compat.py
+++ b/docker/amd_rocm/spconv_rocm_compat.py
@@ -18,6 +18,7 @@
This is the ROCm-unblock shim; the upstream-appropriate fix belongs in spconv_rocm
(accept KRSC checkpoints under the Native algo).
"""
+
import torch
_orig_load_state_dict = torch.nn.Module.load_state_dict
@@ -36,7 +37,9 @@ def _patched_load_state_dict(self, state_dict, strict=True, *args, **kwargs):
try:
own = self.state_dict()
except Exception:
- return _orig_load_state_dict(self, state_dict, strict=strict, *args, **kwargs)
+ return _orig_load_state_dict(
+ self, state_dict, strict=strict, *args, **kwargs
+ )
converted = 0
fixed = dict(state_dict)
@@ -52,9 +55,14 @@ def _patched_load_state_dict(self, state_dict, strict=True, *args, **kwargs):
fixed[name] = native.reshape(inc, out)
converted += 1
if converted:
- print(f"[spconv-rocm-compat] converted {converted} KRSC->Native conv weights")
+ print(
+ f"[spconv-rocm-compat] converted {converted} KRSC->Native conv weights"
+ )
return _orig_load_state_dict(self, fixed, strict=strict, *args, **kwargs)
-if getattr(torch.nn.Module.load_state_dict, "__name__", "") != "_patched_load_state_dict":
+if (
+ getattr(torch.nn.Module.load_state_dict, "__name__", "")
+ != "_patched_load_state_dict"
+):
torch.nn.Module.load_state_dict = _patched_load_state_dict
diff --git a/docs/assets/real2sim_mujoco.gif b/docs/assets/real2sim_mujoco.gif
deleted file mode 100644
index b3f8ed2..0000000
Binary files a/docs/assets/real2sim_mujoco.gif and /dev/null differ
diff --git a/docs/assets/scene3d.gif b/docs/assets/scene3d.gif
deleted file mode 100644
index b816d17..0000000
Binary files a/docs/assets/scene3d.gif and /dev/null differ
diff --git a/docs/LICENSE b/docs/documentation/LICENSE
similarity index 100%
rename from docs/LICENSE
rename to docs/documentation/LICENSE
diff --git a/docs/acknowledgement.md b/docs/documentation/acknowledgement.md
similarity index 79%
rename from docs/acknowledgement.md
rename to docs/documentation/acknowledgement.md
index b69fdae..2dd4f29 100644
--- a/docs/acknowledgement.md
+++ b/docs/documentation/acknowledgement.md
@@ -9,6 +9,16 @@ EmbodiedGen builds upon the following amazing projects and models:
If you use EmbodiedGen in your research or projects, please cite:
+
+```bibtex
+@article{wang2026embodiedgenv2,
+ title={EmbodiedGen V2: An Agentic, Simulation-Ready 3D World Engine for Embodied AI},
+ author={Xinjie Wang and Liu Liu and Taojun Ding and Andrew Choi and Chaodong Huang and Mengao Zhao and Ziang Li and Jackson Jiang and Chunlei Yu and Shengxiang Liu and Wei Xu and Zhizhong Su},
+ journal={arXiv preprint arXiv:2506.10600},
+ year={2026},
+}
+```
+
```bibtex
@misc{wang2025embodiedgengenerative3dworld,
title={EmbodiedGen: Towards a Generative 3D World Engine for Embodied Intelligence},
diff --git a/docs/api/data.md b/docs/documentation/api/data.md
similarity index 68%
rename from docs/api/data.md
rename to docs/documentation/api/data.md
index 4770d0a..9eebce5 100644
--- a/docs/api/data.md
+++ b/docs/documentation/api/data.md
@@ -21,5 +21,17 @@
heading_level: 3
::: embodied_gen.data.convex_decomposer
+ options:
+ heading_level: 3
+
+::: embodied_gen.data.backproject
+ options:
+ heading_level: 3
+
+::: embodied_gen.data.backproject_v3
+ options:
+ heading_level: 3
+
+::: embodied_gen.data.utils
options:
heading_level: 3
\ No newline at end of file
diff --git a/docs/api/envs.md b/docs/documentation/api/envs.md
similarity index 100%
rename from docs/api/envs.md
rename to docs/documentation/api/envs.md
diff --git a/docs/api/index.md b/docs/documentation/api/index.md
similarity index 88%
rename from docs/api/index.md
rename to docs/documentation/api/index.md
index f354168..4132143 100644
--- a/docs/api/index.md
+++ b/docs/documentation/api/index.md
@@ -10,5 +10,6 @@ browse the different components.
* [**Envs API**](envs.md): Simulation environment definitions.
* [**Models API**](models.md): The core generative models (Texture, 3DGS, Layout, etc.).
* [**Trainer API**](trainer.md): PyTorch-Lightning style trainers for models.
+* [**Skills API**](skills.md): Utility skills for asset processing and spatial computing.
* [**Utilities API**](utils.md): Helper functions and configuration.
* [**Validators API**](validators.md): Tools for checking and validating assets.
diff --git a/docs/api/models.md b/docs/documentation/api/models.md
similarity index 81%
rename from docs/api/models.md
rename to docs/documentation/api/models.md
index 034a816..9170533 100644
--- a/docs/api/models.md
+++ b/docs/documentation/api/models.md
@@ -29,5 +29,13 @@
heading_level: 3
::: embodied_gen.models.delight_model
+ options:
+ heading_level: 3
+
+::: embodied_gen.models.sam3d
+ options:
+ heading_level: 3
+
+::: embodied_gen.models.hunyuan3d
options:
heading_level: 3
\ No newline at end of file
diff --git a/docs/documentation/api/skills.md b/docs/documentation/api/skills.md
new file mode 100644
index 0000000..0158096
--- /dev/null
+++ b/docs/documentation/api/skills.md
@@ -0,0 +1,35 @@
+# Skills API
+
+Utility skills for asset processing and spatial computing tasks.
+
+::: embodied_gen.skills.asset-process
+ options:
+ heading_level: 3
+
+::: embodied_gen.skills.asset-process.asset_process
+ options:
+ heading_level: 3
+
+::: embodied_gen.skills.spatial-computing
+ options:
+ heading_level: 3
+
+::: embodied_gen.skills.spatial-computing.core.geometry
+ options:
+ heading_level: 3
+
+::: embodied_gen.skills.spatial-computing.core.collector
+ options:
+ heading_level: 3
+
+::: embodied_gen.skills.spatial-computing.core.visualizer
+ options:
+ heading_level: 3
+
+::: embodied_gen.skills.spatial-computing.core.trajectory
+ options:
+ heading_level: 3
+
+::: embodied_gen.skills.spatial-computing.api.floorplan_api
+ options:
+ heading_level: 3
diff --git a/docs/api/trainer.md b/docs/documentation/api/trainer.md
similarity index 100%
rename from docs/api/trainer.md
rename to docs/documentation/api/trainer.md
diff --git a/docs/api/utils.md b/docs/documentation/api/utils.md
similarity index 70%
rename from docs/api/utils.md
rename to docs/documentation/api/utils.md
index e67f42f..cfa0248 100644
--- a/docs/api/utils.md
+++ b/docs/documentation/api/utils.md
@@ -42,6 +42,22 @@ General-purpose utility functions, configuration, and helper classes.
options:
heading_level: 3
-::: embodied_gen.utils.monkey_patches
+::: embodied_gen.utils.inference
options:
- heading_level: 3
\ No newline at end of file
+ heading_level: 3
+
+::: embodied_gen.utils.llm_resolve
+ options:
+ heading_level: 3
+
+::: embodied_gen.utils.general
+ options:
+ heading_level: 3
+
+::: embodied_gen.utils.io_utils
+ options:
+ heading_level: 3
+
+::: embodied_gen.utils.vis_utils
+ options:
+ heading_level: 3
diff --git a/docs/api/validators.md b/docs/documentation/api/validators.md
similarity index 100%
rename from docs/api/validators.md
rename to docs/documentation/api/validators.md
diff --git a/docs/assets/Iscene_demo1.gif b/docs/documentation/assets/Iscene_demo1.gif
similarity index 100%
rename from docs/assets/Iscene_demo1.gif
rename to docs/documentation/assets/Iscene_demo1.gif
diff --git a/docs/assets/Iscene_demo2.gif b/docs/documentation/assets/Iscene_demo2.gif
similarity index 100%
rename from docs/assets/Iscene_demo2.gif
rename to docs/documentation/assets/Iscene_demo2.gif
diff --git a/docs/assets/articulate.gif b/docs/documentation/assets/articulate.gif
similarity index 100%
rename from docs/assets/articulate.gif
rename to docs/documentation/assets/articulate.gif
diff --git a/docs/documentation/assets/favicon.svg b/docs/documentation/assets/favicon.svg
new file mode 100644
index 0000000..926f25f
--- /dev/null
+++ b/docs/documentation/assets/favicon.svg
@@ -0,0 +1,7 @@
+
+
+
+
+
+
+
diff --git a/docs/assets/image_to_3d.jpg b/docs/documentation/assets/image_to_3d.jpg
similarity index 100%
rename from docs/assets/image_to_3d.jpg
rename to docs/documentation/assets/image_to_3d.jpg
diff --git a/docs/assets/layout1.gif b/docs/documentation/assets/layout1.gif
similarity index 100%
rename from docs/assets/layout1.gif
rename to docs/documentation/assets/layout1.gif
diff --git a/docs/assets/layout2.gif b/docs/documentation/assets/layout2.gif
similarity index 100%
rename from docs/assets/layout2.gif
rename to docs/documentation/assets/layout2.gif
diff --git a/docs/assets/layout3.gif b/docs/documentation/assets/layout3.gif
similarity index 100%
rename from docs/assets/layout3.gif
rename to docs/documentation/assets/layout3.gif
diff --git a/docs/assets/layout4.gif b/docs/documentation/assets/layout4.gif
similarity index 100%
rename from docs/assets/layout4.gif
rename to docs/documentation/assets/layout4.gif
diff --git a/docs/assets/logo.png b/docs/documentation/assets/logo.png
similarity index 100%
rename from docs/assets/logo.png
rename to docs/documentation/assets/logo.png
diff --git a/docs/assets/overall.jpg b/docs/documentation/assets/overall.jpg
similarity index 100%
rename from docs/assets/overall.jpg
rename to docs/documentation/assets/overall.jpg
diff --git a/docs/documentation/assets/overview_v2.jpg b/docs/documentation/assets/overview_v2.jpg
new file mode 100644
index 0000000..c4864db
Binary files /dev/null and b/docs/documentation/assets/overview_v2.jpg differ
diff --git a/docs/assets/parallel_sim.gif b/docs/documentation/assets/parallel_sim.gif
similarity index 100%
rename from docs/assets/parallel_sim.gif
rename to docs/documentation/assets/parallel_sim.gif
diff --git a/docs/assets/parallel_sim2.gif b/docs/documentation/assets/parallel_sim2.gif
similarity index 100%
rename from docs/assets/parallel_sim2.gif
rename to docs/documentation/assets/parallel_sim2.gif
diff --git a/docs/assets/simulators_collision.jpg b/docs/documentation/assets/simulators_collision.jpg
similarity index 100%
rename from docs/assets/simulators_collision.jpg
rename to docs/documentation/assets/simulators_collision.jpg
diff --git a/docs/assets/text_to_3d.jpg b/docs/documentation/assets/text_to_3d.jpg
similarity index 100%
rename from docs/assets/text_to_3d.jpg
rename to docs/documentation/assets/text_to_3d.jpg
diff --git a/docs/assets/texture_gen.jpg b/docs/documentation/assets/texture_gen.jpg
similarity index 100%
rename from docs/assets/texture_gen.jpg
rename to docs/documentation/assets/texture_gen.jpg
diff --git a/docs/documentation/assets/vibe_coding.gif b/docs/documentation/assets/vibe_coding.gif
new file mode 100644
index 0000000..745a10d
Binary files /dev/null and b/docs/documentation/assets/vibe_coding.gif differ
diff --git a/docs/documentation/assets/worlds.gif b/docs/documentation/assets/worlds.gif
new file mode 100644
index 0000000..e0ad807
Binary files /dev/null and b/docs/documentation/assets/worlds.gif differ
diff --git a/docs/documentation/index.md b/docs/documentation/index.md
new file mode 100644
index 0000000..040dba2
--- /dev/null
+++ b/docs/documentation/index.md
@@ -0,0 +1,76 @@
+---
+hide:
+ - navigation
+---
+
+# ๐ Welcome to EmbodiedGen V2
+
+[](https://horizonrobotics.github.io/EmbodiedGen/)
+[](https://github.com/HorizonRobotics/EmbodiedGen)
+[](https://arxiv.org/abs/2506.10600)
+[](https://huggingface.co/datasets/HorizonRobotics/EmbodiedGenData)
+[](https://www.youtube.com/watch?v=rG4odybuJRk)
+
+*EmbodiedGen V2*: An Agentic, Simulation-Ready 3D World Engine for Embodied AI.
+
+> **From intent to executable 3D worlds.** ***EmbodiedGen*** compiles language, images, and edit commands into **simulation-ready 3D worlds** โ physically plausible assets, large-scale scenes, and task-driven interactive environments, deployable across every major robotics simulator.
+
+
+
+## What can it do?
+
+
+
+- ๐งฑ **Generate โ Sim-Ready Assets**
+
+ ---
+
+ Turn a single image or a text prompt into a simulation-ready asset: metric geometry, collision proxy, and physical properties.
+
+ [โ Image-to-3D](tutorials/image_to_3d.md) ยท [Text-to-3D](tutorials/text_to_3d.md) ยท [Texture Generation](tutorials/texture_edit.md) ยท [Soft-Body Simulation](tutorials/soft_body.md) ยท [Affordance](tutorials/affordance.md)
+
+- ๐ **Scale โ Large-Scale Scenes**
+
+ ---
+
+ Multi-room, navigable, instance-editable houses and photo-realistic 3DGS backgrounds at controllable complexity.
+
+ [โ Room Generation](tutorials/room_gen.md) ยท [3D Scenes](tutorials/scene_gen.md)
+
+- ๐ **Compose โ Task-Driven Worlds**
+
+ ---
+
+ Natural-language tasks become physically stable, directly loadable interactive layouts with robots and objects.
+
+ [โ Layout Generation](tutorials/layout_gen.md) ยท [Digital Twin Creation](tutorials/digital_twin.md)
+
+- ๐ฌ **Edit โ 3D Vibe Coding**
+
+ ---
+
+ Build and edit deployable world states through dialogue โ every instruction a bounded, physics-validated skill call.
+
+ [โ 3D Vibe Coding](tutorials/vibe_coding.md)
+
+- ๐ฆ **Export โ Any Simulators**
+
+ ---
+
+ One standardized layout loads consistently across SAPIEN, Isaac Sim, Isaac Gym, MuJoCo, Genesis, and PyBullet.
+
+ [โ Any Simulators](tutorials/any_simulators.md)
+
+- ๐ค **Train โ Robot Learning**
+
+ ---
+
+ Generated worlds double as online training environments: parallel gym envs, trajectory recording, grasp evaluation.
+
+ [โ Robot Learning](tutorials/robot_learning.md)
+
+
+
+Start with the [Installation Guide](install.md), then follow the [Tutorials](tutorials/index.md). Online demos live in [Services](services/index.md).
+
+---
diff --git a/docs/install.md b/docs/documentation/install.md
similarity index 81%
rename from docs/install.md
rename to docs/documentation/install.md
index e3c3534..0c5ade1 100644
--- a/docs/install.md
+++ b/docs/documentation/install.md
@@ -7,12 +7,13 @@ hide:
```sh
git clone https://github.com/HorizonRobotics/EmbodiedGen.git
cd EmbodiedGen
-git checkout v0.1.7
-git submodule update --init --recursive --progress
+git checkout v2.0.0
+bash install/init_submodules.sh
conda create -n embodiedgen python=3.10.13 -y # recommended to use a new env.
conda activate embodiedgen
-bash install.sh basic # around 20 mins
-# Optional: `bash install.sh extra` for scene3d-cli
+# bash install.sh cu126 && conda deactivate && conda activate embodiedgen # Optional: if you don't have local cuda126.
+bash install.sh basic # around 10 mins
+# Optional: `bash install.sh scene3d` for scene3d-cli; `bash install.sh room` for room-cli; `bash install.sh affordance` for affordance-cli.
```
Please `huggingface-cli login` to ensure that the ckpts can be downloaded automatically afterwards.
diff --git a/docs/js/model_viewer.js b/docs/documentation/js/model_viewer.js
similarity index 100%
rename from docs/js/model_viewer.js
rename to docs/documentation/js/model_viewer.js
diff --git a/docs/services/image_to_3d.md b/docs/documentation/services/image_to_3d.md
similarity index 97%
rename from docs/services/image_to_3d.md
rename to docs/documentation/services/image_to_3d.md
index 674d243..11629de 100644
--- a/docs/services/image_to_3d.md
+++ b/docs/documentation/services/image_to_3d.md
@@ -74,4 +74,4 @@ Support the use of [SAM3D](https://github.com/facebookresearch/sam-3d-objects) o
!!! tip "Getting Started"
- Try it directly online via our [Hugging Face Space](https://huggingface.co/spaces/HorizonRobotics/EmbodiedGen-Image-to-3D) โ no installation required.
- Explore EmbodiedGen generated sim-ready [Assets Gallery](https://huggingface.co/spaces/HorizonRobotics/EmbodiedGen-Gallery-Explorer).
- - For instructions on using the generated asset in any simulator, see [Any Simulators Tutorial](tutorials/any_simulators.md).
\ No newline at end of file
+ - For instructions on using the generated asset in any simulator, see [Any Simulators Tutorial](../tutorials/any_simulators.md).
\ No newline at end of file
diff --git a/docs/services/index.md b/docs/documentation/services/index.md
similarity index 91%
rename from docs/services/index.md
rename to docs/documentation/services/index.md
index 92613af..d0e9699 100644
--- a/docs/services/index.md
+++ b/docs/documentation/services/index.md
@@ -5,7 +5,6 @@ Each service is optimized for visual quality, simulation compatibility, and scal
---
-## โ๏ธ Prerequisites
!!! tip "Prerequisites"
Make sure to finish the [Installation Guide](../install.md) before launching any service. Missing dependencies will cause initialization errors. Model weights are automatically downloaded on first run.
@@ -40,7 +39,3 @@ Each service is optimized for visual quality, simulation compatibility, and scal
- [๐ Tutorials](../tutorials/index.md) โ Learn how to use EmbodiedGen in generating interactive 3D scenes for embodied intelligence.
- [๐งฑ API Reference](../api/index.md) โ Integrate EmbodiedGen code programmatically.
-
----
-
-> ๐ก *EmbodiedGen bridges the gap between AI-driven 3D generation and physically grounded simulation, enabling true embodiment for intelligent agents.*
diff --git a/docs/services/text_to_3d.md b/docs/documentation/services/text_to_3d.md
similarity index 98%
rename from docs/services/text_to_3d.md
rename to docs/documentation/services/text_to_3d.md
index 4d590f5..afb5601 100644
--- a/docs/services/text_to_3d.md
+++ b/docs/documentation/services/text_to_3d.md
@@ -86,4 +86,4 @@ CUDA_VISIBLE_DEVICES=0 nohup python apps/text_to_3d.py > /dev/null 2>&1 &
!!! tip "Getting Started"
- You can also try Text-to-3D instantly online via our [Hugging Face Space](https://huggingface.co/spaces/HorizonRobotics/EmbodiedGen-Text-to-3D) โ no installation required.
- Explore EmbodiedGen generated sim-ready [Assets Gallery](https://huggingface.co/spaces/HorizonRobotics/EmbodiedGen-Gallery-Explorer).
- - For instructions on using the generated asset in any simulator, see [Any Simulators Tutorial](tutorials/any_simulators.md).
\ No newline at end of file
+ - For instructions on using the generated asset in any simulator, see [Any Simulators Tutorial](../tutorials/any_simulators.md).
\ No newline at end of file
diff --git a/docs/services/texture_edit.md b/docs/documentation/services/texture_edit.md
similarity index 98%
rename from docs/services/texture_edit.md
rename to docs/documentation/services/texture_edit.md
index 8a1c935..a51fe55 100644
--- a/docs/services/texture_edit.md
+++ b/docs/documentation/services/texture_edit.md
@@ -138,4 +138,4 @@ CUDA_VISIBLE_DEVICES=0 nohup python apps/texture_edit.py > /dev/null 2>&1 &
!!! tip "Getting Started"
- Try it directly online via our [Hugging Face Space](https://huggingface.co/spaces/HorizonRobotics/EmbodiedGen-Texture-Gen) โ no installation required.
- Explore EmbodiedGen generated sim-ready [Assets Gallery](https://huggingface.co/spaces/HorizonRobotics/EmbodiedGen-Gallery-Explorer).
- - For instructions on using the generated asset in any simulator, see [Any Simulators Tutorial](tutorials/any_simulators.md).
+ - For instructions on using the generated asset in any simulator, see [Any Simulators Tutorial](../tutorials/any_simulators.md).
diff --git a/docs/services/visualize_asset.md b/docs/documentation/services/visualize_asset.md
similarity index 86%
rename from docs/services/visualize_asset.md
rename to docs/documentation/services/visualize_asset.md
index 81a5553..5e7882e 100644
--- a/docs/services/visualize_asset.md
+++ b/docs/documentation/services/visualize_asset.md
@@ -5,4 +5,4 @@
!!! tip "Getting Started"
- Explore EmbodiedGen generated sim-ready [Assets Gallery](https://huggingface.co/spaces/HorizonRobotics/EmbodiedGen-Gallery-Explorer).
- - For instructions on using the generated asset in any simulator, see [Any Simulators Tutorial](tutorials/any_simulators.md).
\ No newline at end of file
+ - For instructions on using the generated asset in any simulator, see [Any Simulators Tutorial](../tutorials/any_simulators.md).
\ No newline at end of file
diff --git a/docs/documentation/stylesheets/extra.css b/docs/documentation/stylesheets/extra.css
new file mode 100644
index 0000000..b014987
--- /dev/null
+++ b/docs/documentation/stylesheets/extra.css
@@ -0,0 +1,121 @@
+/* ---- Brand alignment with the EmbodiedGen V2 landing page ---- */
+:root {
+ --md-accent-fg-color: #1E54FF; /* landing --blue */
+}
+[data-md-color-scheme="default"] {
+ --md-typeset-a-color: #1E54FF;
+}
+[data-md-color-scheme="slate"] {
+ --md-typeset-a-color: #7C9AFF; /* lighter blue for dark background */
+}
+
+/* Header logo: fit within the 2.4rem header bar */
+.md-header__button.md-logo {
+ height: 2.4rem;
+ padding: 0.2rem 0;
+ display: inline-flex;
+ align-items: center;
+}
+.md-header__button.md-logo img {
+ height: 100%;
+ width: auto;
+}
+
+/* Rounded corners for content media (skip badges/shields) */
+.md-typeset img:not([src*="shields.io"]),
+.md-typeset video,
+.md-typeset model-viewer {
+ border-radius: 12px;
+}
+
+.md-typeset pre code {
+ font-size: 0.7rem;
+ font-family: "JetBrains Mono", "Fira Code", monospace;
+}
+
+.md-typeset .admonition,
+.md-typeset details {
+ font-size: 0.77rem;
+}
+
+/* ---- Feature grid cards ("What can it do?") ----
+ Subtle brand-blue tint that reads well in both light and dark schemes. */
+.md-typeset .grid.cards > ul > li,
+.md-typeset .grid.cards > ol > li {
+ border-radius: 14px;
+ border: 1px solid var(--md-default-fg-color--lightest);
+ transition: transform .18s ease, box-shadow .18s ease, border-color .18s ease;
+}
+[data-md-color-scheme="default"] .md-typeset .grid.cards > ul > li,
+[data-md-color-scheme="default"] .md-typeset .grid.cards > ol > li {
+ background: linear-gradient(158deg, #ffffff 0%, #f4f6ff 62%, #eef2ff 100%);
+ box-shadow: 0 1px 2px rgba(16, 24, 40, .05);
+}
+[data-md-color-scheme="slate"] .md-typeset .grid.cards > ul > li,
+[data-md-color-scheme="slate"] .md-typeset .grid.cards > ol > li {
+ background: linear-gradient(158deg, rgba(124, 154, 255, .10) 0%, rgba(124, 154, 255, .22) 100%);
+ border-color: rgba(124, 154, 255, .30);
+ box-shadow: 0 1px 2px rgba(0, 0, 0, .35);
+}
+.md-typeset .grid.cards > ul > li:hover,
+.md-typeset .grid.cards > ol > li:hover {
+ transform: translateY(-3px);
+ border-color: var(--md-accent-fg-color);
+ box-shadow: 0 10px 26px rgba(30, 84, 255, .14);
+}
+
+/* ---- Header / footer: deep brand-navy gradient (no black) ----
+ Horizontal gradient so the header bar and the tabs bar blend seamlessly. */
+.md-header,
+.md-tabs {
+ background: linear-gradient(90deg, #0e1739 0%, #1A2C6E 55%, #2246B4 100%);
+}
+.md-header {
+ box-shadow: 0 1px 0 rgba(124, 154, 255, .22);
+}
+.md-footer {
+ background: linear-gradient(90deg, #0e1739 0%, #1A2C6E 55%, #2246B4 100%);
+}
+.md-footer-meta {
+ background: rgba(11, 20, 56, .45);
+}
+
+/* ---- Page background: landing-style paper + faded blueprint grid ----
+ Base: warm paper (light) / deep blue-ink (dark), with soft color washes
+ for depth. The grid lives on body::before and fades out downward via a
+ radial mask โ same technique as the landing `.sect--alt::before` wash. */
+body[data-md-color-scheme="default"] {
+ --md-default-bg-color: #FAFAF7;
+ background:
+ radial-gradient(90rem 34rem at 85% -8%, rgba(30, 84, 255, .06), transparent 70%),
+ radial-gradient(70rem 30rem at -12% 2%, rgba(228, 176, 82, .07), transparent 70%),
+ #FAFAF7;
+ background-attachment: fixed;
+}
+body[data-md-color-scheme="slate"] {
+ --md-default-bg-color: #151926;
+ background:
+ radial-gradient(90rem 34rem at 85% -8%, rgba(83, 118, 255, .12), transparent 70%),
+ radial-gradient(70rem 30rem at -12% 2%, rgba(124, 154, 255, .05), transparent 70%),
+ #151926;
+ background-attachment: fixed;
+}
+body[data-md-color-scheme="default"]::before,
+body[data-md-color-scheme="slate"]::before {
+ content: "";
+ position: fixed;
+ inset: 0;
+ z-index: -1;
+ pointer-events: none;
+ background:
+ linear-gradient(var(--eg-grid-line) 1px, transparent 1px) 0 0/30px 30px,
+ linear-gradient(90deg, var(--eg-grid-line) 1px, transparent 1px) 0 0/30px 30px;
+ -webkit-mask-image: radial-gradient(135% 92% at 50% -8%, #000 40%, transparent 85%);
+ mask-image: radial-gradient(135% 92% at 50% -8%, #000 40%, transparent 85%);
+}
+body[data-md-color-scheme="default"] {
+ --eg-grid-line: rgba(18, 20, 23, .05);
+}
+body[data-md-color-scheme="slate"] {
+ --eg-grid-line: rgba(124, 154, 255, .07);
+}
diff --git a/docs/documentation/tutorials/affordance.md b/docs/documentation/tutorials/affordance.md
new file mode 100644
index 0000000..89846e4
--- /dev/null
+++ b/docs/documentation/tutorials/affordance.md
@@ -0,0 +1,130 @@
+# ๐ฆพ Affordance โ Semantic Parts & Grasps
+
+Generate **part-level affordance annotations** for a simulator-ready URDF asset.
+
+The pipeline labels three kinds of information: functional part segmentation, part-wise semantic affordances, and simulation-validated grasp poses. Starting from a URDF with visual and collision meshes, it assigns mesh faces to functional parts, annotates each part with interaction semantics, generates 6-DoF grasp candidates, and filters grasps with physics simulation.
+
+---
+
+## โก Command-Line Usage
+
+Install the optional affordance dependencies before running the pipeline:
+
+```bash
+bash install.sh affordance
+```
+
+The semantic annotation stage uses the project's GPT Agent. Configure `embodied_gen/utils/gpt_config.yaml` or export the corresponding environment variables described in the installation guide before running the full pipeline.
+
+Run the demo asset:
+
+```bash
+affordance-cli \
+ --urdf-paths apps/assets/example_affordance/ear_hear/sample.urdf \
+ --output-dirs outputs/affordance_annotation/ear_hear
+```
+
+The input test case is:
+
+```sh
+apps/assets/example_affordance/ear_hear
+โโโ mesh
+โ โโโ material.mtl
+โ โโโ material_0.png
+โ โโโ sample.obj
+โ โโโ sample_collision.obj
+โโโ sample.urdf
+```
+
+- `sample.urdf` provides the simulator-ready asset wrapper, object category, visual mesh, collision mesh, and physical parameters.
+- `mesh/sample.obj` is used for visual rendering and part segmentation.
+- `mesh/sample_collision.obj` is used for grasp generation and physical validation.
+
+You can omit `--output-dirs`; by default, outputs are written to `affordance/` next to each input URDF.
+
+---
+
+## Pipeline Stages
+
+The pipeline has three stages:
+
+1. **Functional part segmentation**
+
+ Segment the visual mesh into functional part regions.
+
+2. **Part-wise semantic annotation**
+
+ Annotate each part with semantic name, graspability, scenarios, functions, and appearance.
+
+3. **Grasp generation and physical validation**
+
+ Generate 6-DoF grasps and keep simulation-validated candidates.
+
+Stages are dependent: semantics require segmentation, grasp generation requires semantics, and grasp evaluation requires generated grasps.
+
+---
+
+## Demo Output
+
+Running the demo command above produced:
+
+```sh
+outputs/affordance_annotation/ear_hear
+โโโ affordance_annot.json
+โโโ mesh_part_seg.glb
+```
+
+The run also updates the input URDF in place with `custom_data` entries that point to the generated segmentation mesh and affordance JSON.
+
+- `mesh_part_seg.glb` โ colored part-segmentation mesh for visualization; it also stores per-face `face_ids` in metadata, readable via `embodied_gen.utils.io_utils.load_mesh`.
+- `affordance_annot.json` โ part-level affordance schema with semantic labels and simulation-filtered grasps.
+
+An `affordance_annot.json` entry has this structure:
+
+```json
+{
+ "part_name": "headband",
+ "mask_color": "Red",
+ "graspable": true,
+ "grasp_scenarios": [
+ {
+ "scenario": "grasp the top of the headband to pick up the headphones",
+ "confidence": 0.94
+ }
+ ],
+ "functional_labels": [
+ "bridge the two earcups",
+ "rest on the top of the head",
+ "support wearing",
+ "provide a primary carrying point"
+ ],
+ "semantic_description": "Curved over-head band spanning the top of the headphones...",
+ "id": 0,
+ "grasp_group": {
+ "grasp_0": {
+ "confidence": 0.9834713339805603,
+ "position": [0.09800969052594155, 0.0028345893369987607, 0.08563571982085705],
+ "orientation": {
+ "w": 0.6647695595179328,
+ "xyz": [-0.08831381837710446, -0.7409686920659632, 0.035319960363011355]
+ }
+ }
+ }
+}
+```
+
+---
+
+## Useful Options
+
+- `--urdf-paths` โ one or more input URDF files.
+- `--output-dirs` โ output directory for each URDF. The number of output directories must match the number of URDFs.
+- `--no-run-part-seg` โ skip functional part segmentation when `mesh_part_seg.glb` already exists and the URDF points to it.
+- `--no-run-partsemantics-annot` โ skip semantic annotation when `affordance_annot.json` already contains part records.
+- `--no-run-grasp` โ skip grasp generation when the JSON already contains `grasp_group` entries.
+- `--no-run-grasp-eval` โ skip SAPIEN grasp validation and keep generated grasp proposals unfiltered.
+
+!!! tip "Getting Started"
+ - Use `apps/assets/example_affordance/ear_hear/sample.urdf` as the first smoke test.
+ - Use generated URDFs from [Image-to-3D](image_to_3d.md) or [Text-to-3D](text_to_3d.md) as inputs for new assets.
+
diff --git a/docs/tutorials/any_simulators.md b/docs/documentation/tutorials/any_simulators.md
similarity index 100%
rename from docs/tutorials/any_simulators.md
rename to docs/documentation/tutorials/any_simulators.md
diff --git a/docs/documentation/tutorials/articulated_gen.md b/docs/documentation/tutorials/articulated_gen.md
new file mode 100644
index 0000000..c50f5de
--- /dev/null
+++ b/docs/documentation/tutorials/articulated_gen.md
@@ -0,0 +1,12 @@
+# โ๏ธ Articulated Object Generation (DIPO)
+
+Generate **articulated objects** (doors, drawers, laptops, ...) from dual images depicting closed and open states, powered by **DIPO** โ our paper published in **NeurIPS 2025**.
+
+[[arXiv Paper]](https://arxiv.org/abs/2505.20460) |
+[[Gradio Demo]](https://huggingface.co/spaces/HorizonRobotics/DIPO) |
+[[Code]](https://github.com/RQ-Wu/DIPO)
+
+
+
+!!! note
+ DIPO is maintained in its own repository. Please refer to the [DIPO codebase](https://github.com/RQ-Wu/DIPO) for installation and usage.
diff --git a/docs/documentation/tutorials/digital_twin.md b/docs/documentation/tutorials/digital_twin.md
new file mode 100644
index 0000000..bf2eafd
--- /dev/null
+++ b/docs/documentation/tutorials/digital_twin.md
@@ -0,0 +1,11 @@
+# ๐ง Real-to-Sim Digital Twin Creation
+
+Recreate real-world scenes in simulation: capture a photo of each real object, generate its **physically plausible 3D twin** with [Image-to-3D](image_to_3d.md), and compose the twins into a simulator scene ([Any Simulators](any_simulators.md)).
+
+```sh
+img3d-cli --image_path path/to/real_capture.jpg --output_root outputs/digital_twin
+```
+
+Each asset keeps metric scale, collision geometry, and physical properties inferred from the image, so the resulting scene behaves consistently with its real counterpart โ below, an EmbodiedGen digital twin replayed in MuJoCo.
+
+
diff --git a/docs/tutorials/image_to_3d.md b/docs/documentation/tutorials/image_to_3d.md
similarity index 72%
rename from docs/tutorials/image_to_3d.md
rename to docs/documentation/tutorials/image_to_3d.md
index 0081d6b..cd4329b 100644
--- a/docs/tutorials/image_to_3d.md
+++ b/docs/documentation/tutorials/image_to_3d.md
@@ -1,11 +1,17 @@
-# ๐ผ๏ธ Image-to-3D: Physically Plausible 3D Asset Generation
+# ๐ผ๏ธ Image-to-3D โ Physically Plausible 3D Asset Generation
Generate **physically plausible 3D assets** from a single input image, supporting **digital twin** and **simulation environments**.
---
## โก Command-Line Usage
-Support the use of [SAM3D](https://github.com/facebookresearch/sam-3d-objects) or [TRELLIS](https://github.com/microsoft/TRELLIS) as 3D generation model, modify `IMAGE3D_MODEL` in `embodied_gen/scripts/imageto3d.py` to switch model.
+Three 3D generation backends are supported:
+
+- [`SAM3D`](https://github.com/facebookresearch/sam-3d-objects) โ local model (default)
+- [`TRELLIS`](https://github.com/microsoft/TRELLIS) โ local model
+- `HUNYUAN3D` โ Tencent Hunyuan3D Pro cloud API (no local GPU model needed)
+
+Select the backend via `--image3d_model` (case-insensitive). Omit to use the default `SAM3D`.
```bash
img3d-cli --image_path apps/assets/example_image/sample_00.jpg \
@@ -13,6 +19,18 @@ apps/assets/example_image/sample_01.jpg \
--n_retry 2 --output_root outputs/imageto3d
```
+### Using the Hunyuan3D Cloud Backend
+
+Hunyuan3D Pro runs entirely on Tencent Cloud โ useful when you don't have a local GPU. It requires Tencent Cloud Hunyuan3D `SecretId` / `SecretKey` and network access to `ai3d.tencentcloudapi.com` and the COS download host.
+
+```bash
+export TENCENT_SECRET_ID='your-secret-id'
+export TENCENT_SECRET_KEY='your-secret-key'
+img3d-cli --image3d_model HUNYUAN3D \
+ --image_path apps/assets/example_image/sample_00.jpg \
+ --output_root outputs/imageto3d_hunyuan
+```
+
You will get the following results:
diff --git a/docs/tutorials/index.md b/docs/documentation/tutorials/index.md
similarity index 74%
rename from docs/tutorials/index.md
rename to docs/documentation/tutorials/index.md
index b1569d6..a643cd0 100644
--- a/docs/tutorials/index.md
+++ b/docs/documentation/tutorials/index.md
@@ -1,17 +1,18 @@
# Tutorials & Interface Usage
-Welcome to the tutorials for `EmbodiedGen`. `EmbodiedGen` is a powerful toolset for generating 3D assets, textures, scenes, and interactive layouts ready for simulators and digital twin environments.
+Welcome to the tutorials for **EmbodiedGen V2** โ an agentic, simulation-ready 3D world engine for embodied AI. The tutorials are organized by capability: **generate** sim-ready assets, **scale** to large scenes, **compose** task-driven worlds, **edit** them through dialogue, **export** to any simulator, and **train** robot policies inside.
---
-## โ๏ธ Prerequisites
!!! tip "Prerequisites"
Make sure to finish the [Installation Guide](../install.md) before starting tutorial. Missing dependencies will cause initialization errors. Model weights are automatically downloaded on first run.
---
-## [๐ผ๏ธ Image-to-3D](image_to_3d.md)
+## ๐งฑ Generate โ Sim-Ready 3D Assets
+
+### [๐ผ๏ธ Image-to-3D](image_to_3d.md)
Generate **physically plausible 3D assets** from a single input image, supporting digital twin and simulation environments.
@@ -49,7 +50,7 @@ Generate **physically plausible 3D assets** from a single input image, supportin
---
-## [๐ Text-to-3D](text_to_3d.md)
+### [๐ Text-to-3D](text_to_3d.md)
Create **physically plausible 3D assets** from **text descriptions**, supporting a wide range of geometry, style, and material details.
@@ -94,7 +95,7 @@ Create **physically plausible 3D assets** from **text descriptions**, supporting
---
-## [๐จ Texture Generation](texture_gen.md)
+### [๐จ Texture Generation](texture_edit.md)
Generate **high-quality textures** for 3D meshes using **text prompts**, supporting both Chinese and English, to enhance the visual appearance of existing 3D assets.
@@ -125,7 +126,27 @@ Generate **high-quality textures** for 3D meshes using **text prompts**, support
---
-## [๐ 3D Scene Generation](scene_gen.md)
+### [๐งฅ Soft-Body Simulation](soft_body.md)
+
+Text-conditioned **garments** deploy as **deformable meshes** in Genesis โ the same generate-and-export path, beyond rigid bodies.
+
+### [โ๏ธ Articulated Objects (DIPO)](articulated_gen.md)
+
+Generate **articulated objects** from dual-state images with **DIPO** (NeurIPS 2025).
+
+### [๐ฆพ Affordance](affordance.md)
+
+Semantic part segmentation and grasp pose annotation for sim-ready assets.
+
+---
+
+## ๐ Scale โ Large-Scale Scenes
+
+### [๐ Room Generation](room_gen.md)
+
+Generate **multi-room, navigable, instance-editable indoor scenes** at a controllable complexity tier, exported to URDF/USD.
+
+### [๐ 3D Scene Generation](scene_gen.md)
Generate **physically consistent and visually coherent 3D environments** from text prompts. Typically used as **background** 3DGS scenes in simulators for efficient and photo-realistic rendering.
@@ -133,7 +154,9 @@ Generate **physically consistent and visually coherent 3D environments** from te
---
-## [๐๏ธ Layout Generation](layout_gen.md)
+## ๐ Compose โ Task-Driven Worlds
+
+### [๐๏ธ Layout Generation](layout_gen.md)
Generate diverse, physically realistic, and scalable **interactive 3D scenes** from natural language task descriptions, while also modeling the robot and manipulable objects.
@@ -144,24 +167,31 @@ Generate diverse, physically realistic, and scalable **interactive 3D scenes** f
+### [๐ง Real-to-Sim Digital Twin Creation](digital_twin.md)
+
+Recreate real-world scenes in simulation with physically plausible digital twins.
+
+
+
+
---
-## [๐๏ธ Parallel Simulation](gym_env.md)
+## ๐ฌ Edit โ 3D Vibe Coding
-Generate multiple **parallel simulation environments** with `gym.make` and record sensor and trajectory data.
+### [๐ฌ 3D Vibe Coding](vibe_coding.md)
-
-
-
-
+Build and edit sim-ready 3D worlds **through natural-language dialogue** via Claude Code slash commands (`/embodiedgen:*`) โ each instruction a bounded, physics-validated skill call.
+
+
+
---
-## [๐ฎ Use in Any Simulator](any_simulators.md)
+## ๐ฆ Export โ Any Simulators
+
+### [๐ฎ Use in Any Simulator](any_simulators.md)
Seamlessly use EmbodiedGen-generated assets in major simulators like **IsaacSim**, **MuJoCo**, **Genesis**, **PyBullet**, **IsaacGym**, and **SAPIEN**, featuring **accurate physical collisions** and **consistent visual appearance**.
@@ -169,8 +199,17 @@ Seamlessly use EmbodiedGen-generated assets in major simulators like **IsaacSim*
-## [๐ง Real-to-Sim Digital Twin Creation](digital_twin.md)
+---
-
-
+## ๐ค Train โ Robot Learning
+
+### [๐๏ธ Robot Learning](robot_learning.md)
+
+Spin up **parallel simulation environments** with `gym.make`, record sensor and trajectory data, and evaluate grasp quality of generated assets.
+
+
+
+
diff --git a/docs/tutorials/layout_gen.md b/docs/documentation/tutorials/layout_gen.md
similarity index 78%
rename from docs/tutorials/layout_gen.md
rename to docs/documentation/tutorials/layout_gen.md
index 3fb4e85..6bffdd0 100644
--- a/docs/tutorials/layout_gen.md
+++ b/docs/documentation/tutorials/layout_gen.md
@@ -52,6 +52,27 @@ You will get the following results:
+The generated results are organized as follows:
+```sh
+outputs/layouts_gen/task_0000
+โโโ layout.json # Standardized scene description, loadable by sim-cli / gym envs
+โโโ asset3d # Generated interactive assets, one URDF package per object
+โ โโโ
/result
+โ โโโ .urdf
+โ โโโ mesh # Visual mesh (.obj/.glb), collision mesh, 3DGS (.ply)
+โ โโโ video.mp4
+โโโ background # Background scene (3DGS + color mesh)
+โ โโโ gs_model.ply
+โ โโโ mesh_model.ply
+โโโ images # Text-to-image intermediates for each asset
+โโโ scene_tree.jpg # Parsed scene-graph visualization
+โโโ Iscene.mp4 # Interactive scene preview video
+```
+
+- `layout.json` โ The single source of truth for the composed world: asset poses, physics metadata, and background reference; consumed by `sim-cli`, `compose_layout.py`, and parallel gym envs
+- `asset3d/` โ Each manipulable/context object as a standard EmbodiedGen URDF asset
+- `scene_tree.jpg` โ Visualization of the scene graph parsed from the task description
+
### Batch Generation
You can also run multiple tasks via a task list file in the backend.
diff --git a/docs/documentation/tutorials/robot_learning.md b/docs/documentation/tutorials/robot_learning.md
new file mode 100644
index 0000000..ff5e5cf
--- /dev/null
+++ b/docs/documentation/tutorials/robot_learning.md
@@ -0,0 +1,52 @@
+# ๐ค Robot Learning
+
+EmbodiedGen-generated worlds are not just viewable โ they are **online training environments**. Spin up parallel `gym` environments from a generated layout, record sensor and trajectory data, and evaluate the grasp quality of generated assets.
+
+In a companion [sim-to-real RL study](https://arxiv.org/abs/2603.18532), policies trained purely in EmbodiedGen-generated worlds reached **79.8%** simulation task success (from 9.7%) and **75.0%** real-robot task success (from 21.7%). The training code is not part of this repository; the tools below provide the environment side.
+
+---
+
+## ๐๏ธ Parallel Simulation Environments
+
+Generate multiple parallel simulation environments with `gym.make` and record sensor and trajectory data.
+
+```sh
+python embodied_gen/scripts/parallel_sim.py \
+--layout_file "outputs/layouts_gen/task_0000/layout.json" \
+--output_dir "outputs/parallel_sim/task_0000" \
+--num_envs 16
+```
+
+
+
+
+
+
+The generated results are organized as follows:
+
+```sh
+outputs/parallel_sim/task_0000
+โโโ 0.mp4 # Tiled preview render across the parallel envs
+โโโ layout.json # Copy of the layout the envs were built from
+โโโ scene_tree.jpg # Scene-graph visualization
+```
+
+---
+
+## ๐ฆพ Grasp-Quality Evaluation
+
+Evaluate the grasp quality of a generated URDF via a top-down Panda grasp trial (ManiSkill + SAPIEN). Outputs a JSON report and per-trial MP4 next to the URDF.
+
+```sh
+python embodied_gen/scripts/eval_collision_success.py \
+ --urdf-path outputs/imageto3d/sample_00/result/sample_00.urdf \
+ --num-trials 4
+```
+
+---
+
+!!! tip "Next Steps"
+ - Compose task-driven worlds to train in โ see [Layout Generation](layout_gen.md).
+ - Export environments to your simulator โ see [Any Simulators](any_simulators.md).
diff --git a/docs/documentation/tutorials/room_gen.md b/docs/documentation/tutorials/room_gen.md
new file mode 100644
index 0000000..8381271
--- /dev/null
+++ b/docs/documentation/tutorials/room_gen.md
@@ -0,0 +1,104 @@
+# ๐ Room Generation โ Large-Scale Indoor Scenes
+
+Generate **multi-room, navigable, instance-editable indoor scenes** as sim-ready backgrounds, built on top of [Infinigen](https://github.com/princeton-vl/infinigen). Rooms are generated at a controllable complexity tier and can be exported to **URDF** and **USD** for direct use in simulators.
+
+
+
+!!! note "Installation"
+ Run `bash install.sh room` to install the additional dependencies (including the bundled Blender runtime) before using room generation.
+
+---
+
+## โก Command-Line Usage
+
+```bash
+# Natural-language mode: a GPT router infers room type & complexity
+room-cli -m embodied_gen.scripts.room_gen.gen_room \
+ --output-root outputs/rooms \
+ --prompt "Wipe the table in a simple dining room"
+
+# Explicit mode
+room-cli -m embodied_gen.scripts.room_gen.gen_room \
+ --output-root outputs/rooms \
+ --room-type Kitchen \
+ --seed 42 \
+ --complexity simple
+```
+
+!!! note "Natural-language mode"
+ `--prompt` feeds the description to a GPT router (`route_room.py`) that selects a plausible room type (or `House` for cross-room tasks) and a complexity tier, overriding `--room-type` / `--complexity`. It requires a configured GPT agent (`embodied_gen/utils/gpt_config.yaml`).
+
+!!! note "`room-cli` vs `python -m`"
+ `room-cli` forwards its arguments to the bundled **Blender Python** (which has `bpy` and all room dependencies installed by `bash install.sh room`). Running `python -m embodied_gen.scripts.room_gen.gen_room` from the `embodiedgen` env is equivalent for generation, since heavy stages are dispatched to Blender Python internally either way. Run from the repository root; set `BLENDER_PYTHON_BIN` if Blender is installed elsewhere.
+
+### Parameters
+
+- `--output-root` (required): base output directory.
+- `--prompt`: natural-language task/scene description; infers room type and complexity via GPT, overriding the two flags below.
+- `--room-type`: `Bedroom | LivingRoom | Kitchen | Bathroom | DiningRoom | Office | House`.
+- `--seed`: random seed. Pass explicitly for reproducible runs; if omitted, a random seed is generated (check logs for the value).
+- `--complexity`: `minimalist | simple | medium | detail`.
+- `--custom-params`: gin file copied to Infinigen `custom_solve.gin`.
+- `--large-scene`: only for `House`; enables more rooms.
+- `--gen/--no-gen`, `--urdf/--no-urdf`, `--usd/--no-usd`: pipeline stage switches.
+
+### Complexity Tiers
+
+| Tier | Description |
+|------|-------------|
+| `minimalist` | Fastest, sparse furniture |
+| `simple` | Default, balanced quality/time |
+| `medium` | Richer layout, slower |
+| `detail` | Highest detail, longest runtime |
+
+### Stage Control
+
+```bash
+# Generation only (no export)
+python -m embodied_gen.scripts.room_gen.gen_room \
+ --output-root outputs/rooms --room-type LivingRoom \
+ --seed 100 --complexity medium --no-urdf --no-usd
+
+# Export only, from an existing Blender output
+python -m embodied_gen.scripts.room_gen.gen_room \
+ --output-root outputs/rooms --room-type LivingRoom \
+ --seed 100 --no-gen --urdf --usd
+```
+
+The generated results are organized as follows:
+
+```sh
+/_seed
+โโโ blender # Raw Infinigen/Blender scene output
+โโโ urdf/export_scene
+โ โโโ scene.urdf # Whole-room URDF referencing all instances
+โ โโโ / # Per-instance meshes with convex-decomposed collisions
+โโโ usd/export_scene
+ โโโ export_scene.usdc # USD scene for Isaac Sim
+ โโโ textures # Baked PBR textures (diffuse / normal / roughness)
+```
+
+- `urdf/export_scene/scene.urdf` โ Load directly in SAPIEN / PyBullet / IsaacGym; each furniture instance stays individually editable
+- `usd/export_scene/export_scene.usdc` โ USD scene for Isaac Sim; **requires a physics post-processing step before use** (see below)
+- `blender/` โ Reusable for re-export (`--no-gen --urdf --usd`)
+
+!!! warning "Add physics to the exported USD before loading it in Isaac Sim"
+ The exported `export_scene.usdc` carries no physics collision properties yet. Post-process it with `PhysicsUSDAdder` (requires an Isaac Sim / USD environment), which applies convex-decomposition collision and rigid-body APIs to every mesh prim:
+
+ ```python
+ from embodied_gen.data.asset_converter import PhysicsUSDAdder
+
+ PhysicsUSDAdder().convert(
+ "outputs/rooms/Kitchen_seed42/usd/export_scene/export_scene.usdc"
+ )
+ # In-place by default; pass output_file=... to write a separate copy.
+ ```
+
+!!! tip "Blender Python (`room-cli`)"
+ `room-cli` is a wrapper that executes a script within the bundled Blender Python environment (which provides `bpy`). Steps that write USD require it, e.g. `room-cli -m embodied_gen.skills.spatial-computing.cli.main ...` when editing scenes with the [spatial computing skill](vibe_coding.md). If Blender is installed elsewhere, set `BLENDER_PYTHON_BIN`.
+
+---
+
+!!! tip "Next Steps"
+ - Place or remove objects in the generated rooms via natural language โ see [3D Vibe Coding](vibe_coding.md).
+ - Use generated rooms as backgrounds for task-driven worlds โ see [Layout Generation](layout_gen.md).
diff --git a/docs/tutorials/scene_gen.md b/docs/documentation/tutorials/scene_gen.md
similarity index 94%
rename from docs/tutorials/scene_gen.md
rename to docs/documentation/tutorials/scene_gen.md
index 15c5a14..8e36cd7 100644
--- a/docs/tutorials/scene_gen.md
+++ b/docs/documentation/tutorials/scene_gen.md
@@ -10,7 +10,7 @@ Generate **physically consistent and visually coherent 3D environments** from te
## โก Command-Line Usage
-> ๐ก Run `bash install.sh extra` to install additional dependencies if you plan to use `scene3d-cli`.
+> ๐ก Run `bash install.sh scene3d` to install additional dependencies if you plan to use `scene3d-cli`.
It typically takes ~30 minutes per scene to generate both the colored mesh and 3D Gaussian Splat(3DGS) representation.
diff --git a/docs/documentation/tutorials/soft_body.md b/docs/documentation/tutorials/soft_body.md
new file mode 100644
index 0000000..42d8055
--- /dev/null
+++ b/docs/documentation/tutorials/soft_body.md
@@ -0,0 +1,60 @@
+# ๐งฅ Soft-Body Simulation
+
+The same generate-and-export path reaches **soft bodies**: text-conditioned garments generated by EmbodiedGen deploy as **deformable meshes in [Genesis](https://github.com/Genesis-Embodied-AI/Genesis)**, with no manual preparation.
+
+---
+
+## โก Usage
+
+The soft-body toolchain lives in `embodied_gen/scripts/deformable_sim/`:
+
+1. **Generate a garment asset** with [Text-to-3D](text_to_3d.md) (e.g. *"a red hooded jacket"*).
+
+2. **Convert the asset for cloth simulation**:
+
+```bash
+python embodied_gen/scripts/deformable_sim/genesis_cloth_converter.py \
+ --asset_path outputs/textto3d/asset3d/sample3d_00/result \
+ --output_dir outputs/cloth
+```
+
+3. **Run the Genesis cloth simulation** (drop-and-shake demo):
+
+```bash
+python embodied_gen/scripts/deformable_sim/simulate_genesis_cloth.py \
+ --asset_path outputs/cloth \
+ --init_height 1.0 \
+ --duration_seconds 6.0 \
+ --shake
+```
+
+4. **Visualize per-vertex displacement** as a heatmap:
+
+```bash
+python embodied_gen/scripts/deformable_sim/render_mesh_displacement_heatmap.py \
+ --asset_path outputs/cloth
+```
+
+Run any script with `--help` for the full set of options (simulation steps, shake amplitude, camera, device, etc.).
+
+The generated results are organized as follows:
+
+```sh
+outputs/cloth
+โโโ genesis
+โ โโโ cloth_mesh.obj # Simulation-ready cloth mesh
+โ โโโ cloth_material.json # Cloth material parameters for Genesis
+โ โโโ material_0.png / material.mtl
+โ โโโ manifest.json
+โโโ render
+ โโโ video.mp4 # Cloth simulation preview
+ โโโ sim_config.json # Simulation settings used for the run
+ โโโ run_summary.json # Run statistics
+ โโโ *_contact_sheet.jpg # Per-vertex displacement heatmap sheets
+```
+
+---
+
+!!! tip "Next Steps"
+ - Generate garment meshes from text โ see [Text-to-3D](text_to_3d.md).
+ - Rigid-body export to other simulators โ see [Any Simulators](any_simulators.md).
diff --git a/docs/tutorials/text_to_3d.md b/docs/documentation/tutorials/text_to_3d.md
similarity index 84%
rename from docs/tutorials/text_to_3d.md
rename to docs/documentation/tutorials/text_to_3d.md
index 0c4b0dc..db1530c 100644
--- a/docs/tutorials/text_to_3d.md
+++ b/docs/documentation/tutorials/text_to_3d.md
@@ -1,4 +1,4 @@
-# ๐ Text-to-3D: Generate 3D Assets from Text
+# ๐ Text-to-3D โ Generate 3D Assets from Text
Create **physically plausible 3D assets** from **text descriptions**, supporting a wide range of geometry, style, and material details.
@@ -80,6 +80,27 @@ bash embodied_gen/scripts/textto3d.sh \
> Models with more permissive licenses can be found in `embodied_gen/models/image_comm_model.py`.
+### Choosing the 3D Backend
+
+Three 3D generation backends are supported via `--image3d_model` (case-insensitive):
+
+- `SAM3D` (default) โ text โ image โ 3D, local SAM3D model
+- `TRELLIS` โ text โ image โ 3D, local TRELLIS model
+- `HUNYUAN3D` โ Tencent Hunyuan3D Pro **text-to-3D** API; skips the text-to-image stage entirely and generates 3D directly from the prompt
+
+### Using the Hunyuan3D Cloud Backend
+
+Hunyuan3D Pro takes the prompt directly to a 3D mesh (no GPU model loaded locally; one job โ 3 minutes; Tencent Cloud is billed per submit). Set up credentials once:
+
+```bash
+export TENCENT_SECRET_ID='your-secret-id'
+export TENCENT_SECRET_KEY='your-secret-key'
+text3d-cli --image3d_model HUNYUAN3D \
+ --prompts "small bronze figurine of a lion" \
+ --output_root outputs/textto3d_hunyuan
+```
+
+
The generated results are organized as follows:
```sh
diff --git a/docs/tutorials/texture_edit.md b/docs/documentation/tutorials/texture_edit.md
similarity index 76%
rename from docs/tutorials/texture_edit.md
rename to docs/documentation/tutorials/texture_edit.md
index 480d81c..58f7379 100644
--- a/docs/tutorials/texture_edit.md
+++ b/docs/documentation/tutorials/texture_edit.md
@@ -1,4 +1,4 @@
-# ๐จ Texture Generation: Create Visually Rich Textures for 3D Meshes
+# ๐จ Texture Generation โ Create Visually Rich Textures for 3D Meshes
Generate **high-quality textures** for 3D meshes using **text prompts**, supporting both **Chinese and English**. This allows you to enhance the visual appearance of existing 3D assets for simulation, visualization, or digital twin applications.
@@ -49,6 +49,22 @@ You will get the following results:
+The generated results are organized as follows:
+```sh
+outputs/texture_gen/
+โโโ multi_view # Multi-view intermediates (rgb/normal/position, texture samples)
+โโโ texture_mesh
+โ โโโ .obj
+โ โโโ .glb
+โ โโโ material_0.png # Baked texture map
+โ โโโ material.mtl
+โโโ color.mp4 # Turntable preview of the textured mesh
+```
+
+- `texture_mesh/` โ Final textured mesh in OBJ and GLB formats with the baked texture
+- `multi_view/` โ Intermediate multi-view generations used for texture baking
+- `color.mp4` โ Preview video of the textured result
+
---
!!! tip "Getting Started"
diff --git a/docs/documentation/tutorials/vibe_coding.md b/docs/documentation/tutorials/vibe_coding.md
new file mode 100644
index 0000000..bdbf747
--- /dev/null
+++ b/docs/documentation/tutorials/vibe_coding.md
@@ -0,0 +1,53 @@
+# ๐ฌ 3D Vibe Coding โ Build 3D Worlds Through Dialogue
+
+Build and edit **sim-ready 3D worlds through natural-language dialogue**. EmbodiedGen ships a set of reusable agent skills and a **Claude Code plugin** whose slash commands wrap them โ each instruction is a bounded, physics-validated skill call that preserves a deployable, simulator-ready world state.
+
+
+
+---
+
+## ๐ Install the Claude Code Plugin
+
+```bash
+bash install/install_agent_plugin.sh
+```
+
+This registers the local marketplace `embodiedgen-local` and installs the `embodiedgen` plugin into [Claude Code](https://claude.com/claude-code). The plugin package lives in `embodied_gen/skills/claude_plugin/plugins/embodiedgen/`.
+
+## ๐งฐ Available Commands
+
+| Command | What it does |
+|---------|--------------|
+| `/embodiedgen:gen_assets` | Generate 3D assets from images or text (`img3d-cli`, `text3d-cli`, `texture-cli`) |
+| `/embodiedgen:gen_indoor` | Generate rooms or multi-room houses ([Room Generation](room_gen.md)) |
+| `/embodiedgen:gen_bg` | Generate 3DGS background scenes (`scene3d-cli`) |
+| `/embodiedgen:gen_layout` | Compose task-driven interactive worlds (`layout-cli`) |
+| `/embodiedgen:vibe3d` | Insert / remove / place objects in a scene via natural language (spatial computing) |
+| `/embodiedgen:sim` | Render layouts in SAPIEN simulation (`sim-cli`) |
+| `/embodiedgen:convert` | Export assets to USD / MJCF / URDF ([Any Simulators](any_simulators.md)) |
+| `/embodiedgen:process` | Scale or rotate existing URDF-based assets |
+
+Example session:
+
+```text
+> /embodiedgen:gen_indoor Create a simple kitchen, seed 42
+> /embodiedgen:vibe3d Put a lamp on the bookshelf in the living room
+> /embodiedgen:sim Render outputs/layouts_gen/task_0000/layout.json
+```
+
+## ๐ง Skill Sources
+
+The slash commands are thin adapters over generic, runtime-agnostic skills under `embodied_gen/skills/`:
+
+- `asset-creator`, `asset-retrieval`, `asset-converter`, `asset-process`
+- `background-creator`, `room-creator`, `layout-creator`, `sim-runner`
+- `spatial-computing` โ floorplan visualization and collision-aware object placement/deletion with semantic instance matching (e.g. *"put lamp on bookshelf"*, *"delete sofa in living room"*). See `embodied_gen/skills/spatial-computing/SKILL.md` for details.
+
+!!! note "USD updates require Blender Python"
+ Scene edits that update USD files must run under `room-cli` (Blender Python provides `bpy`); the skills prompt the agent to choose the correct invocation automatically.
+
+---
+
+!!! tip "Next Steps"
+ - Load the edited world into simulation โ see [Robot Learning](robot_learning.md).
+ - Export to your simulator of choice โ see [Any Simulators](any_simulators.md).
diff --git a/docs/index.md b/docs/index.md
deleted file mode 100644
index 2a777c6..0000000
--- a/docs/index.md
+++ /dev/null
@@ -1,25 +0,0 @@
----
-hide:
- - navigation
----
-
-# ๐ Welcome to EmbodiedGen
-
-[](https://horizonrobotics.github.io/EmbodiedGen/)
-[](https://github.com/HorizonRobotics/EmbodiedGen)
-[](https://arxiv.org/abs/2506.10600)
-[](https://www.youtube.com/watch?v=rG4odybuJRk)
-[](https://mp.weixin.qq.com/s/HH1cPBhK2xcDbyCK4BBTbw)
-
-[](https://huggingface.co/spaces/HorizonRobotics/EmbodiedGen-Gallery-Explorer)
-[](https://huggingface.co/spaces/HorizonRobotics/EmbodiedGen-Image-to-3D)
-[](https://huggingface.co/spaces/HorizonRobotics/EmbodiedGen-Text-to-3D)
-[](https://huggingface.co/spaces/HorizonRobotics/EmbodiedGen-Texture-Gen)
-
-*EmbodiedGen*: Towards a Generative 3D World Engine for Embodied Intelligence.
-
-
-
-> ***EmbodiedGen*** is a generative engine to create diverse and interactive 3D worlds composed of high-quality 3D assets(mesh & 3DGS) with plausible physics, leveraging generative AI to address the challenges of generalization in embodied intelligence related research.
-
----
diff --git a/docs/landing/README.md b/docs/landing/README.md
new file mode 100644
index 0000000..99b544d
--- /dev/null
+++ b/docs/landing/README.md
@@ -0,0 +1,55 @@
+# EmbodiedGen V2 โ Project Landing Page
+
+Standalone static site (`docs/landing/`). No build step, no framework, no dependencies โ
+open `index.html` over any static server.
+
+On GitHub Pages this is published at the site **root** (`https://horizonrobotics.github.io/EmbodiedGen/`);
+the MkDocs documentation is published under **`/docs/`**. See `.github/workflows/deploy_docs.yml`.
+
+## Local preview
+```bash
+cd docs/landing
+python3 -m http.server 8123
+# open http://localhost:8123
+```
+Fonts load from Google Fonts (needs internet); a system fallback stack is configured.
+The repo `.gitignore` ignores `*.html` / `*.mp4` / `*.json` globally, but re-includes them under
+`docs/landing/**` (see the negation block at the end of `.gitignore`), so these site files are tracked normally.
+
+## Files
+| File | Responsibility |
+|------|----------------|
+| `index.html` | Structure + all section containers |
+| `style.css` | Design system (CSS vars, type, grid, components, responsive) โ single source of truth |
+| `script.js` | Scroll-spy, reveal-on-scroll, lazy video play/pause, asset 3D viewer, vibe sessions, count-up, BibTeX copy |
+| `tools/cut_videos_from_master.sh` | Section videos cut from the master demo video |
+| `tools/build_assets.sh` | Figures (JPG), brand logo, sim-ready models + `assets.json`, vibe clips, affordance |
+| `tools/build_affordance.py` | Part-seg GLB + baked grippers + `affordance.json` |
+| `favicon.svg` + `favicon-{32,180}.png` | Browser tab icon โ brand-blue 3D cube (SVG primary, PNG fallback / apple-touch). Static brand asset. |
+
+## Assets โ how they're produced (idempotent)
+Two scripts own disjoint sets of outputs under `assets/`:
+
+- **`tools/cut_videos_from_master.sh`** โ the five section videos + posters
+ (`hero_bg`, `scenes_gen`, `worlds`, `multi_sim`, `closed_loop`), cut as timestamped
+ segments from the master demo video. The master `.mov` is **not** kept in the repo.
+- **`tools/build_assets.sh`** โ everything else: figure JPGs (โค1 MB), `logo.png`,
+ the GLB models + `assets.json`, the six `scene_*` vibe clips, and affordance data.
+ Reads READ-ONLY sources from `outputs/embodiedgenv2/{paper/figure,video}` and the asset buckets.
+
+## Performance notes (production)
+- Section videos use `preload="none"` + `data-autoplay`: fetched and played only when
+ scrolled into view, paused when offscreen (see `script.js`).
+- The `model-viewer` library (~1 MB) and the first GLB load lazily โ warmed right after
+ page load (idle) or when the Assets section nears, whichever comes first.
+- Hero streams progressively (`+faststart` + poster). Large figures are JPG inside
+ collapsed `` with `loading="lazy"`, so they don't load until expanded.
+- Server side (deploy): enable brotli/gzip, long `Cache-Control` for hashed assets, a CDN,
+ and consider self-hosting `model-viewer` instead of the unpkg CDN.
+
+## Pre-release TODO
+- [ ] Replace placeholder authors with the final list.
+- [ ] Point the Dataset button to the final HF dataset URL.
+- [ ] Confirm the arXiv number for EmbodiedGen V2 (currently `2506.10600`).
+- [ ] Add an OG/share image.
+- [ ] Capture acceptance screenshots at 1440 / 1024 / 390.
diff --git a/docs/landing/assets/img/closed_loop_poster.jpg b/docs/landing/assets/img/closed_loop_poster.jpg
new file mode 100644
index 0000000..791eecf
Binary files /dev/null and b/docs/landing/assets/img/closed_loop_poster.jpg differ
diff --git a/docs/landing/assets/img/deformable_heatmap.jpg b/docs/landing/assets/img/deformable_heatmap.jpg
new file mode 100644
index 0000000..0e5c65f
Binary files /dev/null and b/docs/landing/assets/img/deformable_heatmap.jpg differ
diff --git a/docs/landing/assets/img/hero_poster.jpg b/docs/landing/assets/img/hero_poster.jpg
new file mode 100644
index 0000000..5a2e259
Binary files /dev/null and b/docs/landing/assets/img/hero_poster.jpg differ
diff --git a/docs/landing/assets/img/large_scale.jpg b/docs/landing/assets/img/large_scale.jpg
new file mode 100644
index 0000000..5d8f61a
Binary files /dev/null and b/docs/landing/assets/img/large_scale.jpg differ
diff --git a/docs/landing/assets/img/layout_pipe.jpg b/docs/landing/assets/img/layout_pipe.jpg
new file mode 100644
index 0000000..ba36e23
Binary files /dev/null and b/docs/landing/assets/img/layout_pipe.jpg differ
diff --git a/docs/landing/assets/img/logo.png b/docs/landing/assets/img/logo.png
new file mode 100644
index 0000000..47a95d0
Binary files /dev/null and b/docs/landing/assets/img/logo.png differ
diff --git a/docs/landing/assets/img/multi_sim_poster.jpg b/docs/landing/assets/img/multi_sim_poster.jpg
new file mode 100644
index 0000000..7eff5bd
Binary files /dev/null and b/docs/landing/assets/img/multi_sim_poster.jpg differ
diff --git a/docs/landing/assets/img/og_card.png b/docs/landing/assets/img/og_card.png
new file mode 100644
index 0000000..adf6748
Binary files /dev/null and b/docs/landing/assets/img/og_card.png differ
diff --git a/docs/landing/assets/img/overview.jpg b/docs/landing/assets/img/overview.jpg
new file mode 100644
index 0000000..c4864db
Binary files /dev/null and b/docs/landing/assets/img/overview.jpg differ
diff --git a/docs/landing/assets/img/policy_deploy.jpg b/docs/landing/assets/img/policy_deploy.jpg
new file mode 100644
index 0000000..297f8e7
Binary files /dev/null and b/docs/landing/assets/img/policy_deploy.jpg differ
diff --git a/docs/landing/assets/img/scenes_gen_poster.jpg b/docs/landing/assets/img/scenes_gen_poster.jpg
new file mode 100644
index 0000000..593ae2a
Binary files /dev/null and b/docs/landing/assets/img/scenes_gen_poster.jpg differ
diff --git a/docs/landing/assets/img/sim_pipeline.jpg b/docs/landing/assets/img/sim_pipeline.jpg
new file mode 100644
index 0000000..faf38cc
Binary files /dev/null and b/docs/landing/assets/img/sim_pipeline.jpg differ
diff --git a/docs/landing/assets/img/worlds_poster.jpg b/docs/landing/assets/img/worlds_poster.jpg
new file mode 100644
index 0000000..aa82f30
Binary files /dev/null and b/docs/landing/assets/img/worlds_poster.jpg differ
diff --git a/docs/landing/assets/models/affordance.json b/docs/landing/assets/models/affordance.json
new file mode 100644
index 0000000..8e23a02
--- /dev/null
+++ b/docs/landing/assets/models/affordance.json
@@ -0,0 +1,565 @@
+{
+ "ear_hear": {
+ "parts": [
+ {
+ "part_name": "headband",
+ "color": "#E6194B",
+ "color_name": "Red",
+ "graspable": true,
+ "labels": [
+ "span between earcups",
+ "support wearing on the head",
+ "main carrying point",
+ "maintain overall frame shape"
+ ],
+ "description": "Wide arched headband with a pale pink outer surface and white inner side, likely plastic with soft padding, smooth finish, and a gently curved medium-thickness form across the top.",
+ "scenarios": [
+ "grasp the top of the headband to pick up the headphones",
+ "hold the headband to hang, carry, or reposition the headphones"
+ ]
+ },
+ {
+ "part_name": "earcup housing and yoke",
+ "color": "#911EB4",
+ "color_name": "Purple",
+ "graspable": true,
+ "labels": [
+ "enclose speaker components",
+ "support ear cushions",
+ "connect to headband through the side support",
+ "provide structural shell"
+ ],
+ "description": "Rounded over-ear outer housings with integrated side support arms, predominantly white to very pale pink, likely hard plastic with a smooth semi-matte finish, broad bulbous shape, and medium size on both lower sides of the frame.",
+ "scenarios": [
+ "grasp one earcup housing from the side to lift or reorient the headphones",
+ "hold both earcup housings to place the headphones onto a surface or stand"
+ ]
+ },
+ {
+ "part_name": "ear cushion",
+ "color": "#46F0F0",
+ "color_name": "Cyan",
+ "graspable": false,
+ "labels": [
+ "cushion the ears",
+ "provide wearing comfort",
+ "improve acoustic seal",
+ "contact the side of the head"
+ ],
+ "description": "Oval white padded ear cushions, likely foam covered in smooth synthetic leather, with a soft slightly matte surface and thick ring-like shape around the ear openings.",
+ "scenarios": []
+ },
+ {
+ "part_name": "hinge accent joint",
+ "color": "#3CB44B",
+ "color_name": "Green",
+ "graspable": false,
+ "labels": [
+ "join the headband to the earcup support",
+ "allow local articulation or rotation",
+ "reinforce the transition between major parts",
+ "transfer load between headband and earcup assembly"
+ ],
+ "description": "Small narrow connector accents at the junctions between the headband and earcup supports, appearing rose-gold or copper-toned, hard, smooth, and slightly reflective.",
+ "scenarios": []
+ }
+ ]
+ },
+ "basket": {
+ "parts": [
+ {
+ "part_name": "basket handle",
+ "color": "#E6194B",
+ "color_name": "Red",
+ "graspable": true,
+ "labels": [
+ "provide a carrying grip",
+ "lift the basket",
+ "enable pulling",
+ "support repositioning"
+ ],
+ "description": "An arched tan-orange woven handle made of wicker or rattan with a matte finish, hard slightly rough texture, and slender elongated shape spanning across the basket top.",
+ "scenarios": [
+ "grasp the handle to lift and carry the basket",
+ "hold the handle to reposition or swing the basket"
+ ]
+ },
+ {
+ "part_name": "basket body",
+ "color": "#3CB44B",
+ "color_name": "Green",
+ "graspable": true,
+ "labels": [
+ "contain items",
+ "support carried contents",
+ "provide side support",
+ "form the basket base and walls",
+ "stabilize the object when set down"
+ ],
+ "description": "A medium-sized round tan-orange woven basket body, likely wicker or rattan, with a matte finish, hard slightly rough texture, open lattice bottom, and curved sidewalls forming the main container.",
+ "scenarios": [
+ "hold the basket body to stabilize it during loading or unloading",
+ "grasp the basket rim or sidewall to pick up the basket when the handle is inaccessible"
+ ]
+ }
+ ]
+ },
+ "decoration": {
+ "parts": [
+ {
+ "part_name": "bird body",
+ "color": "#0082C8",
+ "color_name": "Blue",
+ "graspable": true,
+ "labels": [
+ "main structural form",
+ "connect attached parts",
+ "primary grasp surface",
+ "provide decorative bird silhouette"
+ ],
+ "description": "Large glossy bird body in deep blue to violet tones, made of hard smooth material, with a rounded chest, tapered rear, and the head and torso forming most of the visible mass.",
+ "scenarios": [
+ "grasp the torso to pick up and reposition the bird figurine",
+ "hold the body to stabilize the object during placement or inspection"
+ ]
+ },
+ {
+ "part_name": "left wing",
+ "color": "#FFE119",
+ "color_name": "Yellow",
+ "graspable": false,
+ "labels": [
+ "suggest folded wing anatomy",
+ "provide side decoration",
+ "add visual detail"
+ ],
+ "description": "Medium-sized glossy wing on one side of the bird, blue with darker shading, made of hard smooth material and sculpted with layered feather-like contours in a folded shape against the body.",
+ "scenarios": []
+ },
+ {
+ "part_name": "right wing",
+ "color": "#911EB4",
+ "color_name": "Purple",
+ "graspable": false,
+ "labels": [
+ "suggest folded wing anatomy",
+ "provide side decoration",
+ "add bilateral detail"
+ ],
+ "description": "Medium-sized glossy wing on the opposite side, blue with darker violet shading, made of hard smooth material and shaped with layered feather-like sculpting in a folded profile along the body.",
+ "scenarios": []
+ },
+ {
+ "part_name": "left foot",
+ "color": "#E6194B",
+ "color_name": "Red",
+ "graspable": false,
+ "labels": [
+ "support standing",
+ "stabilize the figurine",
+ "contact surface"
+ ],
+ "description": "Small dark blue foot at the bottom of the bird, made of hard smooth material and shaped as a stylized splayed support that helps the figurine stand.",
+ "scenarios": []
+ },
+ {
+ "part_name": "right foot",
+ "color": "#3CB44B",
+ "color_name": "Green",
+ "graspable": false,
+ "labels": [
+ "support standing",
+ "stabilize the figurine",
+ "contact surface"
+ ],
+ "description": "Small dark blue foot opposite the other one at the bottom, made of hard smooth material and formed as a stylized splayed support for standing stability.",
+ "scenarios": []
+ }
+ ]
+ },
+ "glasses": {
+ "parts": [
+ {
+ "part_name": "right lens",
+ "color": "#3CB44B",
+ "color_name": "Green",
+ "graspable": false,
+ "labels": [
+ "filter incoming light",
+ "protect the right eye",
+ "provide tinted viewing"
+ ],
+ "description": "Small rectangular slightly curved lens on the wearer's right side, made of translucent smooth glossy material with a purple-violet tint and a hard glass-like or plastic surface.",
+ "scenarios": []
+ },
+ {
+ "part_name": "left lens",
+ "color": "#E6194B",
+ "color_name": "Red",
+ "graspable": false,
+ "labels": [
+ "filter incoming light",
+ "protect the left eye",
+ "provide tinted viewing"
+ ],
+ "description": "Small rectangular slightly curved lens on the wearer's left side, made of translucent smooth glossy material with a cyan-blue tint and a hard glass-like or plastic surface.",
+ "scenarios": []
+ },
+ {
+ "part_name": "frame and temples",
+ "color": "#0082C8",
+ "color_name": "Blue",
+ "graspable": true,
+ "labels": [
+ "support the lenses",
+ "rest on the ears and nose",
+ "provide main grasp structure",
+ "maintain glasses shape",
+ "position the lenses on the face"
+ ],
+ "description": "Thin black full frame with slender temple arms, likely plastic, having a matte to low-sheen hard smooth finish and lightweight angular shape spanning the front and sides of the glasses.",
+ "scenarios": [
+ "grasp the frame front to pick up the glasses from a table",
+ "grasp one temple arm to lift and reposition the glasses"
+ ]
+ }
+ ]
+ },
+ "glue_stick": {
+ "parts": [
+ {
+ "part_name": "glue stick body",
+ "color": "#911EB4",
+ "color_name": "Purple",
+ "graspable": true,
+ "labels": [
+ "main housing",
+ "provide primary grip surface",
+ "enclose internal glue mechanism",
+ "support adhesive during use"
+ ],
+ "description": "Large bright orange plastic body with a mostly matte to lightly glossy finish, ribbed texture, and tapered cylindrical shape forming most of the glue stick exterior.",
+ "scenarios": [
+ "grasp the ribbed body to pick up and reposition the glue stick",
+ "hold the body to stabilize the glue stick while applying adhesive"
+ ]
+ },
+ {
+ "part_name": "glue stick adhesive",
+ "color": "#E6194B",
+ "color_name": "Red",
+ "graspable": false,
+ "labels": [
+ "apply adhesive",
+ "bond paper or light materials",
+ "provide glue dispensing surface",
+ "mark the usable end"
+ ],
+ "description": "Small white solid adhesive cylinder with a smooth slightly matte surface and blunt rounded end, protruding from the top opening of the glue stick body.",
+ "scenarios": []
+ },
+ {
+ "part_name": "small side notch / molded feature",
+ "color": "#0082C8",
+ "color_name": "Blue",
+ "graspable": false,
+ "labels": [
+ "minor molded body detail",
+ "possible manufacturing or assembly feature"
+ ],
+ "description": "Tiny blue-masked feature on the side of the orange body near the middle ring area, appearing as a small notch or recess rather than a separate top ring.",
+ "scenarios": []
+ }
+ ]
+ },
+ "ingredients": {
+ "parts": [
+ {
+ "part_name": "mushroom cap",
+ "color": "#3CB44B",
+ "color_name": "Green",
+ "graspable": true,
+ "labels": [
+ "main upper body",
+ "supports the underside gill structure",
+ "provides broad contact area"
+ ],
+ "description": "Large domed golden-brown to tan cap with a smooth matte surface and dense white speckling on top, broad rounded shape, and a pale yellow rim around the edge.",
+ "scenarios": [
+ "grasp the cap from the sides to pick up the mushroom gently",
+ "hold the cap to stabilize the mushroom during inspection or placement"
+ ]
+ },
+ {
+ "part_name": "mushroom stem",
+ "color": "#E6194B",
+ "color_name": "Red",
+ "graspable": true,
+ "labels": [
+ "support the cap",
+ "connect cap to base",
+ "provide main grasp point",
+ "elevate the cap"
+ ],
+ "description": "Short thick off-white stem with a matte smooth to slightly fibrous surface, firm texture, cylindrical slightly tapered shape, and central position beneath the cap with dark soil-like residue at the base.",
+ "scenarios": [
+ "grasp the stem to lift and move the mushroom",
+ "hold the stem to orient the mushroom for underside inspection"
+ ]
+ }
+ ]
+ },
+ "knife": {
+ "parts": [
+ {
+ "part_name": "knife blade",
+ "color": "#3CB44B",
+ "color_name": "Green",
+ "graspable": false,
+ "labels": [
+ "cut food or materials",
+ "slice with the sharpened edge",
+ "pierce with the tip",
+ "provide the main working edge"
+ ],
+ "description": "Silver metal blade with a reflective smooth finish, broad profile, slightly curved cutting edge, and pointed tip extending from the front of the knife.",
+ "scenarios": [
+ "avoid using the blade as the primary robotic grasp point because of slip and cut risk",
+ "briefly pinch the unsharpened spine near the base for careful inspection or reorientation"
+ ]
+ },
+ {
+ "part_name": "knife handle",
+ "color": "#FFE119",
+ "color_name": "Yellow",
+ "graspable": true,
+ "labels": [
+ "provide a safe grip",
+ "control blade orientation",
+ "keep hand away from the cutting edge",
+ "support lifting and manipulation"
+ ],
+ "description": "Black handle made of hard plastic or composite with a smooth slightly glossy finish, gently contoured elongated shape, and a rounded butt at the end.",
+ "scenarios": [
+ "hold the handle to orient the blade for cutting",
+ "grasp the handle to pick up and carry the knife safely"
+ ]
+ }
+ ]
+ },
+ "office_tools": {
+ "parts": [
+ {
+ "part_name": "top cap",
+ "color": "#E6194B",
+ "color_name": "Red",
+ "graspable": true,
+ "labels": [
+ "cover the top opening",
+ "provide an upper grip surface",
+ "house the pencil entry hole",
+ "help guide pencil insertion"
+ ],
+ "description": "Small yellow plastic top cap with a glossy smooth hard surface, rounded rectangular shape, and a visible circular entry hole on the upper face.",
+ "scenarios": [
+ "grasp the top cap to pick up or reposition the pencil sharpener",
+ "hold the top cap to stabilize the sharpener while inserting a pencil"
+ ]
+ },
+ {
+ "part_name": "sharpener body",
+ "color": "#0082C8",
+ "color_name": "Blue",
+ "graspable": true,
+ "labels": [
+ "main grasp surface",
+ "house the sharpening mechanism",
+ "support the top cap",
+ "enclose internal shavings space"
+ ],
+ "description": "Medium-sized light gray plastic body with a smooth semi-gloss hard surface, tapered block-like shape, and side walls forming the main central housing.",
+ "scenarios": [
+ "grasp the body from the sides to lift and move the pencil sharpener",
+ "hold the body to stabilize the sharpener during sharpening"
+ ]
+ },
+ {
+ "part_name": "base",
+ "color": "#3CB44B",
+ "color_name": "Green",
+ "graspable": true,
+ "labels": [
+ "support on a surface",
+ "stabilize the object",
+ "provide a broad contact footprint",
+ "anchor the housing"
+ ],
+ "description": "Dark gray plastic base with a matte smooth hard surface, broad rounded footprint, and slightly larger lower platform that supports the sharpener body.",
+ "scenarios": [
+ "hold the base to steady the sharpener on a table while sharpening",
+ "grasp the base to pick up the sharpener from below or the side"
+ ]
+ }
+ ]
+ },
+ "repair_tools": {
+ "parts": [
+ {
+ "part_name": "handle",
+ "color": "#E6194B",
+ "color_name": "Red",
+ "graspable": true,
+ "labels": [
+ "main grip surface",
+ "transmit hand or tool torque",
+ "support tool handling",
+ "stabilize during fastening"
+ ],
+ "description": "Large yellow plastic handle with a mostly matte hard smooth surface, slightly contoured blocky shape, and a small recessed notch near the rear, forming the main rear body of the screwdriver.",
+ "scenarios": [
+ "grasp the handle to pick up and reposition the screwdriver",
+ "hold the handle firmly while turning the screwdriver"
+ ]
+ },
+ {
+ "part_name": "collar",
+ "color": "#0082C8",
+ "color_name": "Blue",
+ "graspable": true,
+ "labels": [
+ "connect handle to shaft",
+ "support front alignment",
+ "provide a transition stop",
+ "position the shaft"
+ ],
+ "description": "Small yellow plastic collar at the front of the handle, with a matte hard smooth finish and short cylindrical shape that surrounds the shaft base.",
+ "scenarios": [
+ "lightly grasp the collar to orient the screwdriver tip before engagement",
+ "pinch the collar to stabilize the front of the tool during precise positioning"
+ ]
+ },
+ {
+ "part_name": "screwdriver bit",
+ "color": "#911EB4",
+ "color_name": "Purple",
+ "graspable": false,
+ "labels": [
+ "engage screw head",
+ "transmit torque to screw",
+ "fasten screws",
+ "remove screws"
+ ],
+ "description": "Short dark gray metal bit with a reflective hard finish, narrow stepped cylindrical form, and slightly textured machined profile projecting from the front of the screwdriver.",
+ "scenarios": []
+ }
+ ]
+ },
+ "salt_shaker": {
+ "parts": [
+ {
+ "part_name": "shaker body",
+ "color": "#E6194B",
+ "color_name": "Red",
+ "graspable": true,
+ "labels": [
+ "main grasp surface",
+ "contain salt",
+ "support shaking for dispensing",
+ "provide standing base"
+ ],
+ "description": "Clear to frosted glass body with a glossy hard smooth surface, vertically ribbed tapered shape, and larger lower section forming the main container and base.",
+ "scenarios": [
+ "grasp the body around the middle to pick up and move the salt shaker",
+ "hold the body while shaking to dispense salt"
+ ]
+ },
+ {
+ "part_name": "shaker cap",
+ "color": "#3CB44B",
+ "color_name": "Green",
+ "graspable": true,
+ "labels": [
+ "dispense salt through holes",
+ "cover the container opening",
+ "control flow direction",
+ "enable refilling when removed"
+ ],
+ "description": "Small black likely metal or plastic cap with a glossy reflective smooth finish, rounded cylindrical shape, and multiple top perforation holes at the narrow top of the shaker.",
+ "scenarios": [
+ "grasp the cap to orient the shaker and control the pour direction",
+ "pinch the cap to lift or reposition the shaker when body access is limited"
+ ]
+ }
+ ]
+ },
+ "ware": {
+ "parts": [
+ {
+ "part_name": "bottle body",
+ "color": "#FFE119",
+ "color_name": "Yellow",
+ "graspable": true,
+ "labels": [
+ "contain liquid",
+ "main grasp surface",
+ "support lifting and carrying",
+ "provide structural enclosure",
+ "stabilize during opening"
+ ],
+ "description": "Large translucent light cyan-blue plastic bottle body with a smooth slightly glossy surface, rounded cylindrical shape with a tapered shoulder, occupying most of the object below the neck.",
+ "scenarios": [
+ "grasp the bottle body at the midsection to pick up and carry the bottle",
+ "hold the bottle body to stabilize it while twisting the cap"
+ ]
+ },
+ {
+ "part_name": "cap",
+ "color": "#0082C8",
+ "color_name": "Blue",
+ "graspable": true,
+ "labels": [
+ "seal the bottle opening",
+ "enable opening and closing",
+ "provide twisting interface",
+ "prevent spills"
+ ],
+ "description": "Small dark blue plastic screw cap with a slightly glossy hard finish and ribbed grip texture, centered at the top of the bottle neck.",
+ "scenarios": [
+ "grasp the cap to twist it open or closed",
+ "pinch the cap to orient the bottle by the top"
+ ]
+ },
+ {
+ "part_name": "neck ring",
+ "color": "#3CB44B",
+ "color_name": "Green",
+ "graspable": false,
+ "labels": [
+ "connect cap to bottle neck",
+ "provide tamper-band or tether support",
+ "reinforce the opening area",
+ "indicate cap interface"
+ ],
+ "description": "Thin greenish-cyan plastic ring around the bottle neck directly beneath the cap, with a smooth hard surface and narrow circular shape.",
+ "scenarios": []
+ },
+ {
+ "part_name": "cap tether strap",
+ "color": "#E6194B",
+ "color_name": "Red",
+ "graspable": true,
+ "labels": [
+ "tether the cap to the bottle",
+ "prevent cap loss",
+ "assist cap manipulation",
+ "connect cap area to neck ring"
+ ],
+ "description": "Small side tether strap attached near the cap and neck area, protruding slightly from the shoulder/neck, with a smooth slightly glossy plastic surface.",
+ "scenarios": [
+ "pinch the tether strap to pull the cap assembly sideways during opening",
+ "hold the tether briefly to reorient the top area while manipulating the cap"
+ ]
+ }
+ ]
+ }
+}
\ No newline at end of file
diff --git a/docs/landing/assets/models/assets.json b/docs/landing/assets/models/assets.json
new file mode 100644
index 0000000..470b632
--- /dev/null
+++ b/docs/landing/assets/models/assets.json
@@ -0,0 +1,134 @@
+[
+ {
+ "name": "ear_hear",
+ "label": "Headphones",
+ "model": "ear_hear.glb",
+ "collision": "ear_hear_collision.glb",
+ "category": "headphones",
+ "description": "over-ear white headphones with cushioned ear cups and adjustable headband",
+ "height": "0.15",
+ "mass": "0.3000",
+ "friction": "0.50",
+ "aesthetic": ""
+ },
+ {
+ "name": "basket",
+ "label": "Basket",
+ "model": "basket.glb",
+ "collision": "basket_collision.glb",
+ "category": "basket",
+ "description": "colorful woven basket with a curved handle",
+ "height": "0.350",
+ "mass": "0.7500",
+ "friction": "0.40",
+ "aesthetic": "4.783598899841309"
+ },
+ {
+ "name": "decoration",
+ "label": "Decoration",
+ "model": "decoration.glb",
+ "collision": "decoration_collision.glb",
+ "category": "decoration",
+ "description": "blue bird figurine with detailed feathers and perched stance",
+ "height": "0.275",
+ "mass": "0.5500",
+ "friction": "0.50",
+ "aesthetic": "5.161412239074707"
+ },
+ {
+ "name": "glasses",
+ "label": "Glasses",
+ "model": "glasses.glb",
+ "collision": "glasses_collision.glb",
+ "category": "glasses",
+ "description": "black-framed glasses with reflective blue and purple lenses",
+ "height": "0.06",
+ "mass": "0.0350",
+ "friction": "0.80",
+ "aesthetic": "4.662111759185791"
+ },
+ {
+ "name": "glue_stick",
+ "label": "Glue Stick",
+ "model": "glue_stick.glb",
+ "collision": "glue_stick_collision.glb",
+ "category": "glue stick",
+ "description": "cylindrical orange glue stick with white adhesive tip",
+ "height": "0.045",
+ "mass": "0.0750",
+ "friction": "0.80",
+ "aesthetic": "4.560391426086426"
+ },
+ {
+ "name": "ingredients",
+ "label": "Mushroom",
+ "model": "ingredients.glb",
+ "collision": "ingredients_collision.glb",
+ "category": "mushroom",
+ "description": "realistic mushroom with orange cap and white stem",
+ "height": "0.080",
+ "mass": "0.3000",
+ "friction": "0.50",
+ "aesthetic": "5.24479866027832"
+ },
+ {
+ "name": "knife",
+ "label": "Knife",
+ "model": "knife.glb",
+ "collision": "knife_collision.glb",
+ "category": "knife",
+ "description": "sleek black-handled knife with long, thin blade",
+ "height": "0.03",
+ "mass": "0.3000",
+ "friction": "0.80",
+ "aesthetic": "4.95788311958313"
+ },
+ {
+ "name": "office_tools",
+ "label": "Pencil Sharpener",
+ "model": "office_tools.glb",
+ "collision": "office_tools_collision.glb",
+ "category": "pencil sharpener",
+ "description": "compact rectangular sharpener with yellow top and gray base",
+ "height": "0.1",
+ "mass": "0.3000",
+ "friction": "0.80",
+ "aesthetic": "4.191756725311279"
+ },
+ {
+ "name": "repair_tools",
+ "label": "Screwdriver",
+ "model": "repair_tools.glb",
+ "collision": "repair_tools_collision.glb",
+ "category": "screwdriver",
+ "description": "yellow-handled screwdriver with metallic tip and ergonomic grip design",
+ "height": "0.04",
+ "mass": "0.2000",
+ "friction": "0.60",
+ "aesthetic": "4.641516089439392"
+ },
+ {
+ "name": "salt_shaker",
+ "label": "Salt Shaker",
+ "model": "salt_shaker.glb",
+ "collision": "salt_shaker_collision.glb",
+ "category": "salt shaker",
+ "description": "ribbed glass body with black cap and metallic rim",
+ "height": "0.125",
+ "mass": "0.3000",
+ "friction": "0.80",
+ "aesthetic": "5.088344216346741"
+ },
+ {
+ "name": "ware",
+ "label": "Water Bottle",
+ "model": "ware.glb",
+ "collision": "ware_collision.glb",
+ "category": "water bottle",
+ "description": "blue plastic bottle with handle and ridged design",
+ "height": "0.275",
+ "mass": "0.4000",
+ "friction": "0.80",
+ "aesthetic": "4.694127082824707"
+ }
+]
\ No newline at end of file
diff --git a/docs/landing/assets/models/basket.glb b/docs/landing/assets/models/basket.glb
new file mode 100644
index 0000000..359bae1
Binary files /dev/null and b/docs/landing/assets/models/basket.glb differ
diff --git a/docs/landing/assets/models/basket_afford.glb b/docs/landing/assets/models/basket_afford.glb
new file mode 100644
index 0000000..3345a28
Binary files /dev/null and b/docs/landing/assets/models/basket_afford.glb differ
diff --git a/docs/landing/assets/models/basket_collision.glb b/docs/landing/assets/models/basket_collision.glb
new file mode 100644
index 0000000..5ad46f6
Binary files /dev/null and b/docs/landing/assets/models/basket_collision.glb differ
diff --git a/docs/landing/assets/models/decoration.glb b/docs/landing/assets/models/decoration.glb
new file mode 100644
index 0000000..8a1213e
Binary files /dev/null and b/docs/landing/assets/models/decoration.glb differ
diff --git a/docs/landing/assets/models/decoration_afford.glb b/docs/landing/assets/models/decoration_afford.glb
new file mode 100644
index 0000000..9f902b5
Binary files /dev/null and b/docs/landing/assets/models/decoration_afford.glb differ
diff --git a/docs/landing/assets/models/decoration_collision.glb b/docs/landing/assets/models/decoration_collision.glb
new file mode 100644
index 0000000..b5cc81f
Binary files /dev/null and b/docs/landing/assets/models/decoration_collision.glb differ
diff --git a/docs/landing/assets/models/ear_hear.glb b/docs/landing/assets/models/ear_hear.glb
new file mode 100644
index 0000000..9bdbd94
Binary files /dev/null and b/docs/landing/assets/models/ear_hear.glb differ
diff --git a/docs/landing/assets/models/ear_hear_afford.glb b/docs/landing/assets/models/ear_hear_afford.glb
new file mode 100644
index 0000000..e60646e
Binary files /dev/null and b/docs/landing/assets/models/ear_hear_afford.glb differ
diff --git a/docs/landing/assets/models/ear_hear_collision.glb b/docs/landing/assets/models/ear_hear_collision.glb
new file mode 100644
index 0000000..a114dd5
Binary files /dev/null and b/docs/landing/assets/models/ear_hear_collision.glb differ
diff --git a/docs/landing/assets/models/glasses.glb b/docs/landing/assets/models/glasses.glb
new file mode 100644
index 0000000..f4d53da
Binary files /dev/null and b/docs/landing/assets/models/glasses.glb differ
diff --git a/docs/landing/assets/models/glasses_afford.glb b/docs/landing/assets/models/glasses_afford.glb
new file mode 100644
index 0000000..f318d8b
Binary files /dev/null and b/docs/landing/assets/models/glasses_afford.glb differ
diff --git a/docs/landing/assets/models/glasses_collision.glb b/docs/landing/assets/models/glasses_collision.glb
new file mode 100644
index 0000000..980364c
Binary files /dev/null and b/docs/landing/assets/models/glasses_collision.glb differ
diff --git a/docs/landing/assets/models/glue_stick.glb b/docs/landing/assets/models/glue_stick.glb
new file mode 100644
index 0000000..25c9480
Binary files /dev/null and b/docs/landing/assets/models/glue_stick.glb differ
diff --git a/docs/landing/assets/models/glue_stick_afford.glb b/docs/landing/assets/models/glue_stick_afford.glb
new file mode 100644
index 0000000..ad74e16
Binary files /dev/null and b/docs/landing/assets/models/glue_stick_afford.glb differ
diff --git a/docs/landing/assets/models/glue_stick_collision.glb b/docs/landing/assets/models/glue_stick_collision.glb
new file mode 100644
index 0000000..8084f62
Binary files /dev/null and b/docs/landing/assets/models/glue_stick_collision.glb differ
diff --git a/docs/landing/assets/models/ingredients.glb b/docs/landing/assets/models/ingredients.glb
new file mode 100644
index 0000000..465115f
Binary files /dev/null and b/docs/landing/assets/models/ingredients.glb differ
diff --git a/docs/landing/assets/models/ingredients_afford.glb b/docs/landing/assets/models/ingredients_afford.glb
new file mode 100644
index 0000000..4797e4d
Binary files /dev/null and b/docs/landing/assets/models/ingredients_afford.glb differ
diff --git a/docs/landing/assets/models/ingredients_collision.glb b/docs/landing/assets/models/ingredients_collision.glb
new file mode 100644
index 0000000..cf1be87
Binary files /dev/null and b/docs/landing/assets/models/ingredients_collision.glb differ
diff --git a/docs/landing/assets/models/knife.glb b/docs/landing/assets/models/knife.glb
new file mode 100644
index 0000000..99341f8
Binary files /dev/null and b/docs/landing/assets/models/knife.glb differ
diff --git a/docs/landing/assets/models/knife_afford.glb b/docs/landing/assets/models/knife_afford.glb
new file mode 100644
index 0000000..e9d864e
Binary files /dev/null and b/docs/landing/assets/models/knife_afford.glb differ
diff --git a/docs/landing/assets/models/knife_collision.glb b/docs/landing/assets/models/knife_collision.glb
new file mode 100644
index 0000000..68e4f9e
Binary files /dev/null and b/docs/landing/assets/models/knife_collision.glb differ
diff --git a/docs/landing/assets/models/office_tools.glb b/docs/landing/assets/models/office_tools.glb
new file mode 100644
index 0000000..84f5bc5
Binary files /dev/null and b/docs/landing/assets/models/office_tools.glb differ
diff --git a/docs/landing/assets/models/office_tools_afford.glb b/docs/landing/assets/models/office_tools_afford.glb
new file mode 100644
index 0000000..a81fbf1
Binary files /dev/null and b/docs/landing/assets/models/office_tools_afford.glb differ
diff --git a/docs/landing/assets/models/office_tools_collision.glb b/docs/landing/assets/models/office_tools_collision.glb
new file mode 100644
index 0000000..08efaf5
Binary files /dev/null and b/docs/landing/assets/models/office_tools_collision.glb differ
diff --git a/docs/landing/assets/models/repair_tools.glb b/docs/landing/assets/models/repair_tools.glb
new file mode 100644
index 0000000..5bff0ff
Binary files /dev/null and b/docs/landing/assets/models/repair_tools.glb differ
diff --git a/docs/landing/assets/models/repair_tools_afford.glb b/docs/landing/assets/models/repair_tools_afford.glb
new file mode 100644
index 0000000..89a66cf
Binary files /dev/null and b/docs/landing/assets/models/repair_tools_afford.glb differ
diff --git a/docs/landing/assets/models/repair_tools_collision.glb b/docs/landing/assets/models/repair_tools_collision.glb
new file mode 100644
index 0000000..66d9aad
Binary files /dev/null and b/docs/landing/assets/models/repair_tools_collision.glb differ
diff --git a/docs/landing/assets/models/salt_shaker.glb b/docs/landing/assets/models/salt_shaker.glb
new file mode 100644
index 0000000..f99e4eb
Binary files /dev/null and b/docs/landing/assets/models/salt_shaker.glb differ
diff --git a/docs/landing/assets/models/salt_shaker_afford.glb b/docs/landing/assets/models/salt_shaker_afford.glb
new file mode 100644
index 0000000..61071f4
Binary files /dev/null and b/docs/landing/assets/models/salt_shaker_afford.glb differ
diff --git a/docs/landing/assets/models/salt_shaker_collision.glb b/docs/landing/assets/models/salt_shaker_collision.glb
new file mode 100644
index 0000000..e378e7a
Binary files /dev/null and b/docs/landing/assets/models/salt_shaker_collision.glb differ
diff --git a/docs/landing/assets/models/ware.glb b/docs/landing/assets/models/ware.glb
new file mode 100644
index 0000000..77e935c
Binary files /dev/null and b/docs/landing/assets/models/ware.glb differ
diff --git a/docs/landing/assets/models/ware_afford.glb b/docs/landing/assets/models/ware_afford.glb
new file mode 100644
index 0000000..05e3711
Binary files /dev/null and b/docs/landing/assets/models/ware_afford.glb differ
diff --git a/docs/landing/assets/models/ware_collision.glb b/docs/landing/assets/models/ware_collision.glb
new file mode 100644
index 0000000..7b055be
Binary files /dev/null and b/docs/landing/assets/models/ware_collision.glb differ
diff --git a/docs/landing/assets/video/closed_loop.mp4 b/docs/landing/assets/video/closed_loop.mp4
new file mode 100644
index 0000000..21f1b7e
Binary files /dev/null and b/docs/landing/assets/video/closed_loop.mp4 differ
diff --git a/docs/landing/assets/video/hero_bg.mp4 b/docs/landing/assets/video/hero_bg.mp4
new file mode 100644
index 0000000..1eab6fd
Binary files /dev/null and b/docs/landing/assets/video/hero_bg.mp4 differ
diff --git a/docs/landing/assets/video/multi_sim.mp4 b/docs/landing/assets/video/multi_sim.mp4
new file mode 100644
index 0000000..615141b
Binary files /dev/null and b/docs/landing/assets/video/multi_sim.mp4 differ
diff --git a/docs/landing/assets/video/scene_kitchen_S0.mp4 b/docs/landing/assets/video/scene_kitchen_S0.mp4
new file mode 100644
index 0000000..647513b
Binary files /dev/null and b/docs/landing/assets/video/scene_kitchen_S0.mp4 differ
diff --git a/docs/landing/assets/video/scene_kitchen_S1.mp4 b/docs/landing/assets/video/scene_kitchen_S1.mp4
new file mode 100644
index 0000000..566d970
Binary files /dev/null and b/docs/landing/assets/video/scene_kitchen_S1.mp4 differ
diff --git a/docs/landing/assets/video/scene_kitchen_S2.mp4 b/docs/landing/assets/video/scene_kitchen_S2.mp4
new file mode 100644
index 0000000..66adfcb
Binary files /dev/null and b/docs/landing/assets/video/scene_kitchen_S2.mp4 differ
diff --git a/docs/landing/assets/video/scene_living_room_S0.mp4 b/docs/landing/assets/video/scene_living_room_S0.mp4
new file mode 100644
index 0000000..1ed5f64
Binary files /dev/null and b/docs/landing/assets/video/scene_living_room_S0.mp4 differ
diff --git a/docs/landing/assets/video/scene_living_room_S1.mp4 b/docs/landing/assets/video/scene_living_room_S1.mp4
new file mode 100644
index 0000000..2875f43
Binary files /dev/null and b/docs/landing/assets/video/scene_living_room_S1.mp4 differ
diff --git a/docs/landing/assets/video/scene_living_room_S2.mp4 b/docs/landing/assets/video/scene_living_room_S2.mp4
new file mode 100644
index 0000000..7c3d697
Binary files /dev/null and b/docs/landing/assets/video/scene_living_room_S2.mp4 differ
diff --git a/docs/landing/assets/video/scenes_gen.mp4 b/docs/landing/assets/video/scenes_gen.mp4
new file mode 100644
index 0000000..331d7e0
Binary files /dev/null and b/docs/landing/assets/video/scenes_gen.mp4 differ
diff --git a/docs/landing/assets/video/soft_sim_6x2_16x9_captioned.mp4 b/docs/landing/assets/video/soft_sim_6x2_16x9_captioned.mp4
new file mode 100644
index 0000000..5b0dcd7
Binary files /dev/null and b/docs/landing/assets/video/soft_sim_6x2_16x9_captioned.mp4 differ
diff --git a/docs/landing/assets/video/worlds.mp4 b/docs/landing/assets/video/worlds.mp4
new file mode 100644
index 0000000..46e5482
Binary files /dev/null and b/docs/landing/assets/video/worlds.mp4 differ
diff --git a/docs/landing/favicon-180.png b/docs/landing/favicon-180.png
new file mode 100644
index 0000000..9e7fd12
Binary files /dev/null and b/docs/landing/favicon-180.png differ
diff --git a/docs/landing/favicon-32.png b/docs/landing/favicon-32.png
new file mode 100644
index 0000000..92209bb
Binary files /dev/null and b/docs/landing/favicon-32.png differ
diff --git a/docs/landing/favicon.svg b/docs/landing/favicon.svg
new file mode 100644
index 0000000..926f25f
--- /dev/null
+++ b/docs/landing/favicon.svg
@@ -0,0 +1,7 @@
+
+
+
+
+
+
+
diff --git a/docs/landing/index.html b/docs/landing/index.html
new file mode 100644
index 0000000..622f8ed
--- /dev/null
+++ b/docs/landing/index.html
@@ -0,0 +1,468 @@
+
+
+
+
+
+EmbodiedGen V2 โ An Agentic, Simulation-Ready 3D World Engine for Embodied AI
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
EmbodiedGenV2
+
An Agentic, Simulation-Ready 3D World Engine for Embodied AI
+
+
+ From intent to executable 3D worlds
+
+
+
+
+ Xinjie Wang1 ยท Liu Liu1 ยท Taojun Ding1 ยท Andrew Choi1 ยท
+ Chaodong Huang1 ยท Mengao Zhao1 ยท Ziang Li1 ยท
+ Jackson Jiang2 ยท Chunlei Yu2 ยท Shengxiang Liu2 ยท
+ Wei Xu1 ยท Zhizhong Su1
+
+
1 Horizon Robotics ยท 2 WuwenAI
+
+
+
+
+
+
+
+
+
+
+
+ Overview
+ Generative simulation infrastructure for embodied AI
+
+ From text, images, and natural-language dialogue to simulation-ready 3D worlds โ
+ assets, interactive scenes, and large-scale environments, deployable across every major simulator.
+
+
+
+
+
+ View the system overview figure
+
+
+
+
+
+
+
+
+
+ Generate
+ Sim-Ready 3D Assets
+
+ Real generated assets โ drag to rotate. Each carries metric geometry ,
+ a collision proxy , physical properties , and
+ cross-simulator interfaces .
+
+
+
+
+
+
+
+
+
+
+ Visual mesh
+ Collision proxy
+ Affordance
+
+
drag to orbit ยท scroll to zoom
+
+
+
+
+
+
+
+ affordance
+
+
+
+
Gripper markers show grasp poses, colored by part.
+
+
+
+
+
+
Exports to+ 3DGS splat
+
+ URDF SAPIEN3 ยท PyBullet
+ USD Isaac Sim
+ MJCF Isaac Gym ยท Genesis ยท MuJoCo
+
+
+
+
+
+
+
+
+
+
Pipeline text or a single image โ a sim-ready asset
+
+ 01 Input Preparation Text โ image, or segment the foreground โ even under occlusion.
+ 02 3D Generation & QC Pluggable image-to-3D, gated by semantic, geometric & aesthetic checks.
โป auto-retry
+ 03 Geometry & Texture Mesh repair, simplification, convex decomposition, baked texture.
+ 04 Physical Recovery A VLM infers real-world scale, mass and friction for metric rescaling.
+ 05 Cross-Format Export One URDF intermediate โ URDF ยท USD ยท MJCF for every major engine.
+
+
+
+
+
+
+ View the full pipeline figure
+
+
+
+ Beyond rigid bodies โ soft-body simulation
+
+ The same generate-and-export path reaches soft bodies: 12 text-conditioned garments deploy
+ as deformable meshes in Genesis, with no manual preparation.
+
+
+
+
+ 12 generated garments ยท per-vertex displacement ยท Genesis
+
+
+
+
+
+
+
+
+
+ Scale
+ Large-Scale Scenes
+
+ Beyond tabletops โ multi-room, navigable, instance-editable houses, generated as
+ sim-ready backgrounds at a controllable complexity tier.
+
+
+
+
+
+
+
+
+
+
+
+
+ View the large-scale generation pipeline
+
+
+
+
+
+
+
+
+
+ Compose
+ Task-Driven Worlds
+
+ From a natural-language task, EmbodiedGen parses a Scene Graph and composes a
+ physically stable, directly loadable layout โ then settles it under gravity in simulation.
+
+
+
+
+
โบ Put the broccoli on the white dish
+
โ
+
+ Background
+ Context
+ Manipulated
+ Distractor
+ Robot
+
+
+
+
+
+
+
+
+
+
+ View the scene-generation pipeline
+
+
+
+
+
+
+
+
+
+ Edit
+ Vibe Coding for Sim-Ready 3D Worlds
+
+ Build and edit worlds through natural-language dialogue. Each instruction is a
+ bounded, physics-validated skill call that preserves a deployable,
+ sim-ready world state โ add, remove, replace, refine.
+
+
+
+
+
+
+ Kitchen
+ Living room
+
+
+
+
+
+ S0
+
+
+
+
+
+
+
+
+
+ Export
+ One World, Every Simulator
+
+ One standardized layout โ no manual adaptation . The same generated scene loads
+ with consistent geometry, collision, textures, and physical metadata across six mainstream
+ physics simulators.
+
+
+
+
+
+
+
+
+ Genesis Isaac Gym Isaac Sim
+ MuJoCo PyBullet SAPIEN3
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
9.7 โ 79.8 %
+
Simulation task success
+
+
+
21.7 โ 75.0 %
+
Real-robot task success
+
+
+ Results from a companion sim-to-real RL study; policies trained in EmbodiedGen-generated worlds.
+
+
+
+
+ View the policy-learning & deployment figure
+
+
+
+
+
+
+
+
+
+
+
Copy
+
@article{wang2026embodiedgenv2,
+ title = {EmbodiedGen V2: An Agentic, Simulation-Ready 3D World
+ Engine for Embodied AI},
+ author = {Xinjie Wang and Liu Liu and Taojun Ding and Andrew Choi and
+ Chaodong Huang and Mengao Zhao and Ziang Li and Jackson Jiang and
+ Chunlei Yu and Shengxiang Liu and Wei Xu and Zhizhong Su},
+ journal = {arXiv preprint arXiv:2506.10600},
+ year = {2026}
+}
+
+
+
Copy
+
@misc{wang2025embodiedgengenerative3dworld,
+ title = {EmbodiedGen: Towards a Generative 3D World Engine for
+ Embodied Intelligence},
+ author = {Xinjie Wang and Liu Liu and Yu Cao and Ruiqi Wu and
+ Wenkang Qin and Dehui Wang and Wei Sui and Zhizhong Su},
+ year = {2025},
+ eprint = {2506.10600},
+ archivePrefix = {arXiv},
+ primaryClass = {cs.RO},
+ url = {https://arxiv.org/abs/2506.10600}
+}
+
+
+
+
+
+
+
+
+
+
diff --git a/docs/landing/script.js b/docs/landing/script.js
new file mode 100644
index 0000000..1dffca8
--- /dev/null
+++ b/docs/landing/script.js
@@ -0,0 +1,321 @@
+/* EmbodiedGen V2 โ interactions. Vanilla, no deps (model-viewer loaded separately). */
+(function () {
+ "use strict";
+ const reduce = window.matchMedia("(prefers-reduced-motion: reduce)").matches;
+
+ /* 1. Nav: transparent over hero -> solid after scroll */
+ const nav = document.getElementById("nav");
+ const onScroll = () => nav.classList.toggle("is-solid", window.scrollY > 40);
+ onScroll();
+ window.addEventListener("scroll", onScroll, { passive: true });
+
+ /* 2. Reveal on scroll */
+ const reveals = document.querySelectorAll(".reveal");
+ if (reduce || !("IntersectionObserver" in window)) {
+ reveals.forEach((el) => el.classList.add("is-in"));
+ } else {
+ const io = new IntersectionObserver(
+ (es, obs) => es.forEach((e) => {
+ if (e.isIntersecting) { e.target.classList.add("is-in"); obs.unobserve(e.target); }
+ }),
+ { rootMargin: "0px 0px -10% 0px", threshold: 0.12 }
+ );
+ reveals.forEach((el) => io.observe(el));
+ }
+
+ /* 3. Nav scroll-spy */
+ const links = Array.from(document.querySelectorAll(".nav__link[data-sect]"));
+ const map = new Map();
+ links.forEach((l) => {
+ const id = l.dataset.sect;
+ const t = document.getElementById(id === "top" ? "top" : id);
+ if (t) map.set(t, l);
+ });
+ if ("IntersectionObserver" in window && map.size) {
+ const sio = new IntersectionObserver(
+ (es) => es.forEach((e) => {
+ if (e.isIntersecting) {
+ links.forEach((l) => l.classList.remove("is-active"));
+ const a = map.get(e.target); if (a) a.classList.add("is-active");
+ }
+ }),
+ { rootMargin: "-45% 0px -50% 0px" }
+ );
+ map.forEach((_, t) => sio.observe(t));
+ }
+
+ /* 4. Asset 3D viewer */
+ const mv = document.getElementById("mv");
+ const gallery = document.getElementById("gallery");
+ if (mv && gallery) {
+ const BASE = "assets/models/";
+ let assets = [];
+ let afford = {};
+ let cur = 0;
+ let mode = "visual";
+ const affCard = document.getElementById("affCard");
+ const metaCard = document.getElementById("metaCard");
+ const fmtCard = document.getElementById("fmtCard");
+ const affParts = document.getElementById("affParts");
+ const affDetail = document.getElementById("affDetail");
+
+ const el = {
+ name: document.getElementById("m-name"),
+ cat: document.getElementById("m-cat"),
+ desc: document.getElementById("m-desc"),
+ h: document.getElementById("m-h"),
+ m: document.getElementById("m-m"),
+ f: document.getElementById("m-f"),
+ a: document.getElementById("m-a"),
+ };
+
+ function load() {
+ const a = assets[cur];
+ const file = mode === "collision" ? a.collision
+ : mode === "affordance" ? a.name + "_afford.glb"
+ : a.model;
+ mv.src = BASE + file;
+ mv.setAttribute("alt", a.label + " โ " + mode);
+ const isAff = mode === "affordance";
+ if (affCard) affCard.hidden = !isAff;
+ if (metaCard) metaCard.hidden = isAff;
+ if (fmtCard) fmtCard.hidden = isAff;
+ if (isAff) renderAfford();
+ }
+
+ function renderAfford() {
+ const a = assets[cur];
+ const data = afford[a.name];
+ if (!data || !affParts) return;
+ affParts.innerHTML = data.parts.map((p, i) =>
+ `
+
+ ${p.part_name}
+ ${p.graspable ? "graspable" : "โ"}
+ `).join("");
+ affParts.querySelectorAll(".part").forEach((b) =>
+ b.addEventListener("click", () => {
+ affParts.querySelectorAll(".part").forEach((x) => x.classList.toggle("is-active", x === b));
+ showPart(+b.dataset.i);
+ }));
+ showPart(0);
+ }
+ function showPart(i) {
+ const p = afford[assets[cur].name].parts[i];
+ const labels = (p.labels || []).map((l) => `${l} `).join("");
+ affDetail.innerHTML =
+ `${labels}
+ ${p.description || ""}
`;
+ }
+
+ function fillMeta() {
+ const a = assets[cur];
+ el.name.textContent = a.label;
+ el.cat.textContent = a.category;
+ el.desc.textContent = a.description || "โ";
+ el.h.textContent = a.height ? (+a.height).toFixed(2) + " m" : "โ";
+ el.m.textContent = a.mass ? (+a.mass).toFixed(2) + " kg" : "โ";
+ el.f.textContent = a.friction ? (+a.friction).toFixed(2) : "โ";
+ el.a.textContent = a.aesthetic ? (+a.aesthetic).toFixed(1) + " / 10" : "โ";
+ }
+
+ function select(i) {
+ cur = i;
+ document.querySelectorAll(".thumb").forEach((t, k) =>
+ t.classList.toggle("is-active", k === i));
+ load(); fillMeta();
+ }
+
+ let locked = false;
+ let autoTimer = null;
+ function setMode(m) {
+ mode = m;
+ document.querySelectorAll(".mode").forEach((x) =>
+ x.classList.toggle("is-active", x.dataset.mode === m));
+ load();
+ }
+ const AUTO_ORDER = ["visual", "collision", "affordance"];
+ function startAuto() {
+ if (reduce || locked || autoTimer) return;
+ autoTimer = setInterval(() => {
+ const next = AUTO_ORDER[(AUTO_ORDER.indexOf(mode) + 1) % AUTO_ORDER.length];
+ setMode(next);
+ }, 4000);
+ }
+ function lockModes() {
+ locked = true;
+ if (autoTimer) { clearInterval(autoTimer); autoTimer = null; }
+ }
+ document.querySelectorAll(".mode").forEach((b) =>
+ b.addEventListener("click", () => { lockModes(); setMode(b.dataset.mode); }));
+
+ // Heavy init (model-viewer lib ~1MB + first GLB) is deferred until the
+ // Assets section approaches the viewport, to keep initial page load light.
+ let inited = false;
+ function initViewer() {
+ if (inited) return;
+ inited = true;
+ if (!window.__mvLoaded) {
+ window.__mvLoaded = true;
+ const s = document.createElement("script");
+ s.type = "module";
+ s.src = "https://unpkg.com/@google/model-viewer@3.5.0/dist/model-viewer.min.js";
+ document.head.appendChild(s);
+ }
+ fetch(BASE + "affordance.json").then((r) => r.json()).then((j) => { afford = j; }).catch(() => {});
+ fetch(BASE + "assets.json")
+ .then((r) => r.json())
+ .then((data) => {
+ assets = data;
+ gallery.innerHTML = data.map((a, i) =>
+ `
+ ${a.label} `).join("");
+ gallery.querySelectorAll(".thumb").forEach((t) =>
+ t.addEventListener("click", () => select(+t.dataset.i)));
+ select(0);
+ startAuto();
+ })
+ .catch((e) => { gallery.innerHTML = "assets.json failed to load (serve over http). "; console.error(e); });
+
+ if (reduce) mv.removeAttribute("auto-rotate");
+ }
+
+ // Trigger viewer init on the FIRST of: (a) user scrolls near Assets, or
+ // (b) the page finishes loading + goes idle โ so the first asset is warmed
+ // in the background right after the hero (not competing with first paint),
+ // and is ready before the user scrolls. initViewer() is idempotent.
+ const assetsSect = document.getElementById("assets");
+ if ("IntersectionObserver" in window && assetsSect) {
+ const aio = new IntersectionObserver((es, obs) => {
+ if (es.some((e) => e.isIntersecting)) { obs.disconnect(); initViewer(); }
+ }, { rootMargin: "400px 0px" });
+ aio.observe(assetsSect);
+ }
+ const warm = () => (window.requestIdleCallback || ((cb) => setTimeout(cb, 1)))(initViewer);
+ if (document.readyState === "complete") warm();
+ else window.addEventListener("load", warm, { once: true });
+ setTimeout(warm, 3000); // fallback if the load event is delayed by media
+ }
+
+ /* ---- 5. Vibe Coding edit sessions ---- */
+ const vibeHist = document.getElementById("vibeHist");
+ const vibeVid = document.getElementById("vibeVid");
+ const vibeBadge = document.getElementById("vibeBadge");
+ if (vibeHist && vibeVid) {
+ const sessions = {
+ kitchen: {
+ prefix: "scene_kitchen_S",
+ turns: [
+ { cmd: "Generate a simple kitchen", d: "init" },
+ { cmd: "Add a dining table with four chairs", d: "add", t: "+ table ยท 4 chairs" },
+ { cmd: "Put some fruit on the table", d: "add", t: "+ fruit" },
+ ],
+ },
+ living: {
+ prefix: "scene_living_room_S",
+ turns: [
+ { cmd: "Generate a simple living room", d: "init" },
+ { cmd: "Set up a TV, vase, table and chair", d: "add", t: "+ tv ยท vase ยท table ยท chair" },
+ { cmd: "Set the table for chess", d: "add", t: "+ chessboard" },
+ ],
+ },
+ };
+ let sess = "kitchen";
+
+ function play(stage) {
+ const s = sessions[sess];
+ vibeVid.src = "assets/video/" + s.prefix + stage + ".mp4";
+ vibeVid.load();
+ if (!reduce) { const p = vibeVid.play(); if (p && p.catch) p.catch(() => {}); }
+ vibeBadge.textContent = "S" + stage;
+ vibeHist.querySelectorAll(".turn").forEach((t, i) =>
+ t.classList.toggle("is-active", i === stage));
+ }
+ function render() {
+ const s = sessions[sess];
+ vibeHist.innerHTML = s.turns.map((tn, i) => {
+ const dcls = tn.d === "add" ? "add" : tn.d === "del" ? "del" : "init";
+ const dtxt = tn.d === "init" ? "init" : tn.t;
+ return `
+ ${tn.cmd}
+ ${dtxt} `;
+ }).join("");
+ vibeHist.querySelectorAll(".turn").forEach((b) =>
+ b.addEventListener("click", () => play(+b.dataset.s)));
+ play(0);
+ }
+ document.querySelectorAll(".vtab").forEach((b) =>
+ b.addEventListener("click", () => {
+ sess = b.dataset.sess;
+ document.querySelectorAll(".vtab").forEach((x) => x.classList.toggle("is-active", x === b));
+ render();
+ }));
+ render();
+ }
+
+ /* ---- 5a. Lazy-load + pause offscreen videos (network + CPU perf) ----
+ Section videos use preload="none" + data-autoplay: they only fetch and
+ play when scrolled into view, and pause when scrolled away. The hero keeps
+ native autoplay for instant first paint but is paused offscreen too. */
+ const dav = Array.from(document.querySelectorAll("video[data-autoplay]"));
+ if (dav.length) {
+ const playV = (v) => { if (reduce) return; const p = v.play(); if (p && p.catch) p.catch(() => {}); };
+ if ("IntersectionObserver" in window) {
+ const vio = new IntersectionObserver((es) => es.forEach((e) => {
+ if (e.isIntersecting) playV(e.target); else e.target.pause();
+ }), { rootMargin: "300px 0px", threshold: 0.1 });
+ dav.forEach((v) => vio.observe(v));
+ } else {
+ dav.forEach(playV);
+ }
+ }
+
+ /* ---- 5b. Result count-up (animate the after-value from its baseline) ---- */
+ const vals = Array.from(document.querySelectorAll(".result__val"));
+ if (vals.length) {
+ const run = (el) => {
+ const from = parseFloat(el.dataset.from);
+ const to = parseFloat(el.dataset.to);
+ if (reduce || !isFinite(from) || !isFinite(to)) { el.textContent = to.toFixed(1); return; }
+ const dur = 1100, t0 = performance.now();
+ const tick = (now) => {
+ const p = Math.min(1, (now - t0) / dur);
+ const e = 1 - Math.pow(1 - p, 3); // easeOutCubic
+ el.textContent = (from + (to - from) * e).toFixed(1);
+ if (p < 1) requestAnimationFrame(tick);
+ else el.textContent = to.toFixed(1);
+ };
+ requestAnimationFrame(tick);
+ };
+ if (reduce || !("IntersectionObserver" in window)) {
+ vals.forEach((el) => (el.textContent = parseFloat(el.dataset.to).toFixed(1)));
+ } else {
+ const cio = new IntersectionObserver((es, obs) => es.forEach((e) => {
+ if (e.isIntersecting) { run(e.target); obs.unobserve(e.target); }
+ }), { threshold: 0.6 });
+ vals.forEach((el) => cio.observe(el));
+ }
+ }
+
+ /* ---- 6. BibTeX copy (one button per cite block) ---- */
+ document.querySelectorAll(".cite__copy").forEach((btn) => {
+ const code = btn.parentElement.querySelector(".cite__code");
+ if (!code) return;
+ btn.addEventListener("click", () => {
+ const txt = code.textContent;
+ const done = () => {
+ btn.textContent = "Copied";
+ btn.classList.add("is-done");
+ setTimeout(() => { btn.textContent = "Copy"; btn.classList.remove("is-done"); }, 1800);
+ };
+ if (navigator.clipboard && navigator.clipboard.writeText) {
+ navigator.clipboard.writeText(txt).then(done).catch(() => {});
+ } else {
+ const r = document.createRange(); r.selectNode(code);
+ const s = window.getSelection(); s.removeAllRanges(); s.addRange(r);
+ try { document.execCommand("copy"); done(); } catch (e) {}
+ s.removeAllRanges();
+ }
+ });
+ });
+})();
diff --git a/docs/landing/style.css b/docs/landing/style.css
new file mode 100644
index 0000000..0a15953
--- /dev/null
+++ b/docs/landing/style.css
@@ -0,0 +1,443 @@
+/* =========================================================
+ EmbodiedGen V2 โ webpage. Refined minimal + blueprint precision.
+ Single source of truth for color / type / spacing.
+ ========================================================= */
+:root{
+ --paper:#FAFAF7; --paper-2:#F2F1EC;
+ --ink:#121417; --ink-soft:#5A6068;
+ --line:rgba(18,20,23,.10); --line-2:rgba(18,20,23,.06);
+ --blue:#1E54FF; --blue-soft:rgba(30,84,255,.08); --blue-ink:#0B2E9E;
+ --green:#119C5B; --red:#E0463E;
+ --sans:"Hanken Grotesk",-apple-system,BlinkMacSystemFont,sans-serif;
+ --mono:"JetBrains Mono",ui-monospace,SFMono-Regular,monospace;
+ --serif:"Newsreader",Georgia,serif;
+ --wrap:1120px; --pad:24px; --r:14px;
+ --shadow:0 1px 2px rgba(18,20,23,.04),0 8px 30px rgba(18,20,23,.05);
+ --shadow-lift:0 2px 6px rgba(18,20,23,.06),0 18px 50px rgba(18,20,23,.10);
+ /* motion */
+ --ease:cubic-bezier(.22,.61,.36,1);
+ --ease-out:cubic-bezier(.16,1,.3,1);
+ --ease-spring:cubic-bezier(.34,1.32,.46,1);
+ /* blueprint texture */
+ --grid-line:rgba(18,20,23,.045);
+ --grid-size:30px;
+}
+*{box-sizing:border-box;}
+html{scroll-behavior:smooth; scroll-padding-top:86px;}
+body{margin:0; background:var(--paper); color:var(--ink); font-family:var(--sans);
+ font-size:17px; line-height:1.55; -webkit-font-smoothing:antialiased; text-rendering:optimizeLegibility;}
+img,video{max-width:100%; display:block;}
+a{color:inherit; text-decoration:none;}
+h1,h2,h3{margin:0; letter-spacing:-.02em; line-height:1.06; font-weight:800;}
+strong{font-weight:700;} em{font-style:normal;}
+.wrap{width:100%; max-width:var(--wrap); margin:0 auto; padding:0 var(--pad);}
+
+.verb{display:inline-block; font-family:var(--mono); font-size:12px; font-weight:600;
+ letter-spacing:.14em; text-transform:uppercase; color:var(--ink-soft);}
+/* section eyebrows get a blueprint tick โ a measured lead-in rule */
+.verb--accent{color:var(--blue); display:inline-flex; align-items:center; gap:.7em;}
+.verb--accent::before{content:""; width:20px; height:1px; background:currentColor; opacity:.55;
+ flex:none;}
+
+/* buttons */
+.btn{display:inline-flex; align-items:center; gap:.5em; font-family:var(--sans);
+ font-weight:600; font-size:15px; padding:.62em 1.1em; border-radius:999px;
+ border:1px solid var(--line); background:#fff; color:var(--ink); cursor:pointer;
+ transition:transform .15s,border-color .15s,background .15s,box-shadow .15s; white-space:nowrap;}
+.btn:hover{transform:translateY(-1px); box-shadow:var(--shadow);}
+.btn__ic{width:16px; height:16px; display:block; flex:none;}
+/* glass buttons over video */
+.btn--glass{background:rgba(255,255,255,.12); border-color:rgba(255,255,255,.30);
+ color:#fff; backdrop-filter:blur(8px);}
+.btn--glass:hover{background:rgba(255,255,255,.22);}
+
+/* =========================================================
+ NAV โ transparent over hero, solid after scroll
+ ========================================================= */
+.nav{position:fixed; top:0; left:0; right:0; z-index:50; transition:background .3s,border-color .3s,box-shadow .3s;
+ background:transparent; border-bottom:1px solid transparent;}
+.nav__inner{max-width:var(--wrap); margin:0 auto; padding:0 var(--pad); height:74px;
+ display:flex; align-items:center; gap:22px;}
+.nav__brand{display:flex; align-items:center; font-weight:800; color:#fff; font-size:22px; letter-spacing:-.01em;}
+.nav__links{display:flex; align-items:center; gap:2px; margin-left:auto;}
+.nav__link{font-size:16px; font-weight:500; color:rgba(255,255,255,.78); padding:8px 13px;
+ border-radius:8px; transition:color .15s,background .15s;}
+a.nav__link:hover{color:#fff; background:rgba(255,255,255,.14);}
+.nav__link.is-active{color:#fff; background:rgba(255,255,255,.18);}
+
+/* scrolled state */
+.nav.is-solid{background:rgba(250,250,247,.85); backdrop-filter:blur(12px);
+ border-bottom-color:var(--line); box-shadow:0 1px 0 rgba(0,0,0,.02);}
+.nav.is-solid .nav__brand{color:var(--ink);}
+.nav.is-solid .nav__link{color:var(--ink-soft);}
+.nav.is-solid a.nav__link:hover{color:var(--ink); background:var(--blue-soft);}
+.nav.is-solid .nav__link.is-active{color:var(--blue); background:var(--blue-soft);}
+
+/* =========================================================
+ HERO โ full viewport video background
+ ========================================================= */
+.hero{position:relative; height:100svh; min-height:600px; display:grid; place-items:center; overflow:hidden;}
+.hero__bg{position:absolute; inset:0; z-index:0;}
+.hero__video{width:100%; height:100%; object-fit:cover; background:#0a0b0d;}
+.hero__scrim{position:absolute; inset:0;
+ background:
+ radial-gradient(120% 90% at 50% 45%, rgba(8,9,12,.30) 0%, rgba(8,9,12,.62) 100%),
+ linear-gradient(180deg, rgba(8,9,12,.62) 0%, rgba(8,9,12,.42) 30%, rgba(8,9,12,.46) 62%, rgba(8,9,12,.82) 100%);}
+/* logo floats left of the centered title; offset is clamped so it never flies off-screen */
+.hero__logo{--lx:clamp(-450px,-34vw,-330px); position:absolute; z-index:3; left:53%; top:50%;
+ transform:translate(var(--lx),-120px); width:clamp(128px,11vw,158px); height:auto;
+ filter:drop-shadow(0 6px 22px rgba(0,0,0,.4));
+ transition:transform .35s var(--ease-out), filter .35s ease;
+ transform-origin:center; cursor:pointer;
+ animation:hero-fade 1s ease .1s both;}
+.hero__logo:hover{transform:translate(var(--lx),-120px) scale(1.1);
+ filter:drop-shadow(0 10px 30px rgba(0,0,0,.5));}
+.hero__content{position:relative; z-index:1; text-align:center; color:#fff; padding:0 var(--pad);
+ max-width:1000px;}
+.hero__title{font-size:clamp(38px,6.2vw,65px); font-weight:800; letter-spacing:-.05em; line-height:.92;
+ text-shadow:0 2px 40px rgba(0,0,0,.35);}
+/* V2 badge pinned to the top-right of the wordmark (superscript) */
+.hero__word{position:relative; display:inline-block;}
+.hero__ver{position:absolute; left:100%; top:-.02em; margin-left:.1em; line-height:1;
+ font-family:var(--mono); font-weight:600; font-size:.26em;
+ color:#fff; background:var(--blue); padding:.14em .34em; border-radius:.18em;
+ letter-spacing:0;}
+.hero__subtitle{font-size:clamp(18px,2.4vw,24px); font-weight:500; color:rgba(255,255,255,.94);
+ margin:22px auto 0; max-width:780px; line-height:1.32; text-shadow:0 2px 20px rgba(0,0,0,.5);}
+.hero__taglinewrap{margin:26px 0 0; display:flex; align-items:center; justify-content:center; gap:18px;}
+.hero__rule{height:1px; width:clamp(28px,6vw,72px);
+ background:linear-gradient(90deg, transparent, rgba(255,255,255,.55), transparent);}
+.hero__tagline{font-family:var(--serif); font-style:italic; font-weight:500;
+ font-size:clamp(16px,2.2vw,23px); color:#fff; letter-spacing:.01em;
+ text-shadow:0 2px 24px rgba(0,0,0,.5); white-space:nowrap;}
+.hero__authors{margin:26px auto 0; max-width:820px; font-size:clamp(15px,1.7vw,18px);
+ font-weight:500; color:rgba(255,255,255,.92); line-height:1.7; text-shadow:0 2px 16px rgba(0,0,0,.5);}
+.hero__authors sup{font-size:.66em; opacity:.85;}
+.hero__affil{margin:8px 0 0; font-size:clamp(15px,1.7vw,18px); font-weight:500; color:rgba(255,255,255,.88); text-shadow:0 2px 16px rgba(0,0,0,.5);}
+.hero__btns{display:flex; flex-wrap:wrap; gap:11px; justify-content:center; margin:30px 0 0;}
+/* hero entrance choreography โ staggered rise on load (graceful: no anim => visible) */
+@keyframes hero-fade{from{opacity:0;}to{opacity:1;}}
+@keyframes hero-up{from{opacity:0; transform:translateY(22px);}to{opacity:1; transform:none;}}
+@keyframes rule-grow{from{opacity:0; transform:scaleX(.2);}to{opacity:1; transform:scaleX(1);}}
+.hero__title{animation:hero-up .9s var(--ease-out) .16s both;}
+.hero__subtitle{animation:hero-up .9s var(--ease-out) .30s both;}
+.hero__taglinewrap{animation:hero-up .9s var(--ease-out) .42s both;}
+.hero__rule{transform-origin:center; animation:rule-grow 1s var(--ease-out) .54s both;}
+.hero__authors{animation:hero-up .9s var(--ease-out) .56s both;}
+.hero__affil{animation:hero-up .9s var(--ease-out) .62s both;}
+.hero__btns{animation:hero-up .9s var(--ease-out) .70s both;}
+
+.hero__scroll{position:absolute; bottom:26px; left:50%; transform:translateX(-50%); z-index:1;
+ width:26px; height:42px; border:2px solid rgba(255,255,255,.55); border-radius:14px;}
+.hero__scroll span{position:absolute; top:8px; left:50%; width:4px; height:8px; border-radius:2px;
+ background:#fff; transform:translateX(-50%); animation:scrolldot 1.6s ease infinite;}
+@keyframes scrolldot{0%{opacity:0; top:8px;}40%{opacity:1;}80%{opacity:0; top:22px;}100%{opacity:0;}}
+
+/* =========================================================
+ SECTION
+ ========================================================= */
+.sect{padding:64px 0 56px; position:relative;}
+.sect--alt{background:var(--paper-2); border-top:1px solid var(--line); border-bottom:1px solid var(--line);}
+/* blueprint grid wash โ faint, softly faded so it never competes with text */
+.sect--alt::before{content:""; position:absolute; inset:0; z-index:0; pointer-events:none;
+ background:
+ linear-gradient(var(--grid-line) 1px, transparent 1px) 0 0/var(--grid-size) var(--grid-size),
+ linear-gradient(90deg, var(--grid-line) 1px, transparent 1px) 0 0/var(--grid-size) var(--grid-size);
+ -webkit-mask-image:radial-gradient(135% 92% at 50% -8%, #000 42%, transparent 86%);
+ mask-image:radial-gradient(135% 92% at 50% -8%, #000 42%, transparent 86%);}
+.sect > .wrap{position:relative; z-index:1;}
+.sect__head{max-width:980px;}
+.sect__title{font-size:clamp(24px,3vw,33px); margin:12px 0 0;}
+.sect__lede{margin:20px 0 0; font-size:clamp(17px,1.9vw,20px); color:var(--ink-soft);}
+.sect__lede strong{color:var(--ink);}
+.block-eyebrow{margin:0 0 18px; color:var(--ink-soft); font-size:14px;}
+.block-eyebrow .verb{margin-right:10px;}
+
+/* =========================================================
+ VIEWER
+ ========================================================= */
+/* --vh fixes the stage + side row height so switching visual/collision/affordance
+ (and affordance parts) never changes the layout height โ no click/auto-cycle jump. */
+.viewer{--vh:385px; margin:32px 0 44px; display:grid; grid-template-columns:1.5fr 1fr; gap:22px;
+ grid-template-rows:var(--vh) auto; grid-template-areas:"stage side" "gallery gallery";}
+.viewer__stage{grid-area:stage; position:relative; border:1px solid var(--line); border-radius:18px;
+ overflow:hidden; background:
+ linear-gradient(var(--grid-line) 1px, transparent 1px) 0 0/26px 26px,
+ linear-gradient(90deg, var(--grid-line) 1px, transparent 1px) 0 0/26px 26px,
+ radial-gradient(120% 100% at 50% 0%, #fff, var(--paper-2));
+ box-shadow:inset 0 0 70px rgba(18,20,23,.055);
+ min-height:0;}
+model-viewer{width:100%; height:100%; min-height:0; --poster-color:transparent;
+ background:transparent;}
+.viewer__modes{position:absolute; top:16px; left:16px; display:flex; gap:6px; z-index:3;
+ background:rgba(255,255,255,.7); backdrop-filter:blur(8px); padding:4px; border-radius:999px;
+ border:1px solid var(--line);}
+.mode{border:0; background:transparent; font-family:var(--sans); font-weight:600; font-size:13px;
+ color:var(--ink-soft); padding:7px 14px; border-radius:999px; cursor:pointer; transition:.15s;}
+.mode.is-active{background:var(--blue); color:#fff; box-shadow:0 2px 8px rgba(30,84,255,.35);}
+.viewer__hint{position:absolute; bottom:14px; left:0; right:0; text-align:center; z-index:3;
+ font-family:var(--mono); font-size:11px; color:rgba(18,20,23,.4); pointer-events:none;}
+
+.viewer__side{grid-area:side; display:flex; flex-direction:column; gap:14px; min-height:0; height:100%;}
+.viewer__side .card[hidden]{display:none;} /* ensure the mode toggle truly hides the inactive card(s) */
+.card{border:1px solid var(--line); border-radius:var(--r); background:#fff; padding:18px;}
+.card--meta{flex:1; min-height:0; overflow-y:auto;}
+.meta__top{display:flex; align-items:baseline; justify-content:space-between; gap:10px;}
+.meta__name{font-size:22px; font-weight:800;}
+.meta__cat{font-family:var(--mono); font-size:11px; color:var(--blue); background:var(--blue-soft);
+ padding:3px 9px; border-radius:999px;}
+.meta__desc{margin:10px 0 0; color:var(--ink-soft); font-size:14px; line-height:1.45;
+ display:-webkit-box; -webkit-line-clamp:2; line-clamp:2; -webkit-box-orient:vertical; overflow:hidden;}
+.meta__grid{display:grid; grid-template-columns:1fr 1fr; gap:12px; margin:16px 0 0;}
+.meta__grid dt{font-family:var(--mono); font-size:10.5px; color:var(--ink-soft); letter-spacing:.04em; text-transform:uppercase;}
+.meta__grid dd{margin:2px 0 0; font-weight:700; font-size:17px; color:var(--blue-ink); letter-spacing:-.01em; font-variant-numeric:tabular-nums;}
+.meta__qc{display:flex; flex-wrap:wrap; gap:6px; margin:14px 0 0; padding-top:14px; border-top:1px solid var(--line);}
+.qc{font-family:var(--mono); font-size:11px; padding:4px 9px; border-radius:999px;}
+.qc--ok{color:var(--green); background:rgba(17,156,91,.10); border:1px solid rgba(17,156,91,.22);}
+
+/* affordance card โ same fixed height as meta+fmt; overflowing content scrolls inside */
+.card--afford{flex:1; min-height:0; overflow-y:auto;}
+.parts{list-style:none; margin:14px 0 0; padding:0; display:flex; flex-direction:column; gap:6px;}
+.part{width:100%; text-align:left; display:flex; align-items:center; gap:10px; cursor:pointer;
+ border:1px solid var(--line); background:var(--paper); border-radius:10px; padding:9px 12px; transition:.15s;}
+.part:hover{border-color:rgba(18,20,23,.22);}
+.part.is-active{border-color:var(--blue); background:var(--blue-soft);}
+.part__dot{width:11px; height:11px; border-radius:50%; flex:none; box-shadow:0 0 0 1px rgba(0,0,0,.08);}
+.part__name{font-weight:600; font-size:14px; flex:1;}
+.part__grasp{font-family:var(--mono); font-size:10.5px; padding:2px 7px; border-radius:999px;}
+.part__grasp.ok{color:var(--green); background:rgba(17,156,91,.10);}
+.part__grasp.no{color:var(--ink-soft); background:#fff;}
+.partdetail{margin-top:14px; padding-top:14px; border-top:1px solid var(--line);}
+.pd__labels{display:flex; flex-wrap:wrap; gap:6px;}
+.pd__chip{font-family:var(--mono); font-size:10.5px; color:var(--blue-ink); background:var(--blue-soft);
+ border:1px solid rgba(30,84,255,.18); padding:3px 8px; border-radius:999px;}
+.pd__desc{margin:12px 0 0; font-size:13.5px; color:var(--ink-soft); line-height:1.55;}
+.afford__hint{margin-top:14px; font-family:var(--mono); font-size:10.5px; color:rgba(18,20,23,.45);}
+
+.card--fmt{flex:none; background:var(--paper-2); border-color:var(--line-2); padding:11px 14px;}
+.card__label{display:flex; align-items:baseline; gap:8px; font-family:var(--mono); font-size:11px;
+ color:var(--ink-soft); text-transform:uppercase; letter-spacing:.06em;}
+.fmt__gs{font-style:normal; text-transform:none; letter-spacing:0; font-size:10.5px;
+ color:var(--blue-ink); margin-left:auto;}
+/* format โ simulators pairs flow inline and wrap (short pairs share a line) */
+.fmtmap{list-style:none; margin:6px 0 0; padding:0; display:flex; flex-wrap:wrap; gap:6px 16px;}
+.fmtmap li{display:flex; align-items:center; gap:8px; white-space:nowrap;}
+.fmt__b{font-family:var(--mono); font-size:11px; font-weight:600; color:#fff; background:var(--blue);
+ padding:2px 8px; border-radius:6px; text-align:center; flex:none;}
+.fmt__s{font-family:var(--mono); font-size:11.5px; color:var(--ink-soft);}
+
+.gallery{grid-area:gallery; display:flex; flex-wrap:wrap; gap:10px; margin-top:4px;}
+.thumb{border:1px solid var(--line); background:#fff; border-radius:11px; padding:11px 18px;
+ font-weight:600; font-size:14.5px; color:var(--ink-soft); cursor:pointer;
+ display:flex; align-items:center; gap:9px;
+ transition:transform .18s var(--ease-out),border-color .15s,color .15s,background .15s,box-shadow .15s;}
+.thumb:hover{border-color:rgba(18,20,23,.24); color:var(--ink); transform:translateY(-1px);}
+.thumb.is-active{border-color:var(--blue); color:var(--blue); background:var(--blue-soft);
+ box-shadow:0 0 0 3px var(--blue-soft);}
+.thumb__dot{width:7px; height:7px; border-radius:50%; background:currentColor; opacity:.5;}
+.thumb.is-active .thumb__dot{opacity:1;}
+
+/* =========================================================
+ TASK-DRIVEN SCENES
+ ========================================================= */
+.taskflow{display:flex; align-items:center; flex-wrap:wrap; gap:14px 18px; margin:30px 0 28px;}
+.taskflow__prompt{font-family:var(--mono); font-size:14px; color:var(--ink); background:#fff;
+ border:1px solid var(--line); border-radius:10px; padding:11px 16px; box-shadow:var(--shadow);}
+.taskflow__caret{color:var(--blue); font-weight:700; margin-right:6px;}
+.taskflow__arrow{color:var(--blue); font-family:var(--mono);}
+.roles{display:flex; flex-wrap:wrap; gap:8px;}
+.role{font-family:var(--mono); font-size:12px; font-weight:600; padding:6px 12px; border-radius:999px;
+ border:1px solid var(--line); color:var(--ink-soft); background:#fff;}
+.role--bg{color:#5A6068;}
+.role--ctx{color:var(--blue-ink); border-color:rgba(30,84,255,.25); background:var(--blue-soft);}
+.role--tgt{color:var(--green); border-color:rgba(17,156,91,.28); background:rgba(17,156,91,.08);}
+.role--dis{color:#9a6a00; border-color:rgba(200,140,0,.28); background:rgba(255,183,77,.12);}
+.role--bot{color:#7a3da6; border-color:rgba(122,61,166,.28); background:rgba(160,90,210,.10);}
+
+/* =========================================================
+ CROSS-SIMULATOR
+ ========================================================= */
+/* section videos align to the text column width (sect__head is 980px), left-aligned */
+.crossfig{margin:30px 0 22px; max-width:980px; border:1px solid var(--line); border-radius:18px; overflow:hidden;
+ background:#0c0d10; box-shadow:var(--shadow);}
+.crossfig video{width:100%; display:block; background:#0c0d10; aspect-ratio:16/9; object-fit:cover;}
+.results{display:flex; flex-wrap:wrap; align-items:flex-end; gap:18px 48px; margin:26px 0 4px;}
+.result__nums{font-weight:800; letter-spacing:-.03em; line-height:1; font-size:clamp(34px,4.4vw,48px); color:var(--blue-ink); display:flex; align-items:baseline; font-variant-numeric:tabular-nums;}
+.result__to{font-family:var(--mono); font-weight:500; font-size:.5em; margin:0 .14em; color:var(--green);}
+.result__u{font-size:.46em; font-weight:700; margin-left:.08em;}
+.result__label{margin-top:8px; font-weight:600; font-size:14px; color:var(--ink-soft);}
+.result__cap{flex-basis:100%; margin:8px 0 0; font-family:var(--mono); font-size:11px; color:rgba(18,20,23,.45);}
+.tiers{display:flex; flex-wrap:wrap; gap:8px; margin:24px 0 22px;}
+.tier{font-family:var(--mono); font-size:12.5px; font-weight:600; color:var(--ink-soft);
+ background:#fff; border:1px solid var(--line); padding:7px 16px; border-radius:999px;}
+.tier--on{color:#fff; background:var(--blue); border-color:var(--blue);}
+.simbar{display:flex; flex-wrap:wrap; gap:8px; margin-bottom:6px;}
+.sim{font-family:var(--mono); font-size:12.5px; font-weight:600; color:var(--blue-ink);
+ background:var(--blue-soft); border:1px solid rgba(30,84,255,.18); padding:7px 14px; border-radius:999px;}
+
+/* =========================================================
+ PIPELINE
+ ========================================================= */
+.pipe{list-style:none; margin:0; padding:0; display:grid; grid-template-columns:repeat(5,1fr);
+ border:1px solid var(--line); border-radius:var(--r); overflow:hidden; background:#fff;}
+.pipe__step{position:relative; padding:24px 20px 26px; border-right:1px solid var(--line);}
+.pipe__step:last-child{border-right:0;}
+.pipe__step::after{content:"โ"; position:absolute; right:-9px; top:30px; z-index:2; font-family:var(--mono);
+ color:var(--blue); background:#fff; width:18px; height:18px; display:grid; place-items:center;
+ border-radius:50%; border:1px solid var(--line); font-size:11px;}
+.pipe__step:last-child::after{display:none;}
+.pipe__n{font-family:var(--mono); font-size:12px; font-weight:600; color:var(--blue);}
+.pipe__t{font-size:16.5px; font-weight:700; margin:12px 0 0;}
+.pipe__d{margin:9px 0 0; font-size:13px; color:var(--ink-soft); line-height:1.5;}
+.pipe__retry{display:inline-block; margin-top:12px; font-family:var(--mono); font-size:11px;
+ color:var(--green); background:rgba(17,156,91,.10); border:1px solid rgba(17,156,91,.22);
+ padding:3px 8px; border-radius:999px;}
+.figdrop{margin-top:18px;}
+.figdrop summary{cursor:pointer; font-family:var(--mono); font-size:13px; color:var(--ink-soft);
+ list-style:none; display:inline-flex; align-items:center; gap:8px; padding:8px 12px;
+ border:1px solid var(--line); border-radius:999px; background:#fff;}
+.figdrop summary::-webkit-details-marker{display:none;}
+.figdrop summary::before{content:"๏ผ"; color:var(--blue); font-weight:700;}
+.figdrop[open] summary::before{content:"๏ผ";}
+.figdrop[open] summary{margin-bottom:16px;}
+.figdrop img,.figdrop video{border:1px solid var(--line); border-radius:var(--r);}
+.figrow{display:flex; flex-wrap:wrap; gap:14px 28px; margin-top:18px; align-items:flex-start;}
+.figrow .figdrop{margin-top:0;}
+.figdrop--soft{display:block;}
+@media (max-width:760px){ .figrow{flex-direction:column; gap:12px;} }
+.figdrop__note{margin:0 0 14px; max-width:70ch; color:var(--ink-soft); font-size:15px;}
+.figdrop__cap{margin:10px 0 0; font-family:var(--mono); font-size:11.5px; color:rgba(18,20,23,.45);}
+
+/* =========================================================
+ VIBE CODING
+ ========================================================= */
+.vibe{margin-top:30px; display:grid; grid-template-columns:.85fr 1.15fr; gap:18px; align-items:stretch;}
+.vibe__panel{border:1px solid var(--line); border-radius:18px; background:#fff; overflow:hidden; display:flex; flex-direction:column;}
+.vibe__tabs{display:flex; gap:4px; padding:12px; border-bottom:1px solid var(--line); background:var(--paper);}
+.vtab{flex:1; border:1px solid var(--line); background:#fff; font-family:var(--sans); font-weight:600;
+ font-size:14px; color:var(--ink-soft); padding:9px 12px; border-radius:10px; cursor:pointer; transition:.15s;}
+.vtab.is-active{background:var(--blue); color:#fff; border-color:var(--blue);}
+.vibe__hist{list-style:none; margin:0; padding:14px; display:flex; flex-direction:column; gap:10px; flex:1;}
+.turn{text-align:left; width:100%; border:1px solid var(--line); background:var(--paper);
+ border-radius:12px; padding:13px 15px; cursor:pointer; transition:.15s; display:flex; flex-direction:column; gap:7px;}
+.turn:hover{border-color:rgba(18,20,23,.22);}
+.turn.is-active{border-color:var(--blue); background:var(--blue-soft); box-shadow:inset 3px 0 0 var(--blue);}
+.turn__cmd{font-family:var(--mono); font-size:13px; color:var(--ink); line-height:1.4;}
+.turn__cmd::before{content:"โบ "; color:var(--blue); font-weight:700;}
+.turn__delta{font-family:var(--mono); font-size:11px; font-weight:600; align-self:flex-start;
+ padding:2px 8px; border-radius:999px;}
+.turn__delta--add{color:var(--green); background:rgba(17,156,91,.10); border:1px solid rgba(17,156,91,.22);}
+.turn__delta--del{color:var(--red); background:rgba(224,70,62,.08); border:1px solid rgba(224,70,62,.22);}
+.turn__delta--init{color:var(--ink-soft); background:#fff; border:1px solid var(--line);}
+.vibe__stage{position:relative; border:1px solid var(--line); border-radius:18px; overflow:hidden; background:#0c0d10; min-height:340px;}
+.vibe__stage video{width:100%; height:100%; aspect-ratio:11/9; object-fit:cover; display:block; background:#0c0d10;}
+.vibe__badge{position:absolute; top:14px; right:14px; font-family:var(--mono); font-size:12px; font-weight:600;
+ color:#fff; background:rgba(30,84,255,.9); padding:4px 10px; border-radius:999px;}
+
+/* overview + bibtex */
+.caps{display:grid; grid-template-columns:repeat(3,1fr); gap:14px; margin:30px 0 18px;}
+.cap{position:relative; display:block; border:1px solid var(--line); border-radius:var(--r);
+ background:#fff; padding:20px 20px 18px; transition:transform .2s var(--ease-out),border-color .2s,box-shadow .2s;}
+.cap:hover{transform:translateY(-3px); border-color:rgba(30,84,255,.35); box-shadow:var(--shadow-lift);}
+.cap__head{display:flex; align-items:center; gap:11px;}
+.cap__ic{flex:none; display:inline-flex; align-items:center; justify-content:center; width:34px; height:34px;
+ border-radius:9px; background:var(--blue-soft); color:var(--blue);
+ transition:background .15s, color .15s;}
+.cap__ic svg{width:19px; height:19px;}
+.cap:hover .cap__ic{background:var(--blue); color:#fff;}
+.cap__t{font-size:17px; font-weight:700; margin:0; letter-spacing:-.01em; line-height:1.25; color:#3b4149;}
+.cap__verb{color:var(--blue);}
+.cap__d{margin:8px 0 0; font-size:13.5px; color:var(--ink-soft); line-height:1.5;}
+.cap__go{position:absolute; top:18px; right:18px; color:var(--ink-soft); font-size:16px;
+ transition:transform .15s,color .15s;}
+.cap:hover .cap__go{color:var(--blue); transform:translateY(3px);}
+/* expanded figures (all sections): centered, framed, soft shadow, subtle hover */
+.figdrop[open]{padding:18px; border:1px solid var(--line); border-radius:18px;
+ background:linear-gradient(180deg, #fff, var(--paper-2)); box-shadow:var(--shadow); margin-top:14px;}
+.figdrop[open] img,.figdrop[open] video{display:block; max-width:960px; width:100%; margin:0 auto;
+ border:1px solid var(--line); border-radius:14px; background:#fff;
+ box-shadow:0 10px 40px rgba(18,20,23,.12); transition:transform .3s ease;}
+.figdrop[open] img:hover,.figdrop[open] video:hover{transform:scale(1.012);}
+.cite{position:relative; margin-top:26px;}
+.cite__code{font-family:var(--mono); font-size:13px; line-height:1.6; color:var(--ink);
+ background:#fff; border:1px solid var(--line); border-radius:var(--r); padding:22px 22px; overflow:auto; margin:0;}
+.cite__copy{position:absolute; top:12px; right:12px; font-family:var(--sans); font-weight:600; font-size:13px;
+ color:var(--ink); background:var(--paper); border:1px solid var(--line); border-radius:999px;
+ padding:7px 15px; cursor:pointer; transition:.15s;}
+.cite__copy:hover{border-color:var(--blue); color:var(--blue);}
+.cite__copy.is-done{color:var(--green); border-color:var(--green);}
+
+/* footer */
+.foot{border-top:1px solid var(--line); background:#fff;}
+.foot__inner{display:flex; align-items:center; justify-content:space-between; gap:24px; padding:26px var(--pad);}
+.foot__links{display:flex; flex-wrap:wrap; gap:24px; align-items:center;}
+.foot__links a{font-weight:500; font-size:14.5px; color:var(--ink-soft); transition:color .15s; position:relative;}
+.foot__links a:hover{color:var(--blue);}
+.foot__meta{display:flex; align-items:center; gap:8px; font-size:13.5px; font-weight:500; color:var(--ink-soft); white-space:nowrap;}
+.foot__brand{font-weight:700; color:var(--ink);}
+.foot__dot{color:rgba(18,20,23,.3);}
+@media (max-width:640px){
+ .foot__inner{flex-direction:column; gap:16px; text-align:center;}
+ .foot__links{justify-content:center; gap:18px;}
+}
+
+/* reveal โ soft rise + subtle de-blur on the established easing */
+.reveal{opacity:0; transform:translateY(22px); filter:blur(6px);
+ transition:opacity .8s var(--ease-out), transform .8s var(--ease-out), filter .8s var(--ease-out);
+ will-change:opacity,transform;}
+.reveal.is-in{opacity:1; transform:none; filter:none;}
+/* staggered children: add .stagger to a .reveal container */
+.reveal.stagger.is-in > *{animation:rise-in .7s var(--ease-out) both;}
+.reveal.stagger.is-in > *:nth-child(1){animation-delay:.04s;}
+.reveal.stagger.is-in > *:nth-child(2){animation-delay:.10s;}
+.reveal.stagger.is-in > *:nth-child(3){animation-delay:.16s;}
+.reveal.stagger.is-in > *:nth-child(4){animation-delay:.22s;}
+.reveal.stagger.is-in > *:nth-child(5){animation-delay:.28s;}
+.reveal.stagger.is-in > *:nth-child(6){animation-delay:.34s;}
+@keyframes rise-in{from{opacity:0; transform:translateY(14px);}to{opacity:1; transform:none;}}
+
+/* =========================================================
+ RESPONSIVE
+ ========================================================= */
+@media (max-width:1000px){
+ .hero__logo{display:none;}
+ /* single column: drop the fixed row height; stage gets its own min-height */
+ .viewer{grid-template-columns:1fr; grid-template-rows:none; grid-template-areas:"stage" "side" "gallery";}
+ .viewer__stage{min-height:380px;}
+ .viewer__side{height:auto;}
+ .pipe{grid-template-columns:repeat(2,1fr);}
+ .pipe__step:nth-child(2n)::after{display:none;}
+ .pipe__step{border-bottom:1px solid var(--line);}
+ .vibe{grid-template-columns:1fr;}
+ .caps{grid-template-columns:repeat(2,1fr);}
+}
+@media (max-width:760px){
+ /* keep nav usable on phones: links become a horizontally-scrollable strip */
+ .nav__inner{gap:12px; height:64px;}
+ .nav__links{margin-left:auto; overflow-x:auto; max-width:64vw; flex-wrap:nowrap;
+ scrollbar-width:none; -webkit-overflow-scrolling:touch;
+ -webkit-mask-image:linear-gradient(90deg,transparent 0,#000 10px,#000 calc(100% - 10px),transparent 100%);
+ mask-image:linear-gradient(90deg,transparent 0,#000 10px,#000 calc(100% - 10px),transparent 100%);}
+ .nav__links::-webkit-scrollbar{display:none;}
+ .nav__link{flex:none; font-size:14px; padding:6px 10px; white-space:nowrap;}
+}
+@media (max-width:640px){
+ :root{--pad:20px;}
+ body{font-size:16px;}
+ .sect{padding:64px 0;}
+ .pipe{grid-template-columns:1fr;}
+ .pipe__step{border-right:0;}
+ .pipe__step::after{display:none;}
+ .meta__grid{grid-template-columns:1fr 1fr;}
+ .caps{grid-template-columns:1fr;}
+ .hero__btns .btn{font-size:13.5px; padding:.5em .85em;}
+}
+
+/* reduced motion */
+@media (prefers-reduced-motion:reduce){
+ html{scroll-behavior:auto;}
+ .reveal{opacity:1; transform:none; filter:none; transition:none;}
+ .reveal.stagger.is-in > *{animation:none;}
+ .hero__scroll span{animation:none;}
+ *{animation-duration:.001ms !important;}
+}
diff --git a/docs/landing/tools/build_assets.sh b/docs/landing/tools/build_assets.sh
new file mode 100755
index 0000000..dfdfa96
--- /dev/null
+++ b/docs/landing/tools/build_assets.sh
@@ -0,0 +1,69 @@
+#!/usr/bin/env bash
+# Idempotent asset build for the EmbodiedGen V2 webpage.
+# Produces: figures (JPG), brand logo (PNG), sim-ready models + assets.json,
+# vibe-coding scene clips, and affordance data.
+# NOTE: the five master-derived section videos (hero / scenes_gen / worlds /
+# multi_sim / closed_loop) are produced by tools/cut_videos_from_master.sh,
+# NOT here. Sources are READ-ONLY. Outputs -> docs/landing/assets/{img,video,models}
+set -euo pipefail
+
+# repo root (this script lives at docs/landing/tools/)
+ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/../../.." && pwd)"
+SRC_VID="$ROOT/outputs/embodiedgenv2/video"
+SRC_FIG="$ROOT/outputs/embodiedgenv2/paper/figure"
+IMG="$ROOT/docs/landing/assets/img"
+VID="$ROOT/docs/landing/assets/video"
+MOD="$ROOT/docs/landing/assets/models"
+mkdir -p "$IMG" "$VID" "$MOD"
+
+# Render a source (PDF first page, or raster image) to a web JPG (<=1MB, <=1920w wide).
+fig2jpg() { #
+ python3 - "$1" "$2" <<'PY'
+import sys, os, subprocess, tempfile
+from PIL import Image
+src, dst = sys.argv[1], sys.argv[2]
+if src.lower().endswith(".pdf"):
+ base = tempfile.mktemp(prefix="_fig")
+ subprocess.run(["pdftoppm", "-png", "-r", "150", "-singlefile", src, base], check=True)
+ src = base + ".png"
+im = Image.open(src).convert("RGB")
+w, h = im.size
+if w > 1920:
+ im = im.resize((1920, round(h * 1920 / w)), Image.LANCZOS)
+for q in (90, 88, 85, 82, 78, 74):
+ im.save(dst, "JPEG", quality=q, optimize=True, progressive=True)
+ if os.path.getsize(dst) <= 1024 * 1024:
+ break
+print(f" -> {os.path.basename(dst)} {im.size} q={q} {os.path.getsize(dst)//1024}K")
+PY
+}
+
+echo "[1/4] Figures -> JPG (<=1MB)"
+fig2jpg "$SRC_FIG/sim_ready_asset_gen.pdf" "$IMG/sim_pipeline.jpg"
+fig2jpg "$SRC_FIG/overview.pdf" "$IMG/overview.jpg"
+fig2jpg "$SRC_FIG/layout_pipe.pdf" "$IMG/layout_pipe.jpg"
+fig2jpg "$SRC_FIG/large_scale_scene_gen.pdf" "$IMG/large_scale.jpg"
+fig2jpg "$SRC_FIG/heatmap_grid_2x6.png" "$IMG/deformable_heatmap.jpg"
+fig2jpg "$SRC_FIG/policy_learning_and_deploy.png" "$IMG/policy_deploy.jpg"
+
+echo "[1b] Brand logo (keep PNG transparency, trim margins)"
+python3 - "$ROOT/docs/documentation/assets/logo.png" "$IMG/logo.png" <<'PY'
+import sys; from PIL import Image
+im = Image.open(sys.argv[1]).convert("RGBA"); bbox = im.getbbox()
+if bbox: im = im.crop(bbox)
+im.save(sys.argv[2]); print(" -> logo.png", im.size)
+PY
+
+echo "[2/3] Sim-ready asset viewer models (visual + collision + affordance + json)"
+# All three viewer variants, assets.json and affordance.json for every complete
+# asset in the project_example_v2 export. Requires the horizon-bucket mount.
+python3 "$ROOT/docs/landing/tools/build_models_v2.py" "$MOD"
+
+echo "[3/3] Vibe-coding scene clips (kitchen/living, S0-2) -> 1080w/crf26"
+for n in kitchen_S0 kitchen_S1 kitchen_S2 living_room_S0 living_room_S1 living_room_S2; do
+ ffmpeg -y -i "$SRC_VID/$n.mp4" -vf "scale='min(1080,iw)':-2" -c:v libx264 -crf 26 -preset slow -an \
+ -movflags +faststart "$VID/scene_$n.mp4" -hide_banner -loglevel error
+done
+
+echo "Done. (Section videos come from tools/cut_videos_from_master.sh.)"
+du -h "$IMG"/*.jpg "$VID"/scene_*.mp4 "$MOD"/*.glb 2>/dev/null | sort -h | tail -24
diff --git a/docs/landing/tools/build_models_v2.py b/docs/landing/tools/build_models_v2.py
new file mode 100644
index 0000000..1715664
--- /dev/null
+++ b/docs/landing/tools/build_models_v2.py
@@ -0,0 +1,238 @@
+#!/usr/bin/env python3
+"""Rebuild the landing asset viewer models from a project_example_v2 export.
+
+Source layout (per asset, dir name may contain spaces):
+ /.urdf
+ /mesh/.glb visual mesh (mesh file may be renamed/suffixed)
+ /mesh/_collision.obj collision proxy
+ /affordance/mesh_part_seg.glb + affordance_annot.json
+
+For each complete asset this produces, under docs/landing/assets/models/:
+ .glb visual mesh (copied as-is)
+ _collision.glb per-part-colorized collision proxy
+ _afford.glb part-seg mesh + baked parallel-jaw grippers (top-k grasps)
+plus assets.json (gallery + physics meta) and affordance.json (part legend).
+
+ = asset dir name, spaces -> underscores, lowercased. Grasp confidence is
+only used to pick the top-k grippers; it is never displayed. Idempotent.
+"""
+import glob
+import json
+import os
+import re
+import sys
+
+import numpy as np
+import trimesh
+from trimesh.transformations import quaternion_matrix
+
+# Ordered source roots. Each: (dir, names) โ names=None means every subdir,
+# otherwise only that curated set. The original headphones showcase asset (from
+# the v1 export) leads the gallery, followed by the full project_example_v3 batch.
+_LUCAS = "/horizon-bucket/robot_lab/users/lucas.ding/output"
+SRC_ROOTS = [
+ (f"{_LUCAS}/project_example", ["ear_hear"]),
+ (f"{_LUCAS}/project_example_v3", None),
+]
+OUT = sys.argv[1] if len(sys.argv) > 1 else os.path.join(
+ os.path.dirname(__file__), "..", "assets", "models")
+OUT = os.path.abspath(OUT)
+TOPK = 2 # grippers baked per part (top-k by confidence)
+# Assets intentionally dropped from the gallery (by key). screwdriver = the
+# green-handled one (yellow-handled repair_tools is kept).
+EXCLUDE = {"screwdriver", "tableware", "thermos"}
+
+# mask_color name -> hex (must match repo PALETTE in vis_utils.py).
+COLOR = {
+ "red": "#E6194B", "green": "#3CB44B", "blue": "#0082C8", "yellow": "#FFE119",
+ "orange": "#F58231", "purple": "#911EB4", "cyan": "#46F0F0", "magenta": "#F032E6",
+ "pink": "#FABED4", "lime green": "#D2F53C", "teal": "#008080", "brown": "#AA6E28",
+ "maroon": "#800000", "navy": "#000080", "olive": "#6B8E23", "gray": "#808080",
+ "crimson": "#DC143C", "white": "#FFFFFF", "burnt orange": "#CC5500", "jade": "#00998F",
+}
+# collision per-connected-component palette (RGBA)
+PAL = [(99, 179, 237), (129, 199, 132), (255, 183, 77), (186, 104, 200),
+ (240, 98, 146), (77, 208, 225), (255, 138, 101), (174, 213, 129)]
+
+
+def key_of(name):
+ return re.sub(r"\s+", "_", name.strip()).lower()
+
+
+def tag(text, name, default=""):
+ m = re.search(rf"<{name}>(.*?){name}>", text, re.S)
+ return m.group(1).strip() if m else default
+
+
+def hex_rgba(h, a=255):
+ h = h.lstrip("#")
+ return [int(h[0:2], 16), int(h[2:4], 16), int(h[4:6], 16), a]
+
+
+def box(extents, transform, rgba):
+ m = trimesh.creation.box(extents=extents, transform=transform)
+ m.visual = trimesh.visual.ColorVisuals(
+ mesh=m, face_colors=np.tile(rgba, (len(m.faces), 1)))
+ return m
+
+
+def gripper(rgba):
+ """Franka-panda style parallel-jaw marker in the grasp (hand) frame.
+ Hand origin at z=0, approach along +Z, fingers separated along X, contact
+ (TCP) at z=TCP. Matches the convention where grasp xyz is the hand origin.
+ """
+ TCP = 0.1034 # hand -> fingertip along +z
+ base = 0.0584 # hand -> finger base (crossbar) along +z
+ fw = 0.041 # finger half-separation (x)
+ T = 0.006 # tube thickness
+ tr = trimesh.transformations.translation_matrix
+ parts = [
+ box([T, T, base], tr([0, 0, base / 2]), rgba), # stem
+ box([2 * fw + T, T, T], tr([0, 0, base]), rgba), # crossbar
+ box([T, T, TCP - base], tr([fw, 0, (base + TCP) / 2]), rgba), # finger +x
+ box([T, T, TCP - base], tr([-fw, 0, (base + TCP) / 2]), rgba), # finger -x
+ ]
+ return trimesh.util.concatenate(parts)
+
+
+def load_mesh(path):
+ m = trimesh.load(path, force="mesh", process=False)
+ if isinstance(m, trimesh.Scene):
+ m = m.dump(concatenate=True)
+ return m
+
+
+def build_collision(obj_path, dst):
+ m = load_mesh(obj_path)
+ parts = m.split(only_watertight=False) or [m]
+ if len(parts) == 0:
+ parts = [m]
+ sc = trimesh.Scene()
+ for i, p in enumerate(parts):
+ c = PAL[i % len(PAL)]
+ p.visual = trimesh.visual.ColorVisuals(
+ mesh=p, face_colors=np.tile([*c, 235], (len(p.faces), 1)))
+ sc.add_geometry(p)
+ sc.export(dst)
+
+
+def build_affordance(seg_glb, annot_path, dst):
+ """Bake top-k grippers onto the part-seg mesh. The grasps live in the OBJECT
+ frame, which differs from the raw part-seg glb by the URDF visual_seg origin
+ (rpy = 1.5708,0,0 = Rx(90)). Bring the mesh into the object frame, place each
+ gripper directly (translation @ quaternion), then rotate the whole scene back
+ so it displays in the same raw-glb orientation as the visual/collision tabs.
+ """
+ annot = json.load(open(annot_path))
+ mesh = load_mesh(seg_glb)
+ Morigin = trimesh.transformations.euler_matrix(1.5708, 0, 0, axes="sxyz")
+ mesh.apply_transform(Morigin)
+ scene = trimesh.Scene(mesh)
+
+ parts_out = []
+ gi = 0
+ for part in annot.get("affordances", []):
+ grasps = part.get("grasp_group", {}) or {}
+ top = sorted(grasps.values(), key=lambda g: -g.get("confidence", 0))[:TOPK]
+ cname = (part.get("mask_color") or "").strip().lower()
+ phex = COLOR.get(cname, "#8A9099")
+ for g in top:
+ pos = np.array(g["position"], float)
+ q = g["orientation"]
+ Robj = quaternion_matrix(
+ [q["w"], q["xyz"][0], q["xyz"][1], q["xyz"][2]])[:3, :3]
+ pose = np.eye(4)
+ pose[:3, :3] = Robj
+ pose[:3, 3] = pos
+ grip = gripper(hex_rgba(phex))
+ grip.apply_transform(pose)
+ scene.add_geometry(grip, geom_name=f"grasp_{gi}")
+ gi += 1
+ scen = sorted((part.get("grasp_scenarios") or []),
+ key=lambda s: -s.get("confidence", 0))[:2]
+ parts_out.append({
+ "part_name": part.get("part_name", ""),
+ "color": phex,
+ "color_name": part.get("mask_color", ""),
+ "graspable": bool(part.get("graspable", False)),
+ "labels": part.get("functional_labels", [])[:5],
+ "description": part.get("semantic_description", ""),
+ "scenarios": [s.get("scenario", "") for s in scen],
+ })
+ scene.apply_transform(np.linalg.inv(Morigin))
+ scene.export(dst)
+ return parts_out, gi
+
+
+def iter_assets():
+ """Yield (name, asset_dir) across all source roots, in gallery order."""
+ for root, names in SRC_ROOTS:
+ if not os.path.isdir(root):
+ print(f" (source missing: {root})")
+ continue
+ subdirs = names if names is not None else sorted(
+ d for d in os.listdir(root) if os.path.isdir(f"{root}/{d}"))
+ for name in subdirs:
+ yield name, f"{root}/{name}"
+
+
+def main():
+ os.makedirs(OUT, exist_ok=True)
+ assets = []
+ afford = {}
+ seen = set()
+ for name, d in iter_assets():
+ key = key_of(name)
+ if key in seen or key in EXCLUDE:
+ continue
+ # visual: the textured mesh glb (not the collision proxy, not the GS glb)
+ glbs = [x for x in glob.glob(f"{d}/mesh/*.glb")
+ if not x.endswith(("_gs.glb", "_collision.glb"))]
+ # collision proxy: .obj (v2) or .glb (v1)
+ colls = (glob.glob(f"{d}/mesh/*_collision.obj")
+ or glob.glob(f"{d}/mesh/*_collision.glb"))
+ seg = f"{d}/affordance/mesh_part_seg.glb"
+ annot = f"{d}/affordance/affordance_annot.json"
+ urdf = glob.glob(f"{d}/*.urdf")
+ if not (glbs and colls and os.path.isfile(seg)
+ and os.path.isfile(annot) and urdf):
+ print(f" SKIP {name} (incomplete)")
+ continue
+ seen.add(key)
+
+ os.system(f'cp -f "{glbs[0]}" "{OUT}/{key}.glb"')
+ try:
+ build_collision(colls[0], f"{OUT}/{key}_collision.glb")
+ coll_name = f"{key}_collision.glb"
+ except Exception as e:
+ print(f" collision fail {key}: {e}")
+ coll_name = ""
+ parts_out, gi = build_affordance(seg, annot, f"{OUT}/{key}_afford.glb")
+ afford[key] = {"parts": parts_out}
+
+ t = open(urdf[0]).read()
+ mass = re.search(r'([0-9.]+)", t)
+ aes = re.search(r"([0-9.]+)", t)
+ cat = tag(t, "category", name)
+ assets.append({
+ "name": key,
+ "label": (cat or name).replace("_", " ").title(),
+ "model": f"{key}.glb",
+ "collision": coll_name,
+ "category": cat,
+ "description": tag(t, "description"),
+ "height": tag(t, "real_height"),
+ "mass": mass.group(1) if mass else "",
+ "friction": mu.group(1) if mu else "",
+ "aesthetic": aes.group(1) if aes else "",
+ })
+ print(f" -> {key} parts={len(parts_out)} grasps={gi}")
+
+ json.dump(assets, open(f"{OUT}/assets.json", "w"), indent=2)
+ json.dump(afford, open(f"{OUT}/affordance.json", "w"), indent=2)
+ print(f"\nDone: {len(assets)} assets -> {OUT}")
+
+
+if __name__ == "__main__":
+ main()
diff --git a/docs/landing/tools/cut_videos_from_master.sh b/docs/landing/tools/cut_videos_from_master.sh
new file mode 100644
index 0000000..e441aa5
--- /dev/null
+++ b/docs/landing/tools/cut_videos_from_master.sh
@@ -0,0 +1,54 @@
+#!/usr/bin/env bash
+# Re-cut landing section videos from the latest master video.
+# Source (READ-ONLY): docs/landing/assets/EmbodiedGenV2_0627_web.mov (kept out of the repo)
+# Outputs (idempotent, overwritten): docs/landing/assets/video/.mp4 + assets/img/_poster.jpg
+# Timestamps locked in outputs/embodiedgenv2/webpage_polish/WORKLOG.md (T0.1).
+# Does NOT touch the 6 scene_*_S* vibe-coding clips.
+set -euo pipefail
+
+ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" # docs/landing/
+MASTER="$ROOT/assets/EmbodiedGenV2_0627_web.mov"
+VID="$ROOT/assets/video"
+IMG="$ROOT/assets/img"
+mkdir -p "$VID" "$IMG"
+[ -f "$MASTER" ] || { echo "ERROR: master not found: $MASTER" >&2; exit 1; }
+
+# slot | in(s) | dur(s) | width | crf | poster_offset(s into clip) | poster_basename
+# NOTE: index.html references the hero poster as `hero_poster.jpg` (not hero_bg_poster).
+# NOTE: multi_sim has ~8px black pillarbars in the master; after this step it was
+# cropped with: ffmpeg -i multi_sim.mp4 -vf "crop=1904:1080:8:0" -crf 16 ... (full height kept).
+SLOTS=(
+ "hero_bg|0|35|1600|32|17|hero"
+ "scenes_gen|80|20|1200|27|10|scenes_gen"
+ "worlds|51.83|15.6|1200|27|5|worlds"
+ "multi_sim|68|3|1920|18|1.5|multi_sim"
+ "closed_loop|127|15|1200|27|12|closed_loop"
+)
+
+cut_one() {
+ local name=$1 ss=$2 dur=$3 w=$4 crf=$5 poff=$6 pname=$7
+ local out="$VID/$name.mp4" poster="$IMG/${pname}_poster.jpg"
+ echo "[cut] $name in=${ss}s dur=${dur}s ${w}w crf${crf}"
+ ffmpeg -y -hide_banner -loglevel error \
+ -ss "$ss" -t "$dur" -i "$MASTER" \
+ -map 0:v:0 -an \
+ -vf "scale='min(${w},iw)':-2:flags=lanczos" \
+ -c:v libx264 -crf "$crf" -preset slow -pix_fmt yuv420p \
+ -movflags +faststart "$out"
+ ffmpeg -y -hide_banner -loglevel error \
+ -ss "$poff" -i "$out" -vframes 1 -q:v 4 "$poster"
+}
+
+for row in "${SLOTS[@]}"; do
+ IFS='|' read -r name ss dur w crf poff pname <<<"$row"
+ cut_one "$name" "$ss" "$dur" "$w" "$crf" "$poff" "$pname"
+done
+
+echo "Done. New section videos + posters:"
+for row in "${SLOTS[@]}"; do
+ IFS='|' read -r name _ _ _ _ _ <<<"$row"
+ d=$(ffprobe -v error -show_entries format=duration -of csv=p=0 "$VID/$name.mp4")
+ wh=$(ffprobe -v error -select_streams v:0 -show_entries stream=width,height -of csv=p=0 "$VID/$name.mp4")
+ sz=$(du -h "$VID/$name.mp4" | cut -f1)
+ printf " %-14s %ss %s %s\n" "$name.mp4" "$d" "$wh" "$sz"
+done
diff --git a/docs/stylesheets/extra.css b/docs/stylesheets/extra.css
deleted file mode 100644
index 0bbeb6f..0000000
--- a/docs/stylesheets/extra.css
+++ /dev/null
@@ -1,22 +0,0 @@
-/* Adjust the logo size */
-.md-header__button.md-logo {
- height: 4rem;
- padding: 0;
- display: inline-flex;
- align-items: center;
-}
-.md-header__button.md-logo img {
- height: 4rem;
- width: auto;
-}
-
-.md-typeset pre code {
- font-size: 0.7rem;
- /* line-height: 1.5; */
- font-family: "Fira Code", "JetBrains Mono", monospace;
-}
-
-.md-typeset .admonition,
-.md-typeset details {
- font-size: 0.77rem;
-}
\ No newline at end of file
diff --git a/docs/tutorials/articulated_gen.md b/docs/tutorials/articulated_gen.md
deleted file mode 100644
index e69de29..0000000
diff --git a/docs/tutorials/digital_twin.md b/docs/tutorials/digital_twin.md
deleted file mode 100644
index 85a7315..0000000
--- a/docs/tutorials/digital_twin.md
+++ /dev/null
@@ -1,3 +0,0 @@
-# Real-to-Sim Digital Twin Creation
-
-
diff --git a/docs/tutorials/gym_env.md b/docs/tutorials/gym_env.md
deleted file mode 100644
index 8696966..0000000
--- a/docs/tutorials/gym_env.md
+++ /dev/null
@@ -1,22 +0,0 @@
-# Simulation in Parallel Envs
-
-Generate multiple parallel simulation environments with `gym.make` and record sensor and trajectory data.
-
----
-
-## โก Command-Line Usage
-
-```sh
-python embodied_gen/scripts/parallel_sim.py \
---layout_file "outputs/layouts_gen/task_0000/layout.json" \
---output_dir "outputs/parallel_sim/task_0000" \
---num_envs 16
-```
-
-
-
-
-
-
diff --git a/embodied_gen/data/asset_converter.py b/embodied_gen/data/asset_converter.py
index 7b09b70..04bd40e 100644
--- a/embodied_gen/data/asset_converter.py
+++ b/embodied_gen/data/asset_converter.py
@@ -24,6 +24,7 @@
from glob import glob
from shutil import copy, copytree, rmtree
+import numpy as np
import trimesh
from scipy.spatial.transform import Rotation
from embodied_gen.utils.enum import AssetType
@@ -204,6 +205,123 @@ def _copy_asset_file(self, src: str, dst: str) -> None:
os.makedirs(os.path.dirname(dst), exist_ok=True)
copy(src, dst)
+ @staticmethod
+ def _ensure_collision_volume(
+ mesh: trimesh.Trimesh,
+ min_thickness: float = 1e-3,
+ ) -> trimesh.Trimesh:
+ """Replace degenerate collision surfaces with thin boxes."""
+ extents = mesh.extents
+ if min(extents) >= min_thickness:
+ return mesh
+
+ box_extents = [max(float(extent), min_thickness) for extent in extents]
+ transform = trimesh.transformations.translation_matrix(
+ mesh.bounds.mean(axis=0)
+ )
+ return trimesh.creation.box(extents=box_extents, transform=transform)
+
+ @staticmethod
+ def _origin_to_matrix(origin: ET.Element | None) -> np.ndarray:
+ """Convert a URDF ```` element to a 4x4 homogeneous matrix.
+
+ A missing element is treated as the identity transform. The rotation
+ follows the URDF convention (intrinsic XYZ Euler angles in radians),
+ matching ``transform_mesh`` which applies ``v' = R @ v + t``.
+ """
+ matrix = np.eye(4)
+ if origin is None:
+ return matrix
+ xyz = list(map(float, origin.get("xyz", "0 0 0").split()))
+ rpy = list(map(float, origin.get("rpy", "0 0 0").split()))
+ matrix[:3, :3] = Rotation.from_euler(
+ "xyz", rpy, degrees=False
+ ).as_matrix()
+ matrix[:3, 3] = xyz
+ return matrix
+
+ @staticmethod
+ def _bake_obj_vertices(
+ input_obj: str, output_obj: str, matrix: np.ndarray
+ ) -> None:
+ """Bake a rigid transform into an OBJ's vertex/normal lines only.
+
+ Unlike a full mesh re-export, this rewrites just the ``v`` (rotated and
+ translated) and ``vn`` (rotated) lines, leaving ``vt`` UVs, faces,
+ ``usemtl``/``mtllib`` and the referenced texture atlas byte-identical.
+ That keeps MuJoCo/Isaac Gym texturing identical to the source asset
+ while the geometry is placed in world space.
+
+ Args:
+ input_obj (str): Source OBJ path.
+ output_obj (str): Destination OBJ path.
+ matrix (np.ndarray): 4x4 homogeneous transform (rotation only,
+ no scale).
+ """
+ rotation = matrix[:3, :3]
+ translation = matrix[:3, 3]
+
+ lines: list[str] = []
+ with open(input_obj, "r", encoding="utf-8", errors="ignore") as handle:
+ for line in handle:
+ if line.startswith("v "):
+ parts = line.split()
+ point = (
+ rotation @ np.array(parts[1:4], dtype=float)
+ + translation
+ )
+ extra = " " + " ".join(parts[4:]) if len(parts) > 4 else ""
+ lines.append(
+ f"v {point[0]:.6f} {point[1]:.6f} {point[2]:.6f}{extra}\n"
+ )
+ elif line.startswith("vn "):
+ parts = line.split()
+ normal = rotation @ np.array(parts[1:4], dtype=float)
+ lines.append(
+ f"vn {normal[0]:.6f} {normal[1]:.6f} {normal[2]:.6f}\n"
+ )
+ else:
+ lines.append(line)
+
+ os.makedirs(os.path.dirname(output_obj), exist_ok=True)
+ with open(output_obj, "w", encoding="utf-8") as handle:
+ handle.writelines(lines)
+
+ @classmethod
+ def _compose_origin(
+ cls,
+ outer: ET.Element | None,
+ inner: ET.Element | None,
+ ) -> ET.Element | None:
+ """Compose two URDF origins into a single ```` element.
+
+ The returned transform applies ``inner`` first and then ``outer``
+ (``M = M_outer @ M_inner``), so baking a link's world frame onto its
+ local visual/collision origin yields world-space mesh vertices. The
+ result always carries both ``xyz`` and ``rpy`` so it can be consumed by
+ ``transform_mesh``. Returns ``None`` when both inputs are absent.
+
+ Args:
+ outer (ET.Element | None): Outer (parent/world) origin.
+ inner (ET.Element | None): Inner (local) origin.
+
+ Returns:
+ ET.Element | None: Composed origin, or ``None`` if both are ``None``.
+ """
+ if outer is None and inner is None:
+ return None
+
+ matrix = cls._origin_to_matrix(outer) @ cls._origin_to_matrix(inner)
+ xyz = matrix[:3, 3]
+ rpy = Rotation.from_matrix(matrix[:3, :3]).as_euler(
+ "xyz", degrees=False
+ )
+
+ origin = ET.Element("origin")
+ origin.set("xyz", " ".join(f"{value:.8g}" for value in xyz))
+ origin.set("rpy", " ".join(f"{value:.8g}" for value in rpy))
+ return origin
+
def add_geometry(
self,
mujoco_element: ET.Element,
@@ -215,6 +333,7 @@ def add_geometry(
mesh_name: str,
material: ET.Element | None = None,
is_collision: bool = False,
+ body_origin: ET.Element | None = None,
) -> None:
"""Adds geometry to MJCF body from URDF link.
@@ -228,6 +347,8 @@ def add_geometry(
mesh_name (str): Mesh name.
material (ET.Element, optional): Material element.
is_collision (bool, optional): If True, treat as collision geometry.
+ body_origin (ET.Element, optional): Link world origin to bake into
+ the mesh vertices; when None only the local origin is applied.
"""
element = link.find(tag)
geometry = element.find("geometry")
@@ -238,9 +359,18 @@ def add_geometry(
output_mesh = f"{output_dir}/{filename}"
self._copy_asset_file(input_mesh, output_mesh)
- mesh_origin = element.find("origin")
+ mesh_origin = self._compose_origin(body_origin, element.find("origin"))
if mesh_origin is not None:
- self.transform_mesh(input_mesh, output_mesh, mesh_origin)
+ if not is_collision and input_mesh.lower().endswith(".obj"):
+ # Preserve UVs/material atlas for textured visuals; a trimesh
+ # re-export would rewrite mtllib and drop the diffuse atlas.
+ self._bake_obj_vertices(
+ input_mesh,
+ output_mesh,
+ self._origin_to_matrix(mesh_origin),
+ )
+ else:
+ self.transform_mesh(input_mesh, output_mesh, mesh_origin)
if is_collision:
mesh_parts = trimesh.load(
@@ -251,11 +381,13 @@ def add_geometry(
mesh_parts = [trimesh.load(output_mesh, force="mesh")]
for idx, mesh_part in enumerate(mesh_parts):
if is_collision:
+ mesh_part = self._ensure_collision_volume(mesh_part)
idx_mesh_name = f"{mesh_name}_{idx}"
base, ext = os.path.splitext(filename)
idx_filename = f"{base}_{idx}{ext}"
- base_outdir = os.path.dirname(output_mesh)
- mesh_part.export(os.path.join(base_outdir, '..', idx_filename))
+ idx_output_mesh = os.path.join(output_dir, idx_filename)
+ os.makedirs(os.path.dirname(idx_output_mesh), exist_ok=True)
+ mesh_part.export(idx_output_mesh)
geom_attrs = {
"contype": "1",
"conaffinity": "1",
@@ -263,7 +395,11 @@ def add_geometry(
}
else:
idx_mesh_name, idx_filename = mesh_name, filename
- geom_attrs = {"contype": "0", "conaffinity": "0"}
+ geom_attrs = {
+ "contype": "0",
+ "conaffinity": "0",
+ "density": "0",
+ }
ET.SubElement(
mujoco_element,
@@ -271,6 +407,7 @@ def add_geometry(
name=idx_mesh_name,
file=idx_filename,
scale=scale,
+ inertia="shell",
)
geom = ET.SubElement(body, "geom", type="mesh", mesh=idx_mesh_name)
geom.attrib.update(geom_attrs)
@@ -286,7 +423,7 @@ def add_materials(
output_dir: str,
name: str,
reflectance: float = 0.2,
- ) -> ET.Element:
+ ) -> ET.Element | None:
"""Adds materials to MJCF asset from URDF link.
Args:
@@ -306,37 +443,51 @@ def add_materials(
mesh = geometry.find("mesh")
filename = mesh.get("filename")
dirname = os.path.dirname(filename)
- material = None
- for path in glob(f"{input_dir}/{dirname}/*.png"):
- file_name = os.path.basename(path)
- if "keep_materials" in self.kwargs:
- find_flag = False
- for keep_key in self.kwargs["keep_materials"]:
- if keep_key in file_name.lower():
- find_flag = True
- if find_flag is False:
- continue
-
- self._copy_asset_file(
- path,
- f"{output_dir}/{dirname}/{file_name}",
- )
- texture_name = f"texture_{name}_{os.path.splitext(file_name)[0]}"
- material = ET.SubElement(
- mujoco_element,
- "material",
- name=f"material_{name}",
- texture=texture_name,
- reflectance=str(reflectance),
- )
- ET.SubElement(
- mujoco_element,
- "texture",
- name=texture_name,
- type="2d",
- file=f"{dirname}/{file_name}",
- )
-
+ texture_paths = sorted(glob(f"{input_dir}/{dirname}/*.png"))
+ keep_materials = self.kwargs.get("keep_materials")
+ if keep_materials:
+ texture_paths = [
+ path
+ for path in texture_paths
+ if any(
+ key.lower() in os.path.basename(path).lower()
+ for key in keep_materials
+ )
+ ]
+ if not texture_paths:
+ return None
+
+ texture_path = next(
+ (
+ path
+ for path in texture_paths
+ if "diffuse" in os.path.basename(path).lower()
+ ),
+ texture_paths[0],
+ )
+ file_name = os.path.basename(texture_path)
+ texture_file = (
+ os.path.join(dirname, file_name) if dirname else file_name
+ )
+ self._copy_asset_file(
+ texture_path,
+ os.path.join(output_dir, texture_file),
+ )
+ texture_name = f"texture_{name}_{os.path.splitext(file_name)[0]}"
+ material = ET.SubElement(
+ mujoco_element,
+ "material",
+ name=f"material_{name}",
+ texture=texture_name,
+ reflectance=str(reflectance),
+ )
+ ET.SubElement(
+ mujoco_element,
+ "texture",
+ name=texture_name,
+ type="2d",
+ file=texture_file,
+ )
return material
def convert(self, urdf_path: str, mjcf_path: str):
@@ -369,7 +520,7 @@ def convert(self, urdf_path: str, mjcf_path: str):
output_dir,
name=str(idx),
)
- joint = ET.SubElement(body, "joint", attrib={"type": "free"})
+ ET.SubElement(body, "joint", attrib={"type": "free"})
self.add_geometry(
mujoco_asset,
link,
@@ -427,6 +578,37 @@ def convert(self, urdf_path: str, mjcf_path: str, **kwargs) -> str:
output_dir = os.path.dirname(mjcf_path)
os.makedirs(output_dir, exist_ok=True)
+ parent_by_child = {}
+ origin_by_child = {}
+ for joint in root.findall("joint"):
+ joint_type = joint.get("type")
+ if joint_type != "fixed":
+ raise NotImplementedError(
+ f"URDFtoMJCFConverter only supports fixed joints, got "
+ f"{joint_type!r} for joint {joint.get('name')!r}."
+ )
+ parent_by_child[joint.find("child").get("link")] = joint.find(
+ "parent"
+ ).get("link")
+ origin_by_child[joint.find("child").get("link")] = joint.find(
+ "origin"
+ )
+
+ link_world_origins = {}
+
+ def get_link_world_origin(link_name: str) -> ET.Element | None:
+ if link_name in link_world_origins:
+ return link_world_origins[link_name]
+ parent_link = parent_by_child.get(link_name)
+ if parent_link is None:
+ link_world_origins[link_name] = None
+ else:
+ link_world_origins[link_name] = self._compose_origin(
+ get_link_world_origin(parent_link),
+ origin_by_child.get(link_name),
+ )
+ return link_world_origins[link_name]
+
body_dict = {}
for idx, link in enumerate(root.findall("link")):
link_name = link.get("name", f"unnamed_link_{idx}")
@@ -450,6 +632,7 @@ def convert(self, urdf_path: str, mjcf_path: str, **kwargs) -> str:
output_dir,
f"visual_mesh_{idx}",
material,
+ body_origin=get_link_world_origin(link_name),
)
if link.find("collision") is not None:
self.add_geometry(
@@ -461,18 +644,15 @@ def convert(self, urdf_path: str, mjcf_path: str, **kwargs) -> str:
output_dir,
f"collision_mesh_{idx}",
is_collision=True,
+ body_origin=get_link_world_origin(link_name),
)
- # Process joints to set transformations and hierarchy
+ # Fixed-joint world transforms are baked into the mesh vertices above,
+ # so here we only rebuild the body hierarchy (all bodies stay at the
+ # identity frame), keeping the output identical across MuJoCo/Isaac Gym.
for joint in root.findall("joint"):
- joint_type = joint.get("type")
- if joint_type != "fixed":
- logger.warning("Only support fixed joints in conversion now.")
- continue
-
parent_link = joint.find("parent").get("link")
child_link = joint.find("child").get("link")
- origin = joint.find("origin")
if parent_link not in body_dict or child_link not in body_dict:
logger.warning(
f"Parent or child link not found for joint: {joint.get('name')}"
@@ -483,11 +663,6 @@ def convert(self, urdf_path: str, mjcf_path: str, **kwargs) -> str:
mujoco_worldbody.remove(child_body)
parent_body = body_dict[parent_link]
parent_body.append(child_body)
- if origin is not None:
- xyz = origin.get("xyz", "0 0 0")
- rpy = origin.get("rpy", "0 0 0")
- child_body.set("pos", xyz)
- child_body.set("euler", rpy)
tree = ET.ElementTree(mujoco_struct)
ET.indent(tree, space=" ", level=0)
@@ -916,13 +1091,6 @@ def create(
urdf_paths = [
'outputs/EmbodiedGenData/demo_assets/banana/result/banana.urdf',
- 'outputs/EmbodiedGenData/demo_assets/book/result/book.urdf',
- 'outputs/EmbodiedGenData/demo_assets/lamp/result/lamp.urdf',
- 'outputs/EmbodiedGenData/demo_assets/mug/result/mug.urdf',
- 'outputs/EmbodiedGenData/demo_assets/remote_control/result/remote_control.urdf',
- "outputs/EmbodiedGenData/demo_assets/rubik's_cube/result/rubik's_cube.urdf",
- 'outputs/EmbodiedGenData/demo_assets/table/result/table.urdf',
- 'outputs/EmbodiedGenData/demo_assets/vase/result/vase.urdf',
]
if target_asset_type == AssetType.MJCF:
@@ -937,13 +1105,6 @@ def create(
elif target_asset_type == AssetType.USD:
output_files = [
'outputs/EmbodiedGenData/demo_assets/banana/usd/banana.usd',
- 'outputs/EmbodiedGenData/demo_assets/book/usd/book.usd',
- 'outputs/EmbodiedGenData/demo_assets/lamp/usd/lamp.usd',
- 'outputs/EmbodiedGenData/demo_assets/mug/usd/mug.usd',
- 'outputs/EmbodiedGenData/demo_assets/remote_control/usd/remote_control.usd',
- "outputs/EmbodiedGenData/demo_assets/rubik's_cube/usd/rubik's_cube.usd",
- 'outputs/EmbodiedGenData/demo_assets/table/usd/table.usd',
- 'outputs/EmbodiedGenData/demo_assets/vase/usd/vase.usd',
]
asset_converter = AssetConverterFactory.create(
target_type=AssetType.USD,
@@ -979,8 +1140,21 @@ def create(
# # Convert infinigen usdc to physics usdc
# converter = PhysicsUSDAdder()
+ # usd_paths = [
+ # "outputs/gen_rooms_v2/simple/House_seed0/usd/export_scene/export_scene.usdc",
+ # ]
# with converter:
- # converter.convert(
- # usd_path="/home/users/xinjie.wang/xinjie/infinigen/outputs/usdc/export_scene/export_scene.usdc",
- # output_file="/home/users/xinjie.wang/xinjie/infinigen/outputs/usdc_p3/export_scene/export_scene.usdc",
- # )
+ # for usd_path in usd_paths:
+ # output_path = usd_path.replace("/export_scene/", "/export_scene_physics/")
+ # converter.convert(usd_path=usd_path, output_file=output_path)
+
+ # converter = URDFtoMJCFConverter()
+ # urdf_paths = [
+ # "outputs/gen_rooms_v2/simple/House_seed0/urdf/export_scene/scene.urdf",
+
+ # # "outputs/gen_rooms_v2/detail/House_seed0/urdf/export_scene/scene.urdf",
+ # ]
+ # for urdf_path in urdf_paths:
+ # output_path = urdf_path.replace("/urdf/export_scene/", "/mjcf/export_scene/").replace(".urdf", ".xml")
+ # os.makedirs(os.path.dirname(output_path), exist_ok=True)
+ # converter.convert(urdf_path=urdf_path, mjcf_path=output_path)
diff --git a/embodied_gen/data/backproject_v3.py b/embodied_gen/data/backproject_v3.py
index 81cea59..2c053c3 100644
--- a/embodied_gen/data/backproject_v3.py
+++ b/embodied_gen/data/backproject_v3.py
@@ -419,7 +419,7 @@ def parse_args():
parser.add_argument(
"--save_glb_path", type=str, default=None, help="Save glb path."
)
- parser.add_argument("--n_max_faces", type=int, default=30000)
+ parser.add_argument("--n_max_faces", type=int, default=50000)
args, unknown = parser.parse_known_args()
return args
diff --git a/embodied_gen/data/differentiable_render.py b/embodied_gen/data/differentiable_render.py
index 52a8406..870c712 100644
--- a/embodied_gen/data/differentiable_render.py
+++ b/embodied_gen/data/differentiable_render.py
@@ -80,7 +80,11 @@ def create_mp4_from_images(
color = (255, 255, 255)
position = (20, 25)
- with imageio.get_writer(output_path, fps=fps) as writer:
+ with imageio.get_writer(
+ output_path,
+ fps=fps,
+ macro_block_size=1,
+ ) as writer:
for image in images:
image = image.clip(min=0, max=1)
image = (255.0 * image).astype(np.uint8)
@@ -147,6 +151,7 @@ class ImageRender(object):
gen_viewnormal_mp4 (bool, optional): Generate MP4 of view-space normals. Defaults to False.
gen_glonormal_mp4 (bool, optional): Generate MP4 of global-space normals. Defaults to False.
no_index_file (bool, optional): Skip saving index file. Defaults to False.
+ video_fps (int, optional): FPS for generated videos. Defaults to 15.
light_factor (float, optional): PBR light intensity multiplier. Defaults to 1.0.
Example:
@@ -185,7 +190,9 @@ def __init__(
gen_viewnormal_mp4: bool = False,
gen_glonormal_mp4: bool = False,
no_index_file: bool = False,
+ video_fps: int = 15,
light_factor: float = 1.0,
+ metallic: bool = False,
) -> None:
camera = init_kal_camera(camera_params)
self.camera = camera
@@ -218,7 +225,9 @@ def __init__(
self.gen_color_mp4 = gen_color_mp4
self.gen_viewnormal_mp4 = gen_viewnormal_mp4
self.gen_glonormal_mp4 = gen_glonormal_mp4
+ self.video_fps = video_fps
self.light_factor = light_factor
+ self.metallic = metallic
self.no_index_file = no_index_file
def render_mesh(
@@ -313,7 +322,7 @@ def __call__(
create_mp4_from_images(
rendered_normals,
output_path=f"{output_dir}/normal.mp4",
- fps=15,
+ fps=self.video_fps,
prompt=prompt,
)
else:
@@ -325,9 +334,9 @@ def __call__(
data_dict[RenderItems.GLOBAL_NORMAL.value] = render_paths
if RenderItems.VIEW_NORMAL.value in self.render_items:
- assert (
- RenderItems.GLOBAL_NORMAL in self.render_items
- ), f"Must render global normal firstly, got render_items: {self.render_items}." # noqa
+ assert RenderItems.GLOBAL_NORMAL in self.render_items, (
+ f"Must render global normal firstly, got render_items: {self.render_items}."
+ ) # noqa
rendered_view_normals = self.renderer.transform_normal(
rendered_normals, self.mv, masks, to_view=True
)
@@ -336,7 +345,7 @@ def __call__(
create_mp4_from_images(
rendered_view_normals,
output_path=f"{output_dir}/view_normal.mp4",
- fps=15,
+ fps=self.video_fps,
prompt=prompt,
)
else:
@@ -388,7 +397,10 @@ def __call__(
try:
for idx, cam in enumerate(self.camera):
image, albedo, diffuse, _ = render_pbr(
- mesh, cam, light_factor=self.light_factor
+ mesh,
+ cam,
+ light_factor=self.light_factor,
+ metallic=self.metallic,
)
image = torch.cat([image[0], masks[idx]], axis=-1)
images.append(image.detach().cpu().numpy())
@@ -409,14 +421,14 @@ def __call__(
create_gif_from_images(
images,
output_path=f"{output_dir}/color.gif",
- fps=15,
+ fps=self.video_fps,
)
if self.gen_color_mp4:
create_mp4_from_images(
images,
output_path=f"{output_dir}/color.mp4",
- fps=15,
+ fps=self.video_fps,
prompt=prompt,
)
@@ -507,6 +519,12 @@ def parse_args():
default=1.0,
help="Light factor for mesh PBR rendering (default: 1.)",
)
+ parser.add_argument(
+ "--pbr_metallic",
+ action=argparse.BooleanOptionalAction,
+ default=False,
+ help="Whether to keep metallic material properties for mesh PBR rendering (default: True).",
+ )
parser.add_argument(
"--with_mtl",
action="store_true",
@@ -544,6 +562,12 @@ def parse_args():
default=None,
help="Text prompts for the rendering.",
)
+ parser.add_argument(
+ "--video_fps",
+ type=int,
+ default=15,
+ help="FPS for generated video outputs.",
+ )
args, unknown = parser.parse_known_args()
@@ -607,7 +631,9 @@ def entrypoint(**kwargs) -> None:
gen_color_mp4=args.gen_color_mp4,
gen_viewnormal_mp4=args.gen_viewnormal_mp4,
gen_glonormal_mp4=args.gen_glonormal_mp4,
+ video_fps=args.video_fps,
light_factor=args.pbr_light_factor,
+ metallic=args.pbr_metallic,
no_index_file=gen_video or args.no_index_file,
)
image_render.render_mesh(
diff --git a/embodied_gen/data/utils.py b/embodied_gen/data/utils.py
index 74f96c6..b0a2d61 100644
--- a/embodied_gen/data/utils.py
+++ b/embodied_gen/data/utils.py
@@ -21,6 +21,7 @@
import time
import zipfile
from contextlib import contextmanager
+from copy import deepcopy
from dataclasses import dataclass, field
from shutil import rmtree
from typing import List, Tuple, Union
@@ -441,6 +442,37 @@ def save_images(
return save_paths
+def _disable_metallic_for_render(materials):
+ if materials is None:
+ return
+
+ for material in materials:
+ if hasattr(material, "metallic_texture"):
+ material.metallic_texture = None
+ if (
+ hasattr(material, "metallic_value")
+ and material.metallic_value is not None
+ ):
+ if torch.is_tensor(material.metallic_value):
+ material.metallic_value = torch.zeros_like(
+ material.metallic_value
+ )
+ else:
+ material.metallic_value = 0.0
+
+
+def _build_render_materials(mesh, metallic: bool = False):
+ if metallic:
+ return None
+
+ if mesh.materials is None:
+ return None
+
+ render_materials = deepcopy(mesh.materials)
+ _disable_metallic_for_render(render_materials)
+ return render_materials
+
+
def _current_lighting(
azimuths: List[float],
elevations: List[float],
@@ -471,29 +503,59 @@ def _current_lighting(
return light_condition
+def _uniform_lighting(
+ light_factor: float = 1.0,
+ device: str = "cuda",
+ sharpness: float = 0.5,
+ num_lights: int = 1024,
+):
+ indices = torch.arange(num_lights, dtype=torch.float32, device=device)
+ golden_angle = math.pi * (3.0 - math.sqrt(5.0))
+ z = 1.0 - 2.0 * (indices + 0.5) / num_lights
+ radius = torch.sqrt(torch.clamp(1.0 - z * z, min=0.0))
+ theta = golden_angle * indices
+ directions = torch.stack(
+ [
+ radius * torch.cos(theta),
+ radius * torch.sin(theta),
+ z,
+ ],
+ dim=1,
+ )
+ directions = F.normalize(directions, dim=1)
+
+ amplitude = torch.ones_like(directions) * (light_factor / len(directions))
+ light_condition = kal.render.lighting.SgLightingParameters(
+ amplitude=amplitude,
+ direction=directions,
+ sharpness=sharpness,
+ ).to(device)
+
+ return light_condition
+
+
def render_pbr(
mesh,
camera,
device="cuda",
cxt=None,
- custom_materials=None,
light_factor=1.0,
+ metallic: bool = False,
):
if cxt is None:
cxt = dr.RasterizeCudaContext()
- light_condition = _current_lighting(
- azimuths=[0, 90, 180, 270],
- elevations=[90, 60, 30, 20],
+ light_condition = _uniform_lighting(
light_factor=light_factor,
device=device,
)
+ render_materials = _build_render_materials(mesh, metallic)
render_res = kal.render.easy_render.render_mesh(
camera,
mesh,
lighting=light_condition,
nvdiffrast_context=cxt,
- custom_materials=custom_materials,
+ custom_materials=render_materials,
)
image = render_res[kal.render.easy_render.RenderPass.render]
@@ -676,9 +738,9 @@ def import_kaolin_mesh(mesh_path: str, with_mtl: bool = False):
mesh = kal.io.obj.import_mesh(mesh_path, with_materials=with_material)
if with_mtl and mesh.materials and len(mesh.materials) > 0:
material = kal.render.materials.PBRMaterial()
- assert (
- "map_Kd" in mesh.materials[0]
- ), "'map_Kd' not found in materials."
+ assert "map_Kd" in mesh.materials[0], (
+ "'map_Kd' not found in materials."
+ )
material.diffuse_texture = mesh.materials[0]["map_Kd"] / 255.0
mesh.materials = [material]
elif mesh_path.endswith(".ply"):
@@ -727,6 +789,7 @@ def save_mesh_with_mtl(
output_path: str,
material_base=(250, 250, 250, 255),
mesh_process: bool = True,
+ glossiness: float = 250.0,
) -> trimesh.Trimesh:
if isinstance(texture, np.ndarray):
texture = Image.fromarray(texture)
@@ -742,6 +805,8 @@ def save_mesh_with_mtl(
diffuse=material_base,
ambient=material_base,
specular=material_base,
+ # 250 gives a tight visible highlight similar to glossy plastic.
+ glossiness=glossiness,
)
dir_name = os.path.dirname(output_path)
@@ -963,6 +1028,6 @@ def model_device_ctx(
if verbose:
model_names = [m.__class__.__name__ for m in models]
- logger.debug(
+ logger.info(
f"[model_device_ctx] {model_names} to cuda: {to_cuda_time:.1f}s, to cpu: {to_cpu_time:.1f}s"
)
diff --git a/embodied_gen/models/hunyuan3d.py b/embodied_gen/models/hunyuan3d.py
new file mode 100644
index 0000000..21102f8
--- /dev/null
+++ b/embodied_gen/models/hunyuan3d.py
@@ -0,0 +1,957 @@
+# Project EmbodiedGen
+#
+# Copyright (c) 2025 Horizon Robotics. All Rights Reserved.
+#
+# 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.
+
+from __future__ import annotations
+
+import base64
+import hashlib
+import hmac
+import json
+import os
+import re
+import socket
+import time
+import urllib.error
+import urllib.request
+from dataclasses import dataclass
+from datetime import datetime, timezone
+from glob import glob
+from http.client import HTTPSConnection
+from shutil import copy, copytree, rmtree
+from typing import Optional, Tuple
+
+import numpy as np
+import trimesh
+from PIL import Image
+from embodied_gen.data.differentiable_render import (
+ entrypoint as render_pbr_video,
+)
+from embodied_gen.data.utils import delete_dir
+from embodied_gen.utils.gpt_clients import GPT_CLIENT
+from embodied_gen.utils.log import logger
+from embodied_gen.utils.process_media import combine_images_to_grid
+from embodied_gen.utils.tags import VERSION
+from embodied_gen.validators.quality_checkers import (
+ BaseChecker,
+ ImageSegChecker,
+)
+from embodied_gen.validators.urdf_convertor import URDFGenerator
+
+
+@dataclass(frozen=True)
+class HunyuanConfig:
+ """Tencent Hunyuan3D Pro endpoint + timing.
+
+ Defaults match the validated probe in ``outputs/hunyuan3d_api_expert/``.
+ Only the Pro action set is supported.
+ """
+
+ host: str = "ai3d.tencentcloudapi.com"
+ service: str = "ai3d"
+ region: str = "ap-guangzhou"
+ version: str = "2025-05-13"
+ image_action: str = "SubmitHunyuanTo3DProJob"
+ query_action: str = "QueryHunyuanTo3DProJob"
+ result_format: str = "GLB"
+ texture_size: int = 2048
+ connect_timeout: float = 10.0
+ read_timeout: float = 60.0
+ poll_interval: float = 10.0
+ max_wait_seconds: float = 900.0
+ max_download_bytes: int = 512 * 1024 * 1024
+
+
+def load_credentials() -> Tuple[str, str]:
+ """Read Tencent Cloud SecretId/SecretKey from environment.
+
+ Prefers ``TENCENT_SECRET_ID/KEY``; falls back to ``TENCENTCLOUD_*``.
+ Raises ``RuntimeError`` (credential-free message) when missing.
+ """
+ sid = os.environ.get("TENCENT_SECRET_ID") or os.environ.get(
+ "TENCENTCLOUD_SECRET_ID"
+ )
+ skey = os.environ.get("TENCENT_SECRET_KEY") or os.environ.get(
+ "TENCENTCLOUD_SECRET_KEY"
+ )
+ if not sid or not skey:
+ raise RuntimeError(
+ "HUNYUAN3D backend requires Tencent Cloud credentials. Set "
+ "TENCENT_SECRET_ID and TENCENT_SECRET_KEY (or TENCENTCLOUD_*) "
+ "in the environment, e.g. `source .secrets/hunyuan3d.env`."
+ )
+ return sid, skey
+
+
+def _signed_headers(
+ payload: str,
+ action: str,
+ credentials: Tuple[str, str],
+ cfg: HunyuanConfig,
+) -> dict:
+ """Build fresh TC3-HMAC-SHA256 auth headers (re-built every request)."""
+ sid, skey = credentials
+ ts = int(time.time())
+ date = datetime.fromtimestamp(ts, tz=timezone.utc).strftime("%Y-%m-%d")
+ canon_headers = (
+ "content-type:application/json; charset=utf-8\n"
+ f"host:{cfg.host}\n"
+ f"x-tc-action:{action.lower()}\n"
+ )
+ signed = "content-type;host;x-tc-action"
+ canon_req = (
+ f"POST\n/\n\n{canon_headers}\n{signed}\n"
+ f"{hashlib.sha256(payload.encode()).hexdigest()}"
+ )
+ scope = f"{date}/{cfg.service}/tc3_request"
+ string_to_sign = (
+ f"TC3-HMAC-SHA256\n{ts}\n{scope}\n"
+ f"{hashlib.sha256(canon_req.encode()).hexdigest()}"
+ )
+
+ def _sign(key, msg):
+ return hmac.new(key, msg.encode(), hashlib.sha256).digest()
+
+ sd = _sign(("TC3" + skey).encode(), date)
+ ss = _sign(sd, cfg.service)
+ signing = _sign(ss, "tc3_request")
+ sig = hmac.new(
+ signing, string_to_sign.encode(), hashlib.sha256
+ ).hexdigest()
+ return {
+ "Authorization": (
+ f"TC3-HMAC-SHA256 Credential={sid}/{scope}, "
+ f"SignedHeaders={signed}, Signature={sig}"
+ ),
+ "Content-Type": "application/json; charset=utf-8",
+ "Host": cfg.host,
+ "X-TC-Action": action,
+ "X-TC-Timestamp": str(ts),
+ "X-TC-Version": cfg.version,
+ "X-TC-Region": cfg.region,
+ }
+
+
+def _post_signed(
+ payload_obj: dict,
+ action: str,
+ credentials: Tuple[str, str],
+ cfg: HunyuanConfig,
+) -> dict:
+ """POST a Tencent Cloud TC3-signed JSON request and return ``Response``.
+
+ Routes through ``HTTPS_PROXY`` via CONNECT (``HTTPSConnection`` does not
+ honor the env var on its own). Never logs credentials, signed headers,
+ or the request payload (which carries base64 image data).
+ """
+ payload = json.dumps(
+ payload_obj, separators=(",", ":"), ensure_ascii=False
+ )
+ headers = _signed_headers(payload, action, credentials, cfg)
+ # ``http.client.HTTPSConnection`` does NOT auto-honor ``HTTPS_PROXY``
+ # (unlike ``urllib.request.urlopen``); read it explicitly and tunnel
+ # via CONNECT, otherwise direct connections to Tencent Cloud will be
+ # blocked by the corporate egress firewall.
+ proxy = os.environ.get("HTTPS_PROXY") or os.environ.get("HTTP_PROXY")
+ timeout = cfg.connect_timeout + cfg.read_timeout
+ if proxy:
+ from urllib.parse import urlparse
+
+ p = urlparse(proxy)
+ conn = HTTPSConnection(p.hostname, p.port or 80, timeout=timeout)
+ conn.set_tunnel(cfg.host, 443)
+ else:
+ conn = HTTPSConnection(cfg.host, timeout=timeout)
+ try:
+ conn.request("POST", "/", body=payload.encode(), headers=headers)
+ resp = conn.getresponse()
+ status, body = resp.status, resp.read().decode(errors="replace")
+ finally:
+ conn.close()
+
+ if not 200 <= status < 300:
+ raise RuntimeError(
+ f"Hunyuan3D {action} HTTP {status}; len={len(body)}."
+ )
+ try:
+ data = json.loads(body).get("Response", {})
+ except json.JSONDecodeError as exc:
+ raise RuntimeError(
+ f"Hunyuan3D {action} non-JSON (HTTP {status}): {exc}"
+ )
+ err = data.get("Error")
+ if err:
+ raise RuntimeError(
+ f"Hunyuan3D {action} Tencent error: "
+ f"Code={err.get('Code')} Message={err.get('Message')} "
+ f"RequestId={data.get('RequestId')}"
+ )
+ return data
+
+
+def submit_pro_job(
+ image_path: Optional[str] = None,
+ credentials: Tuple[str, str] = None,
+ cfg: HunyuanConfig = None,
+ prompt: Optional[str] = None,
+) -> str:
+ """Submit a Hunyuan3D Pro job, return its ``JobId``.
+
+ Provide exactly one of ``image_path`` (image-to-3D, body field
+ ``ImageBase64``) or ``prompt`` (text-to-3D, body field ``Prompt``).
+ Tencent's ``SubmitHunyuanTo3DProJob`` action is shared between both
+ modes; only the body discriminator differs.
+ """
+ if (image_path is None) == (prompt is None):
+ raise ValueError(
+ "submit_pro_job requires exactly one of image_path or prompt."
+ )
+ if credentials is None or cfg is None:
+ raise ValueError("credentials and cfg are required.")
+
+ payload = {"ResultFormat": cfg.result_format, "EnablePBR": True}
+ if image_path is not None:
+ if not os.path.isfile(image_path):
+ raise FileNotFoundError(
+ f"Hunyuan3D input image missing: {image_path}"
+ )
+ with open(image_path, "rb") as fh:
+ payload["ImageBase64"] = base64.b64encode(fh.read()).decode()
+ mode = "image"
+ else:
+ payload["Prompt"] = prompt
+ mode = "text"
+
+ resp = _post_signed(payload, cfg.image_action, credentials, cfg)
+ job_id = resp.get("JobId")
+ if not job_id:
+ raise RuntimeError(
+ f"Hunyuan3D submit returned no JobId; "
+ f"RequestId={resp.get('RequestId')}."
+ )
+ logger.info(
+ "HUNYUAN3D submit OK (%s): JobId=%s RequestId=%s",
+ mode,
+ job_id,
+ resp.get("RequestId"),
+ )
+ return job_id
+
+
+def wait_for_pro_job(
+ job_id: str,
+ credentials: Tuple[str, str],
+ cfg: HunyuanConfig,
+) -> dict:
+ """Poll the job until DONE; raise on FAIL/unknown/timeout."""
+ deadline = time.time() + cfg.max_wait_seconds
+ last_status = None
+ while True:
+ resp = _post_signed(
+ {"JobId": job_id}, cfg.query_action, credentials, cfg
+ )
+ status = resp.get("Status")
+ if status != last_status:
+ logger.info(
+ "HUNYUAN3D job %s status=%s RequestId=%s",
+ job_id,
+ status,
+ resp.get("RequestId"),
+ )
+ if last_status is None and status in ("WAIT", "RUN"):
+ logger.info(
+ "HUNYUAN3D Pro inference typically takes ~3 minutes; "
+ "polling every %ss.",
+ int(cfg.poll_interval),
+ )
+ last_status = status
+ if status == "DONE":
+ return resp
+ if status == "FAIL":
+ raise RuntimeError(
+ f"Hunyuan3D job {job_id} FAIL: "
+ f"code={resp.get('ErrorCode')} "
+ f"message={resp.get('ErrorMessage')} "
+ f"RequestId={resp.get('RequestId')}."
+ )
+ if status not in ("WAIT", "RUN"):
+ raise RuntimeError(
+ f"Hunyuan3D job {job_id} unknown status={status!r}; "
+ f"RequestId={resp.get('RequestId')}."
+ )
+ if time.time() >= deadline:
+ raise TimeoutError(
+ f"Hunyuan3D job {job_id} did not finish within "
+ f"{cfg.max_wait_seconds}s (last status={status})."
+ )
+ time.sleep(cfg.poll_interval)
+
+
+def _download_url_to_path(url: str, dst: str, cfg: HunyuanConfig) -> int:
+ """Stream ``url`` to ``dst`` with size/timeout caps. Returns bytes written.
+
+ Logs only the host (signed URL paths carry short-lived auth tokens).
+ """
+ from urllib.parse import urlparse
+
+ os.makedirs(os.path.dirname(os.path.abspath(dst)), exist_ok=True)
+ logger.info(
+ "HUNYUAN3D downloading %s from host=%s",
+ os.path.basename(dst),
+ urlparse(url).hostname or "?",
+ )
+ total = 0
+ timeout = cfg.connect_timeout + cfg.read_timeout
+ try:
+ with urllib.request.urlopen(url, timeout=timeout) as resp:
+ if not 200 <= resp.status < 300:
+ raise RuntimeError(
+ f"Hunyuan3D download HTTP {resp.status} for "
+ f"{os.path.basename(dst)}."
+ )
+ with open(dst, "wb") as out:
+ while True:
+ chunk = resp.read(1024 * 1024)
+ if not chunk:
+ break
+ total += len(chunk)
+ if total > cfg.max_download_bytes:
+ raise RuntimeError(
+ f"Hunyuan3D download exceeded "
+ f"{cfg.max_download_bytes} bytes for "
+ f"{os.path.basename(dst)}."
+ )
+ out.write(chunk)
+ except (urllib.error.URLError, socket.timeout) as exc:
+ raise RuntimeError(
+ f"Hunyuan3D download failed for {os.path.basename(dst)}: {exc}"
+ )
+ return total
+
+
+def acquire_pro_glb(
+ image_path: Optional[str] = None,
+ output_dir: str = None,
+ asset_name: str = None,
+ credentials: Tuple[str, str] = None,
+ cfg: HunyuanConfig = None,
+ prompt: Optional[str] = None,
+) -> str:
+ """End-to-end: submit + poll + download GLB into the output dir.
+
+ Provide exactly one of ``image_path`` or ``prompt`` (see
+ :func:`submit_pro_job` for the body-field difference).
+ """
+ if (
+ output_dir is None
+ or asset_name is None
+ or credentials is None
+ or cfg is None
+ ):
+ raise ValueError(
+ "output_dir, asset_name, credentials and cfg are required."
+ )
+ os.makedirs(output_dir, exist_ok=True)
+ glb_path = os.path.join(output_dir, f"{asset_name}.glb")
+
+ job_id = submit_pro_job(
+ image_path=image_path,
+ prompt=prompt,
+ credentials=credentials,
+ cfg=cfg,
+ )
+ resp = wait_for_pro_job(job_id, credentials, cfg)
+
+ files = resp.get("ResultFile3Ds") or []
+ glb_url = next(
+ (
+ f.get("Url")
+ for f in files
+ if (f.get("Type") or "").upper() == "GLB" and f.get("Url")
+ ),
+ None,
+ )
+ if not glb_url:
+ raise RuntimeError(
+ f"Hunyuan3D job {job_id} returned no GLB; "
+ f"RequestId={resp.get('RequestId')}."
+ )
+ _download_url_to_path(glb_url, glb_path, cfg)
+ return glb_path
+
+
+def _texture_array(tex) -> Optional[np.ndarray]:
+ """Return RGB ndarray for a glTF texture, or None if absent/invalid."""
+ if tex is None or not hasattr(tex, "convert"):
+ return None
+ return np.asarray(tex.convert("RGB"))
+
+
+def _save_rgb(arr: Optional[np.ndarray], dst: str, max_edge: int) -> bool:
+ """Save an RGB texture as PNG, capping the longest edge at ``max_edge``."""
+ if arr is None:
+ return False
+ img = Image.fromarray(arr)
+ longest = max(img.size)
+ if longest > max_edge:
+ scale = max_edge / float(longest)
+ img = img.resize(
+ (
+ max(1, int(img.size[0] * scale)),
+ max(1, int(img.size[1] * scale)),
+ ),
+ Image.LANCZOS,
+ )
+ img.save(dst)
+ return True
+
+
+def _bake_scene_transform(
+ scene: trimesh.Scene,
+) -> Tuple[trimesh.Trimesh, np.ndarray, str]:
+ """Apply scene-graph transforms to mesh vertices; return one Trimesh."""
+ if len(scene.graph.nodes_geometry) != 1:
+ parts = []
+ for n in scene.graph.nodes_geometry:
+ xform, gname = scene.graph[n]
+ m = scene.geometry[gname].copy()
+ m.apply_transform(xform)
+ parts.append(m)
+ return trimesh.util.concatenate(parts), np.eye(4), "concatenated"
+ n = next(iter(scene.graph.nodes_geometry))
+ xform, gname = scene.graph[n]
+ mesh = scene.geometry[gname].copy()
+ mesh.apply_transform(xform)
+ return mesh, xform, gname
+
+
+def export_glb_to_obj(
+ glb_path: str,
+ output_dir: str,
+ asset_name: str,
+ texture_size: int = 2048,
+ pre_align_rotation: Optional[np.ndarray] = None,
+) -> str:
+ """Convert a Hunyuan3D Pro GLB into the full-PBR OBJ + MTL + PBR PNGs.
+
+ Bakes the GLB scene transform, optionally applies ``pre_align_rotation``
+ to the vertex array (used by the text-to-3D path, whose endpoint emits
+ a frame rotated 90ยฐ around the up axis relative to the image-to-3D
+ endpoint), recenters to the bbox origin (matching SAM3D's convention
+ of putting the model origin at the geometric center), and writes a
+ Blender-compatible OBJ/MTL referencing 4 PBR PNGs (baseColor /
+ metallic / roughness / normal) plus a ``_pbr_material.json`` metadata
+ sidecar. The source GLB at ``glb_path`` is overwritten with the
+ aligned mesh so downstream steps can reuse it. Returns the OBJ path.
+ """
+ from trimesh.exchange.obj import export_obj
+
+ os.makedirs(output_dir, exist_ok=True)
+ obj_path = os.path.join(output_dir, f"{asset_name}.obj")
+ mtl_path = os.path.join(output_dir, f"{asset_name}.mtl")
+ json_path = os.path.join(output_dir, f"{asset_name}_pbr_material.json")
+
+ scene = trimesh.load(glb_path, force="scene", process=False)
+ mesh, baked_xform, geom_name = _bake_scene_transform(scene)
+ material = getattr(getattr(mesh, "visual", None), "material", None)
+
+ # Align to SAM3D convention: optional pre-rotation (text-to-3D needs a
+ # -90ยฐ around the up axis to share the image-to-3D frame) + recenter to
+ # the bbox origin. Overwrite the source GLB so downstream steps can
+ # reuse the aligned full-PBR mesh without an extra load/export pass.
+ V = np.asarray(mesh.vertices, dtype=np.float32)
+ if pre_align_rotation is not None:
+ V = V @ np.asarray(pre_align_rotation, dtype=np.float32)
+ bbox_center = (V.min(axis=0) + V.max(axis=0)) * 0.5
+ mesh.vertices = V - bbox_center
+ mesh.export(glb_path)
+
+ raw_name = getattr(material, "name", None) or f"{asset_name}_material"
+ material_name = re.sub(r"[^A-Za-z0-9_.-]+", "_", raw_name).strip("._")
+ if not material_name:
+ material_name = f"{asset_name}_material"
+
+ # Write OBJ (rewrite usemtl so it points at our material, after mtllib).
+ obj_text = export_obj(
+ mesh,
+ include_normals=True,
+ include_color=True,
+ include_texture=True,
+ return_texture=False,
+ write_texture=False,
+ mtl_name=os.path.basename(mtl_path),
+ header=(
+ "Exported from Hunyuan3D Pro GLB; "
+ "scene transform baked, recentered to bbox origin"
+ ),
+ )
+ obj_text = re.sub(
+ r"^usemtl\s+.+$",
+ f"usemtl {material_name}",
+ obj_text,
+ flags=re.MULTILINE,
+ )
+ mtllib_line = f"mtllib {os.path.basename(mtl_path)}\n"
+ if f"usemtl {material_name}" not in obj_text:
+ obj_text = obj_text.replace(
+ mtllib_line, f"{mtllib_line}usemtl {material_name}\n", 1
+ )
+ with open(obj_path, "w", encoding="utf-8") as fh:
+ fh.write(obj_text)
+
+ # PBR textures. metallicRoughnessTexture: G=roughness, B=metallic.
+ base_arr = _texture_array(getattr(material, "baseColorTexture", None))
+ mr_arr = _texture_array(
+ getattr(material, "metallicRoughnessTexture", None)
+ )
+ normal_arr = _texture_array(getattr(material, "normalTexture", None))
+ files = {
+ "baseColor": f"{asset_name}_baseColor.png",
+ "metallic": f"{asset_name}_metallic.png",
+ "roughness": f"{asset_name}_roughness.png",
+ "normal": f"{asset_name}_normal.png",
+ }
+ metallic_arr = (
+ np.stack([mr_arr[:, :, 2]] * 3, axis=-1)
+ if mr_arr is not None
+ else None
+ )
+ roughness_arr = (
+ np.stack([mr_arr[:, :, 1]] * 3, axis=-1)
+ if mr_arr is not None
+ else None
+ )
+ saved = {
+ "baseColor": _save_rgb(
+ base_arr,
+ os.path.join(output_dir, files["baseColor"]),
+ texture_size,
+ ),
+ "metallic": _save_rgb(
+ metallic_arr,
+ os.path.join(output_dir, files["metallic"]),
+ texture_size,
+ ),
+ "roughness": _save_rgb(
+ roughness_arr,
+ os.path.join(output_dir, files["roughness"]),
+ texture_size,
+ ),
+ "normal": _save_rgb(
+ normal_arr,
+ os.path.join(output_dir, files["normal"]),
+ texture_size,
+ ),
+ }
+
+ def _factor(attr: str, default: float = 1.0) -> float:
+ v = getattr(material, attr, default)
+ return default if v is None else float(v)
+
+ bc = getattr(material, "baseColorFactor", None)
+ if bc is None:
+ base_factor = [1.0, 1.0, 1.0, 1.0]
+ else:
+ arr = np.asarray(bc, dtype=float).reshape(-1)
+ if arr.max(initial=1.0) > 1.0:
+ arr = arr / 255.0
+ base_factor = [float(arr[0]), float(arr[1]), float(arr[2])]
+ base_factor.append(float(arr[3]) if len(arr) >= 4 else 1.0)
+ metallic_factor = _factor("metallicFactor", 1.0)
+ roughness_factor = _factor("roughnessFactor", 1.0)
+
+ ns = max(1.0, min(1000.0, (1.0 - roughness_factor) * 1000.0))
+ lines = [
+ "# Exported from Hunyuan3D Pro GLB",
+ "# PBR note: glTF metallicRoughnessTexture stores roughness in G "
+ "and metallic in B.",
+ f"newmtl {material_name}",
+ f"Ka {base_factor[0]:.8g} {base_factor[1]:.8g} {base_factor[2]:.8g}",
+ f"Kd {base_factor[0]:.8g} {base_factor[1]:.8g} {base_factor[2]:.8g}",
+ "Ks 0 0 0",
+ f"Ns {ns:.8g}",
+ f"d {base_factor[3]:.8g}",
+ "illum 2",
+ f"Pm {metallic_factor:.8g}",
+ f"Pr {roughness_factor:.8g}",
+ ]
+ if saved["baseColor"]:
+ lines.append(f"map_Kd {files['baseColor']}")
+ if saved["normal"]:
+ lines.append(f"norm {files['normal']}")
+ lines.append(f"bump {files['normal']}")
+ if saved["metallic"]:
+ lines.append(f"map_Pm {files['metallic']}")
+ if saved["roughness"]:
+ lines.append(f"map_Pr {files['roughness']}")
+ with open(mtl_path, "w", encoding="utf-8") as fh:
+ fh.write("\n".join(lines) + "\n")
+
+ metadata = {
+ "source": glb_path,
+ "obj": os.path.basename(obj_path),
+ "mtl": os.path.basename(mtl_path),
+ "material": material_name,
+ "geometry": geom_name,
+ "alignment": "recenter_to_bbox_origin",
+ "bakedTransform": np.asarray(baked_xform).tolist(),
+ "sourceSceneBounds": np.asarray(scene.bounds).tolist(),
+ "exportedObjBounds": np.asarray(mesh.bounds).tolist(),
+ "baseColorFactor": base_factor,
+ "metallicFactor": metallic_factor,
+ "roughnessFactor": roughness_factor,
+ "textureMaxEdge": texture_size,
+ "textures": {k: (files[k] if saved[k] else None) for k in files},
+ }
+ with open(json_path, "w", encoding="utf-8") as fh:
+ fh.write(json.dumps(metadata, indent=2) + "\n")
+
+ return obj_path
+
+
+def _ship_scaled_pbr_artefacts(
+ aligned_glb: str,
+ urdf_path: str,
+ output_root: str,
+ final_mesh_dir: str,
+ asset_name: str,
+) -> None:
+ """Write scaled OBJ companions + GLB with full PBR into ``final_mesh_dir``.
+
+ URDFGen's trimesh roundtrip drops Hunyuan's metallic/roughness/normal
+ maps; we restore PBR fidelity by:
+
+ 1. Inferring the scale factor URDFGen applied by comparing the
+ scaled OBJ's extent with the aligned source GLB's extent (the
+ URDF ```` element stores ``real_height`` instead, which is
+ a different quantity).
+ 2. Loading the aligned full-PBR source GLB, scaling it, and writing
+ it next to URDFGen's OBJ so both share the same scale.
+ 3. Copying the 4 PBR PNGs from ``output_root`` into the mesh dir.
+ 4. Patching URDFGen's ``material.mtl`` so Phong rendering has a
+ visible specular highlight and PBR-aware OBJ importers pick up
+ ``map_Pm`` / ``map_Pr`` / ``norm`` / ``bump`` references.
+ """
+ # Derive the actual scale factor from URDFGen's scaled OBJ rather than
+ # the URDF ```` element (which stores real_height โ a midpoint
+ # value distinct from the geometric scaling factor URDFGen applied).
+ urdfgen_obj = trimesh.load(
+ os.path.join(final_mesh_dir, f"{asset_name}.obj"),
+ force="mesh",
+ process=False,
+ )
+ target_max = float(urdfgen_obj.extents.max())
+
+ scene = trimesh.load(aligned_glb, force="scene", process=False)
+ mesh, _, _ = _bake_scene_transform(scene)
+ V = np.asarray(mesh.vertices, dtype=np.float32)
+ src_max = float((V.max(axis=0) - V.min(axis=0)).max())
+ scale = target_max / src_max if src_max > 1e-9 else 1.0
+ mesh.vertices = V * scale
+ mesh.export(os.path.join(final_mesh_dir, f"{asset_name}.glb"))
+
+ pbr_pngs = {
+ "metallic": f"{asset_name}_metallic.png",
+ "roughness": f"{asset_name}_roughness.png",
+ "normal": f"{asset_name}_normal.png",
+ }
+ base_color_png = f"{asset_name}_baseColor.png"
+ pbr_json = f"{asset_name}_pbr_material.json"
+ for fname in (
+ base_color_png,
+ pbr_pngs["metallic"],
+ pbr_pngs["roughness"],
+ pbr_pngs["normal"],
+ pbr_json,
+ ):
+ src = os.path.join(output_root, fname)
+ if os.path.exists(src):
+ copy(src, os.path.join(final_mesh_dir, fname))
+
+ mtl_path = os.path.join(final_mesh_dir, "material.mtl")
+ if os.path.exists(mtl_path):
+ with open(mtl_path) as fh:
+ mtl_text = fh.read()
+ # trimesh's OBJ exporter writes ``Ks 0 0 0`` + ``Ns 1`` which makes
+ # Blender's OBJ Phong path render the surface as flat matte. Bump
+ # specular and shininess so the OBJ has visible highlights matching
+ # the PBR GLB, then append the PBR texture map references that
+ # PBR-aware OBJ importers (Blender 3.6+, others) will pick up.
+ mtl_text = re.sub(
+ r"^Ks\s.+$", "Ks 0.5 0.5 0.5", mtl_text, flags=re.MULTILINE
+ )
+ mtl_text = re.sub(r"^Ns\s.+$", "Ns 250", mtl_text, flags=re.MULTILINE)
+ if not re.search(r"^illum\s", mtl_text, re.MULTILINE):
+ mtl_text = mtl_text.rstrip() + "\nillum 2\n"
+ extras = []
+ if os.path.exists(os.path.join(final_mesh_dir, pbr_pngs["metallic"])):
+ extras.append(f"map_Pm {pbr_pngs['metallic']}")
+ if os.path.exists(os.path.join(final_mesh_dir, pbr_pngs["roughness"])):
+ extras.append(f"map_Pr {pbr_pngs['roughness']}")
+ if os.path.exists(os.path.join(final_mesh_dir, pbr_pngs["normal"])):
+ extras.append(f"norm {pbr_pngs['normal']}")
+ extras.append(f"bump {pbr_pngs['normal']}")
+ if extras and not any(
+ line in mtl_text for line in ("map_Pm", "map_Pr", "norm ")
+ ):
+ mtl_text = mtl_text.rstrip() + "\n" + "\n".join(extras) + "\n"
+ with open(mtl_path, "w") as fh:
+ fh.write(mtl_text)
+
+
+def _build_asset_attrs(args, idx: int) -> dict:
+ """Build the URDF asset_attrs dict from CLI args."""
+ attrs = {"version": args.version or VERSION}
+ if args.height_range:
+ lo, hi = map(float, args.height_range.split("-"))
+ attrs["min_height"], attrs["max_height"] = lo, hi
+ if args.mass_range:
+ lo, hi = map(float, args.mass_range.split("-"))
+ attrs["min_mass"], attrs["max_mass"] = lo, hi
+ if isinstance(args.asset_type, list) and args.asset_type[idx]:
+ attrs["category"] = args.asset_type[idx]
+ return attrs
+
+
+def _render_color_video(
+ obj_path: str, work_dir: str, filename: str
+) -> Optional[str]:
+ """Render a turntable color mp4 via the shared kaolin renderer.
+
+ Returns the produced mp4 path, or ``None`` on failure (caller logs).
+ """
+ try:
+ # differentiable_render hardcodes mp4 fps=15; 90 frames -> 6s,
+ # matching SAM3D/TRELLIS gs_mesh.mp4 duration.
+ render_pbr_video(
+ mesh_path=obj_path,
+ output_root=work_dir,
+ uuid=[filename],
+ num_images=90,
+ elevation=[20.0],
+ distance=5.0,
+ fov=30.0,
+ with_mtl=True,
+ gen_color_mp4=True,
+ no_index_file=True,
+ )
+ mp4 = os.path.join(work_dir, filename, "color.mp4")
+ return mp4 if os.path.exists(mp4) else None
+ except Exception as exc: # pragma: no cover - rendering is optional
+ logger.warning(f"HUNYUAN3D video render failed: {exc}")
+ return None
+
+
+def _process_glb(
+ args,
+ idx: int,
+ output_root: str,
+ filename: str,
+ cfg: HunyuanConfig,
+ checkers: list,
+ log_label: str,
+ seg_input_pair: Optional[Tuple[str, str]] = None,
+ pre_align_rotation: Optional[np.ndarray] = None,
+) -> str:
+ """GLB-to-result post-processing shared by image and text paths.
+
+ Expects an aligned full-PBR GLB at ``{output_root}/{filename}.glb``.
+ Runs ``export_glb_to_obj`` โ video render โ URDFGen โ PBR fidelity
+ fixup โ single-arg quality checks (skipped when ``checkers`` is empty)
+ โ ``result/`` organization. ``seg_input_pair`` lets the image path
+ feed raw/cond images to ``ImageSegChecker``; text path passes ``None``.
+ ``pre_align_rotation`` (3x3) is folded into the single mesh transform
+ inside ``export_glb_to_obj``, avoiding a separate load/export pass.
+ Returns the result dir path.
+ """
+ export_glb_to_obj(
+ glb_path=os.path.join(output_root, f"{filename}.glb"),
+ output_dir=output_root,
+ asset_name=filename,
+ texture_size=cfg.texture_size,
+ pre_align_rotation=pre_align_rotation,
+ )
+ mesh_obj_path = os.path.join(output_root, f"{filename}.obj")
+
+ video_path = _render_color_video(
+ mesh_obj_path, os.path.join(output_root, "_video"), filename
+ )
+
+ urdf_convertor = URDFGenerator(
+ GPT_CLIENT,
+ render_view_num=4,
+ decompose_convex=not args.disable_decompose_convex,
+ )
+ urdf_root = f"{output_root}/URDF_{filename}"
+ urdf_path = urdf_convertor(
+ mesh_path=mesh_obj_path,
+ output_root=urdf_root,
+ **_build_asset_attrs(args, idx),
+ )
+
+ # Final mesh dir: keep URDFGen's scaled OBJ + collision, restore full
+ # PBR fidelity that URDFGen's simple trimesh roundtrip strips (rescaled
+ # source GLB + PBR map refs appended to material.mtl).
+ final_mesh_dir = f"{urdf_root}/{urdf_convertor.output_mesh_dir}"
+ _ship_scaled_pbr_artefacts(
+ aligned_glb=os.path.join(output_root, f"{filename}.glb"),
+ urdf_path=urdf_path,
+ output_root=output_root,
+ final_mesh_dir=final_mesh_dir,
+ asset_name=filename,
+ )
+
+ # Quality checks: only the single-arg (BaseChecker.validate) ones go
+ # here. Two-arg checkers like TextGenAlignChecker run in the caller.
+ if checkers:
+ render_image_paths = glob(
+ f"{urdf_root}/{urdf_convertor.output_render_dir}/image_color/*.png"
+ )
+ images_list = []
+ for ch in checkers:
+ if isinstance(ch, ImageSegChecker) and seg_input_pair is not None:
+ images_list.append(list(seg_input_pair))
+ else:
+ images_list.append(combine_images_to_grid(render_image_paths))
+ qa_results = BaseChecker.validate(checkers, images_list)
+ urdf_convertor.add_quality_tag(urdf_path, qa_results)
+
+ # Organize result/ (no gs.ply; video.mp4 included when render OK).
+ result_dir = f"{output_root}/result"
+ if os.path.exists(result_dir):
+ rmtree(result_dir, ignore_errors=True)
+ os.makedirs(result_dir, exist_ok=True)
+ copy(urdf_path, f"{result_dir}/{os.path.basename(urdf_path)}")
+ copytree(
+ f"{urdf_root}/{urdf_convertor.output_mesh_dir}",
+ f"{result_dir}/{urdf_convertor.output_mesh_dir}",
+ )
+ if video_path and os.path.exists(video_path):
+ copy(video_path, f"{result_dir}/video.mp4")
+
+ if not args.keep_intermediate:
+ delete_dir(output_root, keep_subs=["result"])
+
+ logger.info(f"Saved results for {log_label} in {result_dir}")
+ return result_dir
+
+
+# Rotation that aligns a Hunyuan3D **text**-to-3D GLB with the **image**-to-3D
+# frame. -90ยฐ around the file-coord up axis (Y), i.e. x' = z, z' = -x.
+# Applied as a single multiplication inside ``export_glb_to_obj`` so the
+# text path does not need a separate GLB load/save pass.
+TEXT_TO_IMAGE_FRAME_ROTATION = np.array(
+ [[0.0, 0.0, -1.0], [0.0, 1.0, 0.0], [1.0, 0.0, 0.0]],
+ dtype=np.float32,
+)
+
+
+def _acquire_or_reuse_glb(
+ output_root: str,
+ filename: str,
+ cfg: HunyuanConfig,
+ hunyuan_credentials: Optional[Tuple[str, str]],
+ *,
+ image_path: Optional[str] = None,
+ prompt: Optional[str] = None,
+) -> None:
+ """Ensure ``{output_root}/{filename}.glb`` exists.
+
+ Reuses an existing GLB at that path (dev fixture short-circuit) or
+ calls :func:`acquire_pro_glb` with ``image_path`` or ``prompt``.
+ """
+ glb_path = os.path.join(output_root, f"{filename}.glb")
+ if os.path.exists(glb_path):
+ logger.info(
+ "HUNYUAN3D reusing existing GLB at %s; skipping Tencent API call.",
+ glb_path,
+ )
+ return
+ creds = hunyuan_credentials or load_credentials()
+ acquire_pro_glb(
+ image_path=image_path,
+ prompt=prompt,
+ output_dir=output_root,
+ asset_name=filename,
+ credentials=creds,
+ cfg=cfg,
+ )
+
+
+def process_image(
+ args,
+ idx: int,
+ image_path: str,
+ output_root: str,
+ filename: str,
+ hunyuan_config: Optional[HunyuanConfig],
+ hunyuan_credentials: Optional[Tuple[str, str]],
+ checkers: list,
+) -> None:
+ """HUNYUAN3D image-to-3D entry: image โ GLB โ export โ URDF โ result/."""
+ cfg = hunyuan_config or HunyuanConfig()
+ _acquire_or_reuse_glb(
+ output_root, filename, cfg, hunyuan_credentials, image_path=image_path
+ )
+ _process_glb(
+ args=args,
+ idx=idx,
+ output_root=output_root,
+ filename=filename,
+ cfg=cfg,
+ checkers=checkers,
+ log_label=image_path,
+ seg_input_pair=(
+ f"{output_root}/{filename}_raw.png",
+ f"{output_root}/{filename}_cond.png",
+ ),
+ )
+
+
+def process_prompt(
+ args,
+ idx: int,
+ prompt: str,
+ output_root: str,
+ filename: str,
+ hunyuan_config: Optional[HunyuanConfig],
+ hunyuan_credentials: Optional[Tuple[str, str]],
+ checkers: list,
+) -> None:
+ """HUNYUAN3D text-to-3D entry: prompt โ GLB โ export โ URDF โ result/.
+
+ Text path skips ``text-to-image`` entirely; ``checkers`` should only
+ contain single-arg (``BaseChecker.validate``-compatible) checkers.
+ Two-arg checkers like ``TextGenAlignChecker`` should be invoked by
+ the caller after this returns.
+ """
+ cfg = hunyuan_config or HunyuanConfig()
+ _acquire_or_reuse_glb(
+ output_root, filename, cfg, hunyuan_credentials, prompt=prompt
+ )
+ # Text endpoint sits 90ยฐ offset around the up axis vs the image
+ # endpoint; fold the alignment rotation into export_glb_to_obj's
+ # single mesh-transform pass to avoid a separate GLB roundtrip.
+ _process_glb(
+ args=args,
+ idx=idx,
+ output_root=output_root,
+ filename=filename,
+ cfg=cfg,
+ checkers=checkers,
+ log_label=f"prompt={prompt!r}",
+ seg_input_pair=None,
+ pre_align_rotation=TEXT_TO_IMAGE_FRAME_ROTATION,
+ )
diff --git a/embodied_gen/models/sam3d.py b/embodied_gen/models/sam3d.py
index 4b28e40..ffb62ad 100644
--- a/embodied_gen/models/sam3d.py
+++ b/embodied_gen/models/sam3d.py
@@ -14,7 +14,7 @@
# implied. See the License for the specific language governing
# permissions and limitations under the License.
-from embodied_gen.utils.monkey_patches import monkey_patch_sam3d
+from embodied_gen.utils.monkey_patch.sam3d import monkey_patch_sam3d
monkey_patch_sam3d()
import os
@@ -30,7 +30,7 @@
current_dir = os.path.dirname(current_file_path)
sys.path.append(os.path.join(current_dir, "../.."))
from loguru import logger
-from thirdparty.sam3d.sam3d_objects.pipeline.inference_pipeline_pointmap import (
+from sam3d_objects.pipeline.inference_pipeline_pointmap import (
InferencePipelinePointMap,
)
@@ -51,6 +51,7 @@ class Sam3dInference:
Args:
local_dir (str): Directory to store or load model weights and configs.
compile (bool): Whether to compile the model for faster inference.
+ device (str): Device to run the model on (e.g., "cuda" or "cpu").
Methods:
merge_mask_to_rgba(image, mask):
@@ -62,7 +63,10 @@ class Sam3dInference:
"""
def __init__(
- self, local_dir: str = "weights/sam-3d-objects", compile: bool = False
+ self,
+ local_dir: str = "weights/sam-3d-objects",
+ compile: bool = False,
+ device: str = "cuda",
) -> None:
if not os.path.exists(local_dir):
snapshot_download("facebook/sam-3d-objects", local_dir=local_dir)
@@ -78,6 +82,7 @@ def __init__(
config["slat_decoder_gs_ckpt_path"] = config.pop(
"slat_decoder_gs_4_ckpt_path", "slat_decoder_gs_4.ckpt"
)
+ config["device"] = device
self.pipeline: InferencePipelinePointMap = instantiate(config)
def merge_mask_to_rgba(
diff --git a/embodied_gen/scripts/affordance_annot/affordance_annot.py b/embodied_gen/scripts/affordance_annot/affordance_annot.py
new file mode 100644
index 0000000..e69803d
--- /dev/null
+++ b/embodied_gen/scripts/affordance_annot/affordance_annot.py
@@ -0,0 +1,313 @@
+# Project EmbodiedGen
+#
+# Copyright (c) 2025 Horizon Robotics. All Rights Reserved.
+#
+# 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.
+
+import dataclasses
+import os
+import sys
+import time
+from dataclasses import dataclass, field
+
+import tyro
+
+if __package__ is None or __package__ == "":
+ sys.path.append(os.path.abspath(os.path.join(os.path.dirname(__file__), "../../..")))
+
+from embodied_gen.utils.general import filter_warnings
+
+filter_warnings()
+
+from embodied_gen.scripts.affordance_annot.eval_grasps import (
+ EvalGraspsConfig,
+ run_eval,
+)
+from embodied_gen.scripts.affordance_annot.gen_grasp import (
+ GraspGenerator,
+ GraspPoseConfig,
+)
+from embodied_gen.scripts.affordance_annot.part_seg import (
+ PartSegConfig,
+ PartSegmenter,
+)
+from embodied_gen.scripts.affordance_annot.partsemantics_annot import (
+ PartSemanticsAnnotator,
+ PartSemanticsAnnotConfig,
+)
+from embodied_gen.utils.log import logger
+
+__all__ = [
+ "AffordanceAnnotConfig",
+ "AffordanceAnnotator",
+ "run_affordance_annot",
+ "entrypoint",
+]
+
+
+@dataclass
+class AffordanceAnnotConfig:
+ urdf_paths: list[str] = field(default_factory=list)
+ output_dirs: list[str] = field(default_factory=list)
+ run_part_seg: bool = True
+ run_partsemantics_annot: bool = True
+ run_grasp: bool = True
+ run_grasp_eval: bool = True
+ part_seg: PartSegConfig = field(default_factory=PartSegConfig)
+ partsemantics: PartSemanticsAnnotConfig = field(
+ default_factory=PartSemanticsAnnotConfig
+ )
+ grasp: GraspPoseConfig = field(default_factory=GraspPoseConfig)
+ grasp_eval: EvalGraspsConfig = field(default_factory=EvalGraspsConfig)
+
+
+class AffordanceAnnotator:
+ def __init__(self, cfg: AffordanceAnnotConfig):
+ self.validate_config(cfg)
+ self.cfg = cfg
+ self.part_segmenter: PartSegmenter | None = None
+ self.partsemantics_annotator: PartSemanticsAnnotator | None = None
+ self.grasp_generator: GraspGenerator | None = None
+ self.asset_timings: list[dict[str, float | str]] = []
+
+ def validate_config(self, cfg: AffordanceAnnotConfig) -> None:
+ if not cfg.urdf_paths:
+ raise ValueError("urdf_paths must be provided.")
+
+ if len(cfg.output_dirs) == 0:
+ cfg.output_dirs = [
+ os.path.join(os.path.dirname(path), "affordance")
+ for path in cfg.urdf_paths
+ ]
+
+ if len(cfg.urdf_paths) != len(cfg.output_dirs):
+ raise ValueError(
+ "urdf_paths and output_dirs must have the same length, "
+ f"got {len(cfg.urdf_paths)} and {len(cfg.output_dirs)}."
+ )
+
+ def sync_batch_stage_config(self, cfg):
+ updates = {"urdf_paths": list(self.cfg.urdf_paths)}
+ if "output_dirs" in {field.name for field in dataclasses.fields(cfg)}:
+ updates["output_dirs"] = list(self.cfg.output_dirs)
+ return dataclasses.replace(cfg, **updates)
+
+ def sync_single_stage_config(self, cfg, urdf_path: str, output_dir: str):
+ updates = {"urdf_paths": [urdf_path]}
+ if "output_dirs" in {field.name for field in dataclasses.fields(cfg)}:
+ updates["output_dirs"] = [output_dir]
+ return dataclasses.replace(cfg, **updates)
+
+ def process_part_seg(self, urdf_path: str, output_dir: str) -> bool:
+ if not self.cfg.run_part_seg:
+ logger.info("Skipping part segmentation stage.")
+ return True
+
+ return bool(self.get_part_segmenter().process(urdf_path, output_dir))
+
+ def process_partsemantics_annot(
+ self, urdf_path: str, output_dir: str
+ ) -> bool:
+ if not self.cfg.run_partsemantics_annot:
+ logger.info("Skipping part semantics annotation stage.")
+ return True
+
+ return bool(
+ self.get_partsemantics_annotator().process(urdf_path, output_dir)
+ )
+
+ def process_grasp(self, urdf_path: str, output_dir: str) -> bool:
+ if not self.cfg.run_grasp:
+ logger.info("Skipping grasp generation stage.")
+ return True
+
+ return bool(self.get_grasp_generator().process(urdf_path))
+
+ def process_grasp_eval(self, urdf_path: str, output_dir: str) -> bool:
+ if not self.cfg.run_grasp_eval:
+ logger.info("Skipping grasp evaluation stage.")
+ return True
+
+ cfg = self.sync_single_stage_config(
+ self.cfg.grasp_eval,
+ urdf_path,
+ output_dir,
+ )
+ run_eval(cfg)
+ return True
+
+ def get_part_segmenter(self) -> PartSegmenter:
+ if self.part_segmenter is None:
+ cfg = self.sync_batch_stage_config(self.cfg.part_seg)
+ self.part_segmenter = PartSegmenter(cfg)
+ return self.part_segmenter
+
+ def get_partsemantics_annotator(self) -> PartSemanticsAnnotator:
+ if self.partsemantics_annotator is None:
+ cfg = self.sync_batch_stage_config(self.cfg.partsemantics)
+ self.partsemantics_annotator = PartSemanticsAnnotator(cfg)
+ return self.partsemantics_annotator
+
+ def get_grasp_generator(self) -> GraspGenerator:
+ if self.grasp_generator is None:
+ cfg = self.sync_batch_stage_config(self.cfg.grasp)
+ self.grasp_generator = GraspGenerator(cfg)
+ return self.grasp_generator
+
+ def run_stage(
+ self,
+ stage_name: str,
+ stage_fn,
+ urdf_path: str,
+ ) -> bool:
+ try:
+ success = bool(stage_fn())
+ except Exception as exc:
+ logger.warning(
+ "{} failed for URDF {}: {}".format(
+ stage_name,
+ urdf_path,
+ exc,
+ )
+ )
+ return False
+
+ if not success:
+ logger.warning(
+ "{} returned failure for URDF {}".format(
+ stage_name,
+ urdf_path,
+ )
+ )
+ return success
+
+ def process_one_urdf(
+ self,
+ urdf_path: str,
+ output_dir: str,
+ index: int,
+ ) -> dict[str, bool]:
+ start_time = time.perf_counter()
+ logger.info(
+ "Starting affordance pipeline for URDF {}/{}: {}".format(
+ index,
+ len(self.cfg.urdf_paths),
+ urdf_path,
+ )
+ )
+ stages = [
+ (
+ "part segmentation",
+ lambda: self.process_part_seg(urdf_path, output_dir),
+ ),
+ (
+ "part semantics annotation",
+ lambda: self.process_partsemantics_annot(urdf_path, output_dir),
+ ),
+ (
+ "grasp generation",
+ lambda: self.process_grasp(urdf_path, output_dir),
+ ),
+ (
+ "grasp evaluation",
+ lambda: self.process_grasp_eval(urdf_path, output_dir),
+ ),
+ ]
+
+ results = {}
+ for stage_name, stage_fn in stages:
+ logger.info(f"Starting affordance stage: {stage_name}")
+ results[stage_name] = self.run_stage(
+ stage_name,
+ stage_fn,
+ urdf_path,
+ )
+ logger.info(
+ "Finished affordance stage: {} ({})".format(
+ stage_name,
+ "success" if results[stage_name] else "failed",
+ )
+ )
+
+ failed_stages = [
+ stage_name for stage_name, success in results.items() if not success
+ ]
+ if failed_stages:
+ logger.warning(
+ "Affordance pipeline finished with failed stages for {}: {}".format(
+ urdf_path,
+ ", ".join(failed_stages),
+ )
+ )
+ else:
+ logger.info(f"Affordance pipeline finished successfully: {urdf_path}")
+
+ elapsed_seconds = time.perf_counter() - start_time
+ self.asset_timings.append(
+ {
+ "urdf_path": urdf_path,
+ "output_dir": output_dir,
+ "elapsed_seconds": elapsed_seconds,
+ }
+ )
+ logger.info(
+ "Affordance pipeline time for {}: {:.2f}s".format(
+ urdf_path,
+ elapsed_seconds,
+ )
+ )
+ return results
+
+ def log_timing_summary(self) -> None:
+ if not self.asset_timings:
+ return
+
+ total_seconds = sum(
+ float(item["elapsed_seconds"]) for item in self.asset_timings
+ )
+ asset_count = len(self.asset_timings)
+ logger.info(
+ "Affordance pipeline total time: {:.2f}s for {} asset(s).".format(
+ total_seconds,
+ asset_count,
+ )
+ )
+ logger.info(
+ "Affordance pipeline average time per asset: {:.2f}s".format(
+ total_seconds / asset_count,
+ )
+ )
+
+ def process(self) -> list[dict[str, bool]]:
+ results = []
+ for idx, (urdf_path, output_dir) in enumerate(
+ zip(self.cfg.urdf_paths, self.cfg.output_dirs),
+ start=1,
+ ):
+ results.append(self.process_one_urdf(urdf_path, output_dir, idx))
+ self.log_timing_summary()
+ return results
+
+
+def run_affordance_annot(cfg: AffordanceAnnotConfig) -> None:
+ annotator = AffordanceAnnotator(cfg)
+ annotator.process()
+
+
+def entrypoint(*args, **kwargs) -> None:
+ cfg = tyro.cli(AffordanceAnnotConfig)
+ run_affordance_annot(cfg)
+
+
+if __name__ == "__main__":
+ entrypoint()
diff --git a/embodied_gen/scripts/affordance_annot/eval_grasps.py b/embodied_gen/scripts/affordance_annot/eval_grasps.py
new file mode 100644
index 0000000..0acb3c4
--- /dev/null
+++ b/embodied_gen/scripts/affordance_annot/eval_grasps.py
@@ -0,0 +1,1287 @@
+import concurrent.futures
+import dataclasses
+import os
+import threading
+import time
+import warnings
+from collections import defaultdict
+from dataclasses import dataclass, field
+
+warnings.filterwarnings("ignore", category=UserWarning)
+
+from typing import Literal
+
+import imageio
+import numpy as np
+import sapien.core as sapien
+import sapien.physx as physx
+import torch
+import tyro
+from PIL import Image, ImageColor
+from scipy.spatial.transform import Rotation as R
+from tqdm import tqdm
+from embodied_gen.data.utils import DiffrastRender
+from embodied_gen.utils.geometry import MeshInfo
+from embodied_gen.utils.io_utils import (
+ URDFFile,
+ load_json,
+ load_mesh_info,
+ write_json,
+)
+from embodied_gen.utils.log import logger
+
+COLORMAP = list(set(ImageColor.colormap.values()))
+COLOR_PALETTE = np.array(
+ [ImageColor.getrgb(c) for c in COLORMAP], dtype=np.uint8
+)
+MAX_NUM_WORKERS = 10
+MAX_GRASPS_PER_WORKER_PROCESS = 8
+SAPIEN_DEFAULT_STATIC_FRICTION = 0.3
+SAPIEN_DEFAULT_DYNAMIC_FRICTION = 0.3
+SAPIEN_DEFAULT_RESTITUTION = 0.1
+
+_EVALUATOR_KEEPALIVE: list["EvalGrasps"] = []
+_EVALUATOR_KEEPALIVE_LOCK = threading.Lock()
+
+__all__ = [
+ "EvalGraspsConfig",
+ "GripperControlInfo",
+ "SapienSceneManager",
+ "EvalGrasps",
+ "run_eval",
+ "entrypoint",
+]
+
+
+@dataclass
+class EvalGraspsConfig:
+ urdf_paths: list[str] = field(default_factory=list)
+ gripper_name: str = "franka_panda"
+ gripper_urdf_path: str = (
+ f"embodied_gen/scripts/affordance_annot/{gripper_name}/gripper.urdf"
+ )
+ output_dirs: list[str] = field(default_factory=list)
+ sim_freq: int = 240
+ video_fps: int = 30
+ render_interval: int = 24
+ image_hw: tuple[int, int] = (512, 512)
+ num_cameras: int = 1
+ fovy_deg: float = 45.0
+ sim_backend: str = "cpu"
+ ray_tracing: bool = False
+ gripper_move_speed: float = 0.1
+ gripper_close_speed: float = 0.01
+ action_wait_seconds: float = 0.5
+ lift_height: float = 0.5
+ sweep_distance: float = 0.5
+ num_workers: int | None = None
+ max_grasps_per_worker_process: int = MAX_GRASPS_PER_WORKER_PROCESS
+ gripper_drive_stiffness: float = 2500.0
+ gripper_static_friction: float = SAPIEN_DEFAULT_STATIC_FRICTION
+ gripper_dynamic_friction: float = SAPIEN_DEFAULT_DYNAMIC_FRICTION
+ gripper_restitution: float = SAPIEN_DEFAULT_RESTITUTION
+ root_drive_stiffness: float = 1e6
+ root_drive_damping: float = 1e4
+ object_bottom_clearance: float = 0.1
+ hold_object_during_close: bool = True
+ slip_translation_threshold: float = 0.05
+ slip_rotation_threshold_deg: float = 30.0
+ check_close_slip: bool = True
+ debug_mode: bool = False
+ save_failed_grasps: bool = False
+
+
+@dataclass
+class GripperControlInfo:
+ open_qpos: float
+ close_qpos: float
+ drive_damping: float
+ force_limit: float
+
+
+class GripperActionAgent:
+ def __init__(
+ self,
+ scene_manager: "SapienSceneManager",
+ gripper: physx.PhysxArticulation,
+ root_target: sapien.Entity,
+ root_drive: physx.PhysxDriveComponent,
+ ) -> None:
+ self.scene_manager = scene_manager
+ self.gripper = gripper
+ self.root_target = root_target
+ self.root_drive = root_drive
+
+ def set_action(self, action: np.ndarray | torch.Tensor) -> None:
+ if isinstance(action, torch.Tensor):
+ values = action.detach().cpu().numpy().reshape(-1)
+ else:
+ values = np.asarray(action).reshape(-1)
+ if values.size != 8:
+ raise ValueError(
+ f"Gripper action must have 8 values, got {values.size}."
+ )
+ self.root_target.set_pose(
+ sapien.Pose(
+ p=values[:3].astype(np.float64),
+ q=values[3:7].astype(np.float64),
+ )
+ )
+ self.scene_manager.set_gripper_target(
+ self.gripper,
+ float(values[7]),
+ )
+
+
+class SapienSceneManager:
+ def __init__(
+ self, sim_freq: int, ray_tracing: bool, device: str = "cpu"
+ ) -> None:
+ self.sim_freq = sim_freq
+ self.ray_tracing = ray_tracing
+ self.device = device
+ self.renderer = sapien.SapienRenderer()
+ self.scene = self.setup_scene()
+ self.cameras: list[sapien.render.RenderCameraComponent] = []
+ self.actors: dict[str, sapien.pysapien.Entity] = {}
+
+ def setup_scene(self) -> sapien.Scene:
+ # Ray tracing settings
+ if self.ray_tracing:
+ sapien.render.set_camera_shader_dir("rt")
+ sapien.render.set_ray_tracing_samples_per_pixel(64)
+ sapien.render.set_ray_tracing_path_depth(10)
+ sapien.render.set_ray_tracing_denoiser("oidn")
+
+ scene = sapien.Scene()
+ scene.set_timestep(1 / self.sim_freq)
+
+ # Add lighting
+ scene.set_ambient_light([0.2, 0.2, 0.2])
+ scene.add_directional_light(
+ direction=[0, 1, -1],
+ color=[1.5, 1.45, 1.4],
+ shadow=True,
+ shadow_map_size=2048,
+ )
+ scene.add_directional_light(
+ direction=[0, -0.5, 1], color=[0.8, 0.8, 0.85], shadow=False
+ )
+ scene.add_directional_light(
+ direction=[0, -1, 1], color=[1.0, 1.0, 1.0], shadow=False
+ )
+
+ ground_material = self.renderer.create_material()
+ ground_material.base_color = [0.5, 0.5, 0.5, 1] # rgba, gray
+ ground_material.roughness = 0.7
+ ground_material.metallic = 0.0
+ scene.add_ground(0, render_material=ground_material)
+ return scene
+
+ def set_ground_color(self, rgba: list[float]) -> None:
+ """Update the default ground plane material color for this scene."""
+ for actor in self.scene.get_all_actors():
+ if actor.name != "ground":
+ continue
+ for component in actor.components:
+ render_shapes = getattr(component, "render_shapes", None)
+ if render_shapes is None:
+ continue
+ for render_shape in render_shapes:
+ render_shape.material.set_base_color(rgba)
+ return
+ raise ValueError("Ground actor not found in the scene.")
+
+ def create_camera(
+ self,
+ cam_name: str,
+ pose: sapien.Pose,
+ image_hw: tuple[int, int],
+ fovy_deg: float,
+ ) -> sapien.render.RenderCameraComponent:
+ cam_actor = self.scene.create_actor_builder().build_kinematic()
+ cam_actor.set_pose(pose)
+ camera = self.scene.add_mounted_camera(
+ name=cam_name,
+ mount=cam_actor,
+ pose=sapien.Pose(p=[0, 0, 0], q=[1, 0, 0, 0]),
+ width=image_hw[1],
+ height=image_hw[0],
+ fovy=np.deg2rad(fovy_deg),
+ near=0.01,
+ far=100,
+ )
+ self.cameras.append(camera)
+ return camera
+
+ def add_ring_cameras(
+ self,
+ num_cameras: int,
+ radius: float,
+ height: float,
+ target_pt: list[float],
+ image_hw: tuple[int, int],
+ fovy_deg: float,
+ ) -> list[sapien.render.RenderCameraComponent]:
+ angle_step = 2 * np.pi / num_cameras
+ world_up_vec = np.array([0.0, 0.0, 1.0])
+ target_pt = np.array(target_pt)
+
+ for i in range(num_cameras):
+ angle = i * angle_step
+ cam_x = target_pt[0] + radius * np.cos(angle)
+ cam_y = target_pt[1] + radius * np.sin(angle)
+ cam_z = height
+ eye_pos = [cam_x, cam_y, cam_z]
+
+ forward_vec = target_pt - eye_pos
+ forward_vec = forward_vec / np.linalg.norm(forward_vec)
+ temp_right_vec = np.cross(forward_vec, world_up_vec)
+
+ if np.linalg.norm(temp_right_vec) < 1e-6:
+ temp_right_vec = np.array([1.0, 0.0, 0.0])
+ if np.abs(np.dot(temp_right_vec, forward_vec)) > 0.99:
+ temp_right_vec = np.array([0.0, 1.0, 0.0])
+
+ right_vec = temp_right_vec / np.linalg.norm(temp_right_vec)
+ up_vec = np.cross(right_vec, forward_vec)
+ rotation_matrix = np.array([forward_vec, -right_vec, up_vec]).T
+
+ rot = R.from_matrix(rotation_matrix)
+ scipy_quat = rot.as_quat() # (x, y, z, w)
+ quat = [
+ scipy_quat[3],
+ scipy_quat[0],
+ scipy_quat[1],
+ scipy_quat[2],
+ ] # (w, x, y, z)
+
+ self.create_camera(
+ f"camera_{i}",
+ sapien.Pose(p=eye_pos, q=quat),
+ image_hw,
+ fovy_deg,
+ )
+
+ return self.cameras
+
+ def add_actor(
+ self,
+ mesh_info: MeshInfo,
+ position: list[float],
+ ) -> sapien.Entity:
+ builder = self.scene.create_actor_builder()
+ material = physx.PhysxMaterial(
+ static_friction=float(
+ np.clip(mesh_info.static_friction, 0.1, 0.7)
+ ),
+ dynamic_friction=float(
+ np.clip(mesh_info.dynamic_friction, 0.1, 0.6)
+ ),
+ restitution=0.05,
+ )
+ builder.set_physx_body_type("dynamic")
+ builder.add_multiple_convex_collisions_from_file(
+ mesh_info.collision_mesh_path,
+ scale=mesh_info.collision_mesh_scale,
+ pose=mesh_info.collision_local_pose,
+ material=material,
+ )
+ builder.add_visual_from_file(
+ mesh_info.visual_mesh_path,
+ scale=mesh_info.visual_mesh_scale,
+ pose=mesh_info.visual_local_pose,
+ )
+
+ builder.set_initial_pose(sapien.Pose(p=position))
+ actor = builder.build(name=mesh_info.actor_name)
+ if mesh_info.mass is not None and hasattr(
+ actor.components[1], "set_mass"
+ ):
+ actor.components[1].set_mass(mesh_info.mass)
+ return actor
+
+ def add_gripper(
+ self,
+ gripper_urdf_path: str,
+ root_pose: sapien.Pose,
+ open_qpos: float,
+ drive_stiffness: float,
+ drive_damping: float,
+ force_limit: float,
+ static_friction: float,
+ dynamic_friction: float,
+ restitution: float,
+ root_drive_stiffness: float,
+ root_drive_damping: float,
+ ) -> tuple[
+ physx.PhysxArticulation,
+ sapien.Entity,
+ physx.PhysxDriveComponent,
+ ]:
+ loader = self.scene.create_urdf_loader()
+ loader.fix_root_link = False
+ gripper = loader.load(gripper_urdf_path)
+ gripper.set_root_pose(root_pose)
+ self.set_articulation_contact_material(
+ gripper,
+ physx.PhysxMaterial(
+ static_friction,
+ dynamic_friction,
+ restitution,
+ ),
+ )
+ for link in gripper.get_links():
+ link.set_disable_gravity(True)
+
+ root_target = self.scene.create_actor_builder().build_kinematic(
+ name="gripper_root_target"
+ )
+ root_target.set_pose(root_pose)
+ root_drive = self.scene.create_drive(
+ root_target,
+ sapien.Pose(),
+ gripper.get_root(),
+ sapien.Pose(),
+ )
+ root_drive.set_limit_x(
+ 0.0, 0.0, root_drive_stiffness, root_drive_damping
+ )
+ root_drive.set_limit_y(
+ 0.0, 0.0, root_drive_stiffness, root_drive_damping
+ )
+ root_drive.set_limit_z(
+ 0.0, 0.0, root_drive_stiffness, root_drive_damping
+ )
+ root_drive.set_limit_twist(
+ 0.0, 0.0, root_drive_stiffness, root_drive_damping
+ )
+ root_drive.set_limit_cone(
+ 0.0, 0.0, root_drive_stiffness, root_drive_damping
+ )
+ root_drive.set_drive_property_x(
+ root_drive_stiffness, root_drive_damping
+ )
+ root_drive.set_drive_property_y(
+ root_drive_stiffness, root_drive_damping
+ )
+ root_drive.set_drive_property_z(
+ root_drive_stiffness, root_drive_damping
+ )
+ root_drive.set_drive_property_slerp(
+ root_drive_stiffness,
+ root_drive_damping,
+ )
+ root_drive.set_inv_mass_scales(0.0, 1.0)
+
+ qpos = []
+ for joint in gripper.get_active_joints():
+ joint.set_drive_properties(
+ drive_stiffness,
+ drive_damping,
+ force_limit,
+ )
+ joint.set_friction(0.1)
+ limit = np.asarray(joint.get_limits()).reshape(-1, 2)[0]
+ target = float(np.clip(open_qpos, limit[0], limit[1]))
+ joint.set_drive_target(target)
+ qpos.append(target)
+ if qpos:
+ gripper.set_qpos(qpos)
+ gripper.set_qvel(np.zeros(len(qpos), dtype=np.float32))
+ return gripper, root_target, root_drive
+
+ @staticmethod
+ def set_articulation_contact_material(
+ articulation: physx.PhysxArticulation,
+ material: physx.PhysxMaterial,
+ ) -> None:
+ for link in articulation.get_links():
+ for shape in link.get_collision_shapes():
+ shape.set_physical_material(material)
+
+ def set_gripper_target(
+ self,
+ gripper: physx.PhysxArticulation,
+ finger_qpos: float,
+ ) -> None:
+ for joint in gripper.get_active_joints():
+ limit = np.asarray(joint.get_limits()).reshape(-1, 2)[0]
+ target = float(np.clip(finger_qpos, limit[0], limit[1]))
+ joint.set_drive_target(target)
+
+ def render_images(
+ self,
+ camera,
+ render_keys: list[
+ Literal[
+ "Color",
+ "Segmentation",
+ "Normal",
+ "Mask",
+ "Depth",
+ "Foreground",
+ ]
+ ] = None,
+ ) -> dict[str, Image.Image]:
+ if render_keys is None:
+ render_keys = [
+ "Color",
+ "Segmentation",
+ "Normal",
+ "Mask",
+ "Depth",
+ "Foreground",
+ ]
+
+ results: dict[str, Image.Image] = {}
+ if "Color" in render_keys:
+ color = camera.get_picture("Color")
+ color_rgb = (np.clip(color[..., :3], 0, 1) * 255).astype(np.uint8)
+ results["Color"] = Image.fromarray(color_rgb)
+
+ if "Mask" in render_keys:
+ alpha = (np.clip(color[..., 3], 0, 1) * 255).astype(np.uint8)
+ results["Mask"] = Image.fromarray(alpha)
+
+ if "Segmentation" in render_keys:
+ seg_labels = camera.get_picture("Segmentation")
+ label0 = seg_labels[..., 0].astype(np.uint8)
+ seg_color = COLOR_PALETTE[label0]
+ results["Segmentation"] = Image.fromarray(seg_color)
+
+ if "Foreground" in render_keys:
+ seg_labels = camera.get_picture("Segmentation")
+ label0 = seg_labels[..., 0]
+ mask = np.where((label0 > 1), 255, 0).astype(np.uint8)
+ color = camera.get_picture("Color")
+ color_rgb = (np.clip(color[..., :3], 0, 1) * 255).astype(np.uint8)
+ foreground = np.concatenate([color_rgb, mask[..., None]], axis=-1)
+ results["Foreground"] = Image.fromarray(foreground)
+
+ if "Normal" in render_keys:
+ normal = camera.get_picture("Normal")[..., :3]
+ normal_img = (((normal + 1) / 2) * 255).astype(np.uint8)
+ results["Normal"] = Image.fromarray(normal_img)
+
+ if "Depth" in render_keys:
+ position_map = camera.get_picture("Position")
+ depth = -position_map[..., 2]
+ alpha = torch.tensor(color[..., 3], dtype=torch.float32)
+ norm_depth = DiffrastRender.normalize_map_by_mask(
+ torch.tensor(depth), alpha
+ )
+ depth_img = (norm_depth * 255).to(torch.uint8).numpy()
+ results["Depth"] = Image.fromarray(depth_img)
+
+ return results
+
+ def step_action(
+ self,
+ agent: GripperActionAgent | None,
+ action: np.ndarray | torch.Tensor | None,
+ render_keys: list[str],
+ sim_steps_per_control: int = 1,
+ render: bool = True,
+ ) -> dict:
+ if action is not None:
+ agent.set_action(action)
+ frames = defaultdict(defaultdict)
+ for _ in range(sim_steps_per_control):
+ self.scene.step()
+ if not render:
+ return frames
+ self.scene.update_render()
+ for camera in self.cameras:
+ camera.take_picture()
+ images = self.render_images(camera, render_keys=render_keys)
+ frames[camera.name].update(images)
+ return frames
+
+
+class EvalGrasps:
+ def __init__(
+ self,
+ cfg: EvalGraspsConfig,
+ mesh_info: MeshInfo,
+ gripper_control: GripperControlInfo,
+ grasp: dict,
+ ) -> None:
+ self.cfg = cfg
+ self.mesh_info = mesh_info
+ self.gripper_control = gripper_control
+ self.grasp = grasp
+ self.init_scene()
+ if self.cfg.debug_mode:
+ self.setup_cameras(mesh_info)
+ self.object_actor = self.add_object(mesh_info)
+
+ self.frames = {}
+ self.step_index = 0
+ self.world_x_offset = np.array(
+ [self.cfg.sweep_distance, 0.0, 0.0],
+ dtype=np.float64,
+ )
+ self.world_y_offset = np.array(
+ [0.0, self.cfg.sweep_distance, 0.0],
+ dtype=np.float64,
+ )
+
+ def should_render(self) -> bool:
+ return (
+ self.cfg.debug_mode
+ and self.step_index % max(1, self.cfg.render_interval) == 0
+ )
+
+ def init_scene(self) -> None:
+ self.scene_manager = SapienSceneManager(
+ sim_freq=self.cfg.sim_freq,
+ ray_tracing=self.cfg.ray_tracing,
+ device=self.cfg.sim_backend,
+ )
+ if self.cfg.debug_mode:
+ self.scene_manager.set_ground_color([0.78, 0.90, 0.72, 1.0])
+
+ def setup_cameras(self, mesh_info: MeshInfo) -> None:
+ bounds = mesh_info.transformed_mesh.bounds.copy()
+
+ mesh_size = bounds[1] - bounds[0]
+ mesh_height = max(float(mesh_size[2]), 0.5)
+ target_pt = [
+ float((bounds[0, 0] + bounds[1, 0]) * 0.5),
+ float((bounds[0, 1] + bounds[1, 1]) * 0.5),
+ float(bounds[1, 2]),
+ ]
+
+ aspect = self.cfg.image_hw[1] / self.cfg.image_hw[0]
+ half_vfov = np.deg2rad(self.cfg.fovy_deg) * 0.5
+ half_hfov = np.arctan(np.tan(half_vfov) * aspect)
+ half_min_fov = min(half_vfov, half_hfov)
+ framing_half_extents = np.array(
+ [
+ max(float(mesh_size[0]) * 0.5, 1e-6),
+ max(float(mesh_size[1]) * 0.5, 1e-6),
+ mesh_height,
+ ],
+ dtype=np.float64,
+ )
+ camera_radius = max(
+ float(
+ np.linalg.norm(framing_half_extents)
+ / np.sin(half_min_fov)
+ * 1.5
+ ),
+ 2.5,
+ )
+ camera_height = target_pt[2]
+
+ self.scene_manager.add_ring_cameras(
+ num_cameras=self.cfg.num_cameras,
+ radius=camera_radius,
+ height=camera_height,
+ target_pt=target_pt,
+ image_hw=self.cfg.image_hw,
+ fovy_deg=self.cfg.fovy_deg,
+ )
+
+ def add_object(
+ self,
+ mesh_info: MeshInfo,
+ ) -> sapien.Entity:
+ spawn_z = self.cfg.object_bottom_clearance - float(
+ mesh_info.transformed_mesh.bounds[0, 2]
+ )
+ actor = self.scene_manager.add_actor(mesh_info, [0.0, 0.0, spawn_z])
+ self.set_object_gravity(not self.cfg.hold_object_during_close, actor)
+ return actor
+
+ def set_object_gravity(
+ self,
+ enabled: bool,
+ actor: sapien.Entity | None = None,
+ ) -> None:
+ actor = self.object_actor if actor is None else actor
+ for component in actor.components:
+ set_disable_gravity = getattr(
+ component,
+ "set_disable_gravity",
+ None,
+ )
+ if set_disable_gravity is not None:
+ set_disable_gravity(not enabled)
+ if enabled:
+ wake_up = getattr(component, "wake_up", None)
+ if wake_up is not None:
+ wake_up()
+
+ def run_actions(
+ self,
+ agent: GripperActionAgent,
+ actions: np.ndarray,
+ ) -> None:
+ for action in actions:
+ this_frames = self.scene_manager.step_action(
+ agent=agent,
+ action=action,
+ render_keys=["Color"],
+ sim_steps_per_control=1,
+ render=self.should_render(),
+ )
+ self.update_frames(this_frames)
+ self.step_index += 1
+
+ @staticmethod
+ def make_pose(
+ position: np.ndarray,
+ quat_wxyz: np.ndarray,
+ ) -> sapien.Pose:
+ return sapien.Pose(
+ p=[float(v) for v in position],
+ q=[float(v) for v in quat_wxyz],
+ )
+
+ @staticmethod
+ def pose_matrix(pose: sapien.Pose) -> np.ndarray:
+ quat_wxyz = np.asarray(pose.q, dtype=np.float64)
+ quat_norm = np.linalg.norm(quat_wxyz)
+ if quat_norm <= 0.0:
+ raise ValueError("Pose quaternion has zero norm.")
+ quat_wxyz = quat_wxyz / quat_norm
+
+ matrix = np.eye(4, dtype=np.float64)
+ matrix[:3, :3] = R.from_quat(
+ [
+ quat_wxyz[1],
+ quat_wxyz[2],
+ quat_wxyz[3],
+ quat_wxyz[0],
+ ]
+ ).as_matrix()
+ matrix[:3, 3] = np.asarray(pose.p, dtype=np.float64)
+ return matrix
+
+ @staticmethod
+ def pose_delta(
+ reference_relative_pose: np.ndarray,
+ current_relative_pose: np.ndarray,
+ ) -> dict[str, float]:
+ delta_pose = (
+ np.linalg.inv(reference_relative_pose) @ current_relative_pose
+ )
+ translation_m = float(np.linalg.norm(delta_pose[:3, 3]))
+ rotation_rad = float(R.from_matrix(delta_pose[:3, :3]).magnitude())
+ return {
+ "translation_m": translation_m,
+ "rotation_rad": rotation_rad,
+ "rotation_deg": float(np.rad2deg(rotation_rad)),
+ }
+
+ def object_rel_pose(
+ self,
+ agent: GripperActionAgent,
+ ) -> np.ndarray:
+ gripper_pose = self.pose_matrix(agent.gripper.get_root_pose())
+ object_pose = self.pose_matrix(self.object_actor.get_pose())
+ return np.linalg.inv(gripper_pose) @ object_pose
+
+ def object_bottom_z(self) -> float:
+ object_pose = self.pose_matrix(self.object_actor.get_pose())
+ vertices = np.asarray(self.mesh_info.transformed_mesh.vertices)
+ world_vertices = (object_pose[:3, :3] @ vertices.T).T + object_pose[
+ :3, 3
+ ]
+ return float(np.min(world_vertices[:, 2]))
+
+ def check_slip(
+ self,
+ agent: GripperActionAgent,
+ reference_relative_pose: np.ndarray,
+ ) -> dict[str, float | bool]:
+ delta = self.pose_delta(
+ reference_relative_pose,
+ self.object_rel_pose(agent),
+ )
+ slipped = (
+ delta["translation_m"] > self.cfg.slip_translation_threshold
+ or delta["rotation_deg"] > self.cfg.slip_rotation_threshold_deg
+ )
+ return {
+ **delta,
+ "slipped": slipped,
+ }
+
+ def checked_action(
+ self,
+ step_name: str,
+ agent: GripperActionAgent,
+ action_fn,
+ *,
+ check_slip: bool = True,
+ ) -> dict:
+ reference_relative_pose = self.object_rel_pose(agent)
+ action_fn()
+ self.wait_scene(self.cfg.action_wait_seconds)
+ delta = self.check_slip(
+ agent,
+ reference_relative_pose,
+ )
+ slipped = bool(delta["slipped"])
+ success = not slipped if check_slip else True
+ return {
+ "step": step_name,
+ "success": success,
+ "checked": check_slip,
+ "slipped": slipped,
+ "reason": None if success else "relative_pose_slip",
+ "relative_pose_delta": {
+ "translation_m": delta["translation_m"],
+ "rotation_rad": delta["rotation_rad"],
+ "rotation_deg": delta["rotation_deg"],
+ },
+ }
+
+ def get_steps(
+ self,
+ distance: float,
+ speed: float,
+ ) -> int:
+ if distance <= 0.0:
+ return 2
+ duration = distance / speed
+ return max(2, int(np.ceil(duration * self.cfg.sim_freq)))
+
+ def move_to_pose(
+ self,
+ agent: GripperActionAgent,
+ start_pose: sapien.Pose,
+ end_pose: sapien.Pose,
+ finger_qpos: float | None = None,
+ ) -> None:
+ if finger_qpos is None:
+ finger_qpos = self.gripper_control.close_qpos
+
+ start_pos = np.asarray(start_pose.p, dtype=np.float64)
+ end_pos = np.asarray(end_pose.p, dtype=np.float64)
+ travel_distance = float(np.linalg.norm(end_pos - start_pos))
+ num_steps = self.get_steps(
+ travel_distance,
+ self.cfg.gripper_move_speed,
+ )
+ alphas = np.linspace(0.0, 1.0, num_steps)
+ quat = np.asarray(start_pose.q, dtype=np.float64)
+
+ actions = []
+ for alpha in alphas:
+ pose = self.make_pose(
+ (1.0 - alpha) * start_pos + alpha * end_pos,
+ quat,
+ )
+ actions.append(self.make_action(pose, finger_qpos))
+ self.run_actions(agent, np.stack(actions, axis=0))
+
+ def move_gripper(
+ self,
+ agent: GripperActionAgent,
+ offset: np.ndarray,
+ ) -> None:
+ start_pose = agent.gripper.get_root_pose()
+ end_pose = self.make_pose(
+ np.asarray(start_pose.p, dtype=np.float64)
+ + np.asarray(offset, dtype=np.float64),
+ np.asarray(start_pose.q, dtype=np.float64),
+ )
+ self.move_to_pose(agent, start_pose, end_pose)
+
+ def init_grasp_pose(self) -> sapien.Pose:
+ orientation = self.grasp["orientation"]
+ self.grasp = sapien.Pose(
+ p=[float(v) for v in self.grasp["position"]],
+ q=[
+ float(orientation["w"]),
+ float(orientation["xyz"][0]),
+ float(orientation["xyz"][1]),
+ float(orientation["xyz"][2]),
+ ],
+ )
+ gripper_pose = self.object_actor.get_pose() * self.grasp
+ return gripper_pose
+
+ def make_action(
+ self,
+ pose: sapien.Pose,
+ finger_qpos: float,
+ ) -> np.ndarray:
+ return np.array(
+ [
+ float(pose.p[0]),
+ float(pose.p[1]),
+ float(pose.p[2]),
+ float(pose.q[0]),
+ float(pose.q[1]),
+ float(pose.q[2]),
+ float(pose.q[3]),
+ float(finger_qpos),
+ ],
+ dtype=np.float32,
+ )
+
+ def add_gripper_agent(self) -> GripperActionAgent:
+ gripper_pose = self.init_grasp_pose()
+ gripper, root_target, root_drive = self.scene_manager.add_gripper(
+ self.cfg.gripper_urdf_path,
+ gripper_pose,
+ self.gripper_control.open_qpos,
+ self.cfg.gripper_drive_stiffness,
+ self.gripper_control.drive_damping,
+ self.gripper_control.force_limit,
+ self.cfg.gripper_static_friction,
+ self.cfg.gripper_dynamic_friction,
+ self.cfg.gripper_restitution,
+ self.cfg.root_drive_stiffness,
+ self.cfg.root_drive_damping,
+ )
+ return GripperActionAgent(
+ self.scene_manager,
+ gripper,
+ root_target,
+ root_drive,
+ )
+
+ def update_frames(self, this_frames: dict) -> None:
+ for cam_name, images_list in this_frames.items():
+ if cam_name not in self.frames:
+ self.frames[cam_name] = []
+ self.frames[cam_name].append(np.array(images_list["Color"]))
+
+ def wait_scene(self, seconds: float) -> None:
+ num_steps = int(self.cfg.sim_freq * seconds)
+ for _ in range(num_steps):
+ this_frames = self.scene_manager.step_action(
+ agent=None,
+ action=None,
+ render_keys=["Color"],
+ sim_steps_per_control=1,
+ render=self.should_render(),
+ )
+ self.update_frames(this_frames)
+ self.step_index += 1
+
+ def close_gripper(self, agent: GripperActionAgent) -> None:
+ num_close_steps = self.get_steps(
+ abs(
+ self.gripper_control.open_qpos
+ - self.gripper_control.close_qpos
+ ),
+ self.cfg.gripper_close_speed,
+ )
+ root_pose = agent.gripper.get_root_pose()
+ actions = [
+ self.make_action(
+ root_pose,
+ (1.0 - alpha) * self.gripper_control.open_qpos
+ + alpha * self.gripper_control.close_qpos,
+ )
+ for alpha in np.linspace(0.0, 1.0, max(1, num_close_steps))
+ ]
+ self.run_actions(agent, np.stack(actions, axis=0))
+
+ @staticmethod
+ def summarize_steps(step_results: list[dict]) -> dict:
+ failure = next(
+ (
+ step_result
+ for step_result in step_results
+ if not step_result["success"]
+ ),
+ None,
+ )
+ result = {
+ "success": failure is None,
+ "relative_pose_deltas": {
+ step_result["step"]: step_result["relative_pose_delta"]
+ for step_result in step_results
+ },
+ }
+ if failure is not None:
+ result.update(
+ {
+ "failure_step": failure["step"],
+ "reason": failure["reason"],
+ }
+ )
+ return result
+
+ def run(self) -> dict:
+ step_results = []
+ self.wait_scene(2.0)
+
+ this_agent = self.add_gripper_agent()
+ self.wait_scene(self.cfg.action_wait_seconds)
+
+ # Close the gripper around the object.
+ step_result = self.checked_action(
+ "close_gripper",
+ this_agent,
+ lambda: self.close_gripper(this_agent),
+ check_slip=self.cfg.check_close_slip,
+ )
+ step_results.append(step_result)
+ if not step_result["success"]:
+ return self.summarize_steps(step_results)
+
+ if self.cfg.hold_object_during_close:
+ self.set_object_gravity(True)
+
+ # Lift the grasped object upward.
+ step_result = self.checked_action(
+ "lift_gripper",
+ this_agent,
+ lambda: self.move_gripper(
+ this_agent,
+ np.array(
+ [0.0, 0.0, self.cfg.lift_height],
+ dtype=np.float64,
+ ),
+ ),
+ )
+ step_results.append(step_result)
+ if not step_result["success"]:
+ return self.summarize_steps(step_results)
+
+ shake_start_pose = this_agent.gripper.get_root_pose()
+ shake_end_pose = self.make_pose(
+ np.asarray(shake_start_pose.p, dtype=np.float64)
+ + self.world_x_offset,
+ np.asarray(shake_start_pose.q, dtype=np.float64),
+ )
+
+ # Move the gripper outward along the X axis.
+ step_result = self.checked_action(
+ "move_gripper_out_x",
+ this_agent,
+ lambda: self.move_to_pose(
+ this_agent,
+ shake_start_pose,
+ shake_end_pose,
+ ),
+ )
+ step_results.append(step_result)
+ if not step_result["success"]:
+ return self.summarize_steps(step_results)
+
+ # Move the gripper back from the X-axis offset.
+ step_result = self.checked_action(
+ "move_gripper_back_x",
+ this_agent,
+ lambda: self.move_to_pose(
+ this_agent,
+ this_agent.gripper.get_root_pose(),
+ shake_start_pose,
+ ),
+ )
+ step_results.append(step_result)
+ if not step_result["success"]:
+ return self.summarize_steps(step_results)
+
+ shake_start_pose = this_agent.gripper.get_root_pose()
+ shake_end_pose = self.make_pose(
+ np.asarray(shake_start_pose.p, dtype=np.float64)
+ + self.world_y_offset,
+ np.asarray(shake_start_pose.q, dtype=np.float64),
+ )
+
+ # Move the gripper outward along the Y axis.
+ step_result = self.checked_action(
+ "move_gripper_out_y",
+ this_agent,
+ lambda: self.move_to_pose(
+ this_agent,
+ shake_start_pose,
+ shake_end_pose,
+ ),
+ )
+ step_results.append(step_result)
+ if not step_result["success"]:
+ return self.summarize_steps(step_results)
+
+ # Move the gripper back from the Y-axis offset.
+ step_result = self.checked_action(
+ "move_gripper_back_y",
+ this_agent,
+ lambda: self.move_to_pose(
+ this_agent,
+ this_agent.gripper.get_root_pose(),
+ shake_start_pose,
+ ),
+ )
+ step_results.append(step_result)
+ if not step_result["success"]:
+ return self.summarize_steps(step_results)
+
+ # Lower the grasped object back down.
+ descend_offset = np.array(
+ [0.0, 0.0, 0.01 - self.object_bottom_z()],
+ dtype=np.float64,
+ )
+ step_result = self.checked_action(
+ "descend_gripper",
+ this_agent,
+ lambda: self.move_gripper(this_agent, descend_offset),
+ )
+ step_results.append(step_result)
+ if not step_result["success"]:
+ return self.summarize_steps(step_results)
+
+ return self.summarize_steps(step_results)
+
+ def export_video(self, output_dir: str) -> None:
+ if not self.cfg.debug_mode:
+ return
+ os.makedirs(output_dir, exist_ok=True)
+ for cam_name, this_frames in self.frames.items():
+ this_output_path = os.path.join(output_dir, f"{cam_name}.mp4")
+ imageio.mimsave(
+ this_output_path, this_frames, fps=self.cfg.video_fps
+ )
+
+
+def _validate_config(cfg: EvalGraspsConfig) -> None:
+ if not cfg.urdf_paths:
+ raise ValueError("urdf_paths must be provided.")
+
+ if len(cfg.output_dirs) == 0:
+ cfg.output_dirs = [
+ os.path.join(os.path.dirname(path), "affordance")
+ for path in cfg.urdf_paths
+ ]
+
+ if len(cfg.urdf_paths) != len(cfg.output_dirs):
+ raise ValueError(
+ "urdf_paths and output_dirs must have the same length, "
+ f"got {len(cfg.urdf_paths)} and {len(cfg.output_dirs)}."
+ )
+
+
+def _set_single_config(
+ cfg: EvalGraspsConfig,
+ urdf_path: str,
+ output_dir: str,
+) -> EvalGraspsConfig:
+ return dataclasses.replace(
+ cfg,
+ urdf_paths=[urdf_path],
+ output_dirs=[output_dir],
+ )
+
+
+def _eval_grasp(
+ cfg: EvalGraspsConfig,
+ grasp_id: str,
+ grasp: dict,
+) -> dict:
+ output_dir = cfg.output_dirs[0]
+
+ mesh_info = load_mesh_info(cfg.urdf_paths[0])
+ gripper_control = GripperControlInfo(
+ **URDFFile(cfg.gripper_urdf_path).get_prismatic_joint_control_info()
+ )
+ evaluator = EvalGrasps(cfg, mesh_info, gripper_control, grasp)
+ result = evaluator.run()
+ evaluator.export_video(
+ os.path.join(output_dir, "eval_grasp_renders", grasp_id)
+ )
+ if threading.current_thread() is not threading.main_thread():
+ with _EVALUATOR_KEEPALIVE_LOCK:
+ _EVALUATOR_KEEPALIVE.append(evaluator)
+ return {
+ "grasp_id": grasp_id,
+ "output_dir": output_dir,
+ "simulation_result": result,
+ }
+
+
+def _release_thread_evaluators() -> None:
+ with _EVALUATOR_KEEPALIVE_LOCK:
+ evaluators = list(_EVALUATOR_KEEPALIVE)
+ _EVALUATOR_KEEPALIVE.clear()
+ for evaluator in evaluators:
+ del evaluator
+
+
+def _eval_grasp_task(task: tuple[EvalGraspsConfig, str, dict]) -> dict:
+ cfg, grasp_id, grasp = task
+ return _eval_grasp(cfg, grasp_id, grasp)
+
+
+def _slip_thresholds(cfg: EvalGraspsConfig) -> dict[str, float]:
+ return {
+ "translation_m": cfg.slip_translation_threshold,
+ "rotation_deg": cfg.slip_rotation_threshold_deg,
+ }
+
+
+def _is_successful_grasp_result(result: dict) -> bool:
+ simulation_result = result.get("simulation_result", {})
+ if not isinstance(simulation_result, dict):
+ return False
+ return bool(simulation_result.get("success", False))
+
+
+def _grasp_success_by_id(results: list[dict]) -> dict[str, bool]:
+ return {
+ result["grasp_id"]: _is_successful_grasp_result(result)
+ for result in results
+ }
+
+
+def _build_eval_summary(
+ cfg: EvalGraspsConfig,
+ results: list[dict],
+ elapsed_seconds: float,
+) -> dict:
+ successful_grasp_ids = [
+ result["grasp_id"]
+ for result in results
+ if _is_successful_grasp_result(result)
+ ]
+ failed_grasp_ids = [
+ result["grasp_id"]
+ for result in results
+ if not _is_successful_grasp_result(result)
+ ]
+ num_grasps = len(results)
+ num_success = len(successful_grasp_ids)
+ return {
+ "num_grasps": num_grasps,
+ "num_success": num_success,
+ "num_failure": len(failed_grasp_ids),
+ "success_rate": num_success / max(1, num_grasps),
+ "successful_grasp_ids": successful_grasp_ids,
+ "failed_grasp_ids": failed_grasp_ids,
+ "slip_thresholds": _slip_thresholds(cfg),
+ "elapsed_seconds": elapsed_seconds,
+ }
+
+
+def _run_single_eval(cfg: EvalGraspsConfig) -> None:
+ start_time = time.perf_counter()
+ output_dir = cfg.output_dirs[0]
+ os.makedirs(output_dir, exist_ok=True)
+ urdf_path = cfg.urdf_paths[0]
+ urdf = URDFFile(urdf_path)
+
+ affordance_path = urdf.get_affordance_annot_path()
+ affordance_payload = load_json(affordance_path)
+ affordances = affordance_payload.get("affordances", [])
+ grasps = {}
+ for affordance in affordances:
+ grasps.update(affordance.get("grasp_group", {}))
+
+ if not grasps:
+ return
+
+ num_workers = cfg.num_workers
+ if num_workers is None:
+ num_workers = min(len(grasps), os.cpu_count() or 1, MAX_NUM_WORKERS)
+ else:
+ num_workers = min(num_workers, len(grasps), MAX_NUM_WORKERS)
+
+ logger.info(
+ f"Running {len(grasps)} grasp poses with {num_workers} worker(s)"
+ )
+ results = []
+ if num_workers == 1:
+ for grasp_id, grasp in tqdm(grasps.items(), desc="Evaluating grasps"):
+ results.append(_eval_grasp(cfg, grasp_id, grasp))
+ else:
+ worker_cfg = dataclasses.replace(cfg, num_workers=1)
+ tasks = [
+ (worker_cfg, grasp_id, grasp) for grasp_id, grasp in grasps.items()
+ ]
+ try:
+ with concurrent.futures.ThreadPoolExecutor(
+ max_workers=num_workers
+ ) as executor:
+ futures = [
+ executor.submit(_eval_grasp_task, task) for task in tasks
+ ]
+ for future in tqdm(
+ concurrent.futures.as_completed(futures),
+ total=len(futures),
+ desc="Evaluating grasps",
+ ):
+ results.append(future.result())
+ results.sort(
+ key=lambda result: list(grasps.keys()).index(
+ result["grasp_id"]
+ )
+ )
+ finally:
+ _release_thread_evaluators()
+
+ elapsed_seconds = time.perf_counter() - start_time
+ summary = _build_eval_summary(cfg, results, elapsed_seconds)
+ failed_grasp_ids = set(summary["failed_grasp_ids"])
+ grasp_success_by_id = _grasp_success_by_id(results)
+ for affordance in affordance_payload.get("affordances", []):
+ grasp_group = affordance.get("grasp_group")
+ if isinstance(grasp_group, dict):
+ if cfg.debug_mode or cfg.save_failed_grasps:
+ for grasp_id, grasp in grasp_group.items():
+ if (
+ isinstance(grasp, dict)
+ and grasp_id in grasp_success_by_id
+ ):
+ grasp["simulation_success"] = grasp_success_by_id[
+ grasp_id
+ ]
+ continue
+ for failed_grasp_id in failed_grasp_ids:
+ grasp_group.pop(failed_grasp_id, None)
+
+ write_json(affordance_payload, affordance_path)
+
+ logger.info(f"Grasp evaluation simulation took {elapsed_seconds:.2f}s")
+ logger.info(f"Updated grasp simulation results in {affordance_path}")
+ logger.info(
+ f"Grasp success rate: {summary['num_success']}/"
+ f"{summary['num_grasps']} ({summary['success_rate']:.2%})"
+ )
+
+
+def _run_single_eval_safe(cfg: EvalGraspsConfig) -> bool:
+ urdf_path = cfg.urdf_paths[0]
+ try:
+ _run_single_eval(cfg)
+ except Exception as exc:
+ logger.error(
+ "Grasp evaluation failed for URDF {}: {}".format(
+ urdf_path,
+ exc,
+ )
+ )
+ return False
+ return True
+
+
+def run_eval(cfg: EvalGraspsConfig) -> None:
+ _validate_config(cfg)
+ for idx, (urdf_path, output_dir) in enumerate(
+ zip(cfg.urdf_paths, cfg.output_dirs),
+ start=1,
+ ):
+ single_cfg = _set_single_config(cfg, urdf_path, output_dir)
+ logger.info(
+ f"Running grasp evaluation for URDF {idx}/{len(cfg.urdf_paths)}: "
+ f"{urdf_path}"
+ )
+ _run_single_eval_safe(single_cfg)
+
+
+def entrypoint(*args, **kwargs) -> None:
+ cfg = tyro.cli(EvalGraspsConfig)
+ run_eval(cfg)
+
+
+if __name__ == "__main__":
+ entrypoint()
diff --git a/embodied_gen/scripts/affordance_annot/franka_panda/coll_mesh.obj b/embodied_gen/scripts/affordance_annot/franka_panda/coll_mesh.obj
new file mode 100644
index 0000000..256a947
--- /dev/null
+++ b/embodied_gen/scripts/affordance_annot/franka_panda/coll_mesh.obj
@@ -0,0 +1,464 @@
+# https://github.com/mikedh/trimesh
+v -0.09012898 0.01673458 0.06443186 0.00000000 1.00000000 0.00000000
+v 0.08927116 0.01713458 0.06361439 0.00000000 1.00000000 0.00000000
+v 0.00055840 0.03158979 0.00621423 0.00000000 1.00000000 0.00000000
+v 0.09552484 -0.01705332 0.02212120 0.00000000 1.00000000 0.00000000
+v 0.09790768 -0.01751002 0.05166616 0.00000000 1.00000000 0.00000000
+v 0.00198662 -0.03159750 0.00115293 0.00000000 1.00000000 0.00000000
+v -0.07576962 -0.02598126 -0.00508223 0.00000000 1.00000000 0.00000000
+v 0.00057885 -0.03163592 0.00597307 0.00000000 1.00000000 0.00000000
+v 0.09680219 0.01703352 0.03032204 0.00000000 1.00000000 0.00000000
+v 0.00186074 0.03161579 0.00117971 0.00000000 1.00000000 0.00000000
+v 0.09740699 0.01758546 0.05198465 0.00000000 1.00000000 0.00000000
+v -0.09460201 0.01855551 0.05661729 0.00000000 1.00000000 0.00000000
+v 0.09190618 -0.01680383 0.00629548 0.00000000 1.00000000 0.00000000
+v 0.08753296 -0.01681192 -0.00120645 0.00000000 1.00000000 0.00000000
+v 0.09953746 0.01623047 0.05158409 0.00000000 1.00000000 0.00000000
+v 0.10398972 -0.00004070 0.00569088 0.00000000 1.00000000 0.00000000
+v 0.10374270 -0.00001689 -0.00919930 0.00000000 1.00000000 0.00000000
+v 0.10115783 -0.00361640 -0.01261032 0.00000000 1.00000000 0.00000000
+v 0.10259786 -0.00342918 -0.00896430 0.00000000 1.00000000 0.00000000
+v -0.08206502 -0.02527066 0.00519704 0.00000000 1.00000000 0.00000000
+v -0.04178420 -0.02801102 -0.00639640 0.00000000 1.00000000 0.00000000
+v -0.09280849 -0.00011291 0.06596215 0.00000000 1.00000000 0.00000000
+v -0.09371245 -0.01256706 0.06516847 0.00000000 1.00000000 0.00000000
+v -0.09127641 -0.01442229 0.06568974 0.00000000 1.00000000 0.00000000
+v 0.09070038 -0.01725433 0.06308201 0.00000000 1.00000000 0.00000000
+v 0.09070638 -0.01547137 0.06519010 0.00000000 1.00000000 0.00000000
+v -0.09025214 -0.01676757 0.06433220 0.00000000 1.00000000 0.00000000
+v 0.09160285 0.01695586 0.00556283 0.00000000 1.00000000 0.00000000
+v 0.09384751 0.01760751 0.05844849 0.00000000 1.00000000 0.00000000
+v 0.10018747 0.01250605 0.05689415 0.00000000 1.00000000 0.00000000
+v -0.10019985 0.01555056 0.05467260 0.00000000 1.00000000 0.00000000
+v -0.09849364 0.01926846 0.04494174 0.00000000 1.00000000 0.00000000
+v -0.10028528 0.01621802 0.04586977 0.00000000 1.00000000 0.00000000
+v 0.04551993 0.00191760 -0.02565943 0.00000000 1.00000000 0.00000000
+v -0.04204245 0.02801493 -0.00634960 0.00000000 1.00000000 0.00000000
+v 0.08296617 0.00381064 -0.02444934 0.00000000 1.00000000 0.00000000
+v -0.09369192 0.02067053 0.04330829 0.00000000 1.00000000 0.00000000
+v -0.08118060 0.02544764 0.00350060 0.00000000 1.00000000 0.00000000
+v 0.08358592 -0.00363651 -0.02460626 0.00000000 1.00000000 0.00000000
+v 0.08223634 -0.01653239 -0.00511811 0.00000000 1.00000000 0.00000000
+v 0.10009117 -0.01401675 0.05602695 0.00000000 1.00000000 0.00000000
+v 0.09962869 -0.01601276 0.04930143 0.00000000 1.00000000 0.00000000
+v 0.08776600 -0.00321477 -0.02388439 0.00000000 1.00000000 0.00000000
+v 0.09249580 -0.01298759 0.06571195 0.00000000 1.00000000 0.00000000
+v 0.09294532 -0.00050530 0.06595311 0.00000000 1.00000000 0.00000000
+v -0.09267219 0.01353924 0.06568788 0.00000000 1.00000000 0.00000000
+v 0.09192696 0.01342515 0.06573131 0.00000000 1.00000000 0.00000000
+v -0.09726050 -0.01922958 0.05058843 0.00000000 1.00000000 0.00000000
+v 0.08513211 0.01655853 -0.00387274 0.00000000 1.00000000 0.00000000
+v 0.10271563 0.00349515 -0.00802934 0.00000000 1.00000000 0.00000000
+v -0.09040149 0.02386882 0.00436824 0.00000000 1.00000000 0.00000000
+v -0.09373564 0.02270746 0.01434002 0.00000000 1.00000000 0.00000000
+v -0.07529767 0.02603403 -0.00503085 0.00000000 1.00000000 0.00000000
+v -0.07437764 0.02481648 -0.00715570 0.00000000 1.00000000 0.00000000
+v 0.04586986 -0.00149998 -0.02592480 0.00000000 1.00000000 0.00000000
+v -0.08025997 -0.00083407 -0.00732172 0.00000000 1.00000000 0.00000000
+v -0.07790146 0.02295881 -0.00739904 0.00000000 1.00000000 0.00000000
+v -0.08017428 -0.02276142 -0.00690954 0.00000000 1.00000000 0.00000000
+v 0.09945143 0.00003218 -0.01716121 0.00000000 1.00000000 0.00000000
+v 0.09855405 -0.01663178 0.05473394 0.00000000 1.00000000 0.00000000
+v 0.10119467 0.00360250 -0.01262764 0.00000000 1.00000000 0.00000000
+v -0.09747105 -0.00161709 0.06230734 0.00000000 1.00000000 0.00000000
+v 0.09177952 0.01571872 0.06491254 0.00000000 1.00000000 0.00000000
+v -0.09201546 -0.01844902 0.05839317 0.00000000 1.00000000 0.00000000
+v 0.08797474 -0.00062667 -0.02496160 0.00000000 1.00000000 0.00000000
+v 0.08797288 0.00307632 -0.02397873 0.00000000 1.00000000 0.00000000
+v -0.09701113 0.01980169 0.02091398 0.00000000 1.00000000 0.00000000
+v -0.10003477 0.00994118 0.05834080 0.00000000 1.00000000 0.00000000
+v -0.09906728 0.01712909 0.05430516 0.00000000 1.00000000 0.00000000
+v -0.09461535 -0.00031448 0.00757466 0.00000000 1.00000000 0.00000000
+v -0.09849606 -0.00001631 0.02708179 0.00000000 1.00000000 0.00000000
+v -0.08656162 -0.00033764 -0.00416328 0.00000000 1.00000000 0.00000000
+v -0.08825006 0.02231396 -0.00193320 0.00000000 1.00000000 0.00000000
+v -0.08335528 0.02237667 -0.00552611 0.00000000 1.00000000 0.00000000
+v -0.09092682 -0.02132529 0.00097980 0.00000000 1.00000000 0.00000000
+v -0.08543439 -0.02273309 -0.00412033 0.00000000 1.00000000 0.00000000
+v -0.08589226 -0.02449557 -0.00183273 0.00000000 1.00000000 0.00000000
+v 0.08400230 0.00184310 -0.02552490 0.00000000 1.00000000 0.00000000
+v -0.07666791 -0.02465053 -0.00693213 0.00000000 1.00000000 0.00000000
+v 0.09406284 0.01313908 0.06468318 0.00000000 1.00000000 0.00000000
+v 0.09998268 -0.00932350 0.05848920 0.00000000 1.00000000 0.00000000
+v -0.09769707 -0.01906715 0.02462748 0.00000000 1.00000000 0.00000000
+v -0.10042586 -0.00449476 0.05811909 0.00000000 1.00000000 0.00000000
+v -0.10016816 -0.01462347 0.05573492 0.00000000 1.00000000 0.00000000
+v 0.09941621 0.01580527 0.05497027 0.00000000 1.00000000 0.00000000
+v -0.09433321 0.02134224 0.00894201 0.00000000 1.00000000 0.00000000
+v -0.09099504 -0.00017374 0.00057678 0.00000000 1.00000000 0.00000000
+v -0.09179476 0.02148257 0.00268413 0.00000000 1.00000000 0.00000000
+v -0.08170268 0.02451487 -0.00505142 0.00000000 1.00000000 0.00000000
+v -0.08574020 0.02472907 -0.00123169 0.00000000 1.00000000 0.00000000
+v -0.09466784 -0.02065799 0.00953798 0.00000000 1.00000000 0.00000000
+v 0.10030148 0.00119096 0.05862313 0.00000000 1.00000000 0.00000000
+v 0.09665588 0.00046996 0.06307836 0.00000000 1.00000000 0.00000000
+v -0.09290565 0.01539656 0.06455627 0.00000000 1.00000000 0.00000000
+v 0.08254847 -0.00086753 -0.02587020 0.00000000 1.00000000 0.00000000
+v -0.09116623 -0.02355612 0.00460792 0.00000000 1.00000000 0.00000000
+v 0.09344175 -0.01461668 0.06450005 0.00000000 1.00000000 0.00000000
+v -0.09289678 -0.02302643 0.00987686 0.00000000 1.00000000 0.00000000
+v -0.09544163 -0.02182249 0.02175636 0.00000000 1.00000000 0.00000000
+v -0.09951787 -0.01819300 0.04381417 0.00000000 1.00000000 0.00000000
+v -0.09320920 -0.01604587 0.06361687 0.00000000 1.00000000 0.00000000
+v -0.09936280 -0.01699521 0.05411479 0.00000000 1.00000000 0.00000000
+v 0.06600004 -0.01099976 0.05940000 0.00000000 1.00000000 0.00000000
+v 0.06600004 -0.01099976 0.07940000 0.00000000 1.00000000 0.00000000
+v 0.05100004 -0.01099981 0.05940000 0.00000000 1.00000000 0.00000000
+v 0.05100004 -0.01099981 0.07940000 0.00000000 1.00000000 0.00000000
+v 0.06599996 0.01100024 0.05940000 0.00000000 1.00000000 0.00000000
+v 0.06599996 0.01100024 0.07940000 0.00000000 1.00000000 0.00000000
+v 0.05099996 0.01100019 0.05940000 0.00000000 1.00000000 0.00000000
+v 0.05099996 0.01100019 0.07940000 0.00000000 1.00000000 0.00000000
+v 0.05120004 -0.01099981 0.05870000 0.00000000 1.00000000 0.00000000
+v 0.05120004 -0.01099981 0.06250000 0.00000000 1.00000000 0.00000000
+v 0.04240004 -0.01099984 0.05870000 0.00000000 1.00000000 0.00000000
+v 0.04240004 -0.01099984 0.06250000 0.00000000 1.00000000 0.00000000
+v 0.05119996 0.01100019 0.05870000 0.00000000 1.00000000 0.00000000
+v 0.05119996 0.01100019 0.06250000 0.00000000 1.00000000 0.00000000
+v 0.04239996 0.01100016 0.05870000 0.00000000 1.00000000 0.00000000
+v 0.04239996 0.01100016 0.06250000 0.00000000 1.00000000 0.00000000
+v 0.06480612 -0.00874976 0.07832420 0.00000000 1.00000000 0.00000000
+v 0.05305612 -0.00874981 0.09867580 0.00000000 1.00000000 0.00000000
+v 0.05874394 -0.00874978 0.07482420 0.00000000 1.00000000 0.00000000
+v 0.04699394 -0.00874983 0.09517580 0.00000000 1.00000000 0.00000000
+v 0.06480606 0.00875024 0.07832420 0.00000000 1.00000000 0.00000000
+v 0.05305606 0.00875019 0.09867580 0.00000000 1.00000000 0.00000000
+v 0.05874388 0.00875022 0.07482420 0.00000000 1.00000000 0.00000000
+v 0.04699388 0.00875017 0.09517580 0.00000000 1.00000000 0.00000000
+v 0.05518003 -0.00874980 0.09440000 0.00000000 1.00000000 0.00000000
+v 0.05518003 -0.00874980 0.11290000 0.00000000 1.00000000 0.00000000
+v 0.03998003 -0.00874985 0.09440000 0.00000000 1.00000000 0.00000000
+v 0.03998003 -0.00874985 0.11290000 0.00000000 1.00000000 0.00000000
+v 0.05517997 0.00875020 0.09440000 0.00000000 1.00000000 0.00000000
+v 0.05517997 0.00875020 0.11290000 0.00000000 1.00000000 0.00000000
+v 0.03997997 0.00875015 0.09440000 0.00000000 1.00000000 0.00000000
+v 0.03997997 0.00875015 0.11290000 0.00000000 1.00000000 0.00000000
+v -0.05099996 -0.01100019 0.05940000 0.00000000 1.00000000 0.00000000
+v -0.05099996 -0.01100019 0.07940000 0.00000000 1.00000000 0.00000000
+v -0.06599996 -0.01100024 0.05940000 0.00000000 1.00000000 0.00000000
+v -0.06599996 -0.01100024 0.07940000 0.00000000 1.00000000 0.00000000
+v -0.05100004 0.01099981 0.05940000 0.00000000 1.00000000 0.00000000
+v -0.05100004 0.01099981 0.07940000 0.00000000 1.00000000 0.00000000
+v -0.06600004 0.01099976 0.05940000 0.00000000 1.00000000 0.00000000
+v -0.06600004 0.01099976 0.07940000 0.00000000 1.00000000 0.00000000
+v -0.04239996 -0.01100016 0.05870000 0.00000000 1.00000000 0.00000000
+v -0.04239996 -0.01100016 0.06250000 0.00000000 1.00000000 0.00000000
+v -0.05119996 -0.01100019 0.05870000 0.00000000 1.00000000 0.00000000
+v -0.05119996 -0.01100019 0.06250000 0.00000000 1.00000000 0.00000000
+v -0.04240004 0.01099984 0.05870000 0.00000000 1.00000000 0.00000000
+v -0.04240004 0.01099984 0.06250000 0.00000000 1.00000000 0.00000000
+v -0.05120004 0.01099981 0.05870000 0.00000000 1.00000000 0.00000000
+v -0.05120004 0.01099981 0.06250000 0.00000000 1.00000000 0.00000000
+v -0.05874388 -0.00875022 0.07482420 0.00000000 1.00000000 0.00000000
+v -0.04699388 -0.00875017 0.09517580 0.00000000 1.00000000 0.00000000
+v -0.06480606 -0.00875024 0.07832420 0.00000000 1.00000000 0.00000000
+v -0.05305606 -0.00875019 0.09867580 0.00000000 1.00000000 0.00000000
+v -0.05874394 0.00874978 0.07482420 0.00000000 1.00000000 0.00000000
+v -0.04699394 0.00874983 0.09517580 0.00000000 1.00000000 0.00000000
+v -0.06480612 0.00874976 0.07832420 0.00000000 1.00000000 0.00000000
+v -0.05305612 0.00874981 0.09867580 0.00000000 1.00000000 0.00000000
+v -0.03997997 -0.00875015 0.09440000 0.00000000 1.00000000 0.00000000
+v -0.03997997 -0.00875015 0.11290000 0.00000000 1.00000000 0.00000000
+v -0.05517997 -0.00875020 0.09440000 0.00000000 1.00000000 0.00000000
+v -0.05517997 -0.00875020 0.11290000 0.00000000 1.00000000 0.00000000
+v -0.03998003 0.00874985 0.09440000 0.00000000 1.00000000 0.00000000
+v -0.03998003 0.00874985 0.11290000 0.00000000 1.00000000 0.00000000
+v -0.05518003 0.00874980 0.09440000 0.00000000 1.00000000 0.00000000
+v -0.05518003 0.00874980 0.11290000 0.00000000 1.00000000 0.00000000
+f 1 2 3
+f 4 5 6
+f 7 6 8
+f 9 10 11
+f 10 3 11
+f 12 1 3
+f 13 6 14
+f 8 6 5
+f 4 6 13
+f 15 16 17
+f 18 19 13
+f 20 7 8
+f 21 6 7
+f 22 23 24
+f 25 26 27
+f 28 10 9
+f 2 29 3
+f 29 11 3
+f 30 16 15
+f 31 32 33
+f 34 35 36
+f 37 3 38
+f 6 39 40
+f 25 8 5
+f 41 42 16
+f 18 14 43
+f 22 24 44
+f 44 45 22
+f 46 22 45
+f 45 47 46
+f 20 8 48
+f 9 11 15
+f 36 10 49
+f 50 28 9
+f 10 36 35
+f 12 37 32
+f 37 12 3
+f 37 38 51
+f 37 52 32
+f 52 37 51
+f 10 35 53
+f 10 53 38
+f 10 38 3
+f 54 35 34
+f 55 56 57
+f 56 55 58
+f 57 54 34
+f 39 6 21
+f 59 18 43
+f 40 39 43
+f 14 40 43
+f 14 6 40
+f 60 25 5
+f 59 61 17
+f 42 60 5
+f 16 42 17
+f 62 22 46
+f 22 62 23
+f 1 46 63
+f 47 63 46
+f 25 64 8
+f 64 25 27
+f 64 48 8
+f 65 59 43
+f 66 36 49
+f 63 2 1
+f 33 32 67
+f 68 62 46
+f 32 69 12
+f 70 71 67
+f 53 35 54
+f 72 73 74
+f 75 76 77
+f 78 55 34
+f 55 79 58
+f 4 42 5
+f 50 15 17
+f 59 17 18
+f 7 20 77
+f 24 26 44
+f 27 26 24
+f 45 80 47
+f 41 16 81
+f 82 71 70
+f 62 83 23
+f 33 84 83
+f 49 28 61
+f 61 28 50
+f 61 50 17
+f 63 85 29
+f 85 30 15
+f 80 85 63
+f 85 80 30
+f 9 15 50
+f 85 15 11
+f 11 29 85
+f 83 62 68
+f 34 36 78
+f 70 67 86
+f 71 33 67
+f 58 76 72
+f 73 87 88
+f 72 74 56
+f 56 74 57
+f 34 55 57
+f 21 79 55
+f 21 7 79
+f 89 73 90
+f 39 21 55
+f 91 82 70
+f 84 23 83
+f 28 49 10
+f 92 80 93
+f 31 33 68
+f 68 33 83
+f 51 86 52
+f 69 32 31
+f 46 94 68
+f 94 31 68
+f 94 12 69
+f 76 75 72
+f 90 53 89
+f 86 51 88
+f 75 70 87
+f 87 70 88
+f 95 55 78
+f 39 55 95
+f 14 18 13
+f 42 41 60
+f 19 17 42
+f 19 4 13
+f 45 93 80
+f 92 93 81
+f 16 30 92
+f 92 81 16
+f 96 77 20
+f 66 59 65
+f 65 78 66
+f 59 66 61
+f 49 61 66
+f 2 63 29
+f 80 63 47
+f 80 92 30
+f 78 36 66
+f 94 69 31
+f 94 46 1
+f 12 94 1
+f 70 86 88
+f 67 52 86
+f 52 67 32
+f 57 89 54
+f 53 90 38
+f 90 51 38
+f 75 87 72
+f 72 87 73
+f 56 58 72
+f 42 4 19
+f 19 18 17
+f 97 81 44
+f 81 93 44
+f 41 97 60
+f 26 97 44
+f 98 99 91
+f 100 33 71
+f 84 33 100
+f 96 75 77
+f 95 78 65
+f 73 89 74
+f 53 54 89
+f 73 88 51
+f 90 73 51
+f 41 81 97
+f 44 93 45
+f 25 97 26
+f 101 48 64
+f 64 27 101
+f 102 48 101
+f 96 98 91
+f 48 99 20
+f 99 98 20
+f 98 96 20
+f 96 91 75
+f 84 101 23
+f 27 24 101
+f 89 57 74
+f 77 76 79
+f 79 76 58
+f 79 7 77
+f 102 84 100
+f 91 99 82
+f 48 100 99
+f 100 48 102
+f 75 91 70
+f 84 102 101
+f 24 23 101
+f 43 95 65
+f 25 60 97
+f 82 100 71
+f 43 39 95
+f 100 82 99
+f 104 106 103
+f 107 104 103
+f 103 106 105
+f 105 107 103
+f 104 110 106
+f 108 104 107
+f 108 110 104
+f 106 110 105
+f 109 107 105
+f 105 110 109
+f 109 108 107
+f 110 108 109
+f 112 114 111
+f 115 112 111
+f 111 114 113
+f 113 115 111
+f 112 118 114
+f 116 112 115
+f 116 118 112
+f 114 118 113
+f 117 115 113
+f 113 118 117
+f 117 116 115
+f 118 116 117
+f 120 122 119
+f 123 120 119
+f 119 122 121
+f 121 123 119
+f 120 126 122
+f 124 120 123
+f 124 126 120
+f 122 126 121
+f 125 123 121
+f 121 126 125
+f 125 124 123
+f 126 124 125
+f 128 130 127
+f 131 128 127
+f 127 130 129
+f 129 131 127
+f 128 134 130
+f 132 128 131
+f 132 134 128
+f 130 134 129
+f 133 131 129
+f 129 134 133
+f 133 132 131
+f 134 132 133
+f 136 138 135
+f 139 136 135
+f 135 138 137
+f 137 139 135
+f 136 142 138
+f 140 136 139
+f 140 142 136
+f 138 142 137
+f 141 139 137
+f 137 142 141
+f 141 140 139
+f 142 140 141
+f 144 146 143
+f 147 144 143
+f 143 146 145
+f 145 147 143
+f 144 150 146
+f 148 144 147
+f 148 150 144
+f 146 150 145
+f 149 147 145
+f 145 150 149
+f 149 148 147
+f 150 148 149
+f 152 154 151
+f 155 152 151
+f 151 154 153
+f 153 155 151
+f 152 158 154
+f 156 152 155
+f 156 158 152
+f 154 158 153
+f 157 155 153
+f 153 158 157
+f 157 156 155
+f 158 156 157
+f 160 162 159
+f 163 160 159
+f 159 162 161
+f 161 163 159
+f 160 166 162
+f 164 160 163
+f 164 166 160
+f 162 166 161
+f 165 163 161
+f 161 166 165
+f 165 164 163
+f 166 164 165
+
diff --git a/embodied_gen/scripts/affordance_annot/franka_panda/gripper.urdf b/embodied_gen/scripts/affordance_annot/franka_panda/gripper.urdf
new file mode 100644
index 0000000..b31ab49
--- /dev/null
+++ b/embodied_gen/scripts/affordance_annot/franka_panda/gripper.urdf
@@ -0,0 +1,120 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/embodied_gen/scripts/affordance_annot/franka_panda/meshes/collision/hand.stl b/embodied_gen/scripts/affordance_annot/franka_panda/meshes/collision/hand.stl
new file mode 100644
index 0000000..4e82090
Binary files /dev/null and b/embodied_gen/scripts/affordance_annot/franka_panda/meshes/collision/hand.stl differ
diff --git a/embodied_gen/scripts/affordance_annot/franka_panda/meshes/visual/finger.dae b/embodied_gen/scripts/affordance_annot/franka_panda/meshes/visual/finger.dae
new file mode 100644
index 0000000..8d249b9
--- /dev/null
+++ b/embodied_gen/scripts/affordance_annot/franka_panda/meshes/visual/finger.dae
@@ -0,0 +1,178 @@
+
+
+
+
+ Blender User
+ Blender 2.79.0
+
+ 2018-02-20T15:00:35
+ 2018-02-20T15:00:35
+
+ Z_UP
+
+
+
+
+
+
+
+
+ 0 0 0 1
+
+
+ 0 0 0 1
+
+
+ 0.2509804 0.2509804 0.2509804 1
+
+
+ 0.5 0.5 0.5 1
+
+
+ 0
+
+
+ 1 1 1 1
+
+ 1
+
+ 1
+
+
+
+
+
+
+
+
+
+
+ 0 0 0 1
+
+
+ 0 0 0 1
+
+
+ 0.9019608 0.9215686 0.9294118 1
+
+
+ 0.5 0.5 0.5 1
+
+
+ 0
+
+
+ 1 1 1 1
+
+ 1
+
+ 1
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 0.002999246 0.01089721 0.04464477 3.59715e-4 0.01089966 0.04796832 0.002338767 0.010885 0.04695528 0.00234574 0.01088672 0.04317915 0.001025557 0.01089477 0.04214996 0.001999616 0.01089692 0.04408597 8.26939e-4 0.01089149 0.04701918 0.002051293 0.01086962 0.0458244 9.84775e-4 0.01088815 0.04306632 -0.001819312 0.009437561 0.04387903 -1.12074e-5 0.00943017 0.04711711 -6.97705e-4 0.0094316 0.04301643 -0.001464784 0.009429216 0.0465517 -0.00208646 0.009431064 0.04526871 -1.55725e-4 0.009388387 0.04323405 0.001297712 0.009390771 0.04378831 1.56458e-4 0.009388387 0.04676586 0.001741111 0.009388566 0.04558634 -0.002886593 0.009435176 0.04399472 -0.001784682 0.009434103 0.0424965 5.2098e-4 0.009434282 0.04803037 -0.001791715 0.009433507 0.04755115 1.48399e-5 0.009437739 0.04194247 -0.002975225 0.009436786 0.04570305 -0.008930683 0.009314417 0.03609037 0.008931338 0.009313642 0.03609037 -8.60488e-4 0.001913011 0.04356497 -0.001740396 0.009388566 0.04441362 -0.00152558 0.001912534 0.04591751 -0.001296997 0.009390771 0.0462116 -9.70155e-4 0.010885 0.04216766 0.004843115 -1.70612e-5 0.05371719 0.008756995 -2.97773e-5 0.04727178 0.008758902 -2.87572e-5 0.04272794 -0.002268254 -3.64761e-5 0.05374616 -0.008709371 -1.03085e-5 0.04993945 -0.00875771 -2.8976e-5 0.0472719 -0.002273738 -3.68374e-5 0.03624957 -0.002243995 -7.46945e-5 0.0427556 -0.008758783 -3.04484e-5 0.04272389 0.002263069 -8.37643e-5 0.04726266 0.00226897 -3.64764e-5 0.05374616 3.65234e-7 0.001558899 0.05380403 3.6521e-7 0.001488626 0.04499995 3.65614e-7 0.001561164 0.03619641 0.002270221 -3.43831e-5 0.0362488 0.002248227 -7.64219e-5 0.04275208 -0.002262294 -8.37643e-5 0.04726266 -0.005691945 1.10341e-5 0.05360394 0.00686866 0.009352147 0.05310285 -0.007860779 0.009355306 0.05219143 0.003445267 0.009386718 0.04705679 0.008785963 0.009348154 0.0505712 0.003923535 0.009389936 0.04573357 -7.30475e-4 0.009391903 0.04108011 0.003038406 0.009433627 0.04465281 7.34002e-4 0.009389936 0.04107677 0.002057075 0.009386718 0.04155498 0.004730045 0.009351372 0.05389988 -7.33049e-4 0.009390354 0.04892301 -0.00205636 0.009386718 0.04844492 0.007075071 -1.88821e-5 0.05276924 0.008674919 -6.99964e-6 0.05031853 0.007492721 0.009395003 0.0442506 0.007331073 0.001441657 0.04408031 0.004115462 0.00143361 0.04404103 9.13617e-4 0.001440465 0.0409246 0.002675354 0.001427888 0.04171341 -9.16098e-4 0.001442193 0.03767275 -0.004073202 0.001442134 0.04408985 -0.003944575 0.009400427 0.04425376 -0.002707123 0.009383678 0.04193902 -0.003045201 0.001419723 0.04195654 -0.007492482 0.00939387 0.03750175 -9.09494e-4 0.001442074 0.04092669 -0.004972159 0.009392738 0.0524379 -0.007140517 0.001431167 0.05044227 -0.007258474 0.009387791 0.05068916 -0.007491827 0.009394824 0.04574936 -0.004114806 0.001433432 0.04595905 -9.13103e-4 0.001440107 0.04907548 -0.002675294 0.001426517 0.04828691 -9.16344e-4 0.001443028 0.05232822 -0.005090415 0.001432299 0.05222845 0.007365286 0.009393692 0.05032676 0.004924833 0.001437306 0.05226349 0.00534445 0.009385824 0.0523557 9.15562e-4 0.001443564 0.05232697 7.49809e-4 0.009395062 0.05249238 9.59386e-4 0.001433491 0.04911512 0.004075884 0.001440107 0.04591345 0.003287315 0.001426517 0.04767566 0.007309973 0.001435518 0.0495814 0.006608247 0.00141263 0.05116909 -0.001967191 0.0108897 0.04407691 -0.002455234 0.0108962 0.04326856 -7.23478e-4 0.01089477 0.04287755 0.002154886 0.009418427 0.04456418 7.21602e-4 0.009433567 0.04298967 0.001448452 0.009429216 0.04656761 8.61216e-4 0.00191313 0.04643499 0.001526296 0.001912474 0.0440824 -0.007818162 -3.97311e-5 0.05191779 -0.008771955 2.75426e-5 0.03626441 -0.008893072 0.009342074 0.04989963 0.008762598 1.08415e-5 0.03626585 -0.005339801 0.009355187 0.05381947 -0.007331967 0.00144273 0.04407978 -0.007496535 0.009394824 0.04424685 -0.007332861 0.001443088 0.03767186 -7.55759e-4 0.009394407 0.03750163 0.00254625 0.009436607 0.04679846 0.002077639 0.009434342 0.04267549 0.007333219 0.001442193 0.03767275 0.007498383 0.00939387 0.03750687 9.16392e-4 0.001442492 0.03767257 0.003923356 0.009390354 0.04426658 7.54132e-4 0.009395062 0.03750258 0.003375649 0.00939083 0.04281467 -0.007332205 0.001443505 0.04591983 -0.003922641 0.009390354 0.04573339 -7.53554e-4 0.009394705 0.05249726 -0.003374874 0.00939083 0.0471853 0.007332324 0.001442849 0.04592007 7.34002e-4 0.009389936 0.04892319 0.007497489 0.009394824 0.04575371 0.002185642 0.00939083 0.0483753 -0.002153158 0.01088654 0.0454204 -0.001070141 0.01088356 0.04698097 -0.003009378 0.01089417 0.04527872 -0.00187093 0.01090669 0.04741126 -0.008804023 0.001558899 0.04499995 0.008809328 0.001564621 0.04499995
+
+
+
+
+
+
+
+
+
+ 0.9590276 0.08210206 0.2711559 0.9707201 -0.02025878 -0.2393575 0.356231 0.02584302 -0.9340405 0.2114398 0.06310987 -0.9753515 0.5969651 -0.02077841 0.8019981 0.8966066 0.08637297 -0.4343227 0.8202709 -0.05451494 -0.5693714 0.01862794 0.09478819 0.9953232 0.3569161 0.1740584 -0.917777 0.6076866 0.08563488 0.7895466 0.6939369 -0.0273537 0.7195162 0.9792594 0.07404083 0.1885975 0.9900662 0.03109622 0.1371198 -0.9979203 -0.0384593 -0.05173081 -0.9530383 0.1508886 -0.2625846 -0.2413043 0.07884323 0.9672415 -0.2028678 0.01909875 0.9790199 -0.8043072 -0.05650782 -0.5915207 -0.5880531 0.1500057 -0.7947906 -0.2937553 -0.05141419 -0.954497 -0.009515166 0.1474139 -0.9890291 -0.8421047 0.005896389 0.539282 -0.8765521 0.1154791 0.4672484 0.007623791 -0.9999444 0.00728625 0.003054559 -0.9999539 0.009112834 0.01089298 -0.9999382 0.002262771 0.008302092 -0.9999359 0.007699072 0.007297933 -0.9999547 -0.006123363 0.006982326 -0.9999549 -0.006440281 -0.0141775 -0.9998731 0.007279753 0.008410274 -0.9997746 0.01949483 -0.01479846 -0.999857 -0.008204162 -0.008424222 -0.999939 0.007150232 -0.006763219 -0.9999605 -0.005787611 -0.009926855 -0.9999107 -0.008956611 0.999624 0.005454719 0.02687352 0.9996895 -0.0124461 0.0215857 0.9999408 -0.004187285 0.01004832 0.9997202 -0.02283918 -0.006177306 0.9998357 -0.01812744 4.58244e-4 -0.9998302 -0.003178 0.01815414 -0.9997246 -0.01958203 0.01294118 -0.9997529 -0.02172666 -0.004701018 -0.9998528 -0.01705491 0.001882493 -0.9999096 -0.01316875 0.002750456 -0.5751125 -0.8180485 0.006493091 -0.5750488 -0.8180931 0.006525993 -0.5749478 -0.8181582 -0.007234573 -0.5759127 -0.8174836 -0.006735205 0.575049 -0.8180931 0.006525993 0.5751125 -0.8180485 0.006493151 0.5760833 -0.8173633 -0.006734192 0.5748195 -0.818247 -0.007387042 -0.002469599 -0.01878017 -0.9998206 -0.007602214 -0.02243256 -0.9997196 0.007440984 -0.02224701 -0.9997249 0.002750813 -0.01890742 -0.9998174 -8.40573e-7 -0.01367795 -0.9999065 -0.00798428 -0.007449865 0.9999404 -0.02916109 -0.0219537 0.9993337 -0.04118311 0.02231454 0.9989025 0.01138383 -0.02008819 0.9997335 0.0116266 -0.01935064 0.9997453 -0.03979802 0.9988015 -0.02849191 -0.03750216 0.998896 -0.02829426 0.03617066 0.9993457 1.13911e-6 0.01915013 0.9992586 0.03339964 -0.0110442 0.9997916 0.01717156 2.13537e-4 0.998414 -0.05629932 -0.01905494 0.9995746 0.02207905 0.05574911 0.9984449 -1.19817e-4 0.04555356 0.9989601 -0.00192207 4.14317e-5 0.9983577 -0.05728983 -3.92216e-4 0.9985226 -0.05433702 -0.05497872 0.9984876 -1.74394e-4 -0.04532593 0.9989706 -0.001876235 -0.03215342 0.9994451 0.008700311 0.04420334 0.9982572 0.03909951 0.01356661 0.9997569 0.01738047 -0.02931368 0.9995663 0.002813339 -8.40756e-4 0.9996371 0.02692854 0.02857166 0.9993905 0.02005881 -0.0197553 0.9981452 -0.05758422 0.04960417 0.9985613 0.02036315 -0.01980298 0.9991547 -0.03602528 0.03987985 0.9991744 0.007757186 -1.57562e-4 0.9995709 0.02929502 -0.03734642 0.9993024 9.92346e-5 0.04899358 0.9987992 2.78484e-4 0.001217305 0.9985316 -0.05415779 0.01685023 0.9989423 -0.04278373 0.01720976 0.9990151 -0.04089969 0.0420978 0.9989927 -0.01554173 1.35017e-4 0.9995306 0.0306375 -2.56052e-4 0.9999992 -0.001215219 0.001257061 0.9999992 -2.30649e-5 0.03529703 0.9992358 -0.01679521 -0.03647798 0.9984742 0.04145729 0.03809303 0.9983424 -0.04314595 -8.39925e-5 0.9984248 -0.0561068 -0.001366019 0.9999991 2.84488e-4 -0.03207468 0.9992364 0.02231508 0.03182566 0.9994928 0.00116384 -0.01953393 0.998728 0.04648566 -0.0483641 0.9986693 0.01790457 0.002701222 0.999723 0.02338445 -0.008154749 0.9992551 0.03772181 0.001346409 0.9999982 0.001443564 2.85219e-4 0.999999 -0.001440584 -4.26651e-4 0.9999998 7.07991e-4 -0.001589 0.9999756 0.006819844 -0.04870289 0.9987851 0.007510781 2.78449e-4 0.9987719 0.04954522 -0.001296997 0.9999992 1.20695e-4 -0.03349715 0.9994353 -0.002676308 0.009328544 0.999618 0.02601873 0.01659917 0.9988194 0.04565507 0.7971736 -0.003928363 0.6037375 0.8371688 -0.02469092 0.5463871 0.3907819 -0.02414345 0.9201667 0.349193 -0.01405209 0.9369456 -0.6207736 -0.02563965 0.7835705 -0.542501 0.001066267 0.8400545 -0.9117302 -0.01616036 0.4104716 -0.9116256 -0.01609712 0.4107064 -0.9997932 0.02033382 -3.35866e-4 -0.9997844 0.02074885 -8.37824e-4 0.01214045 0.02862757 -0.9995165 -0.004507839 0.02149677 -0.9997588 0.3398677 0.02567046 -0.940123 0.4086727 0.01358455 -0.91258 0.6901827 0.03923451 -0.7225708 0.8504014 -0.002022802 -0.5261305 0.935144 0.03257489 -0.3527673 0.9997449 0.02257227 8.67363e-4 0.9997735 0.02051991 0.005660176 -2.96537e-5 0.02086174 0.9997825 -6.34519e-4 0.02136081 0.9997717 0.006855368 0.9999764 6.32379e-4 4.84183e-5 0.9999936 0.00359565 -0.002482593 0.9999966 -9.32055e-4 -0.002998709 0.9999955 8.29423e-5 1.54639e-5 0.02138823 0.9997712 -1.39133e-4 0.02151739 0.9997684 -0.9997454 0.02247363 0.002029716 -0.9997687 0.02031034 0.00707817 -0.8811984 0.03637057 -0.4713457 -0.9005144 0.02349865 -0.4341907 -0.3984712 0.02666592 -0.9167931 -0.4344211 0.01645815 -0.9005595 0.001909852 0.02104699 -0.9997767 0.003094434 0.02054262 -0.9997842 0.9997859 0.02069485 -1.41352e-4 0.9997981 0.02008581 5.95311e-4 1.38179e-4 0.9999859 0.005308985 0.005393385 0.9999855 5.00562e-5 -0.01043295 0.9999456 5.47132e-5 2.11252e-4 0.9999458 -0.01041203 0.6565264 0.03311085 -0.7535761 0.6074793 0.01187402 -0.7942469 0.9989751 0.01611906 -0.04229778 0.998706 0.01903963 -0.04715883 0.004506647 0.02152347 0.9997582 -0.01208919 0.02863895 0.9995168 -0.3397133 0.02572649 0.9401772 -0.4084981 0.01364552 0.9126572 -0.6901832 0.03932148 0.7225656 -0.8505302 -0.001964092 0.5259227 -0.9351432 0.03258693 0.3527681 -0.9997715 0.02059018 -0.005760371 -0.9997436 0.0226224 -0.001016139 0.02383154 0.02594166 -0.9993794 0.01405942 0.02096039 -0.9996815 0.003094553 0.9999918 0.002593219 0.001917719 0.9999966 0.001798987 4.78276e-4 0.9999992 -0.001183092 -0.005550026 0.9999728 -0.004876852 -0.002536773 0.9999964 -8.84468e-4 -0.9139914 0.04412901 -0.4033268 -0.5442567 0.0384522 -0.8380371 -0.7084817 -0.007274329 -0.7056916 -0.0296927 0.02016824 -0.9993556 -0.01581388 0.01243138 -0.9997978 0.9997709 0.02093356 -0.00445789 0.999498 0.02863979 0.01354718 0.9401232 0.0257008 0.3398647 0.9126565 0.01364713 0.4084995 0.7225604 0.03932052 0.6901888 0.5259218 -0.001965582 0.8505307 0.3529133 0.03255659 0.9350895 -0.002043247 0.02258056 0.9997431 -0.005658864 0.02102994 0.9997629 -0.9999532 0.00752151 -0.006095588 -0.9993792 0.02015662 -0.02889633 0.001256287 0.9999954 -0.002805233 0.001515209 0.9999939 -0.00313121 -0.004870176 0.9999728 0.005549013 -8.41709e-4 0.9999977 0.001997113 -0.007501363 0.9999103 0.01110303 0.01065987 0.9999243 -0.006161391 0.002291321 0.9999756 0.006608426 0.00793755 0.9999635 0.003200709 -0.002152085 0.9999123 -0.01307111 0.008297443 0.9999594 0.003510475 -0.005011558 0.9999145 -0.01207667 0.003060996 0.9999685 0.007328271 0.01080811 0.9998736 -0.01166355 -0.01513278 0.9998643 0.0065158 0.0114302 0.9999113 -0.006848156 0.02179026 0.9996752 -0.01321882 0.004884779 0.9999445 -0.009344935 -0.003964126 0.9999364 -0.010562 0.01132041 0.9994015 -0.03268706 -0.00926429 0.9998297 0.01596432 -0.0349425 0.9993784 -0.004681348 0.01821058 0.9998341 3.55366e-4 4.26147e-4 0.9999064 -0.01367837 0.974332 0.0273106 0.2234531 0.9557826 0.1073004 0.2737999 0.3330882 -0.09754341 -0.9378367 0.5195872 -0.01804101 0.8542271 0.6077356 0.157705 -0.7783231 0.454423 0.08751672 0.8864765 0.9130505 -0.02745264 -0.4069218 0.8992295 0.02166306 -0.4369408 -0.7316485 0.1390356 0.6673527 0.02041035 -0.07851171 -0.9967043 -0.7062311 0.09077769 0.7021376 -0.3488763 0.1379652 -0.9269579 -0.1099019 -0.03246331 0.9934122 -0.6970786 -0.07612544 -0.7129421 -0.9202278 0.2171145 -0.3256413 -0.9950422 -0.0954045 0.02808898 -0.9590265 0.08210206 -0.2711592 -0.9707195 -0.0202586 0.2393601 -0.3562378 0.02584266 0.9340379 -0.2114403 0.06310951 0.9753514 -0.5969607 -0.02077889 -0.8020014 0.2474833 0.9674915 0.05208033 -0.2217323 0.9520062 0.2109952 0.002188146 0.9818791 0.1894964 0.03618705 0.9948542 -0.094635 -0.03970462 0.9977954 -0.0531792 0.1493319 0.9880552 -0.03803724 0.05737549 0.9980043 -0.02637308 0.03952348 0.9942676 -0.09934759 0.06763625 0.9944091 0.08109295 0.05163925 0.9962646 0.06921261 -0.5420612 0.8172764 -0.1955223 -0.05316966 0.9883101 -0.1428857 -0.04296433 0.9990067 0.01181876 -0.03037488 0.9966946 0.07534843 2.75087e-4 1 -2.00694e-4 -2.71444e-4 1 1.27208e-4 -0.006520807 -0.8174653 0.5759411 -0.008352398 -0.819939 0.5723901 -0.007724702 -0.8196955 -0.5727477 -0.006526708 -0.8180925 -0.5750496 0.007609963 -0.8185901 -0.5743277 0.007052063 -0.8178415 -0.5754005 0.007047355 -0.8173345 0.5761204 0.008122503 -0.8187863 0.574041
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 26 0 27 0 28 0 27 1 29 1 28 1 29 2 16 2 28 2 28 3 16 3 100 3 14 4 27 4 26 4 13 5 127 5 12 5 127 6 128 6 12 6 98 7 96 7 11 7 12 8 128 8 10 8 11 9 96 9 9 9 96 10 94 10 9 10 9 11 94 11 13 11 94 12 127 12 13 12 18 13 23 13 129 13 18 14 129 14 95 14 21 15 1 15 130 15 21 16 20 16 1 16 19 17 18 17 95 17 19 18 95 18 30 18 22 19 19 19 30 19 22 20 30 20 4 20 23 21 21 21 130 21 23 22 130 22 129 22 40 23 31 23 41 23 40 24 61 24 31 24 62 25 61 25 40 25 32 26 62 26 40 26 105 27 33 27 46 27 105 28 46 28 45 28 47 29 34 29 48 29 47 30 48 30 102 30 47 31 102 31 35 31 47 32 35 32 36 32 37 33 38 33 39 33 37 34 39 34 103 34 132 35 62 35 32 35 132 36 52 36 62 36 25 37 52 37 132 37 33 38 25 38 132 38 105 39 25 39 33 39 131 40 36 40 35 40 104 41 131 41 35 41 24 42 39 42 131 42 24 43 103 43 39 43 24 44 131 44 104 44 40 45 41 45 42 45 43 46 40 46 42 46 44 47 45 47 46 47 44 48 46 48 43 48 47 49 43 49 42 49 47 50 42 50 34 50 44 51 43 51 38 51 37 52 44 52 38 52 24 53 37 53 103 53 44 54 37 54 24 54 25 55 45 55 44 55 25 56 105 56 45 56 25 57 44 57 24 57 42 58 58 58 106 58 42 59 106 59 48 59 42 60 48 60 34 60 31 61 42 61 41 61 31 62 58 62 42 62 18 63 71 63 70 63 18 64 19 64 71 64 52 65 63 65 125 65 126 66 111 66 20 66 106 67 77 67 50 67 114 68 25 68 117 68 106 69 75 69 77 69 63 70 25 70 114 70 52 71 25 71 63 71 25 72 24 72 117 72 117 73 24 73 110 73 73 74 24 74 108 74 108 75 24 75 104 75 50 76 77 76 104 76 126 77 51 77 111 77 49 78 84 78 86 78 77 79 78 79 104 79 106 80 121 80 75 80 49 81 52 81 84 81 22 82 71 82 19 82 51 83 53 83 111 83 22 84 54 84 71 84 111 85 53 85 55 85 106 86 88 86 121 86 78 87 108 87 104 87 53 88 116 88 55 88 22 89 56 89 54 89 112 90 57 90 22 90 22 91 57 91 56 91 116 92 118 92 55 92 106 93 58 93 88 93 88 94 59 94 121 94 78 95 120 95 108 95 55 96 118 96 112 96 60 97 21 97 122 97 112 98 118 98 57 98 73 99 110 99 24 99 53 100 125 100 116 100 21 101 23 101 122 101 52 102 125 102 84 102 59 103 21 103 60 103 122 104 23 104 120 104 58 105 86 105 88 105 59 106 20 106 21 106 56 107 117 107 54 107 88 108 124 108 59 108 54 109 117 109 110 109 120 110 70 110 108 110 120 111 23 111 70 111 124 112 20 112 59 112 125 113 63 113 116 113 23 114 18 114 70 114 49 115 86 115 58 115 126 116 20 116 124 116 52 117 49 117 61 117 52 118 61 118 62 118 49 119 31 119 61 119 49 120 58 120 31 120 50 121 102 121 48 121 106 122 50 122 48 122 104 123 35 123 102 123 50 124 104 124 102 124 63 125 113 125 64 125 114 126 113 126 63 126 116 127 64 127 65 127 63 128 64 128 116 128 66 129 56 129 57 129 67 130 66 130 57 130 67 131 57 131 118 131 65 132 67 132 118 132 65 133 118 133 116 133 115 134 56 134 66 134 115 135 117 135 56 135 113 136 114 136 115 136 115 137 114 137 117 137 67 138 115 138 66 138 113 139 115 139 67 139 64 140 67 140 65 140 64 141 113 141 67 141 109 142 110 142 73 142 68 143 110 143 109 143 54 144 68 144 74 144 110 145 68 145 54 145 72 146 70 146 71 146 72 147 69 147 70 147 74 148 71 148 54 148 74 149 72 149 71 149 70 150 107 150 108 150 70 151 69 151 107 151 109 152 108 152 107 152 109 153 73 153 108 153 72 154 68 154 109 154 72 155 109 155 107 155 74 156 68 156 72 156 69 157 72 157 107 157 76 158 77 158 83 158 77 159 75 159 83 159 119 160 77 160 76 160 119 161 78 161 77 161 119 162 120 162 78 162 79 163 120 163 119 163 80 164 59 164 60 164 81 165 80 165 60 165 81 166 60 166 122 166 79 167 81 167 122 167 79 168 122 168 120 168 59 169 82 169 121 169 59 170 80 170 82 170 75 171 82 171 83 171 121 172 82 172 75 172 79 173 119 173 76 173 81 174 79 174 76 174 83 175 81 175 76 175 83 176 80 176 81 176 82 177 80 177 83 177 93 178 84 178 92 178 85 179 86 179 93 179 86 180 84 180 93 180 86 181 87 181 88 181 86 182 85 182 87 182 87 183 124 183 88 183 89 184 124 184 87 184 90 185 53 185 51 185 91 186 90 186 51 186 91 187 51 187 126 187 89 188 91 188 126 188 89 189 126 189 124 189 123 190 53 190 90 190 123 191 125 191 53 191 84 192 123 192 92 192 125 193 123 193 84 193 85 194 91 194 89 194 85 195 89 195 87 195 92 196 90 196 91 196 92 197 123 197 90 197 93 198 92 198 91 198 93 199 91 199 85 199 96 200 95 200 94 200 94 201 95 201 129 201 96 202 30 202 95 202 127 203 94 203 129 203 96 204 4 204 30 204 8 205 4 205 96 205 127 206 129 206 130 206 5 207 4 207 8 207 3 208 4 208 5 208 128 209 127 209 130 209 0 210 3 210 5 210 1 211 6 211 128 211 1 212 128 212 130 212 0 213 5 213 7 213 2 214 0 214 7 214 1 215 7 215 6 215 2 216 7 216 1 216 111 217 55 217 0 217 111 218 0 218 2 218 112 219 22 219 4 219 20 220 111 220 2 220 112 221 4 221 3 221 20 222 2 222 1 222 112 223 3 223 0 223 55 224 112 224 0 224 97 225 5 225 98 225 128 226 6 226 10 226 5 227 8 227 98 227 10 228 6 228 99 228 8 229 96 229 98 229 6 230 7 230 99 230 99 231 7 231 97 231 7 232 5 232 97 232 100 233 17 233 101 233 17 234 15 234 101 234 15 235 14 235 101 235 101 236 14 236 26 236 16 237 17 237 100 237 27 238 9 238 13 238 15 239 97 239 98 239 14 240 98 240 11 240 10 241 29 241 12 241 99 242 17 242 16 242 29 243 27 243 12 243 12 244 27 244 13 244 10 245 16 245 29 245 27 246 11 246 9 246 27 247 14 247 11 247 99 248 97 248 17 248 10 249 99 249 16 249 17 250 97 250 15 250 15 251 98 251 14 251 100 252 101 252 26 252 100 253 26 253 28 253 38 254 43 254 131 254 38 255 131 255 39 255 47 256 36 256 131 256 43 257 47 257 131 257 132 258 32 258 40 258 132 259 40 259 43 259 132 260 43 260 46 260 33 261 132 261 46 261
+
+
+
+
+
+
+ -9.00074e-4 0.009423434 0.04836726 -0.003461062 0.009425997 0.04545509 0.003367424 0.009420156 0.04579621 0.002354204 0.009420096 0.04753577 0.001209616 0.009421825 0.04820466 -0.002568781 0.009414196 0.04729557 -0.01040536 0.01838725 0.003964126 0.01030278 0.01967525 0.003999948 -0.01030677 0.0197041 0.003999948 -0.01032835 0.01741087 0.006311535 0.01033771 0.01741051 0.006348371 -0.01034963 0.002563178 0.001799941 -0.01046431 0.008072018 0.00174117 -0.01036626 0.002524316 1.33332e-4 -0.01033806 0.008028566 0.003838419 -0.01049768 0.01066809 0.003808438 -0.01037496 0.01157689 0.01777273 -0.01037907 0.01279222 0.01885205 -0.01040232 0.01741403 0.004769504 -0.01033318 0.01621478 0.01882749 -0.0105009 0.01703566 0.01771438 -0.01046431 0.02075433 0.00174117 -0.01025623 0.02079778 0.003920257 -0.01039052 0.02630412 1.63633e-4 -0.01034963 0.02626317 0.001799941 -0.005033195 0.005965054 0.003874957 0.006783962 0.007870256 0.003922104 0.01033878 0.008028566 0.003838419 -0.006688237 0.007825791 0.003912925 -0.004874587 0.002588212 0.003874957 0.004875361 0.002538204 0.003824949 0.004882395 0.006314396 0.00399816 0.01035034 0.002563178 0.001799941 0.004861891 0.002482414 0.001761496 0.0103783 0.002535223 1.52542e-4 -0.004861116 0.002482414 0.001761496 0.0103892 0.02627986 1.33333e-4 0.004900753 0.02634549 0.001760601 0.01035034 0.02626317 0.0018 -0.004838049 0.0262978 0.003838419 0.004838824 0.02625167 0.003884613 -0.004913091 0.02631509 0.001778841 -0.004740357 0.02308672 0.003991544 0.004736721 0.02311271 0.003989934 0.01039779 0.0114066 0.004751801 -0.01040315 0.01144498 0.004798948 0.01038271 0.01261192 0.0187999 0.0102685 0.02076315 0.00393176 0.01045852 0.02080255 0.001745045 0.01046502 0.008072018 0.00174117 0.0104981 0.01065367 0.00379908 0.01040905 0.01161694 0.01771855 0.01049864 0.01723229 0.004730284 0.01032787 0.01605618 0.01887345 0.0104016 0.01821219 0.003997564 0.01050156 0.01703572 0.01771444 0.007072865 0.02100592 0.001899957 0.005206525 0.0225414 0.001899957 0.005030691 0.02334862 0.003700613 0.005556464 0.02213406 0.003700196 0.00697416 0.02102053 0.003698945 0.00515145 0.006138801 0.001899957 0.006992816 0.007812917 0.001899957 0.00512129 0.005962073 0.003693938 -0.00520581 0.0225414 0.001899957 -0.007104516 0.02099138 0.001899957 -0.006963312 0.02102977 0.003688812 -0.005555689 0.02213406 0.003700196 -0.005031466 0.02334022 0.003702998 -0.005051195 0.005644857 0.001899957 -0.005777955 0.006963193 0.001899957 -0.007155239 0.007843613 0.001899957 0.0102126 0.01028144 0.004017531 -0.01020997 0.01025789 0.004015088 0.01020628 0.01744186 0.005109727 0.01019817 0.01733398 0.0177704 -0.01019358 0.01735031 0.01768088 -0.006566226 0.02081871 0.003997445 0.006602585 0.02079534 0.003997147 -0.010293 0.02316558 0.004009008 -0.01038885 0.02522027 0.01918762 0.01040047 0.02525341 0.01903659 -0.008802294 0.01565176 0.0359255 0.008803725 0.01566779 0.03590935 -0.00874871 0.01441204 0.0360046 0.008746623 0.01441162 0.0360043 -0.005330324 0.01430171 0.05381 -0.007801711 0.01423847 0.05229121 0.006546616 0.01429051 0.05324614 0.004800438 0.01433104 0.05383157 0.008537709 0.01441401 0.05053359 -0.008656799 0.01441329 0.04983228 -0.007084131 0.009504258 0.05282139 -0.008779346 0.009461343 0.05011796 -0.005467712 0.009490966 0.05374723 -0.008745193 0.009413957 0.03600448 -0.003062069 0.009426355 0.04349088 0.005491077 0.009412288 0.05355769 0.007737815 0.009533464 0.05224508 0.008802413 0.00950849 0.05020254 0.008748948 0.009413778 0.03600472 -0.009015202 0.008043587 0.03574204 0.009009778 0.008124589 0.03584498 -0.01019251 0.01739948 0.01925081 0.008962452 0.008020699 0.03557926 0.01018828 0.01739251 0.01925343 -0.01036179 0.02530467 0.006144404 0.01038175 0.02526181 0.006118535 0.01029372 0.02316558 0.004009008 -0.01047909 0.01767802 0.01932358 -0.009055435 0.015468 0.03570801 -0.009049296 0.009711623 0.03573459 -0.009055495 0.01410454 0.03572821 0.008973121 0.0141133 0.04968112 0.009049952 0.009715855 0.03573578 0.00905615 0.015468 0.03570801 0.00905627 0.01410877 0.03572702 0.01047974 0.01767796 0.01932418 -0.008394658 0.009712696 0.0514611 -0.008945405 0.01410996 0.04996031 0.008197128 0.01411491 0.05185294 0.005532205 0.009717047 0.05385756 -0.00195378 0.009417772 0.04215568 0.003167867 0.009425044 0.04377222 -2.00269e-6 0.01280522 0.04499375 1.44479e-4 0.00943315 0.04151052 0.001943349 0.009422361 0.04219353 -0.009823203 0.01750212 0.01110798 -0.008970022 0.01753544 0.00949794 -0.008382201 0.02041316 0.008978068 -0.006987035 0.02041316 0.01564162 -0.007580995 0.01771301 0.01545852 -0.007902503 0.01771277 0.008742332 -0.008936882 0.02041321 0.01448625 -0.006165742 0.01771926 0.008206784 -0.005999624 0.02041316 0.008207798 -0.009010076 0.01771265 0.01427406 -0.009712457 0.01771759 0.01274335 -0.009828627 0.02041321 0.01161444 -0.008163809 0.01741361 0.01548886 -0.002160131 0.01771426 0.01179015 -0.001917064 0.0174154 0.01287972 -0.009805202 0.01741385 0.01357632 -0.002761185 0.01771289 0.01393586 -0.003453075 0.01741337 0.01519817 -0.003756344 0.01771312 0.01503348 -0.005418896 0.01771914 0.01575213 -0.005659103 0.01741391 0.01611226 -0.002533555 0.01741403 0.009761214 -0.007441639 0.01741355 0.008155763 -0.004799664 0.01741427 0.008044362 -0.00424081 0.01770812 0.008646845 -0.002989172 0.01771265 0.009725868 -0.003062367 0.02041316 0.01448625 -0.003617048 0.02041316 0.008978068 -0.00524491 0.02041316 0.01571643 -0.002170562 0.02041321 0.01161444 0.002471983 0.01753765 0.01027405 0.003617763 0.02041316 0.008978068 0.005012929 0.02041316 0.01564162 0.004418909 0.01771301 0.01545852 0.004097461 0.01771277 0.008742332 0.003063082 0.02041316 0.01448625 0.005834221 0.01771926 0.008206784 0.006000339 0.02041316 0.008207798 0.002989888 0.01771265 0.01427406 0.002158105 0.01760274 0.01248186 0.002171337 0.02041321 0.01161444 0.002778768 0.01741474 0.01464384 0.009839832 0.01771426 0.01179015 0.009967148 0.01741391 0.01086372 0.009844422 0.01741361 0.01344197 0.009238719 0.01771289 0.01393586 0.008363068 0.01759368 0.01505017 0.006581008 0.01771914 0.01575213 0.00593394 0.01741504 0.01617604 0.006598711 0.01741325 0.00796616 0.008496105 0.01741367 0.008740007 0.004167318 0.01741486 0.008277237 0.007759153 0.01770812 0.008646845 0.009010791 0.01771265 0.009725868 0.008937597 0.02041316 0.01448625 0.008382916 0.02041316 0.008978068 0.006755053 0.02041316 0.01571643 0.009829342 0.02041321 0.01161444
+
+
+
+
+
+
+
+
+
+ -0.6173208 -0.6997168 -0.3595991 -0.3576143 -0.707046 -0.6100803 -0.05557465 -0.6977123 -0.7142192 0.3884154 -0.7004421 -0.5987607 0.6412798 -0.6994922 -0.3153902 -3.80453e-5 -0.02722054 0.9996295 -0.001623749 -0.00173664 0.9999972 -0.001385629 0.9999968 0.002131819 -2.94082e-5 0.9996801 0.02529293 -0.9997308 -0.02070355 0.01047992 -0.996492 -0.05960285 0.05874812 -0.9995005 -0.0228253 0.0218603 -0.9997318 -0.02303814 0.002409517 -0.9999711 1.03593e-4 -0.007609546 -0.9871179 0.01438945 0.1593462 -0.9997745 0.01160097 0.01778519 -0.9955217 6.06525e-4 0.09453237 -0.9998695 0 -0.01615536 -0.9992002 0.01117813 0.03839474 -0.9936029 0.06317126 0.09361022 -0.9989078 0 -0.04672628 -0.9983385 -9.44458e-4 -0.05761367 -0.9994639 0.02053815 0.02549898 0.02691906 -0.07772469 0.9966114 -7.57925e-5 -0.03944176 0.9992219 -5.39687e-4 -0.04273873 0.9990862 -0.02465045 -0.07755041 0.9966837 -0.01240146 -5.80474e-4 0.999923 0.004887759 -0.04581922 0.9989379 -8.45258e-4 0.04984575 0.9987566 -0.0113424 -0.03047823 0.9994711 0.01459175 -0.9997453 0.017219 0 -0.999635 0.02701693 -0.004865527 -0.9987398 0.04995161 -0.01454573 -0.9996188 0.02346885 5.54909e-4 -0.9995224 -0.03089886 0 -0.9996694 -0.02571326 0.01502466 0.9998336 0.01034975 0.004553616 0.9990093 0.04427027 -0.003079414 0.9999591 0.008511543 -0.009448766 0.9996371 0.02522802 0.00111252 0.999331 -0.03655552 -0.003088533 0.9999886 0.003658115 -0.004537403 0.04749792 0.998861 8.04919e-5 0.0335403 0.9994374 9.2647e-4 -8.09439e-4 -0.9999993 -0.001456677 0.001272737 -0.9999982 0.001968502 -0.9998666 0.01621508 -0.001822233 -0.9999467 0.01016861 -0.00216782 0.007191956 0.9999718 0.002324879 -0.02130818 0.9997704 0.9947002 0.05414348 0.08740782 0.9991514 5.23699e-4 -0.04118567 0.9986065 -5.02086e-4 -0.05277365 0.9996373 -0.02062958 0.01731967 0.9995319 0.0195676 0.02351826 0.9964926 -0.05959349 0.05874836 0.9998502 -0.01730716 -4.88135e-4 0.9998542 -0.0170713 -5.9048e-4 0.9990535 -0.01702678 0.04002922 0.9873026 0.01232677 0.1583726 0.9928981 -0.01674908 0.1177833 0.9937262 0.01559793 -0.1107475 0.9998602 5.16274e-4 -0.01671421 0.9991797 0.01174086 0.03876018 0.0451585 -0.009151577 0.998938 0.01034229 0.01257103 0.9998675 -0.006676495 0.03607821 0.9993267 0.9141238 0.39574 -0.08813399 0.6350729 0.7719225 0.02860087 0.616694 0.7850795 0.05778169 0.9974451 0.06390476 0.03193026 0.9947919 0.08216506 0.06031489 0.05975353 0.9982017 -0.004800856 0.07612389 0.9967962 0.02454829 -0.006453335 -0.03733032 0.9992822 0.04496628 0.009730398 0.9989412 0.01094931 -0.01204329 0.9998676 0.6699684 -0.7369019 0.09009993 0.7559792 -0.652562 -0.05155879 0.07367104 -0.9971508 -0.0162183 0.04523348 -0.998431 0.03301161 0.9974095 -0.07126808 0.009756922 0.9968822 -0.07878315 -0.004374206 -0.04653424 -0.00969702 0.9988697 0.00358504 0.03181004 0.9994875 -0.01030498 0.01262295 0.9998673 -0.6320471 0.7742158 0.03326278 -0.6164938 0.7852315 0.05785328 -0.9135053 0.397269 -0.08766609 -0.07136505 0.9973254 -0.01578521 -0.06973493 0.9974779 -0.01322406 -0.9973001 0.06351983 0.03685021 -0.9949859 0.07914489 0.06114923 0.006362855 -0.04335838 0.9990394 0 0 1 -0.01212346 -0.0116012 0.9998593 -0.04727256 0.009681224 0.9988352 -0.8725091 -0.4809787 0.08595079 -0.7466658 -0.6630428 -0.05352085 -0.5349152 -0.8368087 0.1166919 -0.9987596 -0.04689872 0.01672834 -0.9982018 -0.0598486 -0.003362953 -0.06805241 -0.9975442 -0.01656448 -0.05555498 -0.9984475 0.004042565 8.8907e-4 -0.5465997 0.8373936 5.33059e-4 -0.5509776 0.83452 0.004123806 0.6375477 0.7703999 -0.01049071 0.8228656 0.5681393 -0.002518236 0.7106881 0.7035028 0.003334283 0.6536505 0.7567892 -0.003888607 -0.6640311 0.7476949 0.003183066 -0.7358403 0.6771478 -0.01673263 0.8151549 0.5790015 -0.373636 0.4680295 0.8008399 -0.7724587 0.6221879 0.1272386 -0.5668749 0.2445532 0.7866681 -0.6982629 0.01288533 0.7157256 -0.9592375 0.06434804 0.2751776 -0.7147101 0.6993146 0.0121932 -0.4388056 0.8982897 0.02291625 -0.5722144 0.6164847 0.5408489 0.7010007 0.001137077 0.7131597 0.5716685 0.2472016 0.7823597 0.7760108 0.6250861 0.08411097 0.3656786 0.464663 0.8064537 0.01790195 0.7870998 0.6165658 -0.8352044 -0.3911832 0.3865352 0.8707776 0.05801951 0.4882419 -0.6089326 -0.02781635 0.7927342 0.8206587 0.3920653 0.4156971 0.5498748 0.8344168 -0.03723388 0.7022116 0.7118432 0.01335102 0.7825589 0.6225079 0.009247243 0.6339415 0.5412512 0.5524178 0.825253 -0.3975813 0.4011068 0.6290233 -0.02652961 0.7769336 0.7685624 -0.03074413 0.6390359 0.3894513 -0.4363483 0.8111275 -0.00314182 -6.49825e-5 -0.9999951 0.02068835 0.03777194 -0.9990723 -3.36067e-4 0.8681395 0.4963201 0.002206206 0.8693922 0.4941177 8.46412e-4 0.07534265 0.9971574 1.67392e-5 0.0636698 0.9979711 -0.08732438 0.9910644 0.1008257 -0.002960801 0.9995301 0.03051143 -0.001022875 0.9997116 0.02399551 2.38456e-5 1 -1.64642e-4 -4.06208e-5 1 -8.76669e-5 0.05079007 0.9952879 0.08259797 -0.002142012 0.005291461 0.9999838 -0.009764373 -0.01276755 0.9998709 -2.90966e-4 -0.9998711 0.01605635 -0.01195007 -0.9999074 0.006530165 -0.001630723 -0.9999364 0.01116681 -0.008608043 -0.9999605 -0.00224334 -0.007068097 -0.9999536 0.006551086 -0.003706216 -0.9999876 0.003347218 0.001899778 -0.9999982 2.12748e-4 -8.44581e-4 -0.9999991 -0.001112341 -0.0017578 -0.9999984 -3.81976e-4 -0.003001391 -0.9999879 0.003933608 0.04073494 -0.9989163 -0.02251619 0.007122337 -0.9998478 0.01593202 0.005625069 -0.9999686 -0.005591452 0.0110715 -0.9999184 0.006386518 -0.00471729 -0.9999871 -0.001937091 1.70855e-4 -0.9999988 0.001574873 0.01087236 -0.9999189 0.006631433 -0.004614412 -0.9999826 -0.003687381 -1.05398e-5 -0.9999939 0.003507912 -1.60083e-5 -0.1229799 0.9924092 -0.004767358 -0.1871572 0.9823184 -0.005576372 -0.8699347 -0.4931353 -2.32577e-4 -0.8672547 -0.4978647 -9.39586e-4 -0.9995101 0.03128528 -3.46689e-4 -0.999222 0.03943848 0.9927443 -0.1197105 0.01132869 -0.9563636 -0.2920154 0.00978285 -0.001544773 0.9999779 0.006467223 0.002068638 0.9999976 6.48183e-4 -0.01753318 0.03230512 -0.9993243 0.003435969 -3.46711e-4 -0.999994 0.9999831 -0.005649566 -0.001454889 0.9998875 0.01003545 -0.01114994 0.9990121 0.002701461 -0.04435819 0.9997629 -0.006217002 -0.02087032 -0.9999886 -0.00428152 -0.00210607 -0.9998611 0.01174783 -0.01182723 -0.9993346 0.004062354 -0.03624898 -0.9998942 -0.004529297 -0.01382708 -0.9959966 0.01350748 0.08836561 -0.9962916 -0.005001425 0.08589476 0.1560158 -0.001218974 -0.9877539 -0.8825191 -0.006937742 -0.4702252 -0.9992199 -0.03507834 0.01814252 -0.9999691 -0.001396 0.007733643 0.9999812 -0.001430571 0.005960941 0.999244 -0.03516072 0.01659327 0.9861967 -0.03598099 -0.1616215 0.9268067 -0.005166351 -0.3755034 0.8828601 -0.002207458 -0.469631 0.99599 0.01377451 0.08839821 0.9963099 -0.01399958 0.08467978 -9.92955e-4 -0.7099062 -0.7042956 0.001225948 -0.7197995 -0.694181 -0.004333555 0.7094319 -0.7047607 -0.002025246 0.6938492 -0.7201175 5.66681e-4 0.7029646 -0.7112247 0.007812917 0.7066024 -0.7075677 -0.5236616 0.003840386 0.8519178 -0.4967814 0.02186524 0.8676003 -0.722224 -0.03209161 0.6909145 -0.897934 0.03721666 0.4385541 -0.9598154 -0.02480667 0.2795337 0.9416905 0.003191292 0.3364652 0.8854467 -0.04922211 0.4621273 0.6450628 7.23494e-4 0.7641294 0.5888193 -0.02258223 0.8079492 0.3185542 0.05585145 0.9462579 0.6454741 0.03606939 0.7629301 0.7021592 -0.638997 -0.3140944 0.7092592 -0.704945 0.00203222 0.40139 -0.669321 0.6252164 0.007280707 -0.701826 0.7123112 -0.4307134 -0.8567411 0.2836915 0.6988441 -0.7152389 0.007094681 -0.6998528 -0.7142868 7.04012e-4 0.9143519 0.003415346 -0.4049062 -0.6693444 -0.01035511 0.7428802 -0.7015902 -0.6401234 -0.3130708 0.7415134 0.5508129 0.3830968 -0.6960386 -0.7179734 0.006675958 0.6727006 0.01042085 0.7398415 0.6373302 0.02924919 0.7700356 -0.8728793 0.004739582 -0.4879134 0.5790942 0.8152195 0.008191704 0.7009998 0.7131506 0.003946483 0.8712383 0.3797509 0.3110197 0.3404745 0.8942072 0.2906386 -0.6368297 0.7211554 0.2727323 -0.7100141 0.7041722 0.00465697 -0.7232721 0.6905426 0.005329906 -0.7358887 0.5554174 0.3872717 -0.6753374 0.01095151 0.7374276 -0.6596332 0.01942574 0.7513366 -0.7151693 -0.6955538 0.06883221 -0.5688336 -0.6933217 0.4424177 0.7013344 -0.6984758 0.1423438 0.5473927 -0.6998478 0.4588838 0.215398 -0.6970678 0.6838861 -0.2572834 -0.6996731 0.6665304 0.002106964 -0.9314457 0.3638744 0.881494 -0.07933795 0.4654824 0.8730121 -0.09179598 0.4789817 0.04290962 -0.01601725 -0.9989507 0.1343826 0.03758537 -0.9902165 0.5740847 0.03054869 0.8182259 0.5090357 -0.05372262 -0.8590674 0.3075678 -0.01931774 0.9513301 0.2946367 -0.03103911 0.9551053 0.6375395 0.04316061 -0.7692078 0.908856 0.008138895 -0.4170307 0.9517405 -0.08275151 -0.2955374 0.9974929 0.013933 -0.06938272 0.3853201 -0.7972646 -0.464648 0.6791377 -0.4461136 -0.5828849 -0.4734979 -0.8706647 -0.1332018 0.5195622 -0.8197226 -0.2410601 -0.4938721 -0.7454801 -0.4476045 -0.7729855 -0.3740035 -0.5124595 0.9405061 -0.3392458 -0.01898998 -0.2958472 -0.6721335 -0.678757 -0.3007575 -0.6191048 -0.7254338 0.08517628 -0.7873173 -0.6106361 0.2099796 -0.49436 -0.8435145 -2.64102e-4 1 1.74548e-4 -1.1962e-5 1 1.26157e-4 2.06489e-4 1 -9.33148e-5 -0.001511156 0.9999989 -1.27516e-4 -0.04181706 0.9984763 0.03600627 -0.005665898 0.9997345 0.02233892 -0.002253532 0.9998073 0.01950085 0.06646537 0.9976769 -0.01493656 -0.4830842 -0.6704126 0.5631843 -0.5432407 -0.436838 0.7169812 -0.5465892 -0.8079434 0.2201545 -0.9770032 -0.09024524 0.193185 0.560274 -0.4717366 0.6808507 0.1733226 -0.8155052 0.5521872 0.03994673 -0.3244244 0.9450678 -0.1251857 -0.8410195 0.526322 -0.9616039 -0.05233085 -0.2694056 -0.7397416 0.05420136 -0.6707046 -0.3073062 -0.04552447 0.9505212 -0.2227703 0.0133779 0.9747792 -0.4903982 -0.05028861 -0.8700463 -0.3967096 0.01345318 -0.9178457 -0.6519777 0.05776602 0.7560345 -0.8745346 -0.07046216 0.479817 -0.9277643 0.02065652 0.3725948 -0.9547636 -0.02300214 -0.2964749 -8.72595e-7 -1 -9.46364e-6 -3.17071e-7 -1 0 -3.17071e-7 -1 8.62569e-7 -4.37507e-7 -1 0 0 -1 0 3.97679e-6 -1 0 0 -1 1.98224e-6 0.8691138 -0.1313883 0.476842 0.04290962 -0.01601725 -0.9989507 0.1343826 0.03758537 -0.9902165 0.6814061 0.0573436 0.7296558 0.5090351 -0.0537225 -0.8590676 0.3075684 -0.01931768 0.9513298 0.2946355 -0.03103935 0.9551055 0.6375406 0.04316031 -0.7692069 0.9068413 0.008536219 -0.4213859 0.9506452 -0.09556525 -0.295197 0.9894707 0.03842526 0.1395403 0.3696854 -0.8152374 -0.445781 -0.9627035 -0.2666468 -0.04584342 -0.5335696 -0.8323478 -0.1500031 0.6228252 -0.7435162 -0.2434592 -0.6888185 -0.4255956 -0.5868538 -0.3595518 -0.4060154 -0.8401631 0.07133811 -0.8588427 -0.5072478 0.4361646 0.05485165 -0.8981936 -3.43774e-4 1 1.10847e-4 3.18553e-4 1 -7.99342e-5 -0.2044163 0.9340437 -0.2928762 0.09703636 0.9950554 0.02118116 6.65985e-4 0.9999999 1.63433e-4 -1.12083e-4 1 -2.28791e-4 -3.81542e-6 1 -2.18706e-4 0.2618119 0.9650791 0.008770644 -0.3162638 -0.5461402 0.7756986 -0.3802565 -0.8110634 0.4445014 -0.7460662 -0.4198443 0.5168328 -0.5037703 -0.839652 0.2029784 0.5497425 -0.6634614 0.5075454 0.1725108 -0.8174732 0.5495251 0.1148756 -0.4207645 0.8998672 -0.1376492 -0.8015738 0.5818353 -0.9616038 -0.05233103 -0.2694066 -0.7876538 0.03749954 -0.6149759 -0.3073074 -0.04552465 0.9505208 -0.4896735 -0.07398062 -0.8687616 -0.3657975 0.01128739 -0.9306262 -0.6519766 0.05776649 0.7560356 -0.8745356 -0.07046252 0.4798154 -0.9277645 0.02065676 0.372594 -0.9547633 -0.02300065 -0.2964761 -3.97679e-6 -1 1.11854e-6 -1.04644e-6 -1 -6.13438e-7 1.79553e-6 -1 5.72717e-7 3.97679e-6 -1 5.59268e-7 -8.85583e-7 -1 0 0 -1 4.29807e-7
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 3 0 124 0 2 0 4 1 124 1 3 1 4 2 0 2 124 2 0 3 5 3 124 3 1 4 124 4 5 4 6 5 7 5 8 5 6 6 54 6 7 6 9 7 74 7 18 7 9 8 10 8 74 8 11 9 12 9 13 9 14 10 15 10 12 10 17 11 20 11 16 11 16 12 20 12 45 12 45 13 20 13 18 13 17 14 19 14 20 14 18 15 6 15 15 15 45 16 18 16 15 16 15 17 21 17 12 17 6 18 21 18 15 18 6 19 22 19 21 19 21 20 23 20 12 20 12 21 23 21 13 21 21 22 24 22 23 22 72 23 26 23 27 23 72 24 73 24 26 24 26 25 73 25 28 25 73 26 14 26 28 26 25 27 29 27 31 27 31 28 29 28 30 28 26 29 28 29 31 29 28 30 25 30 31 30 32 31 33 31 34 31 30 32 35 32 33 32 29 33 35 33 30 33 11 34 13 34 35 34 13 35 34 35 33 35 13 36 33 36 35 36 36 37 37 37 38 37 37 38 39 38 40 38 41 39 39 39 37 39 23 40 24 40 41 40 36 41 23 41 37 41 37 42 23 42 41 42 42 43 40 43 39 43 42 44 43 44 40 44 13 45 36 45 34 45 13 46 23 46 36 46 51 47 16 47 44 47 16 48 45 48 44 48 19 49 17 49 53 49 53 50 17 50 46 50 47 51 54 51 48 51 36 52 48 52 49 52 36 53 49 53 34 53 49 54 32 54 34 54 38 55 48 55 36 55 50 56 27 56 49 56 55 57 44 57 52 57 55 58 51 58 44 58 55 59 46 59 51 59 53 60 46 60 55 60 52 61 44 61 50 61 54 62 52 62 50 62 48 63 50 63 49 63 48 64 54 64 50 64 38 65 56 65 48 65 57 66 56 66 38 66 37 67 57 67 38 67 58 68 59 68 57 68 57 69 60 69 56 69 59 70 60 70 57 70 40 71 58 71 37 71 37 72 58 72 57 72 60 73 48 73 56 73 60 74 47 74 48 74 61 75 33 75 32 75 62 76 32 76 49 76 62 77 61 77 32 77 62 78 26 78 61 78 26 79 63 79 61 79 27 80 62 80 49 80 27 81 26 81 62 81 63 82 30 82 61 82 61 83 30 83 33 83 24 84 21 84 65 84 24 85 64 85 41 85 24 86 65 86 64 86 65 87 66 87 64 87 66 88 67 88 64 88 67 89 68 89 64 89 22 90 66 90 65 90 22 91 65 91 21 91 68 92 39 92 41 92 68 93 41 93 64 93 11 94 35 94 69 94 71 95 69 95 70 95 71 96 11 96 69 96 12 97 11 97 71 97 69 98 25 98 70 98 25 99 28 99 70 99 70 100 28 100 71 100 29 101 25 101 69 101 29 102 69 102 35 102 71 103 14 103 12 103 28 104 14 104 71 104 44 105 45 105 72 105 45 106 73 106 72 106 6 107 18 107 54 107 54 108 18 108 74 108 75 109 76 109 19 109 53 110 75 110 19 110 46 111 17 111 16 111 51 112 46 112 16 112 66 113 22 113 77 113 66 114 77 114 67 114 77 115 42 115 67 115 67 116 42 116 68 116 39 117 68 117 42 117 8 118 22 118 6 118 76 119 9 119 20 119 9 120 18 120 20 120 19 121 76 121 20 121 40 122 43 122 58 122 58 123 43 123 59 123 43 124 78 124 59 124 59 125 78 125 60 125 78 126 47 126 60 126 45 127 15 127 73 127 54 128 47 128 7 128 14 129 73 129 15 129 74 130 52 130 54 130 10 131 52 131 74 131 55 132 10 132 75 132 55 133 52 133 10 133 75 134 53 134 55 134 72 135 50 135 44 135 27 136 50 136 72 136 31 137 30 137 63 137 26 138 31 138 63 138 77 139 79 139 42 139 77 140 22 140 79 140 82 141 83 141 80 141 83 142 81 142 80 142 82 143 85 143 83 143 84 144 85 144 82 144 86 145 91 145 87 145 89 146 91 146 86 146 90 147 91 147 89 147 85 148 84 148 90 148 90 149 84 149 91 149 90 150 89 150 88 150 86 151 121 151 89 151 94 152 121 152 86 152 93 153 5 153 92 153 92 154 5 154 94 154 5 155 0 155 94 155 93 156 1 156 5 156 94 157 0 157 97 157 93 158 95 158 1 158 95 159 96 159 1 159 0 160 4 160 97 160 4 161 3 161 97 161 95 162 122 162 96 162 97 163 3 163 98 163 3 164 99 164 98 164 95 165 125 165 122 165 2 166 99 166 3 166 123 167 100 167 2 167 126 168 100 168 123 168 2 169 100 169 99 169 125 170 100 170 126 170 95 171 100 171 125 171 95 172 102 172 100 172 101 173 102 173 95 173 103 174 104 174 101 174 105 175 104 175 103 175 76 176 75 176 103 176 75 177 105 177 103 177 10 178 105 178 75 178 103 179 9 179 76 179 80 180 81 180 106 180 81 181 107 181 106 181 47 182 78 182 108 182 108 183 78 183 43 183 10 184 107 184 81 184 10 185 81 185 117 185 7 186 108 186 107 186 7 187 107 187 10 187 106 188 9 188 80 188 80 189 9 189 109 189 79 190 8 190 106 190 106 191 8 191 9 191 109 192 110 192 80 192 109 193 101 193 110 193 112 194 101 194 111 194 112 195 110 195 101 195 111 196 93 196 119 196 111 197 119 197 112 197 113 198 114 198 116 198 99 199 114 199 113 199 104 200 114 200 102 200 115 201 116 201 104 201 104 202 116 202 114 202 115 203 117 203 81 203 104 204 117 204 115 204 7 205 9 205 8 205 10 206 9 206 7 206 108 207 43 207 107 207 42 208 106 208 43 208 43 209 106 209 107 209 42 210 79 210 106 210 94 211 86 211 87 211 92 212 94 212 87 212 118 213 92 213 87 213 118 214 87 214 119 214 93 215 118 215 119 215 99 216 113 216 120 216 98 217 99 217 120 217 98 218 120 218 88 218 121 219 98 219 88 219 121 220 88 220 89 220 100 221 102 221 114 221 104 222 105 222 117 222 99 223 100 223 114 223 98 224 121 224 97 224 121 225 94 225 97 225 118 226 93 226 92 226 105 227 10 227 117 227 95 228 93 228 111 228 7 229 47 229 108 229 101 230 95 230 111 230 103 231 101 231 109 231 81 232 83 232 115 232 109 233 9 233 103 233 115 234 85 234 116 234 83 235 85 235 115 235 8 236 79 236 22 236 85 237 90 237 113 237 85 238 113 238 116 238 120 239 113 239 90 239 120 240 90 240 88 240 119 241 87 241 91 241 91 242 84 242 112 242 91 243 112 243 119 243 82 244 80 244 110 244 84 245 110 245 112 245 84 246 82 246 110 246 2 247 124 247 123 247 126 248 123 248 124 248 96 249 124 249 1 249 96 250 122 250 124 250 122 251 125 251 124 251 125 252 126 252 124 252 102 253 101 253 104 253 127 254 128 254 138 254 138 255 128 255 129 255 155 256 146 256 130 256 146 257 131 257 130 257 128 258 132 258 129 258 130 259 131 259 133 259 129 260 134 260 135 260 132 261 134 261 129 261 131 262 136 262 133 262 136 263 137 263 133 263 133 264 137 264 138 264 137 265 127 265 138 265 131 266 139 266 136 266 139 267 142 267 136 267 140 268 141 268 143 268 136 269 142 269 137 269 143 270 144 270 145 270 141 271 144 271 143 271 142 272 127 272 137 272 145 273 144 273 146 273 144 274 147 274 146 274 146 275 147 275 131 275 147 276 139 276 131 276 149 277 144 277 150 277 150 278 144 278 148 278 147 279 144 279 149 279 148 280 144 280 141 280 142 281 139 281 127 281 139 282 147 282 127 282 127 283 147 283 128 283 128 284 147 284 149 284 151 285 148 285 152 285 150 286 148 286 151 286 152 287 148 287 140 287 148 288 141 288 140 288 128 289 149 289 132 289 132 290 149 290 134 290 149 291 150 291 134 291 134 292 150 292 151 292 140 293 143 293 153 293 143 294 145 294 153 294 135 295 151 295 154 295 134 296 151 296 135 296 153 297 145 297 155 297 145 298 146 298 155 298 151 299 152 299 154 299 154 300 152 300 156 300 152 301 140 301 156 301 156 302 140 302 153 302 153 303 155 303 130 303 153 304 130 304 133 304 153 305 133 305 138 305 154 306 153 306 138 306 154 307 138 307 129 307 154 308 156 308 153 308 154 309 129 309 135 309 167 310 157 310 158 310 183 311 174 311 159 311 174 312 160 312 159 312 157 313 161 313 158 313 159 314 160 314 162 314 158 315 163 315 164 315 161 316 163 316 158 316 160 317 165 317 162 317 165 318 166 318 162 318 162 319 166 319 167 319 166 320 157 320 167 320 160 321 168 321 165 321 170 322 171 322 169 322 169 323 171 323 172 323 165 324 168 324 166 324 171 325 173 325 172 325 173 326 175 326 174 326 174 327 175 327 160 327 175 328 168 328 160 328 177 329 171 329 170 329 175 330 171 330 177 330 175 331 173 331 171 331 157 332 168 332 178 332 178 333 168 333 176 333 176 334 175 334 177 334 168 335 175 335 176 335 166 336 168 336 157 336 176 337 177 337 179 337 179 338 177 338 180 338 177 339 170 339 180 339 180 340 170 340 169 340 157 341 178 341 161 341 161 342 178 342 163 342 178 343 176 343 163 343 163 344 176 344 179 344 169 345 172 345 181 345 172 346 173 346 181 346 164 347 179 347 182 347 163 296 179 296 164 296 181 348 173 348 183 348 173 349 174 349 183 349 179 350 180 350 182 350 182 351 180 351 184 351 180 352 169 352 184 352 184 353 169 353 181 353 158 354 162 354 167 354 182 355 183 355 159 355 182 356 181 356 183 356 182 357 184 357 181 357 182 307 159 307 164 307 164 358 159 358 162 358 164 359 162 359 158 359
+
+
+
+
+
+
+
+
+ 1 0 0 0 0 1 0 0 0 0 1 0 0 0 0 1
+
+
+
+
+
+
+
+
+
+ 1 0 0 0 0 1 0 0 0 0 1 0 0 0 0 1
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/embodied_gen/scripts/affordance_annot/franka_panda/meshes/visual/hand.dae b/embodied_gen/scripts/affordance_annot/franka_panda/meshes/visual/hand.dae
new file mode 100644
index 0000000..454bfcd
--- /dev/null
+++ b/embodied_gen/scripts/affordance_annot/franka_panda/meshes/visual/hand.dae
@@ -0,0 +1,403 @@
+
+
+
+
+ Blender User
+ Blender 2.79.0
+
+ 2018-02-22T14:32:03
+ 2018-02-22T14:32:03
+
+ Z_UP
+
+
+
+
+
+
+
+
+ 0 0 0 1
+
+
+ 0 0 0 1
+
+
+ 0.9019608 0.9215686 0.9294118 1
+
+
+ 0.03125 0.03125 0.03125 1
+
+
+ 0
+
+
+ 1 1 1 1
+
+ 1
+
+ 1
+
+
+
+
+
+
+
+
+
+
+ 0 0 0 1
+
+
+ 0 0 0 1
+
+
+ 1 1 1 1
+
+
+ 0.03125 0.03125 0.03125 1
+
+
+ 0
+
+
+ 1 1 1 1
+
+ 1
+
+ 1
+
+
+
+
+
+
+
+
+
+
+ 0 0 0 1
+
+
+ 0 0 0 1
+
+
+ 0.2509804 0.2509804 0.2509804 1
+
+
+ 0.03125 0.03125 0.03125 1
+
+
+ 0
+
+
+ 1 1 1 1
+
+ 1
+
+ 1
+
+
+
+
+
+
+
+
+
+
+ 0 0 0 1
+
+
+ 0 0 0 1
+
+
+ 0.2509804 0.2509804 0.2509804 1
+
+
+ 0.015625 0.015625 0.015625 1
+
+
+ 0
+
+
+ 1 1 1 1
+
+ 1
+
+ 1
+
+
+
+
+
+
+
+
+
+
+ 0 0 0 1
+
+
+ 0 0 0 1
+
+
+ 0.9019608 0.9215686 0.9294118 1
+
+
+ 0.03125 0.03125 0.03125 1
+
+
+ 0
+
+
+ 1 1 1 1
+
+ 1
+
+ 1
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 0.02017825 0.01584839 7.17238e-5 0.01838719 0.02086764 8.44626e-7 0.0178166 0.02069735 2.03923e-4 0.01916742 0.01478332 4.37059e-7 0.01887512 0.01490169 2.00046e-4 0.01912039 0.02033436 2.00529e-4 0.01739293 0.01458746 6.0486e-5 0.02006298 0.01964491 7.73282e-5 0.02095651 0.01803833 1.80386e-6 0.02065283 0.01821488 2.00466e-4 0.02059245 0.01687484 2.00371e-4 -0.002628803 0.01455265 -0.002500057 -0.001612365 0.01574534 -0.001504898 -0.006113469 0.0145573 -0.001514554 -0.007182717 0.01294827 -0.002500176 -0.00960642 0.01252943 -0.001490414 -0.01020991 0.01067572 -0.002499938 -0.01240199 0.009769976 -0.001489222 -0.0128479 0.007396519 -0.002500236 -0.01421493 0.006837606 -0.001508653 -0.01451277 0.002914965 -0.002500057 -0.01538765 0.003536403 -0.001499593 -0.01577347 -7.67544e-5 -0.001500487 -0.01475828 -8.12761e-4 -0.002500057 -0.01535922 -0.003654897 -0.00150007 -0.01380175 -0.005391955 -0.002500176 -0.01359897 -0.008096933 -0.001493275 -0.01136183 -0.009479463 -0.002500057 -0.01067048 -0.01163631 -0.001490533 -0.007584929 -0.01274478 -0.002500355 -0.007468104 -0.01391035 -0.001489222 -0.003531396 -0.01540726 -0.001505196 -0.002375543 -0.01461595 -0.002500236 5.49074e-5 -0.01576226 -0.00149703 -0.004696846 0.03124696 9.998e-4 -0.009213864 0.02917921 -1.64052e-7 -0.01236826 0.02904361 9.99946e-4 -0.01573807 0.02617561 0 -0.01635605 0.02698409 0.001000702 -0.01983654 0.02319985 0 -0.02109485 0.02342647 9.99703e-4 -0.02441293 0.01845329 -1.82938e-7 -0.02421003 0.02019095 9.9969e-4 -0.02683353 0.01654559 9.99675e-4 -0.02864557 0.01076871 -1.47618e-7 -0.02891248 0.01256448 9.99667e-4 -0.03040432 0.008328199 9.99662e-4 -0.03052353 0.002198934 -1.19772e-7 -0.03127944 0.003922879 9.99656e-4 -0.03151953 -5.61752e-4 9.99658e-4 -0.02989315 -0.006551444 -1.36875e-7 -0.03111976 -0.005035281 9.99658e-4 -0.03008842 -0.009406507 9.99663e-4 -0.02680617 -0.01476359 -1.59811e-7 -0.02844607 -0.01358664 9.99671e-4 -0.02622658 -0.0174914 9.99679e-4 -0.02151536 -0.02176249 -1.82759e-7 -0.02347499 -0.02104103 9.99691e-4 -0.01979357 -0.02455765 0.001001358 -0.01573807 -0.02617561 0 -0.01603806 -0.02718704 0.001004636 -0.009213864 -0.02917921 -1.64054e-7 -0.008953928 -0.03030282 9.9985e-4 -0.002150416 -0.03046697 0 -0.002154827 -0.03145825 9.99984e-4 0.02011495 0.01588898 0.006499946 0.01738888 0.02070087 0.006499946 0.01879709 0.01483595 0.006499946 0.01887929 0.02045166 0.006499946 0.02008414 0.01953077 0.006499946 0.01661098 0.01479429 0.006499946 0.02075195 0.01762264 0.006499946 -0.001451373 0.01576071 0 -0.005658626 0.01472359 0 -0.008795738 0.01311135 0 -0.01229602 0.009965598 0 -0.01510941 0.004793167 0 -0.01578581 -2.75392e-4 0 -0.01504236 -0.004922449 0 -0.01288729 -0.009120285 0 -0.01052016 -0.0117529 0 -0.006979048 -0.01420569 0 -0.002848327 -0.01551449 0 -0.01944303 -7.32697e-5 0.007499933 -0.01944303 7.32697e-5 0.003999948 -0.02367246 -0.00537759 0.007499933 -0.02405256 -0.005505025 0.003999948 -0.02002227 0.00243026 0.007499933 -0.02008599 0.002556622 0.003999948 -0.02157527 -0.004353523 0.007499933 -0.02146518 -0.004264712 0.003999948 -0.02146518 0.004264712 0.007499933 -0.02157527 0.004353523 0.003999948 -0.02008599 -0.002556622 0.007499933 -0.02002227 -0.00243026 0.003999948 -0.02310794 0.005177915 0.007499933 -0.02367246 0.00537759 0.003999948 -0.02491819 0.005537629 0.007499933 -0.02669602 0.005338311 0.003999948 0.03054976 -4.79156e-4 0.007494628 0.02669674 -0.005338311 0.003999948 0.02979689 0.002889633 0.007499933 0.0290687 0.003850281 0.003999948 0.0269587 -0.005164623 0.007499933 0.02925068 -0.003646016 0.007499933 0.02965658 -0.003113925 0.003999948 0.02778822 0.004768431 0.007499933 0.02616012 0.005440473 0.003999948 0.02591401 0.005456805 0.007499933 0.03058212 4.71106e-4 0.003999948 0.02690047 0.002703607 0 0.0268085 0.002766132 0.003999948 0.02583068 -0.003198683 0.003999948 0.02451616 -0.003313422 0 0.02690047 -0.002703607 0 0.02440094 0.003294587 0.003999948 0.02747058 -0.002168715 0.003999948 0.02804946 -0.001214981 0 0.0281465 -8.73551e-4 0.003999948 0.02821916 8.34403e-4 0 0.02818858 9.44507e-4 0.003999948 -0.0225026 0.002195835 0.003999948 -0.02309924 0.002703607 0 -0.02429378 -0.003205657 0.003999948 -0.02600705 -0.003168821 0 -0.02407521 -0.003140032 0 -0.02469694 0.003311336 0.003999948 -0.02477186 0.003265202 0 -0.02291899 -0.002526998 0.003999948 -0.02247065 -0.002148032 0 -0.02194052 -0.001218736 0.003999948 -0.0217514 -4.0392e-4 0 -0.0217514 4.0392e-4 0.003999948 -0.02192622 0.001166701 0 -0.01502948 0.02783823 0.01002681 -0.01500052 0.02825462 0.009084582 -0.01484274 0.02843225 0.007539093 -0.01502281 -0.02784794 0.01011466 -0.0149914 -0.02824354 0.009112536 0.005090713 -0.0539658 0.001058518 -0.01483881 -0.02843737 0.00753206 -0.007413208 -0.03200536 9.99985e-4 -0.007269084 -0.05396461 0.001057982 0.01606065 0.02719628 0.001004219 0.01751917 0.02618968 0.006611347 0.02072632 0.02375382 0.006499946 0.02109581 0.02342647 9.99702e-4 0.02388381 0.020576 0.006499946 0.02421075 0.02019095 9.9969e-4 0.02655941 0.01698309 0.006500065 0.02683413 0.01654565 9.99676e-4 0.02869862 0.01304692 0.006500184 0.02891314 0.01256448 9.99667e-4 0.03025799 0.008847177 0.006500184 0.03040528 0.008328258 9.99662e-4 0.03120684 0.004468679 0.006500244 0.0312801 0.003922879 9.99657e-4 0.03152513 0 0.006500244 0.03152 -5.61854e-4 9.9966e-4 0.03120684 -0.004468679 0.006500244 0.0311203 -0.005035281 9.99657e-4 0.03025799 -0.008847177 0.006500184 0.03008908 -0.009406566 9.99663e-4 0.02869862 -0.01304692 0.006500184 0.02844703 -0.01358664 9.99671e-4 0.02655947 -0.01698309 0.006500065 0.02622753 -0.0174914 9.99679e-4 0.02388381 -0.020576 0.006499946 0.02347546 -0.02104103 9.99691e-4 0.02072632 -0.02375382 0.006499946 0.01979643 -0.02455574 0.001001298 0.01752132 -0.02618575 0.006620645 0.01631653 -0.02698135 0.001005411 0.01439923 0.01803833 1.50399e-6 0.01573973 -0.001452088 0 0.02176666 -7.11787e-4 0 0.005005061 -0.03018718 -1.6111e-7 0.001452088 -0.01576071 0 0.02963924 0.007621884 -1.36824e-7 0.006979823 0.01420563 0 0.02451616 0.003313422 0 0.02072 0.02252209 -1.19944e-7 0.002849102 0.01551449 0 0.01585781 0.02042824 1.54782e-6 0.02216142 0.001776099 0 0.01552808 0.002857983 0 0.01525849 0.02644068 0 0.0190503 -0.02395027 -1.79867e-7 0.01035392 -0.01190012 0 0.02506119 -0.01756376 -1.74597e-7 3.65869e-7 0.03063488 -3.22523e-7 -0.02772492 -0.001847684 0 0.01306551 -0.008907735 0 0.03058362 -0.001100242 0 0.01449173 0.006229639 0 0.00921458 0.02917921 -1.50383e-7 0.02901494 -0.009731888 -1.4752e-7 0.006755113 -0.01431363 0 -0.02825868 -3.03727e-4 0 0.01239109 -0.02793645 0 0.02625852 0.0157178 -1.42093e-7 -0.02791029 0.001607716 0 0.01486063 -0.005290091 0 0.01281559 0.009213507 0 -0.02633082 0.003005683 0 0.01530337 0.01538842 1.70407e-6 0.02265894 -0.002287209 0 0.01052099 0.0117529 0 -0.02622509 -0.003075838 0.003999948 -0.02705711 0.002612173 0.003999948 -0.02833425 3.04093e-4 0.003999948 -0.02771627 -0.001841962 0.003999948 0.02258968 -0.002214074 0.003999948 0.02389997 -0.003097653 0.003999948 0.02210134 0.001676261 0.003999948 0.0217787 -7.09125e-4 0.003999948 -0.01259875 -0.03302896 0.007499933 0.007270216 0.05396372 0.001057624 0.01245743 -0.05484914 0.001058518 -0.01252752 -0.05451166 0.001057982 0.00521183 -0.03234159 9.99985e-4 0.01252871 0.05451083 0.001057624 -0.005089521 0.05396658 0.001058876 -0.005211234 0.03234159 9.99985e-4 -0.01245635 0.05484992 0.001058876 0.007413983 0.03200536 9.99985e-4 0.01261663 -0.032965 0.007499933 -0.01261574 0.03296494 0.007499933 -0.0137791 0.02992922 0.007499933 0.01377964 -0.02992916 0.007499933 0.0148437 -0.02843225 0.007539093 0.01503044 -0.02783823 0.01002681 0.01500105 -0.02825462 0.009084522 0.01502341 0.02784794 0.01011466 0.01499187 0.0282436 0.009112477 0.01485008 0.02842468 0.007549822 0.01377964 0.02992916 0.007499933 0.01259922 0.03302896 0.007499933 -0.0137791 -0.02992922 0.007499933 0.02491921 -0.005537629 0.007499933 0.02654528 -0.01522821 0.007500171 0.022879 -0.005100548 0.007499933 0.02093786 -0.003791928 0.007499933 0.02188551 -0.02132511 0.007500052 0.01733422 -0.02524077 0.007560253 0.01762181 0.02496439 0.007520318 0.0195021 5.34771e-4 0.007499933 0.01968365 -0.001554131 0.007499933 0.03020447 0.004925012 0.007500112 0.02756989 0.01328349 0.007500112 0.02390164 0.00541377 0.007499933 0.02175498 0.004510939 0.007499933 0.0226739 0.02055311 0.007500171 0.02977305 -0.007080197 0.007500112 0.0200867 0.002556622 0.007499933 -0.01761198 -0.02614468 0.006569862 -0.02155703 -0.02300179 0.006499946 -0.02459967 -0.01971393 0.006500065 -0.02714574 -0.0160281 0.006500065 -0.02914351 -0.0120185 0.006500184 -0.03055292 -0.007765889 0.006500184 -0.031345 -0.003356397 0.006500244 -0.03150451 0.001120567 0.006500244 -0.03102749 0.005575299 0.006500244 -0.02992391 0.00991714 0.006500184 -0.028216 0.01405853 0.006500065 -0.02593821 0.01791656 0.006500065 -0.02199542 0.02265912 0.006500661 -0.01689887 0.02659595 0.006760299 -0.02188462 0.02132511 0.007500052 -0.01734161 0.02524489 0.007557332 -0.02654451 0.01522821 0.007500171 -0.02977228 0.007080197 0.007500112 -0.02695775 0.005164623 0.007499933 -0.02924996 0.003646016 0.007499933 -0.03054893 4.79141e-4 0.007494628 -0.0302037 -0.004925012 0.007500112 -0.02979642 -0.002889633 0.007499933 -0.02778726 -0.004768431 0.007499933 -0.02591305 -0.005456805 0.007499933 -0.02756923 -0.01328349 0.007500112 -0.02267313 -0.02055311 0.007500171 -0.01761502 -0.02496832 0.00752151 -0.01257807 0.03322494 9.99987e-4 0.008954644 0.03030282 9.9985e-4 0.01267254 0.03255248 9.99987e-4 -0.01402586 0.02941173 9.99994e-4 0.002155542 0.03145825 9.99984e-4 0.0142126 0.02923357 9.99998e-4 0.004697561 -0.03124696 9.998e-4 -0.01421213 -0.02923357 9.99998e-4 0.01236873 -0.02904361 9.99946e-4 0.01402682 -0.02941173 9.99994e-4 -0.01267158 -0.03255242 9.99987e-4 0.01257854 -0.03322494 9.99987e-4 0.02204746 0.004668712 0.003999948 0.02066063 0.003418326 0.003999948 0.02390164 -0.00541377 0.003999948 0.02175498 -0.004510939 0.003999948 0.0200867 -0.002556622 0.003999948 0.0195021 -5.34771e-4 0.003999948 0.02377152 0.005385756 0.003999948 0.01968365 0.001554131 0.003999948 -0.02965611 0.003113925 0.003999948 -0.02666658 -0.005266368 0.003999948 -0.02906823 -0.003850281 0.003999948 -0.03058159 -4.71104e-4 0.003999948 0.003647208 -0.01537954 -0.001504719 0.007234454 -0.0140174 -0.001492202 0.009882867 -0.01229453 -0.001492679 0.01234978 -0.009828865 -0.001495659 0.01414155 -0.006988048 -0.001502335 0.01535969 -0.003654778 -0.00150007 0.01578557 8.35693e-4 -0.001519322 0.01499605 0.004940271 -0.001489222 0.0130729 0.008896648 -0.001485705 0.01039224 0.01186668 -0.001492798 0.007471561 0.01390892 -0.001490533 0.003540337 0.01540529 -0.00150609 0.01021087 0.01067572 -0.002499938 0.01284837 0.007396519 -0.002500355 0.007183492 0.01294827 -0.002500176 0.001752555 0.01472157 -0.002500534 0.01451373 0.002914965 -0.002500057 0.01475924 -8.12738e-4 -0.002499938 0.01380223 -0.005391955 -0.002500236 0.01136279 -0.009479522 -0.002500057 0.007585704 -0.01274484 -0.002500236 0.002115488 -0.01466852 -0.002500474 -0.01552802 -0.02710878 0.008796334 -0.01681864 -0.02568984 0.007736921 -0.01643556 0.02605623 0.007932126 -0.01586234 0.02667683 0.008392214 -0.01522058 0.02752405 0.009343862 -0.01520997 -0.02756184 0.009366571 0.0155285 0.02710878 0.008796334 0.01668334 0.02581346 0.007787227 0.01647228 -0.02602022 0.007911145 0.01585328 -0.0266934 0.008396923 0.01522183 -0.02752393 0.009342491 0.01602768 0.02649205 0.008229017 0.0152105 0.02756172 0.009366512 -0.01553088 0.02769005 0.008022308 -0.01595336 -0.02728122 0.007494211 0.01587164 -0.02734774 0.007543206 0.01595383 0.02728122 0.007494211 0.01540583 0.01978302 0.006499946 0.01539742 0.01966208 2.00232e-4 0.01648092 0.02045339 2.00048e-4 0.01623553 0.01502066 2.00527e-4 0.01522827 0.01590639 2.00517e-4 0.01497954 0.01624715 0.006499946 0.01470321 0.0171402 2.00467e-4 0.01466286 0.01789528 0.006499946 0.01476359 0.01848018 2.00373e-4 -0.01606631 -0.02643465 0.008195102
+
+
+
+
+
+
+
+
+
+ -0.3878833 0.4229412 -0.8189429 -0.1460442 -0.5166733 -0.8436349 -0.106861 0.7785623 -0.6184025 -0.5896601 -0.8076108 0.008111357 -0.6427345 -0.3248529 -0.6938033 -0.5373642 0.02427244 -0.843001 -0.9028105 0.3296426 0.2761687 -0.1882963 0.7188342 -0.669195 -0.2148882 0.610001 -0.762707 -0.3843859 0.6543421 -0.6512174 -0.4019857 0.5354199 -0.7427874 -0.5258526 0.53246 -0.663299 -0.5168516 0.415838 -0.7482936 -0.639658 0.3998061 -0.6565003 -0.651036 0.2418355 -0.7194914 -0.6303365 0.2218793 -0.7439392 -0.7261036 0.07770419 -0.6831805 -0.6841356 0.04505699 -0.7279618 -0.7295626 -0.08454185 -0.6786686 -0.6362175 -0.1328859 -0.7599795 -0.7237011 -0.287731 -0.6272699 -0.5485831 -0.3274752 -0.7692963 -0.5910454 -0.4895315 -0.6411118 -0.4253311 -0.4919068 -0.7596848 -0.4359284 -0.6142834 -0.6577404 -0.2227846 -0.6202858 -0.7520722 -0.2495834 -0.6487054 -0.7189503 -0.0715878 -0.7384957 -0.6704472 -0.02526056 0.653059 -0.7568857 -0.00785762 -0.6679161 -0.7441952 -0.02457094 0.7971621 -0.6032652 -0.09345364 0.5914639 -0.8008977 -0.2218709 0.7724491 -0.5950593 -0.2682718 0.5826909 -0.7671386 -0.3243855 0.6278392 -0.7075254 -0.4120318 0.5674735 -0.712884 -0.4380421 0.5836777 -0.6836954 -0.493132 0.4754866 -0.7285146 -0.4945542 0.4761538 -0.7271133 -0.6183036 0.4449847 -0.6478344 -0.5607421 0.3088493 -0.7682321 -0.6058407 0.3163737 -0.7299759 -0.7192334 0.2532943 -0.6469509 -0.6266231 0.1373189 -0.7671292 -0.6672238 0.1325358 -0.7329712 -0.7619319 0.04079049 -0.6463715 -0.641052 -0.04618048 -0.7661069 -0.6743703 -0.06026327 -0.7359301 -0.7431221 -0.1753317 -0.6457774 -0.6029223 -0.2266434 -0.7649298 -0.627268 -0.2464391 -0.7387846 -0.6642743 -0.3775855 -0.6451115 -0.515028 -0.3893279 -0.7636557 -0.5298315 -0.410723 -0.7420143 -0.5309829 -0.5561689 -0.6393226 -0.3935499 -0.5152328 -0.76135 -0.4059151 -0.5806279 -0.705765 -0.2899509 -0.6297839 -0.7206252 -0.2961568 -0.6723217 -0.6784355 -0.1231381 -0.6755132 -0.7269932 -0.1197642 -0.7047943 -0.6992293 -0.624224 0.7811669 -0.01107895 -0.1649158 -0.9862502 -0.01065105 -0.5876579 0.8091087 0.001177668 -0.2681843 -0.9633371 0.007673561 -0.6072358 -0.7944768 -0.00844711 -0.208114 0.9780747 0.007639706 -0.01904135 0.9992265 -0.0344066 -0.5913781 -0.8062994 -0.01238089 -0.9243256 -0.3815867 -0.003735959 -0.9437379 -0.3302978 -0.01620042 -0.9987875 0.0450083 0.01994603 -0.9385179 0.3448033 -0.0171684 -0.926801 0.3753768 -0.01149654 -0.2550333 0.9664803 -0.02956187 -0.2393136 0.9708163 0.01565045 -0.4567499 0.8887153 0.03955763 -0.5013775 0.8628453 -0.06417608 -0.6666534 0.7417856 0.07298898 -0.7018336 0.7110159 -0.04342865 -0.8505201 0.5258736 -0.008521974 -0.8714964 0.4740348 0.1256394 -0.937198 0.3326421 -0.1049244 -0.9943301 0.1061751 0.005878269 -0.9885818 0.1319148 0.07283151 -0.993093 -0.1149756 -0.02338784 -0.9846711 -0.1575304 0.0748797 -0.9235189 -0.3661264 -0.1142998 -0.8841909 -0.45391 0.1103287 -0.7685708 -0.6359737 -0.06954455 -0.7434059 -0.6684548 0.02271181 -0.569393 -0.8220371 -0.006843924 -0.5787454 -0.8150128 0.02842622 -0.3548272 -0.932427 -0.06839257 -0.3013269 -0.9510217 0.06899172 -0.09830707 -0.9908221 0.09277582 -0.05711692 -0.9973468 -0.04513287 -0.03531277 0.9988467 -0.03253316 -0.9680439 -0.2506346 0.008575439 -0.9742197 -0.2254043 -0.009437322 -0.4387373 0.8984913 0.01493304 -0.4322505 0.9017059 0.009284257 -0.7699089 -0.6381035 0.008025348 -0.785969 -0.6182141 -0.008010327 -0.7699089 0.6381036 -0.008025348 -0.785969 0.6182141 0.008010387 -0.4858585 -0.8740103 -0.006895363 -0.438703 -0.8984205 0.01950407 -0.194834 -0.9805299 -0.02451443 -0.9680439 0.2506346 -0.008575499 -0.9742196 0.2254043 0.009437322 0.01297342 -0.9986508 0.0502817 -0.9736822 -0.217477 -0.0681684 -0.9099065 -0.4075174 0.07745873 -0.1797959 0.9830694 -0.03532814 -0.5523357 0.8336219 -2.8732e-5 -0.5999647 0.798349 0.0517807 -0.6819333 -0.7290924 -0.05823606 -0.4789872 -0.8761231 0.05458569 -0.3447 -0.9385029 -0.01985919 -0.9240754 0.3789826 -0.04956704 -0.9665728 0.2495383 0.05888694 -0.817094 -0.5764244 0.009620249 -0.7970482 -0.6038503 -0.008897304 -0.2474249 0.9674255 0.05356156 -0.2477691 -0.9687731 0.009443044 -0.2143871 -0.9766894 -0.01077133 -0.5314793 0.8462448 -0.03741109 -0.7912364 0.6107186 0.03110837 -0.8864087 0.4625539 -0.01799184 -0.9964454 0.08248418 0.01711869 -0.999698 0.02313911 -0.008279442 -0.7942366 -0.6061887 0.04151755 -0.01489931 0.9997675 0.0155887 -0.4526906 -0.890505 -0.04552036 -0.3182402 -0.9478596 0.01689761 -0.5257211 0.8503819 0.0216332 -0.442645 0.8966469 -0.009471118 -0.8003644 0.5986046 -0.03300851 -0.923943 0.3810238 0.03391838 -0.9930009 0.115769 -0.02338123 -0.9936144 -0.1105966 0.02233618 -0.9217259 -0.3864013 -0.03340131 0.6044605 0.796508 -0.01423239 0.5993183 0.800477 -0.0073812 0.7093539 0.7048295 0.005703687 0.7203913 0.6935442 -0.005739212 0.8020324 0.597252 0.005852937 0.811651 0.5841128 -0.005915343 0.8786108 0.4775022 0.005907416 0.8863968 0.4628871 -0.006029248 0.9374427 0.3480876 0.006030261 0.9431826 0.3322192 -0.006096005 0.977299 0.2117749 0.006179988 0.9808284 0.194773 -0.006263315 0.9974536 0.07104617 0.006235003 0.9985515 0.0534271 -0.006377816 0.997453 -0.07104498 0.006337463 0.9960099 -0.08900648 -0.006501734 0.9772976 -0.2117739 0.006438493 0.9732633 -0.2295992 -0.006547749 0.937439 -0.3480871 0.006602287 0.9307415 -0.3656166 -0.006693124 0.878612 -0.4774899 0.006669402 0.8693488 -0.4941524 -0.00679773 0.8020216 -0.5972566 0.006793558 0.7902837 -0.6127027 -0.00687009 0.7093482 -0.7048243 0.006926715 0.6907216 -0.7230316 -0.01136314 0.6047466 -0.7962974 0.01386106 0.571811 -0.8203601 -0.0064525 3.28512e-4 3.65835e-5 -1 0 0 -1 -1.82409e-5 -1.76621e-5 -1 4.05716e-6 -1.55132e-5 -1 -1.78945e-5 -1.57497e-5 -1 8.79391e-6 -4.78329e-6 -1 8.5712e-6 -9.31713e-6 -1 1.16112e-4 1.67638e-4 -1 -2.27333e-5 5.56887e-6 -1 2.5126e-5 1.43161e-5 -1 -2.34053e-4 -2.53001e-4 -1 7.77064e-6 9.15295e-6 -1 -6.22803e-6 -2.43514e-5 -1 -8.48016e-6 8.80656e-6 -1 -2.2259e-4 -2.79624e-4 -1 2.34251e-5 1.09337e-6 -1 1.5782e-5 -1.8357e-5 -1 1.65217e-5 2.14831e-5 -1 -8.80049e-6 7.9747e-6 -1 9.50357e-6 4.87878e-6 -1 3.3147e-6 1.04616e-5 -1 -9.61047e-5 -2.67016e-4 -1 -1.23487e-6 -2.1563e-5 -1 -8.07915e-6 1.85589e-5 -1 -1.88091e-5 1.37636e-5 -1 -6.74557e-6 1.00584e-5 -1 1.55098e-4 -7.63176e-5 -1 8.55686e-5 2.95817e-5 -1 5.09268e-5 5.62335e-6 -1 -2.16489e-5 -8.95651e-6 -1 2.25266e-4 -4.36634e-5 -1 -0.01014369 0.07635593 -0.9970291 -4.22024e-5 1.84247e-5 -1 -4.98466e-6 1.94899e-5 -1 -7.73312e-5 -3.29424e-5 -1 1.28251e-5 4.40856e-5 -1 -8.84002e-6 7.91992e-6 -1 4.40167e-5 -8.02266e-6 -1 -0.01240515 -0.02750581 -0.9995447 -0.0359885 0.01972311 -0.9991576 -4.11477e-6 2.00996e-5 -1 0.007962346 0.004472851 -0.9999583 -9.01805e-6 4.47471e-6 -1 2.47844e-5 -8.96656e-6 -1 -3.0386e-6 -9.58972e-6 -1 -5.67913e-4 -4.59017e-4 -0.9999998 3.65019e-5 -4.12395e-5 -1 2.5928e-6 1.31397e-5 -1 0.02445101 -0.009574949 -0.9996553 -1.39886e-5 -2.03182e-5 -1 -0.09381502 -0.005375683 -0.9955752 1.79354e-5 9.53505e-6 -1 2.11227e-4 1.90865e-4 -1 3.04771e-5 -9.92825e-6 -1 3.01581e-6 -1.81162e-5 -1 1.93707e-5 8.01397e-6 -1 0.02227014 -0.03198313 -0.9992403 2.63763e-6 -1.07e-5 -1 -6.13535e-7 4.11689e-5 -1 -2.85551e-6 1.04644e-5 -1 9.13994e-6 8.21842e-6 -1 -7.9901e-6 -2.37981e-5 -1 0.6095553 0.7926054 0.01480227 0.6374887 0.7704559 -0.002471268 0.164204 -0.9863916 0.008298635 0.2837739 -0.9579392 -0.04271882 0.6620663 -0.7479941 0.0466175 0.06705397 0.9975582 -0.01953417 0.8731262 -0.4831541 -0.06490767 0.9827602 -0.1791223 0.04580092 0.9446398 0.3265685 -0.03176343 0.9609487 0.2767159 -0.002459585 0.483453 0.8749453 0.02727848 0.5590878 0.8290904 -0.005478322 0.5466344 -0.8372883 0.01179522 0.575465 -0.8177418 -0.0117672 0.05220174 0.9975871 -0.0457713 0.9875855 -0.1567024 0.01092231 0.9909722 -0.1340367 -0.002893686 0.8803156 0.4743792 -0.00296545 0.8701149 0.4928118 0.006055772 -2.72722e-4 0.002624392 -0.9999966 -2.72761e-4 0.002624392 -0.9999966 4.31693e-5 -0.002587974 -0.9999967 2.74446e-4 -0.002640545 -0.9999966 2.74377e-4 -0.002640545 -0.9999965 -0.07962495 0.03051352 -0.9963578 0.07953327 -0.03046739 -0.9963666 0.09969389 0.03796273 -0.9942937 -0.06518971 -0.02482366 -0.9975641 -3.07194e-5 1.94422e-5 1 5.34433e-6 2.55205e-5 1 2.05626e-5 3.11413e-5 1 -3.20221e-5 5.05861e-6 1 0.01263272 6.89592e-4 0.9999201 0.003861367 0.002163589 0.9999902 0.01970535 6.82403e-4 0.9998057 0.002279341 -6.59015e-4 0.9999972 0.01368218 0.001189351 0.9999057 -6.96992e-5 -7.41886e-5 1 -6.32125e-5 -1.71717e-4 1 0.003506302 -7.906e-4 0.9999936 -4.35736e-5 -1.37063e-5 1 6.85213e-7 -2.30103e-5 1 1.13839e-5 -2.83359e-5 1 -9.22061e-6 -1.43347e-5 1 0.002886295 4.91205e-4 0.9999958 0.003785669 -2.317e-4 0.9999929 9.43943e-4 -8.0589e-4 0.9999992 -3.17009e-5 4.74248e-5 1 -1.32115e-5 7.37687e-5 1 -0.5735221 -0.8191432 -0.008766114 -0.6231938 -0.7817763 0.02134364 -0.6906334 -0.7230073 -0.0169149 -0.7338711 -0.6791487 0.01379722 -0.7902663 -0.6126085 -0.01378881 -0.82271 -0.5682958 0.01371324 -0.8692861 -0.4941166 -0.01380449 -0.8949702 -0.4459167 0.01366204 -0.9306581 -0.3656316 -0.0137518 -0.9491372 -0.3145658 0.01367247 -0.9731861 -0.2296111 -0.01369684 -0.9841525 -0.1767978 0.01365196 -0.9959385 -0.08899927 -0.013628 -0.9992737 -0.03559231 0.01361316 -0.9984774 0.05345499 -0.0136227 -0.9942243 0.1064629 0.01355725 -0.980746 0.1948134 -0.01360088 -0.969095 0.2463187 0.01349604 -0.9431314 0.3321436 -0.0135644 -0.9243882 0.381214 0.01349842 -0.8863348 0.4628482 -0.01349443 -0.8610369 0.5083647 0.01344466 -0.8115831 0.5840818 -0.01347744 -0.7687686 0.6391229 0.0227341 -0.7202212 0.6934215 -0.02117234 -0.6003244 0.7996466 0.01327234 -0.6111689 0.7914888 -0.004261076 -0.01233965 -3.10366e-4 0.9999239 -0.002782881 -0.002188861 0.9999938 3.36945e-5 -6.16037e-6 1 -1.59836e-5 -2.929e-5 1 -0.007210671 -0.001668453 0.9999727 -5.09507e-6 -2.54519e-5 1 3.07183e-5 -1.94417e-5 1 1.33946e-5 -7.37684e-5 1 3.16989e-5 -4.74255e-5 1 -0.002885341 -4.91197e-4 0.9999957 -0.003511786 7.90527e-4 0.9999936 6.9648e-5 7.41977e-5 1 6.32097e-5 1.71709e-4 1 4.3493e-5 1.37052e-5 1 -1.25407e-6 2.30788e-5 1 -1.50263e-5 2.98646e-5 1 9.16831e-6 1.41158e-5 1 -0.02002984 -6.04195e-4 0.9997992 -0.001025855 8.49733e-4 0.9999992 -0.002607762 6.75231e-4 0.9999964 -0.003970921 2.83832e-4 0.9999921 -2.70033e-5 8.44242e-6 -1 -7.33479e-6 7.298e-5 -1 -1.00625e-4 0.002585291 -0.9999967 -2.72785e-4 0.002624392 -0.9999966 -7.72199e-5 -2.1752e-4 -1 3.26292e-4 0.00272268 -0.9999963 3.03565e-5 1.07784e-5 -1 2.13329e-5 1.79419e-4 -1 -4.8219e-4 -0.002509295 -0.9999968 -2.15499e-5 8.71965e-6 -1 5.28553e-4 0.002737998 -0.9999962 -2.13033e-5 -1.79419e-4 -1 4.33232e-4 0.001820743 -0.9999983 -3.03423e-5 -1.07818e-5 -1 2.69972e-5 -8.44272e-6 -1 2.15504e-5 -8.71961e-6 -1 7.32338e-6 -7.29807e-5 -1 -4.31325e-7 0 1 -2.37459e-7 0 1 0 0 1 0 0 1 2.42721e-7 0 1 -1.34787e-7 0 1 1.49199e-7 0 1 0 0 1 -4.48395e-7 0 1 0 0 1 1.30509e-7 0 1 0.9958935 0.08658075 -0.0264585 0.02137994 -0.9997524 0.006168782 0.02290099 -0.9997122 0.007150352 0.9601866 0.2776306 0.0310359 0.8716594 0.488513 -0.0395599 0.383998 -0.9232847 -0.009530484 0.3876736 -0.9217699 -0.007034778 0.7598307 0.6486207 0.04414516 0.6694737 -0.7424957 0.02247506 0.5585451 0.8285185 -0.03980445 0.7601035 -0.6488538 -0.03509408 0.3875002 0.9213562 0.03076654 0.8853095 -0.4639707 0.03095829 0.2094076 0.9774749 -0.02629083 0.9601865 -0.2776306 -0.0310359 -0.0269618 0.9987035 0.043181 0.9958935 -0.08658075 0.0264585 1.69218e-7 0 1 1.45553e-7 0 1 -3.30466e-7 0 1 2.38823e-7 0 1 3.29533e-7 0 1 -1.86383e-7 0 1 -2.9257e-7 0 1 1.68974e-7 0 1 2.39101e-7 0 1 0 0 1 0 0 1 2.73963e-7 0 1 0.973697 0.2173971 -0.06821203 0.9099128 0.4075021 0.07746374 0.1797899 -0.9830702 -0.03533768 0.5523064 -0.8336412 -6.04852e-5 0.5999408 -0.7983688 0.05175399 0.6818651 0.7291537 -0.05826687 0.5075054 0.8607171 0.04005718 0.3446757 0.9384366 -0.02314013 0.09086632 0.9952622 0.03459185 0.9240849 -0.378952 -0.04962295 0.966581 -0.2495237 0.0588153 0.1052321 -0.9896875 -0.09718561 0.2607226 -0.9554544 0.1383144 0.354314 -0.9324027 -0.07132136 0.5452805 -0.8382082 0.008727431 0.5564542 -0.8297334 0.04360491 0.7064428 -0.7068441 -0.03619551 0.7397357 -0.6703227 0.05880951 0.8432873 -0.5320783 -0.0758897 0.8933674 -0.4432848 0.07343947 0.9374802 -0.3425596 -0.06151485 0.9715781 -0.2225562 0.08065283 0.9892064 -0.09428417 -0.1121663 0.9934408 0.04879707 0.103413 0.9782856 0.1888014 -0.08550703 0.9535832 0.2930866 0.06913453 0.8969017 0.4360262 -0.07381463 0.8709092 0.4892367 0.04652768 0.7418863 0.67037 -0.01445293 0.742305 0.6699379 -0.01290315 0.5693385 0.8219907 0.01360321 0.5728433 0.8192557 0.02590137 0.3551535 0.9323009 -0.06841915 0.3013312 0.9509918 0.06938463 0.06582581 0.9976817 -0.01726943 0.0571137 0.9973048 -0.04605722 -4.47352e-4 -2.32802e-4 -0.9999999 1.54028e-4 1.00903e-4 -1 7.35594e-5 2.31728e-5 -1 -3.01437e-5 -3.39043e-5 -1 -1.04078e-4 -1.25488e-4 -1 5.43797e-5 5.37291e-5 -1 3.47665e-5 -2.47308e-5 -1 -3.70738e-4 3.89041e-4 -0.9999999 -5.40938e-6 4.54674e-6 -1 1.76215e-4 -1.48932e-4 -1 -4.98205e-5 2.65524e-4 -1 -9.42433e-5 6.228e-6 -1 2.20286e-5 -6.33492e-6 -1 -1.19077e-4 4.15879e-6 -1 4.51832e-5 6.27041e-5 -1 -6.57917e-7 6.32132e-6 -1 -9.5557e-7 5.2831e-6 -1 -2.14731e-4 -1.57467e-4 -1 -0.625623 -4.92636e-4 0.7801255 -0.9701128 -8.68054e-5 0.242655 -0.8287012 -9.45762e-4 0.5596905 -0.9630692 3.08627e-4 0.2692542 -0.7448144 3.83941e-4 0.6672716 -0.2604878 7.29606e-4 0.9654769 -0.3817374 -6.60655e-4 0.9242706 -0.5196982 6.24447e-4 0.8543497 -0.8733304 3.26552e-5 0.4871284 0.6170563 4.60189e-4 0.7869189 0.5585923 -2.92129e-4 0.8294423 0.970121 9.26674e-5 0.242622 0.8312533 8.90357e-4 0.5558932 0.9630802 -3.0342e-4 0.2692148 0.7505438 -3.75729e-4 0.6608207 0.2729058 -7.98033e-4 0.9620404 0.3765192 6.81254e-4 0.9264086 0.8733491 -3.3716e-5 0.4870949 -0.9337579 0.3578376 0.006968021 -0.9348441 0.3549498 0.008782923 -0.8152576 0.5789009 -0.01513165 -0.7321852 0.6810964 0.003533959 -0.7296226 0.6837571 0.01127302 -0.7213625 0.6923918 0.01515978 -0.6213366 0.7835227 -0.005757331 -0.5634955 -0.8261062 -0.004626929 -0.7063567 -0.7078554 -0.001046776 -0.7461954 -0.6656579 0.009594738 -0.7461235 -0.6657388 0.009565114 -0.8153089 -0.5789759 -0.007648229 -0.9069443 -0.4209704 0.01536697 -0.9344245 -0.3558193 -0.01560801 0.9337978 -0.3577319 0.007033169 0.9348145 -0.3550291 0.008731365 0.8151543 -0.5790473 -0.0150969 0.7257627 -0.6879261 0.005120456 0.7257026 -0.6878972 0.01238155 0.7278003 -0.6856993 0.01109784 0.5732614 -0.8193434 -0.006907403 0.5714947 0.8206048 -0.001331508 0.7093147 0.7048878 0.002449691 0.740172 0.6723322 0.01072144 0.740615 0.6718413 0.01089501 0.8149024 0.5795469 -0.007732808 0.9069854 0.4208815 0.01538175 0.9344266 0.3558164 -0.01555132 0.5897432 -0.8074567 0.01471948 0.3518972 0.935953 0.0126689 0.4198982 -0.9072301 -0.02488094 0.1796258 -0.9836524 0.01275092 0.6649845 0.7467235 -0.01413655 0.6602749 0.7509222 -0.01236116 0.9200471 0.3915151 0.01514881 0.9819052 0.1886685 -0.0163232 0.9989168 -0.04501265 0.01179337 0.9303536 -0.3661648 -0.01912128 0.881226 -0.4726291 0.007905364 -1.54675e-7 0 -1 4.03001e-7 0 -1 -0.7917277 0.3721471 0.4844315 -0.7735698 0.424754 0.4702912 -0.681057 0.5245636 0.5108761 -0.9393773 0.3233867 0.1139805 -0.8968004 0.3609194 0.2559031 -0.8870642 0.3838317 0.2564966 -0.4581764 -0.5597569 0.6904684 -0.4436971 -0.5047249 0.7405307 -0.5608943 -0.5190946 0.6449329 -0.55124 -0.3807752 0.7423912 -0.535752 -0.3608236 0.7633978 -0.6836693 -0.3406544 0.6454076 -0.6144521 -0.193666 0.7648152 -0.6396425 -0.2119925 0.7388618 -0.7513716 -0.1349884 0.645925 -0.6773061 -0.02412295 0.7353058 -0.7350058 -0.04626703 0.6764805 -0.6921133 0.08083456 0.7172483 -0.6728622 0.0720517 0.7362506 -0.7399502 0.1880844 0.6458312 -0.5993402 0.2374185 0.7644762 -0.6221196 0.2565807 0.7396849 -0.6578665 0.3884111 0.6452508 -0.5211517 0.398333 0.7548058 -0.56075 0.466102 0.6843307 -0.4502276 0.5111129 0.7321604 -0.4659057 0.5578566 0.6868246 -0.6154972 -0.4966527 0.6119635 -0.8850673 -0.3259735 0.3322607 -0.9350801 -0.3387137 0.104395 -0.6492186 -0.4468703 0.6154853 -0.858979 -0.3896424 0.3321657 -0.9050076 -0.3690268 0.2116142 -0.7508099 -0.4777954 0.456066 0.8012729 -0.4664156 0.3747243 0.6595357 -0.4626315 0.5924397 0.6578961 -0.4615162 0.5951265 0.9392097 -0.3238959 0.1139145 0.8855822 -0.3676795 0.283824 0.8913969 -0.3502594 0.2876284 0.447185 0.5567669 0.7000258 0.4402218 0.5007674 0.7452764 0.5340507 0.5306442 0.6581843 0.5717086 0.4257584 0.701341 0.5236362 0.3526559 0.7755251 0.6599774 0.3587001 0.6601244 0.6709328 0.2491282 0.6984156 0.6015722 0.1896142 0.7759881 0.7325553 0.1587479 0.661938 0.7168567 0.05105984 0.6953485 0.7024822 0.04416346 0.7103298 0.6610348 -0.07716047 0.7463775 0.7252771 -0.05165946 0.6865162 0.7452849 -0.1615072 0.6468894 0.6401598 -0.2377013 0.7305433 0.5955803 -0.2359288 0.7678683 0.6693338 -0.3637761 0.6478111 0.5505174 -0.4099884 0.727214 0.5258302 -0.4018992 0.7496531 0.5300338 -0.5266525 0.6646061 0.4533268 -0.515731 0.7269913 0.4632244 -0.5770938 0.6725965 0.622905 0.4990805 0.6024185 0.7731975 0.4694176 0.4263951 0.8850665 0.3259757 0.3322606 0.9350953 0.3386905 0.1043338 0.6552466 0.4472088 0.6088154 0.858956 0.3896933 0.3321653 0.905009 0.3690139 0.2116306 0.02188134 -0.7100487 -0.7038125 0.02696502 -0.6901801 -0.7231352 0.2017934 -0.7025226 -0.6824525 0.2007877 -0.6588565 -0.7249776 0.3330473 -0.6356999 -0.6963943 0.3304044 -0.5519995 -0.765591 0.4199259 -0.6035966 -0.6777414 0.5002204 -0.52393 -0.6894034 0.4596723 -0.4326357 -0.7755823 0.594022 -0.4605441 -0.6595733 0.6214354 -0.3532359 -0.6993157 0.5630274 -0.2842254 -0.7760259 0.6983928 -0.2743455 -0.6610463 0.6982894 -0.164733 -0.6966024 0.6200703 -0.1126797 -0.7764125 0.7458613 -0.06665271 -0.6627582 0.718866 0.03846281 -0.6940838 0.6261067 0.06778311 -0.7767857 0.733006 0.1455608 -0.6644654 0.6812972 0.2399767 -0.6915528 0.5807681 0.2425141 -0.777107 0.6609826 0.3451737 -0.6663011 0.5881928 0.4233018 -0.6890898 0.4878385 0.3970955 -0.7773859 0.5359673 0.5159951 -0.6681977 0.4342283 0.5807949 -0.688566 0.3966403 0.5528365 -0.7328358 0.2896551 0.6392274 -0.7123821 0.2943923 0.6724607 -0.6790654 0.1066001 0.6746797 -0.7303724 0.1087098 0.6397558 -0.7608513 0.07852125 -0.7502104 -0.6565203 0.2140853 -0.6086832 -0.7639846 0.2465717 -0.6426619 -0.7253882 0.411656 -0.6329827 -0.6556466 0.428512 -0.4956306 -0.7554654 0.5145913 -0.5156794 -0.6850333 0.5924841 -0.375381 -0.7127776 0.5742433 -0.342738 -0.7434886 0.7095689 -0.2588658 -0.6553629 0.6362528 -0.1329211 -0.7599436 0.7483468 -0.07379001 -0.6591906 0.6532179 0.04299795 -0.7559481 0.7441488 0.1479146 -0.6514322 0.6166157 0.2290779 -0.7531988 0.6863368 0.3341845 -0.6459586 0.5458238 0.4910125 -0.6789574 0.5243366 0.4218258 -0.7396851 0.4164442 0.5963621 -0.6862409 0.4132668 0.5504715 -0.7253907 0.1976081 0.6050422 -0.7712815 0.2551868 0.6630774 -0.7037103 0.05116367 0.7776069 -0.6266657 0.8531719 -0.5206965 -0.03119045 0.2927159 -0.400981 -0.868062 0.3559947 0.9327895 0.0563181 0.1653984 -0.8991188 -0.4052516 0.1616793 -0.9312418 -0.326571 0.3338037 0.3796206 -0.8628229 0.763605 0.3249205 -0.5579732 0.9463607 0.3229027 0.01162922 0.4999326 -0.02258843 -0.8657697 0.7674614 -0.4116703 -0.4914577 3.26256e-4 0.00272268 -0.9999963 -3.24323e-4 -0.002706289 -0.9999963 2.74459e-4 -0.002640545 -0.9999965 -3.24337e-4 -0.002706289 -0.9999964 -3.24323e-4 -0.002706289 -0.9999964 3.26269e-4 0.00272268 -0.9999963 -7.33475e-6 7.29797e-5 -1 2.13033e-5 1.79421e-4 -1 -2.13331e-5 -1.79419e-4 -1 7.33501e-6 -7.29809e-5 -1
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 0 0 3 0 4 0 2 1 1 1 5 1 3 2 6 2 4 2 1 3 7 3 5 3 7 4 8 4 9 4 9 5 8 5 10 5 8 6 0 6 10 6 13 7 12 7 11 7 13 8 11 8 14 8 14 9 15 9 13 9 15 10 14 10 16 10 16 11 17 11 15 11 17 12 16 12 18 12 19 13 17 13 18 13 21 14 18 14 20 14 21 15 19 15 18 15 20 16 22 16 21 16 23 17 22 17 20 17 23 18 24 18 22 18 24 19 23 19 25 19 25 20 26 20 24 20 27 21 26 21 25 21 27 22 28 22 26 22 28 23 27 23 29 23 29 24 30 24 28 24 31 25 29 25 32 25 31 26 30 26 29 26 32 27 33 27 31 27 12 28 322 28 11 28 33 29 32 29 328 29 34 30 287 30 190 30 34 31 190 31 35 31 36 32 34 32 35 32 36 33 35 33 37 33 38 34 36 34 37 34 38 35 37 35 39 35 40 36 38 36 39 36 40 37 39 37 41 37 42 38 40 38 41 38 43 39 42 39 41 39 43 40 41 40 44 40 45 41 43 41 44 41 46 42 45 42 44 42 46 43 44 43 47 43 48 44 46 44 47 44 49 45 48 45 47 45 49 46 47 46 50 46 51 47 49 47 50 47 52 48 51 48 50 48 52 49 50 49 53 49 54 50 52 50 53 50 55 51 54 51 53 51 55 52 53 52 56 52 57 53 55 53 56 53 58 54 57 54 56 54 58 55 56 55 59 55 60 56 58 56 59 56 60 57 59 57 61 57 62 58 60 58 61 58 62 59 61 59 63 59 64 60 62 60 63 60 65 61 0 61 67 61 66 62 2 62 68 62 0 63 4 63 67 63 2 64 5 64 68 64 68 65 5 65 69 65 4 66 6 66 67 66 67 67 6 67 70 67 5 68 7 68 69 68 7 69 9 69 69 69 69 70 9 70 71 70 9 71 10 71 71 71 71 72 10 72 65 72 10 73 0 73 65 73 73 74 12 74 13 74 73 75 72 75 12 75 74 76 73 76 13 76 74 77 13 77 15 77 75 78 74 78 15 78 75 79 15 79 17 79 75 80 17 80 19 80 76 81 75 81 19 81 76 82 19 82 21 82 77 83 21 83 22 83 77 84 76 84 21 84 77 85 22 85 24 85 78 86 77 86 24 86 78 87 24 87 26 87 79 88 78 88 26 88 79 89 26 89 28 89 80 90 79 90 28 90 81 91 80 91 28 91 81 92 28 92 30 92 81 93 30 93 31 93 82 94 81 94 31 94 177 95 31 95 33 95 177 96 82 96 31 96 279 97 85 97 86 97 84 98 87 98 88 98 83 99 87 99 84 99 85 100 89 100 86 100 86 101 89 101 90 101 88 102 91 102 92 102 87 103 91 103 88 103 89 104 93 104 90 104 90 105 93 105 94 105 91 106 95 106 92 106 92 107 95 107 96 107 95 108 97 108 96 108 93 109 83 109 94 109 94 110 83 110 84 110 96 111 97 111 98 111 99 112 101 112 109 112 109 113 101 113 102 113 239 114 103 114 100 114 103 115 104 115 100 115 100 116 104 116 105 116 101 117 106 117 102 117 102 118 106 118 107 118 106 119 108 119 107 119 104 120 99 120 105 120 105 121 99 121 109 121 119 122 120 122 110 122 120 123 111 123 110 123 113 124 112 124 114 124 110 125 111 125 180 125 111 126 115 126 180 126 112 127 116 127 114 127 114 128 116 128 117 128 116 129 118 129 117 129 117 130 118 130 119 130 118 131 120 131 119 131 133 132 121 132 122 132 124 133 123 133 125 133 121 134 126 134 122 134 122 135 126 135 127 135 125 136 128 136 129 136 123 137 128 137 125 137 128 138 130 138 129 138 129 139 130 139 131 139 130 140 132 140 131 140 131 141 132 141 133 141 132 142 121 142 133 142 143 143 144 143 145 143 146 144 143 144 145 144 146 145 145 145 147 145 148 146 146 146 147 146 148 147 147 147 149 147 150 148 148 148 149 148 150 149 149 149 151 149 152 150 150 150 151 150 152 151 151 151 153 151 154 152 152 152 153 152 154 153 153 153 155 153 156 154 154 154 155 154 156 155 155 155 157 155 158 156 156 156 157 156 158 157 157 157 159 157 160 158 158 158 159 158 160 159 159 159 161 159 162 160 160 160 161 160 162 161 161 161 163 161 164 162 162 162 163 162 164 163 163 163 165 163 166 164 164 164 165 164 166 165 165 165 167 165 168 166 166 166 167 166 168 167 167 167 169 167 170 168 168 168 169 168 170 169 169 169 171 169 172 170 170 170 171 170 205 171 207 171 173 171 206 172 174 172 175 172 119 172 110 172 193 172 193 173 110 173 178 173 122 174 44 174 76 174 73 175 37 175 35 175 74 172 37 172 73 172 131 172 133 172 77 172 76 176 44 176 75 176 77 172 133 172 76 172 44 177 41 177 75 177 207 178 179 178 173 178 74 172 39 172 37 172 75 172 39 172 74 172 63 179 177 179 176 179 75 180 41 180 39 180 183 181 181 181 1 181 175 172 174 172 184 172 56 182 53 182 79 182 174 172 185 172 184 172 133 172 122 172 76 172 110 183 180 183 178 183 63 172 82 172 177 172 187 184 188 184 189 184 183 185 186 185 181 185 61 186 82 186 63 186 182 187 190 187 195 187 124 188 50 188 191 188 188 189 192 189 189 189 79 190 53 190 78 190 61 191 81 191 82 191 114 172 117 172 193 172 185 172 194 172 184 172 184 172 194 172 180 172 183 192 195 192 186 192 72 193 190 193 182 193 196 194 114 194 193 194 59 195 81 195 61 195 187 196 197 196 188 196 173 197 195 197 183 197 191 198 47 198 198 198 50 199 47 199 191 199 199 200 197 200 187 200 179 201 195 201 173 201 3 202 0 202 200 202 180 203 3 203 178 203 196 204 113 204 114 204 178 205 3 205 200 205 194 206 3 206 180 206 189 207 192 207 196 207 198 208 47 208 201 208 0 209 8 209 200 209 117 172 119 172 193 172 194 210 6 210 3 210 196 211 202 211 113 211 203 212 6 212 194 212 192 213 202 213 196 213 59 172 80 172 81 172 56 214 80 214 59 214 182 215 195 215 179 215 200 216 8 216 181 216 78 172 129 172 77 172 201 217 47 217 204 217 78 172 125 172 129 172 53 218 125 218 78 218 203 219 205 219 6 219 72 220 35 220 190 220 113 172 202 172 206 172 129 172 131 172 77 172 8 221 7 221 181 221 50 222 125 222 53 222 203 223 207 223 205 223 47 224 44 224 204 224 204 225 44 225 127 225 176 226 197 226 199 226 1 227 181 227 7 227 73 228 35 228 72 228 50 229 124 229 125 229 176 230 177 230 197 230 80 231 56 231 79 231 127 232 44 232 122 232 202 172 174 172 206 172 191 233 208 233 124 233 211 234 208 234 191 234 127 235 126 235 204 235 126 236 209 236 204 236 204 237 209 237 201 237 208 238 123 238 124 238 209 239 210 239 201 239 201 240 210 240 198 240 198 241 210 241 191 241 210 242 211 242 191 242 206 243 213 243 113 243 212 244 213 244 206 244 180 245 115 245 184 245 115 246 214 246 184 246 213 247 112 247 113 247 184 248 214 248 175 248 214 249 215 249 175 249 215 250 212 250 175 250 175 251 212 251 206 251 225 252 221 252 285 252 225 253 217 253 221 253 142 254 64 254 139 254 142 255 219 255 141 255 142 256 141 256 64 256 136 257 227 257 228 257 229 258 230 258 226 258 236 259 237 259 235 259 216 260 140 260 238 260 239 261 240 261 253 261 241 262 240 262 239 262 242 263 240 263 241 263 242 264 243 264 240 264 242 265 244 265 243 265 247 266 244 266 242 266 245 267 244 267 246 267 245 268 246 268 254 268 246 269 244 269 247 269 248 270 106 270 101 270 248 271 108 271 106 271 101 272 99 272 248 272 249 273 108 273 248 273 249 274 250 274 108 274 249 275 251 275 250 275 252 276 251 276 249 276 104 277 253 277 99 277 245 278 251 278 252 278 245 279 254 279 251 279 104 280 103 280 253 280 103 281 239 281 253 281 58 282 60 282 255 282 58 283 255 283 256 283 57 284 58 284 256 284 57 285 256 285 257 285 55 286 57 286 257 286 55 287 257 287 258 287 54 288 55 288 258 288 54 289 258 289 259 289 52 290 54 290 259 290 52 291 259 291 260 291 51 292 52 292 260 292 51 293 260 293 261 293 49 294 51 294 261 294 49 295 261 295 262 295 48 296 49 296 262 296 48 297 262 297 263 297 46 298 48 298 263 298 46 299 263 299 264 299 45 300 46 300 264 300 45 301 264 301 265 301 43 302 45 302 265 302 43 303 265 303 266 303 42 304 43 304 266 304 42 305 266 305 267 305 40 306 42 306 267 306 38 307 40 307 267 307 38 308 267 308 268 308 269 309 91 309 270 309 91 310 87 310 270 310 271 311 91 311 269 311 271 312 95 312 91 312 87 313 83 313 270 313 271 314 97 314 95 314 272 315 97 315 271 315 272 316 273 316 97 316 272 317 274 317 273 317 272 318 275 318 274 318 276 319 277 319 275 319 277 320 276 320 278 320 278 321 276 321 279 321 276 322 280 322 279 322 279 323 280 323 85 323 85 324 280 324 89 324 280 325 281 325 89 325 83 326 282 326 270 326 89 327 282 327 93 327 93 328 282 328 83 328 89 329 281 329 282 329 36 330 286 330 283 330 225 331 284 331 287 331 287 332 222 332 217 332 287 333 217 333 225 333 38 334 286 334 36 334 283 335 224 335 223 335 284 336 285 336 288 336 223 337 34 337 283 337 284 338 288 338 143 338 36 339 283 339 34 339 290 340 60 340 62 340 289 341 220 341 64 341 292 342 291 342 172 342 293 343 290 343 62 343 294 344 291 344 292 344 289 345 291 345 294 345 62 346 141 346 293 346 296 347 214 347 295 347 116 348 100 348 105 348 112 349 100 349 116 349 118 349 116 349 105 349 118 349 105 349 109 349 120 349 118 349 109 349 112 350 297 350 100 350 213 349 297 349 112 349 120 349 109 349 102 349 213 349 298 349 297 349 212 351 298 351 213 351 111 349 120 349 102 349 299 349 298 349 212 349 299 349 212 349 215 349 107 352 111 352 102 352 107 353 115 353 111 353 300 349 299 349 215 349 301 349 115 349 107 349 302 349 215 349 214 349 302 354 300 354 215 354 295 355 115 355 301 355 296 356 302 356 214 356 295 357 214 357 115 357 246 358 247 358 300 358 108 359 250 359 107 359 107 360 250 360 301 360 300 361 247 361 299 361 247 362 242 362 299 362 301 363 251 363 295 363 250 364 251 364 301 364 299 365 242 365 298 365 295 366 251 366 296 366 242 367 241 367 298 367 251 368 254 368 296 368 298 369 241 369 297 369 296 370 254 370 302 370 241 371 239 371 297 371 254 372 246 372 302 372 297 373 239 373 100 373 302 374 246 374 300 374 303 349 209 349 98 349 128 349 90 349 94 349 130 375 128 375 94 375 123 376 86 376 90 376 123 349 90 349 128 349 130 377 94 377 84 377 132 378 130 378 84 378 208 349 86 349 123 349 208 349 304 349 86 349 132 379 84 379 88 379 121 349 132 349 88 349 208 349 305 349 304 349 121 380 88 380 92 380 211 381 305 381 208 381 92 349 126 349 121 349 96 382 126 382 92 382 306 383 305 383 211 383 306 349 211 349 210 349 98 349 126 349 96 349 98 384 209 384 126 384 303 385 306 385 210 385 303 386 210 386 209 386 275 387 277 387 306 387 306 388 277 388 305 388 97 389 273 389 98 389 273 390 274 390 98 390 98 391 274 391 303 391 277 392 278 392 305 392 305 393 278 393 304 393 278 394 279 394 304 394 304 395 279 395 86 395 274 396 275 396 303 396 303 397 275 397 306 397 177 398 33 398 307 398 197 399 177 399 307 399 197 400 307 400 308 400 197 401 308 401 309 401 188 402 197 402 309 402 188 403 309 403 310 403 192 404 188 404 310 404 192 405 310 405 311 405 202 406 192 406 311 406 202 407 311 407 312 407 174 408 202 408 312 408 174 409 312 409 313 409 185 410 174 410 313 410 185 411 313 411 314 411 194 412 185 412 314 412 194 413 314 413 315 413 203 414 194 414 315 414 207 415 203 415 315 415 207 416 315 416 316 416 179 417 207 417 316 417 179 418 316 418 317 418 179 419 317 419 318 419 182 420 179 420 318 420 72 421 318 421 12 421 72 422 182 422 318 422 323 423 319 423 320 423 323 424 321 424 319 424 324 425 322 425 321 425 324 426 321 426 323 426 11 427 322 427 324 427 325 428 11 428 324 428 18 429 14 429 11 429 18 430 16 430 14 430 327 431 11 431 325 431 327 432 325 432 326 432 32 433 327 433 328 433 25 434 23 434 20 434 25 435 18 435 11 435 25 436 20 436 18 436 25 437 32 437 29 437 25 438 11 438 327 438 25 439 327 439 32 439 27 440 25 440 29 440 331 441 355 441 332 441 334 442 137 442 333 442 332 443 329 443 333 443 137 444 134 444 333 444 355 445 329 445 332 445 330 446 270 446 282 446 330 447 331 447 270 447 331 448 330 448 355 448 329 449 334 449 333 449 337 450 340 450 338 450 336 451 340 451 337 451 341 452 233 452 339 452 338 453 335 453 339 453 233 454 231 454 339 454 340 455 335 455 338 455 336 456 244 456 245 456 336 457 337 457 244 457 335 458 341 458 339 458 227 459 286 459 228 459 283 460 286 460 227 460 228 461 286 461 136 461 136 462 342 462 135 462 136 463 286 463 342 463 286 464 38 464 342 464 342 465 38 465 268 465 255 466 60 466 343 466 343 467 60 467 138 467 138 468 60 468 140 468 60 469 290 469 140 469 140 470 290 470 238 470 290 471 293 471 238 471 238 472 293 472 216 472 226 473 292 473 229 473 294 474 292 474 226 474 229 475 292 475 230 475 230 476 344 476 232 476 230 477 292 477 344 477 292 478 172 478 344 478 344 479 172 479 171 479 144 480 143 480 345 480 345 481 143 481 234 481 234 482 143 482 235 482 143 483 288 483 235 483 235 484 288 484 236 484 288 485 285 485 236 485 236 486 285 486 237 486 347 487 348 487 346 487 6 488 349 488 70 488 346 489 348 489 66 489 348 490 2 490 66 490 70 491 350 491 351 491 349 492 350 492 70 492 350 493 352 493 351 493 351 494 352 494 353 494 352 495 354 495 353 495 353 496 354 496 346 496 354 497 347 497 346 497 346 172 66 172 68 172 346 172 69 172 71 172 346 172 68 172 69 172 351 172 346 172 71 172 351 498 65 498 67 498 351 499 67 499 70 499 351 172 353 172 346 172 351 172 71 172 65 172 268 500 331 500 332 500 268 501 332 501 342 501 268 502 270 502 331 502 333 503 134 503 135 503 342 504 333 504 135 504 342 505 332 505 333 505 256 506 255 506 282 506 256 507 282 507 281 507 257 508 256 508 281 508 258 509 257 509 281 509 258 510 281 510 280 510 259 511 258 511 280 511 260 512 280 512 276 512 260 513 259 513 280 513 261 514 260 514 276 514 262 515 261 515 276 515 262 516 276 516 275 516 262 517 275 517 272 517 263 518 262 518 272 518 264 519 263 519 272 519 264 520 272 520 271 520 265 521 264 521 271 521 266 522 265 522 271 522 266 523 271 523 269 523 267 524 266 524 269 524 267 525 269 525 270 525 268 526 267 526 270 526 255 527 330 527 282 527 138 528 329 528 343 528 138 529 137 529 334 529 330 530 255 530 343 530 343 531 329 531 355 531 138 532 334 532 329 532 355 533 330 533 343 533 344 534 337 534 338 534 344 535 244 535 337 535 344 536 171 536 244 536 339 537 231 537 232 537 344 538 339 538 232 538 344 539 338 539 339 539 145 540 144 540 245 540 145 541 245 541 252 541 147 542 145 542 252 542 149 543 147 543 252 543 149 544 252 544 249 544 151 545 149 545 249 545 153 546 151 546 249 546 153 547 249 547 248 547 155 548 153 548 248 548 157 549 155 549 248 549 157 550 248 550 99 550 159 551 99 551 253 551 159 552 157 552 99 552 161 553 159 553 253 553 163 554 161 554 253 554 163 555 253 555 240 555 165 556 163 556 240 556 167 557 165 557 240 557 167 558 240 558 243 558 169 559 167 559 243 559 169 560 243 560 244 560 171 561 169 561 244 561 144 562 336 562 245 562 340 563 336 563 345 563 234 564 335 564 345 564 234 565 233 565 341 565 336 566 144 566 345 566 345 567 335 567 340 567 234 568 341 568 335 568 289 569 64 569 63 569 289 570 63 570 176 570 291 571 289 571 176 571 291 572 176 572 199 572 172 573 291 573 199 573 172 574 199 574 187 574 170 575 172 575 187 575 168 576 170 576 187 576 168 577 187 577 189 577 166 578 168 578 189 578 164 579 166 579 189 579 164 580 189 580 196 580 162 581 164 581 196 581 160 582 162 582 196 582 160 583 196 583 193 583 158 584 160 584 193 584 156 585 158 585 193 585 156 586 193 586 178 586 154 587 156 587 178 587 152 588 154 588 178 588 152 589 178 589 200 589 150 590 152 590 200 590 148 591 150 591 200 591 148 592 200 592 181 592 146 593 148 593 181 593 143 594 146 594 181 594 143 595 181 595 186 595 143 596 186 596 195 596 284 597 143 597 195 597 284 598 195 598 190 598 287 599 284 599 190 599 328 600 307 600 33 600 307 601 328 601 327 601 327 602 308 602 307 602 327 603 309 603 308 603 326 604 309 604 327 604 326 605 310 605 309 605 311 606 310 606 326 606 311 607 326 607 325 607 325 608 312 608 311 608 312 609 325 609 324 609 324 610 313 610 312 610 323 611 313 611 324 611 323 612 314 612 313 612 320 613 314 613 323 613 320 614 315 614 314 614 319 615 316 615 315 615 319 616 315 616 320 616 321 617 317 617 316 617 321 618 316 618 319 618 318 619 321 619 322 619 318 620 317 620 321 620 322 621 12 621 318 621 173 622 183 622 347 622 347 623 183 623 348 623 6 624 205 624 349 624 183 625 2 625 348 625 183 626 1 626 2 626 349 627 205 627 350 627 350 628 205 628 352 628 205 629 173 629 352 629 352 630 173 630 354 630 354 631 173 631 347 631 223 632 224 632 222 632 218 633 139 633 220 633 219 634 293 634 141 634 139 635 64 635 220 635 218 636 220 636 294 636 223 637 222 637 287 637 225 638 285 638 284 638 223 639 287 639 34 639 289 640 294 640 220 640 62 641 64 641 141 641
+
+
+
+
+
+
+ 0.01275008 -0.03599995 0.00575 0.01275008 -0.03499972 0.006749987 0.01275008 -0.03499972 -0.005782008 0.01275008 -0.03599995 -0.005782008 0.01525527 -0.06846272 0.02643764 0.01525521 -0.06962722 0.02613562 0.01525551 0.06845593 0.02644127 0.01525419 0.06938499 0.01338958 0.01525408 0.07093381 0.0182076 0.01525521 0.07196158 0.02213627 0.01525527 0.07189172 0.02323365 0.01525527 0.07155174 0.02429103 0.01525533 0.07087653 0.02522397 0.01525533 0.07015961 0.02582722 0.01525515 -0.07078915 0.02532345 0.01525509 -0.07158952 0.02423405 0.01525533 0.06931853 0.02623498 0.001991987 -0.03195571 -0.005969822 0.00496602 -0.03161674 -0.005917847 -0.00129956 -0.03202265 -0.006027281 0.01149225 -0.0298767 -0.005804002 0.008071541 -0.03098642 -0.005863726 -0.00129956 -0.03202265 -0.00693649 0.003953099 -0.04506886 -0.01389724 0.003290772 -0.04505723 -0.01448982 0.004000306 -0.04506981 -0.008430123 5.17262e-4 -0.09991276 -0.007521629 0.001460254 -0.09867751 -0.01254737 0.00107187 -0.098921 -0.0114395 0.001556456 -0.09993737 -0.01020169 0.001873791 -0.09821575 -0.01431518 0.001491308 -0.100473 -0.00773859 9.93769e-4 -0.1000581 -0.007834136 0.002012014 -0.1001339 -0.01349753 0.001963317 -0.1006669 -0.0108065 0.001826882 -0.1007585 -0.009040892 0.001973927 -0.09888023 -0.01406329 0.001145005 -0.09978842 -0.009343564 9.79465e-4 -0.09774833 -0.01326966 5.00708e-4 -0.09952217 -0.009230971 0.001466155 -0.09737598 -0.01431459 0.001979172 -0.1015655 -0.007331848 0.001722872 -0.1009498 -0.006336629 0.001148343 -0.1003187 -0.006359696 0.001866698 -0.09977471 -0.01184284 6.62972e-4 -0.09655189 -0.01442646 0.001978993 -0.101453 0.007302224 0.002015531 -0.1015341 0.01072841 0.002020776 -0.1026339 0.006547868 0.001977384 -0.09735929 0.01488965 0.002018392 -0.09894138 0.01482957 0.002050161 -0.1009005 -0.01287823 0.001512587 -0.09692031 0.01886814 0.001895666 -0.09708678 0.01906824 0.003141701 -0.09672558 0.01721382 0.002631664 -0.09668362 0.01724505 0.01525461 -0.06638556 0.01107048 0.01531684 -0.02809709 0.008742451 0.01527011 -0.02812165 0.01008749 0.01541322 -0.03495347 0.00679636 0.01529753 2.91088e-4 0.00986433 0.01538336 0.02805906 0.009663701 0.01598632 0.02772003 0.009319663 0.01574355 -0.02785933 0.001120865 0.01574355 -0.03494775 0.001120865 0.01525497 0.06630653 0.01106137 0.01525533 -0.0719729 0.02268934 0.01525533 -0.0718559 0.02158552 0.01525431 0.06754839 0.01146334 0.01525491 0.06862276 0.01224523 0.01525545 -0.06930655 0.013206 0.01525849 0.0308212 0.01073813 0.01525515 -0.06847184 0.01210939 0.01525521 -0.06758683 0.01148784 0.01478117 -0.02838206 -0.004833817 0.01479393 -0.03496432 -0.004783034 0.0154075 0.0280478 0.002368271 0.01434761 -0.03497207 -0.005438685 0.01440834 -0.02857267 -0.005360782 0.01381129 -0.02886861 -0.005728781 0.01364868 -0.03498429 -0.005753815 0.01395577 0.09093081 0.06572037 0.01413846 -0.08728599 0.06573677 0.01145249 -0.06581288 0.06580018 0.01405632 0.08162027 0.06573927 0.0113241 0.09212976 0.06576734 0.01083076 -0.06659364 0.06581091 0.01023006 -0.06681382 0.0658214 0.01223301 -0.09178471 0.065764 0.01055824 0.06674385 0.0658158 0.01128304 0.06613343 0.06580305 0.0140239 -0.09052318 0.0657038 0.01149034 0.06554079 0.06579941 -0.00129956 0.03202265 -0.00693649 0.002183437 -0.03343588 -0.008461833 0.00580883 -0.03301286 -0.008398652 0.01049309 -0.03185337 -0.008318066 0.01400738 -0.07363653 -0.008202195 0.004000306 -0.07299888 -0.008418977 0.0195263 0.03413528 -0.008130788 0.02478319 0.03692895 -0.008059024 0.02265363 0.07229477 -0.008082211 0.004749238 0.03318434 -0.008417069 0.008216202 0.03248172 -0.008356451 0.01192337 0.03136295 -0.008298933 0.01711064 0.03255575 -0.008184432 0.01410186 -0.09920972 0.03568029 0.01402086 -0.1002057 0.04965913 0.01409626 -0.09731644 0.02168977 0.01405912 -0.09597444 0.01439887 0.009232044 -0.1003876 0.05701899 0.01348567 -0.100314 0.05461937 0.01386356 -0.09829264 0.02796608 0.003682017 -0.09666198 0.01693809 0.003991723 -0.09654337 0.01634633 0.01604408 -0.09726452 0.05765986 0.01604801 -0.09880876 0.05532085 0.009691536 -0.09662902 0.06232154 0.01709461 -0.09492969 0.05831378 0.01550072 -0.09585183 0.06038784 0.01631009 -0.09385323 0.06169879 0.01731884 -0.09655898 0.05505621 0.01127833 -0.09782427 0.06056421 0.01694846 -0.09035426 0.06338804 0.01672852 -0.09837257 0.05418431 0.01712381 -0.09166079 0.06193685 0.01684755 -0.0965237 0.05687052 0.01680636 -0.0929594 0.06160837 0.009895503 -0.09986299 0.05823487 0.01766943 -0.09368145 0.05754315 0.01738828 -0.09758037 0.05228775 0.01750987 -0.09083026 0.06109374 0.01798969 -0.09532123 0.05020284 0.01750129 -0.09752768 0.04964751 0.01751691 -0.09707927 0.04404056 0.01825076 -0.09299457 0.04874593 0.0173105 -0.09594506 0.03055042 0.01247197 -0.09562397 0.0627858 0.01788491 -0.09307253 0.03116083 0.01137804 -0.0940665 0.06480026 0.01717102 -0.09325778 0.01391702 0.01771134 -0.09048223 0.01480507 0.01298511 -0.0994578 0.05770701 0.01709634 -0.09175789 0.008280813 0.01780426 -0.0880571 0.009735107 0.01702117 -0.09012889 0.004008412 0.0133897 -0.09355521 0.06479984 0.01824826 -0.08454859 0.009548366 0.01778542 -0.0889334 0.06070017 0.01792943 -0.09211969 0.05757761 0.0169571 -0.08845549 9.66452e-4 0.0139051 -0.09571915 0.06199598 0.01731973 -0.08840882 0.0629692 0.01824975 -0.07915502 0.003960371 0.01791191 -0.09458613 0.05424767 0.01394724 -0.09495264 0.06292432 0.0182501 -0.08402371 0.05801284 0.01733583 -0.08140993 0.06283122 0.01756638 -0.08019298 -4.51478e-4 0.01697736 -0.08238166 -0.003934085 0.01708048 -0.07817596 -0.004988372 0.01824975 -0.0735929 0.0011819 0.01386237 -0.09941178 0.05715268 0.01692163 -0.08644747 -0.0014835 0.01480656 -0.09313273 0.06443524 0.0146678 -0.09550595 0.06171298 0.0171507 -0.07364398 -0.005523383 0.01533156 -0.09399074 0.06294918 0.0182414 -0.09188705 0.05447721 0.01495373 -0.09932076 0.05627477 0.005028963 -0.09980821 0.05901831 0.003704071 -0.09690874 0.06261616 0.0182479 -0.0896359 0.05662399 0.007416248 -0.09860134 0.06036168 0.01588678 -0.09228217 0.06406396 0.0087049 -0.09386259 0.06517255 0.01645541 -0.09149169 0.0637471 0.01785743 0.0760917 0.02556651 0.02032935 0.08272057 0.02274179 0.01917499 0.08650046 0.03726208 0.01812297 0.07934641 0.02859729 0.02211022 0.09042674 0.03104621 0.01871496 0.08513331 0.03771275 0.01826 0.08106082 0.04406738 0.02072077 0.09240239 0.04328978 0.01887381 0.08946073 0.05670869 0.01932829 0.09226977 0.05330705 0.019912 0.09287786 0.04919689 0.0183652 0.08324337 0.05208474 0.01827234 0.08228188 0.05795037 0.01778447 0.07098555 0.03128939 0.01772993 -0.06843483 0.0268085 0.01774889 -0.07007747 0.02642405 0.01773142 -0.07100701 0.02564436 0.01781612 -0.0762946 0.03292757 0.0177403 0.06959044 0.01304525 0.01792448 0.07042783 0.01237481 0.01771783 0.07222032 0.02149569 0.0177415 0.07233536 0.02313441 0.01771545 0.068578 0.0267769 0.01857239 0.07278394 0.01231306 0.01771301 0.06990343 0.02634787 0.0192365 0.08104592 0.02463907 0.01854521 0.08113002 0.02866518 0.02123636 0.07894605 0.01174789 0.01770973 0.07186877 0.02436202 0.0177477 0.07134896 0.02541404 0.02421218 0.08571201 0.01263672 0.02334362 0.08791196 0.02022451 0.01773983 -0.07193219 0.02441436 0.02003532 0.07641583 0.01209843 0.01824945 -0.02628624 9.94972e-4 0.01725304 -0.02695399 -0.005264997 0.02212774 0.07617062 0.007264137 0.01789242 0.0269348 0.009540677 0.02564173 0.07851934 0.003552198 0.01841259 0.06721901 0.00915426 0.01794481 0.06854307 0.01061469 0.01775461 0.06726753 0.01095432 0.01772058 0.06867736 0.01191878 0.02596211 0.0760793 0.002159178 0.02149051 0.07753175 0.009496092 0.01966446 0.06746518 0.007466316 0.02575343 0.0563125 0.002372682 0.02272903 0.05958044 0.004726171 0.01838475 0.02730405 0.001024127 0.02490073 0.0829305 0.007684946 0.01982951 0.06876266 0.007353723 0.01889127 0.02856278 0.002166986 0.02450364 0.08473956 0.01037549 0.01985681 0.07064169 0.007851123 0.02527326 0.0808801 0.005470693 0.01942962 0.07219213 0.009357094 0.01870036 0.06902253 0.009092986 0.02001422 0.02999681 0.003858387 0.02184522 0.07192295 0.005612254 0.0207647 0.03073251 0.004001617 0.01844459 0.07102495 0.01078271 0.01971387 0.02990382 0.001285374 0.02142792 0.03157699 0.004470229 0.01780122 -0.07776343 0.0260486 0.01774072 -0.07238233 0.02200543 0.02105152 0.07302445 0.007121682 0.02089798 0.03079861 0.002607226 0.02205663 0.06956535 0.004966378 0.0218814 0.03147017 0.003079295 0.01778531 -0.06935691 0.009533822 0.026425 0.07150012 0.001014769 0.01967447 0.0744946 0.01077049 0.02619856 0.07397419 0.001366317 0.01791524 0.06647169 0.01015043 0.01771545 -0.06585615 0.01072973 0.0178557 -0.02655535 0.007797241 0.017717 0.02181679 0.01073485 0.01786464 0.02380549 0.007799804 0.01806694 0.02641183 0.007799327 0.01846611 0.02820539 0.007529318 0.01802515 0.03160429 0.009856343 0.0186094 0.02984207 0.008288681 0.01772081 -0.07217788 0.02350479 0.01872003 0.03256022 0.008717298 0.01772058 0.0291875 0.01078933 0.01850378 0.0277273 0.006415784 0.01922029 0.0302186 0.007136464 0.01812714 0.0358783 0.009753465 0.01935786 0.02996009 0.006607592 0.01926463 0.02931791 0.005723297 0.01839613 0.02704596 0.004577577 0.01878613 0.02812594 0.004526615 0.01953727 0.03148323 0.007274866 0.0177192 -0.0709868 0.01721084 0.01865535 0.03663831 0.008950293 0.01938754 0.02920562 0.004087448 0.01966124 0.032655 0.007475674 0.02056092 0.03167861 0.005917072 0.0203197 0.03066635 0.005221307 0.01943558 0.0346952 0.008025765 0.02037376 0.03577584 0.007138192 0.02091366 0.03268069 0.006049692 0.01823639 0.02629685 9.45812e-4 0.01867288 0.05988305 0.00883615 0.02108323 0.03381973 0.006216585 0.02214223 0.03311836 0.004917621 0.02263033 0.03255778 0.003968894 0.02343142 0.0342983 0.004197776 0.0221641 0.03184098 0.003617525 0.02360594 0.03264099 0.002813458 0.01939404 0.04723829 0.008030593 0.02365881 0.0354979 0.00427252 0.02175712 0.03580552 0.005886912 0.02064394 0.03080344 9.93824e-4 0.0215494 0.03136479 9.5651e-4 0.02470123 0.03379857 0.002830028 0.02290761 0.03190577 0.001644492 0.02535498 0.03298443 0.00100255 0.02827692 0.03666877 9.96365e-4 0.0277574 0.03533804 9.87156e-4 0.02723366 0.03452372 9.91919e-4 0.02650457 0.03375881 9.6469e-4 0.0177505 -0.06960195 0.01277863 0.01825147 -0.08987236 0.02800357 0.02274304 0.03951668 0.005180716 0.01771903 -0.06869029 0.01183742 0.02094292 0.06274831 0.006255626 0.01772117 -0.0671156 0.0109198 0.02798712 0.04933762 9.99945e-4 0.02425217 0.03745794 0.004032135 0.02861595 0.03937429 9.97967e-4 0.01806557 0.02880376 0.00940442 0.0181955 0.02632331 0.005757808 0.0177403 0.06602144 0.0107541 0.02334398 0.03219801 0.00217694 0.01969182 0.03009063 -0.004388213 0.01873248 0.02900457 -0.004573345 0.02835083 0.03714478 -0.004518747 0.0281111 0.03623747 -0.004650533 0.02765685 0.03518486 -0.004490256 0.02856642 0.03869539 -0.004543066 0.0272063 0.03448969 -0.004193961 0.02062785 0.03086858 -0.004704952 0.01790994 0.02767449 -0.005126774 0.01759243 0.02672994 -0.004978299 0.02165019 0.03145796 -0.00480926 0.02680569 0.03409284 -0.00473237 0.02594316 0.03334671 -0.004358232 0.02460747 0.03260397 -0.004352152 0.02857756 0.04071611 -0.004353344 0.0144928 0.09549635 0.06215095 0.01275247 0.09729647 0.06080383 0.02159857 0.09347671 0.03178572 0.02186501 0.09493488 0.02326601 0.02299153 0.09281504 0.01331341 0.02403748 0.0881769 0.009839057 0.02365827 0.09104245 0.007481694 0.02432781 0.08836299 0.003360271 0.02511799 0.08348435 -0.001874268 0.01745975 0.08553916 0.06286764 0.0180087 0.08921474 0.06077188 0.01735335 0.08836823 0.06330859 0.01708579 0.09049236 0.06336343 0.01024436 0.09407389 0.0649814 0.01709741 0.09187358 0.06248897 0.01769191 0.09160608 0.06114113 0.01640081 0.09197062 0.06381958 0.01646602 0.09367853 0.06212985 0.01566225 0.09435176 0.06246185 0.01541656 0.09266525 0.06443399 0.01465344 0.09337788 0.06440985 0.0113784 0.09566038 0.06317824 0.01349532 0.09477913 0.06354916 0.01369553 0.09364044 0.06467497 0.01836138 0.09154033 0.05878388 0.006538152 0.09799247 0.06123512 0.01541125 0.09688031 0.05954456 0.01661854 0.09594374 0.05920177 0.01793837 0.09387618 0.05856812 0.009525954 0.09813839 0.0606215 0.006978929 0.09990084 0.05869418 0.01084989 0.09979736 0.05807662 0.01894474 0.092938 0.05522114 0.02635508 0.07218623 -0.004719674 0.01869237 0.09497624 0.05487537 0.01435667 0.09964692 0.05661839 0.0260064 0.07611167 -0.004228115 0.01589971 0.09923243 0.0558235 0.02550464 0.08057665 -0.003193676 0.012959 0.09975892 0.05728316 0.01727145 0.09405899 0.06018805 0.01696348 0.09851688 0.05528253 0.01735687 0.08128958 0.06274062 0.01815778 0.09648334 0.05507731 0.01865309 0.09752655 0.05203855 0.01943701 0.0960164 0.04995155 0.01761883 0.09818732 0.05439507 0.01806944 0.09834259 0.05285596 0.02004313 0.09488779 0.04691523 0.01919049 0.09751802 0.04899847 0.01966089 0.09719043 0.04589515 0.0210728 0.09605967 0.03105217 0.02063584 0.09185111 0.002674818 0.02035927 0.09312283 0.005159318 0.01972121 0.09506952 0.01071655 0.02259999 0.0753104 -0.007855296 0.02188146 0.08197921 -0.006174385 0.02222675 0.07886165 -0.007216811 0.02144169 0.08441877 -0.004971683 0.02094697 0.0881344 -0.00215888 0.02092295 0.09064459 8.06822e-4 0.02163052 0.0865609 -0.003486454 0.0210939 0.08937001 -8.3139e-4 0.02019309 0.09406846 0.00757271 0.0168839 0.09953457 0.03971993 0.01337915 0.1003247 0.05524343 0.01772028 0.09880161 0.03267461 0.01612508 0.1000757 0.04775166 0.01848602 0.09779846 0.02521109 0.008262813 0.1004368 0.05679821 0.01918029 0.09657603 0.01802015 0.01730364 0.0433129 0.06294345 0.01391977 -0.09338915 0.004576802 0.004000306 -0.09407943 0.005886793 0.01383769 -0.08758366 -0.004167318 0.004000306 -0.08794635 -0.004130721 0.004000306 -0.08644878 -0.005256295 0.013628 -0.07976871 -0.007645428 0.004000306 -0.08113706 -0.007529914 0.0140286 -0.09442085 0.007765829 0.004000306 -0.09553843 0.01137673 0.01352363 -0.08885848 -0.003047168 0.004000306 -0.08937203 -0.002773225 0.004000306 -0.08414578 -0.006506204 0.0138278 -0.08998435 -0.001728296 0.01375025 -0.08395832 -0.00635457 0.004000306 -0.09089785 -8.4768e-4 0.01397037 -0.07645881 -0.008056342 0.004000306 -0.07734733 -0.00818777 0.01374655 -0.0912975 1.17136e-4 0.01360583 -0.0857535 -0.005475759 0.004000246 -0.09254413 0.001986086 0.01359117 -0.09256219 0.002498209 0.00396043 -0.100551 -0.01259547 0.003996491 -0.09862416 -0.01355731 0.0039891 -0.1019378 -0.008611142 0.003995895 -0.1018359 0.007622063 0.003965914 -0.1006268 0.0115987 0.01730519 0.02691859 0.007834672 0.01730448 0.02691823 -0.00603348 0.01693195 0.02717745 -0.006693661 0.01658165 0.02736848 0.008097469 0.01613301 0.02763557 0.008442938 0.01571041 0.02787822 0.008903443 0.01406306 0.02874881 -0.006753444 0.01097625 0.03007668 -0.006806433 0.007792294 0.03104156 -0.006869137 0.002095818 0.03194195 -0.006974041 0.01737529 0.01386004 0.007827043 0.01509308 -0.02822136 -0.006732821 0.01620465 -0.0275942 0.00831604 0.001919448 -0.03195518 -0.006979823 0.01653671 -0.02739608 -0.00670886 0.01689577 -0.02717638 -0.006172418 0.01720732 -0.02698153 0.007858633 0.004953086 -0.03162568 -0.006921291 0.008110463 -0.03097021 -0.006862223 0.01068258 -0.03016918 -0.006816089 0.01284831 -0.02931982 -0.006778836 0.01550889 -0.02798962 0.009178698 0.0195418 0.03197079 -0.007339179 0.01965516 0.03298032 -0.007791817 0.01963526 0.03018623 -0.00521028 0.0186029 0.02908128 -0.00537312 0.01886272 0.03069287 -0.006884217 0.01972895 0.0307343 -0.006037831 0.01768273 0.03154402 -0.007909119 0.01804322 0.03064543 -0.007393181 0.01841217 0.02931785 -0.006098926 0.01790899 0.02950787 -0.006853282 0.01765668 0.02802896 -0.006104648 0.0171945 0.02840209 -0.006946027 0.0163781 0.03021115 -0.007998883 0.02839928 0.03962457 -0.00544399 0.01686894 0.02971839 -0.007681131 0.01642018 0.02854341 -0.007632434 0.02759778 0.03959286 -0.006766915 0.02731513 0.03486478 -0.005115211 0.02762407 0.03573542 -0.005613446 0.02631807 0.03377467 -0.005123853 0.02815294 0.03720879 -0.005432069 0.02663391 0.03436774 -0.005726516 0.02680438 0.03484261 -0.00612843 0.02704781 0.03600966 -0.006648063 0.02777248 0.03716588 -0.00621742 0.02656579 0.03985047 -0.007583558 0.02580296 0.03377348 -0.005909681 0.02746719 0.03785765 -0.006737709 0.02500844 0.03294843 -0.005195021 0.02563184 0.03839391 -0.007939338 0.02458435 0.03310203 -0.005979299 0.02479165 0.03372406 -0.006682336 0.02696537 0.03734266 -0.007151663 0.02580666 0.03442209 -0.006684184 0.02642631 0.03659904 -0.007384896 0.0260964 0.03739589 -0.007698953 0.02625763 0.03540915 -0.007074296 0.02549302 0.03620797 -0.007763445 0.02513086 0.03499871 -0.007462561 0.02400469 0.03420168 -0.007388472 0.02159279 0.03173613 -0.005717337 0.02411347 0.03537482 -0.007891893 0.02131634 0.0319916 -0.00636667 0.02119076 0.0327202 -0.00717777 0.02059346 0.0311079 -0.005544245 0.01531285 0.02976495 -0.00819683 0.02796757 0.04108822 -0.006179094 0.01998221 0.03157371 -0.006802499 0.02603971 0.07215046 -0.005738556 0.02549082 0.07241696 -0.006608009 0.02475482 0.07251006 -0.007301986 0.02405291 0.07239085 -0.007722735 0.02338039 0.07293188 -0.007950425 0.02586454 0.07494807 -0.005416274 0.02541518 0.07517117 -0.006174743 0.0224809 0.09368479 0.01141023 0.02277725 0.09329557 0.01196503 0.0218873 0.0942226 0.01111209 0.02255088 0.09223526 0.005462467 0.02243566 0.09147268 0.003393948 0.02315843 0.09022623 0.002144217 0.02133291 0.09235417 0.003963887 0.02109259 0.09487408 0.01171529 0.02255219 0.09015947 0.001122832 0.02328389 0.08847135 -4.18373e-4 0.02176403 0.09108489 0.001904964 0.02189761 0.09000521 3.16573e-4 0.02314645 0.08741676 -0.001852512 0.02400445 0.08654731 -0.001642346 0.02068847 0.09456551 0.009591698 0.02350747 0.08530092 -0.003496587 0.02312469 0.09214586 0.007413566 0.02007305 0.09565222 0.01381278 0.02433764 0.08399498 -0.003522217 0.02369409 0.08299136 -0.004932463 0.0246405 0.08106517 -0.005014479 0.02216893 0.09344315 0.008467197 0.02371168 0.08099937 -0.005937755 0.02438145 0.07891488 -0.006216347 0.02505761 0.07865035 -0.005567014 0.02467328 0.0766198 -0.006679952 0.02351367 0.07814842 -0.007057845 0.02153444 0.09387749 0.008433938 0.02493667 0.07486206 -0.006810486 0.0231387 0.09151887 0.005215883 0.02188003 0.09267038 0.005340278 0.02361482 0.08969438 0.002285957 0.02397215 0.0895701 0.00362432 0.02109295 0.09349417 0.006587684 0.02259302 0.08666884 -0.003021419 0.02415245 0.0749523 -0.007376611 0.02396637 0.08834439 7.67516e-4 0.02453917 0.08682519 4.78764e-4 0.02283072 0.08467364 -0.004417598 0.02242356 0.08326458 -0.005456984 0.02288842 0.08102083 -0.006334245 0.0232464 0.07647395 -0.007526755 0.02453583 0.08579176 -0.001308858 0.0223003 0.08890777 -8.76855e-4 0.02205628 0.08533 -0.004282772 0.02159595 0.09499824 0.01486802 0.02173113 0.08788686 -0.002253055 0.02498739 0.08216941 -0.00370419 0.02554512 0.07820188 -0.004844546 0.01963663 0.09717452 0.0229175 0.02058821 0.09696173 0.03028047 0.02059394 0.09658706 0.02244108 0.02001577 0.09754824 0.03028059 0.01908481 0.09815973 0.03020846 0.01570564 0.1001212 0.05293631 0.01917874 0.09846764 0.03785353 0.01839143 0.0988993 0.03620785 0.02024459 0.09719789 0.03687477 0.01081275 0.1002487 0.05711257 0.0194801 0.09812742 0.04026007 0.02079242 0.09573632 0.01629662 0.01773518 0.09944945 0.04233723 0.02166253 0.095344 0.02069306 0.01817673 0.09923118 0.0453754 0.01846194 0.09890687 0.04776012 0.01920193 0.09804558 0.04670304 0.01717591 0.09976452 0.04760015 0.01641726 0.09958606 0.05406814 0.01516723 0.09998124 0.0549544 0.01384425 0.1000375 0.05609238 0.0171352 0.09966903 0.05082076 0.005185425 0.1003054 0.05800729 0.01863676 0.09828937 0.05021017 0.01753842 0.09918975 0.05206251 0.01696991 0.09909987 0.05424112 0.01471227 0.08861172 0.06562489 0.01505506 0.09158569 0.06520736 0.01265275 0.09369188 0.06495857 0.01405543 0.0924921 0.06529772 0.01033169 0.09321093 0.06550502 0.0128737 0.09240245 0.06558048 0.01698964 0.08179032 0.06371951 0.01638817 0.08193767 0.06451648 0.01591134 0.08146893 0.06495219 0.01505017 0.08267289 0.06545823 0.01656985 0.09088844 0.06409984 0.01692467 0.0857771 0.06404495 0.01589435 0.0905053 0.06490945 0.01566338 0.08772248 0.06523299 0.01532536 0.08994162 0.06532716 0.01639926 0.08798825 0.06469219 0.0158714 0.09167486 0.06457889 0.01466888 0.09270209 0.06490921 0.01686424 0.08931946 0.06406539 0.01631027 -0.03330969 0.06460201 0.01458257 -0.03430306 0.06561368 0.01538306 -0.02485287 0.06529045 0.016963 -0.01812714 0.0637573 0.01731508 -0.0200001 0.06288248 0.01458144 0.06712359 0.06562495 0.01533079 0.06196159 0.06531643 0.01585274 -0.03474557 0.06499207 0.01714766 0.05355525 0.06336748 0.01512825 -0.06701487 0.06541526 0.01680219 0.04728096 0.06399744 0.01618486 0.04449743 0.06471604 0.01651024 -0.06627243 0.06437349 0.01550287 0.04270136 0.06521964 0.01685917 -0.075567 0.06390714 0.01467519 0.04364603 0.06558966 0.01586258 -0.06804811 0.06498634 0.01716297 -0.08074456 0.06331968 0.01465499 0.01112139 0.06560593 0.01666086 0.02990281 0.0641992 0.01710283 0.03484451 0.06346976 0.01532387 -0.08206605 0.06533408 0.01625269 -0.08013206 0.06465351 0.01612544 -0.08367443 0.06480574 0.01670312 -0.08151417 0.06415039 0.01554948 0.01232856 0.0651921 0.01629728 -0.00743705 0.06460422 0.0149551 -0.003743231 0.06548756 0.01578903 -0.007540047 0.06504142 0.0149492 -0.08867633 0.065503 0.01676559 -0.08659207 0.06417524 0.0173183 -0.08612412 0.06321275 0.01575332 -0.08683866 0.06512302 0.01623821 -0.08888655 0.06466281 0.01690804 -0.08883953 0.06382048 0.01558005 -0.09015786 0.06506723 0.01380324 -0.09166884 0.06558007 0.01485037 -0.09138506 0.06527715 0.0124315 -0.09283959 0.06544744 0.01421791 -0.09237033 0.0652188 0.01575338 -0.09148055 0.06460362 0.01508086 -0.09226918 0.06480371 0.009714245 -0.09273362 0.06567174 0.01642924 -0.09007793 0.06428569 0.01664972 -0.07374691 -0.006529033 0.01600009 -0.02838844 -0.007301092 0.01578384 -0.07313781 -0.007447898 0.01490014 -0.02972543 -0.0079813 0.01493877 -0.07361114 -0.007924079 0.01379168 -0.03052783 -0.008250653 0.01497548 -0.07693153 -0.007665276 0.01514303 -0.07947468 -0.007218599 0.01665377 -0.08196669 -0.004944026 0.016707 -0.07840979 -0.005825519 0.0160101 -0.08008617 -0.00643599 0.01576501 -0.07676792 -0.00719887 0.01636767 -0.07686084 -0.006570875 0.01495969 -0.09428876 0.008395612 0.0153858 -0.0953083 0.01359111 0.01539635 -0.09435242 0.009603738 0.01631981 -0.09446614 0.01311218 0.01672023 -0.0927304 0.008604466 0.01593804 -0.09345191 0.007821619 0.01453095 -0.09241276 0.002682328 0.01485455 -0.09150606 0.001196265 0.01475661 -0.09051245 -5.26103e-4 0.01459527 -0.08903914 -0.002481341 0.01518654 -0.0880025 -0.003079593 0.01498961 -0.08713907 -0.00398463 0.01490265 -0.09322619 0.005085051 0.01453131 -0.08592408 -0.005084574 0.01532608 -0.0851612 -0.005048096 0.01569056 -0.09281122 0.005316615 0.01487469 -0.08400964 -0.005960464 0.01583677 -0.08595138 -0.004083454 0.01632022 -0.08510792 -0.004008114 0.01391458 -0.08247613 -0.006904244 0.01576668 -0.08319312 -0.005659699 0.01606762 -0.09196281 0.004086613 0.01668792 -0.08475911 -0.003446221 0.01489174 -0.08163189 -0.006810009 0.0142349 -0.07974433 -0.007534623 0.01662677 -0.09114253 0.004035711 0.0152474 -0.09226191 0.00320065 0.01499563 -0.096385 0.01807034 0.01561892 -0.09069097 7.24274e-4 0.01613694 -0.09031963 0.00107342 0.01626902 -0.08234423 -0.005387485 0.01659858 -0.0892865 6.88927e-4 0.01554173 -0.08895123 -0.001738846 0.01611471 -0.08858692 -0.001338005 0.01660913 -0.08713096 -0.001833379 0.01602774 -0.08746844 -0.002630591 0.01656645 -0.0958119 0.02144742 0.01698637 -0.0951581 0.02162808 0.01538282 -0.09762263 0.02645736 0.01590484 -0.09639948 0.0211156 0.01686412 -0.09700971 0.03179198 0.01471602 -0.09760856 0.02458173 0.01616895 -0.09758764 0.03019654 0.01472645 -0.09857308 0.03138488 0.01674443 -0.09872752 0.05221796 0.01639539 -0.09815913 0.03639066 0.01451653 -0.1001753 0.05252099 0.01529031 -0.09878087 0.03501069 0.017039 -0.09789073 0.04178822 0.015621 -0.09923893 0.04171693 0.01631283 -0.09898674 0.0446071 0.01452738 -0.09970319 0.04198527 0.01712846 -0.09832078 0.04919201 0.01596599 -0.09946501 0.05287086 0.0152778 -0.09991806 0.05254602 0.01626133 -0.09903174 0.05392843 0.01534664 -0.09957122 0.05494046 0.0157693 -0.09962052 0.04860121 0.01471275 -0.1000501 0.04882246 0.01662176 -0.09895336 0.05023396 0.01254308 -0.1000522 0.05682724 0.01298117 -0.1002654 0.05575144 0.005778968 -0.1002416 0.05816143 0.01448673 -0.1000347 0.05489182 0.01394444 -0.09985607 0.05620849 0.01525533 -0.07075327 0.01761788 0.01736629 -0.07195091 0.0217306 0.001533508 -0.09950399 0.01012212 0.001025557 -0.09884017 0.01056593 5.92975e-4 -0.09863483 0.01050209 0.001832902 -0.1003246 0.009448587 0.001974523 -0.09983032 0.01168042 5.17552e-4 -0.09765166 0.01208102 0.001766562 -0.09878975 0.01216787 0.001187205 -0.09792006 0.01224917 0.001035809 -0.1002627 0.005401015 0.001462638 -0.1005636 0.006095826 0.001776695 -0.1010034 0.006177127 0.001020014 -0.1000542 0.006943464 9.99035e-4 -0.09638971 0.01389873 0.001452565 -0.09654808 0.01425457 5.23344e-4 -0.09938168 0.008710205 0.001776695 -0.09684705 0.0146051 0.001020133 -0.09967523 0.00847727 0.001451373 -0.1000428 0.008424937 0.01603966 0.006741762 0.0084728 0.01609271 0.01873928 0.008420407 0.0153107 0.02153575 0.009673953 0.01662915 0.01279479 0.008060336 0.01563102 0.008166491 0.008982539 0.01684528 -0.003383696 0.007970631 0.01614218 -0.00299412 0.008378505 0.01545441 -0.01094591 0.009310483 0.01633054 -0.01369261 0.008235692 0.01579999 -0.01718688 0.008743107 0.01698243 -0.01318687 0.007912099 0.01626271 -0.02141422 0.008293986 0.01701664 -0.02209883 0.00789535 0.005159437 0.03159219 -0.006914615 0.01742732 -0.06941688 0.01333642 0.0174365 -0.06881499 0.01238256 0.01738965 -0.06804949 0.01169764 0.01744282 -0.06699973 0.01116895 0.01744437 -0.06594496 0.01097697 0.0174641 -0.0707519 0.01735627 0.01738774 -0.07152187 0.0244373 0.01745486 -0.0719093 0.02348417 0.01734173 -0.07199579 0.02276599 0.01740998 -0.06840187 0.02650886 0.01739174 -0.0693944 0.02626132 0.01747173 -0.07031303 0.02581375 0.01743316 -0.07097434 0.02520775 0.01745283 0.07020527 0.02588206 0.01748377 0.06900221 0.02641522 0.01747649 0.07195955 0.02169626 0.01746118 0.07203048 0.02248591 0.01748692 0.07198733 0.02311468 0.01745164 0.07170301 0.02413064 0.017457 0.07102608 0.0251646 0.01749169 0.0693317 0.01310926 0.01749461 0.07079732 0.01747363 0.01754784 0.06598627 0.01096141 0.01751565 0.06671994 0.01108694 0.01752322 0.06751316 0.01136583 0.01744717 0.06845861 0.01201063 0.01753789 0.04110854 0.01096284 0.017443 0.0231027 0.01097673 0.01760327 -0.05609285 0.02661913 0.0175144 0.06807923 0.02655339 0.01762413 -0.06951296 0.02638369 0.01767903 0.07083916 0.02565068 0.01759839 -0.07153367 0.0247004 0.01756387 -0.072106 0.02209818 0.01767337 -0.06961202 0.01328706 0.01762712 -0.06796371 0.01147615 0.0176115 -0.01517248 0.01086509 0.003214836 -0.09815907 -0.01448351 0.003617227 -0.09879928 -0.01423656 0.003900349 -0.09870046 -0.01389956 0.003238976 -0.1014407 -0.01234865 0.003275692 -0.1001176 -0.01381379 0.00388211 -0.09989446 -0.013381 0.003621459 -0.1009687 -0.01281023 0.003143072 -0.1009114 -0.01315134 0.003636479 -0.09998744 -0.01367527 0.003143787 -0.09918689 -0.01429826 0.003884196 -0.1014927 -0.01094985 0.003306925 -0.1025606 -0.009138345 0.00320065 -0.1029571 -0.00673151 0.003933787 -0.1023779 -0.006398856 0.003597438 -0.1027849 -0.006709754 0.003797352 -0.1022346 -0.009019017 0.0031268 -0.1019954 -0.01107579 0.003540754 -0.1019176 -0.01090216 0.003161132 -0.1029484 0.005852937 0.003511488 -0.1028565 0.005402863 0.003209114 -0.1015919 0.01104986 0.003589451 -0.1011623 0.01161509 0.003132641 -0.1007271 0.01272881 0.003884673 -0.09967941 0.01338398 0.003291547 -0.09772789 0.01634824 0.003581166 -0.09997171 0.0135954 0.00313282 -0.1022199 0.009431004 0.003539979 -0.1021322 0.009283125 0.003767073 -0.09815424 0.01552397 0.003884971 -0.1018062 0.009046554 0.003133237 -0.1026273 0.007927179 0.003252506 -0.09930837 0.01473021 0.003602862 -0.1026018 0.007202208 0.003960192 -0.09809124 0.01507306 0.003898561 -0.1024042 0.006148159 0.001409173 -0.0969783 -0.01446241 0.002282798 -0.09864825 -0.01443433 0.002213478 -0.09724247 0.01674741 0.002250254 -0.09901076 0.01505315 0.002225339 -0.1008701 0.01246374 0.002260625 -0.1020631 0.009856343 0.002211153 -0.1025637 0.008052587 0.002240896 -0.1028411 0.0065189 0.002202332 -0.1029523 0.00494641 0.002248108 -0.1029615 -0.006088137 0.002312481 -0.1028251 -0.007832586 0.0022946 -0.1022176 -0.01045769 0.002231538 -0.1012285 -0.01269668 0.002250075 -0.100399 -0.01360625 0.002197444 -0.09962171 -0.01407009 0.002043545 -0.1027451 -0.006976664 0.002110064 -0.1025344 -0.008810997 0.002024829 -0.101992 -0.01044631 0.002162456 -0.09663522 0.01718097 0.001996159 -0.09631699 0.01582747 0.001991093 -0.09637141 0.01606673 0.001991689 -0.0964697 0.01649612 0.002002358 -0.09655088 0.01684886 0.002127408 -0.09661984 0.01712179 6.26781e-4 -0.09585702 0.0141291 9.80615e-4 -0.09591048 0.01428204 0.001439452 -0.09602177 0.01466393 0.001133084 -0.09594517 0.01439899 0.001774132 -0.09614801 0.01513957 0.001859962 -0.09621816 0.01542663 0.001551747 -0.09606069 0.01480835 -0.01275008 -0.03599995 0.00575 -0.01275008 -0.03499972 0.006749987 -0.01275008 -0.03499972 -0.005782008 -0.01275008 -0.03599995 -0.005782008 -0.01525527 -0.06846272 0.02643764 -0.01525521 -0.06962722 0.02613562 -0.01525551 0.06845593 0.02644127 -0.01525419 0.06938499 0.01338958 -0.01525408 0.07093381 0.0182076 -0.01525521 0.07196158 0.02213627 -0.01525527 0.07189172 0.02323365 -0.01525527 0.07155174 0.02429103 -0.01525533 0.07087653 0.02522397 -0.01525533 0.07015961 0.02582722 -0.01525515 -0.07078915 0.02532345 -0.01525509 -0.07158952 0.02423405 -0.01525533 0.06931853 0.02623498 -0.001991987 -0.03195571 -0.005969822 -0.00496602 -0.03161674 -0.005917847 0.00129956 -0.03202265 -0.006027281 -0.01149225 -0.0298767 -0.005804002 -0.008071541 -0.03098642 -0.005863726 0.00129956 -0.03202265 -0.00693649 0 -0.03199917 -0.007007956 0 -0.09719133 0.01258903 0 -0.09900844 -0.01053547 0 -0.09854811 0.01050466 0 -0.09759068 -0.01308089 0 -0.09960412 0.007763147 0 -0.09642875 -0.01442515 0 -0.09998333 0.005358457 0 -0.04499971 -0.008499979 0 -0.04499971 -0.01444751 0 -0.03349435 -0.008499979 0 -0.0960161 0.01387166 0 -0.09996247 -0.006706297 0 -0.09962952 -0.008555948 -0.003953099 -0.04506886 -0.01389724 -0.003290772 -0.04505723 -0.01448982 -0.004000306 -0.04506981 -0.008430123 -5.17262e-4 -0.09991276 -0.007521629 -0.001460254 -0.09867751 -0.01254737 -0.00107187 -0.098921 -0.0114395 0 -0.09874641 -0.01122105 -0.001556456 -0.09993737 -0.01020169 -0.001873791 -0.09821575 -0.01431518 -0.001491308 -0.100473 -0.00773859 -9.93769e-4 -0.1000581 -0.007834136 -0.002012014 -0.1001339 -0.01349753 -0.001963317 -0.1006669 -0.0108065 -0.001826882 -0.1007585 -0.009040892 -0.001973927 -0.09888023 -0.01406329 -0.001145005 -0.09978842 -0.009343564 -9.79465e-4 -0.09774833 -0.01326966 -5.00708e-4 -0.09952217 -0.009230971 0 -0.09753227 -0.01321661 -0.001466155 -0.09737598 -0.01431459 -0.001979172 -0.1015655 -0.007331848 -0.001722872 -0.1009498 -0.006336629 -0.001148343 -0.1003187 -0.006359696 -0.001866698 -0.09977471 -0.01184284 0 -0.1000351 -0.005976319 -6.62972e-4 -0.09655189 -0.01442646 -0.001978993 -0.101453 0.007302224 -0.002015531 -0.1015341 0.01072841 -0.002020776 -0.1026339 0.006547868 -0.001977384 -0.09735929 0.01488965 -0.002018392 -0.09894138 0.01482957 -0.002050161 -0.1009005 -0.01287823 -0.001512587 -0.09692031 0.01886814 0 -0.09720784 0.01963317 -0.001895666 -0.09708678 0.01906824 -0.003141701 -0.09672558 0.01721382 -0.002631664 -0.09668362 0.01724505 0 -0.09545648 0.0641179 0 -0.09746867 0.062168 0 -0.09860819 0.06087726 0 -0.09355777 0.06558054 0 -0.1002315 0.05855453 0 -0.09227573 0.06597638 0 -0.1005687 0.05736058 0 -0.1005471 0.05282706 0 -0.1001303 0.04343301 0 -0.09935426 0.03458958 0 -0.06699997 0.06599998 0 -0.09843009 0.02715712 -0.01525461 -0.06638556 0.01107048 -0.01531684 -0.02809709 0.008742451 -0.01527011 -0.02812165 0.01008749 -0.01541322 -0.03495347 0.00679636 -0.01529753 2.91088e-4 0.00986433 -0.01538336 0.02805906 0.009663701 -0.01598632 0.02772003 0.009319663 -0.01574355 -0.02785933 0.001120865 -0.01574355 -0.03494775 0.001120865 -0.01525497 0.06630653 0.01106137 -0.01525533 -0.0719729 0.02268934 -0.01525533 -0.0718559 0.02158552 -0.01525431 0.06754839 0.01146334 -0.01525491 0.06862276 0.01224523 -0.01525545 -0.06930655 0.013206 -0.01525849 0.0308212 0.01073813 -0.01525515 -0.06847184 0.01210939 -0.01525521 -0.06758683 0.01148784 -0.01478117 -0.02838206 -0.004833817 -0.01479393 -0.03496432 -0.004783034 -0.0154075 0.0280478 0.002368271 -0.01434761 -0.03497207 -0.005438685 -0.01440834 -0.02857267 -0.005360782 -0.01381129 -0.02886861 -0.005728781 -0.01364868 -0.03498429 -0.005753815 -0.01395577 0.09093081 0.06572037 -0.01413846 -0.08728599 0.06573677 -0.01145249 -0.06581288 0.06580018 -0.01405632 0.08162027 0.06573927 -0.0113241 0.09212976 0.06576734 -0.01083076 -0.06659364 0.06581091 -0.01023006 -0.06681382 0.0658214 -0.01223301 -0.09178471 0.065764 0 0.06699997 0.06599998 0 0.09224295 0.06598007 -0.01055824 0.06674385 0.0658158 -0.01128304 0.06613343 0.06580305 -0.0140239 -0.09052318 0.0657038 -0.01149034 0.06554079 0.06579941 0 0.08190739 -0.006633341 0 0.09197574 0.002167284 0 0.08425253 -0.005493581 0 0.08619076 -0.004260599 0 0.0934062 0.06565153 0 0.09003198 -6.26707e-4 0 0.0881462 -0.002672195 0 0.09534668 0.0642085 0 0.09691917 0.06275653 0 0.09877943 0.06064033 0 0.1001479 0.05870962 0 0.03351694 -0.008500039 0 0.03199923 -0.007008314 0 0.1005699 0.05746334 0 0.1005215 0.05219072 0 0.1001657 0.04405695 0 0.09912401 0.03232187 0 0.09753769 0.02142173 0 0.07273262 -0.008476912 0 0.09593093 0.01289165 0 0.07633733 -0.008128881 0 0.09505736 0.009426057 0 0.0942201 0.006911933 0 0.07924503 -0.007503211 0 0.09313482 0.00434643 0.00129956 0.03202265 -0.00693649 -0.002183437 -0.03343588 -0.008461833 -0.00580883 -0.03301286 -0.008398652 -0.01049309 -0.03185337 -0.008318066 -0.01400738 -0.07363653 -0.008202195 -0.004000306 -0.07299888 -0.008418977 -0.0195263 0.03413528 -0.008130788 -0.02478319 0.03692895 -0.008059024 -0.02265363 0.07229477 -0.008082211 -0.004749238 0.03318434 -0.008417069 -0.008216202 0.03248172 -0.008356451 -0.01192337 0.03136295 -0.008298933 -0.01711064 0.03255575 -0.008184432 -0.01410186 -0.09920972 0.03568029 -0.01402086 -0.1002057 0.04965913 -0.01409626 -0.09731644 0.02168977 -0.01405912 -0.09597444 0.01439887 -0.009232044 -0.1003876 0.05701899 -0.01348567 -0.100314 0.05461937 -0.01386356 -0.09829264 0.02796608 -0.003682017 -0.09666198 0.01693809 -0.003991723 -0.09654337 0.01634633 -0.01604408 -0.09726452 0.05765986 -0.01604801 -0.09880876 0.05532085 -0.009691536 -0.09662902 0.06232154 -0.01709461 -0.09492969 0.05831378 -0.01550072 -0.09585183 0.06038784 -0.01631009 -0.09385323 0.06169879 -0.01731884 -0.09655898 0.05505621 -0.01127833 -0.09782427 0.06056421 -0.01694846 -0.09035426 0.06338804 -0.01672852 -0.09837257 0.05418431 -0.01712381 -0.09166079 0.06193685 -0.01684755 -0.0965237 0.05687052 -0.01680636 -0.0929594 0.06160837 -0.009895503 -0.09986299 0.05823487 -0.01766943 -0.09368145 0.05754315 -0.01738828 -0.09758037 0.05228775 -0.01750987 -0.09083026 0.06109374 -0.01798969 -0.09532123 0.05020284 -0.01750129 -0.09752768 0.04964751 -0.01751691 -0.09707927 0.04404056 -0.01825076 -0.09299457 0.04874593 -0.0173105 -0.09594506 0.03055042 -0.01247197 -0.09562397 0.0627858 -0.01788491 -0.09307253 0.03116083 -0.01137804 -0.0940665 0.06480026 -0.01717102 -0.09325778 0.01391702 -0.01771134 -0.09048223 0.01480507 -0.01298511 -0.0994578 0.05770701 -0.01709634 -0.09175789 0.008280813 -0.01780426 -0.0880571 0.009735107 -0.01702117 -0.09012889 0.004008412 -0.0133897 -0.09355521 0.06479984 -0.01824826 -0.08454859 0.009548366 -0.01778542 -0.0889334 0.06070017 -0.01792943 -0.09211969 0.05757761 -0.0169571 -0.08845549 9.66452e-4 -0.0139051 -0.09571915 0.06199598 -0.01731973 -0.08840882 0.0629692 -0.01824975 -0.07915502 0.003960371 -0.01791191 -0.09458613 0.05424767 -0.01394724 -0.09495264 0.06292432 -0.0182501 -0.08402371 0.05801284 -0.01733583 -0.08140993 0.06283122 -0.01756638 -0.08019298 -4.51478e-4 -0.01697736 -0.08238166 -0.003934085 -0.01708048 -0.07817596 -0.004988372 -0.01824975 -0.0735929 0.0011819 -0.01386237 -0.09941178 0.05715268 -0.01692163 -0.08644747 -0.0014835 -0.01480656 -0.09313273 0.06443524 -0.0146678 -0.09550595 0.06171298 -0.0171507 -0.07364398 -0.005523383 -0.01533156 -0.09399074 0.06294918 -0.0182414 -0.09188705 0.05447721 -0.01495373 -0.09932076 0.05627477 -0.005028963 -0.09980821 0.05901831 -0.003704071 -0.09690874 0.06261616 -0.0182479 -0.0896359 0.05662399 -0.007416248 -0.09860134 0.06036168 -0.01588678 -0.09228217 0.06406396 -0.0087049 -0.09386259 0.06517255 -0.01645541 -0.09149169 0.0637471 -0.01785743 0.0760917 0.02556651 -0.02032935 0.08272057 0.02274179 -0.01917499 0.08650046 0.03726208 -0.01812297 0.07934641 0.02859729 -0.02211022 0.09042674 0.03104621 -0.01871496 0.08513331 0.03771275 -0.01826 0.08106082 0.04406738 -0.02072077 0.09240239 0.04328978 -0.01887381 0.08946073 0.05670869 -0.01932829 0.09226977 0.05330705 -0.019912 0.09287786 0.04919689 -0.0183652 0.08324337 0.05208474 -0.01827234 0.08228188 0.05795037 -0.01778447 0.07098555 0.03128939 -0.01772993 -0.06843483 0.0268085 -0.01774889 -0.07007747 0.02642405 -0.01773142 -0.07100701 0.02564436 -0.01781612 -0.0762946 0.03292757 -0.0177403 0.06959044 0.01304525 -0.01792448 0.07042783 0.01237481 -0.01771783 0.07222032 0.02149569 -0.0177415 0.07233536 0.02313441 -0.01771545 0.068578 0.0267769 -0.01857239 0.07278394 0.01231306 -0.01771301 0.06990343 0.02634787 -0.0192365 0.08104592 0.02463907 -0.01854521 0.08113002 0.02866518 -0.02123636 0.07894605 0.01174789 -0.01770973 0.07186877 0.02436202 -0.0177477 0.07134896 0.02541404 -0.02421218 0.08571201 0.01263672 -0.02334362 0.08791196 0.02022451 -0.01773983 -0.07193219 0.02441436 -0.02003532 0.07641583 0.01209843 -0.01824945 -0.02628624 9.94972e-4 -0.01725304 -0.02695399 -0.005264997 -0.02212774 0.07617062 0.007264137 -0.01789242 0.0269348 0.009540677 -0.02564173 0.07851934 0.003552198 -0.01841259 0.06721901 0.00915426 -0.01794481 0.06854307 0.01061469 -0.01775461 0.06726753 0.01095432 -0.01772058 0.06867736 0.01191878 -0.02596211 0.0760793 0.002159178 -0.02149051 0.07753175 0.009496092 -0.01966446 0.06746518 0.007466316 -0.02575343 0.0563125 0.002372682 -0.02272903 0.05958044 0.004726171 -0.01838475 0.02730405 0.001024127 -0.02490073 0.0829305 0.007684946 -0.01982951 0.06876266 0.007353723 -0.01889127 0.02856278 0.002166986 -0.02450364 0.08473956 0.01037549 -0.01985681 0.07064169 0.007851123 -0.02527326 0.0808801 0.005470693 -0.01942962 0.07219213 0.009357094 -0.01870036 0.06902253 0.009092986 -0.02001422 0.02999681 0.003858387 -0.02184522 0.07192295 0.005612254 -0.0207647 0.03073251 0.004001617 -0.01844459 0.07102495 0.01078271 -0.01971387 0.02990382 0.001285374 -0.02142792 0.03157699 0.004470229 -0.01780122 -0.07776343 0.0260486 -0.01774072 -0.07238233 0.02200543 -0.02105152 0.07302445 0.007121682 -0.02089798 0.03079861 0.002607226 -0.02205663 0.06956535 0.004966378 -0.0218814 0.03147017 0.003079295 -0.01778531 -0.06935691 0.009533822 -0.026425 0.07150012 0.001014769 -0.01967447 0.0744946 0.01077049 -0.02619856 0.07397419 0.001366317 -0.01791524 0.06647169 0.01015043 -0.01771545 -0.06585615 0.01072973 -0.0178557 -0.02655535 0.007797241 -0.017717 0.02181679 0.01073485 -0.01786464 0.02380549 0.007799804 -0.01806694 0.02641183 0.007799327 -0.01846611 0.02820539 0.007529318 -0.01802515 0.03160429 0.009856343 -0.0186094 0.02984207 0.008288681 -0.01772081 -0.07217788 0.02350479 -0.01872003 0.03256022 0.008717298 -0.01772058 0.0291875 0.01078933 -0.01850378 0.0277273 0.006415784 -0.01922029 0.0302186 0.007136464 -0.01812714 0.0358783 0.009753465 -0.01935786 0.02996009 0.006607592 -0.01926463 0.02931791 0.005723297 -0.01839613 0.02704596 0.004577577 -0.01878613 0.02812594 0.004526615 -0.01953727 0.03148323 0.007274866 -0.0177192 -0.0709868 0.01721084 -0.01865535 0.03663831 0.008950293 -0.01938754 0.02920562 0.004087448 -0.01966124 0.032655 0.007475674 -0.02056092 0.03167861 0.005917072 -0.0203197 0.03066635 0.005221307 -0.01943558 0.0346952 0.008025765 -0.02037376 0.03577584 0.007138192 -0.02091366 0.03268069 0.006049692 -0.01823639 0.02629685 9.45812e-4 -0.01867288 0.05988305 0.00883615 -0.02108323 0.03381973 0.006216585 -0.02214223 0.03311836 0.004917621 -0.02263033 0.03255778 0.003968894 -0.02343142 0.0342983 0.004197776 -0.0221641 0.03184098 0.003617525 -0.02360594 0.03264099 0.002813458 -0.01939404 0.04723829 0.008030593 -0.02365881 0.0354979 0.00427252 -0.02175712 0.03580552 0.005886912 -0.02064394 0.03080344 9.93824e-4 -0.0215494 0.03136479 9.5651e-4 -0.02470123 0.03379857 0.002830028 -0.02290761 0.03190577 0.001644492 -0.02535498 0.03298443 0.00100255 -0.02827692 0.03666877 9.96365e-4 -0.0277574 0.03533804 9.87156e-4 -0.02723366 0.03452372 9.91919e-4 -0.02650457 0.03375881 9.6469e-4 -0.0177505 -0.06960195 0.01277863 -0.01825147 -0.08987236 0.02800357 -0.02274304 0.03951668 0.005180716 -0.01771903 -0.06869029 0.01183742 -0.02094292 0.06274831 0.006255626 -0.01772117 -0.0671156 0.0109198 -0.02798712 0.04933762 9.99945e-4 -0.02425217 0.03745794 0.004032135 -0.02861595 0.03937429 9.97967e-4 -0.01806557 0.02880376 0.00940442 -0.0181955 0.02632331 0.005757808 -0.0177403 0.06602144 0.0107541 -0.02334398 0.03219801 0.00217694 -0.01969182 0.03009063 -0.004388213 -0.01873248 0.02900457 -0.004573345 -0.02835083 0.03714478 -0.004518747 -0.0281111 0.03623747 -0.004650533 -0.02765685 0.03518486 -0.004490256 -0.02856642 0.03869539 -0.004543066 -0.0272063 0.03448969 -0.004193961 -0.02062785 0.03086858 -0.004704952 -0.01790994 0.02767449 -0.005126774 -0.01759243 0.02672994 -0.004978299 -0.02165019 0.03145796 -0.00480926 -0.02680569 0.03409284 -0.00473237 -0.02594316 0.03334671 -0.004358232 -0.02460747 0.03260397 -0.004352152 -0.02857756 0.04071611 -0.004353344 -0.0144928 0.09549635 0.06215095 -0.01275247 0.09729647 0.06080383 -0.02159857 0.09347671 0.03178572 -0.02186501 0.09493488 0.02326601 -0.02299153 0.09281504 0.01331341 -0.02403748 0.0881769 0.009839057 -0.02365827 0.09104245 0.007481694 -0.02432781 0.08836299 0.003360271 -0.02511799 0.08348435 -0.001874268 -0.01745975 0.08553916 0.06286764 -0.0180087 0.08921474 0.06077188 -0.01735335 0.08836823 0.06330859 -0.01708579 0.09049236 0.06336343 -0.01024436 0.09407389 0.0649814 -0.01709741 0.09187358 0.06248897 -0.01769191 0.09160608 0.06114113 -0.01640081 0.09197062 0.06381958 -0.01646602 0.09367853 0.06212985 -0.01566225 0.09435176 0.06246185 -0.01541656 0.09266525 0.06443399 -0.01465344 0.09337788 0.06440985 -0.0113784 0.09566038 0.06317824 -0.01349532 0.09477913 0.06354916 -0.01369553 0.09364044 0.06467497 -0.01836138 0.09154033 0.05878388 -0.006538152 0.09799247 0.06123512 -0.01541125 0.09688031 0.05954456 -0.01661854 0.09594374 0.05920177 -0.01793837 0.09387618 0.05856812 -0.009525954 0.09813839 0.0606215 -0.006978929 0.09990084 0.05869418 -0.01084989 0.09979736 0.05807662 -0.01894474 0.092938 0.05522114 -0.02635508 0.07218623 -0.004719674 -0.01869237 0.09497624 0.05487537 -0.01435667 0.09964692 0.05661839 -0.0260064 0.07611167 -0.004228115 -0.01589971 0.09923243 0.0558235 -0.02550464 0.08057665 -0.003193676 -0.012959 0.09975892 0.05728316 -0.01727145 0.09405899 0.06018805 -0.01696348 0.09851688 0.05528253 -0.01735687 0.08128958 0.06274062 -0.01815778 0.09648334 0.05507731 -0.01865309 0.09752655 0.05203855 -0.01943701 0.0960164 0.04995155 -0.01761883 0.09818732 0.05439507 -0.01806944 0.09834259 0.05285596 -0.02004313 0.09488779 0.04691523 -0.01919049 0.09751802 0.04899847 -0.01966089 0.09719043 0.04589515 -0.0210728 0.09605967 0.03105217 -0.02063584 0.09185111 0.002674818 -0.02035927 0.09312283 0.005159318 -0.01972121 0.09506952 0.01071655 -0.02259999 0.0753104 -0.007855296 -0.02188146 0.08197921 -0.006174385 -0.02222675 0.07886165 -0.007216811 -0.02144169 0.08441877 -0.004971683 -0.02094697 0.0881344 -0.00215888 -0.02092295 0.09064459 8.06822e-4 -0.02163052 0.0865609 -0.003486454 -0.0210939 0.08937001 -8.3139e-4 -0.02019309 0.09406846 0.00757271 -0.0168839 0.09953457 0.03971993 -0.01337915 0.1003247 0.05524343 -0.01772028 0.09880161 0.03267461 -0.01612508 0.1000757 0.04775166 -0.01848602 0.09779846 0.02521109 -0.008262813 0.1004368 0.05679821 -0.01918029 0.09657603 0.01802015 -0.01730364 0.0433129 0.06294345 -0.01391977 -0.09338915 0.004576802 -0.004000306 -0.09407943 0.005886793 -0.01383769 -0.08758366 -0.004167318 -0.004000306 -0.08794635 -0.004130721 -0.004000306 -0.08644878 -0.005256295 -0.013628 -0.07976871 -0.007645428 -0.004000306 -0.08113706 -0.007529914 -0.0140286 -0.09442085 0.007765829 -0.004000306 -0.09553843 0.01137673 -0.01352363 -0.08885848 -0.003047168 -0.004000306 -0.08937203 -0.002773225 -0.004000306 -0.08414578 -0.006506204 -0.0138278 -0.08998435 -0.001728296 -0.01375025 -0.08395832 -0.00635457 -0.004000306 -0.09089785 -8.4768e-4 -0.01397037 -0.07645881 -0.008056342 -0.004000306 -0.07734733 -0.00818777 -0.01374655 -0.0912975 1.17136e-4 -0.01360583 -0.0857535 -0.005475759 -0.004000246 -0.09254413 0.001986086 -0.01359117 -0.09256219 0.002498209 -0.00396043 -0.100551 -0.01259547 -0.003996491 -0.09862416 -0.01355731 -0.0039891 -0.1019378 -0.008611142 -0.003995895 -0.1018359 0.007622063 -0.003965914 -0.1006268 0.0115987 -0.01730519 0.02691859 0.007834672 -0.01730448 0.02691823 -0.00603348 -0.01693195 0.02717745 -0.006693661 -0.01658165 0.02736848 0.008097469 -0.01613301 0.02763557 0.008442938 -0.01571041 0.02787822 0.008903443 -0.01406306 0.02874881 -0.006753444 -0.01097625 0.03007668 -0.006806433 -0.007792294 0.03104156 -0.006869137 -0.002095818 0.03194195 -0.006974041 -0.01737529 0.01386004 0.007827043 -0.01509308 -0.02822136 -0.006732821 -0.01620465 -0.0275942 0.00831604 -0.001919448 -0.03195518 -0.006979823 -0.01653671 -0.02739608 -0.00670886 -0.01689577 -0.02717638 -0.006172418 -0.01720732 -0.02698153 0.007858633 -0.004953086 -0.03162568 -0.006921291 -0.008110463 -0.03097021 -0.006862223 -0.01068258 -0.03016918 -0.006816089 -0.01284831 -0.02931982 -0.006778836 -0.01550889 -0.02798962 0.009178698 -0.0195418 0.03197079 -0.007339179 -0.01965516 0.03298032 -0.007791817 -0.01963526 0.03018623 -0.00521028 -0.0186029 0.02908128 -0.00537312 -0.01886272 0.03069287 -0.006884217 -0.01972895 0.0307343 -0.006037831 -0.01768273 0.03154402 -0.007909119 -0.01804322 0.03064543 -0.007393181 -0.01841217 0.02931785 -0.006098926 -0.01790899 0.02950787 -0.006853282 -0.01765668 0.02802896 -0.006104648 -0.0171945 0.02840209 -0.006946027 -0.0163781 0.03021115 -0.007998883 -0.02839928 0.03962457 -0.00544399 -0.01686894 0.02971839 -0.007681131 -0.01642018 0.02854341 -0.007632434 -0.02759778 0.03959286 -0.006766915 -0.02731513 0.03486478 -0.005115211 -0.02762407 0.03573542 -0.005613446 -0.02631807 0.03377467 -0.005123853 -0.02815294 0.03720879 -0.005432069 -0.02663391 0.03436774 -0.005726516 -0.02680438 0.03484261 -0.00612843 -0.02704781 0.03600966 -0.006648063 -0.02777248 0.03716588 -0.00621742 -0.02656579 0.03985047 -0.007583558 -0.02580296 0.03377348 -0.005909681 -0.02746719 0.03785765 -0.006737709 -0.02500844 0.03294843 -0.005195021 -0.02563184 0.03839391 -0.007939338 -0.02458435 0.03310203 -0.005979299 -0.02479165 0.03372406 -0.006682336 -0.02696537 0.03734266 -0.007151663 -0.02580666 0.03442209 -0.006684184 -0.02642631 0.03659904 -0.007384896 -0.0260964 0.03739589 -0.007698953 -0.02625763 0.03540915 -0.007074296 -0.02549302 0.03620797 -0.007763445 -0.02513086 0.03499871 -0.007462561 -0.02400469 0.03420168 -0.007388472 -0.02159279 0.03173613 -0.005717337 -0.02411347 0.03537482 -0.007891893 -0.02131634 0.0319916 -0.00636667 -0.02119076 0.0327202 -0.00717777 -0.02059346 0.0311079 -0.005544245 -0.01531285 0.02976495 -0.00819683 -0.02796757 0.04108822 -0.006179094 -0.01998221 0.03157371 -0.006802499 -0.02603971 0.07215046 -0.005738556 -0.02549082 0.07241696 -0.006608009 -0.02475482 0.07251006 -0.007301986 -0.02405291 0.07239085 -0.007722735 -0.02338039 0.07293188 -0.007950425 -0.02586454 0.07494807 -0.005416274 -0.02541518 0.07517117 -0.006174743 -0.0224809 0.09368479 0.01141023 -0.02277725 0.09329557 0.01196503 -0.0218873 0.0942226 0.01111209 -0.02255088 0.09223526 0.005462467 -0.02243566 0.09147268 0.003393948 -0.02315843 0.09022623 0.002144217 -0.02133291 0.09235417 0.003963887 -0.02109259 0.09487408 0.01171529 -0.02255219 0.09015947 0.001122832 -0.02328389 0.08847135 -4.18373e-4 -0.02176403 0.09108489 0.001904964 -0.02189761 0.09000521 3.16573e-4 -0.02314645 0.08741676 -0.001852512 -0.02400445 0.08654731 -0.001642346 -0.02068847 0.09456551 0.009591698 -0.02350747 0.08530092 -0.003496587 -0.02312469 0.09214586 0.007413566 -0.02007305 0.09565222 0.01381278 -0.02433764 0.08399498 -0.003522217 -0.02369409 0.08299136 -0.004932463 -0.0246405 0.08106517 -0.005014479 -0.02216893 0.09344315 0.008467197 -0.02371168 0.08099937 -0.005937755 -0.02438145 0.07891488 -0.006216347 -0.02505761 0.07865035 -0.005567014 -0.02467328 0.0766198 -0.006679952 -0.02351367 0.07814842 -0.007057845 -0.02153444 0.09387749 0.008433938 -0.02493667 0.07486206 -0.006810486 -0.0231387 0.09151887 0.005215883 -0.02188003 0.09267038 0.005340278 -0.02361482 0.08969438 0.002285957 -0.02397215 0.0895701 0.00362432 -0.02109295 0.09349417 0.006587684 -0.02259302 0.08666884 -0.003021419 -0.02415245 0.0749523 -0.007376611 -0.02396637 0.08834439 7.67516e-4 -0.02453917 0.08682519 4.78764e-4 -0.02283072 0.08467364 -0.004417598 -0.02242356 0.08326458 -0.005456984 -0.02288842 0.08102083 -0.006334245 -0.0232464 0.07647395 -0.007526755 -0.02453583 0.08579176 -0.001308858 -0.0223003 0.08890777 -8.76855e-4 -0.02205628 0.08533 -0.004282772 -0.02159595 0.09499824 0.01486802 -0.02173113 0.08788686 -0.002253055 -0.02498739 0.08216941 -0.00370419 -0.02554512 0.07820188 -0.004844546 -0.01963663 0.09717452 0.0229175 -0.02058821 0.09696173 0.03028047 -0.02059394 0.09658706 0.02244108 -0.02001577 0.09754824 0.03028059 -0.01908481 0.09815973 0.03020846 -0.01570564 0.1001212 0.05293631 -0.01917874 0.09846764 0.03785353 -0.01839143 0.0988993 0.03620785 -0.02024459 0.09719789 0.03687477 -0.01081275 0.1002487 0.05711257 -0.0194801 0.09812742 0.04026007 -0.02079242 0.09573632 0.01629662 -0.01773518 0.09944945 0.04233723 -0.02166253 0.095344 0.02069306 -0.01817673 0.09923118 0.0453754 -0.01846194 0.09890687 0.04776012 -0.01920193 0.09804558 0.04670304 -0.01717591 0.09976452 0.04760015 -0.01641726 0.09958606 0.05406814 -0.01516723 0.09998124 0.0549544 -0.01384425 0.1000375 0.05609238 -0.0171352 0.09966903 0.05082076 -0.005185425 0.1003054 0.05800729 -0.01863676 0.09828937 0.05021017 -0.01753842 0.09918975 0.05206251 -0.01696991 0.09909987 0.05424112 -0.01471227 0.08861172 0.06562489 -0.01505506 0.09158569 0.06520736 -0.01265275 0.09369188 0.06495857 -0.01405543 0.0924921 0.06529772 -0.01033169 0.09321093 0.06550502 -0.0128737 0.09240245 0.06558048 -0.01698964 0.08179032 0.06371951 -0.01638817 0.08193767 0.06451648 -0.01591134 0.08146893 0.06495219 -0.01505017 0.08267289 0.06545823 -0.01656985 0.09088844 0.06409984 -0.01692467 0.0857771 0.06404495 -0.01589435 0.0905053 0.06490945 -0.01566338 0.08772248 0.06523299 -0.01532536 0.08994162 0.06532716 -0.01639926 0.08798825 0.06469219 -0.0158714 0.09167486 0.06457889 -0.01466888 0.09270209 0.06490921 -0.01686424 0.08931946 0.06406539 -0.01631027 -0.03330969 0.06460201 -0.01458257 -0.03430306 0.06561368 -0.01538306 -0.02485287 0.06529045 -0.016963 -0.01812714 0.0637573 -0.01731508 -0.0200001 0.06288248 -0.01458144 0.06712359 0.06562495 -0.01533079 0.06196159 0.06531643 -0.01585274 -0.03474557 0.06499207 -0.01714766 0.05355525 0.06336748 -0.01512825 -0.06701487 0.06541526 -0.01680219 0.04728096 0.06399744 -0.01618486 0.04449743 0.06471604 -0.01651024 -0.06627243 0.06437349 -0.01550287 0.04270136 0.06521964 -0.01685917 -0.075567 0.06390714 -0.01467519 0.04364603 0.06558966 -0.01586258 -0.06804811 0.06498634 -0.01716297 -0.08074456 0.06331968 -0.01465499 0.01112139 0.06560593 -0.01666086 0.02990281 0.0641992 -0.01710283 0.03484451 0.06346976 -0.01532387 -0.08206605 0.06533408 -0.01625269 -0.08013206 0.06465351 -0.01612544 -0.08367443 0.06480574 -0.01670312 -0.08151417 0.06415039 -0.01554948 0.01232856 0.0651921 -0.01629728 -0.00743705 0.06460422 -0.0149551 -0.003743231 0.06548756 -0.01578903 -0.007540047 0.06504142 -0.0149492 -0.08867633 0.065503 -0.01676559 -0.08659207 0.06417524 -0.0173183 -0.08612412 0.06321275 -0.01575332 -0.08683866 0.06512302 -0.01623821 -0.08888655 0.06466281 -0.01690804 -0.08883953 0.06382048 -0.01558005 -0.09015786 0.06506723 -0.01380324 -0.09166884 0.06558007 -0.01485037 -0.09138506 0.06527715 -0.0124315 -0.09283959 0.06544744 -0.01421791 -0.09237033 0.0652188 -0.01575338 -0.09148055 0.06460362 -0.01508086 -0.09226918 0.06480371 -0.009714245 -0.09273362 0.06567174 -0.01642924 -0.09007793 0.06428569 -0.01664972 -0.07374691 -0.006529033 -0.01600009 -0.02838844 -0.007301092 -0.01578384 -0.07313781 -0.007447898 -0.01490014 -0.02972543 -0.0079813 -0.01493877 -0.07361114 -0.007924079 -0.01379168 -0.03052783 -0.008250653 -0.01497548 -0.07693153 -0.007665276 -0.01514303 -0.07947468 -0.007218599 -0.01665377 -0.08196669 -0.004944026 -0.016707 -0.07840979 -0.005825519 -0.0160101 -0.08008617 -0.00643599 -0.01576501 -0.07676792 -0.00719887 -0.01636767 -0.07686084 -0.006570875 -0.01495969 -0.09428876 0.008395612 -0.0153858 -0.0953083 0.01359111 -0.01539635 -0.09435242 0.009603738 -0.01631981 -0.09446614 0.01311218 -0.01672023 -0.0927304 0.008604466 -0.01593804 -0.09345191 0.007821619 -0.01453095 -0.09241276 0.002682328 -0.01485455 -0.09150606 0.001196265 -0.01475661 -0.09051245 -5.26103e-4 -0.01459527 -0.08903914 -0.002481341 -0.01518654 -0.0880025 -0.003079593 -0.01498961 -0.08713907 -0.00398463 -0.01490265 -0.09322619 0.005085051 -0.01453131 -0.08592408 -0.005084574 -0.01532608 -0.0851612 -0.005048096 -0.01569056 -0.09281122 0.005316615 -0.01487469 -0.08400964 -0.005960464 -0.01583677 -0.08595138 -0.004083454 -0.01632022 -0.08510792 -0.004008114 -0.01391458 -0.08247613 -0.006904244 -0.01576668 -0.08319312 -0.005659699 -0.01606762 -0.09196281 0.004086613 -0.01668792 -0.08475911 -0.003446221 -0.01489174 -0.08163189 -0.006810009 -0.0142349 -0.07974433 -0.007534623 -0.01662677 -0.09114253 0.004035711 -0.0152474 -0.09226191 0.00320065 -0.01499563 -0.096385 0.01807034 -0.01561892 -0.09069097 7.24274e-4 -0.01613694 -0.09031963 0.00107342 -0.01626902 -0.08234423 -0.005387485 -0.01659858 -0.0892865 6.88927e-4 -0.01554173 -0.08895123 -0.001738846 -0.01611471 -0.08858692 -0.001338005 -0.01660913 -0.08713096 -0.001833379 -0.01602774 -0.08746844 -0.002630591 -0.01656645 -0.0958119 0.02144742 -0.01698637 -0.0951581 0.02162808 -0.01538282 -0.09762263 0.02645736 -0.01590484 -0.09639948 0.0211156 -0.01686412 -0.09700971 0.03179198 -0.01471602 -0.09760856 0.02458173 -0.01616895 -0.09758764 0.03019654 -0.01472645 -0.09857308 0.03138488 -0.01674443 -0.09872752 0.05221796 -0.01639539 -0.09815913 0.03639066 -0.01451653 -0.1001753 0.05252099 -0.01529031 -0.09878087 0.03501069 -0.017039 -0.09789073 0.04178822 -0.015621 -0.09923893 0.04171693 -0.01631283 -0.09898674 0.0446071 -0.01452738 -0.09970319 0.04198527 -0.01712846 -0.09832078 0.04919201 -0.01596599 -0.09946501 0.05287086 -0.0152778 -0.09991806 0.05254602 -0.01626133 -0.09903174 0.05392843 -0.01534664 -0.09957122 0.05494046 -0.0157693 -0.09962052 0.04860121 -0.01471275 -0.1000501 0.04882246 -0.01662176 -0.09895336 0.05023396 -0.01254308 -0.1000522 0.05682724 -0.01298117 -0.1002654 0.05575144 -0.005778968 -0.1002416 0.05816143 -0.01448673 -0.1000347 0.05489182 -0.01394444 -0.09985607 0.05620849 -0.01525533 -0.07075327 0.01761788 -0.01736629 -0.07195091 0.0217306 -0.001533508 -0.09950399 0.01012212 -0.001025557 -0.09884017 0.01056593 -5.92975e-4 -0.09863483 0.01050209 -0.001832902 -0.1003246 0.009448587 -0.001974523 -0.09983032 0.01168042 -5.17552e-4 -0.09765166 0.01208102 -0.001766562 -0.09878975 0.01216787 -0.001187205 -0.09792006 0.01224917 0 -0.1000301 0.005374789 -0.001035809 -0.1002627 0.005401015 -0.001462638 -0.1005636 0.006095826 0 -0.09625643 0.01372545 0 -0.09983032 0.006888747 -0.001776695 -0.1010034 0.006177127 -0.001020014 -0.1000542 0.006943464 -9.99035e-4 -0.09638971 0.01389873 -0.001452565 -0.09654808 0.01425457 -5.23344e-4 -0.09938168 0.008710205 -0.001776695 -0.09684705 0.0146051 -0.001020133 -0.09967523 0.00847727 -0.001451373 -0.1000428 0.008424937 -0.01603966 0.006741762 0.0084728 -0.01609271 0.01873928 0.008420407 -0.0153107 0.02153575 0.009673953 -0.01662915 0.01279479 0.008060336 -0.01563102 0.008166491 0.008982539 -0.01684528 -0.003383696 0.007970631 -0.01614218 -0.00299412 0.008378505 -0.01545441 -0.01094591 0.009310483 -0.01633054 -0.01369261 0.008235692 -0.01579999 -0.01718688 0.008743107 -0.01698243 -0.01318687 0.007912099 -0.01626271 -0.02141422 0.008293986 -0.01701664 -0.02209883 0.00789535 -0.005159437 0.03159219 -0.006914615 -0.01742732 -0.06941688 0.01333642 -0.0174365 -0.06881499 0.01238256 -0.01738965 -0.06804949 0.01169764 -0.01744282 -0.06699973 0.01116895 -0.01744437 -0.06594496 0.01097697 -0.0174641 -0.0707519 0.01735627 -0.01738774 -0.07152187 0.0244373 -0.01745486 -0.0719093 0.02348417 -0.01734173 -0.07199579 0.02276599 -0.01740998 -0.06840187 0.02650886 -0.01739174 -0.0693944 0.02626132 -0.01747173 -0.07031303 0.02581375 -0.01743316 -0.07097434 0.02520775 -0.01745283 0.07020527 0.02588206 -0.01748377 0.06900221 0.02641522 -0.01747649 0.07195955 0.02169626 -0.01746118 0.07203048 0.02248591 -0.01748692 0.07198733 0.02311468 -0.01745164 0.07170301 0.02413064 -0.017457 0.07102608 0.0251646 -0.01749169 0.0693317 0.01310926 -0.01749461 0.07079732 0.01747363 -0.01754784 0.06598627 0.01096141 -0.01751565 0.06671994 0.01108694 -0.01752322 0.06751316 0.01136583 -0.01744717 0.06845861 0.01201063 -0.01753789 0.04110854 0.01096284 -0.017443 0.0231027 0.01097673 -0.01760327 -0.05609285 0.02661913 -0.0175144 0.06807923 0.02655339 -0.01762413 -0.06951296 0.02638369 -0.01767903 0.07083916 0.02565068 -0.01759839 -0.07153367 0.0247004 -0.01756387 -0.072106 0.02209818 -0.01767337 -0.06961202 0.01328706 -0.01762712 -0.06796371 0.01147615 -0.0176115 -0.01517248 0.01086509 -0.003214836 -0.09815907 -0.01448351 -0.003617227 -0.09879928 -0.01423656 -0.003900349 -0.09870046 -0.01389956 -0.003238976 -0.1014407 -0.01234865 -0.003275692 -0.1001176 -0.01381379 -0.00388211 -0.09989446 -0.013381 -0.003621459 -0.1009687 -0.01281023 -0.003143072 -0.1009114 -0.01315134 -0.003636479 -0.09998744 -0.01367527 -0.003143787 -0.09918689 -0.01429826 -0.003884196 -0.1014927 -0.01094985 -0.003306925 -0.1025606 -0.009138345 -0.00320065 -0.1029571 -0.00673151 -0.003933787 -0.1023779 -0.006398856 -0.003597438 -0.1027849 -0.006709754 -0.003797352 -0.1022346 -0.009019017 -0.0031268 -0.1019954 -0.01107579 -0.003540754 -0.1019176 -0.01090216 -0.003161132 -0.1029484 0.005852937 -0.003511488 -0.1028565 0.005402863 -0.003209114 -0.1015919 0.01104986 -0.003589451 -0.1011623 0.01161509 -0.003132641 -0.1007271 0.01272881 -0.003884673 -0.09967941 0.01338398 -0.003291547 -0.09772789 0.01634824 -0.003581166 -0.09997171 0.0135954 -0.00313282 -0.1022199 0.009431004 -0.003539979 -0.1021322 0.009283125 -0.003767073 -0.09815424 0.01552397 -0.003884971 -0.1018062 0.009046554 -0.003133237 -0.1026273 0.007927179 -0.003252506 -0.09930837 0.01473021 -0.003602862 -0.1026018 0.007202208 -0.003960192 -0.09809124 0.01507306 -0.003898561 -0.1024042 0.006148159 -0.001409173 -0.0969783 -0.01446241 -0.002282798 -0.09864825 -0.01443433 -0.002213478 -0.09724247 0.01674741 -0.002250254 -0.09901076 0.01505315 -0.002225339 -0.1008701 0.01246374 -0.002260625 -0.1020631 0.009856343 -0.002211153 -0.1025637 0.008052587 -0.002240896 -0.1028411 0.0065189 -0.002202332 -0.1029523 0.00494641 -0.002248108 -0.1029615 -0.006088137 -0.002312481 -0.1028251 -0.007832586 -0.0022946 -0.1022176 -0.01045769 -0.002231538 -0.1012285 -0.01269668 -0.002250075 -0.100399 -0.01360625 -0.002197444 -0.09962171 -0.01407009 -0.002043545 -0.1027451 -0.006976664 -0.002110064 -0.1025344 -0.008810997 -0.002024829 -0.101992 -0.01044631 0 -0.0958119 0.0140556 -0.002162456 -0.09663522 0.01718097 0 -0.09704548 0.019432 -0.001996159 -0.09631699 0.01582747 -0.001991093 -0.09637141 0.01606673 -0.001991689 -0.0964697 0.01649612 -0.002002358 -0.09655088 0.01684886 -0.002127408 -0.09661984 0.01712179 0 -0.09707444 0.01959288 0 -0.09703934 0.01943969 0 -0.09583681 0.01407915 -6.26781e-4 -0.09585702 0.0141291 0 -0.09580558 0.01404684 -9.80615e-4 -0.09591048 0.01428204 -0.001439452 -0.09602177 0.01466393 -0.001133084 -0.09594517 0.01439899 -0.001774132 -0.09614801 0.01513957 -0.001859962 -0.09621816 0.01542663 -0.001551747 -0.09606069 0.01480835 0.01055824 0.06674385 0.05841577 0 0.06699997 0.05859994 0.01023006 -0.06681382 0.05842137 0 -0.06699997 0.05859994 -0.01023006 -0.06681382 0.05842137 0.01128304 0.06613343 0.05840301 0.01149034 0.06554079 0.05839937 0.01083076 -0.06659364 0.05841088 -0.01055824 0.06674385 0.05841577 -0.01128304 0.06613343 0.05840301 -0.01149034 0.06554079 0.05839937 -0.01083076 -0.06659364 0.05841088 -0.01145249 -0.06581288 0.05840015 0.01145249 -0.06581288 0.05840015 -0.00121957 -0.03436344 0.001007497 0.008393943 -0.03436237 0.001004397 0.01256412 -0.03580063 0.00100696 0.01083761 -0.0339505 0.00100249 0.009583592 -0.03421688 0.001003623 -0.002814948 -0.03436338 0.001007437 -0.006783187 -0.03436303 0.001006364 -0.008395314 -0.03436291 0.001005887 -0.009584665 -0.03421747 0.001005172 -0.01083827 -0.03395122 0.001004159 -0.01264971 -0.0358923 0.001008808 -0.01259809 -0.03599995 0.00575 -0.01259964 -0.03595864 0.005630373 -0.01260024 -0.03594356 0.005586683 -0.01260101 -0.03591597 0.005526542 -0.01260304 -0.03584235 0.005384922 0.01259064 -0.03599995 0.00575 0.01258999 -0.03593969 0.005575478 0.0125671 -0.03580385 0.001397073 0.01258927 -0.03582757 0.005350768 0.01258975 -0.03590965 0.005509614 0.01524066 0.02799975 0.007224917 0.01530236 0.027933 0.008590221 0.01518046 0.02784478 0.009001314 0.01516103 0.02771604 0.009308993 -0.001834511 0.0319367 9.99985e-4 -0.004390239 0.03171497 9.99891e-4 -0.004458308 0.03170716 9.99888e-4 -0.005600452 0.03147375 9.99821e-4 -0.008357644 0.03091019 9.99876e-4 -0.008438527 0.03089326 9.99878e-4 -0.00853765 0.03086322 9.99879e-4 -0.01218509 0.02960032 9.99947e-4 -0.01230597 0.02954828 9.99949e-4 -0.01239156 0.02950865 9.99951e-4 0.009391963 0.0305674 9.99866e-4 0.01186645 0.02971065 9.99932e-4 -0.01306217 0.02919769 9.99966e-4 0.008438527 0.03089326 9.99897e-4 0.00853765 0.03086322 9.99894e-4 0.004459321 0.03169798 9.99985e-4 0.008357644 0.03091019 9.99899e-4 0.01218515 0.02960032 0.001000046 0.01230597 0.02954834 0.001000106 0.01518654 0.02815991 0.001002192 0.01493489 0.0283035 0.001001954 0.01445698 0.02855104 0.001001536 -0.01526182 0.02812409 0.001000344 -0.01519101 0.02816319 0.001000344 -0.01493471 0.02830362 0.001000285 -0.01445692 0.02855104 0.001000165 -0.015208 0.02815705 0.002053856 -0.01543217 0.02780288 0.007561981 -0.01544368 0.02778404 0.007961034 0.01520138 0.02806812 0.005650043 0.01514273 0.02819281 0.001950919 -0.0154522 0.02777373 0.008179783 -0.01516085 0.02772217 0.009273529 -0.01515603 0.02772003 0.009319663 0.002006292 0.0319218 0.001001179 0.002255916 0.03190016 0.00100106 0.001416862 0.03197294 0.001001119 0.001384198 0.03197383 0.001001119 -0.001286625 0.03202176 0.001000344 -0.001384079 0.03197383 0.001000225 0.001286745 0.03202176 0.001001179 0.00129956 0.03202199 0.001001238 -0.001416981 0.03197294 0.001000165 -0.00129956 0.03202199 0.001000344 0.004390299 0.03171497 0.001000046
+
+
+
+
+
+
+
+
+
+ -1 -1.59635e-6 0 -1 0 0 -0.01745879 5.66838e-6 0.9998477 -0.03099942 -0.003267168 0.9995142 -0.03130018 -0.003394365 0.9995043 -0.01746416 0 0.9998475 0.0174759 0.9998474 -1.47093e-5 0.01747453 0.9998474 0 0.01750534 0.9998469 2.12921e-5 -0.8034423 -0.5025768 -0.3192133 -0.6049628 -0.7711433 -0.1983887 -0.1815953 -0.9678186 -0.1742135 -0.64287 -0.7447756 -0.1789625 -0.6709684 -0.6392459 -0.3757208 -0.4038424 -0.8927095 -0.1999528 -0.4035468 -0.8928104 -0.2000992 -0.2107745 -0.827871 -0.5198112 -0.238038 -0.8297455 -0.504837 -0.8557923 -0.4151744 -0.3086255 -0.08978402 -0.9711232 -0.2210401 -0.551455 -0.6708173 -0.4958847 -0.1951844 -0.9357898 -0.2935994 -0.7332546 -0.6714453 -0.107233 -0.9514362 -0.302337 -0.05798053 -0.9748796 -0.2054636 -0.08599156 -0.7305006 -0.6396387 -0.2392305 -0.5852484 -0.6383147 -0.5000388 -0.8037986 -0.5576477 -0.2072126 -0.7955585 -0.6014449 -0.07314944 -0.1929651 -0.7079515 -0.6793888 -0.2218383 -0.9036865 -0.3662495 -0.4063439 -0.8812811 -0.2413056 -0.5372357 0.7747809 0.3333054 -0.1404054 -0.7490733 -0.6474376 -0.2898097 -0.9415841 -0.1715513 -0.02813524 -0.9337258 -0.3568818 -0.9987557 -0.042279 -0.02644765 -0.933096 -0.3491821 -0.0860458 -0.8658127 -0.4547505 -0.2087355 -0.6308395 -0.7727088 -0.07044732 -0.9426785 -0.2912657 -0.1628544 -0.06031548 -0.9932758 -0.09881925 -0.9729693 -0.1940726 -0.1251666 -0.5862614 -0.7234748 -0.3645298 -0.3635715 -0.9231372 -0.1250337 -0.9259803 -0.3665481 -0.09057033 -0.876915 -0.4643304 -0.1241677 -0.9990906 -0.04152858 0.009671509 -0.9984933 -0.05486011 -0.001229941 -0.9993687 -0.03552681 2.60892e-4 -0.9983755 -0.05655789 -0.006890535 -0.999947 0.009648978 0.003597617 -0.9989839 -0.03777807 0.02457976 -0.999618 -0.02527379 -0.01119279 -0.9994125 -0.0267201 0.02146446 -0.9945605 -0.09076625 -0.05109775 -0.9995116 -0.029621 0.009957849 -0.1673778 -0.7671129 -0.6192919 -0.0996887 -0.8503339 -0.5167151 -0.9995042 0.001211762 0.03146576 -0.9983105 0 -0.05810439 -0.998438 0.001815259 -0.05584293 -0.9896948 0.0107997 -0.142785 -0.9987263 1.66942e-4 -0.0504558 -0.9991551 -1.01301e-4 -0.04110199 -0.999939 1.50395e-6 -0.01105445 -0.9873094 -6.79567e-4 0.158807 -0.9871919 0 0.1595373 -0.8266589 0.002745985 0.5626965 -0.8164678 7.27504e-4 0.5773904 -0.5227415 -0.005411803 0.852474 -0.4111508 0.007200181 0.911539 0.01746851 -7.98497e-4 0.9998471 0.01811242 -6.86813e-4 0.9998358 0.01772737 -7.47488e-4 0.9998427 0.01886183 0.00223267 0.9998196 0.01727402 -9.33403e-4 0.9998504 0.0174098 -0.00132209 0.9998477 0.01772445 7.8853e-4 0.9998427 0.0188052 0.00221771 0.9998207 0.01746636 0.001036703 0.999847 0.01829272 8.41666e-4 0.9998324 0.04179811 -0.01165378 0.9990581 0.01947081 6.42348e-4 0.9998102 0.02357411 -3.30777e-6 0.9997222 0.02344107 -8.45551e-7 0.9997253 0.01746749 0 -0.9998475 0.01742506 -3.92388e-6 -0.9998482 0.01719295 3.08708e-5 -0.9998522 0.02121454 -0.001945197 -0.9997732 0.0174604 1.51403e-6 -0.9998476 0.02163219 -3.98991e-4 -0.999766 0.01984745 -0.001024484 -0.9998025 0.01750886 5.89638e-4 -0.9998466 0.01768302 9.94426e-4 -0.9998432 0.01742815 3.93877e-4 -0.9998481 0.01775342 6.19029e-4 -0.9998423 0.01923215 0.0123291 -0.9997391 0.0192992 0.004440188 -0.9998039 0.02233517 -0.01634991 -0.9996169 0.02470326 -0.01147276 -0.9996291 0.01989352 -0.9996085 -0.01967996 0.01934939 -0.9927514 -0.1186183 0.01703572 -0.9922142 -0.1233735 0.01943552 -0.9997998 -0.004760861 0.0143091 -0.9989149 -0.04432153 0.02297264 -0.9976972 -0.06381577 0.01578736 -0.9869378 -0.160327 0.01847827 -0.9815429 -0.1903477 0.01872217 -0.9880514 -0.1529839 0.01561284 -0.9871308 -0.1591508 0.01939213 -0.9822306 -0.1866738 0.02138304 -0.99661 -0.07944422 0.01705265 -0.9800229 -0.1981528 0.01696908 -0.9960284 -0.08740454 0.01979613 -0.9837764 -0.1783035 0.01728594 -0.9998506 -2.47442e-5 0.02095782 -0.9832437 -0.1810876 0.1253872 -0.7822577 0.6102057 0.6999183 -0.5677042 0.4333895 0.7951682 -0.5054311 0.3350331 0.839672 -0.4302388 0.3314297 0.7981774 -0.485123 0.3571672 0.8386119 -0.4320478 0.3317605 0.8176898 -0.4904768 0.3013572 0.8842906 -0.4020133 0.2375196 0.9291118 -0.2758641 0.2462726 0.9019222 -0.3582373 0.2412517 0.9130543 -0.3746582 0.1611305 0.9248166 -0.2887638 0.2476485 0.9485685 -0.1695718 0.2673265 0.9922474 -0.1229003 -0.0184623 0.9844369 -0.1738134 -0.02594 0.99068 -0.1345283 -0.0213353 0.9813615 -0.1896609 -0.03095823 0.950689 -0.2195699 0.2190429 0.9814023 -0.1877177 -0.040147 0.9830605 -0.1794582 -0.03723984 0.9886385 -0.1464882 -0.03369456 0.9920595 -0.1207952 -0.03502172 0.9844915 -0.1658528 -0.0571798 0.9832056 -0.1709966 -0.06377297 0.9571346 -0.1266056 0.2605083 0.2021561 -0.7932954 0.5742956 0.1572667 -0.7381256 0.6560776 0.9908866 -0.1276811 -0.04291266 0.984417 -0.1578545 -0.0774939 0.160057 -0.7429847 0.6498889 0.930772 -0.2973116 0.2127665 0.9705829 -0.09509754 0.2211913 0.9876324 -0.1296753 -0.08812803 0.9703214 -0.1653879 0.176418 0.9877303 -0.1271015 -0.09074258 0.2785251 -0.7530081 0.5961565 0.9893272 -0.104973 -0.1010568 0.9627891 -0.2340731 0.1350815 0.9633386 -0.2429561 0.1138032 0.1847709 -0.7264155 0.6619519 0.9868744 -0.09516292 -0.1304725 0.2781606 -0.7528234 0.5965598 0.273667 -0.7305225 0.6256543 0.9896749 -0.05719709 -0.131423 0.9882733 -0.06824409 -0.1365973 0.9881712 -0.06946885 -0.1367176 0.2827916 -0.7458986 0.6030458 0.9923574 -0.08801817 -0.08648473 0.9898642 -0.083018 -0.1152263 0.9862914 -0.03434658 -0.1613993 0.9814007 -0.1099103 0.1573928 0.3663855 -0.6906484 0.6235114 0.4103875 -0.7122417 0.569468 0.9765391 -0.2077852 0.05654132 0.9824274 -0.1654213 0.08644151 0.9856721 -0.1295133 0.1080605 0.9736756 -0.2248843 0.03718644 0.4577629 -0.6737805 0.5800629 0.4659113 -0.6849941 0.560098 0.9783759 -0.02132129 0.205733 0.9889522 -0.0785306 0.1257238 0.01830273 -0.6957897 0.7180124 0.01616126 -0.8195578 0.5727687 0.03322637 -0.7492409 0.6614636 0.5463141 -0.6600257 0.5156618 0.6265675 -0.5583565 0.5437383 0.6730809 -0.5764623 0.4633069 0.04404544 -0.780815 0.6232079 0.01575714 -0.6101837 0.7921034 0.9913349 -0.1245634 0.04170143 0.04566699 -0.7613181 0.6467685 0.0342608 -0.6748678 0.737143 0.9914663 -0.09145587 0.09290027 0.8119737 -0.4086565 0.4167715 0.9770201 -0.212668 -0.01428622 0.8087608 -0.4145488 0.4171994 0.9949041 -0.09867942 0.02069348 0.04845446 -0.6875662 0.7245032 0.06748998 -0.7413517 0.6677147 0.992611 -0.02952563 0.1176931 0.9834827 0.01097685 0.1806693 0.0840938 -0.8109217 0.5790805 0.9840058 0.004345715 0.1780837 0.8835192 -0.3057748 0.3548176 0.4076388 -0.7298558 0.5487633 0.1211359 -0.7664896 0.6307298 0.8932934 -0.2749823 0.3555445 0.3909217 -0.7320415 0.5579387 0.06763797 -0.716306 0.6945005 0.4781578 -0.706994 0.5210801 0.5233851 -0.6844011 0.5076054 0.6237269 -0.6364217 0.4537976 0.6911811 -0.5388131 0.4816111 0.6569291 -0.6286823 0.4161762 0.9966731 -0.08150392 2.03999e-4 0.9524557 -0.2848325 0.1081607 0.997439 -0.07151782 -9.09486e-4 0.990747 -0.1339695 0.02173936 0.9525427 -0.2892342 0.09489941 0.9999021 5.91623e-5 -0.01399832 0.9998617 -0.005580723 -0.01567196 0.9977933 -0.06621515 0.004933476 0.9986519 -0.05177986 0.003662884 0.9996635 -0.02255314 -0.01281929 0.9965376 -0.08311623 0.002143919 0.9998722 0.009425699 -0.01291197 0.9981262 -0.0576238 0.02058708 0.9998823 1.02548e-4 -0.01534831 0.9847103 -0.1581978 0.07293158 0.9077377 -0.3714796 0.1949748 0.9876556 -0.1420511 0.06601345 0.8942601 -0.3954588 0.2095509 0.9999351 0.01137971 5.4073e-4 0.9958796 -0.08996421 0.01142263 0.9992064 -0.03818309 0.01134485 0.9995424 -0.02915608 -0.008059084 0.9032166 -0.3923565 0.1739434 0.9139956 -0.3708 0.16468 0.8982402 -0.4063356 0.1674991 0.9994637 -0.02971118 -0.01376765 0.9999191 0.01245421 -0.002579092 0.8862463 -0.4107457 0.2141386 0.881514 -0.4168394 0.2217611 0.9893674 -0.1429979 0.02652788 0.9999004 -0.002684354 -0.01386141 0.9048901 -0.3961129 0.1557839 0.8863194 -0.4184833 0.1982665 0.9998505 2.25246e-5 -0.01729297 0.953999 -0.2849129 0.09333133 0.9998626 0.003604054 -0.01618969 0.9314187 -0.3235363 0.1666846 0.9145103 -0.3571165 0.1901025 0.9655016 -0.2329917 0.1162828 0.9986745 -0.02160376 -0.04671943 0.9998337 -1.40502e-4 -0.0182383 0.9997087 -0.02288389 -0.007672011 0.9688758 -0.232593 0.08473682 0.9011116 -0.3967394 0.1749165 0.9564006 -0.259486 0.1340337 0.987578 -6.13579e-4 -0.1571291 0.9868326 -0.001266837 -0.1617401 0.9790058 -0.09846448 0.1784731 0.903779 -0.130355 0.4076656 0.9855014 -0.139856 0.09605944 0.8500247 -0.3975164 0.3455991 0.8064607 -0.3600128 0.4690544 0.8171443 -0.1585741 0.5541926 0.8322694 -0.3953443 0.3886266 0.8447867 -0.2344477 0.4810091 0.9999476 0.009360015 -0.004164636 0.7830683 -0.1697595 0.5983192 0.8249986 -0.2600499 0.5017485 0.8214367 -0.3028863 0.4832201 0.9998053 0.01874428 -0.006168127 0.8333461 -0.3331225 0.4410937 0.6803615 -0.04169857 0.7316896 0.7277947 -0.1686196 0.6647424 0.961631 -0.07297515 0.264463 0.8143395 -0.3622732 0.4534419 0.9987723 0.02213066 0.04432165 0.9984066 6.4737e-4 0.05642551 0.9690277 -0.1923443 0.1548842 0.9413942 -0.2062478 0.2669059 0.8862311 -0.1071378 0.4506841 0.9334923 -0.3169826 0.1676732 0.8861044 -0.2566355 0.38595 0.8566516 -0.008013367 0.5158331 0.939336 -0.3358802 0.06951636 0.8852143 -0.2638434 0.3831217 0.999948 0.01008421 -0.001540601 0.8462046 -0.35764 0.3950082 0.8428248 -0.01737707 0.5379074 0.8507916 -0.1749396 0.4955297 0.7951518 -0.1259014 0.5931968 0.7352342 -0.02704411 0.6772735 0.7974341 -0.07326406 0.5989419 0.827529 -0.4383106 0.3508271 0.8014264 -0.589566 0.1006369 0.8161679 -0.406208 0.4109318 0.7731875 -0.3427993 0.5335445 0.7888563 -0.2587664 0.5574458 0.9397879 -0.3406285 -0.02776598 0.9893935 -0.1446139 -0.0136888 0.7408024 -0.09724253 0.6646471 0.697625 -0.08229655 0.711721 0.7282235 -0.4974959 0.4713686 0.670284 -0.5950039 0.4434972 0.7148401 -0.3757026 0.5897891 0.6142647 -0.6314877 0.4731832 0.7666139 0.00228846 0.6421043 0.5225549 -0.848378 -0.08480167 0.6263596 -0.486037 0.6094602 0.6302187 -0.6033326 0.4886862 0.65042 -0.2798703 0.7061349 0.9997482 0.004352629 -0.02201581 0.7692263 -0.002964437 0.6389696 0.6860318 0.001989066 0.7275689 0.4295055 -0.8924897 0.1377941 0.6276609 -0.3321142 0.7040895 0.5979711 -0.1622525 0.7849234 0.6114866 -0.389255 0.6888866 0.7648816 0.002588272 0.6441657 0.9996875 0.02479803 -0.003159224 0.999602 0.01317906 0.02494746 0.8139794 0.003692209 0.580882 0.7452095 -0.1131882 0.657154 0.66407 0.02674442 0.7471919 0.8817777 -0.3113138 0.3543331 0.902064 -0.3187274 0.2910214 0.6170705 0.04296857 0.7857339 0.9991887 0.03589826 0.01825916 0.9995554 0.02695298 0.01275891 0.8641069 -0.3765161 0.3339984 0.7566836 -0.2463898 0.6055759 0.9992576 0.03809225 0.005770921 0.9986754 0.04938572 0.01444369 0.9987434 -1.79876e-4 0.05011653 0.8029536 -0.5193358 0.2924996 0.8345749 0.002077758 0.5508906 0.700552 -0.3303332 0.6325401 0.9990949 0.03047466 0.0296809 0.5819605 -0.5752165 0.5748462 0.7807046 -0.3161647 0.5390179 0.8612982 -0.4169598 0.2903618 0.998327 7.04014e-4 0.05781608 0.5901761 -0.08014678 0.8032863 0.5911455 -0.07454204 0.8031131 0.9984635 2.2637e-4 0.05541443 0.9988715 -2.04215e-5 0.04749751 0.9902908 -0.00150752 0.1390036 0.9682919 -0.02613735 0.2484507 0.9703034 -0.07308292 0.2305871 0.9156512 -0.05114305 0.3987072 0.9666863 -0.2457603 0.07155197 0.9728974 -0.1987245 0.118234 0.9736685 -0.2263741 0.02691841 0.909096 -0.4137464 0.04856538 0.9157475 -0.3647742 0.1683645 0.9674425 -0.2529085 0.009614646 0.9686595 -0.05015563 0.243276 0.8858013 -0.4536748 0.09764832 0.8057123 -0.05128818 0.5900825 0.9991257 -0.02674639 0.03213471 0.9347838 -0.3544083 -0.02396023 0.9895267 -0.07678669 0.1222323 0.9631999 -0.07032281 0.259424 0.6652163 -0.7183068 0.2037709 0.8627047 -0.5027486 -0.0546326 0.8409392 -0.5398923 -0.03657478 0.7395165 -0.1889618 0.6460718 0.7373679 -0.2041169 0.6439138 0.7725084 -0.6314226 -0.06735235 0.6683695 -0.1045533 0.7364448 0.6345512 -0.7714993 -0.0461927 0.6964656 -0.7168512 0.03255867 0.7017239 -0.2044622 0.68248 0.7945989 -0.5961863 0.1147808 0.6734076 -0.7313112 -0.1081959 0.6743286 -0.628569 0.3875333 0.636246 -0.08868336 0.7663722 0.999759 0.01921737 0.01061594 0.6568482 -0.2941091 0.6942985 0.7040277 -0.6751476 0.2202743 0.662127 -0.1709712 0.7296278 0.5669958 -0.7669677 0.3004601 0.4838921 -0.871315 0.08160114 0.59956 -0.006991624 0.8002993 0.5592772 -0.8288601 0.01414161 0.5084058 -0.7906879 0.3410811 0.6396111 -0.6901578 0.3384965 0.6405761 -0.7469623 0.1780722 0.9605395 -2.50187e-4 0.278144 0.9503276 3.70412e-4 0.3112515 0.6707879 -0.03725409 0.7407131 0.5702635 -0.7852043 0.2413582 0.585801 -0.2340826 0.7759141 0.6687998 0.01826786 0.7432182 0.696855 0.003721237 0.7172025 0.8945932 3.9591e-4 0.4468812 0.7072718 0.01026564 0.7068673 0.5214484 -0.8453131 0.1163507 0.626136 0.01809501 0.7795039 0.6347709 -0.09816575 0.7664394 0.7928649 0.006401479 0.6093638 0.4261791 -0.8939781 -0.1384723 0.9991803 0.03894299 0.01105678 0.9994299 0.0304706 0.01454758 0.9698206 -0.1607656 0.1833102 0.8255867 -0.1129474 0.5528557 0.5284081 -0.7665355 0.3649773 0.7161047 0.01497799 0.6978322 0.9006683 -0.2072536 0.3818933 0.8791531 -0.2367359 0.4135769 0.7366104 -0.03509271 0.6754062 0.9502267 -0.2071314 0.2327355 0.6783651 0.02801966 0.7341905 0.5875428 -0.5642579 0.5800055 0.943493 -0.253867 0.2130085 0.9994679 0.009257197 0.03127729 0.7884263 -0.2193863 0.5746768 0.6403692 0.04026466 0.7670112 0.6195802 0.0182088 0.7847223 0.7218515 -0.1218771 0.6812315 0.9013065 -0.3477669 0.2582731 0.8497527 -0.3384929 0.4041572 0.5793851 -0.02971047 0.8145124 0.7799242 -0.2918754 0.5536488 0.5803191 0.03646469 0.8135725 0.5886996 0.02953797 0.807812 0.8764829 -0.3740673 0.3030699 0.7657119 -0.2565068 0.5898219 0.8030188 0.00265032 0.5959479 0.9180306 -0.0991277 0.3839189 0.9922216 -0.1243218 0.006372153 0.6370411 -0.7707568 -0.01062333 0.917812 -0.3969738 -0.005747079 0.9904742 -0.1376923 0.001390993 0.834382 -0.5483762 -0.05559164 0.6906746 -0.7226809 -0.0264793 0.9671006 -0.2542361 -0.008980214 0.526842 -0.8499513 -0.004515528 0.4985106 -0.8668097 -0.01133024 0.9315186 -0.3636938 4.24121e-4 0.7513518 -0.6594418 -0.02463459 0.651686 -0.7584229 -0.01000779 0.8410596 -0.540942 -8.87543e-4 0.8391264 -0.5439366 -1.92523e-4 0.7238118 -0.6899971 7.08583e-4 0.7066066 -0.7075946 -0.004125475 0.5588254 -0.8292687 0.005271732 0.85909 -0.509011 -0.05359333 0.4859443 -0.873971 -0.005744516 0.4051367 -0.9142177 0.008395314 0.9393456 -0.3302903 -0.09240293 0.9497876 -0.2992665 -0.09134119 0.3712522 -0.9285276 -0.002878129 0.3625061 -0.9319407 -0.008713066 0.9838228 -0.1357653 -0.1168786 0.9999541 -0.004706621 -0.008352875 0.9861158 0.1464021 0.07837098 0.9758641 0.2081241 0.06613314 0.9840897 0.163339 0.0699144 0.9863744 0.1490208 0.06970185 0.9829711 0.1707122 0.06800878 0.9874022 0.1441202 0.06531894 0.9823251 0.1778962 0.05822622 0.9887333 0.1416552 0.04837661 0.9884151 0.1435276 0.04935073 0.9898362 0.1364072 0.04021853 0.9841309 0.170544 0.04900026 0.9907798 0.1327789 0.02693414 0.9908114 0.1327618 0.02583211 0.9684005 -0.002447962 0.2493885 0.9780344 -0.02833908 0.2065078 0.9433425 0.1107484 0.3127934 0.01359707 0.5966742 0.8023684 0.9575801 0.08433055 0.2755517 0.8618338 0.2661107 0.4317728 0.9081181 0.2184958 0.3571852 0.6842237 0.4995388 0.5313181 0.6617255 0.5270945 0.5331893 0.5444465 0.6027739 0.5833024 0.4864616 0.6542824 0.5790247 0.118789 0.7072181 0.6969445 0.3626163 0.6866884 0.6300544 0.3735443 0.6897157 0.6202878 0.183753 0.7221205 0.666916 0.2182285 0.7052632 0.6745222 0.04589992 0.7126456 0.7000212 0.07444757 0.7250069 0.6847062 0.2008591 0.7484988 0.6319851 0.3045005 0.7416657 0.5976717 0.9772138 0.03126388 0.2099424 0.5131017 0.6699592 0.5365459 0.6180287 0.6196005 0.4838757 0.03031909 0.7507178 0.659927 0.04075187 0.7955309 0.6045411 0.1703692 0.7825818 0.5987823 0.08628958 0.7893757 0.607816 0.1146036 0.8038727 0.5836564 0.3486093 0.7538391 0.5569545 0.9958213 0.0913158 -0.00120604 0.424135 0.7293874 0.536753 0.9960728 0.08853846 -3.84789e-4 0.9940306 0.1088474 0.007444083 0.993909 0.1099533 0.007448732 0.2277405 0.7915922 0.5670239 0.9925391 0.1207824 0.01666563 0.3252192 0.7658391 0.5547278 0.9921321 0.1239465 0.01763796 0.4601253 0.7219688 0.5167647 0.0288425 0.6781072 0.7343968 0.9223889 0.2935099 0.2510989 0.9809667 0.1151494 0.1563489 0.9818028 0.1090952 0.15544 0.870855 0.3934344 0.2946541 0.8904831 0.3657388 0.2706937 0.9811482 0.1135306 0.1563944 0.9541618 0.2290192 0.1926795 0.9504087 0.2621939 0.1672652 0.9883078 0.08167916 0.1287487 0.9643156 0.2353165 0.1213328 0.9704862 0.212011 0.1149262 0.9737244 0.2174466 0.0676611 0.09796762 0.7491569 0.6551079 0.1644921 0.765187 0.6224397 0.95906 0.08537709 0.2700277 0.9448227 0.1954149 0.2629129 0.6720836 0.5683104 0.4746862 0.7707257 0.483896 0.4145197 0.8972955 0.2848233 0.3372487 0.9630897 0.1564329 0.2190592 0.9479869 0.1949416 0.251632 0.03014898 0.8154801 0.5779995 0.9815306 0.06083124 0.1813758 0.9824857 0.04854005 0.1799054 0.849352 0.4044459 0.3391532 0.7789989 0.4876115 0.3942028 0.9722524 0.1507517 0.1788834 0.6446693 0.6117882 0.458385 0.9829009 -0.02923059 0.1818007 0.979789 -0.04851573 0.1940619 0.8965482 0.2853096 0.3388214 0.835247 0.3657544 0.4105927 0.8252052 0.381019 0.4169664 0.7934733 0.4743809 0.3812652 0.8775686 0.3560542 0.3210901 0.9175293 0.2888014 0.2733747 0.6194328 0.6243177 0.4759523 0.9863397 0.1382077 0.08962595 0.9766895 0.1984145 0.08191078 0.9726551 0.2195382 0.07579648 0.985663 0.1440169 0.08790725 0.0164141 0.09608912 -0.9952374 0.01702201 0.7351194 -0.6777241 0.01882708 0.07535433 -0.9969791 0.01834714 0.4447649 -0.8954595 0.01687955 0.8827525 -0.469535 0.0163201 0.7309854 -0.6821979 0.01864814 0.8908481 -0.4539184 0.01837313 0.8411619 -0.5404713 0.01884829 0.7900673 -0.6127305 0.01698803 0.6304083 -0.7760779 0.01882857 0.3189331 -0.9475902 0.01899224 0.6495426 -0.7600879 0.01609587 0.9208652 -0.3895488 0.015091 0.8207966 -0.5710213 0.01970678 0.5685012 -0.8224464 0.01760244 0.3105244 -0.9504024 0.01742738 0.4370533 -0.8992668 0.01637333 0.5366696 -0.8436336 0.01539522 0.9695539 -0.244394 0.01889294 0.9313532 -0.3636265 0.01746135 0.948628 -0.3159116 0.01909273 0.9535129 -0.300747 0.01622331 0.210334 -0.9774951 0.02003943 0.1789587 -0.9836525 0.0140112 0.9995648 -0.02596062 0.02010893 0.9946738 -0.1010928 0.01555967 0.9894557 -0.1439977 0.02064228 0.9911541 -0.1311012 0.02052527 0.9976561 -0.06527632 0.01644057 0.9825853 -0.1850833 0.01452022 0.9959784 -0.0884099 0.02066886 0.9794745 -0.2005059 0.02056121 0.9994587 -0.02568364 0.01680064 0.9998168 -0.009176969 0.01558744 0.9989233 -0.04369544 0.02005255 0.9859764 -0.1656761 0.9822358 -3.74495e-4 0.1876511 0.9824767 8.5876e-5 0.1863862 0.9817806 -5.99298e-5 0.1900181 0.997518 0.0703125 -0.003739595 0.9975128 0.07040512 -0.003367662 0.9979771 0.06298536 0.00863403 0.01917374 -0.6007092 -0.7992377 0.02333098 -0.5794701 -0.8146595 0.02542954 -0.9508864 -0.3084939 0.01521944 -0.9663415 -0.2568121 0.0163514 -0.6894849 -0.7241156 0.02558052 -0.2614392 -0.964881 0.02165645 -0.6629167 -0.7483799 0.02633762 -0.9732859 -0.2280811 0.01623618 -0.3220722 -0.9465759 0.02211129 -0.7578562 -0.6520468 0.02129006 -0.3497069 -0.9366173 0.01719933 -0.7836464 -0.620969 0.01789253 -0.05308651 -0.9984297 0.01832669 -0.05184179 -0.9984872 0.02388674 -0.8150426 -0.5789083 0.02243298 -0.4410243 -0.8972148 0.01426988 -0.8645871 -0.5022805 0.01444494 -0.4769552 -0.878809 0.0233134 -0.8835448 -0.4677659 0.02427053 -0.1256365 -0.9917794 0.01248556 -0.1710177 -0.9851889 0.01812005 -0.9280207 -0.3720878 0.01638293 -0.9303938 -0.3661954 0.9999628 -3.43077e-6 -0.008634448 0.9999998 -6.27348e-4 -2.62343e-4 0.9999997 -7.19308e-4 -4.14065e-4 0.999989 7.80791e-4 -0.004636347 0.9999998 -3.1176e-5 -5.86872e-4 0.9999997 -7.31427e-4 -4.03607e-4 0.9999351 -0.006345689 0.009468019 0.9999997 -6.51853e-4 -5.16522e-4 0.9999773 -0.006743013 3.6354e-4 0.9999999 -6.04585e-4 -1.60694e-4 1 -7.30191e-5 -4.20962e-4 1 -1.08423e-4 -3.1853e-4 1 -1.38827e-4 -2.55898e-4 1 -1.64391e-4 -2.18608e-4 1 -1.85419e-4 -1.94714e-4 0.9999991 -0.001209497 6.83952e-4 0.9994983 -0.02698415 -0.01658421 -0.5700857 -0.8215852 -3.15402e-4 -0.5586506 -0.8294031 9.77381e-5 -0.5538896 -0.8325901 -1.58589e-4 -0.5474074 -0.8368663 4.96079e-5 -0.5712265 -0.8207925 5.08672e-5 -0.5284429 -0.8489674 -0.001550793 -0.4948008 -0.8690065 2.251e-4 -0.5099476 -0.8602049 -9.67121e-4 0.5134386 0.8581202 0.003271281 -0.4750306 -0.8799573 0.004602372 -0.04419469 0.00324732 -0.9990177 -0.05681723 6.08966e-5 -0.9983846 -0.0229457 0.9997342 0.002231061 -0.4675983 0.8839389 -0.002028167 -0.502037 0.8648461 3.12021e-4 -0.521626 0.8531743 -2.74574e-4 -0.5283476 0.8490282 -5.85651e-5 -0.108116 0.9941033 0.008354187 -0.521533 0.8532312 -2.68663e-4 -0.1131131 0.9935547 -0.007394731 -0.1988043 0.9800198 -0.006170392 -0.5492256 0.8356741 -1.59758e-4 -0.2034209 0.9790585 0.008029162 -0.5587552 0.8293326 4.73159e-4 -0.496353 0.8681203 0.001082301 -0.297385 0.9547495 0.003967881 -0.3080072 0.9509598 -0.02840965 -0.3653694 0.9305692 0.02337098 -0.3977041 0.9170447 -0.0293343 -0.4866741 0.8735831 -9.60718e-4 -0.4469754 0.8945264 0.005972743 -0.439741 0.8979558 0.01741832 -0.4558066 0.8900789 4.25005e-4 -0.4929839 0.8700384 -4.59306e-4 -0.4920362 0.8705722 0.002146542 0.4487232 -0.5017189 -0.7395442 0.2372201 -0.2491019 -0.9389755 0.4754063 -0.5082566 -0.7180976 0.394433 -0.4125075 -0.8211336 0.4963651 -0.4193131 -0.7601304 0.8040832 -0.4697007 -0.3644605 0.708598 -0.4128057 -0.5722591 0.7081128 -0.4092777 -0.575385 0.9850921 0.01071816 -0.1716939 0.7825629 -0.2821735 -0.5549536 0.7831303 -0.2837737 -0.553335 0.6067152 -0.2831676 -0.7427737 0.7958134 -0.5908819 -0.1324375 0.7954367 -0.5842185 -0.1611502 0.8553164 -0.007871091 -0.5180463 0.8805508 -0.4149046 -0.2290951 0.861873 -0.444958 -0.2432848 0.9659546 -0.1377381 -0.2189975 0.7223709 -0.6242554 -0.297465 0.7799614 -0.4954676 -0.3823248 0.7387246 -0.5698642 -0.3599179 0.9503061 -0.21896 -0.2213029 0.6790946 -0.6688961 -0.3023386 0.795858 -0.3037379 -0.5237876 0.8545321 -0.05541419 -0.5164343 0.755976 -0.391288 -0.52478 0.8161763 -0.07107663 -0.5734148 0.5710821 -0.6400739 -0.5139755 0.6728223 -0.06304204 -0.7371132 0.4343809 -0.7343086 -0.5216361 0.614266 -0.03785294 -0.7881907 0.7279953 -0.176893 -0.6623682 0.3801073 -0.9053941 -0.1891563 0.5292001 -0.06141489 -0.8462716 0.4603609 -0.2847398 -0.8408277 0.721026 -0.2054476 -0.6617498 0.3604747 -0.8591572 -0.36319 0.6414723 -0.2361115 -0.7299073 0.4012476 -0.03369766 -0.9153497 0.5242716 -0.1154598 -0.8436874 0.4136551 -0.3345489 -0.8467388 0.3430337 -0.5551275 -0.7577344 0.4309589 -0.1707959 -0.8860605 0.3593156 -0.8856893 -0.2940184 0.27094 -0.3080447 -0.9119759 0.2888543 -0.09499919 -0.9526482 0.2985582 -0.095371 -0.9496143 0.2050696 -0.191594 -0.9598116 0.9551677 -0.09880024 -0.2790935 0.8979955 -0.09371441 -0.429909 0.406206 -0.7781445 -0.479049 0.1958205 -0.4019685 -0.8944695 0.9066664 -0.2874506 -0.3087529 0.0904836 -0.1449542 -0.9852924 0.8737641 -0.2633132 -0.4089041 0.5305085 -0.8236044 -0.2005911 0.7030426 -0.6007243 -0.3806067 0.6960297 -0.632419 -0.3399837 0.8683866 -0.4601542 -0.1848325 0.2124959 -0.142889 -0.9666583 0.8036662 -0.4616596 -0.3754877 0.2414885 -0.1513035 -0.9585357 0.2596958 -0.2009903 -0.9445428 0.3939446 -0.1877597 -0.8997521 0.8875718 -0.3461406 -0.3039785 0.8897227 -0.3039412 -0.3406071 0.5913747 -0.7077269 -0.3865214 0.6561555 -0.4168846 -0.6290209 0.5724297 -0.5140439 -0.6388139 0.6117911 -0.2798547 -0.7398602 0.4661687 -0.6793172 -0.5667583 0.6145395 -0.7774805 -0.1336611 0.4389327 -0.4965392 -0.7488572 0.3239277 -0.7539134 -0.5715641 0.4817089 -0.8673305 -0.1252779 0.333764 -0.8201324 -0.4647415 0.2259466 -0.4017459 -0.8874393 0.4742453 -0.8329411 -0.2851327 0.5154525 -0.7880181 -0.3366545 0.5342125 -0.7672691 -0.3548451 0.6065565 -0.7838559 -0.1328881 0.5505517 -0.7233621 -0.4167017 0.4591497 -0.7915328 -0.4033085 0.7521357 -0.633208 -0.1825914 0.734049 -0.6669121 -0.1280643 0.6032387 -0.5353147 -0.5912203 0.1420999 -0.1842986 -0.9725439 0.3219148 -0.3709662 -0.8710655 0.6386926 -0.5286141 -0.5591414 0.4682185 -0.3397181 -0.8156979 0.6718072 -0.3807885 -0.6353545 0.327614 -0.2594794 -0.908482 0.4464302 -0.304082 -0.8415666 0.6121592 -0.2647009 -0.7451139 0.5624826 -0.6351616 -0.5293233 0.2840811 -0.6909618 -0.6647329 0.3799313 -0.5543881 -0.740477 0.2749173 -0.626003 -0.7297539 0.4620617 -0.8476977 -0.2605911 0.2286453 -0.5486891 -0.8041529 0.4122375 -0.6450861 -0.6433694 0.6293915 -0.7401357 -0.2367815 0.411351 -0.6785236 -0.6086018 0.3428271 -0.5983707 -0.7241699 0.1219258 -0.2669585 -0.955964 0.2750952 -0.4189261 -0.8653459 0.5481361 -0.6475763 -0.5293315 0.9721323 0.06603336 -0.224941 0.9525157 0.06640845 -0.2971594 0.9097923 0.06228387 -0.4103642 0.8522662 0.06026321 -0.5196257 0.778911 0.05302298 -0.624889 0.6263484 0.04144215 -0.778441 0.6884373 0.0476939 -0.7237261 0.5093808 0.03565782 -0.8598023 0.3268637 0.0211997 -0.9448338 0.3361623 0.02161067 -0.9415561 0.1262421 0.007910788 -0.991968 0.1702578 0.009605646 -0.9853528 0.9670361 0.1143999 -0.2274948 0.1506012 0.1902469 -0.9701162 0.950142 0.09377187 -0.2973841 0.8553804 0.1118433 -0.505782 0.8686597 0.1001802 -0.4851745 0.6938919 0.7139679 -0.0936163 0.9239674 0.382325 -0.01058584 0.5715429 0.8112706 -0.1232022 0.6307787 0.6542874 -0.4171646 0.6635497 0.5263414 -0.531664 0.9005011 0.4329512 -0.04063367 0.3104823 0.9305111 -0.1942934 0.6627196 0.4333012 -0.6107806 0.2523401 0.94533 -0.2065812 0.7011166 0.7007494 -0.1318549 0.8401361 0.5376179 -0.07168382 0.6485618 0.3474779 -0.6772198 0.6606936 0.3040359 -0.6863281 0.5400193 0.8227921 -0.177179 0.5621795 0.8076024 -0.1781372 0.7434853 0.6554691 -0.1326267 0.6892651 0.09675538 -0.7180196 0.428487 0.8789816 -0.2092612 0.6945868 0.6646705 -0.2752496 0.6742321 0.6799541 -0.2882248 0.5562119 0.7692102 -0.3145539 0.7670917 0.5930572 -0.2446501 0.7165363 0.6739658 -0.1798497 0.7279258 0.6605579 -0.1838135 0.4338329 0.804713 -0.4052481 0.1727408 0.9457377 -0.2752109 0.2551234 0.8460261 -0.4681366 0.418224 0.7671858 -0.4863277 0.3564887 0.8941663 -0.2708922 0.2649121 0.8076883 -0.5267458 0.8987404 0.4290147 -0.09062117 0.5585703 0.791742 -0.247273 0.5561047 0.7934176 -0.2474595 0.9567775 0.2890043 -0.03245919 0.8683757 0.4783111 -0.1309279 0.2669869 0.9185155 -0.2916288 0.5353102 0.5733145 -0.620285 0.7219703 0.4689633 -0.5087557 0.4428065 0.8509327 -0.2825525 0.2072691 0.9156236 -0.3444896 0.5166586 0.4817888 -0.7077736 0.7127863 0.3754227 -0.5924471 0.362791 0.866356 -0.3432345 0.8436851 0.5063883 -0.1782316 0.7192604 0.2242625 -0.6575492 0.2579874 0.8945239 -0.3650611 0.5638751 0.2318005 -0.7926623 0.7740626 0.1852992 -0.6053853 0.7497231 0.1600276 -0.6421109 0.7640536 0.1206819 -0.6337649 0.5900495 0.147366 -0.7938042 0.5901609 0.12116 -0.7981419 0.8750161 0.4442319 -0.1923664 0.9497956 0.2995835 -0.09021216 0.4572315 0.8097441 -0.367769 0.5737629 0.7214376 -0.3877163 0.4854694 0.7674058 -0.4188171 0.9126157 0.3750259 -0.1627522 0.4520176 0.5469454 -0.7046495 0.4219729 0.4114207 -0.8078812 0.9811018 0.1911457 -0.03004533 0.7586367 0.5641387 -0.3259111 0.2221903 0.8342339 -0.5046636 0.5182465 0.1868246 -0.834576 0.4413524 0.175976 -0.8799093 0.7326597 0.5777425 -0.3597547 0.89775 0.3806356 -0.2217239 0.4995916 0.09690749 -0.8608236 0.3955757 0.1078738 -0.9120764 0.5346938 0.684365 -0.4957289 0.8356799 0.4465322 -0.3197627 0.4597434 0.7255503 -0.5120673 0.9464619 0.2787061 -0.1628893 0.7598573 0.5268473 -0.3808528 0.3485606 0.5649008 -0.7479256 0.4216091 0.4562284 -0.7836463 0.4053664 0.4045044 -0.8197892 0.2656756 0.7526388 -0.6024546 0.5380619 0.6536198 -0.5322318 0.6810135 0.5572512 -0.4750704 0.361702 0.1242008 -0.9239836 0.2531931 0.7239274 -0.6417339 0.1650972 0.8852809 -0.4347649 0.4323875 0.6525623 -0.6222568 0.2181965 0.7811416 -0.5849856 0.2730507 0.6440154 -0.7146241 0.8233512 0.4023127 -0.4002965 0.2970693 0.588975 -0.7515707 0.2927511 0.5113809 -0.8079521 0.3669983 0.6680312 -0.6473382 0.2358689 0.3958639 -0.8875008 0.1657267 0.3291895 -0.9296069 0.1441369 0.7163324 -0.6827096 0.2416725 0.2257167 -0.9437406 0.2624336 0.1243836 -0.9569 0.8680478 0.3445817 -0.3574305 0.9124016 0.2987547 -0.2797662 0.5473905 0.8282271 -0.1200149 0.1239272 0.6707748 -0.7312341 0.9442622 0.2316963 -0.2338498 0.11254 0.07654994 -0.9906942 0.8021045 0.3347753 -0.494524 0.08790725 0.5623412 -0.8222194 0.1848001 0.5103447 -0.8398793 0.9351046 0.2128672 -0.2833141 0.8673832 0.2462183 -0.4324617 0.8632218 0.2495157 -0.4388511 0.9676772 0.1548488 -0.199055 0.09583663 0.453988 -0.8858388 0.7198177 0.2705989 -0.6392486 0.5310502 0.277644 -0.800562 0.9584942 0.1379373 -0.2495238 0.4194957 0.3065561 -0.8544278 0.274302 0.2906293 -0.916675 0.8627526 0.1717411 -0.4755659 0.8690199 0.1671357 -0.4656932 0.7152109 0.6981363 -0.03284871 0.8740147 0.4855361 0.01878732 0.4921748 0.8668237 -0.07987922 0.304301 0.9467816 -0.1049077 0.297674 0.9510669 -0.08283823 0.2858605 0.9581583 0.01471561 0.550738 0.8337024 -0.04034864 0.7155863 0.6985011 -0.005711257 0.08532679 0.9963514 -0.001840114 0.05827313 0.9910234 0.1203196 0.2396035 0.9681766 -0.07228106 0.7377271 0.6750922 0.003057539 0.09400743 0.9763991 -0.1944415 0.5151776 0.8568075 -0.02174806 0.2717366 0.9606745 -0.05713129 0.9284188 0.3665953 0.06038618 0.5282959 0.8489136 -0.01579248 0.6847699 0.7285552 0.01724898 0.9016506 0.4284626 0.05870467 0.7825543 0.6211851 0.04168552 0.6845877 0.7287269 0.0172289 0.2603225 0.8791406 0.3991794 0.1818583 0.9188605 0.3501757 0.1714242 0.894779 0.4122917 0.5242796 0.8509503 0.03185486 0.5707961 0.8199521 0.04325008 0.06795459 0.9270891 0.3686301 0.8071018 0.5835904 0.08949315 0.9120463 0.3986525 0.09616559 0.3443839 0.9382775 0.03217291 0.8812198 0.4597185 0.1100492 0.7670533 0.6398779 -0.0467503 0.7671782 0.6231725 0.1519656 0.8762089 0.4475051 0.178878 0.6413512 0.6677755 0.3778157 0.3943915 0.9074394 -0.1449455 0.8997052 0.4363187 0.0125128 0.3639522 0.9213464 -0.1366005 0.5781874 0.8105238 -0.09354382 0.2418634 0.9601762 -0.1398712 0.2676005 0.9581868 -0.101332 0.5512821 0.8320596 -0.06136047 0.8705231 0.4913438 0.02776473 0.5440052 0.8381066 -0.04044258 0.8084414 0.5881998 0.0210607 0.2313241 0.9718982 -0.04362499 0.8431016 0.536015 0.0432173 0.280162 0.9595538 -0.02767211 0.4637728 0.8859462 -0.003755807 0.6396045 0.7637581 0.08706289 0.7383946 0.6645264 0.1147956 0.9030373 0.3992474 0.1585096 0.4620988 0.8780323 0.1245949 0.5454632 0.8260694 0.1417019 0.4047711 0.8984234 0.1702812 0.7235386 0.6556646 0.2158608 0.6137188 0.7658873 0.1917446 0.2062492 0.9708218 0.1223369 0.7589706 0.5871586 0.2814397 0.2164089 0.9546627 0.2044176 0.5672892 0.7587558 0.3201135 0.4777216 0.8232375 0.3066954 0.7417096 0.5832758 0.331144 0.3272244 0.8828854 0.3368051 0.1485404 0.9586083 0.2429113 0.4135529 0.8324717 0.3687343 0.0910505 0.9032443 0.4193565 0.0447815 0.9744976 0.2198844 0.03070712 0.9822753 0.1849119 0.03270465 0.8961814 0.4424809 0.01466637 0.9470722 0.3206853 0.8161212 0.5770385 -0.03119117 0.3006612 0.9429131 -0.1432405 0.481693 0.8700386 -0.1049031 0.8887241 0.4584331 0.002956807 0.7221446 0.6899893 -0.04921233 0.8642184 0.5029748 0.01196342 0.7353793 0.6769084 -0.03181803 0.1750321 0.4518939 0.8747319 0.09103959 0.5233001 0.8472715 0.1160184 0.4475552 0.8866984 0.06402641 0.2904145 0.9547565 0.0903908 0.1599033 0.9829854 0.8510233 0.2522162 0.4605936 0.4856094 -0.01998811 0.8739474 0.3184022 0.06492739 0.9457297 0.4896333 0.1431755 0.8600931 0.3568196 0.1261147 0.9256214 0.7401019 -0.02086925 0.6721709 0.8971794 -0.03488063 0.4402868 0.6715468 0.3996925 0.6239157 0.577242 0.2325613 0.782756 0.4731797 0.5305173 0.7033153 0.5439478 0.3606606 0.7576575 0.5090568 0.382085 0.7712798 0.3185484 0.5270752 0.787857 0.4060637 0.3643077 0.8380885 0.2478496 0.5207151 0.8169618 0.8883684 0.1003325 0.4480345 0.1356908 0.003476083 0.9907451 0.2943172 0.2321767 0.9270769 0.2082395 0.2431641 0.9473688 0.8455563 0.1473853 0.5131397 0.3981729 0.02172487 0.9170532 0.7326735 0.1227754 0.6694146 0.02070134 0.2536399 0.9670772 0.7009244 0.2066907 0.6826302 0.0219388 0.520244 0.8537359 0.7349935 0.2752878 0.6196784 0.3600221 0.5597491 0.746368 0.01877987 0.2717511 0.9621843 0.7093515 0.08186596 0.7000846 0.9309954 -0.02873474 0.3638983 0.8953775 -0.02171516 0.4447782 0.7944062 -0.03654581 0.6062864 0.6862358 -0.02236485 0.7270353 0.5950692 -0.0124886 0.8035775 0.8462712 0.01137828 0.532631 0.2826996 -0.01083207 0.9591473 0.7927983 0.01002132 0.6094017 0.3773559 -0.004517853 0.9260573 0.5806435 0.04631233 0.8128397 0.5584672 0.04991674 0.8280234 -0.6372125 -8.57373e-4 0.7706876 0.9276649 8.81056e-7 0.3734139 0.8808678 -9.73908e-4 0.4733614 0.3730631 -0.001297593 0.9278051 0.7869395 4.97721e-4 0.6170301 0.9421383 4.69714e-4 0.3352244 0.381515 1.34819e-4 0.9243627 0.4072474 -9.51243e-4 0.9133174 0.5043529 -4.17653e-7 0.8634976 0.5952608 -7.2852e-4 0.8035323 0.4083262 2.02339e-4 0.9128361 0.781852 -0.001935839 0.6234613 0.9092139 0.004319727 0.416307 0.7557348 0.001459658 0.6548764 0.7982382 5.1372e-4 0.6023417 0.2897408 0.008032143 0.9570715 0.7375269 1.42513e-4 0.6753178 0.2112468 -5.45067e-5 0.9774329 0.3980193 2.11053e-4 0.9173771 0.4768729 0.001460552 0.8789712 0.9352526 -1.70901e-4 0.353981 0.3831195 -8.58235e-4 0.9236984 0.2282079 9.65953e-5 0.9736125 0.6518905 0.002108514 0.7583103 0.6511824 -0.001309454 0.7589201 0.7810532 -8.33036e-4 0.6244641 0.9273546 -4.17906e-4 0.3741835 0.687174 7.68143e-5 0.7264929 0.3497681 -3.97871e-4 0.9368364 0.7545199 0.007050633 0.6562392 0.8761705 -0.003266453 0.4819903 0.5063762 0.001932501 0.8623105 0.3023521 -3.19332e-4 0.9531964 0.325088 -8.74358e-4 0.9456834 0.7882971 3.43229e-4 0.6152949 0.5841652 -0.002023518 0.8116324 0.6361002 0.006932795 0.7715753 0.2699548 0.002186059 0.9628706 0.5076404 9.79461e-4 0.8615685 0.9364485 8.65123e-4 0.3508043 0.7598031 -6.61626e-4 0.6501531 0.6743673 2.72718e-4 0.7383961 0.5222658 3.80535e-4 0.8527827 0.8017793 1.09669e-4 0.5976203 0.6843645 0.002010822 0.7291374 0.8867834 -3.97252e-4 0.4621852 0.9430441 6.87493e-4 0.3326672 0.5498072 -4.05463e-4 0.8352916 0.7187809 0.004055917 0.6952249 0.8535293 5.74281e-4 0.5210446 0.8959571 -0.004741668 0.4441155 0.8583049 -0.001019656 0.513139 0.6520929 2.62468e-4 0.7581391 0.4201027 -1.78109e-4 0.9074767 0.5937098 1.81576e-4 0.8046793 0.6118255 -3.78656e-4 0.7909927 0.4185447 0.001217782 0.9081955 0.4679625 -0.001060068 0.8837477 0.3691363 5.20362e-4 0.9293751 0.5261948 -6.62794e-4 0.8503639 0.317407 -4.57342e-4 0.9482894 0.8687416 0.01524418 0.4950309 0.7473203 0.00170958 0.6644618 0.8643308 0.01308304 0.5027536 0.729586 0.01231288 0.6837782 0.9513235 0.02135753 0.3074532 0.571256 0.01511377 0.8206328 0.675943 -0.005584418 0.7369328 0.9079014 -0.04387378 0.4168816 0.6902608 -0.00490719 0.7235441 0.7830584 -0.04822325 0.6200761 0.1248292 -0.1302724 0.9835888 0.1313367 -0.2621942 0.9560361 0.5559512 -0.275084 0.7843769 0.2083321 -0.3476539 0.9141852 0.9135818 -0.0889436 0.396809 0.7389863 -0.2873266 0.6093789 0.2313385 -0.4640325 0.8550769 0.4272328 -0.4662429 0.7746546 0.02753883 -0.4247338 0.9048994 0.02349233 -0.1583332 0.9871063 0.05638092 -0.445488 0.8935109 0.01605659 -0.2949803 0.9553684 0.4296026 -0.001277923 0.9030172 0.2473888 -0.01862072 0.9687374 0.3143914 -0.1615809 0.9354409 0.3340293 -0.2679486 0.9036747 0.5714019 -0.2037659 0.7949713 0.440182 -0.3319967 0.8342769 0.6276991 -0.3601883 0.6901147 0.5935385 -0.4679301 0.6547927 0.8715506 -0.02205252 0.4898095 0.5013791 -0.04069954 0.86427 0.5834968 -0.04409039 0.8109177 0.4131 -0.09061747 0.9061661 0.7826379 -0.07028841 0.6184961 0.380564 -0.09061044 0.9203047 0.8409967 -0.1276898 0.5257565 0.6783976 -0.1206288 0.7247244 0.8238807 -0.1883866 0.5345384 0.6764192 -0.1631022 0.7182303 0.7526866 -0.2221705 0.6197607 0.3552345 -0.5223898 0.7751886 0.0684452 -0.27492 0.9590278 0.1409465 -0.5538647 0.82059 0.07919281 -0.5174406 0.8520469 0.8951178 -0.001315295 -0.4458276 0.9304787 -1.11409e-5 -0.3663461 0.8310511 -1.31719e-4 -0.5561962 0.7273604 -0.001263499 -0.6862545 0.7417041 -7.93885e-4 -0.6707268 0.5257568 2.4992e-4 -0.8506351 0.4912093 -7.04034e-4 -0.8710414 0.286161 -9.97851e-4 -0.9581811 0.2360528 8.91349e-5 -0.9717403 0.3618207 -0.138096 -0.9219628 0.3081546 -0.1249469 -0.9430955 0.8275405 -0.1466832 -0.5419049 0.5227355 -0.1139484 -0.8448452 0.9327313 -0.109015 -0.3436978 0.9187718 -0.107865 -0.3797681 0.6100252 -0.1344715 -0.7808884 0.7092959 -0.1077675 -0.6966244 0.7945274 -0.1130341 -0.5966151 0.2870625 -0.05319797 -0.9564336 0.3331019 -0.06960272 -0.9403184 0.5156738 -0.06090188 -0.8546178 0.5170717 -0.06125015 -0.8537478 0.8455649 -0.06947588 -0.5293329 0.7093984 -0.05189961 -0.7028943 0.7162809 -0.05553174 -0.6955989 0.8959414 -0.05548304 -0.4406933 0.9177467 -0.06056791 -0.3925207 0.281373 -0.9340249 -0.2200607 0.3349282 -0.9190397 -0.2078199 0.4469612 -0.8701742 -0.2074191 0.6044363 -0.7750649 -0.1842047 0.6287618 -0.7540464 -0.1899281 0.8480618 -0.5153951 -0.123122 0.7565799 -0.6299495 -0.1753579 0.9127117 -0.3916946 -0.1163306 0.2292597 -0.7135729 -0.6620073 0.2534506 -0.6729936 -0.6948687 0.3992835 -0.6183935 -0.6768767 0.3060805 -0.9026739 -0.3024808 0.3263139 -0.8983886 -0.2939683 0.5096902 -0.8166018 -0.2708829 0.4903739 -0.4759629 -0.7300636 0.2790109 -0.4402323 -0.8534334 0.2794362 -0.4404711 -0.853171 0.5358468 -0.8037654 -0.2585142 0.4548025 -0.4367012 -0.7761744 0.5689787 -0.4624266 -0.6800184 0.6961938 -0.3427552 -0.6307402 0.6763904 -0.3524128 -0.6467623 0.2937571 -0.3608325 -0.8851592 0.5752955 -0.3574256 -0.7357188 0.7095361 -0.6626873 -0.2395915 0.9511428 -0.1688416 -0.2584955 0.3580892 -0.3164154 -0.8784381 0.7692019 -0.6023046 -0.2134426 0.3257061 -0.8790986 -0.3479961 0.8015743 -0.5594838 -0.2108478 0.2186655 -0.8943125 -0.3903726 0.4277716 -0.8340007 -0.3485029 0.5130787 -0.7985469 -0.3147589 0.9021415 -0.4014739 -0.1579866 0.607444 -0.728643 -0.3163722 0.225055 -0.8665097 -0.445546 0.2830419 -0.844906 -0.4538958 0.3212815 -0.8122621 -0.4868354 0.9193701 -0.3619301 -0.1541607 0.2638568 -0.7913503 -0.551493 0.5501976 -0.7400016 -0.3868853 0.3363129 -0.7385742 -0.584296 0.6287015 -0.6958728 -0.3471248 0.6876445 -0.6439601 -0.3353512 0.5322612 -0.2882106 -0.7960106 0.7842303 -0.5521446 -0.2830533 0.5044004 -0.7352212 -0.4528025 0.3452237 -0.9189181 -0.1908142 0.8374984 -0.4748508 -0.2703945 0.9227451 -0.3337029 -0.1928314 0.9077954 -0.3591033 -0.2166846 0.5681673 -0.6636605 -0.4865602 0.6800429 -0.5886451 -0.4370799 0.6985979 -0.5779978 -0.4217577 0.4933959 -0.6746094 -0.5490562 0.5149284 -0.6295201 -0.5818533 0.7952976 -0.4888088 -0.3585636 0.451722 -0.8019388 -0.3909491 0.3516617 -0.6246338 -0.6972566 0.8481983 -0.404417 -0.3420622 0.6366624 -0.5398607 -0.5506463 0.6860292 -0.5267332 -0.5019124 0.9145201 -0.3062972 -0.2642636 0.9227289 -0.2949216 -0.2481787 0.2394578 -0.5444085 -0.8039152 0.3360484 -0.5592959 -0.7577993 0.8095732 -0.4158744 -0.4142941 0.6320956 -0.492968 -0.5978609 0.6222174 -0.4943262 -0.6070315 0.7647837 -0.3927935 -0.5107048 0.5460295 -0.8027503 -0.2396748 0.8196081 -0.361119 -0.4447873 0.8731459 -0.2936175 -0.3891079 0.9119962 -0.2509571 -0.3244742 0.1836859 -0.2414202 -0.9528777 0.7717469 -0.2724857 -0.574594 0.8732954 -0.2219339 -0.4337055 0.3028939 -0.2478653 -0.9202272 0.9107361 -0.1864913 -0.3684844 0.8251156 -0.2399855 -0.51146 0.3772104 -0.2145901 -0.9009236 0.5886713 -0.2390615 -0.7722148 0.56553 -0.2166185 -0.7957717 0.7250998 -0.2198653 -0.6526023 0.8108229 -0.1674254 -0.5608342 0.1832931 -0.1397604 -0.9730728 0.3158124 -0.93487 -0.1621129 0.4077106 -0.9004224 -0.1516956 0.690966 -0.7139208 -0.1135044 0.6076163 -0.7850536 -0.1203888 0.7578904 -0.6452029 -0.0965169 0.2098006 -0.9672795 -0.1426683 0.3515772 -0.9268148 -0.1319389 0.8979503 -0.4390956 0.02967172 0.8700163 -0.4910168 0.04443305 0.4221301 -0.8989321 -0.1171641 0.5447733 -0.8317623 -0.1067414 0.5763999 -0.8115153 -0.09594869 0.8028535 -0.5924824 -0.06626433 0.5661652 -0.820008 -0.08392834 0.3724329 -0.9244728 -0.08151006 0.6002247 -0.7963837 -0.07418525 0.7571888 -0.6506227 -0.05792605 0.8655301 -0.4916219 0.09573811 0.7741829 -0.631286 -0.04603058 0.755539 -0.6188594 0.2148811 0.3776835 -0.9240307 -0.05934941 0.5470079 -0.835111 -0.05806863 0.9015452 -0.4311892 -0.03594738 0.1928225 -0.9809646 -0.02297729 0.1146958 -0.9933571 -0.009311914 0.3657577 -0.9289602 -0.05704772 0.1142514 -0.9333312 0.3403521 0.1114717 -0.9651752 0.2366667 0.07917523 -0.9302871 0.3581859 0.6159237 -0.7878059 3.10891e-4 0.3722967 -0.9278137 -0.02359944 0.5481057 -0.8363929 0.005206644 0.4041105 -0.813629 0.4179744 0.4327601 -0.8522775 0.2938396 0.2802334 -0.9290695 0.2414522 0.2295046 -0.9626568 0.1435955 0.932505 -0.355221 -0.06521201 0.2733339 -0.9457784 -0.1754765 0.8488592 -0.5217661 -0.08484256 0.8868273 -0.4563823 -0.07247507 0.7484536 -0.6573208 -0.08801496 0.9132169 -0.404638 -0.04798954 0.2294878 -0.9636256 -0.1369717 0.7491468 -0.6613583 -0.03720533 0.6525166 -0.7571263 -0.03133481 0.8454674 -0.5226991 -0.1094104 0.1628922 -0.9791424 -0.1214345 0.896627 -0.4394303 -0.05441552 0.2877392 -0.9525432 -0.09933578 0.2905511 -0.9522176 -0.09413671 0.9111937 -0.4108611 -0.03032004 0.1561626 -0.986239 -0.05427616 0.7959104 -0.6050941 0.01969653 0.7036161 -0.7096012 0.03729021 0.3205332 -0.9471178 -0.01505529 0.720481 -0.6887304 0.08097904 0.7887676 -0.6037689 0.1153641 0.614185 -0.7228845 0.3165673 0.6624004 -0.7172284 0.2163541 0.6166314 -0.773814 0.1448367 0.4663677 -0.8772592 0.1136555 0.5101845 -0.8531439 0.1088912 0.3177718 -0.9462596 0.06011778 0.4413909 -0.8493603 0.2894152 0.3075116 -0.8504134 0.4268885 0.3102627 -0.8527194 0.4202461 0.2529525 -0.9655893 0.06043595 0.2559781 -0.9230427 0.2871713 0.1942337 -0.8553164 0.48032 0.05525386 -0.9961971 0.06736689 0.07056248 -0.8638777 0.4987347 0.03058588 -0.8808897 0.4723323 0.02681291 -0.9781782 0.2060308 0.01680094 -0.9622162 0.271768 0.8497481 -0.5162419 -0.1068766 0.6167717 -0.7718368 -0.1544692 0.6948838 -0.7064101 -0.1346157 0.5148013 -0.8426787 -0.1577097 0.4869197 -0.8605625 -0.1494709 0.009360432 0.999009 0.04351586 0.03748595 0.9937298 0.1053378 -0.9994353 0.03056854 -0.01396346 -0.9082689 -0.3926103 0.1445856 -0.1214106 -0.8451977 0.5204809 -0.4351623 -0.7734745 0.4608371 -0.2054911 -0.8202065 0.5338866 -0.8092146 -0.5198711 0.2736898 -0.5450575 -0.7125226 0.4418416 -0.9622244 -0.2464764 0.115645 -0.7862054 -0.5500673 0.2816156 -0.4274555 -0.7753443 0.4648904 -0.06315785 -0.7707143 0.6340431 -0.2203823 -0.9669202 0.1284409 -0.2191537 -0.9673056 0.1276394 -0.4236199 -0.6876567 0.5896394 -0.2067376 -0.777187 0.59434 -0.6700302 -0.7365221 0.09270828 -0.6083079 -0.6152649 0.5014088 -0.7216579 -0.5231258 0.4533754 -0.04168057 -0.9672892 0.2502285 -0.8130387 -0.4536219 0.3649594 -0.9445059 -0.2549495 0.2071458 -0.3660542 -0.8737065 0.320377 -0.2203862 -0.9469321 0.2339863 -0.9297856 -0.291262 0.2250895 -0.580172 -0.7954609 0.1750499 -0.6239032 -0.758676 0.1874986 -0.2460314 -0.9044806 0.3484012 -0.7906211 -0.5983574 0.1299487 -0.8181557 -0.5600472 0.1302639 -0.9635681 -0.2581111 0.07011014 -0.6061215 -0.7491577 0.2671696 -0.3503207 -0.8693848 0.3484903 -0.6632817 -0.6942982 0.2792983 -0.1319174 -0.9131433 0.3857035 -0.4429216 -0.8213191 0.3595213 -0.8610861 -0.4711248 0.1912387 -0.9218757 -0.3874791 0.00230515 -0.9148077 -0.4038778 0.003093779 -0.8139487 -0.580936 0.001013338 -0.739561 -0.6730676 0.005430281 -0.5743888 -0.8185812 -0.001598 -0.2191628 -0.975685 0.002549469 -0.2396256 -0.9708653 4.28121e-4 -0.3406838 0.001283764 -0.9401772 -0.6083663 0.004765152 -0.7936422 -0.7354025 0.005046546 -0.6776118 -0.3000902 0.001281321 -0.95391 -0.9177354 0.009598016 -0.3970763 -0.5849315 0.001696169 -0.8110809 -0.9268405 -0.002789855 -0.3754451 -0.9440962 0.003067553 -0.329656 -0.7521974 0.001208007 -0.6589367 -0.1454187 -9.98855e-4 -0.9893697 -0.5026132 -0.00191915 -0.8645094 -0.6934545 6.52371e-4 -0.7205002 -0.2970736 -0.001038014 -0.9548541 -0.8582589 -8.75253e-4 -0.5132163 -0.06078004 2.56664e-4 -0.9981512 -0.7773864 -0.004410266 -0.6290079 -0.6786513 0.003755033 -0.7344511 -0.4428312 -0.002878785 -0.8966004 -0.3044661 0.001427531 -0.9525221 -0.7783778 6.15825e-4 -0.6277959 -0.4440901 -8.63335e-4 -0.8959819 -0.9675365 -0.001051306 -0.2527295 -0.6833276 -0.00228095 -0.7301084 -0.5006784 0.002742826 -0.8656291 -0.1085388 0.001450955 -0.9940912 -0.4674941 -1.36518e-4 -0.8839963 -0.8470867 0.001401484 -0.5314529 -0.7832147 -0.002225577 -0.6217475 -0.09668087 0.00371021 -0.9953086 -0.9671323 -0.001125276 -0.2542717 -0.4665263 0.001226663 -0.8845065 -0.410382 -0.009164392 -0.9118676 -0.8936396 0.001798331 -0.4487819 -0.5656901 -0.001099646 -0.8246172 -0.779222 7.08456e-4 -0.6267478 -0.5549561 3.08527e-4 -0.8318797 -0.1326645 -0.6928784 -0.7087452 -0.007478833 -0.7009537 -0.7131676 -0.1291336 -0.697957 -0.7044009 -0.1932238 -0.6767569 -0.7103976 -0.1920776 -0.679823 -0.707776 -0.3238776 -0.6185403 -0.715899 -0.03841859 -0.7212472 -0.6916115 -0.3076736 -0.6145222 -0.7264292 -0.06529247 -0.6936894 -0.7173088 -0.2636454 -0.6416324 -0.720277 -0.2851762 -0.6499127 -0.7044772 -0.3369374 0.6101421 -0.7170773 -0.3267958 0.628116 -0.7061692 -0.2947785 0.6324383 -0.7163291 -0.2903965 0.6399878 -0.7113969 -0.251886 0.6626426 -0.7053072 -0.2467704 0.6603077 -0.7092941 -0.1941459 0.6649892 -0.7211773 -0.1621834 0.7033224 -0.692123 -0.1287243 0.6846478 -0.7174172 -0.06234985 0.7004667 -0.7109563 -0.07016646 0.7066074 -0.7041185 -0.005834221 0.7068884 -0.707301 -0.006548881 0.7063436 -0.7078389 0.01090675 0.8455997 0.533706 0.04926282 0.7947363 0.6049525 0.01167285 0.6671476 0.7448341 0.04408442 0.5742034 0.8175251 0.009582519 0.4494033 0.8932776 0.04953968 0.3277626 0.9434604 0.006001055 0.1791087 0.9838111 0.02495372 0.963186 0.2676751 0.0307728 0.963794 0.2648664 0.03629148 0.9495909 0.3113843 0.02928888 0.9487043 0.3148055 0.006467938 0.9265372 -0.3761476 0.05887627 0.9688743 -0.2404499 0.01536655 0.9924196 -0.1219319 0.02492588 0.2509707 -0.9676737 0.03079527 0.2413474 -0.96995 0.005139052 0.4383511 -0.8987892 0.05816298 0.5719304 -0.8182377 0.01822048 0.6749067 -0.7376782 0.0370602 0.8053286 -0.5916693 0.02948945 0.8139602 -0.5801718 0.04962146 2.8745e-4 -0.9987681 0.0330637 2.64412e-5 -0.9994533 0.03147977 -0.4359766 -0.8994073 0.0165109 -0.4047903 -0.9142605 0.04562616 -0.2323396 -0.971564 0.0245403 -0.1471472 -0.9888102 0.0168702 -0.9958178 0.0897901 0.0393387 -0.997208 -0.06347161 0.04224079 -0.9966413 -0.07015627 0.04316651 -0.9511221 -0.3057835 0.02693831 -0.9628903 -0.2685453 0.03918218 -0.809444 -0.5858885 0.017596 -0.836471 -0.5477285 0.02440005 -0.6579978 -0.7526245 0.03243505 -0.6435353 -0.7647289 0.04221993 -0.9511697 0.3057673 0.01729947 -0.9478372 0.3182854 0.02822256 -0.9637321 0.2653753 0.04917871 -0.9662739 0.2527773 0.01953345 -0.1677842 0.9856303 0.045502 -0.307615 0.9504224 0.03536283 -0.3317762 0.9426951 0.04244142 -0.5879433 0.8077881 0.02681404 -0.5617716 0.8268579 0.04957044 -0.8312343 0.5537078 0.007939875 -0.7830361 0.6219257 0.04356092 4.00256e-5 0.9990508 0.04269218 3.35394e-6 0.9990884 0.0400142 6.89602e-5 0.9991991 0.04877531 5.13436e-4 0.9988096 0.8300011 -4.03251e-5 -0.5577618 0.5000187 -1.00605e-4 -0.8660147 0.7434641 1.78034e-4 -0.6687759 0.6831784 -0.004193246 -0.7302395 0.6795988 0.2101997 -0.702824 0.5330599 0.1955041 -0.82318 0.8063185 -0.0612204 -0.5883048 0.8166131 0.1404229 -0.5598435 0.77265 -0.240522 -0.5875042 0.6789677 -0.2250122 -0.6988365 0.577953 0.3975604 -0.7126824 0.6189954 -0.4546468 -0.6404224 0.9490876 0.1918181 -0.2498772 0.5869393 0.3925728 -0.7080882 0.8046064 -0.3357039 -0.4898076 0.7355816 -0.4479771 -0.5081694 0.8488299 0.4016403 -0.343763 0.01911634 0.6748773 -0.7376824 0.9729597 -0.1911982 -0.1295871 0.7175865 -0.5809704 -0.3841133 0.5626952 -0.6523117 -0.5078027 0.7257637 0.5521428 -0.4103723 0.6279267 0.6164607 -0.4750624 0.7967095 -0.5714989 -0.1965785 0.6433648 -0.7427491 -0.1854874 0.8060127 -0.5911434 0.02988564 0.3781345 -0.9223914 -0.07879388 0.7592893 -0.6466605 0.07286894 0.8306647 0.5530338 -0.06441932 0.9011176 0.4134795 -0.1304676 0.6916421 0.6630265 -0.286404 0.08980363 0.9870383 -0.1330065 0.4370998 0.8435235 -0.312109 0.558473 0.828065 0.04915803 0.711489 0.7013471 -0.04354053 0.7844805 -0.59705 0.1676962 0.5728263 -0.7822185 0.2449579 0.7301926 -0.647843 0.2170677 0.63233 0.736926 0.2389538 0.7256084 0.6520049 0.2199595 0.8872163 0.4385426 0.1432749 0.8547483 0.4993765 0.1415225 0.7192634 0.6718853 0.1767207 0.2463712 0.9332803 0.2613222 0.9702418 0.1893367 0.1509388 0.5720116 0.6839096 0.4528471 0.6130761 0.6654419 0.4258227 0.7020049 0.3345619 0.6286951 0.5698717 0.3464149 0.7451463 0.5006587 0.4353085 0.7482295 0.3999871 0.6246047 0.6707305 0.7983508 0.3460142 0.4928594 0.7942368 -0.2082667 0.5708003 0.704855 -0.3600808 0.6111637 0.5854573 -0.4480662 0.6756304 0.6437984 -0.6115853 0.4598773 0.6987519 -0.5616833 0.4430098 0.6881272 0.1113373 0.7169972 0.642746 0.1363086 0.7538552 0.7374608 -0.115329 0.6654704 0.649119 -0.1005796 0.7540083 0.5540223 1.07626e-5 0.832502 0.7326836 -2.53906e-4 0.6805694 0.6739027 -5.90305e-4 0.73882 0.663654 7.39838e-4 0.7480394 0.6991221 3.07313e-4 0.7150024 0.7838357 -5.01964e-5 0.6209684 0.6458511 -0.005950748 0.7634403 0.5970039 -0.002527356 0.8022344 -0.01738655 0.9998489 -3.84669e-6 -0.01737922 0.9998491 0 -0.01722729 0.9998516 -2.68459e-4 -0.01733958 0.9998497 -1.70131e-5 -0.01753181 0.9998464 1.1454e-4 0.5219836 -8.48765e-4 -0.8529552 0.6668203 5.38727e-4 -0.7452184 0.7658174 -7.25405e-4 -0.6430578 0.9627895 -9.35272e-4 -0.2702509 0.2987796 -0.7799257 -0.5499516 0.4020935 -0.6252939 -0.6688262 0.4659919 -0.5890972 -0.6601637 0.7523989 -0.4420377 -0.4883633 0.7958506 -0.423191 -0.4330489 0.4139548 -0.3734977 -0.8301451 0.4472486 -0.3761354 -0.8114745 0.9807555 -0.09018617 -0.1731622 0.7801864 -0.2591825 -0.5693275 0.7756202 -0.2593162 -0.5754724 0.9611511 -0.1374698 -0.2393549 0.2774112 -0.1889126 -0.9419953 0.9689356 -0.2313222 -0.08748596 0.5762234 -0.7894743 -0.2114167 0.2937704 -0.9099406 -0.2927579 0.7958426 -0.6009278 -0.07429927 0.8797292 -0.4700662 -0.07151567 0.7491882 -0.6288716 -0.2079369 0.3145736 -0.880033 -0.3557885 0.4007072 -0.9066954 -0.1316706 0.57179 -0.8078401 -0.1430061 0.9008306 -0.4173067 -0.1198307 0.5174683 -0.774706 -0.3633968 0.8108588 -0.5233554 -0.2619299 0.7314122 -0.6224257 -0.2786084 0.3982247 -0.9172843 -0.002595782 0.2554771 -0.9668141 0.001471757 0.7705313 -0.6374018 8.25359e-4 0.7585182 -0.6516501 0.001530885 0.9924141 -0.1229402 3.56279e-4 0.9845918 -0.1748524 0.002397596 0.4056591 -0.7825981 0.4722085 -0.007520258 -0.6542255 0.7562621 0.7638975 -0.555004 0.329289 0.7780209 -0.536974 0.3261023 0.3119825 -0.8906396 0.3307992 0.4294816 -0.552618 0.7142542 0.4396661 -0.8325669 0.3369368 0.5701803 -0.5477234 0.6122855 0.7992547 -0.5548099 0.2310367 0.7487448 -0.6175182 0.2409412 0.8645087 -0.3188867 0.3885049 0.2898901 -0.9237409 0.2503331 0.9673829 -0.2402421 0.08033806 0.8918317 -0.2980763 0.3402745 0.7359248 -0.6553407 0.1701269 0.3800351 -0.8997511 0.2145263 0.8129897 -0.5695005 0.1213138 0.2836406 -0.9470602 0.1504166 0.9272421 -0.3658539 0.07983225 0.3824746 -0.9173437 0.1104248 0.4832302 -0.6320878 0.6057669 0.5763477 -0.6217389 0.5303434 0.703172 -0.7080862 0.06452322 0.9976773 -0.05423104 0.04122167 0.2883724 -0.8458126 0.4488235 0.7648666 -0.5039891 0.4012158 0.281294 -0.7907415 0.5436928 0.8682474 -0.3802341 0.3186982 0.9645332 -0.2124599 0.1566417 -0.01175308 -1.03149e-4 -0.999931 -0.04736262 0.00118792 -0.9988771 -0.03439801 -0.03475373 -0.9988039 -0.01288056 -4.33582e-4 -0.999917 -0.002185523 -0.001118898 -0.999997 -0.01728618 -0.6576848 0.753095 -0.04450172 -0.6916269 0.7208827 0.01263266 -0.7154505 0.6985492 -0.05300712 -0.8108932 0.5827887 -0.04096162 -0.8139745 0.5794547 0.006277799 -0.8888592 0.4581374 -0.0704602 -0.9074401 0.4142315 0.008559107 -0.9324147 0.361289 -0.0422033 -0.9624226 0.2682567 -0.03099644 -0.9647466 0.2613488 -0.04374474 -0.9832389 0.1769965 -0.004558205 -0.9882283 0.1529192 -0.06398034 -0.9953533 0.07196199 0.003407895 -0.999994 7.0118e-4 0.005200386 -0.9999861 8.56602e-4 -0.04920631 -0.995606 -0.07967126 0.05196726 -0.9857331 -0.1600927 -0.03656625 -0.9736557 -0.2250723 0.04509484 -0.9578742 -0.2836256 -0.05355089 -0.9139656 -0.4022429 -0.05540072 -0.9135245 -0.4029937 0.01491498 -0.8339375 -0.5516574 -0.07957899 -0.7357323 -0.672581 0.02134627 -0.6426488 -0.7658636 -0.03251081 -0.5137825 -0.8573043 -0.001630246 -0.4619501 -0.8869045 -0.06752485 -0.3444938 -0.9363571 0.04269438 -0.1800265 -0.9827348 -0.253718 -0.3687384 -0.8942367 -0.4974309 -0.2408108 -0.8334103 -0.640411 -0.3465276 -0.685414 -0.5138589 -0.260795 -0.8172729 -0.8981794 -0.2675846 -0.3488155 -0.8349961 -0.2472368 -0.491585 -0.7332244 -0.4490029 -0.5106647 -0.5405342 -0.6161186 -0.5729056 -0.7082646 -0.3964948 -0.5840833 -0.9232773 -0.3763957 -0.07671809 -0.5244047 -0.8471563 -0.08559215 -0.6319026 -0.7448847 -0.2141167 -0.3048267 -0.9283396 -0.2127592 -0.7735345 -0.5751408 -0.266191 -0.6188445 -0.7248202 -0.302766 -0.5959268 -0.7950577 -0.1129368 -0.7217317 -0.6921688 -0.002417325 -0.8627808 -0.5055712 0.002704501 -0.8536391 -0.5039889 0.1315134 -0.6677092 -0.5222085 0.5305306 -0.6790301 -0.7309229 0.06833714 -0.8414792 -0.3590322 0.4037435 -0.8954865 -0.2907016 0.3370409 -0.7139958 -0.669358 0.2053534 -0.4529529 -0.813012 0.3658486 -0.6721762 -0.7307428 0.1191397 -0.868691 -0.4184268 0.2651324 -0.6290362 -0.6285623 0.4574089 -0.01820272 -0.9998344 -8.22108e-5 -0.02031451 0.9997937 0 -0.0184195 0.9998095 -0.006467461 -0.8175181 -0.3517578 0.4559941 -0.9527996 -0.1866111 0.2394772 -0.6388359 -0.482093 0.5995624 -0.7724793 -0.4016821 0.4918611 -0.5917028 -0.5173369 0.618264 -0.2125322 -0.509186 0.8340023 -0.08144503 -0.6422971 0.7621162 -0.9993522 -0.02694588 0.02385747 -0.9387846 -0.2209367 0.2643301 -0.1685807 -0.5821439 0.7954177 1 -1.59635e-6 0 1 0 0 0.01745879 5.66838e-6 0.9998477 0.03099942 -0.003267168 0.9995142 0.03130018 -0.003394365 0.9995043 0.01746416 0 0.9998475 -0.0174759 0.9998474 -1.47093e-5 -0.01747453 0.9998474 0 -0.01750534 0.9998469 2.12921e-5 0.8034423 -0.5025768 -0.3192133 0.6049628 -0.7711433 -0.1983887 0.1815953 -0.9678186 -0.1742135 0.64287 -0.7447756 -0.1789625 0.6709684 -0.6392459 -0.3757208 0.4038424 -0.8927095 -0.1999528 0.4035468 -0.8928104 -0.2000992 0.2107745 -0.827871 -0.5198112 0.238038 -0.8297455 -0.504837 0.8557923 -0.4151744 -0.3086255 0.08978402 -0.9711232 -0.2210401 0.551455 -0.6708173 -0.4958847 0.1951844 -0.9357898 -0.2935994 0.7332546 -0.6714453 -0.107233 0.9514362 -0.302337 -0.05798053 0.9748796 -0.2054636 -0.08599156 0.7305006 -0.6396387 -0.2392305 0.5852484 -0.6383147 -0.5000388 0.8037986 -0.5576477 -0.2072126 0.7955585 -0.6014449 -0.07314944 0.1929651 -0.7079515 -0.6793888 0.2218383 -0.9036865 -0.3662495 0.4063439 -0.8812811 -0.2413056 0.5372357 0.7747809 0.3333054 0.1404054 -0.7490733 -0.6474376 0.2898097 -0.9415841 -0.1715513 0.02813524 -0.9337258 -0.3568818 0.9987557 -0.042279 -0.02644765 0.933096 -0.3491821 -0.0860458 0.8658127 -0.4547505 -0.2087355 0.6308395 -0.7727088 -0.07044732 0.9426785 -0.2912657 -0.1628544 0.06031548 -0.9932758 -0.09881925 0.9729693 -0.1940726 -0.1251666 0.5862614 -0.7234748 -0.3645298 0.3635715 -0.9231372 -0.1250337 0.9259803 -0.3665481 -0.09057033 0.876915 -0.4643304 -0.1241677 0.9990906 -0.04152858 0.009671509 0.9984933 -0.05486011 -0.001229941 0.9993687 -0.03552681 2.60892e-4 0.9983755 -0.05655789 -0.006890535 0.999947 0.009648978 0.003597617 0.9989839 -0.03777807 0.02457976 0.999618 -0.02527379 -0.01119279 0.9994125 -0.0267201 0.02146446 0.9945605 -0.09076625 -0.05109775 0.9995116 -0.029621 0.009957849 0.1673778 -0.7671129 -0.6192919 0.0996887 -0.8503339 -0.5167151 0.9995042 0.001211762 0.03146576 0.9983105 0 -0.05810439 0.998438 0.001815259 -0.05584293 0.9896948 0.0107997 -0.142785 0.9987263 1.66942e-4 -0.0504558 0.9991551 -1.01301e-4 -0.04110199 0.999939 1.50395e-6 -0.01105445 0.9873094 -6.79567e-4 0.158807 0.9871919 0 0.1595373 0.8266589 0.002745985 0.5626965 0.8164678 7.27504e-4 0.5773904 0.5227415 -0.005411803 0.852474 0.4111508 0.007200181 0.911539 -0.01746851 -7.98497e-4 0.9998471 -0.01811242 -6.86813e-4 0.9998358 -0.01772737 -7.47488e-4 0.9998427 -0.01886183 0.00223267 0.9998196 -0.01727402 -9.33403e-4 0.9998504 -0.0174098 -0.00132209 0.9998477 -0.01772445 7.8853e-4 0.9998427 -0.0188052 0.00221771 0.9998207 -0.01746636 0.001036703 0.999847 -0.01829272 8.41666e-4 0.9998324 -0.04179811 -0.01165378 0.9990581 -0.01947081 6.42348e-4 0.9998102 -0.02357411 -3.30777e-6 0.9997222 -0.02344107 -8.45551e-7 0.9997253 -0.01746749 0 -0.9998475 -0.01742506 -3.92388e-6 -0.9998482 -0.01719295 3.08708e-5 -0.9998522 -0.02121454 -0.001945197 -0.9997732 -0.0174604 1.51403e-6 -0.9998476 -0.02163219 -3.98991e-4 -0.999766 -0.01984745 -0.001024484 -0.9998025 -0.01750886 5.89638e-4 -0.9998466 -0.01768302 9.94426e-4 -0.9998432 -0.01742815 3.93877e-4 -0.9998481 -0.01775342 6.19029e-4 -0.9998423 -0.01923215 0.0123291 -0.9997391 -0.0192992 0.004440188 -0.9998039 -0.02233517 -0.01634991 -0.9996169 -0.02470326 -0.01147276 -0.9996291 -0.01989352 -0.9996085 -0.01967996 -0.01934939 -0.9927514 -0.1186183 -0.01703572 -0.9922142 -0.1233735 -0.01943552 -0.9997998 -0.004760861 -0.0143091 -0.9989149 -0.04432153 -0.02297264 -0.9976972 -0.06381577 -0.01578736 -0.9869378 -0.160327 -0.01847827 -0.9815429 -0.1903477 -0.01872217 -0.9880514 -0.1529839 -0.01561284 -0.9871308 -0.1591508 -0.01939213 -0.9822306 -0.1866738 -0.02138304 -0.99661 -0.07944422 -0.01705265 -0.9800229 -0.1981528 -0.01696908 -0.9960284 -0.08740454 -0.01979613 -0.9837764 -0.1783035 -0.01728594 -0.9998506 -2.47442e-5 -0.02095782 -0.9832437 -0.1810876 -0.1253872 -0.7822577 0.6102057 -0.6999183 -0.5677042 0.4333895 -0.7951682 -0.5054311 0.3350331 -0.839672 -0.4302388 0.3314297 -0.7981774 -0.485123 0.3571672 -0.8386119 -0.4320478 0.3317605 -0.8176898 -0.4904768 0.3013572 -0.8842906 -0.4020133 0.2375196 -0.9291118 -0.2758641 0.2462726 -0.9019222 -0.3582373 0.2412517 -0.9130543 -0.3746582 0.1611305 -0.9248166 -0.2887638 0.2476485 -0.9485685 -0.1695718 0.2673265 -0.9922474 -0.1229003 -0.0184623 -0.9844369 -0.1738134 -0.02594 -0.99068 -0.1345283 -0.0213353 -0.9813615 -0.1896609 -0.03095823 -0.950689 -0.2195699 0.2190429 -0.9814023 -0.1877177 -0.040147 -0.9830605 -0.1794582 -0.03723984 -0.9886385 -0.1464882 -0.03369456 -0.9920595 -0.1207952 -0.03502172 -0.9844915 -0.1658528 -0.0571798 -0.9832056 -0.1709966 -0.06377297 -0.9571346 -0.1266056 0.2605083 -0.2021561 -0.7932954 0.5742956 -0.1572667 -0.7381256 0.6560776 -0.9908866 -0.1276811 -0.04291266 -0.984417 -0.1578545 -0.0774939 -0.160057 -0.7429847 0.6498889 -0.930772 -0.2973116 0.2127665 -0.9705829 -0.09509754 0.2211913 -0.9876324 -0.1296753 -0.08812803 -0.9703214 -0.1653879 0.176418 -0.9877303 -0.1271015 -0.09074258 -0.2785251 -0.7530081 0.5961565 -0.9893272 -0.104973 -0.1010568 -0.9627891 -0.2340731 0.1350815 -0.9633386 -0.2429561 0.1138032 -0.1847709 -0.7264155 0.6619519 -0.9868744 -0.09516292 -0.1304725 -0.2781606 -0.7528234 0.5965598 -0.273667 -0.7305225 0.6256543 -0.9896749 -0.05719709 -0.131423 -0.9882733 -0.06824409 -0.1365973 -0.9881712 -0.06946885 -0.1367176 -0.2827916 -0.7458986 0.6030458 -0.9923574 -0.08801817 -0.08648473 -0.9898642 -0.083018 -0.1152263 -0.9862914 -0.03434658 -0.1613993 -0.9814007 -0.1099103 0.1573928 -0.3663855 -0.6906484 0.6235114 -0.4103875 -0.7122417 0.569468 -0.9765391 -0.2077852 0.05654132 -0.9824274 -0.1654213 0.08644151 -0.9856721 -0.1295133 0.1080605 -0.9736756 -0.2248843 0.03718644 -0.4577629 -0.6737805 0.5800629 -0.4659113 -0.6849941 0.560098 -0.9783759 -0.02132129 0.205733 -0.9889522 -0.0785306 0.1257238 -0.01830273 -0.6957897 0.7180124 -0.01616126 -0.8195578 0.5727687 -0.03322637 -0.7492409 0.6614636 -0.5463141 -0.6600257 0.5156618 -0.6265675 -0.5583565 0.5437383 -0.6730809 -0.5764623 0.4633069 -0.04404544 -0.780815 0.6232079 -0.01575714 -0.6101837 0.7921034 -0.9913349 -0.1245634 0.04170143 -0.04566699 -0.7613181 0.6467685 -0.0342608 -0.6748678 0.737143 -0.9914663 -0.09145587 0.09290027 -0.8119737 -0.4086565 0.4167715 -0.9770201 -0.212668 -0.01428622 -0.8087608 -0.4145488 0.4171994 -0.9949041 -0.09867942 0.02069348 -0.04845446 -0.6875662 0.7245032 -0.06748998 -0.7413517 0.6677147 -0.992611 -0.02952563 0.1176931 -0.9834827 0.01097685 0.1806693 -0.0840938 -0.8109217 0.5790805 -0.9840058 0.004345715 0.1780837 -0.8835192 -0.3057748 0.3548176 -0.4076388 -0.7298558 0.5487633 -0.1211359 -0.7664896 0.6307298 -0.8932934 -0.2749823 0.3555445 -0.3909217 -0.7320415 0.5579387 -0.06763797 -0.716306 0.6945005 -0.4781578 -0.706994 0.5210801 -0.5233851 -0.6844011 0.5076054 -0.6237269 -0.6364217 0.4537976 -0.6911811 -0.5388131 0.4816111 -0.6569291 -0.6286823 0.4161762 -0.9966731 -0.08150392 2.03999e-4 -0.9524557 -0.2848325 0.1081607 -0.997439 -0.07151782 -9.09486e-4 -0.990747 -0.1339695 0.02173936 -0.9525427 -0.2892342 0.09489941 -0.9999021 5.91623e-5 -0.01399832 -0.9998617 -0.005580723 -0.01567196 -0.9977933 -0.06621515 0.004933476 -0.9986519 -0.05177986 0.003662884 -0.9996635 -0.02255314 -0.01281929 -0.9965376 -0.08311623 0.002143919 -0.9998722 0.009425699 -0.01291197 -0.9981262 -0.0576238 0.02058708 -0.9998823 1.02548e-4 -0.01534831 -0.9847103 -0.1581978 0.07293158 -0.9077377 -0.3714796 0.1949748 -0.9876556 -0.1420511 0.06601345 -0.8942601 -0.3954588 0.2095509 -0.9999351 0.01137971 5.4073e-4 -0.9958796 -0.08996421 0.01142263 -0.9992064 -0.03818309 0.01134485 -0.9995424 -0.02915608 -0.008059084 -0.9032166 -0.3923565 0.1739434 -0.9139956 -0.3708 0.16468 -0.8982402 -0.4063356 0.1674991 -0.9994637 -0.02971118 -0.01376765 -0.9999191 0.01245421 -0.002579092 -0.8862463 -0.4107457 0.2141386 -0.881514 -0.4168394 0.2217611 -0.9893674 -0.1429979 0.02652788 -0.9999004 -0.002684354 -0.01386141 -0.9048901 -0.3961129 0.1557839 -0.8863194 -0.4184833 0.1982665 -0.9998505 2.25246e-5 -0.01729297 -0.953999 -0.2849129 0.09333133 -0.9998626 0.003604054 -0.01618969 -0.9314187 -0.3235363 0.1666846 -0.9145103 -0.3571165 0.1901025 -0.9655016 -0.2329917 0.1162828 -0.9986745 -0.02160376 -0.04671943 -0.9998337 -1.40502e-4 -0.0182383 -0.9997087 -0.02288389 -0.007672011 -0.9688758 -0.232593 0.08473682 -0.9011116 -0.3967394 0.1749165 -0.9564006 -0.259486 0.1340337 -0.987578 -6.13579e-4 -0.1571291 -0.9868326 -0.001266837 -0.1617401 -0.9790058 -0.09846448 0.1784731 -0.903779 -0.130355 0.4076656 -0.9855014 -0.139856 0.09605944 -0.8500247 -0.3975164 0.3455991 -0.8064607 -0.3600128 0.4690544 -0.8171443 -0.1585741 0.5541926 -0.8322694 -0.3953443 0.3886266 -0.8447867 -0.2344477 0.4810091 -0.9999476 0.009360015 -0.004164636 -0.7830683 -0.1697595 0.5983192 -0.8249986 -0.2600499 0.5017485 -0.8214367 -0.3028863 0.4832201 -0.9998053 0.01874428 -0.006168127 -0.8333461 -0.3331225 0.4410937 -0.6803615 -0.04169857 0.7316896 -0.7277947 -0.1686196 0.6647424 -0.961631 -0.07297515 0.264463 -0.8143395 -0.3622732 0.4534419 -0.9987723 0.02213066 0.04432165 -0.9984066 6.4737e-4 0.05642551 -0.9690277 -0.1923443 0.1548842 -0.9413942 -0.2062478 0.2669059 -0.8862311 -0.1071378 0.4506841 -0.9334923 -0.3169826 0.1676732 -0.8861044 -0.2566355 0.38595 -0.8566516 -0.008013367 0.5158331 -0.939336 -0.3358802 0.06951636 -0.8852143 -0.2638434 0.3831217 -0.999948 0.01008421 -0.001540601 -0.8462046 -0.35764 0.3950082 -0.8428248 -0.01737707 0.5379074 -0.8507916 -0.1749396 0.4955297 -0.7951518 -0.1259014 0.5931968 -0.7352342 -0.02704411 0.6772735 -0.7974341 -0.07326406 0.5989419 -0.827529 -0.4383106 0.3508271 -0.8014264 -0.589566 0.1006369 -0.8161679 -0.406208 0.4109318 -0.7731875 -0.3427993 0.5335445 -0.7888563 -0.2587664 0.5574458 -0.9397879 -0.3406285 -0.02776598 -0.9893935 -0.1446139 -0.0136888 -0.7408024 -0.09724253 0.6646471 -0.697625 -0.08229655 0.711721 -0.7282235 -0.4974959 0.4713686 -0.670284 -0.5950039 0.4434972 -0.7148401 -0.3757026 0.5897891 -0.6142647 -0.6314877 0.4731832 -0.7666139 0.00228846 0.6421043 -0.5225549 -0.848378 -0.08480167 -0.6263596 -0.486037 0.6094602 -0.6302187 -0.6033326 0.4886862 -0.65042 -0.2798703 0.7061349 -0.9997482 0.004352629 -0.02201581 -0.7692263 -0.002964437 0.6389696 -0.6860318 0.001989066 0.7275689 -0.4295055 -0.8924897 0.1377941 -0.6276609 -0.3321142 0.7040895 -0.5979711 -0.1622525 0.7849234 -0.6114866 -0.389255 0.6888866 -0.7648816 0.002588272 0.6441657 -0.9996875 0.02479803 -0.003159224 -0.999602 0.01317906 0.02494746 -0.8139794 0.003692209 0.580882 -0.7452095 -0.1131882 0.657154 -0.66407 0.02674442 0.7471919 -0.8817777 -0.3113138 0.3543331 -0.902064 -0.3187274 0.2910214 -0.6170705 0.04296857 0.7857339 -0.9991887 0.03589826 0.01825916 -0.9995554 0.02695298 0.01275891 -0.8641069 -0.3765161 0.3339984 -0.7566836 -0.2463898 0.6055759 -0.9992576 0.03809225 0.005770921 -0.9986754 0.04938572 0.01444369 -0.9987434 -1.79876e-4 0.05011653 -0.8029536 -0.5193358 0.2924996 -0.8345749 0.002077758 0.5508906 -0.700552 -0.3303332 0.6325401 -0.9990949 0.03047466 0.0296809 -0.5819605 -0.5752165 0.5748462 -0.7807046 -0.3161647 0.5390179 -0.8612982 -0.4169598 0.2903618 -0.998327 7.04014e-4 0.05781608 -0.5901761 -0.08014678 0.8032863 -0.5911455 -0.07454204 0.8031131 -0.9984635 2.2637e-4 0.05541443 -0.9988715 -2.04215e-5 0.04749751 -0.9902908 -0.00150752 0.1390036 -0.9682919 -0.02613735 0.2484507 -0.9703034 -0.07308292 0.2305871 -0.9156512 -0.05114305 0.3987072 -0.9666863 -0.2457603 0.07155197 -0.9728974 -0.1987245 0.118234 -0.9736685 -0.2263741 0.02691841 -0.909096 -0.4137464 0.04856538 -0.9157475 -0.3647742 0.1683645 -0.9674425 -0.2529085 0.009614646 -0.9686595 -0.05015563 0.243276 -0.8858013 -0.4536748 0.09764832 -0.8057123 -0.05128818 0.5900825 -0.9991257 -0.02674639 0.03213471 -0.9347838 -0.3544083 -0.02396023 -0.9895267 -0.07678669 0.1222323 -0.9631999 -0.07032281 0.259424 -0.6652163 -0.7183068 0.2037709 -0.8627047 -0.5027486 -0.0546326 -0.8409392 -0.5398923 -0.03657478 -0.7395165 -0.1889618 0.6460718 -0.7373679 -0.2041169 0.6439138 -0.7725084 -0.6314226 -0.06735235 -0.6683695 -0.1045533 0.7364448 -0.6345512 -0.7714993 -0.0461927 -0.6964656 -0.7168512 0.03255867 -0.7017239 -0.2044622 0.68248 -0.7945989 -0.5961863 0.1147808 -0.6734076 -0.7313112 -0.1081959 -0.6743286 -0.628569 0.3875333 -0.636246 -0.08868336 0.7663722 -0.999759 0.01921737 0.01061594 -0.6568482 -0.2941091 0.6942985 -0.7040277 -0.6751476 0.2202743 -0.662127 -0.1709712 0.7296278 -0.5669958 -0.7669677 0.3004601 -0.4838921 -0.871315 0.08160114 -0.59956 -0.006991624 0.8002993 -0.5592772 -0.8288601 0.01414161 -0.5084058 -0.7906879 0.3410811 -0.6396111 -0.6901578 0.3384965 -0.6405761 -0.7469623 0.1780722 -0.9605395 -2.50187e-4 0.278144 -0.9503276 3.70412e-4 0.3112515 -0.6707879 -0.03725409 0.7407131 -0.5702635 -0.7852043 0.2413582 -0.585801 -0.2340826 0.7759141 -0.6687998 0.01826786 0.7432182 -0.696855 0.003721237 0.7172025 -0.8945932 3.9591e-4 0.4468812 -0.7072718 0.01026564 0.7068673 -0.5214484 -0.8453131 0.1163507 -0.626136 0.01809501 0.7795039 -0.6347709 -0.09816575 0.7664394 -0.7928649 0.006401479 0.6093638 -0.4261791 -0.8939781 -0.1384723 -0.9991803 0.03894299 0.01105678 -0.9994299 0.0304706 0.01454758 -0.9698206 -0.1607656 0.1833102 -0.8255867 -0.1129474 0.5528557 -0.5284081 -0.7665355 0.3649773 -0.7161047 0.01497799 0.6978322 -0.9006683 -0.2072536 0.3818933 -0.8791531 -0.2367359 0.4135769 -0.7366104 -0.03509271 0.6754062 -0.9502267 -0.2071314 0.2327355 -0.6783651 0.02801966 0.7341905 -0.5875428 -0.5642579 0.5800055 -0.943493 -0.253867 0.2130085 -0.9994679 0.009257197 0.03127729 -0.7884263 -0.2193863 0.5746768 -0.6403692 0.04026466 0.7670112 -0.6195802 0.0182088 0.7847223 -0.7218515 -0.1218771 0.6812315 -0.9013065 -0.3477669 0.2582731 -0.8497527 -0.3384929 0.4041572 -0.5793851 -0.02971047 0.8145124 -0.7799242 -0.2918754 0.5536488 -0.5803191 0.03646469 0.8135725 -0.5886996 0.02953797 0.807812 -0.8764829 -0.3740673 0.3030699 -0.7657119 -0.2565068 0.5898219 -0.8030188 0.00265032 0.5959479 -0.9180306 -0.0991277 0.3839189 -0.9922216 -0.1243218 0.006372153 -0.6370411 -0.7707568 -0.01062333 -0.917812 -0.3969738 -0.005747079 -0.9904742 -0.1376923 0.001390993 -0.834382 -0.5483762 -0.05559164 -0.6906746 -0.7226809 -0.0264793 -0.9671006 -0.2542361 -0.008980214 -0.526842 -0.8499513 -0.004515528 -0.4985106 -0.8668097 -0.01133024 -0.9315186 -0.3636938 4.24121e-4 -0.7513518 -0.6594418 -0.02463459 -0.651686 -0.7584229 -0.01000779 -0.8410596 -0.540942 -8.87543e-4 -0.8391264 -0.5439366 -1.92523e-4 -0.7238118 -0.6899971 7.08583e-4 -0.7066066 -0.7075946 -0.004125475 -0.5588254 -0.8292687 0.005271732 -0.85909 -0.509011 -0.05359333 -0.4859443 -0.873971 -0.005744516 -0.4051367 -0.9142177 0.008395314 -0.9393456 -0.3302903 -0.09240293 -0.9497876 -0.2992665 -0.09134119 -0.3712522 -0.9285276 -0.002878129 -0.3625061 -0.9319407 -0.008713066 -0.9838228 -0.1357653 -0.1168786 -0.9999541 -0.004706621 -0.008352875 -0.9861158 0.1464021 0.07837098 -0.9758641 0.2081241 0.06613314 -0.9840897 0.163339 0.0699144 -0.9863744 0.1490208 0.06970185 -0.9829711 0.1707122 0.06800878 -0.9874022 0.1441202 0.06531894 -0.9823251 0.1778962 0.05822622 -0.9887333 0.1416552 0.04837661 -0.9884151 0.1435276 0.04935073 -0.9898362 0.1364072 0.04021853 -0.9841309 0.170544 0.04900026 -0.9907798 0.1327789 0.02693414 -0.9908114 0.1327618 0.02583211 -0.9684005 -0.002447962 0.2493885 -0.9780344 -0.02833908 0.2065078 -0.9433425 0.1107484 0.3127934 -0.01359707 0.5966742 0.8023684 -0.9575801 0.08433055 0.2755517 -0.8618338 0.2661107 0.4317728 -0.9081181 0.2184958 0.3571852 -0.6842237 0.4995388 0.5313181 -0.6617255 0.5270945 0.5331893 -0.5444465 0.6027739 0.5833024 -0.4864616 0.6542824 0.5790247 -0.118789 0.7072181 0.6969445 -0.3626163 0.6866884 0.6300544 -0.3735443 0.6897157 0.6202878 -0.183753 0.7221205 0.666916 -0.2182285 0.7052632 0.6745222 -0.04589992 0.7126456 0.7000212 -0.07444757 0.7250069 0.6847062 -0.2008591 0.7484988 0.6319851 -0.3045005 0.7416657 0.5976717 -0.9772138 0.03126388 0.2099424 -0.5131017 0.6699592 0.5365459 -0.6180287 0.6196005 0.4838757 -0.03031909 0.7507178 0.659927 -0.04075187 0.7955309 0.6045411 -0.1703692 0.7825818 0.5987823 -0.08628958 0.7893757 0.607816 -0.1146036 0.8038727 0.5836564 -0.3486093 0.7538391 0.5569545 -0.9958213 0.0913158 -0.00120604 -0.424135 0.7293874 0.536753 -0.9960728 0.08853846 -3.84789e-4 -0.9940306 0.1088474 0.007444083 -0.993909 0.1099533 0.007448732 -0.2277405 0.7915922 0.5670239 -0.9925391 0.1207824 0.01666563 -0.3252192 0.7658391 0.5547278 -0.9921321 0.1239465 0.01763796 -0.4601253 0.7219688 0.5167647 -0.0288425 0.6781072 0.7343968 -0.9223889 0.2935099 0.2510989 -0.9809667 0.1151494 0.1563489 -0.9818028 0.1090952 0.15544 -0.870855 0.3934344 0.2946541 -0.8904831 0.3657388 0.2706937 -0.9811482 0.1135306 0.1563944 -0.9541618 0.2290192 0.1926795 -0.9504087 0.2621939 0.1672652 -0.9883078 0.08167916 0.1287487 -0.9643156 0.2353165 0.1213328 -0.9704862 0.212011 0.1149262 -0.9737244 0.2174466 0.0676611 -0.09796762 0.7491569 0.6551079 -0.1644921 0.765187 0.6224397 -0.95906 0.08537709 0.2700277 -0.9448227 0.1954149 0.2629129 -0.6720836 0.5683104 0.4746862 -0.7707257 0.483896 0.4145197 -0.8972955 0.2848233 0.3372487 -0.9630897 0.1564329 0.2190592 -0.9479869 0.1949416 0.251632 -0.03014898 0.8154801 0.5779995 -0.9815306 0.06083124 0.1813758 -0.9824857 0.04854005 0.1799054 -0.849352 0.4044459 0.3391532 -0.7789989 0.4876115 0.3942028 -0.9722524 0.1507517 0.1788834 -0.6446693 0.6117882 0.458385 -0.9829009 -0.02923059 0.1818007 -0.979789 -0.04851573 0.1940619 -0.8965482 0.2853096 0.3388214 -0.835247 0.3657544 0.4105927 -0.8252052 0.381019 0.4169664 -0.7934733 0.4743809 0.3812652 -0.8775686 0.3560542 0.3210901 -0.9175293 0.2888014 0.2733747 -0.6194328 0.6243177 0.4759523 -0.9863397 0.1382077 0.08962595 -0.9766895 0.1984145 0.08191078 -0.9726551 0.2195382 0.07579648 -0.985663 0.1440169 0.08790725 -0.0164141 0.09608912 -0.9952374 -0.01702201 0.7351194 -0.6777241 -0.01882708 0.07535433 -0.9969791 -0.01834714 0.4447649 -0.8954595 -0.01687955 0.8827525 -0.469535 -0.0163201 0.7309854 -0.6821979 -0.01864814 0.8908481 -0.4539184 -0.01837313 0.8411619 -0.5404713 -0.01884829 0.7900673 -0.6127305 -0.01698803 0.6304083 -0.7760779 -0.01882857 0.3189331 -0.9475902 -0.01899224 0.6495426 -0.7600879 -0.01609587 0.9208652 -0.3895488 -0.015091 0.8207966 -0.5710213 -0.01970678 0.5685012 -0.8224464 -0.01760244 0.3105244 -0.9504024 -0.01742738 0.4370533 -0.8992668 -0.01637333 0.5366696 -0.8436336 -0.01539522 0.9695539 -0.244394 -0.01889294 0.9313532 -0.3636265 -0.01746135 0.948628 -0.3159116 -0.01909273 0.9535129 -0.300747 -0.01622331 0.210334 -0.9774951 -0.02003943 0.1789587 -0.9836525 -0.0140112 0.9995648 -0.02596062 -0.02010893 0.9946738 -0.1010928 -0.01555967 0.9894557 -0.1439977 -0.02064228 0.9911541 -0.1311012 -0.02052527 0.9976561 -0.06527632 -0.01644057 0.9825853 -0.1850833 -0.01452022 0.9959784 -0.0884099 -0.02066886 0.9794745 -0.2005059 -0.02056121 0.9994587 -0.02568364 -0.01680064 0.9998168 -0.009176969 -0.01558744 0.9989233 -0.04369544 -0.02005255 0.9859764 -0.1656761 -0.9822358 -3.74495e-4 0.1876511 -0.9824767 8.5876e-5 0.1863862 -0.9817806 -5.99298e-5 0.1900181 -0.997518 0.0703125 -0.003739595 -0.9975128 0.07040512 -0.003367662 -0.9979771 0.06298536 0.00863403 -0.01917374 -0.6007092 -0.7992377 -0.02333098 -0.5794701 -0.8146595 -0.02542954 -0.9508864 -0.3084939 -0.01521944 -0.9663415 -0.2568121 -0.0163514 -0.6894849 -0.7241156 -0.02558052 -0.2614392 -0.964881 -0.02165645 -0.6629167 -0.7483799 -0.02633762 -0.9732859 -0.2280811 -0.01623618 -0.3220722 -0.9465759 -0.02211129 -0.7578562 -0.6520468 -0.02129006 -0.3497069 -0.9366173 -0.01719933 -0.7836464 -0.620969 -0.01789253 -0.05308651 -0.9984297 -0.01832669 -0.05184179 -0.9984872 -0.02388674 -0.8150426 -0.5789083 -0.02243298 -0.4410243 -0.8972148 -0.01426988 -0.8645871 -0.5022805 -0.01444494 -0.4769552 -0.878809 -0.0233134 -0.8835448 -0.4677659 -0.02427053 -0.1256365 -0.9917794 -0.01248556 -0.1710177 -0.9851889 -0.01812005 -0.9280207 -0.3720878 -0.01638293 -0.9303938 -0.3661954 -0.9999628 -3.43077e-6 -0.008634448 -0.9999998 -6.27348e-4 -2.62343e-4 -0.9999997 -7.19308e-4 -4.14065e-4 -0.999989 7.80791e-4 -0.004636347 -0.9999998 -3.1176e-5 -5.86872e-4 -0.9999997 -7.31427e-4 -4.03607e-4 -0.9999351 -0.006345689 0.009468019 -0.9999997 -6.51853e-4 -5.16522e-4 -0.9999773 -0.006743013 3.6354e-4 -0.9999999 -6.04585e-4 -1.60694e-4 -1 -7.30191e-5 -4.20962e-4 -1 -1.08423e-4 -3.1853e-4 -1 -1.38827e-4 -2.55898e-4 -1 -1.64391e-4 -2.18608e-4 -1 -1.85419e-4 -1.94714e-4 -0.9999991 -0.001209497 6.83952e-4 -0.9994983 -0.02698415 -0.01658421 0.5700857 -0.8215852 -3.15402e-4 0.5586506 -0.8294031 9.77381e-5 0.5538896 -0.8325901 -1.58589e-4 0.5474074 -0.8368663 4.96079e-5 0.5712265 -0.8207925 5.08672e-5 0.5284429 -0.8489674 -0.001550793 0.4948008 -0.8690065 2.251e-4 0.5099476 -0.8602049 -9.67121e-4 -0.5134386 0.8581202 0.003271281 0.4836996 -0.87523 0.002691626 0.04419469 0.00324732 -0.9990177 0.05681723 6.08966e-5 -0.9983846 0.0229457 0.9997342 0.002231061 0.4675983 0.8839389 -0.002028167 0.502037 0.8648461 3.12021e-4 0.521626 0.8531743 -2.74574e-4 0.5283476 0.8490282 -5.85651e-5 0.108116 0.9941033 0.008354187 0.521533 0.8532312 -2.68663e-4 0.1131131 0.9935547 -0.007394731 0.1988043 0.9800198 -0.006170392 0.5492256 0.8356741 -1.59758e-4 0.2034209 0.9790585 0.008029162 0.5587552 0.8293326 4.73159e-4 0.496353 0.8681203 0.001082301 0.297385 0.9547495 0.003967881 0.3080072 0.9509598 -0.02840965 0.3653694 0.9305692 0.02337098 0.3977041 0.9170447 -0.0293343 0.4866741 0.8735831 -9.60718e-4 0.4469754 0.8945264 0.005972743 0.439741 0.8979558 0.01741832 0.4558066 0.8900789 4.25005e-4 0.4929839 0.8700384 -4.59306e-4 0.4920362 0.8705722 0.002146542 -0.4487232 -0.5017189 -0.7395442 -0.2372201 -0.2491019 -0.9389755 -0.4754063 -0.5082566 -0.7180976 -0.394433 -0.4125075 -0.8211336 -0.4963651 -0.4193131 -0.7601304 -0.8040832 -0.4697007 -0.3644605 -0.708598 -0.4128057 -0.5722591 -0.7081128 -0.4092777 -0.575385 -0.9850921 0.01071816 -0.1716939 -0.7825629 -0.2821735 -0.5549536 -0.7831303 -0.2837737 -0.553335 -0.6067152 -0.2831676 -0.7427737 -0.7958134 -0.5908819 -0.1324375 -0.7954367 -0.5842185 -0.1611502 -0.8553164 -0.007871091 -0.5180463 -0.8805508 -0.4149046 -0.2290951 -0.861873 -0.444958 -0.2432848 -0.9659546 -0.1377381 -0.2189975 -0.7223709 -0.6242554 -0.297465 -0.7799614 -0.4954676 -0.3823248 -0.7387246 -0.5698642 -0.3599179 -0.9503061 -0.21896 -0.2213029 -0.6790946 -0.6688961 -0.3023386 -0.795858 -0.3037379 -0.5237876 -0.8545321 -0.05541419 -0.5164343 -0.755976 -0.391288 -0.52478 -0.8161763 -0.07107663 -0.5734148 -0.5710821 -0.6400739 -0.5139755 -0.6728223 -0.06304204 -0.7371132 -0.4343809 -0.7343086 -0.5216361 -0.614266 -0.03785294 -0.7881907 -0.7279953 -0.176893 -0.6623682 -0.3801073 -0.9053941 -0.1891563 -0.5292001 -0.06141489 -0.8462716 -0.4603609 -0.2847398 -0.8408277 -0.721026 -0.2054476 -0.6617498 -0.3604747 -0.8591572 -0.36319 -0.6414723 -0.2361115 -0.7299073 -0.4012476 -0.03369766 -0.9153497 -0.5242716 -0.1154598 -0.8436874 -0.4136551 -0.3345489 -0.8467388 -0.3430337 -0.5551275 -0.7577344 -0.4309589 -0.1707959 -0.8860605 -0.3593156 -0.8856893 -0.2940184 -0.27094 -0.3080447 -0.9119759 -0.2888543 -0.09499919 -0.9526482 -0.2985582 -0.095371 -0.9496143 -0.2050696 -0.191594 -0.9598116 -0.9551677 -0.09880024 -0.2790935 -0.8979955 -0.09371441 -0.429909 -0.406206 -0.7781445 -0.479049 -0.1958205 -0.4019685 -0.8944695 -0.9066664 -0.2874506 -0.3087529 -0.0904836 -0.1449542 -0.9852924 -0.8737641 -0.2633132 -0.4089041 -0.5305085 -0.8236044 -0.2005911 -0.7030426 -0.6007243 -0.3806067 -0.6960297 -0.632419 -0.3399837 -0.8683866 -0.4601542 -0.1848325 -0.2124959 -0.142889 -0.9666583 -0.8036662 -0.4616596 -0.3754877 -0.2414885 -0.1513035 -0.9585357 -0.2596958 -0.2009903 -0.9445428 -0.3939446 -0.1877597 -0.8997521 -0.8875718 -0.3461406 -0.3039785 -0.8897227 -0.3039412 -0.3406071 -0.5913747 -0.7077269 -0.3865214 -0.6561555 -0.4168846 -0.6290209 -0.5724297 -0.5140439 -0.6388139 -0.6117911 -0.2798547 -0.7398602 -0.4661687 -0.6793172 -0.5667583 -0.6145395 -0.7774805 -0.1336611 -0.4389327 -0.4965392 -0.7488572 -0.3239277 -0.7539134 -0.5715641 -0.4817089 -0.8673305 -0.1252779 -0.333764 -0.8201324 -0.4647415 -0.2259466 -0.4017459 -0.8874393 -0.4742453 -0.8329411 -0.2851327 -0.5154525 -0.7880181 -0.3366545 -0.5342125 -0.7672691 -0.3548451 -0.6065565 -0.7838559 -0.1328881 -0.5505517 -0.7233621 -0.4167017 -0.4591497 -0.7915328 -0.4033085 -0.7521357 -0.633208 -0.1825914 -0.734049 -0.6669121 -0.1280643 -0.6032387 -0.5353147 -0.5912203 -0.1420999 -0.1842986 -0.9725439 -0.3219148 -0.3709662 -0.8710655 -0.6386926 -0.5286141 -0.5591414 -0.4682185 -0.3397181 -0.8156979 -0.6718072 -0.3807885 -0.6353545 -0.327614 -0.2594794 -0.908482 -0.4464302 -0.304082 -0.8415666 -0.6121592 -0.2647009 -0.7451139 -0.5624826 -0.6351616 -0.5293233 -0.2840811 -0.6909618 -0.6647329 -0.3799313 -0.5543881 -0.740477 -0.2749173 -0.626003 -0.7297539 -0.4620617 -0.8476977 -0.2605911 -0.2286453 -0.5486891 -0.8041529 -0.4122375 -0.6450861 -0.6433694 -0.6293915 -0.7401357 -0.2367815 -0.411351 -0.6785236 -0.6086018 -0.3428271 -0.5983707 -0.7241699 -0.1219258 -0.2669585 -0.955964 -0.2750952 -0.4189261 -0.8653459 -0.5481361 -0.6475763 -0.5293315 -0.9721323 0.06603336 -0.224941 -0.9525157 0.06640845 -0.2971594 -0.9097923 0.06228387 -0.4103642 -0.8522662 0.06026321 -0.5196257 -0.778911 0.05302298 -0.624889 -0.6263484 0.04144215 -0.778441 -0.6884373 0.0476939 -0.7237261 -0.5093808 0.03565782 -0.8598023 -0.3268637 0.0211997 -0.9448338 -0.3361623 0.02161067 -0.9415561 -0.1262421 0.007910788 -0.991968 -0.1702578 0.009605646 -0.9853528 -0.9670361 0.1143999 -0.2274948 -0.1506012 0.1902469 -0.9701162 -0.950142 0.09377187 -0.2973841 -0.8553804 0.1118433 -0.505782 -0.8686597 0.1001802 -0.4851745 -0.6938919 0.7139679 -0.0936163 -0.9239674 0.382325 -0.01058584 -0.5715429 0.8112706 -0.1232022 -0.6307787 0.6542874 -0.4171646 -0.6635497 0.5263414 -0.531664 -0.9005011 0.4329512 -0.04063367 -0.3104823 0.9305111 -0.1942934 -0.6627196 0.4333012 -0.6107806 -0.2523401 0.94533 -0.2065812 -0.7011166 0.7007494 -0.1318549 -0.8401361 0.5376179 -0.07168382 -0.6485618 0.3474779 -0.6772198 -0.6606936 0.3040359 -0.6863281 -0.5400193 0.8227921 -0.177179 -0.5621795 0.8076024 -0.1781372 -0.7434853 0.6554691 -0.1326267 -0.6892651 0.09675538 -0.7180196 -0.428487 0.8789816 -0.2092612 -0.6945868 0.6646705 -0.2752496 -0.6742321 0.6799541 -0.2882248 -0.5562119 0.7692102 -0.3145539 -0.7670917 0.5930572 -0.2446501 -0.7165363 0.6739658 -0.1798497 -0.7279258 0.6605579 -0.1838135 -0.4338329 0.804713 -0.4052481 -0.1727408 0.9457377 -0.2752109 -0.2551234 0.8460261 -0.4681366 -0.418224 0.7671858 -0.4863277 -0.3564887 0.8941663 -0.2708922 -0.2649121 0.8076883 -0.5267458 -0.8987404 0.4290147 -0.09062117 -0.5585703 0.791742 -0.247273 -0.5561047 0.7934176 -0.2474595 -0.9567775 0.2890043 -0.03245919 -0.8683757 0.4783111 -0.1309279 -0.2669869 0.9185155 -0.2916288 -0.5353102 0.5733145 -0.620285 -0.7219703 0.4689633 -0.5087557 -0.4428065 0.8509327 -0.2825525 -0.2072691 0.9156236 -0.3444896 -0.5166586 0.4817888 -0.7077736 -0.7127863 0.3754227 -0.5924471 -0.362791 0.866356 -0.3432345 -0.8436851 0.5063883 -0.1782316 -0.7192604 0.2242625 -0.6575492 -0.2579874 0.8945239 -0.3650611 -0.5638751 0.2318005 -0.7926623 -0.7740626 0.1852992 -0.6053853 -0.7497231 0.1600276 -0.6421109 -0.7640536 0.1206819 -0.6337649 -0.5900495 0.147366 -0.7938042 -0.5901609 0.12116 -0.7981419 -0.8750161 0.4442319 -0.1923664 -0.9497956 0.2995835 -0.09021216 -0.4572315 0.8097441 -0.367769 -0.5737629 0.7214376 -0.3877163 -0.4854694 0.7674058 -0.4188171 -0.9126157 0.3750259 -0.1627522 -0.4520176 0.5469454 -0.7046495 -0.4219729 0.4114207 -0.8078812 -0.9811018 0.1911457 -0.03004533 -0.7586367 0.5641387 -0.3259111 -0.2221903 0.8342339 -0.5046636 -0.5182465 0.1868246 -0.834576 -0.4413524 0.175976 -0.8799093 -0.7326597 0.5777425 -0.3597547 -0.89775 0.3806356 -0.2217239 -0.4995916 0.09690749 -0.8608236 -0.3955757 0.1078738 -0.9120764 -0.5346938 0.684365 -0.4957289 -0.8356799 0.4465322 -0.3197627 -0.4597434 0.7255503 -0.5120673 -0.9464619 0.2787061 -0.1628893 -0.7598573 0.5268473 -0.3808528 -0.3485606 0.5649008 -0.7479256 -0.4216091 0.4562284 -0.7836463 -0.4053664 0.4045044 -0.8197892 -0.2656756 0.7526388 -0.6024546 -0.5380619 0.6536198 -0.5322318 -0.6810135 0.5572512 -0.4750704 -0.361702 0.1242008 -0.9239836 -0.2531931 0.7239274 -0.6417339 -0.1650972 0.8852809 -0.4347649 -0.4323875 0.6525623 -0.6222568 -0.2181965 0.7811416 -0.5849856 -0.2730507 0.6440154 -0.7146241 -0.8233512 0.4023127 -0.4002965 -0.2970693 0.588975 -0.7515707 -0.2927511 0.5113809 -0.8079521 -0.3669983 0.6680312 -0.6473382 -0.2358689 0.3958639 -0.8875008 -0.1657267 0.3291895 -0.9296069 -0.1441369 0.7163324 -0.6827096 -0.2416725 0.2257167 -0.9437406 -0.2624336 0.1243836 -0.9569 -0.8680478 0.3445817 -0.3574305 -0.9124016 0.2987547 -0.2797662 -0.5473905 0.8282271 -0.1200149 -0.1239272 0.6707748 -0.7312341 -0.9442622 0.2316963 -0.2338498 -0.11254 0.07654994 -0.9906942 -0.8021045 0.3347753 -0.494524 -0.08790725 0.5623412 -0.8222194 -0.1848001 0.5103447 -0.8398793 -0.9351046 0.2128672 -0.2833141 -0.8673832 0.2462183 -0.4324617 -0.8632218 0.2495157 -0.4388511 -0.9676772 0.1548488 -0.199055 -0.09583663 0.453988 -0.8858388 -0.7198177 0.2705989 -0.6392486 -0.5310502 0.277644 -0.800562 -0.9584942 0.1379373 -0.2495238 -0.4194957 0.3065561 -0.8544278 -0.274302 0.2906293 -0.916675 -0.8627526 0.1717411 -0.4755659 -0.8690199 0.1671357 -0.4656932 -0.7152109 0.6981363 -0.03284871 -0.8740147 0.4855361 0.01878732 -0.4921748 0.8668237 -0.07987922 -0.304301 0.9467816 -0.1049077 -0.297674 0.9510669 -0.08283823 -0.2858605 0.9581583 0.01471561 -0.550738 0.8337024 -0.04034864 -0.7155863 0.6985011 -0.005711257 -0.08532679 0.9963514 -0.001840114 -0.05827313 0.9910234 0.1203196 -0.2396035 0.9681766 -0.07228106 -0.7377271 0.6750922 0.003057539 -0.09400743 0.9763991 -0.1944415 -0.5151776 0.8568075 -0.02174806 -0.2717366 0.9606745 -0.05713129 -0.9284188 0.3665953 0.06038618 -0.5282959 0.8489136 -0.01579248 -0.6847699 0.7285552 0.01724898 -0.9016506 0.4284626 0.05870467 -0.7825543 0.6211851 0.04168552 -0.6845877 0.7287269 0.0172289 -0.2603225 0.8791406 0.3991794 -0.1818583 0.9188605 0.3501757 -0.1714242 0.894779 0.4122917 -0.5242796 0.8509503 0.03185486 -0.5707961 0.8199521 0.04325008 -0.06795459 0.9270891 0.3686301 -0.8071018 0.5835904 0.08949315 -0.9120463 0.3986525 0.09616559 -0.3443839 0.9382775 0.03217291 -0.8812198 0.4597185 0.1100492 -0.7670533 0.6398779 -0.0467503 -0.7671782 0.6231725 0.1519656 -0.8762089 0.4475051 0.178878 -0.6413512 0.6677755 0.3778157 -0.3943915 0.9074394 -0.1449455 -0.8997052 0.4363187 0.0125128 -0.3639522 0.9213464 -0.1366005 -0.5781874 0.8105238 -0.09354382 -0.2418634 0.9601762 -0.1398712 -0.2676005 0.9581868 -0.101332 -0.5512821 0.8320596 -0.06136047 -0.8705231 0.4913438 0.02776473 -0.5440052 0.8381066 -0.04044258 -0.8084414 0.5881998 0.0210607 -0.2313241 0.9718982 -0.04362499 -0.8431016 0.536015 0.0432173 -0.280162 0.9595538 -0.02767211 -0.4637728 0.8859462 -0.003755807 -0.6396045 0.7637581 0.08706289 -0.7383946 0.6645264 0.1147956 -0.9030373 0.3992474 0.1585096 -0.4620988 0.8780323 0.1245949 -0.5454632 0.8260694 0.1417019 -0.4047711 0.8984234 0.1702812 -0.7235386 0.6556646 0.2158608 -0.6137188 0.7658873 0.1917446 -0.2062492 0.9708218 0.1223369 -0.7589706 0.5871586 0.2814397 -0.2164089 0.9546627 0.2044176 -0.5672892 0.7587558 0.3201135 -0.4777216 0.8232375 0.3066954 -0.7417096 0.5832758 0.331144 -0.3272244 0.8828854 0.3368051 -0.1485404 0.9586083 0.2429113 -0.4135529 0.8324717 0.3687343 -0.0910505 0.9032443 0.4193565 -0.0447815 0.9744976 0.2198844 -0.03070712 0.9822753 0.1849119 -0.03270465 0.8961814 0.4424809 -0.01466637 0.9470722 0.3206853 -0.8161212 0.5770385 -0.03119117 -0.3006612 0.9429131 -0.1432405 -0.481693 0.8700386 -0.1049031 -0.8887241 0.4584331 0.002956807 -0.7221446 0.6899893 -0.04921233 -0.8642184 0.5029748 0.01196342 -0.7353793 0.6769084 -0.03181803 -0.1750321 0.4518939 0.8747319 -0.09103959 0.5233001 0.8472715 -0.1160184 0.4475552 0.8866984 -0.06402641 0.2904145 0.9547565 -0.0903908 0.1599033 0.9829854 -0.8510233 0.2522162 0.4605936 -0.4856094 -0.01998811 0.8739474 -0.3184022 0.06492739 0.9457297 -0.4896333 0.1431755 0.8600931 -0.3568196 0.1261147 0.9256214 -0.7401019 -0.02086925 0.6721709 -0.8971794 -0.03488063 0.4402868 -0.6715468 0.3996925 0.6239157 -0.577242 0.2325613 0.782756 -0.4731797 0.5305173 0.7033153 -0.5439478 0.3606606 0.7576575 -0.5090568 0.382085 0.7712798 -0.3185484 0.5270752 0.787857 -0.4060637 0.3643077 0.8380885 -0.2478496 0.5207151 0.8169618 -0.8883684 0.1003325 0.4480345 -0.1356908 0.003476083 0.9907451 -0.2943172 0.2321767 0.9270769 -0.2082395 0.2431641 0.9473688 -0.8455563 0.1473853 0.5131397 -0.3981729 0.02172487 0.9170532 -0.7326735 0.1227754 0.6694146 -0.02070134 0.2536399 0.9670772 -0.7009244 0.2066907 0.6826302 -0.0219388 0.520244 0.8537359 -0.7349935 0.2752878 0.6196784 -0.3600221 0.5597491 0.746368 -0.01877987 0.2717511 0.9621843 -0.7093515 0.08186596 0.7000846 -0.9309954 -0.02873474 0.3638983 -0.8953775 -0.02171516 0.4447782 -0.7944062 -0.03654581 0.6062864 -0.6862358 -0.02236485 0.7270353 -0.5950692 -0.0124886 0.8035775 -0.8462712 0.01137828 0.532631 -0.2826996 -0.01083207 0.9591473 -0.7927983 0.01002132 0.6094017 -0.3773559 -0.004517853 0.9260573 -0.5806435 0.04631233 0.8128397 -0.5584672 0.04991674 0.8280234 0.6372125 -8.57373e-4 0.7706876 -0.9276649 8.81056e-7 0.3734139 -0.8808678 -9.73908e-4 0.4733614 -0.3730631 -0.001297593 0.9278051 -0.7869395 4.97721e-4 0.6170301 -0.9421383 4.69714e-4 0.3352244 -0.381515 1.34819e-4 0.9243627 -0.4072474 -9.51243e-4 0.9133174 -0.5043529 -4.17653e-7 0.8634976 -0.5952608 -7.2852e-4 0.8035323 -0.4083262 2.02339e-4 0.9128361 -0.781852 -0.001935839 0.6234613 -0.9092139 0.004319727 0.416307 -0.7557348 0.001459658 0.6548764 -0.7982382 5.1372e-4 0.6023417 -0.2897408 0.008032143 0.9570715 -0.7375269 1.42513e-4 0.6753178 -0.2112468 -5.45067e-5 0.9774329 -0.3980193 2.11053e-4 0.9173771 -0.4768729 0.001460552 0.8789712 -0.9352526 -1.70901e-4 0.353981 -0.3831195 -8.58235e-4 0.9236984 -0.2282079 9.65953e-5 0.9736125 -0.6518905 0.002108514 0.7583103 -0.6511824 -0.001309454 0.7589201 -0.7810532 -8.33036e-4 0.6244641 -0.9273546 -4.17906e-4 0.3741835 -0.687174 7.68143e-5 0.7264929 -0.3497681 -3.97871e-4 0.9368364 -0.7545199 0.007050633 0.6562392 -0.8761705 -0.003266453 0.4819903 -0.5063762 0.001932501 0.8623105 -0.3023521 -3.19332e-4 0.9531964 -0.325088 -8.74358e-4 0.9456834 -0.7882971 3.43229e-4 0.6152949 -0.5841652 -0.002023518 0.8116324 -0.6361002 0.006932795 0.7715753 -0.2699548 0.002186059 0.9628706 -0.5076404 9.79461e-4 0.8615685 -0.9364485 8.65123e-4 0.3508043 -0.7598031 -6.61626e-4 0.6501531 -0.6743673 2.72718e-4 0.7383961 -0.5222658 3.80535e-4 0.8527827 -0.8017793 1.09669e-4 0.5976203 -0.6843645 0.002010822 0.7291374 -0.8867834 -3.97252e-4 0.4621852 -0.9430441 6.87493e-4 0.3326672 -0.5498072 -4.05463e-4 0.8352916 -0.7187809 0.004055917 0.6952249 -0.8535293 5.74281e-4 0.5210446 -0.8959571 -0.004741668 0.4441155 -0.8583049 -0.001019656 0.513139 -0.6520929 2.62468e-4 0.7581391 -0.4201027 -1.78109e-4 0.9074767 -0.5937098 1.81576e-4 0.8046793 -0.6118255 -3.78656e-4 0.7909927 -0.4185447 0.001217782 0.9081955 -0.4679625 -0.001060068 0.8837477 -0.3691363 5.20362e-4 0.9293751 -0.5261948 -6.62794e-4 0.8503639 -0.317407 -4.57342e-4 0.9482894 -0.8687416 0.01524418 0.4950309 -0.7473203 0.00170958 0.6644618 -0.8643308 0.01308304 0.5027536 -0.729586 0.01231288 0.6837782 -0.9513235 0.02135753 0.3074532 -0.571256 0.01511377 0.8206328 -0.675943 -0.005584418 0.7369328 -0.9079014 -0.04387378 0.4168816 -0.6902608 -0.00490719 0.7235441 -0.7830584 -0.04822325 0.6200761 -0.1248292 -0.1302724 0.9835888 -0.1313367 -0.2621942 0.9560361 -0.5559512 -0.275084 0.7843769 -0.2083321 -0.3476539 0.9141852 -0.9135818 -0.0889436 0.396809 -0.7389863 -0.2873266 0.6093789 -0.2313385 -0.4640325 0.8550769 -0.4272328 -0.4662429 0.7746546 -0.02753883 -0.4247338 0.9048994 -0.02349233 -0.1583332 0.9871063 -0.05638092 -0.445488 0.8935109 -0.01605659 -0.2949803 0.9553684 -0.4296026 -0.001277923 0.9030172 -0.2473888 -0.01862072 0.9687374 -0.3143914 -0.1615809 0.9354409 -0.3340293 -0.2679486 0.9036747 -0.5714019 -0.2037659 0.7949713 -0.440182 -0.3319967 0.8342769 -0.6276991 -0.3601883 0.6901147 -0.5935385 -0.4679301 0.6547927 -0.8715506 -0.02205252 0.4898095 -0.5013791 -0.04069954 0.86427 -0.5834968 -0.04409039 0.8109177 -0.4131 -0.09061747 0.9061661 -0.7826379 -0.07028841 0.6184961 -0.380564 -0.09061044 0.9203047 -0.8409967 -0.1276898 0.5257565 -0.6783976 -0.1206288 0.7247244 -0.8238807 -0.1883866 0.5345384 -0.6764192 -0.1631022 0.7182303 -0.7526866 -0.2221705 0.6197607 -0.3552345 -0.5223898 0.7751886 -0.0684452 -0.27492 0.9590278 -0.1409465 -0.5538647 0.82059 -0.07919281 -0.5174406 0.8520469 -0.8951178 -0.001315295 -0.4458276 -0.9304787 -1.11409e-5 -0.3663461 -0.8310511 -1.31719e-4 -0.5561962 -0.7273604 -0.001263499 -0.6862545 -0.7417041 -7.93885e-4 -0.6707268 -0.5257568 2.4992e-4 -0.8506351 -0.4912093 -7.04034e-4 -0.8710414 -0.286161 -9.97851e-4 -0.9581811 -0.2360528 8.91349e-5 -0.9717403 -0.3618207 -0.138096 -0.9219628 -0.3081546 -0.1249469 -0.9430955 -0.8275405 -0.1466832 -0.5419049 -0.5227355 -0.1139484 -0.8448452 -0.9327313 -0.109015 -0.3436978 -0.9187718 -0.107865 -0.3797681 -0.6100252 -0.1344715 -0.7808884 -0.7092959 -0.1077675 -0.6966244 -0.7945274 -0.1130341 -0.5966151 -0.2870625 -0.05319797 -0.9564336 -0.3331019 -0.06960272 -0.9403184 -0.5156738 -0.06090188 -0.8546178 -0.5170717 -0.06125015 -0.8537478 -0.8455649 -0.06947588 -0.5293329 -0.7093984 -0.05189961 -0.7028943 -0.7162809 -0.05553174 -0.6955989 -0.8959414 -0.05548304 -0.4406933 -0.9177467 -0.06056791 -0.3925207 -0.281373 -0.9340249 -0.2200607 -0.3349282 -0.9190397 -0.2078199 -0.4469612 -0.8701742 -0.2074191 -0.6044363 -0.7750649 -0.1842047 -0.6287618 -0.7540464 -0.1899281 -0.8480618 -0.5153951 -0.123122 -0.7565799 -0.6299495 -0.1753579 -0.9127117 -0.3916946 -0.1163306 -0.2292597 -0.7135729 -0.6620073 -0.2534506 -0.6729936 -0.6948687 -0.3992835 -0.6183935 -0.6768767 -0.3060805 -0.9026739 -0.3024808 -0.3263139 -0.8983886 -0.2939683 -0.5096902 -0.8166018 -0.2708829 -0.4903739 -0.4759629 -0.7300636 -0.2790109 -0.4402323 -0.8534334 -0.2794362 -0.4404711 -0.853171 -0.5358468 -0.8037654 -0.2585142 -0.4548025 -0.4367012 -0.7761744 -0.5689787 -0.4624266 -0.6800184 -0.6961938 -0.3427552 -0.6307402 -0.6763904 -0.3524128 -0.6467623 -0.2937571 -0.3608325 -0.8851592 -0.5752955 -0.3574256 -0.7357188 -0.7095361 -0.6626873 -0.2395915 -0.9511428 -0.1688416 -0.2584955 -0.3580892 -0.3164154 -0.8784381 -0.7692019 -0.6023046 -0.2134426 -0.3257061 -0.8790986 -0.3479961 -0.8015743 -0.5594838 -0.2108478 -0.2186655 -0.8943125 -0.3903726 -0.4277716 -0.8340007 -0.3485029 -0.5130787 -0.7985469 -0.3147589 -0.9021415 -0.4014739 -0.1579866 -0.607444 -0.728643 -0.3163722 -0.225055 -0.8665097 -0.445546 -0.2830419 -0.844906 -0.4538958 -0.3212815 -0.8122621 -0.4868354 -0.9193701 -0.3619301 -0.1541607 -0.2638568 -0.7913503 -0.551493 -0.5501976 -0.7400016 -0.3868853 -0.3363129 -0.7385742 -0.584296 -0.6287015 -0.6958728 -0.3471248 -0.6876445 -0.6439601 -0.3353512 -0.5322612 -0.2882106 -0.7960106 -0.7842303 -0.5521446 -0.2830533 -0.5044004 -0.7352212 -0.4528025 -0.3452237 -0.9189181 -0.1908142 -0.8374984 -0.4748508 -0.2703945 -0.9227451 -0.3337029 -0.1928314 -0.9077954 -0.3591033 -0.2166846 -0.5681673 -0.6636605 -0.4865602 -0.6800429 -0.5886451 -0.4370799 -0.6985979 -0.5779978 -0.4217577 -0.4933959 -0.6746094 -0.5490562 -0.5149284 -0.6295201 -0.5818533 -0.7952976 -0.4888088 -0.3585636 -0.451722 -0.8019388 -0.3909491 -0.3516617 -0.6246338 -0.6972566 -0.8481983 -0.404417 -0.3420622 -0.6366624 -0.5398607 -0.5506463 -0.6860292 -0.5267332 -0.5019124 -0.9145201 -0.3062972 -0.2642636 -0.9227289 -0.2949216 -0.2481787 -0.2394578 -0.5444085 -0.8039152 -0.3360484 -0.5592959 -0.7577993 -0.8095732 -0.4158744 -0.4142941 -0.6320956 -0.492968 -0.5978609 -0.6222174 -0.4943262 -0.6070315 -0.7647837 -0.3927935 -0.5107048 -0.5460295 -0.8027503 -0.2396748 -0.8196081 -0.361119 -0.4447873 -0.8731459 -0.2936175 -0.3891079 -0.9119962 -0.2509571 -0.3244742 -0.1836859 -0.2414202 -0.9528777 -0.7717469 -0.2724857 -0.574594 -0.8732954 -0.2219339 -0.4337055 -0.3028939 -0.2478653 -0.9202272 -0.9107361 -0.1864913 -0.3684844 -0.8251156 -0.2399855 -0.51146 -0.3772104 -0.2145901 -0.9009236 -0.5886713 -0.2390615 -0.7722148 -0.56553 -0.2166185 -0.7957717 -0.7250998 -0.2198653 -0.6526023 -0.8108229 -0.1674254 -0.5608342 -0.1832931 -0.1397604 -0.9730728 -0.3158124 -0.93487 -0.1621129 -0.4077106 -0.9004224 -0.1516956 -0.690966 -0.7139208 -0.1135044 -0.6076163 -0.7850536 -0.1203888 -0.7578904 -0.6452029 -0.0965169 -0.2098006 -0.9672795 -0.1426683 -0.3515772 -0.9268148 -0.1319389 -0.8979503 -0.4390956 0.02967172 -0.8700163 -0.4910168 0.04443305 -0.4221301 -0.8989321 -0.1171641 -0.5447733 -0.8317623 -0.1067414 -0.5763999 -0.8115153 -0.09594869 -0.8028535 -0.5924824 -0.06626433 -0.5661652 -0.820008 -0.08392834 -0.3724329 -0.9244728 -0.08151006 -0.6002247 -0.7963837 -0.07418525 -0.7571888 -0.6506227 -0.05792605 -0.8655301 -0.4916219 0.09573811 -0.7741829 -0.631286 -0.04603058 -0.755539 -0.6188594 0.2148811 -0.3776835 -0.9240307 -0.05934941 -0.5470079 -0.835111 -0.05806863 -0.9015452 -0.4311892 -0.03594738 -0.1928225 -0.9809646 -0.02297729 -0.1146958 -0.9933571 -0.009311914 -0.3657577 -0.9289602 -0.05704772 -0.1142514 -0.9333312 0.3403521 -0.1114717 -0.9651752 0.2366667 -0.07917523 -0.9302871 0.3581859 -0.6159237 -0.7878059 3.10891e-4 -0.3722967 -0.9278137 -0.02359944 -0.5481057 -0.8363929 0.005206644 -0.4041105 -0.813629 0.4179744 -0.4327601 -0.8522775 0.2938396 -0.2802334 -0.9290695 0.2414522 -0.2295046 -0.9626568 0.1435955 -0.932505 -0.355221 -0.06521201 -0.2733339 -0.9457784 -0.1754765 -0.8488592 -0.5217661 -0.08484256 -0.8868273 -0.4563823 -0.07247507 -0.7484536 -0.6573208 -0.08801496 -0.9132169 -0.404638 -0.04798954 -0.2294878 -0.9636256 -0.1369717 -0.7491468 -0.6613583 -0.03720533 -0.6525166 -0.7571263 -0.03133481 -0.8454674 -0.5226991 -0.1094104 -0.1628922 -0.9791424 -0.1214345 -0.896627 -0.4394303 -0.05441552 -0.2877392 -0.9525432 -0.09933578 -0.2905511 -0.9522176 -0.09413671 -0.9111937 -0.4108611 -0.03032004 -0.1561626 -0.986239 -0.05427616 -0.7959104 -0.6050941 0.01969653 -0.7036161 -0.7096012 0.03729021 -0.3205332 -0.9471178 -0.01505529 -0.720481 -0.6887304 0.08097904 -0.7887676 -0.6037689 0.1153641 -0.614185 -0.7228845 0.3165673 -0.6624004 -0.7172284 0.2163541 -0.6166314 -0.773814 0.1448367 -0.4663677 -0.8772592 0.1136555 -0.5101845 -0.8531439 0.1088912 -0.3177718 -0.9462596 0.06011778 -0.4413909 -0.8493603 0.2894152 -0.3075116 -0.8504134 0.4268885 -0.3102627 -0.8527194 0.4202461 -0.2529525 -0.9655893 0.06043595 -0.2559781 -0.9230427 0.2871713 -0.1942337 -0.8553164 0.48032 -0.05525386 -0.9961971 0.06736689 -0.07056248 -0.8638777 0.4987347 -0.03058588 -0.8808897 0.4723323 -0.02681291 -0.9781782 0.2060308 -0.01680094 -0.9622162 0.271768 -0.8497481 -0.5162419 -0.1068766 -0.6167717 -0.7718368 -0.1544692 -0.6948838 -0.7064101 -0.1346157 -0.5148013 -0.8426787 -0.1577097 -0.4869197 -0.8605625 -0.1494709 -0.009360432 0.999009 0.04351586 -0.03748595 0.9937298 0.1053378 0.9994353 0.03056854 -0.01396346 0.9082689 -0.3926103 0.1445856 0.1214106 -0.8451977 0.5204809 0.4351623 -0.7734745 0.4608371 0.2054911 -0.8202065 0.5338866 0.8092146 -0.5198711 0.2736898 0.5450575 -0.7125226 0.4418416 0.9622244 -0.2464764 0.115645 0.7862054 -0.5500673 0.2816156 0.4274555 -0.7753443 0.4648904 0.06315785 -0.7707143 0.6340431 0.2203823 -0.9669202 0.1284409 0.2191537 -0.9673056 0.1276394 0.4236199 -0.6876567 0.5896394 0.2067376 -0.777187 0.59434 0.6700302 -0.7365221 0.09270828 0.6083079 -0.6152649 0.5014088 0.7216579 -0.5231258 0.4533754 0.04168057 -0.9672892 0.2502285 0.8130387 -0.4536219 0.3649594 0.9445059 -0.2549495 0.2071458 0.3660542 -0.8737065 0.320377 0.2203862 -0.9469321 0.2339863 0.9297856 -0.291262 0.2250895 0.580172 -0.7954609 0.1750499 0.6239032 -0.758676 0.1874986 0.2460314 -0.9044806 0.3484012 0.7906211 -0.5983574 0.1299487 0.8181557 -0.5600472 0.1302639 0.9635681 -0.2581111 0.07011014 0.6061215 -0.7491577 0.2671696 0.3503207 -0.8693848 0.3484903 0.6632817 -0.6942982 0.2792983 0.1319174 -0.9131433 0.3857035 0.4429216 -0.8213191 0.3595213 0.8610861 -0.4711248 0.1912387 0.9218757 -0.3874791 0.00230515 0.9148077 -0.4038778 0.003093779 0.8139487 -0.580936 0.001013338 0.739561 -0.6730676 0.005430281 0.5743888 -0.8185812 -0.001598 0.2191628 -0.975685 0.002549469 0.2396256 -0.9708653 4.28121e-4 0.3406838 0.001283764 -0.9401772 0.6083663 0.004765152 -0.7936422 0.7354025 0.005046546 -0.6776118 0.3000902 0.001281321 -0.95391 0.9177354 0.009598016 -0.3970763 0.5849315 0.001696169 -0.8110809 0.9268405 -0.002789855 -0.3754451 0.9440962 0.003067553 -0.329656 0.7521974 0.001208007 -0.6589367 0.1454187 -9.98855e-4 -0.9893697 0.5026132 -0.00191915 -0.8645094 0.6934545 6.52371e-4 -0.7205002 0.2970736 -0.001038014 -0.9548541 0.8582589 -8.75253e-4 -0.5132163 0.06078004 2.56664e-4 -0.9981512 0.7773864 -0.004410266 -0.6290079 0.6786513 0.003755033 -0.7344511 0.4428312 -0.002878785 -0.8966004 0.3044661 0.001427531 -0.9525221 0.7783778 6.15825e-4 -0.6277959 0.4440901 -8.63335e-4 -0.8959819 0.9675365 -0.001051306 -0.2527295 0.6833276 -0.00228095 -0.7301084 0.5006784 0.002742826 -0.8656291 0.1085388 0.001450955 -0.9940912 0.4674941 -1.36518e-4 -0.8839963 0.8470867 0.001401484 -0.5314529 0.7832147 -0.002225577 -0.6217475 0.09668087 0.00371021 -0.9953086 0.9671323 -0.001125276 -0.2542717 0.4665263 0.001226663 -0.8845065 0.410382 -0.009164392 -0.9118676 0.8936396 0.001798331 -0.4487819 0.5656901 -0.001099646 -0.8246172 0.779222 7.08456e-4 -0.6267478 0.5549561 3.08527e-4 -0.8318797 0.1326645 -0.6928784 -0.7087452 0.007478833 -0.7009537 -0.7131676 0.1291336 -0.697957 -0.7044009 0.1932238 -0.6767569 -0.7103976 0.1920776 -0.679823 -0.707776 0.3238776 -0.6185403 -0.715899 0.03841859 -0.7212472 -0.6916115 0.3076736 -0.6145222 -0.7264292 0.06529247 -0.6936894 -0.7173088 0.2636454 -0.6416324 -0.720277 0.2851762 -0.6499127 -0.7044772 0.3369374 0.6101421 -0.7170773 0.3267958 0.628116 -0.7061692 0.2947785 0.6324383 -0.7163291 0.2903965 0.6399878 -0.7113969 0.251886 0.6626426 -0.7053072 0.2467704 0.6603077 -0.7092941 0.1941459 0.6649892 -0.7211773 0.1621834 0.7033224 -0.692123 0.1287243 0.6846478 -0.7174172 0.06234985 0.7004667 -0.7109563 0.07016646 0.7066074 -0.7041185 0.005834221 0.7068884 -0.707301 0.006548881 0.7063436 -0.7078389 -0.01090675 0.8455997 0.533706 -0.04926282 0.7947363 0.6049525 -0.01167285 0.6671476 0.7448341 -0.04408442 0.5742034 0.8175251 -0.009582519 0.4494033 0.8932776 -0.04953968 0.3277626 0.9434604 -0.006001055 0.1791087 0.9838111 -0.02495372 0.963186 0.2676751 -0.0307728 0.963794 0.2648664 -0.03629148 0.9495909 0.3113843 -0.02928888 0.9487043 0.3148055 -0.006467938 0.9265372 -0.3761476 -0.05887627 0.9688743 -0.2404499 -0.01536655 0.9924196 -0.1219319 -0.02492588 0.2509707 -0.9676737 -0.03079527 0.2413474 -0.96995 -0.005139052 0.4383511 -0.8987892 -0.05816298 0.5719304 -0.8182377 -0.01822048 0.6749067 -0.7376782 -0.0370602 0.8053286 -0.5916693 -0.02948945 0.8139602 -0.5801718 -0.04962146 2.8745e-4 -0.9987681 -0.0330637 2.64412e-5 -0.9994533 -0.03147977 -0.4359766 -0.8994073 -0.0165109 -0.4047903 -0.9142605 -0.04562616 -0.2323396 -0.971564 -0.0245403 -0.1471472 -0.9888102 -0.0168702 -0.9958178 0.0897901 -0.0393387 -0.997208 -0.06347161 -0.04224079 -0.9966413 -0.07015627 -0.04316651 -0.9511221 -0.3057835 -0.02693831 -0.9628903 -0.2685453 -0.03918218 -0.809444 -0.5858885 -0.017596 -0.836471 -0.5477285 -0.02440005 -0.6579978 -0.7526245 -0.03243505 -0.6435353 -0.7647289 -0.04221993 -0.9511697 0.3057673 -0.01729947 -0.9478372 0.3182854 -0.02822256 -0.9637321 0.2653753 -0.04917871 -0.9662739 0.2527773 -0.01953345 -0.1677842 0.9856303 -0.045502 -0.307615 0.9504224 -0.03536283 -0.3317762 0.9426951 -0.04244142 -0.5879433 0.8077881 -0.02681404 -0.5617716 0.8268579 -0.04957044 -0.8312343 0.5537078 -0.007939875 -0.7830361 0.6219257 -0.04356092 4.00256e-5 0.9990508 -0.04269218 3.35394e-6 0.9990884 -0.0400142 6.89602e-5 0.9991991 -0.04877531 5.13436e-4 0.9988096 -0.8300011 -4.03251e-5 -0.5577618 -0.5000187 -1.00605e-4 -0.8660147 -0.7434641 1.78034e-4 -0.6687759 -0.6831784 -0.004193246 -0.7302395 -0.6795988 0.2101997 -0.702824 -0.5330599 0.1955041 -0.82318 -0.8063185 -0.0612204 -0.5883048 -0.8166131 0.1404229 -0.5598435 -0.77265 -0.240522 -0.5875042 -0.6789677 -0.2250122 -0.6988365 -0.577953 0.3975604 -0.7126824 -0.6189954 -0.4546468 -0.6404224 -0.9490876 0.1918181 -0.2498772 -0.5869393 0.3925728 -0.7080882 -0.8046064 -0.3357039 -0.4898076 -0.7355816 -0.4479771 -0.5081694 -0.8488299 0.4016403 -0.343763 -0.01911634 0.6748773 -0.7376824 -0.9729597 -0.1911982 -0.1295871 -0.7175865 -0.5809704 -0.3841133 -0.5626952 -0.6523117 -0.5078027 -0.7257637 0.5521428 -0.4103723 -0.6279267 0.6164607 -0.4750624 -0.7967095 -0.5714989 -0.1965785 -0.6433648 -0.7427491 -0.1854874 -0.8060127 -0.5911434 0.02988564 -0.3781345 -0.9223914 -0.07879388 -0.7592893 -0.6466605 0.07286894 -0.8306647 0.5530338 -0.06441932 -0.9011176 0.4134795 -0.1304676 -0.6916421 0.6630265 -0.286404 -0.08980363 0.9870383 -0.1330065 -0.4370998 0.8435235 -0.312109 -0.558473 0.828065 0.04915803 -0.711489 0.7013471 -0.04354053 -0.7844805 -0.59705 0.1676962 -0.5728263 -0.7822185 0.2449579 -0.7301926 -0.647843 0.2170677 -0.63233 0.736926 0.2389538 -0.7256084 0.6520049 0.2199595 -0.8872163 0.4385426 0.1432749 -0.8547483 0.4993765 0.1415225 -0.7192634 0.6718853 0.1767207 -0.2463712 0.9332803 0.2613222 -0.9702418 0.1893367 0.1509388 -0.5720116 0.6839096 0.4528471 -0.6130761 0.6654419 0.4258227 -0.7020049 0.3345619 0.6286951 -0.5698717 0.3464149 0.7451463 -0.5006587 0.4353085 0.7482295 -0.3999871 0.6246047 0.6707305 -0.7983508 0.3460142 0.4928594 -0.7942368 -0.2082667 0.5708003 -0.704855 -0.3600808 0.6111637 -0.5854573 -0.4480662 0.6756304 -0.6437984 -0.6115853 0.4598773 -0.6987519 -0.5616833 0.4430098 -0.6881272 0.1113373 0.7169972 -0.642746 0.1363086 0.7538552 -0.7374608 -0.115329 0.6654704 -0.649119 -0.1005796 0.7540083 -0.5540223 1.07626e-5 0.832502 -0.7326836 -2.53906e-4 0.6805694 -0.6739027 -5.90305e-4 0.73882 -0.663654 7.39838e-4 0.7480394 -0.6991221 3.07313e-4 0.7150024 -0.7838357 -5.01964e-5 0.6209684 -0.6458511 -0.005950748 0.7634403 -0.5970039 -0.002527356 0.8022344 0.01738655 0.9998489 -3.84669e-6 0.01737922 0.9998491 0 0.01722729 0.9998516 -2.68459e-4 0.01733958 0.9998497 -1.70131e-5 0.01753181 0.9998464 1.1454e-4 -0.5219836 -8.48765e-4 -0.8529552 -0.6668203 5.38727e-4 -0.7452184 -0.7658174 -7.25405e-4 -0.6430578 -0.9627895 -9.35272e-4 -0.2702509 -0.2987796 -0.7799257 -0.5499516 -0.4020935 -0.6252939 -0.6688262 -0.4659919 -0.5890972 -0.6601637 -0.7523989 -0.4420377 -0.4883633 -0.7958506 -0.423191 -0.4330489 -0.4139548 -0.3734977 -0.8301451 -0.4472486 -0.3761354 -0.8114745 -0.9807555 -0.09018617 -0.1731622 -0.7801864 -0.2591825 -0.5693275 -0.7756202 -0.2593162 -0.5754724 -0.9611511 -0.1374698 -0.2393549 -0.2774112 -0.1889126 -0.9419953 -0.9689356 -0.2313222 -0.08748596 -0.5762234 -0.7894743 -0.2114167 -0.2937704 -0.9099406 -0.2927579 -0.7958426 -0.6009278 -0.07429927 -0.8797292 -0.4700662 -0.07151567 -0.7491882 -0.6288716 -0.2079369 -0.3145736 -0.880033 -0.3557885 -0.4007072 -0.9066954 -0.1316706 -0.57179 -0.8078401 -0.1430061 -0.9008306 -0.4173067 -0.1198307 -0.5174683 -0.774706 -0.3633968 -0.8108588 -0.5233554 -0.2619299 -0.7314122 -0.6224257 -0.2786084 -0.3982247 -0.9172843 -0.002595782 -0.2554771 -0.9668141 0.001471757 -0.7705313 -0.6374018 8.25359e-4 -0.7585182 -0.6516501 0.001530885 -0.9924141 -0.1229402 3.56279e-4 -0.9845918 -0.1748524 0.002397596 -0.4056591 -0.7825981 0.4722085 0.007520258 -0.6542255 0.7562621 -0.7638975 -0.555004 0.329289 -0.7780209 -0.536974 0.3261023 -0.3119825 -0.8906396 0.3307992 -0.4294816 -0.552618 0.7142542 -0.4396661 -0.8325669 0.3369368 -0.5701803 -0.5477234 0.6122855 -0.7992547 -0.5548099 0.2310367 -0.7487448 -0.6175182 0.2409412 -0.8645087 -0.3188867 0.3885049 -0.2898901 -0.9237409 0.2503331 -0.9673829 -0.2402421 0.08033806 -0.8918317 -0.2980763 0.3402745 -0.7359248 -0.6553407 0.1701269 -0.3800351 -0.8997511 0.2145263 -0.8129897 -0.5695005 0.1213138 -0.2836406 -0.9470602 0.1504166 -0.9272421 -0.3658539 0.07983225 -0.3824746 -0.9173437 0.1104248 -0.4832302 -0.6320878 0.6057669 -0.5763477 -0.6217389 0.5303434 -0.703172 -0.7080862 0.06452322 -0.9976773 -0.05423104 0.04122167 -0.2883724 -0.8458126 0.4488235 -0.7648666 -0.5039891 0.4012158 -0.281294 -0.7907415 0.5436928 -0.8682474 -0.3802341 0.3186982 -0.9645332 -0.2124599 0.1566417 0.01175308 -1.03149e-4 -0.999931 0.04736262 0.00118792 -0.9988771 0.03439801 -0.03475373 -0.9988039 0.01288056 -4.33582e-4 -0.999917 0.002185523 -0.001118898 -0.999997 0.01728618 -0.6576848 0.753095 0.04450172 -0.6916269 0.7208827 -0.01263266 -0.7154505 0.6985492 0.05300712 -0.8108932 0.5827887 0.04096162 -0.8139745 0.5794547 -0.006277799 -0.8888592 0.4581374 0.0704602 -0.9074401 0.4142315 -0.008559107 -0.9324147 0.361289 0.0422033 -0.9624226 0.2682567 0.03099644 -0.9647466 0.2613488 0.04374474 -0.9832389 0.1769965 0.004558205 -0.9882283 0.1529192 0.06398034 -0.9953533 0.07196199 -0.003407895 -0.999994 7.0118e-4 -0.005200386 -0.9999861 8.56602e-4 0.04920631 -0.995606 -0.07967126 -0.05196726 -0.9857331 -0.1600927 0.03656625 -0.9736557 -0.2250723 -0.04509484 -0.9578742 -0.2836256 0.05355089 -0.9139656 -0.4022429 0.05540072 -0.9135245 -0.4029937 -0.01491498 -0.8339375 -0.5516574 0.07957899 -0.7357323 -0.672581 -0.02134627 -0.6426488 -0.7658636 0.03251081 -0.5137825 -0.8573043 0.001630246 -0.4619501 -0.8869045 0.06752485 -0.3444938 -0.9363571 -0.04269438 -0.1800265 -0.9827348 0.253718 -0.3687384 -0.8942367 0.4974309 -0.2408108 -0.8334103 0.640411 -0.3465276 -0.685414 0.5138589 -0.260795 -0.8172729 0.8981794 -0.2675846 -0.3488155 0.8349961 -0.2472368 -0.491585 0.7332244 -0.4490029 -0.5106647 0.5405342 -0.6161186 -0.5729056 0.7082646 -0.3964948 -0.5840833 0.9232773 -0.3763957 -0.07671809 0.5244047 -0.8471563 -0.08559215 0.6319026 -0.7448847 -0.2141167 0.3048267 -0.9283396 -0.2127592 0.7735345 -0.5751408 -0.266191 0.6188445 -0.7248202 -0.302766 0.5959268 -0.7950577 -0.1129368 0.7217317 -0.6921688 -0.002417325 0.8627808 -0.5055712 0.002704501 0.8536391 -0.5039889 0.1315134 0.6677092 -0.5222085 0.5305306 0.6790301 -0.7309229 0.06833714 0.8416066 -0.3589228 0.4035751 0.8954865 -0.2907016 0.3370409 0.7139958 -0.669358 0.2053534 0.4529529 -0.813012 0.3658486 0.6721762 -0.7307428 0.1191397 0.868691 -0.4184268 0.2651324 0.6290362 -0.6285623 0.4574089 0.01802378 -0.9998376 -8.32091e-5 0.02031451 0.9997937 0 0.0184195 0.9998095 -0.006467461 0.8174431 -0.3517671 0.4561214 0.9527996 -0.1866111 0.2394772 0.6388378 -0.4820944 0.5995593 0.7724793 -0.4016821 0.4918611 0.5917028 -0.5173369 0.618264 0.3950634 -0.5830751 0.7098932 0.08144503 -0.6422971 0.7621162 0.9993572 -0.0268073 0.02380055 0.9387736 -0.2209494 0.2643587 0.1685807 -0.5821439 0.7954177 -0.8416066 -0.3589228 0.4035751 -0.8174431 -0.3517671 0.4561214 -0.6388633 -0.482093 0.5995333 -0.3950634 -0.5830751 0.7098932 -0.9993572 -0.0268073 0.02380055 -0.9387736 -0.2209494 0.2643587 0.8414792 -0.3590322 0.4037435 0.8175181 -0.3517578 0.4559941 0.6388635 -0.4820924 0.5995334 0.2125322 -0.509186 0.8340023 0.9993522 -0.02694588 0.02385747 0.9387846 -0.2209367 0.2643301 0.7822449 0.622971 0 0 5.89904e-6 1 -0.943921 -0.3301715 0 0.02425062 -0.9997059 -6.77216e-7 0.3441495 0.938915 0 -0.3441466 0.938916 0 0.0181936 0.9998346 0 1 2.877e-4 0 -0.02425068 -0.9997059 0 0.6442047 -0.7648532 0 0.9439194 -0.3301764 0 -0.6442038 -0.7648539 0 -0.0181936 0.9998345 6.99034e-7 -0.782244 0.6229723 0 -1 2.87695e-4 0 0.782244 0.6229723 0 0.01743829 -1.12488e-4 0.999848 -0.01757544 7.24527e-6 0.9998456 0 5.89904e-6 1 -0.01754611 -2.34883e-4 0.9998461 0.01745605 -1.23084e-4 0.9998477 0.01754611 -2.34883e-4 0.9998461 0.01757568 7.22191e-6 0.9998456 -0.01743829 -1.12484e-4 0.999848 -0.01745599 -1.23081e-4 0.9998477 0 0.1659705 0.9861307 0 -0.1361732 0.9906851 -0.9439194 -0.3301764 0 0.02425068 -0.9997059 0 0.3441466 0.938916 0 -0.3441495 0.938915 0 0.0181936 0.9998345 -6.99034e-7 1 2.87695e-4 0 -0.02425062 -0.9997059 6.77216e-7 0.6442038 -0.7648539 0 0.943921 -0.3301715 0 -0.6442047 -0.7648532 0 -0.0181936 0.9998346 0 -0.7822449 0.622971 0 -1 2.877e-4 0 -0.1102538 0.9039703 0.4131365 -0.7311016 0.6822687 3.11173e-7 0 0.9453283 0.3261206 0.1850436 0.8729357 0.4513782 0.1102519 0.9039698 0.4131383 0.7310634 0.6823096 4.91107e-6 -0.1850159 0.8729384 0.4513844 0 0.9453019 0.3261968 0.4804584 -0.8770099 0.003657162 -0.1850437 0.8729355 0.4513784 0.2002525 -0.9797381 -0.003499805 -0.02734595 -0.9996249 0.001548588 0.290118 -0.9569733 0.005794227 -0.3952281 -0.9185686 0.005154311 0.2046815 -0.9788273 -0.001634836 -0.1980775 -0.9801765 -0.004418075 -0.4804379 -0.8770236 0.003002047 -0.3271751 -0.9449379 -0.006992518 0.4206321 -0.9072296 -0.001770913 -0.2390277 -0.9709784 -0.008172035 1.29394e-6 -0.9988902 -0.04710227 -0.3431033 -0.9391663 -0.01571428 2.42765e-5 0.9452299 0.3264057 0.4598581 -0.8879923 7.11529e-4 0.0864495 -0.9962502 -0.003487706 0.02732276 -0.9996255 0.001559197 0.1135271 -0.9935253 0.00438261 -0.01792263 -0.9998395 -6.69956e-5 -0.4598631 -0.8879896 7.11251e-4 -0.2047106 -0.9788212 -0.001633763 0.3271746 -0.9449382 -0.006992578 0.01792281 -0.9998394 -6.6316e-5 0.1850274 0.8729594 0.451339 -0.08644551 -0.9962506 -0.003486514 0.1103088 0.9039428 0.4131822 0.3952153 -0.918574 0.005155801 -0.2901195 -0.956973 0.00579518 -0.1102757 0.9039758 0.4131187 3.84575e-7 0.9453015 0.326198 0 0.9452952 0.3262163 -0.4206334 -0.907229 -0.001772046 -0.01802378 -0.9998376 -8.32178e-5 -0.4896597 -0.8718804 0.007632195 0.4837058 -0.8752266 0.002692818 0.4836842 -0.8752385 0.002700269 0.01810359 -0.9998362 -8.25113e-5 -0.1102519 0.9039694 0.4131391 -0.7311171 0.6822521 0 -0.7311007 0.6822696 -1.61415e-6 -0.7310996 0.6822709 1.87864e-6 0.1850453 0.8729346 0.4513794 0.1102526 0.9039692 0.4131394 0.7311134 0.6822561 -1.97937e-6 0.7311016 0.6822686 -3.11248e-7 0.7311024 0.6822677 4.23477e-7 0.7310934 0.6822775 0 -2.16306e-6 0.9453022 0.326196 0.480448 -0.8770157 0.003654778 -0.1850422 0.872935 0.4513801 0.2002557 -0.9797374 -0.003499925 -0.02732276 -0.9996255 0.001559019 0.2901195 -0.956973 0.00579518 -0.3952345 -0.9185659 0.00515151 0.2046823 -0.9788271 -0.001633524 -0.4956629 -0.8684954 0.005856275 -0.3271694 -0.9449398 -0.00699228 -0.3271737 -0.9449384 -0.006991982 0.4206551 -0.9072189 -0.001770973 0.4206348 -0.9072285 -0.001772463 -0.1135271 -0.9935253 0.004383146 0.4302322 -0.9018105 -0.04047411 9.00899e-4 -0.9988914 -0.04706799 -1.46959e-4 -0.9988966 -0.0469641 -2.16356e-6 -0.9988902 -0.04709899 0 -0.9988902 -0.04709988 0.009410798 -0.9226575 -0.3855059 -0.2626345 -0.9642494 -0.03530317 -0.0609681 -0.9720597 -0.2266778 -0.452348 -0.8915483 -0.0228694 -0.2626395 -0.964248 -0.03530526 0.0864464 -0.9962505 -0.003487884 0.02737605 -0.9996241 0.001551687 0.1135662 -0.9935209 0.004371464 -0.2046814 -0.9788274 -0.00163412 -0.08644592 -0.9962505 -0.003486812 -0.08645534 -0.9962496 -0.003486692 0.3952279 -0.9185687 0.00515443 -0.2901129 -0.9569751 0.005789995 0 0.9453023 0.3261958 -8.49572e-7 0.9453026 0.326195
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 2 0 0 0 1 0 2 1 3 1 0 1 21 2 2 2 20 2 20 3 80 3 79 3 2 4 80 4 20 4 21 5 3 5 2 5 23 6 24 6 887 6 23 7 887 7 886 7 23 8 886 8 25 8 44 9 27 9 30 9 32 10 37 10 31 10 890 11 891 11 26 11 31 12 37 12 29 12 28 13 38 13 27 13 32 14 39 14 37 14 26 15 39 15 32 15 28 16 910 16 38 16 898 17 910 17 28 17 27 18 40 18 30 18 26 19 891 19 39 19 27 20 38 20 40 20 880 21 39 21 891 21 43 22 31 22 42 22 42 23 35 23 41 23 35 24 44 24 34 24 37 25 28 25 29 25 38 26 45 26 40 26 35 27 29 27 44 27 42 28 31 28 35 28 38 29 910 29 45 29 37 30 898 30 28 30 39 31 898 31 37 31 882 32 45 32 910 32 884 33 45 33 882 33 916 34 26 34 43 34 880 35 898 35 39 35 34 36 36 36 33 36 41 37 34 37 33 37 29 38 27 38 44 38 43 39 32 39 31 39 44 40 36 40 34 40 890 41 26 41 916 41 44 42 30 42 36 42 28 43 27 43 29 43 43 44 26 44 32 44 41 45 35 45 34 45 31 46 29 46 35 46 46 47 48 47 47 47 41 48 839 48 48 48 41 49 48 49 46 49 841 50 839 50 41 50 844 51 49 51 845 51 724 52 47 52 50 52 33 53 841 53 41 53 724 54 50 54 49 54 51 55 841 55 33 55 46 56 47 56 724 56 52 57 925 57 1759 57 53 58 925 58 52 58 60 59 58 59 56 59 63 60 64 60 59 60 63 61 59 61 57 61 61 62 740 62 71 62 740 63 60 63 71 63 71 64 60 64 56 64 71 65 56 65 65 65 64 66 74 66 75 66 64 67 63 67 74 67 75 68 74 68 77 68 74 69 78 69 77 69 78 70 79 70 77 70 77 71 79 71 80 71 939 72 82 72 87 72 86 73 82 73 83 73 87 74 82 74 86 74 85 75 84 75 81 75 934 76 82 76 939 76 934 77 88 77 82 77 974 78 84 78 975 78 975 79 84 79 85 79 974 80 89 80 84 80 89 81 90 81 84 81 88 82 91 82 82 82 90 83 92 83 84 83 82 84 84 84 83 84 83 85 84 85 92 85 886 86 888 86 94 86 25 87 94 87 95 87 25 88 95 88 96 88 25 89 96 89 645 89 25 90 886 90 94 90 97 91 98 91 25 91 97 92 25 92 645 92 102 93 991 93 998 93 100 94 103 94 102 94 100 95 998 95 101 95 100 96 102 96 998 96 105 97 104 97 103 97 99 98 105 98 103 98 99 99 103 99 100 99 492 100 104 100 105 100 936 101 107 101 111 101 938 102 112 102 106 102 940 103 112 103 938 103 936 104 110 104 935 104 937 105 107 105 936 105 937 106 704 106 107 106 925 107 108 107 940 107 113 108 109 108 54 108 940 109 108 109 112 109 925 110 53 110 108 110 114 111 109 111 113 111 937 112 106 112 704 112 407 113 109 113 114 113 938 114 106 114 937 114 53 115 54 115 108 115 936 116 111 116 110 116 54 117 109 117 108 117 128 118 122 118 173 118 115 119 120 119 119 119 115 120 116 120 126 120 118 121 120 121 126 121 126 122 120 122 115 122 118 123 127 123 120 123 116 124 124 124 126 124 121 125 126 125 124 125 129 126 127 126 118 126 126 127 121 127 118 127 130 128 121 128 124 128 127 129 129 129 125 129 125 130 131 130 123 130 135 131 132 131 300 131 134 132 138 132 132 132 132 133 138 133 300 133 136 134 138 134 134 134 129 135 131 135 125 135 136 136 141 136 138 136 140 137 141 137 136 137 138 138 141 138 300 138 300 139 141 139 147 139 143 140 144 140 140 140 140 141 144 141 141 141 123 142 131 142 152 142 128 143 142 143 122 143 117 144 137 144 139 144 141 145 144 145 147 145 145 146 144 146 143 146 122 147 137 147 117 147 121 148 129 148 118 148 131 149 148 149 152 149 145 150 147 150 144 150 129 151 149 151 131 151 145 152 150 152 147 152 122 153 151 153 137 153 147 154 150 154 153 154 121 155 154 155 129 155 130 156 154 156 121 156 137 157 146 157 139 157 150 158 158 158 153 158 122 159 142 159 151 159 137 160 155 160 146 160 160 161 158 161 159 161 153 162 160 162 161 162 158 163 160 163 153 163 137 164 151 164 155 164 163 165 158 165 150 165 159 166 158 166 163 166 160 167 166 167 161 167 149 168 148 168 131 168 146 169 155 169 164 169 151 170 165 170 155 170 130 171 132 171 154 171 129 172 168 172 149 172 154 173 168 173 129 173 133 174 132 174 130 174 165 175 167 175 155 175 155 176 167 176 164 176 152 177 148 177 627 177 149 178 172 178 148 178 930 179 171 179 929 179 933 180 170 180 931 180 931 181 171 181 930 181 165 182 119 182 167 182 164 183 167 183 174 183 119 184 120 184 167 184 170 185 173 185 931 185 929 186 175 186 932 186 132 187 168 187 154 187 931 188 173 188 171 188 171 189 175 189 929 189 168 190 172 190 149 190 120 191 127 191 174 191 134 192 132 192 133 192 174 193 127 193 176 193 132 194 135 194 168 194 171 195 139 195 175 195 171 196 173 196 117 196 172 197 156 197 148 197 627 198 156 198 157 198 128 199 173 199 170 199 148 200 156 200 627 200 127 201 125 201 176 201 151 202 142 202 165 202 173 203 122 203 117 203 176 204 125 204 123 204 142 205 162 205 165 205 171 206 117 206 139 206 162 207 169 207 165 207 169 208 119 208 165 208 169 209 115 209 119 209 167 210 120 210 174 210 169 211 116 211 115 211 177 212 180 212 183 212 182 213 203 213 179 213 180 214 185 214 183 214 180 215 186 215 185 215 182 216 179 216 186 216 190 217 194 217 191 217 192 218 194 218 193 218 189 219 183 219 188 219 183 220 189 220 190 220 177 221 198 221 197 221 189 222 188 222 185 222 194 223 172 223 168 223 195 224 177 224 197 224 199 225 190 225 191 225 177 226 195 226 196 226 178 227 202 227 210 227 196 228 180 228 177 228 204 229 178 229 210 229 191 230 194 230 192 230 183 231 185 231 188 231 177 232 205 232 198 232 201 233 206 233 190 233 202 234 178 234 179 234 202 235 179 235 203 235 184 236 179 236 181 236 177 237 183 237 190 237 193 238 194 238 209 238 208 239 178 239 204 239 208 240 204 240 207 240 180 241 182 241 186 241 201 242 190 242 199 242 187 243 179 243 184 243 181 244 178 244 208 244 194 245 190 245 156 245 187 246 186 246 179 246 172 247 194 247 156 247 203 248 200 248 202 248 200 249 210 249 202 249 180 250 196 250 203 250 206 251 205 251 177 251 156 252 190 252 189 252 177 253 190 253 206 253 203 254 182 254 180 254 178 255 181 255 179 255 196 256 200 256 203 256 211 257 161 257 212 257 161 258 166 258 212 258 218 259 217 259 219 259 216 260 233 260 217 260 219 261 196 261 195 261 221 262 229 262 204 262 231 263 226 263 213 263 227 264 230 264 233 264 221 265 226 265 229 265 230 266 232 266 233 266 194 267 240 267 209 267 235 268 230 268 227 268 230 269 242 269 232 269 232 270 242 270 213 270 240 271 194 271 168 271 213 272 221 272 232 272 247 273 249 273 244 273 249 274 220 274 235 274 250 275 217 275 218 275 213 276 226 276 221 276 246 277 153 277 161 277 252 278 251 278 246 278 214 279 255 279 256 279 256 280 258 280 308 280 260 281 257 281 258 281 263 282 256 282 262 282 256 283 263 283 258 283 257 284 260 284 264 284 262 285 267 285 268 285 258 286 263 286 269 286 241 287 240 287 270 287 265 288 263 288 262 288 260 289 271 289 264 289 269 290 273 290 258 290 258 291 273 291 260 291 271 292 260 292 276 292 273 293 276 293 260 293 265 294 275 294 274 294 266 295 272 295 275 295 265 296 274 296 263 296 278 297 269 297 274 297 274 298 269 298 263 298 268 299 267 299 225 299 225 300 267 300 279 300 276 301 273 301 277 301 277 302 273 302 281 302 275 303 239 303 274 303 274 304 239 304 283 304 283 305 282 305 274 305 283 306 285 306 286 306 276 307 287 307 271 307 290 308 291 308 243 308 292 309 282 309 283 309 283 310 286 310 292 310 282 311 292 311 284 311 209 312 240 312 259 312 257 313 264 313 250 313 276 314 277 314 287 314 293 315 294 315 311 315 292 316 295 316 284 316 288 317 284 317 295 317 297 318 296 318 292 318 271 319 287 319 280 319 135 320 240 320 168 320 302 321 246 321 304 321 264 322 280 322 216 322 227 323 244 323 235 323 224 324 247 324 244 324 237 325 232 325 248 325 237 326 248 326 200 326 247 327 223 327 305 327 240 328 299 328 270 328 246 329 299 329 240 329 248 330 221 330 204 330 235 331 220 331 215 331 300 332 240 332 135 332 299 333 246 333 302 333 254 334 253 334 252 334 275 335 265 335 266 335 264 336 271 336 280 336 282 337 278 337 274 337 147 338 153 338 246 338 292 339 298 339 297 339 213 340 215 340 231 340 229 341 207 341 204 341 211 342 252 342 246 342 306 343 288 343 295 343 295 344 307 344 306 344 211 345 246 345 161 345 251 346 252 346 253 346 253 347 214 347 261 347 257 348 261 348 308 348 214 349 256 349 308 349 308 350 258 350 257 350 309 351 262 351 255 351 262 352 256 352 255 352 309 353 267 353 262 353 268 354 266 354 262 354 262 355 266 355 265 355 279 356 267 356 309 356 310 357 250 357 218 357 268 358 272 358 266 358 222 359 227 359 216 359 254 360 214 360 253 360 228 361 268 361 225 361 255 362 214 362 254 362 308 363 261 363 214 363 275 364 234 364 236 364 228 365 272 365 268 365 238 366 272 366 228 366 269 367 278 367 273 367 273 368 278 368 281 368 238 369 234 369 272 369 277 370 281 370 289 370 243 371 234 371 238 371 243 372 236 372 234 372 278 373 282 373 281 373 272 374 234 374 275 374 243 375 238 375 290 375 285 376 283 376 239 376 281 377 288 377 289 377 259 378 240 378 241 378 282 379 284 379 281 379 275 380 236 380 239 380 284 381 288 381 281 381 311 382 286 382 245 382 293 383 311 383 245 383 301 384 289 384 306 384 243 385 245 385 236 385 286 386 311 386 294 386 236 387 285 387 239 387 245 388 285 388 236 388 261 389 250 389 310 389 257 390 250 390 261 390 301 391 277 391 289 391 286 392 285 392 245 392 296 393 295 393 292 393 303 394 287 394 224 394 224 395 287 395 277 395 250 396 264 396 216 396 287 397 303 397 222 397 243 398 293 398 245 398 223 399 224 399 301 399 306 400 289 400 288 400 287 401 222 401 280 401 291 402 293 402 243 402 300 403 147 403 240 403 240 404 147 404 246 404 219 405 217 405 196 405 216 406 227 406 233 406 294 407 298 407 286 407 303 408 244 408 222 408 233 409 237 409 217 409 237 410 233 410 232 410 222 411 244 411 227 411 217 412 237 412 196 412 224 413 244 413 303 413 298 414 292 414 286 414 237 415 200 415 196 415 304 416 246 416 251 416 235 417 242 417 230 417 223 418 247 418 224 418 277 419 301 419 224 419 235 420 244 420 249 420 200 421 248 421 210 421 221 422 248 422 232 422 307 423 301 423 306 423 242 424 215 424 213 424 305 425 301 425 307 425 305 426 223 426 301 426 204 427 210 427 248 427 235 428 215 428 242 428 216 429 280 429 222 429 250 430 216 430 217 430 295 431 317 431 307 431 319 432 290 432 312 432 315 433 296 433 316 433 317 434 295 434 314 434 238 435 228 435 313 435 290 436 238 436 312 436 295 437 315 437 314 437 291 438 290 438 322 438 322 439 290 439 319 439 296 440 315 440 295 440 312 441 238 441 313 441 323 442 298 442 324 442 297 443 318 443 296 443 296 444 318 444 316 444 298 445 318 445 297 445 318 446 298 446 323 446 294 447 324 447 298 447 313 448 228 448 320 448 294 449 325 449 324 449 293 450 325 450 294 450 321 451 320 451 225 451 228 452 225 452 320 452 293 453 291 453 325 453 325 454 291 454 322 454 321 455 225 455 279 455 326 456 307 456 317 456 208 457 329 457 181 457 330 458 329 458 331 458 208 459 332 459 329 459 207 460 332 460 208 460 329 461 332 461 331 461 229 462 332 462 207 462 331 463 332 463 333 463 229 464 334 464 332 464 226 465 334 465 229 465 231 466 334 466 226 466 333 467 332 467 334 467 215 468 334 468 231 468 335 469 334 469 215 469 336 470 337 470 338 470 185 471 337 471 336 471 338 472 342 472 339 472 340 473 987 473 984 473 337 474 342 474 338 474 339 475 341 475 343 475 339 476 342 476 341 476 343 477 344 477 346 477 344 478 345 478 346 478 346 479 345 479 347 479 345 480 327 480 347 480 579 481 348 481 340 481 347 482 349 482 350 482 327 483 349 483 347 483 579 484 349 484 348 484 350 485 349 485 579 485 352 486 988 486 340 486 352 487 340 487 348 487 348 488 349 488 328 488 328 489 349 489 327 489 337 490 185 490 351 490 345 491 353 491 327 491 353 492 345 492 354 492 988 493 352 493 989 493 989 494 352 494 357 494 328 495 358 495 356 495 352 496 356 496 357 496 357 497 356 497 358 497 327 498 362 498 328 498 247 499 360 499 249 499 353 500 362 500 327 500 360 501 363 501 249 501 249 502 363 502 220 502 363 503 365 503 220 503 358 504 328 504 366 504 220 505 365 505 215 505 328 506 362 506 366 506 365 507 335 507 215 507 362 508 353 508 364 508 340 509 988 509 987 509 371 510 370 510 361 510 187 511 359 511 186 511 372 512 359 512 375 512 373 513 370 513 374 513 370 514 371 514 374 514 375 515 359 515 187 515 361 516 372 516 371 516 371 517 372 517 376 517 184 518 375 518 187 518 376 519 372 519 377 519 375 520 377 520 372 520 378 521 329 521 330 521 348 522 356 522 352 522 348 523 328 523 356 523 337 524 351 524 342 524 342 525 351 525 355 525 344 526 354 526 345 526 354 527 344 527 367 527 342 528 355 528 367 528 361 529 351 529 359 529 361 530 355 530 351 530 990 531 989 531 357 531 185 532 186 532 351 532 351 533 186 533 359 533 370 534 373 534 367 534 367 535 373 535 368 535 359 536 372 536 361 536 354 537 368 537 364 537 369 538 189 538 336 538 336 539 189 539 185 539 367 540 341 540 342 540 343 541 341 541 367 541 367 542 344 542 343 542 354 543 367 543 368 543 367 544 355 544 370 544 370 545 355 545 361 545 353 546 354 546 364 546 181 547 375 547 184 547 375 548 329 548 377 548 377 549 329 549 378 549 181 550 329 550 375 550 382 551 998 551 1000 551 986 552 985 552 386 552 998 553 382 553 101 553 982 554 385 554 383 554 1004 555 379 555 981 555 386 556 985 556 389 556 1004 557 380 557 379 557 387 558 981 558 379 558 985 559 387 559 389 559 388 560 983 560 986 560 980 561 383 561 384 561 986 562 386 562 388 562 1002 563 380 563 1004 563 387 564 985 564 981 564 983 565 388 565 385 565 1003 566 980 566 384 566 980 567 982 567 383 567 385 568 982 568 983 568 1001 569 999 569 381 569 380 570 1002 570 390 570 1002 571 1001 571 390 571 390 572 1001 572 381 572 384 573 1000 573 1003 573 382 574 1000 574 384 574 392 575 993 575 396 575 393 576 996 576 391 576 997 577 996 577 395 577 395 578 996 578 393 578 391 579 995 579 394 579 397 580 999 580 997 580 996 581 995 581 391 581 397 582 381 582 999 582 394 583 994 583 392 583 392 584 994 584 993 584 394 585 995 585 994 585 397 586 997 586 395 586 398 587 189 587 369 587 157 588 156 588 398 588 156 589 189 589 398 589 247 590 305 590 360 590 305 591 326 591 360 591 305 592 307 592 326 592 401 593 402 593 403 593 401 594 403 594 417 594 406 595 400 595 399 595 406 596 407 596 400 596 408 597 409 597 402 597 672 598 405 598 404 598 408 599 402 599 401 599 109 600 407 600 406 600 672 601 410 601 405 601 411 602 409 602 408 602 412 603 410 603 672 603 411 604 413 604 409 604 414 605 415 605 98 605 414 606 98 606 97 606 416 607 413 607 411 607 417 608 410 608 412 608 416 609 418 609 413 609 417 610 403 610 410 610 419 611 418 611 416 611 404 612 415 612 414 612 404 613 405 613 415 613 399 614 418 614 419 614 399 615 400 615 418 615 98 616 23 616 25 616 423 617 418 617 400 617 422 618 418 618 423 618 421 619 23 619 98 619 421 620 98 620 415 620 422 621 413 621 418 621 423 622 407 622 424 622 422 623 409 623 413 623 424 624 407 624 114 624 423 625 400 625 407 625 421 626 415 626 405 626 421 627 405 627 410 627 421 628 410 628 403 628 421 629 403 629 402 629 421 630 402 630 409 630 421 631 409 631 422 631 420 632 421 632 422 632 309 633 255 633 279 633 279 634 255 634 321 634 255 635 425 635 321 635 321 636 425 636 426 636 426 637 425 637 427 637 425 638 428 638 427 638 62 639 76 639 427 639 428 640 62 640 427 640 428 641 429 641 62 641 76 642 1834 642 427 642 254 643 425 643 255 643 252 644 435 644 254 644 17 645 438 645 878 645 74 646 63 646 436 646 63 647 437 647 439 647 439 648 437 648 440 648 440 649 441 649 212 649 17 650 442 650 438 650 437 651 441 651 440 651 17 652 18 652 442 652 18 653 21 653 442 653 441 654 252 654 212 654 21 655 443 655 442 655 212 656 252 656 211 656 63 657 439 657 436 657 21 658 444 658 443 658 21 659 20 659 444 659 20 660 445 660 444 660 20 661 79 661 445 661 58 662 446 662 57 662 78 663 436 663 79 663 79 664 436 664 445 664 78 665 74 665 436 665 57 666 437 666 63 666 446 667 437 667 57 667 447 668 494 668 451 668 453 669 99 669 448 669 454 670 447 670 451 670 454 671 448 671 447 671 454 672 451 672 456 672 457 673 455 673 450 673 458 674 455 674 457 674 456 675 455 675 458 675 317 676 460 676 326 676 458 677 457 677 427 677 426 678 427 678 457 678 458 679 427 679 462 679 316 680 323 680 464 680 316 681 318 681 323 681 463 682 493 682 460 682 465 683 315 683 316 683 465 684 316 684 464 684 467 685 317 685 314 685 323 686 468 686 464 686 465 687 464 687 469 687 464 688 468 688 469 688 315 689 467 689 314 689 466 690 468 690 323 690 465 691 470 691 471 691 471 692 463 692 460 692 469 693 470 693 465 693 474 694 463 694 471 694 473 695 469 695 468 695 479 696 463 696 474 696 478 697 473 697 477 697 479 698 472 698 463 698 479 699 474 699 471 699 475 700 325 700 322 700 482 701 472 701 479 701 481 702 483 702 484 702 470 703 479 703 471 703 477 704 475 704 487 704 481 705 479 705 470 705 476 706 472 706 482 706 481 707 482 707 479 707 485 708 484 708 483 708 486 709 485 709 478 709 482 710 481 710 484 710 487 711 475 711 322 711 484 712 485 712 488 712 484 713 476 713 482 713 100 714 476 714 484 714 488 715 100 715 484 715 460 716 317 716 467 716 460 717 467 717 471 717 491 718 489 718 487 718 488 719 486 719 448 719 465 720 467 720 315 720 100 721 488 721 99 721 465 722 471 722 467 722 475 723 466 723 324 723 455 724 449 724 450 724 452 725 449 725 455 725 313 726 320 726 450 726 105 727 453 727 459 727 457 728 450 728 320 728 459 729 492 729 105 729 459 730 462 730 492 730 459 731 461 731 462 731 321 732 426 732 320 732 457 733 320 733 426 733 468 734 466 734 473 734 483 735 470 735 469 735 480 736 483 736 469 736 483 737 481 737 470 737 478 738 480 738 473 738 466 739 323 739 324 739 480 740 485 740 483 740 477 741 489 741 478 741 325 742 475 742 324 742 477 743 487 743 489 743 488 744 485 744 486 744 322 745 491 745 487 745 473 746 466 746 475 746 452 747 489 747 491 747 449 748 319 748 312 748 491 749 449 749 452 749 477 750 473 750 475 750 450 751 312 751 313 751 450 752 449 752 312 752 455 753 451 753 452 753 99 754 453 754 105 754 453 755 448 755 454 755 455 756 456 756 451 756 454 757 458 757 461 757 454 758 456 758 458 758 453 759 461 759 459 759 453 760 454 760 461 760 458 761 462 761 461 761 469 762 473 762 480 762 489 763 490 763 478 763 485 764 480 764 478 764 486 765 478 765 490 765 491 766 322 766 319 766 490 767 448 767 486 767 494 768 490 768 489 768 449 769 491 769 319 769 452 770 494 770 489 770 447 771 490 771 494 771 99 772 488 772 448 772 447 773 448 773 490 773 451 774 494 774 452 774 360 775 326 775 460 775 495 776 360 776 460 776 495 777 460 777 493 777 496 778 495 778 493 778 496 779 493 779 463 779 497 780 463 780 472 780 497 781 496 781 463 781 498 782 497 782 472 782 498 783 472 783 476 783 499 784 498 784 476 784 499 785 476 785 100 785 101 786 499 786 100 786 500 787 363 787 360 787 382 788 384 788 543 788 495 789 500 789 360 789 496 790 500 790 495 790 496 791 501 791 500 791 504 792 547 792 502 792 503 793 331 793 333 793 509 794 547 794 504 794 510 795 507 795 511 795 517 796 514 796 515 796 518 797 503 797 333 797 516 798 519 798 509 798 521 799 517 799 520 799 381 800 519 800 516 800 523 801 504 801 502 801 518 802 502 802 503 802 524 803 521 803 522 803 524 804 522 804 525 804 529 805 509 805 504 805 529 806 504 806 523 806 523 807 502 807 518 807 497 808 530 808 496 808 516 809 509 809 529 809 505 810 531 810 507 810 505 811 507 811 506 811 506 812 532 812 505 812 507 813 531 813 533 813 505 814 523 814 518 814 505 815 518 815 531 815 512 816 508 816 506 816 390 817 381 817 516 817 379 818 508 818 512 818 510 819 513 819 512 819 535 820 516 820 529 820 387 821 512 821 513 821 534 822 518 822 333 822 532 823 523 823 505 823 532 824 529 824 523 824 534 825 333 825 334 825 531 826 518 826 534 826 390 827 516 827 535 827 517 828 536 828 514 828 515 829 520 829 517 829 535 830 529 830 532 830 380 831 390 831 535 831 521 832 540 832 517 832 520 833 522 833 521 833 508 834 535 834 532 834 533 835 531 835 534 835 526 836 527 836 525 836 380 837 535 837 508 837 528 838 525 838 527 838 527 839 526 839 501 839 530 840 527 840 501 840 530 841 501 841 496 841 537 842 527 842 530 842 497 843 537 843 530 843 538 844 533 844 534 844 539 845 534 845 334 845 508 846 532 846 506 846 510 847 506 847 507 847 510 848 512 848 506 848 538 849 534 849 539 849 540 850 536 850 517 850 524 851 541 851 521 851 539 852 334 852 335 852 538 853 507 853 533 853 387 854 379 854 512 854 537 855 528 855 527 855 537 856 543 856 528 856 511 857 507 857 538 857 544 858 538 858 539 858 498 859 537 859 497 859 498 860 499 860 537 860 545 861 510 861 511 861 515 862 538 862 544 862 545 863 513 863 510 863 544 864 539 864 335 864 511 865 538 865 515 865 540 866 546 866 536 866 541 867 540 867 521 867 542 868 541 868 524 868 389 869 513 869 545 869 514 870 545 870 511 870 514 871 511 871 515 871 499 872 543 872 537 872 548 873 389 873 545 873 508 874 379 874 380 874 536 875 545 875 514 875 513 876 389 876 387 876 548 877 536 877 388 877 520 878 515 878 544 878 546 879 388 879 536 879 541 880 546 880 540 880 548 881 545 881 536 881 542 882 383 882 541 882 542 883 384 883 383 883 386 884 389 884 548 884 543 885 384 885 528 885 499 886 382 886 543 886 520 887 544 887 549 887 549 888 544 888 335 888 509 889 562 889 547 889 388 890 386 890 548 890 549 891 335 891 365 891 101 892 382 892 499 892 522 893 520 893 549 893 385 894 388 894 546 894 385 895 546 895 541 895 550 896 549 896 365 896 526 897 549 897 550 897 526 898 522 898 549 898 550 899 365 899 363 899 383 900 385 900 541 900 525 901 522 901 526 901 528 902 524 902 525 902 500 903 550 903 363 903 528 904 542 904 524 904 384 905 542 905 528 905 501 906 526 906 550 906 501 907 550 907 500 907 552 908 553 908 554 908 330 909 552 909 378 909 553 910 551 910 555 910 555 911 551 911 395 911 393 912 558 912 555 912 556 913 568 913 394 913 557 914 554 914 555 914 557 915 552 915 554 915 556 916 394 916 392 916 560 917 392 917 396 917 558 918 393 918 391 918 557 919 561 919 552 919 397 920 519 920 381 920 558 921 563 921 557 921 558 922 391 922 563 922 377 923 378 923 559 923 565 924 557 924 563 924 561 925 557 925 566 925 377 926 559 926 567 926 567 927 561 927 566 927 565 928 566 928 557 928 366 929 362 929 571 929 366 930 571 930 560 930 358 931 366 931 560 931 565 932 568 932 572 932 572 933 566 933 565 933 573 934 357 934 560 934 567 935 566 935 574 935 377 936 567 936 376 936 568 937 556 937 572 937 574 938 376 938 567 938 564 939 502 939 547 939 574 940 575 940 374 940 374 941 371 941 574 941 364 942 368 942 576 942 519 943 562 943 509 943 331 944 503 944 564 944 562 945 519 945 551 945 553 946 547 946 562 946 395 947 551 947 397 947 393 948 555 948 395 948 553 949 555 949 554 949 552 950 559 950 378 950 557 951 555 951 558 951 559 952 552 952 561 952 563 953 391 953 394 953 561 954 567 954 559 954 568 955 563 955 394 955 568 956 565 956 563 956 575 957 566 957 572 957 575 958 574 958 566 958 574 959 371 959 376 959 556 960 569 960 572 960 569 961 575 961 572 961 556 962 570 962 569 962 576 963 374 963 575 963 576 964 575 964 569 964 570 965 556 965 392 965 576 966 373 966 374 966 392 967 571 967 570 967 364 968 576 968 569 968 570 969 364 969 569 969 576 970 368 970 373 970 362 971 570 971 571 971 392 972 560 972 571 972 362 973 364 973 570 973 357 974 358 974 560 974 573 975 560 975 396 975 573 976 396 976 993 976 357 977 573 977 990 977 990 978 573 978 993 978 502 979 564 979 503 979 551 980 519 980 397 980 553 981 562 981 551 981 330 982 331 982 564 982 564 983 547 983 553 983 330 984 564 984 552 984 553 985 552 985 564 985 582 986 580 986 579 986 340 987 581 987 579 987 581 988 582 988 579 988 581 989 85 989 582 989 85 990 81 990 582 990 587 991 339 991 343 991 585 992 590 992 586 992 81 993 577 993 591 993 578 994 591 994 589 994 81 995 591 995 578 995 592 996 584 996 588 996 338 997 369 997 336 997 343 998 346 998 593 998 593 999 578 999 589 999 346 1000 347 1000 594 1000 346 1001 578 1001 593 1001 594 1002 578 1002 346 1002 580 1003 594 1003 350 1003 580 1004 578 1004 594 1004 580 1005 350 1005 579 1005 338 1006 339 1006 595 1006 577 1007 81 1007 84 1007 81 1008 578 1008 580 1008 580 1009 582 1009 81 1009 595 1010 339 1010 587 1010 590 1011 591 1011 577 1011 587 1012 589 1012 595 1012 85 1013 581 1013 975 1013 587 1014 593 1014 589 1014 984 1015 581 1015 340 1015 593 1016 587 1016 343 1016 594 1017 347 1017 350 1017 581 1018 984 1018 975 1018 592 1019 595 1019 589 1019 583 1020 369 1020 338 1020 338 1021 588 1021 583 1021 583 1022 588 1022 584 1022 585 1023 584 1023 592 1023 592 1024 590 1024 585 1024 338 1025 595 1025 588 1025 84 1026 586 1026 577 1026 592 1027 588 1027 595 1027 590 1028 577 1028 586 1028 590 1029 592 1029 589 1029 591 1030 590 1030 589 1030 600 1031 157 1031 398 1031 599 1032 157 1032 600 1032 604 1033 583 1033 606 1033 602 1034 586 1034 601 1034 608 1035 599 1035 596 1035 398 1036 369 1036 604 1036 602 1037 601 1037 611 1037 609 1038 602 1038 611 1038 603 1039 605 1039 612 1039 609 1040 607 1040 585 1040 609 1041 611 1041 614 1041 608 1042 618 1042 610 1042 616 1043 398 1043 606 1043 606 1044 607 1044 615 1044 606 1045 583 1045 584 1045 617 1046 82 1046 625 1046 615 1047 607 1047 622 1047 614 1048 601 1048 84 1048 614 1049 611 1049 601 1049 624 1050 623 1050 598 1050 616 1051 600 1051 398 1051 597 1052 598 1052 623 1052 614 1053 84 1053 82 1053 622 1054 621 1054 624 1054 603 1055 608 1055 596 1055 599 1056 615 1056 622 1056 599 1057 600 1057 616 1057 608 1058 603 1058 612 1058 623 1059 614 1059 597 1059 618 1060 620 1060 610 1060 610 1061 620 1061 613 1061 617 1062 612 1062 605 1062 597 1063 614 1063 82 1063 605 1064 82 1064 617 1064 599 1065 622 1065 596 1065 598 1066 596 1066 624 1066 598 1067 603 1067 596 1067 586 1068 84 1068 601 1068 586 1069 602 1069 585 1069 604 1070 369 1070 583 1070 607 1071 606 1071 584 1071 607 1072 584 1072 585 1072 585 1073 602 1073 609 1073 610 1074 599 1074 608 1074 608 1075 612 1075 618 1075 613 1076 599 1076 610 1076 613 1077 157 1077 599 1077 619 1078 612 1078 617 1078 619 1079 618 1079 612 1079 615 1080 599 1080 616 1080 606 1081 398 1081 604 1081 606 1082 615 1082 616 1082 622 1083 624 1083 596 1083 609 1084 614 1084 621 1084 621 1085 607 1085 609 1085 607 1086 621 1086 622 1086 621 1087 614 1087 623 1087 624 1088 621 1088 623 1088 605 1089 598 1089 597 1089 605 1090 603 1090 598 1090 605 1091 597 1091 82 1091 613 1092 620 1092 627 1092 619 1093 620 1093 618 1093 626 1094 627 1094 620 1094 626 1095 620 1095 619 1095 157 1096 613 1096 627 1096 619 1097 617 1097 628 1097 619 1098 628 1098 629 1098 627 1099 630 1099 152 1099 626 1100 619 1100 629 1100 629 1101 630 1101 626 1101 91 1102 88 1102 632 1102 634 1103 632 1103 88 1103 637 1104 636 1104 633 1104 634 1105 635 1105 632 1105 123 1106 152 1106 630 1106 174 1107 176 1107 636 1107 635 1108 634 1108 146 1108 164 1109 637 1109 635 1109 638 1110 932 1110 175 1110 934 1111 638 1111 88 1111 175 1112 634 1112 638 1112 932 1113 638 1113 934 1113 628 1114 617 1114 625 1114 91 1115 625 1115 82 1115 632 1116 633 1116 91 1116 635 1117 633 1117 632 1117 636 1118 631 1118 633 1118 637 1119 633 1119 635 1119 637 1120 174 1120 636 1120 174 1121 637 1121 164 1121 626 1122 630 1122 627 1122 631 1123 628 1123 625 1123 629 1124 628 1124 631 1124 625 1125 633 1125 631 1125 639 1126 630 1126 629 1126 91 1127 633 1127 625 1127 639 1128 123 1128 630 1128 639 1129 629 1129 631 1129 639 1130 176 1130 123 1130 636 1131 639 1131 631 1131 636 1132 176 1132 639 1132 146 1133 164 1133 635 1133 634 1134 88 1134 638 1134 139 1135 146 1135 634 1135 139 1136 634 1136 175 1136 440 1137 166 1137 640 1137 440 1138 212 1138 166 1138 439 1139 440 1139 640 1139 641 1140 640 1140 642 1140 641 1141 439 1141 640 1141 643 1142 641 1142 642 1142 643 1143 642 1143 644 1143 643 1144 644 1144 97 1144 645 1145 643 1145 97 1145 646 1146 647 1146 677 1146 646 1147 677 1147 414 1147 649 1148 648 1148 650 1148 651 1149 647 1149 646 1149 160 1150 159 1150 648 1150 160 1151 648 1151 649 1151 651 1152 650 1152 647 1152 652 1153 650 1153 651 1153 649 1154 650 1154 652 1154 644 1155 414 1155 97 1155 644 1156 646 1156 414 1156 642 1157 646 1157 644 1157 642 1158 651 1158 646 1158 640 1159 649 1159 652 1159 640 1160 651 1160 642 1160 640 1161 652 1161 651 1161 166 1162 649 1162 640 1162 166 1163 160 1163 649 1163 653 1164 109 1164 406 1164 653 1165 654 1165 109 1165 655 1166 654 1166 653 1166 656 1167 654 1167 655 1167 658 1168 656 1168 655 1168 657 1169 140 1169 656 1169 657 1170 656 1170 658 1170 143 1171 140 1171 657 1171 662 1172 411 1172 408 1172 662 1173 408 1173 401 1173 664 1174 663 1174 662 1174 665 1175 406 1175 399 1175 665 1176 653 1176 406 1176 668 1177 653 1177 665 1177 666 1178 667 1178 664 1178 417 1179 412 1179 669 1179 666 1180 417 1180 669 1180 668 1181 658 1181 653 1181 669 1182 667 1182 666 1182 667 1183 670 1183 664 1183 670 1184 673 1184 671 1184 670 1185 667 1185 673 1185 669 1186 412 1186 672 1186 669 1187 673 1187 667 1187 674 1188 658 1188 668 1188 675 1189 159 1189 163 1189 669 1190 672 1190 676 1190 674 1191 657 1191 658 1191 659 1192 665 1192 399 1192 678 1193 657 1193 674 1193 659 1194 399 1194 419 1194 679 1195 665 1195 659 1195 679 1196 668 1196 665 1196 143 1197 657 1197 678 1197 674 1198 668 1198 679 1198 659 1199 419 1199 416 1199 660 1200 659 1200 416 1200 660 1201 416 1201 661 1201 145 1202 143 1202 678 1202 416 1203 411 1203 661 1203 681 1204 679 1204 660 1204 662 1205 661 1205 411 1205 681 1206 674 1206 679 1206 682 1207 674 1207 681 1207 669 1208 676 1208 673 1208 678 1209 674 1209 682 1209 681 1210 660 1210 661 1210 109 1211 654 1211 680 1211 684 1212 678 1212 682 1212 150 1213 678 1213 684 1213 150 1214 145 1214 678 1214 685 1215 681 1215 661 1215 686 1216 681 1216 685 1216 686 1217 682 1217 681 1217 685 1218 661 1218 662 1218 685 1219 662 1219 663 1219 684 1220 682 1220 686 1220 659 1221 660 1221 679 1221 664 1222 662 1222 401 1222 687 1223 684 1223 686 1223 688 1224 685 1224 663 1224 688 1225 686 1225 685 1225 163 1226 150 1226 684 1226 163 1227 684 1227 687 1227 666 1228 401 1228 417 1228 666 1229 664 1229 401 1229 687 1230 686 1230 688 1230 670 1231 688 1231 663 1231 670 1232 663 1232 664 1232 671 1233 688 1233 670 1233 658 1234 655 1234 653 1234 671 1235 687 1235 688 1235 675 1236 687 1236 671 1236 163 1237 687 1237 675 1237 677 1238 672 1238 404 1238 683 1239 671 1239 673 1239 648 1240 675 1240 671 1240 677 1241 676 1241 672 1241 159 1242 675 1242 648 1242 648 1243 671 1243 683 1243 647 1244 676 1244 677 1244 650 1245 673 1245 676 1245 650 1246 676 1246 647 1246 650 1247 683 1247 673 1247 648 1248 683 1248 650 1248 414 1249 677 1249 404 1249 694 1250 108 1250 680 1250 691 1251 694 1251 680 1251 692 1252 689 1252 695 1252 691 1253 692 1253 695 1253 695 1254 689 1254 693 1254 112 1255 108 1255 694 1255 691 1256 696 1256 694 1256 130 1257 705 1257 133 1257 697 1258 705 1258 130 1258 691 1259 700 1259 696 1259 691 1260 695 1260 700 1260 700 1261 695 1261 698 1261 698 1262 693 1262 701 1262 698 1263 702 1263 700 1263 700 1264 702 1264 704 1264 702 1265 698 1265 703 1265 701 1266 703 1266 698 1266 697 1267 130 1267 124 1267 705 1268 703 1268 701 1268 116 1269 708 1269 124 1269 702 1270 710 1270 704 1270 702 1271 703 1271 710 1271 705 1272 701 1272 134 1272 107 1273 711 1273 699 1273 111 1274 107 1274 699 1274 710 1275 711 1275 704 1275 110 1276 713 1276 128 1276 110 1277 714 1277 713 1277 110 1278 128 1278 715 1278 706 1279 710 1279 712 1279 711 1280 710 1280 707 1280 707 1281 710 1281 706 1281 162 1282 717 1282 169 1282 717 1283 716 1283 169 1283 716 1284 717 1284 714 1284 716 1285 714 1285 111 1285 690 1286 140 1286 136 1286 108 1287 109 1287 680 1287 689 1288 690 1288 693 1288 693 1289 690 1289 136 1289 698 1290 695 1290 693 1290 136 1291 134 1291 701 1291 696 1292 112 1292 694 1292 705 1293 712 1293 703 1293 710 1294 703 1294 712 1294 689 1295 656 1295 140 1295 106 1296 112 1296 696 1296 701 1297 693 1297 136 1297 106 1298 696 1298 700 1298 106 1299 700 1299 704 1299 134 1300 133 1300 705 1300 711 1301 107 1301 704 1301 712 1302 705 1302 697 1302 697 1303 706 1303 712 1303 711 1304 707 1304 699 1304 708 1305 706 1305 697 1305 708 1306 697 1306 124 1306 169 1307 709 1307 116 1307 709 1308 708 1308 116 1308 709 1309 706 1309 708 1309 707 1310 709 1310 716 1310 707 1311 706 1311 709 1311 716 1312 699 1312 707 1312 716 1313 709 1313 169 1313 713 1314 717 1314 162 1314 713 1315 162 1315 142 1315 716 1316 111 1316 699 1316 714 1317 717 1317 713 1317 128 1318 713 1318 142 1318 110 1319 111 1319 714 1319 170 1320 715 1320 128 1320 933 1321 715 1321 170 1321 110 1322 715 1322 935 1322 935 1323 715 1323 933 1323 689 1324 140 1324 690 1324 656 1325 692 1325 654 1325 692 1326 656 1326 689 1326 692 1327 680 1327 654 1327 692 1328 691 1328 680 1328 760 1329 719 1329 66 1329 66 1330 719 1330 67 1330 844 1331 843 1331 49 1331 46 1332 724 1332 723 1332 722 1333 725 1333 881 1333 722 1334 721 1334 725 1334 881 1335 725 1335 879 1335 723 1336 726 1336 720 1336 720 1337 727 1337 721 1337 723 1338 724 1338 726 1338 720 1339 726 1339 727 1339 721 1340 727 1340 725 1340 725 1341 1643 1341 879 1341 728 1342 731 1342 1640 1342 1640 1343 731 1343 1644 1343 727 1344 732 1344 725 1344 725 1345 732 1345 1643 1345 728 1346 729 1346 731 1346 727 1347 733 1347 732 1347 726 1348 733 1348 727 1348 1644 1349 734 1349 883 1349 726 1350 735 1350 733 1350 726 1351 49 1351 735 1351 1644 1352 736 1352 734 1352 731 1353 736 1353 1644 1353 724 1354 49 1354 726 1354 729 1355 737 1355 731 1355 731 1356 737 1356 736 1356 883 1357 734 1357 881 1357 729 1358 723 1358 737 1358 730 1359 723 1359 729 1359 730 1360 46 1360 723 1360 737 1361 720 1361 736 1361 736 1362 721 1362 734 1362 736 1363 720 1363 721 1363 734 1364 722 1364 881 1364 734 1365 721 1365 722 1365 723 1366 720 1366 737 1366 41 1367 730 1367 42 1367 46 1368 730 1368 41 1368 730 1369 729 1369 42 1369 42 1370 729 1370 43 1370 729 1371 728 1371 43 1371 728 1372 1640 1372 43 1372 43 1373 1640 1373 916 1373 428 1374 425 1374 741 1374 739 1375 429 1375 428 1375 430 1376 429 1376 739 1376 743 1377 741 1377 435 1377 740 1378 61 1378 430 1378 741 1379 744 1379 738 1379 60 1380 740 1380 742 1380 60 1381 742 1381 745 1381 747 1382 742 1382 744 1382 748 1383 435 1383 252 1383 743 1384 744 1384 741 1384 749 1385 747 1385 746 1385 741 1386 425 1386 435 1386 742 1387 747 1387 745 1387 435 1388 425 1388 254 1388 747 1389 437 1389 446 1389 747 1390 749 1390 437 1390 748 1391 749 1391 746 1391 743 1392 435 1392 748 1392 739 1393 742 1393 430 1393 746 1394 743 1394 748 1394 745 1395 58 1395 60 1395 746 1396 747 1396 744 1396 746 1397 744 1397 743 1397 750 1398 748 1398 252 1398 748 1399 750 1399 749 1399 745 1400 747 1400 446 1400 738 1401 744 1401 742 1401 441 1402 750 1402 252 1402 446 1403 58 1403 745 1403 437 1404 749 1404 750 1404 437 1405 750 1405 441 1405 742 1406 740 1406 430 1406 738 1407 739 1407 741 1407 742 1408 739 1408 738 1408 739 1409 428 1409 741 1409 103 1410 433 1410 751 1410 991 1411 434 1411 992 1411 103 1412 751 1412 102 1412 104 1413 433 1413 103 1413 104 1414 432 1414 433 1414 427 1415 431 1415 492 1415 102 1416 434 1416 991 1416 462 1417 427 1417 492 1417 102 1418 751 1418 434 1418 104 1419 431 1419 432 1419 492 1420 431 1420 104 1420 436 1421 439 1421 641 1421 643 1422 436 1422 641 1422 643 1423 445 1423 436 1423 645 1424 445 1424 643 1424 96 1425 445 1425 645 1425 96 1426 444 1426 445 1426 96 1427 443 1427 444 1427 95 1428 443 1428 96 1428 95 1429 442 1429 443 1429 94 1430 438 1430 442 1430 94 1431 442 1431 95 1431 94 1432 878 1432 438 1432 94 1433 888 1433 878 1433 752 1434 753 1434 70 1434 753 1435 72 1435 70 1435 753 1436 754 1436 72 1436 72 1437 754 1437 73 1437 754 1438 755 1438 73 1438 755 1439 56 1439 73 1439 756 1440 56 1440 755 1440 67 1441 719 1441 718 1441 719 1442 757 1442 718 1442 718 1443 757 1443 70 1443 757 1444 752 1444 70 1444 758 1445 759 1445 15 1445 15 1446 759 1446 66 1446 759 1447 760 1447 66 1447 4 1448 761 1448 5 1448 761 1449 762 1449 5 1449 762 1450 763 1450 5 1450 5 1451 763 1451 14 1451 763 1452 764 1452 14 1452 14 1453 764 1453 15 1453 764 1454 758 1454 15 1454 781 1455 761 1455 6 1455 761 1456 4 1456 6 1456 13 1457 765 1457 16 1457 765 1458 766 1458 16 1458 16 1459 766 1459 6 1459 766 1460 781 1460 6 1460 767 1461 768 1461 9 1461 9 1462 769 1462 10 1462 768 1463 769 1463 9 1463 10 1464 770 1464 11 1464 769 1465 770 1465 10 1465 11 1466 771 1466 12 1466 770 1467 771 1467 11 1467 771 1468 765 1468 12 1468 12 1469 765 1469 13 1469 7 1470 773 1470 8 1470 772 1471 773 1471 7 1471 773 1472 767 1472 8 1472 8 1473 767 1473 9 1473 774 1474 775 1474 65 1474 65 1475 775 1475 68 1475 775 1476 776 1476 68 1476 68 1477 777 1477 69 1477 776 1478 777 1478 68 1478 69 1479 772 1479 7 1479 777 1480 772 1480 69 1480 65 1481 778 1481 774 1481 56 1482 756 1482 779 1482 65 1483 56 1483 779 1483 65 1484 779 1484 778 1484 780 1485 199 1485 191 1485 780 1486 761 1486 781 1486 199 1487 780 1487 781 1487 780 1488 191 1488 761 1488 782 1489 761 1489 191 1489 761 1490 782 1490 762 1490 781 1491 766 1491 199 1491 782 1492 191 1492 192 1492 766 1493 765 1493 201 1493 766 1494 201 1494 199 1494 763 1495 762 1495 782 1495 783 1496 201 1496 765 1496 193 1497 782 1497 192 1497 193 1498 763 1498 782 1498 783 1499 206 1499 201 1499 771 1500 783 1500 765 1500 763 1501 193 1501 784 1501 764 1502 763 1502 784 1502 205 1503 206 1503 783 1503 771 1504 770 1504 205 1504 771 1505 205 1505 783 1505 784 1506 193 1506 209 1506 764 1507 784 1507 758 1507 205 1508 769 1508 198 1508 770 1509 769 1509 205 1509 769 1510 197 1510 198 1510 768 1511 197 1511 769 1511 767 1512 197 1512 768 1512 785 1513 259 1513 241 1513 259 1514 784 1514 209 1514 759 1515 784 1515 259 1515 760 1516 759 1516 785 1516 759 1517 758 1517 784 1517 719 1518 760 1518 785 1518 759 1519 259 1519 785 1519 767 1520 773 1520 197 1520 197 1521 773 1521 195 1521 195 1522 773 1522 772 1522 757 1523 786 1523 752 1523 270 1524 786 1524 757 1524 786 1525 270 1525 299 1525 785 1526 241 1526 270 1526 757 1527 785 1527 270 1527 757 1528 719 1528 785 1528 302 1529 786 1529 299 1529 753 1530 786 1530 302 1530 753 1531 752 1531 786 1531 755 1532 787 1532 304 1532 755 1533 754 1533 787 1533 304 1534 787 1534 302 1534 787 1535 754 1535 753 1535 302 1536 787 1536 753 1536 218 1537 776 1537 775 1537 218 1538 777 1538 776 1538 218 1539 219 1539 777 1539 219 1540 772 1540 777 1540 219 1541 195 1541 772 1541 251 1542 755 1542 304 1542 756 1543 755 1543 251 1543 218 1544 774 1544 310 1544 775 1545 774 1545 218 1545 788 1546 779 1546 756 1546 774 1547 778 1547 310 1547 788 1548 756 1548 251 1548 779 1549 788 1549 253 1549 778 1550 261 1550 310 1550 253 1551 788 1551 251 1551 261 1552 779 1552 253 1552 261 1553 778 1553 779 1553 64 1554 1 1554 59 1554 2 1555 1 1555 64 1555 75 1556 80 1556 2 1556 75 1557 2 1557 64 1557 77 1558 80 1558 75 1558 790 1559 789 1559 24 1559 790 1560 24 1560 23 1560 791 1561 790 1561 23 1561 421 1562 791 1562 23 1562 795 1563 792 1563 796 1563 793 1564 795 1564 796 1564 797 1565 795 1565 793 1565 794 1566 795 1566 797 1566 794 1567 420 1567 795 1567 790 1568 793 1568 798 1568 790 1569 797 1569 793 1569 420 1570 794 1570 791 1570 791 1571 794 1571 797 1571 791 1572 797 1572 790 1572 421 1573 420 1573 791 1573 789 1574 790 1574 798 1574 420 1575 422 1575 799 1575 804 1576 800 1576 806 1576 806 1577 800 1577 805 1577 804 1578 802 1578 803 1578 422 1579 802 1579 804 1579 799 1580 804 1580 806 1580 806 1581 805 1581 792 1581 800 1582 803 1582 801 1582 804 1583 803 1583 800 1583 799 1584 422 1584 804 1584 806 1585 792 1585 795 1585 799 1586 795 1586 420 1586 799 1587 806 1587 795 1587 808 1588 801 1588 803 1588 808 1589 807 1589 801 1589 823 1590 803 1590 802 1590 823 1591 808 1591 803 1591 423 1592 802 1592 422 1592 423 1593 823 1593 802 1593 810 1594 814 1594 811 1594 813 1595 54 1595 55 1595 424 1596 812 1596 814 1596 424 1597 814 1597 810 1597 816 1598 809 1598 815 1598 813 1599 113 1599 54 1599 816 1600 810 1600 809 1600 817 1601 113 1601 813 1601 818 1602 424 1602 810 1602 818 1603 810 1603 816 1603 817 1604 114 1604 113 1604 816 1605 815 1605 819 1605 423 1606 424 1606 818 1606 822 1607 114 1607 817 1607 818 1608 816 1608 821 1608 821 1609 816 1609 819 1609 823 1610 818 1610 821 1610 821 1611 819 1611 807 1611 423 1612 818 1612 823 1612 808 1613 821 1613 807 1613 817 1614 813 1614 820 1614 814 1615 817 1615 820 1615 823 1616 821 1616 808 1616 822 1617 424 1617 114 1617 810 1618 811 1618 809 1618 812 1619 817 1619 814 1619 814 1620 820 1620 811 1620 812 1621 822 1621 817 1621 424 1622 822 1622 812 1622 789 1623 824 1623 24 1623 824 1624 45 1624 24 1624 789 1625 825 1625 824 1625 24 1626 884 1626 887 1626 45 1627 884 1627 24 1627 813 1628 55 1628 826 1628 827 1629 813 1629 826 1629 820 1630 813 1630 827 1630 828 1631 820 1631 827 1631 811 1632 820 1632 828 1632 809 1633 811 1633 828 1633 829 1634 809 1634 828 1634 815 1635 809 1635 829 1635 830 1636 815 1636 829 1636 819 1637 815 1637 830 1637 831 1638 819 1638 830 1638 807 1639 819 1639 831 1639 832 1640 807 1640 831 1640 832 1641 801 1641 807 1641 833 1642 801 1642 832 1642 801 1643 833 1643 834 1643 800 1644 801 1644 834 1644 800 1645 834 1645 835 1645 805 1646 800 1646 835 1646 792 1647 835 1647 836 1647 792 1648 805 1648 835 1648 796 1649 792 1649 836 1649 796 1650 836 1650 837 1650 793 1651 796 1651 837 1651 793 1652 837 1652 838 1652 798 1653 793 1653 838 1653 798 1654 838 1654 825 1654 789 1655 798 1655 825 1655 45 1656 824 1656 40 1656 825 1657 30 1657 40 1657 824 1658 825 1658 40 1658 838 1659 30 1659 825 1659 36 1660 30 1660 838 1660 36 1661 838 1661 33 1661 51 1662 33 1662 837 1662 836 1663 51 1663 837 1663 33 1664 838 1664 837 1664 839 1665 841 1665 840 1665 833 1666 839 1666 834 1666 840 1667 841 1667 835 1667 840 1668 835 1668 834 1668 841 1669 51 1669 836 1669 841 1670 836 1670 835 1670 839 1671 840 1671 834 1671 839 1672 833 1672 832 1672 839 1673 832 1673 48 1673 830 1674 47 1674 48 1674 826 1675 50 1675 827 1675 48 1676 832 1676 831 1676 847 1677 826 1677 842 1677 846 1678 50 1678 847 1678 830 1679 829 1679 47 1679 828 1680 47 1680 829 1680 831 1681 830 1681 48 1681 50 1682 47 1682 828 1682 50 1683 828 1683 827 1683 93 1684 1853 1684 1858 1684 17 1685 22 1685 19 1685 878 1686 22 1686 17 1686 735 1687 854 1687 733 1687 735 1688 853 1688 852 1688 733 1689 851 1689 732 1689 733 1690 854 1690 850 1690 732 1691 851 1691 849 1691 732 1692 848 1692 1643 1692 1643 1693 848 1693 1767 1693 49 1694 846 1694 845 1694 49 1695 853 1695 735 1695 826 1696 55 1696 842 1696 857 1697 856 1697 855 1697 857 1698 855 1698 858 1698 876 1699 875 1699 857 1699 875 1700 964 1700 965 1700 857 1701 875 1701 965 1701 876 1702 857 1702 858 1702 892 1703 887 1703 893 1703 892 1704 886 1704 887 1704 892 1705 894 1705 886 1705 915 1706 900 1706 896 1706 902 1707 901 1707 907 1707 890 1708 895 1708 891 1708 901 1709 899 1709 907 1709 897 1710 896 1710 908 1710 882 1698 880 1698 898 1698 902 1711 907 1711 909 1711 895 1712 902 1712 909 1712 897 1713 908 1713 910 1713 898 1714 897 1714 910 1714 896 1715 900 1715 911 1715 895 1716 909 1716 891 1716 882 1698 898 1698 910 1698 896 1717 911 1717 908 1717 880 1718 891 1718 909 1718 914 1719 913 1719 901 1719 913 1720 912 1720 905 1720 905 1721 904 1721 915 1721 907 1722 899 1722 897 1722 908 1723 911 1723 917 1723 905 1724 915 1724 899 1724 913 1725 905 1725 901 1725 908 1726 917 1726 910 1726 907 1727 897 1727 898 1727 909 1728 907 1728 898 1728 882 1729 910 1729 917 1729 884 1730 882 1730 917 1730 916 1731 914 1731 895 1731 880 1732 909 1732 898 1732 904 1733 903 1733 906 1733 912 1734 903 1734 904 1734 899 1735 915 1735 896 1735 914 1736 901 1736 902 1736 915 1737 904 1737 906 1737 890 1738 916 1738 895 1738 915 1739 906 1739 900 1739 897 1740 899 1740 896 1740 914 1741 902 1741 895 1741 912 1742 904 1742 905 1742 901 1743 905 1743 899 1743 918 1744 919 1744 920 1744 912 1745 920 1745 1754 1745 912 1746 918 1746 920 1746 1756 1747 912 1747 1754 1747 1761 1748 1762 1748 921 1748 1636 1749 922 1749 919 1749 903 1750 912 1750 1756 1750 1636 1751 921 1751 922 1751 923 1752 903 1752 1756 1752 918 1753 1636 1753 919 1753 924 1754 1759 1754 925 1754 926 1755 924 1755 925 1755 1759 1698 1766 1698 925 1698 1765 1698 925 1698 1766 1698 945 1756 941 1756 943 1756 948 1757 944 1757 949 1757 948 1758 942 1758 944 1758 946 1759 956 1759 1655 1759 1655 1760 956 1760 945 1760 956 1761 941 1761 945 1761 956 1762 950 1762 941 1762 949 1763 960 1763 959 1763 949 1764 959 1764 948 1764 960 1765 962 1765 959 1765 959 1766 962 1766 963 1766 963 1767 962 1767 964 1767 962 1768 965 1768 964 1768 939 1769 972 1769 967 1769 971 1770 968 1770 967 1770 972 1771 971 1771 967 1771 970 1772 966 1772 969 1772 934 1773 939 1773 967 1773 934 1774 967 1774 973 1774 974 1775 975 1775 969 1775 975 1776 970 1776 969 1776 974 1777 969 1777 976 1777 976 1778 969 1778 977 1778 973 1779 967 1779 978 1779 977 1780 969 1780 979 1780 967 1781 968 1781 969 1781 968 1782 979 1782 969 1782 886 1783 1006 1783 888 1783 894 1784 1007 1784 1006 1784 894 1785 1008 1785 1007 1785 894 1786 1557 1786 1008 1786 894 1787 1006 1787 886 1787 1009 1788 894 1788 1010 1788 1009 1789 1557 1789 894 1789 1014 1790 998 1790 991 1790 1012 1791 1014 1791 1015 1791 1012 1792 1013 1792 998 1792 1012 1793 998 1793 1014 1793 1017 1794 1015 1794 1016 1794 1011 1795 1015 1795 1017 1795 1011 1796 1012 1796 1015 1796 1404 1797 1017 1797 1016 1797 936 1798 1023 1798 1019 1798 938 1799 1018 1799 1024 1799 940 1800 938 1800 1024 1800 936 1801 935 1801 1022 1801 937 1802 936 1802 1019 1802 937 1803 1019 1803 1616 1803 925 1804 940 1804 1020 1804 1025 1805 927 1805 1021 1805 940 1806 1024 1806 1020 1806 925 1807 1020 1807 926 1807 1026 1808 1025 1808 1021 1808 937 1809 1616 1809 1018 1809 1319 1810 1026 1810 1021 1810 938 1811 937 1811 1018 1811 926 1812 1020 1812 927 1812 936 1813 1022 1813 1023 1813 927 1814 1020 1814 1021 1814 1040 1815 1085 1815 1034 1815 1027 1816 1031 1816 1032 1816 1027 1817 1038 1817 1028 1817 1030 1818 1038 1818 1032 1818 1038 1819 1027 1819 1032 1819 1030 1820 1032 1820 1039 1820 1028 1821 1038 1821 1036 1821 1033 1822 1036 1822 1038 1822 1041 1823 1030 1823 1039 1823 1038 1824 1030 1824 1033 1824 1042 1825 1036 1825 1033 1825 1039 1826 1037 1826 1041 1826 1037 1827 1035 1827 1043 1827 1047 1828 1212 1828 1044 1828 1046 1829 1044 1829 1050 1829 1044 1830 1212 1830 1050 1830 1048 1831 1046 1831 1050 1831 1041 1832 1037 1832 1043 1832 1048 1833 1050 1833 1053 1833 1052 1834 1048 1834 1053 1834 1050 1835 1212 1835 1053 1835 1212 1836 1059 1836 1053 1836 1055 1837 1052 1837 1056 1837 1052 1838 1053 1838 1056 1838 1035 1839 1064 1839 1043 1839 1040 1840 1034 1840 1054 1840 1029 1841 1051 1841 1049 1841 1053 1842 1059 1842 1056 1842 1057 1843 1055 1843 1056 1843 1034 1844 1029 1844 1049 1844 1033 1845 1030 1845 1041 1845 1043 1846 1064 1846 1060 1846 1057 1847 1056 1847 1059 1847 1041 1848 1043 1848 1061 1848 1057 1849 1059 1849 1062 1849 1034 1850 1049 1850 1063 1850 1059 1851 1065 1851 1062 1851 1033 1852 1041 1852 1066 1852 1042 1853 1033 1853 1066 1853 1049 1854 1051 1854 1058 1854 1062 1855 1065 1855 1070 1855 1034 1856 1063 1856 1054 1856 1049 1857 1058 1857 1067 1857 1072 1858 1071 1858 1070 1858 1065 1859 1073 1859 1072 1859 1070 1860 1065 1860 1072 1860 1049 1861 1067 1861 1063 1861 1075 1862 1062 1862 1070 1862 1071 1863 1075 1863 1070 1863 1072 1864 1073 1864 1078 1864 1061 1865 1043 1865 1060 1865 1058 1866 1076 1866 1067 1866 1063 1867 1067 1867 1077 1867 1042 1868 1066 1868 1044 1868 1041 1869 1061 1869 1080 1869 1066 1870 1041 1870 1080 1870 1045 1871 1042 1871 1044 1871 1077 1872 1067 1872 1079 1872 1067 1873 1076 1873 1079 1873 1064 1874 1539 1874 1060 1874 1061 1875 1060 1875 1084 1875 930 1876 929 1876 1083 1876 933 1877 931 1877 1082 1877 931 1878 930 1878 1083 1878 1077 1879 1079 1879 1031 1879 1076 1880 1086 1880 1079 1880 1031 1881 1079 1881 1032 1881 1082 1882 931 1882 1085 1882 929 1883 932 1883 1087 1883 1044 1884 1066 1884 1080 1884 931 1885 1083 1885 1085 1885 1083 1886 929 1886 1087 1886 1080 1887 1061 1887 1084 1887 1032 1888 1086 1888 1039 1888 1046 1889 1045 1889 1044 1889 1086 1890 1088 1890 1039 1890 1044 1891 1080 1891 1047 1891 1083 1892 1087 1892 1051 1892 1083 1893 1029 1893 1085 1893 1084 1894 1060 1894 1068 1894 1539 1895 1069 1895 1068 1895 1040 1896 1082 1896 1085 1896 1060 1897 1539 1897 1068 1897 1039 1898 1088 1898 1037 1898 1063 1899 1077 1899 1054 1899 1085 1900 1029 1900 1034 1900 1088 1901 1035 1901 1037 1901 1054 1902 1077 1902 1074 1902 1083 1903 1051 1903 1029 1903 1074 1904 1077 1904 1081 1904 1081 1905 1077 1905 1031 1905 1081 1906 1031 1906 1027 1906 1079 1907 1086 1907 1032 1907 1081 1908 1027 1908 1028 1908 1089 1909 1095 1909 1092 1909 1094 1910 1091 1910 1115 1910 1092 1911 1095 1911 1097 1911 1092 1912 1097 1912 1098 1912 1094 1913 1098 1913 1091 1913 1102 1914 1103 1914 1106 1914 1104 1915 1105 1915 1106 1915 1101 1916 1100 1916 1095 1916 1095 1917 1102 1917 1101 1917 1089 1918 1109 1918 1110 1918 1101 1919 1097 1919 1100 1919 1106 1920 1080 1920 1084 1920 1107 1921 1109 1921 1089 1921 1111 1922 1103 1922 1102 1922 1089 1923 1108 1923 1107 1923 1090 1924 1122 1924 1114 1924 1108 1925 1089 1925 1092 1925 1116 1926 1122 1926 1090 1926 1103 1927 1104 1927 1106 1927 1095 1928 1100 1928 1097 1928 1089 1929 1110 1929 1117 1929 1113 1930 1102 1930 1118 1930 1114 1931 1091 1931 1090 1931 1114 1932 1115 1932 1091 1932 1096 1933 1093 1933 1091 1933 1089 1934 1102 1934 1095 1934 1105 1935 1121 1935 1106 1935 1120 1936 1116 1936 1090 1936 1120 1937 1119 1937 1116 1937 1092 1938 1098 1938 1094 1938 1113 1939 1111 1939 1102 1939 1099 1940 1096 1940 1091 1940 1093 1941 1120 1941 1090 1941 1106 1942 1068 1942 1102 1942 1099 1943 1091 1943 1098 1943 1084 1944 1068 1944 1106 1944 1115 1945 1114 1945 1112 1945 1112 1946 1114 1946 1122 1946 1092 1947 1115 1947 1108 1947 1118 1948 1089 1948 1117 1948 1068 1949 1101 1949 1102 1949 1089 1950 1118 1950 1102 1950 1115 1951 1092 1951 1094 1951 1090 1952 1091 1952 1093 1952 1108 1953 1115 1953 1112 1953 1123 1954 1124 1954 1073 1954 1073 1955 1124 1955 1078 1955 1130 1956 1131 1956 1129 1956 1128 1957 1129 1957 1145 1957 1131 1958 1107 1958 1108 1958 1133 1959 1116 1959 1141 1959 1143 1960 1125 1960 1138 1960 1139 1961 1145 1961 1142 1961 1133 1962 1141 1962 1138 1962 1142 1963 1145 1963 1144 1963 1106 1964 1121 1964 1152 1964 1147 1965 1139 1965 1142 1965 1142 1966 1144 1966 1154 1966 1144 1967 1125 1967 1154 1967 1152 1968 1080 1968 1106 1968 1125 1969 1144 1969 1133 1969 1159 1970 1156 1970 1161 1970 1161 1971 1147 1971 1132 1971 1162 1972 1130 1972 1129 1972 1125 1973 1133 1973 1138 1973 1158 1974 1073 1974 1065 1974 1164 1975 1158 1975 1163 1975 1126 1976 1168 1976 1167 1976 1168 1977 1220 1977 1170 1977 1172 1978 1170 1978 1169 1978 1175 1979 1174 1979 1168 1979 1168 1980 1170 1980 1175 1980 1169 1981 1176 1981 1172 1981 1174 1982 1180 1982 1179 1982 1170 1983 1181 1983 1175 1983 1153 1984 1182 1984 1152 1984 1177 1985 1174 1985 1175 1985 1172 1986 1176 1986 1183 1986 1181 1987 1170 1987 1185 1987 1170 1988 1172 1988 1185 1988 1183 1989 1188 1989 1172 1989 1185 1990 1172 1990 1188 1990 1177 1991 1186 1991 1187 1991 1178 1992 1187 1992 1184 1992 1177 1993 1175 1993 1186 1993 1190 1994 1186 1994 1181 1994 1186 1995 1175 1995 1181 1995 1180 1996 1137 1996 1179 1996 1137 1997 1191 1997 1179 1997 1188 1998 1189 1998 1185 1998 1189 1999 1193 1999 1185 1999 1187 2000 1186 2000 1151 2000 1186 2001 1195 2001 1151 2001 1195 2002 1186 2002 1194 2002 1195 2003 1198 2003 1197 2003 1188 2004 1183 2004 1199 2004 1202 2005 1155 2005 1203 2005 1204 2006 1195 2006 1194 2006 1195 2007 1204 2007 1198 2007 1194 2008 1196 2008 1204 2008 1121 2009 1171 2009 1152 2009 1169 2010 1162 2010 1176 2010 1188 2011 1199 2011 1189 2011 1205 2012 1223 2012 1206 2012 1204 2013 1196 2013 1207 2013 1200 2014 1207 2014 1196 2014 1209 2015 1204 2015 1208 2015 1183 2016 1192 2016 1199 2016 1047 2017 1080 2017 1152 2017 1214 2018 1216 2018 1158 2018 1176 2019 1128 2019 1192 2019 1139 2020 1147 2020 1156 2020 1136 2021 1156 2021 1159 2021 1149 2022 1160 2022 1144 2022 1149 2023 1112 2023 1160 2023 1159 2024 1217 2024 1135 2024 1152 2025 1182 2025 1211 2025 1158 2026 1152 2026 1211 2026 1160 2027 1116 2027 1133 2027 1147 2028 1127 2028 1132 2028 1212 2029 1047 2029 1152 2029 1211 2030 1214 2030 1158 2030 1166 2031 1164 2031 1165 2031 1187 2032 1178 2032 1177 2032 1176 2033 1192 2033 1183 2033 1194 2034 1186 2034 1190 2034 1059 2035 1158 2035 1065 2035 1204 2036 1209 2036 1210 2036 1125 2037 1143 2037 1127 2037 1141 2038 1116 2038 1119 2038 1123 2039 1158 2039 1164 2039 1218 2040 1207 2040 1200 2040 1207 2041 1218 2041 1219 2041 1123 2042 1073 2042 1158 2042 1163 2043 1165 2043 1164 2043 1165 2044 1173 2044 1126 2044 1169 2045 1220 2045 1173 2045 1126 2046 1220 2046 1168 2046 1220 2047 1169 2047 1170 2047 1221 2048 1167 2048 1174 2048 1174 2049 1167 2049 1168 2049 1221 2050 1174 2050 1179 2050 1180 2051 1174 2051 1178 2051 1174 2052 1177 2052 1178 2052 1191 2053 1221 2053 1179 2053 1222 2054 1130 2054 1162 2054 1180 2055 1178 2055 1184 2055 1134 2056 1128 2056 1139 2056 1166 2057 1165 2057 1126 2057 1140 2058 1137 2058 1180 2058 1167 2059 1166 2059 1126 2059 1220 2060 1126 2060 1173 2060 1187 2061 1148 2061 1146 2061 1140 2062 1180 2062 1184 2062 1150 2063 1140 2063 1184 2063 1181 2064 1185 2064 1190 2064 1185 2065 1193 2065 1190 2065 1150 2066 1184 2066 1146 2066 1189 2067 1201 2067 1193 2067 1155 2068 1150 2068 1146 2068 1155 2069 1146 2069 1148 2069 1190 2070 1193 2070 1194 2070 1184 2071 1187 2071 1146 2071 1155 2072 1202 2072 1150 2072 1197 2073 1151 2073 1195 2073 1193 2074 1201 2074 1200 2074 1171 2075 1153 2075 1152 2075 1194 2076 1193 2076 1196 2076 1187 2077 1151 2077 1148 2077 1196 2078 1193 2078 1200 2078 1223 2079 1157 2079 1198 2079 1205 2080 1157 2080 1223 2080 1213 2081 1218 2081 1201 2081 1155 2082 1148 2082 1157 2082 1198 2083 1206 2083 1223 2083 1148 2084 1151 2084 1197 2084 1157 2085 1148 2085 1197 2085 1173 2086 1222 2086 1162 2086 1169 2087 1173 2087 1162 2087 1213 2088 1201 2088 1189 2088 1198 2089 1157 2089 1197 2089 1208 2090 1204 2090 1207 2090 1215 2091 1136 2091 1199 2091 1136 2092 1189 2092 1199 2092 1162 2093 1128 2093 1176 2093 1199 2094 1134 2094 1215 2094 1155 2095 1157 2095 1205 2095 1135 2096 1213 2096 1136 2096 1218 2097 1200 2097 1201 2097 1199 2098 1192 2098 1134 2098 1203 2099 1155 2099 1205 2099 1212 2100 1152 2100 1059 2100 1152 2101 1158 2101 1059 2101 1131 2102 1108 2102 1129 2102 1128 2103 1145 2103 1139 2103 1206 2104 1198 2104 1210 2104 1215 2105 1134 2105 1156 2105 1145 2106 1129 2106 1149 2106 1149 2107 1144 2107 1145 2107 1134 2108 1139 2108 1156 2108 1129 2109 1108 2109 1149 2109 1136 2110 1215 2110 1156 2110 1210 2111 1198 2111 1204 2111 1149 2112 1108 2112 1112 2112 1216 2113 1163 2113 1158 2113 1147 2114 1142 2114 1154 2114 1135 2115 1136 2115 1159 2115 1189 2116 1136 2116 1213 2116 1147 2117 1161 2117 1156 2117 1112 2118 1122 2118 1160 2118 1133 2119 1144 2119 1160 2119 1219 2120 1218 2120 1213 2120 1154 2121 1125 2121 1127 2121 1217 2122 1219 2122 1213 2122 1217 2123 1213 2123 1135 2123 1116 2124 1160 2124 1122 2124 1147 2125 1154 2125 1127 2125 1128 2126 1134 2126 1192 2126 1162 2127 1129 2127 1128 2127 1207 2128 1219 2128 1229 2128 1231 2129 1224 2129 1202 2129 1227 2130 1228 2130 1208 2130 1229 2131 1226 2131 1207 2131 1150 2132 1225 2132 1140 2132 1202 2133 1224 2133 1150 2133 1207 2134 1226 2134 1227 2134 1203 2135 1234 2135 1202 2135 1234 2136 1231 2136 1202 2136 1208 2137 1207 2137 1227 2137 1224 2138 1225 2138 1150 2138 1235 2139 1236 2139 1210 2139 1209 2140 1208 2140 1230 2140 1208 2141 1228 2141 1230 2141 1210 2142 1209 2142 1230 2142 1230 2143 1235 2143 1210 2143 1206 2144 1210 2144 1236 2144 1225 2145 1232 2145 1140 2145 1206 2146 1236 2146 1237 2146 1205 2147 1206 2147 1237 2147 1233 2148 1137 2148 1232 2148 1140 2149 1232 2149 1137 2149 1205 2150 1237 2150 1203 2150 1237 2151 1234 2151 1203 2151 1233 2152 1191 2152 1137 2152 1238 2153 1229 2153 1219 2153 1120 2154 1093 2154 1241 2154 1242 2155 1243 2155 1241 2155 1120 2156 1241 2156 1244 2156 1119 2157 1120 2157 1244 2157 1241 2158 1243 2158 1244 2158 1141 2159 1119 2159 1244 2159 1243 2160 1245 2160 1244 2160 1141 2161 1244 2161 1246 2161 1138 2162 1141 2162 1246 2162 1143 2163 1138 2163 1246 2163 1245 2164 1246 2164 1244 2164 1127 2165 1143 2165 1246 2165 1247 2166 1127 2166 1246 2166 1248 2167 1250 2167 1249 2167 1097 2168 1248 2168 1249 2168 1250 2169 1251 2169 1254 2169 1252 2170 984 2170 987 2170 1249 2171 1250 2171 1254 2171 1251 2172 1255 2172 1253 2172 1251 2173 1253 2173 1254 2173 1255 2174 1258 2174 1256 2174 1256 2175 1258 2175 1257 2175 1258 2176 1259 2176 1257 2176 1257 2177 1259 2177 1239 2177 1491 2178 1252 2178 1260 2178 1259 2179 1262 2179 1261 2179 1239 2180 1259 2180 1261 2180 1491 2181 1260 2181 1261 2181 1262 2182 1491 2182 1261 2182 1264 2183 1252 2183 988 2183 1264 2184 1260 2184 1252 2184 1260 2185 1240 2185 1261 2185 1240 2186 1239 2186 1261 2186 1249 2187 1263 2187 1097 2187 1257 2188 1239 2188 1265 2188 1265 2189 1266 2189 1257 2189 988 2190 989 2190 1264 2190 989 2191 1269 2191 1264 2191 1240 2192 1268 2192 1270 2192 1264 2193 1269 2193 1268 2193 1269 2194 1270 2194 1268 2194 1239 2195 1240 2195 1274 2195 1159 2196 1161 2196 1272 2196 1265 2197 1239 2197 1274 2197 1272 2198 1161 2198 1275 2198 1161 2199 1132 2199 1275 2199 1275 2200 1132 2200 1277 2200 1270 2201 1278 2201 1240 2201 1132 2202 1127 2202 1277 2202 1240 2203 1278 2203 1274 2203 1277 2204 1127 2204 1247 2204 1274 2205 1276 2205 1265 2205 1252 2206 987 2206 988 2206 1283 2207 1273 2207 1282 2207 1099 2208 1098 2208 1271 2208 1284 2209 1287 2209 1271 2209 1285 2210 1286 2210 1282 2210 1282 2211 1286 2211 1283 2211 1287 2212 1099 2212 1271 2212 1273 2213 1283 2213 1284 2213 1283 2214 1288 2214 1284 2214 1096 2215 1099 2215 1287 2215 1288 2216 1289 2216 1284 2216 1287 2217 1284 2217 1289 2217 1290 2218 1242 2218 1241 2218 1260 2219 1264 2219 1268 2219 1260 2220 1268 2220 1240 2220 1249 2221 1254 2221 1263 2221 1254 2222 1267 2222 1263 2222 1256 2223 1257 2223 1266 2223 1266 2224 1279 2224 1256 2224 1254 2225 1279 2225 1267 2225 1273 2226 1271 2226 1263 2226 1273 2227 1263 2227 1267 2227 990 2228 1269 2228 989 2228 1097 2229 1263 2229 1098 2229 1263 2230 1271 2230 1098 2230 1282 2231 1279 2231 1285 2231 1279 2232 1280 2232 1285 2232 1271 2233 1273 2233 1284 2233 1266 2234 1276 2234 1280 2234 1281 2235 1248 2235 1101 2235 1248 2236 1097 2236 1101 2236 1279 2237 1254 2237 1253 2237 1255 2238 1279 2238 1253 2238 1279 2239 1255 2239 1256 2239 1266 2240 1280 2240 1279 2240 1279 2241 1282 2241 1267 2241 1282 2242 1273 2242 1267 2242 1265 2243 1276 2243 1266 2243 1093 2244 1096 2244 1287 2244 1287 2245 1289 2245 1241 2245 1289 2246 1290 2246 1241 2246 1093 2247 1287 2247 1241 2247 1294 2248 1000 2248 998 2248 986 2249 1298 2249 985 2249 998 2250 1013 2250 1294 2250 982 2251 1295 2251 1297 2251 1004 2252 981 2252 1291 2252 1298 2253 1301 2253 985 2253 1004 2254 1291 2254 1292 2254 1299 2255 1291 2255 981 2255 985 2256 1301 2256 1299 2256 1300 2257 986 2257 983 2257 980 2258 1296 2258 1295 2258 986 2259 1300 2259 1298 2259 1002 2260 1004 2260 1292 2260 1299 2261 981 2261 985 2261 983 2262 1297 2262 1300 2262 1003 2263 1296 2263 980 2263 980 2264 1295 2264 982 2264 1297 2265 983 2265 982 2265 1001 2266 1293 2266 999 2266 1292 2267 1302 2267 1002 2267 1002 2268 1302 2268 1001 2268 1302 2269 1293 2269 1001 2269 1296 2270 1003 2270 1000 2270 1294 2271 1296 2271 1000 2271 1304 2272 1308 2272 993 2272 1305 2273 1303 2273 996 2273 997 2274 1307 2274 996 2274 1307 2275 1305 2275 996 2275 1303 2276 1306 2276 995 2276 1309 2277 997 2277 999 2277 996 2278 1303 2278 995 2278 1309 2279 999 2279 1293 2279 1306 2280 1304 2280 994 2280 1304 2281 993 2281 994 2281 1306 2282 994 2282 995 2282 1309 2283 1307 2283 997 2283 1310 2284 1281 2284 1101 2284 1069 2285 1310 2285 1068 2285 1068 2286 1310 2286 1101 2286 1159 2287 1272 2287 1217 2287 1217 2288 1272 2288 1238 2288 1217 2289 1238 2289 1219 2289 1313 2290 1315 2290 1314 2290 1313 2291 1329 2291 1315 2291 1318 2292 1311 2292 1312 2292 1318 2293 1312 2293 1319 2293 1320 2294 1314 2294 1321 2294 1584 2295 1316 2295 1317 2295 1320 2296 1313 2296 1314 2296 1021 2297 1318 2297 1319 2297 1584 2298 1317 2298 1322 2298 1323 2299 1320 2299 1321 2299 1324 2300 1584 2300 1322 2300 1323 2301 1321 2301 1325 2301 1326 2302 1010 2302 1327 2302 1326 2303 1009 2303 1010 2303 1328 2304 1323 2304 1325 2304 1329 2305 1324 2305 1322 2305 1328 2306 1325 2306 1330 2306 1329 2307 1322 2307 1315 2307 1331 2308 1328 2308 1330 2308 1316 2309 1326 2309 1327 2309 1316 2310 1327 2310 1317 2310 1311 2311 1331 2311 1330 2311 1311 2312 1330 2312 1312 2312 1010 2313 894 2313 892 2313 1335 2314 1312 2314 1330 2314 1334 2315 1335 2315 1330 2315 1333 2316 1010 2316 892 2316 1333 2317 1327 2317 1010 2317 1334 2318 1330 2318 1325 2318 1335 2319 1336 2319 1319 2319 1334 2320 1325 2320 1321 2320 1336 2321 1026 2321 1319 2321 1335 2322 1319 2322 1312 2322 1333 2323 1317 2323 1327 2323 1333 2324 1322 2324 1317 2324 1333 2325 1315 2325 1322 2325 1333 2326 1314 2326 1315 2326 1333 2327 1321 2327 1314 2327 1333 2328 1334 2328 1321 2328 1332 2329 1334 2329 1333 2329 1221 2330 1191 2330 1167 2330 1191 2331 1233 2331 1167 2331 1167 2332 1233 2332 1337 2332 1233 2333 1338 2333 1337 2333 1338 2334 1339 2334 1337 2334 1337 2335 1339 2335 1340 2335 947 2336 1339 2336 961 2336 1340 2337 1339 2337 947 2337 1340 2338 947 2338 1341 2338 1339 2339 1837 2339 961 2339 1166 2340 1167 2340 1337 2340 1164 2341 1166 2341 1347 2341 872 2342 878 2342 1350 2342 959 2343 1348 2343 948 2343 948 2344 1351 2344 1349 2344 1351 2345 1352 2345 1349 2345 1352 2346 1124 2346 1353 2346 872 2347 1350 2347 1354 2347 1349 2348 1352 2348 1353 2348 872 2349 1354 2349 873 2349 873 2350 1354 2350 876 2350 1353 2351 1124 2351 1164 2351 876 2352 1354 2352 1355 2352 1124 2353 1123 2353 1164 2353 948 2354 1348 2354 1351 2354 876 2355 1355 2355 1356 2355 876 2356 1356 2356 875 2356 875 2357 1356 2357 1357 2357 875 2358 1357 2358 964 2358 943 2359 942 2359 1358 2359 963 2360 964 2360 1348 2360 964 2361 1357 2361 1348 2361 963 2362 1348 2362 959 2362 942 2363 948 2363 1349 2363 1358 2364 942 2364 1349 2364 1359 2365 1363 2365 1406 2365 1365 2366 1360 2366 1011 2366 1366 2367 1363 2367 1359 2367 1366 2368 1359 2368 1360 2368 1366 2369 1368 2369 1363 2369 1369 2370 1362 2370 1367 2370 1370 2371 1369 2371 1367 2371 1368 2372 1370 2372 1367 2372 1229 2373 1238 2373 1372 2373 1370 2374 1339 2374 1369 2374 1338 2375 1369 2375 1339 2375 1370 2376 1374 2376 1339 2376 1228 2377 1376 2377 1235 2377 1228 2378 1235 2378 1230 2378 1375 2379 1372 2379 1405 2379 1377 2380 1228 2380 1227 2380 1377 2381 1376 2381 1228 2381 1379 2382 1226 2382 1229 2382 1235 2383 1376 2383 1380 2383 1377 2384 1381 2384 1376 2384 1376 2385 1381 2385 1380 2385 1227 2386 1226 2386 1379 2386 1378 2387 1235 2387 1380 2387 1377 2388 1383 2388 1382 2388 1383 2389 1372 2389 1375 2389 1381 2390 1377 2390 1382 2390 1386 2391 1383 2391 1375 2391 1385 2392 1380 2392 1381 2392 1391 2393 1386 2393 1375 2393 1390 2394 1389 2394 1385 2394 1391 2395 1375 2395 1384 2395 1391 2396 1383 2396 1386 2396 1387 2397 1234 2397 1237 2397 1394 2398 1391 2398 1384 2398 1393 2399 1396 2399 1395 2399 1382 2400 1383 2400 1391 2400 1389 2401 1399 2401 1387 2401 1393 2402 1382 2402 1391 2402 1388 2403 1394 2403 1384 2403 1393 2404 1391 2404 1394 2404 1397 2405 1395 2405 1396 2405 1398 2406 1390 2406 1397 2406 1394 2407 1396 2407 1393 2407 1399 2408 1234 2408 1387 2408 1396 2409 1400 2409 1397 2409 1396 2410 1394 2410 1388 2410 1012 2411 1396 2411 1388 2411 1400 2412 1396 2412 1012 2412 1372 2413 1379 2413 1229 2413 1372 2414 1383 2414 1379 2414 1403 2415 1399 2415 1401 2415 1400 2416 1360 2416 1398 2416 1377 2417 1227 2417 1379 2417 1012 2418 1011 2418 1400 2418 1377 2419 1379 2419 1383 2419 1387 2420 1236 2420 1378 2420 1367 2421 1362 2421 1361 2421 1364 2422 1367 2422 1361 2422 1225 2423 1362 2423 1232 2423 1017 2424 1371 2424 1365 2424 1369 2425 1232 2425 1362 2425 1371 2426 1017 2426 1404 2426 1371 2427 1404 2427 1374 2427 1371 2428 1374 2428 1373 2428 1233 2429 1232 2429 1338 2429 1369 2430 1338 2430 1232 2430 1380 2431 1385 2431 1378 2431 1395 2432 1381 2432 1382 2432 1392 2433 1381 2433 1395 2433 1395 2434 1382 2434 1393 2434 1390 2435 1385 2435 1392 2435 1378 2436 1236 2436 1235 2436 1392 2437 1395 2437 1397 2437 1389 2438 1390 2438 1401 2438 1237 2439 1236 2439 1387 2439 1389 2440 1401 2440 1399 2440 1400 2441 1398 2441 1397 2441 1234 2442 1399 2442 1403 2442 1385 2443 1387 2443 1378 2443 1364 2444 1403 2444 1401 2444 1361 2445 1224 2445 1231 2445 1403 2446 1364 2446 1361 2446 1389 2447 1387 2447 1385 2447 1362 2448 1225 2448 1224 2448 1362 2449 1224 2449 1361 2449 1367 2450 1364 2450 1363 2450 1011 2451 1017 2451 1365 2451 1365 2452 1366 2452 1360 2452 1367 2453 1363 2453 1368 2453 1366 2454 1373 2454 1370 2454 1366 2455 1370 2455 1368 2455 1365 2456 1371 2456 1373 2456 1365 2457 1373 2457 1366 2457 1370 2458 1373 2458 1374 2458 1381 2459 1392 2459 1385 2459 1401 2460 1390 2460 1402 2460 1397 2461 1390 2461 1392 2461 1398 2462 1402 2462 1390 2462 1403 2463 1231 2463 1234 2463 1402 2464 1398 2464 1360 2464 1406 2465 1401 2465 1402 2465 1361 2466 1231 2466 1403 2466 1364 2467 1401 2467 1406 2467 1359 2468 1406 2468 1402 2468 1011 2469 1360 2469 1400 2469 1359 2470 1402 2470 1360 2470 1363 2471 1364 2471 1406 2471 1272 2472 1372 2472 1238 2472 1407 2473 1372 2473 1272 2473 1407 2474 1405 2474 1372 2474 1408 2475 1405 2475 1407 2475 1408 2476 1375 2476 1405 2476 1409 2477 1384 2477 1375 2477 1409 2478 1375 2478 1408 2478 1410 2479 1384 2479 1409 2479 1410 2480 1388 2480 1384 2480 1411 2481 1388 2481 1410 2481 1411 2482 1012 2482 1388 2482 1013 2483 1012 2483 1411 2483 1412 2484 1272 2484 1275 2484 1294 2485 1455 2485 1296 2485 1407 2486 1272 2486 1412 2486 1408 2487 1407 2487 1412 2487 1408 2488 1412 2488 1413 2488 1416 2489 1414 2489 1459 2489 1415 2490 1245 2490 1243 2490 1421 2491 1416 2491 1459 2491 1422 2492 1423 2492 1419 2492 1429 2493 1427 2493 1426 2493 1430 2494 1245 2494 1415 2494 1428 2495 1421 2495 1431 2495 1433 2496 1432 2496 1429 2496 1293 2497 1428 2497 1431 2497 1435 2498 1414 2498 1416 2498 1430 2499 1415 2499 1414 2499 1436 2500 1434 2500 1433 2500 1436 2501 1437 2501 1434 2501 1441 2502 1416 2502 1421 2502 1441 2503 1435 2503 1416 2503 1435 2504 1430 2504 1414 2504 1409 2505 1408 2505 1442 2505 1428 2506 1441 2506 1421 2506 1417 2507 1419 2507 1443 2507 1417 2508 1418 2508 1419 2508 1418 2509 1417 2509 1444 2509 1419 2510 1445 2510 1443 2510 1417 2511 1430 2511 1435 2511 1417 2512 1443 2512 1430 2512 1424 2513 1418 2513 1420 2513 1302 2514 1428 2514 1293 2514 1291 2515 1424 2515 1420 2515 1422 2516 1424 2516 1425 2516 1447 2517 1441 2517 1428 2517 1299 2518 1425 2518 1424 2518 1446 2519 1245 2519 1430 2519 1444 2520 1417 2520 1435 2520 1444 2521 1435 2521 1441 2521 1446 2522 1246 2522 1245 2522 1443 2523 1446 2523 1430 2523 1302 2524 1447 2524 1428 2524 1429 2525 1426 2525 1448 2525 1427 2526 1429 2526 1432 2526 1447 2527 1444 2527 1441 2527 1292 2528 1447 2528 1302 2528 1433 2529 1429 2529 1452 2529 1432 2530 1433 2530 1434 2530 1420 2531 1444 2531 1447 2531 1445 2532 1446 2532 1443 2532 1438 2533 1437 2533 1439 2533 1292 2534 1420 2534 1447 2534 1440 2535 1439 2535 1437 2535 1439 2536 1413 2536 1438 2536 1442 2537 1413 2537 1439 2537 1442 2538 1408 2538 1413 2538 1449 2539 1442 2539 1439 2539 1409 2540 1442 2540 1449 2540 1450 2541 1446 2541 1445 2541 1451 2542 1246 2542 1446 2542 1420 2543 1418 2543 1444 2543 1422 2544 1419 2544 1418 2544 1422 2545 1418 2545 1424 2545 1450 2546 1451 2546 1446 2546 1452 2547 1429 2547 1448 2547 1436 2548 1433 2548 1453 2548 1451 2549 1247 2549 1246 2549 1450 2550 1445 2550 1419 2550 1299 2551 1424 2551 1291 2551 1449 2552 1439 2552 1440 2552 1449 2553 1440 2553 1455 2553 1423 2554 1450 2554 1419 2554 1456 2555 1451 2555 1450 2555 1410 2556 1409 2556 1449 2556 1410 2557 1449 2557 1411 2557 1457 2558 1423 2558 1422 2558 1427 2559 1456 2559 1450 2559 1457 2560 1422 2560 1425 2560 1456 2561 1247 2561 1451 2561 1423 2562 1427 2562 1450 2562 1452 2563 1448 2563 1458 2563 1453 2564 1433 2564 1452 2564 1454 2565 1436 2565 1453 2565 1301 2566 1457 2566 1425 2566 1426 2567 1423 2567 1457 2567 1426 2568 1427 2568 1423 2568 1411 2569 1449 2569 1455 2569 1460 2570 1457 2570 1301 2570 1420 2571 1292 2571 1291 2571 1448 2572 1426 2572 1457 2572 1425 2573 1299 2573 1301 2573 1460 2574 1300 2574 1448 2574 1432 2575 1456 2575 1427 2575 1458 2576 1448 2576 1300 2576 1453 2577 1452 2577 1458 2577 1460 2578 1448 2578 1457 2578 1454 2579 1453 2579 1295 2579 1454 2580 1295 2580 1296 2580 1298 2581 1460 2581 1301 2581 1455 2582 1440 2582 1296 2582 1411 2583 1455 2583 1294 2583 1432 2584 1461 2584 1456 2584 1461 2585 1247 2585 1456 2585 1421 2586 1459 2586 1474 2586 1300 2587 1460 2587 1298 2587 1461 2588 1277 2588 1247 2588 1013 2589 1411 2589 1294 2589 1434 2590 1461 2590 1432 2590 1297 2591 1458 2591 1300 2591 1297 2592 1453 2592 1458 2592 1462 2593 1277 2593 1461 2593 1438 2594 1462 2594 1461 2594 1438 2595 1461 2595 1434 2595 1462 2596 1275 2596 1277 2596 1295 2597 1453 2597 1297 2597 1437 2598 1438 2598 1434 2598 1440 2599 1437 2599 1436 2599 1412 2600 1275 2600 1462 2600 1440 2601 1436 2601 1454 2601 1296 2602 1440 2602 1454 2602 1413 2603 1462 2603 1438 2603 1413 2604 1412 2604 1462 2604 1464 2605 1466 2605 1465 2605 1242 2606 1290 2606 1464 2606 1465 2607 1467 2607 1463 2607 1467 2608 1307 2608 1463 2608 1305 2609 1467 2609 1470 2609 1468 2610 1306 2610 1480 2610 1469 2611 1467 2611 1466 2611 1469 2612 1466 2612 1464 2612 1468 2613 1304 2613 1306 2613 1472 2614 1308 2614 1304 2614 1470 2615 1303 2615 1305 2615 1469 2616 1464 2616 1473 2616 1309 2617 1293 2617 1431 2617 1470 2618 1469 2618 1475 2618 1470 2619 1475 2619 1303 2619 1289 2620 1471 2620 1290 2620 1477 2621 1475 2621 1469 2621 1473 2622 1478 2622 1469 2622 1289 2623 1479 2623 1471 2623 1479 2624 1478 2624 1473 2624 1477 2625 1469 2625 1478 2625 1278 2626 1483 2626 1274 2626 1278 2627 1472 2627 1483 2627 1270 2628 1472 2628 1278 2628 1477 2629 1484 2629 1480 2629 1484 2630 1477 2630 1478 2630 1485 2631 1472 2631 1269 2631 1479 2632 1486 2632 1478 2632 1289 2633 1288 2633 1479 2633 1480 2634 1484 2634 1468 2634 1486 2635 1479 2635 1288 2635 1476 2636 1459 2636 1414 2636 1486 2637 1286 2637 1487 2637 1286 2638 1486 2638 1283 2638 1276 2639 1488 2639 1280 2639 1431 2640 1421 2640 1474 2640 1243 2641 1476 2641 1415 2641 1474 2642 1463 2642 1431 2642 1465 2643 1474 2643 1459 2643 1307 2644 1309 2644 1463 2644 1305 2645 1307 2645 1467 2645 1465 2646 1466 2646 1467 2646 1464 2647 1290 2647 1471 2647 1469 2648 1470 2648 1467 2648 1471 2649 1473 2649 1464 2649 1475 2650 1306 2650 1303 2650 1473 2651 1471 2651 1479 2651 1480 2652 1306 2652 1475 2652 1480 2653 1475 2653 1477 2653 1487 2654 1484 2654 1478 2654 1487 2655 1478 2655 1486 2655 1486 2656 1288 2656 1283 2656 1468 2657 1484 2657 1481 2657 1481 2658 1484 2658 1487 2658 1468 2659 1481 2659 1482 2659 1488 2660 1487 2660 1286 2660 1488 2661 1481 2661 1487 2661 1482 2662 1304 2662 1468 2662 1488 2663 1286 2663 1285 2663 1304 2664 1482 2664 1483 2664 1276 2665 1481 2665 1488 2665 1482 2666 1481 2666 1276 2666 1488 2667 1285 2667 1280 2667 1274 2668 1483 2668 1482 2668 1304 2669 1483 2669 1472 2669 1274 2670 1482 2670 1276 2670 1269 2671 1472 2671 1270 2671 1485 2672 1308 2672 1472 2672 1485 2673 993 2673 1308 2673 1269 2674 990 2674 1485 2674 990 2675 993 2675 1485 2675 1414 2676 1415 2676 1476 2676 1463 2677 1309 2677 1431 2677 1465 2678 1463 2678 1474 2678 1242 2679 1476 2679 1243 2679 1476 2680 1465 2680 1459 2680 1242 2681 1464 2681 1476 2681 1465 2682 1476 2682 1464 2682 1494 2683 1491 2683 1492 2683 1252 2684 1491 2684 1493 2684 1493 2685 1491 2685 1494 2685 1493 2686 1494 2686 970 2686 970 2687 1494 2687 966 2687 1499 2688 1255 2688 1251 2688 1497 2689 1498 2689 1502 2689 966 2690 1503 2690 1489 2690 1490 2691 1501 2691 1503 2691 966 2692 1490 2692 1503 2692 1504 2693 1500 2693 1496 2693 1250 2694 1248 2694 1281 2694 1255 2695 1505 2695 1258 2695 1505 2696 1501 2696 1490 2696 1258 2697 1506 2697 1259 2697 1258 2698 1505 2698 1490 2698 1506 2699 1258 2699 1490 2699 1492 2700 1262 2700 1506 2700 1492 2701 1506 2701 1490 2701 1492 2702 1491 2702 1262 2702 1250 2703 1507 2703 1251 2703 1489 2704 969 2704 966 2704 966 2705 1492 2705 1490 2705 1492 2706 966 2706 1494 2706 1507 2707 1499 2707 1251 2707 1502 2708 1489 2708 1503 2708 1499 2709 1507 2709 1501 2709 970 2710 975 2710 1493 2710 1499 2711 1501 2711 1505 2711 984 2712 1252 2712 1493 2712 1505 2713 1255 2713 1499 2713 1506 2714 1262 2714 1259 2714 1493 2715 975 2715 984 2715 1504 2716 1501 2716 1507 2716 1495 2717 1250 2717 1281 2717 1250 2718 1495 2718 1500 2718 1495 2719 1496 2719 1500 2719 1497 2720 1504 2720 1496 2720 1504 2721 1497 2721 1502 2721 1250 2722 1500 2722 1507 2722 969 2723 1489 2723 1498 2723 1504 2724 1507 2724 1500 2724 1502 2725 1498 2725 1489 2725 1502 2726 1501 2726 1504 2726 1503 2727 1501 2727 1502 2727 1512 2728 1310 2728 1069 2728 1511 2729 1512 2729 1069 2729 1516 2730 1518 2730 1495 2730 1514 2731 1513 2731 1498 2731 1520 2732 1508 2732 1511 2732 1310 2733 1516 2733 1281 2733 1514 2734 1523 2734 1513 2734 1521 2735 1523 2735 1514 2735 1515 2736 1524 2736 1517 2736 1521 2737 1497 2737 1519 2737 1521 2738 1526 2738 1523 2738 1520 2739 1522 2739 1530 2739 1528 2740 1518 2740 1310 2740 1518 2741 1527 2741 1519 2741 1518 2742 1496 2742 1495 2742 1529 2743 1537 2743 967 2743 1527 2744 1534 2744 1519 2744 1526 2745 969 2745 1513 2745 1526 2746 1513 2746 1523 2746 1536 2747 1510 2747 1535 2747 1528 2748 1310 2748 1512 2748 1509 2749 1535 2749 1510 2749 1526 2750 967 2750 969 2750 1534 2751 1536 2751 1533 2751 1515 2752 1508 2752 1520 2752 1511 2753 1534 2753 1527 2753 1511 2754 1528 2754 1512 2754 1520 2755 1524 2755 1515 2755 1535 2756 1509 2756 1526 2756 1530 2757 1522 2757 1532 2757 1522 2758 1525 2758 1532 2758 1529 2759 1517 2759 1524 2759 1509 2760 967 2760 1526 2760 1517 2761 1529 2761 967 2761 1511 2762 1508 2762 1534 2762 1510 2763 1536 2763 1508 2763 1510 2764 1508 2764 1515 2764 1498 2765 1513 2765 969 2765 1498 2766 1497 2766 1514 2766 1516 2767 1495 2767 1281 2767 1519 2768 1496 2768 1518 2768 1519 2769 1497 2769 1496 2769 1497 2770 1521 2770 1514 2770 1522 2771 1520 2771 1511 2771 1520 2772 1530 2772 1524 2772 1525 2773 1522 2773 1511 2773 1525 2774 1511 2774 1069 2774 1531 2775 1529 2775 1524 2775 1531 2776 1524 2776 1530 2776 1527 2777 1528 2777 1511 2777 1518 2778 1516 2778 1310 2778 1518 2779 1528 2779 1527 2779 1534 2780 1508 2780 1536 2780 1521 2781 1533 2781 1526 2781 1533 2782 1521 2782 1519 2782 1519 2783 1534 2783 1533 2783 1533 2784 1535 2784 1526 2784 1536 2785 1535 2785 1533 2785 1517 2786 1509 2786 1510 2786 1517 2787 1510 2787 1515 2787 1517 2788 967 2788 1509 2788 1525 2789 1539 2789 1532 2789 1531 2790 1530 2790 1532 2790 1538 2791 1532 2791 1539 2791 1538 2792 1531 2792 1532 2792 1069 2793 1539 2793 1525 2793 1531 2794 1540 2794 1529 2794 1531 2795 1541 2795 1540 2795 1539 2796 1064 2796 1542 2796 1538 2797 1541 2797 1531 2797 1541 2798 1538 2798 1542 2798 978 2799 1544 2799 973 2799 1546 2800 973 2800 1544 2800 1549 2801 1545 2801 1548 2801 1546 2802 1544 2802 1547 2802 1035 2803 1542 2803 1064 2803 1086 2804 1548 2804 1088 2804 1547 2805 1058 2805 1546 2805 1076 2806 1547 2806 1549 2806 1550 2807 1087 2807 932 2807 934 2808 973 2808 1550 2808 1087 2809 1550 2809 1546 2809 932 2810 934 2810 1550 2810 1540 2811 1537 2811 1529 2811 978 2812 967 2812 1537 2812 1544 2813 978 2813 1545 2813 1547 2814 1544 2814 1545 2814 1548 2815 1545 2815 1543 2815 1549 2816 1547 2816 1545 2816 1549 2817 1548 2817 1086 2817 1086 2818 1076 2818 1549 2818 1538 2819 1539 2819 1542 2819 1543 2820 1537 2820 1540 2820 1541 2821 1543 2821 1540 2821 1537 2822 1543 2822 1545 2822 1551 2823 1541 2823 1542 2823 978 2824 1537 2824 1545 2824 1551 2825 1542 2825 1035 2825 1551 2826 1543 2826 1541 2826 1551 2827 1035 2827 1088 2827 1548 2828 1543 2828 1551 2828 1548 2829 1551 2829 1088 2829 1058 2830 1547 2830 1076 2830 1546 2831 1550 2831 973 2831 1051 2832 1546 2832 1058 2832 1051 2833 1087 2833 1546 2833 1352 2834 1552 2834 1078 2834 1352 2835 1078 2835 1124 2835 1351 2836 1552 2836 1352 2836 1553 2837 1554 2837 1552 2837 1553 2838 1552 2838 1351 2838 1555 2839 1554 2839 1553 2839 1555 2840 1556 2840 1554 2840 1555 2841 1009 2841 1556 2841 1557 2842 1009 2842 1555 2842 1558 2843 1589 2843 1559 2843 1558 2844 1326 2844 1589 2844 1561 2845 1562 2845 1560 2845 1563 2846 1558 2846 1559 2846 1072 2847 1560 2847 1071 2847 1072 2848 1561 2848 1560 2848 1563 2849 1559 2849 1562 2849 1564 2850 1563 2850 1562 2850 1561 2851 1564 2851 1562 2851 1556 2852 1009 2852 1326 2852 1556 2853 1326 2853 1558 2853 1554 2854 1556 2854 1558 2854 1554 2855 1558 2855 1563 2855 1552 2856 1564 2856 1561 2856 1552 2857 1554 2857 1563 2857 1552 2858 1563 2858 1564 2858 1078 2859 1552 2859 1561 2859 1078 2860 1561 2860 1072 2860 1565 2861 1318 2861 1021 2861 1565 2862 1021 2862 1566 2862 1567 2863 1565 2863 1566 2863 1568 2864 1567 2864 1566 2864 1570 2865 1567 2865 1568 2865 1569 2866 1568 2866 1052 2866 1569 2867 1570 2867 1568 2867 1055 2868 1569 2868 1052 2868 1574 2869 1320 2869 1323 2869 1574 2870 1313 2870 1320 2870 1576 2871 1574 2871 1575 2871 1577 2872 1311 2872 1318 2872 1577 2873 1318 2873 1565 2873 1580 2874 1577 2874 1565 2874 1578 2875 1576 2875 1579 2875 1329 2876 1581 2876 1324 2876 1578 2877 1581 2877 1329 2877 1580 2878 1565 2878 1570 2878 1581 2879 1578 2879 1579 2879 1579 2880 1576 2880 1582 2880 1582 2881 1583 2881 1585 2881 1582 2882 1585 2882 1579 2882 1581 2883 1584 2883 1324 2883 1581 2884 1579 2884 1585 2884 1586 2885 1580 2885 1570 2885 1587 2886 1075 2886 1071 2886 1581 2887 1588 2887 1584 2887 1586 2888 1570 2888 1569 2888 1571 2889 1311 2889 1577 2889 1590 2890 1586 2890 1569 2890 1571 2891 1331 2891 1311 2891 1591 2892 1571 2892 1577 2892 1591 2893 1577 2893 1580 2893 1055 2894 1590 2894 1569 2894 1586 2895 1591 2895 1580 2895 1571 2896 1328 2896 1331 2896 1572 2897 1328 2897 1571 2897 1572 2898 1573 2898 1328 2898 1057 2899 1590 2899 1055 2899 1328 2900 1573 2900 1323 2900 1593 2901 1572 2901 1591 2901 1574 2902 1323 2902 1573 2902 1593 2903 1591 2903 1586 2903 1594 2904 1593 2904 1586 2904 1581 2905 1585 2905 1588 2905 1590 2906 1594 2906 1586 2906 1593 2907 1573 2907 1572 2907 1021 2908 1592 2908 1566 2908 1596 2909 1594 2909 1590 2909 1062 2910 1596 2910 1590 2910 1062 2911 1590 2911 1057 2911 1597 2912 1573 2912 1593 2912 1598 2913 1597 2913 1593 2913 1598 2914 1593 2914 1594 2914 1597 2915 1574 2915 1573 2915 1597 2916 1575 2916 1574 2916 1596 2917 1598 2917 1594 2917 1571 2918 1591 2918 1572 2918 1576 2919 1313 2919 1574 2919 1599 2920 1598 2920 1596 2920 1600 2921 1575 2921 1597 2921 1600 2922 1597 2922 1598 2922 1075 2923 1596 2923 1062 2923 1075 2924 1599 2924 1596 2924 1578 2925 1329 2925 1313 2925 1578 2926 1313 2926 1576 2926 1599 2927 1600 2927 1598 2927 1582 2928 1575 2928 1600 2928 1582 2929 1576 2929 1575 2929 1583 2930 1582 2930 1600 2930 1570 2931 1565 2931 1567 2931 1583 2932 1600 2932 1599 2932 1587 2933 1583 2933 1599 2933 1075 2934 1587 2934 1599 2934 1589 2935 1316 2935 1584 2935 1595 2936 1585 2936 1583 2936 1560 2937 1583 2937 1587 2937 1589 2938 1584 2938 1588 2938 1071 2939 1560 2939 1587 2939 1560 2940 1595 2940 1583 2940 1559 2941 1589 2941 1588 2941 1562 2942 1588 2942 1585 2942 1562 2943 1559 2943 1588 2943 1562 2944 1585 2944 1595 2944 1560 2945 1562 2945 1595 2945 1326 2946 1316 2946 1589 2946 1606 2947 1592 2947 1020 2947 1603 2948 1592 2948 1606 2948 1604 2949 1607 2949 1601 2949 1603 2950 1607 2950 1604 2950 1607 2951 1605 2951 1601 2951 1024 2952 1606 2952 1020 2952 1603 2953 1606 2953 1608 2953 1042 2954 1045 2954 1617 2954 1609 2955 1042 2955 1617 2955 1603 2956 1608 2956 1612 2956 1603 2957 1612 2957 1607 2957 1612 2958 1610 2958 1607 2958 1610 2959 1613 2959 1605 2959 1610 2960 1612 2960 1614 2960 1612 2961 1616 2961 1614 2961 1614 2962 1615 2962 1610 2962 1613 2963 1610 2963 1615 2963 1609 2964 1036 2964 1042 2964 1617 2965 1613 2965 1615 2965 1028 2966 1036 2966 1620 2966 1614 2967 1616 2967 1622 2967 1614 2968 1622 2968 1615 2968 1617 2969 1046 2969 1613 2969 1019 2970 1611 2970 1623 2970 1023 2971 1611 2971 1019 2971 1622 2972 1616 2972 1623 2972 1022 2973 1040 2973 1625 2973 1022 2974 1625 2974 1626 2974 1022 2975 1627 2975 1040 2975 1618 2976 1624 2976 1622 2976 1623 2977 1619 2977 1622 2977 1619 2978 1618 2978 1622 2978 1074 2979 1081 2979 1629 2979 1629 2980 1081 2980 1628 2980 1628 2981 1626 2981 1629 2981 1628 2982 1023 2982 1626 2982 1602 2983 1048 2983 1052 2983 1020 2984 1592 2984 1021 2984 1601 2985 1605 2985 1602 2985 1605 2986 1048 2986 1602 2986 1610 2987 1605 2987 1607 2987 1048 2988 1613 2988 1046 2988 1608 2989 1606 2989 1024 2989 1617 2990 1615 2990 1624 2990 1622 2991 1624 2991 1615 2991 1601 2992 1052 2992 1568 2992 1018 2993 1608 2993 1024 2993 1613 2994 1048 2994 1605 2994 1018 2995 1612 2995 1608 2995 1018 2996 1616 2996 1612 2996 1046 2997 1617 2997 1045 2997 1623 2998 1616 2998 1019 2998 1624 2999 1609 2999 1617 2999 1609 3000 1624 3000 1618 3000 1623 3001 1611 3001 1619 3001 1620 3002 1609 3002 1618 3002 1620 3003 1036 3003 1609 3003 1081 3004 1028 3004 1621 3004 1621 3005 1028 3005 1620 3005 1621 3006 1620 3006 1618 3006 1619 3007 1628 3007 1621 3007 1619 3008 1621 3008 1618 3008 1628 3009 1619 3009 1611 3009 1628 3010 1081 3010 1621 3010 1625 3011 1074 3011 1629 3011 1625 3012 1054 3012 1074 3012 1628 3013 1611 3013 1023 3013 1626 3014 1625 3014 1629 3014 1040 3015 1054 3015 1625 3015 1022 3016 1626 3016 1023 3016 1082 3017 1040 3017 1627 3017 933 3018 1082 3018 1627 3018 1022 3019 935 3019 1627 3019 935 3020 933 3020 1627 3020 1601 3021 1602 3021 1052 3021 1568 3022 1566 3022 1604 3022 1604 3023 1601 3023 1568 3023 1604 3024 1566 3024 1592 3024 1604 3025 1592 3025 1603 3025 1675 3026 951 3026 1631 3026 951 3027 952 3027 1631 3027 1761 3028 921 3028 1760 3028 918 3029 1635 3029 1636 3029 1634 3030 881 3030 1637 3030 1634 3031 1637 3031 1633 3031 881 3032 879 3032 1637 3032 1635 3033 1632 3033 1638 3033 1632 3034 1633 3034 1639 3034 1635 3035 1638 3035 1636 3035 1632 3036 1639 3036 1638 3036 1633 3037 1637 3037 1639 3037 1637 3038 879 3038 1643 3038 885 1698 883 1698 1644 1698 1640 1698 885 1698 1644 1698 1641 3039 1640 3039 1646 3039 1640 3040 1644 3040 1646 3040 1639 3041 1637 3041 1647 3041 1637 3042 1643 3042 1647 3042 879 1698 889 1698 1643 1698 1641 3043 1646 3043 1642 3043 1639 3044 1647 3044 1648 3044 1638 3045 1639 3045 1648 3045 1644 3046 883 3046 1649 3046 1638 3047 1648 3047 1650 3047 1638 3048 1650 3048 921 3048 1644 3049 1649 3049 1651 3049 1646 3050 1644 3050 1651 3050 1636 3051 1638 3051 921 3051 1642 3052 1646 3052 1652 3052 1646 3053 1651 3053 1652 3053 883 3054 881 3054 1649 3054 1642 3055 1652 3055 1635 3055 1645 3056 1642 3056 1635 3056 1645 3057 1635 3057 918 3057 1652 3058 1651 3058 1632 3058 1651 3059 1649 3059 1633 3059 1651 3060 1633 3060 1632 3060 1649 3061 881 3061 1634 3061 1649 3062 1634 3062 1633 3062 1635 3063 1652 3063 1632 3063 912 3064 913 3064 1645 3064 918 3065 912 3065 1645 3065 1645 3066 913 3066 1642 3066 913 3067 914 3067 1642 3067 1642 3068 914 3068 1641 3068 1641 3069 914 3069 1640 3069 914 3070 916 3070 1640 3070 916 1698 890 1698 885 1698 1640 1698 916 1698 885 1698 1340 3071 1656 3071 1337 3071 1654 3072 1340 3072 1341 3072 1342 3073 1654 3073 1341 3073 1658 3074 1347 3074 1656 3074 1655 3075 1342 3075 946 3075 1656 3076 1653 3076 1659 3076 945 3077 1657 3077 1655 3077 945 3078 1660 3078 1657 3078 1662 3079 1659 3079 1657 3079 1663 3080 1164 3080 1347 3080 1658 3081 1656 3081 1659 3081 1664 3082 1661 3082 1662 3082 1656 3083 1347 3083 1337 3083 1657 3084 1660 3084 1662 3084 1347 3085 1166 3085 1337 3085 1662 3086 1358 3086 1349 3086 1662 3087 1349 3087 1664 3087 1663 3088 1661 3088 1664 3088 1658 3089 1663 3089 1347 3089 1654 3090 1342 3090 1657 3090 1661 3091 1663 3091 1658 3091 1660 3092 945 3092 943 3092 1661 3093 1659 3093 1662 3093 1661 3094 1658 3094 1659 3094 1665 3095 1164 3095 1663 3095 1663 3096 1664 3096 1665 3096 1660 3097 1358 3097 1662 3097 1653 3098 1657 3098 1659 3098 1353 3099 1164 3099 1665 3099 1358 3100 1660 3100 943 3100 1349 3101 1665 3101 1664 3101 1349 3102 1353 3102 1665 3102 1657 3103 1342 3103 1655 3103 1653 3104 1656 3104 1654 3104 1657 3105 1653 3105 1654 3105 1654 3106 1656 3106 1340 3106 1015 3107 1666 3107 1345 3107 991 3108 992 3108 1346 3108 1015 3109 1014 3109 1666 3109 1016 3110 1015 3110 1345 3110 1016 3111 1345 3111 1344 3111 1339 3112 1404 3112 1343 3112 1014 3113 991 3113 1346 3113 1374 3114 1404 3114 1339 3114 1014 3115 1346 3115 1666 3115 1016 3116 1344 3116 1343 3116 1404 3117 1016 3117 1343 3117 1348 3118 1553 3118 1351 3118 1555 3119 1553 3119 1348 3119 1555 3120 1348 3120 1357 3120 1557 3121 1555 3121 1357 3121 1008 3122 1557 3122 1357 3122 1008 3123 1357 3123 1356 3123 1008 3124 1356 3124 1355 3124 1007 3125 1008 3125 1355 3125 1007 3126 1355 3126 1354 3126 1006 3127 1354 3127 1350 3127 1006 3128 1007 3128 1354 3128 1006 3129 1350 3129 878 3129 1006 3130 878 3130 888 3130 1667 3131 955 3131 1668 3131 1668 3132 955 3132 957 3132 1668 3133 957 3133 1669 3133 957 3134 958 3134 1669 3134 1669 3135 958 3135 1670 3135 1670 3136 958 3136 941 3136 1671 3137 1670 3137 941 3137 952 3138 1630 3138 1631 3138 1631 3139 1630 3139 1672 3139 1630 3140 955 3140 1672 3140 1672 3141 955 3141 1667 3141 1673 3142 870 3142 1674 3142 870 3143 951 3143 1674 3143 1674 3144 951 3144 1675 3144 859 3145 860 3145 1676 3145 1676 3146 860 3146 1677 3146 1677 3147 860 3147 1678 3147 860 3148 869 3148 1678 3148 1678 3149 869 3149 1679 3149 869 3150 870 3150 1679 3150 1679 3151 870 3151 1673 3151 1696 3152 861 3152 1676 3152 1676 3153 861 3153 859 3153 868 3154 871 3154 1680 3154 1680 3155 871 3155 1681 3155 871 3156 861 3156 1681 3156 1681 3157 861 3157 1696 3157 1682 3158 864 3158 1683 3158 864 3159 865 3159 1684 3159 1683 3160 864 3160 1684 3160 865 3161 866 3161 1685 3161 1684 3162 865 3162 1685 3162 866 3163 867 3163 1686 3163 1685 3164 866 3164 1686 3164 1686 3165 867 3165 1680 3165 867 3166 868 3166 1680 3166 862 3167 863 3167 1688 3167 1687 3168 862 3168 1688 3168 1688 3169 863 3169 1682 3169 863 3170 864 3170 1682 3170 1689 3171 950 3171 1690 3171 950 3172 953 3172 1690 3172 1690 3173 953 3173 1691 3173 953 3174 954 3174 1692 3174 1691 3175 953 3175 1692 3175 954 3176 862 3176 1687 3176 1692 3177 954 3177 1687 3177 950 3178 1689 3178 1693 3178 941 3179 1694 3179 1671 3179 950 3180 1694 3180 941 3180 950 3181 1693 3181 1694 3181 1695 3182 1103 3182 1111 3182 1695 3183 1696 3183 1676 3183 1111 3184 1696 3184 1695 3184 1695 3185 1676 3185 1103 3185 1697 3186 1103 3186 1676 3186 1676 3187 1677 3187 1697 3187 1696 3188 1111 3188 1681 3188 1697 3189 1104 3189 1103 3189 1681 3190 1113 3190 1680 3190 1681 3191 1111 3191 1113 3191 1678 3192 1697 3192 1677 3192 1698 3193 1680 3193 1113 3193 1105 3194 1104 3194 1697 3194 1105 3195 1697 3195 1678 3195 1698 3196 1113 3196 1118 3196 1686 3197 1680 3197 1698 3197 1678 3198 1699 3198 1105 3198 1679 3199 1699 3199 1678 3199 1117 3200 1698 3200 1118 3200 1686 3201 1117 3201 1685 3201 1686 3202 1698 3202 1117 3202 1699 3203 1121 3203 1105 3203 1679 3204 1673 3204 1699 3204 1117 3205 1110 3205 1684 3205 1685 3206 1117 3206 1684 3206 1684 3207 1110 3207 1109 3207 1683 3208 1684 3208 1109 3208 1682 3209 1683 3209 1109 3209 1700 3210 1153 3210 1171 3210 1171 3211 1121 3211 1699 3211 1674 3212 1171 3212 1699 3212 1675 3213 1700 3213 1674 3213 1674 3214 1699 3214 1673 3214 1631 3215 1700 3215 1675 3215 1674 3216 1700 3216 1171 3216 1682 3217 1109 3217 1688 3217 1109 3218 1107 3218 1688 3218 1107 3219 1687 3219 1688 3219 1672 3220 1667 3220 1701 3220 1182 3221 1672 3221 1701 3221 1701 3222 1211 3222 1182 3222 1700 3223 1182 3223 1153 3223 1672 3224 1182 3224 1700 3224 1672 3225 1700 3225 1631 3225 1214 3226 1211 3226 1701 3226 1668 3227 1214 3227 1701 3227 1668 3228 1701 3228 1667 3228 1670 3229 1216 3229 1702 3229 1670 3230 1702 3230 1669 3230 1216 3231 1214 3231 1702 3231 1702 3232 1668 3232 1669 3232 1214 3233 1668 3233 1702 3233 1130 3234 1690 3234 1691 3234 1130 3235 1691 3235 1692 3235 1130 3236 1692 3236 1131 3236 1131 3237 1692 3237 1687 3237 1131 3238 1687 3238 1107 3238 1163 3239 1216 3239 1670 3239 1671 3240 1163 3240 1670 3240 1130 3241 1222 3241 1689 3241 1690 3242 1130 3242 1689 3242 1703 3243 1671 3243 1694 3243 1689 3244 1222 3244 1693 3244 1703 3245 1163 3245 1671 3245 1694 3246 1165 3246 1703 3246 1693 3247 1222 3247 1173 3247 1165 3248 1163 3248 1703 3248 1173 3249 1165 3249 1694 3249 1173 3250 1694 3250 1693 3250 949 3251 944 3251 856 3251 857 3252 949 3252 856 3252 960 3253 857 3253 965 3253 960 3254 949 3254 857 3254 962 3255 960 3255 965 3255 1705 3256 893 3256 1704 3256 1705 3257 892 3257 893 3257 1706 3258 892 3258 1705 3258 1333 3259 892 3259 1706 3259 1710 3260 1711 3260 1707 3260 1708 3261 1711 3261 1710 3261 1712 3262 1708 3262 1710 3262 1709 3263 1712 3263 1710 3263 1709 3264 1710 3264 1332 3264 1705 3265 1713 3265 1708 3265 1705 3266 1708 3266 1712 3266 1332 3267 1706 3267 1709 3267 1706 3268 1712 3268 1709 3268 1706 3269 1705 3269 1712 3269 1333 3270 1706 3270 1332 3270 1704 3271 1713 3271 1705 3271 1332 3272 1714 3272 1334 3272 1719 3273 1721 3273 1715 3273 1721 3274 1720 3274 1715 3274 1719 3275 1718 3275 1717 3275 1334 3276 1719 3276 1717 3276 1714 3277 1721 3277 1719 3277 1721 3278 1707 3278 1720 3278 1715 3279 1716 3279 1718 3279 1719 3280 1715 3280 1718 3280 1714 3281 1719 3281 1334 3281 1721 3282 1710 3282 1707 3282 1714 3283 1332 3283 1710 3283 1714 3284 1710 3284 1721 3284 1723 3285 1718 3285 1716 3285 1723 3286 1716 3286 1722 3286 1738 3287 1717 3287 1718 3287 1738 3288 1718 3288 1723 3288 1335 3289 1334 3289 1717 3289 1335 3290 1717 3290 1738 3290 1725 3291 1726 3291 1729 3291 1728 3292 928 3292 927 3292 1336 3293 1729 3293 1727 3293 1336 3294 1725 3294 1729 3294 1731 3295 1730 3295 1724 3295 1728 3296 927 3296 1025 3296 1731 3297 1724 3297 1725 3297 1732 3298 1728 3298 1025 3298 1733 3299 1725 3299 1336 3299 1733 3300 1731 3300 1725 3300 1732 3301 1025 3301 1026 3301 1731 3302 1734 3302 1730 3302 1335 3303 1733 3303 1336 3303 1737 3304 1732 3304 1026 3304 1733 3305 1736 3305 1731 3305 1736 3306 1734 3306 1731 3306 1738 3307 1736 3307 1733 3307 1736 3308 1722 3308 1734 3308 1335 3309 1738 3309 1733 3309 1723 3310 1722 3310 1736 3310 1732 3311 1735 3311 1728 3311 1729 3312 1735 3312 1732 3312 1738 3313 1723 3313 1736 3313 1737 3314 1026 3314 1336 3314 1725 3315 1724 3315 1726 3315 1727 3316 1729 3316 1732 3316 1729 3317 1726 3317 1735 3317 1727 3318 1732 3318 1737 3318 1336 3319 1727 3319 1737 3319 1704 3320 893 3320 1739 3320 1739 3321 893 3321 917 3321 1704 3322 1739 3322 1740 3322 893 3323 887 3323 884 3323 917 3324 893 3324 884 3324 1728 3325 1741 3325 928 3325 1742 3326 1741 3326 1728 3326 1735 3327 1742 3327 1728 3327 1743 3328 1742 3328 1735 3328 1726 3329 1743 3329 1735 3329 1724 3330 1743 3330 1726 3330 1744 3331 1743 3331 1724 3331 1730 3332 1744 3332 1724 3332 1745 3333 1744 3333 1730 3333 1734 3334 1745 3334 1730 3334 1746 3335 1745 3335 1734 3335 1722 3336 1746 3336 1734 3336 1747 3337 1746 3337 1722 3337 1747 3338 1722 3338 1716 3338 1748 3339 1747 3339 1716 3339 1716 3340 1749 3340 1748 3340 1715 3341 1749 3341 1716 3341 1715 3342 1750 3342 1749 3342 1720 3343 1750 3343 1715 3343 1707 3344 1751 3344 1750 3344 1707 3345 1750 3345 1720 3345 1711 3346 1751 3346 1707 3346 1711 3347 1752 3347 1751 3347 1708 3348 1752 3348 1711 3348 1708 3349 1753 3349 1752 3349 1713 3350 1753 3350 1708 3350 1713 3351 1740 3351 1753 3351 1704 3352 1740 3352 1713 3352 917 3353 911 3353 1739 3353 1740 3354 911 3354 900 3354 1739 3355 911 3355 1740 3355 1753 3356 1740 3356 900 3356 906 3357 1753 3357 900 3357 906 3358 903 3358 1753 3358 923 3359 1752 3359 903 3359 1751 3360 1752 3360 923 3360 903 3361 1752 3361 1753 3361 1754 3362 1755 3362 1756 3362 1748 3363 1749 3363 1754 3363 1755 3364 1750 3364 1756 3364 1755 3365 1749 3365 1750 3365 1756 3366 1751 3366 923 3366 1756 3367 1750 3367 1751 3367 1754 3368 1749 3368 1755 3368 1754 3369 1747 3369 1748 3369 1754 3370 920 3370 1747 3370 1745 3371 920 3371 919 3371 1741 3372 1742 3372 922 3372 920 3373 1746 3373 1747 3373 1764 3374 1741 3374 922 3374 1763 3375 1764 3375 922 3375 1745 3376 919 3376 1744 3376 1743 3377 1744 3377 919 3377 1746 3378 920 3378 1745 3378 922 3379 1743 3379 919 3379 922 3380 1742 3380 1743 3380 1005 3381 1855 3381 992 3381 872 3382 874 3382 877 3382 878 3383 872 3383 877 3383 1650 3384 1775 3384 1773 3384 1650 3385 1773 3385 1774 3385 1772 3386 1648 3386 1647 3386 1648 3387 1771 3387 1775 3387 1647 3388 1770 3388 1772 3388 1647 3389 1768 3389 1770 3389 1643 3390 1767 3390 1768 3390 921 3391 1763 3391 922 3391 921 3392 1774 3392 1760 3392 889 1698 1769 1698 1757 1698 889 1698 1767 1698 1643 1698 1741 3393 1758 3393 928 3393 847 3394 50 3394 826 3394 735 3395 852 3395 854 3395 733 3396 850 3396 851 3396 732 3397 849 3397 848 3397 49 3398 50 3398 846 3398 49 3399 843 3399 853 3399 1764 3400 1758 3400 1741 3400 1650 3401 1648 3401 1775 3401 1772 3402 1771 3402 1648 3402 1647 3403 1643 3403 1768 3403 921 3404 1762 3404 1763 3404 921 3405 1650 3405 1774 3405 889 1698 1757 1698 1767 1698 968 3406 1787 3406 1788 3406 1786 3407 1789 3407 1782 3407 92 3408 1781 3408 1782 3408 976 3409 1777 3409 974 3409 971 3410 1780 3410 1787 3410 86 3411 1778 3411 87 3411 972 3412 1779 3412 1780 3412 979 3413 1788 3413 1786 3413 89 3414 1777 3414 1776 3414 977 3415 1784 3415 976 3415 979 3416 1785 3416 977 3416 90 3417 1776 3417 1781 3417 87 3418 1779 3418 939 3418 83 3419 1783 3419 86 3419 92 3420 1789 3420 83 3420 968 3421 971 3421 1787 3421 1776 3422 1777 3422 1782 3422 1784 3423 1785 3423 1786 3423 1786 3424 1788 3424 1789 3424 1787 3425 1780 3425 1788 3425 1779 3426 1778 3426 1789 3426 1783 3427 1789 3427 1778 3427 1782 3428 1781 3428 1776 3428 1777 3429 1784 3429 1786 3429 1788 3430 1780 3430 1779 3430 1779 3431 1789 3431 1788 3431 1782 3432 1777 3432 1786 3432 92 3433 90 3433 1781 3433 976 3434 1784 3434 1777 3434 971 3435 972 3435 1780 3435 86 3436 1783 3436 1778 3436 972 3437 939 3437 1779 3437 979 3438 968 3438 1788 3438 89 3439 974 3439 1777 3439 977 3440 1785 3440 1784 3440 979 3441 1786 3441 1785 3441 90 3442 89 3442 1776 3442 87 3443 1778 3443 1779 3443 83 3444 1789 3444 1783 3444 92 3445 1782 3445 1789 3445 18 3446 1791 3446 1794 3446 858 3447 1799 3447 876 3447 855 3448 1801 3448 1802 3448 876 3449 1798 3449 873 3449 873 3450 1797 3450 872 3450 3 3451 1792 3451 1808 3451 0 3452 1809 3452 1810 3452 872 3453 1796 3453 17 3453 1838 3454 1343 3454 1839 3454 1794 3455 21 3455 18 3455 1818 3456 1666 3456 1817 3456 1852 3457 434 3457 1851 3457 1821 3458 1345 3458 1820 3458 1833 3459 432 3459 431 3459 1819 3460 1345 3460 1666 3460 1831 3461 1830 3461 751 3461 1834 3462 431 3462 427 3462 1826 3463 1825 3463 432 3463 1824 3464 1827 3464 1343 3464 1859 3465 751 3465 1830 3465 1842 3466 1846 3466 947 3466 1844 3467 76 3467 62 3467 1802 3468 1803 3468 855 3468 1840 3469 1839 3469 1343 3469 1815 3470 1346 3470 1857 3470 1854 3471 1346 3471 992 3471 1816 3472 1666 3472 1346 3472 1852 3473 1853 3473 992 3473 1835 3474 1836 3474 431 3474 1831 3475 433 3475 1828 3475 1821 3476 1822 3476 1344 3476 1855 3477 1854 3477 992 3477 1804 3478 1805 3478 855 3478 1849 3479 1851 3479 434 3479 1803 3480 1804 3480 855 3480 1823 3481 1344 3481 1822 3481 1829 3482 433 3482 432 3482 1810 3483 1807 3483 0 3483 1790 3484 1791 3484 17 3484 1807 3485 1806 3485 0 3485 1836 3486 1833 3486 431 3486 93 3487 992 3487 1853 3487 76 3488 1845 3488 1834 3488 1841 3489 961 3489 1837 3489 1339 3490 1838 3490 1837 3490 1005 3491 1856 3491 1855 3491 18 3492 17 3492 1791 3492 858 3493 855 3493 1800 3493 1800 3494 1799 3494 858 3494 855 3495 1805 3495 1800 3495 876 3496 1799 3496 1798 3496 873 3497 1798 3497 1797 3497 1809 3498 0 3498 1808 3498 3 3499 21 3499 1793 3499 3 3500 1793 3500 1792 3500 1808 3501 0 3501 3 3501 872 3502 1797 3502 1796 3502 1838 3503 1339 3503 1343 3503 1794 3504 1793 3504 21 3504 1818 3505 1819 3505 1666 3505 1852 3506 992 3506 434 3506 1821 3507 1344 3507 1345 3507 1833 3508 1832 3508 432 3508 1819 3509 1820 3509 1345 3509 1834 3510 1835 3510 431 3510 432 3511 1832 3511 1826 3511 1825 3512 1829 3512 432 3512 1343 3513 1823 3513 1824 3513 1827 3514 1840 3514 1343 3514 1859 3515 434 3515 751 3515 961 3516 1841 3516 1842 3516 1842 3517 1843 3517 1846 3517 1846 3518 1847 3518 1848 3518 947 3519 961 3519 1842 3519 1846 3520 1848 3520 947 3520 62 3521 1814 3521 1813 3521 1812 3522 1811 3522 62 3522 62 3523 1813 3523 1812 3523 1845 3524 76 3524 1844 3524 1811 3525 1844 3525 62 3525 1815 3526 1816 3526 1346 3526 1854 3527 1857 3527 1346 3527 1816 3528 1817 3528 1666 3528 1831 3529 751 3529 433 3529 434 3530 1859 3530 1850 3530 1850 3531 1849 3531 434 3531 1823 3532 1343 3532 1344 3532 1829 3533 1828 3533 433 3533 17 3534 1796 3534 1795 3534 1795 3535 1790 3535 17 3535
+
+
+
+
+
+
+ -0.001230061 -0.08825844 -0.01570701 0.001855731 -0.08876121 -0.02375388 0.003545284 -0.08878087 -0.01839298 0.002912819 -0.08877468 -0.02295589 0.004446268 -0.08827072 -0.02154719 0.003852069 -0.08876985 -0.02131778 0.004616618 -0.0882948 -0.0199179 0.00432837 -0.08858829 -0.02072292 0.004155874 -0.08870941 -0.01967525 0.004369974 -0.08833891 -0.01871931 0.002599656 -0.08853298 -0.02376759 0.00237149 -0.08877897 -0.01693314 0.003786921 -0.08858287 -0.02241599 0.003365576 -0.08863538 -0.01744747 0.003825128 -0.08820146 -0.01748609 -0.001938879 -0.08874779 -0.01657629 -0.002142727 -0.08845925 -0.0162242 -0.002068281 -0.08807855 -0.01592922 0.003189504 -0.08830332 -0.01683652 0.002202153 -0.0883919 -0.01614505 5.3544e-4 -0.08877742 -0.0161578 0.003418982 -0.08803212 -0.02345407 0.001095831 -0.0883463 -0.01570928 0.003946185 -0.0880934 -0.02273052 -6.40279e-5 -0.08830082 -0.01552975 -6.17289e-4 -0.08860176 -0.01584917 0.00244683 -0.08498775 -0.0153672 0.002920508 -0.07690161 -0.01545542 -0.004406273 -0.07583689 -0.01703739 0.001987159 -0.08682101 -0.01554507 -0.005019426 -0.08244973 -0.01751846 0.002911806 -0.0758301 -0.01561492 -0.004309475 -0.08829164 -0.0184955 -0.004718542 -0.07690143 -0.01725435 -0.005106151 -0.08013021 -0.0175327 -0.004962027 -0.08635628 -0.01887983 -0.004906654 -0.0774368 -0.01746129 -0.005057752 -0.07821476 -0.01754534 0.004806399 -0.07581883 -0.01771563 0.002047777 -0.0829544 -0.01491737 0.002713382 -0.0774486 -0.01526749 -0.005443215 -0.08318245 -0.0187208 0.001655817 -0.08053338 -0.01464217 -0.005507588 -0.08013385 -0.01844722 0.00202471 -0.0779823 -0.01486378 0.001192927 -0.08300203 -0.01464754 0.00196588 -0.07580852 -0.0151593 -0.005458891 -0.07750123 -0.01878947 7.92568e-4 -0.08594703 -0.0150153 -0.005053341 -0.08673226 -0.02017194 -0.005399346 -0.08466273 -0.01955366 -0.00571984 -0.08135163 -0.0196461 9.23015e-4 -0.07790899 -0.01457947 -0.004616141 -0.08829468 -0.02042734 0.001079559 -0.075823 -0.01487952 -0.005710661 -0.07904893 -0.01966369 5.25934e-4 -0.07972854 -0.01443994 4.96488e-5 -0.08531284 -0.01484698 -0.005425155 -0.08465963 -0.02043682 -0.005540013 -0.07680213 -0.02000159 -0.005654454 -0.08263963 -0.02033638 1.67439e-4 -0.07683658 -0.0146324 -1.26264e-4 -0.08321624 -0.01456499 -0.00571537 -0.08127903 -0.02039444 -1.60259e-4 -0.08263576 -0.01451987 -9.89911e-4 -0.085886 -0.01503223 -0.005005538 -0.08627909 -0.02136296 -2.41246e-4 -0.08127814 -0.01444751 -2.59754e-4 -0.08040529 -0.01443243 -0.005740404 -0.07936739 -0.0204429 -0.005641162 -0.08181506 -0.02118659 -0.005384266 -0.0835756 -0.02168709 -0.005626738 -0.07768917 -0.02030706 -0.002353549 -0.07582139 -0.01531136 -0.0055812 -0.07739621 -0.02080678 -2.11364e-4 -0.07895094 -0.01446861 -0.001103103 -0.08272635 -0.01461535 -1.37895e-4 -0.07762908 -0.01454859 -0.005401015 -0.07582092 -0.0201984 -7.91284e-4 -0.07698106 -0.01465493 -9.59221e-4 -0.07923752 -0.01450783 -0.005263507 -0.07581061 -0.02132529 -0.005520522 -0.08052247 -0.02182954 -0.005419433 -0.07759475 -0.02176553 -0.004745304 -0.0855956 -0.02252393 -0.00195682 -0.08430689 -0.01504498 -0.001764774 -0.08060014 -0.01467645 -0.001895785 -0.07787168 -0.01483803 -0.003905773 -0.08812963 -0.02277255 -0.004993975 -0.07582306 -0.02221876 -0.005233824 -0.07937061 -0.02254641 -0.005074322 -0.08277809 -0.02264142 -0.005175292 -0.08155441 -0.02261286 -0.002707481 -0.08586668 -0.01569235 -0.001405358 -0.07581776 -0.01496005 -0.002637565 -0.08370202 -0.01529455 -0.002429485 -0.0825808 -0.01505088 -0.005111515 -0.07821476 -0.02262449 -0.005014479 -0.07738453 -0.02267819 -0.004628241 -0.07581549 -0.02295124 -0.002898633 -0.08314412 -0.0153687 -0.003326237 -0.08461761 -0.01586627 0.004634857 -0.08576828 -0.01768893 -0.003012776 -0.08219254 -0.0153495 0.0048123 -0.08304095 -0.01726782 -0.003049314 -0.08127713 -0.01533204 0.004161894 -0.08497858 -0.01672786 -0.00320965 -0.07581657 -0.01584059 0.004469633 -0.07581228 -0.01715308 -0.003756046 -0.08124327 -0.01582032 0.004833817 -0.0792827 -0.01709794 -0.003970861 -0.08347785 -0.01622498 0.004825413 -0.07832324 -0.01716685 0.004526615 -0.07838249 -0.01669847 0.004175484 -0.08167189 -0.01623046 0.003215372 -0.08664327 -0.01625967 -0.00383234 -0.07809013 -0.01596659 0.003946125 -0.07581782 -0.01648092 -0.004054844 -0.08630567 -0.01700693 -0.004181206 -0.08811563 -0.01798373 -0.004673421 -0.08190584 -0.01687949 0.003619194 -0.08369302 -0.01594543 0.003911197 -0.07975989 -0.01594752 -0.004688978 -0.07986783 -0.01682412 0.003651857 -0.07704645 -0.0159446 -0.004763305 -0.08482742 -0.0175895 -0.004672408 -0.0830723 -0.01705116 0.003201663 -0.08244436 -0.01549685 0.003302693 -0.0808072 -0.01547569 -0.004752159 -0.0837419 -0.01731669 0.003088891 -0.08319115 -0.01549834 0.002858579 -0.08373564 -0.0154218 -0.004671812 -0.07824623 -0.01692235 0.003217816 -0.07581847 -0.02450639 -1.2959e-5 -0.07581651 -0.01477557 0.005086958 -0.07581013 -0.01840794 0.005349159 -0.07584196 -0.01928788 0.005397975 -0.07581841 -0.0203619 0.005316615 -0.07580959 -0.02103883 0.005093336 -0.07582229 -0.02198976 0.004644334 -0.07582068 -0.02293086 0.004276633 -0.07582592 -0.0234645 0.003834128 -0.07581895 -0.02397412 0.002381563 -0.07581979 -0.02502042 -0.004801273 -0.08282005 -0.02312481 -0.004838228 -0.08147895 -0.02323716 -0.004832208 -0.07975143 -0.02330601 -0.005141258 -0.07891362 -0.02263021 -0.004856646 -0.08372706 -0.02285254 0.004809081 -0.07745224 -0.01730293 0.004835367 -0.08134537 -0.0171085 0.004841089 -0.08023995 -0.0170753 2.12069e-4 -0.08370989 -0.01462942 -0.00292927 -0.07755357 -0.01537215 -0.003158271 -0.08042943 -0.01534169 -0.003028512 -0.07848995 -0.01535362 -0.002625465 -0.07686555 -0.01528668 -0.005735874 -0.08040046 -0.02042394 -0.005543768 -0.08373719 -0.02001929 -0.005602419 -0.08320802 -0.02027922 -0.005060732 -0.07905709 -0.01751637 0.002641141 -0.07996714 -0.01506847 0.002651691 -0.07905483 -0.01510798 0.002654314 -0.07834756 -0.015145 0.002681136 -0.08256411 -0.01519036 0.002645075 -0.08120632 -0.01508527 0.004668295 -0.07820099 -0.02344036 -0.002407789 -0.08592694 -0.02480942 -0.003257453 -0.07821553 -0.0248447 0.004395723 -0.08580893 -0.02302396 0.004737675 -0.08381235 -0.02305018 0.004675865 -0.08283066 -0.02332139 -0.001491308 -0.08818823 -0.0246073 -0.002916455 -0.0768975 -0.02489447 0.00504446 -0.08399766 -0.02243965 -0.002683043 -0.08254975 -0.02515679 -0.002686262 -0.07584953 -0.02487272 0.004873454 -0.08641999 -0.02176219 -0.00130558 -0.08593606 -0.02523428 -0.002056181 -0.08428984 -0.02526938 -0.001954257 -0.08156573 -0.02556872 -0.002710402 -0.07744079 -0.02508378 -0.002681136 -0.07819688 -0.02516877 0.005432069 -0.08062285 -0.02214348 0.004743635 -0.07687628 -0.02304577 -0.002136528 -0.07798182 -0.02543354 0.005264878 -0.07747048 -0.02217084 -0.001363337 -0.07976961 -0.0257706 -5.61171e-4 -0.08631891 -0.02527976 -0.001204073 -0.0822789 -0.02575415 0.005476951 -0.08272731 -0.02161639 3.01031e-4 -0.08808398 -0.02488809 -0.001076281 -0.07752168 -0.02570563 0.005097329 -0.08649885 -0.02033543 0.005404472 -0.0842958 -0.02117133 -1.20325e-4 -0.08379495 -0.02571815 -4.86869e-4 -0.07581466 -0.02555531 0.005558669 -0.0777055 -0.02115774 0.005713105 -0.08164072 -0.0206992 -4.59967e-4 -0.0786401 -0.02585244 0.005727052 -0.0793972 -0.0207082 6.04467e-4 -0.08591216 -0.02535635 -3.73343e-4 -0.07774561 -0.02579259 8.48708e-5 -0.08321171 -0.02577412 1.58228e-4 -0.08265084 -0.02582556 0.005541026 -0.07681506 -0.02034527 0.005599856 -0.08319491 -0.02009934 0.001041293 -0.08353179 -0.02566581 2.43898e-4 -0.08127444 -0.02590215 0.005126357 -0.08589643 -0.01911813 0.005658626 -0.08264303 -0.02000159 0.005611419 -0.08244454 -0.01919615 0.001369297 -0.07987385 -0.02579182 0.005712926 -0.08127915 -0.01996022 0.005631089 -0.07769924 -0.02003502 0.005716562 -0.07948559 -0.01995652 0.00162363 -0.08639615 -0.02502757 7.60066e-4 -0.07740837 -0.02574133 0.005614936 -0.07837617 -0.01911199 0.004775166 -0.08635783 -0.01830005 0.005272388 -0.08377462 -0.01842594 0.001801848 -0.07751846 -0.02551341 0.001486659 -0.0821461 -0.02570354 0.002111971 -0.08460915 -0.02519446 0.005493521 -0.08016204 -0.01841247 0.00288403 -0.08610439 -0.02450001 0.005354523 -0.07764005 -0.01840502 0.001755118 -0.07581853 -0.02527052 0.004892468 -0.08366304 -0.01755166 0.005135834 -0.08253312 -0.01774466 0.002419114 -0.08068126 -0.02538383 0.004920244 -0.07695734 -0.01758867 0.002944767 -0.08280527 -0.02498441 -0.003890872 -0.08571529 -0.02372825 -0.004874706 -0.07685297 -0.02282041 0.003495395 -0.08228367 -0.02468395 -0.00447458 -0.08218824 -0.02372032 0.00301218 -0.08190637 -0.02500373 0.003056526 -0.08131259 -0.0250132 -0.002932429 -0.08817958 -0.02381986 -0.004282653 -0.07756513 -0.02384728 0.003620624 -0.07751572 -0.02449238 -0.003969252 -0.08234465 -0.02426636 0.003971636 -0.08418828 -0.0239951 -0.004112482 -0.07583171 -0.02368307 -0.0031268 -0.08636075 -0.02423578 0.004188597 -0.08160507 -0.02408885 -0.004045307 -0.07917046 -0.02425122 0.004110038 -0.07917892 -0.02418512 -0.003142774 -0.08323413 -0.02483296 -0.003326833 -0.0800355 -0.02486932 0.004699766 -0.08025789 -0.02349889 -0.002856731 -0.08373707 -0.02492719 0.002492845 -0.0830267 -0.02520591 0.002452254 -0.07926869 -0.02533406 0.002439022 -0.08200049 -0.02532374 0.002685487 -0.0837168 -0.02502751 0.005090713 -0.07947117 -0.02281957 0.004960954 -0.07800269 -0.02287799 0.005003392 -0.08246833 -0.02283769 0.005080223 -0.08118915 -0.0228284 2.33458e-4 -0.08036744 -0.02590638 -4.01839e-4 -0.08287191 -0.0257982 1.48528e-4 -0.07768756 -0.0258072 0.005747437 -0.08040964 -0.01990449 0.005541086 -0.08381092 -0.0203033 -0.002647817 -0.07998734 -0.02526938 -0.002654731 -0.07905143 -0.02523905 -0.002620041 -0.08101946 -0.02529358 0.00249207 -0.0778473 -0.02523124 0.002454996 -0.07864552 -0.02531015 0.004685342 -0.081959 -0.02342391 0.001446664 -0.08841198 -0.02450954 -0.00373286 -0.088786 -0.02166312 -0.00293076 -0.08878266 -0.01751238 -0.003935933 -0.08874177 -0.01902627 -0.00451833 -0.08841896 -0.01959681 -0.004229128 -0.08867305 -0.02046895 -0.004327476 -0.08835858 -0.0217303 0.002323508 -0.08827447 -0.0241723 -0.003929734 -0.08861285 -0.01837241 -0.002448678 -0.08878237 -0.02328985 -0.003233194 -0.08855932 -0.02320408 -0.001645386 -0.08861196 -0.02422052 -0.003254473 -0.08855885 -0.01719975 -3.76365e-5 -0.0887748 -0.02422362 -0.003271043 -0.08811575 -0.01677501 -1.18197e-4 -0.08847099 -0.02467072 -0.001774132 -0.07581704 -0.02527898 8.86961e-4 -0.07581901 -0.02550441 -0.003339529 -0.07581222 -0.02441102 -0.005235195 -0.07581084 -0.01885372 -0.004908442 -0.07582497 -0.01788496 -0.003893077 -0.07581835 -0.01643764 0.003116786 -0.07938385 -0.02500391 0.002898693 -0.07745647 -0.02497464 0.002632141 -0.07688105 -0.02505975 -0.00481224 -0.07812297 -0.02316564 0.005153 -0.07866972 -0.01773703 0.00520873 -0.07967329 -0.0177564 -4.98219e-4 -0.08108383 -0.0258882 -1.88584e-4 -0.07695978 -0.02572602 -4.98997e-4 -0.07990032 -0.0258904 -0.002446532 -0.07870548 -0.01503252 -0.002418518 -0.0800873 -0.01495707 -0.003217935 -0.08232492 -0.02485471 0.003234088 -0.0781579 -0.01550173 0.003274083 -0.07922494 -0.01547712 4.6018e-4 -0.07579386 -0.01539129 0.002468526 -0.07579386 -0.01608866 0.003889322 -0.07579386 -0.01738214 -0.002259254 -0.07579386 -0.0159102 0.004659116 -0.07579386 -0.0191347 -0.004216969 -0.07579386 -0.01787143 0.004727244 -0.07579386 -0.02083808 0.00419265 -0.07579386 -0.0224567 -0.004808485 -0.07579386 -0.02057611 0.002800941 -0.07579386 -0.02407878 -0.003996074 -0.07579386 -0.02278447 1.86244e-4 -0.07579386 -0.02499634 -0.002489149 -0.07579386 -0.02428412 -0.003231704 -0.07149857 -0.02370291 -0.002000272 -0.07149744 -0.01585966 -0.003376841 -0.07150393 -0.01681602 -0.001698136 -0.0715062 -0.02460438 -0.004328668 -0.07150548 -0.01818895 -0.004755914 -0.07150167 -0.02024698 0.001301646 -0.07151323 -0.0247417 0.003191709 -0.07150477 -0.02377051 -0.004345774 -0.07150149 -0.02213948 -0.004398465 -0.07136237 -0.02097791 -0.003719747 -0.07094103 -0.01789343 -0.001599252 -0.07068502 -0.02466166 -0.00421071 -0.07098561 -0.02122068 0.00130856 -0.0712161 -0.02433151 0.002403378 -0.07112449 -0.0237962 -0.003814399 -0.07132452 -0.01793026 -0.001508772 -0.07086867 -0.02433753 -0.001656532 -0.07121527 -0.02420032 -0.004635334 -0.07068109 -0.0212773 0.002824664 -0.07140713 -0.02370125 -0.004480004 -0.070679 -0.0185092 -0.004099428 -0.07068198 -0.0225628 -0.003889322 -0.07106775 -0.02213174 -0.003596544 -0.07093632 -0.02266639 -8.55932e-4 -0.07125079 -0.02445596 -0.004144668 -0.07122707 -0.01883578 -0.004334211 -0.07086235 -0.01922786 -4.94809e-5 -0.07106089 -0.02454406 -1.86716e-4 -0.07069426 -0.02493059 -3.74373e-4 -0.0715025 -0.02490985 -0.004732251 -0.07068186 -0.01987189 -0.003584206 -0.0714001 -0.02292317 -0.003120303 -0.07067626 -0.02377665 2.72134e-4 -0.07075625 -0.02471208 -0.004298448 -0.07116806 -0.01947808 -0.002644181 -0.07069468 -0.01621848 -0.003511548 -0.07068228 -0.01697129 2.03777e-4 -0.07132643 -0.02459621 -0.002982497 -0.07094639 -0.01698118 -0.001908779 -0.07094424 -0.01623708 0.001313388 -0.0706833 -0.02475887 -0.002892315 -0.07132703 -0.02353698 -0.002953231 -0.07132512 -0.01686537 -0.001763105 -0.07131832 -0.01611876 -0.002693593 -0.07094973 -0.02360898 -0.004353523 -0.07094287 -0.02039718 0.002767384 -0.07069033 -0.02404427 0.002137184 -0.07084494 -0.02409708 0.002082765 -0.06880754 -0.02452898 -0.004714131 -0.0688005 -0.02107119 -0.002627253 -0.0688042 -0.01612174 -0.004151165 -0.06880623 -0.02260851 -0.002969384 -0.06880336 -0.02394682 -0.003948271 -0.06879556 -0.01745045 -0.001787245 -0.06879842 -0.02464812 -0.004745483 -0.0688132 -0.01917332 -1.21168e-4 -0.06879854 -0.02501761 -0.004276633 -0.06816262 -0.02163279 -0.004053652 -0.06807225 -0.01803344 -0.001969337 -0.06825643 -0.02416193 -0.002501547 -0.06858235 -0.02391988 -0.003673017 -0.06851232 -0.01767778 -0.004529774 -0.06864094 -0.02040338 0.002463281 -0.06852978 -0.02387267 0.001753032 -0.06826591 -0.02426326 -0.004267573 -0.06854468 -0.02140653 -0.001652777 -0.06799107 -0.02473944 -0.001133561 -0.06811636 -0.02457553 -0.004316747 -0.06799483 -0.01790583 -0.004035532 -0.06798231 -0.0228942 -4.08262e-4 -0.06799483 -0.02503407 -0.004836976 -0.0679897 -0.01955986 -0.004274189 -0.06858372 -0.01887828 -0.003357768 -0.06822597 -0.02312517 -7.90929e-4 -0.06871074 -0.02469629 -6.55541e-4 -0.06841409 -0.02455765 -0.003115594 -0.06805926 -0.02354431 -0.003743112 -0.06860429 -0.02268028 -0.00450623 -0.06810355 -0.01951849 8.69387e-4 -0.06799095 -0.02495235 -9.40977e-5 -0.06810325 -0.02471995 -0.00437361 -0.06841623 -0.01943498 0.002350807 -0.06798869 -0.02445816 4.83838e-4 -0.06850475 -0.02459228 -0.00283575 -0.06799262 -0.02414137 -0.001946568 -0.06798791 -0.01571649 -0.003252625 -0.06798273 -0.01654422 -0.002907991 -0.06811171 -0.01665562 -0.001734495 -0.06811124 -0.01597851 -0.002748847 -0.06851172 -0.01667487 -0.004745185 -0.06798964 -0.02129513 0.001160979 -0.06809777 -0.02458477 -0.001690745 -0.06851834 -0.01607441 0.001052916 -0.06869667 -0.02463001 -0.004413843 -0.06831753 -0.02040416 0.00314486 -0.06802076 -0.02369683 -0.00456804 -0.06610161 -0.02229374 -0.00356096 -0.06610149 -0.0237354 0.002580821 -0.06610596 -0.02452373 -0.003610134 -0.06610691 -0.01666522 -0.002321839 -0.06608879 -0.02462726 -0.00117135 -0.06609767 -0.02508813 -0.004909694 -0.06611049 -0.01908463 5.67796e-4 -0.06609576 -0.02519232 -0.005011916 -0.06609749 -0.02067399 0.001289427 -0.06579542 -0.02468144 -0.00313735 -0.0658009 -0.02364259 -0.004744172 -0.06529194 -0.02215957 -0.003432393 -0.06554263 -0.01694828 0.001949489 -0.06555008 -0.02444928 -0.002601206 -0.0659306 -0.02413946 -0.002901792 -0.06531554 -0.02425175 -0.001993596 -0.06556844 -0.02444458 -0.00484395 -0.06527888 -0.01843214 -0.003843784 -0.06533735 -0.01711541 -0.00415194 -0.06529086 -0.01713842 -0.004604518 -0.06554663 -0.02108579 0.002055287 -0.06529271 -0.0248875 -0.004672706 -0.06583636 -0.02068626 -0.00443238 -0.06539481 -0.0220927 -0.003544867 -0.06588143 -0.0170477 0.002114415 -0.06535959 -0.02455091 -0.002399146 -0.06529635 -0.02473068 -0.004194796 -0.06554853 -0.01805645 -0.00443691 -0.06609421 -0.01786768 -0.001537919 -0.06589925 -0.02466315 -0.004576206 -0.06553149 -0.01905936 -0.004352033 -0.06579548 -0.02187037 -0.00423336 -0.06588143 -0.01807183 0.002799928 -0.06587958 -0.0239945 -0.003832817 -0.06528687 -0.0236178 -0.001214802 -0.06527167 -0.02513349 -3.06013e-4 -0.06554716 -0.02487599 -0.00390166 -0.06580132 -0.02275842 -0.004809558 -0.06538826 -0.01978445 3.59728e-4 -0.06529062 -0.02530348 -0.004633188 -0.06588143 -0.019239 -0.003426969 -0.06554663 -0.02339005 -0.005159497 -0.06528848 -0.02017176 5.85308e-5 -0.06594312 -0.02493679 -0.003096878 -0.06529682 -0.01607847 -0.002656996 -0.06538027 -0.01615142 -0.002564847 -0.06574708 -0.01627492 8.01294e-4 -0.06538128 -0.02492207 -0.002016425 -0.06592547 -0.01587212 -0.001940429 -0.06529366 -0.01540291 9.09726e-5 -0.06529283 -0.01501214 -0.005381405 -0.06339061 -0.02018094 -0.001324236 -0.06339102 -0.01494342 -0.005172789 -0.06340157 -0.02170825 -0.004680514 -0.06339222 -0.02284044 -0.00278455 -0.06339228 -0.01555407 0.004558801 -0.06340736 -0.01730537 0.00409311 -0.06529152 -0.01707172 0.00350368 -0.06339842 -0.01606619 -0.003922045 -0.06339323 -0.01647847 0.002989292 -0.06529045 -0.01598256 -0.004753589 -0.06339204 -0.01764291 0.00182873 -0.06529366 -0.01536995 0.002133071 -0.06339293 -0.01522392 -0.005234241 -0.06339108 -0.01891714 5.8952e-4 -0.06340026 -0.01480424 -2.61723e-4 -0.06276369 -0.0252453 8.07552e-4 -0.06272673 -0.02521497 -0.004298031 -0.06331831 -0.01719456 -0.005012929 -0.06299293 -0.02023077 -0.003732204 -0.0629996 -0.01683658 -3.93031e-4 -0.06339663 -0.02554214 -0.003392696 -0.06276428 -0.02397596 -0.005082011 -0.06331264 -0.02134561 -0.003485739 -0.06257438 -0.0244168 9.19636e-4 -0.06258809 -0.02557998 6.13157e-4 -0.06308418 -0.02518045 -0.002963602 -0.0633307 -0.02447581 -0.005276799 -0.06258082 -0.02169221 -0.003200054 -0.06309503 -0.02406013 -0.005146205 -0.0625956 -0.01826351 -0.004424214 -0.06271845 -0.01763391 -0.003371894 -0.06338816 -0.0243752 -0.002638101 -0.06258445 -0.02500206 -0.00479871 -0.0629996 -0.02160269 0.001115739 -0.06339114 -0.02545017 -0.002349913 -0.06271845 -0.02470248 -0.005148172 -0.06267738 -0.02103459 -0.002023458 -0.06310284 -0.02480638 -0.004579544 -0.06309586 -0.01805675 0.001488566 -0.06333255 -0.02517229 -0.004303455 -0.06259346 -0.02357345 -0.005095362 -0.06262266 -0.01883572 0.002107918 -0.06266236 -0.0249204 0.001827836 -0.06299299 -0.02482813 -0.001342177 -0.06258577 -0.02549827 -0.005416989 -0.06259506 -0.0193184 -0.001309812 -0.06274139 -0.02510017 -0.004368901 -0.06274205 -0.02278316 0.002223134 -0.06258881 -0.02517229 -0.002327442 -0.06339174 -0.02502322 -0.004834592 -0.06281203 -0.01866525 0.002771437 -0.06300264 -0.02434492 -0.004217028 -0.0631721 -0.02298283 -0.001306176 -0.0633164 -0.02518564 0.002594888 -0.06339263 -0.02489358 -0.003671646 -0.06258749 -0.01608192 -0.003633499 -0.06266349 -0.0164355 -0.002107501 -0.06267708 -0.01545596 -0.005128324 -0.06333178 -0.01924026 -0.004106462 -0.06340515 -0.02365183 -0.002685487 -0.0629419 -0.01594084 -0.001290798 -0.06339335 -0.02540642 -0.004988551 -0.06309622 -0.01949322 -0.002820253 -0.06330984 -0.01584798 -2.28112e-4 -0.06321036 -0.0252487 -0.005463898 -0.06259477 -0.02060168 -0.004958212 -0.05896776 -0.02305454 -0.004718959 -0.06216186 -0.01730817 -0.004914343 -0.05816942 -0.01718634 -0.004028797 -0.05885595 -0.02425789 -0.002965271 -0.05844718 -0.02509748 -1.22788e-4 -0.05817484 -0.02592867 -8.10335e-4 -0.05964034 -0.02583885 -0.005170941 -0.0606181 -0.01793295 -0.004941046 -0.06061828 -0.01746577 -0.005218923 -0.05969977 -0.01788491 -0.004984736 -0.05971968 -0.01743942 -0.004685223 -0.0606184 -0.01703763 -0.004371464 -0.06061667 -0.01662379 -0.004724979 -0.05971968 -0.01701343 -0.004424631 -0.05970823 -0.01659703 6.43779e-4 -0.06061923 -0.02577209 1.25248e-4 -0.06061834 -0.02580899 6.8337e-4 -0.05971699 -0.02583909 1.24564e-4 -0.05971968 -0.02585804 -3.73015e-4 -0.06061822 -0.02579689 -3.74339e-4 -0.05971974 -0.02584815 -8.9008e-4 -0.06061738 -0.02573567 -0.002991557 -0.05863261 -0.01526921 -0.001852214 -0.05883651 -0.02559572 -0.003992319 -0.05880665 -0.0160607 -0.001017272 -0.05974704 -0.02577656 -1.22608e-4 -0.06216156 -0.0256989 -0.005555272 -0.05894351 -0.01879054 0.001910388 -0.05961513 -0.02555096 -0.005735397 -0.05887562 -0.02001965 0.002733886 -0.05965042 -0.02517575 -0.005655467 -0.05874037 -0.02109694 -0.005479574 -0.05876821 -0.02185463 -0.004962742 -0.0614959 -0.02271097 -0.004823625 -0.0454939 -0.01697444 0.001513898 -0.0454939 -0.02575904 -0.00350517 -0.0454939 -0.01559877 0.001247942 -0.04943978 -0.02578634 0.00127691 -0.04599386 -0.02578055 6.91279e-4 -0.04995536 -0.02588587 -0.001972377 -0.0454939 -0.01472312 -2.52076e-5 -0.04599398 -0.02594578 1.08585e-5 -0.04948252 -0.02592426 -6.36077e-4 -0.0454939 -0.02591133 -0.004032671 -0.04674571 -0.02425462 -0.004056513 -0.0454939 -0.02428287 -0.004736304 -0.04678529 -0.02336227 -0.005191564 -0.0454939 -0.02270865 -0.005087733 -0.04679214 -0.02277415 -0.002531111 -0.0454939 -0.02536487 -0.003232777 -0.05204272 -0.02489906 -0.003547549 -0.04738587 -0.02468568 -0.005473256 -0.05203723 -0.02184921 -0.005679488 -0.0454939 -0.02113199 -0.005511939 -0.04683107 -0.02168828 -0.005702555 -0.0454939 -0.01918172 0.002904653 -0.07149386 -0.01636821 8.86906e-4 -0.071491 -0.01545137 0.004382908 -0.0715053 -0.02202403 0.004726231 -0.07150334 -0.0205422 -0.001023173 -0.07149481 -0.01552343 0.004599213 -0.0715062 -0.01895821 0.004319131 -0.07094496 -0.01953858 0.004074633 -0.07094973 -0.01860731 0.001207888 -0.07077658 -0.0158382 0.003505587 -0.07083088 -0.02292162 0.002258956 -0.07136499 -0.01632905 0.003393352 -0.07110273 -0.02293044 0.004425823 -0.07134509 -0.01966065 0.001586735 -0.07070118 -0.0156728 0.001669228 -0.07104492 -0.01614964 0.003919601 -0.07132393 -0.02225154 0.004616141 -0.07068061 -0.02141088 0.004372 -0.07068139 -0.01827532 0.004064679 -0.07094395 -0.02178645 0.003627717 -0.07068049 -0.01710265 0.004049241 -0.07132488 -0.01840502 0.001021981 -0.07136982 -0.01584267 0.003442883 -0.07086235 -0.01737529 1.02698e-4 -0.07069075 -0.01541721 3.33726e-4 -0.07123482 -0.01581794 0.003401875 -0.07123827 -0.01742208 -4.38899e-4 -0.07086235 -0.01575917 0.004349291 -0.07094967 -0.02054584 0.003752768 -0.07150501 -0.0172773 0.004356622 -0.07132643 -0.02095991 -0.001312673 -0.0706833 -0.01558971 0.004737496 -0.07067584 -0.01976186 0.002775669 -0.07067823 -0.01632732 -6.65931e-4 -0.07116806 -0.01587069 0.003743946 -0.07069432 -0.02313202 0.00238502 -0.07093828 -0.01651352 0.004632771 -0.06880962 -0.01881426 0.003775835 -0.06880313 -0.01718926 0.003534734 -0.06880199 -0.02342689 0.002034187 -0.0688005 -0.01575917 0.004377782 -0.06879985 -0.0221908 3.44954e-4 -0.0688008 -0.01539665 0.004783511 -0.06880843 -0.02075952 -0.001078963 -0.06881159 -0.01548624 0.002805054 -0.06864172 -0.01660794 0.003500044 -0.06825256 -0.02294147 0.004451155 -0.06861543 -0.01933729 0.004404425 -0.06811207 -0.02138733 0.004423022 -0.06829202 -0.01980769 0.004270792 -0.06827181 -0.01894325 0.002064228 -0.06809741 -0.0160833 0.002262473 -0.06826251 -0.01633656 0.003980994 -0.06860429 -0.02228337 0.00326848 -0.0686053 -0.02324926 0.001268506 -0.06799483 -0.01546514 6.97807e-4 -0.06802254 -0.01554042 0.00454694 -0.06804698 -0.01914846 0.004026234 -0.06827181 -0.02205669 0.004652917 -0.06799125 -0.0216335 0.0040645 -0.06797039 -0.01749449 0.004607677 -0.06798845 -0.01863873 0.003770053 -0.06821602 -0.01779961 0.001368463 -0.06858021 -0.01593405 5.46765e-4 -0.06827181 -0.01576364 0.002851307 -0.06799429 -0.0162186 0.003842771 -0.06860488 -0.01784223 -4.63665e-4 -0.06799483 -0.01531928 0.004375219 -0.06852 -0.02093172 -2.29186e-4 -0.06859147 -0.01566672 0.003078997 -0.06827038 -0.01696646 0.004870057 -0.06799483 -0.01991254 0.002999544 -0.0682953 -0.02345973 -5.51327e-4 -0.06806445 -0.01560235 -6.62064e-4 -0.06827187 -0.01577746 0.003844082 -0.06799483 -0.02317589 0.005013287 -0.06610327 -0.01969087 0.004399776 -0.06609731 -0.01770651 0.003485143 -0.06609129 -0.01651263 0.001949608 -0.06610035 -0.01552712 0.004179477 -0.06609106 -0.02301752 2.80798e-4 -0.06609922 -0.01513081 0.004903078 -0.06609547 -0.02139467 -0.001848936 -0.06609851 -0.01544821 0.004664957 -0.06539809 -0.01896727 0.004004895 -0.06537866 -0.02287209 -0.001316905 -0.06554776 -0.01565057 0.003667116 -0.06600511 -0.0232923 0.004623711 -0.06573814 -0.01944375 0.004655182 -0.06602609 -0.01877802 0.002937734 -0.06569397 -0.01651597 0.00353235 -0.0656917 -0.02324599 0.002462983 -0.06539094 -0.01602596 0.00469923 -0.06528884 -0.01808923 0.004206061 -0.06538623 -0.01783514 0.004426479 -0.0656917 -0.01865106 0.004217088 -0.06554687 -0.02225387 0.002633154 -0.06600785 -0.01612699 6.05007e-4 -0.06539231 -0.01537573 0.001593053 -0.06578773 -0.01577281 0.004868686 -0.06528973 -0.02185362 0.004388988 -0.06595879 -0.02205908 0.004598975 -0.06543934 -0.02129447 0.003497183 -0.06539481 -0.01687866 0.003957331 -0.06578814 -0.01767343 2.97031e-4 -0.0657255 -0.01549488 0.004708588 -0.06587284 -0.0207479 0.005119383 -0.06529635 -0.02058333 0.005051195 -0.0652945 -0.01916795 0.002985537 -0.06529676 -0.02434909 0.003145754 -0.06543099 -0.02373003 0.004707813 -0.06551539 -0.02030915 -3.40458e-4 -0.06597214 -0.01538515 -0.00127542 -0.06534355 -0.0154367 0.003304243 -0.06592142 -0.01673758 0.004089832 -0.06529021 -0.02328968 0.005267441 -0.06339675 -0.01902836 0.005382061 -0.06339913 -0.02006262 0.003563344 -0.06339442 -0.02421379 0.004448354 -0.06339299 -0.02323997 0.005194962 -0.06339854 -0.02163285 0.003281116 -0.06332463 -0.02423352 0.005081892 -0.06277048 -0.01981574 0.003222048 -0.06277006 -0.01624327 0.00390625 -0.06285673 -0.02338153 0.002400696 -0.06263774 -0.01551538 -0.001321852 -0.06259155 -0.0148428 4.99658e-5 -0.0625832 -0.01471692 -1.49172e-4 -0.06310093 -0.01513069 0.003401756 -0.06318849 -0.01641923 0.004896283 -0.06260216 -0.01764899 0.004473865 -0.06258934 -0.02334713 0.003887951 -0.06310606 -0.02339619 0.005019724 -0.06318801 -0.0195719 0.004633724 -0.06266266 -0.02255219 -2.17408e-4 -0.06328833 -0.01498144 -7.24289e-4 -0.06282323 -0.01513898 0.00452435 -0.06332933 -0.02275615 0.002858519 -0.06258934 -0.01549768 0.004765272 -0.06263613 -0.01800566 0.004610359 -0.06288743 -0.01812422 0.002164006 -0.06299787 -0.01566249 0.004539966 -0.0629996 -0.02226918 -0.001723766 -0.06319308 -0.01542335 0.005074262 -0.06258779 -0.02227014 -0.002405524 -0.06258744 -0.01524978 0.004852652 -0.06322568 -0.01865208 0.001985192 -0.06332945 -0.01535815 0.001488268 -0.06259959 -0.01490145 8.44159e-4 -0.06271988 -0.01513457 0.001309514 -0.06309622 -0.01531225 0.004988789 -0.06319451 -0.02107203 0.004212558 -0.06314253 -0.0173738 0.003534555 -0.06259435 -0.02437442 0.003429651 -0.06263899 -0.02412521 0.004945874 -0.06281203 -0.02126789 0.005472481 -0.06257855 -0.02046424 0.004007339 -0.06271845 -0.01701599 0.003741443 -0.06259477 -0.01617187 0.005434811 -0.06259447 -0.01947689 0.005286753 -0.0606094 -0.01819342 0.005316555 -0.05970209 -0.01811689 0.0048424 -0.06216198 -0.01752007 0.005039811 -0.05817246 -0.01741254 0.00452435 -0.06059819 -0.01679039 0.004593551 -0.05970185 -0.01679915 0.003604233 -0.05869859 -0.01569736 0.00481677 -0.06061828 -0.01724886 0.004861414 -0.05971968 -0.01722449 0.002427339 -0.05954915 -0.01501303 0.00505948 -0.0606184 -0.01768445 0.005100429 -0.05971968 -0.01766246 0.001627922 -0.05871284 -0.01467782 0.0034765 -0.05879104 -0.02472949 0.004176378 -0.05857712 -0.0241127 7.47335e-4 -0.05962795 -0.01451987 0.00485301 -0.05910307 -0.02320814 -1.87598e-4 -0.05952417 -0.01447254 -9.80031e-4 -0.05910372 -0.01454359 0.00541377 -0.05868816 -0.02207458 0.005487263 -0.06048613 -0.0214889 -0.001903951 -0.05892992 -0.01477402 0.005740344 -0.05860751 -0.02046358 0.00564599 -0.05884963 -0.01920342 0.005605161 -0.04549384 -0.02170586 0.005549848 -0.04674708 -0.02162593 -0.002474606 -0.05204278 -0.01501137 0.004187047 -0.05203706 -0.02407681 -0.002133071 -0.04738306 -0.01483732 -0.001074135 -0.04674232 -0.01453649 0.004426956 -0.0454939 -0.02388757 2.3146e-4 -0.0454939 -0.01440495 0.004036128 -0.04678303 -0.02425867 1.5535e-4 -0.04671704 -0.01442658 0.003259599 -0.0454939 -0.02492952 0.001535952 -0.04678678 -0.01464152 0.004899978 -0.04671955 -0.02318024 0.002755463 -0.04549384 -0.01503366 0.001824498 -0.04716742 -0.01474463 0.004824638 -0.0454939 -0.0169844 0.005713999 -0.04549384 -0.01920622 0.005733609 -0.04704272 -0.02008301 0.005681395 -0.04738557 -0.02099072 -0.002760112 -0.04721403 -0.02499699 -0.00276947 -0.05198431 -0.02501595 -0.002774477 -0.04715687 -0.02518254 -0.003009021 -0.0522257 -0.02506381 -0.002744257 -0.05203241 -0.02521145 -0.002930462 -0.05210828 -0.02497059 -0.003362834 -0.04746997 -0.02476513 -0.00438106 -0.04739397 -0.02390164 -0.005369901 -0.04738253 -0.02221179 -0.005393147 -0.04753458 -0.02199482 -0.005511105 -0.05213981 -0.02159667 -0.00562489 -0.05206269 -0.02130073 -0.005436122 -0.0519945 -0.02136713 -0.005444884 -0.04722845 -0.02136754 -0.00562644 -0.04720515 -0.02128171 -0.005455851 -0.0469802 -0.02152574 -0.004059255 -0.04682844 -0.02410858 -0.003032803 -0.04678004 -0.02505064 -0.003044784 -0.04687225 -0.02490431 0.005708873 -0.05204266 -0.02061218 0.005657732 -0.04746997 -0.02079153 0.005456149 -0.04738181 -0.02197128 0.004957914 -0.04738867 -0.02307355 0.004345774 -0.04741698 -0.02390468 0.004042625 -0.05212789 -0.02419346 0.00378865 -0.05206269 -0.02448213 0.003775835 -0.05203855 -0.02428293 0.003756642 -0.04722803 -0.02429342 0.00377357 -0.04720449 -0.02449214 0.003957331 -0.04689455 -0.02416265 0.005510032 -0.04682672 -0.02143472 0.005293846 -0.04680204 -0.02227568 0.004904687 -0.04682219 -0.02300781 0.005686104 -0.04682928 -0.02051872 0.005569815 -0.04707998 -0.02019286 0.005554616 -0.0519731 -0.02014714 0.005728006 -0.05204194 -0.02006107 0.00573951 -0.05222564 -0.02033597 0.005619287 -0.05210828 -0.02031439 -0.002641141 -0.05210632 -0.01520836 -0.002803266 -0.05222934 -0.0151636 -0.002303481 -0.04751598 -0.01496464 -1.95145e-4 -0.04738336 -0.01442807 0.001058042 -0.04741728 -0.0145455 0.001321852 -0.05206435 -0.01464468 0.001305401 -0.05224889 -0.01458001 0.001658022 -0.05205816 -0.01484704 0.00182718 -0.05206042 -0.01474004 0.001687765 -0.04723984 -0.0148828 0.001466989 -0.0468955 -0.01476866 -0.002561688 -0.04674333 -0.01503247 -0.00248003 -0.04685539 -0.01517105 -0.001556396 -0.04682075 -0.01472038 2.31479e-6 -0.04682219 -0.01451021 -0.002786338 -0.04714137 -0.01535075 -0.002982616 -0.04711902 -0.01527255 -0.002805769 -0.0520088 -0.01534301 -0.003026247 -0.05200344 -0.01529031 -0.003382146 -0.04552716 -0.02393525 -4.30908e-4 -0.04554009 -0.02517354 -3.81967e-4 -0.04549396 -0.02448034 0.001508951 -0.04554361 -0.02495282 5.02283e-4 -0.04552888 -0.01513683 0.001797795 -0.04552888 -0.01548361 -0.00187844 -0.04552847 -0.02486515 0.002984583 -0.0455107 -0.01611644 -0.001032352 -0.04552417 -0.01522392 0.003783643 -0.04551845 -0.01680976 0.001884758 -0.04553264 -0.01631087 0.002270162 -0.04557079 -0.01651751 -0.002757132 -0.0455321 -0.015953 0.004528999 -0.04552668 -0.01789784 -0.002675831 -0.0455355 -0.01680934 0.005021572 -0.04553711 -0.01956111 -0.003810882 -0.04551041 -0.01685953 -0.003008544 -0.04556822 -0.01709771 -0.004440665 -0.04551625 -0.01772975 0.004867792 -0.04552686 -0.02155685 -0.00490576 -0.04552698 -0.01889461 0.004101574 -0.04552692 -0.02313745 -0.005039632 -0.04553562 -0.0206356 0.002685904 -0.0455302 -0.02447706 -0.004461348 -0.04552716 -0.02255851 0.001328766 -0.04548984 -0.02431178 6.31979e-4 -0.04599386 -0.02554124 7.20985e-4 -0.04936182 -0.02557075 4.35617e-4 -0.04932934 -0.02560722 -0.002546191 -0.05205446 -0.02442961 -0.002708554 -0.04854935 -0.02436697 -0.002219319 -0.04784178 -0.02397656 -0.002829492 -0.04714155 -0.02431094 -0.004133403 -0.04712688 -0.0231024 -0.004797339 -0.04714608 -0.02163302 -0.004808664 -0.04854822 -0.02150225 -0.004298329 -0.04786407 -0.02115821 -0.004814267 -0.0520479 -0.02132898 -0.004889011 -0.05205333 -0.02100735 -0.004917085 -0.04695588 -0.02111524 -0.004078686 -0.04684364 -0.02319926 -0.002378523 -0.0469492 -0.02457857 0.004971206 -0.05201721 -0.02027004 0.004985809 -0.04854935 -0.02042376 0.004403114 -0.04784178 -0.0201956 0.00499773 -0.04714155 -0.02055656 0.004603028 -0.04712688 -0.02229017 0.003662705 -0.04714608 -0.0235998 0.00355494 -0.04854822 -0.02367514 0.003001928 -0.04786413 -0.02340495 0.003458738 -0.05197459 -0.02376759 0.003182828 -0.05196332 -0.0240345 0.003273487 -0.04695594 -0.02396219 0.005004465 -0.04694914 -0.02003264 0.004647374 -0.04684364 -0.0222218 0.0049299 -0.052064 -0.01992022 -0.002716243 -0.05206006 -0.01602113 -0.002276122 -0.04854935 -0.01573193 -0.002187669 -0.04793727 -0.01632905 -0.00217092 -0.04714727 -0.01566845 -8.5659e-4 -0.04712694 -0.01518946 0.001128435 -0.04714059 -0.01527845 0.001254737 -0.04854822 -0.01534551 0.001298964 -0.04785919 -0.01596587 0.001407146 -0.05204701 -0.01542598 0.00175029 -0.05205363 -0.01552504 0.001644194 -0.04695588 -0.015446 -5.61739e-4 -0.04684364 -0.01512753 -0.002622425 -0.04694283 -0.01591122 -0.002999782 -0.05209136 -0.0171144 0.001435697 -0.04559898 -0.02427381 -5.06209e-4 -0.04560041 -0.02448439 -0.003728449 -0.0520612 -0.01693272 -0.004608929 -0.05200392 -0.01828837 -0.004855275 -0.05196017 -0.01900053 -0.002256274 -0.05200225 -0.02461946 -4.62573e-4 -0.0520426 -0.02510654 -0.004099428 -0.05197924 -0.02302438 -0.004410684 -0.05204582 -0.02243942 -0.004685938 -0.05203956 -0.02178162 0.004929244 -0.05196446 -0.02102804 0.004518568 -0.05197924 -0.02229952 0.004167616 -0.05204582 -0.02286154 0.00373584 -0.05203801 -0.02343004 0.001533687 -0.05202329 -0.02490878 0.00493586 -0.05194991 -0.01936203 0.004743397 -0.05196613 -0.01860618 0.004416406 -0.05197566 -0.01784843 0.004027724 -0.05198425 -0.01723212 0.002943277 -0.05205756 -0.01619362 0.003428459 -0.05204916 -0.01660019 0.004122376 -0.05207926 -0.01753467 0.004531741 -0.05207324 -0.01829361 0.004725098 -0.05207467 -0.01883888 -0.00172472 -0.05196446 -0.01547873 -4.18011e-4 -0.05197924 -0.0151984 1.96919e-4 -0.05196708 -0.01518279 8.27066e-4 -0.05204999 -0.01529377 -5.07458e-4 -0.05208677 -0.02446371 -0.004682779 -0.05207926 -0.01874524 -0.004163742 -0.05207443 -0.01757687 -0.004207909 -0.05197107 -0.01748543 -0.004919767 -0.0520541 -0.01968121 -0.004950642 -0.05205035 -0.02027285 -0.00185585 -0.05207258 -0.024706 -0.001192629 -0.05198574 -0.0250222 -0.001126289 -0.05208081 -0.02492922 -0.004266679 -0.04854774 -0.02279973 -0.003232419 -0.04854559 -0.02397829 -0.004684567 -0.0485509 -0.02189517 -0.003628492 -0.04854786 -0.02359968 -0.003137052 -0.05207175 -0.023956 -0.003523409 -0.05205065 -0.02366507 -0.002786636 -0.04777175 -0.02360707 -0.003355622 -0.04776418 -0.02306902 -0.004166722 -0.04774463 -0.02173537 0.004407584 -0.04854774 -0.0225569 0.004911065 -0.04854553 -0.02107203 0.003833353 -0.0485509 -0.02337104 0.004781246 -0.04854786 -0.02160418 0.004843592 -0.05207186 -0.02100056 0.004646122 -0.05207282 -0.02175933 0.004366636 -0.04777175 -0.02087152 0.004185259 -0.04776412 -0.02163344 0.00343585 -0.04774451 -0.0230028 0.00279963 -0.05206722 -0.02421772 0.002334833 -0.05205142 -0.02454179 0.003210604 -0.05209231 -0.02383464 -1.39736e-4 -0.04854768 -0.01516622 -0.001676619 -0.04854559 -0.01547229 8.52314e-4 -0.0485509 -0.01525646 -0.001151621 -0.04854786 -0.01531893 -0.002406239 -0.05205398 -0.01585114 -0.001157641 -0.05207777 -0.01540386 -0.002183258 -0.0476976 -0.01627844 -7.88007e-4 -0.04783904 -0.01584351 -9.90314e-5 -0.04776704 -0.01574665 -0.002652049 -0.05209392 -0.01677978 0.001850426 -0.05209356 -0.01628363 -0.003664255 -0.05210882 -0.02231788 -0.00285542 -0.05213522 -0.0232023 -0.003629148 -0.05214369 -0.0181486 -0.00420171 -0.05211478 -0.02034205 -0.004086315 -0.05213481 -0.02094173 -0.002465963 -0.05215406 -0.01684492 -0.001276612 -0.05212879 -0.01621407 -5.50861e-4 -0.05219876 -0.01608836 0.001526117 -0.05213934 -0.01630604 2.8015e-4 -0.05214107 -0.01602119 0.002152204 -0.05218935 -0.01665234 0.002636194 -0.05220079 -0.01699376 -0.003077507 -0.05220448 -0.01742607 0.003033101 -0.05211347 -0.01724851 0.003286898 -0.05222797 -0.01769709 -0.001989245 -0.05212897 -0.01651412 0.003540337 -0.05211395 -0.01789468 0.002265334 -0.05208981 -0.01652342 0.003719866 -0.05221349 -0.01839739 0.003872752 -0.05211627 -0.01852554 0.004050791 -0.05213063 -0.0192523 -0.004091322 -0.05220121 -0.01966065 -0.003847897 -0.05218863 -0.01867496 -0.004169583 -0.05211246 -0.01953423 -0.004124164 -0.05218368 -0.02046859 0.003233015 -0.05479604 -0.01769757 0.002452969 -0.05472773 -0.02345252 0.00222826 -0.05213242 -0.02367931 6.44438e-4 -0.05472517 -0.0161255 0.003196239 -0.05474478 -0.02272588 -3.87445e-4 -0.05473005 -0.01610213 0.003814876 -0.05214405 -0.02182078 0.003922045 -0.05473613 -0.02135092 -0.001383185 -0.05472677 -0.01631617 -0.002405822 -0.0547229 -0.01685667 0.004154801 -0.05214673 -0.02006721 0.003838181 -0.05483239 -0.02124989 0.004037976 -0.05478537 -0.02053385 -0.003833353 -0.05479609 -0.02148509 -0.00148499 -0.05482804 -0.02389115 -0.001389861 -0.05472368 -0.02403891 -0.003240466 -0.05479729 -0.02263814 0.004043042 -0.05479937 -0.02006161 0.004002094 -0.05479753 -0.01952844 -2.20249e-4 -0.05482506 -0.02418678 -1.51253e-4 -0.05472987 -0.02426427 8.72911e-4 -0.054793 -0.02414423 0.001735389 -0.05478245 -0.02385061 0.002608597 -0.05483233 -0.02320671 0.003535211 -0.0548039 -0.02213454 -0.00247699 -0.05480957 -0.02337247 -0.003998458 -0.05479902 -0.02082687 -0.003865957 -0.04794168 -0.0223692 -0.002941966 -0.0479834 -0.02353072 0.003834307 -0.04794162 -0.02242517 0.004378259 -0.0479834 -0.02104425 0.001429498 -0.05209308 -0.02425992 0.002835214 -0.05213809 -0.02322369 3.72601e-4 -0.04795295 -0.01573812 -0.001455128 -0.0478748 -0.01600927 7.48995e-4 -0.0520969 -0.02436286 6.56423e-4 -0.0522055 -0.0242443 -1.38901e-5 -0.05214631 -0.02432471 -0.00155282 -0.05213147 -0.02402848 -0.00217849 -0.05217128 -0.02368474 -0.003300249 -0.05220067 -0.02265143 0.004055619 -0.05213844 -0.02109909 0.001437962 -0.05215793 -0.02406859 0.003451824 -0.05214422 -0.02248919 -5.60279e-4 -0.05217164 -0.02428025 -0.003798127 -0.05219042 -0.02181071 7.03854e-4 -0.04776471 -0.01580232 -0.003095865 -0.05471998 -0.01749575 -0.003682494 -0.05475753 -0.01840204 -0.0040856 -0.0547508 -0.02014333 -0.003916323 -0.05479419 -0.01909399 -0.004000723 -0.05482316 -0.01993364 0.00383538 -0.05479443 -0.0188629 0.001586437 -0.05480068 -0.01644206 2.2029e-4 -0.05482578 -0.01616185 -0.00139904 -0.05482804 -0.01640731 -0.002501547 -0.05482757 -0.0170536 -0.003198683 -0.05482757 -0.01776731 0.002457201 -0.05480808 -0.01695859 -1.93878e-4 -0.0548439 -0.02288585 -0.001508772 -0.0548439 -0.02178514 -4.37748e-4 -0.0548439 -0.02113974 -0.001295626 -0.0548439 -0.02121824 -6.79429e-4 -0.0548439 -0.0192902 -5.64825e-4 -0.0548439 -0.01838219 -0.001110017 -0.0548439 -0.02027171 -0.002091646 -0.0548439 -0.02161628 0.002309978 -0.0548439 -0.02079379 0.002131104 -0.0548439 -0.0213738 -0.00187844 -0.0548439 -0.02104938 -4.98435e-4 -0.0548439 -0.01777452 0.001538813 -0.0548439 -0.02124351 -0.002255499 -0.0548439 -0.01864564 0.002445936 -0.0548439 -0.01898664 -0.002635121 -0.0548439 -0.01956194 7.87719e-4 -0.0548439 -0.02276653 0.001852512 -0.0548439 -0.01819705 0.001055479 -0.0548439 -0.02007097 0.001717627 -0.0548439 -0.02066326 6.40154e-4 -0.0548439 -0.02108711 1.05546e-4 -0.0548439 -0.01779145 3.91598e-5 -0.0548439 -0.01839911 4.37616e-4 -0.0548439 -0.01919203 5.00683e-4 -0.05564361 -0.01915383 -4.18319e-4 -0.05075895 -0.01774621 -6.13656e-4 -0.05070906 -0.01806378 -4.32767e-4 -0.05078274 -0.0184291 -4.09634e-5 -0.05075895 -0.01842767 -1.2261e-4 -0.0504952 -0.01813066 -3.82746e-4 -0.05051386 -0.01804566 -2.99823e-4 -0.05057716 -0.01835101 -0.001932919 -0.0507313 -0.02111583 -0.002054274 -0.05069762 -0.02134758 -0.002016723 -0.05065762 -0.02157354 -0.001809418 -0.05075812 -0.02179139 -0.001454353 -0.0507313 -0.02171856 -0.001513779 -0.050525 -0.02131903 -0.001665592 -0.05053251 -0.02161324 -0.001309514 -0.05070459 -0.02140218 0.001603901 -0.05075407 -0.02080458 0.001557767 -0.05069613 -0.02101415 0.001632452 -0.05075377 -0.02127224 0.001917362 -0.05072051 -0.02140146 0.00224477 -0.05075407 -0.02123272 0.002290964 -0.05069613 -0.02102309 0.002216339 -0.05075377 -0.02076506 0.001931369 -0.05072051 -0.02063584 0.001801908 -0.05049026 -0.02093333 -0.001157701 -0.05564361 -0.02023828 -0.002637863 -0.05564361 -0.01955854 -4.60505e-4 -0.05564361 -0.02108031 -1.95578e-4 -0.05564361 -0.02289229 7.87013e-4 -0.05564361 -0.02276313 6.38175e-4 -0.05564361 -0.02102094 0.001105368 -0.05564361 -0.02002781 0.002441883 -0.05564361 -0.01900023 0.001862645 -0.05564361 -0.01819258 -6.17179e-4 -0.05564361 -0.01931202 -0.002250969 -0.05564361 -0.01865017 1.5438e-4 -0.05070906 -0.01811009 -5.75396e-5 -0.05072015 -0.0177412 -0.001577854 -0.05075812 -0.021043 5.33719e-4 -0.05564361 -0.02044934 -1.68928e-4 -0.05559366 -0.02041834 -2.08432e-4 -0.05561506 -0.02014178 -5.13986e-4 -0.05560725 -0.01988869 4.20689e-4 -0.05559366 -0.01942372 1.82133e-4 -0.05561029 -0.01944983 -4.21984e-4 -0.05556887 -0.02072376 2.09437e-4 -0.05564361 -0.0200265 -1.63486e-4 -0.05051732 -0.01791721 -0.001765251 -0.05049282 -0.02130722 0.002043724 -0.05049026 -0.02110332 4.26601e-4 -0.05544388 -0.02020782 5.34596e-4 -0.05544388 -0.0204575 1.70687e-4 -0.05544388 -0.01989394 -2.36002e-4 -0.05544388 -0.02022022
+
+
+
+
+
+
+
+
+
+ -0.32203 -0.5397178 0.7778184 0.5317468 -0.8291063 -0.1727088 0.6235024 -0.7553236 -0.2018195 0.8276974 -0.5531246 -0.09471189 0.6977751 -0.7066029 0.1175684 0.5751342 -0.785032 0.2300992 0.5106645 -0.8340241 0.2088679 0.6762819 -0.7361753 0.0262441 0.6555593 -0.6619495 0.3634074 0.2367015 -0.9525094 0.1915682 0.6142224 -0.6054666 0.506104 0.3606134 -0.8041889 0.4724808 0.4325411 -0.7340123 0.5235784 0.2665063 -0.8907847 -0.3680723 0.3992908 -0.9159455 -0.04013341 0.5531696 -0.4776532 -0.6825328 0.1960203 -0.8628525 0.4658989 0.4277687 -0.807792 -0.4055691 0.1839002 -0.813305 0.5520106 0.5803864 -0.6160548 -0.5325676 0.6531256 -0.5482441 -0.5223557 0.06990194 -0.7613908 0.6445136 0.314238 -0.9326147 -0.1774387 0.05258023 -0.7706884 0.6350391 0.7650603 -0.5054087 -0.3990553 -0.06302809 -0.9501435 0.3053765 -0.1426261 -0.5851097 0.7983136 0.138603 -0.990289 0.01080942 -0.2096924 -0.8122882 0.5442584 -0.2321101 -0.6957381 0.6797598 0.5677103 0.1115994 0.815629 0.4461652 -0.1079778 0.8884129 0.5686786 0.1256702 0.8129034 -0.9144418 -0.1101472 0.3894406 0.4253548 -0.0585637 0.9031299 -0.9166432 -0.2466047 0.3145656 -0.9010505 -0.2053062 0.382044 -0.931271 0.2129404 0.2956196 -0.8519788 0.1322163 0.5066072 0.4489506 -0.1081431 0.8869885 -0.7875559 0.02396345 0.6157772 0.445414 0.1354085 0.885026 -0.9151583 -0.03175008 0.4018423 -0.9306769 -0.1576433 0.3301351 0.3396749 -0.2946544 0.8931964 -0.9153984 0.02578061 0.4017227 0.4024128 -0.01117736 0.9153902 0.384112 -0.04262906 0.922302 -0.8782913 0.1206352 0.4626573 0.4498196 0.09149825 0.8884203 0.4136631 0.03467643 0.9097695 -0.9315026 -0.05197572 0.3600023 0.4165108 0.1417403 0.8980136 0.4292457 0.04554569 0.9020388 -0.9383681 -0.2747461 0.2097141 0.2961064 -0.1654856 0.9407101 0.3051813 -0.1482374 0.940686 0.4325555 0.1329374 0.891753 0.3850343 0.02446627 0.9225779 0.2920416 -0.1575042 0.9433474 0.2401983 -0.2415813 0.9401826 -0.9607486 -0.1571156 0.2286418 0.3034686 -0.05894798 0.9510164 -0.9564766 -0.2550359 0.1418066 -0.9391614 0.0613147 0.3379591 -0.9260708 0.1092001 0.3612041 -0.9762023 -0.03974479 0.213189 0.1378127 -0.2590923 0.9559701 0.2526065 0.04742383 0.9664062 -0.9575129 -0.2795299 0.07093775 -0.9714654 -0.2003462 0.1269513 -0.938109 0.1362371 0.3184196 0.2962372 0.1365301 0.9453058 -0.9781749 0.04448431 0.2029657 -0.9855714 0.005216658 0.16918 -0.9891237 -0.09107047 0.1155022 -0.9885232 -0.09526288 0.1172481 0.2555611 0.1188291 0.9594625 0.05584049 -0.05185556 0.9970922 0.02972704 -0.2237406 0.9741954 0.1981099 0.03189033 0.9796609 0.15463 -0.03111779 0.9874823 0.1003122 -0.1437863 0.9845116 0.118383 -0.1388736 0.9832089 -0.9803606 0.1351875 0.1435881 -0.9794017 0.162724 0.1195552 -0.9897244 -0.1401332 0.02843105 -0.9845163 -0.1730159 0.02816158 -0.9884174 0.09309589 0.1198505 -0.9926953 -0.1191967 -0.01866555 -0.98406 -0.1045583 -0.1438528 0.09464621 0.1347732 0.986346 -0.05194556 -0.2205834 0.9739839 0.02952915 0.07001817 0.9971086 -0.006555795 -0.1338356 0.9909819 0.1675808 0.1660275 0.9717776 -0.9756223 -0.1892244 -0.1111547 -0.09797191 -0.1407272 0.985189 -0.9599273 -0.2469521 -0.1324929 -0.9420688 -0.272674 -0.1953344 -0.09261739 -0.08255165 0.9922738 -0.9969928 -0.04725515 -0.06142109 -0.9872694 -0.1061839 -0.1184239 -0.9861291 -0.1080849 -0.1259649 -0.9733857 -0.1564395 -0.1674728 -0.9962459 0.07143467 -0.04890096 -0.9938877 0.1067979 -0.02795511 -0.9967805 -0.02581745 -0.07590878 -0.9937379 -0.006397783 -0.1115525 -0.1543753 -0.255809 0.9543218 -0.1180015 -0.1340268 0.9839271 -0.0430727 0.1313893 0.9903947 -0.04151308 0.1208071 0.9918076 -0.09506142 -0.05863636 0.9937431 -0.9903059 0.1310542 -0.04603463 -0.07690399 0.06451976 0.9949488 -0.06262969 0.02690273 0.9976742 -0.1402276 -0.02007609 0.9899159 -0.9803754 0.1577857 -0.1181859 -0.09065705 0.07150107 0.9933122 -0.1518173 -0.02420783 0.9881122 -0.9725453 0.1322504 -0.1914823 -0.9862767 0.01008081 -0.164793 -0.2956799 -0.2335348 0.9263018 -0.1291854 0.1872192 0.9737866 -0.9815701 0.03798371 -0.1872896 -0.9093191 -0.2359615 -0.342726 -0.9833311 0.04698896 -0.1756476 -0.9547022 -0.05623972 -0.2922 -0.1665427 -0.02352774 0.9857535 -0.9361343 -0.1681669 -0.3088243 -0.2338675 -0.1353894 0.9627959 -0.3319092 -0.07629758 0.9402207 -0.346009 -0.2044364 0.9156875 -0.2749634 0.04369741 0.9604612 -0.8771011 -0.2503799 -0.4098824 -0.2255846 0.08008414 0.9709264 -0.9480987 0.03962802 -0.3154973 -0.2540825 0.117673 0.9599975 -0.9454399 0.1535406 -0.2873477 -0.9306595 -0.06841975 -0.3594326 -0.9393832 -0.05769211 -0.3379805 -0.363506 -0.2024298 0.9093325 -0.4301432 -0.1147274 0.8954409 -0.3002448 -0.0789687 0.9505878 -0.9091929 -0.1227554 -0.3978685 -0.9129385 -0.1228305 -0.3891735 -0.4156366 -0.03237241 0.9089546 -0.9211382 -0.01284068 -0.389024 -0.917293 0.1234394 -0.3785979 -0.9303274 0.04257339 -0.3642504 -0.89247 0.1001219 -0.4398556 -0.9178432 0.08215802 -0.3883478 -0.962971 0.2069479 -0.1727995 -0.8862647 0.1414112 -0.4410645 -0.4528995 -0.1469724 0.8793641 -0.3694009 0.03727036 0.9285225 -0.3891021 0.04236429 0.92022 -0.3445939 0.137421 0.9286392 -0.438621 0.08094954 0.8950189 -0.5298544 -0.1367014 0.8369989 -0.3907524 0.1233201 0.9121978 -0.5526059 -0.2450059 0.7966171 -0.5141481 -0.1367552 0.8467288 0.8334202 -0.1368933 0.5354167 0.8402034 -0.1357279 0.5250108 0.7935717 -0.2166905 0.5685854 -0.5692504 -0.03843981 0.8212652 -0.5693346 -0.06378877 0.8196276 -0.6036778 -0.09224694 0.7918736 -0.6046274 -0.08844703 0.7915825 -0.6259809 -0.2053021 0.752329 -0.6222296 0.180049 0.7618482 -0.519824 0.1431873 0.842188 0.850091 0.1415705 0.5072508 0.833783 0.1234108 0.5381226 -0.6079569 -0.08451884 0.7894588 0.8092836 -0.06585669 0.583715 0.8329221 0.1006361 0.5441631 0.8031308 -0.02203911 0.5953952 0.8177282 0.1177731 0.5634271 0.8137745 0.01995563 0.5808381 0.7877302 -0.09479671 0.6086829 0.8394675 0.04620587 0.541442 0.7123692 -0.2402372 0.659406 0.7241236 -0.2287371 0.6506338 -0.6675239 -0.1970509 0.718041 -0.5798713 0.07755815 0.8110081 -0.6940881 -0.1726412 0.6988826 0.7809246 0.00551784 0.6246011 -0.5941166 0.1186277 0.7955835 -0.5916791 0.04451227 0.8049439 -0.6413506 0.02006119 0.7669857 0.7822282 0.1252701 0.6102675 0.7281333 -0.1048836 0.6773636 0.7641853 0.01019495 0.6449162 -0.7714671 -0.2596339 0.5808863 -0.6519311 0.1372225 0.7457585 0.5676287 -0.2781115 0.7748883 -0.7390611 -0.05059671 0.6717356 0.7410141 0.1115389 0.6621611 -0.7483002 -0.02374243 0.6629353 -0.7223644 0.01459294 0.6913586 0.7080582 0.06692969 0.7029752 -0.7885075 -0.1420504 0.5983958 0.6586906 -0.1682713 0.7333563 0.6330103 -0.06544631 0.7713721 -0.7879479 -0.1429182 0.5989262 -0.7924025 -0.1410121 0.5934762 -0.7555952 0.1135789 0.6451169 0.5761511 -0.1641454 0.8006911 -0.7807936 -0.09160536 0.6180371 0.5138352 -0.2438211 0.8225113 -0.7543264 0.04764771 0.6547683 0.5142097 -0.2095963 0.8316597 0.6452954 0.06086933 0.7615042 0.6253266 -0.04865521 0.7788449 0.5903182 -0.09074479 0.8020535 -0.7565481 0.114126 0.6439025 0.6394287 -0.0253278 0.7684332 0.5576996 -0.1396115 0.8182175 0.7024145 -0.201207 0.6827369 0.6303429 0.1787639 0.7554545 -0.843052 -0.2086644 0.495704 0.6002068 0.0115751 0.7997612 0.6156661 0.04127061 0.7869257 -0.8029734 0.1164075 0.5845367 -0.8773156 -0.1084819 0.4674925 0.06931602 0.9928815 0.09685939 0.2241326 0.9740478 0.03155064 0.129417 0.9866698 -0.09866142 0.9996827 0.007020473 -0.02419251 0.7484778 -0.03737556 -0.6621058 -0.5459979 -0.1562188 -0.8230929 0.7806971 0.02970749 -0.6242032 0.7506675 0.1171805 -0.6502054 -0.4514349 -0.1052645 -0.8860734 0.8632846 -0.1541792 -0.4805918 -0.4710524 -0.1434013 -0.8703711 -0.5675904 0.1273931 -0.8133955 -0.8164838 0.1906482 -0.544984 0.8778551 -0.2500655 -0.4084576 0.8691207 -0.227431 -0.4392089 -0.3521453 -0.2239472 -0.9087581 -0.4005597 -0.08524113 -0.9122971 0.8767816 -0.09893989 -0.4706008 0.815994 0.128834 -0.5635208 0.7735404 0.1400833 -0.6180712 -0.4249909 -0.06325507 -0.9029848 0.8874715 -0.1811755 -0.4237568 0.9236988 -0.07307916 -0.3760854 -0.4763712 0.1227057 -0.87064 -0.4616011 0.08142685 -0.8833427 0.9049401 -0.05128186 -0.4224379 0.419316 0.03255671 -0.9072565 0.8905481 -0.003102123 -0.4548785 -0.3549973 -0.181452 -0.9170889 -0.4552225 0.05899983 -0.8884208 -0.385102 0.01880544 -0.9226824 -0.427572 0.1252319 -0.8952649 -0.407206 0.1326872 -0.9036468 0.8836325 0.1024074 -0.456844 0.899116 0.06222975 -0.4332643 -0.3201643 -0.09161311 -0.9429221 0.8937644 0.1398435 -0.4261795 0.8883605 0.131481 -0.4399188 0.9207871 0.04547297 -0.3874059 0.9410624 -0.1510765 -0.3026177 -0.3685382 0.0158537 -0.9294775 -0.1862334 -0.2492153 -0.9503731 0.9251138 -0.07355594 -0.3724971 -0.2402279 -0.1300767 -0.9619619 -0.369699 0.01721435 -0.9289922 0.9456927 -0.1284425 -0.2986099 -0.1330845 -0.2761925 -0.9518436 -0.2941942 0.1169747 -0.9485604 0.9569544 -0.2398632 -0.163414 -0.273519 0.06266856 -0.959823 -0.2603135 -0.02285039 -0.9652538 0.9765291 -0.03249585 -0.2129201 0.9848582 -0.08791255 -0.1494184 -0.1534633 -0.1348926 -0.978904 0.9669315 -0.1964949 -0.1625833 -0.1586905 -0.1332392 -0.9782968 0.962257 0.04871112 -0.2677477 0.9806458 -0.006881237 -0.1956694 -0.1352534 -0.1465784 -0.9799088 0.9544649 -0.2796368 -0.1039229 0.9724751 0.03560471 -0.2302709 -0.1833162 -0.001997172 -0.983052 0.9601293 0.1272745 -0.2489038 -0.1396158 -0.0153526 -0.9900867 -0.09535586 -0.05516713 -0.9939135 -0.2075688 0.1566988 -0.9655883 0.9847073 -0.1617972 -0.06460136 -0.1322329 0.03398221 -0.9906361 -0.1489506 0.0475682 -0.9876999 0.009775936 -0.2120339 -0.9772135 0.9900799 -0.09701615 -0.1016356 0.9638848 0.1373708 -0.2281565 -0.005346 -0.1702198 -0.9853917 -0.09866762 0.07587683 -0.9922235 -0.08918535 0.1049516 -0.9904702 -0.09947741 0.1213151 -0.9876168 0.01908951 0.08882808 0.9958641 0.9831797 0.1333511 -0.1248009 0.9944321 0.08109992 -0.0672881 0.06954884 -0.1100143 -0.9914938 0.9834007 0.1392014 -0.1163881 0.07666999 -0.1423888 -0.986837 0.9669526 -0.2369209 0.09418725 -0.99044 0.1180997 -0.07128369 0.1787599 0.1904555 -0.9652832 0.9849125 -0.1632855 0.05731719 0.1153481 -0.06406247 -0.9912572 0.9885843 -0.1222495 0.08806902 0.1018423 -0.003467202 -0.9947945 0.1146983 -0.06230795 -0.9914445 0.9850891 -0.1360453 0.1053144 0.08326786 0.03949058 -0.9957444 0.03954958 0.1463567 -0.9884411 0.9991973 -0.0398966 0.003632068 0.9468638 -0.2629892 0.1851642 0.9967831 0.03730416 0.07093548 0.9972731 0.05009824 0.05419051 0.9901224 0.1238862 0.06564992 0.9885984 0.1427008 0.04806011 0.03705531 0.1444456 -0.9888187 0.1966957 -0.2328039 -0.9524249 0.234618 -0.2611935 -0.9363399 0.9967107 -0.04263073 0.06892335 0.9889899 0.1105493 0.09837687 0.08788925 0.04207527 -0.9952414 0.1361677 -0.03141123 -0.9901877 0.2300454 -0.1668837 -0.9587643 0.933198 -0.2462566 0.2617233 0.9843601 -0.004493772 0.176111 0.2587591 -0.1596839 -0.9526515 0.9709618 -0.131929 0.1995692 0.2206843 0.07442361 -0.9725017 0.2956492 -0.1208249 -0.9476249 0.9870026 -0.004176139 0.1606503 0.2247683 0.1260907 -0.9662196 0.9366348 -0.1652318 0.3088912 0.9494832 -0.06412982 0.3071954 0.3807855 -0.2334684 -0.8947038 0.423435 -0.2601462 -0.8677713 0.4169742 -0.1968188 -0.8873527 0.9533571 0.05167084 0.2973894 0.9497802 -0.05660617 0.3077551 0.2574139 0.1436095 -0.9555703 0.8733282 -0.2560865 0.4143881 0.8485215 -0.2698762 0.4551681 0.3722653 -0.09630745 -0.9231162 0.9177404 -0.1589099 0.364006 0.4325898 -0.1106199 -0.894779 0.9503707 0.1393968 0.2781443 0.9652868 0.1174284 0.2333066 0.9019774 -0.1370685 0.4094497 0.9040449 -0.1248736 0.4087903 0.3803462 -0.08579319 -0.9208562 0.3499214 -0.01828998 -0.9366006 0.3749901 -0.03649157 -0.9263103 -0.7081292 -0.1629965 -0.6870117 0.3779461 0.02374261 -0.9255231 0.3673009 0.1414471 -0.9192838 0.9170996 0.1346833 0.3752183 0.3730115 0.03384405 -0.9272093 0.9111296 -0.02154183 0.4115567 -0.8066973 -0.213197 -0.5511683 0.3639186 0.04314714 -0.9304308 0.4844645 -0.150124 -0.8618335 0.9179121 0.1334575 0.3736662 0.9280569 0.04439872 0.3697826 0.5844502 -0.1284118 -0.8012044 0.9320833 0.05031859 0.3587324 0.4063369 0.07124859 -0.9109413 0.9080659 0.08795088 0.4094889 -0.8425238 -0.06800574 -0.5343492 0.4169139 0.09985119 -0.9034449 -0.8141577 -0.1384819 -0.5638884 0.3981916 0.1277327 -0.9083654 0.5690124 -0.2645543 -0.7786116 0.5669851 -0.1298851 -0.8134235 -0.8035331 -0.1316744 -0.580514 0.5195066 -0.05722737 -0.8525479 0.5221868 -0.05266225 -0.8512037 -0.8129074 -0.02034389 -0.5820375 0.6220476 0.1751958 -0.7631273 -0.714542 -0.2536719 -0.6519818 -0.8114679 0.06005746 -0.5813028 -0.826357 0.1095159 -0.5523951 0.5798862 -0.01416802 -0.8145743 0.5183354 0.1401779 -0.8436105 0.5581722 0.07570785 -0.826264 -0.8198662 0.1242882 -0.5589025 -0.7456304 -0.1219254 -0.6551104 0.6019049 -0.1342107 -0.787209 -0.7803027 0.04165059 -0.6240137 0.558184 0.1257036 -0.8201399 -0.8086706 0.1336583 -0.5728762 -0.6669853 -0.2331031 -0.7076677 0.6766543 -0.1971392 -0.7094189 0.6952544 -0.08431589 -0.7138012 -0.735913 -0.01440423 -0.6769229 -0.7674306 -0.003623962 -0.6411219 0.6303133 0.04226654 -0.7751894 0.6363358 -0.002034068 -0.7714096 0.6561904 -0.008673906 -0.7545455 -0.6544082 -0.1340216 -0.7441694 0.7344136 -0.1970069 -0.6494805 0.6465573 0.1472679 -0.7485157 -0.6312049 -0.1486213 -0.7612439 0.7775538 -0.2282536 -0.585927 0.7573506 -0.0013718 -0.6530072 -0.6605518 -0.01224648 -0.7506806 -0.6811212 0.1350364 -0.7196105 0.79594 -0.1440594 -0.5879851 0.7349205 0.09213709 -0.6718651 -0.6156623 -0.0342881 -0.7872638 -0.6139612 -0.0696147 -0.7862604 -0.6553604 0.1123983 -0.7469066 -0.5110577 -0.2431426 -0.8244404 0.7935256 -0.1150871 -0.5975551 -0.5554495 -0.1587427 -0.8162578 -0.6653121 0.08817398 -0.7413402 0.7678364 -0.08306092 -0.6352386 0.7750878 -0.08782225 -0.6257204 0.7391643 0.1099515 -0.66449 -0.46376 -0.2635622 -0.8458497 -0.6276257 0.03444731 -0.7777529 0.7920549 -0.03358262 -0.6095255 -0.8562419 -0.01093333 -0.5164595 0.7661836 0.0029428 -0.642615 0.6727657 -0.003510355 -0.7398473 0.9155446 -0.002729773 -0.4022073 0.903047 -0.00196135 -0.4295376 0.9999329 -0.004804313 0.01055157 0.9023942 -0.005988955 -0.4308699 0.6066869 -0.001397311 -0.7949398 0.9744405 -6.94649e-4 -0.2246451 0.9840334 -0.00296384 -0.1779597 0.9908561 0.005084872 0.1348275 0.9502205 0.005673944 0.3115268 0.6246744 -4.03495e-5 -0.7808853 -0.5943981 -0.7878969 0.160964 -0.6701734 -0.7120467 0.2094212 0.3582947 -0.6564731 -0.6638284 -0.7005729 -0.7032985 -0.1207016 -0.5645235 -0.8101622 -0.1579576 -0.5366508 -0.7922052 -0.2905459 -0.7002063 -0.7135262 -0.02432334 0.2432503 -0.8236465 -0.5122849 -0.6626507 -0.6291188 -0.4063298 -0.289343 -0.9290628 -0.2304845 -0.5340741 -0.8426671 0.06839019 -0.667228 -0.4427514 -0.5989809 -0.2906433 -0.8648965 -0.409244 -0.2239929 -0.9745904 -9.04619e-4 -0.2897645 -0.8945422 0.3403392 -0.3399177 -0.8142861 0.4705254 -0.4894164 -0.612917 0.6203261 -0.09779179 -0.9605622 -0.2603017 -0.4164042 -0.6687916 -0.615894 -0.4986603 -0.4678834 0.7296733 -0.4092066 -0.5294939 -0.7430923 -0.318367 -0.9294859 0.186275 -0.08524477 -0.831181 -0.5494285 -0.3711931 -0.8928977 0.2548518 -0.6830853 -0.5185413 0.5143049 -0.6496363 -0.6441728 0.4037503 -0.1650373 -0.6315258 -0.7575868 -0.1211918 -0.3831551 -0.9156991 -0.6654496 -0.6386348 0.3864229 -0.05897951 -0.9981848 0.01219815 0.08954584 -0.8162265 -0.5707502 0.1229742 -0.8738541 -0.4703791 0.1053419 -0.5713676 -0.8139055 -0.1795503 0.927125 -0.3289393 -0.1348572 0.9839544 -0.116822 -0.1217225 0.9912776 -0.05052018 -0.1672788 0.979428 0.1128663 -0.6818928 0.003999471 -0.7314413 0.5845068 -0.002370893 -0.8113855 0.6931095 2.81332e-4 -0.7208323 0.6727985 -9.69986e-4 -0.7398252 -0.9983707 -0.009152412 0.05632251 0.00215274 -0.9999976 -5.28241e-4 5.08702e-4 -0.9999985 0.00170052 3.79128e-4 -0.9999958 0.00290215 0.002289175 -0.9999912 -0.003528833 0.00167042 -0.9999915 -0.003786027 0.001296758 -0.9999969 0.002197623 3.75594e-4 -0.9999998 7.34266e-4 0.01691412 -0.9990854 -0.03927302 -0.0222193 -0.9979072 0.06072598 0.001181483 0.9984628 0.05541402 0.006740868 0.9997888 0.01941663 0.01017397 0.9989541 0.04457741 0.02380698 0.9993063 0.02863931 -0.005231499 0.9994505 0.03273332 0.02177327 0.999445 0.02521049 0.02287817 0.9994225 0.02512496 -0.005789339 0.9995225 0.03035509 -0.01969367 0.9988892 0.04280918 0.02937537 0.9995509 0.005928516 -0.0207411 0.9988747 0.04265409 0.03237915 0.9993745 0.01422494 0.009867727 0.9998142 0.01656097 -0.02223563 0.9995007 0.02245163 0.03557145 0.9993661 0.001417219 -0.02210551 0.999512 0.02207446 -0.04545682 0.998593 0.02730965 0.03556829 0.9993662 0.001423358 0.02988654 0.9995088 0.009439229 -0.04550361 0.9985165 0.02990525 0.02398002 0.9996811 -0.007922708 0.04375857 0.9987632 -0.02361035 -0.02112978 0.9997662 0.004619657 0.04071074 0.9990141 -0.01770538 -0.04672056 0.9989066 -0.001725196 0.02657508 0.9993867 -0.02280867 0.03363293 0.9991349 -0.02445906 -0.04315543 0.9990609 0.003883779 0.02738046 0.9991508 -0.0307905 -0.01938855 0.999756 -0.01058459 -0.03117007 0.9994483 -0.01147174 0.02111488 0.9990949 -0.03692674 0.01144576 0.9994027 -0.03261077 0.01154446 0.9996471 -0.02392947 0.009315371 0.9992865 -0.03660285 -0.02821201 0.9993448 -0.02267485 -0.01876652 0.9996461 -0.0188567 0.004751563 0.9990694 -0.04286992 -0.01634496 0.9992525 -0.03503525 -0.007496178 0.9995748 -0.02818006 -0.008100032 0.9995445 -0.02907538 -0.7051538 -0.02603489 -0.7085764 -0.5704646 0.0247181 0.8209503 -0.5064117 0.02905482 -0.8618023 -0.7073034 -0.03519254 0.7060336 -0.8216495 0.02076566 0.5696147 -0.2248154 -0.009731769 -0.9743528 -0.2571735 -0.0247125 -0.9660493 -0.976865 -0.009629011 0.2136403 -0.9791185 -0.003596365 0.2032586 0.09994655 0.03309929 -0.9944422 0.3309767 -0.03015017 -0.9431571 0.456866 0.02229017 -0.8892562 -0.9769259 0.02820223 -0.2117085 -0.9382216 -0.02457636 -0.3451615 -0.8142309 0.02083474 -0.5801671 -0.5396534 -0.7709721 0.3381955 -0.770821 0.1310597 0.6234247 -0.7707652 0.1302429 0.6236648 0.3981295 0.3024902 -0.8660211 -0.3510082 0.7766597 -0.5230611 0.3279402 0.7624722 -0.5577558 -0.4717139 0.7947453 0.381924 -0.09259378 0.9818016 -0.165807 -0.5403494 -0.8012778 -0.2568593 -0.3050952 -0.2353708 -0.922777 -0.8163965 0.4734464 -0.3306741 -0.4236094 -0.8945973 0.1423057 -0.9151901 0.1917863 0.3544647 -0.474507 -0.8578709 -0.1972328 -0.1284019 0.08669191 -0.9879259 -0.9086025 -0.02394288 0.4169752 -0.1337876 0.8107013 -0.5699688 -0.04629069 -0.8745607 -0.4827015 -0.5688221 0.8131824 -0.1231912 -0.5434081 -0.833207 0.102342 -0.1745766 -0.3276718 -0.9285226 -0.9017095 0.2036038 -0.3813996 -0.1572406 0.7834253 -0.6012656 -0.257617 0.9579408 -0.126424 -0.9415382 -0.09353798 -0.323661 -0.4310175 0.8668767 0.2504972 -0.0424363 -0.9810218 -0.1891965 0.1775866 0.9183829 -0.3536042 -0.9407413 -0.2738146 0.2000788 0.03571653 -0.997699 -0.05762904 -0.1256318 0.07428973 -0.9892916 -0.4565707 -0.5959566 0.6605899 -0.3584435 -0.7393625 0.569966 -0.6506323 0.7470327 0.1364544 0.04867148 -0.929391 -0.3658735 -0.002052366 0.8735944 -0.4866503 -0.8028905 -0.3020382 -0.5139454 -0.5306751 0.06084507 0.8453886 -0.4755082 -0.7590535 -0.4446682 -0.4212366 0.8564092 -0.2985348 -0.5008047 -0.8629099 0.06768566 -0.5115217 0.7039057 -0.4928107 -0.2973356 0.8331321 0.4663501 -0.4112231 -0.8474642 -0.3357084 -0.6993446 0.2398469 -0.6733428 -0.558004 0.2029296 0.8046436 0.1840779 0.3553713 -0.9164206 -0.9902429 -0.1369461 0.02578347 -0.8036824 0.5434761 0.2423394 0.07014536 -0.5374246 -0.8403896 -0.9977743 0.01801639 0.06420242 -0.3068958 -0.7974081 -0.5195726 0.06280034 0.8163841 -0.5740846 -0.4976254 0.863812 -0.07872599 0.05330944 -0.9527526 -0.299033 -0.5787423 -0.8145117 -0.04035264 -0.6291694 -0.774205 -0.06893754 0.3080574 -0.08952677 -0.9471461 -0.2090697 -0.8746888 -0.4372752 0.059758 0.8079631 -0.5861951 -0.4164139 -0.8465605 0.3315644 -0.5143952 -0.1171332 -0.849516 -0.9216971 0.3447958 -0.1777372 -0.3529265 0.9033703 0.2436493 -0.6646425 0.01953321 -0.7469062 -0.4771751 0.08439749 -0.8747463 0.4229649 -0.4461254 -0.788716 0.2164952 0.03079307 -0.975798 -0.9990581 -0.04002183 -0.01677191 -0.9974582 0.01864618 -0.06877124 0.4397054 -0.07061916 -0.8953614 -0.9390236 -0.001648366 -0.3438488 -0.9224486 -0.03482657 -0.3845462 -0.7777379 -0.03676372 -0.6275129 -0.749567 2.58445e-4 -0.6619285 -0.6553873 -0.032745 0.754583 -0.7090308 0.01493012 0.7050195 -0.5096095 -0.04464107 -0.859247 -0.5027092 -0.03795915 -0.8636218 -0.844718 -0.06047314 0.5317844 -0.9074425 0.02025443 0.4196879 -0.2164878 -0.01459747 -0.9761763 -0.1871325 -0.03861767 -0.9815753 0.1139478 -0.04951822 -0.9922519 -0.9805072 -0.0748558 0.1816655 -0.9207139 0.3295395 -0.2090212 0.475095 -0.02326095 -0.8796269 -0.1405667 -0.949501 -0.2805155 0.1392551 0.8803914 -0.453342 -0.4577094 0.7829439 0.4213089 -0.2478654 -0.9658898 0.0749641 -0.5280036 0.8473472 -0.05670148 -0.824667 -0.3621274 0.4344976 -0.413376 -0.001221656 -0.9105596 -0.3020601 -0.532451 -0.7907311 -0.3918315 -0.8973231 -0.2031734 -0.2700025 0.657293 -0.7036083 -0.9026108 -0.2349841 -0.360661 -0.3366824 -0.9336436 0.122289 -0.0566436 -0.8716412 -0.4868606 -0.09391087 0.984005 -0.1513775 -0.1976258 0.4876449 -0.8503803 -0.4416193 -0.7164083 -0.5401219 0.001748561 -0.9997944 0.02020448 -0.05024689 -0.8016775 -0.5956413 -0.4638569 0.8552616 -0.2310073 -0.9755775 0.08878016 0.200915 -0.9077739 -0.3094479 0.283176 -0.6059284 0.7631165 -0.2247307 -0.5874434 0.7702117 0.2483631 -0.1172962 -0.4502949 -0.8851419 -0.03998458 -0.1237376 -0.9915091 -0.2306675 -0.9492002 -0.2140364 -0.09983712 0.8872371 -0.4503811 -0.8240653 0.2311675 -0.5171829 -0.216091 -0.9014192 0.3751642 -0.5434703 0.8034106 0.2432517 -0.4811401 -0.2321041 0.845359 0.0370959 -0.951341 -0.305899 -0.3235185 -0.9460665 -0.01714533 0.005944013 0.4211671 -0.9069637 -0.2763031 0.8231865 0.4960046 0.03228145 0.2841163 -0.9582462 0.1064096 -0.1589362 -0.9815378 0.00196892 0.9656652 -0.2597829 -0.9855446 -0.03199505 0.1663682 -0.7009314 -0.1140025 -0.7040587 -0.3541136 -0.9351212 -0.0123279 -0.9204611 -0.3908312 -0.001591742 -0.4593336 0.8871886 0.04369562 -0.9938984 0.01990866 -0.1084879 -0.4616602 0.7886877 -0.4060069 -0.3802428 0.8494742 -0.3657993 -0.9387603 0.3366931 0.07325905 0.1234868 -0.92238 -0.3660141 -0.1126356 -0.980314 -0.1621661 -0.6429588 0.7658812 -0.005500972 -0.4140307 -0.7380599 -0.5327723 -0.4875935 0.7229622 0.4894674 -0.4559046 0.1142037 -0.8826713 -0.6992448 -0.3092159 0.6445482 0.3040215 -0.4907667 -0.8165287 0.1440058 0.7585408 -0.6355143 -0.2829737 -0.9361939 0.2084875 -0.5561308 0.3474422 -0.7549852 -0.4693727 -0.8571122 -0.2122451 -0.9878329 -0.09591996 -0.1224162 0.120375 0.5228422 -0.8438874 0.1062713 -0.9826154 -0.1522281 -0.2028607 -0.9648428 0.1671108 0.4028657 -0.2596292 -0.8776629 -0.7622659 -0.1505495 0.6295123 -0.9633068 -0.04917705 -0.2638592 -0.9062968 -0.1280105 -0.4027897 -0.8187924 -0.04921817 -0.5719761 -0.7153705 -0.1268219 -0.68714 -0.5674479 -0.05515289 0.8215602 -0.5333061 -0.09184581 0.8409216 -0.5831478 -0.0495581 -0.8108531 -0.3712463 -0.07602971 -0.9254165 -0.4485552 -0.105455 -0.8875121 -0.7826978 -0.1096517 0.6126669 -0.8226704 -0.0633518 0.5649778 -0.2290636 -0.1196604 -0.9660286 -0.9297948 -0.06605243 0.3621033 -0.05966013 -0.05240255 -0.9968425 -0.9480265 -0.1120014 0.2978278 0.0637508 -0.1153697 -0.9912748 -0.9964751 -0.05465424 0.06364202 0.3140737 -0.07131135 -0.9467166 0.3157445 -0.06957435 -0.94629 -0.9910828 -0.1224994 -0.05242967 0.365034 -0.1069586 -0.9248299 -0.3761495 0.7127543 -0.5920246 -0.1541309 -0.9853248 0.07334089 -0.5828869 0.3671236 -0.7248884 -0.3398896 0.8199983 -0.460519 0.0112164 -0.9995383 -0.02823859 -0.530676 -0.7629538 0.3691672 -0.5204508 0.7714871 0.3659765 0.1716429 0.2266968 -0.9587216 -0.4414136 -0.01540207 -0.8971716 0.1146675 0.9366311 -0.3310189 -0.3439691 -0.9126662 0.220739 -0.9316413 -0.3003374 -0.2045533 -0.8247627 0.1110982 0.5544581 -0.8220591 0.06911641 0.565192 0.3943127 -0.2154802 -0.8933565 -0.9344774 -0.02825808 0.3548992 0.2602514 0.8440863 -0.4688151 -0.9547103 -0.1384544 -0.2633603 -0.4989321 0.7990979 0.3354243 -0.605171 0.7782056 -0.1678216 -0.3282163 -0.674264 -0.6615453 -0.274477 -0.945568 -0.1748241 -0.6871037 0.7083549 -0.1616225 -0.2315318 0.878695 -0.4174783 -0.1175337 -0.85684 -0.5020072 -0.6248415 -0.779327 0.04714453 -0.8207263 -0.3941072 -0.4136277 -0.2409828 0.2815582 -0.9287908 -0.5912809 0.7760626 0.2193489 -0.1065763 -0.9879969 -0.1118208 -0.94183 0.09419685 0.3226194 -0.934439 -0.03006792 0.3548519 -0.5519165 0.7836645 -0.285058 -0.7647339 -0.3286742 -0.5542162 -0.3416179 -0.9374374 0.06714439 -0.2186501 0.7982295 -0.5612681 -0.5602987 0.805749 0.1919218 -0.1690602 -0.00445789 -0.9855957 -0.791944 -0.03567749 -0.6095505 -0.599376 0.6822702 -0.4186357 -0.08746087 0.768727 -0.6335688 -0.0328496 -0.9802678 -0.1949257 -0.02747762 0.8964833 -0.442225 0.0534541 -0.5819417 -0.8114718 -0.6206771 -0.3857434 0.6826141 0.01437145 -0.9996986 -0.01990634 -0.7400476 -0.2073516 -0.6397929 -0.1608992 -0.965771 0.203465 -0.4361249 -0.7457228 0.503679 -0.1936359 -0.9509452 -0.2412646 -0.8164635 -0.5568196 0.1527725 -0.5357226 -0.5568025 0.634801 -0.6217665 0.7818281 0.04638409 -0.4414856 0.4260784 0.7896504 0.03404349 -0.4716842 -0.8811102 -0.9889515 0.1448513 0.0315141 -0.3249955 0.8236867 0.46467 -0.4686131 0.7852596 -0.4046844 -0.3254349 -0.8447411 -0.4248585 -0.3008698 -0.9516997 -0.06119781 -0.2370191 -0.9708387 -0.03597694 -0.6167491 -0.02611821 0.7867265 0.1604536 0.292344 -0.9427565 -0.6085369 0.7924504 0.04129499 -0.5866166 0.1265193 -0.7999211 -0.9857264 0.04852271 -0.1612117 -0.4288384 0.8083313 0.4033588 0.1154559 -0.9279963 -0.3542495 0.04856818 -0.9779989 -0.2028774 -0.9874111 0.01529562 -0.1574344 0.2261117 -0.6035045 -0.7646278 -0.700363 -0.1572691 0.6962458 -0.1484163 -0.1480804 -0.9777755 -0.1078178 -0.166387 -0.9801483 0.2347119 -0.1673665 -0.9575483 -0.9645616 -0.1706634 -0.201234 -0.07267147 -0.08997005 0.9932897 -0.9842543 -0.1157307 -0.1336044 -0.1859741 -0.1733378 0.9671441 -0.3838866 -0.09742754 0.9182261 -0.910974 -0.1117724 -0.397031 -0.4981342 -0.1532995 0.8534411 0.8499166 -0.1473309 0.5059006 -0.62684 -0.1096034 0.7714005 0.7566334 -0.1070227 0.645021 -0.8095117 -0.1029287 0.5780109 -0.8720191 -0.152752 0.4650265 0.5203998 -0.1040453 0.8475605 0.4624704 -0.1412748 0.8753072 -0.9307701 -0.1022495 0.3510159 0.2611533 -0.1154197 0.9583722 0.1990448 -0.1586663 0.9670607 -0.9718381 -0.1554132 0.1771374 -0.9867244 -0.1148169 0.1148566 -0.8666859 0.4986429 -0.01452887 -0.9968154 0.06753963 0.04239761 0.03213644 -0.113215 -0.9930508 -0.01392513 -0.9333441 -0.358713 -0.4207855 -0.8501043 0.3166421 -0.1984289 0.9431107 -0.2667739 -0.2389879 0.9710161 0.003566861 -0.7580406 -0.3958897 0.5183106 -0.7693533 -0.6280029 -0.1170809 -0.1832906 -0.950976 -0.2490966 -0.533567 -0.09711015 -0.8401643 -0.2041491 -0.9139775 -0.3506684 -0.454344 0.829297 0.3253278 -0.7694798 -0.3093869 0.5587312 -0.5224317 0.8491566 -0.07744878 0.05543732 0.1627793 -0.9851039 -0.6246894 0.6821775 0.3799961 0.009797692 0.9756994 -0.2188941 0.08356481 0.8194308 -0.567054 -0.7846749 0.6070755 -0.1254782 -0.2094736 -0.9724972 -0.1018332 -0.5464557 -0.2478816 -0.799963 0.1190835 -0.9397906 -0.3203326 0.07672095 -0.9659818 -0.2469679 -0.192448 -0.9808703 0.02927815 -0.3827711 0.6931596 -0.6107504 0.2196323 -0.4303921 -0.8755138 -0.6866679 -0.6467793 0.3319094 -0.1369479 -0.989972 0.03465342 -0.1335649 -0.9461755 -0.2948091 0.2870349 -0.2453656 -0.9259626 -0.5163689 -0.8328402 -0.1993501 -0.214742 0.9113351 -0.3512184 -0.9274513 -0.03539884 0.3722649 0.1792693 0.6062234 -0.7748263 -0.8803023 -0.2814418 -0.3819142 0.3811634 -0.5335822 -0.7549865 -0.1354023 -0.9264784 -0.3511466 -0.3576155 -0.05169796 -0.9324368 -0.9173123 -0.1533422 -0.3674567 -0.2492098 0.9638068 0.09471499 -0.3434654 0.9284144 -0.1416979 0.4202862 0.4037362 -0.8126234 0.4255601 0.3913447 -0.8159338 -0.3791049 0.2882 -0.8793295 -0.144414 -0.9666334 0.2115762 0.1056951 0.971979 -0.2099657 -0.1282431 -0.9723476 0.1951763 0.1475222 0.9090828 -0.3896226 -0.8933163 0.2679718 0.3608008 -0.819473 0.4423736 -0.3643758 -0.6160196 0.7067183 -0.3479499 -0.5112815 -0.3339999 0.7918557 -0.1797509 -0.9639692 -0.1960946 -0.1220988 0.9345458 -0.3342395 -0.1084589 0.9662235 -0.233771 -0.9268484 -0.2669705 0.2639679 -0.2496944 0.9636516 0.09501838 -0.6463285 -0.7461308 0.1598387 -0.7275982 0.6540233 0.2070131 -0.2454976 -0.2715253 -0.930594 -0.3821451 -0.8752641 -0.2964422 -0.9508354 0.3008344 0.07355749 -0.06944119 -0.9310085 -0.358331 -0.08446133 0.9935545 -0.07560193 -0.1122007 0.7599852 -0.6401824 -0.07036733 -0.9974697 -0.01013994 0.06028938 -0.9981781 -0.002424955 -0.9447246 -0.3264141 0.03081107 -0.501711 -0.5982471 0.6248092 -0.1372816 -0.002919733 -0.9905278 -0.2355604 0.9279224 0.288914 -0.04670572 0.9422395 -0.3316677 -0.7069823 -0.7063862 0.03456318 -0.3807741 0.9235874 0.04469275 -0.609414 0.3964932 0.6865915 -0.6330488 0.3459112 0.6925277 -0.1136592 0.8664772 -0.486106 -0.1864573 0.962191 0.1985504 0.07879149 0.0133863 -0.9968013 -0.7454033 -0.2803519 -0.6047949 -0.7280047 0.05999177 -0.6829424 0.04455673 -0.7175107 -0.695121 -0.2561392 -0.9503599 0.1766601 -0.373865 0.8549507 -0.3595615 -0.6806052 0.3999176 -0.613875 -0.8755716 -0.170875 -0.4518586 -0.7630594 -0.387602 0.5172089 -0.8348278 -0.07265144 -0.5456962 -0.7878121 -0.05445533 -0.6135038 -0.7150544 -0.07473748 -0.6950623 -0.03426253 -0.3450968 -0.9379416 -0.6057034 -0.05454874 -0.7938185 -0.5675359 -0.06382381 -0.8208712 -0.8950688 -0.06980627 0.4404304 -0.8847712 -0.05658304 0.4625781 -0.8947545 -0.08450371 0.43849 -0.7957054 -0.0514484 0.6034948 -0.8085014 -0.06455236 0.5849432 0.07074552 -0.07696312 -0.9945209 0.03409415 -0.05438798 -0.9979377 0.07810389 -0.08594095 -0.9932341 -0.1078304 -0.06236845 -0.9922111 -0.1174677 -0.05673736 -0.9914547 -0.5475955 -0.07705008 0.8331882 -0.4237093 -0.05437242 -0.9041649 -0.3566976 -0.07273399 -0.9313843 -0.6132797 -0.05643802 0.7878471 -0.2758526 -0.06245809 -0.9591687 -0.1917557 -0.07399386 -0.9786494 -0.2638398 -0.05064094 -0.9632363 -0.7206632 -0.06499803 0.6902318 -0.131509 -0.08887493 -0.987323 -0.714849 -0.06235718 0.6964931 -0.7748239 -0.00448513 0.6321614 -0.7737122 -0.1028783 0.6251285 0.1250621 -0.07894009 -0.9890035 -0.9429746 -0.06633317 0.3261885 0.233445 -0.06830835 -0.9699677 -0.9516367 -0.0632103 0.3006528 0.2972983 -0.08977687 -0.9505546 -0.9660593 -0.07242894 0.2479584 -0.9882259 -0.05789679 0.1416247 0.4109516 -0.07250261 -0.9087696 -0.9962555 -0.07842344 0.03639841 -0.9948856 -0.05989754 -0.0813325 -0.9829847 -0.07064133 -0.1695612 -0.9727635 -0.06126374 -0.2235574 -0.9321274 -0.06475925 -0.3562929 -0.9190427 -0.05145138 -0.3907858 -0.7749684 -0.005015552 0.6319801 0.429188 -2.2822e-4 -0.9032151 0.4145571 -0.001769661 -0.9100217 -0.6198326 -0.004550635 0.7847209 -0.7219744 0.002407431 0.6919157 -0.5430269 -1.68914e-4 0.8397154 0.1432433 -0.002791404 -0.9896836 0.2292195 -0.007906496 -0.9733427 0.08871901 9.24892e-4 -0.9960563 -0.4940959 1.81711e-4 0.8694074 0.1803857 -0.004865705 -0.983584 -0.5258899 0.001605033 0.8505513 0.1012554 -0.004855692 -0.9948487 -0.4959862 0.01476466 0.8682048 0.05599999 -3.54338e-4 -0.9984308 0.1258235 -0.01684498 -0.9919096 -0.6231923 -0.02950501 -0.7815119 -0.7842773 -0.02892267 -0.619736 -0.8574196 -0.04267495 -0.5128456 -0.9293177 -0.0558896 -0.3650274 -0.8630569 0.006071329 -0.5050704 -0.6851661 0.001585423 -0.7283852 -0.1349382 0.002577424 -0.9908507 0.069821 0.1531962 -0.9857261 -0.06185424 -0.006796479 -0.9980621 -0.04787892 -0.004527747 -0.998843 -0.2288361 -0.002168655 -0.9734626 -0.2770725 0.002526283 -0.9608458 -0.4100036 -0.005081176 -0.9120697 -0.95028 0.03823721 0.3090404 -0.4877446 0.001302838 -0.8729854 -0.6038848 9.83252e-4 -0.7970711 -0.4834972 -0.02517789 -0.8749838 -0.5928093 1.97277e-4 -0.805343 -0.6188372 -0.003328144 -0.7855122 -0.4979197 0.006075203 -0.8672019 -0.578354 0.0263912 -0.8153589 -0.7911927 -0.005305171 -0.611544 -0.8110982 0.01008558 -0.5848231 -0.9170845 0.001185357 -0.3986914 -0.9337325 -0.007145702 -0.3579003 -0.9890123 0.001048684 -0.1478304 -0.9325228 0.009613096 -0.3609833 -0.9741177 -0.002437472 -0.2260286 -0.955296 0.003288626 -0.295633 -0.9611627 -0.005645096 -0.2759249 -0.9972383 -0.002004802 -0.07424217 -0.9746909 -0.002358973 -0.2235445 -0.9388117 0.00104475 -0.3444292 -0.999925 0.00319451 -0.01182705 -0.9894625 -0.006627798 0.1446381 -0.9374732 -1.4119e-4 0.3480573 -0.9290512 0.002143502 0.3699451 -0.9165617 -1.23469e-4 0.3998934 -0.07141077 0.007066488 -0.997422 -0.219967 7.67655e-4 -0.9755071 -0.2680455 -5.38605e-4 -0.9634062 0.1530399 0.003134071 -0.9882151 0.3001112 -0.002165198 -0.9539018 -0.9975639 0.00624305 0.06947898 -0.948001 5.5285e-4 0.3182668 -0.9863321 -0.003735005 -0.1647275 -0.7192498 0.003243684 -0.6947439 -0.6927182 0.001551091 -0.7212067 -0.4798631 -6.56626e-4 -0.8773432 0.08775234 0.004411578 -0.9961326 -0.2985023 -0.002401292 -0.9544059 -0.9788734 -0.001615881 -0.2044609 0.9855856 -2.03925e-5 -0.169178 0.9936882 0.001107275 -0.112173 0.9901427 -3.09613e-4 -0.1400629 0.9302835 0.002840399 0.3668306 0.516426 -0.003997147 0.8563225 0.616344 0.001793146 0.7874751 0.2773244 -0.003142595 0.9607712 0.9609295 -0.00245279 0.2767825 0.4290365 0.00201714 -0.9032849 0.2279046 -0.00660777 -0.9736611 -0.7459719 0.001005649 -0.6659767 -0.5685763 -0.01038104 -0.8225651 0.2785859 0.001676321 -0.9604099 0.3425763 -8.87447e-4 -0.9394897 -0.5836345 0.001863062 -0.8120145 -0.2412923 -0.003527104 -0.9704462 -0.2556254 -0.002843797 -0.9667719 0.6120027 -6.87318e-4 0.7908555 0.652161 -0.001500725 0.7580791 0.7312956 6.54107e-4 0.6820604 0.7980411 -1.00966e-4 0.6026031 -0.4904804 -0.002806961 0.8714478 0.2594355 0.002103328 0.9657583 0.1835649 -0.002492725 0.9830045 -0.02729767 0.007942914 0.9995958 0.1677474 0.001793742 0.9858285 0.4934704 0.003050863 0.8697572 0.1782996 0.001594841 0.9839749 -0.2696712 -6.31193e-4 0.9629523 -0.8384159 -0.00627011 0.5449951 -0.7617456 6.02479e-4 0.6478762 -0.2750015 -9.03422e-4 0.9614434 -0.9660232 -0.005473554 0.2583976 0.8549072 -6.79513e-4 -0.5187806 0.6602842 -0.001409709 -0.7510145 0.5996043 4.0948e-4 -0.8002966 0.9121305 0.0024634 0.4098926 0.8539478 0.001231253 -0.5203574 0.8794054 -0.003556847 0.4760605 0.9590402 -0.001688718 -0.2832654 0.9627578 -0.002310395 -0.2703555 -0.9638285 0.00153321 -0.2665191 -0.9382577 -5.41089e-4 -0.345937 -0.9467978 -4.71072e-4 -0.3218289 -0.3101159 0.001848101 -0.950697 -0.1482022 9.98137e-4 -0.9889566 0.1197112 -0.01391321 -0.9927114 -0.6800114 -0.002424061 -0.7331976 0.2260274 0.004560589 -0.9741104 -0.8075274 0.004104316 -0.5898158 -0.6293721 0.01145404 0.7770198 -0.7701166 5.25765e-4 0.6379029 -0.4788874 0.01597893 0.8777309 -0.9003854 0.001597762 -0.4350903 -0.7387861 1.36712e-4 0.6739401 -0.89298 1.33448e-4 -0.4500965 -0.9597995 -0.002816677 0.2806725 -0.9755011 -3.3318e-5 0.2199947 -0.2587497 -0.003745734 0.9659371 0.91229 0.01085335 0.4094011 -0.4901528 0.001096487 0.8716359 -0.95922 0.002135694 0.2826525 -0.9919683 -0.003252446 0.1264461 -0.736776 -0.002546846 0.6761322 -0.992884 0.00330007 -0.1190398 -0.5897973 0.004068255 0.8075411 0.9831711 6.36005e-4 0.1826863 0.9569501 0.002965092 0.2902376 0.8979216 9.76526e-4 0.4401543 0.3112087 0.001846671 0.9503399 0.1464061 3.24827e-5 0.9892247 0.02714049 -0.006094515 0.9996131 -0.4382838 0.004351437 0.8988261 0.6755717 -0.002501189 0.7372901 -0.1707515 -0.002197265 0.9853116 0.8031473 0.003920078 0.5957677 0.7312281 0.006608068 0.6821011 0.6730617 -0.0202015 0.7393104 0.4136441 0.01722466 0.9102758 0.7587532 -0.02233898 -0.6509951 0.3279614 -0.01933717 0.9444932 0.8259705 0.0201928 -0.5633517 0.9494982 -0.01048487 -0.3135975 0.9740686 0.01579439 -0.225701 -0.03772836 0.0176922 0.9991314 -0.1873431 -0.03446722 0.9816898 0.9967425 0.0106346 0.07994651 0.999198 -0.002520024 0.03996378 -0.3253929 0.008478164 0.9455409 0.8930202 0.01744085 0.4496787 0.915565 -0.003762364 0.4021524 0.4068076 0.2585797 0.8761531 0.537302 0.02226001 0.8430961 -0.007220625 0.999794 -0.01897215 0.5686035 -0.8097051 0.1451476 0.7961477 0.04128247 -0.6036925 0.4674115 -0.1968722 0.8618399 -0.2636674 0.8893677 0.3735032 0.9516811 0.1772527 0.2507682 0.8334897 -0.3297302 -0.4433656 0.5028262 0.7538064 -0.4230151 0.5911291 -0.720338 -0.3628768 0.2266975 0.9616615 -0.1543228 0.4473363 -0.8478811 0.284584 0.9544367 0.08902472 0.284825 0.3612848 0.1499414 0.9203211 0.7960957 0.3226791 -0.5119668 0.4296167 -0.8581271 0.2811541 0.2751673 -0.4882994 0.8281587 0.407182 0.906996 0.1075233 0.1274023 0.9341704 0.3333081 0.133531 0.5270357 0.8392872 0.8749771 -0.1525793 0.4594938 0.822596 0.2258131 0.5218663 0.06363952 -0.9550278 0.2896069 0.4916082 -0.8629543 -0.1167535 0.5118178 0.8171737 0.2650845 0.9468238 0.0305379 -0.3203002 0.05059182 -0.05241888 0.997343 0.5343227 0.801505 0.268494 0.05775833 -0.9259393 0.3732297 0.2452145 -0.8262915 -0.5070624 0.5983623 0.7756223 -0.2009298 0.4028059 0.9103897 -0.09454208 0.9427735 0.2547616 -0.2151156 0.1147946 0.4203826 0.900056 -0.0129131 0.9564134 0.2917306 0.749641 -0.6613516 0.02554464 0.3655488 -0.8384866 0.4041217 0.6501943 -0.1763079 -0.7390283 0.3455053 -0.8717785 0.3473162 0.9957194 0.07645618 -0.05193793 -0.07001322 -0.2955565 0.9527563 0.2508172 -0.9263716 -0.2809385 0.5268515 -0.8491812 -0.03631454 -0.03901231 0.1908822 0.9808374 0.6301724 -0.163131 0.7591251 0.6343015 -0.2380109 -0.7355355 -0.1116378 0.9418057 0.3170791 0.6802777 0.1121587 0.7243223 0.5288806 0.5802454 -0.6193551 -0.05555844 -0.9072658 0.4168716 -0.1663069 -0.7502065 0.6399471 0.4414617 0.8972173 -0.01062428 0.9679473 0.2496087 0.02781474 -0.3008031 -0.1216737 0.9458927 0.3871105 0.7778999 0.4949923 0.1167978 -0.9770922 0.1779022 0.5878932 -0.7964557 0.1415627 -0.2425192 0.2055712 0.9481166 0.5705212 0.8199238 0.04723042 -0.02188503 -0.9995794 0.01902973 0.2327578 -0.7725921 0.5906991 -0.2148565 0.8842785 0.4145943 0.7880437 -0.272022 -0.55226 0.8845064 0.01106244 0.4663969 0.8435335 -0.04186749 0.5354422 0.6044947 0.0147587 -0.7964725 0.6345183 0.01675516 0.7727262 0.6729078 -0.01899856 0.7394824 0.6820487 -0.03839242 -0.7302983 0.4820388 -0.07380747 0.8730356 0.8261271 0.003532528 -0.5634729 0.889799 -0.07398533 -0.4503156 0.2096849 -0.03753781 0.9770483 0.1697208 0.004827618 0.9854804 0.9620711 0.008853673 -0.2726552 -0.06280368 -0.002838253 0.9980219 -0.1210513 -0.03968971 0.9918525 0.9952999 -0.06355786 -0.07306486 0.9968711 0.01670902 0.07725906 -0.4263246 -0.04242044 0.9035751 -0.3796994 -0.003705322 0.9251026 0.9689906 -0.06647318 0.237989 0.7717245 0.2829291 -0.5695545 0.7039473 -0.2680538 -0.6577275 0.0379455 -0.9961538 0.07897967 0.8016618 -0.3502785 -0.4843999 0.8752204 -0.06172597 -0.47977 0.2059234 -0.7550346 0.6225097 0.9372204 0.3110101 0.1577681 0.4213207 0.8516749 -0.311671 0.4227992 -0.04287558 0.9052086 0.1637679 -0.9820827 0.09323942 0.5077473 -0.8409566 0.1870422 0.4968517 -0.4958643 0.7122198 0.2211388 -0.7215591 0.656087 0.2070875 0.9103169 0.3583825 0.7339854 -0.5821765 0.3497657 0.8399845 0.2229176 -0.4947059 0.205408 0.006521344 0.9786548 0.9250152 0.05432528 0.376026 0.9108529 0.1273006 0.3926086 0.4780369 0.8150098 -0.3274747 0.01844996 -0.9755041 0.2192064 0.6867753 0.672949 0.2747352 0.3863447 0.9003788 0.2001395 0.1212691 0.815606 0.5657567 0.8178722 -0.4537657 -0.3538104 0.1648591 -0.1027683 0.9809487 -0.01494091 -0.6620178 0.7493392 0.9570541 0.0627008 -0.2830479 0.4176124 -0.8610318 0.290214 0.07792609 0.8613666 0.5019714 0.469384 0.8621633 -0.1906652 0.3686769 -0.7116906 -0.5979748 -0.008766829 -0.6424749 0.7662567 0.5319579 0.2223622 -0.8170533 0.1196258 -0.9926708 -0.0171619 0.02108788 -0.9723269 0.2326708 0.7670103 0.05165004 0.6395526 0.7623668 0.0720815 0.6431183 0.435987 -0.8999516 -0.001588761 -0.01091557 0.3143111 0.9492574 0.149851 -0.9808899 0.1240956 0.9388635 -0.3406598 -0.04986155 -0.04799491 0.7374806 0.6736608 0.3311327 0.8929192 0.3050352 0.2774753 -0.9031957 -0.327483 0.5228635 -0.4908527 0.6969057 0.9950618 -0.0966224 -0.02272105 0.5454553 -0.6942992 -0.4694967 0.6153745 0.7697233 -0.1698248 0.9987347 0.01938396 -0.04640501 0.3191764 -0.9450706 0.07048457 -0.07407045 -0.9628213 0.259786 0.5462336 -0.4386798 0.7135748 0.5850344 0.2970097 0.7546656 0.6117753 -0.006143033 -0.7910078 -0.2327419 -0.551985 0.8007146 0.5706444 0.8209038 0.02195632 0.6847746 -0.7156998 0.137323 0.4127045 0.7280194 -0.5474146 0.3728739 0.8469277 -0.3790496 -0.2140013 -0.2458422 0.9453915 0.4401224 0.7214112 0.534657 -0.2700731 -0.03218221 0.9623019 -0.1874981 -0.9384919 0.2899612 -0.08104085 -0.925487 0.3700086 0.6566644 0.752433 0.05134737 0.05790096 -0.9979284 -0.02804589 -0.1538431 0.9098377 0.385393 -0.2032347 0.5296565 0.8235046 0.2738597 -0.943482 -0.1866619 -0.4838631 -0.2497082 0.8387625 0.9746856 -0.09738868 0.2012549 0.9545585 -0.04060041 0.2952451 0.8969829 -0.1121704 0.4275974 0.7915263 -0.07301974 0.6067574 0.7193922 -0.1343131 0.6814946 0.5396429 -0.04988855 0.8404146 0.6476126 -0.1053771 -0.7546479 0.684799 -0.06023025 -0.7262387 0.4265312 -0.1240068 0.8959317 0.230719 -0.05112755 0.9716763 0.9110745 -0.06902199 -0.4064226 0.8796833 -0.1166254 -0.4610379 0.08315539 -0.1308794 0.9879049 -0.1472714 -0.04042088 0.9882699 0.995598 -0.06780356 -0.06471014 0.9855955 -0.1143596 -0.1245936 -0.2572774 -0.1228048 0.9585027 0.3455348 0.8601197 -0.3752331 0.9778317 0.2050421 0.04246187 0.2468712 -0.9627695 0.110135 -0.2798798 -0.04417765 0.9590182 0.8639069 -0.4423083 0.2408909 -0.06475412 -0.9823255 0.1756235 0.6218886 0.2953097 0.7252908 0.1402056 -0.9063134 0.398671 0.07883411 -0.9864013 0.1442145 0.4448257 -0.5022349 0.7415459 0.4048675 0.9107064 0.08183044 0.7049054 -0.5849814 -0.4011301 -0.3364962 -0.6475195 0.6837316 0.2706565 -0.9514688 -0.1464665 -0.3652231 -0.3781437 0.8506582 -0.09313768 0.9083891 0.4076209 0.2905814 0.8387665 0.4604709 0.8162618 0.06943857 -0.5734936 0.3708024 0.5591374 0.7415329 -0.4613795 0.3858397 0.7989097 0.7819116 0.4061068 -0.4729608 0.2554382 -0.9576295 -0.1330304 0.2594633 0.947387 -0.1874476 0.9259583 -0.1321306 -0.3537552 0.3279578 -0.1214511 0.9368529 0.3408159 0.7896574 0.5101821 0.6847518 0.697982 0.2096096 0.5529091 -0.8124012 0.1851917 0.2074723 -0.9707239 0.1210392 0.291329 -0.9338366 0.2075493 0.8739192 -0.2889673 0.3908493 0.8530635 -0.3871698 0.3498319 0.1645101 -0.4634344 0.8707267 0.664729 0.6966306 0.2698911 0.5127474 0.8281593 -0.2263677 0.9177849 -0.2002483 -0.3428869 0.9304953 0.2726052 -0.2446731 0.5718998 0.7981694 0.1893573 -0.02656555 -0.9996471 3.70726e-5 0.07333308 -0.9307145 0.3583195 0.4347115 -0.9000312 -0.03114265 0.1005107 -0.972325 -0.210907 0.261982 -0.9098247 0.3218456 0.7788344 -0.2506503 0.5749709 0.2044106 -0.09490042 0.9742742 0.4247149 -0.6839868 -0.59311 0.08326077 0.6727585 0.7351623 -0.03244185 -0.9747826 0.220786 0.5031827 0.1167725 -0.8562543 0.2637038 -0.9645557 0.00962764 0.07185381 0.9512757 0.2998529 0.7558556 0.6539053 -0.03301918 0.6431996 -0.469598 0.6047908 0.9872562 0.1242262 -0.09946429 -0.01999121 -0.9994354 0.02700775 0.5289716 0.7067252 0.4698176 0.3147187 -0.8610833 -0.3993593 0.6548642 0.5355178 0.533267 -0.04116487 -0.7185598 0.6942459 0.1708254 -0.9698042 0.1740649 0.9581893 0.150407 -0.2434152 -0.1189382 -0.2350251 0.9646849 0.7253316 -0.1652858 -0.6682626 -0.03249275 0.4751713 0.8792933 0.6162199 -0.2449265 0.7485212 0.5117645 0.6927012 -0.5081951 0.9933481 -0.0884096 0.07377934 0.6940238 -0.2346917 -0.6806253 0.2728753 0.9619582 -0.01324957 -0.09132725 0.7834575 0.6146981 0.6669313 0.06549894 0.7422348 0.2180162 -0.962812 -0.1595686 0.1440759 0.9839357 0.1054167 0.3084343 -0.9453685 0.1055784 -0.05648696 -0.9535819 0.2957887 0.9814521 -0.1857095 0.04758101 0.6167382 0.3680167 -0.6958432 0.6935015 -0.1574906 0.7030308 0.6841692 -0.1571538 -0.7121904 0.06093072 -0.1009404 -0.993025 0.9866433 -0.1204935 0.1096198 0.9424043 -0.1300621 0.3081527 0.9213664 -0.08887827 0.3783974 0.3502408 -0.1024432 -0.9310407 0.4952846 -0.1433966 -0.8568142 -0.8368883 -0.1596128 -0.5235856 0.5705363 -0.1154611 -0.8131157 -0.8105896 -0.1282305 -0.5714031 0.7346475 -0.1213557 -0.6675072 -0.6957333 -0.1140235 -0.7091925 -0.6055914 -0.1636595 -0.7787649 0.8688651 -0.1518579 -0.4711824 -0.5242553 -0.1099634 -0.8444315 0.9016085 -0.1066125 -0.4192087 -0.3439612 -0.1298866 -0.9299572 -0.315622 -0.1489387 -0.9371233 0.970565 -0.1449977 -0.1923002 0.9875426 -0.1044228 -0.1177091 0.9875547 -0.1498434 0.04777622 0.2517387 -0.9577652 -0.1389728 0.434744 0.8236079 0.3642358 0.4892972 0.8720068 -0.01387685 0.7252104 0.3206613 -0.6092997 -0.04536426 0.7384337 0.6727985 0.1781789 -0.9835937 0.02820885 0.6453935 -0.7451023 0.168196 0.5623437 0.8246921 0.06043791 0.7963436 -0.5767749 0.1821193 0.4836348 -0.5840503 0.651907 0.6772741 -0.574472 -0.4596542 -0.1575081 -0.2983027 0.9413855 0.009280085 -0.9993529 -0.03475105 0.499884 -0.1443054 0.853986 0.1907614 0.9689287 -0.1574395 -0.1753135 0.6516154 0.7380126 0.9629306 0.01391553 0.2693901 -0.06676685 -0.9467008 0.3151186 0.911026 0.3706748 0.180643 0.8664574 -0.03430777 -0.4980708 0.4704717 0.8740453 0.1212491 -0.2392207 -0.5875505 0.7730187 0.5212377 0.4072585 0.749968 0.655838 0.6151541 -0.4375636 0.09520566 -0.9747304 0.2020803 0.2453555 0.8627217 0.4421674 0.1903921 -0.9810118 -0.03697127 0.3954825 0.9005357 -0.1806353 0.8515818 -0.4552438 -0.2599262 0.1668588 0.9496036 0.2653512 -0.2270672 -0.7150195 0.6612017 0.9289012 -0.08084267 -0.361396 -0.3729572 0.3457129 0.8610376 0.8906146 -0.0645613 0.4501529 0.5402739 0.8102402 0.2271897 -0.370312 0.3525315 0.859413 0.2519776 0.5771098 0.7768214 0.08229267 -0.9864261 -0.1420965 0.2311519 -0.4941333 0.8380937 0.6809005 -0.6899213 -0.2457298 -0.118529 0.952206 0.2815225 0.1464061 -0.9655854 -0.2149657 -0.2761456 -0.4650784 0.841098 -0.0824213 0.971224 0.223452 0.8902577 0.3633719 -0.274595 0.3763427 0.02813464 0.9260534 0.4790858 -0.3931189 -0.7848149 0.5622001 0.7944191 0.2298467 0.3968248 0.8997811 -0.1814507 0.6512784 -0.6126355 0.4477881 0.4226197 -0.9062044 -0.01365172 0.247839 0.9031157 -0.3506534 0.1554222 -0.9871575 0.03693217 0.1037467 -0.9711898 0.2145391 0.8797078 0.02505481 0.4748543 0.08910405 0.9347271 0.3440144 0.1355603 -0.9835749 -0.1191801 0.166337 0.2968264 0.9403331 0.5382492 -0.6508255 -0.5354569 0.1187407 -0.3000515 0.9465039 0.4854184 0.7505136 0.4484398 0.06791496 0.9536952 0.2930071 0.6473688 -0.002698183 -0.7621723 0.2812228 -0.9332613 0.223466 0.993502 0.06289893 -0.09485566 0.09704774 0.5981853 0.7954597 0.05573821 -0.9972628 0.04858058 0.5283681 0.6455684 -0.5514243 0.2487128 0.9415006 -0.2274174 -0.004192113 0.02095067 0.9997717 0.03750663 -0.9264544 0.3745338 0.7012986 0.001519799 0.712866 0.7552167 -0.1689092 0.6333385 0.6450588 -0.1341882 0.7522584 0.9871677 -0.1584716 -0.01966899 0.134557 -0.9838787 -0.1178017 0.5975353 0.7987021 -0.07089835 0.05882817 -0.9102841 0.4097831 0.09885925 -0.9855656 0.1374313 0.7458547 -0.3346673 -0.5759327 -0.04641872 -0.9987623 -0.01786679 0.87103 -0.03684324 0.4898461 0.7821071 -0.11706 0.6120503 0.7206185 -0.04891741 0.6916041 0.7093012 -0.06055271 0.7023 0.6048766 -0.07524693 0.7927561 0.5332307 -0.05888938 0.8439177 0.8404199 -0.05965334 0.5386428 0.8437709 -0.05642992 0.5337288 0.9062829 -0.0128017 0.4224779 0.9122105 -0.05153596 0.4064677 0.902099 -0.06561189 0.4265122 0.5173289 -0.09092462 -0.8509428 0.39793 -0.08910274 0.9130786 0.3264141 -0.06599539 0.9429202 0.5703105 -0.06772077 -0.818633 0.6700888 -0.05885154 -0.7399443 0.2405692 -0.06438273 0.9684944 0.7361508 -0.07378137 -0.6727842 0.1256437 -0.09547483 0.9874707 0.7821592 -0.06032812 -0.6201514 0.04202032 -0.07632285 0.9961974 0.8710187 -0.0753647 -0.4854346 -0.09056019 -0.08629536 0.9921451 -0.1271196 -0.07242321 0.9892399 0.9030244 -0.05726802 -0.425755 0.9445971 -0.06611537 -0.3215045 -0.2518807 -0.0582031 0.9660065 0.9705827 -0.1113882 -0.2134527 0.9800127 -0.02425891 -0.1974505 -0.350878 -0.07333105 0.9335455 0.9902718 -0.06677848 -0.1220753 -0.4269748 -0.05883657 0.9023475 0.9970508 -0.06726241 0.03695476 0.9961671 -0.06077009 0.06291496 0.9724372 -0.07105487 0.2220745 0.9535178 -0.1102141 0.2804579 0.9664167 -0.05295681 0.2514647 0.8967466 -0.002946078 -0.4425346 -0.4190433 -0.002661406 0.9079623 0.8006787 -2.86994e-4 -0.599094 -0.5165059 0.03176027 0.8556945 0.6968052 0.002792716 -0.7172551 -0.4151022 -0.003041326 0.9097697 -0.4178298 -0.003310263 0.9085194 0.6269111 0.003245472 -0.7790841 0.7515856 0.007327556 -0.6595948 -0.2419014 5.19286e-4 0.9703008 -0.09125089 -0.003699243 0.9958211 -0.1427444 0.04505604 0.9887335 0.05021089 -0.003621935 0.9987321 0.6617071 -0.003134608 -0.7497559 0.1196682 -9.64997e-4 0.9928136 0.1823115 -0.005781114 0.9832238 0.6369343 -0.001207351 -0.7709171 0.6658424 0.01287996 -0.7459813 0.6592461 0.005828499 -0.7519048 0.6489423 4.46373e-4 -0.7608375 0.5188119 -0.004397153 -0.8548771 -0.3150562 -0.08542758 0.9452205 -0.08878725 -0.01213675 0.9959768 0.1524037 -0.02700197 0.9879495 0.09324145 -2.86762e-4 0.9956435 -0.2065902 0.004196286 0.9784186 0.9907107 -0.05174386 -0.1257572 0.9303126 -0.06408971 -0.3611245 0.9146015 -0.04249763 -0.4021169 0.8780166 0.06521594 -0.4741666 0.7808329 -0.05765128 -0.6220744 0.8051999 -0.001168489 -0.5930026 0.9112348 3.47731e-4 -0.411887 0.9745949 -0.005399525 -0.22391 0.2932663 1.66338e-4 0.9560308 0.2411759 0.06617701 0.9682226 0.3842025 -0.002864539 0.9232444 0.3198267 -0.01433205 0.9473677 0.3440208 -0.006657063 0.9389384 0.2947113 0.001057863 0.9555858 0.5083274 -0.0106067 0.8610987 0.7425433 -0.003344118 0.6697897 0.6859569 0.007520735 0.7276033 0.8153467 -0.005712747 0.5789449 0.9334599 0.003730595 0.3586623 0.9283827 0.002522051 0.3716171 0.9580123 -0.004818141 0.2866867 0.9991053 -7.14996e-4 0.04228842 0.9972679 -0.005062699 0.0736978 0.9999916 0.001649141 0.003753006 0.9997078 -0.001014709 0.02415174 0.9940682 0.002304077 -0.1087348 0.9983602 0.03724628 -0.04346984 0.9882625 -0.06357818 -0.1389073 0.9998127 0.00436598 -0.0188567 0.9800821 -0.001859188 -0.1985843 -0.08720976 -0.370853 -0.9245879 -0.9969761 0.001647055 0.07769125 -0.9919399 -0.005397617 -0.1265954 -0.6659392 0.6832702 -0.2994444 -0.6342106 0.7278279 -0.2608515 -0.04312866 0.6387298 -0.7682216 -0.3889607 0.0159161 -0.9211168 -0.9952326 0.01458722 -0.09643393 -0.4295807 0.81976 -0.3787531 -0.4270352 0.003392994 -0.9042288 -0.3316103 -7.02598e-4 -0.9434162 0.003201782 -0.736297 -0.676651 -0.4216949 -0.5003245 -0.7562069 -0.4289067 -0.7480063 -0.5064836 -0.4272802 -0.7509971 -0.5034233 -0.5723154 -0.6643506 -0.4807219 -0.8256543 -0.4443442 -0.3476395 -0.7788236 -0.6139107 -0.1286371 0.6862199 0.6390644 0.3474175 0.9920798 0.01598232 0.124589 0.3437538 0.9346258 -0.09114784 0.9963532 0.003268301 0.08526307 0.9979605 0.004120588 0.06370228 0.8352894 -0.5476047 0.04920184 0.8548188 -0.5188061 0.0111925 0.5838033 -0.6905511 -0.4269813 0.5473722 -0.7334553 -0.4030225 0.7113268 -0.6890497 -0.1386533 0.6284092 -0.7558662 -0.1837615 0.7226561 -0.5851004 -0.3680024 0.4642667 -0.7743003 -0.4300181 0.4414854 -0.009754776 -0.8972154 0.557663 -0.00426799 -0.8300566 0.5064258 0.7557141 -0.4152459 0.5892024 0.6829637 -0.4317421 -0.5540487 0.8059809 0.2083864 -0.6059493 0.01470512 0.7953674 0.9299343 0.04500502 0.3649614 0.4963375 0.2474361 0.8321206 -0.2166601 0.9068641 0.3614637 -0.710717 2.7026e-4 0.7034781 -0.5342368 0.009571194 0.8452808 -0.4569048 -0.5383047 0.7081427 -0.4493187 -0.5271587 0.7212604 0.07769548 -0.6873589 0.7221504 0.07149857 -0.7769221 0.6255238 -0.2269652 -0.818352 0.5280029 -0.304382 -0.2700406 0.9134712 -0.07513773 -0.8307244 0.5515898 -0.0620144 -0.5643185 0.8232247 0.2036858 -0.6790316 0.7052859 0.2334077 -0.684265 0.6908708 0.3701288 1.10772e-5 0.9289805 0.2320899 -0.005609989 0.9726783 -0.2543625 0.608657 0.751556 0.1965595 0.6201724 0.759441 0.004203319 0.9977658 -0.06667703 0.4668601 0.8824737 0.05728918 -0.02555847 0.9996551 -0.006041824 -0.01314204 0.9985257 -0.05266708 -0.004514396 0.9979563 0.06373941 0.02410608 0.9990013 0.03761941 -0.01328366 0.9986795 -0.04962849 0.02857118 0.9987804 0.04026842 0.02013301 0.9981772 0.05689471 -0.02664208 0.9994193 -0.02124655 0.01447153 0.998635 0.0501877 0.005660712 0.9989375 -0.04573726 -0.00472933 0.9998506 -0.01662802 0.006870806 0.9988433 -0.04759126 -0.02873837 0.9991213 -0.03050923 -0.01875001 0.9998035 -0.006437003 0.009447395 0.986338 -0.1644635 0.01169985 0.9991883 -0.03854835 -0.02811157 0.9992468 -0.02675342 0.02792036 0.9984146 -0.04887491 -0.04953557 0.9985761 -0.01980531 -0.05138373 0.9984482 -0.02147358 0.01828712 0.9996792 -0.0175268 0.02217847 0.9997525 -0.001798331 0.04027158 0.9989275 -0.02284717 0.0282759 0.9890229 -0.1450325 0.01858448 0.9996241 -0.02015733 -0.06378519 0.9979599 0.002776205 -0.04302698 0.9990383 0.008448362 0.03266566 0.9993818 -0.0130043 0.05227965 0.9981796 -0.03007286 -0.04074752 0.9989752 0.01970964 -0.04517316 0.9986818 0.02437496 0.04449391 0.9989745 -0.008385241 0.06447416 0.9979192 7.6396e-4 -0.03114563 0.9990504 0.03046792 0.05249881 0.9984889 0.01624834 0.04199981 0.9989725 0.01702845 -0.03354865 0.9987301 0.03758746 -0.02375245 0.9984673 0.04999125 -0.03614211 0.9974657 0.06128513 0.03961944 0.9988064 0.0285694 0.03515714 0.9990028 0.02752339 0.5977923 0.001234829 -0.8016501 0.5241612 -0.01399832 -0.851504 6.82319e-5 -1 1.91376e-4 -0.3788926 -0.001890718 -0.9254388 -0.3479632 0.004497468 -0.9374975 -0.2765833 0.4613707 -0.8429941 0.1637578 0.456165 -0.8746982 0.3929936 0.503073 -0.7697231 0.9030561 -0.03378492 0.428192 0.8769831 -0.04294854 0.4785982 0.8766193 -0.0516802 0.4784014 0.8451191 -0.03259104 0.5335839 0.872144 -0.05654036 0.4859713 0.8387576 -0.08066117 0.5384976 0.9103466 -0.0848757 0.40505 0.8713467 -0.07081651 0.4855309 -0.1173141 -0.08187311 -0.9897142 -0.02433997 -0.06727784 -0.9974374 -0.02435642 -0.05697816 -0.9980783 0.07070153 -0.08230465 -0.9940962 -0.01978176 -0.05444991 -0.9983206 0.03406304 -0.04020476 -0.9986107 -0.02987551 -0.04717516 -0.9984398 -0.0198071 -0.04882699 -0.9986109 -0.7935843 -0.08818066 0.6020368 -0.8562723 -0.07096219 0.5116271 -0.8941116 -0.083723 0.439949 -0.8526452 -0.05171793 0.5199244 -0.8570602 -0.05668681 0.5120883 -0.8852431 -0.03551423 0.4637709 -0.8101357 -0.0336796 0.5852742 -0.8528805 -0.04617321 0.5200606 -0.7313237 -0.02320921 0.6816355 -0.6114204 -0.001945674 0.7913036 -0.1650995 0.9860924 0.0190804 -0.4036548 0.7270562 0.5553848 0.1597943 0.964644 -0.2095898 0.5643765 0.040892 -0.8245042 0.5670244 0.04022336 -0.8227184 0.5482624 0.08022964 -0.8324491 0.6155486 0.08406519 -0.7836027 0.07335567 0.7652763 -0.6395086 -0.278946 0.9535611 -0.1136241 -0.3363524 0.9195793 -0.2030788 -0.2032907 0.9515168 -0.2308433 -0.2362338 0.9412481 -0.2413412 -0.6565953 0.6488843 0.3844889 -0.6187362 0.03983527 0.7845883 -0.6265224 0.07742363 0.7755484 -0.6062881 0.03648698 0.7944077 -0.5556777 0.08168685 0.8273752 -0.1259491 0.9919553 -0.01271426 -0.2477917 0.9099547 0.3325382 0.4529293 0.6789627 -0.5778103 0.5494509 9.27788e-4 -0.8355256 0.4382727 -0.01659685 -0.8986888 0.5600476 -0.4273558 -0.709728 0.530535 -0.5254632 -0.6651474 -0.1125615 -0.9887506 -0.09850025 -0.07872003 -0.994686 -0.06635463 -0.07449644 -0.9956344 -0.05623441 -0.08391761 -0.996302 -0.01845222 -0.09490114 -0.9946481 -0.04085421 -0.0571509 -0.998205 -0.0179044 -0.418157 -0.5453757 0.7264366 0.1517477 0.9546399 0.256194 0.1476365 0.9778714 0.1482267 0.4214787 0.0383867 0.9060255 0.4292199 0.04019069 0.9023054 0.4465194 0.0802834 0.8911651 0.3710135 0.08409655 0.9248117 0.5173557 0.7642107 0.3851302 0.2768342 0.9371356 -0.2124615 0.2391968 0.9483249 -0.2084819 0.3364046 0.928406 -0.15778 0.3012085 0.9516334 -0.06055909 0.3086811 0.9487795 -0.06733036 -0.3880196 0.03574371 -0.9209579 -0.3585904 0.0775457 -0.9302686 -0.3394502 0.04667586 -0.9394654 -0.3801072 0.07873266 -0.9215855 -0.1657357 0.8667696 -0.470364 -0.1958734 0.7634107 -0.615498 0.3071098 0.8185871 0.485385 0.1901194 0.9173619 0.3497168 0.3867749 0.003554165 0.9221674 0.5567469 -0.02206116 0.8303893 0.2850485 -0.4937022 0.8215872 0.1255131 -0.9897468 -0.06817507 0.02695006 -0.9996051 -0.007966637 0.06052327 -0.9971596 -0.04483067 0.06377291 -0.9979439 -0.006419658 0.08785396 -0.9961203 -0.005103707 0.07823437 -0.9969336 -0.001677989 -0.3410902 -0.6822854 -0.6466407 -0.2762774 -0.01708167 -0.9609262 -0.3403252 -0.007728576 -0.940276 -0.3388405 0.663254 -0.667294 -0.316195 0.9479152 -0.03843933 -0.05331456 0.9966776 0.06157463 -0.9958716 0.03974902 -0.08160817 -0.9954288 0.03853142 -0.08738929 -0.9939782 0.04713463 -0.0989241 -0.9850274 0.1224021 -0.1214035 -0.9998378 0.01745408 0.004474222 -0.6981676 0.6797624 0.2246887 0.05865752 0.932111 0.3573912 0.02014958 0.952673 0.3033286 -0.08007645 0.9215461 0.3799216 -0.1152961 0.9542086 0.2760304 0.9931707 0.04074692 0.1093236 0.9838573 0.08067846 0.1597372 0.9843822 0.05229413 0.168099 0.9885587 0.08269912 0.1261448 -0.01566749 0.9998666 0.004623949 0.02653592 0.9993316 0.02514505 -0.9908584 0.005115866 -0.1348093 -0.9975697 -0.02178716 0.0661835 -0.8541089 -0.4879738 -0.1799432 -0.03769141 -0.9968934 0.06915974 -0.0393238 -0.9961388 0.07849395 -0.00636512 -0.9991539 0.04063367 0.004557013 -0.9977275 0.06722372 0.003409147 -0.9931639 0.1166788 0.6969869 -0.7153679 -0.0495789 0.4094855 0.01208573 -0.9122366 0.9617356 -0.02349478 0.2729701 0.9913538 -0.003747224 0.1311631 0.5082202 0.861225 0.001994729 0.6844214 0.7169851 0.1322873 -0.6505457 0.7594533 -0.004606008 0.1281428 0.01209044 -0.9916821 0.8099617 0.01674222 -0.5862439 0.7556192 0.0024544 -0.6550067 0.6306712 0.09679538 0.7699899 0.5258761 -0.0135554 0.8504532 0.9284663 0.01944047 -0.3709074 0.8946271 -8.21752e-4 -0.4468129 0.945222 0.01278799 -0.3261777 0.9970121 0.009086906 -0.0767098 0.9954747 0.004407107 -0.09492516 0.9985871 0.0107426 -0.05204278 0.9755418 0.01001793 0.2195857 0.9963388 0.01826018 0.08352053 0.4870817 0.004716753 0.8733438 0.3539152 0.01483505 0.9351599 0.2083536 -0.001667797 0.9780521 0.9575482 -0.01460617 0.2879032 0.9227645 0.08529007 0.3758075 0.1154971 0.009662628 0.9932609 0.7850225 -0.07133203 0.6153465 0.6161506 0.01921039 0.7873941 0.5958651 0.01323741 0.8029755 0.7441518 0.009855329 0.6679379 0.6909474 0.007233917 0.7228689 0.7817105 -0.002727091 0.6236356 0.8821636 0.01217567 0.4707857 0.9224667 0.01220303 0.3858839 0.9077746 0.005830228 0.4194181 0.9619542 0.01197326 0.2729484 0.9535571 0.009428799 0.3010648 -0.998423 0.006674945 0.05574065 -0.993414 -0.003806948 0.1145176 -0.9515815 0.01058572 0.3072146 -0.9714999 -0.002112388 0.2370308 -0.930954 -0.002729952 0.3651263 -0.8487692 0.01218879 0.528623 -0.7962477 0.01202207 0.6048513 -0.8171787 0.005865156 0.5763547 -0.7724368 0.004552006 0.6350755 -0.7374947 0.009186387 0.6752905 -0.9830334 0.08158791 0.1642827 -0.9969583 -0.01355111 0.07674992 -0.3748531 0.00487852 0.9270715 -0.4160314 0.01337254 0.9092519 -0.999862 0.0147444 0.007662951 -0.5147527 0.01920366 0.8571237 -0.6575688 0.001034915 0.7533938 -0.9999063 0.01325815 -0.003427386 -0.9690905 0.005295813 -0.246649 -0.9588379 -6.8909e-4 -0.2839537 -0.9181427 0.01299524 -0.3960368 -0.6871485 -0.02096736 0.7262144 -0.7840692 0.1008713 0.6124219 -0.8458573 0.01068174 -0.5333024 -0.8249617 0.005775392 -0.5651592 -0.7263978 0.01745891 -0.6870527 -0.6552563 0.0113992 -0.7553207 -0.8976685 -0.06948262 0.4351593 -0.6423047 0.01309382 -0.7663376 -0.809949 0.5533419 -0.1944108 -0.5030356 0.767034 -0.3982638 -0.597 0.7373191 -0.3161512 -0.7597515 0.5656278 -0.3206914 -0.7070722 0.6605278 -0.2524914 -0.7244549 0.6768539 -0.1305143 -0.6564481 0.6363109 -0.4051968 0.4782602 0.01211303 -0.8781347 0.3974381 -0.003863155 -0.9176209 0.2098193 0.01053303 -0.9776834 0.2803865 -0.002136707 -0.9598848 0.1492249 -0.002811372 -0.9887993 0.02533012 0.007337808 -0.9996523 -0.1720242 0.01176124 -0.9850226 -0.09060221 -0.004094123 -0.9958788 -0.222247 0.01275509 -0.974907 -0.2161232 0.01198619 -0.9762926 -0.3097581 0.008283734 -0.9507793 -0.2585749 0.002373397 -0.9659883 -0.1369407 0.1038568 -0.9851199 0.05637824 -0.05267995 -0.9970187 0.388084 0.07133358 -0.9188594 0.3551732 0.006229877 -0.9347798 -0.9941115 0.003726124 -0.1082978 -0.9975716 -2.99637e-5 -0.06964987 -0.2841033 0.00713706 -0.9587672 -0.241844 0.002157926 -0.9703128 0.6898604 0.6222557 -0.3699871 0.7558511 0.6153632 -0.2236454 0.4826008 0.8483014 -0.2179021 0.6229649 0.6505849 -0.4343433 0.7115531 0.6837405 -0.1618367 0.2629711 0.7493089 0.6077685 0.1338212 0.7392613 0.6599884 0.2002429 0.7530659 0.6267334 0.2682187 0.6241142 0.7338531 0.5615653 0.4151002 0.7157767 0.4897999 0.7978608 0.351446 0.4591824 0.6755436 0.5768817 0.5194344 0.6622787 0.5399769 0.5192223 0.65219 0.5523192 0.5858011 0.7331042 0.3455364 0.6796544 0.6621036 0.3157355 0.6826345 0.668931 0.2941795 -0.7681248 0.6344071 0.08667236 -0.5799125 0.813678 0.04036784 -0.7427684 0.6412679 0.1925373 -0.5782179 0.7699507 0.2698889 -0.476513 0.7060106 0.5239127 -0.8698248 0.4088178 0.2761755 -0.7291684 0.6755329 0.1094025 -0.7274034 0.6622374 0.1797944 -0.7378882 0.6522409 0.1735018 -0.5921581 0.7330847 0.3345677 -0.6132842 0.6621233 0.4306684 -0.596062 0.6689615 0.4440729 0.4373717 4.83125e-4 -0.8992807 0.489112 0.007204234 -0.8721913 0.9873624 -6.03505e-4 0.1584779 0.9941822 0.004527688 0.107616 -0.4794841 0.5119902 0.7127139 -0.4479055 0.7216588 0.5278155 -5.21248e-4 0.7998653 -0.6001794 0.01386404 0.8811225 -0.4726848 0.231992 0.759772 -0.6073931 0.1970764 0.39319 -0.8980883 0.3943274 0.6783389 -0.6199696 0.1430501 0.6769299 -0.7220129 0.2631707 0.6092854 -0.7480057 0.03243523 0.6777806 -0.7345485 -0.0536428 0.6631005 -0.7466059 -0.03020995 0.6356893 -0.7713538 -0.09345883 0.6765945 -0.7304009 0.9964462 0.003992438 0.08413708 0.9955117 0.005249381 0.09449368 -0.9944846 0.007903814 -0.1045855 -0.9913575 0.004643142 -0.1311058 0.05159682 0.996643 0.06356602 -0.03983587 0.9970424 0.06572431 0.03321051 0.9968671 0.07178497 -0.02406269 0.9987073 0.04477429 0.08018761 0.9964889 0.02408248 0.07576125 0.9958252 0.0509181 0.08975714 0.9945884 0.05232363 0.03099876 0.9986595 -0.04145455 0.04551327 0.995907 -0.07809042 0.08445447 0.995557 -0.04164075 0.06198364 0.9980758 -0.001672625 0.07602035 0.9970782 0.00749582 0.07864749 0.9968845 -0.005995213 0.09165364 0.9945555 0.04958915 0.08589333 0.9963026 0.001937448 0.102078 0.9946837 -0.01358634 0.08600723 0.9962895 0.003173828 0.09341579 0.9944969 0.04742842 0.097727 0.9944357 0.03933626 0.04072368 0.9967991 0.06879723 0.04155057 0.9952398 0.0881555 0.09062278 0.994906 0.04415398 0.1038671 -0.4837365 -0.8690286 -0.5604114 0.2091716 -0.8013653 -0.1235316 0.9722846 -0.198501 -0.1992509 0.8524525 -0.4833465 -0.4597141 0.7627615 -0.4548166 -0.5999662 0.7163596 -0.3561875 0.6013837 0.6910927 -0.4009098 0.6145441 0.7721424 -0.1616534 -0.330293 0.8648942 -0.3779744 -0.651076 0.5607847 -0.5114886 -0.6994986 0.6109092 -0.3707988 -0.6257159 0.7616543 -0.1684119 0.7274396 0.6854754 -0.03090649 0.6550552 0.7555111 -0.01028507 0.299457 0.870417 -0.3907681 0.9082782 -0.2147447 -0.3590483 0.2262389 0.9470423 -0.227875 0.1823549 0.9251499 -0.3329331 -0.6895279 0.03758066 -0.7232835 -0.4816589 0.03383898 -0.8757053 -0.5094977 0.04117423 -0.8594864 -0.486878 0.03411507 0.8728036 -0.600086 0.01792764 0.7997346 -0.2228711 0.007930636 -0.9748156 -0.3152929 0.04130941 -0.948095 -0.6980382 0.03987586 0.7149494 -0.7657446 0.01674228 0.6429268 0.07983165 0.01057553 -0.9967523 -0.0228073 0.03711289 -0.9990509 -0.8781775 0.03606814 0.476973 -0.8651795 0.03953307 0.4999015 0.1735422 0.0315634 -0.9843206 -0.8973365 0.04252827 0.4392933 0.2101767 0.01888561 -0.9774811 -0.9484987 0.01811236 0.3162628 0.3879628 0.02029699 -0.9214515 0.4670067 0.04164445 -0.8832727 -0.9890903 0.03703719 0.1425784 -0.9950262 0.02710032 0.09585618 -0.9990411 0.04211503 0.01196998 -0.9913894 0.03126966 -0.1271583 -0.9961802 0.04180687 -0.07666265 -0.9330797 0.02297782 -0.3589351 -0.9690965 0.04285055 -0.2429319 -0.8505948 0.01794528 -0.5255154 -0.8875411 0.04301333 -0.4587163 -0.7336484 0.01552039 -0.6793521 -0.5757484 0.02850383 -0.8171299 0.1459006 0.7613754 0.6316807 0.1515695 0.5553357 0.8176975 -0.04865521 0.6512657 0.7572883 -0.3625459 0.7524237 0.5499265 -0.5743075 0.5563829 0.6005073 -0.3798813 0.8732577 0.3051413 0.3688187 0.63058 0.6828922 -0.3352184 0.9416092 0.03163719 -0.6698237 0.7294045 0.138944 -0.600911 0.7653795 0.2304348 0.6728197 -0.6831291 0.2839865 0.7201974 -0.6538653 0.2318965 0.6766158 -0.6677784 0.3102627 0.5382776 -0.710193 0.4537437 0.5638972 -0.6924902 0.4499747 0.507517 -0.6817206 0.526957 0.4855172 -0.6551405 0.5788472 0.4293961 -0.6877986 0.5852795 0.7951675 -0.3587849 0.488858 -0.5822645 -0.683209 0.440674 -0.5608453 -0.6539674 0.5077196 -0.607055 -0.6677799 0.4307601 -0.6621208 -0.7101646 0.2392955 -0.6716502 -0.6924886 0.2633355 -0.7100698 -0.6817424 0.1761483 -0.7440772 -0.6551018 0.1311132 -0.7215427 -0.6878244 0.07920724 -0.0861966 0.996146 0.01623129 -0.086833 0.9959926 0.02142339 -0.01315301 0.9943934 0.104923 -0.08692502 0.9959867 0.02132755 -0.0364238 0.9990759 -0.02282196 -0.03974777 0.9964208 -0.07460373 -0.08879566 0.9948968 0.04791462 -0.1010609 0.9925417 0.06817525 -0.04935204 0.9964129 -0.06874376 -0.03975927 0.9986983 -0.03195548 -0.09198606 0.9920815 0.08551502 -0.09131658 0.9923595 0.08297055 -0.1087363 0.9940405 0.007741689 -0.04830551 0.998668 -0.01813656 -0.05388712 0.9980322 -0.03206384 -0.03208863 0.9979617 0.05516254 -0.05712479 0.9981046 -0.02289402 -0.06149828 0.9974989 -0.03483986 -0.02598476 0.9953215 0.09305942 -0.02890539 0.9955231 0.08999025 -0.01741755 0.9956598 0.09142392 -0.07960098 0.9968131 -0.005239844 -0.1000104 0.9944605 -0.03234839 -0.106355 0.9943275 0.001210629 -0.8212126 -0.3580594 0.4443011 -0.1155279 -0.6605541 -0.7418367 -0.1517766 -0.6910705 -0.7066721 -0.07022666 -0.6593741 -0.7485279 -0.002418577 -0.6920889 -0.7218084 0.2159387 -0.6500002 -0.7286085 0.1161572 -0.6286661 -0.7689517 0.2099944 -0.6523275 -0.7282659 0.3408467 -0.6812657 -0.6478431 0.3038626 -0.6545194 -0.6922946 0.03134787 0.9951936 -0.09277451 0.06820929 0.9969228 -0.0386334 -0.02978271 0.9936737 -0.1082848 -0.01136273 0.9943168 -0.1058548 -0.1347037 0.9884235 -0.06981366 -0.007330894 0.9928908 -0.1188035 -0.02546095 0.9941113 -0.1053308 0.01593738 0.99775 -0.06512296 0.04836022 0.9954521 -0.08207708 0.02789634 0.9973729 -0.06685179 -0.06695777 -0.2142648 -0.974478 0.3799898 0.1844187 -0.9064202 -0.2475997 0.1076951 -0.9628583 -0.3324859 0.002334713 0.9431053 -0.1496068 0.005483329 0.9887304 -0.08000957 -6.85068e-4 0.9967939 -0.09806346 0.004376769 0.9951706 -0.03530353 0.00319314 0.9993716 0.6933965 0.002775132 -0.720551 0.6550145 -0.001030564 -0.7556157 0.657078 0.4732229 0.5867783 -0.1888961 0.9274495 0.3227319 -0.03211635 0.8724196 0.4877014 0.1499688 0.8800666 0.4505466 0.3484563 -0.7619547 0.5458968 -0.01463645 0.742824 0.6693269 0.9174102 0.276102 0.2865768 0.450822 0.8549527 0.2565451 0.7663919 0.6298054 0.1264458 -0.05403089 0.9449735 0.3226544 -0.1795952 0.7910999 0.5847278 -0.008181929 0.9083535 0.4181233 0.7733348 0.1769429 0.6088059 0.8661067 0.4121399 0.2828426 0.5356904 -0.09023821 0.839579 0.6664274 -0.2336116 0.7080255 -0.9138379 0.1772683 0.365344 -0.6777613 0.412894 0.6084063 -0.9948886 -0.09075129 0.04428249 -0.9463652 -0.2338542 0.2229462 -0.5003717 -9.7652e-4 -0.8658102 -0.4725485 0.001140892 -0.881304 0.1108719 0.2373018 -0.9650883 0.5811874 0.03049594 0.8131981 0.4800367 0.02664864 0.8768436 0.519748 0.04219007 0.8532773 0.5694789 0.01707237 -0.8218287 0.6880907 0.01507431 -0.7254681 0.6794113 0.01924395 -0.7335053 0.2462981 0.01158368 0.9691249 0.1790667 0.03477191 0.9832223 0.7958315 0.04232597 -0.6040371 0.8392803 0.008933603 -0.5436256 0.08021682 0.01904916 0.9965955 0.9227272 0.01849871 -0.3850096 -0.1164648 0.02867358 0.9927809 -0.114582 0.02932083 0.992981 -0.2205036 0.01928943 0.9751955 0.9466046 0.02680826 -0.3212801 0.9707078 0.01308876 -0.2399066 -0.3223937 0.0419867 0.9456741 0.9869359 0.03210765 -0.1578825 -0.4423739 0.02430576 0.8965013 0.9991346 0.009891629 -0.0404002 0.9917734 0.03064423 0.124284 0.9958473 0.03643673 0.08343112 0.9490661 0.04474294 0.3118844 0.9697887 0.01729756 0.2433323 0.888437 0.04507011 0.4567805 0.8603399 0.02241432 0.5092278 0.7766168 0.0211026 0.6296199 0.6927036 0.05225062 0.7193272 0.7766707 0.62199 -0.09955495 0.6154991 0.7878049 0.02289736 -0.1366484 0.7916359 -0.5955166 -0.02254635 0.4228819 -0.9059043 0.07982993 0.8412427 -0.5347317 0.1624703 0.64589 -0.7459421 0.3807651 0.5849134 -0.7161664 0.3305057 0.7573491 -0.5631948 0.5438051 0.6022207 -0.5844709 0.4860285 0.7337862 -0.4746938 0.3540694 0.9199401 -0.16836 0.7011371 0.5309905 -0.4758738 0.02272909 0.9997117 0.00775516 0.006339788 0.9999514 0.007569968 0 1 0 0.00532335 0.9998171 0.01837521 0.001923382 0.9995011 0.03152734 -0.02436476 0.9996749 0.007521867 -0.01500582 0.9995702 -0.02518522 -0.02914577 0.9995743 -0.001322746 -3.30017e-4 0.9999306 -0.0117768 2.54572e-4 0.999938 -0.01113641 -0.02342486 0.9997118 -0.00526154 0.02878987 0.9994981 0.0132209 0.001465678 0.9999767 0.006668984 0 1 -9.66868e-6 -0.0289936 0.9995773 0.002182424 -0.03412902 0.9992852 -0.01626121 -0.03439074 0.9993195 -0.0133354 0 1 -8.40478e-6 0.005232751 0.9999535 -0.008113324 0.02842688 0.9995031 0.01362174 0.001560449 0.9999487 -0.01001417 0.007842659 0.9999689 -8.5117e-4 0.02826327 0.9995349 0.01145744 0.02036023 0.9997634 -0.00765109 0.009064674 0.9999197 -0.008857369 0.02235591 0.9996855 0.01136481 0.007771909 0.9996026 0.02709877 -0.005646407 0.9999803 0.002782464 0.009326159 0.9978638 0.06466072 -0.02421832 0.9990792 0.03541624 0.03367841 0.9993354 -0.01395487 0.02708315 0.9995781 0.0105015 -0.02938038 0.9994623 -0.01456767 -0.02723914 0.9994194 -0.02046632 0.02522432 0.9989944 -0.03706872 -0.0044564 0.9993186 0.03664004 -0.02995163 0.9994382 0.01504009 0 1 3.71936e-6 -0.006764948 0.9996493 -0.02560442 0.001013576 0.9998955 0.01442593 0 1 1.2341e-5 -0.005916416 0.9996578 -0.02548348 -0.02030891 0.9997469 -0.009691178 0 1 -3.08208e-5 0 1 1.45069e-5 0 1 1.16205e-5 0 1 -3.58849e-6 -0.8507769 0.01304513 0.5253652 -0.993884 -0.02009946 0.1085854 -0.8939802 0.02390086 -0.4474686 0.004352927 -0.01169192 -0.9999222 -0.02809852 -0.007525205 -0.9995769 -0.07854652 0.6296858 0.772869 -0.3253499 0.7502521 0.57556 -0.5919604 0.6566787 0.4672857 -0.5982627 0.7367315 -0.3151326 -0.6629311 0.5991181 -0.4489766 -0.01872932 0.3653517 -0.9306811 -0.1466574 0.9599276 -0.2388109 -0.04003536 0.9946523 0.09520506 -0.8869344 -0.02190506 0.4613758 -0.9359718 -0.006705999 0.3520114 -0.9868331 0.01930147 -0.1605864 -0.7197651 0.01996678 -0.6939306 -0.2781922 -0.02195358 -0.9602745 0.2001119 0.01321238 -0.979684 -0.3225817 0.7048658 -0.6317477 0.1247782 0.5648907 -0.8156771 -0.3496467 0.9077466 -0.2318265 0.5593985 0.7644444 -0.3204658 0.555212 0.7732806 -0.3062304 0.1226063 0.9885023 -0.08849245 -0.4594261 0.01790994 0.8880355 -0.9770135 -0.007316529 0.213052 -0.9555231 -0.01639544 0.2944599 -0.9591983 0.01997846 -0.2820277 -0.4133439 0.003067076 -0.9105699 -0.214897 -0.01767545 -0.9764767 0.5388979 0.775438 0.3290671 0.3129771 0.82006 0.4791107 0.3373935 0.8098198 0.4799557 -0.6699327 0.664461 0.3311826 -0.4061973 0.6285719 0.6632505 0.4219337 0.01274812 0.9065372 0.4173219 0.002366125 0.9087557 0.7700803 -0.01925188 0.6376565 0.7903842 0.02299976 0.6121797 0.9893299 -0.01740217 0.1446497 0.9903829 -0.003208756 0.1383162 -0.1303253 0.004331469 0.9914619 -0.1207274 -0.007663786 0.9926562 -0.9961506 -0.004770219 -0.08752804 -0.9963702 5.17424e-4 -0.08512508 -0.9043787 -0.03345656 0.4254174 -0.9251443 0.03354632 0.3781307 -0.6095097 0.004759907 0.7927644 -0.6149269 -0.01028251 0.7885172 -0.8125131 0.0140199 -0.5827745 -0.7993068 -0.01348483 -0.6007719 0.5765886 0.006382703 -0.8170097 0.5752304 0.002715885 -0.8179869 0.1400242 -0.03629422 -0.9894827 0.0874921 0.033899 -0.9955883 -0.37546 0.003122985 -0.9268334 -0.3785637 -0.004280984 -0.9255654 0.9200072 0.007430672 -0.3918311 0.9238427 -0.004766762 -0.3827427 0.8507807 0.01302212 -0.5253598 0.9938855 -0.02010506 -0.10857 0.8663913 0.02818888 0.4985695 -0.01251631 -0.0126788 0.9998414 0.02801626 -0.007477104 0.9995796 0.9096307 -0.01539564 -0.4151325 0.9360051 -0.006687998 -0.3519229 0.7986688 0.02938926 0.6010528 0.2782183 -0.02196764 0.9602667 -0.2001218 0.01319265 0.9796823 0.4594886 0.01787471 -0.8880038 0.9769675 -0.007311284 -0.2132629 0.9554694 -0.01638692 -0.2946348 0.9592406 0.0199806 0.2818834 0.4132636 0.003065228 0.9106063 0.2151701 -0.01764625 0.9764171 -0.08230882 0.9954488 0.0480324 -0.01862442 0.9911237 0.1316319 -0.09057134 0.9741098 0.2071402 0.07415407 0.9971547 0.0135594 0.01114445 0.9783914 -0.2064612 0.1825929 0.9750395 0.1263247 -0.05190664 0.996846 -0.06003242 0 1 -7.25239e-6 0 1 4.20326e-6 0.03316068 0.9984375 -0.0449782 -0.7597067 0.6480303 -0.05387705 -0.03891235 0.9987231 -0.03222143 0.06414908 0.9951764 0.07422256 0.356648 0.7450901 -0.5635983 0.5649787 0.6879319 -0.4555754 0.5904526 0.7834062 0.1940116 0.6368705 0.667474 0.3858426 0.6617964 0.4900316 0.5673576 0.1260028 0.7433714 0.6569036 -0.1016285 0.6651368 0.7397735 -0.5661663 0.7200482 0.4012312 -0.5818601 0.8119392 0.04683524 -0.3134354 0.8192282 -0.4802327 -0.5425045 0.7727118 -0.3295537 -0.3382732 0.8087162 -0.4811958 0.6649056 0.6693284 -0.331512 0.4050844 0.6325178 -0.6601727 0.9162064 0.3922542 0.08186918 0.02738261 0.7761643 -0.629936 -0.1311886 0.7406295 0.6589822 -0.7080003 0.7023148 -0.07409197 0.2728385 -0.04045796 -0.9612088 0.1328517 -0.5299428 0.8375626 0.5274063 0.5383874 -0.6572531 0.2223832 -0.3764066 -0.8993685 0.9617798 0.2258302 0.154856 0.1948286 -0.7493439 0.632871 0.04691725 0.04055947 0.998075 -0.08860629 0.9784842 0.1863266 -0.2564693 0.9658013 0.03809732 -0.06682664 0.973864 -0.217079 -0.221207 0.9354607 0.2756463 -0.5229378 0.8521725 -0.01839023
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 17 0 16 0 0 0 12 1 7 1 5 1 4 2 7 2 12 2 4 3 6 3 7 3 9 4 8 4 6 4 13 5 2 5 8 5 9 6 13 6 8 6 7 7 6 7 8 7 14 8 13 8 9 8 13 9 11 9 2 9 18 10 13 10 14 10 19 11 11 11 13 11 18 12 19 12 13 12 3 13 1 13 10 13 8 14 5 14 7 14 21 15 10 15 279 15 20 16 11 16 19 16 12 17 3 17 10 17 22 18 20 18 19 18 21 19 12 19 10 19 23 20 12 20 21 20 24 21 20 21 22 21 12 22 5 22 3 22 24 23 25 23 20 23 4 24 12 24 23 24 15 25 20 25 25 25 0 26 25 26 24 26 8 27 2 27 5 27 16 28 15 28 25 28 16 29 25 29 0 29 124 30 27 30 306 30 164 31 26 31 131 31 27 32 124 32 31 32 30 33 41 33 125 33 165 34 39 34 164 34 119 35 35 35 32 35 119 36 125 36 35 36 28 37 36 37 33 37 292 38 36 38 28 38 164 39 39 39 26 39 160 40 37 40 34 40 27 41 31 41 40 41 34 42 43 42 30 42 125 43 41 43 35 43 19 44 29 44 22 44 34 45 37 45 43 45 165 46 161 46 42 46 165 47 42 47 39 47 36 48 292 48 37 48 40 49 44 49 163 49 162 50 44 50 161 50 30 51 43 51 41 51 31 52 44 52 40 52 163 53 44 53 162 53 32 54 35 54 276 54 26 55 48 55 29 55 26 56 39 56 45 56 46 57 44 57 31 57 161 58 44 58 42 58 26 59 45 59 48 59 29 60 48 60 22 60 41 61 50 61 35 61 39 62 42 62 45 62 35 63 49 63 276 63 37 64 47 64 43 64 37 65 292 65 47 65 43 66 51 66 41 66 22 67 48 67 24 67 44 68 52 68 42 68 276 69 49 69 53 69 35 70 50 70 49 70 291 71 47 71 292 71 46 72 54 72 44 72 43 73 47 73 55 73 43 74 55 74 51 74 158 75 41 75 51 75 50 76 41 76 158 76 44 77 54 77 52 77 152 78 45 78 56 78 48 79 57 79 24 79 52 80 56 80 42 80 42 81 56 81 45 81 48 82 152 82 57 82 45 83 152 83 48 83 291 84 59 84 47 84 78 85 59 85 291 85 158 86 58 86 50 86 50 87 58 87 49 87 47 88 59 88 55 88 158 89 159 89 58 89 159 90 60 90 58 90 134 91 52 91 54 91 57 92 65 92 24 92 52 93 61 93 56 93 152 94 62 94 57 94 61 95 52 95 134 95 58 96 66 96 49 96 57 97 62 97 65 97 49 98 66 98 53 98 53 99 66 99 278 99 64 100 76 100 62 100 63 101 70 101 60 101 60 102 70 102 58 102 58 103 70 103 71 103 58 104 71 104 66 104 72 105 74 105 69 105 59 106 74 106 72 106 157 107 70 107 63 107 157 108 69 108 70 108 24 109 65 109 0 109 62 110 76 110 65 110 134 111 79 111 61 111 61 112 79 112 77 112 64 113 67 113 76 113 78 114 74 114 59 114 75 115 77 115 80 115 75 116 80 116 68 116 68 117 80 117 67 117 81 118 74 118 78 118 77 119 79 119 80 119 67 120 80 120 76 120 81 121 83 121 74 121 69 122 82 122 70 122 65 123 17 123 0 123 94 124 79 124 134 124 69 125 83 125 82 125 66 126 84 126 278 126 74 127 83 127 69 127 70 128 82 128 71 128 80 129 86 129 76 129 66 130 71 130 84 130 65 131 76 131 85 131 76 132 86 132 85 132 65 133 85 133 17 133 80 134 87 134 86 134 278 135 84 135 88 135 80 136 79 136 87 136 83 137 90 137 82 137 94 138 87 138 79 138 89 139 83 139 81 139 71 140 92 140 91 140 82 141 92 141 71 141 85 142 93 142 17 142 85 143 96 143 95 143 86 144 96 144 85 144 71 145 91 145 84 145 84 146 91 146 148 146 86 147 304 147 96 147 82 148 90 148 92 148 89 149 98 149 83 149 147 150 83 150 97 150 90 151 83 151 147 151 83 152 98 152 97 152 234 153 98 153 99 153 99 154 98 154 89 154 95 155 93 155 85 155 86 156 87 156 304 156 304 157 87 157 303 157 94 158 73 158 87 158 87 159 156 159 303 159 95 160 100 160 93 160 87 161 73 161 156 161 17 162 93 162 286 162 100 163 101 163 93 163 228 164 104 164 102 164 104 165 106 165 102 165 102 166 106 166 14 166 105 167 109 167 103 167 154 168 109 168 105 168 100 169 111 169 101 169 100 170 103 170 111 170 93 171 101 171 286 171 73 172 153 172 156 172 107 173 153 173 73 173 38 174 108 174 231 174 231 175 108 175 149 175 103 176 109 176 111 176 150 177 114 177 104 177 149 178 113 178 112 178 150 179 151 179 114 179 149 180 108 180 113 180 110 181 113 181 151 181 104 182 114 182 106 182 110 183 112 183 113 183 14 184 115 184 18 184 14 185 106 185 115 185 101 186 118 186 286 186 155 187 153 187 116 187 101 188 111 188 118 188 151 189 113 189 114 189 107 190 116 190 153 190 155 191 116 191 154 191 154 192 116 192 109 192 108 193 117 193 113 193 114 194 121 194 106 194 113 195 122 195 114 195 286 196 118 196 119 196 293 197 116 197 107 197 18 198 115 198 19 198 109 199 120 199 111 199 117 200 124 200 113 200 109 201 123 201 120 201 116 202 123 202 109 202 113 203 124 203 122 203 111 204 125 204 118 204 106 205 121 205 115 205 114 206 127 206 121 206 111 207 126 207 125 207 129 208 125 208 126 208 293 209 132 209 116 209 121 210 26 210 115 210 111 211 120 211 126 211 115 212 29 212 19 212 116 213 132 213 123 213 115 214 26 214 29 214 124 215 306 215 122 215 114 216 128 216 127 216 121 217 127 217 130 217 28 218 132 218 293 218 122 219 128 219 114 219 121 220 130 220 26 220 26 221 130 221 131 221 31 222 124 222 117 222 118 223 125 223 119 223 122 224 307 224 128 224 122 225 306 225 307 225 28 226 33 226 132 226 129 227 30 227 125 227 31 228 117 228 46 228 136 229 137 229 135 229 141 230 142 230 140 230 110 231 151 231 112 231 162 232 161 232 163 232 252 233 167 233 249 233 248 234 166 234 251 234 142 235 141 235 166 235 175 236 179 236 252 236 170 237 174 237 169 237 252 238 179 238 167 238 290 239 176 239 168 239 168 240 176 240 173 240 23 241 177 241 4 241 23 242 169 242 177 242 167 243 178 243 172 243 175 244 180 244 179 244 259 245 174 245 170 245 141 246 140 246 166 246 166 247 140 247 184 247 268 248 180 248 175 248 169 249 174 249 177 249 259 250 183 250 174 250 173 251 176 251 181 251 181 252 185 252 182 252 260 253 183 253 259 253 266 254 267 254 268 254 257 255 183 255 260 255 167 256 179 256 178 256 182 257 185 257 267 257 267 258 185 258 268 258 176 259 185 259 181 259 288 260 185 260 176 260 258 261 184 261 186 261 258 262 186 262 257 262 180 263 189 263 179 263 139 264 186 264 140 264 140 265 186 265 184 265 257 266 186 266 183 266 174 267 194 267 177 267 268 268 185 268 187 268 178 269 188 269 172 269 174 270 183 270 190 270 179 271 189 271 178 271 268 272 187 272 180 272 174 273 190 273 194 273 172 274 188 274 191 274 288 275 192 275 185 275 177 276 193 276 4 276 185 277 192 277 187 277 180 278 187 278 189 278 183 279 198 279 190 279 190 280 198 280 194 280 189 281 262 281 178 281 177 282 194 282 193 282 178 283 262 283 195 283 186 284 197 284 183 284 183 285 200 285 198 285 178 286 195 286 188 286 4 287 193 287 6 287 183 288 197 288 200 288 300 289 189 289 302 289 139 290 197 290 186 290 189 291 187 291 302 291 189 292 300 292 262 292 288 293 196 293 192 293 194 294 265 294 193 294 187 295 199 295 302 295 187 296 192 296 199 296 188 297 201 297 191 297 198 298 265 298 194 298 139 299 138 299 197 299 188 300 195 300 201 300 199 301 192 301 202 301 192 302 301 302 202 302 196 303 301 303 192 303 204 304 195 304 203 304 138 305 205 305 197 305 200 306 197 306 205 306 195 307 204 307 207 307 138 308 137 308 205 308 195 309 207 309 201 309 193 310 209 310 6 310 210 311 265 311 206 311 263 312 301 312 196 312 193 313 265 313 209 313 204 314 222 314 207 314 210 315 211 315 265 315 261 316 212 316 208 316 204 317 208 317 222 317 265 318 211 318 209 318 263 319 212 319 261 319 196 320 217 320 263 320 213 321 264 321 210 321 6 322 209 322 9 322 215 323 218 323 264 323 214 324 218 324 215 324 205 325 136 325 214 325 137 326 136 326 205 326 196 327 289 327 217 327 201 328 216 328 191 328 191 329 216 329 272 329 210 330 264 330 211 330 214 331 136 331 218 331 263 332 217 332 212 332 208 333 212 333 222 333 201 334 207 334 216 334 209 335 219 335 9 335 264 336 218 336 211 336 207 337 223 337 216 337 211 338 220 338 209 338 217 339 221 339 212 339 207 340 222 340 223 340 218 341 224 341 211 341 217 342 289 342 221 342 209 343 220 343 219 343 211 344 229 344 220 344 272 345 225 345 279 345 216 346 225 346 272 346 216 347 223 347 225 347 218 348 226 348 224 348 211 349 224 349 229 349 227 350 221 350 289 350 9 351 102 351 14 351 219 352 102 352 9 352 223 353 222 353 253 353 220 354 102 354 219 354 256 355 223 355 253 355 135 356 226 356 136 356 136 357 226 357 218 357 220 358 228 358 102 358 220 359 229 359 228 359 255 360 253 360 222 360 212 361 230 361 222 361 222 362 230 362 255 362 84 363 148 363 144 363 212 364 254 364 230 364 143 365 221 365 227 365 135 366 231 366 226 366 212 367 270 367 254 367 224 368 299 368 229 368 84 369 233 369 88 369 221 370 270 370 212 370 256 371 225 371 223 371 38 372 231 372 135 372 224 373 298 373 299 373 232 374 225 374 256 374 226 375 298 375 224 375 221 376 269 376 270 376 226 377 231 377 298 377 145 378 236 378 144 378 269 379 221 379 296 379 84 380 144 380 233 380 143 381 296 381 221 381 279 382 225 382 21 382 232 383 235 383 225 383 144 384 236 384 233 384 237 385 235 385 232 385 237 386 238 386 235 386 146 387 236 387 145 387 296 388 143 388 295 388 88 389 233 389 239 389 297 390 240 390 146 390 297 391 234 391 240 391 238 392 294 392 235 392 133 393 295 393 143 393 295 394 241 394 294 394 99 395 240 395 234 395 236 396 242 396 233 396 225 397 235 397 243 397 146 398 240 398 247 398 133 399 241 399 295 399 99 400 244 400 240 400 233 401 245 401 239 401 225 402 243 402 21 402 235 403 246 403 243 403 236 404 247 404 242 404 146 405 247 405 236 405 294 406 241 406 248 406 294 407 248 407 235 407 235 408 248 408 246 408 242 409 249 409 233 409 243 410 169 410 21 410 133 411 142 411 241 411 233 412 249 412 245 412 21 413 169 413 23 413 248 414 251 414 246 414 247 415 250 415 242 415 244 416 290 416 240 416 243 417 170 417 169 417 241 418 166 418 248 418 242 419 250 419 305 419 242 420 305 420 249 420 290 421 168 421 240 421 245 422 167 422 239 422 243 423 171 423 170 423 245 424 249 424 167 424 240 425 168 425 247 425 243 426 271 426 171 426 246 427 271 427 243 427 142 428 166 428 241 428 239 429 167 429 172 429 247 430 168 430 250 430 246 431 251 431 271 431 268 432 175 432 266 432 296 433 256 433 269 433 256 434 253 434 269 434 269 435 255 435 270 435 253 436 255 436 269 436 270 437 255 437 254 437 254 438 255 438 230 438 258 439 259 439 184 439 170 440 171 440 184 440 184 441 171 441 166 441 171 442 271 442 166 442 271 443 251 443 166 443 259 444 170 444 184 444 280 445 276 445 275 445 32 446 276 446 280 446 279 447 10 447 272 447 278 448 277 448 53 448 278 449 273 449 277 449 278 450 282 450 273 450 276 451 53 451 277 451 1 452 272 452 10 452 88 453 282 453 278 453 282 454 281 454 273 454 277 455 275 455 276 455 239 456 282 456 88 456 283 457 281 457 282 457 277 458 273 458 275 458 284 459 274 459 15 459 284 460 15 460 16 460 286 461 284 461 16 461 283 462 285 462 281 462 239 463 283 463 282 463 286 464 16 464 17 464 172 465 283 465 239 465 280 466 275 466 274 466 287 467 285 467 283 467 280 468 274 468 284 468 119 469 284 469 286 469 119 470 280 470 284 470 172 471 287 471 283 471 191 472 287 472 172 472 32 473 280 473 119 473 275 474 273 474 274 474 272 475 285 475 287 475 272 476 1 476 285 476 272 477 287 477 191 477 290 478 288 478 176 478 244 479 99 479 290 479 89 480 81 480 99 480 28 481 293 481 292 481 90 482 147 482 92 482 202 483 262 483 199 483 199 484 262 484 302 484 302 485 262 485 300 485 120 486 123 486 126 486 273 487 281 487 5 487 274 488 2 488 11 488 274 489 11 489 20 489 273 490 5 490 2 490 281 491 285 491 3 491 281 492 3 492 5 492 273 493 2 493 274 493 285 494 1 494 3 494 274 495 20 495 15 495 308 496 54 496 46 496 309 497 308 497 46 497 308 498 134 498 54 498 309 499 46 499 117 499 308 500 94 500 134 500 310 501 117 501 108 501 310 502 309 502 117 502 311 503 94 503 308 503 311 504 73 504 94 504 310 505 108 505 38 505 311 506 107 506 73 506 312 507 310 507 38 507 312 508 38 508 135 508 311 509 293 509 107 509 137 510 312 510 135 510 313 511 293 511 311 511 313 512 292 512 293 512 314 513 312 513 137 513 138 514 314 514 137 514 313 515 291 515 292 515 315 516 314 516 138 516 139 517 315 517 138 517 316 518 291 518 313 518 140 519 315 519 139 519 78 520 291 520 316 520 142 521 317 521 315 521 142 522 315 522 140 522 81 523 78 523 316 523 133 524 317 524 142 524 99 525 81 525 316 525 99 526 316 526 318 526 143 527 317 527 133 527 143 528 319 528 317 528 227 529 319 529 143 529 289 530 319 530 227 530 290 531 99 531 318 531 290 532 318 532 320 532 196 533 319 533 289 533 288 534 290 534 320 534 196 535 320 535 319 535 196 536 288 536 320 536 320 537 318 537 321 537 311 538 322 538 323 538 320 539 321 539 324 539 313 540 311 540 323 540 313 541 323 541 325 541 319 542 324 542 350 542 319 543 320 543 324 543 316 544 313 544 325 544 316 545 325 545 326 545 319 546 350 546 327 546 317 547 319 547 327 547 317 548 327 548 328 548 316 549 326 549 329 549 318 550 316 550 329 550 318 551 329 551 321 551 341 552 331 552 357 552 359 553 336 553 363 553 331 554 336 554 359 554 340 555 334 555 335 555 362 556 338 556 324 556 340 557 327 557 334 557 336 558 325 558 363 558 352 559 324 559 321 559 339 560 344 560 333 560 337 561 345 561 338 561 344 562 343 562 333 562 341 563 347 563 331 563 346 564 336 564 331 564 342 565 344 565 339 565 348 566 345 566 337 566 331 567 347 567 346 567 338 568 350 568 324 568 332 569 354 569 337 569 330 570 329 570 326 570 351 571 347 571 341 571 354 572 348 572 337 572 333 573 352 573 330 573 338 574 345 574 350 574 352 575 329 575 330 575 343 576 352 576 333 576 346 577 325 577 336 577 349 578 354 578 332 578 340 579 328 579 327 579 347 580 355 580 346 580 356 581 357 581 620 581 348 582 358 582 345 582 620 583 359 583 360 583 357 584 359 584 620 584 346 585 326 585 325 585 361 586 354 586 349 586 358 587 350 587 345 587 344 588 362 588 343 588 360 589 363 589 364 589 342 590 365 590 344 590 352 591 321 591 329 591 351 592 366 592 347 592 343 593 362 593 352 593 363 594 323 594 364 594 353 595 365 595 342 595 365 596 362 596 344 596 359 597 363 597 360 597 348 598 334 598 358 598 347 599 366 599 355 599 355 600 326 600 346 600 354 601 334 601 348 601 366 602 330 602 355 602 332 603 365 603 353 603 358 604 327 604 350 604 355 605 330 605 326 605 361 606 368 606 354 606 339 607 333 607 351 607 351 608 333 608 366 608 354 609 368 609 334 609 332 610 337 610 365 610 334 611 327 611 358 611 357 612 331 612 359 612 337 613 338 613 365 613 333 614 330 614 366 614 363 615 325 615 323 615 362 616 324 616 352 616 365 617 338 617 362 617 335 618 334 618 368 618 369 619 361 619 377 619 370 620 339 620 376 620 376 621 339 621 351 621 367 622 361 622 369 622 372 623 339 623 370 623 372 624 342 624 339 624 372 625 353 625 342 625 373 626 353 626 372 626 371 627 357 627 356 627 374 628 357 628 371 628 375 629 332 629 373 629 373 630 332 630 353 630 374 631 341 631 357 631 376 632 341 632 374 632 377 633 332 633 375 633 377 634 349 634 332 634 377 635 361 635 349 635 376 636 351 636 341 636 386 637 383 637 415 637 384 638 414 638 385 638 387 639 388 639 405 639 384 640 369 640 414 640 382 641 374 641 410 641 389 642 392 642 379 642 386 643 370 643 383 643 379 644 393 644 382 644 380 645 395 645 381 645 388 646 396 646 380 646 390 647 394 647 378 647 381 648 395 648 373 648 378 649 398 649 386 649 392 650 399 650 379 650 387 651 401 651 388 651 395 652 375 652 373 652 396 653 395 653 380 653 397 654 394 654 390 654 391 655 400 655 387 655 400 656 401 656 387 656 398 657 372 657 386 657 379 658 402 658 393 658 399 659 402 659 379 659 386 660 372 660 370 660 382 661 393 661 374 661 401 662 404 662 388 662 388 663 404 663 396 663 405 664 397 664 390 664 375 665 395 665 377 665 394 666 398 666 378 666 407 667 408 667 409 667 393 668 376 668 374 668 408 669 410 669 413 669 400 670 412 670 401 670 411 671 399 671 392 671 396 672 404 672 395 672 410 673 371 673 413 673 404 674 414 674 395 674 412 675 404 675 401 675 414 676 377 676 395 676 402 677 383 677 393 677 394 678 381 678 398 678 411 679 378 679 399 679 399 680 415 680 402 680 383 681 376 681 393 681 378 682 415 682 399 682 398 683 373 683 372 683 381 684 373 684 398 684 415 685 383 685 402 685 403 686 412 686 400 686 405 687 388 687 397 687 383 688 370 688 376 688 397 689 380 689 394 689 410 690 374 690 371 690 388 691 380 691 397 691 408 692 382 692 410 692 412 693 385 693 404 693 377 694 414 694 369 694 407 695 379 695 408 695 380 696 381 696 394 696 411 697 390 697 378 697 378 698 386 698 415 698 385 699 414 699 404 699 416 700 412 700 403 700 389 701 379 701 407 701 416 702 385 702 412 702 379 703 382 703 408 703 417 704 411 704 425 704 417 705 390 705 411 705 418 706 390 706 417 706 418 707 405 707 390 707 420 708 407 708 672 708 672 709 407 709 406 709 421 710 405 710 418 710 422 711 387 711 421 711 421 712 387 712 405 712 420 713 389 713 407 713 445 714 389 714 420 714 422 715 391 715 387 715 423 716 389 716 445 716 424 717 391 717 422 717 423 718 392 718 389 718 424 719 400 719 391 719 425 720 392 720 423 720 419 721 403 721 424 721 424 722 403 722 400 722 425 723 411 723 392 723 430 724 426 724 464 724 427 725 431 725 418 725 434 726 435 726 436 726 433 727 431 727 427 727 431 728 421 728 418 728 443 729 452 729 432 729 435 730 444 730 429 730 441 731 445 731 420 731 426 732 419 732 460 732 433 733 446 733 431 733 460 734 419 734 424 734 434 735 447 735 435 735 440 736 448 736 437 736 429 737 449 737 441 737 444 738 449 738 429 738 450 739 426 739 430 739 447 740 444 740 435 740 450 741 419 741 426 741 437 742 448 742 439 742 441 743 449 743 445 743 439 744 417 744 425 744 452 745 433 745 432 745 451 746 440 746 428 746 448 747 417 747 439 747 446 748 421 748 431 748 452 749 453 749 433 749 434 750 455 750 447 750 440 751 454 751 448 751 453 752 446 752 433 752 449 753 423 753 445 753 451 754 432 754 440 754 444 755 457 755 449 755 447 756 457 756 444 756 454 757 417 757 448 757 432 758 458 758 440 758 459 759 455 759 434 759 446 760 422 760 421 760 449 761 457 761 423 761 453 762 460 762 446 762 440 763 458 763 454 763 454 764 418 764 417 764 460 765 424 765 446 765 456 766 464 766 452 766 424 767 422 767 446 767 464 768 453 768 452 768 462 769 429 769 463 769 461 770 436 770 466 770 458 771 427 771 454 771 436 772 435 772 466 772 466 773 435 773 462 773 443 774 432 774 451 774 455 775 439 775 447 775 435 776 429 776 462 776 457 777 425 777 423 777 463 778 420 778 465 778 464 779 426 779 453 779 447 780 439 780 457 780 465 781 420 781 672 781 427 782 418 782 454 782 432 783 433 783 458 783 428 784 440 784 459 784 459 785 440 785 455 785 429 786 441 786 463 786 453 787 426 787 460 787 457 788 439 788 425 788 433 789 427 789 458 789 440 790 437 790 455 790 441 791 420 791 463 791 456 792 442 792 464 792 438 793 442 793 456 793 455 794 437 794 439 794 442 795 430 795 464 795 476 796 436 796 461 796 488 797 452 797 529 797 488 798 456 798 452 798 502 799 438 799 456 799 470 800 428 800 459 800 469 801 467 801 482 801 470 802 459 802 468 802 467 803 469 803 466 803 469 804 472 804 466 804 470 805 471 805 428 805 466 806 472 806 461 806 682 807 473 807 474 807 472 808 476 808 461 808 473 809 475 809 474 809 478 810 436 810 476 810 436 811 478 811 434 811 475 812 480 812 477 812 477 813 480 813 479 813 481 814 434 814 478 814 480 815 482 815 479 815 479 816 482 816 467 816 481 817 459 817 434 817 468 818 459 818 481 818 530 819 490 819 526 819 486 820 490 820 530 820 484 821 493 821 483 821 492 822 484 822 512 822 497 823 498 823 524 823 494 824 499 824 520 824 526 825 490 825 468 825 524 826 498 826 487 826 504 827 501 827 486 827 500 828 503 828 491 828 489 829 505 829 496 829 491 830 503 830 489 830 485 831 478 831 476 831 498 832 506 832 487 832 468 833 490 833 470 833 493 834 507 834 532 834 506 835 485 835 487 835 488 836 507 836 502 836 532 837 507 837 488 837 501 838 490 838 486 838 508 839 504 839 495 839 503 840 505 840 489 840 492 841 510 841 484 841 516 842 510 842 492 842 497 843 509 843 498 843 505 844 494 844 496 844 510 845 511 845 484 845 509 846 518 846 498 846 513 847 509 847 497 847 500 848 514 848 503 848 484 849 511 849 493 849 508 850 515 850 504 850 499 851 494 851 517 851 518 852 506 852 498 852 511 853 507 853 493 853 504 854 515 854 501 854 519 855 511 855 510 855 500 856 512 856 514 856 514 857 505 857 503 857 515 858 520 858 501 858 478 859 485 859 481 859 470 860 490 860 471 860 519 861 507 861 511 861 710 862 507 862 519 862 505 863 521 863 494 863 734 864 524 864 525 864 522 865 507 865 710 865 523 866 524 866 734 866 502 867 507 867 522 867 506 868 526 868 485 868 501 869 527 869 490 869 520 870 527 870 501 870 524 871 528 871 525 871 508 872 491 872 515 872 517 873 521 873 529 873 494 874 521 874 517 874 518 875 530 875 506 875 485 876 526 876 481 876 509 877 486 877 518 877 530 878 526 878 506 878 514 879 532 879 505 879 491 880 489 880 515 880 486 881 530 881 518 881 512 882 483 882 514 882 490 883 527 883 471 883 532 884 521 884 505 884 513 885 495 885 509 885 533 886 495 886 513 886 504 887 486 887 509 887 524 888 487 888 528 888 483 889 532 889 514 889 472 890 531 890 476 890 521 891 488 891 529 891 495 892 504 892 509 892 481 893 526 893 468 893 528 894 487 894 531 894 487 895 485 895 531 895 532 896 488 896 521 896 531 897 485 897 476 897 483 898 493 898 532 898 489 899 496 899 515 899 515 900 496 900 520 900 512 901 484 901 483 901 497 902 524 902 523 902 520 903 499 903 527 903 496 904 494 904 520 904 495 905 567 905 508 905 535 906 497 906 523 906 567 907 534 907 508 907 534 908 537 908 508 908 508 909 537 909 491 909 560 910 492 910 512 910 537 911 538 911 491 911 538 912 500 912 491 912 541 913 542 913 543 913 543 914 542 914 544 914 535 915 541 915 497 915 545 916 546 916 547 916 548 917 547 917 546 917 549 918 550 918 551 918 551 919 550 919 552 919 560 920 549 920 492 920 559 921 554 921 555 921 555 922 554 922 553 922 734 923 556 923 523 923 538 924 557 924 500 924 500 925 557 925 512 925 556 926 558 926 523 926 557 927 559 927 512 927 512 928 559 928 555 928 540 929 559 929 557 929 558 930 548 930 523 930 555 931 560 931 512 931 523 932 548 932 546 932 536 933 548 933 558 933 546 934 535 934 523 934 549 935 551 935 492 935 541 936 543 936 497 936 551 937 562 937 492 937 543 938 561 938 497 938 492 939 562 939 516 939 497 940 561 940 513 940 561 941 563 941 513 941 562 942 564 942 516 942 513 943 563 943 533 943 563 944 565 944 533 944 533 945 565 945 495 945 565 946 566 946 495 946 495 947 566 947 567 947 566 948 534 948 567 948 568 949 536 949 558 949 569 950 783 950 564 950 569 951 564 951 562 951 570 952 558 952 556 952 570 953 568 953 558 953 849 954 570 954 556 954 569 955 551 955 571 955 569 956 562 956 551 956 572 957 569 957 571 957 832 958 849 958 556 958 573 959 571 959 551 959 570 960 849 960 847 960 573 961 551 961 539 961 574 962 570 962 847 962 576 963 573 963 539 963 572 964 575 964 569 964 809 965 578 965 579 965 578 966 580 966 579 966 580 967 582 967 581 967 582 968 588 968 581 968 800 969 799 969 534 969 799 970 585 970 537 970 576 971 539 971 540 971 577 972 569 972 575 972 577 973 575 973 576 973 577 974 576 974 540 974 577 975 540 975 557 975 583 976 577 976 557 976 583 977 557 977 538 977 796 978 583 978 538 978 795 979 796 979 538 979 794 980 583 980 796 980 809 981 583 981 794 981 584 982 795 982 538 982 585 983 538 983 537 983 585 984 584 984 538 984 579 985 583 985 809 985 799 986 537 986 534 986 581 987 579 987 580 987 586 988 534 988 566 988 800 989 534 989 586 989 802 990 586 990 566 990 803 991 802 991 566 991 803 992 566 992 565 992 587 993 581 993 588 993 587 994 588 994 806 994 587 995 565 995 563 995 587 996 803 996 565 996 587 997 806 997 803 997 589 998 587 998 563 998 589 999 563 999 561 999 589 1000 561 1000 543 1000 568 1001 589 1001 543 1001 568 1002 543 1002 536 1002 294 1003 238 1003 295 1003 238 1004 237 1004 295 1004 237 1005 232 1005 295 1005 232 1006 256 1006 295 1006 295 1007 256 1007 296 1007 146 1008 145 1008 297 1008 145 1009 144 1009 297 1009 297 1010 144 1010 234 1010 234 1011 91 1011 98 1011 148 1012 91 1012 234 1012 98 1013 91 1013 97 1013 97 1014 91 1014 147 1014 91 1015 92 1015 147 1015 144 1016 148 1016 234 1016 151 1017 150 1017 112 1017 112 1018 150 1018 149 1018 150 1019 104 1019 149 1019 149 1020 104 1020 231 1020 231 1021 229 1021 298 1021 228 1022 229 1022 231 1022 298 1023 229 1023 299 1023 104 1024 228 1024 231 1024 257 1025 260 1025 258 1025 260 1026 259 1026 258 1026 261 1027 204 1027 263 1027 208 1028 204 1028 261 1028 195 1029 262 1029 301 1029 301 1030 262 1030 202 1030 204 1031 203 1031 263 1031 263 1032 195 1032 301 1032 203 1033 195 1033 263 1033 68 1034 67 1034 75 1034 67 1035 64 1035 75 1035 75 1036 64 1036 77 1036 64 1037 62 1037 77 1037 61 1038 152 1038 56 1038 77 1039 152 1039 61 1039 62 1040 152 1040 77 1040 154 1041 105 1041 155 1041 155 1042 103 1042 153 1042 105 1043 103 1043 155 1043 103 1044 100 1044 153 1044 153 1045 95 1045 156 1045 156 1046 96 1046 303 1046 95 1047 96 1047 156 1047 100 1048 95 1048 153 1048 96 1049 304 1049 303 1049 264 1050 213 1050 215 1050 215 1051 210 1051 214 1051 213 1052 210 1052 215 1052 265 1053 198 1053 205 1053 210 1054 206 1054 214 1054 205 1055 198 1055 200 1055 214 1056 265 1056 205 1056 206 1057 265 1057 214 1057 266 1058 175 1058 267 1058 267 1059 175 1059 182 1059 182 1060 175 1060 181 1060 252 1061 249 1061 173 1061 173 1062 305 1062 168 1062 249 1063 305 1063 173 1063 181 1064 252 1064 173 1064 168 1065 305 1065 250 1065 175 1066 252 1066 181 1066 157 1067 63 1067 69 1067 69 1068 60 1068 72 1068 63 1069 60 1069 69 1069 158 1070 51 1070 59 1070 60 1071 159 1071 72 1071 59 1072 51 1072 55 1072 72 1073 159 1073 59 1073 159 1074 158 1074 59 1074 34 1075 30 1075 160 1075 160 1076 30 1076 37 1076 37 1077 30 1077 36 1077 129 1078 126 1078 33 1078 33 1079 126 1079 132 1079 36 1080 129 1080 33 1080 132 1081 126 1081 123 1081 30 1082 129 1082 36 1082 161 1083 165 1083 163 1083 165 1084 164 1084 163 1084 163 1085 164 1085 40 1085 131 1086 130 1086 27 1086 27 1087 127 1087 306 1087 130 1088 127 1088 27 1088 306 1089 127 1089 307 1089 40 1090 131 1090 27 1090 127 1091 128 1091 307 1091 164 1092 131 1092 40 1092 310 1093 618 1093 590 1093 309 1094 310 1094 590 1094 309 1095 590 1095 591 1095 315 1096 317 1096 328 1096 308 1097 309 1097 591 1097 315 1098 328 1098 592 1098 314 1099 315 1099 592 1099 314 1100 592 1100 593 1100 308 1101 591 1101 594 1101 311 1102 308 1102 594 1102 312 1103 593 1103 595 1103 312 1104 314 1104 593 1104 311 1105 594 1105 322 1105 310 1106 595 1106 618 1106 310 1107 312 1107 595 1107 604 1108 600 1108 625 1108 600 1109 591 1109 618 1109 618 1110 591 1110 590 1110 607 1111 597 1111 596 1111 601 1112 605 1112 340 1112 598 1113 604 1113 625 1113 323 1114 322 1114 364 1114 597 1115 602 1115 596 1115 608 1116 601 1116 599 1116 605 1117 592 1117 340 1117 606 1118 608 1118 599 1118 340 1119 592 1119 328 1119 609 1120 597 1120 607 1120 597 1121 610 1121 602 1121 604 1122 611 1122 600 1122 608 1123 605 1123 601 1123 609 1124 612 1124 597 1124 598 1125 614 1125 604 1125 610 1126 595 1126 602 1126 611 1127 591 1127 600 1127 614 1128 611 1128 604 1128 612 1129 615 1129 597 1129 597 1130 615 1130 610 1130 603 1131 616 1131 598 1131 606 1132 617 1132 608 1132 615 1133 618 1133 610 1133 608 1134 619 1134 605 1134 616 1135 614 1135 598 1135 610 1136 618 1136 595 1136 613 1137 616 1137 603 1137 367 1138 368 1138 361 1138 619 1139 593 1139 605 1139 605 1140 593 1140 592 1140 617 1141 619 1141 608 1141 614 1142 594 1142 611 1142 594 1143 591 1143 611 1143 606 1144 596 1144 617 1144 622 1145 625 1145 609 1145 368 1146 601 1146 335 1146 609 1147 625 1147 612 1147 617 1148 602 1148 619 1148 616 1149 623 1149 614 1149 367 1150 599 1150 368 1150 621 1151 596 1151 606 1151 623 1152 322 1152 614 1152 612 1153 625 1153 615 1153 599 1154 601 1154 368 1154 614 1155 322 1155 594 1155 625 1156 600 1156 615 1156 601 1157 340 1157 335 1157 620 1158 360 1158 613 1158 613 1159 360 1159 616 1159 602 1160 593 1160 619 1160 596 1161 602 1161 617 1161 616 1162 360 1162 623 1162 600 1163 618 1163 615 1163 603 1164 598 1164 622 1164 607 1165 596 1165 621 1165 360 1166 364 1166 623 1166 602 1167 595 1167 593 1167 624 1168 606 1168 367 1168 622 1169 598 1169 625 1169 623 1170 364 1170 322 1170 606 1171 599 1171 367 1171 627 1172 607 1172 626 1172 627 1173 609 1173 607 1173 628 1174 367 1174 369 1174 629 1175 622 1175 627 1175 627 1176 622 1176 609 1176 628 1177 624 1177 367 1177 629 1178 603 1178 622 1178 630 1179 624 1179 628 1179 630 1180 606 1180 624 1180 631 1181 613 1181 629 1181 629 1182 613 1182 603 1182 632 1183 606 1183 630 1183 633 1184 613 1184 631 1184 633 1185 620 1185 613 1185 632 1186 621 1186 606 1186 626 1187 621 1187 632 1187 356 1188 620 1188 371 1188 371 1189 620 1189 633 1189 626 1190 607 1190 621 1190 661 1191 642 1191 643 1191 635 1192 642 1192 661 1192 644 1193 645 1193 654 1193 416 1194 647 1194 635 1194 637 1195 647 1195 416 1195 645 1196 640 1196 654 1196 639 1197 636 1197 638 1197 642 1198 628 1198 643 1198 641 1199 652 1199 634 1199 650 1200 649 1200 646 1200 649 1201 651 1201 646 1201 640 1202 652 1202 641 1202 645 1203 653 1203 640 1203 652 1204 629 1204 634 1204 646 1205 651 1205 639 1205 647 1206 642 1206 635 1206 653 1207 652 1207 640 1207 639 1208 655 1208 636 1208 651 1209 655 1209 639 1209 642 1210 630 1210 628 1210 656 1211 645 1211 644 1211 655 1212 627 1212 636 1212 636 1213 627 1213 626 1213 652 1214 631 1214 629 1214 637 1215 657 1215 647 1215 653 1216 658 1216 652 1216 645 1217 662 1217 653 1217 647 1218 657 1218 642 1218 649 1219 659 1219 651 1219 658 1220 631 1220 652 1220 657 1221 630 1221 642 1221 416 1222 661 1222 385 1222 662 1223 663 1223 653 1223 661 1224 384 1224 385 1224 660 1225 646 1225 648 1225 656 1226 662 1226 645 1226 659 1227 634 1227 651 1227 651 1228 634 1228 655 1228 648 1229 646 1229 637 1229 663 1230 658 1230 653 1230 654 1231 640 1231 649 1231 646 1232 638 1232 637 1232 658 1233 633 1233 631 1233 634 1234 627 1234 655 1234 664 1235 416 1235 403 1235 640 1236 659 1236 649 1236 637 1237 638 1237 657 1237 416 1238 635 1238 661 1238 657 1239 632 1239 630 1239 638 1240 636 1240 657 1240 660 1241 650 1241 646 1241 656 1242 406 1242 662 1242 640 1243 641 1243 659 1243 641 1244 634 1244 659 1244 661 1245 643 1245 384 1245 662 1246 409 1246 663 1246 636 1247 632 1247 657 1247 646 1248 639 1248 638 1248 384 1249 628 1249 369 1249 643 1250 628 1250 384 1250 409 1251 413 1251 663 1251 634 1252 629 1252 627 1252 663 1253 413 1253 658 1253 407 1254 409 1254 406 1254 406 1255 409 1255 662 1255 636 1256 626 1256 632 1256 664 1257 648 1257 416 1257 658 1258 371 1258 633 1258 413 1259 371 1259 658 1259 648 1260 637 1260 416 1260 413 1261 409 1261 408 1261 665 1262 650 1262 660 1262 666 1263 650 1263 665 1263 666 1264 649 1264 650 1264 667 1265 649 1265 666 1265 667 1266 654 1266 649 1266 668 1267 654 1267 667 1267 419 1268 664 1268 403 1268 669 1269 664 1269 419 1269 668 1270 644 1270 654 1270 670 1271 644 1271 668 1271 671 1272 648 1272 669 1272 669 1273 648 1273 664 1273 670 1274 656 1274 644 1274 672 1275 656 1275 670 1275 665 1276 660 1276 671 1276 671 1277 660 1277 648 1277 672 1278 406 1278 656 1278 419 1279 676 1279 669 1279 677 1280 678 1280 695 1280 682 1281 683 1281 673 1281 675 1282 465 1282 701 1282 673 1283 684 1283 677 1283 462 1284 702 1284 466 1284 679 1285 686 1285 703 1285 477 1286 687 1286 681 1286 479 1287 687 1287 477 1287 681 1288 688 1288 679 1288 678 1289 665 1289 695 1289 674 1290 685 1290 680 1290 462 1291 675 1291 702 1291 689 1292 674 1292 704 1292 463 1293 675 1293 462 1293 465 1294 672 1294 701 1294 667 1295 686 1295 668 1295 685 1296 690 1296 680 1296 688 1297 686 1297 679 1297 463 1298 465 1298 675 1298 680 1299 690 1299 676 1299 689 1300 691 1300 674 1300 676 1301 690 1301 669 1301 691 1302 685 1302 674 1302 687 1303 688 1303 681 1303 688 1304 668 1304 686 1304 665 1305 678 1305 666 1305 684 1306 693 1306 677 1306 474 1307 692 1307 682 1307 682 1308 692 1308 683 1308 673 1309 693 1309 684 1309 683 1310 693 1310 673 1310 687 1311 694 1311 688 1311 693 1312 678 1312 677 1312 690 1313 671 1313 669 1313 691 1314 695 1314 685 1314 685 1315 695 1315 690 1315 693 1316 666 1316 678 1316 696 1317 697 1317 689 1317 467 1318 687 1318 479 1318 697 1319 691 1319 689 1319 438 1320 699 1320 442 1320 474 1321 681 1321 692 1321 692 1322 693 1322 683 1322 694 1323 701 1323 688 1323 442 1324 699 1324 430 1324 688 1325 701 1325 668 1325 467 1326 702 1326 687 1326 699 1327 450 1327 430 1327 697 1328 673 1328 691 1328 701 1329 670 1329 668 1329 673 1330 700 1330 691 1330 692 1331 679 1331 693 1331 700 1332 695 1332 691 1332 698 1333 704 1333 438 1333 703 1334 666 1334 693 1334 704 1335 699 1335 438 1335 679 1336 703 1336 693 1336 702 1337 675 1337 687 1337 477 1338 681 1338 474 1338 695 1339 665 1339 690 1339 687 1340 675 1340 694 1340 680 1341 450 1341 699 1341 675 1342 701 1342 694 1342 681 1343 679 1343 692 1343 450 1344 676 1344 419 1344 700 1345 677 1345 695 1345 674 1346 680 1346 699 1346 690 1347 665 1347 671 1347 670 1348 701 1348 672 1348 703 1349 686 1349 666 1349 704 1350 674 1350 699 1350 666 1351 686 1351 667 1351 682 1352 673 1352 697 1352 466 1353 702 1353 467 1353 673 1354 677 1354 700 1354 680 1355 676 1355 450 1355 475 1356 477 1356 474 1356 707 1357 704 1357 698 1357 502 1358 456 1358 488 1358 706 1359 705 1359 697 1359 705 1360 682 1360 697 1360 473 1361 682 1361 705 1361 522 1362 438 1362 502 1362 438 1363 522 1363 698 1363 428 1364 471 1364 451 1364 522 1365 707 1365 698 1365 471 1366 527 1366 451 1366 707 1367 708 1367 704 1367 527 1368 499 1368 451 1368 451 1369 499 1369 443 1369 704 1370 708 1370 689 1370 517 1371 443 1371 499 1371 708 1372 709 1372 689 1372 529 1373 443 1373 517 1373 443 1374 529 1374 452 1374 709 1375 696 1375 689 1375 706 1376 696 1376 709 1376 696 1377 706 1377 697 1377 733 1378 723 1378 720 1378 718 1379 475 1379 473 1379 722 1380 706 1380 740 1380 721 1381 726 1381 710 1381 724 1382 469 1382 482 1382 719 1383 728 1383 745 1383 745 1384 728 1384 711 1384 722 1385 705 1385 706 1385 728 1386 729 1386 711 1386 714 1387 730 1387 712 1387 723 1388 731 1388 713 1388 725 1389 732 1389 717 1389 715 1390 734 1390 716 1390 712 1391 730 1391 718 1391 710 1392 726 1392 708 1392 732 1393 724 1393 717 1393 711 1394 729 1394 722 1394 734 1395 525 1395 716 1395 729 1396 735 1396 722 1396 713 1397 731 1397 721 1397 735 1398 705 1398 722 1398 716 1399 525 1399 725 1399 730 1400 736 1400 718 1400 731 1401 726 1401 721 1401 737 1402 714 1402 727 1402 736 1403 475 1403 718 1403 733 1404 745 1404 723 1404 708 1405 726 1405 709 1405 723 1406 744 1406 731 1406 475 1407 736 1407 480 1407 525 1408 528 1408 725 1408 744 1409 740 1409 731 1409 528 1410 732 1410 725 1410 729 1411 741 1411 735 1411 473 1412 705 1412 735 1412 531 1413 732 1413 528 1413 739 1414 736 1414 730 1414 742 1415 743 1415 516 1415 714 1416 738 1416 730 1416 745 1417 744 1417 723 1417 469 1418 531 1418 472 1418 516 1419 743 1419 510 1419 732 1420 531 1420 724 1420 724 1421 531 1421 469 1421 731 1422 740 1422 726 1422 738 1423 739 1423 730 1423 743 1424 519 1424 510 1424 741 1425 473 1425 735 1425 740 1426 709 1426 726 1426 728 1427 746 1427 729 1427 711 1428 744 1428 745 1428 522 1429 710 1429 707 1429 719 1430 746 1430 728 1430 737 1431 738 1431 714 1431 746 1432 741 1432 729 1432 736 1433 482 1433 480 1433 742 1434 720 1434 743 1434 739 1435 724 1435 736 1435 743 1436 713 1436 519 1436 738 1437 717 1437 739 1437 718 1438 473 1438 741 1438 724 1439 482 1439 736 1439 713 1440 721 1440 519 1440 719 1441 712 1441 746 1441 711 1442 740 1442 744 1442 717 1443 724 1443 739 1443 747 1444 714 1444 719 1444 721 1445 710 1445 519 1445 707 1446 710 1446 708 1446 725 1447 717 1447 738 1447 737 1448 716 1448 738 1448 712 1449 718 1449 746 1449 746 1450 718 1450 741 1450 714 1451 712 1451 719 1451 711 1452 722 1452 740 1452 720 1453 723 1453 743 1453 740 1454 706 1454 709 1454 716 1455 725 1455 738 1455 727 1456 714 1456 747 1456 723 1457 713 1457 743 1457 748 1458 719 1458 745 1458 749 1459 751 1459 719 1459 719 1460 753 1460 747 1460 754 1461 755 1461 753 1461 753 1462 755 1462 747 1462 747 1463 755 1463 727 1463 755 1464 758 1464 727 1464 753 1465 756 1465 754 1465 754 1466 756 1466 757 1466 751 1467 753 1467 719 1467 759 1468 749 1468 760 1468 750 1469 760 1469 749 1469 516 1470 564 1470 742 1470 727 1471 758 1471 737 1471 758 1472 761 1472 737 1472 564 1473 762 1473 742 1473 762 1474 763 1474 742 1474 737 1475 761 1475 764 1475 742 1476 763 1476 720 1476 737 1477 764 1477 716 1477 763 1478 765 1478 720 1478 764 1479 766 1479 716 1479 720 1480 765 1480 733 1480 766 1481 715 1481 716 1481 766 1482 767 1482 715 1482 765 1483 768 1483 733 1483 768 1484 769 1484 733 1484 767 1485 770 1485 715 1485 733 1486 769 1486 745 1486 768 1487 771 1487 769 1487 715 1488 770 1488 734 1488 769 1489 771 1489 745 1489 770 1490 556 1490 734 1490 745 1491 771 1491 748 1491 771 1492 772 1492 748 1492 772 1493 749 1493 748 1493 748 1494 749 1494 719 1494 772 1495 750 1495 749 1495 814 1496 768 1496 765 1496 775 1497 832 1497 556 1497 814 1498 765 1498 763 1498 842 1499 574 1499 847 1499 815 1500 763 1500 776 1500 770 1501 775 1501 556 1501 777 1502 775 1502 770 1502 816 1503 776 1503 763 1503 817 1504 816 1504 763 1504 777 1505 770 1505 767 1505 834 1506 767 1506 766 1506 780 1507 574 1507 778 1507 834 1508 766 1508 764 1508 817 1509 763 1509 762 1509 835 1510 764 1510 837 1510 837 1511 764 1511 761 1511 783 1512 817 1512 762 1512 783 1513 779 1513 781 1513 783 1514 781 1514 820 1514 783 1515 820 1515 817 1515 783 1516 762 1516 564 1516 842 1517 778 1517 574 1517 778 1518 782 1518 780 1518 782 1519 784 1519 780 1519 835 1520 834 1520 764 1520 834 1521 777 1521 767 1521 825 1522 774 1522 773 1522 774 1523 823 1523 773 1523 823 1524 785 1524 773 1524 785 1525 779 1525 773 1525 785 1526 781 1526 779 1526 815 1527 814 1527 763 1527 814 1528 813 1528 768 1528 813 1529 791 1529 771 1529 839 1530 837 1530 761 1530 786 1531 780 1531 784 1531 758 1532 839 1532 761 1532 786 1533 784 1533 787 1533 786 1534 839 1534 758 1534 786 1535 787 1535 839 1535 786 1536 758 1536 755 1536 788 1537 755 1537 754 1537 788 1538 786 1538 755 1538 788 1539 754 1539 752 1539 788 1540 752 1540 750 1540 789 1541 788 1541 750 1541 789 1542 750 1542 772 1542 829 1543 828 1543 771 1543 789 1544 772 1544 771 1544 789 1545 771 1545 828 1545 790 1546 789 1546 828 1546 811 1547 829 1547 771 1547 773 1548 789 1548 790 1548 773 1549 790 1549 825 1549 791 1550 811 1550 771 1550 813 1551 771 1551 768 1551 559 1552 540 1552 554 1552 793 1553 792 1553 794 1553 796 1554 793 1554 794 1554 796 1555 795 1555 797 1555 796 1556 797 1556 793 1556 795 1557 584 1557 797 1557 798 1558 584 1558 585 1558 586 1559 801 1559 800 1559 803 1560 804 1560 802 1560 803 1561 806 1561 805 1561 804 1562 803 1562 805 1562 807 1563 806 1563 588 1563 807 1564 805 1564 806 1564 578 1565 809 1565 810 1565 808 1566 578 1566 810 1566 808 1567 580 1567 578 1567 794 1568 810 1568 809 1568 792 1569 810 1569 794 1569 829 1570 811 1570 830 1570 812 1571 811 1571 791 1571 816 1572 817 1572 818 1572 817 1573 820 1573 819 1573 818 1574 817 1574 819 1574 821 1575 820 1575 781 1575 821 1576 819 1576 820 1576 781 1577 785 1577 824 1577 824 1578 821 1578 781 1578 822 1579 774 1579 825 1579 822 1580 823 1580 774 1580 824 1581 785 1581 823 1581 790 1582 826 1582 825 1582 828 1583 827 1583 826 1583 828 1584 826 1584 790 1584 828 1585 830 1585 827 1585 828 1586 829 1586 830 1586 832 1587 775 1587 831 1587 833 1588 775 1588 777 1588 836 1589 835 1589 837 1589 836 1590 837 1590 838 1590 837 1591 839 1591 838 1591 839 1592 787 1592 840 1592 838 1593 839 1593 840 1593 841 1594 787 1594 784 1594 841 1595 840 1595 787 1595 784 1596 782 1596 845 1596 845 1597 841 1597 784 1597 844 1598 842 1598 843 1598 844 1599 778 1599 842 1599 778 1600 844 1600 845 1600 845 1601 782 1601 778 1601 846 1602 842 1602 847 1602 846 1603 843 1603 842 1603 848 1604 846 1604 847 1604 849 1605 848 1605 847 1605 832 1606 831 1606 848 1606 849 1607 832 1607 848 1607 851 1608 852 1608 875 1608 569 1609 875 1609 853 1609 569 1610 851 1610 875 1610 854 1611 780 1611 786 1611 577 1612 851 1612 569 1612 583 1613 850 1613 856 1613 855 1614 854 1614 786 1614 583 1615 579 1615 850 1615 577 1616 856 1616 851 1616 857 1617 855 1617 786 1617 577 1618 583 1618 856 1618 858 1619 780 1619 854 1619 859 1620 857 1620 786 1620 858 1621 574 1621 780 1621 859 1622 786 1622 788 1622 860 1623 855 1623 857 1623 861 1624 860 1624 857 1624 862 1625 574 1625 858 1625 863 1626 859 1626 788 1626 862 1627 570 1627 574 1627 863 1628 788 1628 789 1628 865 1629 863 1629 789 1629 866 1630 568 1630 570 1630 866 1631 862 1631 864 1631 866 1632 570 1632 862 1632 866 1633 864 1633 867 1633 868 1634 568 1634 866 1634 773 1635 865 1635 789 1635 869 1636 865 1636 773 1636 589 1637 568 1637 868 1637 870 1638 589 1638 868 1638 871 1639 869 1639 773 1639 779 1640 871 1640 773 1640 872 1641 589 1641 870 1641 587 1642 589 1642 872 1642 779 1643 873 1643 871 1643 581 1644 587 1644 872 1644 581 1645 872 1645 874 1645 783 1646 873 1646 779 1646 569 1647 873 1647 783 1647 569 1648 853 1648 873 1648 579 1649 581 1649 874 1649 579 1650 874 1650 850 1650 576 1651 575 1651 878 1651 575 1652 876 1652 878 1652 876 1653 575 1653 572 1653 877 1654 876 1654 571 1654 876 1655 572 1655 571 1655 573 1656 877 1656 571 1656 573 1657 878 1657 877 1657 576 1658 878 1658 573 1658 752 1659 760 1659 750 1659 752 1660 757 1660 760 1660 757 1661 759 1661 760 1661 752 1662 754 1662 757 1662 757 1663 756 1663 759 1663 753 1664 751 1664 756 1664 759 1665 751 1665 749 1665 756 1666 751 1666 759 1666 555 1667 553 1667 560 1667 553 1668 550 1668 560 1668 553 1669 554 1669 550 1669 550 1670 549 1670 560 1670 554 1671 552 1671 550 1671 552 1672 539 1672 551 1672 540 1673 539 1673 554 1673 554 1674 539 1674 552 1674 546 1675 545 1675 535 1675 545 1676 542 1676 535 1676 542 1677 541 1677 535 1677 547 1678 544 1678 545 1678 545 1679 544 1679 542 1679 544 1680 536 1680 543 1680 548 1681 536 1681 547 1681 547 1682 536 1682 544 1682 891 1683 792 1683 925 1683 925 1684 792 1684 793 1684 879 1685 925 1685 797 1685 797 1686 925 1686 793 1686 584 1687 879 1687 797 1687 584 1688 880 1688 879 1688 584 1689 798 1689 880 1689 880 1690 882 1690 881 1690 798 1691 882 1691 880 1691 882 1692 798 1692 585 1692 800 1693 884 1693 883 1693 799 1694 800 1694 883 1694 585 1695 883 1695 882 1695 585 1696 799 1696 883 1696 884 1697 800 1697 801 1697 887 1698 885 1698 586 1698 886 1699 884 1699 885 1699 885 1700 801 1700 586 1700 884 1701 801 1701 885 1701 888 1702 887 1702 802 1702 802 1703 887 1703 586 1703 804 1704 888 1704 802 1704 804 1705 805 1705 888 1705 805 1706 889 1706 888 1706 805 1707 807 1707 889 1707 807 1708 588 1708 889 1708 889 1709 588 1709 890 1709 588 1710 582 1710 890 1710 582 1711 580 1711 890 1711 580 1712 808 1712 890 1712 890 1713 810 1713 891 1713 808 1714 810 1714 890 1714 810 1715 792 1715 891 1715 811 1716 892 1716 830 1716 892 1717 905 1717 830 1717 811 1718 893 1718 892 1718 811 1719 812 1719 893 1719 893 1720 895 1720 894 1720 812 1721 895 1721 893 1721 895 1722 812 1722 791 1722 815 1723 897 1723 896 1723 814 1724 815 1724 896 1724 813 1725 814 1725 896 1725 791 1726 896 1726 895 1726 791 1727 813 1727 896 1727 900 1728 898 1728 776 1728 899 1729 897 1729 898 1729 898 1730 815 1730 776 1730 897 1731 815 1731 898 1731 816 1732 977 1732 776 1732 977 1733 900 1733 776 1733 901 1734 977 1734 818 1734 818 1735 977 1735 816 1735 818 1736 819 1736 901 1736 819 1737 902 1737 901 1737 819 1738 821 1738 902 1738 903 1739 904 1739 825 1739 904 1740 822 1740 825 1740 904 1741 823 1741 822 1741 904 1742 824 1742 823 1742 902 1743 821 1743 904 1743 904 1744 821 1744 824 1744 825 1745 826 1745 903 1745 903 1746 826 1746 905 1746 905 1747 826 1747 827 1747 830 1748 905 1748 827 1748 775 1749 982 1749 831 1749 982 1750 906 1750 831 1750 775 1751 907 1751 982 1751 775 1752 833 1752 907 1752 833 1753 908 1753 907 1753 833 1754 909 1754 908 1754 909 1755 984 1755 908 1755 909 1756 833 1756 777 1756 834 1757 835 1757 911 1757 834 1758 911 1758 910 1758 777 1759 834 1759 910 1759 777 1760 910 1760 909 1760 914 1761 912 1761 836 1761 913 1762 911 1762 912 1762 912 1763 835 1763 836 1763 911 1764 835 1764 912 1764 838 1765 915 1765 836 1765 915 1766 914 1766 836 1766 838 1767 840 1767 915 1767 840 1768 916 1768 915 1768 840 1769 841 1769 916 1769 845 1770 917 1770 841 1770 841 1771 917 1771 916 1771 844 1772 917 1772 845 1772 843 1773 917 1773 844 1773 843 1774 918 1774 917 1774 843 1775 846 1775 918 1775 906 1776 862 1776 918 1776 918 1777 846 1777 906 1777 906 1778 846 1778 848 1778 831 1779 906 1779 848 1779 875 1780 920 1780 853 1780 921 1781 852 1781 851 1781 876 1782 877 1782 878 1782 866 1783 922 1783 868 1783 922 1784 951 1784 868 1784 890 1785 891 1785 850 1785 891 1786 856 1786 850 1786 868 1787 951 1787 870 1787 951 1788 923 1788 870 1788 923 1789 924 1789 870 1789 870 1790 924 1790 872 1790 924 1791 952 1791 872 1791 952 1792 953 1792 872 1792 872 1793 888 1793 889 1793 953 1794 888 1794 872 1794 891 1795 925 1795 856 1795 925 1796 955 1796 856 1796 856 1797 955 1797 851 1797 889 1798 874 1798 872 1798 889 1799 890 1799 874 1799 955 1800 926 1800 851 1800 890 1801 850 1801 874 1801 879 1802 958 1802 962 1802 880 1803 958 1803 879 1803 962 1804 960 1804 927 1804 958 1805 960 1805 962 1805 960 1806 957 1806 927 1806 927 1807 957 1807 928 1807 928 1808 959 1808 929 1808 957 1809 959 1809 928 1809 929 1810 885 1810 887 1810 959 1811 885 1811 929 1811 892 1812 893 1812 930 1812 893 1813 967 1813 930 1813 930 1814 969 1814 931 1814 967 1815 969 1815 930 1815 969 1816 966 1816 931 1816 931 1817 966 1817 932 1817 932 1818 968 1818 933 1818 966 1819 968 1819 932 1819 933 1820 898 1820 900 1820 968 1821 898 1821 933 1821 904 1822 903 1822 869 1822 903 1823 865 1823 869 1823 853 1824 934 1824 873 1824 934 1825 976 1825 873 1825 905 1826 935 1826 903 1826 976 1827 901 1827 873 1827 873 1828 901 1828 902 1828 903 1829 935 1829 865 1829 935 1830 936 1830 865 1830 865 1831 936 1831 863 1831 936 1832 937 1832 863 1832 902 1833 871 1833 873 1833 902 1834 904 1834 871 1834 937 1835 938 1835 863 1835 863 1836 938 1836 859 1836 938 1837 940 1837 859 1837 859 1838 940 1838 857 1838 904 1839 869 1839 871 1839 940 1840 939 1840 857 1840 943 1841 936 1841 935 1841 938 1842 941 1842 940 1842 941 1843 937 1843 942 1843 936 1844 942 1844 937 1844 943 1845 942 1845 936 1845 905 1846 943 1846 935 1846 941 1847 938 1847 937 1847 982 1848 907 1848 944 1848 907 1849 979 1849 944 1849 944 1850 981 1850 945 1850 979 1851 981 1851 944 1851 981 1852 978 1852 945 1852 945 1853 978 1853 946 1853 946 1854 980 1854 947 1854 978 1855 980 1855 946 1855 947 1856 912 1856 914 1856 980 1857 912 1857 947 1857 855 1858 915 1858 916 1858 916 1859 854 1859 855 1859 916 1860 917 1860 854 1860 917 1861 858 1861 854 1861 917 1862 862 1862 858 1862 917 1863 918 1863 862 1863 921 1864 851 1864 926 1864 921 1865 926 1865 948 1865 866 1866 867 1866 922 1866 922 1867 867 1867 919 1867 951 1868 950 1868 923 1868 923 1869 949 1869 924 1869 923 1870 950 1870 949 1870 922 1871 950 1871 951 1871 924 1872 949 1872 952 1872 925 1873 879 1873 954 1873 955 1874 956 1874 926 1874 955 1875 954 1875 956 1875 925 1876 954 1876 955 1876 962 1877 961 1877 879 1877 927 1878 928 1878 962 1878 881 1879 882 1879 963 1879 963 1880 883 1880 964 1880 882 1881 883 1881 963 1881 964 1882 883 1882 965 1882 883 1883 884 1883 965 1883 965 1884 884 1884 886 1884 930 1885 970 1885 892 1885 892 1886 970 1886 905 1886 930 1887 971 1887 970 1887 931 1888 932 1888 971 1888 977 1889 933 1889 900 1889 930 1890 931 1890 971 1890 894 1891 895 1891 972 1891 972 1892 896 1892 973 1892 895 1893 896 1893 972 1893 973 1894 896 1894 974 1894 896 1895 897 1895 974 1895 974 1896 897 1896 899 1896 1006 1897 861 1897 939 1897 861 1898 857 1898 939 1898 920 1899 1045 1899 934 1899 853 1900 920 1900 934 1900 976 1901 975 1901 901 1901 901 1902 975 1902 977 1902 946 1903 947 1903 945 1903 945 1904 947 1904 983 1904 944 1905 983 1905 982 1905 944 1906 945 1906 983 1906 984 1907 909 1907 1048 1907 1048 1908 910 1908 985 1908 909 1909 910 1909 1048 1909 985 1910 910 1910 986 1910 986 1911 911 1911 1060 1911 910 1912 911 1912 986 1912 1060 1913 911 1913 913 1913 987 1914 864 1914 906 1914 864 1915 862 1915 906 1915 855 1916 860 1916 915 1916 915 1917 860 1917 988 1917 954 1918 879 1918 1052 1918 956 1919 948 1919 926 1919 956 1920 954 1920 1052 1920 956 1921 1052 1921 948 1921 928 1922 929 1922 989 1922 962 1923 928 1923 989 1923 962 1924 989 1924 990 1924 950 1925 922 1925 919 1925 991 1926 950 1926 919 1926 949 1927 950 1927 991 1927 1012 1928 949 1928 991 1928 952 1929 949 1929 1012 1929 992 1930 952 1930 1012 1930 961 1931 962 1931 990 1931 953 1932 952 1932 992 1932 888 1933 992 1933 993 1933 888 1934 953 1934 992 1934 887 1935 993 1935 989 1935 887 1936 888 1936 993 1936 961 1937 990 1937 1052 1937 879 1938 961 1938 1052 1938 929 1939 887 1939 989 1939 998 1940 996 1940 995 1940 1000 1941 999 1941 1002 1941 997 1942 988 1942 1006 1942 1006 1943 999 1943 997 1943 1000 1944 1002 1944 1003 1944 1003 1945 1005 1945 1007 1945 991 1946 919 1946 1001 1946 1012 1947 1011 1947 1010 1947 1006 1948 1002 1948 999 1948 1002 1949 1005 1949 1003 1949 1005 1950 1008 1950 1007 1950 1008 1951 1009 1951 1007 1951 1010 1952 992 1952 1012 1952 1010 1953 1013 1953 992 1953 994 1954 1001 1954 919 1954 1011 1955 1012 1955 991 1955 994 1956 919 1956 987 1956 1004 1957 994 1957 987 1957 1014 1958 1072 1958 1000 1958 999 1959 1067 1959 997 1959 1072 1960 1067 1960 999 1960 1036 1961 1015 1961 1016 1961 1016 1962 1015 1962 1046 1962 997 1963 1017 1963 998 1963 1067 1964 1017 1964 997 1964 1015 1965 1018 1965 1046 1965 1046 1966 1018 1966 1057 1966 998 1967 1019 1967 996 1967 1017 1968 1019 1968 998 1968 1057 1969 1038 1969 1020 1969 1018 1970 1038 1970 1057 1970 996 1971 1022 1971 995 1971 1038 1972 1021 1972 1020 1972 1019 1973 1022 1973 996 1973 1020 1974 1021 1974 1055 1974 995 1975 1022 1975 1004 1975 1022 1976 1023 1976 1004 1976 1021 1977 1026 1977 1055 1977 1055 1978 1026 1978 1024 1978 1026 1979 1031 1979 1024 1979 1024 1980 1032 1980 1009 1980 1031 1981 1032 1981 1024 1981 1009 1982 1066 1982 1007 1982 1032 1983 1066 1983 1009 1983 1007 1984 1014 1984 1003 1984 1066 1985 1014 1985 1007 1985 1003 1986 1014 1986 1000 1986 1000 1987 1072 1987 999 1987 1029 1988 1028 1988 1033 1988 1029 1989 1033 1989 1034 1989 1034 1990 1033 1990 1035 1990 1036 1991 1037 1991 1015 1991 1015 1992 1037 1992 1018 1992 1038 1993 1018 1993 1037 1993 1028 1994 1029 1994 1039 1994 1031 1995 1026 1995 1025 1995 1026 1996 1021 1996 1025 1996 1025 1997 1021 1997 1038 1997 886 1998 959 1998 1041 1998 885 1999 959 1999 886 1999 959 2000 957 2000 1041 2000 1041 2001 960 2001 1042 2001 957 2002 960 2002 1041 2002 960 2003 958 2003 1042 2003 1042 2004 880 2004 881 2004 958 2005 880 2005 1042 2005 964 2006 965 2006 1041 2006 899 2007 968 2007 1043 2007 898 2008 968 2008 899 2008 968 2009 966 2009 1043 2009 1043 2010 969 2010 1044 2010 966 2011 969 2011 1043 2011 969 2012 967 2012 1044 2012 1044 2013 893 2013 894 2013 967 2014 893 2014 1044 2014 1055 2015 1024 2015 970 2015 971 2016 1055 2016 970 2016 934 2017 1045 2017 976 2017 1020 2018 1055 2018 971 2018 1006 2019 939 2019 940 2019 1002 2020 1006 2020 940 2020 1057 2021 1020 2021 971 2021 932 2022 1057 2022 971 2022 1002 2023 940 2023 941 2023 1005 2024 1002 2024 941 2024 1046 2025 1057 2025 932 2025 933 2026 1046 2026 932 2026 977 2027 1046 2027 933 2027 1005 2028 941 2028 942 2028 1008 2029 1005 2029 942 2029 977 2030 1016 2030 1046 2030 943 2031 1008 2031 942 2031 1009 2032 1008 2032 943 2032 975 2033 1016 2033 977 2033 976 2034 1016 2034 975 2034 1045 2035 1016 2035 976 2035 1009 2036 943 2036 905 2036 1024 2037 1009 2037 905 2037 970 2038 1024 2038 905 2038 973 2039 974 2039 1043 2039 913 2040 912 2040 1047 2040 912 2041 980 2041 1047 2041 980 2042 978 2042 1047 2042 1047 2043 978 2043 985 2043 985 2044 981 2044 1048 2044 978 2045 981 2045 985 2045 981 2046 979 2046 1048 2046 1048 2047 907 2047 908 2047 979 2048 907 2048 1048 2048 1004 2049 906 2049 982 2049 987 2050 906 2050 1004 2050 997 2051 947 2051 914 2051 997 2052 914 2052 915 2052 997 2053 915 2053 988 2053 998 2054 983 2054 947 2054 998 2055 947 2055 997 2055 995 2056 983 2056 998 2056 1004 2057 982 2057 983 2057 1004 2058 983 2058 995 2058 986 2059 1060 2059 1047 2059 908 2060 984 2060 1048 2060 1060 2061 913 2061 1047 2061 920 2062 875 2062 1045 2062 1045 2063 875 2063 1049 2063 1049 2064 875 2064 948 2064 875 2065 852 2065 948 2065 852 2066 921 2066 948 2066 919 2067 867 2067 987 2067 867 2068 864 2068 987 2068 989 2069 1054 2069 990 2069 1056 2070 1016 2070 1045 2070 1051 2071 1049 2071 948 2071 1052 2072 1058 2072 948 2072 990 2073 1053 2073 1052 2073 1050 2074 1049 2074 1051 2074 993 2075 1059 2075 989 2075 1054 2076 989 2076 1059 2076 1013 2077 993 2077 992 2077 1049 2078 1056 2078 1045 2078 1050 2079 1056 2079 1049 2079 1051 2080 948 2080 1058 2080 964 2081 1041 2081 1042 2081 886 2082 1041 2082 965 2082 963 2083 1042 2083 881 2083 1042 2084 963 2084 964 2084 973 2085 1043 2085 1044 2085 899 2086 1043 2086 974 2086 972 2087 1044 2087 894 2087 1044 2088 972 2088 973 2088 988 2089 860 2089 1006 2089 860 2090 861 2090 1006 2090 986 2091 1047 2091 985 2091 990 2092 1039 2092 1053 2092 1053 2093 1029 2093 1052 2093 1039 2094 1029 2094 1053 2094 1004 2095 1023 2095 994 2095 994 2096 1061 2096 1001 2096 1023 2097 1061 2097 994 2097 1052 2098 1029 2098 1058 2098 1029 2099 1034 2099 1058 2099 1001 2100 1062 2100 991 2100 1061 2101 1062 2101 1001 2101 1058 2102 1034 2102 1051 2102 991 2103 1062 2103 1011 2103 1051 2104 1035 2104 1050 2104 1034 2105 1035 2105 1051 2105 1050 2106 1035 2106 1056 2106 1062 2107 1064 2107 1011 2107 1011 2108 1064 2108 1010 2108 1035 2109 1036 2109 1056 2109 1064 2110 1063 2110 1010 2110 1056 2111 1036 2111 1016 2111 1010 2112 1063 2112 1013 2112 1063 2113 1040 2113 1013 2113 1013 2114 1040 2114 993 2114 993 2115 1040 2115 1059 2115 1040 2116 1027 2116 1059 2116 1027 2117 1030 2117 1059 2117 1059 2118 1030 2118 1054 2118 1054 2119 1030 2119 990 2119 1030 2120 1039 2120 990 2120 1064 2121 1065 2121 1063 2121 1065 2122 1040 2122 1063 2122 1017 2123 1067 2123 1068 2123 1017 2124 1068 2124 1019 2124 1019 2125 1068 2125 1069 2125 1019 2126 1069 2126 1022 2126 1022 2127 1069 2127 1023 2127 1069 2128 1070 2128 1023 2128 1023 2129 1070 2129 1061 2129 1070 2130 1071 2130 1061 2130 1064 2131 1062 2131 1071 2131 1061 2132 1071 2132 1062 2132 1039 2133 1074 2133 1028 2133 1028 2134 1074 2134 1073 2134 1075 2135 1076 2135 1079 2135 1039 2136 1080 2136 1074 2136 1038 2137 1081 2137 1025 2137 1038 2138 1082 2138 1081 2138 1076 2135 1083 2135 1079 2135 1025 2139 1081 2139 1031 2139 1037 2140 1082 2140 1038 2140 1084 2141 1069 2141 1094 2141 1094 2142 1069 2142 1068 2142 1031 2143 1081 2143 1032 2143 1039 2144 1030 2144 1080 2144 1037 2145 1085 2145 1082 2145 1077 2146 1086 2146 1078 2146 1081 2147 1087 2147 1032 2147 1066 2148 1087 2148 1014 2148 1032 2149 1087 2149 1066 2149 1083 2150 1088 2150 1079 2150 1084 2151 1070 2151 1069 2151 1030 2152 1027 2152 1080 2152 1086 2153 1070 2153 1078 2153 1078 2154 1070 2154 1084 2154 1027 2155 1040 2155 1080 2155 1080 2156 1040 2156 1083 2156 1086 2157 1071 2157 1070 2157 1083 2158 1040 2158 1088 2158 1040 2159 1065 2159 1088 2159 1037 2160 1089 2160 1085 2160 1036 2161 1089 2161 1037 2161 1035 2162 1089 2162 1036 2162 1088 2163 1064 2163 1086 2163 1086 2164 1064 2164 1071 2164 1014 2165 1090 2165 1072 2165 1087 2166 1090 2166 1014 2166 1088 2167 1065 2167 1064 2167 1081 2135 1091 2135 1087 2135 1092 2135 1091 2135 1081 2135 1035 2168 1073 2168 1089 2168 1033 2169 1073 2169 1035 2169 1089 2135 1093 2135 1085 2135 1085 2135 1091 2135 1092 2135 1085 2170 1093 2170 1091 2170 1090 2171 1067 2171 1072 2171 1033 2172 1028 2172 1073 2172 1096 2135 1094 2135 1090 2135 1095 2173 1094 2173 1096 2173 1090 2174 1094 2174 1067 2174 1067 2175 1094 2175 1068 2175 1096 2176 1078 2176 1095 2176 1077 2177 1078 2177 1096 2177 1075 2178 1074 2178 1076 2178 1073 2179 1074 2179 1075 2179 1084 2180 1098 2180 1099 2180 1078 2181 1084 2181 1099 2181 1078 2182 1099 2182 1100 2182 1078 2183 1100 2183 1101 2183 1095 2184 1078 2184 1101 2184 1098 2185 1134 2185 1144 2185 1098 2186 1144 2186 1103 2186 1099 2187 1098 2187 1103 2187 1099 2188 1103 2188 1104 2188 1104 2189 1100 2189 1099 2189 1100 2190 1104 2190 1101 2190 1104 2191 1103 2191 1102 2191 1102 2192 1103 2192 1144 2192 1080 2193 1105 2193 1106 2193 1080 2194 1083 2194 1105 2194 1080 2195 1106 2195 1107 2195 1080 2196 1107 2196 1108 2196 1074 2197 1080 2197 1108 2197 1074 2198 1108 2198 1109 2198 1108 2199 1107 2199 1111 2199 1111 2200 1109 2200 1108 2200 1111 2201 1107 2201 1145 2201 1111 2202 1112 2202 1109 2202 1110 2203 1112 2203 1111 2203 1145 2204 1110 2204 1111 2204 1092 2205 1120 2205 1113 2205 1085 2206 1113 2206 1114 2206 1085 2207 1092 2207 1113 2207 1085 2208 1114 2208 1115 2208 1085 2209 1115 2209 1116 2209 1082 2210 1085 2210 1116 2210 1119 2211 1118 2211 1146 2211 1119 2212 1146 2212 1120 2212 1121 2213 1120 2213 1146 2213 1121 2214 1114 2214 1113 2214 1120 2215 1121 2215 1113 2215 1079 2216 1088 2216 1122 2216 1122 2217 1088 2217 1123 2217 1079 2218 1122 2218 1124 2218 1075 2219 1079 2219 1124 2219 1125 2220 1075 2220 1124 2220 1073 2221 1075 2221 1125 2221 1126 2222 1089 2222 1125 2222 1089 2223 1073 2223 1125 2223 1093 2224 1089 2224 1127 2224 1127 2225 1089 2225 1126 2225 1091 2226 1127 2226 1128 2226 1091 2227 1093 2227 1127 2227 1129 2228 1091 2228 1128 2228 1087 2229 1091 2229 1129 2229 1130 2230 1087 2230 1129 2230 1090 2231 1087 2231 1130 2231 1096 2232 1130 2232 1097 2232 1096 2233 1090 2233 1130 2233 1096 2234 1097 2234 1131 2234 1077 2235 1096 2235 1131 2235 1086 2236 1131 2236 1132 2236 1086 2237 1077 2237 1131 2237 1123 2238 1086 2238 1132 2238 1088 2239 1086 2239 1123 2239 1095 2240 1101 2240 1133 2240 1094 2241 1095 2241 1133 2241 1094 2242 1133 2242 1134 2242 1094 2243 1134 2243 1098 2243 1084 2244 1094 2244 1098 2244 1076 2245 1109 2245 1112 2245 1076 2246 1074 2246 1109 2246 1076 2247 1112 2247 1135 2247 1083 2248 1076 2248 1135 2248 1083 2249 1135 2249 1105 2249 1082 2250 1116 2250 1117 2250 1081 2251 1117 2251 1118 2251 1081 2252 1082 2252 1117 2252 1081 2253 1118 2253 1119 2253 1081 2254 1119 2254 1120 2254 1092 2255 1081 2255 1120 2255 1127 2135 1136 2135 1128 2135 1122 2135 1132 2135 1131 2135 1122 2135 1123 2135 1132 2135 1122 2256 1131 2256 1139 2256 1128 2135 1130 2135 1129 2135 1128 2135 1097 2135 1130 2135 1141 2257 1131 2257 1097 2257 1141 2258 1097 2258 1140 2258 1142 2259 1136 2259 1127 2259 1124 2260 1142 2260 1127 2260 1128 2261 1140 2261 1097 2261 1143 2262 1131 2262 1141 2262 1125 2263 1124 2263 1127 2263 1125 2264 1127 2264 1126 2264 1136 2265 1140 2265 1128 2265 1139 2266 1142 2266 1124 2266 1139 2267 1124 2267 1122 2267 1139 2268 1131 2268 1143 2268 1101 2269 1104 2269 1102 2269 1133 2270 1101 2270 1102 2270 1133 2271 1102 2271 1144 2271 1144 2272 1134 2272 1133 2272 1110 2273 1135 2273 1112 2273 1135 2274 1110 2274 1145 2274 1145 2275 1105 2275 1135 2275 1145 2276 1106 2276 1105 2276 1106 2277 1145 2277 1107 2277 1115 2278 1121 2278 1116 2278 1115 2279 1114 2279 1121 2279 1146 2280 1116 2280 1121 2280 1146 2281 1118 2281 1117 2281 1116 2282 1146 2282 1117 2282 1147 2283 1140 2283 1136 2283 1137 2284 1147 2284 1136 2284 1147 2285 1138 2285 1143 2285 1141 2286 1147 2286 1143 2286 1142 2287 1148 2287 1136 2287 1149 2288 1139 2288 1143 2288 1150 2289 1149 2289 1143 2289 1150 2290 1143 2290 1138 2290 1150 2291 1138 2291 1137 2291 1148 2292 1150 2292 1137 2292 1148 2293 1137 2293 1136 2293 1147 2294 1141 2294 1140 2294 1137 2295 1138 2295 1147 2295 1148 2296 1142 2296 1150 2296 1150 2297 1139 2297 1149 2297 1142 2298 1139 2298 1150 2298
+
+
+
+
+
+
+ 2.01478e-4 -0.09650927 -0.01928788 0.001418471 -0.09399372 -0.02006733 0.00194329 -0.09633708 -0.0176292 5.60637e-4 -0.1039075 -0.006619811 1.39895e-4 -0.09730553 -0.01308649 0.001500368 -0.0884034 -0.02148753 0.001647472 -0.09698545 -0.01792258 0.00173527 -0.1021295 -0.01057893 0.001123428 -0.09673684 -0.01873165 9.82738e-4 -0.09901046 -0.01690161 0.001683056 -0.09920328 -0.01572144 0.001909375 -0.09370738 -0.01932621 0.001931965 -0.08773255 -0.02076143 0.00199753 -0.09734463 -0.01598441 0.002003252 -0.1012304 -0.01053082 0.0019418 -0.09905636 -0.0149945 0.001183927 -0.09699088 -0.01406759 0.001883149 -0.09769099 -0.01474255 0.002003312 -0.09318274 -0.0189132 0.001993954 -0.08772176 -0.01988041 8.65165e-4 -0.09507972 -0.01994282 0.001953065 -0.1008347 -0.009739577 -4.04078e-4 -0.08809405 -0.01816385 0.001344621 -0.1034662 -0.006599426 0.001897633 -0.09296935 -0.01818627 7.10192e-5 -0.09602826 -0.01451802 9.58795e-4 -0.09521114 -0.01557844 4.05031e-4 -0.09398728 -0.02066344 1.17352e-4 -0.1035446 -0.009197056 0.002018392 -0.1019958 -0.007576227 0.001745939 -0.102891 -0.007282257 0.001683712 -0.09462267 -0.01680403 7.64986e-4 -0.1031787 -0.0100243 0.001814842 -0.1010777 -0.006338477 0.001256108 -0.1028296 -0.0101422 0.001125693 -0.1002805 -0.006457388 5.31216e-4 -0.09461206 -0.01579821 1.65612e-4 -0.09178394 -0.02150905 -5.90576e-5 -0.09994214 -0.006241202 0.00113368 -0.09121179 -0.02132898 1.34076e-4 -0.0928055 -0.01684105 8.94276e-4 -0.09345036 -0.01668107 0.001745522 -0.1006504 -0.008390307 2.64757e-4 -0.0998218 -0.007431149 0.001666963 -0.09121203 -0.02074635 8.24713e-5 -0.1026694 -0.01183974 0.001317918 -0.09999537 -0.008803129 4.81123e-4 -0.08977782 -0.02192199 0.001943171 -0.09110486 -0.02016115 5.57317e-4 -0.1017385 -0.01359754 9.7214e-4 -0.09193426 -0.0174545 0.001541197 -0.1012319 -0.01319605 5.7004e-4 -0.09946304 -0.009154915 0.001942992 -0.0898838 -0.01941132 0.00159049 -0.0908845 -0.01841223 0.001959085 -0.1006067 -0.01258111 -2.90257e-4 -0.09958225 -0.008543789 4.15741e-5 -0.08786255 -0.02217179 1.96058e-4 -0.100077 -0.01605987 0.001007318 -0.1005595 -0.01501458 0.00176692 -0.09974652 -0.01096385 7.9116e-4 -0.08781743 -0.02201879 0.001070201 -0.08919131 -0.01833873 9.59182e-4 -0.09848564 -0.01178973 -4.17479e-4 -0.08993732 -0.01787823 4.31024e-4 -0.09832519 -0.01166975 5.31994e-4 -0.09012717 -0.01786392 0.001688182 -0.08813482 -0.01903921 0.001793861 -0.09857255 -0.01328212 0.001506209 -0.09820872 -0.01305681 4.00896e-5 -0.09798157 -0.01823014 6.01227e-4 -0.08820676 -0.01821184 -0.001122713 -0.09673672 0.01838308 -6.2472e-5 -0.09777891 0.01804769 -0.001682341 -0.09920328 0.01537281 -2.79486e-4 -0.1039465 0.006315112 4.22278e-4 -0.09767806 0.01232808 -7.43646e-4 -0.09774702 0.0124011 -0.001646757 -0.09698551 0.01757407 -9.81997e-4 -0.09901046 0.01655298 -0.001694381 -0.1020403 0.01061034 -0.001995742 -0.1010382 0.01094472 -0.002000272 -0.09829813 0.01502978 -0.001948893 -0.09707993 0.01538532 -0.001166105 -0.09624737 0.01446533 -0.001975417 -0.1007321 0.009766876 -0.001802444 -0.09720957 0.01459968 -9.11043e-4 -0.1037716 0.006079554 -1.41085e-4 -0.09605133 0.01414376 -0.001698911 -0.1030676 0.005905628 -0.001321554 -0.1033604 0.007096827 -1.08991e-4 -0.1035463 0.008845806 -0.001997232 -0.102176 0.006441295 -0.001062512 -0.1030824 0.009495973 -0.001950383 -0.1013963 0.007032275 -0.001656413 -0.1007189 0.007044434 -0.001832485 -0.1025014 0.008178889 -0.001231074 -0.100358 0.005998611 -4.21097e-4 -0.1028966 0.01080083 -3.53223e-4 -0.09998482 0.005894601 2.15917e-4 -0.1026326 0.0115906 -8.10576e-4 -0.1019778 0.01255548 -9.79746e-4 -0.0997039 0.008661329 -4.49004e-4 -0.09966719 0.007983922 -0.001540482 -0.1012319 0.01284742 -0.001409113 -0.0996769 0.009722709 -2.1419e-5 -0.1012635 0.01416409 -0.001897871 -0.1003902 0.01300293 2.89281e-4 -0.09958195 0.008194804 -1.9828e-4 -0.1000794 0.01570779 -0.001006603 -0.1005595 0.01466614 -0.0019508 -0.09939277 0.01246309 1.18067e-4 -0.09886169 0.01011919 -3.20684e-4 -0.09810948 0.01163125 -0.001571655 -0.09826511 0.01271092 0.002040684 -0.08099997 -0.02017432 -0.001966774 -0.08099997 -0.02071636 -0.00126332 -0.08099997 -0.02175331 2.81818e-4 -0.08099997 -0.02224934 0.001639485 -0.08099997 -0.02135854 0.001443028 -0.1033757 0.006306588 0.001943886 -0.1024528 0.006736874 -0.002010822 -0.1017597 -0.007161736 -0.001864612 -0.1027277 -0.00634253 -0.001328766 -0.1034813 -0.006633222 -4.65197e-4 -0.1039047 -0.006850302 5.29436e-4 -0.10391 0.006335437 0.001630604 -0.09946954 0.01520287 0.001581013 -0.1018044 0.01160699 0.001895606 -0.1017298 0.01018846 0.001890659 -0.09850668 0.01550078 0.0010764 -0.09941047 0.01604086 4.16946e-4 -0.09963095 0.01618188 0.001954317 -0.09729939 0.01514786 0.00200808 -0.09740984 0.0159015 0.002004683 -0.1013709 0.009030818 6.47287e-4 -0.1000525 0.005991518 0.00130558 -0.1004391 0.006103575 0.001657128 -0.1007595 0.006956577 0.001810133 -0.1002607 0.009693026 0.001895606 -0.1012312 0.007030606 0.001825392 -0.09749513 0.01431924 8.61524e-4 -0.09982812 0.007980585 0.001029551 -0.09718012 0.01811951 0.00133574 -0.09992974 0.008811533 0.001229882 -0.09687757 0.01394504 0.00163877 -0.1027129 0.008680105 9.57846e-4 -0.09905236 0.01032054 9.26486e-4 -0.1033169 0.008862495 0.001223862 -0.09861356 0.01156955 0.001665294 -0.0988748 0.01209878 0.001978874 -0.09948581 0.01246166 0.001942396 -0.100624 0.01237756 0.00107336 -0.1024111 0.01133519 7.58029e-4 -0.09608691 0.014328 9.80704e-4 -0.1012123 0.01374012 -0.001795411 -0.1009984 -0.007325232 -0.00129348 -0.100396 -0.006473779 -0.001467466 -0.08805084 -0.02153122 -0.001332104 -0.09633547 -0.01491379 -0.001339495 -0.09949839 -0.01598078 -4.38746e-4 -0.09462642 -0.01575094 -0.001509726 -0.1021512 -0.01136523 -0.001759052 -0.09912067 -0.01559662 -0.001401424 -0.09304118 -0.02050834 -0.001760125 -0.09369087 -0.01969087 -9.05586e-4 -0.09442025 -0.02026355 -0.001336932 -0.09448581 -0.01641601 -0.001873016 -0.1019899 -0.01002496 -5.32979e-4 -0.09990465 -0.01616913 -0.001941025 -0.09566491 -0.01682364 -0.001759529 -0.08883523 -0.02108716 -0.001998186 -0.1012525 -0.0106101 -0.001976907 -0.09845119 -0.01540488 -0.002003729 -0.09352439 -0.01894485 -0.001842916 -0.09681743 -0.01763826 -0.001998722 -0.08736026 -0.02040517 -0.001994729 -0.09805667 -0.01487743 -8.93731e-4 -0.09343934 -0.01668739 -0.001912355 -0.1004065 -0.01037395 -0.001166999 -0.09796196 -0.01772356 -0.001901507 -0.09415072 -0.01764661 -9.80967e-4 -0.1000648 -0.007441282 -0.001949191 -0.08760732 -0.01969629 -8.36334e-4 -0.09186679 -0.01740705 -0.001604676 -0.09218513 -0.01796025 -0.001451969 -0.0999183 -0.00966078 -0.001519322 -0.09580093 -0.01894485 -6.6366e-4 -0.09649157 -0.01918607 -9.98944e-4 -0.09957033 -0.009416222 -0.001633465 -0.1028133 -0.008623421 -6.40294e-4 -0.09889179 -0.01065087 -0.001854956 -0.09012794 -0.01909619 -0.001332223 -0.09022605 -0.01827198 -0.001129806 -0.1029881 -0.009964525 -3.85311e-4 -0.0938825 -0.02070432 -5.78044e-4 -0.1032167 -0.0101071 -5.30497e-4 -0.09801465 -0.01220893 -0.001878142 -0.09068983 -0.02048671 -0.001336753 -0.09812241 -0.01283586 -0.001390755 -0.08826082 -0.01865816 -0.001898527 -0.1003476 -0.0134148 -0.001910746 -0.09898805 -0.01311028 -7.10211e-4 -0.09125018 -0.02153551 -8.58028e-4 -0.09667056 -0.01417243 -6.49685e-4 -0.1022612 -0.01247745 -0.001261711 -0.08978378 -0.02151781 -0.001519501 -0.1007606 -0.01399189 -6.54156e-4 -0.1012892 -0.01424103 -6.78687e-4 -0.08849036 -0.02203637 0.001639485 -0.08099997 -0.01898974 2.81805e-4 -0.08099997 -0.018099 -0.001694977 -0.08099997 -0.01894575 0.001288414 -0.09672689 0.01820081 -0.001680552 -0.09617227 0.01522505 -0.001115739 -0.09603321 0.0146045 7.3151e-4 -0.09595912 0.01442146 -1.75009e-4 -0.09594029 0.01424348 0.001769304 -0.09614932 0.01544392 -0.00165683 -0.09668666 0.01772367 -0.001948714 -0.09655058 0.0170353 2.45773e-4 -0.09684038 0.01865184 0.001828253 -0.09619694 0.01568049 0.001814663 -0.09655815 0.01743227 -1.6137e-4 -0.09685736 0.01869505 0.002006947 -0.09638363 0.01660346 0.001506209 -0.09608227 0.01509308 -0.001634418 -0.09669595 0.01777094 -0.001921415 -0.0965687 0.01712614 0.001525521 -0.09666365 0.0179165 8.63157e-4 -0.09679132 0.01847273 -0.001810967 -0.0966252 0.01741087 -1.02043e-4 -0.09593838 0.01424103 -0.001193702 -0.09604555 0.01465702 8.1192e-4 -0.09596723 0.01446849 0.001966059 -0.09642261 0.01678889 0.001868844 -0.09651637 0.0172345 0.001036763 -0.09676671 0.01836973 0.001487851 -0.09667831 0.01798409 -0.001808047 -0.09626775 0.0156762 0.00126636 -0.09601187 0.01472872 -0.001958847 -0.09632986 0.01596343 -0.002000749 -0.09639614 0.01628088 -0.001920223 -0.09629845 0.0158146 0.001988053 -0.0963431 0.01640528 0.00194782 -0.09623217 0.01586306
+
+
+
+
+
+
+
+
+
+ 0.4760167 -0.3357164 -0.8128362 0.9237204 -0.2668175 -0.2748618 0.3545836 0.7652994 0.5372034 0.3564854 -0.5991145 -0.7169241 0.2542089 0.8089997 0.5299977 0.9935095 -0.07943218 -0.08142089 0.6475098 -0.5017401 -0.5735747 0.3130326 -0.5765289 -0.7547351 0.8299185 0.4695996 0.3011838 0.8086919 0.4804141 0.3394405 0.3442769 -0.5184457 -0.7827437 0.361832 0.7041809 0.6109066 0.9527007 0.2841764 0.1077274 0.02974092 -0.9890738 -0.1443905 0.1944815 -0.4817556 -0.8544521 0.6241743 -0.3952772 -0.6739157 0.979272 0.1529048 0.1328412 0.7277346 -0.3619443 -0.5825794 0.2657379 0.6432265 0.7180829 0.2960004 -0.9375098 -0.1829189 0.8092726 0.3629409 0.4619003 0.487675 -0.8592162 -0.154663 0.5473037 0.5808068 0.6025962 0.5457471 -0.8222628 -0.1613811 0.8479096 -0.2644777 -0.4594573 0.9973086 -0.04311692 -0.05930042 0.8938103 -0.2349628 -0.3819628 0.727128 -0.6722901 -0.1389641 0.9467939 -0.3128981 -0.07534027 0.3845403 0.5471279 0.7434917 0.02649319 0.6655984 0.7458398 0.1312811 -0.9405613 -0.3132249 0.988516 -0.07273256 -0.1324615 0.9893762 0.08274441 0.1195331 0.7718148 -0.6189817 -0.1454778 0.3446015 -0.3034716 -0.8883439 0.03453874 0.5053212 0.86224 0.4785836 -0.3215482 -0.8170462 0.8862054 0.2242529 0.405402 0.9873277 0.1503217 0.05086666 0.6905132 0.4018834 0.6013994 0.7433939 0.6593746 0.1122088 0.8762882 0.4767227 0.06967455 0.2999415 0.9375021 0.176423 0.3071027 0.5131303 0.8014894 0.7186954 -0.2248265 -0.6579742 0.8506987 -0.1951469 -0.4880876 0.7874988 0.6007714 0.1375483 0.2762428 0.5253096 0.8048229 0.9050549 -0.1405318 -0.4014056 0.3342956 -0.8672413 -0.3689703 0.995783 0.08991873 0.01819062 0.3119566 -0.2383745 -0.9197069 0.9565702 -0.2809603 -0.07768416 0.4850669 -0.8009365 -0.3510144 0.987154 -0.06028771 -0.1479611 0.785484 0.3339599 0.5210428 0.5378783 -0.7665512 -0.3508365 0.5275942 0.8370332 0.1449825 0.6096414 0.3351498 0.7183398 0.7038766 -0.6542598 -0.2765898 0.2336054 0.9428534 0.2376048 0.9904119 0.03815495 0.1327723 0.7255277 0.178909 0.6645308 0.1872211 0.4387484 0.8788902 0.9545275 -0.2788701 -0.1053984 0.01038539 0.978598 0.2055194 0.7438513 0.6286256 0.2269698 0.7310249 -0.1332296 -0.6692178 0.9880828 0.1532865 -0.01398319 0.5455634 -0.721238 -0.4268213 0.8579967 0.1785984 0.4816061 0.5067818 -0.1222842 -0.8533575 0.8945253 -0.3982983 -0.2029362 0.5318123 -0.1167925 -0.8387702 0.9070175 -0.07087248 -0.4150859 0.8843711 -0.06934934 -0.4616042 0.2039878 -0.0804364 -0.9756634 0.2644819 -0.7809796 -0.5657918 0.7159897 0.1908865 0.6715066 0.8995442 -0.3645714 -0.2406409 0.7456646 0.6264301 0.2270898 0.1588277 0.9027889 0.3996825 0.8405681 0.0958634 0.5331564 0.9978924 -0.009654343 -0.06416839 0.9975 -0.009158372 -0.07007145 0.3112852 0.281493 0.9076692 0.7069296 -0.5702702 -0.4183808 0.3410592 -0.7243949 -0.5991083 0.4171406 0.8302955 0.3695991 0.923545 0.05376696 0.3797025 0.3527138 0.287672 0.8904145 0.05573946 0.3489987 0.9354641 0.3460865 0.86182 0.3707972 0.8293128 0.4945666 0.2600856 0.9715285 0.1942965 0.135578 0.8970893 -0.3677133 -0.2449854 -0.8704219 0.4432056 0.2143234 0.6560819 -0.5791002 -0.4839417 0.6193768 0.6674985 0.4133014 0.6132175 0.1548349 0.7745905 0.9980686 -0.05009257 -0.03673911 0.5130018 0.1350559 0.8476964 0.20855 -0.6960508 -0.6870372 0.9288877 0.05748724 0.3658729 0.02072167 0.1774775 0.9839068 0.9670699 0.2077553 0.1470154 0.06410223 0.1523855 0.9862402 0.9973083 -0.05823493 -0.04455244 0.713425 -0.487819 -0.5030481 0.7185426 0.5468298 0.4297367 0.9121443 -0.2812597 -0.2981374 -0.9415512 -0.241629 0.2347269 -0.3616564 -0.5980541 0.7152175 -0.08110588 0.8838425 -0.4607 -0.647385 -0.5018017 0.5736618 -0.3177062 -0.5675977 0.7595365 -0.7992887 0.4776842 -0.3646306 -0.5807747 0.5974773 -0.552921 -0.01996976 0.97929 0.2014753 -0.9973562 -0.04013001 0.06058198 -0.3275557 0.731265 -0.5982967 -0.08561974 0.7287026 -0.6794571 -0.8784007 0.4672041 -0.1006619 0.04069632 -0.987327 0.153393 -0.6696492 -0.7420819 0.02974086 -0.725995 -0.3653327 0.5826349 -0.3355183 -0.926723 0.1691509 -0.3224003 -0.9305799 0.1734341 -0.9460234 0.2414262 -0.2162249 -0.4853389 -0.8609824 0.1521692 -0.7353987 0.4110829 -0.5387018 -0.9429312 -0.331871 0.02725124 -0.7923403 -0.6012871 0.1032018 -0.5254853 0.5470739 -0.6515945 -0.8970617 -0.2218143 0.3822027 -0.7505574 -0.6422591 0.1554573 -0.8905048 -0.2275152 0.3940029 -0.3433222 0.5672935 -0.7485373 -0.04188996 0.6604759 -0.7496779 -0.9636241 0.1549516 -0.2177584 -0.2700228 -0.9257965 0.2645535 -0.7679077 0.3297805 -0.5491471 -0.7566626 -0.6331483 0.1630491 -4.67773e-4 0.9321833 0.3619863 -0.1635403 -0.8884179 0.4289154 -0.9962239 0.08183723 -0.02899712 -0.7597344 0.6444408 -0.08660078 -0.9402055 -0.3208732 0.1142544 -0.9763154 -0.2096843 0.05329936 -0.4438165 -0.8305718 0.3364187 -0.3168235 0.9275965 -0.1979586 -0.9115939 0.3974857 -0.1048887 -0.9993772 0.03429228 -0.008333444 -0.6151063 -0.7240567 0.3120678 -0.4020197 0.9023129 -0.1556009 -0.6391325 -0.7082169 0.2998974 -0.571641 0.7811306 -0.2511212 -0.1328067 -0.880047 0.4559385 -0.9323402 -0.3298265 0.1481764 -0.3130909 -0.8014878 0.5095013 -0.999359 0.03485178 -0.008187949 -0.5571517 -0.7122847 0.426887 -0.8851667 -0.4147895 0.2107834 -0.2678115 -0.7790539 0.5668793 -0.007809519 0.9362943 -0.3511299 -0.3691653 0.8976798 -0.2405994 -0.6096775 0.7468132 -0.2656378 -0.7069748 -0.5702362 0.4183509 -0.3417598 -0.7241657 0.5989861 -0.9236411 0.3465364 -0.1637063 -0.8556697 0.4484034 -0.2583871 -0.8611314 -0.4188689 0.2880998 -0.6560977 -0.5790697 0.4839569 -0.06805187 0.8852373 -0.4601347 -0.5883945 0.7182608 -0.37134 -0.2546022 0.8134884 -0.5228903 -0.1981137 -0.693188 0.6929945 -0.997942 0.05273634 -0.03647911 -0.9921658 0.09827458 -0.07713222 -0.9444044 -0.2514558 0.2118265 -0.9868151 -0.1349195 0.08940291 -0.8782982 0.3635617 -0.310508 -0.713389 -0.4878634 0.5030561 -0.9346672 0.2790737 -0.2202618 -0.9122614 -0.2808742 0.2981424 0.9470506 0.01268482 -0.3208338 0.9974926 -0.009998023 -0.07006162 -0.9504343 -0.01043289 -0.3107504 -0.8275149 0.006275951 -0.5614088 -0.8383721 0.003620684 -0.5450865 -0.5434941 -0.01070171 -0.8393449 -0.3056391 0.01212304 -0.9520703 -0.1807517 -0.00478661 -0.9835171 0.2010018 -0.01810437 -0.9794237 0.5485526 0.00425291 -0.8361054 0.6092874 0.01871252 -0.7927287 0.8625712 -0.007398605 -0.5058816 0.818876 -0.5739664 -0.002219915 0.8784134 -0.4778951 0.002534508 0.9562641 -0.2924718 -0.004360795 -0.9883136 -0.15239 -0.003676295 -0.9475998 -0.3194358 0.003955006 0.9980183 -0.06284385 0.003188669 -0.815882 -0.5781967 -0.005007863 -0.6668954 -0.7451353 0.004896819 -0.4414318 -0.8972749 -0.005984067 -0.2671382 -0.963658 7.08701e-4 -0.002023875 -0.9999931 -0.003145396 0.04507845 -0.9989835 -8.46115e-5 0.4905065 -0.871437 0.001013696 0.5048854 -0.8631837 0.00220251 0.4524161 0.5857086 -0.6725067 0.2793011 -0.7752425 0.5665597 0.3678087 -0.7394751 0.5638203 0.4438283 0.5909826 -0.6736142 0.09929478 -0.784011 0.612754 0.8048502 0.3792936 -0.4564564 0.9572035 -0.1840575 0.2233482 0.0808562 0.9859482 -0.1461798 0.8192614 0.3226607 -0.4740262 0.8291344 -0.3769255 0.4128721 0.8480912 -0.3515906 0.3963904 -0.05073869 0.9749712 -0.2164641 0.51958 0.8410015 -0.150842 0.3458743 0.9274913 -0.1418837 0.7245191 -0.4617226 0.5117464 -0.01926386 0.9792208 0.2018801 0.4782342 0.8621139 -0.1674869 0.3561831 -0.6158234 0.7027769 0.3078342 -0.6452146 0.6992399 -0.01929992 0.9792411 0.2017782 0.8092398 0.5756508 -0.1172918 -0.01929694 0.9792446 0.2017619 0.9743958 -0.1259856 0.1862269 0.9948387 0.05799847 -0.08326023 0.2688856 -0.5820749 0.7673913 0.9738656 -0.1275422 0.1879329 0.1104122 -0.5633147 0.818832 0.5473902 -0.4403038 0.7116858 0.2741227 0.9094132 -0.3127689 0.2812137 0.9067019 -0.3143418 0.7467548 0.6457033 -0.1594511 0.8630996 -0.5003607 0.06854581 0.6268868 0.7314569 -0.268298 0.8930037 0.4312996 -0.1285501 -0.01928472 0.9792298 0.2018346 0.6615265 -0.734645 0.1505974 0.5039502 -0.8551418 0.1215189 0.9190919 -0.382897 0.0930587 0.9799067 0.1954482 -0.03978514 0.4116259 0.8288765 -0.3788508 0.9872474 -0.1524787 0.04574877 0.8201356 0.5157573 -0.2477342 0.7136828 0.6429887 -0.2778891 -0.07916975 0.8831619 -0.4623389 0.2093091 -0.9587871 0.1921384 0.913596 0.373703 -0.1602765 0.302281 0.8222437 -0.4822255 0.8616617 -0.4796087 0.165876 0.9613844 0.2441863 -0.1269372 0.5278543 0.7598974 -0.3793758 0.6630578 -0.7147531 0.2224466 0.6950481 -0.6826493 0.2256063 0.9990807 -0.0339533 0.02617144 0.9888709 -0.1402298 0.04969942 0.3230278 -0.8946796 0.3085473 0.00195688 -0.9488783 0.3156362 0.2027335 -0.936048 0.2875995 0.9018145 -0.3931581 0.1793249 0.3574825 0.753592 -0.5516389 0.7646957 0.5193398 -0.3814797 0.1593281 0.7593307 -0.6308975 0.6258386 -0.6881412 0.3671345 0.7703465 0.5152046 -0.3756738 0.5495967 0.6063525 -0.5747001 0.8546307 -0.4407938 0.2744214 0.9130047 0.3137257 -0.2607657 0.9993953 -0.03286796 0.01135081 0.3436716 -0.8351064 0.4295197 0.728832 -0.5788064 0.3657694 0.2433565 -0.8468165 0.4729475 0.9185917 -0.3198893 0.2320784 0.9402999 0.2681195 -0.209638 0.9947869 0.08335208 -0.05874991 0.9821681 -0.1476598 0.1163724 -0.01927191 0.979228 0.2018445 0.7576742 0.436786 -0.4849203 0.63153 -0.6229674 0.4616077 0.182779 0.6048204 -0.7751026 -0.0213834 -0.7123087 0.7015405 -0.6586947 0.752365 -0.008262813 -0.9619904 0.2730541 0.003993988 -0.9172834 0.3981856 -0.00628823 0.9753221 0.2207617 -0.00335735 -0.9983195 0.0578857 0.002768874 0.8888719 0.4578589 -0.0164951 0.8924443 0.4511575 -2.11465e-4 0.7567011 0.653756 -0.00258243 0.6857833 0.7277467 -0.009282112 0.505944 0.8625588 0.003647089 0.2734098 0.9618714 -0.007109642 0.06699651 0.99774 0.00513333 -0.3442428 0.9388672 -0.005042135 -0.3913124 0.9202576 -8.45878e-4 -0.7695869 0.6385393 0.001904904 -0.4767 -0.6995636 -0.5323231 -0.6104906 0.4983428 0.6155938 -0.408261 0.6089209 0.6801016 -0.9313615 0.2421567 0.2718932 -0.1026956 -0.8048965 -0.5844617 -0.9996266 -0.01932644 -0.01932293 -0.9035056 0.2226113 0.3662266 -0.7976944 0.3789256 0.4691472 -0.9425391 -0.240176 -0.2322405 -0.219447 0.5510243 0.8051182 -0.7889111 -0.4234527 -0.4453172 -0.781466 -0.4276395 -0.4543517 -0.3702852 0.9089545 0.191548 -0.5005373 0.4097535 0.7626039 -0.9859693 -0.1009113 -0.1329714 -0.9890553 0.1058495 0.1027889 -0.1223223 0.4167268 0.9007642 0.1450759 0.9796711 0.1385557 -0.4653314 -0.6406042 -0.6108133 -0.355166 0.9174916 0.1790713 -0.3018345 -0.6537395 -0.6939169 -0.06676876 -0.7201892 -0.6905574 -0.4532604 0.8779507 0.154135 -0.8400406 0.5288479 0.1210442 -0.274708 0.932148 0.2358721 -0.7582989 -0.4100507 -0.5067949 -0.5504121 0.4202023 0.7214407 -0.6381672 0.339538 0.6909824 -0.3140987 -0.6133202 -0.7246932 -0.5707363 -0.4725693 -0.6715195 -0.6601253 0.7272264 0.1880865 -0.8677387 0.2034125 0.4534897 -0.9881932 0.09226548 0.1223171 -0.1073229 -0.5723034 -0.8129886 -0.8246113 -0.5622403 -0.06246685 -0.1738769 0.9268964 0.3326107 -0.9809306 -0.1917397 -0.03179562 -0.9837183 -0.176635 -0.03314501 -0.9487506 -0.1554978 -0.2751233 -0.2645222 0.2825643 0.9220551 -0.8795199 -0.2446634 -0.4081479 -0.802946 0.2102663 0.5577328 -0.5515709 -0.4347204 -0.7118903 -0.4102782 0.8442623 0.3448088 -0.9140266 -0.3981462 -0.07768535 -0.4559113 0.3032829 0.8367582 -0.8145274 0.5628488 0.1405221 -0.7709748 -0.288675 -0.5676837 -0.6538707 -0.7418146 -0.1488767 -0.6266313 0.206691 0.7514067 -0.9526804 0.2894796 0.09274554 -0.6503298 -0.3532299 -0.6725323 -0.4662727 -0.8706432 -0.1567483 -0.9951325 0.09795743 0.01076143 -0.9936835 0.04804283 0.1014149 -0.6630924 0.7204029 0.2032934 -0.8165369 0.1997287 0.541642 -0.2350834 -0.4700013 -0.8507847 -0.4150084 -0.8929737 -0.1742583 -0.01787948 -0.4776584 -0.8783637 -0.1116645 -0.4908838 -0.8640395 0.006082892 0.8712036 0.4908844 -0.7337929 -0.6520941 -0.1905815 -0.1963016 -0.9607247 -0.1961473 -0.8367527 -0.522468 -0.1639269 -0.9516674 -0.1131438 -0.2855303 -0.4324851 0.798386 0.4189706 -0.4622138 0.1390945 0.8757918 -0.4327329 0.1614022 0.8869564 -0.07313454 0.802295 0.5924307 -0.4671221 0.7767727 0.4223991 -0.9950645 0.08833503 0.04520541 -0.8471097 -0.1670913 -0.5044658 -0.8422278 0.4817847 0.2419421 -0.8556952 0.4596267 0.2377582 -0.9598016 -0.2553625 -0.1164941 -0.8372777 0.08131337 0.5406979 -0.03679692 -0.948797 -0.3137359 -0.8913491 -0.4105563 -0.1921986 -0.4460334 -0.3094744 -0.8398094 -0.1077325 0.8119013 0.5737683 -0.3972857 -0.3205399 -0.8598943 -0.5106759 0.6673763 0.5420509 -0.9206205 0.05752682 0.3861978 8.15347e-4 -0.3582078 -0.9336416 -0.524141 -0.7861263 -0.3275386 -0.7716242 -0.150741 -0.6179589 -0.9969115 0.01886892 0.07623273 -0.9895953 -0.03240489 -0.1401827 -0.4325259 -0.8406959 -0.3258095 -0.7936645 -0.1487549 -0.5898886 -0.8580511 -0.4551939 -0.2377958 -0.1933536 -0.9119932 -0.3617773 -0.1639513 -0.3153586 -0.9347026 -0.5951288 -0.2144734 -0.7744824 -0.9903481 -0.1177151 -0.07316952 -0.2527814 0.7158476 0.6508947 -0.8520309 0.09088438 0.5155419 -0.7475586 -0.09384697 -0.6575325 -0.5592848 -0.7334935 -0.3862484 -0.5919899 -0.7064721 -0.3878727 -0.9650184 0.210231 0.1566604 -0.08503693 -0.1880368 -0.9784739 -0.9996077 -0.02202415 -0.01730376 -0.4414123 -0.1553443 -0.8837553 -0.4345232 0.6833646 0.5866876 -0.9606636 -0.02802371 -0.2762973 -0.09943902 -0.176581 -0.9792503 -0.06845557 -0.8738854 -0.481288 -0.0678541 -0.8738448 -0.4814469 -0.7692043 0.4853436 0.4156516 -0.8207832 0.4223354 0.38464 -0.06118845 -0.142883 -0.9878464 -0.5666093 -0.07362586 -0.8206906 -0.870159 -0.4036712 -0.2826181 -0.7863869 -0.4868949 -0.3801698 -0.9469536 -0.250456 -0.2013719 -0.1449734 0.6231951 0.768512 -0.01120263 -0.8296415 -0.5581841 -0.5536965 -0.679508 -0.4813411 -0.8853785 -0.01869839 0.4644948 -0.9882095 0.0207901 0.1516897 -0.9973192 0.008565783 0.07267075 0.9471256 0.007441461 0.3207769 0.9391919 0.004029393 0.3433695 0.7494627 5.24931e-4 0.6620464 0.5483078 0.02831256 0.8357973 0.2536703 -0.003893911 0.967283 0.04609525 -0.01358777 0.9988447 -0.3935477 0.02965039 0.9188259 -0.4500396 0.01650822 0.892856 0 1 -1.41987e-5 0 1 0 0 1 -8.85274e-6 0 1 -2.00012e-5 0 1 1.48025e-5 0 1 3.14136e-6 0.5012832 -0.4544294 0.7363486 -0.01964962 0.9792284 0.2018058 -0.01787686 0.9792438 0.2018958 -0.01978045 0.9792311 0.2017803 -0.01929265 0.9792418 0.2017754 -0.0191158 0.9792103 0.2019452 0.9967535 0.04891496 -0.06395179 -0.01962333 0.9792256 0.2018219 -0.990503 0.07538789 -0.1149809 -0.01955121 0.9792488 0.2017169 -0.01934576 0.979356 0.2012155 -0.0194714 0.9792487 0.2017246 -0.01933807 0.9792672 0.2016478 -0.01988261 0.9791602 0.2021144 -0.01931762 0.9794434 0.200792 0.0747677 0.9971935 0.003896296 -0.01930367 0.9792469 0.2017502 -0.01948201 0.9792788 0.2015776 -0.0189625 0.9792383 0.2018238 -0.01949083 0.9792154 0.2018844 0.9586042 0.2050265 -0.1975905 -0.01928579 0.9792222 0.2018717 -0.01931017 0.9792389 0.201788 -0.01930236 0.9792494 0.2017381 -0.01586526 0.9780254 0.2078811 -0.0193035 0.9792413 0.2017769 -0.01930391 0.979238 0.2017925 -0.01922589 0.9793872 0.2010752 -0.01924145 0.9792571 0.2017064 0.6931421 -0.4906067 0.5280712 -0.9415042 -0.2417277 0.2348136 -0.9460332 0.2414383 -0.2161682 -0.7353951 0.4111274 -0.5386728 -0.3433569 0.5673769 -0.7484581 -0.9122632 -0.2810836 0.2979395 0.4437415 0.5910767 -0.6735888 0.9571976 -0.1840738 0.2233598 0.8480209 -0.3516787 0.3964628 0.2688034 -0.5821988 0.7673262 0.7576746 0.4367859 -0.48492 0.1827605 0.6047275 -0.7751795 0.9967544 0.04891568 -0.06393718 -0.9905145 0.07531875 -0.114927 0.958606 0.2050262 -0.1975826 0.6931623 -0.4906555 0.5279995
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 1 0 20 0 27 0 2 1 15 1 10 1 16 2 65 2 63 2 8 3 9 3 70 3 4 4 65 4 16 4 13 5 15 5 2 5 8 6 6 6 9 6 8 7 70 7 0 7 16 8 69 8 68 8 16 9 68 9 17 9 20 10 8 10 0 10 25 11 4 11 16 11 21 12 60 12 42 12 3 13 125 13 28 13 20 14 0 14 27 14 1 15 8 15 20 15 31 16 17 16 13 16 1 17 6 17 8 17 26 18 25 18 16 18 32 19 3 19 28 19 31 20 16 20 17 20 23 21 3 21 32 21 26 22 16 22 31 22 34 23 23 23 32 23 11 24 6 24 1 24 18 25 13 25 2 25 11 26 2 26 6 26 30 27 23 27 34 27 29 28 30 28 7 28 36 29 25 29 26 29 36 30 161 30 25 30 32 31 28 31 45 31 18 32 2 32 11 32 24 33 13 33 18 33 7 34 30 34 34 34 39 35 27 35 37 35 36 36 40 36 161 36 39 37 1 37 27 37 31 38 13 38 24 38 21 39 33 39 29 39 41 40 26 40 31 40 35 41 33 41 42 41 42 42 33 42 21 42 43 43 38 43 35 43 41 44 36 44 26 44 44 45 1 45 39 45 44 46 11 46 1 46 46 47 35 47 42 47 40 48 36 48 41 48 48 49 11 49 44 49 49 50 32 50 45 50 21 51 29 51 14 51 47 52 39 52 37 52 14 53 29 53 7 53 49 54 34 54 32 54 48 55 18 55 11 55 50 56 31 56 24 56 51 57 34 57 49 57 52 58 35 58 46 58 50 59 41 59 31 59 51 60 7 60 34 60 43 61 35 61 52 61 53 62 24 62 18 62 54 63 50 63 24 63 40 64 41 64 50 64 55 65 14 65 7 65 52 66 56 66 43 66 60 67 46 67 42 67 5 68 44 68 39 68 60 69 21 69 14 69 59 70 51 70 49 70 54 71 24 71 53 71 61 72 39 72 47 72 55 73 7 73 51 73 5 74 39 74 61 74 12 75 48 75 44 75 12 76 44 76 5 76 61 77 47 77 57 77 59 78 49 78 58 78 62 79 50 79 54 79 15 80 55 80 51 80 63 81 46 81 60 81 52 82 65 82 56 82 19 83 53 83 18 83 19 84 18 84 48 84 19 85 48 85 12 85 66 86 50 86 62 86 10 87 51 87 59 87 9 88 59 88 58 88 63 89 52 89 46 89 54 90 53 90 67 90 66 91 40 91 50 91 66 92 64 92 40 92 65 93 52 93 63 93 69 94 60 94 68 94 17 95 60 95 14 95 15 96 51 96 10 96 68 97 60 97 17 97 10 98 59 98 9 98 63 99 60 99 69 99 62 100 54 100 67 100 13 101 14 101 55 101 71 102 66 102 62 102 9 103 58 103 70 103 67 104 53 104 19 104 71 105 64 105 66 105 17 106 14 106 13 106 71 107 22 107 64 107 13 108 55 108 15 108 6 109 10 109 9 109 16 110 63 110 69 110 2 111 10 111 6 111 82 112 228 112 220 112 72 113 79 113 73 113 77 114 76 114 113 114 72 115 78 115 79 115 72 116 73 116 224 116 84 117 114 117 86 117 84 118 77 118 114 118 229 119 223 119 236 119 242 120 82 120 220 120 88 121 77 121 84 121 88 122 76 122 77 122 85 123 105 123 95 123 75 124 126 124 91 124 90 125 89 125 87 125 227 126 78 126 72 126 93 127 75 127 91 127 93 128 87 128 75 128 214 129 83 129 239 129 90 130 87 130 93 130 84 131 214 131 233 131 96 132 92 132 89 132 96 133 89 133 90 133 215 134 84 134 233 134 219 135 78 135 227 135 96 136 90 136 93 136 219 137 231 137 78 137 217 138 84 138 215 138 217 139 232 139 88 139 243 140 83 140 241 140 98 141 93 141 91 141 243 142 239 142 83 142 80 143 96 143 93 143 224 144 221 144 72 144 101 145 98 145 100 145 85 146 94 146 92 146 102 147 97 147 95 147 81 148 96 148 80 148 81 149 92 149 96 149 101 150 93 150 98 150 103 151 97 151 102 151 95 152 94 152 85 152 85 153 92 153 81 153 104 154 93 154 101 154 103 155 99 155 97 155 104 156 80 156 93 156 105 157 102 157 95 157 106 158 101 158 100 158 107 159 81 159 80 159 110 160 101 160 106 160 111 161 85 161 81 161 110 162 104 162 101 162 107 163 80 163 104 163 110 164 106 164 109 164 112 165 108 165 103 165 112 166 103 166 102 166 113 167 102 167 105 167 74 168 104 168 110 168 79 169 110 169 109 169 114 170 85 170 111 170 114 171 105 171 85 171 107 172 104 172 74 172 74 173 110 173 79 173 113 174 112 174 102 174 77 175 105 175 114 175 77 176 113 176 105 176 79 177 109 177 73 177 83 178 81 178 82 178 83 179 111 179 81 179 82 180 107 180 74 180 82 181 81 181 107 181 114 182 111 182 86 182 78 183 74 183 79 183 86 184 111 184 83 184 228 185 78 185 231 185 12 186 119 186 115 186 19 187 12 187 115 187 171 188 176 188 116 188 158 189 116 189 117 189 158 190 171 190 116 190 209 191 158 191 117 191 209 192 117 192 118 192 57 193 209 193 118 193 61 194 57 194 118 194 5 195 118 195 119 195 5 196 61 196 118 196 12 197 5 197 119 197 120 198 23 198 30 198 121 199 120 199 30 199 121 200 30 200 29 200 92 201 122 201 123 201 89 202 92 202 123 202 135 203 121 203 29 203 89 204 123 204 124 204 87 205 89 205 124 205 87 206 124 206 125 206 75 207 87 207 125 207 75 208 125 208 3 208 126 209 75 209 3 209 126 210 3 210 23 210 120 211 126 211 23 211 234 212 154 212 216 212 132 213 106 213 155 213 132 214 155 214 131 214 240 215 154 215 234 215 132 216 109 216 106 216 226 217 145 217 240 217 223 218 134 218 236 218 136 219 99 219 103 219 245 220 133 220 222 220 229 221 130 221 223 221 238 222 130 222 229 222 108 223 136 223 103 223 137 224 136 224 142 224 142 225 136 225 108 225 143 226 131 226 127 226 233 227 239 227 234 227 144 228 137 228 142 228 73 229 132 229 131 229 73 230 131 230 143 230 226 231 234 231 239 231 138 232 137 232 144 232 228 233 231 233 235 233 235 234 236 234 134 234 225 235 134 235 244 235 221 236 143 236 230 236 235 237 134 237 225 237 221 238 224 238 73 238 237 239 143 239 213 239 147 240 142 240 108 240 147 241 108 241 112 241 139 242 138 242 144 242 146 243 120 243 121 243 147 244 144 244 142 244 139 245 140 245 138 245 219 246 236 246 231 246 148 247 120 247 146 247 148 248 126 248 120 248 129 249 146 249 121 249 135 250 140 250 139 250 149 251 144 251 147 251 129 252 121 252 135 252 150 253 139 253 144 253 150 254 144 254 149 254 76 255 112 255 113 255 91 256 126 256 148 256 151 257 139 257 150 257 76 258 147 258 112 258 128 259 146 259 129 259 151 260 135 260 139 260 149 261 147 261 76 261 153 262 148 262 146 262 153 263 146 263 128 263 152 264 135 264 151 264 152 265 129 265 135 265 100 266 148 266 153 266 100 267 98 267 91 267 100 268 91 268 148 268 128 269 129 269 152 269 145 270 149 270 76 270 150 271 149 271 145 271 154 272 76 272 88 272 155 273 153 273 128 273 141 274 150 274 145 274 145 275 76 275 154 275 127 276 128 276 152 276 141 277 151 277 150 277 152 278 151 278 134 278 155 279 100 279 153 279 155 280 128 280 127 280 106 281 100 281 155 281 130 282 127 282 152 282 133 283 151 283 141 283 134 284 151 284 133 284 130 285 152 285 134 285 217 286 233 286 234 286 141 287 226 287 218 287 131 288 155 288 127 288 154 289 232 289 216 289 132 290 73 290 109 290 95 291 97 291 156 291 95 292 156 292 122 292 94 293 95 293 122 293 140 294 29 294 33 294 92 295 94 295 122 295 140 296 135 296 29 296 138 297 140 297 33 297 137 298 33 298 35 298 137 299 138 299 33 299 136 300 137 300 35 300 136 301 35 301 38 301 99 302 136 302 38 302 99 303 38 303 157 303 97 304 99 304 157 304 97 305 157 305 156 305 169 306 208 306 160 306 167 307 159 307 204 307 167 308 204 308 161 308 174 309 177 309 170 309 169 310 58 310 208 310 173 311 177 311 174 311 181 312 170 312 159 312 181 313 159 313 167 313 175 314 163 314 173 314 178 315 161 315 40 315 175 316 160 316 163 316 180 317 160 317 175 317 182 318 157 318 38 318 178 319 167 319 161 319 175 320 173 320 174 320 177 321 202 321 170 321 178 322 40 322 184 322 56 323 38 323 43 323 180 324 169 324 160 324 182 325 38 325 56 325 70 326 169 326 180 326 70 327 58 327 169 327 186 328 157 328 182 328 156 329 157 329 186 329 189 330 182 330 56 330 187 331 180 331 175 331 185 332 167 332 178 332 185 333 178 333 184 333 70 334 180 334 188 334 188 335 180 335 187 335 186 336 182 336 189 336 185 337 181 337 167 337 174 338 170 338 181 338 188 339 0 339 70 339 124 340 123 340 190 340 191 341 56 341 65 341 168 342 122 342 172 342 168 343 123 343 122 343 165 344 175 344 174 344 64 345 184 345 40 345 165 346 187 346 175 346 192 347 181 347 185 347 166 348 188 348 187 348 189 349 56 349 191 349 190 350 123 350 168 350 193 351 184 351 64 351 179 352 156 352 186 352 164 353 187 353 165 353 194 354 124 354 190 354 193 355 185 355 184 355 179 356 122 356 156 356 166 357 187 357 164 357 125 358 124 358 194 358 172 359 122 359 179 359 174 360 181 360 192 360 186 361 189 361 191 361 192 362 185 362 193 362 195 363 188 363 166 363 196 364 125 364 194 364 195 365 27 365 0 365 195 366 0 366 188 366 197 367 191 367 65 367 194 368 190 368 162 368 28 369 125 369 196 369 168 370 162 370 190 370 198 371 165 371 174 371 199 372 191 372 197 372 200 373 64 373 22 373 200 374 193 374 64 374 197 375 65 375 4 375 199 376 186 376 191 376 172 377 179 377 202 377 164 378 165 378 198 378 202 379 186 379 199 379 202 380 179 380 186 380 201 381 168 381 172 381 192 382 193 382 200 382 196 383 45 383 28 383 162 384 168 384 201 384 203 385 166 385 164 385 204 386 197 386 4 386 195 387 166 387 203 387 204 388 199 388 197 388 183 389 174 389 192 389 195 390 37 390 27 390 205 391 194 391 162 391 171 392 164 392 198 392 176 393 174 393 183 393 198 394 174 394 176 394 205 395 196 395 194 395 206 396 164 396 171 396 207 397 162 397 201 397 205 398 45 398 196 398 37 399 195 399 203 399 203 400 164 400 206 400 201 401 172 401 173 401 204 402 4 402 25 402 183 403 192 403 200 403 158 404 206 404 171 404 208 405 162 405 207 405 208 406 205 406 162 406 177 407 172 407 202 407 203 408 47 408 37 408 177 409 173 409 172 409 209 410 203 410 206 410 159 411 199 411 204 411 171 412 198 412 176 412 209 413 47 413 203 413 208 414 49 414 45 414 208 415 45 415 205 415 170 416 199 416 159 416 170 417 202 417 199 417 209 418 57 418 47 418 209 419 206 419 158 419 163 420 207 420 201 420 160 421 207 421 163 421 163 422 201 422 173 422 161 423 204 423 25 423 208 424 58 424 49 424 160 425 208 425 207 425 183 426 200 426 212 426 183 427 212 427 116 427 176 428 183 428 116 428 19 429 115 429 210 429 67 430 19 430 210 430 62 431 67 431 210 431 62 432 210 432 211 432 71 433 62 433 211 433 22 434 71 434 211 434 22 435 211 435 212 435 200 436 22 436 212 436 119 437 118 437 117 437 119 438 116 438 115 438 119 439 117 439 116 439 210 440 212 440 211 440 115 441 212 441 210 441 115 442 116 442 212 442 230 443 143 443 237 443 235 444 245 444 222 444 235 445 225 445 244 445 235 446 244 446 245 446 227 447 221 447 230 447 216 448 232 448 217 448 134 449 245 449 244 449 241 450 242 450 220 450 83 451 242 451 241 451 233 452 214 452 239 452 220 453 228 453 235 453 213 454 238 454 229 454 219 455 227 455 230 455 222 456 218 456 226 456 234 457 216 457 217 457 227 458 72 458 221 458 226 459 239 459 222 459 230 460 213 460 229 460 239 461 241 461 220 461 226 462 240 462 234 462 133 463 218 463 222 463 231 464 236 464 235 464 220 465 235 465 239 465 229 466 236 466 219 466 230 467 237 467 213 467 235 468 222 468 239 468 230 469 229 469 219 469 217 470 215 470 233 470 239 471 243 471 241 471 143 472 238 472 213 472 82 473 74 473 228 473 214 474 86 474 83 474 84 475 86 475 214 475 217 476 88 476 84 476 228 477 74 477 78 477 240 478 145 478 154 478 223 479 130 479 134 479 238 480 127 480 130 480 221 481 73 481 143 481 141 482 145 482 226 482 154 483 88 483 232 483 134 484 133 484 245 484 83 485 82 485 242 485 133 486 141 486 218 486 143 487 127 487 238 487
+
+
+
+
+
+
+ 0.01525527 -0.06846272 0.02643764 0.01525521 -0.06962722 0.02613562 0.01525551 0.06845593 0.02644127 0.01525419 0.06938499 0.01338958 0.01525408 0.07093381 0.0182076 0.01525521 0.07196158 0.02213627 0.01525527 0.07189172 0.02323365 0.01525527 0.07155174 0.02429103 0.01525533 0.07087653 0.02522397 0.01525533 0.07015961 0.02582722 0.01525515 -0.07078915 0.02532345 0.01525509 -0.07158952 0.02423405 0.01525533 0.06931853 0.02623498 0.01525461 -0.06638556 0.01107048 0.01525497 0.06630653 0.01106137 0.01525533 -0.0719729 0.02268934 0.01525533 -0.0718559 0.02158552 0.01525431 0.06754839 0.01146334 0.01525491 0.06862276 0.01224523 0.01525545 -0.06930655 0.013206 0.01525515 -0.06847184 0.01210939 0.01525521 -0.06758683 0.01148784 0.01525533 -0.07075327 0.01761788 -0.01525527 -0.06846272 0.02643764 -0.01525521 -0.06962722 0.02613562 -0.01525551 0.06845593 0.02644127 -0.01525419 0.06938499 0.01338958 -0.01525408 0.07093381 0.0182076 -0.01525521 0.07196158 0.02213627 -0.01525527 0.07189172 0.02323365 -0.01525527 0.07155174 0.02429103 -0.01525533 0.07087653 0.02522397 -0.01525533 0.07015961 0.02582722 -0.01525515 -0.07078915 0.02532345 -0.01525509 -0.07158952 0.02423405 -0.01525533 0.06931853 0.02623498 -0.01525461 -0.06638556 0.01107048 -0.01525497 0.06630653 0.01106137 -0.01525533 -0.0719729 0.02268934 -0.01525533 -0.0718559 0.02158552 -0.01525431 0.06754839 0.01146334 -0.01525491 0.06862276 0.01224523 -0.01525545 -0.06930655 0.013206 -0.01525515 -0.06847184 0.01210939 -0.01525521 -0.06758683 0.01148784 -0.01525533 -0.07075327 0.01761788
+
+
+
+
+
+
+
+
+
+ 1 -1.75812e-6 -3.48765e-5 -1 -1.75525e-6 -3.48765e-5 1 2.33582e-4 1.24214e-4 1 -5.19849e-5 -2.56819e-7 0.9999999 -4.44366e-4 2.63252e-4 1 1.2616e-4 1.30221e-5 1 3.77994e-4 1.51057e-4 0.9999997 -5.5586e-4 -6.97877e-4 1 -2.25514e-6 -3.93354e-5 0.9999969 -0.001791894 0.001769959 0.9999997 6.6978e-4 -5.75807e-4 1 -4.4534e-5 -5.70551e-5 1 1.65015e-4 5.18604e-5 1 0 0 1 -8.52863e-5 7.26387e-5 1 1.2376e-5 3.54803e-6 0.9999998 6.10682e-4 4.75586e-4 1 2.83882e-4 -6.63786e-5 1 -2.80579e-5 -4.54622e-5 1 3.0039e-4 -7.71428e-5 1 1.16374e-4 -2.33199e-5 1 -1.89527e-4 -2.25066e-4 -0.9999997 -5.55857e-4 -6.97874e-4 -1 3.77252e-4 1.51057e-4 -1 1.21654e-4 1.30221e-5 -1 -4.44365e-4 2.63252e-4 -1 -4.89273e-5 -2.5682e-7 -1 2.54534e-4 1.99353e-4 -1 0 0 -1 1.65015e-4 5.18604e-5 -1 1.84655e-5 -5.30141e-5 -0.9999969 -0.001791894 0.001769959 -0.9999996 6.68681e-4 -5.75808e-4 -0.9999998 6.13384e-4 4.75586e-4 -1 1.2376e-5 3.54804e-6 -1 -8.52864e-5 7.26386e-5 -1 -2.80579e-5 -4.54622e-5 -1 -1.90327e-4 -2.25067e-4 -1 2.83882e-4 -6.63786e-5 -1 1.16374e-4 -2.33199e-5 -1 -2.25895e-6 -3.93355e-5 -1 3.0039e-4 -7.71428e-5
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 14 0 2 0 0 0 25 1 37 1 23 1 12 2 2 2 5 2 0 3 1 3 10 3 10 4 11 4 15 4 15 5 16 5 10 5 22 6 19 6 13 6 20 7 21 7 19 7 13 8 14 8 0 8 17 9 18 9 3 9 3 10 14 10 17 10 5 11 6 11 12 11 7 12 8 12 6 12 9 13 12 13 8 13 0 14 10 14 16 14 16 15 22 15 0 15 21 16 13 16 19 16 3 17 4 17 14 17 6 18 8 18 12 18 2 19 14 19 4 19 22 20 13 20 0 20 4 21 5 21 2 21 44 22 43 22 42 22 42 23 45 23 36 23 39 24 38 24 33 24 34 25 33 25 38 25 24 26 23 26 33 26 25 27 35 27 29 27 32 28 31 28 35 28 30 29 29 29 31 29 28 30 25 30 29 30 26 31 41 31 40 31 40 32 37 32 26 32 36 33 44 33 42 33 45 34 39 34 23 34 33 35 23 35 39 35 35 36 31 36 29 36 28 37 27 37 25 37 26 38 37 38 27 38 36 39 45 39 23 39 23 40 37 40 36 40 27 41 37 41 25 41
+
+
+
+
+
+
+
+
+ 1 0 0 0 0 1 0 0 0 0 1 0 0 0 0 1
+
+
+
+
+
+
+
+
+
+ 1 0 0 0 0 1 0 0 0 0 1 0 0 0 0 1
+
+
+
+
+
+
+
+
+
+ 1 0 0 0 0 1 0 0 0 0 1 0 0 0 0 1
+
+
+
+
+
+
+
+
+
+ 1 0 0 0 0 1 0 0 0 0 1 0 0 0 0 1
+
+
+
+
+
+
+
+
+
+ 1 0 0 0 0 1 0 0 0 0 1 0 0 0 0 1
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/embodied_gen/scripts/affordance_annot/gen_grasp.py b/embodied_gen/scripts/affordance_annot/gen_grasp.py
new file mode 100644
index 0000000..1a0675b
--- /dev/null
+++ b/embodied_gen/scripts/affordance_annot/gen_grasp.py
@@ -0,0 +1,764 @@
+# Project EmbodiedGen
+#
+# Copyright (c) 2025 Horizon Robotics. All Rights Reserved.
+#
+# 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.
+
+import os
+import sys
+import time
+from dataclasses import dataclass, field
+from typing import Literal
+
+from embodied_gen.utils.general import filter_warnings
+
+filter_warnings()
+
+import numpy as np
+import trimesh
+import trimesh.transformations as tra
+import tyro
+
+sys.path.append(".")
+from embodied_gen.utils.geometry import (
+ sample_surface_points,
+)
+from embodied_gen.utils.io_utils import (
+ URDFFile,
+ load_json,
+ load_mesh,
+ write_json,
+)
+from embodied_gen.utils.log import logger
+from embodied_gen.utils.vis_utils import show_grasps
+
+__all__ = [
+ "GraspGenHyperConfig",
+ "GraspGenXHyperConfig",
+ "GraspPoseConfig",
+ "GraspGenerator",
+ "run_grasp",
+ "entrypoint",
+]
+
+FINGER_CENTER_CACHE: dict[str, np.ndarray] = {
+ "franka_panda": np.array([0.0, 0.0, 0.1034], dtype=np.float64),
+}
+
+GRIPPER_GEOMETRY_CACHE: dict[str, dict[str, float]] = {
+ "franka_panda": {
+ "width": 0.10537486,
+ "depth": 0.10527314,
+ },
+}
+
+
+@dataclass
+class GraspGenHyperConfig:
+ gripper_config: str = (
+ "~/.cache/huggingface/hub/GraspGenModels/checkpoints/graspgen_{}.yml"
+ )
+ grasp_threshold: float = -1.0
+ num_grasps: int = 4000
+ return_topk: bool = True
+ topk_num_grasps: int = 4000
+ mesh_scale: tuple[float, float, float] = (1.0, 1.0, 1.0)
+ num_sample_points: int = 2000
+ min_grasps: int = 40
+ max_tries: int = 6
+ remove_outliers: bool = False
+
+
+@dataclass
+class GraspGenXHyperConfig:
+ checkpoint_root: str = "~/.cache/huggingface/hub/GraspGenXModels/release"
+ gen_pth: str | None = None
+ dis_pth: str | None = None
+ assets_dir: str = (
+ "thirdparty/gripper_descriptions/gripper_descriptions/assets"
+ )
+ grasp_threshold: float = -1.0
+ num_grasps: int = 4000
+ return_topk: bool = True
+ topk_num_grasps: int = 4000
+ mesh_scale: tuple[float, float, float] = (1.0, 1.0, 1.0)
+ num_sample_points: int = 2000
+ min_grasps: int = 40
+ max_tries: int = 6
+ remove_outliers: bool = False
+
+
+@dataclass
+class GraspPoseConfig:
+ urdf_paths: list[str] = field(default_factory=list)
+ model_name: str = "GraspGen"
+ gripper_name: str = "franka_panda"
+ mesh_type: Literal["collision", "visual"] = "collision"
+ update_urdf: bool = True
+ overwrite: bool = True
+ gripper_mesh_path: str = (
+ f"embodied_gen/scripts/affordance_annot/{gripper_name}/coll_mesh.obj"
+ )
+ grasps_per_part: int = 20
+ visualize_grasps: bool = False
+ visualizer_port: int = 8080
+ graspgen: GraspGenHyperConfig | None = None
+
+
+class GraspGenerator:
+ def __init__(self, cfg: GraspPoseConfig):
+ self.cfg = cfg
+ self.grasp_visualizer = None
+ self.validate_config(cfg)
+ self.modules = self.load_model()
+ self.pipeline = self.build_sampler()
+ self.finger_center = FINGER_CENTER_CACHE.get(
+ self.cfg.gripper_name,
+ np.array([0.0, 0.0, 0.0], dtype=np.float64),
+ )
+ (
+ self.local_contact_points,
+ self.local_closing_directions,
+ ) = self.build_local_contact_samples()
+
+ def validate_config(self, cfg: GraspPoseConfig) -> None:
+ cfg.model_name = cfg.model_name.lower()
+ if cfg.model_name not in {"graspgen", "graspgenx"}:
+ raise ValueError(
+ "unsupported model '{}', available models: GraspGen, GraspGenX".format(
+ cfg.model_name
+ )
+ )
+
+ cfg.graspgen = (
+ GraspGenHyperConfig()
+ if cfg.model_name == "graspgen"
+ else GraspGenXHyperConfig()
+ )
+ if cfg.model_name == "graspgen":
+ cfg.graspgen.gripper_config = cfg.graspgen.gripper_config.format(
+ cfg.gripper_name
+ )
+
+ def load_model(self):
+ if self.cfg.model_name == "graspgen":
+ sys.path.append("thirdparty/GraspGen")
+ from grasp_gen.grasp_server import GraspGenSampler, load_grasp_cfg
+
+ return {
+ "GraspGenSampler": GraspGenSampler,
+ "model_cfg": load_grasp_cfg,
+ }
+ if self.cfg.model_name == "graspgenx":
+ sys.path.append("thirdparty/GraspGenX")
+ from graspgenx.grasp_server import GraspGenXSampler
+ from graspgenx.utils.checkpoint_io import load_model_cfg
+
+ return {
+ "GraspGenSampler": GraspGenXSampler,
+ "model_cfg": load_model_cfg,
+ }
+
+ def build_sampler(self):
+ if self.cfg.model_name == "graspgen":
+ gripper_config = os.path.expanduser(
+ self.cfg.graspgen.gripper_config
+ )
+
+ if not os.path.exists(gripper_config):
+ logger.info(
+ "Downloading GraspGen weights from Hugging Face. "
+ "First-time use may take several minutes; please wait patiently."
+ )
+ from huggingface_hub import snapshot_download
+
+ download_dir = os.path.expanduser(
+ "~/.cache/huggingface/hub/GraspGenModels"
+ )
+ snapshot_download(
+ repo_id="adithyamurali/GraspGenModels",
+ allow_patterns="checkpoints/*",
+ local_dir=download_dir,
+ )
+
+ grasp_cfg = self.modules["model_cfg"](gripper_config)
+ return self.modules["GraspGenSampler"](grasp_cfg)
+ if self.cfg.model_name == "graspgenx":
+ checkpoint_root = os.path.expanduser(
+ self.cfg.graspgen.checkpoint_root
+ )
+ config_path = os.path.join(checkpoint_root, "gen", "config.yaml")
+ if not os.path.exists(config_path):
+ logger.info(
+ "Downloading GraspGenX weights from Hugging Face. "
+ "First-time use may take several minutes; please wait patiently."
+ )
+ from huggingface_hub import snapshot_download
+
+ download_dir = os.path.expanduser(
+ "~/.cache/huggingface/hub/GraspGenXModels"
+ )
+ snapshot_download(
+ repo_id="adithyamurali/GraspGenXModel",
+ allow_patterns="release/*",
+ local_dir=download_dir,
+ )
+ model_cfg = self.modules["model_cfg"](
+ os.path.join(checkpoint_root, "gen"),
+ os.path.join(checkpoint_root, "dis"),
+ self.cfg.graspgen.gen_pth,
+ self.cfg.graspgen.dis_pth,
+ )
+ return self.modules["GraspGenSampler"](
+ model_cfg,
+ self.cfg.gripper_name,
+ assets_dir=self.cfg.graspgen.assets_dir,
+ )
+
+ def update_urdf(self, urdf_path: str | None, grasp_path: str) -> None:
+ if urdf_path is None or not self.cfg.update_urdf:
+ return
+
+ URDFFile(urdf_path).write(
+ {
+ "custom_data/affordance/affordance_annot": os.path.relpath(
+ grasp_path,
+ os.path.dirname(urdf_path),
+ ),
+ }
+ )
+
+ def get_part_seg_mesh_transform(self, urdf: URDFFile) -> dict:
+ return {
+ "origin_xyz": urdf.read(
+ ".//custom_data/affordance/visual_seg/origin",
+ attr="xyz",
+ default="0 0 0",
+ ),
+ "origin_rpy": urdf.read(
+ ".//custom_data/affordance/visual_seg/origin",
+ attr="rpy",
+ default="0 0 0",
+ ),
+ "scale": urdf.read(
+ ".//custom_data/affordance/visual_seg/geometry/mesh",
+ attr="scale",
+ default="1.0 1.0 1.0",
+ ),
+ }
+
+ def add_grasps_to_affordance(
+ self,
+ affordance_payload: dict,
+ grasps: np.ndarray,
+ confidences: np.ndarray,
+ part_grasp_ids: dict[int, list[int]] | None,
+ ) -> None:
+ for affordance in affordance_payload.get("affordances", []):
+ if (
+ not isinstance(affordance, dict)
+ or affordance.get("id") is None
+ ):
+ continue
+ affordance.pop("grasps", None)
+ affordance.pop("confidences", None)
+ grasp_ids = part_grasp_ids.get(int(affordance["id"]), [])
+ grasp_group = {}
+ for grasp_idx in grasp_ids:
+ grasp = grasps[grasp_idx]
+ quaternion = tra.quaternion_from_matrix(grasp[:3, :3])
+ grasp_group[f"grasp_{grasp_idx}"] = {
+ "confidence": float(confidences[grasp_idx]),
+ "position": grasp[:3, 3].tolist(),
+ "orientation": {
+ "w": float(quaternion[0]),
+ "xyz": quaternion[1:].tolist(),
+ },
+ }
+ affordance["grasp_group"] = grasp_group
+
+ def infer_grasps(
+ self,
+ mesh: trimesh.Trimesh,
+ mesh_path: str,
+ ) -> tuple[np.ndarray, np.ndarray, np.ndarray] | None:
+ points = sample_surface_points(
+ mesh, self.cfg.graspgen.num_sample_points
+ )
+ center_transform = tra.translation_matrix(-points.mean(axis=0))
+ centered_points = tra.transform_points(
+ points, center_transform
+ ).astype(np.float32)
+ topk_limit = (
+ self.cfg.graspgen.topk_num_grasps
+ if self.cfg.graspgen.return_topk
+ else -1
+ )
+ grasps, confidences = self.modules["GraspGenSampler"].run_inference(
+ centered_points,
+ self.pipeline,
+ grasp_threshold=self.cfg.graspgen.grasp_threshold,
+ num_grasps=self.cfg.graspgen.num_grasps,
+ topk_num_grasps=topk_limit,
+ min_grasps=self.cfg.graspgen.min_grasps,
+ max_tries=self.cfg.graspgen.max_tries,
+ remove_outliers=self.cfg.graspgen.remove_outliers,
+ )
+
+ confidences = confidences.detach().cpu().numpy()
+ grasps = grasps.detach().cpu().numpy()
+ if len(grasps) == 0:
+ logger.error(f"No grasp poses generated for mesh: {mesh_path}")
+ return None
+
+ grasps = np.asarray(
+ [tra.inverse_matrix(center_transform) @ grasp for grasp in grasps]
+ )
+ return grasps, confidences
+
+ def get_grasp_centers(
+ self,
+ grasps: np.ndarray,
+ ) -> np.ndarray:
+ local_centers = np.repeat(
+ np.asarray(self.finger_center, dtype=np.float64)[None, :],
+ len(grasps),
+ axis=0,
+ )
+ grasp_center_points = (
+ np.einsum(
+ "nij,nj->ni",
+ grasps[:, :3, :3],
+ local_centers,
+ )
+ + grasps[:, :3, 3]
+ )
+ return grasp_center_points
+
+ def build_local_contact_samples(self) -> tuple[np.ndarray, np.ndarray]:
+ geometry = GRIPPER_GEOMETRY_CACHE.get(self.cfg.gripper_name)
+ if geometry is None:
+ return (
+ np.asarray(self.finger_center, dtype=np.float64)[None, :],
+ np.array([[1.0, 0.0, 0.0]], dtype=np.float64),
+ )
+
+ half_width = float(geometry["width"]) / 2.0
+ depth = float(geometry["depth"])
+ z_values = np.linspace(depth - 0.015, depth, 6, dtype=np.float64)
+ points = []
+ directions = []
+ for z_value in z_values:
+ points.extend(
+ [
+ [-half_width, 0.0, z_value],
+ [half_width, 0.0, z_value],
+ ]
+ )
+ directions.extend(
+ [
+ [1.0, 0.0, 0.0],
+ [-1.0, 0.0, 0.0],
+ ]
+ )
+ return (
+ np.asarray(points, dtype=np.float64),
+ np.asarray(directions, dtype=np.float64),
+ )
+
+ def get_grasp_contact_rays(
+ self,
+ grasps: np.ndarray,
+ ) -> tuple[np.ndarray, np.ndarray]:
+ local_contact_points = np.repeat(
+ self.local_contact_points[None, :, :],
+ len(grasps),
+ axis=0,
+ )
+ local_closing_directions = np.repeat(
+ self.local_closing_directions[None, :, :],
+ len(grasps),
+ axis=0,
+ )
+ ray_origins = (
+ np.einsum(
+ "nij,nkj->nki",
+ grasps[:, :3, :3],
+ local_contact_points,
+ )
+ + grasps[:, None, :3, 3]
+ )
+ ray_directions = np.einsum(
+ "nij,nkj->nki",
+ grasps[:, :3, :3],
+ local_closing_directions,
+ )
+ ray_norms = np.linalg.norm(ray_directions, axis=-1, keepdims=True)
+ ray_directions = np.divide(
+ ray_directions,
+ ray_norms,
+ out=np.zeros_like(ray_directions),
+ where=ray_norms > 0.0,
+ )
+ return ray_origins, ray_directions
+
+ def bind_contact_rays_to_faces(
+ self,
+ part_mesh: trimesh.Trimesh,
+ ray_origins: np.ndarray,
+ ray_directions: np.ndarray,
+ ) -> np.ndarray:
+ ray_origins = np.asarray(ray_origins, dtype=np.float64)
+ ray_directions = np.asarray(ray_directions, dtype=np.float64)
+
+ if (
+ not isinstance(part_mesh, trimesh.Trimesh)
+ or len(part_mesh.faces) == 0
+ ):
+ return np.full(len(ray_origins), -1, dtype=np.int64)
+
+ try:
+ face_ids = part_mesh.ray.intersects_first(
+ ray_origins,
+ ray_directions,
+ )
+ return np.asarray(face_ids, dtype=np.int64)
+ except Exception as exc:
+ logger.warning(
+ "Failed to raycast grasp closing directions to part mesh: "
+ f"{exc}"
+ )
+ return np.full(len(ray_origins), -1, dtype=np.int64)
+
+ def classify_grasps_by_contact(
+ self,
+ grasps: np.ndarray,
+ part_mesh: trimesh.Trimesh,
+ face_ids: np.ndarray | None,
+ ) -> tuple[np.ndarray | None, np.ndarray | None]:
+ if face_ids is None:
+ return None, None
+
+ face_ids = np.asarray(face_ids, dtype=np.int64)
+ if len(face_ids) != len(part_mesh.faces):
+ raise ValueError(
+ "face_ids length must match segmentation mesh faces, got "
+ f"{len(face_ids)} and {len(part_mesh.faces)}"
+ )
+
+ ray_origins, ray_directions = self.get_grasp_contact_rays(grasps)
+ num_grasps, num_contact_points, _ = ray_origins.shape
+ logger.info(
+ "Binding {} grasp closing rays to {} part faces.".format(
+ num_grasps * num_contact_points,
+ len(part_mesh.faces),
+ )
+ )
+ nearest_face_ids = self.bind_contact_rays_to_faces(
+ part_mesh,
+ ray_origins.reshape(-1, 3),
+ ray_directions.reshape(-1, 3),
+ ).reshape(num_grasps, num_contact_points)
+ contact_part_ids = self.parts_for_faces(
+ nearest_face_ids.reshape(-1),
+ face_ids,
+ ).reshape(num_grasps, num_contact_points)
+
+ grasp_part_ids = np.full(num_grasps, -1, dtype=np.int64)
+ contact_scores = np.zeros(num_grasps, dtype=np.float64)
+ for grasp_idx, part_ids in enumerate(contact_part_ids):
+ valid_part_ids = part_ids[part_ids >= 0]
+ if len(valid_part_ids) == 0:
+ continue
+ unique_ids, counts = np.unique(
+ valid_part_ids,
+ return_counts=True,
+ )
+ best_idx = int(np.argmax(counts))
+ grasp_part_ids[grasp_idx] = int(unique_ids[best_idx])
+ contact_scores[grasp_idx] = float(counts[best_idx]) / float(
+ num_contact_points
+ )
+
+ return grasp_part_ids, contact_scores
+
+ def parts_for_faces(
+ self,
+ nearest_face_ids: np.ndarray,
+ face_ids: np.ndarray | None,
+ ) -> np.ndarray | None:
+ if face_ids is None:
+ return None
+
+ nearest_face_ids = np.asarray(nearest_face_ids, dtype=np.int64)
+ grasp_part_ids = np.full(len(nearest_face_ids), -1, dtype=np.int64)
+ valid_mask = (nearest_face_ids >= 0) & (
+ nearest_face_ids < len(face_ids)
+ )
+ grasp_part_ids[valid_mask] = face_ids[nearest_face_ids[valid_mask]]
+ return grasp_part_ids
+
+ def pick_top_grasps(
+ self,
+ grasps: np.ndarray,
+ confidences: np.ndarray,
+ part_ids: np.ndarray | None,
+ graspable_part_ids: set[int] | None = None,
+ part_scores: np.ndarray | None = None,
+ ) -> tuple[
+ np.ndarray, np.ndarray, np.ndarray, dict[int, list[int]] | None
+ ]:
+ if part_ids is None:
+ return (
+ grasps,
+ confidences,
+ np.arange(len(grasps), dtype=np.int64),
+ None,
+ )
+
+ part_ids = np.asarray(part_ids, dtype=np.int64)
+ selected_indices = []
+ selected_part_ids = []
+ for part_id in sorted(
+ int(pid) for pid in np.unique(part_ids[part_ids >= 0])
+ ):
+ if (
+ graspable_part_ids is not None
+ and part_id not in graspable_part_ids
+ ):
+ continue
+ part_indices = np.flatnonzero(part_ids == part_id)
+ if part_scores is None:
+ rank = np.argsort(
+ -confidences[part_indices],
+ kind="mergesort",
+ )
+ else:
+ rank = np.lexsort(
+ (
+ -confidences[part_indices],
+ -part_scores[part_indices],
+ )
+ )
+ ranked_indices = part_indices[rank]
+ if len(ranked_indices) > self.cfg.grasps_per_part:
+ selected = ranked_indices[: self.cfg.grasps_per_part]
+ else:
+ selected = ranked_indices
+ selected_indices.extend(int(idx) for idx in selected)
+ selected_part_ids.extend([part_id] * len(selected))
+
+ selected_indices = np.asarray(selected_indices, dtype=np.int64)
+ if len(selected_indices) == 0:
+ return (
+ grasps[:0],
+ confidences[:0],
+ selected_indices,
+ {},
+ )
+
+ part_grasp_ids: dict[int, list[int]] = {}
+ for new_idx, part_id in enumerate(selected_part_ids):
+ part_grasp_ids.setdefault(part_id, []).append(new_idx)
+
+ return (
+ grasps[selected_indices],
+ confidences[selected_indices],
+ selected_indices,
+ part_grasp_ids,
+ )
+
+ def select_grasps(
+ self,
+ grasps: np.ndarray,
+ confidences: np.ndarray,
+ part_mesh: trimesh.Trimesh,
+ face_ids: np.ndarray,
+ affordance_payload: dict,
+ ) -> tuple[np.ndarray, np.ndarray, np.ndarray, dict[int, list[int]]]:
+ original_count = len(grasps)
+
+ # Raycast from fingertip samples along the gripper closing direction.
+ grasp_center_points = self.get_grasp_centers(grasps)
+ grasp_part_ids, contact_scores = self.classify_grasps_by_contact(
+ grasps,
+ part_mesh,
+ face_ids,
+ )
+
+ affordances = affordance_payload.get("affordances", [])
+ graspable_part_ids = {
+ int(affordance["id"])
+ for affordance in affordances
+ if isinstance(affordance, dict)
+ and affordance.get("id") is not None
+ and affordance.get("graspable") is True
+ }
+
+ # Select top grasps per part
+ grasps, confidences, selected_indices, part_grasp_ids = (
+ self.pick_top_grasps(
+ grasps,
+ confidences,
+ grasp_part_ids,
+ graspable_part_ids,
+ contact_scores,
+ )
+ )
+ grasp_center_points = grasp_center_points[selected_indices]
+ if len(grasps) == 0:
+ logger.info(
+ "Selected per-part grasps: {} -> 0 grasps across 0 parts".format(
+ original_count,
+ )
+ )
+ return grasps, confidences, grasp_center_points, part_grasp_ids
+
+ logger.info(
+ "Selected per-part grasps: {} -> {} grasps across {} parts, confidences range from {:.4f} to {:.4f}".format(
+ original_count,
+ len(grasps),
+ len(part_grasp_ids),
+ np.min(confidences),
+ np.max(confidences),
+ )
+ )
+ return grasps, confidences, grasp_center_points, part_grasp_ids
+
+ def process(
+ self,
+ urdf_path: str,
+ ) -> bool:
+ try:
+ return self._process_impl(urdf_path)
+ except Exception as exc:
+ logger.error(
+ "Grasp generation failed for URDF {}: {}".format(
+ urdf_path,
+ exc,
+ )
+ )
+ return False
+
+ def _process_impl(
+ self,
+ urdf_path: str,
+ ) -> bool:
+ urdf = URDFFile(urdf_path)
+ mesh_path = urdf.get_mesh_path(self.cfg.mesh_type)
+ mesh_transform = urdf.get_mesh_transform(self.cfg.mesh_type)
+ seg_mesh_path = urdf.get_mesh_part_seg_path()
+ seg_mesh_transform = self.get_part_seg_mesh_transform(urdf)
+ affordance_path = urdf.get_affordance_annot_path()
+ affordance_payload = load_json(affordance_path)
+
+ if not self.cfg.overwrite:
+ has_existing_grasp_group = any(
+ isinstance(affordance, dict) and "grasp_group" in affordance
+ for affordance in affordance_payload.get("affordances", [])
+ )
+ if has_existing_grasp_group:
+ logger.info(
+ f"Skip existing grasp poses in affordance JSON: {affordance_path}"
+ )
+ return True
+
+ logger.info(f"Generating grasps for mesh: {mesh_path}")
+ mesh = load_mesh(
+ mesh_path,
+ apply_origin=True,
+ apply_scale=True,
+ **mesh_transform,
+ )
+
+ seg_mesh, face_ids = load_mesh(
+ seg_mesh_path,
+ apply_origin=True,
+ apply_scale=True,
+ **seg_mesh_transform,
+ )
+
+ grasp_result = self.infer_grasps(mesh, mesh_path)
+ if grasp_result is None:
+ return False
+ grasps, confidences = grasp_result
+ grasps, confidences, grasp_center_points, part_grasp_ids = (
+ self.select_grasps(
+ grasps,
+ confidences,
+ seg_mesh,
+ face_ids,
+ affordance_payload,
+ )
+ )
+
+ self.add_grasps_to_affordance(
+ affordance_payload,
+ grasps,
+ confidences,
+ part_grasp_ids,
+ )
+ write_json(affordance_payload, affordance_path)
+ logger.info(f"Saved predicted grasp poses to {affordance_path}")
+
+ self.update_urdf(urdf_path, affordance_path)
+ logger.info(
+ "Grasp generation succeeded: {} -> {} ({} grasps)".format(
+ mesh_path,
+ affordance_path,
+ len(grasps),
+ )
+ )
+ if self.cfg.visualize_grasps:
+ gripper_mesh = load_mesh(self.cfg.gripper_mesh_path)
+ self.grasp_visualizer = show_grasps(
+ mesh,
+ grasps,
+ confidences,
+ self.cfg.gripper_name,
+ self.cfg.model_name,
+ grasp_center_points=grasp_center_points,
+ visualizer=self.grasp_visualizer,
+ visualizer_port=self.cfg.visualizer_port,
+ gripper_info=self.pipeline.get_gripper_info()
+ if self.cfg.model_name == "graspgenx"
+ else None,
+ gripper_mesh=gripper_mesh,
+ )
+ return True
+
+
+def run_grasp(cfg: GraspPoseConfig) -> None:
+ generator = GraspGenerator(cfg)
+ for urdf_path in cfg.urdf_paths:
+ generator.process(urdf_path)
+
+ if cfg.visualize_grasps and generator.grasp_visualizer is not None:
+ logger.info(
+ "Keeping grasp visualization alive at http://localhost:{} "
+ "(press Ctrl+C to exit).".format(cfg.visualizer_port)
+ )
+ try:
+ while True:
+ time.sleep(1)
+ except KeyboardInterrupt:
+ logger.info("Stopping grasp visualization.")
+
+
+def entrypoint(*args, **kwargs) -> None:
+ cfg = tyro.cli(GraspPoseConfig)
+ run_grasp(cfg)
+
+
+if __name__ == "__main__":
+ entrypoint()
diff --git a/embodied_gen/scripts/affordance_annot/part_seg.py b/embodied_gen/scripts/affordance_annot/part_seg.py
new file mode 100644
index 0000000..9736725
--- /dev/null
+++ b/embodied_gen/scripts/affordance_annot/part_seg.py
@@ -0,0 +1,697 @@
+# Project EmbodiedGen
+#
+# Copyright (c) 2025 Horizon Robotics. All Rights Reserved.
+#
+# 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.
+
+
+import os
+import shutil
+import sys
+from dataclasses import asdict, dataclass, field
+from typing import Literal
+
+import json_repair
+from embodied_gen.utils.general import filter_warnings
+from embodied_gen.utils.gpt_clients import GPT_CLIENT
+
+filter_warnings()
+
+import numpy as np
+import trimesh
+import tyro
+from huggingface_hub import snapshot_download
+from embodied_gen.utils.general import set_seed
+from embodied_gen.utils.geometry import (
+ EnclosedFaceLabelFillConfig,
+ SmoothFaceLabelMergeConfig,
+ fill_small_enclosed_face_labels,
+ merge_smooth_face_labels,
+ normalize_mesh,
+)
+from embodied_gen.utils.io_utils import URDFFile, load_mesh, save_mesh
+from embodied_gen.utils.log import logger
+from embodied_gen.utils.process_media import render_asset3d
+from embodied_gen.utils.vis_utils import PALETTE, collect_colors, render_grid
+from embodied_gen.validators.quality_checkers import PartSegChecker
+
+__all__ = [
+ "FaceIdPostProcessConfig",
+ "PartSegConfig",
+ "PartSegmenter",
+ "entrypoint",
+]
+
+
+@dataclass
+class P3SAMConfig:
+ threshold: float = 0.95
+ post_process: bool = True
+ save_mid_res: bool = False
+ show_info: bool = True
+ is_parallel: bool = False
+ seed: int = 42
+ clean_mesh_flag: bool = False
+ prompt_bs: int = 8 # Lower this for reduced GPU memory usage.
+
+
+@dataclass
+class FaceIdPostProcessConfig:
+ enabled: bool = True
+ max_smooth_angle_deg: float = 5.0
+ vertex_merge_tolerance: float = 0.0
+ min_component_area_ratio: float = 0.0
+ min_component_faces: int = 1
+ preserve_negative: bool = False
+ fill_enclosed_components: bool = True
+ max_enclosed_component_area_ratio: float = 0.005
+ max_enclosed_component_faces: int = 100
+ min_enclosure_neighbor_ratio: float = 0.8
+
+
+@dataclass
+class PartSegConfig:
+ urdf_paths: list[str] = field(default_factory=list)
+ output_dirs: list[str] = field(default_factory=list)
+ model_name: str = "p3sam"
+ thirdparty_root: str = ""
+ mesh_type: Literal["visual", "collision"] = "visual"
+ merge_retries: int = 3
+ p3sam: P3SAMConfig = field(default_factory=P3SAMConfig)
+ face_id_postprocess: FaceIdPostProcessConfig = field(
+ default_factory=FaceIdPostProcessConfig
+ )
+ grid_num_images: int = 6
+ grid_rows: int = 2
+ grid_cols: int = 3
+ grid_image_size: int = 512
+ debug_mode: bool = False
+ overwrite: bool = True
+
+
+class PartSegmenter:
+ def __init__(self, cfg: PartSegConfig):
+ self.validate_config(cfg)
+ self.cfg = cfg
+ self.seg_checker = PartSegChecker(GPT_CLIENT)
+ self.model = self.import_model()
+ self.pipeline = self.build_pipeline(self.model)
+
+ def validate_config(self, cfg: PartSegConfig) -> None:
+ if cfg.model_name != "p3sam":
+ raise ValueError(
+ "unsupported model '{}', available models: p3sam".format(
+ cfg.model_name
+ )
+ )
+
+ if cfg.model_name == "p3sam":
+ cfg.thirdparty_root = "thirdparty/Hunyuan3D-Part"
+
+ if not cfg.urdf_paths:
+ raise ValueError("urdf_paths must be provided.")
+
+ if len(cfg.output_dirs) == 0:
+ cfg.output_dirs = [
+ os.path.join(os.path.dirname(path), "affordance")
+ for path in cfg.urdf_paths
+ ]
+ if len(cfg.urdf_paths) != len(cfg.output_dirs):
+ raise ValueError(
+ "urdf-derived mesh paths and output_dirs must have the same length, "
+ f"got {len(cfg.urdf_paths)} and {len(cfg.output_dirs)}."
+ )
+
+ def import_model(self):
+ if self.cfg.model_name == "p3sam":
+ p3sam_root = os.path.join(self.cfg.thirdparty_root, "P3-SAM")
+ p3sam_demo = os.path.join(p3sam_root, "demo")
+ for path in [p3sam_demo, p3sam_root]:
+ if path not in sys.path:
+ sys.path.append(path)
+ from embodied_gen.utils.monkey_patch.p3sam import (
+ monkey_patch_p3sam,
+ )
+
+ monkey_patch_p3sam()
+ from auto_mask import AutoMask
+
+ return AutoMask
+
+ raise ValueError("unsupported model '{}'".format(self.cfg.model_name))
+
+ def build_pipeline(self, model):
+ if self.cfg.model_name == "p3sam":
+ ckpt_path = os.path.expanduser(
+ "~/.cache/huggingface/hub/P3-SAM/p3sam/p3sam.safetensors"
+ )
+ if not os.path.exists(ckpt_path):
+ download_dir = os.path.expanduser(
+ "~/.cache/huggingface/hub/P3-SAM"
+ )
+ logger.info(
+ "Downloading P3-SAM weights from Hugging Face to {}. "
+ "First-time use may take several minutes; please wait patiently.".format(
+ ckpt_path
+ )
+ )
+ snapshot_download(
+ repo_id="tencent/Hunyuan3D-Part",
+ allow_patterns="p3sam/*",
+ local_dir=download_dir,
+ )
+ sonata_path = os.path.expanduser(
+ "~/.cache/huggingface/hub/sonata/sonata.pth"
+ )
+ if not os.path.exists(sonata_path):
+ logger.info(
+ "Downloading Sonata weights from Hugging Face to {}. "
+ "First-time use may take several minutes; please wait patiently.".format(
+ sonata_path
+ )
+ )
+ return model(
+ ckpt_path=ckpt_path,
+ threshold=self.cfg.p3sam.threshold,
+ post_process=self.cfg.p3sam.post_process,
+ )
+
+ raise ValueError("unsupported model '{}'".format(self.cfg.model_name))
+
+ def build_texture(
+ self,
+ mesh: trimesh.Trimesh,
+ face_colors: np.ndarray,
+ min_cell: int = 16,
+ max_tex: int = 8192,
+ ) -> trimesh.Trimesh:
+ if len(face_colors) != len(mesh.faces):
+ raise ValueError(
+ f"face_colors length must match mesh faces, got {len(face_colors)} and {len(mesh.faces)}"
+ )
+
+ from PIL import Image, ImageDraw
+
+ faces = np.asarray(mesh.faces, dtype=np.int64)
+ vertices = np.asarray(mesh.vertices, dtype=np.float64)
+ n_faces = len(faces)
+ grid = int(np.ceil(np.sqrt(n_faces)))
+ textured_size = 1 << int(np.ceil(np.log2(max(1, grid * min_cell))))
+ textured_size = min(max_tex, max(min_cell, textured_size))
+ cell = textured_size / grid
+
+ image = Image.new(
+ "RGBA", (textured_size, textured_size), (255, 255, 255, 255)
+ )
+ draw = ImageDraw.Draw(image)
+ uv = np.zeros((n_faces * 3, 2), dtype=np.float64)
+ local_uv = np.asarray(
+ [
+ [0.18, 0.18],
+ [0.82, 0.18],
+ [0.18, 0.82],
+ ],
+ dtype=np.float64,
+ )
+
+ for face_idx, color in enumerate(face_colors):
+ row, col = divmod(face_idx, grid)
+ rgba = tuple(int(channel) for channel in color)
+ x0 = int(np.floor(col * cell))
+ y0 = int(np.floor(row * cell))
+ x1 = int(np.ceil((col + 1) * cell))
+ y1 = int(np.ceil((row + 1) * cell))
+ draw.rectangle((x0, y0, x1, y1), fill=rgba)
+ uv[face_idx * 3 : face_idx * 3 + 3, 0] = (
+ col + local_uv[:, 0]
+ ) / grid
+ uv[face_idx * 3 : face_idx * 3 + 3, 1] = (
+ 1.0 - (row + local_uv[:, 1]) / grid
+ )
+
+ textured_mesh = trimesh.Trimesh(
+ vertices=vertices[faces.reshape(-1)],
+ faces=np.arange(n_faces * 3, dtype=np.int64).reshape(n_faces, 3),
+ process=False,
+ )
+ material = trimesh.visual.texture.SimpleMaterial(
+ image=image,
+ diffuse=[255, 255, 255, 255],
+ name="part_segmentation",
+ )
+ textured_mesh.visual = trimesh.visual.texture.TextureVisuals(
+ uv=uv, material=material
+ )
+ return textured_mesh
+
+ def remap_to_palette(self, face_ids: np.ndarray) -> np.ndarray:
+ face_ids = np.asarray(face_ids)
+ mapped_ids = np.full(face_ids.shape, -1, dtype=np.int64)
+ unique_ids = np.unique(face_ids[face_ids >= 0])
+ if len(unique_ids) > len(PALETTE):
+ return None
+
+ for palette_idx, part_id in enumerate(unique_ids):
+ palette_id = PALETTE[palette_idx][0]
+ mapped_ids[face_ids == part_id] = palette_id
+ return mapped_ids
+
+ def export_seg_mesh(
+ self,
+ mesh,
+ face_ids: np.ndarray,
+ output_dir: str,
+ mesh_transform: dict | None = None,
+ ) -> None:
+ output_path = os.path.join(output_dir, "mesh_part_seg.glb")
+ palette_colors = {
+ palette_id: color for palette_id, color, _ in PALETTE
+ }
+
+ face_colors = np.zeros((len(face_ids), 4), dtype=np.uint8)
+ face_colors[:, 3] = 255
+ for idx, part_id in enumerate(face_ids):
+ if part_id < 0:
+ face_colors[idx, :3] = np.array([0, 0, 0], dtype=np.uint8)
+ else:
+ face_colors[idx, :3] = palette_colors[int(part_id)]
+
+ color_mesh = self.build_texture(mesh.copy(), face_colors)
+ color_mesh.metadata["face_ids"] = face_ids.tolist()
+ if mesh_transform is None:
+ save_mesh(
+ color_mesh, output_path, apply_origin=False, apply_scale=False
+ )
+ else:
+ save_mesh(
+ color_mesh,
+ output_path,
+ apply_origin=True,
+ apply_scale=True,
+ **mesh_transform,
+ )
+ return output_path
+
+ def count_parts(self, face_ids: np.ndarray) -> int:
+ valid_ids = np.asarray(face_ids)[np.asarray(face_ids) >= 0]
+ return int(len(np.unique(valid_ids)))
+
+ def postprocess_face_ids(
+ self,
+ mesh: trimesh.Trimesh,
+ face_ids: np.ndarray | None,
+ ) -> np.ndarray | None:
+ if face_ids is None or not self.cfg.face_id_postprocess.enabled:
+ return face_ids
+
+ merge_cfg = SmoothFaceLabelMergeConfig(
+ max_smooth_angle_deg=self.cfg.face_id_postprocess.max_smooth_angle_deg,
+ vertex_merge_tolerance=self.cfg.face_id_postprocess.vertex_merge_tolerance,
+ min_component_area_ratio=self.cfg.face_id_postprocess.min_component_area_ratio,
+ min_component_faces=self.cfg.face_id_postprocess.min_component_faces,
+ preserve_negative=self.cfg.face_id_postprocess.preserve_negative,
+ )
+ face_ids, stats = merge_smooth_face_labels(mesh, face_ids, merge_cfg)
+ if stats["faces_changed"] > 0:
+ logger.info(
+ "Part seg face_id postprocess changed "
+ f"{stats['faces_changed']} faces across "
+ f"{stats['components_changed']} smooth components."
+ )
+ if self.cfg.face_id_postprocess.fill_enclosed_components:
+ fill_cfg = EnclosedFaceLabelFillConfig(
+ max_component_area_ratio=(
+ self.cfg.face_id_postprocess.max_enclosed_component_area_ratio
+ ),
+ max_component_faces=(
+ self.cfg.face_id_postprocess.max_enclosed_component_faces
+ ),
+ min_enclosure_neighbor_ratio=(
+ self.cfg.face_id_postprocess.min_enclosure_neighbor_ratio
+ ),
+ vertex_merge_tolerance=(
+ self.cfg.face_id_postprocess.vertex_merge_tolerance
+ ),
+ )
+ face_ids, stats = fill_small_enclosed_face_labels(
+ mesh,
+ face_ids,
+ fill_cfg,
+ )
+ if stats["faces_changed"] > 0:
+ logger.info(
+ "Part seg face_id postprocess filled "
+ f"{stats['faces_changed']} enclosed faces across "
+ f"{stats['components_filled']} components."
+ )
+ return face_ids
+
+ def update_urdf(
+ self,
+ urdf_path: str,
+ seg_mesh_path: str,
+ n_parts: int,
+ check_info: str,
+ mesh_transform: dict | None = None,
+ ) -> None:
+ urdf_dir = os.path.dirname(urdf_path)
+ seg_mesh_file = (
+ os.path.relpath(
+ seg_mesh_path,
+ urdf_dir,
+ )
+ if seg_mesh_path
+ else None
+ )
+ origin_attrs = {"xyz": "0 0 0", "rpy": "0 0 0"}
+ mesh_attrs = {"filename": seg_mesh_file, "scale": "1 1 1"}
+ if mesh_transform:
+ origin_attrs = {
+ "xyz": str(mesh_transform["origin_xyz"]),
+ "rpy": str(mesh_transform["origin_rpy"]),
+ }
+ mesh_attrs["scale"] = str(mesh_transform["scale"])
+
+ updates = {
+ "custom_data/affordance/part_count": n_parts,
+ "custom_data/affordance/visual_seg": {
+ "text": None,
+ "clear_attrs": True,
+ "clear_children": True,
+ },
+ "custom_data/affordance/visual_seg/origin": {
+ "attrs": origin_attrs,
+ },
+ "custom_data/affordance/visual_seg/geometry/mesh": {
+ "attrs": mesh_attrs,
+ },
+ "custom_data/quality/PartSegChecker": check_info,
+ }
+
+ URDFFile(urdf_path).write(updates)
+
+ def render_video(
+ self, mesh_path: str, output_dir: str, output_subdir: str = "renders"
+ ) -> list[str]:
+ return render_asset3d(
+ mesh_path,
+ output_root=output_dir,
+ distance=5.0,
+ num_images=128,
+ elevation=(45.0,),
+ gen_color_mp4=True,
+ output_subdir=output_subdir,
+ pbr_light_factor=1.5,
+ pbr_metallic=False,
+ )
+
+ def render_grid(
+ self,
+ mesh_path: str,
+ output_dir: str,
+ output_subdir: str = "renders",
+ ) -> tuple[str, list[str]]:
+ grid_path, view_paths = render_grid(
+ mesh_path,
+ output_dir,
+ output_subdir=output_subdir,
+ num_images=self.cfg.grid_num_images,
+ grid_rows=self.cfg.grid_rows,
+ grid_cols=self.cfg.grid_cols,
+ view_size=self.cfg.grid_image_size,
+ )
+ return grid_path, view_paths
+
+ def render_seg_result(
+ self,
+ mesh: trimesh.Trimesh,
+ face_ids: np.ndarray,
+ output_dir: str,
+ mesh_transform: dict | None = None,
+ ) -> tuple[str, str, str]:
+ seg_mesh_path = self.export_seg_mesh(
+ mesh,
+ face_ids,
+ output_dir,
+ mesh_transform,
+ )
+ mask_grid_path, _ = self.render_grid(
+ seg_mesh_path,
+ output_dir,
+ output_subdir="part_seg_renders",
+ )
+ color_names = collect_colors(face_ids)
+ logger.info(f"Part seg face colors: {color_names}")
+ return seg_mesh_path, mask_grid_path, color_names
+
+ def apply_merge_hints(
+ self,
+ face_ids: np.ndarray,
+ check_info: str,
+ ) -> tuple[np.ndarray, bool]:
+ merge_groups = self.parse_merge_groups(check_info)
+ if not merge_groups:
+ return face_ids, False
+
+ merged_face_ids = np.asarray(face_ids).copy()
+ color_to_id = {
+ color_name.lower(): palette_id
+ for palette_id, _, color_name in PALETTE
+ }
+ applied_groups = 0
+
+ for group in merge_groups:
+ color_names = group.get("colors", [])
+ palette_ids = []
+ for color_name in color_names:
+ palette_id = color_to_id.get(str(color_name).strip().lower())
+ if palette_id is not None and palette_id not in palette_ids:
+ palette_ids.append(palette_id)
+
+ active_ids = [
+ palette_id
+ for palette_id in palette_ids
+ if np.any(merged_face_ids == palette_id)
+ ]
+ if len(active_ids) < 2:
+ continue
+
+ target_id = max(
+ active_ids,
+ key=lambda palette_id: int(
+ np.count_nonzero(merged_face_ids == palette_id)
+ ),
+ )
+ changed_faces = 0
+ for palette_id in active_ids:
+ if palette_id == target_id:
+ continue
+ source_mask = merged_face_ids == palette_id
+ changed_faces += int(np.count_nonzero(source_mask))
+ merged_face_ids[source_mask] = target_id
+
+ applied_groups += 1
+ logger.info(
+ "Applied PartSegChecker merge group for %s: colors=%s, "
+ "target_palette_id=%s, faces_changed=%s",
+ group.get("target_part", "unknown part"),
+ color_names,
+ target_id,
+ changed_faces,
+ )
+
+ return merged_face_ids, applied_groups > 0
+
+ def parse_merge_groups(self, check_info: str) -> list[dict]:
+ if not isinstance(check_info, str):
+ return []
+
+ json_start = check_info.find("{")
+ json_end = check_info.rfind("}")
+ if json_start < 0 or json_end <= json_start:
+ return []
+
+ try:
+ parsed = json_repair.loads(check_info[json_start : json_end + 1])
+ except Exception as exc:
+ logger.warning(
+ f"Failed to parse PartSegChecker merge hints: {exc}"
+ )
+ return []
+
+ merge_groups = parsed.get("merge_groups", [])
+ if not isinstance(merge_groups, list):
+ return []
+
+ valid_groups = []
+ for group in merge_groups:
+ if not isinstance(group, dict):
+ continue
+ colors = group.get("colors", [])
+ if not isinstance(colors, list) or len(colors) < 2:
+ continue
+ valid_groups.append(group)
+ return valid_groups
+
+ def infer_seg(self, mesh: trimesh.Trimesh, output_dir: str) -> bool:
+ unit_mesh = normalize_mesh(mesh)
+ if self.cfg.model_name == "p3sam":
+ set_seed(self.cfg.p3sam.seed)
+ os.makedirs(output_dir, exist_ok=True)
+ _, face_ids, _ = self.pipeline.predict_aabb(
+ unit_mesh,
+ save_path=output_dir,
+ **asdict(self.cfg.p3sam),
+ )
+ else:
+ raise ValueError(
+ "unsupported model '{}'".format(self.cfg.model_name)
+ )
+ face_ids = self.remap_to_palette(face_ids)
+ return face_ids
+
+ def process(self, urdf_path: str, output_dir: str) -> bool:
+ try:
+ return self._process_impl(urdf_path, output_dir)
+ except Exception as exc:
+ logger.error(
+ "Part segmentation failed for URDF {}: {}".format(
+ urdf_path,
+ exc,
+ )
+ )
+ return False
+
+ def _process_impl(self, urdf_path: str, output_dir: str) -> bool:
+ seg_mesh_path = os.path.join(output_dir, "mesh_part_seg.glb")
+ mask_grid_path = None
+ if not self.cfg.overwrite and os.path.exists(seg_mesh_path):
+ logger.info(
+ f"Skip existing part segmentation result: {seg_mesh_path}"
+ )
+ return True
+
+ urdf = URDFFile(urdf_path)
+ category = urdf.get_category()
+ mesh_path = urdf.get_mesh_path(self.cfg.mesh_type)
+ mesh_transform = {}
+ if urdf_path is not None:
+ mesh_transform = urdf.get_mesh_transform(self.cfg.mesh_type)
+ mesh = load_mesh(
+ mesh_path,
+ apply_origin=True,
+ apply_scale=True,
+ **mesh_transform,
+ )
+ rgb_grid_path, _ = self.render_grid(mesh_path, output_dir)
+
+ face_ids = self.infer_seg(mesh, output_dir)
+ face_ids = self.postprocess_face_ids(mesh, face_ids)
+
+ if face_ids is None:
+ logger.error("Part seg failed due to exceeding palette size. ")
+ success, check_info = (
+ False,
+ f"NO: part number exceeds palette size {len(PALETTE)}",
+ )
+ seg_mesh_path = None
+ else:
+ seg_mesh_path, mask_grid_path, color_names = (
+ self.render_seg_result(
+ mesh,
+ face_ids,
+ output_dir,
+ mesh_transform if urdf_path is not None else None,
+ )
+ )
+ success, check_info = self.seg_checker(
+ category,
+ color_names,
+ rgb_grid_path,
+ mask_grid_path,
+ )
+ self.retry_count = 0
+ while not success and self.retry_count < self.cfg.merge_retries:
+ merged_face_ids, merge_applied = self.apply_merge_hints(
+ face_ids,
+ check_info,
+ )
+ if merge_applied:
+ face_ids = merged_face_ids
+ # ๅคไปฝmask_grid_path
+ mask_grid_path_bak = mask_grid_path.replace(
+ "affordance_grid",
+ f"affordance_grid_{self.retry_count}",
+ )
+ if os.path.exists(mask_grid_path):
+ shutil.copy2(mask_grid_path, mask_grid_path_bak)
+ seg_mesh_path, mask_grid_path, color_names = (
+ self.render_seg_result(
+ mesh,
+ face_ids,
+ output_dir,
+ mesh_transform if urdf_path is not None else None,
+ )
+ )
+ success, check_info = self.seg_checker(
+ category,
+ color_names,
+ rgb_grid_path,
+ mask_grid_path,
+ )
+ self.retry_count += 1
+
+ if success:
+ logger.info(
+ f"Part seg successful for {mesh_path} with {self.count_parts(face_ids)} parts. "
+ )
+ else:
+ logger.error(
+ f"Part seg failed for {mesh_path}. Checker info: {check_info}"
+ )
+ self.update_urdf(
+ urdf_path,
+ seg_mesh_path,
+ self.count_parts(face_ids) if face_ids is not None else 0,
+ check_info,
+ mesh_transform if urdf_path is not None else None,
+ )
+ if self.cfg.debug_mode:
+ self.render_video(mesh_path, output_dir)
+ if seg_mesh_path:
+ self.render_video(
+ seg_mesh_path, output_dir, output_subdir="part_seg_renders"
+ )
+ else:
+ grid_dirs = {os.path.dirname(rgb_grid_path)}
+ if mask_grid_path:
+ grid_dirs.add(os.path.dirname(mask_grid_path))
+ for grid_dir in grid_dirs:
+ if grid_dir:
+ shutil.rmtree(grid_dir, ignore_errors=True)
+ return success
+
+
+def run_part_seg(cfg: PartSegConfig) -> None:
+ segmenter = PartSegmenter(cfg)
+ for urdf_path, output_dir in zip(cfg.urdf_paths, cfg.output_dirs):
+ segmenter.process(urdf_path, output_dir)
+
+
+def entrypoint(*args, **kwargs) -> None:
+ cfg = tyro.cli(PartSegConfig)
+ run_part_seg(cfg)
+
+
+if __name__ == "__main__":
+ entrypoint()
diff --git a/embodied_gen/scripts/affordance_annot/partsemantics_annot.py b/embodied_gen/scripts/affordance_annot/partsemantics_annot.py
new file mode 100644
index 0000000..68bf7a2
--- /dev/null
+++ b/embodied_gen/scripts/affordance_annot/partsemantics_annot.py
@@ -0,0 +1,438 @@
+# Project EmbodiedGen
+#
+# Copyright (c) 2025 Horizon Robotics. All Rights Reserved.
+#
+# 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.
+
+import json
+import os
+import shutil
+from dataclasses import dataclass, field
+from typing import Literal
+
+import json_repair
+import tyro
+from embodied_gen.utils.general import filter_warnings
+
+filter_warnings()
+
+
+from embodied_gen.utils.gpt_clients import GPT_CLIENT
+from embodied_gen.utils.io_utils import URDFFile, load_mesh
+from embodied_gen.utils.log import logger
+from embodied_gen.utils.vis_utils import (
+ PALETTE,
+ collect_colors,
+ render_grid,
+ visualize_partsemantics,
+)
+from embodied_gen.validators.quality_checkers import PartSemanticsChecker
+
+__all__ = [
+ "PartSemanticsAnnotConfig",
+ "PartSemanticsAnnotator",
+ "run_partsemantics_annot",
+ "entrypoint",
+]
+
+
+@dataclass
+class PartSemanticsAnnotConfig:
+ urdf_paths: list[str] = field(default_factory=list)
+ output_dirs: list[str] = field(default_factory=list)
+ mesh_type: Literal["visual", "collision"] = "visual"
+ max_tokens: int = 10240
+ max_repairs: int = 2
+ visualize: bool = False
+ vis_fps: int = 12
+ vis_frames_per_part: int = 36
+ vis_view_size: int = 1080
+ grid_num_images: int = 6
+ grid_rows: int = 2
+ grid_cols: int = 3
+ grid_image_size: int = 512
+ debug_mode: bool = False
+ overwrite: bool = True
+
+
+class PartSemanticsAnnotator:
+ def __init__(self, cfg: PartSemanticsAnnotConfig):
+ self.validate_config(cfg)
+ self.cfg = cfg
+ self.gpt_client = GPT_CLIENT
+ self.checker = PartSemanticsChecker(GPT_CLIENT)
+ self.system_prompt = (
+ "You are an expert annotator for 3D object affordances. You will receive two aligned multi-view grid images of the same object: "
+ "the first grid is the RGB render, and the second grid is a colored part mask where each color denotes one segmented part. "
+ "You will also receive the object category and the list of colors that appear in the mask.\n"
+ "Your task is to infer affordance information for each meaningful colored part.\n"
+ "For every annotated part, provide:\n"
+ "1. A concise self-contained part name. Prefer the natural standalone part name when it is clear, e.g. \"lampshade\" or \"light bulb\"; add the object category only when the name would otherwise be ambiguous, e.g. \"stem of apple\" rather than \"stem\".\n"
+ "2. Whether the part is realistically useful for robotic grasping with a gripper.\n"
+ "3. If it is graspable, concrete robotic grasp scenarios with confidence scores.\n"
+ "4. Functional labels: concrete short phrases describing what the part can be used for and what functional role it provides.\n"
+ "5. Semantic description: a concise natural-language description of the part, including its role, RGB appearance color, material type, surface finish, tactile texture, shape, relative size/position, and any visible markings when inferable.\n"
+ "Use the RGB grid to understand physical appearance and the mask grid to map parts to colors. "
+ "The mask color is only an ID for the segmented part; do not describe mask colors as physical object colors. "
+ "Only use mask colors from the provided color list. Do not invent mask colors. "
+ "If a colored region has no clear affordance, still include it when it is a distinct physical part, with conservative functional labels. "
+ "Return only valid JSON. Do not wrap the JSON in Markdown or code fences."
+ )
+ self.user_prompt = (
+ "Object category: {category}\n"
+ "Mask color list: {color_names}\n"
+ "Image inputs:\n"
+ "- Image 1: RGB multi-view grid of the object.\n"
+ "- Image 2: part mask multi-view grid aligned with Image 1. Different colors indicate different segmented parts.\n\n"
+ "Annotate the affordance of the visible colored parts. Match each part to exactly one color from the color list.\n"
+ "For each part, include these fields:\n"
+ "- \"part_name\": concise semantic name of the part. Use natural standalone names when they are already clear, such as \"lampshade\", \"light bulb\", \"keyboard\", \"wheel\", or \"mug handle\". Use \"part of object\" only when the standalone part name is ambiguous or too generic, such as \"stem of apple\", \"blade of knife\", or \"base of lamp\".\n"
+ "- \"mask_color\": one mask color from the provided color list. This is the segmentation color, not the physical RGB color.\n"
+ "- \"graspable\": true or false.\n"
+ "- \"grasp_scenarios\": list of dicts, each with \"scenario\" and \"confidence\"; each \"confidence\" belongs to that same scenario and is a float from 0.0 to 1.0 estimating how likely a robot gripper would choose this part for that scenario. Use accurate, distinguishable confidences when multiple parts share the same scenario. Do not give high confidence to theoretically possible but uncommon grasps. Use an empty list if graspable is false.\n"
+ "- \"functional_labels\": list of 2 to 6 concrete short phrases describing what this part can be used to do and what functional role it provides.\n"
+ "- \"semantic_description\": one concise but complete sentence describing the part's RGB appearance color, material type, surface finish, tactile texture, shape, relative size, location, and any visible markings when inferable. Do not mention the mask color unless it is also the real RGB appearance.\n\n"
+ "Guidelines:\n"
+ "- Use the RGB grid for appearance and the mask grid for color-to-part mapping.\n"
+ "- Treat grasping as robotic gripper grasping, not human hand grasping.\n"
+ "- A part is graspable only if it is a plausible and useful place for a robot gripper to hold, pick up, stabilize, pull, or manipulate the object.\n"
+ "- If a part is only theoretically graspable but normally poor or uncommon for grasping, keep \"graspable\" True only when there is a realistic scenario, and assign low confidence. For example, an apple stem may be graspable in a delicate stem-picking scenario but is low confidence for lifting the apple.\n"
+ "- If a part is functional but not graspable, set \"graspable\" to False and still describe its function.\n"
+ "- Functional labels should describe what the part can be used for or what function it enables, such as \"support\", \"contain\", \"cut\", \"press\", \"cover\", \"connect\", \"stabilize\", or \"indicate orientation\".\n"
+ "- Semantic descriptions should cover visible material and texture cues when inferable: material type such as metal, plastic, ceramic, rubber, fabric, wood, glass, or food skin; surface finish such as glossy, matte, translucent, reflective, or dull; tactile texture such as soft, hard, smooth, rough, fine-grained, pebbled, ribbed, woven, or granular; inherent patterns or special markings such as wood grain, fabric weave, seams, engraved text, logos, printed icons, labels, or scratches.\n"
+ "- Keep semantic descriptions concise, usually one sentence under 35 words, and do not invent material or markings that are not visually supported.\n"
+ "- Do not assign affordances to background regions.\n"
+ "- Do not include colors that are not in the color list.\n\n"
+ "Output format: Return exactly one JSON object with this schema:\n"
+ "{{\"affordances\": [{{\"part_name\": \"mug handle\", \"mask_color\": \"Red\", \"graspable\": true, \"grasp_scenarios\": [{{\"scenario\": \"grasp the handle to lift the mug\", \"confidence\": 0.92}}, {{\"scenario\": \"hold the handle to stabilize the mug while pouring\", \"confidence\": 0.84}}], \"functional_labels\": [\"provide a side grip\", \"lift the mug without touching the body\", \"stabilize the mug while pouring\"], \"semantic_description\": \"Curved glossy ceramic handle with a hard smooth surface, matching the mug body and sized for side gripping.\"}}, "
+ "{{\"part_name\": \"stem of apple\", \"mask_color\": \"Green\", \"graspable\": true, \"grasp_scenarios\": [{{\"scenario\": \"delicately grasp the stem to orient the apple\", \"confidence\": 0.28}}, {{\"scenario\": \"lift the apple by the stem\", \"confidence\": 0.12}}], \"functional_labels\": [\"connect to branch\", \"indicate top orientation\"], \"semantic_description\": \"Small thin brown woody stem with a hard rough fibrous texture protruding from the apple top.\"}}, "
+ "{{\"part_name\": \"body of apple\", \"mask_color\": \"Blue\", \"graspable\": true, \"grasp_scenarios\": [{{\"scenario\": \"grasp the apple body to pick it up\", \"confidence\": 0.88}}], \"functional_labels\": [\"contain edible flesh\", \"support biting\", \"main grasp surface\"], \"semantic_description\": \"Large rounded red apple body with glossy smooth food skin, firm texture, and subtle natural color speckles.\"}}]}}\n"
+ "If the target object cannot be identified, output exactly: {{\"affordances\": []}}"
+ )
+
+ def validate_config(self, cfg: PartSemanticsAnnotConfig) -> None:
+ if not cfg.urdf_paths:
+ raise ValueError("urdf_paths must be provided.")
+
+ if len(cfg.output_dirs) == 0:
+ cfg.output_dirs = [
+ os.path.join(os.path.dirname(path), "affordance")
+ for path in cfg.urdf_paths
+ ]
+
+ if len(cfg.urdf_paths) != len(cfg.output_dirs):
+ raise ValueError(
+ "urdf_paths and output_dirs must have the same length, "
+ f"got {len(cfg.urdf_paths)} and {len(cfg.output_dirs)}."
+ )
+
+ if cfg.grid_num_images != cfg.grid_rows * cfg.grid_cols:
+ raise ValueError(
+ "grid_num_images must equal grid_rows * grid_cols, "
+ f"got {cfg.grid_num_images} and "
+ f"{cfg.grid_rows} * {cfg.grid_cols}."
+ )
+
+ def check_requires(self, urdf_path: str) -> tuple[str, str]:
+ urdf = URDFFile(urdf_path)
+ seg_mesh_path = urdf.get_mesh_part_seg_path()
+ if not os.path.exists(seg_mesh_path):
+ raise FileNotFoundError(
+ f"Part segmentation result not found. Run part segmentation before "
+ f"partsemantics annotation. Missing: {seg_mesh_path}"
+ )
+ return seg_mesh_path
+
+ def parse_response(self, response: str) -> dict:
+ answer = str(response).strip()
+ parsed = json.loads(answer)
+ return parsed
+
+ def add_palette_ids(self, payload: dict) -> dict:
+ color_to_id = {
+ color_name: palette_id for palette_id, _, color_name in PALETTE
+ }
+ for partsemantics in payload.get("affordances", []):
+ if isinstance(partsemantics, dict):
+ partsemantics["id"] = color_to_id.get(
+ partsemantics.get("mask_color")
+ )
+ return payload
+
+ def check_response(
+ self, response: str, rgb_grid_path: str, mask_grid_path: str
+ ) -> dict:
+ passed, message = self.checker(
+ response,
+ rgb_grid_path,
+ mask_grid_path,
+ )
+ check_info = {
+ "success": passed,
+ "message": message,
+ }
+ if not passed:
+ feedback = self._parse_checker_message(message)
+ modified_response = (
+ feedback.get("modified_response")
+ if isinstance(feedback, dict)
+ else None
+ )
+ if isinstance(modified_response, str):
+ check_info["modified_response"] = modified_response.strip()
+ elif modified_response is not None:
+ check_info["modified_response"] = json.dumps(
+ modified_response,
+ ensure_ascii=False,
+ )
+ return check_info
+
+ def _parse_checker_message(self, checker_message: str) -> dict | None:
+ if not isinstance(checker_message, str):
+ return None
+
+ answer = checker_message.strip()
+ try:
+ feedback = json_repair.loads(answer)
+ except (json.JSONDecodeError, ValueError, TypeError):
+ json_start = answer.find("{")
+ json_end = answer.rfind("}")
+ if json_start < 0 or json_end <= json_start:
+ return None
+ try:
+ feedback = json_repair.loads(answer[json_start : json_end + 1])
+ except (json.JSONDecodeError, ValueError, TypeError):
+ return None
+
+ return feedback if isinstance(feedback, dict) else None
+
+ def _quality_check_for_save(
+ self, quality_check: dict | None
+ ) -> dict | None:
+ if quality_check is None:
+ return None
+ if not isinstance(quality_check, dict):
+ return {"success": None, "message": str(quality_check)}
+
+ message = quality_check.get("message", "")
+ feedback = self._parse_checker_message(message)
+ if isinstance(feedback, dict) and feedback.get("reason") is not None:
+ message = str(feedback["reason"]).strip()
+ elif isinstance(message, str) and message.strip().startswith("NO:"):
+ message = message.strip()[3:].strip()
+
+ return {
+ "success": quality_check.get("success"),
+ "message": message,
+ }
+
+ def save_annotation(
+ self, response: str, output_dir: str, quality_check: dict | None = None
+ ) -> str:
+ annotation_path = os.path.join(output_dir, "affordance_annot.json")
+ if self.cfg.debug_mode:
+ raw_output_path = os.path.join(
+ output_dir, "affordance_annot_raw.txt"
+ )
+ with open(raw_output_path, "w", encoding="utf-8") as f:
+ f.write(str(response))
+
+ try:
+ payload = self.parse_response(response)
+ payload = self.add_palette_ids(payload)
+ except (json.JSONDecodeError, ValueError, TypeError) as exc:
+ payload = {
+ "status": "parse failed",
+ "affordances": [],
+ "parse_error": str(exc),
+ }
+
+ saved_quality_check = self._quality_check_for_save(quality_check)
+ if saved_quality_check is not None:
+ payload["quality_check"] = saved_quality_check
+
+ with open(annotation_path, "w", encoding="utf-8") as f:
+ json.dump(payload, f, ensure_ascii=False, indent=2)
+ return annotation_path
+
+ def update_urdf(self, urdf_path: str, annotation_path: str) -> None:
+ if urdf_path is None:
+ return
+
+ URDFFile(urdf_path).write(
+ {
+ "custom_data/affordance/affordance_annot": os.path.relpath(
+ annotation_path,
+ os.path.dirname(urdf_path),
+ ),
+ }
+ )
+
+ def render_grid(
+ self,
+ mesh_path: str,
+ output_dir: str,
+ output_subdir: str = "renders",
+ ) -> tuple[str, list[str]]:
+ grid_path, view_paths = render_grid(
+ mesh_path,
+ output_dir,
+ output_subdir=output_subdir,
+ num_images=self.cfg.grid_num_images,
+ grid_rows=self.cfg.grid_rows,
+ grid_cols=self.cfg.grid_cols,
+ view_size=self.cfg.grid_image_size,
+ )
+ return grid_path, view_paths
+
+ def _process_once(
+ self,
+ rgb_grid_path: str,
+ mask_grid_path: str,
+ color_names: str,
+ category: str,
+ ) -> tuple[bool, dict, str]:
+ response = self.gpt_client.query(
+ text_prompt=self.user_prompt.format(
+ category=category,
+ color_names=color_names,
+ ),
+ image_base64=[rgb_grid_path, mask_grid_path],
+ system_role=self.system_prompt,
+ params={"max_tokens": self.cfg.max_tokens},
+ )
+ check_info = {"success": False, "message": "NO: checker was not run"}
+ success = False
+ max_checks = self.cfg.max_repairs + 1
+ for check_idx in range(max_checks):
+ logger.info(
+ "PartSemantics quality check: attempt "
+ f"{check_idx + 1}/{max_checks}"
+ )
+ check_info = self.check_response(
+ response,
+ rgb_grid_path,
+ mask_grid_path,
+ )
+ success = check_info.get("success")
+ if success:
+ return success, check_info, response
+
+ if check_idx == max_checks - 1:
+ break
+
+ modified_response = check_info.get("modified_response")
+ if modified_response is None:
+ break
+
+ response = modified_response
+ logger.warning(
+ "PartSemantics checker failed; retrying with modified_response. "
+ )
+
+ return success, check_info, response
+
+ def process(self, urdf_path: str, output_dir: str) -> bool:
+ try:
+ return self._process_impl(urdf_path, output_dir)
+ except Exception as exc:
+ logger.error(
+ "PartSemantics annotation failed for URDF {}: {}".format(
+ urdf_path,
+ exc,
+ )
+ )
+ return False
+
+ def _process_impl(self, urdf_path: str, output_dir: str) -> bool:
+ annotation_path = os.path.join(output_dir, "affordance_annot.json")
+ if not self.cfg.overwrite and os.path.exists(annotation_path):
+ logger.info(
+ f"Skip existing PartSemantics annotation: {annotation_path}"
+ )
+ return True
+
+ urdf = URDFFile(urdf_path)
+ mesh_path = urdf.get_mesh_path(self.cfg.mesh_type)
+ seg_mesh_path = self.check_requires(urdf_path)
+
+ logger.info("Processing PartSemantics annotation...")
+ rgb_grid_path, _ = self.render_grid(mesh_path, output_dir)
+ mask_grid_path, _ = self.render_grid(
+ seg_mesh_path, output_dir, output_subdir="part_seg_renders"
+ )
+
+ _, face_ids = load_mesh(
+ seg_mesh_path, apply_origin=False, apply_scale=False
+ )
+ color_names = collect_colors(face_ids)
+ logger.info(f"Mask colors: {color_names}")
+
+ category = urdf.get_category()
+ success, check_info, response = self._process_once(
+ rgb_grid_path,
+ mask_grid_path,
+ color_names,
+ category,
+ )
+ if success:
+ logger.info(
+ "PartSemantics annotation passed quality check."
+ f"{check_info.get('message')}"
+ )
+ else:
+ logger.error(
+ "PartSemantics annotation failed quality check after "
+ f"{self.cfg.max_repairs} repair attempts;\nsaving last result. "
+ )
+
+ annotation_path = self.save_annotation(
+ response, output_dir, check_info
+ )
+ self.update_urdf(urdf_path, annotation_path)
+ if self.cfg.visualize:
+ visualize_partsemantics(
+ mesh_path,
+ seg_mesh_path,
+ annotation_path,
+ fps=self.cfg.vis_fps,
+ frames_per_part=self.cfg.vis_frames_per_part,
+ view_size=self.cfg.vis_view_size,
+ )
+
+ if not self.cfg.debug_mode:
+ for grid_dir in {
+ os.path.dirname(rgb_grid_path),
+ os.path.dirname(mask_grid_path),
+ }:
+ if grid_dir:
+ shutil.rmtree(grid_dir, ignore_errors=True)
+
+ return success
+
+
+def run_partsemantics_annot(cfg: PartSemanticsAnnotConfig) -> None:
+ annotator = PartSemanticsAnnotator(cfg)
+ for urdf_path, output_dir in zip(
+ annotator.cfg.urdf_paths, annotator.cfg.output_dirs
+ ):
+ annotator.process(urdf_path, output_dir)
+
+
+def entrypoint() -> None:
+ cfg = tyro.cli(PartSemanticsAnnotConfig)
+ run_partsemantics_annot(cfg)
+
+
+if __name__ == "__main__":
+ entrypoint()
diff --git a/embodied_gen/scripts/deformable_sim/genesis_cloth_converter.py b/embodied_gen/scripts/deformable_sim/genesis_cloth_converter.py
new file mode 100644
index 0000000..82f8ea7
--- /dev/null
+++ b/embodied_gen/scripts/deformable_sim/genesis_cloth_converter.py
@@ -0,0 +1,282 @@
+# Project EmbodiedGen
+#
+# Copyright (c) 2025 Horizon Robotics. All Rights Reserved.
+#
+# 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.
+
+
+import json
+import xml.etree.ElementTree as ET
+from dataclasses import dataclass
+from pathlib import Path
+from typing import Any
+
+import numpy as np
+import trimesh
+import tyro
+from scipy.spatial.transform import Rotation
+
+
+@dataclass
+class GenesisClothConvertConfig:
+ input_path: str
+ output_dir: str
+ scale: float | None = None
+ overwrite: bool = False
+
+
+DEFAULT_CLOTH_MATERIAL: dict[str, float | bool] = {
+ "rho": 0.2,
+ "static_friction": 0.5,
+ "kinetic_friction": 0.4,
+ "stretch_compliance": 1e-9,
+ "bending_compliance": 1e-6,
+ "stretch_relaxation": 0.5,
+ "bending_relaxation": 0.2,
+ "air_resistance": 1e-3,
+ "particle_size": 0.02,
+ "self_collision": True,
+}
+
+
+def convert_cloth_to_genesis(
+ input_path: str,
+ output_dir: str,
+ scale: float | None = None,
+ overwrite: bool = False,
+) -> str:
+ """Convert one URDF visual mesh to a Genesis cloth asset package.
+
+ Args:
+ input_path: URDF path containing a visual mesh reference.
+ output_dir: Directory where the ``genesis`` package is written.
+ scale: Optional uniform scale recorded in the manifest and used by the
+ simulator. The exported mesh itself is not rescaled.
+ overwrite: Whether to replace an existing Genesis package.
+
+ Returns:
+ Path to the generated manifest JSON file.
+ """
+
+ urdf_path = Path(input_path)
+ if not urdf_path.exists():
+ raise FileNotFoundError(f"Input URDF does not exist: {urdf_path}")
+ if urdf_path.suffix.lower() != ".urdf":
+ raise ValueError(f"input_path must point to a URDF file: {urdf_path}")
+
+ source_mesh, urdf_transform = _resolve_urdf_visual_mesh(urdf_path)
+ package_root = Path(output_dir)
+ genesis_dir = package_root / "genesis"
+ if genesis_dir.exists() and not overwrite:
+ raise FileExistsError(
+ f"Genesis cloth package already exists: {genesis_dir}. "
+ "Pass overwrite=True to replace it."
+ )
+ genesis_dir.mkdir(parents=True, exist_ok=True)
+
+ mesh = _load_mesh(source_mesh)
+ mesh, warnings = _clean_mesh(mesh)
+ mesh.apply_transform(urdf_transform["matrix"])
+ warnings.append("applied URDF visual origin transform to cloth mesh")
+
+ cloth_mesh_path = genesis_dir / "cloth_mesh.obj"
+ material_path = genesis_dir / "cloth_material.json"
+ manifest_path = genesis_dir / "manifest.json"
+
+ mesh.export(cloth_mesh_path)
+ _write_json(material_path, DEFAULT_CLOTH_MATERIAL)
+
+ mesh_stats = _mesh_stats(mesh)
+ warnings.extend(_mesh_warnings(mesh_stats))
+ manifest = {
+ "asset_name": source_mesh.stem,
+ "source_urdf": _relative_path(urdf_path, package_root),
+ "source_mesh": _relative_path(source_mesh, package_root),
+ "cloth_mesh": _relative_path(cloth_mesh_path, package_root),
+ "material": _relative_path(material_path, package_root),
+ "scale": 1.0 if scale is None else float(scale),
+ "up_axis": "z",
+ "urdf_visual_origin": urdf_transform["origin"],
+ "mesh_stats": mesh_stats,
+ "warnings": warnings,
+ }
+ _write_json(manifest_path, manifest)
+ return str(manifest_path)
+
+
+def entrypoint(**kwargs) -> str:
+ if kwargs:
+ cfg = GenesisClothConvertConfig(**kwargs)
+ else:
+ cfg = tyro.cli(GenesisClothConvertConfig)
+
+ manifest_path = convert_cloth_to_genesis(
+ input_path=cfg.input_path,
+ output_dir=cfg.output_dir,
+ scale=cfg.scale,
+ overwrite=cfg.overwrite,
+ )
+ print(manifest_path)
+ return manifest_path
+
+
+def _resolve_urdf_visual_mesh(urdf_path: Path) -> tuple[Path, dict[str, Any]]:
+ root = ET.parse(urdf_path).getroot()
+ for visual_tag in root.findall(".//visual"):
+ mesh_tag = visual_tag.find("geometry/mesh")
+ if mesh_tag is None:
+ continue
+ filename = mesh_tag.get("filename")
+ if not filename:
+ raise ValueError(
+ f"Visual mesh in URDF has no filename: {urdf_path}"
+ )
+
+ source_mesh = Path(filename)
+ if not source_mesh.is_absolute():
+ source_mesh = urdf_path.parent / source_mesh
+ if not source_mesh.exists():
+ raise FileNotFoundError(
+ f"Visual mesh does not exist: {source_mesh}"
+ )
+
+ origin_tag = visual_tag.find("origin")
+ xyz, rpy = _parse_origin(origin_tag)
+ matrix = np.eye(4, dtype=float)
+ matrix[:3, :3] = Rotation.from_euler("xyz", rpy).as_matrix()
+ matrix[:3, 3] = xyz
+ return source_mesh, {
+ "matrix": matrix,
+ "origin": {
+ "urdf": urdf_path.name,
+ "xyz": [round(float(value), 6) for value in xyz],
+ "rpy": [round(float(value), 6) for value in rpy],
+ },
+ }
+
+ raise ValueError(f"No visual mesh found in URDF: {urdf_path}")
+
+
+def _parse_origin(
+ origin_tag: ET.Element | None,
+) -> tuple[np.ndarray, np.ndarray]:
+ if origin_tag is None:
+ return np.zeros(3, dtype=float), np.zeros(3, dtype=float)
+ xyz = np.fromiter(
+ (float(value) for value in origin_tag.get("xyz", "0 0 0").split()),
+ dtype=float,
+ count=3,
+ )
+ rpy = np.fromiter(
+ (float(value) for value in origin_tag.get("rpy", "0 0 0").split()),
+ dtype=float,
+ count=3,
+ )
+ return xyz, rpy
+
+
+def _load_mesh(source_mesh: Path) -> trimesh.Trimesh:
+ loaded = trimesh.load(source_mesh, force="mesh")
+ if isinstance(loaded, trimesh.Scene):
+ meshes = [
+ geometry
+ for geometry in loaded.geometry.values()
+ if isinstance(geometry, trimesh.Trimesh)
+ ]
+ if not meshes:
+ raise ValueError(f"No mesh geometry found in scene: {source_mesh}")
+ loaded = trimesh.util.concatenate(meshes)
+ if not isinstance(loaded, trimesh.Trimesh):
+ raise TypeError(
+ f"Expected a Trimesh from {source_mesh}, got {type(loaded)}"
+ )
+ if loaded.vertices.size == 0 or loaded.faces.size == 0:
+ raise ValueError(f"Source mesh is empty: {source_mesh}")
+ return loaded
+
+
+def _clean_mesh(mesh: trimesh.Trimesh) -> tuple[trimesh.Trimesh, list[str]]:
+ mesh = mesh.copy()
+ warnings: list[str] = []
+ original_vertices = len(mesh.vertices)
+ original_faces = len(mesh.faces)
+
+ if not mesh.is_watertight:
+ warnings.append(
+ "mesh is not watertight; this is usually acceptable for cloth"
+ )
+
+ mesh.remove_unreferenced_vertices()
+ if hasattr(mesh, "nondegenerate_faces"):
+ mesh.update_faces(mesh.nondegenerate_faces())
+ else:
+ mesh.remove_degenerate_faces()
+ mesh.merge_vertices()
+ mesh.remove_unreferenced_vertices()
+
+ if (
+ len(mesh.vertices) != original_vertices
+ or len(mesh.faces) != original_faces
+ ):
+ warnings.append(
+ "mesh cleanup changed topology from "
+ f"{original_vertices} vertices/{original_faces} faces to "
+ f"{len(mesh.vertices)} vertices/{len(mesh.faces)} faces"
+ )
+ if len(mesh.vertices) == 0 or len(mesh.faces) == 0:
+ raise ValueError("Mesh cleanup produced an empty cloth mesh")
+ return mesh, warnings
+
+
+def _mesh_stats(mesh: trimesh.Trimesh) -> dict[str, Any]:
+ bbox_min, bbox_max = mesh.bounds
+ extents = np.asarray(mesh.extents, dtype=float)
+ return {
+ "vertices": int(len(mesh.vertices)),
+ "faces": int(len(mesh.faces)),
+ "bbox_min": bbox_min.astype(float).round(6).tolist(),
+ "bbox_max": bbox_max.astype(float).round(6).tolist(),
+ "extents": extents.round(6).tolist(),
+ "watertight": bool(mesh.is_watertight),
+ }
+
+
+def _mesh_warnings(mesh_stats: dict[str, Any]) -> list[str]:
+ warnings: list[str] = []
+ if mesh_stats["faces"] > 20000:
+ warnings.append(
+ "mesh has more than 20000 faces; Genesis cloth simulation may be slow"
+ )
+ extents = np.asarray(mesh_stats["extents"], dtype=float)
+ if np.any(extents <= 1e-6):
+ warnings.append("mesh has a near-zero bounding-box extent")
+ if float(extents.max()) > 10.0:
+ warnings.append(
+ "mesh bounding box is larger than 10 meters; check scale"
+ )
+ return warnings
+
+
+def _relative_path(path: Path, root: Path) -> str:
+ try:
+ return path.relative_to(root).as_posix()
+ except ValueError:
+ return path.as_posix()
+
+
+def _write_json(path: Path, data: dict[str, Any]) -> None:
+ path.write_text(json.dumps(data, indent=2) + "\n")
+
+
+if __name__ == "__main__":
+ entrypoint()
diff --git a/embodied_gen/scripts/deformable_sim/render_mesh_displacement_heatmap.py b/embodied_gen/scripts/deformable_sim/render_mesh_displacement_heatmap.py
new file mode 100644
index 0000000..b8569a1
--- /dev/null
+++ b/embodied_gen/scripts/deformable_sim/render_mesh_displacement_heatmap.py
@@ -0,0 +1,551 @@
+import argparse
+import json
+import math
+from pathlib import Path
+
+import cv2
+import numpy as np
+
+
+def main() -> None:
+ parser = argparse.ArgumentParser()
+ parser.add_argument("--video-path", required=True)
+ parser.add_argument("--mesh-path", required=True)
+ parser.add_argument("--particle-positions", required=True)
+ parser.add_argument("--sim-config", required=True)
+ parser.add_argument("--output-image", required=True)
+ parser.add_argument("--output-video", required=True)
+ parser.add_argument("--tmp-video", required=True)
+ args = parser.parse_args()
+
+ render_mesh_displacement_heatmap(
+ video_path=Path(args.video_path),
+ mesh_path=Path(args.mesh_path),
+ particle_positions_path=Path(args.particle_positions),
+ sim_config_path=Path(args.sim_config),
+ output_image_path=Path(args.output_image),
+ output_video_path=Path(args.output_video),
+ tmp_video_path=Path(args.tmp_video),
+ )
+
+
+def render_mesh_displacement_heatmap(
+ video_path: Path,
+ mesh_path: Path,
+ particle_positions_path: Path,
+ sim_config_path: Path,
+ output_image_path: Path,
+ output_video_path: Path,
+ tmp_video_path: Path,
+) -> None:
+ data = np.load(particle_positions_path)
+ positions = data["positions"].astype(np.float32)
+ rest_positions = data["rest_positions"].astype(np.float32)
+ config = json.loads(sim_config_path.read_text())
+
+ if positions.ndim != 3 or positions.shape[2] != 3:
+ raise ValueError(
+ f"Invalid particle positions shape: {positions.shape}"
+ )
+ if rest_positions.shape != positions.shape[1:]:
+ raise ValueError(
+ "rest_positions must have shape "
+ f"{positions.shape[1:]}, got {rest_positions.shape}"
+ )
+ capture = cv2.VideoCapture(str(video_path))
+ if not capture.isOpened():
+ raise RuntimeError(f"Could not open video: {video_path}")
+
+ fps = capture.get(cv2.CAP_PROP_FPS) or float(config.get("fps", 30))
+ width = int(capture.get(cv2.CAP_PROP_FRAME_WIDTH))
+ height = int(capture.get(cv2.CAP_PROP_FRAME_HEIGHT))
+ writer = cv2.VideoWriter(
+ str(tmp_video_path),
+ cv2.VideoWriter_fourcc(*"mp4v"),
+ fps,
+ (width, height),
+ )
+ if not writer.isOpened():
+ raise RuntimeError(f"Could not write video: {tmp_video_path}")
+
+ displacement = np.linalg.norm(
+ positions - rest_positions[None, :, :], axis=2
+ )
+ scale = (
+ float(np.percentile(displacement, 95)) if displacement.size else 1.0
+ )
+ scale = max(scale, 1e-6)
+
+ camera_pos = np.asarray(config["camera_pos"], dtype=np.float32)
+ camera_lookat = np.asarray(config["camera_lookat"], dtype=np.float32)
+ camera_fov = float(config["camera_fov"])
+ inset_bbox = _compute_global_inset_bbox(
+ positions=positions,
+ camera_pos=camera_pos,
+ camera_lookat=camera_lookat,
+ camera_fov=camera_fov,
+ image_size=(width, height),
+ )
+
+ snapshot_index = min(
+ positions.shape[0] - 1,
+ max(0, int(round(fps)) - 1),
+ )
+
+ frame_index = 0
+ snapshot_frame = None
+ try:
+ while frame_index < positions.shape[0]:
+ ok, frame_bgr = capture.read()
+ if not ok:
+ break
+
+ frame_rgb = cv2.cvtColor(frame_bgr, cv2.COLOR_BGR2RGB)
+ heatmap = _compose_displacement_frame(
+ frame_rgb=frame_rgb,
+ positions=positions[frame_index],
+ displacement=displacement[frame_index],
+ scale=scale,
+ camera_pos=camera_pos,
+ camera_lookat=camera_lookat,
+ camera_fov=camera_fov,
+ inset_bbox=inset_bbox,
+ )
+ if frame_index == snapshot_index:
+ snapshot_frame = heatmap
+ writer.write(cv2.cvtColor(heatmap, cv2.COLOR_RGB2BGR))
+ frame_index += 1
+ finally:
+ capture.release()
+ writer.release()
+
+ if frame_index == 0 or snapshot_frame is None:
+ raise RuntimeError("No frames were written to mesh displacement video")
+
+ output_image_path.parent.mkdir(parents=True, exist_ok=True)
+ output_video_path.parent.mkdir(parents=True, exist_ok=True)
+ cv2.imwrite(
+ str(output_image_path), cv2.cvtColor(snapshot_frame, cv2.COLOR_RGB2BGR)
+ )
+
+
+def _compose_displacement_frame(
+ frame_rgb: np.ndarray,
+ positions: np.ndarray,
+ displacement: np.ndarray,
+ scale: float,
+ camera_pos: np.ndarray,
+ camera_lookat: np.ndarray,
+ camera_fov: float,
+ inset_bbox: tuple[int, int, int, int],
+) -> np.ndarray:
+ height, width = frame_rgb.shape[:2]
+ pixels, depths = _project_points(
+ points=positions,
+ camera_pos=camera_pos,
+ camera_lookat=camera_lookat,
+ camera_fov=camera_fov,
+ image_size=(width, height),
+ )
+ normalized = np.clip(displacement / scale, 0.0, 1.0)
+ scalar_layer, weight_layer = _rasterize_particle_scalar_field(
+ pixels=pixels,
+ depths=depths,
+ normalized=normalized,
+ image_size=(width, height),
+ )
+ scalar_blur, weight_blur = _smooth_scalar_field(scalar_layer, weight_layer)
+ return _compose_rgb_with_inset(
+ frame_rgb=frame_rgb,
+ scalar_field=scalar_blur,
+ weight_field=weight_blur,
+ scale=scale,
+ inset_bbox=inset_bbox,
+ )
+
+
+def _compute_global_inset_bbox(
+ positions: np.ndarray,
+ camera_pos: np.ndarray,
+ camera_lookat: np.ndarray,
+ camera_fov: float,
+ image_size: tuple[int, int],
+) -> tuple[int, int, int, int]:
+ width, height = image_size
+ pad = 28
+ flat = positions.reshape(-1, 3)
+ pixels, depths = _project_points(
+ points=flat,
+ camera_pos=camera_pos,
+ camera_lookat=camera_lookat,
+ camera_fov=camera_fov,
+ image_size=image_size,
+ )
+ valid = depths > 1e-5
+ if not np.any(valid):
+ return (0, 0, width, height)
+ px = pixels[valid, 0]
+ py = pixels[valid, 1]
+ x0 = max(0, int(np.floor(px.min())) - pad)
+ x1 = min(width, int(np.ceil(px.max())) + pad)
+ y0 = max(0, int(np.floor(py.min())) - pad)
+ y1 = min(height, int(np.ceil(py.max())) + pad)
+ if x1 <= x0:
+ x1 = min(width, x0 + 1)
+ if y1 <= y0:
+ y1 = min(height, y0 + 1)
+ return (x0, y0, x1, y1)
+
+
+def _rasterize_particle_scalar_field(
+ pixels: np.ndarray,
+ depths: np.ndarray,
+ normalized: np.ndarray,
+ image_size: tuple[int, int],
+) -> tuple[np.ndarray, np.ndarray]:
+ width, height = image_size
+ scalar_layer = np.zeros((height, width), dtype=np.float32)
+ weight_layer = np.zeros((height, width), dtype=np.float32)
+ radius = max(5, int(round(min(width, height) / 140.0)))
+ for particle_index in np.argsort(depths)[::-1]:
+ if depths[particle_index] <= 1e-5:
+ continue
+
+ x_value, y_value = np.rint(pixels[particle_index]).astype(np.int32)
+ if (
+ x_value < -radius
+ or x_value >= width + radius
+ or y_value < -radius
+ or y_value >= height + radius
+ ):
+ continue
+
+ center = (int(x_value), int(y_value))
+ cv2.circle(
+ scalar_layer, center, radius, float(normalized[particle_index]), -1
+ )
+ cv2.circle(weight_layer, center, radius, 1.0, -1)
+
+ return scalar_layer, weight_layer
+
+
+def _smooth_scalar_field(
+ scalar_layer: np.ndarray,
+ weight_layer: np.ndarray,
+) -> tuple[np.ndarray, np.ndarray]:
+ height, width = scalar_layer.shape
+ radius = max(5, int(round(min(width, height) / 140.0)))
+ sigma = max(1.2, float(radius) * 0.55)
+ scalar_blur = cv2.GaussianBlur(scalar_layer * weight_layer, (0, 0), sigma)
+ weight_blur = cv2.GaussianBlur(weight_layer, (0, 0), sigma)
+ scalar_blur = scalar_blur / np.maximum(weight_blur, 1e-6)
+ hard_mask = _close_mask(weight_layer > 0.0, radius=max(4, radius * 2))
+ scalar_blur[~hard_mask] = 0.0
+ weight_blur[~hard_mask] = 0.0
+ return scalar_blur, weight_blur
+
+
+def _close_mask(mask: np.ndarray, radius: int) -> np.ndarray:
+ kernel_size = max(3, radius * 2 + 1)
+ kernel = cv2.getStructuringElement(
+ cv2.MORPH_ELLIPSE,
+ (kernel_size, kernel_size),
+ )
+ closed = cv2.morphologyEx(mask.astype(np.uint8), cv2.MORPH_CLOSE, kernel)
+ return closed.astype(bool)
+
+
+def _compose_rgb_with_inset(
+ frame_rgb: np.ndarray,
+ scalar_field: np.ndarray,
+ weight_field: np.ndarray,
+ scale: float,
+ inset_bbox: tuple[int, int, int, int],
+) -> np.ndarray:
+ height, width = frame_rgb.shape[:2]
+ x0, y0, x1, y1 = inset_bbox
+
+ heat_bgr = cv2.applyColorMap(
+ (np.clip(scalar_field, 0.0, 1.0) * 255).astype(np.uint8),
+ cv2.COLORMAP_TURBO,
+ )
+ heat_layer = cv2.cvtColor(heat_bgr, cv2.COLOR_BGR2RGB)
+
+ gray = cv2.cvtColor(frame_rgb, cv2.COLOR_RGB2GRAY)
+ gray_rgb = cv2.cvtColor(gray, cv2.COLOR_GRAY2RGB)
+ hard_mask = weight_field > 0.0
+ alpha = np.where(hard_mask, 0.82, 0.0).astype(np.float32)
+ inset = (
+ gray_rgb.astype(np.float32) * 0.68
+ + frame_rgb.astype(np.float32) * 0.12
+ )
+ inset = (
+ inset * (1.0 - alpha[:, :, None])
+ + heat_layer.astype(np.float32) * alpha[:, :, None]
+ )
+ inset = np.clip(inset[y0:y1, x0:x1], 0, 255).astype(np.uint8)
+
+ inset_width = 250
+ crop_height, crop_width = inset.shape[:2]
+ inset_height = max(1, int(crop_height * inset_width / max(crop_width, 1)))
+ inset = cv2.resize(
+ inset, (inset_width, inset_height), interpolation=cv2.INTER_AREA
+ )
+
+ legend_width = 30
+ legend_gap = 10
+ label_width = 54
+ margin = 28
+ panel_pad = 12
+ panel_width = (
+ inset_width + legend_gap + legend_width + label_width + panel_pad * 2
+ )
+ panel_height = inset_height + panel_pad * 2
+ panel_x = width - panel_width - margin
+ panel_y = margin
+
+ canvas = frame_rgb.copy()
+ panel = np.full((panel_height, panel_width, 3), 247, dtype=np.uint8)
+ panel = (panel.astype(np.float32) * 0.92).astype(np.uint8)
+ cv2.rectangle(
+ panel,
+ (0, 0),
+ (panel_width - 1, panel_height - 1),
+ (160, 168, 178),
+ 1,
+ )
+
+ ix = panel_pad
+ iy = panel_pad
+ panel[iy : iy + inset_height, ix : ix + inset_width] = inset
+
+ bar_x = ix + inset_width + legend_gap
+ bar = np.linspace(1.0, 0.0, inset_height, dtype=np.float32)[:, None]
+ bar_bgr = cv2.applyColorMap(
+ (bar * 255).astype(np.uint8), cv2.COLORMAP_TURBO
+ )
+ bar_rgb = cv2.cvtColor(bar_bgr, cv2.COLOR_BGR2RGB)
+ panel[iy : iy + inset_height, bar_x : bar_x + legend_width] = np.repeat(
+ bar_rgb,
+ legend_width,
+ axis=1,
+ )
+ text_x = bar_x + legend_width + 4
+ cv2.putText(
+ panel,
+ f"{scale:.3f}m",
+ (text_x, iy + 12),
+ cv2.FONT_HERSHEY_SIMPLEX,
+ 0.36,
+ (35, 40, 48),
+ 1,
+ cv2.LINE_AA,
+ )
+ cv2.putText(
+ panel,
+ "0",
+ (text_x, iy + inset_height),
+ cv2.FONT_HERSHEY_SIMPLEX,
+ 0.36,
+ (35, 40, 48),
+ 1,
+ cv2.LINE_AA,
+ )
+ canvas[
+ panel_y : panel_y + panel_height, panel_x : panel_x + panel_width
+ ] = panel
+ return canvas
+
+
+def _project_points(
+ points: np.ndarray,
+ camera_pos: np.ndarray,
+ camera_lookat: np.ndarray,
+ camera_fov: float,
+ image_size: tuple[int, int],
+) -> tuple[np.ndarray, np.ndarray]:
+ width, height = image_size
+ forward = _normalize(camera_lookat - camera_pos)
+ world_up = np.array([0.0, 0.0, 1.0], dtype=np.float32)
+ right = np.cross(forward, world_up)
+ if np.linalg.norm(right) < 1e-6:
+ world_up = np.array([0.0, 1.0, 0.0], dtype=np.float32)
+ right = np.cross(forward, world_up)
+ right = _normalize(right)
+ up = _normalize(np.cross(right, forward))
+
+ relative = points - camera_pos[None, :]
+ cam_x = relative @ right
+ cam_y = relative @ up
+ cam_z = relative @ forward
+ focal = 0.5 * float(height) / math.tan(math.radians(camera_fov) * 0.5)
+ pixels = np.stack(
+ [
+ width * 0.5 + focal * cam_x / np.maximum(cam_z, 1e-6),
+ height * 0.5 - focal * cam_y / np.maximum(cam_z, 1e-6),
+ ],
+ axis=1,
+ )
+ return pixels, cam_z
+
+
+def _draw_legend(image: np.ndarray, scale: float) -> None:
+ height, width = image.shape[:2]
+ bar_height = min(240, max(120, height // 4))
+ bar_width = 26
+ margin = 28
+ x0 = width - margin - bar_width - 52
+ y0 = margin
+
+ panel = image[
+ y0 - 10 : y0 + bar_height + 18, x0 - 10 : x0 + bar_width + 52
+ ]
+ panel[:] = (panel.astype(np.float32) * 0.35 + 245.0 * 0.65).astype(
+ np.uint8
+ )
+ cv2.rectangle(
+ image,
+ (x0 - 10, y0 - 10),
+ (x0 + bar_width + 52, y0 + bar_height + 18),
+ (155, 164, 176),
+ 1,
+ )
+
+ values = np.linspace(1.0, 0.0, bar_height, dtype=np.float32)[:, None]
+ colors_bgr = cv2.applyColorMap(
+ (values * 255).astype(np.uint8),
+ cv2.COLORMAP_TURBO,
+ )
+ colors_rgb = cv2.cvtColor(colors_bgr, cv2.COLOR_BGR2RGB)
+ image[y0 : y0 + bar_height, x0 : x0 + bar_width] = np.repeat(
+ colors_rgb,
+ bar_width,
+ axis=1,
+ )
+ cv2.putText(
+ image,
+ f"{scale:.3f}m",
+ (x0 + bar_width + 6, y0 + 12),
+ cv2.FONT_HERSHEY_SIMPLEX,
+ 0.38,
+ (35, 40, 48),
+ 1,
+ cv2.LINE_AA,
+ )
+ cv2.putText(
+ image,
+ "0",
+ (x0 + bar_width + 6, y0 + bar_height),
+ cv2.FONT_HERSHEY_SIMPLEX,
+ 0.38,
+ (35, 40, 48),
+ 1,
+ cv2.LINE_AA,
+ )
+
+
+def _load_obj_mesh(mesh_path: Path) -> tuple[np.ndarray, np.ndarray]:
+ vertices: list[list[float]] = []
+ triangles: list[list[int]] = []
+ for line in mesh_path.read_text(errors="ignore").splitlines():
+ if line.startswith("v "):
+ _, x_value, y_value, z_value, *_ = line.split()
+ vertices.append([float(x_value), float(y_value), float(z_value)])
+ continue
+ if not line.startswith("f "):
+ continue
+
+ indices = [
+ _parse_obj_index(token, len(vertices))
+ for token in line.split()[1:]
+ ]
+ if len(indices) < 3:
+ continue
+ for offset in range(1, len(indices) - 1):
+ triangles.append(
+ [indices[0], indices[offset], indices[offset + 1]]
+ )
+
+ if not vertices:
+ raise ValueError(f"No vertices found in OBJ mesh: {mesh_path}")
+ if not triangles:
+ raise ValueError(f"No faces found in OBJ mesh: {mesh_path}")
+ return (
+ np.asarray(vertices, dtype=np.float32),
+ np.asarray(triangles, dtype=np.int32),
+ )
+
+
+def _map_mesh_faces_to_particles(
+ mesh_vertices: np.ndarray,
+ mesh_faces: np.ndarray,
+ rest_positions: np.ndarray,
+ init_height: float,
+ mesh_scale: float,
+) -> np.ndarray:
+ world_vertices = mesh_vertices * float(mesh_scale)
+ world_vertices[:, 2] += float(init_height)
+ vertex_to_particle = _nearest_indices(
+ queries=world_vertices,
+ points=rest_positions,
+ )
+ particle_faces = vertex_to_particle[mesh_faces]
+ unique_counts = np.asarray(
+ [len(set(face.tolist())) for face in particle_faces],
+ dtype=np.int32,
+ )
+ particle_faces = particle_faces[unique_counts == 3]
+ if particle_faces.size == 0:
+ raise ValueError(
+ "No non-degenerate particle faces remained after mapping"
+ )
+ return np.unique(particle_faces, axis=0).astype(np.int32)
+
+
+def _nearest_indices(
+ queries: np.ndarray,
+ points: np.ndarray,
+ chunk_size: int = 1024,
+) -> np.ndarray:
+ nearest: list[np.ndarray] = []
+ points_sq = np.sum(points * points, axis=1)[None, :]
+ for start in range(0, queries.shape[0], chunk_size):
+ chunk = queries[start : start + chunk_size]
+ distances = (
+ np.sum(chunk * chunk, axis=1)[:, None]
+ + points_sq
+ - 2.0 * chunk @ points.T
+ )
+ nearest.append(np.argmin(distances, axis=1).astype(np.int32))
+ return np.concatenate(nearest, axis=0)
+
+
+def _parse_obj_index(token: str, vertex_count: int) -> int:
+ raw = int(token.split("/")[0])
+ if raw < 0:
+ return vertex_count + raw
+ return raw - 1
+
+
+def _is_triangle_outside_image(
+ triangle: np.ndarray,
+ width: int,
+ height: int,
+) -> bool:
+ return (
+ triangle[:, 0].max() < 0
+ or triangle[:, 0].min() >= width
+ or triangle[:, 1].max() < 0
+ or triangle[:, 1].min() >= height
+ )
+
+
+def _normalize(vector: np.ndarray) -> np.ndarray:
+ norm = float(np.linalg.norm(vector))
+ if norm <= 1e-8:
+ raise ValueError("Cannot normalize near-zero vector")
+ return vector / norm
+
+
+if __name__ == "__main__":
+ main()
diff --git a/embodied_gen/scripts/deformable_sim/simulate_genesis_cloth.py b/embodied_gen/scripts/deformable_sim/simulate_genesis_cloth.py
new file mode 100644
index 0000000..6ab5738
--- /dev/null
+++ b/embodied_gen/scripts/deformable_sim/simulate_genesis_cloth.py
@@ -0,0 +1,608 @@
+# Project EmbodiedGen
+#
+# Copyright (c) 2025 Horizon Robotics. All Rights Reserved.
+#
+# 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.
+
+import json
+import math
+from dataclasses import dataclass
+from pathlib import Path
+from typing import Any
+
+import tyro
+from tqdm import tqdm
+from embodied_gen.utils.log import logger
+
+DEFAULT_CAMERA_FOV = 45
+
+
+@dataclass
+class GenesisClothSimConfig:
+ asset_path: str = "outputs/cloth"
+ output_dir: str | None = None
+ init_height: float = 1.0
+ duration_seconds: float = 6.0
+ sim_steps: int | None = None
+ shake: bool = True
+ shake_steps: int | None = None
+ shake_amplitude: float = 0.08
+ device: str = "cpu"
+ headless: bool = True
+ fps: int = 30
+ render_interval: int = 1
+ camera_res: tuple[int, int] = (512, 512)
+ gravity: tuple[float, float, float] = (0.0, 0.0, 0.0)
+ background_color: tuple[float, float, float] = (0.78, 0.82, 0.88)
+ ambient_light: tuple[float, float, float] = (0.55, 0.55, 0.55)
+ floor_size: tuple[float, float] = (6.0, 6.0)
+ floor_tile_size: tuple[float, float] = (0.5, 0.5)
+ floor_color: tuple[float, float, float, float] = (0.72, 0.72, 0.68, 1.0)
+ backdrop: bool = False
+ backdrop_color: tuple[float, float, float, float] = (0.80, 0.84, 0.90, 1.0)
+
+
+def simulate_genesis_cloth(
+ asset_path: str = "outputs/cloth",
+ output_dir: str | None = None,
+ init_height: float = 1.0,
+ duration_seconds: float = 6.0,
+ sim_steps: int | None = None,
+ shake: bool = True,
+ shake_steps: int | None = None,
+ shake_amplitude: float = 0.08,
+ device: str = "cpu",
+ headless: bool = True,
+ fps: int = 30,
+ render_interval: int = 1,
+ camera_res: tuple[int, int] = (512, 512),
+ gravity: tuple[float, float, float] = (0.0, 0.0, 0.0),
+ background_color: tuple[float, float, float] = (0.78, 0.82, 0.88),
+ ambient_light: tuple[float, float, float] = (0.55, 0.55, 0.55),
+ floor_size: tuple[float, float] = (6.0, 6.0),
+ floor_tile_size: tuple[float, float] = (0.5, 0.5),
+ floor_color: tuple[float, float, float, float] = (0.72, 0.72, 0.68, 1.0),
+ backdrop: bool = False,
+ backdrop_color: tuple[float, float, float, float] = (
+ 0.80,
+ 0.84,
+ 0.90,
+ 1.0,
+ ),
+) -> str:
+ """Run a Genesis cloth drop-and-shake demo.
+
+ Args:
+ asset_path: Directory containing ``genesis/manifest.json``.
+ output_dir: Directory for simulation outputs. If unset, writes to
+ ``asset_path/genesis_sim``.
+ init_height: Height in meters for the cloth initial position.
+ duration_seconds: Default output video duration when ``sim_steps`` is not
+ set. Defaults to 6 seconds.
+ sim_steps: Optional number of Genesis simulation steps to run. If unset,
+ it is derived from ``duration_seconds * fps * render_interval``.
+ shake: Whether to apply a visible nonuniform particle displacement.
+ shake_steps: Number of steps that receive the shake motion. If unset,
+ all simulation steps are shaken.
+ shake_amplitude: Maximum displacement magnitude in meters.
+ device: Genesis backend name: ``"cpu"``, ``"cuda"``, or ``"auto"``.
+ headless: Whether to run Genesis without opening the viewer.
+ Genesis 0.4.7 uses Scene(show_viewer=False) for headless mode.
+ fps: Output video frames per second.
+ render_interval: Render every N simulation steps.
+ camera_res: Output camera resolution as ``(width, height)``. Defaults
+ to 512x512.
+ gravity: Gravity vector. Defaults to zero for the wind-sway demo so
+ the shirt does not visibly sag before the lower edge starts moving.
+ background_color: RGB color for the renderer background.
+ ambient_light: RGB ambient light color.
+ floor_size: Visible floor plane size in meters.
+ floor_tile_size: Visual tile size for the floor plane.
+ floor_color: RGBA floor material color.
+ backdrop: Whether to add a vertical background plane behind the cloth.
+ backdrop_color: RGBA backdrop material color.
+
+ Returns:
+ Path to the generated video file.
+ """
+
+ if duration_seconds <= 0:
+ raise ValueError("duration_seconds must be positive")
+ if fps <= 0:
+ raise ValueError("fps must be positive")
+ if render_interval <= 0:
+ raise ValueError("render_interval must be positive")
+ if sim_steps is None:
+ sim_steps = max(
+ 1, int(round(duration_seconds * fps * render_interval))
+ )
+ if sim_steps <= 0:
+ raise ValueError("sim_steps must be positive")
+ if shake_steps is None:
+ shake_steps = sim_steps
+ if shake_steps < 0:
+ raise ValueError("shake_steps must be non-negative")
+
+ asset_dir = Path(asset_path)
+ manifest_path = asset_dir / "genesis" / "manifest.json"
+ manifest = _read_json(manifest_path)
+ material_config = _read_json(asset_dir / manifest["material"])
+ cloth_mesh_path = asset_dir / manifest["cloth_mesh"]
+ if not cloth_mesh_path.exists():
+ raise FileNotFoundError(
+ f"Converted cloth mesh not found: {cloth_mesh_path}"
+ )
+
+ sim_output_dir = (
+ Path(output_dir) if output_dir else asset_dir / "genesis_sim"
+ )
+ sim_output_dir.mkdir(parents=True, exist_ok=True)
+ logger.info(f"Genesis cloth outputs will be saved to {sim_output_dir}")
+ video_path = sim_output_dir / "video.mp4"
+ summary_path = sim_output_dir / "run_summary.json"
+ config_path = sim_output_dir / "sim_config.json"
+ particle_positions_path = sim_output_dir / "particle_positions.npz"
+
+ import genesis as gs
+
+ backend_name, backend_warning = _init_genesis_backend(gs, device)
+
+ pbd_options = gs.options.PBDOptions(
+ particle_size=float(material_config.get("particle_size", 0.02)),
+ gravity=tuple(float(value) for value in gravity),
+ )
+ camera_pos, camera_lookat = _front_camera_pose(manifest, init_height)
+ scene = gs.Scene(
+ show_viewer=not headless,
+ sim_options=gs.options.SimOptions(
+ dt=1.0 / float(fps * render_interval),
+ gravity=tuple(float(value) for value in gravity),
+ ),
+ pbd_options=pbd_options,
+ vis_options=gs.options.VisOptions(
+ background_color=tuple(float(value) for value in background_color),
+ ambient_light=tuple(float(value) for value in ambient_light),
+ shadow=True,
+ plane_reflection=False,
+ ),
+ )
+ _add_scene_floor_and_backdrop(
+ gs=gs,
+ scene=scene,
+ camera_pos=camera_pos,
+ camera_lookat=camera_lookat,
+ floor_size=floor_size,
+ floor_tile_size=floor_tile_size,
+ floor_color=floor_color,
+ backdrop=backdrop,
+ backdrop_color=backdrop_color,
+ )
+ cloth = scene.add_entity(
+ morph=gs.morphs.Mesh(
+ file=str(cloth_mesh_path),
+ pos=(0.0, 0.0, float(init_height)),
+ scale=float(manifest.get("scale", 1.0)),
+ convexify=False,
+ ),
+ material=gs.materials.PBD.Cloth(
+ rho=float(material_config.get("rho", 0.2)),
+ static_friction=float(material_config.get("static_friction", 0.5)),
+ kinetic_friction=float(
+ material_config.get("kinetic_friction", 0.4)
+ ),
+ stretch_compliance=float(
+ material_config.get("stretch_compliance", 1e-6)
+ ),
+ bending_compliance=float(
+ material_config.get("bending_compliance", 1e-4)
+ ),
+ stretch_relaxation=float(
+ material_config.get("stretch_relaxation", 0.3)
+ ),
+ bending_relaxation=float(
+ material_config.get("bending_relaxation", 0.1)
+ ),
+ air_resistance=float(material_config.get("air_resistance", 1e-3)),
+ ),
+ )
+ camera = scene.add_camera(
+ res=camera_res,
+ pos=camera_pos,
+ lookat=camera_lookat,
+ fov=DEFAULT_CAMERA_FOV,
+ )
+ scene.build()
+
+ config = {
+ "asset_path": str(asset_dir),
+ "manifest": str(manifest_path),
+ "manifest_scale": float(manifest.get("scale", 1.0)),
+ "init_height": init_height,
+ "duration_seconds": duration_seconds,
+ "sim_steps": sim_steps,
+ "shake": shake,
+ "shake_steps": shake_steps,
+ "shake_amplitude": shake_amplitude,
+ "device": device,
+ "resolved_backend": backend_name,
+ "backend_warning": backend_warning,
+ "headless": headless,
+ "fps": fps,
+ "render_interval": render_interval,
+ "camera_res": list(camera_res),
+ "camera_pos": list(camera_pos),
+ "camera_lookat": list(camera_lookat),
+ "camera_fov": DEFAULT_CAMERA_FOV,
+ "gravity": list(gravity),
+ "background_color": list(background_color),
+ "ambient_light": list(ambient_light),
+ "floor_size": list(floor_size),
+ "floor_tile_size": list(floor_tile_size),
+ "floor_color": list(floor_color),
+ "backdrop": backdrop,
+ "backdrop_color": list(backdrop_color),
+ }
+ _write_json(config_path, config)
+
+ rest_positions = cloth.get_particles_pos().clone()
+ particle_frames = []
+ rendered_frames = 0
+ camera.start_recording()
+ for step in tqdm(range(sim_steps), desc="Genesis Cloth Simulation"):
+ if shake and step < shake_steps:
+ _shake_cloth_particles(
+ gs,
+ cloth,
+ rest_positions,
+ step,
+ shake_steps,
+ shake_amplitude,
+ )
+ scene.step()
+ if step % render_interval == 0:
+ particle_frames.append(_tensor_to_numpy(cloth.get_particles_pos()))
+ camera.render()
+ rendered_frames += 1
+ _save_camera_recording(camera, video_path, fps)
+ _save_particle_positions(
+ particle_positions_path=particle_positions_path,
+ particle_frames=particle_frames,
+ rest_positions=rest_positions,
+ fps=fps,
+ render_interval=render_interval,
+ )
+
+ summary = {
+ "video": str(video_path),
+ "particle_positions": str(particle_positions_path),
+ "rendered_frames": rendered_frames,
+ "particles": int(cloth.n_particles),
+ "sim_steps": sim_steps,
+ "duration_seconds": duration_seconds,
+ "requested_device": device,
+ "resolved_backend": backend_name,
+ "backend_warning": backend_warning,
+ "status": "completed",
+ }
+ _write_json(summary_path, summary)
+ logger.info(
+ "Genesis cloth simulation outputs: "
+ f"video {video_path}, particles {particle_positions_path}, "
+ f"config {config_path}, summary {summary_path}"
+ )
+ return str(video_path)
+
+
+def entrypoint(**kwargs) -> str:
+ if kwargs:
+ cfg = GenesisClothSimConfig(**kwargs)
+ else:
+ cfg = tyro.cli(GenesisClothSimConfig)
+
+ return simulate_genesis_cloth(
+ asset_path=cfg.asset_path,
+ output_dir=cfg.output_dir,
+ init_height=cfg.init_height,
+ duration_seconds=cfg.duration_seconds,
+ sim_steps=cfg.sim_steps,
+ shake=cfg.shake,
+ shake_steps=cfg.shake_steps,
+ shake_amplitude=cfg.shake_amplitude,
+ device=cfg.device,
+ headless=cfg.headless,
+ fps=cfg.fps,
+ render_interval=cfg.render_interval,
+ camera_res=cfg.camera_res,
+ gravity=cfg.gravity,
+ background_color=cfg.background_color,
+ ambient_light=cfg.ambient_light,
+ floor_size=cfg.floor_size,
+ floor_tile_size=cfg.floor_tile_size,
+ floor_color=cfg.floor_color,
+ backdrop=cfg.backdrop,
+ backdrop_color=cfg.backdrop_color,
+ )
+
+
+def _shake_cloth_particles(
+ gs: Any,
+ cloth: Any,
+ rest_positions: Any,
+ step: int,
+ shake_steps: int,
+ amplitude: float,
+) -> None:
+ if amplitude <= 0.0 or shake_steps <= 0:
+ return
+
+ positions = cloth.get_particles_pos().clone()
+ min_values = rest_positions.min(dim=0).values
+ max_values = rest_positions.max(dim=0).values
+ height = gs.torch.clamp(max_values[2] - min_values[2], min=1e-6)
+ y_values = rest_positions[:, 1]
+ z_values = rest_positions[:, 2]
+
+ top_ratio = gs.torch.clamp((max_values[2] - z_values) / height, 0.0, 1.0)
+ anchor_mask = top_ratio < 0.01
+ wind_strength = (
+ gs.torch.clamp((top_ratio - 0.005) / 0.995, 0.0, 1.0) ** 1.1
+ )
+
+ progress = float(step) / max(float(shake_steps), 1.0)
+ phase = 2.0 * math.pi * 2.2 * progress
+ traveling_phase = phase - 0.85 * top_ratio
+ center_y = rest_positions[:, 1].mean()
+ width = gs.torch.clamp(max_values[1] - min_values[1], min=1e-6)
+ across = (y_values - center_y) / width
+
+ offsets = gs.torch.zeros_like(rest_positions)
+ offsets[:, 0] = (
+ float(amplitude) * 2.25 * gs.torch.sin(traveling_phase) * wind_strength
+ )
+ offsets[:, 1] = (
+ float(amplitude)
+ * 0.04
+ * gs.torch.sin(traveling_phase * 1.10 + across)
+ * wind_strength
+ )
+ offsets[:, 2] = (
+ float(amplitude)
+ * 0.12
+ * gs.torch.sin(traveling_phase * 1.15 + across)
+ * wind_strength
+ )
+
+ target_positions = rest_positions + offsets
+ blend = (0.58 * wind_strength).unsqueeze(1)
+ positions = positions + (target_positions - positions) * blend
+ positions[anchor_mask] = rest_positions[anchor_mask]
+ cloth.set_particles_pos(positions)
+
+
+def _add_scene_floor_and_backdrop(
+ gs: Any,
+ scene: Any,
+ camera_pos: tuple[float, float, float],
+ camera_lookat: tuple[float, float, float],
+ floor_size: tuple[float, float],
+ floor_tile_size: tuple[float, float],
+ floor_color: tuple[float, float, float, float],
+ backdrop: bool,
+ backdrop_color: tuple[float, float, float, float],
+) -> None:
+ scene.add_entity(
+ morph=gs.morphs.Plane(
+ pos=(0.0, 0.0, 0.0),
+ plane_size=tuple(float(value) for value in floor_size),
+ tile_size=tuple(float(value) for value in floor_tile_size),
+ ),
+ material=gs.materials.Rigid(),
+ surface=gs.surfaces.Rough(
+ color=tuple(float(value) for value in floor_color)
+ ),
+ name="floor",
+ )
+
+ if not backdrop:
+ return
+
+ normal = _horizontal_unit_vector(
+ (
+ camera_pos[0] - camera_lookat[0],
+ camera_pos[1] - camera_lookat[1],
+ 0.0,
+ )
+ )
+ camera_distance = math.sqrt(
+ (camera_pos[0] - camera_lookat[0]) ** 2
+ + (camera_pos[1] - camera_lookat[1]) ** 2
+ + (camera_pos[2] - camera_lookat[2]) ** 2
+ )
+ backdrop_distance = max(1.5, camera_distance * 0.72)
+ backdrop_pos = (
+ camera_lookat[0] - normal[0] * backdrop_distance,
+ camera_lookat[1] - normal[1] * backdrop_distance,
+ camera_lookat[2],
+ )
+ backdrop_size = (
+ max(float(floor_size[0]), float(floor_size[1])),
+ max(float(floor_size[0]), float(floor_size[1])) * 0.7,
+ )
+ scene.add_entity(
+ morph=gs.morphs.Plane(
+ pos=backdrop_pos,
+ normal=normal,
+ plane_size=backdrop_size,
+ collision=False,
+ ),
+ material=gs.materials.Rigid(),
+ surface=gs.surfaces.Rough(
+ color=tuple(float(value) for value in backdrop_color)
+ ),
+ name="backdrop",
+ )
+
+
+def _horizontal_unit_vector(
+ vector: tuple[float, float, float],
+) -> tuple[float, float, float]:
+ length = math.sqrt(vector[0] ** 2 + vector[1] ** 2)
+ if length <= 1e-6:
+ return (1.0, 0.0, 0.0)
+ return (vector[0] / length, vector[1] / length, 0.0)
+
+
+def _save_camera_recording(camera: Any, video_path: Path, fps: int) -> None:
+ from moviepy.video.io.ImageSequenceClip import ImageSequenceClip
+
+ if not camera._in_recording:
+ raise RuntimeError("Camera recording was not started.")
+ if not camera._recorded_imgs:
+ raise RuntimeError("No frames were recorded.")
+
+ video_path.parent.mkdir(parents=True, exist_ok=True)
+ clip = ImageSequenceClip(list(camera._recorded_imgs), fps=fps)
+ try:
+ clip.write_videofile(
+ str(video_path),
+ fps=fps,
+ logger=None,
+ codec="libx264",
+ preset="ultrafast",
+ )
+ finally:
+ close = getattr(clip, "close", None)
+ if close is not None:
+ close()
+ camera._recorded_t_prev = -1
+ camera._recorded_imgs.clear()
+ camera._in_recording = False
+
+
+def _save_particle_positions(
+ particle_positions_path: Path,
+ particle_frames: list[Any],
+ rest_positions: Any,
+ fps: int,
+ render_interval: int,
+) -> None:
+ import numpy as np
+
+ if not particle_frames:
+ raise RuntimeError("No particle frames were recorded.")
+
+ particle_positions_path.parent.mkdir(parents=True, exist_ok=True)
+ np.savez_compressed(
+ particle_positions_path,
+ positions=np.stack(particle_frames, axis=0).astype(np.float32),
+ rest_positions=_tensor_to_numpy(rest_positions).astype(np.float32),
+ fps=np.array(fps, dtype=np.int32),
+ render_interval=np.array(render_interval, dtype=np.int32),
+ )
+
+
+def _tensor_to_numpy(tensor: Any) -> Any:
+ array = tensor.detach() if hasattr(tensor, "detach") else tensor
+ array = array.cpu() if hasattr(array, "cpu") else array
+ array = array.numpy() if hasattr(array, "numpy") else array
+ return array
+
+
+def _collar_handle_weights(gs: Any, rest_positions: Any) -> Any:
+ min_values = rest_positions.min(dim=0).values
+ max_values = rest_positions.max(dim=0).values
+ center = rest_positions.mean(dim=0)
+ width = gs.torch.clamp(max_values[1] - min_values[1], min=1e-6)
+ height = gs.torch.clamp(max_values[2] - min_values[2], min=1e-6)
+
+ y_values = rest_positions[:, 1]
+ z_values = rest_positions[:, 2]
+ top_start = max_values[2] - 0.32 * height
+ top_weights = gs.torch.clamp(
+ (z_values - top_start) / (0.32 * height), 0.0, 1.0
+ )
+
+ left_center = center[1] - 0.22 * width
+ right_center = center[1] + 0.22 * width
+ sigma = 0.12 * width
+ left_weights = gs.torch.exp(-0.5 * ((y_values - left_center) / sigma) ** 2)
+ right_weights = gs.torch.exp(
+ -0.5 * ((y_values - right_center) / sigma) ** 2
+ )
+ side_weights = gs.torch.clamp(left_weights + right_weights, 0.0, 1.0)
+ return top_weights * side_weights
+
+
+def _front_camera_pose(
+ manifest: dict[str, Any], init_height: float
+) -> tuple[tuple[float, float, float], tuple[float, float, float]]:
+ stats = manifest.get("mesh_stats", {})
+ bbox_min = stats.get("bbox_min", [-0.5, -0.5, -0.5])
+ bbox_max = stats.get("bbox_max", [0.5, 0.5, 0.5])
+ extents = stats.get("extents", [1.0, 1.0, 1.0])
+
+ center = [
+ 0.5 * (float(bbox_min[index]) + float(bbox_max[index]))
+ for index in range(3)
+ ]
+ width = float(extents[1])
+ height = float(extents[2])
+ depth = max(float(extents[0]), 0.05)
+ distance = max(width, height) * 2.45 + depth
+
+ lookat = (center[0], center[1], center[2] + float(init_height))
+ pos = (
+ center[0] + distance,
+ center[1] - 0.22 * distance,
+ lookat[2] + 0.04 * height,
+ )
+ return pos, lookat
+
+
+def _init_genesis_backend(gs: Any, device: str) -> tuple[str, str | None]:
+ normalized = device.lower()
+ if normalized == "cpu":
+ gs.init(backend=gs.cpu, logging_level="warning")
+ return "cpu", None
+ if normalized == "cuda":
+ gs.init(backend=gs.cuda, logging_level="warning")
+ return "cuda", None
+ if normalized != "auto":
+ raise ValueError(
+ f"Unsupported Genesis backend: {device}. Use 'cpu', 'cuda', or 'auto'."
+ )
+
+ try:
+ gs.init(backend=gs.cuda, logging_level="warning")
+ return "cuda", None
+ except Exception as exc:
+ warning = (
+ "Genesis CUDA backend is unavailable; falling back to CPU. "
+ f"Original error: {type(exc).__name__}: {exc}"
+ )
+ gs.init(backend=gs.cpu, logging_level="warning")
+ return "cpu", warning
+
+
+def _read_json(path: Path) -> dict[str, Any]:
+ if not path.exists():
+ raise FileNotFoundError(f"Required JSON file does not exist: {path}")
+ return json.loads(path.read_text())
+
+
+def _write_json(path: Path, data: dict[str, Any]) -> None:
+ path.write_text(json.dumps(data, indent=2) + "\n")
+
+
+if __name__ == "__main__":
+ entrypoint()
diff --git a/embodied_gen/scripts/eval_collision_success.py b/embodied_gen/scripts/eval_collision_success.py
new file mode 100644
index 0000000..04d822e
--- /dev/null
+++ b/embodied_gen/scripts/eval_collision_success.py
@@ -0,0 +1,813 @@
+# Project EmbodiedGen
+#
+# Copyright (c) 2025 Horizon Robotics. All Rights Reserved.
+#
+# 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.
+
+import json
+import os
+from dataclasses import asdict, dataclass, field
+from typing import Optional
+
+import imageio
+import numpy as np
+import sapien.core as sapien
+import torch
+import trimesh
+import tyro
+from embodied_gen.utils.log import logger
+from embodied_gen.utils.simulation import (
+ FrankaPandaGrasper,
+ SapienSceneManager,
+ capture_frame,
+ create_panda_agent,
+ create_recording_camera,
+ estimate_grasp_width,
+ get_actor_bottom_z,
+ get_actor_mesh,
+ load_actor_from_urdf,
+ load_collision_mesh_from_urdf,
+ quat_from_yaw,
+ set_ground_base_color,
+)
+
+GROUND_BASE_COLOR = [0.78, 0.90, 0.72, 1.0]
+SETTLE_CHECK_INTERVAL = 10
+SETTLE_STABLE_WINDOWS = 3
+SETTLE_BOTTOM_Z_TOL = 5e-4
+MAX_EXTRA_SETTLE_STEPS = 120
+
+
+@dataclass
+class TrialResult:
+ """Stores per-trial evaluation results."""
+
+ yaw_deg: float
+ success: bool
+ scale_factor: float
+ object_width_before_scale: float
+ object_width_after_scale: float
+ settled_bottom_z: float
+ final_bottom_z: float
+ lift_delta: float
+ peak_bottom_z: float | None = None
+ peak_lift_delta: float | None = None
+ lift_success_threshold: float | None = None
+ tcp_object_offset_range: float | None = None
+ sync_tol: float | None = None
+ final_lift_pass: bool | None = None
+ sync_pass: bool | None = None
+ video_path: str | None = None
+ note: str = ""
+
+
+@dataclass
+class EvalCollisionConfig:
+ urdf_path: str
+ output_path: Optional[str] = None
+ num_trials: int = 4
+ max_gripper_width: float = 0.075
+ gripper_clearance_ratio: float = 0.9
+ sim_freq: int = 200
+ control_freq: int = 20
+ settle_steps: int = 240
+ post_grasp_steps: int = 80
+ lift_success_height: Optional[float] = None
+ lift_success_ratio: float = 0.01
+ min_lift_success_height: float = 0.001
+ max_lift_success_height: float = 0.003
+ sync_tol: float = 0.02
+ approach_offset: float = 0.06
+ grasp_clearance: float = 0.004
+ grasp_height_ratio: float = 0.45
+ max_descent_from_top: float = 0.03
+ hover_offset: float = 0.12
+ hover_open_steps: int = 10
+ descent_stage_count: int = 4
+ descent_n_max_step: int = 25
+ lift_distance: float = 0.10
+ close_steps: int = 20
+ object_x: float = 0.55
+ object_y: float = 0.0
+ z_offset: float = 0.005
+ sim_backend: str = "cpu"
+ render_backend: str = "gpu"
+ ray_tracing: bool = False
+ save_video: bool = True
+ video_path: Optional[str] = None
+ video_fps: int = 20
+ render_interval: int = 4
+ image_hw: tuple[int, int] = (512, 512)
+
+ def __post_init__(self) -> None:
+ output_dir = os.path.join(
+ os.path.dirname(self.urdf_path), "grasp_trial"
+ )
+ if self.output_path is None:
+ self.output_path = os.path.join(
+ output_dir, "collision_success_eval.json"
+ )
+ if self.video_path is None:
+ self.video_path = os.path.join(
+ output_dir, "collision_success_eval.mp4"
+ )
+
+
+def _compute_scale_factor(
+ urdf_path: str,
+ max_gripper_width: float,
+ clearance_ratio: float,
+) -> tuple[float, float]:
+ """Compute a scale that fits the asset within the gripper width."""
+ mesh = load_collision_mesh_from_urdf(urdf_path)
+ grasp_width = estimate_grasp_width(mesh)
+ target_width = max_gripper_width * clearance_ratio
+ if grasp_width <= 1e-6:
+ raise ValueError(f"Invalid grasp width estimated from {urdf_path}")
+
+ scale = min(1.0, target_width / grasp_width)
+ return float(scale), float(grasp_width)
+
+
+def _compute_spawn_center_z(
+ mesh: trimesh.Trimesh,
+ scale_factor: float,
+ z_offset: float,
+) -> float:
+ """Compute actor-center z so the scaled mesh bottom is z_offset above z=0."""
+ local_bottom_z = float(mesh.bounds[0, 2] * scale_factor)
+ return z_offset - local_bottom_z
+
+
+def _compute_adaptive_lift_threshold(
+ actor: sapien.Entity,
+ ratio: float,
+ min_height: float,
+ max_height: float,
+ absolute_override: float | None = None,
+) -> float:
+ """Compute a robust lift threshold from the settled object height."""
+ if absolute_override is not None:
+ return float(absolute_override)
+
+ mesh = get_actor_mesh(actor)
+ object_height = float(mesh.bounds[1, 2] - mesh.bounds[0, 2])
+ adaptive_height = object_height * ratio
+ return float(np.clip(adaptive_height, min_height, max_height))
+
+
+def _build_trial_video_path(
+ video_path: str,
+ trial_idx: int,
+ yaw_deg: float,
+) -> str:
+ """Build a unique per-trial video path from the base output path."""
+ root, ext = os.path.splitext(video_path)
+ if not ext:
+ ext = ".mp4"
+ return f"{root}_trial{trial_idx:02d}_yaw{int(round(yaw_deg)):03d}{ext}"
+
+
+@dataclass
+class _GraspTracker:
+ """Tracks gripper-object sync metrics during the grasp/lift phase.
+
+ The lift_delta of the object alone is fragile: a bounced-away object can
+ momentarily rise high before falling back. By logging the per-step offset
+ between the object bottom and the gripper TCP, we can also verify that the
+ object actually moves together with the gripper after closing.
+ """
+
+ actor: sapien.Entity
+ grasper: FrankaPandaGrasper
+ peak_bottom_z: float | None = None
+ tcp_object_offsets: list[float] = field(default_factory=list)
+
+ def update(self) -> None:
+ bottom_z = get_actor_bottom_z(self.actor)
+ tcp_z = float(self.grasper.agent.tcp.pose[0].sp.p[2])
+ self.peak_bottom_z = (
+ bottom_z
+ if self.peak_bottom_z is None
+ else max(self.peak_bottom_z, bottom_z)
+ )
+ self.tcp_object_offsets.append(bottom_z - tcp_z)
+
+ @property
+ def offset_range(self) -> float:
+ if not self.tcp_object_offsets:
+ return 0.0
+ return float(
+ max(self.tcp_object_offsets) - min(self.tcp_object_offsets)
+ )
+
+
+def _execute_actions(
+ scene_manager: SapienSceneManager,
+ agent: object,
+ actions: np.ndarray,
+ control_freq: int,
+ camera: sapien.render.RenderCameraComponent | None = None,
+ render_interval: int = 1,
+ video_frames: list[np.ndarray] | None = None,
+ tracker: _GraspTracker | None = None,
+) -> None:
+ """Run a sequence of robot actions."""
+ sim_steps = max(1, scene_manager.sim_freq // control_freq)
+ cameras = [] if camera is None else [camera]
+ render_keys = [] if camera is None else ["Color"]
+ for idx, action in enumerate(actions):
+ frames = scene_manager.step_action(
+ agent,
+ torch.tensor(action[None, ...], dtype=torch.float32),
+ cameras=cameras,
+ render_keys=render_keys,
+ sim_steps_per_control=sim_steps,
+ )
+ if (
+ camera is not None
+ and video_frames is not None
+ and idx % max(1, render_interval) == 0
+ ):
+ video_frames.append(np.array(frames[camera.name][0]["Color"]))
+ if tracker is not None:
+ tracker.update()
+
+
+def _hold_gripper_state(
+ scene_manager: SapienSceneManager,
+ grasper: FrankaPandaGrasper,
+ gripper_state: int,
+ control_freq: int,
+ n_step: int,
+ camera: sapien.render.RenderCameraComponent | None = None,
+ render_interval: int = 1,
+ video_frames: list[np.ndarray] | None = None,
+ tracker: _GraspTracker | None = None,
+) -> None:
+ """Hold gripper open/close while stepping the scene."""
+ hold_actions = grasper.control_gripper(
+ gripper_state=gripper_state,
+ n_step=n_step,
+ )
+ _execute_actions(
+ scene_manager,
+ grasper.agent,
+ hold_actions,
+ control_freq,
+ camera=camera,
+ render_interval=render_interval,
+ video_frames=video_frames,
+ tracker=tracker,
+ )
+
+
+def _wait_until_actor_settled(
+ scene_manager: SapienSceneManager,
+ grasper: FrankaPandaGrasper,
+ actor: sapien.Entity,
+ control_freq: int,
+ initial_bottom_z: float,
+ max_extra_steps: int = MAX_EXTRA_SETTLE_STEPS,
+ check_interval: int = SETTLE_CHECK_INTERVAL,
+ stable_windows: int = SETTLE_STABLE_WINDOWS,
+ bottom_z_tol: float = SETTLE_BOTTOM_Z_TOL,
+ camera: sapien.render.RenderCameraComponent | None = None,
+ render_interval: int = 1,
+ video_frames: list[np.ndarray] | None = None,
+) -> float:
+ """Wait until the dropped object is visually settled on the ground."""
+ remaining_steps = max(0, max_extra_steps)
+ previous_bottom_z = initial_bottom_z
+ stable_count = 0
+
+ while remaining_steps > 0 and stable_count < stable_windows:
+ n_step = min(check_interval, remaining_steps)
+ _hold_gripper_state(
+ scene_manager,
+ grasper,
+ gripper_state=1,
+ control_freq=control_freq,
+ n_step=n_step,
+ camera=camera,
+ render_interval=render_interval,
+ video_frames=video_frames,
+ )
+ current_bottom_z = get_actor_bottom_z(actor)
+ if abs(current_bottom_z - previous_bottom_z) <= bottom_z_tol:
+ stable_count += 1
+ else:
+ stable_count = 0
+ previous_bottom_z = current_bottom_z
+ remaining_steps -= n_step
+
+ return previous_bottom_z
+
+
+def _plan_scripted_grasp_stages(
+ grasper: FrankaPandaGrasper,
+ actor: sapien.Entity,
+ grasp_height_ratio: float,
+ grasp_clearance: float,
+ approach_offset: float,
+ lift_distance: float,
+ max_descent_from_top: float | None = None,
+) -> tuple[sapien.Pose, sapien.Pose, sapien.Pose]:
+ """Plan a simple top-down scripted grasp."""
+ mesh = get_actor_mesh(actor)
+ bounds = mesh.bounds
+ approaching = np.array([0.0, 0.0, -1.0])
+ center = bounds.mean(axis=0)
+ extents_xy = bounds[1, :2] - bounds[0, :2]
+ closing = (
+ np.array([1.0, 0.0, 0.0])
+ if extents_xy[0] <= extents_xy[1]
+ else np.array([0.0, 1.0, 0.0])
+ )
+ object_height = bounds[1, 2] - bounds[0, 2]
+ grasp_z = bounds[0, 2] + object_height * grasp_height_ratio
+ if max_descent_from_top is not None:
+ grasp_z = max(grasp_z, bounds[1, 2] - max_descent_from_top)
+ grasp_z = float(
+ np.clip(
+ grasp_z,
+ bounds[0, 2] + 0.015,
+ bounds[1, 2] - 0.005,
+ )
+ )
+ center = np.array([center[0], center[1], grasp_z + grasp_clearance])
+ grasp_pose = grasper.agent.build_grasp_pose(approaching, closing, center)
+ pre_grasp_pose = sapien.Pose(
+ p=grasp_pose.p + np.array([0.0, 0.0, approach_offset]),
+ q=grasp_pose.q,
+ )
+ lift_pose = sapien.Pose(
+ p=grasp_pose.p + np.array([0.0, 0.0, lift_distance]),
+ q=grasp_pose.q,
+ )
+
+ return pre_grasp_pose, grasp_pose, lift_pose
+
+
+def _build_grasp_stage_candidates(
+ grasper: FrankaPandaGrasper,
+ actor: sapien.Entity,
+ grasp_height_ratio: float,
+ grasp_clearance: float,
+ approach_offset: float,
+ lift_distance: float,
+ max_descent_from_top: float | None = None,
+) -> list[tuple[float, float, sapien.Pose, sapien.Pose, sapien.Pose]]:
+ """Build fallback grasp-stage candidates for tapered objects like bottles."""
+ ratio_candidates = [
+ grasp_height_ratio,
+ min(0.95, grasp_height_ratio + 0.08),
+ min(0.95, grasp_height_ratio + 0.16),
+ ]
+ clearance_candidates = [
+ grasp_clearance,
+ grasp_clearance + 0.004,
+ grasp_clearance + 0.008,
+ ]
+ candidates = []
+ seen_keys = set()
+ for ratio, clearance in zip(ratio_candidates, clearance_candidates):
+ key = (round(ratio, 4), round(clearance, 4))
+ if key in seen_keys:
+ continue
+ seen_keys.add(key)
+ pre_grasp_pose, grasp_pose, lift_pose = _plan_scripted_grasp_stages(
+ grasper,
+ actor,
+ grasp_height_ratio=ratio,
+ grasp_clearance=clearance,
+ approach_offset=approach_offset,
+ lift_distance=lift_distance,
+ max_descent_from_top=max_descent_from_top,
+ )
+ candidates.append(
+ (ratio, clearance, pre_grasp_pose, grasp_pose, lift_pose)
+ )
+
+ return candidates
+
+
+def _build_hover_pose(
+ actor: sapien.Entity,
+ grasp_pose: sapien.Pose,
+ hover_offset: float,
+) -> sapien.Pose:
+ """Build a hover pose at a fixed offset above the object top surface."""
+ mesh = get_actor_mesh(actor)
+ top_z = float(mesh.bounds[1, 2])
+ return sapien.Pose(
+ p=np.array([grasp_pose.p[0], grasp_pose.p[1], top_z + hover_offset]),
+ q=grasp_pose.q,
+ )
+
+
+def _build_descent_stage_poses(
+ grasp_pose: sapien.Pose,
+ hover_offset: float,
+ num_stages: int,
+) -> list[sapien.Pose]:
+ """Split the downward approach into multiple slow open-gripper stages."""
+ if num_stages <= 0:
+ return [grasp_pose]
+
+ stage_offsets = np.linspace(hover_offset, 0.0, num_stages + 1)[1:]
+ return [
+ sapien.Pose(
+ p=grasp_pose.p + np.array([0.0, 0.0, float(offset)]),
+ q=grasp_pose.q,
+ )
+ for offset in stage_offsets
+ ]
+
+
+def run_single_trial(
+ args: EvalCollisionConfig,
+ yaw_deg: float,
+ scale_factor: float,
+ grasp_width: float,
+ record_video: bool = False,
+ video_path: str | None = None,
+) -> TrialResult:
+ """Run one grasp trial with a fixed yaw."""
+ scene_manager = SapienSceneManager(
+ sim_freq=args.sim_freq,
+ ray_tracing=args.ray_tracing,
+ device=args.sim_backend,
+ )
+ scene = scene_manager.scene
+ set_ground_base_color(scene, GROUND_BASE_COLOR)
+ agent = create_panda_agent(
+ scene,
+ control_freq=args.control_freq,
+ sim_backend=args.sim_backend,
+ render_backend=args.render_backend,
+ )
+ video_frames: list[np.ndarray] | None = None
+ camera = None
+ if record_video:
+ video_frames = []
+ camera = create_recording_camera(
+ scene_manager,
+ eye_pos=[args.object_x - 0.32, args.object_y - 0.52, 0.56],
+ target_pt=[args.object_x - 0.01, args.object_y, 0.27],
+ image_hw=tuple(args.image_hw),
+ fovy_deg=60.0,
+ )
+
+ collision_mesh = load_collision_mesh_from_urdf(args.urdf_path)
+ spawn_center_z = _compute_spawn_center_z(
+ collision_mesh,
+ scale_factor=scale_factor,
+ z_offset=args.z_offset,
+ )
+ spawn_pose = sapien.Pose(
+ p=[
+ args.object_x,
+ args.object_y,
+ spawn_center_z,
+ ],
+ q=quat_from_yaw(yaw_deg),
+ )
+ actor = load_actor_from_urdf(
+ scene,
+ args.urdf_path,
+ pose=spawn_pose,
+ use_static=False,
+ update_mass=True,
+ scale=scale_factor,
+ )
+
+ if video_frames is not None and camera is not None:
+ video_frames.append(capture_frame(scene, camera))
+ grasper = FrankaPandaGrasper(agent, control_freq=args.control_freq)
+ _hold_gripper_state(
+ scene_manager,
+ grasper,
+ gripper_state=1,
+ control_freq=args.control_freq,
+ n_step=max(
+ 1,
+ args.settle_steps
+ // max(1, scene_manager.sim_freq // args.control_freq),
+ ),
+ camera=camera,
+ render_interval=args.render_interval,
+ video_frames=video_frames,
+ )
+ settled_bottom_z = get_actor_bottom_z(actor)
+ settled_bottom_z = _wait_until_actor_settled(
+ scene_manager,
+ grasper,
+ actor,
+ control_freq=args.control_freq,
+ initial_bottom_z=settled_bottom_z,
+ camera=camera,
+ render_interval=args.render_interval,
+ video_frames=video_frames,
+ )
+ grasp_candidates = _build_grasp_stage_candidates(
+ grasper,
+ actor,
+ grasp_height_ratio=args.grasp_height_ratio,
+ grasp_clearance=args.grasp_clearance,
+ approach_offset=args.approach_offset,
+ lift_distance=args.lift_distance,
+ max_descent_from_top=args.max_descent_from_top,
+ )
+ selected_lift_pose = None
+ selected_candidate_note = ""
+ grasp_stage_failure_note = "failed to reach pre-grasp pose"
+ for candidate_idx, candidate in enumerate(grasp_candidates):
+ (
+ candidate_ratio,
+ candidate_clearance,
+ _pre_grasp_pose,
+ grasp_pose,
+ lift_pose,
+ ) = candidate
+ hover_pose = _build_hover_pose(
+ actor, grasp_pose, hover_offset=args.hover_offset
+ )
+ hover_actions = grasper.move_to_pose(
+ hover_pose,
+ grasper.control_timestep,
+ gripper_state=1,
+ n_max_step=80,
+ )
+ if hover_actions is None:
+ grasp_stage_failure_note = "failed to reach hover pose"
+ continue
+ _execute_actions(
+ scene_manager,
+ agent,
+ hover_actions,
+ args.control_freq,
+ camera=camera,
+ render_interval=args.render_interval,
+ video_frames=video_frames,
+ )
+ _hold_gripper_state(
+ scene_manager,
+ grasper,
+ gripper_state=1,
+ control_freq=args.control_freq,
+ n_step=args.hover_open_steps,
+ camera=camera,
+ render_interval=args.render_interval,
+ video_frames=video_frames,
+ )
+
+ descent_failed = False
+ for descent_pose in _build_descent_stage_poses(
+ grasp_pose,
+ hover_offset=args.hover_offset,
+ num_stages=args.descent_stage_count,
+ ):
+ descent_actions = grasper.move_to_pose(
+ descent_pose,
+ grasper.control_timestep,
+ gripper_state=1,
+ n_max_step=args.descent_n_max_step,
+ )
+ if descent_actions is None:
+ descent_failed = True
+ grasp_stage_failure_note = (
+ "failed during slow descent to grasp pose"
+ )
+ break
+ _execute_actions(
+ scene_manager,
+ agent,
+ descent_actions,
+ args.control_freq,
+ camera=camera,
+ render_interval=args.render_interval,
+ video_frames=video_frames,
+ )
+ if descent_failed:
+ continue
+
+ _hold_gripper_state(
+ scene_manager,
+ grasper,
+ gripper_state=1,
+ control_freq=args.control_freq,
+ n_step=2,
+ camera=camera,
+ render_interval=args.render_interval,
+ video_frames=video_frames,
+ )
+ selected_lift_pose = lift_pose
+ selected_candidate_note = (
+ ""
+ if candidate_idx == 0
+ else (
+ f"fallback grasp candidate ratio={candidate_ratio:.2f}, "
+ f"clearance={candidate_clearance:.3f}"
+ )
+ )
+ break
+
+ if selected_lift_pose is None:
+ if video_frames is not None and video_path is not None:
+ os.makedirs(os.path.dirname(video_path), exist_ok=True)
+ imageio.mimsave(video_path, video_frames, fps=args.video_fps)
+ return TrialResult(
+ yaw_deg=yaw_deg,
+ success=False,
+ scale_factor=scale_factor,
+ object_width_before_scale=grasp_width,
+ object_width_after_scale=grasp_width * scale_factor,
+ settled_bottom_z=settled_bottom_z,
+ final_bottom_z=settled_bottom_z,
+ lift_delta=0.0,
+ video_path=video_path,
+ note=grasp_stage_failure_note,
+ )
+
+ lift_success_threshold = _compute_adaptive_lift_threshold(
+ actor,
+ ratio=args.lift_success_ratio,
+ min_height=args.min_lift_success_height,
+ max_height=args.max_lift_success_height,
+ absolute_override=args.lift_success_height,
+ )
+ tracker = _GraspTracker(actor=actor, grasper=grasper)
+ close_actions = grasper.control_gripper(
+ gripper_state=-1,
+ n_step=args.close_steps,
+ )
+ _execute_actions(
+ scene_manager,
+ agent,
+ close_actions,
+ args.control_freq,
+ camera=camera,
+ render_interval=args.render_interval,
+ video_frames=video_frames,
+ tracker=tracker,
+ )
+
+ stage_note = "ok"
+ lift_actions = grasper.move_to_pose(
+ selected_lift_pose,
+ grasper.control_timestep,
+ gripper_state=-1,
+ n_max_step=50,
+ )
+ if lift_actions is not None:
+ _execute_actions(
+ scene_manager,
+ agent,
+ lift_actions,
+ args.control_freq,
+ camera=camera,
+ render_interval=args.render_interval,
+ video_frames=video_frames,
+ tracker=tracker,
+ )
+ else:
+ stage_note = "failed to lift after closing"
+ _hold_gripper_state(
+ scene_manager,
+ grasper,
+ gripper_state=-1,
+ control_freq=args.control_freq,
+ n_step=args.post_grasp_steps,
+ camera=camera,
+ render_interval=args.render_interval,
+ video_frames=video_frames,
+ tracker=tracker,
+ )
+
+ final_bottom_z = get_actor_bottom_z(actor)
+ lift_delta = final_bottom_z - settled_bottom_z
+ peak_bottom_z = (
+ final_bottom_z
+ if tracker.peak_bottom_z is None
+ else tracker.peak_bottom_z
+ )
+ peak_lift_delta = peak_bottom_z - settled_bottom_z
+ offset_range = tracker.offset_range
+ final_lift_pass = bool(lift_delta >= lift_success_threshold)
+ sync_pass = bool(offset_range <= args.sync_tol)
+ success = bool(final_lift_pass and sync_pass)
+ if video_frames is not None and camera is not None:
+ video_frames.append(capture_frame(scene, camera))
+ if video_frames is not None and video_path is not None:
+ os.makedirs(os.path.dirname(video_path), exist_ok=True)
+ imageio.mimsave(video_path, video_frames, fps=args.video_fps)
+
+ if stage_note != "ok":
+ note = stage_note
+ elif success:
+ note = selected_candidate_note or "ok"
+ elif not final_lift_pass and not sync_pass:
+ note = "object dropped and decoupled from gripper"
+ elif not final_lift_pass:
+ note = "object did not stay lifted (likely bounced or dropped)"
+ else:
+ note = "object did not move synchronously with gripper"
+
+ return TrialResult(
+ yaw_deg=yaw_deg,
+ success=success,
+ scale_factor=scale_factor,
+ object_width_before_scale=grasp_width,
+ object_width_after_scale=grasp_width * scale_factor,
+ settled_bottom_z=settled_bottom_z,
+ final_bottom_z=final_bottom_z,
+ lift_delta=lift_delta,
+ peak_bottom_z=peak_bottom_z,
+ peak_lift_delta=peak_lift_delta,
+ lift_success_threshold=lift_success_threshold,
+ tcp_object_offset_range=offset_range,
+ sync_tol=args.sync_tol,
+ final_lift_pass=final_lift_pass,
+ sync_pass=sync_pass,
+ video_path=video_path,
+ note=note,
+ )
+
+
+def entrypoint(**kwargs) -> dict:
+ """Run collision-success evaluation for a URDF asset."""
+ if kwargs:
+ kwargs.setdefault("urdf_path", "__dummy__.urdf")
+ args = EvalCollisionConfig(**kwargs)
+ else:
+ args = tyro.cli(EvalCollisionConfig)
+
+ if not os.path.exists(args.urdf_path):
+ raise FileNotFoundError(f"URDF file not found: {args.urdf_path}")
+
+ logger.info(
+ f"Start collision-success eval: urdf={args.urdf_path}, "
+ f"num_trials={args.num_trials}, sync_tol={args.sync_tol}, "
+ f"output={args.output_path}"
+ )
+ scale_factor, grasp_width = _compute_scale_factor(
+ args.urdf_path,
+ max_gripper_width=args.max_gripper_width,
+ clearance_ratio=args.gripper_clearance_ratio,
+ )
+ yaw_values = np.linspace(0, 360, args.num_trials, endpoint=False)
+ trials = [
+ run_single_trial(
+ args,
+ float(yaw_deg),
+ scale_factor,
+ grasp_width,
+ record_video=args.save_video,
+ video_path=(
+ _build_trial_video_path(args.video_path, idx, float(yaw_deg))
+ if args.save_video
+ else None
+ ),
+ )
+ for idx, yaw_deg in enumerate(yaw_values)
+ ]
+
+ success_count = sum(int(trial.success) for trial in trials)
+ result = {
+ "urdf_path": args.urdf_path,
+ "num_trials": args.num_trials,
+ "num_success": success_count,
+ "collision_success_rate": success_count / max(1, args.num_trials),
+ "scale_factor": scale_factor,
+ "estimated_grasp_width_before_scale": grasp_width,
+ "estimated_grasp_width_after_scale": grasp_width * scale_factor,
+ "video_path": args.video_path if args.save_video else None,
+ "trial_video_paths": [
+ trial.video_path
+ for trial in trials
+ if trial.video_path is not None
+ ],
+ "trials": [asdict(trial) for trial in trials],
+ }
+
+ os.makedirs(os.path.dirname(args.output_path), exist_ok=True)
+ with open(args.output_path, "w", encoding="utf-8") as f:
+ json.dump(result, f, indent=2)
+ logger.info(f"Collision success report saved to {args.output_path}")
+
+ return result
+
+
+if __name__ == "__main__":
+ entrypoint()
diff --git a/embodied_gen/scripts/gen_room.py b/embodied_gen/scripts/gen_room.py
new file mode 100644
index 0000000..3ad860b
--- /dev/null
+++ b/embodied_gen/scripts/gen_room.py
@@ -0,0 +1,69 @@
+# Project EmbodiedGen
+#
+# Copyright (c) 2025 Horizon Robotics. All Rights Reserved.
+#
+# 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.
+
+import os
+import signal
+import subprocess
+import sys
+from pathlib import Path
+
+from embodied_gen.utils.log import logger
+
+
+def get_blender_python_path():
+ """Resolve path to Blender's bundled Python binary from env or default location."""
+ env_path = os.environ.get("BLENDER_PYTHON_BIN")
+ if env_path and os.path.exists(env_path):
+ return env_path
+
+ default_relative_path = Path(
+ "thirdparty/infinigen/blender/4.2/python/bin/python3.11"
+ )
+ if default_relative_path.exists():
+ return str(default_relative_path.resolve())
+
+ logger.error("Error: Could not find Blender Python binary.")
+ logger.error("Please set the BLENDER_PYTHON_BIN environment variable.")
+ sys.exit(1)
+
+
+def entrypoint():
+ """Entry point wrapper to execute script within Blender's Python environment."""
+ blender_python = get_blender_python_path()
+ args = sys.argv[1:]
+ process = subprocess.Popen([blender_python] + args, start_new_session=True)
+ try:
+ return_code = process.wait()
+ sys.exit(return_code)
+
+ except KeyboardInterrupt:
+ try:
+ os.killpg(os.getpgid(process.pid), signal.SIGKILL)
+ except ProcessLookupError:
+ pass
+ logger.error("\nProgram interrupted by user (Cmd+C). Exiting.")
+ sys.exit(130)
+ except Exception as e:
+ logger.error(f"Error: {e}")
+ try:
+ os.killpg(os.getpgid(process.pid), signal.SIGKILL)
+ except Exception:
+ pass
+ sys.exit(1)
+
+
+if __name__ == "__main__":
+ entrypoint()
diff --git a/embodied_gen/scripts/imageto3d.py b/embodied_gen/scripts/imageto3d.py
index 13a1193..6de3be5 100644
--- a/embodied_gen/scripts/imageto3d.py
+++ b/embodied_gen/scripts/imageto3d.py
@@ -29,6 +29,11 @@
# from embodied_gen.models.sr_model import ImageRealESRGAN
# from embodied_gen.models.delight_model import DelightingModel
from embodied_gen.models.gs_model import GaussianOperator
+from embodied_gen.models.hunyuan3d import (
+ HunyuanConfig,
+ load_credentials,
+ process_image,
+)
from embodied_gen.models.segment_model import RembgRemover
from embodied_gen.scripts.render_gs import entrypoint as render_gs_api
from embodied_gen.utils.gpt_clients import GPT_CLIENT
@@ -49,19 +54,47 @@
from embodied_gen.validators.urdf_convertor import URDFGenerator
# random.seed(0)
-IMAGE3D_MODEL = "SAM3D" # TRELLIS or SAM3D
-logger.info(f"Loading {IMAGE3D_MODEL} as Image3D Models...")
-if IMAGE3D_MODEL == "TRELLIS":
- from thirdparty.TRELLIS.trellis.pipelines import TrellisImageTo3DPipeline
-
- PIPELINE = TrellisImageTo3DPipeline.from_pretrained(
- "microsoft/TRELLIS-image-large"
- )
- # PIPELINE.cuda()
-elif IMAGE3D_MODEL == "SAM3D":
- from embodied_gen.models.sam3d import Sam3dInference
+IMAGE3D_MODEL = "SAM3D" # default backend; SAM3D, TRELLIS, or HUNYUAN3D
+SUPPORTED_IMAGE3D_MODELS = ("SAM3D", "TRELLIS", "HUNYUAN3D")
+
+
+_PIPELINE_CACHE: dict = {}
+
+
+def _build_image3d_pipeline(name: str):
+ """Lazily instantiate (and cache) the local image-to-3D pipeline.
+
+ The cache preserves the pre-refactor invariant that the local backend
+ is loaded once per process: ``textto3d.py`` calls ``entrypoint`` in a
+ per-node loop, and re-loading weights each call would regress runtime.
+ Returns ``None`` for backends that have no local model (HUNYUAN3D).
+ """
+ if name == "HUNYUAN3D":
+ return None
+ if name in _PIPELINE_CACHE:
+ return _PIPELINE_CACHE[name]
+ if name == "TRELLIS":
+ logger.info("Loading TRELLIS as Image3D Models...")
+ from thirdparty.TRELLIS.trellis.pipelines import (
+ TrellisImageTo3DPipeline,
+ )
+
+ pipeline = TrellisImageTo3DPipeline.from_pretrained(
+ "microsoft/TRELLIS-image-large"
+ )
+ elif name == "SAM3D":
+ logger.info("Loading SAM3D as Image3D Models...")
+ from embodied_gen.models.sam3d import Sam3dInference
+
+ pipeline = Sam3dInference()
+ else:
+ raise ValueError(
+ f"Unsupported image3d backend {name!r}; "
+ f"expected one of {SUPPORTED_IMAGE3D_MODELS}."
+ )
+ _PIPELINE_CACHE[name] = pipeline
+ return pipeline
- PIPELINE = Sam3dInference()
# DELIGHT = DelightingModel()
# IMAGESR_MODEL = ImageRealESRGAN(outscale=4)
@@ -109,6 +142,17 @@ def parse_args():
)
parser.add_argument("--disable_decompose_convex", action="store_true")
parser.add_argument("--texture_size", type=int, default=2048)
+ parser.add_argument(
+ "--image3d_model",
+ type=str,
+ default=IMAGE3D_MODEL,
+ help=(
+ "Image-to-3D backend. One of "
+ f"{', '.join(SUPPORTED_IMAGE3D_MODELS)} (case-insensitive). "
+ "HUNYUAN3D calls Tencent Hunyuan3D Pro API and requires "
+ "TENCENT_SECRET_ID/TENCENT_SECRET_KEY in the environment."
+ ),
+ )
args, unknown = parser.parse_known_args()
return args
@@ -120,9 +164,31 @@ def entrypoint(**kwargs):
if hasattr(args, k) and v is not None:
setattr(args, k, v)
- assert (
- args.image_path or args.image_root
- ), "Please provide either --image_path or --image_root."
+ args.image3d_model = str(args.image3d_model).strip().upper()
+ if args.image3d_model not in SUPPORTED_IMAGE3D_MODELS:
+ raise ValueError(
+ f"Unsupported --image3d_model {args.image3d_model!r}; "
+ f"expected one of {SUPPORTED_IMAGE3D_MODELS}."
+ )
+
+ hunyuan_config = None
+ hunyuan_credentials = None
+ if args.image3d_model == "HUNYUAN3D":
+ # Fail fast on missing creds before any local model load or network I/O.
+ hunyuan_credentials = load_credentials()
+ hunyuan_config = HunyuanConfig()
+ logger.info(
+ "HUNYUAN3D backend: action=%s host=%s result_format=%s",
+ hunyuan_config.image_action,
+ hunyuan_config.host,
+ hunyuan_config.result_format,
+ )
+
+ pipeline = _build_image3d_pipeline(args.image3d_model)
+
+ assert args.image_path or args.image_root, (
+ "Please provide either --image_path or --image_root."
+ )
if not args.image_path:
args.image_path = glob(os.path.join(args.image_root, "*.png"))
args.image_path += glob(os.path.join(args.image_root, "*.jpg"))
@@ -151,6 +217,19 @@ def entrypoint(**kwargs):
seg_image = RBG_REMOVER(image) if image.mode != "RGBA" else image
seg_image.save(seg_path)
+ if args.image3d_model == "HUNYUAN3D":
+ process_image(
+ args=args,
+ idx=idx,
+ image_path=image_path,
+ output_root=output_root,
+ filename=filename,
+ hunyuan_config=hunyuan_config,
+ hunyuan_credentials=hunyuan_credentials,
+ checkers=CHECKERS,
+ )
+ continue
+
seed = args.seed
asset_node = "unknown"
gs_model = None
@@ -161,10 +240,10 @@ def entrypoint(**kwargs):
f"Try: {try_idx + 1}/{args.n_retry}, Seed: {seed}, Prompt: {seg_path}"
)
try:
- outputs = image3d_model_infer(PIPELINE, seg_image, seed)
+ outputs = image3d_model_infer(pipeline, seg_image, seed)
except Exception as e:
logger.error(
- f"[Image3D Failed] process {image_path}: {e}, retry: {try_idx+1}/{args.n_retry}"
+ f"[Image3D Failed] process {image_path}: {e}, retry: {try_idx + 1}/{args.n_retry}"
)
seed = (
random.randint(0, 100000) if seed is not None else None
@@ -290,10 +369,14 @@ def entrypoint(**kwargs):
)
# Quality check and update .urdf file.
- mesh_out = f"{urdf_root}/{urdf_convertor.output_mesh_dir}/{filename}.obj" # noqa
+ mesh_out = (
+ f"{urdf_root}/{urdf_convertor.output_mesh_dir}/{filename}.obj" # noqa
+ )
trimesh.load(mesh_out).export(mesh_out.replace(".obj", ".glb"))
- image_dir = f"{urdf_root}/{urdf_convertor.output_render_dir}/image_color" # noqa
+ image_dir = (
+ f"{urdf_root}/{urdf_convertor.output_render_dir}/image_color" # noqa
+ )
image_paths = glob(f"{image_dir}/*.png")
images_list = []
for checker in CHECKERS:
diff --git a/embodied_gen/scripts/parallel_sim.py b/embodied_gen/scripts/parallel_sim.py
index b0b0a6b..0b4929f 100644
--- a/embodied_gen/scripts/parallel_sim.py
+++ b/embodied_gen/scripts/parallel_sim.py
@@ -15,7 +15,7 @@
# permissions and limitations under the License.
-from embodied_gen.utils.monkey_patches import monkey_patch_maniskill
+from embodied_gen.utils.monkey_patch.maniskill import monkey_patch_maniskill
monkey_patch_maniskill()
import json
diff --git a/embodied_gen/scripts/render_mv.py b/embodied_gen/scripts/render_mv.py
index 61c8013..5b3a463 100644
--- a/embodied_gen/scripts/render_mv.py
+++ b/embodied_gen/scripts/render_mv.py
@@ -130,11 +130,22 @@ def infer_pipe(
device=device,
)
- if ip_adapt_scale > 0 and ip_img_path is not None and len(ip_img_path) > 0:
+ has_ip_adapter = any(
+ hasattr(attn_processor, "to_k_ip")
+ for attn_processor in pipeline.unet.attn_processors.values()
+ )
+ use_ip_adapter = (
+ ip_adapt_scale > 0 and ip_img_path is not None and len(ip_img_path) > 0
+ )
+ effective_ip_adapt_scale = ip_adapt_scale if use_ip_adapter else 0.0
+ if use_ip_adapter:
ip_image = Image.open(ip_img_path).convert("RGB")
ip_image = ip_image.resize(target_hw[::-1])
ip_image = [ip_image]
- pipeline.set_ip_adapter_scale([ip_adapt_scale])
+ pipeline.set_ip_adapter_scale([effective_ip_adapt_scale])
+ elif has_ip_adapter:
+ ip_image = [Image.new("RGB", target_hw[::-1], color=(0, 0, 0))]
+ pipeline.set_ip_adapter_scale([0.0])
else:
ip_image = None
@@ -182,7 +193,7 @@ def infer_pipe(
[str(item) for sublist in sub_idxs for item in sublist]
)
save_path = os.path.join(
- save_dir, f"sample_idx{str(sub_idxs)}_ip{ip_adapt_scale}.jpg"
+ save_dir, f"sample_idx{str(sub_idxs)}_ip{effective_ip_adapt_scale}.jpg"
)
make_image_grid(grid_image, row_num, col_num).save(save_path)
logger.info(f"Visualize in {save_path}")
diff --git a/embodied_gen/scripts/room_gen/custom_solve.gin b/embodied_gen/scripts/room_gen/custom_solve.gin
new file mode 100644
index 0000000..fa291af
--- /dev/null
+++ b/embodied_gen/scripts/room_gen/custom_solve.gin
@@ -0,0 +1,30 @@
+FloorPlanSolver.n_divide_trials = 25
+FloorPlanSolver.iters_mult = 50
+
+home_room_constraints.has_fewer_rooms = False
+
+compose_indoors.place_cameras = False
+
+solve_objects.addition_weight_scalar = 3.0
+compose_indoors.solve_steps_large = 30
+compose_indoors.solve_steps_medium = 20
+compose_indoors.solve_steps_small = 5
+
+compose_indoors.invisible_room_ceilings_enabled = False
+compose_indoors.hide_other_rooms_enabled = True
+
+compose_indoors.terrain_enabled = False
+
+compose_indoors.pose_cameras_enabled = False
+compose_indoors.animate_cameras_enabled = False
+compose_indoors.overhead_cam_enabled = True
+
+compose_indoors.nature_backdrop_enabled = False
+
+compose_indoors.lights_off_chance = 0.0
+
+compose_indoors.skirting_floor_enabled = False
+compose_indoors.skirting_ceiling_enabled = False
+BlueprintSolidifier.enable_open = False
+
+compose_indoors.room_doors_enabled = False
diff --git a/embodied_gen/scripts/room_gen/export_scene.py b/embodied_gen/scripts/room_gen/export_scene.py
new file mode 100644
index 0000000..d34e0b1
--- /dev/null
+++ b/embodied_gen/scripts/room_gen/export_scene.py
@@ -0,0 +1,1677 @@
+# Project EmbodiedGen
+#
+# Copyright (c) 2025 Horizon Robotics. All Rights Reserved.
+#
+# 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.
+# Some code comes from: https://github.com/princeton-vl/infinigen/blob/main/infinigen/tools/export.py
+
+import argparse
+import logging
+import math
+import os
+import shutil
+import subprocess
+from collections import defaultdict
+from pathlib import Path
+from typing import Dict, List, Optional, Tuple
+
+import bpy
+import coacd
+import gin
+import numpy as np
+import trimesh
+from infinigen.core.util import blender as butil
+from mathutils import Vector
+
+logger = logging.getLogger(__name__)
+
+FORMAT_CHOICES = ["fbx", "obj", "usdc", "usda", "stl", "ply"]
+BAKE_TYPES = {
+ "DIFFUSE": "Base Color",
+ "ROUGHNESS": "Roughness",
+ "NORMAL": "Normal",
+} # "EMIT":"Emission Color" # "GLOSSY": "Specular IOR Level", "TRANSMISSION":"Transmission Weight" don"t export
+SPECIAL_BAKE = {"METAL": "Metallic", "TRANSMISSION": "Transmission Weight"}
+ALL_BAKE = BAKE_TYPES | SPECIAL_BAKE
+
+
+def apply_all_modifiers(obj):
+ for mod in obj.modifiers:
+ if mod is None:
+ continue
+ try:
+ obj.select_set(True)
+ bpy.context.view_layer.objects.active = obj
+ bpy.ops.object.modifier_apply(modifier=mod.name)
+ logger.info(f"Applied modifier {mod} on {obj}")
+ obj.select_set(False)
+ except RuntimeError:
+ logger.info(f"Can't apply {mod} on {obj}")
+ obj.select_set(False)
+ return
+
+
+def realizeInstances(obj):
+ for mod in obj.modifiers:
+ if mod is None or mod.type != "NODES":
+ continue
+ geo_group = mod.node_group
+ outputNode = geo_group.nodes["Group Output"]
+
+ logger.info(f"Realizing instances on {mod}")
+ link = outputNode.inputs[0].links[0]
+ from_socket = link.from_socket
+ geo_group.links.remove(link)
+ realizeNode = geo_group.nodes.new(type="GeometryNodeRealizeInstances")
+ geo_group.links.new(realizeNode.inputs[0], from_socket)
+ geo_group.links.new(outputNode.inputs[0], realizeNode.outputs[0])
+
+
+def remove_shade_smooth(obj):
+ for mod in obj.modifiers:
+ if mod is None or mod.type != "NODES":
+ continue
+ geo_group = mod.node_group
+ outputNode = geo_group.nodes["Group Output"]
+ if geo_group.nodes.get("Set Shade Smooth"):
+ logger.info("Removing shade smooth on " + obj.name)
+ smooth_node = geo_group.nodes["Set Shade Smooth"]
+ else:
+ continue
+
+ link = smooth_node.inputs[0].links[0]
+ from_socket = link.from_socket
+ geo_group.links.remove(link)
+ geo_group.links.new(outputNode.inputs[0], from_socket)
+
+
+def check_material_geonode(node_tree):
+ if node_tree.nodes.get("Set Material"):
+ logger.info("Found set material!")
+ return True
+
+ for node in node_tree.nodes:
+ if node.type == "GROUP" and check_material_geonode(node.node_tree):
+ return True
+
+ return False
+
+
+def handle_geo_modifiers(obj, export_usd):
+ has_geo_nodes = False
+ for mod in obj.modifiers:
+ if mod is None or mod.type != "NODES":
+ continue
+ has_geo_nodes = True
+
+ if has_geo_nodes and not obj.data.materials:
+ mat = bpy.data.materials.new(name=f"{mod.name} shader")
+ obj.data.materials.append(mat)
+ mat.use_nodes = True
+ mat.node_tree.nodes.remove(mat.node_tree.nodes["Principled BSDF"])
+
+ if not export_usd:
+ realizeInstances(obj)
+
+
+def split_glass_mats():
+ split_objs = []
+ for obj in bpy.data.objects:
+ if obj.hide_render or obj.hide_viewport:
+ continue
+ if any(
+ exclude in obj.name
+ for exclude in [
+ "BowlFactory",
+ "CupFactory",
+ "OvenFactory",
+ "BottleFactory",
+ ]
+ ):
+ continue
+ for slot in obj.material_slots:
+ mat = slot.material
+ if mat is None:
+ continue
+ if (
+ "shader_glass" in mat.name or "shader_lamp_bulb" in mat.name
+ ) and len(obj.material_slots) >= 2:
+ logger.info(f"Splitting {obj}")
+ obj.select_set(True)
+ bpy.context.view_layer.objects.active = obj
+ bpy.ops.object.mode_set(mode="EDIT")
+ bpy.ops.mesh.separate(type="MATERIAL")
+ bpy.ops.object.mode_set(mode="OBJECT")
+ obj.select_set(False)
+ split_objs.append(obj.name)
+ break
+
+ matches = [
+ obj
+ for split_obj in split_objs
+ for obj in bpy.data.objects
+ if split_obj in obj.name
+ ]
+ for match in matches:
+ if len(match.material_slots) == 0 or match.material_slots[0] is None:
+ continue
+ mat = match.material_slots[0].material
+ if mat is None:
+ continue
+ if "shader_glass" in mat.name or "shader_lamp_bulb" in mat.name:
+ match.name = f"{match.name}_SPLIT_GLASS"
+
+
+def clean_names(obj=None):
+ if obj is not None:
+ obj.name = (obj.name).replace(" ", "_")
+ obj.name = (obj.name).replace(".", "_")
+
+ if obj.type == "MESH":
+ for uv_map in obj.data.uv_layers:
+ uv_map.name = uv_map.name.replace(".", "_")
+
+ for mat in bpy.data.materials:
+ if mat is None:
+ continue
+ mat.name = (mat.name).replace(" ", "_")
+ mat.name = (mat.name).replace(".", "_")
+
+ for slot in obj.material_slots:
+ mat = slot.material
+ if mat is None:
+ continue
+ mat.name = (mat.name).replace(" ", "_")
+ mat.name = (mat.name).replace(".", "_")
+ return
+
+ for obj in bpy.data.objects:
+ obj.name = (obj.name).replace(" ", "_")
+ obj.name = (obj.name).replace(".", "_")
+
+ if obj.type == "MESH":
+ for uv_map in obj.data.uv_layers:
+ uv_map.name = uv_map.name.replace(
+ ".", "_"
+ ) # if uv has "." in name the node will export wrong in USD
+
+ for mat in bpy.data.materials:
+ if mat is None:
+ continue
+ mat.name = (mat.name).replace(" ", "_")
+ mat.name = (mat.name).replace(".", "_")
+
+
+def remove_obj_parents(obj=None):
+ if obj is not None:
+ world_matrix = obj.matrix_world.copy()
+ obj.parent = None
+ obj.matrix_world = world_matrix
+ return
+
+ for obj in bpy.data.objects:
+ world_matrix = obj.matrix_world.copy()
+ obj.parent = None
+ obj.matrix_world = world_matrix
+
+
+def remove_placeholder_area_lights() -> int:
+ removed_count = 0
+ for obj in list(bpy.data.objects):
+ if obj.type != "LIGHT" or obj.data.type != "AREA":
+ continue
+
+ parent_name = obj.parent.name if obj.parent is not None else ""
+ if "WindowFactory" not in parent_name:
+ continue
+ if not obj.name.startswith("Area"):
+ continue
+ if not math.isclose(float(obj.data.energy), 10.0, abs_tol=1e-4):
+ continue
+
+ world_loc = np.array(obj.matrix_world.translation)
+ if not np.allclose(world_loc, 0.0, atol=1e-4):
+ continue
+
+ bpy.data.objects.remove(obj, do_unlink=True)
+ removed_count += 1
+
+ if removed_count > 0:
+ logger.info(
+ "Removed placeholder window area lights before export: "
+ f"{removed_count}"
+ )
+ return removed_count
+
+
+def _get_export_scene_bounds() -> Optional[Tuple[np.ndarray, np.ndarray]]:
+ positions = []
+ view_objs = set(bpy.context.view_layer.objects)
+ for obj in bpy.data.objects:
+ if (
+ obj.type != "MESH"
+ or obj.data is None
+ or not obj.data.vertices
+ or obj.hide_render
+ or obj not in view_objs
+ ):
+ continue
+ for corner in obj.bound_box:
+ world_corner = obj.matrix_world @ Vector(corner)
+ positions.append(np.array(world_corner))
+
+ if not positions:
+ return None
+
+ points = np.stack(positions)
+ return points.min(axis=0), points.max(axis=0)
+
+
+def _get_world_background_strength() -> float:
+ world = bpy.context.scene.world
+ if world is None or not world.use_nodes:
+ return 0.25
+
+ strengths = []
+ for node in world.node_tree.nodes:
+ if node.type == "BACKGROUND":
+ strengths.append(float(node.inputs["Strength"].default_value))
+
+ if not strengths:
+ return 0.25
+ return max(strengths)
+
+
+def _get_world_sky_rotation() -> tuple[float, float]:
+ world = bpy.context.scene.world
+ if world is None or not world.use_nodes:
+ return (math.radians(55.0), 0.0)
+
+ for node in world.node_tree.nodes:
+ if node.type != "TEX_SKY":
+ continue
+ elevation = float(getattr(node, "sun_elevation", math.radians(35.0)))
+ rotation = float(getattr(node, "sun_rotation", 0.0))
+ return (math.pi * 0.5 - elevation, rotation)
+
+ return (math.radians(55.0), 0.0)
+
+
+def add_world_export_lights(
+ world_strength: float = 8.0,
+) -> list[bpy.types.Object]:
+ world = bpy.context.scene.world
+ if world is None:
+ return []
+
+ bounds = _get_export_scene_bounds()
+ if bounds is None:
+ return []
+
+ min_corner, max_corner = bounds
+ center = (min_corner + max_corner) * 0.5
+ diagonal = float(np.linalg.norm(max_corner - min_corner))
+ strength = max(_get_world_background_strength(), world_strength)
+ sun_pitch, sun_yaw = _get_world_sky_rotation()
+
+ created_lights = []
+
+ bpy.ops.object.light_add(
+ type="SUN",
+ location=(
+ float(center[0]),
+ float(center[1]),
+ float(max_corner[2] + diagonal),
+ ),
+ rotation=(sun_pitch, 0.0, sun_yaw),
+ )
+ sun = bpy.context.object
+ sun.name = "__EXPORT_WORLD_SUN__"
+ sun.data.energy = max(strength * 2.0, 0.5)
+ created_lights.append(sun)
+
+ bpy.ops.object.light_add(
+ type="AREA",
+ location=(
+ float(center[0]),
+ float(center[1]),
+ float(max_corner[2] + 0.5 * diagonal),
+ ),
+ rotation=(0.0, 0.0, 0.0),
+ )
+ area = bpy.context.object
+ area.name = "__EXPORT_WORLD_AREA__"
+ area.data.shape = "DISK"
+ area.data.size = max(diagonal, 2.0)
+ area.data.energy = max(strength * 2500.0, 500.0)
+ created_lights.append(area)
+
+ logger.info(
+ "Added temporary world export lights: "
+ f"{[obj.name for obj in created_lights]}"
+ )
+ return created_lights
+
+
+def remove_temp_export_objects(objects: list[bpy.types.Object]) -> None:
+ for obj in objects:
+ if obj is None:
+ continue
+ if obj.name not in bpy.data.objects:
+ continue
+ bpy.data.objects.remove(obj, do_unlink=True)
+
+
+def delete_objects():
+ logger.info("Deleting placeholders collection")
+ collection_name = "placeholders"
+ collection = bpy.data.collections.get(collection_name)
+
+ if collection:
+ for scene in bpy.data.scenes:
+ if collection.name in scene.collection.children:
+ scene.collection.children.unlink(collection)
+
+ for obj in collection.objects:
+ bpy.data.objects.remove(obj, do_unlink=True)
+
+ def delete_child_collections(parent_collection):
+ for child_collection in parent_collection.children:
+ delete_child_collections(child_collection)
+ bpy.data.collections.remove(child_collection)
+
+ delete_child_collections(collection)
+ bpy.data.collections.remove(collection)
+
+ if bpy.data.objects.get("Grid"):
+ bpy.data.objects.remove(bpy.data.objects["Grid"], do_unlink=True)
+
+ if bpy.data.objects.get("atmosphere"):
+ bpy.data.objects.remove(bpy.data.objects["atmosphere"], do_unlink=True)
+
+ if bpy.data.objects.get("KoleClouds"):
+ bpy.data.objects.remove(bpy.data.objects["KoleClouds"], do_unlink=True)
+
+
+def rename_all_meshes(obj=None):
+ if obj is not None:
+ if obj.data and obj.data.users == 1:
+ obj.data.name = obj.name
+ return
+
+ for obj in bpy.data.objects:
+ if obj.data and obj.data.users == 1:
+ obj.data.name = obj.name
+
+
+def update_visibility():
+ outliner_area = next(
+ a for a in bpy.context.screen.areas if a.type == "OUTLINER"
+ )
+ space = outliner_area.spaces[0]
+ space.show_restrict_column_viewport = (
+ True # Global visibility (Monitor icon)
+ )
+ collection_view = {}
+ obj_view = {}
+ for collection in bpy.data.collections:
+ collection_view[collection] = collection.hide_render
+ collection.hide_viewport = False # reenables viewports for all
+ collection.hide_render = False # enables renders for all collections
+
+ # disables viewports and renders for all objs
+ for obj in bpy.data.objects:
+ obj_view[obj] = obj.hide_render
+ obj.hide_viewport = True
+ obj.hide_render = True
+ obj.hide_set(0)
+
+ return collection_view, obj_view
+
+
+def uv_unwrap(obj):
+ obj.select_set(True)
+ bpy.context.view_layer.objects.active = obj
+
+ obj.data.uv_layers.new(name="ExportUV")
+ bpy.context.object.data.uv_layers["ExportUV"].active = True
+
+ logger.info("UV Unwrapping")
+ bpy.ops.object.mode_set(mode="EDIT")
+ bpy.ops.mesh.select_all(action="SELECT")
+ try:
+ bpy.ops.uv.smart_project(angle_limit=0.7)
+ except RuntimeError:
+ logger.info("UV Unwrap failed, skipping mesh")
+ bpy.ops.object.mode_set(mode="OBJECT")
+ obj.select_set(False)
+ return False
+ bpy.ops.object.mode_set(mode="OBJECT")
+ obj.select_set(False)
+ return True
+
+
+def bakeVertexColors(obj):
+ logger.info(f"Baking vertex color on {obj}")
+ bpy.ops.object.select_all(action="DESELECT")
+ obj.select_set(True)
+ bpy.context.view_layer.objects.active = obj
+ vertColor = bpy.context.object.data.color_attributes.new(
+ name="VertColor", domain="CORNER", type="BYTE_COLOR"
+ )
+ bpy.context.object.data.attributes.active_color = vertColor
+ bpy.ops.object.bake(
+ type="DIFFUSE", pass_filter={"COLOR"}, target="VERTEX_COLORS"
+ )
+ obj.select_set(False)
+
+
+def apply_baked_tex(obj, paramDict={}):
+ bpy.context.view_layer.objects.active = obj
+ bpy.context.object.data.uv_layers["ExportUV"].active_render = True
+ for uv_layer in reversed(obj.data.uv_layers):
+ if "ExportUV" not in uv_layer.name:
+ logger.info(f"Removed extraneous UV Layer {uv_layer}")
+ obj.data.uv_layers.remove(uv_layer)
+
+ for slot in obj.material_slots:
+ mat = slot.material
+ if mat is None:
+ continue
+ mat.use_nodes = True
+ nodes = mat.node_tree.nodes
+ logger.info("Reapplying baked texs on " + mat.name)
+
+ # delete all nodes except baked nodes and bsdf
+ excludedNodes = [type + "_node" for type in ALL_BAKE]
+ excludedNodes.extend(["Material Output", "Principled BSDF"])
+ for n in nodes:
+ if n.name not in excludedNodes:
+ nodes.remove(
+ n
+ ) # deletes an arbitrary principled BSDF in the case of a mix, which is handled below
+
+ output = nodes["Material Output"]
+
+ # stick baked texture in material
+ if nodes.get("Principled BSDF") is None: # no bsdf
+ logger.info("No BSDF, creating new one")
+ principled_bsdf_node = nodes.new("ShaderNodeBsdfPrincipled")
+ elif (
+ len(output.inputs[0].links) != 0
+ and output.inputs[0].links[0].from_node.bl_idname
+ == "ShaderNodeBsdfPrincipled"
+ ): # trivial bsdf graph
+ logger.info("Trivial shader graph, using old BSDF")
+ principled_bsdf_node = nodes["Principled BSDF"]
+ else:
+ logger.info("Non-trivial shader graph, creating new BSDF")
+ nodes.remove(
+ nodes["Principled BSDF"]
+ ) # shader graph was a mix of bsdfs
+ principled_bsdf_node = nodes.new("ShaderNodeBsdfPrincipled")
+
+ links = mat.node_tree.links
+
+ # create the new shader node links
+ links.new(output.inputs[0], principled_bsdf_node.outputs[0])
+ for type in ALL_BAKE:
+ if not nodes.get(type + "_node"):
+ continue
+ tex_node = nodes[type + "_node"]
+ if type == "NORMAL":
+ normal_node = nodes.new("ShaderNodeNormalMap")
+ links.new(normal_node.inputs["Color"], tex_node.outputs[0])
+ links.new(
+ principled_bsdf_node.inputs[ALL_BAKE[type]],
+ normal_node.outputs[0],
+ )
+ continue
+ links.new(
+ principled_bsdf_node.inputs[ALL_BAKE[type]],
+ tex_node.outputs[0],
+ )
+
+ # bring back cleared param values
+ if mat.name in paramDict:
+ principled_bsdf_node.inputs["Metallic"].default_value = paramDict[
+ mat.name
+ ]["Metallic"]
+ principled_bsdf_node.inputs["Sheen Weight"].default_value = (
+ paramDict[mat.name]["Sheen Weight"]
+ )
+ principled_bsdf_node.inputs["Coat Weight"].default_value = (
+ paramDict[mat.name]["Coat Weight"]
+ )
+
+
+def create_glass_shader(node_tree, export_usd):
+ nodes = node_tree.nodes
+ if nodes.get("Glass BSDF"):
+ color = nodes["Glass BSDF"].inputs[0].default_value
+ roughness = nodes["Glass BSDF"].inputs[1].default_value
+ ior = nodes["Glass BSDF"].inputs[2].default_value
+
+ if nodes.get("Principled BSDF"):
+ nodes.remove(nodes["Principled BSDF"])
+
+ principled_bsdf_node = nodes.new("ShaderNodeBsdfPrincipled")
+
+ if nodes.get("Glass BSDF"):
+ principled_bsdf_node.inputs["Base Color"].default_value = color
+ principled_bsdf_node.inputs["Roughness"].default_value = roughness
+ principled_bsdf_node.inputs["IOR"].default_value = ior
+ else:
+ principled_bsdf_node.inputs["Roughness"].default_value = 0
+
+ principled_bsdf_node.inputs["Transmission Weight"].default_value = 1
+ if export_usd:
+ principled_bsdf_node.inputs["Alpha"].default_value = 0.6
+ node_tree.links.new(
+ principled_bsdf_node.outputs[0], nodes["Material Output"].inputs[0]
+ )
+
+
+def process_glass_materials(obj, export_usd):
+ for slot in obj.material_slots:
+ mat = slot.material
+ if mat is None or not mat.use_nodes:
+ continue
+ nodes = mat.node_tree.nodes
+ outputNode = nodes["Material Output"]
+ if nodes.get("Glass BSDF"):
+ if (
+ outputNode.inputs[0].links[0].from_node.bl_idname
+ == "ShaderNodeBsdfGlass"
+ ):
+ logger.info(f"Creating glass material on {obj.name}")
+ else:
+ logger.info(
+ f"Non-trivial glass material on {obj.name}, material export will be inaccurate"
+ )
+ create_glass_shader(mat.node_tree, export_usd)
+ elif "glass" in mat.name or "shader_lamp_bulb" in mat.name:
+ logger.info(f"Creating glass material on {obj.name}")
+ create_glass_shader(mat.node_tree, export_usd)
+
+
+def bake_pass(
+ obj, dest: Path, img_size, bake_type, export_usd, export_name=None
+):
+ if export_name is None:
+ img = bpy.data.images.new(
+ f"{obj.name}_{bake_type}", img_size, img_size
+ )
+ clean_name = (
+ (obj.name).replace(" ", "_").replace(".", "_").replace("/", "_")
+ )
+ clean_name = (
+ clean_name.replace("(", "_").replace(")", "").replace("-", "_")
+ )
+ file_path = dest / f"{clean_name}_{bake_type}.png"
+ else:
+ img = bpy.data.images.new(
+ f"{export_name}_{bake_type}", img_size, img_size
+ )
+ file_path = dest / f"{export_name}_{bake_type}.png"
+ dest = dest / "textures"
+
+ bake_obj = False
+ bake_exclude_mats = {}
+
+ # materials are stored as stack so when removing traverse the reversed list
+ for index, slot in reversed(list(enumerate(obj.material_slots))):
+ mat = slot.material
+ if mat is None:
+ bpy.context.object.active_material_index = index
+ bpy.ops.object.material_slot_remove()
+ continue
+
+ logger.info(mat.name)
+ mat.use_nodes = True
+ nodes = mat.node_tree.nodes
+
+ output = nodes["Material Output"]
+
+ img_node = nodes.new("ShaderNodeTexImage")
+ img_node.name = f"{bake_type}_node"
+ img_node.image = img
+ img_node.select = True
+ nodes.active = img_node
+ img_node.select = True
+
+ if len(output.inputs["Displacement"].links) != 0:
+ bake_obj = True
+
+ if len(output.inputs[0].links) == 0:
+ logger.info(
+ f"{mat.name} has no surface output, not using baked textures"
+ )
+ bake_exclude_mats[mat] = img_node
+ continue
+
+ # surface_node = output.inputs[0].links[0].from_node
+ # if (
+ # bake_type in ALL_BAKE
+ # and surface_node.bl_idname == "ShaderNodeBsdfPrincipled"
+ # and len(surface_node.inputs[ALL_BAKE[bake_type]].links) == 0
+ # ): # trivial bsdf graph
+ # logger.info(
+ # f"{mat.name} has no procedural input for {bake_type}, not using baked textures"
+ # )
+ # bake_exclude_mats[mat] = img_node
+ # continue
+
+ bake_obj = True
+
+ if bake_type in SPECIAL_BAKE:
+ internal_bake_type = "EMIT"
+ else:
+ internal_bake_type = bake_type
+
+ if bake_obj:
+ logger.info(f"Baking {bake_type} pass")
+ bpy.ops.object.bake(
+ type=internal_bake_type,
+ pass_filter={"COLOR"},
+ save_mode="EXTERNAL",
+ )
+ img.filepath_raw = str(file_path)
+ img.save()
+ logger.info(f"Saving to {file_path}")
+ else:
+ logger.info(
+ f"No necessary materials to bake on {obj.name}, skipping bake"
+ )
+
+ for mat, img_node in bake_exclude_mats.items():
+ mat.node_tree.nodes.remove(img_node)
+
+
+def bake_special_emit(
+ obj, dest, img_size, export_usd, bake_type, export_name=None
+):
+ # If at least one material has both a BSDF and non-zero bake type value, then bake
+ should_bake = False
+
+ # (Root node, From Socket, To Socket)
+ links_removed = []
+ links_added = []
+
+ for slot in obj.material_slots:
+ mat = slot.material
+ if mat is None:
+ logger.warn("No material on mesh, skipping...")
+ continue
+ if not mat.use_nodes:
+ logger.warn("Material has no nodes, skipping...")
+ continue
+
+ nodes = mat.node_tree.nodes
+ principled_bsdf_node = None
+ root_node = None
+ logger.info(f"{mat.name} has {len(nodes)} nodes: {nodes}")
+ for node in nodes:
+ if node.type != "GROUP":
+ continue
+
+ for subnode in node.node_tree.nodes:
+ logger.info(
+ f" [{subnode.type}] {subnode.name} {subnode.bl_idname}"
+ )
+ if subnode.type == "BSDF_PRINCIPLED":
+ logger.debug(f" BSDF_PRINCIPLED: {subnode.inputs}")
+ principled_bsdf_node = subnode
+ root_node = node
+
+ if nodes.get("Principled BSDF"):
+ principled_bsdf_node = nodes["Principled BSDF"]
+ root_node = mat
+ elif not principled_bsdf_node:
+ logger.warn("No Principled BSDF, skipping...")
+ continue
+ elif ALL_BAKE[bake_type] not in principled_bsdf_node.inputs:
+ logger.warn(f"No {bake_type} input, skipping...")
+ continue
+
+ # Here, we"ve found the proper BSDF and bake type input. Set up the scene graph
+ # for baking.
+ outputSoc = principled_bsdf_node.outputs[0].links[0].to_socket
+
+ # Remove the BSDF link to Output first
+ link = principled_bsdf_node.outputs[0].links[0]
+ from_socket, to_socket = link.from_socket, link.to_socket
+ logger.debug(f"Removing link: {from_socket.name} => {to_socket.name}")
+ root_node.node_tree.links.remove(link)
+ links_removed.append((root_node, from_socket, to_socket))
+
+ # Get bake_type value
+ bake_input = principled_bsdf_node.inputs[ALL_BAKE[bake_type]]
+ bake_val = bake_input.default_value
+ logger.info(f"{bake_type} value: {bake_val}")
+
+ if bake_val > 0:
+ should_bake = True
+
+ # Make a color input matching the metallic value
+ col = root_node.node_tree.nodes.new("ShaderNodeRGB")
+ col.outputs[0].default_value = (bake_val, bake_val, bake_val, 1.0)
+
+ # Link the color to output
+ new_link = root_node.node_tree.links.new(col.outputs[0], outputSoc)
+ links_added.append((root_node, col.outputs[0], outputSoc))
+ logger.debug(
+ f"Linking {col.outputs[0].name} to {outputSoc.name}({outputSoc.bl_idname}): {new_link}"
+ )
+
+ # After setting up all materials, bake if applicable
+ if should_bake:
+ bake_pass(obj, dest, img_size, bake_type, export_usd, export_name)
+
+ # After baking, undo the temporary changes to the scene graph
+ for n, from_soc, to_soc in links_added:
+ logger.debug(
+ f"Removing added link:\t{n.name}: {from_soc.name} => {to_soc.name}"
+ )
+ for l in n.node_tree.links:
+ if l.from_socket == from_soc and l.to_socket == to_soc:
+ n.node_tree.links.remove(l)
+ logger.debug(
+ f"Removed link:\t{n.name}: {from_soc.name} => {to_soc.name}"
+ )
+
+ for n, from_soc, to_soc in links_removed:
+ logger.debug(
+ f"Adding back link:\t{n.name}: {from_soc.name} => {to_soc.name}"
+ )
+ n.node_tree.links.new(from_soc, to_soc)
+
+
+def remove_params(mat, node_tree):
+ nodes = node_tree.nodes
+ paramDict = {}
+ if nodes.get("Material Output"):
+ output = nodes["Material Output"]
+ elif nodes.get("Group Output"):
+ output = nodes["Group Output"]
+ else:
+ raise ValueError("Could not find material output node")
+
+ if (
+ nodes.get("Principled BSDF")
+ and output.inputs[0].links[0].from_node.bl_idname
+ == "ShaderNodeBsdfPrincipled"
+ ):
+ principled_bsdf_node = nodes["Principled BSDF"]
+ metal = principled_bsdf_node.inputs[
+ "Metallic"
+ ].default_value # store metallic value and set to 0
+ sheen = principled_bsdf_node.inputs["Sheen Weight"].default_value
+ clearcoat = principled_bsdf_node.inputs["Coat Weight"].default_value
+ paramDict[mat.name] = {
+ "Metallic": metal,
+ "Sheen Weight": sheen,
+ "Coat Weight": clearcoat,
+ }
+ principled_bsdf_node.inputs["Metallic"].default_value = 0
+ principled_bsdf_node.inputs["Sheen Weight"].default_value = 0
+ principled_bsdf_node.inputs["Coat Weight"].default_value = 0
+ return paramDict
+
+ for node in nodes:
+ if node.type == "GROUP":
+ paramDict = remove_params(mat, node.node_tree)
+ if len(paramDict) != 0:
+ return paramDict
+
+ return paramDict
+
+
+def process_interfering_params(obj):
+ for slot in obj.material_slots:
+ mat = slot.material
+ if mat is None or not mat.use_nodes:
+ continue
+ paramDict = remove_params(mat, mat.node_tree)
+ return paramDict
+
+
+def skipBake(obj):
+ if not obj.data.materials:
+ logger.info("No material on mesh, skipping...")
+ return True
+
+ if len(obj.data.vertices) == 0:
+ logger.info("Mesh has no vertices, skipping ...")
+ return True
+
+ return False
+
+
+def triangulate_mesh(obj: bpy.types.Object):
+ logger.debug("Triangulating Mesh")
+ if obj.type == "MESH":
+ view_state = obj.hide_viewport
+ obj.hide_viewport = False
+ bpy.context.view_layer.objects.active = obj
+ obj.select_set(True)
+ bpy.ops.object.mode_set(mode="EDIT")
+ bpy.ops.mesh.select_all(action="SELECT")
+ logger.debug(f"Triangulating {obj}")
+ bpy.ops.mesh.quads_convert_to_tris()
+ bpy.ops.object.mode_set(mode="OBJECT")
+ obj.select_set(False)
+ obj.hide_viewport = view_state
+
+
+def triangulate_meshes():
+ logger.debug("Triangulating Meshes")
+ for obj in bpy.context.scene.objects:
+ if obj.type == "MESH":
+ view_state = obj.hide_viewport
+ obj.hide_viewport = False
+ bpy.context.view_layer.objects.active = obj
+ obj.select_set(True)
+ bpy.ops.object.mode_set(mode="EDIT")
+ bpy.ops.mesh.select_all(action="SELECT")
+ logger.debug(f"Triangulating {obj}")
+ bpy.ops.mesh.quads_convert_to_tris()
+ bpy.ops.object.mode_set(mode="OBJECT")
+ obj.select_set(False)
+ obj.hide_viewport = view_state
+
+
+def adjust_wattages():
+ logger.info("Keeping original point light wattage for USD export")
+ for obj in bpy.context.scene.objects:
+ if obj.type == "LIGHT" and obj.data.type == "POINT":
+ light = obj.data
+ if hasattr(light, "energy"):
+ light.energy = float(light.energy)
+
+
+def set_center_of_mass():
+ logger.info("Resetting center of mass of objects")
+ for obj in bpy.context.scene.objects:
+ if not obj.hide_render:
+ view_state = obj.hide_viewport
+ obj.hide_viewport = False
+ obj.select_set(True)
+ bpy.context.view_layer.objects.active = obj
+ bpy.ops.object.origin_set(type="ORIGIN_GEOMETRY", center="MEDIAN")
+ obj.select_set(False)
+ obj.hide_viewport = view_state
+
+
+def duplicate_node_groups(node_tree, group_map=None):
+ if group_map is None:
+ group_map = {}
+
+ for node in node_tree.nodes:
+ if node.type == "GROUP":
+ group = node.node_tree
+ if group not in group_map:
+ group_copy = group.copy()
+ group_copy.name = f"{group.name}_copy"
+ group_map[group] = group_copy
+
+ duplicate_node_groups(group_copy, group_map)
+ else:
+ group_copy = group_map[group]
+
+ node.node_tree = group_copy
+
+ return group_map
+
+
+def deep_copy_material(original_material, new_name_suffix="_deepcopy"):
+ new_mat = original_material.copy()
+ new_mat.name = original_material.name + new_name_suffix
+ if new_mat.use_nodes and new_mat.node_tree:
+ duplicate_node_groups(new_mat.node_tree)
+ return new_mat
+
+
+def bake_object(obj, dest, img_size, export_usd, export_name=None):
+ if not uv_unwrap(obj):
+ return
+
+ bpy.ops.object.select_all(action="DESELECT")
+
+ with butil.SelectObjects(obj):
+ for slot in obj.material_slots:
+ mat = slot.material
+ if mat is not None:
+ slot.material = deep_copy_material(
+ mat
+ ) # we duplicate in the case of distinct meshes sharing materials
+
+ process_glass_materials(obj, export_usd)
+
+ for bake_type in SPECIAL_BAKE:
+ bake_special_emit(
+ obj, dest, img_size, export_usd, bake_type, export_name
+ )
+
+ # bake_normals(obj, dest, img_size, export_usd)
+ paramDict = process_interfering_params(obj)
+ for bake_type in BAKE_TYPES:
+ bake_pass(obj, dest, img_size, bake_type, export_usd, export_name)
+
+ apply_baked_tex(obj, paramDict)
+
+
+def bake_scene(folderPath: Path, image_res, vertex_colors, export_usd):
+ for obj in bpy.data.objects:
+ logger.info("---------------------------")
+ logger.info(obj.name)
+
+ if obj.type != "MESH" or obj not in list(
+ bpy.context.view_layer.objects
+ ):
+ logger.info("Not mesh, skipping ...")
+ continue
+
+ if skipBake(obj):
+ continue
+
+ if format == "stl":
+ continue
+
+ obj.hide_render = False
+ obj.hide_viewport = False
+
+ if vertex_colors:
+ bakeVertexColors(obj)
+ else:
+ bake_object(obj, folderPath, image_res, export_usd)
+
+ obj.hide_render = True
+ obj.hide_viewport = True
+
+
+def run_blender_export(
+ exportPath: Path,
+ format: str,
+ vertex_colors: bool,
+ individual_export: bool,
+ world_strength: float = 8.0,
+):
+ assert exportPath.parent.exists()
+ exportPath = str(exportPath)
+ temp_export_objects: list[bpy.types.Object] = []
+
+ if format == "obj":
+ if vertex_colors:
+ bpy.ops.wm.obj_export(
+ filepath=exportPath,
+ export_colors=True,
+ export_eval_mode="DAG_EVAL_RENDER",
+ export_selected_objects=individual_export,
+ )
+ else:
+ bpy.ops.wm.obj_export(
+ filepath=exportPath,
+ path_mode="COPY",
+ export_materials=True,
+ export_pbr_extensions=False,
+ export_eval_mode="DAG_EVAL_RENDER",
+ export_selected_objects=individual_export,
+ export_triangulated_mesh=True,
+ export_normals=False,
+ )
+
+ if format == "fbx":
+ if vertex_colors:
+ bpy.ops.export_scene.fbx(
+ filepath=exportPath,
+ colors_type="SRGB",
+ use_selection=individual_export,
+ )
+ else:
+ bpy.ops.export_scene.fbx(
+ filepath=exportPath,
+ path_mode="COPY",
+ embed_textures=True,
+ use_selection=individual_export,
+ )
+
+ if format == "stl":
+ bpy.ops.export_mesh.stl(
+ filepath=exportPath, use_selection=individual_export
+ )
+
+ if format == "ply":
+ bpy.ops.wm.ply_export(
+ filepath=exportPath, export_selected_objects=individual_export
+ )
+
+ if format in ["usda", "usdc"]:
+ temp_export_objects = add_world_export_lights(world_strength)
+ try:
+ bpy.ops.wm.usd_export(
+ filepath=exportPath,
+ export_textures=True,
+ # use_instancing=True,
+ overwrite_textures=True,
+ selected_objects_only=individual_export,
+ root_prim_path="/World",
+ )
+ finally:
+ remove_temp_export_objects(temp_export_objects)
+
+
+def export_scene(
+ input_blend: Path,
+ output_folder: Path,
+ pipeline_folder=None,
+ task_uniqname=None,
+ **kwargs,
+):
+ folder = output_folder / f"export_{os.path.splitext(input_blend.name)[0]}"
+ folder.mkdir(exist_ok=True, parents=True)
+ export_curr_scene(folder, **kwargs)
+
+ if pipeline_folder is not None and task_uniqname is not None:
+ (pipeline_folder / "logs" / f"FINISH_{task_uniqname}").touch()
+
+ return folder
+
+
+# side effects: will remove parents of inputted obj and clean its name, hides viewport of all objects
+def export_single_obj(
+ obj: bpy.types.Object,
+ output_folder: Path,
+ format="usdc",
+ image_res=1024,
+ vertex_colors=False,
+):
+ export_usd = format in ["usda", "usdc"]
+
+ export_folder = output_folder
+ export_folder.mkdir(parents=True, exist_ok=True)
+ export_file = export_folder / output_folder.with_suffix(f".{format}").name
+
+ logger.info(f"Exporting to directory {export_folder=}")
+
+ remove_obj_parents(obj)
+ rename_all_meshes(obj)
+
+ collection_views, obj_views = update_visibility()
+
+ bpy.context.scene.render.engine = "CYCLES"
+ bpy.context.scene.cycles.device = "GPU"
+ bpy.context.scene.cycles.samples = 1 # choose render sample
+ # Set the tile size
+ bpy.context.scene.cycles.tile_x = image_res
+ bpy.context.scene.cycles.tile_y = image_res
+
+ if obj.type != "MESH" or obj not in list(bpy.context.view_layer.objects):
+ raise ValueError("Object not mesh")
+
+ if export_usd:
+ apply_all_modifiers(obj)
+ else:
+ realizeInstances(obj)
+ apply_all_modifiers(obj)
+
+ if not skipBake(obj) and format != "stl":
+ if vertex_colors:
+ bakeVertexColors(obj)
+ else:
+ obj.hide_render = False
+ obj.hide_viewport = False
+ bake_object(obj, export_folder / "textures", image_res, export_usd)
+ obj.hide_render = True
+ obj.hide_viewport = True
+
+ for collection, status in collection_views.items():
+ collection.hide_render = status
+
+ for obj, status in obj_views.items():
+ obj.hide_render = status
+
+ clean_names(obj)
+
+ old_loc = obj.location.copy()
+ obj.location = (0, 0, 0)
+
+ if (
+ obj.type != "MESH"
+ or obj.hide_render
+ or len(obj.data.vertices) == 0
+ or obj not in list(bpy.context.view_layer.objects)
+ ):
+ raise ValueError("Object is not mesh or hidden from render")
+
+ export_subfolder = export_folder / obj.name
+ export_subfolder.mkdir(exist_ok=True)
+ export_file = export_subfolder / f"{obj.name}.{format}"
+
+ logger.info(f"Exporting file to {export_file=}")
+ obj.hide_viewport = False
+ obj.select_set(True)
+ run_blender_export(
+ export_file, format, vertex_colors, individual_export=True
+ )
+ obj.select_set(False)
+ obj.location = old_loc
+
+ return export_file
+
+
+def export_sim_ready(
+ obj: bpy.types.Object,
+ output_folder: Path,
+ image_res: int = 1024,
+ translation: Tuple = (0, 0, 0),
+ name: Optional[str] = None,
+ visual_only: bool = False,
+ collision_only: bool = False,
+ separate_asset_dirs: bool = True,
+) -> Dict[str, List[Path]]:
+ """Exports both the visual and collision assets for a geometry."""
+ if not visual_only:
+ assert (
+ coacd is not None
+ ), "coacd is required to export simulation assets."
+
+ asset_exports = defaultdict(list)
+ export_name = name if name is not None else obj.name
+
+ if separate_asset_dirs:
+ visual_export_folder = output_folder / "visual"
+ collision_export_folder = output_folder / "collision"
+ else:
+ visual_export_folder = output_folder
+ collision_export_folder = output_folder
+
+ texture_export_folder = output_folder / "textures"
+
+ visual_export_folder.mkdir(parents=True, exist_ok=True)
+ collision_export_folder.mkdir(parents=True, exist_ok=True)
+
+ logger.info(f"Exporting to directory {output_folder=}")
+
+ collection_views, obj_views = update_visibility()
+
+ bpy.context.scene.render.engine = "CYCLES"
+ bpy.context.scene.cycles.device = "GPU"
+ bpy.context.scene.cycles.samples = 1 # choose render sample
+ # Set the tile size
+ bpy.context.scene.cycles.tile_x = image_res
+ bpy.context.scene.cycles.tile_y = image_res
+
+ if obj.type != "MESH" or obj not in list(bpy.context.view_layer.objects):
+ raise ValueError("Object not mesh")
+
+ # export the textures
+ if not skipBake(obj):
+ texture_export_folder.mkdir(parents=True, exist_ok=True)
+ obj.hide_render = False
+ obj.hide_viewport = False
+ bake_object(obj, texture_export_folder, image_res, False, export_name)
+ obj.hide_render = True
+ obj.hide_viewport = True
+
+ for collection, status in collection_views.items():
+ collection.hide_render = status
+
+ for obj_tmp, status in obj_views.items():
+ obj_tmp.hide_render = status
+
+ # translating object
+ old_loc = obj.location.copy()
+ obj.location = (
+ old_loc[0] + translation[0],
+ old_loc[1] + translation[1],
+ old_loc[2] + translation[2],
+ )
+
+ if (
+ obj.type != "MESH"
+ or obj.hide_render
+ or len(obj.data.vertices) == 0
+ or obj not in list(bpy.context.view_layer.objects)
+ ):
+ raise ValueError("Object is not mesh or hidden from render")
+
+ # export the mesh assets
+ visual_export_file = visual_export_folder / f"{export_name}.obj"
+
+ logger.info(f"Exporting file to {visual_export_file=}")
+ obj.hide_viewport = False
+ obj.select_set(True)
+
+ # export visual asset
+ with butil.SelectObjects(obj, active=1):
+ bpy.ops.wm.obj_export(
+ filepath=str(visual_export_file),
+ up_axis="Z",
+ forward_axis="Y",
+ export_selected_objects=True,
+ export_triangulated_mesh=True, # required for coacd to run properly
+ )
+ if not collision_only:
+ asset_exports["visual"].append(visual_export_file)
+
+ if visual_only:
+ obj.select_set(False)
+ obj.location = old_loc
+ return asset_exports
+
+ clone = butil.deep_clone_obj(obj)
+ parts = butil.split_object(clone)
+
+ part_export_obj_file = visual_export_folder / f"{export_name}_part.obj"
+ part_export_mtl_file = visual_export_folder / f"{export_name}_part.mtl"
+
+ collision_count = 0
+ for part in parts:
+ with butil.SelectObjects(part, active=1):
+ bpy.ops.wm.obj_export(
+ filepath=str(part_export_obj_file),
+ up_axis="Z",
+ forward_axis="Y",
+ export_selected_objects=True,
+ export_triangulated_mesh=True, # required for coacd to run properly
+ )
+
+ # export the collision meshes
+ mesh_tri = trimesh.load(
+ str(part_export_obj_file),
+ merge_norm=True,
+ merge_tex=True,
+ force="mesh",
+ )
+ trimesh.repair.fix_inversion(mesh_tri)
+ preprocess_mode = "off"
+ if not mesh_tri.is_volume:
+ print(
+ mesh_tri.is_watertight,
+ mesh_tri.is_winding_consistent,
+ np.isfinite(mesh_tri.center_mass).all(),
+ mesh_tri.volume > 0.0,
+ )
+ preprocess_mode = "on"
+
+ if len(mesh_tri.vertices) < 4:
+ logger.warning(
+ f"Mesh is not a volume. Only has {len(mesh_tri.vertices)} vertices."
+ )
+ # raise ValueError(f"Mesh is not a volume. Only has {len(mesh_tri.vertices)} vertices.")
+ mesh = coacd.Mesh(mesh_tri.vertices, mesh_tri.faces)
+
+ subparts = coacd.run_coacd(
+ mesh=mesh,
+ threshold=0.05,
+ max_convex_hull=-1,
+ preprocess_mode=preprocess_mode,
+ mcts_max_depth=3,
+ )
+ export_name = export_name.replace("vis", "col")
+ for vs, fs in subparts:
+ collision_export_file = (
+ collision_export_folder
+ / f"{export_name}_col{collision_count}.obj"
+ )
+ subpart_mesh = trimesh.Trimesh(vs, fs)
+
+ # if subpart_mesh.is_empty:
+ # raise ValueError(
+ # "Warning: Collision mesh is completely outside the bounds of the original mesh."
+ # )
+ subpart_mesh.export(str(collision_export_file))
+ asset_exports["collision"].append(collision_export_file)
+ collision_count += 1
+
+ # delete temporary part files
+ part_export_obj_file.unlink(missing_ok=True)
+ part_export_mtl_file.unlink(missing_ok=True)
+
+ obj.select_set(False)
+ obj.location = old_loc
+ butil.delete(clone)
+
+ return asset_exports
+
+
+@gin.configurable
+def export_curr_scene(
+ output_folder: Path,
+ format="usdc",
+ image_res=1024,
+ vertex_colors=False,
+ individual_export=False,
+ omniverse_export=False,
+ pipeline_folder=None,
+ task_uniqname=None,
+ deconvex=False,
+ center_scene=False,
+ world_strength=8.0,
+ align_quat=(0.7071, 0, 0, 0.7071), # xyzw
+) -> Path:
+ export_usd = format in ["usda", "usdc"]
+ export_folder = output_folder
+ export_folder.mkdir(exist_ok=True)
+ export_file = export_folder / output_folder.with_suffix(f".{format}").name
+ texture_export_folder = export_folder / "textures"
+ bake_texture_folder = texture_export_folder
+ if export_usd:
+ bake_texture_folder = export_folder / "_usd_bake_textures"
+ logger.info(f"Exporting to directory {export_folder=}")
+
+ remove_placeholder_area_lights()
+ remove_obj_parents()
+ delete_objects()
+ triangulate_meshes()
+ if omniverse_export and format not in ["usda", "usdc"]:
+ split_glass_mats()
+ rename_all_meshes()
+
+ # remove 0 polygon meshes
+ for obj in bpy.data.objects:
+ if obj.type == "MESH" and len(obj.data.polygons) == 0:
+ logger.info(f"{obj.name} has no faces, removing...")
+ bpy.data.objects.remove(obj, do_unlink=True)
+
+ if center_scene:
+ from mathutils import Vector
+
+ positions = []
+ view_objs = set(bpy.context.view_layer.objects)
+ for obj in bpy.data.objects:
+ if (
+ obj.type == "MESH"
+ and obj.data
+ and obj.data.vertices
+ and obj.data.polygons
+ and not obj.hide_render
+ and obj in view_objs
+ ):
+ pos = np.array(obj.matrix_world.translation)
+ if not np.allclose(pos, 0):
+ positions.append(pos)
+
+ if len(positions) > 0:
+ positions = np.stack(positions)
+ center = (positions.min(axis=0) + positions.max(axis=0)) * 0.5
+ center[2] = positions[:, 2].min() # Set floor to 0 among z-axis.
+ for obj in bpy.data.objects:
+ pos = np.array(obj.matrix_world.translation)
+ if not np.allclose(pos, 0):
+ obj.location -= Vector(center)
+
+ scatter_cols = []
+ if export_usd:
+ if bpy.data.collections.get("scatter"):
+ scatter_cols.append(bpy.data.collections["scatter"])
+ if bpy.data.collections.get("scatters"):
+ scatter_cols.append(bpy.data.collections["scatters"])
+ for col in scatter_cols:
+ for obj in col.all_objects:
+ remove_shade_smooth(obj)
+
+ collection_views, obj_views = update_visibility()
+ for obj in bpy.data.objects:
+ if obj.type != "MESH" or obj not in list(
+ bpy.context.view_layer.objects
+ ):
+ continue
+ if export_usd:
+ apply_all_modifiers(obj)
+ else:
+ realizeInstances(obj)
+ apply_all_modifiers(obj)
+
+ bpy.context.scene.render.engine = "CYCLES"
+ bpy.context.scene.cycles.device = "GPU"
+ bpy.context.scene.cycles.samples = 1 # choose render sample
+ # Set the tile size
+ bpy.context.scene.cycles.tile_x = image_res
+ bpy.context.scene.cycles.tile_y = image_res
+
+ # iterate through all objects and bake them
+ bake_scene(
+ folderPath=bake_texture_folder,
+ image_res=image_res,
+ vertex_colors=vertex_colors,
+ export_usd=export_usd,
+ )
+
+ for collection, status in collection_views.items():
+ collection.hide_render = status
+
+ for obj, status in obj_views.items():
+ obj.hide_render = status
+
+ clean_names()
+
+ for obj in bpy.data.objects:
+ obj.hide_viewport = obj.hide_render
+
+ if omniverse_export:
+ adjust_wattages()
+ set_center_of_mass()
+
+ if individual_export:
+ import math
+ import xml.etree.ElementTree as ET
+ from xml.dom import minidom
+
+ import trimesh
+ from scipy.spatial.transform import Rotation
+ from embodied_gen.data.convex_decomposer import decompose_convex_mesh
+
+ urdf_root = ET.Element("robot", name="multi_object_scene")
+ ET.SubElement(urdf_root, "link", name="base")
+ object_info = []
+ bpy.ops.object.select_all(action="DESELECT")
+ objects = list(bpy.data.objects)
+ for obj in objects:
+ if (
+ obj.type != "MESH"
+ or obj.data is None
+ or len(obj.data.vertices) == 0
+ or len(obj.data.polygons) == 0
+ or obj.hide_render
+ or obj not in list(bpy.context.view_layer.objects)
+ ):
+ continue
+
+ obj_name = obj.name.replace("/", "_").replace("-", "_")
+ obj_name = obj_name.replace("(", "_").replace(")", "")
+ obj.name = obj_name
+ export_subfolder = export_folder / obj_name
+ export_subfolder.mkdir(exist_ok=True, parents=True)
+ export_file = export_subfolder / f"{obj_name}.{format}"
+
+ if "skirtingboard" in obj_name.lower():
+ logger.info(f"Skipping skirting board {obj_name}")
+ continue
+
+ logger.info(f"Exporting file to {export_file=}")
+ obj.hide_viewport = False
+
+ position = obj.matrix_world.to_translation()
+ rotation = Rotation.from_quat(align_quat)
+ rotation = rotation.as_euler("xyz", degrees=False)
+
+ obj.select_set(True)
+ bpy.context.view_layer.objects.active = obj
+ bpy.ops.object.location_clear()
+
+ face_count = len(obj.data.polygons)
+ if face_count > 1000:
+ if face_count > 1000000:
+ ratio = 0.005
+ elif face_count > 100000:
+ ratio = 0.02
+ elif face_count > 10000:
+ ratio = 0.1
+ else:
+ ratio = 0.2
+ angle_threshold = math.radians(5)
+ bpy.ops.object.mode_set(mode="OBJECT")
+ dec_mod = obj.modifiers.new(name="Decimate", type="DECIMATE")
+ dec_mod.decimate_type = "DISSOLVE"
+ dec_mod.angle_limit = angle_threshold
+ dec_mod.use_collapse_triangulate = False
+ dec_mod.ratio = ratio
+ bpy.ops.object.modifier_apply(modifier=dec_mod.name)
+
+ run_blender_export(
+ export_file,
+ format,
+ vertex_colors,
+ individual_export,
+ world_strength=world_strength,
+ )
+ obj.select_set(False)
+
+ mesh = trimesh.load(export_file)
+ if isinstance(mesh, trimesh.Scene) and len(mesh.geometry) == 0:
+ shutil.rmtree(export_file.parent)
+ continue
+
+ object_info.append(
+ {
+ "name": obj_name,
+ "mesh_path": f"{obj_name}/{obj_name}.{format}",
+ "mesh_abs_path": str(export_file),
+ "xyz": tuple(position),
+ "rpy": tuple(rotation),
+ }
+ )
+
+ for obj in object_info:
+ link = ET.SubElement(urdf_root, "link", name=obj["name"])
+ visual = ET.SubElement(link, "visual")
+ geom = ET.SubElement(visual, "geometry")
+ ET.SubElement(
+ geom, "mesh", filename=obj["mesh_path"], scale="1 1 1"
+ )
+ if deconvex:
+ print("Deconvexing mesh for collision, waiting...")
+ d_params = dict(
+ threshold=0.05, max_convex_hull=128, verbose=False
+ )
+ mesh_path = obj["mesh_abs_path"]
+ output_path = mesh_path.replace(".obj", "_collision.obj")
+ decompose_convex_mesh(mesh_path, output_path, **d_params)
+ collision_mesh = obj["mesh_path"].replace(
+ ".obj", "_collision.obj"
+ )
+ collision = ET.SubElement(link, "collision")
+ geom2 = ET.SubElement(collision, "geometry")
+ ET.SubElement(
+ geom2, "mesh", filename=collision_mesh, scale="1 1 1"
+ )
+
+ joint = ET.SubElement(
+ urdf_root, "joint", name=f"joint_{obj['name']}", type="fixed"
+ )
+ ET.SubElement(joint, "parent", link="base")
+ ET.SubElement(joint, "child", link=obj["name"])
+ ET.SubElement(
+ joint,
+ "origin",
+ xyz="%.4f %.4f %.4f" % obj["xyz"],
+ rpy="%.4f %.4f %.4f" % obj["rpy"],
+ )
+
+ urdf_str = minidom.parseString(ET.tostring(urdf_root)).toprettyxml(
+ indent=" "
+ )
+ urdf_path = export_folder / "scene.urdf"
+ with open(urdf_path, "w") as f:
+ f.write(urdf_str)
+ logger.info(f"URDF exported to {urdf_path}")
+
+ return urdf_path
+ else:
+ logger.info(f"Exporting file to {export_file=}")
+ try:
+ run_blender_export(
+ export_file,
+ format,
+ vertex_colors,
+ individual_export,
+ world_strength=world_strength,
+ )
+ finally:
+ if export_usd and bake_texture_folder.exists():
+ shutil.rmtree(bake_texture_folder, ignore_errors=True)
+
+ return export_file
+
+
+def main(args):
+ args.output_folder.mkdir(exist_ok=True)
+ targets = sorted(list(args.input_folder.iterdir()))
+ for blendfile in targets:
+ if not blendfile.suffix == ".blend":
+ print(f"Skipping non-blend file {blendfile}")
+ continue
+
+ bpy.ops.wm.open_mainfile(filepath=str(blendfile))
+
+ folder = export_scene(
+ blendfile,
+ args.output_folder,
+ format=args.format,
+ image_res=args.resolution,
+ vertex_colors=args.vertex_colors,
+ individual_export=args.individual,
+ omniverse_export=args.omniverse,
+ deconvex=args.deconvex,
+ center_scene=args.center_scene,
+ world_strength=args.world_strength,
+ )
+
+ bpy.ops.wm.quit_blender()
+
+
+def make_args():
+ parser = argparse.ArgumentParser()
+
+ parser.add_argument("--input_folder", type=Path)
+ parser.add_argument("--output_folder", type=Path)
+
+ parser.add_argument("-f", "--format", type=str, choices=FORMAT_CHOICES)
+
+ parser.add_argument("-v", "--vertex_colors", action="store_true")
+ parser.add_argument("-r", "--resolution", default=1024, type=int)
+ parser.add_argument("-i", "--individual", action="store_true")
+ parser.add_argument("-o", "--omniverse", action="store_true")
+ parser.add_argument("--deconvex", action="store_true")
+ parser.add_argument("--center_scene", action="store_true")
+ parser.add_argument("--world_strength", default=8.0, type=float)
+
+ args = parser.parse_args()
+
+ if args.format not in FORMAT_CHOICES:
+ raise ValueError("Unsupported or invalid file format.")
+
+ if args.vertex_colors and args.format not in ["ply", "fbx", "obj"]:
+ raise ValueError("File format does not support vertex colors.")
+
+ if args.format == "ply" and not args.vertex_colors:
+ raise ValueError(".ply export must use vertex colors.")
+
+ return args
+
+
+if __name__ == "__main__":
+ args = make_args()
+ main(args)
diff --git a/embodied_gen/scripts/room_gen/gen_room.py b/embodied_gen/scripts/room_gen/gen_room.py
new file mode 100644
index 0000000..d06694a
--- /dev/null
+++ b/embodied_gen/scripts/room_gen/gen_room.py
@@ -0,0 +1,274 @@
+# Project EmbodiedGen
+#
+# Copyright (c) 2025 Horizon Robotics. All Rights Reserved.
+#
+# 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.
+
+
+import os
+import random
+import shutil
+import subprocess
+import sys
+import time
+from dataclasses import dataclass
+from enum import Enum
+
+import tyro
+from embodied_gen.utils.log import logger
+
+EXEC_PYTHON = os.environ.get(
+ "BLENDER_PYTHON",
+ "thirdparty/infinigen/blender/4.2/python/bin/python3.11",
+)
+
+
+class RoomType(str, Enum):
+ bedroom = "Bedroom"
+ livingRoom = "LivingRoom"
+ kitchen = "Kitchen"
+ bathroom = "Bathroom"
+ diningRoom = "DiningRoom"
+ office = "Office"
+ house = "House"
+
+
+class Complexity(str, Enum):
+ minimalist = "minimalist"
+ simple = "simple"
+ medium = "medium"
+ detail = "detail"
+
+
+@dataclass
+class GenRoomArgs:
+ """Configuration for single-seed Infinigen room generation and export."""
+
+ output_root: str
+ """The base output directory for generated rooms."""
+
+ room_type: RoomType = RoomType.kitchen
+ """The type of room to generate."""
+
+ seed: int = None
+ """The specific seed number to generate."""
+
+ # Task Switches (Default to True, use flags like --no-gen to disable)
+ gen: bool = True
+ """Whether to run the indoor generation task (generate_indoors)."""
+
+ urdf: bool = True
+ """Whether to export to URDF (requires generation output)."""
+
+ usd: bool = True
+ """Whether to export to USD (requires generation output)."""
+
+ custom_params: str = "embodied_gen/scripts/room_gen/custom_solve.gin"
+
+ large_scene: bool = False
+ """If True, has_fewer_rooms=False for large scene generation."""
+
+ complexity: Complexity = Complexity.simple
+ """Complexity level: minimalist, simple, medium, or detail."""
+
+ prompt: str | None = None
+ """Natural-language task/scene description. When set, room_type and
+ complexity are inferred from it via a GPT router, overriding the
+ explicit values."""
+
+
+def run_command(cmd: list[str], task_name: str):
+ """Helper: Use Popen to allow killing the child process on Ctrl+C.
+
+ Includes execution time logging.
+ """
+ logger.info(f"--> Running {task_name}...")
+ start_time = time.time()
+ process = subprocess.Popen(cmd, env=None)
+ try:
+ return_code = process.wait()
+ if return_code != 0:
+ raise subprocess.CalledProcessError(return_code, cmd)
+
+ elapsed_mins = (time.time() - start_time) / 60
+ logger.info(
+ f"--> {task_name} successfully in {elapsed_mins:.1f} mins."
+ )
+
+ except KeyboardInterrupt:
+ logger.warning(f"\n[Interrupted] Stopping {task_name}...")
+ process.kill()
+ process.wait()
+ sys.exit(0)
+
+ except subprocess.CalledProcessError as e:
+ logger.info(
+ f"Error occurred during {task_name}. Exit code: {e.returncode}"
+ )
+ sys.exit(1)
+
+
+def generate_room(cfg: GenRoomArgs):
+ if cfg.prompt:
+ # Deferred import: GPT routing deps are only needed in prompt mode.
+ from embodied_gen.scripts.room_gen.route_room import (
+ InfinigenGenRouter,
+ )
+ from embodied_gen.utils.gpt_clients import GPT_CLIENT
+
+ router = InfinigenGenRouter(gpt_client=GPT_CLIENT)
+ room_name, complexity = router.query(cfg.prompt)
+ cfg.room_type = RoomType(room_name)
+ cfg.complexity = Complexity(complexity)
+ logger.info(
+ f"Router mapped prompt {cfg.prompt!r} to room={room_name}, "
+ f"complexity={complexity}."
+ )
+
+ room_type = cfg.room_type.value
+ seed = cfg.seed
+ if seed is None:
+ seed = random.randint(0, 100000)
+
+ blender_dir = f"{cfg.output_root}/{room_type}_seed{seed}/blender"
+ logger.info(
+ f"{room_type} | Seed {seed}: Gen={cfg.gen}, URDF={cfg.urdf}, USD={cfg.usd}"
+ )
+
+ # Complexity configuration mapping
+ complexity_config = {
+ Complexity.minimalist: {
+ "compose_indoors.solve_large_enabled": False,
+ "compose_indoors.solve_medium_enabled": False,
+ "compose_indoors.solve_small_enabled": False,
+ },
+ Complexity.simple: {
+ "compose_indoors.solve_large_enabled": True,
+ "compose_indoors.solve_medium_enabled": False,
+ "compose_indoors.solve_small_enabled": False,
+ },
+ Complexity.medium: {
+ "compose_indoors.solve_large_enabled": True,
+ "compose_indoors.solve_medium_enabled": True,
+ "compose_indoors.solve_small_enabled": False,
+ },
+ Complexity.detail: {
+ "compose_indoors.solve_large_enabled": True,
+ "compose_indoors.solve_medium_enabled": True,
+ "compose_indoors.solve_small_enabled": True,
+ },
+ }
+
+ # Get complexity settings
+ complexity_settings = complexity_config[cfg.complexity]
+ time_cost_info = {
+ Complexity.minimalist: "~1mins",
+ Complexity.simple: "~10mins",
+ Complexity.medium: "~20mins",
+ Complexity.detail: "~70mins",
+ }
+ logger.info(
+ f"Complexity: {cfg.complexity.value} (estimated time: {time_cost_info[cfg.complexity]})"
+ )
+
+ if cfg.gen:
+ dst_gin = "thirdparty/infinigen/infinigen_examples/configs_indoor/custom_solve.gin"
+ shutil.copy(cfg.custom_params, dst_gin)
+ cmd_generate = [
+ EXEC_PYTHON,
+ "embodied_gen/scripts/room_gen/run_generate_indoors.py",
+ "--seed",
+ str(seed),
+ "--task",
+ "coarse",
+ "--output_folder",
+ blender_dir,
+ "-g",
+ "custom_solve.gin",
+ ]
+ if room_type == "House":
+ has_fewer_rooms_value = "False" if cfg.large_scene else "True"
+ cmd_generate.append("-p")
+ cmd_generate.append(
+ f'home_room_constraints.has_fewer_rooms={has_fewer_rooms_value}'
+ )
+ else:
+ cmd_generate.append("-p")
+ cmd_generate.append(
+ f'restrict_solving.restrict_parent_rooms=["{room_type}"]'
+ )
+ cmd_generate.append("restrict_solving.solve_max_rooms=1")
+ if room_type == "Office":
+ cmd_generate.append("home_room_constraints.office_only=True")
+ cmd_generate.append(
+ f"compose_indoors.solve_large_enabled={complexity_settings['compose_indoors.solve_large_enabled']}"
+ )
+ cmd_generate.append(
+ f"compose_indoors.solve_medium_enabled={complexity_settings['compose_indoors.solve_medium_enabled']}"
+ )
+ cmd_generate.append(
+ f"compose_indoors.solve_small_enabled={complexity_settings['compose_indoors.solve_small_enabled']}"
+ )
+ run_command(cmd_generate, "Room Generation")
+
+ if cfg.urdf:
+ if not os.path.exists(blender_dir) and not cfg.gen:
+ logger.warning(f"Warning: {blender_dir} not found. Skipping URDF.")
+ else:
+ cmd_export_urdf = [
+ EXEC_PYTHON,
+ "embodied_gen/scripts/room_gen/export_scene.py",
+ "--input_folder",
+ blender_dir,
+ "--output_folder",
+ f"{cfg.output_root}/{room_type}_seed{seed}/urdf",
+ "-f",
+ "obj",
+ "-r",
+ "1024",
+ "--individual",
+ "--deconvex",
+ "--center_scene",
+ ]
+ run_command(cmd_export_urdf, "Export URDF")
+
+ if cfg.usd:
+ if not os.path.exists(blender_dir) and not cfg.gen:
+ logger.warning(f"Warning: {blender_dir} not found. Skipping USD.")
+ else:
+ cmd_export_usd = [
+ EXEC_PYTHON,
+ "embodied_gen/scripts/room_gen/export_scene.py",
+ "--input_folder",
+ blender_dir,
+ "--output_folder",
+ f"{cfg.output_root}/{room_type}_seed{seed}/usd",
+ "-f",
+ "usdc",
+ "-r",
+ "1024",
+ "--omniverse",
+ "--center_scene",
+ ]
+ run_command(cmd_export_usd, "Export USD")
+
+ logger.info(f"\n=== Completed {room_type} Seed {seed} ===")
+
+
+if __name__ == "__main__":
+ try:
+ cfg = tyro.cli(GenRoomArgs)
+ generate_room(cfg)
+ except KeyboardInterrupt:
+ logger.info("\nProgram interrupted by user (Cmd+C). Exiting.")
+ sys.exit(0)
diff --git a/embodied_gen/scripts/room_gen/gen_trajectory.py b/embodied_gen/scripts/room_gen/gen_trajectory.py
new file mode 100644
index 0000000..cfa2d92
--- /dev/null
+++ b/embodied_gen/scripts/room_gen/gen_trajectory.py
@@ -0,0 +1,306 @@
+# Project EmbodiedGen
+#
+# Copyright (c) 2025 Horizon Robotics. All Rights Reserved.
+#
+# 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.
+
+
+from __future__ import annotations
+
+import json
+import logging
+import subprocess
+from dataclasses import dataclass
+from importlib import import_module
+from pathlib import Path
+
+import numpy as np
+import tyro
+from shapely.geometry import Polygon
+from shapely.ops import unary_union
+
+# The spatial-computing package name contains a hyphen, so it cannot be
+# imported with a normal ``import`` statement; use importlib instead.
+_SC = "embodied_gen.skills.spatial-computing.core"
+UrdfSemanticInfoCollector = import_module(
+ f"{_SC}.collector"
+).UrdfSemanticInfoCollector
+RoamTrajectoryGenerator = import_module(
+ f"{_SC}.trajectory"
+).RoamTrajectoryGenerator
+FloorplanVisualizer = import_module(f"{_SC}.visualizer").FloorplanVisualizer
+
+logger = logging.getLogger(__name__)
+
+
+@dataclass
+class GenTrajectoryArgs:
+ """Configuration for roaming-trajectory generation."""
+
+ urdf_path: str
+ """Path to the input URDF scene file."""
+
+ output_dir: str = "outputs/trajectory"
+ """Directory for the trajectory JSON and floorplan PNG."""
+
+ clearance: float = 0.4
+ """Minimum clearance (m) from walls; keep small so doorways stay passable."""
+
+ obstacle_clearance: float = 0.5
+ """Minimum clearance (m) from furniture/objects (doors/walls excluded)."""
+
+ resolution: float = 0.05
+ """Occupancy-grid cell size in meters (smaller = finer, slower)."""
+
+ num_waypoints: int = 8
+ """Number of roam waypoints to visit across the navigable space."""
+
+ speed: float = 0.5
+ """Constant roaming speed (m/s). Sets the spacing with --time_step."""
+
+ time_step: float = 0.1
+ """Time between consecutive waypoints (s). Spacing = speed * time_step."""
+
+ turn_radius: float = 0.8
+ """Target turning-arc radius (m); larger gives wider, rounder turns."""
+
+ endpoint_clearance: float = 1.5
+ """Minimum distance (m) the start and end keep from walls/obstacles."""
+
+ arrow_stride: int = 10
+ """Draw a heading arrow every N points in the PNG (0 disables)."""
+
+ remove_doors: bool = False
+ """Hide door footprints in the floorplan image and animation."""
+
+ animate: bool = False
+ """Also render a per-frame floorplan animation (frames/ + trajectory.mp4)."""
+
+ anim_frame_stride: int = 1
+ """Animate every Nth waypoint. Match the render --frame_stride to sync."""
+
+ anim_fps: float = 0.0
+ """Animation frame rate. 0 = auto (real-time from speed / stride)."""
+
+ anim_dpi: int = 100
+ """Resolution (dpi) of the animation frames."""
+
+ start_xy: tuple[float, float] | None = None
+ """Optional world (x, y) start; defaults to the largest free region."""
+
+ mesh_sample_num: int = 5000
+ """Number of points sampled per mesh when parsing the URDF."""
+
+ seed: int | None = None
+ """Optional RNG seed for reproducible roaming."""
+
+
+def generate_trajectory(cfg: GenTrajectoryArgs) -> None:
+ """Run the roaming-trajectory pipeline and write outputs.
+
+ Args:
+ cfg: Parsed CLI configuration.
+ """
+ output_dir = Path(cfg.output_dir)
+ output_dir.mkdir(parents=True, exist_ok=True)
+
+ collector = UrdfSemanticInfoCollector(mesh_sample_num=cfg.mesh_sample_num)
+ collector.collect(cfg.urdf_path)
+
+ generator = RoamTrajectoryGenerator.from_collector(
+ collector,
+ clearance=cfg.clearance,
+ resolution=cfg.resolution,
+ obstacle_clearance=cfg.obstacle_clearance,
+ )
+ result = generator.generate(
+ start_xy=cfg.start_xy,
+ num_waypoints=cfg.num_waypoints,
+ point_spacing=cfg.speed * cfg.time_step,
+ turn_radius=cfg.turn_radius,
+ endpoint_clearance=cfg.endpoint_clearance,
+ seed=cfg.seed,
+ )
+
+ stem = Path(cfg.urdf_path).stem
+ json_path = output_dir / f"{stem}_trajectory.json"
+ png_path = output_dir / f"{stem}_trajectory.png"
+
+ # Points are equidistant, so timestamps follow constant-speed motion:
+ # t = cumulative arc length / speed.
+ xy = result.points[:, :2]
+ seg = np.linalg.norm(np.diff(xy, axis=0), axis=1)
+ times = np.concatenate([[0.0], np.cumsum(seg)]) / cfg.speed
+ waypoints = [
+ {
+ "x": round(float(x), 4),
+ "y": round(float(y), 4),
+ "rot": round(float(r), 2),
+ "t": round(float(t), 3),
+ }
+ for (x, y, r), t in zip(result.points, times)
+ ]
+
+ payload = {
+ "num_waypoints": len(waypoints),
+ "clearance": result.clearance,
+ "min_clearance": round(result.min_clearance, 4),
+ "length": round(result.length, 4),
+ "speed": cfg.speed,
+ "time_step": cfg.time_step,
+ "reachable_rooms": result.reachable_rooms,
+ "rot_convention": (
+ "0 deg points +Y (12 o'clock); counter-clockwise positive; "
+ "tangent to the roaming curve (forward heading); range [0, 360)."
+ ),
+ "waypoints": waypoints,
+ }
+ json_path.write_text(json.dumps(payload, indent=2, ensure_ascii=False))
+
+ footprints, occ_area = visible_footprints(collector, cfg.remove_doors)
+
+ FloorplanVisualizer.plot(
+ collector.rooms,
+ footprints,
+ occ_area,
+ str(png_path),
+ trajectory=result.points,
+ arrow_stride=cfg.arrow_stride,
+ )
+
+ logger.info("Trajectory waypoints : %s", json_path)
+ logger.info("Floorplan overlay : %s", png_path)
+ logger.info(
+ "Length %.2fm, min clearance %.2fm, reaches rooms: %s",
+ result.length,
+ result.min_clearance,
+ ", ".join(result.reachable_rooms) or "(none)",
+ )
+
+ if cfg.animate:
+ fps = cfg.anim_fps
+ if fps <= 0.0:
+ fps = (1.0 / cfg.time_step) / max(1, cfg.anim_frame_stride)
+ animate_floorplan(
+ rooms=collector.rooms,
+ footprints=footprints,
+ occ_area=occ_area,
+ points=result.points,
+ output_dir=output_dir,
+ frame_stride=cfg.anim_frame_stride,
+ fps=fps,
+ dpi=cfg.anim_dpi,
+ )
+
+
+def visible_footprints(collector, remove_doors: bool):
+ """Return footprints and occupied area for floorplan drawing.
+
+ When ``remove_doors`` is set, door footprints are excluded and the
+ occupied area is recomputed without them so doors do not appear.
+
+ Args:
+ collector: Parsed scene collector.
+ remove_doors: Whether to hide door footprints.
+
+ Returns:
+ Tuple of (footprints dict, occupied-area geometry).
+ """
+ if not remove_doors:
+ return collector.footprints, collector.occ_area
+
+ footprints = {
+ key: poly
+ for key, poly in collector.footprints.items()
+ if "door" not in key.lower()
+ }
+ occ_area = (
+ unary_union(list(footprints.values())) if footprints else Polygon()
+ )
+ return footprints, occ_area
+
+
+def animate_floorplan(
+ rooms: dict,
+ footprints: dict,
+ occ_area,
+ points: np.ndarray,
+ output_dir: Path,
+ frame_stride: int,
+ fps: float,
+ dpi: int,
+) -> None:
+ """Render a floorplan animation that follows the trajectory progress.
+
+ Each frame shows the traveled path in red, a green dot at the current
+ position, and a red heading arrow; the future path is hidden. Frame
+ indices match the render frames (same stride) for easy pairing.
+
+ Args:
+ rooms: Room-name to floor-polygon map.
+ footprints: Object-name to footprint-polygon map (to draw).
+ occ_area: Occupied-area geometry overlay.
+ points: (N, 3) array of (x, y, rot_deg) waypoints.
+ output_dir: Directory to write ``frames/`` and ``trajectory.mp4``.
+ frame_stride: Render every Nth waypoint.
+ fps: Output video frame rate.
+ dpi: Resolution of each frame.
+ """
+ frames_dir = output_dir / "frames"
+ frames_dir.mkdir(parents=True, exist_ok=True)
+
+ for i in range(0, len(points), max(1, frame_stride)):
+ FloorplanVisualizer.plot(
+ rooms,
+ footprints,
+ occ_area,
+ str(frames_dir / f"frame_{i:04d}.png"),
+ trajectory=points,
+ current_index=i,
+ dpi=dpi,
+ )
+
+ video_path = output_dir / "trajectory.mp4"
+ cmd = [
+ "ffmpeg",
+ "-y",
+ "-framerate",
+ f"{max(fps, 1.0):g}",
+ "-pattern_type",
+ "glob",
+ "-i",
+ str(frames_dir / "frame_*.png"),
+ "-vf",
+ "pad=ceil(iw/2)*2:ceil(ih/2)*2",
+ "-c:v",
+ "libx264",
+ "-pix_fmt",
+ "yuv420p",
+ str(video_path),
+ ]
+ result = subprocess.run(cmd, capture_output=True, text=True)
+ if result.returncode != 0:
+ raise RuntimeError(f"ffmpeg failed for {video_path}:\n{result.stderr}")
+
+ logger.info("Trajectory animation : %s", video_path)
+
+
+def main() -> None:
+ """CLI entry point."""
+ logging.basicConfig(level=logging.INFO, format="%(message)s")
+ cfg = tyro.cli(GenTrajectoryArgs)
+ generate_trajectory(cfg)
+
+
+if __name__ == "__main__":
+ main()
diff --git a/embodied_gen/scripts/room_gen/render_birdseye.py b/embodied_gen/scripts/room_gen/render_birdseye.py
new file mode 100644
index 0000000..9966316
--- /dev/null
+++ b/embodied_gen/scripts/room_gen/render_birdseye.py
@@ -0,0 +1,271 @@
+# Project EmbodiedGen
+#
+# Copyright (c) 2025 Horizon Robotics. All Rights Reserved.
+#
+# 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.
+
+
+"""Render a top-down bird's-eye view of a USD scene with ceilings hidden."""
+
+from __future__ import annotations
+
+import argparse
+import logging
+import tempfile
+from pathlib import Path
+
+import bpy
+from mathutils import Vector
+from embodied_gen.scripts.room_gen.render_usd import RenderUsd
+
+logger = logging.getLogger(__name__)
+
+
+CEILING_KEYWORDS = ("ceiling", "exterior")
+DEFAULT_ROOM_USD_GLOB = "seed*/usd/export_scene/export_scene.usdc"
+
+
+class BirdseyeRenderUsd(RenderUsd):
+ """Top-down USD renderer with ceiling removal and orthographic camera."""
+
+ def __init__(
+ self,
+ *,
+ ortho_margin: float = 1.05,
+ use_cpu: bool = False,
+ **kwargs,
+ ) -> None:
+ super().__init__(**kwargs)
+ self.ortho_margin = ortho_margin
+ self.use_cpu = use_cpu
+
+ def configure_cycles(self) -> None:
+ if self.use_cpu:
+ self.scene.render.engine = "CYCLES"
+ self.scene.cycles.device = "CPU"
+ self.scene.cycles.samples = self.samples
+ self.scene.render.resolution_x = self.resolution[0]
+ self.scene.render.resolution_y = self.resolution[1]
+ self.scene.render.image_settings.file_format = "PNG"
+ else:
+ super().configure_cycles()
+ self.scene.render.film_transparent = True
+ self.scene.render.image_settings.color_mode = "RGBA"
+
+ def remove_ceiling_objects(self) -> int:
+ """Delete any object whose name contains a ceiling keyword."""
+ removed = 0
+ for obj in list(self.scene.objects):
+ lower = obj.name.lower()
+ if any(kw in lower for kw in CEILING_KEYWORDS):
+ bpy.data.objects.remove(obj, do_unlink=True)
+ removed += 1
+ logger.info("Removed %d ceiling objects.", removed)
+ return removed
+
+ def create_orthographic_camera(
+ self, center: Vector, top_z: float, scene_size: float
+ ) -> bpy.types.Object:
+ location = Vector((center.x, center.y, top_z + max(scene_size, 1.0)))
+ bpy.ops.object.camera_add(location=location, rotation=(0.0, 0.0, 0.0))
+ camera = bpy.context.object
+ camera.rotation_mode = "XYZ"
+ camera.data.type = "ORTHO"
+ camera.data.ortho_scale = scene_size * self.ortho_margin
+ camera.data.clip_start = 0.01
+ camera.data.clip_end = 1000.0
+ self.scene.camera = camera
+ return camera
+
+ def run(self) -> None:
+ rgb_output_path = self.get_rgb_output_path()
+ self.output_dir.mkdir(parents=True, exist_ok=True)
+ self.clear_scene()
+ self.import_usd()
+ self.remove_ceiling_objects()
+ self.validate_glb_args()
+ imported_glb_objects = self.import_glb_asset()
+ self.place_glb_asset(imported_glb_objects)
+
+ min_corner, max_corner = self.get_scene_bbox()
+ center = (min_corner + max_corner) * 0.5
+ diagonal = (max_corner - min_corner).length
+ scene_size = max(
+ max_corner.x - min_corner.x, max_corner.y - min_corner.y
+ )
+
+ self.create_orthographic_camera(center, max_corner.z, scene_size)
+ self.ensure_lighting(diagonal, center, max_corner.z)
+ world_created = self.ensure_world()
+ self.add_fill_light(
+ diagonal,
+ center,
+ max_corner.z,
+ energy=self.fill_light_energy,
+ )
+ if world_created:
+ self.add_light_rig(
+ diagonal,
+ center,
+ max_corner.z,
+ area_energy=1500.0,
+ sun_energy=0.35,
+ prefix="Fill",
+ )
+ self.configure_color_management()
+ self.configure_cycles()
+ with tempfile.TemporaryDirectory(
+ prefix="render_birdseye_", dir=None
+ ) as temp_dir:
+ self.temp_dir = Path(temp_dir)
+ self.render(rgb_output_path)
+ self.temp_dir = None
+
+ logger.info("Rendered bird's-eye outputs to %s", self.output_dir)
+
+
+def build_arg_parser() -> argparse.ArgumentParser:
+ parser = argparse.ArgumentParser(
+ description="Render a top-down bird's-eye view of a USD scene."
+ )
+ input_group = parser.add_mutually_exclusive_group(required=True)
+ input_group.add_argument("--usd_path", type=Path)
+ input_group.add_argument(
+ "--input_dir",
+ type=Path,
+ help=(
+ "Directory with seed*/usd/export_scene/export_scene.usdc files "
+ "to render in batch."
+ ),
+ )
+ parser.add_argument(
+ "--output_dir",
+ type=Path,
+ help="Output directory for a single --usd_path render.",
+ )
+ parser.add_argument(
+ "--output_root",
+ type=Path,
+ help=(
+ "Batch output root. Defaults to /bev, with one "
+ "subdirectory per seed."
+ ),
+ )
+ parser.add_argument(
+ "--skip_existing",
+ action="store_true",
+ help="Skip batch items that already have render_rgb.png.",
+ )
+ parser.add_argument(
+ "--resolution",
+ type=int,
+ nargs=2,
+ metavar=("WIDTH", "HEIGHT"),
+ default=(1920, 1920),
+ )
+ parser.add_argument("--samples", type=int, default=512)
+ parser.add_argument("--exposure", type=float, default=-1.0)
+ parser.add_argument("--world_strength", type=float, default=1.0)
+ parser.add_argument("--fill_light_energy", type=float, default=1000.0)
+ parser.add_argument("--ortho_margin", type=float, default=1.05)
+ parser.add_argument("--use_cpu", action="store_true")
+ return parser
+
+
+def find_room_usd_paths(input_dir: Path) -> list[Path]:
+ """Find seed room USD files under an input directory."""
+ return sorted(input_dir.glob(DEFAULT_ROOM_USD_GLOB))
+
+
+def get_batch_output_dir(
+ usd_path: Path, input_dir: Path, output_root: Path
+) -> Path:
+ """Build the batch render output directory for a seed USD path."""
+ try:
+ seed_dir = usd_path.relative_to(input_dir).parts[0]
+ except ValueError:
+ seed_dir = usd_path.parents[2].name
+ return output_root / seed_dir
+
+
+def build_renderer(
+ args: argparse.Namespace, usd_path: Path, output_dir: Path
+) -> BirdseyeRenderUsd:
+ """Build a bird's-eye renderer with shared CLI options."""
+ return BirdseyeRenderUsd(
+ usd_path=usd_path,
+ glb_path=None,
+ glb_xyz=None,
+ glb_rotation_deg=None,
+ output_dir=output_dir,
+ render_passes=("rgb",),
+ depth_mode="normalized",
+ resolution=tuple(args.resolution),
+ samples=args.samples,
+ camera_xyz=(0.0, 0.0, 0.0),
+ camera_rotation_deg=(0.0, 0.0, 0.0),
+ flow_camera_xyz=None,
+ flow_camera_rotation_deg=None,
+ focal_length_mm=20.0,
+ exposure=args.exposure,
+ world_strength=args.world_strength,
+ fill_light_energy=args.fill_light_energy,
+ ortho_margin=args.ortho_margin,
+ use_cpu=args.use_cpu,
+ )
+
+
+def render_single(
+ args: argparse.Namespace, usd_path: Path, output_dir: Path
+) -> None:
+ build_renderer(args, usd_path, output_dir).run()
+
+
+def render_batch(args: argparse.Namespace) -> None:
+ input_dir = args.input_dir
+ output_root = args.output_root or input_dir / "bev"
+ usd_paths = find_room_usd_paths(input_dir)
+ if not usd_paths:
+ raise FileNotFoundError(
+ f"No USD files found under {input_dir} matching "
+ f"{DEFAULT_ROOM_USD_GLOB}."
+ )
+
+ logger.info(
+ "Rendering %d bird's-eye views under %s.", len(usd_paths), input_dir
+ )
+ for usd_path in usd_paths:
+ output_dir = get_batch_output_dir(usd_path, input_dir, output_root)
+ rgb_output_path = output_dir / "render_rgb.png"
+ if args.skip_existing and rgb_output_path.exists():
+ logger.info("Skipping existing render %s", rgb_output_path)
+ continue
+
+ logger.info("Rendering %s to %s", usd_path, output_dir)
+ render_single(args, usd_path, output_dir)
+
+
+def main() -> None:
+ logging.basicConfig(level=logging.INFO)
+ args = build_arg_parser().parse_args()
+ if args.input_dir is not None:
+ render_batch(args)
+ return
+
+ if args.output_dir is None:
+ raise ValueError("--output_dir is required when using --usd_path.")
+ render_single(args, args.usd_path, args.output_dir)
+
+
+if __name__ == "__main__":
+ main()
diff --git a/embodied_gen/scripts/room_gen/render_usd.py b/embodied_gen/scripts/room_gen/render_usd.py
new file mode 100644
index 0000000..7c4995f
--- /dev/null
+++ b/embodied_gen/scripts/room_gen/render_usd.py
@@ -0,0 +1,2601 @@
+# Project EmbodiedGen
+#
+# Copyright (c) 2025 Horizon Robotics. All Rights Reserved.
+#
+# 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.
+
+
+from __future__ import annotations
+
+import argparse
+import csv
+import json
+import logging
+import math
+import shutil
+import subprocess
+import tempfile
+from collections.abc import Callable
+from dataclasses import dataclass
+from pathlib import Path
+
+import bpy
+import cv2
+import numpy as np
+from mathutils import Euler, Matrix, Vector
+
+logger = logging.getLogger(__name__)
+
+DOOR_KEYWORDS = ("door",)
+"""Object-name keywords used to identify door meshes for removal."""
+
+CAMERA_TRAJECTORY_CSV_FIELDS = (
+ "frame",
+ "time_sec",
+ "x",
+ "y",
+ "z",
+ "roll_x_deg",
+ "pitch_y_deg",
+ "yaw_z_deg",
+)
+"""Required fields for a camera trajectory CSV."""
+
+
+@dataclass(frozen=True)
+class CameraTrajectoryFrame:
+ """One fully specified camera pose from a trajectory CSV."""
+
+ frame_index: int
+ time_sec: float
+ xyz: tuple[float, float, float]
+ rotation_deg: tuple[float, float, float]
+
+
+def build_arg_parser() -> argparse.ArgumentParser:
+ """Build the CLI parser for USD rendering."""
+ parser = argparse.ArgumentParser()
+ parser.add_argument("--usd_path", required=True, type=Path)
+ parser.add_argument("--glb_path", type=str, default="")
+ parser.add_argument(
+ "--glb_xyz",
+ type=float,
+ nargs=3,
+ metavar=("X", "Y", "Z"),
+ )
+ parser.add_argument(
+ "--glb_rotation_deg",
+ type=float,
+ nargs=3,
+ metavar=("RX", "RY", "RZ"),
+ )
+ parser.add_argument("--output_dir", required=True, type=Path)
+ parser.add_argument(
+ "--render_passes",
+ nargs="+",
+ choices=("rgb", "depth", "normal", "mesh", "instance_seg", "flow"),
+ default=("rgb",),
+ )
+ parser.add_argument(
+ "--depth_mode",
+ choices=("normalized", "metric"),
+ default="normalized",
+ help=(
+ "normalized: per-frame min-max (default); metric: fixed "
+ "0..--depth_max mapping, consistent across frames."
+ ),
+ )
+ parser.add_argument(
+ "--depth_max",
+ type=float,
+ default=10.0,
+ help="Max depth (m) mapped to white in metric depth mode.",
+ )
+ parser.add_argument(
+ "--resolution",
+ type=int,
+ nargs=2,
+ metavar=("WIDTH", "HEIGHT"),
+ default=(1920, 1080),
+ )
+ parser.add_argument("--samples", type=int, default=1024)
+ parser.add_argument(
+ "--camera_xyz",
+ type=float,
+ nargs=3,
+ metavar=("X", "Y", "Z"),
+ help=(
+ "Required for single-frame mode; ignored with --trajectory_json "
+ "or --trajectory_csv."
+ ),
+ )
+ parser.add_argument(
+ "--camera_rotation_deg",
+ type=float,
+ nargs=3,
+ metavar=("RX", "RY", "RZ"),
+ help=(
+ "Required for single-frame mode; ignored with --trajectory_json "
+ "or --trajectory_csv."
+ ),
+ )
+ parser.add_argument(
+ "--flow_camera_xyz",
+ type=float,
+ nargs=3,
+ metavar=("X", "Y", "Z"),
+ )
+ parser.add_argument(
+ "--flow_camera_rotation_deg",
+ type=float,
+ nargs=3,
+ metavar=("RX", "RY", "RZ"),
+ )
+ parser.add_argument("--focal_length_mm", type=float, default=20.0)
+ parser.add_argument("--exposure", type=float, default=2.2)
+ parser.add_argument("--world_strength", type=float, default=8.0)
+ parser.add_argument("--fill_light_energy", type=float, default=14000.0)
+ parser.add_argument(
+ "--remove_doors",
+ action="store_true",
+ help="Remove all door meshes from the USD before rendering.",
+ )
+
+ # Trajectory modes: roaming JSON follows (x, y, rot) waypoints with a GLB
+ # offset, while camera CSV uses a complete 6DoF camera pose per row.
+ trajectory_group = parser.add_mutually_exclusive_group()
+ trajectory_group.add_argument(
+ "--trajectory_json",
+ type=Path,
+ default=None,
+ help=(
+ "Trajectory JSON ({waypoints:[{x,y,rot,...}]}). When set, renders "
+ "one frame per waypoint instead of a single frame."
+ ),
+ )
+ trajectory_group.add_argument(
+ "--trajectory_csv",
+ type=Path,
+ default=None,
+ help=(
+ "Camera trajectory CSV with fields frame,time_sec,x,y,z,"
+ "roll_x_deg,pitch_y_deg,yaw_z_deg. Each row is rendered as one "
+ "frame using its full camera pose."
+ ),
+ )
+ parser.add_argument(
+ "--camera_z",
+ type=float,
+ default=1.36,
+ help="Camera height (m) for trajectory mode.",
+ )
+ parser.add_argument(
+ "--camera_pitch",
+ type=float,
+ default=80.0,
+ help="Camera pitch deg (rotation_deg[0]) for trajectory mode.",
+ )
+ parser.add_argument(
+ "--camera_roll",
+ type=float,
+ default=0.0,
+ help="Camera roll deg (rotation_deg[1]) for trajectory mode.",
+ )
+ parser.add_argument(
+ "--glb_offset_xyz",
+ type=float,
+ nargs=3,
+ default=(0.0012, 0.345, -1.36),
+ metavar=("DX", "DY", "DZ"),
+ help=(
+ "GLB position minus camera position at yaw=0 (world frame). The "
+ "horizontal part rotates with the camera yaw (rot); DZ is fixed."
+ ),
+ )
+ parser.add_argument(
+ "--glb_base_rotation_deg",
+ type=float,
+ nargs=3,
+ default=(-90.0, 0.0, 90.0),
+ metavar=("RX", "RY", "RZ"),
+ help=(
+ "GLB rotation deg at yaw=0; the Z component gets + rot per frame."
+ ),
+ )
+ parser.add_argument(
+ "--frame_stride",
+ type=int,
+ default=1,
+ help="Render every Nth waypoint/CSV row in trajectory mode.",
+ )
+ parser.add_argument(
+ "--frame_indices",
+ type=int,
+ nargs="+",
+ default=None,
+ metavar="INDEX",
+ help=(
+ "Render only these trajectory frame/waypoint indices. "
+ "When set, --frame_stride is ignored."
+ ),
+ )
+ parser.add_argument(
+ "--overwrite",
+ action="store_true",
+ help=(
+ "Re-render trajectory frames even if they already exist "
+ "(default: skip existing frames for per-frame resume)."
+ ),
+ )
+ parser.add_argument(
+ "--make_video",
+ action=argparse.BooleanOptionalAction,
+ default=True,
+ help="Stitch trajectory RGB frames into an mp4 (default: on).",
+ )
+ parser.add_argument(
+ "--video_fps",
+ type=float,
+ default=0.0,
+ help=(
+ "Video frame rate. 0 = auto from JSON time_step or CSV time_sec "
+ "and --frame_stride for real-time playback."
+ ),
+ )
+ return parser
+
+
+def _parse_args() -> argparse.Namespace:
+ return build_arg_parser().parse_args()
+
+
+class RenderUsd:
+ """USD renderer for RGB, depth, normal, mesh, segmentation, and flow."""
+
+ def __init__(
+ self,
+ *,
+ usd_path: Path,
+ glb_path: Path | str | None,
+ glb_xyz: tuple[float, float, float] | list[float] | None,
+ glb_rotation_deg: tuple[float, float, float] | list[float] | None,
+ output_dir: Path,
+ render_passes: tuple[str, ...] | list[str],
+ depth_mode: str,
+ depth_max: float,
+ resolution: tuple[int, int] | list[int],
+ samples: int,
+ camera_xyz: tuple[float, float, float] | list[float],
+ camera_rotation_deg: tuple[float, float, float] | list[float],
+ flow_camera_xyz: tuple[float, float, float] | list[float] | None,
+ flow_camera_rotation_deg: (
+ tuple[float, float, float] | list[float] | None
+ ),
+ focal_length_mm: float,
+ exposure: float,
+ world_strength: float,
+ fill_light_energy: float,
+ remove_doors: bool = False,
+ ) -> None:
+ """Initialize renderer configuration independent of CLI parsing."""
+ self.usd_path = usd_path
+ self.glb_path = self.normalize_optional_path(glb_path)
+ self.glb_xyz = tuple(glb_xyz) if glb_xyz is not None else None
+ self.glb_rotation_deg = (
+ list(glb_rotation_deg) if glb_rotation_deg is not None else None
+ )
+ if self.glb_rotation_deg is not None:
+ self.glb_rotation_deg[0] += 90
+ self.output_dir = output_dir
+ self.render_passes = tuple(render_passes)
+ self.depth_mode = depth_mode
+ self.depth_max = depth_max
+ self.resolution = tuple(resolution)
+ self.samples = samples
+ self.camera_xyz = tuple(camera_xyz)
+ self.camera_rotation_deg = tuple(camera_rotation_deg)
+ self.flow_camera_xyz = (
+ tuple(flow_camera_xyz) if flow_camera_xyz is not None else None
+ )
+ self.flow_camera_rotation_deg = (
+ tuple(flow_camera_rotation_deg)
+ if flow_camera_rotation_deg is not None
+ else None
+ )
+ self.focal_length_mm = focal_length_mm
+ self.exposure = exposure
+ self.world_strength = world_strength
+ self.fill_light_energy = fill_light_energy
+ self.remove_doors = remove_doors
+ # Trajectory frame layout: route each pass to frames_/
+ # frame_. under frame_base_dir instead of output_dir.
+ self.frame_layout = False
+ self.frame_base_dir: Path | None = None
+ self.frame_index = 0
+ self.temp_dir: Path | None = None
+
+ @classmethod
+ def from_args(cls, args: argparse.Namespace) -> RenderUsd:
+ """Build a renderer from parsed CLI arguments."""
+ return cls(
+ usd_path=args.usd_path,
+ glb_path=args.glb_path,
+ glb_xyz=args.glb_xyz,
+ glb_rotation_deg=args.glb_rotation_deg,
+ output_dir=args.output_dir,
+ render_passes=args.render_passes,
+ depth_mode=args.depth_mode,
+ depth_max=args.depth_max,
+ resolution=args.resolution,
+ samples=args.samples,
+ camera_xyz=args.camera_xyz,
+ camera_rotation_deg=args.camera_rotation_deg,
+ flow_camera_xyz=args.flow_camera_xyz,
+ flow_camera_rotation_deg=args.flow_camera_rotation_deg,
+ focal_length_mm=args.focal_length_mm,
+ exposure=args.exposure,
+ world_strength=args.world_strength,
+ fill_light_energy=args.fill_light_energy,
+ remove_doors=args.remove_doors,
+ )
+
+ @property
+ def scene(self) -> bpy.types.Scene:
+ return bpy.context.scene
+
+ def normalize_optional_path(
+ self, path_value: Path | str | None
+ ) -> Path | None:
+ """Normalize an optional CLI path, treating empty strings as missing."""
+ if path_value is None:
+ return None
+ if isinstance(path_value, Path):
+ return path_value
+
+ normalized = path_value.strip()
+ if not normalized:
+ return None
+ return Path(normalized)
+
+ def build_output_path(self, filename: str) -> Path:
+ """Build a normalized output path under the render directory.
+
+ In trajectory frame layout, each pass is routed to its own
+ ``frames_/frame_.`` directory under
+ ``frame_base_dir``; otherwise the file lives directly in
+ ``output_dir``.
+ """
+ if self.frame_layout and self.frame_base_dir is not None:
+ name = Path(filename)
+ subdir = self.frame_base_dir / f"frames_{name.stem}"
+ return subdir / f"frame_{self.frame_index:04d}{name.suffix}"
+ return self.output_dir / filename
+
+ def build_temp_path(self, filename: str) -> Path:
+ """Build a temporary path outside the final output directory."""
+ if self.temp_dir is None:
+ raise RuntimeError(
+ "Temporary render directory is not initialized."
+ )
+ return self.temp_dir / filename
+
+ def get_rgb_output_path(self) -> Path:
+ return self.build_output_path("render_rgb.png")
+
+ def get_depth_vis_output_path(self, output_path: Path) -> Path:
+ del output_path
+ return self.build_output_path("render_depth.png")
+
+ def get_normal_output_path(self, output_path: Path) -> Path:
+ del output_path
+ return self.build_output_path("render_normal.png")
+
+ def get_mesh_output_path(self, output_path: Path) -> Path:
+ del output_path
+ return self.build_output_path("render_mesh.png")
+
+ def get_instance_seg_vis_output_path(self, output_path: Path) -> Path:
+ del output_path
+ return self.build_output_path("render_instance_seg_vis.png")
+
+ def get_instance_seg_temp_path(self, output_path: Path) -> Path:
+ del output_path
+ return self.build_temp_path("render_instance_seg_raw_0001.exr")
+
+ def get_flow_output_path(self, output_path: Path) -> Path:
+ del output_path
+ return self.build_output_path("render_flow.npy")
+
+ def get_flow_valid_output_path(self, output_path: Path) -> Path:
+ del output_path
+ return self.build_output_path("render_flow_valid.npy")
+
+ def get_flow_vis_output_path(self, output_path: Path) -> Path:
+ del output_path
+ return self.build_output_path("render_flow_vis.png")
+
+ def get_flow_depth_temp_path(self, output_path: Path) -> Path:
+ del output_path
+ return self.build_temp_path("render_flow_depth_raw_0001.exr")
+
+ def get_depth_gray_temp_path(self, output_path: Path) -> Path:
+ del output_path
+ return self.build_temp_path("render_depth_gray_0001.png")
+
+ def get_composite_output_path(
+ self, render_passes: list[str] | tuple[str, ...]
+ ) -> Path:
+ pass_names = "_".join(render_passes)
+ return self.build_output_path(f"render_composite_{pass_names}.png")
+
+ def build_occurrence_output_path(
+ self, output_path: Path, occurrence_index: int
+ ) -> Path:
+ """Build an occurrence-specific path for repeated preview outputs."""
+ if occurrence_index < 1:
+ raise ValueError("occurrence_index must be greater than 0.")
+ if occurrence_index == 1:
+ return output_path
+
+ return output_path.with_name(
+ f"{output_path.stem}_{occurrence_index}{output_path.suffix}"
+ )
+
+ def iter_render_pass_occurrences(self) -> list[tuple[str, int]]:
+ """Return requested render passes with 1-based occurrence indices."""
+ occurrence_counts: dict[str, int] = {}
+ render_pass_occurrences: list[tuple[str, int]] = []
+ for render_pass_name in self.render_passes:
+ occurrence_index = occurrence_counts.get(render_pass_name, 0) + 1
+ occurrence_counts[render_pass_name] = occurrence_index
+ render_pass_occurrences.append(
+ (render_pass_name, occurrence_index)
+ )
+ return render_pass_occurrences
+
+ def get_temp_output_slot_prefix(self, temp_output_path: Path) -> str:
+ """Return the compositor slot prefix without the frame suffix."""
+ stem_parts = temp_output_path.stem.rsplit("_", maxsplit=1)
+ if len(stem_parts) != 2 or not stem_parts[1].isdigit():
+ raise ValueError(
+ f"Unexpected temporary output filename: {temp_output_path.name}"
+ )
+ return f"{stem_parts[0]}_"
+
+ def get_mesh_objects(self) -> list[bpy.types.Object]:
+ return [obj for obj in self.scene.objects if obj.type == "MESH"]
+
+ def clear_scene(self) -> None:
+ bpy.ops.wm.read_factory_settings(use_empty=True)
+
+ def import_usd(self) -> None:
+ if not self.usd_path.exists():
+ raise FileNotFoundError(f"USD file not found: {self.usd_path}")
+ bpy.ops.wm.usd_import(filepath=str(self.usd_path))
+
+ def remove_door_objects(self) -> int:
+ """Delete any object whose name contains a door keyword."""
+ removed = 0
+ for obj in list(self.scene.objects):
+ if any(kw in obj.name.lower() for kw in DOOR_KEYWORDS):
+ bpy.data.objects.remove(obj, do_unlink=True)
+ removed += 1
+ logger.info("Removed %d door objects.", removed)
+ return removed
+
+ def validate_glb_args(self, *, allow_path_only: bool = False) -> None:
+ """Normalize optional GLB arguments and ensure all-or-none usage."""
+ has_glb_path = self.glb_path is not None
+ has_glb_xyz = self.glb_xyz is not None
+ has_glb_rotation = self.glb_rotation_deg is not None
+ if not has_glb_path:
+ if has_glb_xyz or has_glb_rotation:
+ raise ValueError(
+ "--glb_xyz and --glb_rotation_deg require --glb_path."
+ )
+ return
+ if allow_path_only and not has_glb_xyz and not has_glb_rotation:
+ self.validate_glb_path()
+ return
+ if not has_glb_xyz or not has_glb_rotation:
+ raise ValueError(
+ "--glb_path, --glb_xyz, and --glb_rotation_deg must be "
+ "provided together, unless trajectory rendering follows the "
+ "camera with --glb_offset_xyz/--glb_base_rotation_deg."
+ )
+
+ self.validate_glb_path()
+
+ def validate_glb_path(self) -> None:
+ """Ensure the optional GLB path points to an existing .glb file."""
+ if self.glb_path is None:
+ return
+ if not self.glb_path.exists():
+ raise FileNotFoundError(f"GLB file not found: {self.glb_path}")
+ if self.glb_path.suffix.lower() != ".glb":
+ raise ValueError(
+ f"Expected a .glb asset, but got: {self.glb_path}"
+ )
+
+ def enable_gltf_importer(self) -> None:
+ """Ensure Blender's glTF importer add-on is available."""
+ addon_name = "io_scene_gltf2"
+ if addon_name in bpy.context.preferences.addons:
+ return
+
+ try:
+ bpy.ops.preferences.addon_enable(module=addon_name)
+ except Exception as exc:
+ raise RuntimeError(
+ "Failed to enable Blender glTF importer add-on."
+ ) from exc
+
+ def import_glb_asset(self) -> list[bpy.types.Object]:
+ """Import the optional GLB asset and return created objects."""
+ if self.glb_path is None:
+ return []
+
+ self.enable_gltf_importer()
+ existing_object_ids = {obj.as_pointer() for obj in bpy.data.objects}
+ result = bpy.ops.import_scene.gltf(filepath=str(self.glb_path))
+ if "FINISHED" not in result:
+ raise RuntimeError(f"Failed to import GLB asset: {self.glb_path}")
+
+ imported_objects = [
+ obj
+ for obj in bpy.data.objects
+ if obj.as_pointer() not in existing_object_ids
+ ]
+ if not imported_objects:
+ raise ValueError(
+ f"No objects were imported from GLB asset: {self.glb_path}"
+ )
+ return imported_objects
+
+ def get_imported_root_objects(
+ self, imported_objects: list[bpy.types.Object]
+ ) -> list[bpy.types.Object]:
+ """Return top-level imported objects so transforms apply as one asset."""
+ imported_ids = {obj.as_pointer() for obj in imported_objects}
+ root_objects = [
+ obj
+ for obj in imported_objects
+ if obj.parent is None
+ or obj.parent.as_pointer() not in imported_ids
+ ]
+ return root_objects or imported_objects
+
+ def place_glb_asset(
+ self, imported_objects: list[bpy.types.Object]
+ ) -> None:
+ """Place the imported GLB asset using the requested world transform."""
+ if not imported_objects:
+ return
+ if self.glb_xyz is None or self.glb_rotation_deg is None:
+ raise ValueError("GLB transform arguments are not initialized.")
+
+ asset_transform = self.build_camera_matrix_world(
+ self.glb_xyz,
+ self.glb_rotation_deg,
+ )
+ for obj in self.get_imported_root_objects(imported_objects):
+ obj.matrix_world = asset_transform @ obj.matrix_world.copy()
+ bpy.context.view_layer.update()
+
+ def get_scene_bbox(self) -> tuple[Vector, Vector]:
+ """Compute the world-space bounding box across all mesh objects."""
+ mesh_objects = self.get_mesh_objects()
+ if not mesh_objects:
+ raise ValueError("No mesh objects found after USD import.")
+
+ points: list[Vector] = []
+ for obj in mesh_objects:
+ points.extend(
+ obj.matrix_world @ Vector(corner) for corner in obj.bound_box
+ )
+
+ min_corner = Vector(
+ (
+ min(p.x for p in points),
+ min(p.y for p in points),
+ min(p.z for p in points),
+ )
+ )
+ max_corner = Vector(
+ (
+ max(p.x for p in points),
+ max(p.y for p in points),
+ max(p.z for p in points),
+ )
+ )
+ return min_corner, max_corner
+
+ def create_camera(self) -> bpy.types.Object:
+ """Create and configure the primary render camera."""
+ if self.camera_xyz is None:
+ raise ValueError("--camera_xyz is required.")
+
+ location = Vector(tuple(self.camera_xyz))
+ rotation_rad = self.get_rotation_radians(self.camera_rotation_deg)
+ bpy.ops.object.camera_add(location=location, rotation=rotation_rad)
+ camera = bpy.context.object
+ camera.rotation_mode = "XYZ"
+ camera.data.lens = self.focal_length_mm
+ camera.data.clip_start = 0.01
+ camera.data.clip_end = 1000.0
+ self.scene.camera = camera
+ return camera
+
+ def add_light_rig(
+ self,
+ diagonal: float,
+ center: Vector,
+ top_z: float,
+ *,
+ area_energy: float,
+ sun_energy: float,
+ prefix: str,
+ ) -> None:
+ bpy.ops.object.light_add(
+ type="AREA",
+ location=(center.x, center.y, top_z + 0.5 * diagonal),
+ )
+ area = bpy.context.object
+ area.name = f"{prefix}Area"
+ area.data.energy = area_energy
+ area.data.shape = "DISK"
+ area.data.size = max(diagonal, 2.0)
+
+ bpy.ops.object.light_add(
+ type="SUN",
+ location=(
+ center.x + diagonal,
+ center.y - diagonal,
+ top_z + diagonal,
+ ),
+ )
+ sun = bpy.context.object
+ sun.name = f"{prefix}Sun"
+ sun.data.energy = sun_energy
+
+ def add_fill_light(
+ self,
+ diagonal: float,
+ center: Vector,
+ top_z: float,
+ energy: float,
+ ) -> None:
+ if energy <= 0.0:
+ return
+
+ bpy.ops.object.light_add(
+ type="AREA",
+ location=(center.x, center.y, top_z + 0.35 * diagonal),
+ rotation=(0.0, 0.0, 0.0),
+ )
+ area = bpy.context.object
+ area.name = "GlobalFillArea"
+ area.data.energy = energy
+ area.data.shape = "DISK"
+ area.data.size = max(diagonal * 0.9, 3.0)
+
+ def ensure_lighting(
+ self, diagonal: float, center: Vector, top_z: float
+ ) -> None:
+ if any(obj.type == "LIGHT" for obj in self.scene.objects):
+ return
+
+ self.add_light_rig(
+ diagonal,
+ center,
+ top_z,
+ area_energy=5000.0,
+ sun_energy=1.5,
+ prefix="Fallback",
+ )
+
+ def set_world_strength(self, strength: float) -> None:
+ world = self.scene.world
+ if world is None:
+ return
+
+ if not world.use_nodes:
+ world.use_nodes = True
+
+ tree = world.node_tree
+ background_nodes = [
+ node for node in tree.nodes if node.type == "BACKGROUND"
+ ]
+ if not background_nodes:
+ background = tree.nodes.new(type="ShaderNodeBackground")
+ output = next(
+ (node for node in tree.nodes if node.type == "OUTPUT_WORLD"),
+ None,
+ )
+ if output is None:
+ output = tree.nodes.new(type="ShaderNodeOutputWorld")
+ tree.links.new(
+ background.outputs["Background"], output.inputs["Surface"]
+ )
+ background_nodes = [background]
+
+ for background in background_nodes:
+ background.inputs["Strength"].default_value = strength
+
+ def ensure_world(self) -> bool:
+ """Ensure the scene has a world shader and return whether it was created."""
+ if self.scene.world is not None:
+ self.set_world_strength(self.world_strength)
+ return False
+
+ world = bpy.data.worlds.new(name="RenderWorld")
+ world.use_nodes = True
+ tree = world.node_tree
+ tree.nodes.clear()
+
+ output = tree.nodes.new(type="ShaderNodeOutputWorld")
+ background = tree.nodes.new(type="ShaderNodeBackground")
+ sky = tree.nodes.new(type="ShaderNodeTexSky")
+
+ background.inputs["Strength"].default_value = self.world_strength
+
+ tree.links.new(sky.outputs["Color"], background.inputs["Color"])
+ tree.links.new(
+ background.outputs["Background"], output.inputs["Surface"]
+ )
+
+ self.scene.world = world
+ return True
+
+ def configure_cycles(self) -> None:
+ self.scene.render.engine = "CYCLES"
+ self.scene.cycles.device = "GPU"
+ self.scene.cycles.samples = self.samples
+ self.scene.render.resolution_x = self.resolution[0]
+ self.scene.render.resolution_y = self.resolution[1]
+ self.scene.render.image_settings.file_format = "PNG"
+ self.scene.render.film_transparent = False
+ self.scene.render.use_persistent_data = False
+
+ # Adaptive sampling + denoising keep quality high at low sample counts.
+ self.scene.cycles.use_adaptive_sampling = True
+ self.scene.cycles.use_denoising = True
+
+ self.enable_gpu_devices()
+
+ def enable_gpu_devices(self) -> None:
+ """Enable Cycles GPU devices, preferring OptiX then CUDA."""
+ prefs = bpy.context.preferences.addons["cycles"].preferences
+ for backend in ("OPTIX", "CUDA"):
+ try:
+ prefs.compute_device_type = backend
+ except TypeError:
+ continue
+ prefs.get_devices()
+ gpu_devices = [d for d in prefs.devices if d.type == backend]
+ if not gpu_devices:
+ continue
+
+ for device in prefs.devices:
+ device.use = device.type == backend
+ try:
+ self.scene.cycles.denoiser = (
+ "OPTIX" if backend == "OPTIX" else "OPENIMAGEDENOISE"
+ )
+ except (TypeError, AttributeError):
+ pass
+ logger.info(
+ "Cycles GPU backend: %s (%d device(s)).",
+ backend,
+ len(gpu_devices),
+ )
+ return
+
+ raise RuntimeError("No OptiX/CUDA GPU device found in Blender Cycles.")
+
+ def configure_color_management(self) -> None:
+ self.scene.view_settings.exposure = self.exposure
+
+ def snapshot_render_state(
+ self,
+ view_layer: bpy.types.ViewLayer,
+ *,
+ include_filepath: bool = False,
+ include_material_override: bool = False,
+ include_use_pass_z: bool = False,
+ include_use_pass_object_index: bool = False,
+ ) -> dict[str, object]:
+ """Capture the render state that temporary passes need to restore."""
+ state: dict[str, object] = {
+ "film_transparent": self.scene.render.film_transparent,
+ "view_transform": self.scene.view_settings.view_transform,
+ "look": self.scene.view_settings.look,
+ "exposure": self.scene.view_settings.exposure,
+ "gamma": self.scene.view_settings.gamma,
+ "file_format": self.scene.render.image_settings.file_format,
+ "color_mode": self.scene.render.image_settings.color_mode,
+ "color_depth": self.scene.render.image_settings.color_depth,
+ "use_nodes": self.scene.use_nodes,
+ "samples": self.scene.cycles.samples,
+ "use_denoising": self.scene.cycles.use_denoising,
+ }
+ if include_filepath:
+ state["filepath"] = self.scene.render.filepath
+ if include_material_override:
+ state["material_override"] = view_layer.material_override
+ if include_use_pass_z:
+ state["use_pass_z"] = view_layer.use_pass_z
+ if include_use_pass_object_index:
+ state["use_pass_object_index"] = view_layer.use_pass_object_index
+ return state
+
+ def restore_render_state(
+ self, state: dict[str, object], view_layer: bpy.types.ViewLayer
+ ) -> None:
+ """Restore a render state captured by ``snapshot_render_state``."""
+ self.scene.render.film_transparent = state["film_transparent"]
+ self.scene.view_settings.view_transform = state["view_transform"]
+ self.scene.view_settings.look = state["look"]
+ self.scene.view_settings.exposure = state["exposure"]
+ self.scene.view_settings.gamma = state["gamma"]
+ self.scene.render.image_settings.file_format = state["file_format"]
+ self.scene.render.image_settings.color_mode = state["color_mode"]
+ self.scene.render.image_settings.color_depth = state["color_depth"]
+ self.scene.use_nodes = state["use_nodes"]
+ self.scene.cycles.samples = state["samples"]
+ self.scene.cycles.use_denoising = state["use_denoising"]
+ if "filepath" in state:
+ self.scene.render.filepath = state["filepath"]
+ if "material_override" in state:
+ view_layer.material_override = state["material_override"]
+ if "use_pass_z" in state:
+ view_layer.use_pass_z = state["use_pass_z"]
+ if "use_pass_object_index" in state:
+ view_layer.use_pass_object_index = state["use_pass_object_index"]
+
+ def apply_raw_preview_settings(
+ self,
+ *,
+ use_nodes: bool,
+ samples: int,
+ color_mode: str,
+ color_depth: str,
+ ) -> None:
+ """Apply the shared render settings for auxiliary preview passes."""
+ self.scene.render.film_transparent = True
+ self.scene.view_settings.view_transform = "Raw"
+ self.scene.view_settings.look = "None"
+ self.scene.view_settings.exposure = 0.0
+ self.scene.view_settings.gamma = 1.0
+ self.scene.use_nodes = use_nodes
+ self.scene.cycles.samples = samples
+ # Auxiliary passes (normal/mesh/seg/flow depth) must not be denoised:
+ # denoising would blur normals and corrupt integer index passes.
+ self.scene.cycles.use_denoising = False
+ self.scene.render.image_settings.file_format = "PNG"
+ self.scene.render.image_settings.color_mode = color_mode
+ self.scene.render.image_settings.color_depth = color_depth
+
+ def clear_compositor_tree(self) -> bpy.types.NodeTree:
+ """Reset the compositor tree so each pass starts from a clean slate."""
+ self.scene.use_nodes = True
+ tree = self.scene.node_tree
+ tree.nodes.clear()
+ return tree
+
+ def remove_render_nodes(self, created_nodes: list[bpy.types.Node]) -> None:
+ """Remove compositor nodes created for a temporary render pass."""
+ if not created_nodes:
+ return
+
+ node_tree = self.scene.node_tree
+ if node_tree is None:
+ return
+
+ for node in created_nodes:
+ if node.name in node_tree.nodes:
+ node_tree.nodes.remove(node)
+
+ def render_material_override_pass(
+ self,
+ preview_output_path: Path,
+ material_factory: Callable[[], bpy.types.Material],
+ *,
+ color_mode: str,
+ ) -> None:
+ """Render a pass with a temporary material override."""
+ preview_output_path.parent.mkdir(parents=True, exist_ok=True)
+ view_layer = self.scene.view_layers["ViewLayer"]
+ state = self.snapshot_render_state(
+ view_layer,
+ include_filepath=True,
+ include_material_override=True,
+ )
+
+ material = material_factory()
+ try:
+ self.apply_raw_preview_settings(
+ use_nodes=False,
+ samples=min(int(state["samples"]), 64),
+ color_mode=color_mode,
+ color_depth="8",
+ )
+ self.scene.render.filepath = str(preview_output_path)
+ view_layer.material_override = material
+ bpy.ops.render.render(write_still=True)
+ finally:
+ self.restore_render_state(state, view_layer)
+ bpy.data.materials.remove(material, do_unlink=True)
+
+ def render_temp_output_pass(
+ self,
+ output_path: Path,
+ temp_output_path: Path,
+ *,
+ add_output_node: Callable[
+ [Path], tuple[bpy.types.NodeTree, list[bpy.types.Node]]
+ ],
+ load_temp_output: Callable[[Path], np.ndarray],
+ finalize_output: Callable[[np.ndarray], None],
+ color_mode: str,
+ color_depth: str,
+ enable_depth_pass: bool = False,
+ enable_object_index_pass: bool = False,
+ ) -> None:
+ """Render a temporary compositor output and finalize it."""
+ output_path.parent.mkdir(parents=True, exist_ok=True)
+ view_layer = self.scene.view_layers["ViewLayer"]
+ state = self.snapshot_render_state(
+ view_layer,
+ include_use_pass_z=enable_depth_pass,
+ include_use_pass_object_index=enable_object_index_pass,
+ )
+ created_nodes: list[bpy.types.Node] = []
+
+ try:
+ if temp_output_path.exists():
+ temp_output_path.unlink()
+
+ self.apply_raw_preview_settings(
+ use_nodes=True,
+ samples=1,
+ color_mode=color_mode,
+ color_depth=color_depth,
+ )
+ if enable_depth_pass:
+ view_layer.use_pass_z = True
+ if enable_object_index_pass:
+ view_layer.use_pass_object_index = True
+
+ self.clear_compositor_tree()
+ _, created_nodes = add_output_node(output_path)
+ bpy.ops.render.render(write_still=False)
+ finalize_output(load_temp_output(temp_output_path))
+ finally:
+ self.remove_render_nodes(created_nodes)
+ if temp_output_path.exists():
+ temp_output_path.unlink()
+ self.restore_render_state(state, view_layer)
+
+ def get_rotation_radians(
+ self, rotation_deg: tuple[float, float, float] | list[float]
+ ) -> tuple[float, float, float]:
+ return tuple(math.radians(angle_deg) for angle_deg in rotation_deg)
+
+ def validate_flow_args(self) -> None:
+ """Normalize optional flow-camera arguments and fill defaults."""
+ has_flow_xyz = self.flow_camera_xyz is not None
+ has_flow_rotation = self.flow_camera_rotation_deg is not None
+ if has_flow_xyz != has_flow_rotation:
+ raise ValueError(
+ "--flow_camera_xyz and --flow_camera_rotation_deg must be "
+ "provided together."
+ )
+ if not has_flow_xyz:
+ xyz = list(self.camera_xyz)
+ xyz[0] += 0.5
+ self.flow_camera_xyz = tuple(xyz)
+ self.flow_camera_rotation_deg = tuple(self.camera_rotation_deg)
+
+ def build_depth_preview_node(
+ self,
+ tree: bpy.types.NodeTree,
+ render_layers: bpy.types.CompositorNodeRLayers,
+ camera: bpy.types.Camera,
+ depth_mode: str,
+ ) -> bpy.types.Node:
+ """Build the compositor node that converts raw depth to a previewable map."""
+ del camera # Mapping uses self.depth_max, not the camera clip range.
+ if depth_mode == "normalized":
+ normalize = tree.nodes.new(type="CompositorNodeNormalize")
+ tree.links.new(render_layers.outputs["Depth"], normalize.inputs[0])
+ return normalize
+
+ if depth_mode != "metric":
+ raise ValueError(f"Unsupported depth mode: {depth_mode}")
+
+ depth_map = tree.nodes.new(type="CompositorNodeMapRange")
+ # Fixed 0..depth_max mapping keeps depth consistent across frames.
+ depth_map.inputs["From Min"].default_value = 0.0
+ depth_map.inputs["From Max"].default_value = self.depth_max
+ depth_map.inputs["To Min"].default_value = 0.0
+ depth_map.inputs["To Max"].default_value = 1.0
+ depth_map.use_clamp = True
+ tree.links.new(render_layers.outputs["Depth"], depth_map.inputs[0])
+ return depth_map
+
+ def build_depth_vis_output(
+ self,
+ tree: bpy.types.NodeTree,
+ depth_preview_node: bpy.types.Node,
+ output_path: Path,
+ ) -> Path:
+ temp_output_path = self.get_depth_gray_temp_path(output_path)
+ output_node = tree.nodes.new(type="CompositorNodeOutputFile")
+ output_node.base_path = str(temp_output_path.parent)
+ output_node.file_slots[0].path = self.get_temp_output_slot_prefix(
+ temp_output_path
+ )
+ output_node.format.file_format = "PNG"
+ output_node.format.color_mode = "BW"
+ output_node.format.color_depth = "8"
+
+ tree.links.new(depth_preview_node.outputs[0], output_node.inputs[0])
+ return temp_output_path
+
+ def configure_auxiliary_outputs(
+ self,
+ output_path: Path,
+ render_passes: tuple[str, ...] | list[str],
+ depth_mode: str,
+ ) -> list[tuple[Path, Path]]:
+ """Configure compositor outputs needed during the base render."""
+ view_layer = self.scene.view_layers["ViewLayer"]
+ if "depth" in render_passes:
+ view_layer.use_pass_z = True
+
+ if "depth" not in render_passes:
+ return []
+
+ tree = self.clear_compositor_tree()
+
+ render_layers = tree.nodes.new(type="CompositorNodeRLayers")
+ temp_outputs: list[tuple[Path, Path]] = []
+
+ depth_preview_node = self.build_depth_preview_node(
+ tree,
+ render_layers,
+ self.scene.camera.data,
+ depth_mode,
+ )
+ temp_path = self.build_depth_vis_output(
+ tree=tree,
+ depth_preview_node=depth_preview_node,
+ output_path=output_path,
+ )
+ temp_outputs.append(
+ (temp_path, self.get_depth_vis_output_path(output_path))
+ )
+
+ return temp_outputs
+
+ def finalize_depth_output(
+ self, temp_path: Path, output_path: Path
+ ) -> None:
+ """Convert the grayscale depth temp image into the final colored preview."""
+ output_path.parent.mkdir(parents=True, exist_ok=True)
+ if output_path.exists():
+ output_path.unlink()
+ if not temp_path.exists():
+ raise FileNotFoundError(f"Depth file not generated: {temp_path}")
+ try:
+ depth = cv2.imread(str(temp_path), cv2.IMREAD_GRAYSCALE)
+ if depth is None:
+ raise FileNotFoundError(
+ f"Failed to read depth image: {temp_path}"
+ )
+
+ depth_uint8 = np.ascontiguousarray(depth)
+ depth_colormap = cv2.applyColorMap(depth_uint8, cv2.COLORMAP_JET)
+
+ if not cv2.imwrite(str(output_path), depth_colormap):
+ raise RuntimeError(
+ f"Failed to write depth visualization: {output_path}"
+ )
+ finally:
+ if temp_path.exists():
+ temp_path.unlink()
+
+ def create_clean_material(self, material_name: str) -> bpy.types.Material:
+ """Create a material with a cleared node tree."""
+ existing = bpy.data.materials.get(material_name)
+ if existing is not None:
+ bpy.data.materials.remove(existing, do_unlink=True)
+
+ material = bpy.data.materials.new(name=material_name)
+ material.use_nodes = True
+ material.shadow_method = "NONE"
+ tree = material.node_tree
+ tree.nodes.clear()
+ return material
+
+ def create_view_normal_material(self) -> bpy.types.Material:
+ material = self.create_clean_material("EmbodiedGenViewNormal")
+ tree = material.node_tree
+
+ geometry = tree.nodes.new(type="ShaderNodeNewGeometry")
+ invert = tree.nodes.new(type="ShaderNodeVectorMath")
+ invert.operation = "MULTIPLY"
+ invert.inputs[1].default_value = (-1.0, -1.0, -1.0)
+
+ face_mix = tree.nodes.new(type="ShaderNodeMix")
+ face_mix.data_type = "VECTOR"
+ face_mix.clamp_factor = True
+ face_mix.factor_mode = "UNIFORM"
+
+ view_transform = tree.nodes.new(type="ShaderNodeVectorTransform")
+ view_transform.vector_type = "NORMAL"
+ view_transform.convert_from = "WORLD"
+ view_transform.convert_to = "CAMERA"
+
+ flip_x = tree.nodes.new(type="ShaderNodeVectorMath")
+ flip_x.operation = "MULTIPLY"
+ flip_x.inputs[1].default_value = (-1.0, 1.0, -1.0)
+
+ scale_bias = tree.nodes.new(type="ShaderNodeVectorMath")
+ scale_bias.operation = "MULTIPLY_ADD"
+ scale_bias.inputs[1].default_value = (0.5, 0.5, 0.5)
+ scale_bias.inputs[2].default_value = (0.5, 0.5, 0.5)
+
+ emission = tree.nodes.new(type="ShaderNodeEmission")
+ output = tree.nodes.new(type="ShaderNodeOutputMaterial")
+
+ tree.links.new(geometry.outputs["True Normal"], invert.inputs[0])
+ tree.links.new(
+ geometry.outputs["Backfacing"], face_mix.inputs["Factor"]
+ )
+ tree.links.new(geometry.outputs["True Normal"], face_mix.inputs["A"])
+ tree.links.new(invert.outputs["Vector"], face_mix.inputs["B"])
+ tree.links.new(
+ face_mix.outputs["Result"], view_transform.inputs["Vector"]
+ )
+ tree.links.new(view_transform.outputs["Vector"], flip_x.inputs[0])
+ tree.links.new(flip_x.outputs["Vector"], scale_bias.inputs[0])
+ tree.links.new(scale_bias.outputs["Vector"], emission.inputs["Color"])
+ tree.links.new(emission.outputs["Emission"], output.inputs["Surface"])
+
+ return material
+
+ def create_mesh_preview_material(self) -> bpy.types.Material:
+ material = self.create_clean_material("EmbodiedGenMeshPreview")
+ tree = material.node_tree
+
+ layer_weight = tree.nodes.new(type="ShaderNodeLayerWeight")
+ layer_weight.inputs["Blend"].default_value = 0.35
+
+ base_ramp = tree.nodes.new(type="ShaderNodeValToRGB")
+ base_ramp.color_ramp.elements[0].position = 0.1
+ base_ramp.color_ramp.elements[0].color = (0.78, 0.81, 0.87, 1.0)
+ base_ramp.color_ramp.elements[1].position = 0.9
+ base_ramp.color_ramp.elements[1].color = (0.42, 0.48, 0.58, 1.0)
+
+ emission = tree.nodes.new(type="ShaderNodeEmission")
+ emission.inputs["Strength"].default_value = 0.82
+ output = tree.nodes.new(type="ShaderNodeOutputMaterial")
+
+ tree.links.new(layer_weight.outputs["Facing"], base_ramp.inputs["Fac"])
+ tree.links.new(base_ramp.outputs["Color"], emission.inputs["Color"])
+ tree.links.new(emission.outputs["Emission"], output.inputs["Surface"])
+
+ return material
+
+ def assign_instance_ids(self) -> dict[str, int]:
+ """Assign stable per-object pass indices for instance segmentation."""
+ mesh_objects = sorted(
+ self.get_mesh_objects(), key=lambda obj: obj.name
+ )
+ if not mesh_objects:
+ raise ValueError(
+ "No mesh objects found for instance segmentation."
+ )
+
+ instance_id_map: dict[str, int] = {}
+ for instance_id, obj in enumerate(mesh_objects, start=1):
+ obj.pass_index = instance_id
+ instance_id_map[obj.name] = instance_id
+ return instance_id_map
+
+ def snapshot_object_pass_indices(
+ self,
+ ) -> list[tuple[bpy.types.Object, int]]:
+ """Capture original object pass indices before a temporary override."""
+ return [(obj, obj.pass_index) for obj in self.get_mesh_objects()]
+
+ def restore_object_pass_indices(
+ self, original_pass_indices: list[tuple[bpy.types.Object, int]]
+ ) -> None:
+ """Restore object pass indices captured earlier."""
+ for obj, pass_index in original_pass_indices:
+ obj.pass_index = pass_index
+
+ def add_instance_seg_output_node(
+ self,
+ output_path: Path,
+ ) -> tuple[bpy.types.NodeTree, list[bpy.types.Node]]:
+ return self.add_exr_output_node(
+ output_path=output_path,
+ temp_output_path=self.get_instance_seg_temp_path(output_path),
+ render_output_name="IndexOB",
+ )
+
+ def add_flow_depth_output_node(
+ self,
+ output_path: Path,
+ ) -> tuple[bpy.types.NodeTree, list[bpy.types.Node]]:
+ return self.add_exr_output_node(
+ output_path=output_path,
+ temp_output_path=self.get_flow_depth_temp_path(output_path),
+ render_output_name="Depth",
+ )
+
+ def add_exr_output_node(
+ self,
+ output_path: Path,
+ temp_output_path: Path,
+ render_output_name: str,
+ ) -> tuple[bpy.types.NodeTree, list[bpy.types.Node]]:
+ """Attach a file-output EXR node for a specific render-layer socket."""
+ tree = self.scene.node_tree
+ render_layers = tree.nodes.new(type="CompositorNodeRLayers")
+ output_node = tree.nodes.new(type="CompositorNodeOutputFile")
+ output_node.base_path = str(temp_output_path.parent)
+ output_node.file_slots[0].path = self.get_temp_output_slot_prefix(
+ temp_output_path
+ )
+ output_node.format.file_format = "OPEN_EXR"
+ output_node.format.color_mode = "RGB"
+ output_node.format.color_depth = "32"
+ output_node.format.exr_codec = "NONE"
+
+ tree.links.new(
+ render_layers.outputs[render_output_name], output_node.inputs[0]
+ )
+ return tree, [render_layers, output_node]
+
+ def load_temp_exr_first_channel(
+ self,
+ temp_path: Path,
+ error_message: str,
+ ) -> np.ndarray:
+ """Load the first channel from a temporary EXR and flip to image space."""
+ if not temp_path.exists():
+ raise FileNotFoundError(error_message.format(path=temp_path))
+
+ temp_image = bpy.data.images.load(str(temp_path), check_existing=False)
+ try:
+ width, height = temp_image.size
+ channels = temp_image.channels
+ pixels = np.array(temp_image.pixels[:], dtype=np.float32)
+ if pixels.size != width * height * channels:
+ raise RuntimeError(
+ f"Unexpected EXR image layout for {temp_path}."
+ )
+
+ image = pixels.reshape(height, width, channels)[..., 0]
+ return np.flipud(image)
+ finally:
+ bpy.data.images.remove(temp_image)
+
+ def load_instance_seg_temp_output(self, temp_path: Path) -> np.ndarray:
+ instance_seg = self.load_temp_exr_first_channel(
+ temp_path,
+ "Instance segmentation file not generated: {path}",
+ )
+ return np.ascontiguousarray(np.rint(instance_seg).astype(np.uint16))
+
+ def load_flow_depth_temp_output(self, temp_path: Path) -> np.ndarray:
+ depth = self.load_temp_exr_first_channel(
+ temp_path,
+ "Flow depth file not generated: {path}",
+ )
+ depth = np.ascontiguousarray(depth.astype(np.float32))
+ depth[~np.isfinite(depth)] = 0.0
+ return depth
+
+ def build_instance_seg_visualization(
+ self, instance_seg: np.ndarray, max_instance_id: int
+ ) -> np.ndarray:
+ """Map instance ids to deterministic RGB colors for visualization."""
+ color_lut = np.zeros((max_instance_id + 1, 3), dtype=np.uint8)
+ for instance_id in range(1, max_instance_id + 1):
+ color_lut[instance_id] = (
+ (instance_id * 37) % 256,
+ (instance_id * 67) % 256,
+ (instance_id * 97) % 256,
+ )
+ return color_lut[instance_seg]
+
+ def save_instance_seg_outputs(
+ self,
+ output_path: Path,
+ instance_seg: np.ndarray,
+ ) -> None:
+ vis_output_path = self.get_instance_seg_vis_output_path(output_path)
+ vis_output_path.parent.mkdir(parents=True, exist_ok=True)
+
+ visualization = self.build_instance_seg_visualization(
+ instance_seg=instance_seg,
+ max_instance_id=int(instance_seg.max(initial=0)),
+ )
+ if not cv2.imwrite(str(vis_output_path), visualization):
+ raise RuntimeError(
+ f"Failed to write instance segmentation preview: "
+ f"{vis_output_path}"
+ )
+
+ def build_flow_visualization(self, flow: np.ndarray) -> np.ndarray:
+ flow_float = flow.astype(np.float32)
+ magnitude, angle = cv2.cartToPolar(
+ flow_float[..., 0],
+ flow_float[..., 1],
+ angleInDegrees=True,
+ )
+ max_magnitude = float(np.percentile(magnitude, 99.0))
+ if max_magnitude <= 1e-6:
+ max_magnitude = 1.0
+
+ magnitude_norm = np.clip(magnitude / max_magnitude, 0.0, 1.0)
+ hsv = np.zeros((*flow.shape[:2], 3), dtype=np.float32)
+ hsv[..., 0] = np.mod(angle, 360.0)
+ hsv[..., 1] = magnitude_norm
+ hsv[..., 2] = 1.0
+ bgr = cv2.cvtColor(hsv, cv2.COLOR_HSV2BGR)
+ return np.clip(bgr * 255.0, 0.0, 255.0).astype(np.uint8)
+
+ def get_camera_intrinsics(
+ self, camera: bpy.types.Object, width: int, height: int
+ ) -> tuple[float, float, float, float]:
+ camera_data = camera.data
+ fx = width / (2.0 * math.tan(camera_data.angle_x * 0.5))
+ fy = height / (2.0 * math.tan(camera_data.angle_y * 0.5))
+ cx = (width - 1.0) * 0.5
+ cy = (height - 1.0) * 0.5
+ return fx, fy, cx, cy
+
+ def build_camera_matrix_world(
+ self,
+ xyz: tuple[float, float, float] | list[float],
+ rotation_deg: tuple[float, float, float] | list[float],
+ ) -> Matrix:
+ rotation = Euler(self.get_rotation_radians(rotation_deg), "XYZ")
+ translation = Matrix.Translation(Vector(tuple(xyz)))
+ return translation @ rotation.to_matrix().to_4x4()
+
+ def compute_flow_from_depth(
+ self,
+ depth: np.ndarray,
+ camera: bpy.types.Object,
+ ) -> tuple[np.ndarray, np.ndarray]:
+ """Project depth into a target camera and derive dense 2D flow."""
+ height, width = depth.shape
+ fx, fy, cx, cy = self.get_camera_intrinsics(camera, width, height)
+ valid = np.isfinite(depth) & (depth > 0.0)
+ valid_mask = np.zeros((height, width), dtype=bool)
+ if not np.any(valid):
+ return np.zeros((height, width, 2), dtype=np.float32), valid_mask
+
+ u_coords, v_coords = np.meshgrid(
+ np.arange(width, dtype=np.float32),
+ np.arange(height, dtype=np.float32),
+ )
+
+ depth_valid = depth[valid]
+ x_cam = ((u_coords[valid] - cx) / fx) * depth_valid
+ y_cam = (-(v_coords[valid] - cy) / fy) * depth_valid
+ z_cam = -depth_valid
+
+ camera_points = np.stack(
+ [x_cam, y_cam, z_cam, np.ones_like(z_cam)], axis=1
+ )
+
+ source_matrix_world = np.array(camera.matrix_world, dtype=np.float64)
+ target_matrix_world = np.array(
+ self.build_camera_matrix_world(
+ self.flow_camera_xyz,
+ self.flow_camera_rotation_deg,
+ ),
+ dtype=np.float64,
+ )
+ target_world_to_camera = np.linalg.inv(target_matrix_world)
+
+ world_points = camera_points @ source_matrix_world.T
+ target_camera_points = world_points @ target_world_to_camera.T
+
+ target_z = target_camera_points[:, 2]
+ positive_depth = target_z < -1e-6
+ flow = np.zeros((height, width, 2), dtype=np.float32)
+ if not np.any(positive_depth):
+ return flow, valid_mask
+
+ projected_x = (
+ fx
+ * (
+ target_camera_points[positive_depth, 0]
+ / -target_z[positive_depth]
+ )
+ + cx
+ )
+ projected_y = (
+ -fy
+ * (
+ target_camera_points[positive_depth, 1]
+ / -target_z[positive_depth]
+ )
+ + cy
+ )
+ in_frame = (
+ (projected_x >= 0.0)
+ & (projected_x < width)
+ & (projected_y >= 0.0)
+ & (projected_y < height)
+ )
+ if not np.any(in_frame):
+ return flow, valid_mask
+
+ source_x = u_coords[valid][positive_depth]
+ source_y = v_coords[valid][positive_depth]
+ flow_valid = np.stack(
+ [
+ projected_x[in_frame] - source_x[in_frame],
+ projected_y[in_frame] - source_y[in_frame],
+ ],
+ axis=1,
+ ).astype(np.float32)
+
+ flow_buffer = flow[valid]
+ positive_depth_buffer = flow_buffer[positive_depth]
+ positive_depth_buffer[in_frame] = flow_valid
+ flow_buffer[positive_depth] = positive_depth_buffer
+ flow[valid] = flow_buffer
+ valid_mask_buffer = valid_mask[valid]
+ positive_depth_mask = valid_mask_buffer[positive_depth]
+ positive_depth_mask[in_frame] = True
+ valid_mask_buffer[positive_depth] = positive_depth_mask
+ valid_mask[valid] = valid_mask_buffer
+ return flow, valid_mask
+
+ def save_numpy_array(self, output_path: Path, array: np.ndarray) -> None:
+ """Persist a NumPy array atomically to avoid partial writes."""
+ temp_output_path = output_path.with_suffix(".tmp.npy")
+ if temp_output_path.exists():
+ temp_output_path.unlink()
+ np.save(temp_output_path, array)
+ temp_output_path.replace(output_path)
+
+ def save_flow_outputs(
+ self,
+ output_path: Path,
+ flow: np.ndarray,
+ valid_mask: np.ndarray,
+ ) -> None:
+ flow_output_path = self.get_flow_output_path(output_path)
+ flow_valid_output_path = self.get_flow_valid_output_path(output_path)
+ flow_vis_output_path = self.get_flow_vis_output_path(output_path)
+ for path in (
+ flow_output_path,
+ flow_valid_output_path,
+ flow_vis_output_path,
+ ):
+ path.parent.mkdir(parents=True, exist_ok=True)
+
+ self.save_numpy_array(flow_output_path, flow)
+ self.save_numpy_array(flow_valid_output_path, valid_mask)
+ flow_vis = self.build_flow_visualization(flow)
+ if not cv2.imwrite(str(flow_vis_output_path), flow_vis):
+ raise RuntimeError(
+ f"Failed to write flow preview: {flow_vis_output_path}"
+ )
+
+ def get_preview_output_path(
+ self,
+ output_path: Path,
+ render_pass_name: str,
+ occurrence_index: int = 1,
+ ) -> Path | None:
+ preview_output_paths = {
+ "rgb": output_path,
+ "depth": self.get_depth_vis_output_path(output_path),
+ "normal": self.get_normal_output_path(output_path),
+ "mesh": self.get_mesh_output_path(output_path),
+ "instance_seg": self.get_instance_seg_vis_output_path(output_path),
+ "flow": self.get_flow_vis_output_path(output_path),
+ }
+ preview_output_path = preview_output_paths.get(render_pass_name)
+ if preview_output_path is None:
+ return None
+ return self.build_occurrence_output_path(
+ preview_output_path, occurrence_index
+ )
+
+ def load_preview_image(self, image_path: Path) -> np.ndarray:
+ image = cv2.imread(str(image_path), cv2.IMREAD_COLOR)
+ if image is None:
+ raise FileNotFoundError(
+ f"Failed to read preview image: {image_path}"
+ )
+ return image
+
+ def collect_composite_images(
+ self, output_path: Path
+ ) -> list[tuple[str, np.ndarray]]:
+ composite_images: list[tuple[str, np.ndarray]] = []
+ for (
+ render_pass_name,
+ occurrence_index,
+ ) in self.iter_render_pass_occurrences():
+ preview_output_path = self.get_preview_output_path(
+ output_path,
+ render_pass_name,
+ occurrence_index,
+ )
+ if preview_output_path is None or not preview_output_path.exists():
+ continue
+ composite_images.append(
+ (
+ render_pass_name,
+ self.load_preview_image(preview_output_path),
+ )
+ )
+ return composite_images
+
+ def replicate_duplicate_preview_outputs(self, output_path: Path) -> None:
+ """Materialize repeated preview outputs without re-rendering."""
+ for (
+ render_pass_name,
+ occurrence_index,
+ ) in self.iter_render_pass_occurrences():
+ if occurrence_index == 1:
+ continue
+
+ source_output_path = self.get_preview_output_path(
+ output_path, render_pass_name
+ )
+ duplicate_output_path = self.get_preview_output_path(
+ output_path,
+ render_pass_name,
+ occurrence_index,
+ )
+ if source_output_path is None or duplicate_output_path is None:
+ continue
+ if not source_output_path.exists():
+ raise FileNotFoundError(
+ f"Preview output not generated for repeated pass "
+ f"{render_pass_name}: {source_output_path}"
+ )
+ if duplicate_output_path.exists():
+ duplicate_output_path.unlink()
+ shutil.copyfile(source_output_path, duplicate_output_path)
+
+ def get_composite_separator_boundaries(
+ self,
+ render_pass_names: list[str] | tuple[str, ...],
+ boundaries: np.ndarray,
+ ) -> list[float]:
+ """Return separator boundaries for adjacent passes that differ."""
+ if len(boundaries) != len(render_pass_names) + 1:
+ raise ValueError(
+ "boundaries length must match the number of render passes + 1."
+ )
+
+ separator_boundaries: list[float] = []
+ for index, boundary in enumerate(boundaries[1:-1], start=1):
+ if render_pass_names[index - 1] == render_pass_names[index]:
+ continue
+ separator_boundaries.append(float(boundary))
+ return separator_boundaries
+
+ def build_composite_image(
+ self,
+ images: list[np.ndarray],
+ render_pass_names: list[str] | tuple[str, ...],
+ separator_width_px: int = 6,
+ ) -> np.ndarray:
+ if not images:
+ raise ValueError("At least one image is required for composition.")
+ if len(images) != len(render_pass_names):
+ raise ValueError(
+ "images and render_pass_names must have the same length."
+ )
+
+ base_height, base_width = images[0].shape[:2]
+ resized_images = [
+ (
+ image
+ if image.shape[:2] == (base_height, base_width)
+ else cv2.resize(
+ image,
+ (base_width, base_height),
+ interpolation=cv2.INTER_LINEAR,
+ )
+ )
+ for image in images
+ ]
+
+ x_coords = np.broadcast_to(
+ np.arange(base_width, dtype=np.float32),
+ (base_height, base_width),
+ )
+ y_coords = np.broadcast_to(
+ np.arange(base_height, dtype=np.float32)[:, None],
+ (base_height, base_width),
+ )
+ slash_slope = 0.28 * (base_width / base_height)
+ diagonal_coord = x_coords + y_coords * slash_slope
+ diagonal_min = float(diagonal_coord.min())
+ diagonal_max = float(diagonal_coord.max())
+ boundaries = np.linspace(
+ diagonal_min, diagonal_max, len(resized_images) + 1
+ )
+
+ composite = np.zeros_like(resized_images[0])
+ region_indices = np.digitize(
+ diagonal_coord, boundaries[1:-1], right=False
+ )
+ for image_index, image in enumerate(resized_images):
+ composite[region_indices == image_index] = image[
+ region_indices == image_index
+ ]
+
+ slash_mask = np.zeros((base_height, base_width), dtype=bool)
+ separator_boundaries = self.get_composite_separator_boundaries(
+ render_pass_names, boundaries
+ )
+ for boundary in separator_boundaries:
+ slash_mask |= (
+ np.abs(diagonal_coord - boundary) <= separator_width_px
+ )
+ composite[slash_mask] = 255
+ return composite
+
+ def save_composite_preview(self, output_path: Path) -> None:
+ composite_images = self.collect_composite_images(output_path)
+ if len(composite_images) < 2:
+ return
+
+ composite_output_path = self.get_composite_output_path(
+ tuple(render_pass_name for render_pass_name, _ in composite_images)
+ )
+ composite_output_path.parent.mkdir(parents=True, exist_ok=True)
+ composite_image = self.build_composite_image(
+ [image for _, image in composite_images],
+ [render_pass_name for render_pass_name, _ in composite_images],
+ )
+ if not cv2.imwrite(str(composite_output_path), composite_image):
+ raise RuntimeError(
+ f"Failed to write composite preview: {composite_output_path}"
+ )
+
+ def render_flow_pass(self, output_path: Path) -> None:
+ self.validate_flow_args()
+ camera = self.scene.camera
+ if camera is None:
+ raise ValueError("Scene camera is required for flow rendering.")
+
+ temp_output_path = self.get_flow_depth_temp_path(output_path)
+
+ def finalize_flow_output(depth: np.ndarray) -> None:
+ flow, valid_mask = self.compute_flow_from_depth(
+ depth=depth, camera=camera
+ )
+ self.save_flow_outputs(
+ output_path=output_path,
+ flow=flow,
+ valid_mask=valid_mask,
+ )
+
+ self.render_temp_output_pass(
+ output_path=output_path,
+ temp_output_path=temp_output_path,
+ add_output_node=self.add_flow_depth_output_node,
+ load_temp_output=self.load_flow_depth_temp_output,
+ finalize_output=finalize_flow_output,
+ color_mode="RGB",
+ color_depth="8",
+ enable_depth_pass=True,
+ )
+
+ def render_normal_pass(self, output_path: Path) -> None:
+ normal_output_path = self.get_normal_output_path(output_path)
+ self.render_material_override_pass(
+ preview_output_path=normal_output_path,
+ material_factory=self.create_view_normal_material,
+ color_mode="RGB",
+ )
+
+ def render_mesh_pass(self, output_path: Path) -> None:
+ mesh_output_path = self.get_mesh_output_path(output_path)
+ self.render_material_override_pass(
+ preview_output_path=mesh_output_path,
+ material_factory=self.create_mesh_preview_material,
+ color_mode="RGBA",
+ )
+
+ def render_instance_seg_pass(self, output_path: Path) -> None:
+ original_pass_indices = self.snapshot_object_pass_indices()
+ self.assign_instance_ids()
+ temp_output_path = self.get_instance_seg_temp_path(output_path)
+
+ def finalize_instance_seg_output(instance_seg: np.ndarray) -> None:
+ self.save_instance_seg_outputs(
+ output_path=output_path,
+ instance_seg=instance_seg,
+ )
+
+ try:
+ self.render_temp_output_pass(
+ output_path=output_path,
+ temp_output_path=temp_output_path,
+ add_output_node=self.add_instance_seg_output_node,
+ load_temp_output=self.load_instance_seg_temp_output,
+ finalize_output=finalize_instance_seg_output,
+ color_mode="BW",
+ color_depth="16",
+ enable_object_index_pass=True,
+ )
+ finally:
+ self.restore_object_pass_indices(original_pass_indices)
+
+ def render(self, output_path: Path) -> None:
+ """Run the requested render passes and write final outputs."""
+ self.scene.use_nodes = False
+ auxiliary_outputs: list[tuple[Path, Path]] = []
+ needs_base_render = bool({"rgb", "depth"} & set(self.render_passes))
+
+ if "depth" in self.render_passes:
+ auxiliary_outputs = self.configure_auxiliary_outputs(
+ output_path, self.render_passes, self.depth_mode
+ )
+
+ output_path.parent.mkdir(parents=True, exist_ok=True)
+ if "rgb" in self.render_passes:
+ self.scene.render.filepath = str(output_path)
+
+ if needs_base_render:
+ bpy.ops.render.render(write_still="rgb" in self.render_passes)
+
+ for temp_path, final_path in auxiliary_outputs:
+ if final_path == self.get_depth_vis_output_path(output_path):
+ self.finalize_depth_output(temp_path, final_path)
+ continue
+ raise ValueError(f"Unsupported render output target: {final_path}")
+ if auxiliary_outputs:
+ self.clear_compositor_tree()
+ self.scene.use_nodes = False
+
+ if "normal" in self.render_passes:
+ self.render_normal_pass(output_path)
+ if "mesh" in self.render_passes:
+ self.render_mesh_pass(output_path)
+ if "instance_seg" in self.render_passes:
+ self.render_instance_seg_pass(output_path)
+ if "flow" in self.render_passes:
+ self.render_flow_pass(output_path)
+ self.replicate_duplicate_preview_outputs(output_path)
+ self.save_composite_preview(output_path)
+
+ def run(self) -> None:
+ """Prepare the scene, configure rendering, and execute all passes."""
+ rgb_output_path = self.get_rgb_output_path()
+ self.output_dir.mkdir(parents=True, exist_ok=True)
+ self.clear_scene()
+ self.import_usd()
+ if self.remove_doors:
+ self.remove_door_objects()
+ self.validate_glb_args()
+ imported_glb_objects = self.import_glb_asset()
+ self.place_glb_asset(imported_glb_objects)
+ min_corner, max_corner = self.get_scene_bbox()
+ center = (min_corner + max_corner) * 0.5
+ diagonal = (max_corner - min_corner).length
+ self.create_camera()
+ self.ensure_lighting(diagonal, center, max_corner.z)
+ world_created = self.ensure_world()
+ self.add_fill_light(
+ diagonal,
+ center,
+ max_corner.z,
+ energy=self.fill_light_energy,
+ )
+ if world_created:
+ self.add_light_rig(
+ diagonal,
+ center,
+ max_corner.z,
+ area_energy=1500.0,
+ sun_energy=0.35,
+ prefix="Fill",
+ )
+ self.configure_color_management()
+ self.configure_cycles()
+ with tempfile.TemporaryDirectory(
+ prefix="render_usd_", dir=None
+ ) as temp_dir:
+ self.temp_dir = Path(temp_dir)
+ self.render(rgb_output_path)
+ self.temp_dir = None
+
+ logger.info("Rendered outputs to %s", self.output_dir)
+
+ def compute_trajectory_frame_transforms(
+ self,
+ x: float,
+ y: float,
+ rot_deg: float,
+ *,
+ camera_z: float,
+ camera_pitch: float,
+ camera_roll: float,
+ glb_offset_xyz: tuple[float, float, float],
+ glb_base_rotation_deg: tuple[float, float, float],
+ ) -> tuple[Vector, Euler, Matrix]:
+ """Compute per-frame camera and GLB transforms for one waypoint.
+
+ The camera sits at (x, y, camera_z) and yaws by ``rot_deg`` (its
+ heading). The GLB keeps a fixed offset relative to the camera: the
+ horizontal offset rotates with the yaw and its yaw adds ``rot_deg``,
+ while its height stays on the floor.
+
+ Args:
+ x: Waypoint X (m).
+ y: Waypoint Y (m).
+ rot_deg: Waypoint heading in degrees (camera/GLB yaw).
+ camera_z: Camera height (m).
+ camera_pitch: Camera pitch deg (rotation_deg[0]).
+ camera_roll: Camera roll deg (rotation_deg[1]).
+ glb_offset_xyz: GLB-minus-camera offset at yaw=0 (world frame).
+ glb_base_rotation_deg: GLB rotation deg at yaw=0.
+
+ Returns:
+ Tuple of (camera_location, camera_euler, glb_world_transform).
+ """
+ camera_location = Vector((x, y, camera_z))
+ camera_euler = Euler(
+ self.get_rotation_radians((camera_pitch, camera_roll, rot_deg)),
+ "XYZ",
+ )
+
+ yaw = math.radians(rot_deg)
+ cos_yaw, sin_yaw = math.cos(yaw), math.sin(yaw)
+ off_x, off_y, off_z = glb_offset_xyz
+ glb_xyz = (
+ x + off_x * cos_yaw - off_y * sin_yaw,
+ y + off_x * sin_yaw + off_y * cos_yaw,
+ camera_z + off_z,
+ )
+ base_rx, base_ry, base_rz = glb_base_rotation_deg
+ glb_rotation_deg = (base_rx + 90.0, base_ry, base_rz + rot_deg)
+ glb_transform = self.build_camera_matrix_world(
+ glb_xyz, glb_rotation_deg
+ )
+ return camera_location, camera_euler, glb_transform
+
+ def compute_camera_follow_glb_transform(
+ self,
+ camera_xyz: tuple[float, float, float],
+ camera_rotation_deg: tuple[float, float, float],
+ *,
+ glb_offset_xyz: tuple[float, float, float],
+ glb_base_rotation_deg: tuple[float, float, float],
+ ) -> Matrix:
+ """Compute a GLB transform from the default camera-follow offset."""
+ _, _, glb_transform = self.compute_trajectory_frame_transforms(
+ camera_xyz[0],
+ camera_xyz[1],
+ camera_rotation_deg[2],
+ camera_z=camera_xyz[2],
+ camera_pitch=camera_rotation_deg[0],
+ camera_roll=camera_rotation_deg[1],
+ glb_offset_xyz=glb_offset_xyz,
+ glb_base_rotation_deg=glb_base_rotation_deg,
+ )
+ return glb_transform
+
+ def frame_outputs_exist(self) -> bool:
+ """Whether every requested pass output for the current frame exists.
+
+ Used for per-frame resume: a frame is skipped only when all its
+ requested pass preview outputs are already on disk.
+ """
+ rgb_path = self.get_rgb_output_path()
+ for pass_name, occurrence in self.iter_render_pass_occurrences():
+ preview = self.get_preview_output_path(
+ rgb_path, pass_name, occurrence
+ )
+ if preview is None or not preview.exists():
+ return False
+ return True
+
+ def run_trajectory(
+ self,
+ *,
+ waypoints: list[tuple[float, float, float]],
+ camera_z: float,
+ camera_pitch: float,
+ camera_roll: float,
+ glb_offset_xyz: tuple[float, float, float],
+ glb_base_rotation_deg: tuple[float, float, float],
+ frame_stride: int,
+ frame_indices: list[int] | None = None,
+ make_video: bool = False,
+ video_fps: float = 10.0,
+ overwrite: bool = False,
+ ) -> None:
+ """Render one frame per trajectory waypoint following the camera path.
+
+ The scene (USD, GLB, lighting, world) is set up once; each frame only
+ repositions the camera and GLB, then renders into a shared ``frames/``
+ directory with a ``frame__`` filename prefix. When ``make_video``
+ is set, the RGB frames are stitched into an mp4.
+
+ Args:
+ waypoints: List of (x, y, rot_deg) waypoints.
+ camera_z: Camera height (m).
+ camera_pitch: Camera pitch deg (rotation_deg[0]).
+ camera_roll: Camera roll deg (rotation_deg[1]).
+ glb_offset_xyz: GLB-minus-camera offset at yaw=0 (world frame).
+ glb_base_rotation_deg: GLB rotation deg at yaw=0.
+ frame_stride: Render every Nth waypoint.
+ frame_indices: Optional zero-based waypoint indices to render.
+ make_video: Stitch the RGB frames into an mp4 after rendering.
+ video_fps: Frame rate of the stitched video.
+ overwrite: Re-render every frame; if False, frames whose pass
+ outputs already exist are skipped (per-frame resume).
+ """
+ if not waypoints:
+ raise ValueError("Trajectory contains no waypoints.")
+ if self.glb_path is None:
+ raise ValueError(
+ "--glb_path is required for trajectory rendering."
+ )
+ if not self.glb_path.exists():
+ raise FileNotFoundError(f"GLB file not found: {self.glb_path}")
+ if self.glb_path.suffix.lower() != ".glb":
+ raise ValueError(
+ f"Expected a .glb asset, but got: {self.glb_path}"
+ )
+
+ base_output_dir = self.output_dir
+ base_output_dir.mkdir(parents=True, exist_ok=True)
+
+ self.clear_scene()
+ self.import_usd()
+ if self.remove_doors:
+ self.remove_door_objects()
+ imported_glb_objects = self.import_glb_asset()
+ root_objects = self.get_imported_root_objects(imported_glb_objects)
+ original_matrices = {
+ obj.as_pointer(): obj.matrix_world.copy() for obj in root_objects
+ }
+
+ min_corner, max_corner = self.get_scene_bbox()
+ center = (min_corner + max_corner) * 0.5
+ diagonal = (max_corner - min_corner).length
+ camera = self.create_camera()
+ self.ensure_lighting(diagonal, center, max_corner.z)
+ world_created = self.ensure_world()
+ self.add_fill_light(
+ diagonal, center, max_corner.z, energy=self.fill_light_energy
+ )
+ if world_created:
+ self.add_light_rig(
+ diagonal,
+ center,
+ max_corner.z,
+ area_energy=1500.0,
+ sun_energy=0.35,
+ prefix="Fill",
+ )
+ self.configure_color_management()
+ self.configure_cycles()
+
+ if frame_indices is None:
+ frames = list(enumerate(waypoints))[:: max(1, frame_stride)]
+ else:
+ invalid_indices = sorted(
+ {
+ index
+ for index in frame_indices
+ if index < 0 or index >= len(waypoints)
+ }
+ )
+ if invalid_indices:
+ raise ValueError(
+ "Trajectory frame indices out of range "
+ f"[0, {len(waypoints) - 1}]: {invalid_indices}"
+ )
+ frames = [
+ (index, waypoints[index])
+ for index in dict.fromkeys(frame_indices)
+ ]
+ self.frame_layout = True
+ self.frame_base_dir = base_output_dir
+
+ # The highest already-rendered frame is the one most likely left
+ # half-written by an interrupted run, so re-render it even on resume.
+ last_existing = -1
+ if not overwrite:
+ for frame_idx, _ in frames:
+ self.frame_index = frame_idx
+ if self.frame_outputs_exist():
+ last_existing = frame_idx
+
+ with tempfile.TemporaryDirectory(
+ prefix="render_usd_traj_", dir=None
+ ) as temp_dir:
+ self.temp_dir = Path(temp_dir)
+ for frame_idx, (x, y, rot_deg) in frames:
+ self.frame_index = frame_idx
+ # Per-frame resume: skip complete frames, but always redo the
+ # last existing one (possibly a truncated/corrupt write).
+ if (
+ not overwrite
+ and frame_idx != last_existing
+ and self.frame_outputs_exist()
+ ):
+ logger.info("Skip existing frame %04d", frame_idx)
+ continue
+
+ cam_loc, cam_euler, glb_transform = (
+ self.compute_trajectory_frame_transforms(
+ x,
+ y,
+ rot_deg,
+ camera_z=camera_z,
+ camera_pitch=camera_pitch,
+ camera_roll=camera_roll,
+ glb_offset_xyz=glb_offset_xyz,
+ glb_base_rotation_deg=glb_base_rotation_deg,
+ )
+ )
+ camera.location = cam_loc
+ camera.rotation_euler = cam_euler
+ for obj in root_objects:
+ obj.matrix_world = (
+ glb_transform @ original_matrices[obj.as_pointer()]
+ )
+ bpy.context.view_layer.update()
+
+ self.render(self.get_rgb_output_path())
+ logger.info(
+ "Rendered frame %04d at (%.3f, %.3f, rot=%.1f)",
+ frame_idx,
+ x,
+ y,
+ rot_deg,
+ )
+ self.temp_dir = None
+
+ self.frame_layout = False
+ self.frame_base_dir = None
+ self.output_dir = base_output_dir
+ logger.info(
+ "Rendered %d trajectory frames to %s",
+ len(frames),
+ base_output_dir,
+ )
+
+ if make_video:
+ self.stitch_pass_videos(base_output_dir, fps=video_fps)
+
+ def run_camera_trajectory(
+ self,
+ *,
+ frames: list[CameraTrajectoryFrame],
+ glb_offset_xyz: tuple[float, float, float],
+ glb_base_rotation_deg: tuple[float, float, float],
+ frame_stride: int,
+ frame_indices: list[int] | None = None,
+ make_video: bool = False,
+ video_fps: float = 10.0,
+ overwrite: bool = False,
+ ) -> None:
+ """Render fully specified camera poses loaded from a trajectory CSV.
+
+ Args:
+ frames: Camera frames with output index, timestamp, and 6DoF pose.
+ glb_offset_xyz: Local XYZ offset applied to GLB assets.
+ glb_base_rotation_deg: Base XYZ Euler rotation applied to GLB assets.
+ frame_stride: Render every Nth CSV row.
+ frame_indices: Optional CSV ``frame`` values to render.
+ make_video: Stitch rendered image passes into mp4 files.
+ video_fps: Frame rate of the stitched videos.
+ overwrite: Re-render complete frames instead of resuming.
+ """
+ if not frames:
+ raise ValueError("Camera trajectory contains no frames.")
+
+ base_output_dir = self.output_dir
+ base_output_dir.mkdir(parents=True, exist_ok=True)
+
+ self.clear_scene()
+ self.import_usd()
+ if self.remove_doors:
+ self.remove_door_objects()
+ self.validate_glb_args(allow_path_only=True)
+ imported_glb_objects = self.import_glb_asset()
+ glb_follows_camera = (
+ self.glb_path is not None
+ and self.glb_xyz is None
+ and self.glb_rotation_deg is None
+ )
+ root_objects: list[bpy.types.Object] = []
+ original_matrices: dict[int, Matrix] = {}
+ if glb_follows_camera:
+ root_objects = self.get_imported_root_objects(imported_glb_objects)
+ original_matrices = {
+ obj.as_pointer(): obj.matrix_world.copy()
+ for obj in root_objects
+ }
+ else:
+ self.place_glb_asset(imported_glb_objects)
+
+ min_corner, max_corner = self.get_scene_bbox()
+ center = (min_corner + max_corner) * 0.5
+ diagonal = (max_corner - min_corner).length
+ camera = self.create_camera()
+ self.ensure_lighting(diagonal, center, max_corner.z)
+ world_created = self.ensure_world()
+ self.add_fill_light(
+ diagonal, center, max_corner.z, energy=self.fill_light_energy
+ )
+ if world_created:
+ self.add_light_rig(
+ diagonal,
+ center,
+ max_corner.z,
+ area_energy=1500.0,
+ sun_energy=0.35,
+ prefix="Fill",
+ )
+ self.configure_color_management()
+ self.configure_cycles()
+
+ if frame_indices is None:
+ selected_frames = frames[:: max(1, frame_stride)]
+ else:
+ frames_by_index = {frame.frame_index: frame for frame in frames}
+ invalid_indices = sorted(
+ {
+ index
+ for index in frame_indices
+ if index not in frames_by_index
+ }
+ )
+ if invalid_indices:
+ raise ValueError(
+ "Camera trajectory frame indices not found: "
+ f"{invalid_indices}"
+ )
+ selected_frames = [
+ frames_by_index[index]
+ for index in dict.fromkeys(frame_indices)
+ ]
+
+ self.frame_layout = True
+ self.frame_base_dir = base_output_dir
+
+ last_existing = -1
+ if not overwrite:
+ for frame in selected_frames:
+ self.frame_index = frame.frame_index
+ if self.frame_outputs_exist():
+ last_existing = frame.frame_index
+
+ with tempfile.TemporaryDirectory(
+ prefix="render_usd_camera_traj_", dir=None
+ ) as temp_dir:
+ self.temp_dir = Path(temp_dir)
+ for frame in selected_frames:
+ self.frame_index = frame.frame_index
+ if (
+ not overwrite
+ and frame.frame_index != last_existing
+ and self.frame_outputs_exist()
+ ):
+ logger.info("Skip existing frame %04d", frame.frame_index)
+ continue
+
+ camera.location = Vector(frame.xyz)
+ camera.rotation_euler = Euler(
+ self.get_rotation_radians(frame.rotation_deg),
+ "XYZ",
+ )
+ if glb_follows_camera:
+ glb_transform = self.compute_camera_follow_glb_transform(
+ frame.xyz,
+ frame.rotation_deg,
+ glb_offset_xyz=glb_offset_xyz,
+ glb_base_rotation_deg=glb_base_rotation_deg,
+ )
+ for obj in root_objects:
+ obj.matrix_world = (
+ glb_transform @ original_matrices[obj.as_pointer()]
+ )
+ bpy.context.view_layer.update()
+
+ self.render(self.get_rgb_output_path())
+ logger.info(
+ "Rendered frame %04d at "
+ "(%.3f, %.3f, %.3f), rotation=(%.2f, %.2f, %.2f)",
+ frame.frame_index,
+ *frame.xyz,
+ *frame.rotation_deg,
+ )
+ self.temp_dir = None
+
+ self.frame_layout = False
+ self.frame_base_dir = None
+ self.output_dir = base_output_dir
+ logger.info(
+ "Rendered %d camera trajectory frames to %s",
+ len(selected_frames),
+ base_output_dir,
+ )
+
+ if make_video:
+ self.stitch_pass_videos(base_output_dir, fps=video_fps)
+
+ def stitch_pass_videos(self, base_dir: Path, *, fps: float) -> None:
+ """Stitch each ``frames_/`` image sequence into ``.mp4``.
+
+ Scans ``base_dir`` for ``frames_`` directories, orders the
+ ``frame_*.png`` images, and writes one video per pass next to them
+ (e.g. ``frames_render_rgb/`` -> ``render_rgb.mp4``) using ffmpeg.
+
+ Args:
+ base_dir: Trajectory output directory containing the frame dirs.
+ fps: Output frame rate (clamped to a small positive minimum).
+ """
+ fps = max(fps, 1.0)
+ for frames_subdir in sorted(base_dir.glob("frames_*")):
+ if not frames_subdir.is_dir():
+ continue
+ frame_paths = sorted(frames_subdir.glob("frame_*.png"))
+ if not frame_paths:
+ continue
+
+ pass_name = frames_subdir.name[len("frames_") :]
+ output_path = base_dir / f"{pass_name}.mp4"
+ self.encode_frames_to_video(frames_subdir, output_path, fps)
+ logger.info(
+ "Stitched %d frames into %s (%.2f fps).",
+ len(frame_paths),
+ output_path,
+ fps,
+ )
+
+ @staticmethod
+ def encode_frames_to_video(
+ frames_dir: Path, output_path: Path, fps: float
+ ) -> None:
+ """Encode a ``frame_*.png`` sequence into an mp4 via ffmpeg.
+
+ Uses the system ffmpeg (h264/yuv420p) and a glob input pattern so
+ non-contiguous frame indices (e.g. with --frame_stride) work. Even
+ dimensions are enforced for yuv420p compatibility.
+
+ Args:
+ frames_dir: Directory holding the ``frame_*.png`` images.
+ output_path: Destination mp4 path.
+ fps: Output frame rate.
+ """
+ cmd = [
+ "ffmpeg",
+ "-y",
+ "-framerate",
+ f"{fps:g}",
+ "-pattern_type",
+ "glob",
+ "-i",
+ str(frames_dir / "frame_*.png"),
+ "-vf",
+ "pad=ceil(iw/2)*2:ceil(ih/2)*2",
+ "-c:v",
+ "libx264",
+ "-pix_fmt",
+ "yuv420p",
+ str(output_path),
+ ]
+ result = subprocess.run(cmd, capture_output=True, text=True)
+ if result.returncode != 0:
+ raise RuntimeError(
+ f"ffmpeg failed for {output_path}:\n{result.stderr}"
+ )
+
+
+def load_trajectory_waypoints(
+ trajectory_json: Path,
+) -> list[tuple[float, float, float]]:
+ """Load (x, y, rot) waypoints from a trajectory JSON file.
+
+ Accepts either a dict with a ``waypoints`` key or a bare list of records.
+
+ Args:
+ trajectory_json: Path to the trajectory JSON.
+
+ Returns:
+ List of (x, y, rot_deg) tuples.
+ """
+ data = json.loads(Path(trajectory_json).read_text())
+ records = data["waypoints"] if isinstance(data, dict) else data
+ return [
+ (float(rec["x"]), float(rec["y"]), float(rec["rot"]))
+ for rec in records
+ ]
+
+
+def load_camera_trajectory_csv(
+ trajectory_csv: Path,
+) -> list[CameraTrajectoryFrame]:
+ """Load full camera poses from a trajectory CSV file.
+
+ Args:
+ trajectory_csv: CSV containing the required camera trajectory fields.
+
+ Returns:
+ Camera frames in CSV row order.
+ """
+ trajectory_csv = Path(trajectory_csv)
+ if not trajectory_csv.exists():
+ raise FileNotFoundError(
+ f"Camera trajectory CSV not found: {trajectory_csv}"
+ )
+
+ frames: list[CameraTrajectoryFrame] = []
+ seen_indices: set[int] = set()
+ previous_frame_index: int | None = None
+ previous_time_sec: float | None = None
+ with trajectory_csv.open(newline="", encoding="utf-8-sig") as csv_file:
+ reader = csv.DictReader(csv_file)
+ fieldnames = set(reader.fieldnames or ())
+ missing_fields = [
+ field
+ for field in CAMERA_TRAJECTORY_CSV_FIELDS
+ if field not in fieldnames
+ ]
+ if missing_fields:
+ raise ValueError(
+ f"Camera trajectory CSV is missing fields: {missing_fields}"
+ )
+
+ for row_number, row in enumerate(reader, start=2):
+ try:
+ frame_index = int(row["frame"])
+ values = {
+ field: float(row[field])
+ for field in CAMERA_TRAJECTORY_CSV_FIELDS[1:]
+ }
+ except (TypeError, ValueError) as exc:
+ raise ValueError(
+ "Invalid camera trajectory value at CSV row "
+ f"{row_number}: {exc}"
+ ) from exc
+
+ if frame_index < 0:
+ raise ValueError(
+ f"Camera frame must be non-negative at CSV row "
+ f"{row_number}: {frame_index}"
+ )
+ if frame_index in seen_indices:
+ raise ValueError(
+ f"Duplicate camera frame at CSV row {row_number}: "
+ f"{frame_index}"
+ )
+ non_finite_fields = [
+ field
+ for field, value in values.items()
+ if not math.isfinite(value)
+ ]
+ if non_finite_fields:
+ raise ValueError(
+ f"Non-finite values at CSV row {row_number}: "
+ f"{non_finite_fields}"
+ )
+ if (
+ previous_frame_index is not None
+ and frame_index <= previous_frame_index
+ ):
+ raise ValueError(
+ "Camera frames must be strictly increasing at CSV row "
+ f"{row_number}: {frame_index} <= {previous_frame_index}"
+ )
+ if (
+ previous_time_sec is not None
+ and values["time_sec"] <= previous_time_sec
+ ):
+ raise ValueError(
+ "Camera timestamps must be strictly increasing at CSV "
+ f"row {row_number}: {values['time_sec']} <= "
+ f"{previous_time_sec}"
+ )
+
+ seen_indices.add(frame_index)
+ previous_frame_index = frame_index
+ previous_time_sec = values["time_sec"]
+ frames.append(
+ CameraTrajectoryFrame(
+ frame_index=frame_index,
+ time_sec=values["time_sec"],
+ xyz=(values["x"], values["y"], values["z"]),
+ rotation_deg=(
+ values["roll_x_deg"],
+ values["pitch_y_deg"],
+ values["yaw_z_deg"],
+ ),
+ )
+ )
+
+ return frames
+
+
+def resolve_video_fps(
+ video_fps: float, trajectory_json: Path, frame_stride: int
+) -> float:
+ """Resolve the video frame rate, auto-deriving real-time fps if 0.
+
+ Args:
+ video_fps: Requested fps; 0 means auto.
+ trajectory_json: Trajectory JSON (read for ``time_step``).
+ frame_stride: Waypoint stride used during rendering.
+
+ Returns:
+ A positive frame rate.
+ """
+ if video_fps > 0.0:
+ return video_fps
+
+ data = json.loads(Path(trajectory_json).read_text())
+ time_step = data.get("time_step") if isinstance(data, dict) else None
+ if not time_step or time_step <= 0.0:
+ return 10.0
+ return (1.0 / time_step) / max(1, frame_stride)
+
+
+def resolve_csv_video_fps(
+ video_fps: float,
+ frames: list[CameraTrajectoryFrame],
+ frame_stride: int,
+) -> float:
+ """Resolve video fps from CSV timestamps when no fps is specified."""
+ if video_fps > 0.0:
+ return video_fps
+
+ time_deltas = [
+ current.time_sec - previous.time_sec
+ for previous, current in zip(frames, frames[1:])
+ if current.time_sec > previous.time_sec
+ ]
+ if not time_deltas:
+ return 10.0
+
+ time_step = float(np.median(time_deltas))
+ return (1.0 / time_step) / max(1, frame_stride)
+
+
+def run_trajectory_render(args: argparse.Namespace) -> None:
+ """Render a trajectory from parsed CLI arguments."""
+ waypoints = load_trajectory_waypoints(args.trajectory_json)
+ if not waypoints:
+ raise ValueError(f"No waypoints found in {args.trajectory_json}.")
+
+ first_x, first_y, first_rot = waypoints[0]
+ renderer = RenderUsd(
+ usd_path=args.usd_path,
+ glb_path=args.glb_path,
+ glb_xyz=None,
+ glb_rotation_deg=None,
+ output_dir=args.output_dir,
+ render_passes=args.render_passes,
+ depth_mode=args.depth_mode,
+ depth_max=args.depth_max,
+ resolution=args.resolution,
+ samples=args.samples,
+ camera_xyz=(first_x, first_y, args.camera_z),
+ camera_rotation_deg=(args.camera_pitch, args.camera_roll, first_rot),
+ flow_camera_xyz=args.flow_camera_xyz,
+ flow_camera_rotation_deg=args.flow_camera_rotation_deg,
+ focal_length_mm=args.focal_length_mm,
+ exposure=args.exposure,
+ world_strength=args.world_strength,
+ fill_light_energy=args.fill_light_energy,
+ remove_doors=args.remove_doors,
+ )
+ renderer.run_trajectory(
+ waypoints=waypoints,
+ camera_z=args.camera_z,
+ camera_pitch=args.camera_pitch,
+ camera_roll=args.camera_roll,
+ glb_offset_xyz=tuple(args.glb_offset_xyz),
+ glb_base_rotation_deg=tuple(args.glb_base_rotation_deg),
+ frame_stride=args.frame_stride,
+ frame_indices=args.frame_indices,
+ make_video=args.make_video,
+ video_fps=resolve_video_fps(
+ args.video_fps, args.trajectory_json, args.frame_stride
+ ),
+ overwrite=args.overwrite,
+ )
+
+
+def run_camera_trajectory_render(args: argparse.Namespace) -> None:
+ """Render a full-pose camera trajectory from parsed CLI arguments."""
+ frames = load_camera_trajectory_csv(args.trajectory_csv)
+ if not frames:
+ raise ValueError(f"No frames found in {args.trajectory_csv}.")
+
+ first_frame = frames[0]
+ renderer = RenderUsd(
+ usd_path=args.usd_path,
+ glb_path=args.glb_path,
+ glb_xyz=args.glb_xyz,
+ glb_rotation_deg=args.glb_rotation_deg,
+ output_dir=args.output_dir,
+ render_passes=args.render_passes,
+ depth_mode=args.depth_mode,
+ depth_max=args.depth_max,
+ resolution=args.resolution,
+ samples=args.samples,
+ camera_xyz=first_frame.xyz,
+ camera_rotation_deg=first_frame.rotation_deg,
+ flow_camera_xyz=args.flow_camera_xyz,
+ flow_camera_rotation_deg=args.flow_camera_rotation_deg,
+ focal_length_mm=args.focal_length_mm,
+ exposure=args.exposure,
+ world_strength=args.world_strength,
+ fill_light_energy=args.fill_light_energy,
+ remove_doors=args.remove_doors,
+ )
+ renderer.run_camera_trajectory(
+ frames=frames,
+ glb_offset_xyz=tuple(args.glb_offset_xyz),
+ glb_base_rotation_deg=tuple(args.glb_base_rotation_deg),
+ frame_stride=args.frame_stride,
+ frame_indices=args.frame_indices,
+ make_video=args.make_video,
+ video_fps=resolve_csv_video_fps(
+ args.video_fps, frames, args.frame_stride
+ ),
+ overwrite=args.overwrite,
+ )
+
+
+def main() -> None:
+ logging.basicConfig(level=logging.INFO)
+ args = _parse_args()
+ if args.trajectory_json is not None:
+ run_trajectory_render(args)
+ return
+ if args.trajectory_csv is not None:
+ run_camera_trajectory_render(args)
+ return
+
+ if args.camera_xyz is None or args.camera_rotation_deg is None:
+ build_arg_parser().error(
+ "--camera_xyz and --camera_rotation_deg are required unless "
+ "--trajectory_json or --trajectory_csv is given."
+ )
+ RenderUsd.from_args(args).run()
+
+
+if __name__ == "__main__":
+ main()
diff --git a/embodied_gen/scripts/room_gen/route_room.py b/embodied_gen/scripts/room_gen/route_room.py
new file mode 100644
index 0000000..2483f9f
--- /dev/null
+++ b/embodied_gen/scripts/room_gen/route_room.py
@@ -0,0 +1,140 @@
+# Project EmbodiedGen
+#
+# Copyright (c) 2025 Horizon Robotics. All Rights Reserved.
+#
+# 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.
+
+
+from __future__ import annotations
+
+import random
+
+import json_repair
+from embodied_gen.utils.gpt_clients import GPT_CLIENT, GPTclient
+from embodied_gen.utils.log import logger
+
+__all__ = [
+ "InfinigenGenRouter",
+ "DEFAULT_ROUTER_PROMPT",
+]
+
+DEFAULT_ROUTER_PROMPT = """
+You are given a natural-language description of a household task or an indoor
+scene involving objects.
+
+Select all rooms from the predefined list below where this task or scene could
+plausibly occur:
+["Bedroom", "LivingRoom", "Kitchen", "Bathroom", "DiningRoom", "Office"]
+
+Rules:
+1. Output must be a valid JSON nested array (2D list).
+ Format: [["Room_List"], "Complexity_Level"]
+
+2. Room Selection Logic (Index 0):
+ - Standard Case: If the task is contained within specific rooms, select
+ the relevant room name(s).
+ - Special Case: If the task involves moving objects between different rooms
+ (navigation/transport) or implies generating/referencing a complete house
+ layout, use ["House"].
+ - If no rooms are suitable and it is not a "House" case, randomly select
+ one room.
+
+3. Complexity Evaluation Logic (Index 1):
+ - General Rule: Default to "medium".
+ - Overrides (Apply these only if the description fits the specific
+ criteria below):
+ - "minimalist": If explicitly stated as minimalist.
+ - "simple": If the scene is explicitly described as "simple", "basic".
+ - "detail": If the scene is described as "complex", "detailed".
+
+4. Do not include any explanations or additional text.
+
+Example 1:
+Task: minimalist/empty Bedroom.
+Answer: [["Bedroom"], "minimalist"]
+
+Example 2:
+Task: Wiping the table in an simple room.
+Answer: [["DiningRoom", "Kitchen"], "simple"]
+
+Example 3:
+Task: Take the vase from the living room shelf and navigate to the bedroom
+to pack it.
+Answer: [["House"], "medium"]
+
+Example 4:
+Task: Put the apple into the fruit bowl in a complex/detailed env.
+Answer: [["Kitchen", "DiningRoom", "LivingRoom"], "detail"]
+
+Task: {prompt}
+Answer:
+"""
+
+
+class InfinigenGenRouter:
+ """Router that maps task descriptions to room(s) and complexity via GPT."""
+
+ def __init__(
+ self,
+ gpt_client: GPTclient,
+ prompt: str | None = None,
+ ) -> None:
+ """Initialize the router.
+
+ Args:
+ gpt_client: Client used to query the LLM.
+ prompt: Optional custom system/template prompt. Uses
+ DEFAULT_ROUTER_PROMPT if None.
+
+ """
+ self.gpt_client = gpt_client
+ self.prompt = prompt if prompt is not None else DEFAULT_ROUTER_PROMPT
+
+ def query(self, task_description: str) -> tuple[str, str]:
+ """Map a task description to a room and complexity level.
+
+ Args:
+ task_description: Natural-language description of the task or scene.
+
+ Returns:
+ Tuple of (room_name, complexity_level). room_name is one room
+ chosen at random from the list of rooms returned by the LLM.
+
+ """
+ filled_prompt = self.prompt.format(prompt=task_description)
+ response_text = self.gpt_client.query(text_prompt=filled_prompt)
+ if response_text is None:
+ raise RuntimeError(
+ "GPT router got no response. Check the GPT agent setup in "
+ "embodied_gen/utils/gpt_config.yaml and network access."
+ )
+ parsed = json_repair.loads(response_text)
+
+ room_list = parsed[0]
+ complexity = parsed[1]
+ room_name = random.choice(room_list)
+
+ return room_name, complexity
+
+
+def main() -> None:
+ """Demo: run the router on a sample task."""
+ agent = InfinigenGenRouter(gpt_client=GPT_CLIENT)
+ room, complexity = agent.query(
+ "Put the apple into the fruit bowl, complex env"
+ )
+ logger.info(f"Room: {room}, Complexity: {complexity}.")
+
+
+if __name__ == "__main__":
+ main()
diff --git a/embodied_gen/scripts/room_gen/run_generate_indoors.py b/embodied_gen/scripts/room_gen/run_generate_indoors.py
new file mode 100644
index 0000000..738ba9b
--- /dev/null
+++ b/embodied_gen/scripts/room_gen/run_generate_indoors.py
@@ -0,0 +1,31 @@
+# Project EmbodiedGen
+#
+# Copyright (c) 2025 Horizon Robotics. All Rights Reserved.
+#
+# 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.
+
+
+# Wrapper to run infinigen_examples.generate_indoors with correct gin registration.
+# Importing (rather than python -m) ensures the module loads with full module path,
+# avoiding the "Ambiguous selector 'compose_indoors'" error from __main__.
+from embodied_gen.utils.monkey_patch.infinigen import (
+ add_run_main_to_module,
+ monkey_patch_infinigen,
+)
+
+monkey_patch_infinigen()
+
+import infinigen_examples.generate_indoors as gi
+
+add_run_main_to_module(gi)
+gi._run_main()
diff --git a/embodied_gen/scripts/textto3d.py b/embodied_gen/scripts/textto3d.py
index c5fe5f3..140eca2 100644
--- a/embodied_gen/scripts/textto3d.py
+++ b/embodied_gen/scripts/textto3d.py
@@ -17,6 +17,7 @@
import argparse
import os
import random
+import types
from collections import defaultdict
import numpy as np
@@ -25,12 +26,17 @@
from embodied_gen.models.image_comm_model import build_hf_image_pipeline
from embodied_gen.models.segment_model import RembgRemover
from embodied_gen.models.text_model import PROMPT_APPEND
+from embodied_gen.scripts.imageto3d import (
+ IMAGE3D_MODEL,
+ SUPPORTED_IMAGE3D_MODELS,
+)
from embodied_gen.scripts.imageto3d import entrypoint as imageto3d_api
from embodied_gen.utils.gpt_clients import GPT_CLIENT
from embodied_gen.utils.log import logger
from embodied_gen.utils.process_media import (
check_object_edge_truncated,
combine_images_to_grid,
+ parse_text_prompts,
render_asset3d,
)
from embodied_gen.validators.quality_checkers import (
@@ -43,12 +49,32 @@
os.environ["TOKENIZERS_PARALLELISM"] = "false"
random.seed(0)
-logger.info("Loading TEXT2IMG_MODEL...")
-SEMANTIC_CHECKER = SemanticConsistChecker(GPT_CLIENT)
-SEG_CHECKER = ImageSegChecker(GPT_CLIENT)
+# TXTGEN_CHECKER drives the final textโ3D quality gate for every backend
+# (SAM3D / TRELLIS / HUNYUAN3D), so it stays eager.
TXTGEN_CHECKER = TextGenAlignChecker(GPT_CLIENT)
-PIPE_IMG = build_hf_image_pipeline(os.environ.get("TEXT_MODEL", "sd35"))
-BG_REMOVER = RembgRemover()
+
+# The text-to-image stack (PIPE_IMG, BG_REMOVER, SEMANTIC_CHECKER, SEG_CHECKER)
+# is only used on the SAM3D / TRELLIS path. HUNYUAN3D goes directly from prompt to 3D
+SEMANTIC_CHECKER = None
+SEG_CHECKER = None
+PIPE_IMG = None
+BG_REMOVER = None
+
+
+def _ensure_text2img_stack() -> None:
+ """Construct the text-to-image pipeline + image-stage checkers once.
+
+ Called from the SAM3D / TRELLIS path before any ``text_to_image`` run.
+ Idempotent: subsequent calls return immediately.
+ """
+ global SEMANTIC_CHECKER, SEG_CHECKER, PIPE_IMG, BG_REMOVER
+ if PIPE_IMG is not None:
+ return
+ logger.info("Loading TEXT2IMG_MODEL...")
+ SEMANTIC_CHECKER = SemanticConsistChecker(GPT_CLIENT)
+ SEG_CHECKER = ImageSegChecker(GPT_CLIENT)
+ PIPE_IMG = build_hf_image_pipeline(os.environ.get("TEXT_MODEL", "sd35"))
+ BG_REMOVER = RembgRemover()
__all__ = [
@@ -66,6 +92,7 @@ def text_to_image(
image_hw: tuple[int, int] = (1024, 1024),
seed: int = None,
) -> bool:
+ _ensure_text2img_stack()
select_image = None
success_flag = False
assert save_path.endswith(".png"), "Image save path must end with `.png`."
@@ -130,48 +157,115 @@ def text_to_3d(**kwargs) -> dict:
if hasattr(args, k) and v is not None:
setattr(args, k, v)
+ args.image3d_model = str(args.image3d_model).strip().upper()
+ if args.image3d_model not in SUPPORTED_IMAGE3D_MODELS:
+ raise ValueError(
+ f"Unsupported --image3d_model {args.image3d_model!r}; "
+ f"expected one of {SUPPORTED_IMAGE3D_MODELS}."
+ )
+
+ hunyuan_cfg = None
+ hunyuan_creds = None
+ process_prompt = None
+ if args.image3d_model == "HUNYUAN3D":
+ from embodied_gen.models.hunyuan3d import (
+ HunyuanConfig,
+ load_credentials,
+ process_prompt,
+ )
+
+ # Fail fast on missing creds before any network I/O.
+ hunyuan_creds = load_credentials()
+ hunyuan_cfg = HunyuanConfig()
+ logger.info(
+ "HUNYUAN3D text-to-3D backend: action=%s host=%s result_format=%s",
+ hunyuan_cfg.image_action,
+ hunyuan_cfg.host,
+ hunyuan_cfg.result_format,
+ )
+
+ args.prompts = parse_text_prompts(args.prompts)
if args.asset_names is None or len(args.asset_names) == 0:
args.asset_names = [f"sample3d_{i}" for i in range(len(args.prompts))]
- img_save_dir = os.path.join(args.output_root, "images")
asset_save_dir = os.path.join(args.output_root, "asset3d")
- os.makedirs(img_save_dir, exist_ok=True)
os.makedirs(asset_save_dir, exist_ok=True)
+ # HUNYUAN3D path skips text-to-image entirely; the images/ dir only
+ # exists when the local SAM3D / TRELLIS pipeline produces conditioning
+ # images.
+ img_save_dir = os.path.join(args.output_root, "images")
+ if args.image3d_model != "HUNYUAN3D":
+ os.makedirs(img_save_dir, exist_ok=True)
results = defaultdict(dict)
- for prompt, node in zip(args.prompts, args.asset_names):
+ for idx, (prompt, node) in enumerate(zip(args.prompts, args.asset_names)):
success_flag = False
n_pipe_retry = args.n_pipe_retry
seed_img = args.seed_img
seed_3d = args.seed_3d
+ # Tencent Pro API is charged per submit; force a single attempt to
+ # avoid silently multiplying cost when --n_pipe_retry > 1.
+ if args.image3d_model == "HUNYUAN3D" and n_pipe_retry > 1:
+ logger.warning(
+ "HUNYUAN3D mode: --n_pipe_retry forced to 1 (Tencent API "
+ "is charged per submit); user passed %d.",
+ n_pipe_retry,
+ )
+ n_pipe_retry = 1
while success_flag is False and n_pipe_retry > 0:
logger.info(
f"GEN pipeline for node {node}\n"
- f"Try round: {args.n_pipe_retry-n_pipe_retry+1}/{args.n_pipe_retry}, Prompt: {prompt}"
+ f"Try round: {args.n_pipe_retry - n_pipe_retry + 1}/{args.n_pipe_retry}, Prompt: {prompt}"
)
- # Text-to-image GEN
save_node = node.replace(" ", "_")
- gen_image_path = f"{img_save_dir}/{save_node}.png"
- textgen_flag = text_to_image(
- prompt,
- gen_image_path,
- args.n_image_retry,
- args.img_denoise_step,
- args.text_guidance_scale,
- args.n_img_sample,
- seed=seed_img,
- )
-
- # Asset 3D GEN
node_save_dir = f"{asset_save_dir}/{save_node}"
asset_type = node if "sample3d_" not in node else None
- imageto3d_api(
- image_path=[gen_image_path],
- output_root=node_save_dir,
- asset_type=[asset_type],
- seed=random.randint(0, 100000) if seed_3d is None else seed_3d,
- n_retry=args.n_asset_retry,
- keep_intermediate=args.keep_intermediate,
- disable_decompose_convex=args.disable_decompose_convex,
- )
+
+ if args.image3d_model == "HUNYUAN3D":
+ hunyuan_args = types.SimpleNamespace(
+ asset_type=[asset_type],
+ version=None,
+ height_range=None,
+ mass_range=None,
+ disable_decompose_convex=args.disable_decompose_convex,
+ keep_intermediate=args.keep_intermediate,
+ )
+ process_prompt(
+ args=hunyuan_args,
+ idx=0,
+ prompt=prompt,
+ output_root=node_save_dir,
+ filename=save_node,
+ hunyuan_config=hunyuan_cfg,
+ hunyuan_credentials=hunyuan_creds,
+ checkers=[],
+ )
+ else:
+ # Text-to-image GEN (SAM3D / TRELLIS path).
+ gen_image_path = f"{img_save_dir}/{save_node}.png"
+ text_to_image(
+ prompt,
+ gen_image_path,
+ args.n_image_retry,
+ args.img_denoise_step,
+ args.text_guidance_scale,
+ args.n_img_sample,
+ seed=seed_img,
+ )
+
+ # Asset 3D GEN
+ imageto3d_api(
+ image_path=[gen_image_path],
+ output_root=node_save_dir,
+ asset_type=[asset_type],
+ seed=(
+ random.randint(0, 100000)
+ if seed_3d is None
+ else seed_3d
+ ),
+ n_retry=args.n_asset_retry,
+ keep_intermediate=args.keep_intermediate,
+ disable_decompose_convex=args.disable_decompose_convex,
+ image3d_model=args.image3d_model,
+ )
mesh_path = f"{node_save_dir}/result/mesh/{save_node}.obj"
image_path = render_asset3d(
mesh_path,
@@ -272,6 +366,18 @@ def parse_args():
)
parser.add_argument("--keep_intermediate", action="store_true")
parser.add_argument("--disable_decompose_convex", action="store_true")
+ parser.add_argument(
+ "--image3d_model",
+ type=str,
+ default=IMAGE3D_MODEL,
+ help=(
+ "Image-to-3D backend selector forwarded to imageto3d. One of "
+ f"{', '.join(SUPPORTED_IMAGE3D_MODELS)} (case-insensitive). "
+ "HUNYUAN3D skips the text-to-image stage entirely and calls "
+ "Tencent Hunyuan3D Pro text-to-3D directly; it requires "
+ "TENCENT_SECRET_ID/TENCENT_SECRET_KEY in the environment."
+ ),
+ )
args, unknown = parser.parse_known_args()
diff --git a/embodied_gen/skills/README.md b/embodied_gen/skills/README.md
new file mode 100644
index 0000000..1096536
--- /dev/null
+++ b/embodied_gen/skills/README.md
@@ -0,0 +1,62 @@
+# EmbodiedGen Skills
+
+This directory is the canonical home for EmbodiedGen reusable skills.
+
+The root of `embodied_gen/skills` only contains generic skill source.
+Runtime-specific packaging should live in adapter subdirectories such as
+`embodied_gen/skills/claude_plugin/`.
+
+## Included generic skills
+
+- `asset-creator`
+- `asset-retrieval`
+- `background-creator`
+- `layout-creator`
+- `sim-runner`
+- `asset-converter`
+- `asset-process`
+- `room-creator`
+- `spatial-computing`
+
+## Claude plugin package
+
+Claude-compatible slash commands and plugin manifest are under:
+
+```text
+embodied_gen/skills/claude_plugin/
+```
+
+The local marketplace manifest is:
+
+```text
+embodied_gen/skills/claude_plugin/.claude-plugin/marketplace.json
+```
+
+The actual Claude plugin package is:
+
+```text
+embodied_gen/skills/claude_plugin/plugins/embodiedgen/
+```
+
+Current commands include:
+
+- `/embodiedgen:gen_assets`
+- `/embodiedgen:gen_bg`
+- `/embodiedgen:gen_layout`
+- `/embodiedgen:sim`
+- `/embodiedgen:convert`
+- `/embodiedgen:process`
+- `/embodiedgen:gen_indoor`
+- `/embodiedgen:vibe3d`
+
+## Local install for Claude
+
+```bash
+bash install/install_agent_plugin.sh
+```
+
+## Notes
+
+- Generic skills stay in their original directories under `embodied_gen/skills/`.
+- Claude-specific files live only under `embodied_gen/skills/claude_plugin/`.
+- This keeps the skill source portable for Codex, Copilot, and other runtimes.
diff --git a/embodied_gen/skills/asset-converter/SKILL.md b/embodied_gen/skills/asset-converter/SKILL.md
new file mode 100644
index 0000000..13b426c
--- /dev/null
+++ b/embodied_gen/skills/asset-converter/SKILL.md
@@ -0,0 +1,88 @@
+---
+name: asset-converter
+description: Convert EmbodiedGen URDF assets to simulator-specific formats (USD/MJCF/URDF) using embodied_gen.data.asset_converter APIs. Use this skill whenever users ask to export assets for IsaacSim, MuJoCo, Genesis, IsaacGym, PyBullet, or SAPIEN, batch-convert URDF assets, or choose the correct converter/source_type per simulator.
+---
+
+# Asset Converter
+
+Unified entry for simulator-targeted asset conversion using `embodied_gen.data.asset_converter`.
+
+## When To Use
+
+Use this skill when users want to:
+- Convert EmbodiedGen assets for IsaacSim (`USD`) or MuJoCo/Genesis (`MJCF`).
+- Batch-convert multiple URDF assets into simulator-ready outputs.
+- Map simulator names to the correct target format and conversion strategy.
+- Decide when conversion is unnecessary (URDF can be used directly).
+
+## Routing Rules (Core)
+
+1. **IsaacSim** -> convert to `USD`.
+2. **MuJoCo / Genesis** -> convert to `MJCF` (`.xml`).
+3. **SAPIEN / IsaacGym / PyBullet** -> use EmbodiedGen `.urdf` directly (no conversion required).
+
+## Pre-checks
+
+1. Run from repository root with `embodiedgen` environment active.
+2. Confirm input URDF path(s) exist.
+3. For USD conversion, ensure IsaacLab/IsaacSim conversion dependencies are available.
+4. Prefer list inputs for `urdf_files` and `target_dirs` (same length, aligned by index).
+
+## Standard Python API Template
+
+```python
+from embodied_gen.data.asset_converter import cvt_embodiedgen_asset_to_anysim
+from embodied_gen.utils.enum import AssetType, SimAssetMapper
+
+simulator_name = "mujoco" # isaacsim / mujoco / genesis / sapien / isaacgym / pybullet
+
+asset_paths = cvt_embodiedgen_asset_to_anysim(
+ urdf_files=[
+ "outputs/demo_assets/remote_control/result/remote_control.urdf",
+ ],
+ target_dirs=[
+ "outputs/demo_assets/remote_control/mjcf",
+ ],
+ target_type=SimAssetMapper[simulator_name],
+ source_type=AssetType.URDF,
+ overwrite=True,
+)
+print(asset_paths)
+```
+
+## Source Type Guidance
+
+- For `MJCF` target: prefer `source_type=AssetType.URDF`.
+- For `USD` target: use `source_type=AssetType.MESH` by default; `AssetType.URDF` path is also supported when needed.
+- For direct-URDF simulators (`sapien`, `isaacgym`, `pybullet`): skip conversion.
+
+## Direct Converter Template (Advanced)
+
+```python
+from embodied_gen.data.asset_converter import AssetConverterFactory
+from embodied_gen.utils.enum import AssetType
+
+converter = AssetConverterFactory.create(
+ target_type=AssetType.USD,
+ source_type=AssetType.MESH,
+)
+
+with converter:
+ converter.convert(
+ "outputs/demo_assets/remote_control/result/remote_control.urdf",
+ "outputs/demo_assets/remote_control/usd/remote_control.usd",
+ )
+```
+
+## Output Conventions
+
+- `MJCF`: `/.xml`
+- `USD`: `/.usd`
+- API return: `{: }`
+
+## Failure Handling and Retry
+
+1. Unsupported conversion pair: verify `target_type` + `source_type` mapping.
+2. Missing dependencies (USD path): install/activate IsaacLab + required USD stack.
+3. Missing output file: verify parent output directory permissions and path correctness.
+4. Batch mismatch: ensure `len(urdf_files) == len(target_dirs)`.
diff --git a/embodied_gen/skills/asset-creator/SKILL.md b/embodied_gen/skills/asset-creator/SKILL.md
new file mode 100644
index 0000000..3052a23
--- /dev/null
+++ b/embodied_gen/skills/asset-creator/SKILL.md
@@ -0,0 +1,96 @@
+---
+name: asset-creator
+description: Create 3D assets with EmbodiedGen using img3d-cli, text3d-cli, and texture-cli. Use this skill whenever users ask to generate assets from images/text, texture existing meshes, run retry/seed controlled generation, or choose the proper asset-generation CLI from mixed requirements.
+---
+
+# Assets Creator
+
+Unified entry for three EmbodiedGen asset-generation CLIs: `img3d-cli`, `text3d-cli`, and `texture-cli`.
+
+## When To Use
+
+Use this skill when users want to:
+- Generate a 3D asset from one or more input images.
+- Generate 3D assets in batch from text prompts.
+- Generate or edit textures for existing meshes.
+- Get help choosing the correct CLI from mixed asset-generation requirements.
+
+## Routing Rules (Core)
+
+1. `img3d-cli`: input is image paths (`--image_path` or `--image_root`).
+2. `text3d-cli`: input is text prompts (`--prompts`) and target is direct asset output.
+3. `texture-cli`: input is existing mesh path(s) (`--mesh_path`) plus texture prompt(s) (`--prompt`).
+
+## Pre-checks
+
+1. Run commands from the repository root.
+2. Confirm the active environment is `embodiedgen`.
+3. If CLI commands are unavailable, run `pip install -e .` to register entrypoints.
+
+## Standard Command Templates
+
+### 1) Image to 3D: `img3d-cli`
+
+```bash
+img3d-cli --image_path .../sample.jpg --n_retry 1 --output_root outputs/imageto3d
+```
+
+Common parameters:
+- `--image_path` / `--image_root`
+- `--output_root`
+- `--n_retry`
+- `--seed`
+- `--skip_exists`
+
+---
+
+### 2) Text to 3D: `text3d-cli`
+
+```bash
+text3d-cli \
+ --prompts "small bronze figurine of a lion" "A globe with wooden base" \
+ --n_image_retry 1 --n_asset_retry 1 --n_pipe_retry 1 \
+ --seed_img 0 \
+ --output_root outputs/textto3d
+```
+
+Common parameters:
+- `--prompts`
+- `--output_root`
+- `--asset_names`
+- `--n_image_retry --n_asset_retry --n_pipe_retry`
+- `--seed_img --seed_3d`
+
+---
+
+### 3) Mesh Texture Generation: `texture-cli`
+
+```bash
+texture-cli \
+ --mesh_path ".../horse.obj" \
+ --prompt "A gray horse head with flying mane and brown eyes" \
+ --output_root "outputs/texture_gen" \
+ --seed 0
+```
+
+Common parameters:
+- `--mesh_path` (supports multiple inputs)
+- `--prompt` (must align 1:1 with mesh inputs)
+- `--output_root`
+- `--seed`
+- `--texture_size`
+- `--ip_adapt_scale --ip_img_path` (optional reference-image control)
+
+---
+
+## Output Conventions
+
+- `img3d-cli`: each sample is typically under `//result/`.
+- `text3d-cli`: `/asset3d//result/`.
+- `texture-cli`: `//texture_mesh/`.
+
+## Failure Handling and Retry
+
+1. OOM or GPU pressure: reduce batch size and concurrency.
+2. Unstable quality: increase `--n_retry` or `--n_*_retry`.
+3. Missing outputs: verify output-root permissions and path spelling; prefer absolute paths.
diff --git a/embodied_gen/skills/asset-process/SKILL.md b/embodied_gen/skills/asset-process/SKILL.md
new file mode 100644
index 0000000..37ab9ce
--- /dev/null
+++ b/embodied_gen/skills/asset-process/SKILL.md
@@ -0,0 +1,101 @@
+---
+name: asset-process
+description: Scale and rotate complete URDF-based 3D assets, including OBJ, GLB, collision meshes, Gaussian splats, and height metadata. Use this skill whenever a user asks to resize, rotate, reorient, or otherwise transform an EmbodiedGen asset while keeping its related files synchronized.
+---
+
+# Asset Processing
+
+## Overview
+
+Use this skill to scale or rotate an asset and all of its supported geometry
+files consistently. Rotation is provided as XYZ Euler angles in degrees and is
+applied around the asset's local origin.
+
+The input URDF may use either supported asset layout:
+
+```text
+/result/.urdf
+/.urdf
+```
+
+The mesh directory must be beside the URDF as `mesh/`.
+
+Normal mode copies the complete asset directly to ``. Pass the
+full target asset directory, including the desired asset directory name.
+Inplace mode modifies the source asset.
+
+After processing, the tool calculates the transformed mesh's Y-axis extent and
+writes that value to `min_height`, `max_height`, and `real_height` in the URDF.
+It uses the main OBJ first, then the GLB, then the collision OBJ.
+
+## CLI Examples
+
+### Scale Only
+
+```bash
+python -m embodied_gen.skills.asset-process.asset_process \
+ --urdf-path outputs/assets/red_box/result/red_box.urdf \
+ --scale-factor 0.8 \
+ --output-dir outputs/processed
+```
+
+### Rotate Only
+
+```bash
+python -m embodied_gen.skills.asset-process.asset_process \
+ --urdf-path outputs/assets/red_box/result/red_box.urdf \
+ --rot-xyz 90 0 45 \
+ --output-dir outputs/processed
+```
+
+### Scale and Rotate
+
+```bash
+python -m embodied_gen.skills.asset-process.asset_process \
+ --urdf-path outputs/test_wuwen/facial_cleanser_001/facial_cleanser_001.urdf \
+ --scale-factor 1.0 \
+ --rot-xyz 0 90 0 \
+ --keep-urdf-raw-rot \
+ --output-dir outputs/test_wuwen/facial_cleanser_001_processed
+```
+
+`--keep-urdf-raw-rot` is optional. When enabled, the inverse baked rotation is
+composed into visual and collision `origin rpy` values so their effective
+rotation remains unchanged. It is disabled by default.
+
+### Process Inplace
+
+```bash
+python -m embodied_gen.skills.asset-process.asset_process \
+ --urdf-path outputs/assets/red_box/result/red_box.urdf \
+ --scale-factor 0.8 \
+ --rot-xyz 0 0 90 \
+ --inplace
+```
+
+Inplace mode modifies the original asset files.
+
+## Parameters
+
+| Parameter | Default | Meaning |
+|-----------|---------|---------|
+| `urdf_path` | Required | Input URDF path |
+| `scale_factor` | `1.0` | Positive uniform scaling factor |
+| `rot_xyz` | `0 0 0` | XYZ roll, pitch, yaw in degrees |
+| `keep_urdf_raw_rot` | `False` | Preserve the original effective URDF rotation |
+| `inplace` | `False` | Modify source files directly |
+| `output_dir` | `None` | Complete target asset directory; required outside inplace mode |
+
+## Processed Files
+
+- `mesh/.obj`
+- `mesh/.glb`
+- `mesh/_collision.obj`
+- `mesh/_gs.ply` (optional; skipped when absent)
+- `.urdf`
+
+Rotation is always baked into the asset files. URDF visual and collision
+`origin rpy` values are updated only when `keep_urdf_raw_rot` is enabled.
+
+For Python API details, read
+`embodied_gen/skills/asset-process/asset_process.py`.
diff --git a/embodied_gen/skills/asset-process/__init__.py b/embodied_gen/skills/asset-process/__init__.py
new file mode 100644
index 0000000..43b583e
--- /dev/null
+++ b/embodied_gen/skills/asset-process/__init__.py
@@ -0,0 +1,36 @@
+# Project EmbodiedGen
+#
+# Copyright (c) 2025 Horizon Robotics. All Rights Reserved.
+#
+# 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.
+
+"""Asset processing skills for EmbodiedGen."""
+
+from importlib import import_module
+
+__all__ = [
+ "AssetProcessConfig",
+ "AssetProcessor",
+ "entrypoint",
+ "process_asset",
+]
+
+
+def __getattr__(name: str) -> object:
+ """Lazily expose asset processing APIs without import side effects."""
+ if name not in __all__:
+ msg = f"module {__name__!r} has no attribute {name!r}"
+ raise AttributeError(msg)
+
+ module = import_module(f"{__name__}.asset_process")
+ return getattr(module, name)
diff --git a/embodied_gen/skills/asset-process/asset_process.py b/embodied_gen/skills/asset-process/asset_process.py
new file mode 100644
index 0000000..824a082
--- /dev/null
+++ b/embodied_gen/skills/asset-process/asset_process.py
@@ -0,0 +1,443 @@
+# Project EmbodiedGen
+#
+# Copyright (c) 2025 Horizon Robotics. All Rights Reserved.
+#
+# 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.
+
+"""Asset processing utilities for scaling and rotating 3D assets."""
+
+import math
+import shutil
+import warnings
+import xml.etree.ElementTree as ET
+from concurrent.futures import ThreadPoolExecutor
+from dataclasses import dataclass
+from importlib import import_module
+from pathlib import Path
+from typing import Optional
+
+import numpy as np
+import trimesh
+import tyro
+from scipy.spatial.transform import Rotation
+from embodied_gen.utils.log import logger
+
+__all__ = [
+ "AssetProcessConfig",
+ "AssetProcessor",
+ "entrypoint",
+ "process_asset",
+]
+
+URDF_HEIGHT_FIELDS = ("min_height", "max_height", "real_height")
+URDF_RESULT_DIR = "result"
+MESH_DIR = "mesh"
+HEIGHT_AXIS = 1
+
+
+@dataclass
+class AssetProcessConfig:
+ """Configuration for asset scaling and rotation.
+
+ Args:
+ urdf_path: Path to the URDF file to process.
+ scale_factor: Positive uniform scaling factor.
+ rot_xyz: XYZ Euler rotation in degrees.
+ keep_urdf_raw_rot: Whether to preserve the original URDF rotation.
+ inplace: Whether to modify the source asset directly.
+ output_dir: Target asset directory used when ``inplace`` is false.
+ """
+
+ urdf_path: str
+ scale_factor: float = 1.0
+ rot_xyz: tuple[float, float, float] = (0.0, 0.0, 0.0)
+ keep_urdf_raw_rot: bool = False
+ inplace: bool = False
+ output_dir: Optional[str] = None
+
+
+class AssetProcessor:
+ """Scale and rotate mesh, collision, and Gaussian-splat asset files."""
+
+ def __init__(
+ self,
+ urdf_path: str | Path,
+ scale_factor: float = 1.0,
+ rot_xyz: tuple[float, float, float] = (0.0, 0.0, 0.0),
+ keep_urdf_raw_rot: bool = False,
+ output_dir: Optional[str | Path] = None,
+ inplace: bool = False,
+ ) -> None:
+ """Initialize an asset processor.
+
+ Args:
+ urdf_path: Path to the URDF file to process.
+ scale_factor: Positive uniform scaling factor.
+ rot_xyz: XYZ Euler rotation in degrees.
+ keep_urdf_raw_rot: Whether to preserve the original visual and
+ collision rotation after baking the asset rotation.
+ output_dir: Target asset directory used when ``inplace`` is false.
+ inplace: Whether to modify the source asset directly.
+
+ Raises:
+ FileNotFoundError: If the URDF file does not exist.
+ ValueError: If the transform or output configuration is invalid.
+ """
+ self.urdf_path = Path(urdf_path)
+ self.scale_factor = float(scale_factor)
+ self.rot_xyz = tuple(float(value) for value in rot_xyz)
+ self.keep_urdf_raw_rot = keep_urdf_raw_rot
+ self.inplace = inplace
+
+ if not self.urdf_path.is_file():
+ raise FileNotFoundError(f"URDF file not found: {self.urdf_path}")
+ if not math.isfinite(self.scale_factor) or self.scale_factor <= 0:
+ raise ValueError(
+ f"Scale factor must be positive and finite, got: "
+ f"{self.scale_factor}"
+ )
+ if len(self.rot_xyz) != 3 or not all(
+ math.isfinite(value) for value in self.rot_xyz
+ ):
+ raise ValueError(
+ "rot_xyz must contain three finite degree values, got: "
+ f"{self.rot_xyz}"
+ )
+
+ if self.urdf_path.parent.name == URDF_RESULT_DIR:
+ self.asset_dir = self.urdf_path.parent.parent
+ self.result_dir = Path(URDF_RESULT_DIR)
+ else:
+ self.asset_dir = self.urdf_path.parent
+ self.result_dir = Path()
+ self.node_name = self.urdf_path.stem
+ self.rotation = Rotation.from_euler("xyz", self.rot_xyz, degrees=True)
+ self.rotation_transform = np.eye(4)
+ self.rotation_transform[:3, :3] = self.rotation.as_matrix()
+
+ if self.inplace:
+ self.output_dir = self.asset_dir.parent
+ else:
+ if output_dir is None:
+ raise ValueError("output_dir is required when inplace=False")
+ self.output_dir = Path(output_dir)
+ if self.output_dir.resolve() == self.asset_dir.resolve():
+ raise ValueError(
+ "output_dir must differ from the source asset directory; "
+ "use inplace=True to modify the source asset"
+ )
+
+ def process(self) -> Path:
+ """Run the complete asset processing workflow.
+
+ Returns:
+ Path to the processed URDF file.
+ """
+ if self.inplace:
+ output_asset_dir = self.asset_dir
+ output_urdf_path = self.urdf_path
+ else:
+ output_asset_dir = self.output_dir
+ output_urdf_path = self._copy_asset_structure(output_asset_dir)
+
+ self._process_asset_files_parallel(output_asset_dir)
+ actual_height = self._calculate_actual_height(output_asset_dir)
+ self._write_urdf_height(output_urdf_path, actual_height)
+ if self.keep_urdf_raw_rot:
+ self._update_urdf_origins(output_urdf_path)
+
+ logger.info(
+ "Processed %s with scale=%s, rot_xyz=%s -> %s",
+ self.asset_dir,
+ self.scale_factor,
+ self.rot_xyz,
+ output_asset_dir,
+ )
+ return output_urdf_path
+
+ def _copy_asset_structure(self, output_asset_dir: Path) -> Path:
+ """Copy the complete source asset to the output directory."""
+ shutil.rmtree(output_asset_dir, ignore_errors=True)
+ shutil.copytree(self.asset_dir, output_asset_dir)
+ return output_asset_dir / self.result_dir / f"{self.node_name}.urdf"
+
+ def _process_asset_files_parallel(self, output_asset_dir: Path) -> None:
+ """Apply the configured transform to every supported asset format."""
+ mesh_dir = output_asset_dir / self.result_dir / MESH_DIR
+
+ # Keep the legacy asset-scale processing paths separate because each
+ # format has different loading and export behavior.
+ tasks = [
+ (mesh_dir / f"{self.node_name}.obj", self._scale_obj_mesh),
+ (mesh_dir / f"{self.node_name}.glb", self._scale_glb_mesh),
+ (
+ mesh_dir / f"{self.node_name}_collision.obj",
+ self._scale_collision_mesh,
+ ),
+ (
+ mesh_dir / f"{self.node_name}_gs.ply",
+ self._scale_gaussian_splat,
+ ),
+ ]
+
+ with ThreadPoolExecutor(max_workers=4) as executor:
+ futures = [executor.submit(task, path) for path, task in tasks]
+ for future in futures:
+ future.result() # Propagate any exceptions
+
+ def _scale_obj_mesh(self, mesh_path: Path) -> None:
+ """Scale and rotate an OBJ mesh."""
+ if not mesh_path.exists():
+ return
+
+ mesh = trimesh.load(str(mesh_path))
+ mesh.apply_scale(self.scale_factor)
+ self._apply_mesh_rotation(mesh)
+ mesh.export(str(mesh_path))
+
+ def _scale_glb_mesh(self, mesh_path: Path) -> None:
+ """Scale and rotate every geometry in a GLB scene."""
+ if not mesh_path.exists():
+ return
+
+ mesh = trimesh.load(str(mesh_path))
+ for mesh_part in mesh.geometry.values():
+ mesh_part.apply_scale(self.scale_factor)
+ self._apply_mesh_rotation(mesh_part)
+ mesh.export(str(mesh_path))
+
+ def _scale_collision_mesh(self, mesh_path: Path) -> None:
+ """Scale and rotate a potentially multi-object collision OBJ."""
+ if not mesh_path.exists():
+ return
+
+ meshes = self._load_collision_obj(str(mesh_path))
+ scene = trimesh.Scene()
+ for mesh_part in meshes:
+ mesh_part.apply_scale(self.scale_factor)
+ self._apply_mesh_rotation(mesh_part)
+ scene.add_geometry(mesh_part)
+ scene.export(str(mesh_path))
+
+ def _scale_gaussian_splat(self, mesh_path: Path) -> None:
+ """Scale and rotate a Gaussian splatting model."""
+ if not mesh_path.exists():
+ return
+
+ gaussian_operator = import_module(
+ "embodied_gen.models.gs_model"
+ ).GaussianOperator
+ gs_model = gaussian_operator.load_from_ply(str(mesh_path))
+ gs_model.rescale(self.scale_factor)
+ if not self._is_identity_rotation:
+ instance_pose = gaussian_operator.trans_to_quatpose(
+ self.rotation.as_matrix()
+ )
+ gs_model = gs_model.get_gaussians(instance_pose=instance_pose)
+ gs_model.save_to_ply(str(mesh_path))
+
+ @property
+ def _is_identity_rotation(self) -> bool:
+ """Return whether the configured rotation is an identity operation."""
+ return np.allclose(self.rotation.as_matrix(), np.eye(3))
+
+ def _apply_mesh_rotation(self, mesh: trimesh.Trimesh) -> None:
+ """Apply the configured rotation after the legacy scale operation."""
+ if self._is_identity_rotation:
+ return
+ mesh.apply_transform(self.rotation_transform)
+
+ def _calculate_actual_height(self, output_asset_dir: Path) -> float:
+ """Calculate Y-axis height from the best available transformed mesh."""
+ mesh_dir = output_asset_dir / self.result_dir / MESH_DIR
+ candidates = (
+ mesh_dir / f"{self.node_name}.obj",
+ mesh_dir / f"{self.node_name}.glb",
+ mesh_dir / f"{self.node_name}_collision.obj",
+ )
+ for mesh_path in candidates:
+ if not mesh_path.exists():
+ continue
+ scene = trimesh.load(str(mesh_path), force="scene")
+ geometry = scene.to_geometry()
+ if len(geometry.vertices) == 0:
+ continue
+ height = float(np.ptp(geometry.vertices[:, HEIGHT_AXIS]))
+ if not math.isfinite(height):
+ raise ValueError(
+ f"Calculated non-finite height from mesh: {mesh_path}"
+ )
+ return height
+
+ candidate_text = ", ".join(str(path) for path in candidates)
+ raise FileNotFoundError(
+ "No mesh is available for height calculation. Expected one of: "
+ f"{candidate_text}"
+ )
+
+ @staticmethod
+ def _write_urdf_height(urdf_path: Path, height: float) -> None:
+ """Write actual mesh height to all URDF height metadata fields."""
+ tree = ET.parse(str(urdf_path))
+ root = tree.getroot()
+ link = root.find("link")
+ if link is None:
+ raise ValueError(f"No link element found in URDF: {urdf_path}")
+
+ extra_info = link.find("extra_info")
+ if extra_info is None:
+ extra_info = ET.SubElement(link, "extra_info")
+
+ value = f"{height:.4f}"
+ for field in URDF_HEIGHT_FIELDS:
+ element = extra_info.find(field)
+ if element is None:
+ element = ET.SubElement(extra_info, field)
+ element.text = value
+
+ tree.write(str(urdf_path), encoding="utf-8", xml_declaration=True)
+
+ def _update_urdf_origins(self, urdf_path: Path) -> None:
+ """Append the inverse baked rotation to visual/collision origins."""
+ if self._is_identity_rotation:
+ return
+
+ tree = ET.parse(str(urdf_path))
+ root = tree.getroot()
+ inverse_rotation = self.rotation.inv().as_matrix()
+
+ for link in root.findall(".//link"):
+ for geometry_tag in ("visual", "collision"):
+ for geometry in link.findall(geometry_tag):
+ origin = geometry.find("origin")
+ if origin is None:
+ origin = ET.SubElement(
+ geometry,
+ "origin",
+ attrib={"xyz": "0 0 0"},
+ )
+
+ current_rpy = np.fromstring(
+ origin.get("rpy", "0 0 0"),
+ sep=" ",
+ )
+ if current_rpy.size != 3:
+ raise ValueError(
+ f"Invalid rpy in URDF origin: {urdf_path}"
+ )
+
+ current_rotation = Rotation.from_euler(
+ "xyz",
+ current_rpy,
+ degrees=False,
+ ).as_matrix()
+ updated_rotation = current_rotation @ inverse_rotation
+ with warnings.catch_warnings():
+ warnings.simplefilter("ignore", UserWarning)
+ updated_rpy = Rotation.from_matrix(
+ updated_rotation
+ ).as_euler("xyz", degrees=False)
+ origin.set(
+ "rpy",
+ " ".join(f"{value:.4f}" for value in updated_rpy),
+ )
+
+ tree.write(str(urdf_path), encoding="utf-8", xml_declaration=True)
+
+ @staticmethod
+ def _load_collision_obj(filepath: str) -> list[trimesh.Trimesh]:
+ """Robustly load collision OBJ with multiple objects.
+
+ Handles OBJ files with multiple objects/groups by parsing manually
+ to avoid issues with trimesh's default loader.
+
+ Args:
+ filepath: Path to collision OBJ file.
+
+ Returns:
+ List of trimesh objects, one per object group in the file.
+ """
+ vertices = []
+ meshes = []
+ current_faces = []
+
+ # Use lazy iteration instead of readlines() for memory efficiency
+ with open(filepath, "r") as f:
+ for line in f:
+ if line.startswith("v "):
+ parts = line.split()
+ vertices.append(
+ [float(parts[1]), float(parts[2]), float(parts[3])]
+ )
+ elif line.startswith("f "):
+ parts = line.split()
+ face = [int(p.split("/")[0]) - 1 for p in parts[1:]]
+ current_faces.append(face)
+ elif line.startswith("o ") or line.startswith("g "):
+ if current_faces and vertices:
+ m = trimesh.Trimesh(
+ vertices=vertices,
+ faces=current_faces,
+ process=False,
+ )
+ m.remove_unreferenced_vertices()
+ meshes.append(m)
+ current_faces = []
+
+ # Flush final mesh
+ if current_faces and vertices:
+ m = trimesh.Trimesh(
+ vertices=vertices, faces=current_faces, process=False
+ )
+ m.remove_unreferenced_vertices()
+ meshes.append(m)
+
+ return meshes
+
+
+def process_asset(
+ urdf_path: str | Path,
+ scale_factor: float = 1.0,
+ rot_xyz: tuple[float, float, float] = (0.0, 0.0, 0.0),
+ keep_urdf_raw_rot: bool = False,
+ output_dir: Optional[str | Path] = None,
+ inplace: bool = False,
+) -> Path:
+ """Scale and rotate a complete URDF-based asset."""
+ processor = AssetProcessor(
+ urdf_path=urdf_path,
+ scale_factor=scale_factor,
+ rot_xyz=rot_xyz,
+ keep_urdf_raw_rot=keep_urdf_raw_rot,
+ output_dir=output_dir,
+ inplace=inplace,
+ )
+ return processor.process()
+
+
+def entrypoint() -> None:
+ """Run the asset processing CLI."""
+ config = tyro.cli(AssetProcessConfig)
+ output_urdf = process_asset(
+ urdf_path=config.urdf_path,
+ scale_factor=config.scale_factor,
+ rot_xyz=config.rot_xyz,
+ keep_urdf_raw_rot=config.keep_urdf_raw_rot,
+ output_dir=config.output_dir,
+ inplace=config.inplace,
+ )
+ logger.info(f"Processed asset successfully: {output_urdf}")
+
+
+if __name__ == "__main__":
+ entrypoint()
diff --git a/embodied_gen/skills/asset-retrieval/SKILL.md b/embodied_gen/skills/asset-retrieval/SKILL.md
new file mode 100644
index 0000000..07785d6
--- /dev/null
+++ b/embodied_gen/skills/asset-retrieval/SKILL.md
@@ -0,0 +1,87 @@
+---
+name: asset-retrieval
+description: Retrieve existing EmbodiedGen assets from a configurable dataset index CSV by natural-language descriptions and return matching `.urdf` paths. Use when users describe an asset they want, ask to find one or several existing URDF assets, or need a fast lookup over the local asset index instead of scanning folders manually.
+---
+
+# Asset Retrieval
+
+Look up simulation-ready assets from `dataset_index.csv` and return `.urdf`
+paths. The CSV index is the single source of truth.
+
+## Workflow
+
+**Preferred โ agent reads CSV directly:**
+
+1. Read `dataset_index.csv` into context.
+2. Semantically match the user's description (open-ended, fuzzy, or in any
+ language) against `category`, `secondary_category`, `primary_category`,
+ and `description` columns.
+3. Return the best-matching absolute `.urdf` path; return multiple candidates
+ when the request is broad or explicitly asks for several.
+4. Briefly explain why the returned asset matches.
+
+This path handles open-ended queries like "a tall chair suitable for a
+coffee shop" or "่ฝๆพๅจๅฎขๅ
่ง่ฝ็่ฝๅฐ็ฏ" that pure keyword matching cannot
+resolve.
+
+**Fallback โ CLI script (no network / no LLM):**
+
+When the agent is unavailable, use the helper script which performs offline
+keyword-based ranking:
+
+```bash
+python embodied_gen/skills/asset-retrieval/scripts/retrieve_asset.py \
+ "modern dining chair curved backrest"
+```
+
+For the CLI path, rewrite open-ended or Chinese descriptions into compact
+English keywords first (e.g. `่ฝๆพๅจๅฎขๅ
่ง่ฝ็่ฝๅฐ็ฏ` โ `floor lamp`).
+
+## Index Resolution
+
+Checked in order โ first match wins:
+
+1. `--index-file` CLI argument
+2. `$EMBODIEDGEN_DATASET_INDEX` environment variable
+3. `$EMBODIEDGEN_DATASET_ROOT/dataset_index.csv`
+4. `/outputs/EmbodiedGenData/dataset/dataset_index.csv`
+
+Dataset root follows a parallel order (`--dataset-root` โ
+`$EMBODIEDGEN_DATASET_ROOT` โ repo default).
+
+### Required CSV Columns
+
+`uuid`, `primary_category`, `secondary_category`, `category`, `description`,
+`generate_time`, `urdf_path`
+
+## Query Guidelines
+
+- Use explicit object words: `chair`, `bar stool`, `remote control`.
+- Keep discriminating modifiers: `wooden`, `orange`, `modern`, `round`.
+- Open-ended or Chinese descriptions are fine for the agent path; rewrite
+ to English keywords only when using the CLI script.
+
+## Script Usage
+
+```bash
+# Single best match (absolute path on stdout)
+python embodied_gen/skills/asset-retrieval/scripts/retrieve_asset.py \
+ "modern dining chair curved backrest"
+
+# Multiple candidates with scores
+python embodied_gen/skills/asset-retrieval/scripts/retrieve_asset.py \
+ "orange cushioned bar stool" \
+ --top-k 5 --format json
+
+# Custom dataset location
+python embodied_gen/skills/asset-retrieval/scripts/retrieve_asset.py \
+ "black remote control" \
+ --dataset-root /path/to/dataset \
+ --index-file /path/to/dataset/dataset_index.csv
+
+# Relative paths instead of absolute
+python embodied_gen/skills/asset-retrieval/scripts/retrieve_asset.py \
+ "wooden bar stool" --relative-paths
+```
+
+Exit code 1 with `"No matching assets found."` on stderr when nothing matches.
diff --git a/embodied_gen/skills/asset-retrieval/scripts/retrieve_asset.py b/embodied_gen/skills/asset-retrieval/scripts/retrieve_asset.py
new file mode 100644
index 0000000..4b47ddd
--- /dev/null
+++ b/embodied_gen/skills/asset-retrieval/scripts/retrieve_asset.py
@@ -0,0 +1,329 @@
+#!/usr/bin/env python3
+"""Retrieve EmbodiedGen asset URDF paths from a CSV index."""
+
+from __future__ import annotations
+
+import argparse
+import csv
+import json
+import os
+import re
+import unicodedata
+from dataclasses import dataclass
+from pathlib import Path
+
+STOP_WORDS = {
+ "a",
+ "an",
+ "and",
+ "asset",
+ "for",
+ "in",
+ "of",
+ "on",
+ "or",
+ "that",
+ "the",
+ "to",
+ "with",
+}
+TOKEN_PATTERN = re.compile(r"[a-z0-9]+")
+
+
+@dataclass(frozen=True)
+class AssetRecord:
+ """Single asset entry from the CSV index."""
+
+ uuid: str
+ primary_category: str
+ secondary_category: str
+ category: str
+ description: str
+ generate_time: str
+ relative_urdf_path: str
+ absolute_urdf_path: str
+ search_text: str
+ primary_tokens: frozenset[str]
+ secondary_tokens: frozenset[str]
+ category_tokens: frozenset[str]
+ description_tokens: frozenset[str]
+
+
+@dataclass(frozen=True)
+class SearchResult:
+ """Ranked retrieval result."""
+
+ score: float
+ coverage: float
+ record: AssetRecord
+
+ def to_dict(self, use_relative_paths: bool) -> dict[str, object]:
+ """Convert the result to JSON-friendly output."""
+ urdf_path = (
+ self.record.relative_urdf_path
+ if use_relative_paths
+ else self.record.absolute_urdf_path
+ )
+ return {
+ "urdf_path": urdf_path,
+ "score": round(self.score, 3),
+ "coverage": round(self.coverage, 3),
+ "uuid": self.record.uuid,
+ "primary_category": self.record.primary_category,
+ "secondary_category": self.record.secondary_category,
+ "category": self.record.category,
+ "description": self.record.description,
+ "generate_time": self.record.generate_time,
+ }
+
+
+def _repo_root() -> Path:
+ return Path(__file__).resolve().parents[4]
+
+
+def _default_dataset_root() -> Path:
+ configured_root = os.getenv("EMBODIEDGEN_DATASET_ROOT")
+ if configured_root:
+ return Path(configured_root).expanduser().resolve()
+ return _repo_root() / "outputs" / "EmbodiedGenData" / "dataset"
+
+
+def _default_index_file(dataset_root: Path) -> Path:
+ configured_index = os.getenv("EMBODIEDGEN_DATASET_INDEX")
+ if configured_index:
+ return Path(configured_index).expanduser().resolve()
+ return dataset_root / "dataset_index.csv"
+
+
+def _normalize_text(text: str) -> str:
+ text = unicodedata.normalize("NFKC", text or "").lower()
+ text = text.replace("_", " ").replace("-", " ").replace("&", " and ")
+ text = re.sub(r"[^0-9a-z\s]", " ", text)
+ return re.sub(r"\s+", " ", text).strip()
+
+
+def _normalize_token(token: str) -> str:
+ if token.endswith("ies") and len(token) > 4:
+ return token[:-3] + "y"
+ if (
+ token.endswith("s")
+ and len(token) > 3
+ and not token.endswith(("ss", "us"))
+ ):
+ return token[:-1]
+ return token
+
+
+def _tokenize(text: str) -> list[str]:
+ tokens: list[str] = []
+ for raw_token in TOKEN_PATTERN.findall(_normalize_text(text)):
+ token = _normalize_token(raw_token)
+ if len(token) < 2 or token in STOP_WORDS:
+ continue
+ tokens.append(token)
+ return tokens
+
+
+def _dedupe_tokens(tokens: list[str]) -> list[str]:
+ deduped: list[str] = []
+ seen: set[str] = set()
+ for token in tokens:
+ if token in seen:
+ continue
+ deduped.append(token)
+ seen.add(token)
+ return deduped
+
+
+def load_records(index_file: Path, dataset_root: Path) -> list[AssetRecord]:
+ """Load asset records from dataset_index.csv."""
+ records: list[AssetRecord] = []
+ with index_file.open(newline="", encoding="utf-8") as csv_file:
+ reader = csv.DictReader(csv_file)
+ for row in reader:
+ relative_urdf_path = (row.get("urdf_path") or "").strip()
+ absolute_urdf_path = str(
+ (dataset_root / relative_urdf_path).resolve()
+ )
+
+ primary_category = row.get("primary_category", "")
+ secondary_category = row.get("secondary_category", "")
+ category = row.get("category", "")
+ description = row.get("description", "")
+
+ records.append(
+ AssetRecord(
+ uuid=row.get("uuid", ""),
+ primary_category=primary_category,
+ secondary_category=secondary_category,
+ category=category,
+ description=description,
+ generate_time=row.get("generate_time", ""),
+ relative_urdf_path=relative_urdf_path,
+ absolute_urdf_path=absolute_urdf_path,
+ search_text=" ".join(
+ part
+ for part in (
+ _normalize_text(primary_category),
+ _normalize_text(secondary_category),
+ _normalize_text(category),
+ _normalize_text(description),
+ )
+ if part
+ ),
+ primary_tokens=frozenset(_tokenize(primary_category)),
+ secondary_tokens=frozenset(_tokenize(secondary_category)),
+ category_tokens=frozenset(_tokenize(category)),
+ description_tokens=frozenset(_tokenize(description)),
+ )
+ )
+ return records
+
+
+def _score_record(
+ record: AssetRecord,
+ query_text: str,
+ query_tokens: list[str],
+) -> SearchResult | None:
+ matched_tokens = 0
+ score = 0.0
+
+ for token in query_tokens:
+ token_score = 0.0
+ if token in record.category_tokens:
+ token_score = max(token_score, 8.0)
+ if token in record.secondary_tokens:
+ token_score = max(token_score, 5.0)
+ if token in record.primary_tokens:
+ token_score = max(token_score, 3.0)
+ if token in record.description_tokens:
+ token_score = max(token_score, 2.0)
+
+ if token_score > 0:
+ matched_tokens += 1
+ score += token_score
+
+ if query_text and query_text in record.search_text:
+ score += 8.0
+
+ if matched_tokens == 0:
+ return None
+
+ coverage = matched_tokens / len(query_tokens)
+ score += 4.0 * coverage
+ return SearchResult(score=score, coverage=coverage, record=record)
+
+
+def search_assets(
+ records: list[AssetRecord],
+ query: str,
+ top_k: int,
+) -> list[SearchResult]:
+ """Return top-k lexical matches for a query."""
+ query_text = _normalize_text(query)
+ query_tokens = _dedupe_tokens(_tokenize(query))
+ if not query_text or not query_tokens:
+ raise ValueError(
+ "Query must contain searchable keywords after normalization."
+ )
+
+ ranked: list[SearchResult] = []
+ for record in records:
+ result = _score_record(record, query_text, query_tokens)
+ if result is not None:
+ ranked.append(result)
+
+ ranked.sort(
+ key=lambda result: (
+ -result.score,
+ -result.coverage,
+ -int(result.record.generate_time or 0),
+ result.record.absolute_urdf_path,
+ ),
+ )
+ return ranked[:top_k]
+
+
+def _parse_args() -> argparse.Namespace:
+ parser = argparse.ArgumentParser(
+ description="Retrieve EmbodiedGen asset URDF paths from dataset_index.csv."
+ )
+ parser.add_argument("query", help="Natural-language asset query.")
+ parser.add_argument(
+ "--dataset-root",
+ default=str(_default_dataset_root()),
+ help=(
+ "Dataset root. "
+ "Default: $EMBODIEDGEN_DATASET_ROOT or repo-relative dataset path."
+ ),
+ )
+ parser.add_argument(
+ "--index-file",
+ default=None,
+ help=(
+ "CSV index path. "
+ "Default: $EMBODIEDGEN_DATASET_INDEX or /dataset_index.csv."
+ ),
+ )
+ parser.add_argument(
+ "--top-k",
+ type=int,
+ default=1,
+ help="Number of matches to return.",
+ )
+ parser.add_argument(
+ "--format",
+ choices=("paths", "json"),
+ default="paths",
+ help="Output format.",
+ )
+ parser.add_argument(
+ "--relative-paths",
+ action="store_true",
+ help="Return dataset-relative URDF paths instead of absolute paths.",
+ )
+ return parser.parse_args()
+
+
+def main() -> int:
+ args = _parse_args()
+ if args.top_k < 1:
+ raise ValueError("--top-k must be >= 1")
+
+ dataset_root = Path(args.dataset_root).expanduser().resolve()
+ index_file = (
+ Path(args.index_file).expanduser().resolve()
+ if args.index_file
+ else _default_index_file(dataset_root)
+ )
+ if not index_file.exists():
+ raise FileNotFoundError(f"Dataset index not found: {index_file}")
+
+ records = load_records(index_file=index_file, dataset_root=dataset_root)
+ results = search_assets(
+ records=records, query=args.query, top_k=args.top_k
+ )
+ if not results:
+ raise SystemExit("No matching assets found.")
+
+ if args.format == "json":
+ payload = [
+ result.to_dict(use_relative_paths=args.relative_paths)
+ for result in results
+ ]
+ print(json.dumps(payload, ensure_ascii=False, indent=2))
+ return 0
+
+ for result in results:
+ urdf_path = (
+ result.record.relative_urdf_path
+ if args.relative_paths
+ else result.record.absolute_urdf_path
+ )
+ print(urdf_path)
+
+ return 0
+
+
+if __name__ == "__main__":
+ raise SystemExit(main())
diff --git a/embodied_gen/skills/background-creator/SKILL.md b/embodied_gen/skills/background-creator/SKILL.md
new file mode 100644
index 0000000..ad93e42
--- /dev/null
+++ b/embodied_gen/skills/background-creator/SKILL.md
@@ -0,0 +1,70 @@
+---
+name: background-creator
+description: Generate background 3D scenes with EmbodiedGen using scene3d-cli. Use this skill whenever users ask to create room/indoor background scenes from text prompts, pre-generate backgrounds for layout-cli, or control scene3d generation quality/runtime with retry, seed, and gs3d settings.
+---
+
+# Background Creator
+
+Unified entry for EmbodiedGen background scene generation via `scene3d-cli`.
+
+## When To Use
+
+Use this skill when users want to:
+- Generate indoor/background 3D scenes from text prompts.
+- Pre-generate scene assets for `layout-cli`.
+- Control `scene3d-cli` runtime/quality via seed, retry, and `gs3d` settings.
+
+## Routing Rule (Core)
+
+Use `scene3d-cli` when input is scene-level text prompts and output target is a background scene (mesh + 3DGS), not single foreground assets.
+
+## Pre-checks
+
+1. Run commands from the repository root.
+2. Confirm the active environment is `embodiedgen`.
+3. Install scene3d dependencies first if needed:
+ `bash install.sh scene3d`
+4. If CLI commands are unavailable, run `pip install -e .` to register entrypoints.
+
+## Standard Command Template
+
+```bash
+scene3d-cli --prompts "Art studio with easel and canvas" \
+ --output_dir outputs/bg_scenes \
+ --seed 0 \
+ --gs3d.max_steps 4000 \
+ --disable_pano_check
+```
+
+## Common Parameters
+
+- `--prompts`: one or more scene text prompts.
+- `--output_dir`: output root directory for generated scenes.
+- `--seed`: random seed for reproducibility.
+- `--n_retry`: panorama generation retries.
+- `--real_height`: force target real-world room height in meters.
+- `--pano_image_only`: generate only panorama image (debug/fast validation).
+- `--disable_pano_check`: skip panorama quality check.
+- `--keep_middle_result`: keep intermediate training artifacts.
+- `--gs3d.max_steps`: training steps for 3DGS optimization.
+
+## Output Conventions
+
+Each prompt is saved under `/scene_xxxx/`, typically including:
+- `gs_model.ply`
+- `mesh_model.ply`
+- `pano_image.png`
+- `prompt.txt`
+- `video.mp4`
+- `gsplat_cfg.yml`
+
+## Runtime Expectations
+
+- Typical full generation time is around 30 minutes per scene.
+- Use `--pano_image_only` for quick prompt validation before full generation.
+
+## Failure Handling and Retry
+
+1. OOM or GPU pressure: reduce concurrency and lower `--gs3d.max_steps`.
+2. Unstable scene quality: increase `--n_retry` or adjust prompt specificity.
+3. Missing outputs: verify `--output_dir` permissions and use absolute paths.
diff --git a/embodied_gen/skills/claude_plugin/plugins/embodiedgen/commands/convert.md b/embodied_gen/skills/claude_plugin/plugins/embodiedgen/commands/convert.md
new file mode 100644
index 0000000..58c585c
--- /dev/null
+++ b/embodied_gen/skills/claude_plugin/plugins/embodiedgen/commands/convert.md
@@ -0,0 +1,32 @@
+---
+description: Run the EmbodiedGen simulator asset conversion workflow for USD, MJCF, or direct URDF usage
+argument-hint: "[target simulator or conversion request]"
+---
+
+# Convert Skill Command
+
+Route the user's request to the EmbodiedGen asset conversion workflow.
+
+## Workflow
+
+### Step 1: Interpret the request
+
+Use `$ARGUMENTS` if provided. If it is empty, ask for the target simulator and input URDF path.
+
+### Step 2: Load the skill
+
+Use `skill: "embodiedgen:asset-converter"`.
+
+### Step 3: Execute the workflow
+
+Follow the skill and choose:
+- `USD` for IsaacSim
+- `MJCF` for MuJoCo or Genesis
+- direct `URDF` for SAPIEN, IsaacGym, or PyBullet
+
+### Step 4: Deliver
+
+Return:
+1. The exact Python API or command used
+2. The converted output path
+3. Any dependency or simulator-specific notes
diff --git a/embodied_gen/skills/claude_plugin/plugins/embodiedgen/commands/gen_assets.md b/embodied_gen/skills/claude_plugin/plugins/embodiedgen/commands/gen_assets.md
new file mode 100644
index 0000000..1b00761
--- /dev/null
+++ b/embodied_gen/skills/claude_plugin/plugins/embodiedgen/commands/gen_assets.md
@@ -0,0 +1,32 @@
+---
+description: Run the EmbodiedGen asset generation workflow for image-to-3D, text-to-3D, or texture generation
+argument-hint: "[request or command requirements]"
+---
+
+# Gen Assets Skill Command
+
+Route the user's request to the EmbodiedGen asset generation workflow.
+
+## Workflow
+
+### Step 1: Interpret the request
+
+Use `$ARGUMENTS` if provided. If it is empty, ask what the user wants to generate or texture.
+
+### Step 2: Load the skill
+
+Use `skill: "embodiedgen:asset-creator"`.
+
+### Step 3: Execute the correct route
+
+Follow the skill to choose one of:
+- `img3d-cli`
+- `text3d-cli`
+- `texture-cli`
+
+### Step 4: Deliver
+
+Return:
+1. The exact command used
+2. The output directory
+3. Any important runtime notes or dependency issues
diff --git a/embodied_gen/skills/claude_plugin/plugins/embodiedgen/commands/gen_bg.md b/embodied_gen/skills/claude_plugin/plugins/embodiedgen/commands/gen_bg.md
new file mode 100644
index 0000000..bf3924a
--- /dev/null
+++ b/embodied_gen/skills/claude_plugin/plugins/embodiedgen/commands/gen_bg.md
@@ -0,0 +1,29 @@
+---
+description: Run the EmbodiedGen background scene generation workflow with scene3d-cli
+argument-hint: "[scene prompt or generation request]"
+---
+
+# Gen Bg Skill Command
+
+Route the user's request to the EmbodiedGen background generation workflow.
+
+## Workflow
+
+### Step 1: Interpret the request
+
+Use `$ARGUMENTS` if provided. If it is empty, ask for the target room or background scene description.
+
+### Step 2: Load the skill
+
+Use `skill: "embodiedgen:background-creator"`.
+
+### Step 3: Execute the workflow
+
+Follow the skill and build the correct `scene3d-cli` command.
+
+### Step 4: Deliver
+
+Return:
+1. The exact command used
+2. The output directory
+3. Expected runtime and any caveats
diff --git a/embodied_gen/skills/claude_plugin/plugins/embodiedgen/commands/gen_indoor.md b/embodied_gen/skills/claude_plugin/plugins/embodiedgen/commands/gen_indoor.md
new file mode 100644
index 0000000..3a1bcb6
--- /dev/null
+++ b/embodied_gen/skills/claude_plugin/plugins/embodiedgen/commands/gen_indoor.md
@@ -0,0 +1,29 @@
+---
+description: Run the EmbodiedGen room generation workflow with room-creator for room or house generation and export
+argument-hint: "[room generation request]"
+---
+
+# Gen Indoor Skill Command
+
+Route the user's request to the EmbodiedGen room creation workflow.
+
+## Workflow
+
+### Step 1: Interpret the request
+
+Use `$ARGUMENTS` if provided. If it is empty, ask for room type, output root, and whether export is needed.
+
+### Step 2: Load the skill
+
+Use `skill: "embodiedgen:room-creator"`.
+
+### Step 3: Execute the workflow
+
+Follow the skill and build the correct `python -m embodied_gen.scripts.room_gen.gen_room` or `room-cli` command.
+
+### Step 4: Deliver
+
+Return:
+1. The exact command used
+2. The output directory
+3. Runtime and export-stage notes
diff --git a/embodied_gen/skills/claude_plugin/plugins/embodiedgen/commands/gen_layout.md b/embodied_gen/skills/claude_plugin/plugins/embodiedgen/commands/gen_layout.md
new file mode 100644
index 0000000..93453d7
--- /dev/null
+++ b/embodied_gen/skills/claude_plugin/plugins/embodiedgen/commands/gen_layout.md
@@ -0,0 +1,29 @@
+---
+description: Run the EmbodiedGen layout generation workflow with layout-cli
+argument-hint: "[task description or layout generation request]"
+---
+
+# Gen Layout Skill Command
+
+Route the user's request to the EmbodiedGen interactive layout workflow.
+
+## Workflow
+
+### Step 1: Interpret the request
+
+Use `$ARGUMENTS` if provided. If it is empty, ask for the task description or task file path.
+
+### Step 2: Load the skill
+
+Use `skill: "embodiedgen:layout-creator"`.
+
+### Step 3: Execute the workflow
+
+Follow the skill and build the correct `layout-cli` command, including `--bg_list` and output settings.
+
+### Step 4: Deliver
+
+Return:
+1. The exact command used
+2. The output root
+3. Expected runtime and any dependency warnings
diff --git a/embodied_gen/skills/claude_plugin/plugins/embodiedgen/commands/process.md b/embodied_gen/skills/claude_plugin/plugins/embodiedgen/commands/process.md
new file mode 100644
index 0000000..28aeed2
--- /dev/null
+++ b/embodied_gen/skills/claude_plugin/plugins/embodiedgen/commands/process.md
@@ -0,0 +1,32 @@
+---
+description: Scale or rotate EmbodiedGen URDF-based assets and related files
+argument-hint: "[asset scaling or rotation request]"
+---
+
+# Process Skill Command
+
+Route the user's request to the EmbodiedGen asset processing workflow.
+
+## Workflow
+
+### Step 1: Interpret the request
+
+Use `$ARGUMENTS` if provided. If it is empty, ask for the URDF path and
+desired scale factor and/or XYZ rotation in degrees.
+
+### Step 2: Load the skill
+
+Use `skill: "embodiedgen:asset-process"`.
+
+### Step 3: Execute the workflow
+
+Follow the skill and build the correct
+`python -m embodied_gen.skills.asset-process.asset_process` command.
+
+### Step 4: Deliver
+
+Return:
+1. The exact command used
+2. The output path
+3. Whether the operation is normal mode or inplace mode
+4. The applied scale factor and XYZ rotation
diff --git a/embodied_gen/skills/claude_plugin/plugins/embodiedgen/commands/sim.md b/embodied_gen/skills/claude_plugin/plugins/embodiedgen/commands/sim.md
new file mode 100644
index 0000000..3069c99
--- /dev/null
+++ b/embodied_gen/skills/claude_plugin/plugins/embodiedgen/commands/sim.md
@@ -0,0 +1,29 @@
+---
+description: Run the EmbodiedGen simulation rendering workflow with sim-cli
+argument-hint: "[layout path or simulation request]"
+---
+
+# Sim Skill Command
+
+Route the user's request to the EmbodiedGen simulation rendering workflow.
+
+## Workflow
+
+### Step 1: Interpret the request
+
+Use `$ARGUMENTS` if provided. If it is empty, ask for the `layout.json` path or target simulation request.
+
+### Step 2: Load the skill
+
+Use `skill: "embodiedgen:sim-runner"`.
+
+### Step 3: Execute the workflow
+
+Follow the skill and build the correct `sim-cli` command.
+
+### Step 4: Deliver
+
+Return:
+1. The exact command used
+2. The output video path
+3. Any camera, performance, or rendering notes
diff --git a/embodied_gen/skills/claude_plugin/plugins/embodiedgen/commands/vibe3d.md b/embodied_gen/skills/claude_plugin/plugins/embodiedgen/commands/vibe3d.md
new file mode 100644
index 0000000..d77281a
--- /dev/null
+++ b/embodied_gen/skills/claude_plugin/plugins/embodiedgen/commands/vibe3d.md
@@ -0,0 +1,29 @@
+---
+description: Run the EmbodiedGen spatial computing workflow for floorplans and object placement or deletion in scenes
+argument-hint: "[scene editing request]"
+---
+
+# Vibe3D Skill Command
+
+Route the user's request to the EmbodiedGen spatial computing workflow.
+
+## Workflow
+
+### Step 1: Interpret the request
+
+Use `$ARGUMENTS` if provided. If it is empty, ask for the URDF path and target placement, deletion, or query request.
+
+### Step 2: Load the skill
+
+Use `skill: "embodiedgen:spatial-computing"`.
+
+### Step 3: Execute the workflow
+
+Follow the skill and choose the correct `python -m embodied_gen.skills.spatial-computing.cli.main` or `room-cli -m ...` invocation.
+
+### Step 4: Deliver
+
+Return:
+1. The exact command used
+2. The updated output file path
+3. Any constraints about USD updates or exact instance matching
diff --git a/embodied_gen/skills/claude_plugin/plugins/embodiedgen/skills/asset-converter b/embodied_gen/skills/claude_plugin/plugins/embodiedgen/skills/asset-converter
new file mode 120000
index 0000000..7db211d
--- /dev/null
+++ b/embodied_gen/skills/claude_plugin/plugins/embodiedgen/skills/asset-converter
@@ -0,0 +1 @@
+../../../../asset-converter
\ No newline at end of file
diff --git a/embodied_gen/skills/claude_plugin/plugins/embodiedgen/skills/asset-creator b/embodied_gen/skills/claude_plugin/plugins/embodiedgen/skills/asset-creator
new file mode 120000
index 0000000..bd5e9f3
--- /dev/null
+++ b/embodied_gen/skills/claude_plugin/plugins/embodiedgen/skills/asset-creator
@@ -0,0 +1 @@
+../../../../asset-creator
\ No newline at end of file
diff --git a/embodied_gen/skills/claude_plugin/plugins/embodiedgen/skills/asset-process b/embodied_gen/skills/claude_plugin/plugins/embodiedgen/skills/asset-process
new file mode 120000
index 0000000..d587ee2
--- /dev/null
+++ b/embodied_gen/skills/claude_plugin/plugins/embodiedgen/skills/asset-process
@@ -0,0 +1 @@
+../../../../asset-process
\ No newline at end of file
diff --git a/embodied_gen/skills/claude_plugin/plugins/embodiedgen/skills/asset-retrieval b/embodied_gen/skills/claude_plugin/plugins/embodiedgen/skills/asset-retrieval
new file mode 120000
index 0000000..f98e0dd
--- /dev/null
+++ b/embodied_gen/skills/claude_plugin/plugins/embodiedgen/skills/asset-retrieval
@@ -0,0 +1 @@
+../../../../asset-retrieval
\ No newline at end of file
diff --git a/embodied_gen/skills/claude_plugin/plugins/embodiedgen/skills/background-creator b/embodied_gen/skills/claude_plugin/plugins/embodiedgen/skills/background-creator
new file mode 120000
index 0000000..b604af2
--- /dev/null
+++ b/embodied_gen/skills/claude_plugin/plugins/embodiedgen/skills/background-creator
@@ -0,0 +1 @@
+../../../../background-creator
\ No newline at end of file
diff --git a/embodied_gen/skills/claude_plugin/plugins/embodiedgen/skills/layout-creator b/embodied_gen/skills/claude_plugin/plugins/embodiedgen/skills/layout-creator
new file mode 120000
index 0000000..04680d2
--- /dev/null
+++ b/embodied_gen/skills/claude_plugin/plugins/embodiedgen/skills/layout-creator
@@ -0,0 +1 @@
+../../../../layout-creator
\ No newline at end of file
diff --git a/embodied_gen/skills/claude_plugin/plugins/embodiedgen/skills/room-creator b/embodied_gen/skills/claude_plugin/plugins/embodiedgen/skills/room-creator
new file mode 120000
index 0000000..c51533f
--- /dev/null
+++ b/embodied_gen/skills/claude_plugin/plugins/embodiedgen/skills/room-creator
@@ -0,0 +1 @@
+../../../../room-creator
\ No newline at end of file
diff --git a/embodied_gen/skills/claude_plugin/plugins/embodiedgen/skills/sim-runner b/embodied_gen/skills/claude_plugin/plugins/embodiedgen/skills/sim-runner
new file mode 120000
index 0000000..c47855e
--- /dev/null
+++ b/embodied_gen/skills/claude_plugin/plugins/embodiedgen/skills/sim-runner
@@ -0,0 +1 @@
+../../../../sim-runner
\ No newline at end of file
diff --git a/embodied_gen/skills/claude_plugin/plugins/embodiedgen/skills/spatial-computing b/embodied_gen/skills/claude_plugin/plugins/embodiedgen/skills/spatial-computing
new file mode 120000
index 0000000..a2a7108
--- /dev/null
+++ b/embodied_gen/skills/claude_plugin/plugins/embodiedgen/skills/spatial-computing
@@ -0,0 +1 @@
+../../../../spatial-computing
\ No newline at end of file
diff --git a/embodied_gen/skills/layout-creator/SKILL.md b/embodied_gen/skills/layout-creator/SKILL.md
new file mode 100644
index 0000000..9c832d7
--- /dev/null
+++ b/embodied_gen/skills/layout-creator/SKILL.md
@@ -0,0 +1,83 @@
+---
+name: layout-creator
+description: Generate interactive 3D layouts from task descriptions with EmbodiedGen using layout-cli. Use this skill whenever users ask to build task-driven 3D scenes, batch-generate layouts from task files, tune layout generation retries/seeds, or produce simulator-ready layout outputs from background scene lists.
+---
+
+# Layout Creator
+
+Unified entry for EmbodiedGen interactive layout generation via `layout-cli`.
+
+## When To Use
+
+Use this skill when users want to:
+- Generate interactive 3D scenes from task descriptions.
+- Batch-generate layouts from a task list file.
+- Build simulator-ready layout outputs (`layout.json`, renders) with optional robot insertion.
+- Tune generation quality and stability via retry and seed settings.
+
+## Routing Rule (Core)
+
+Use `layout-cli` when the user input is task-level natural language descriptions (e.g., "put the pen in the mug") and the target output is an interactive layout scene, not standalone assets or standalone background scenes.
+
+## Pre-checks
+
+1. Run commands from the repository root.
+2. Confirm the active environment is `embodiedgen`.
+3. Confirm background scene list file exists and is readable (via `--bg_list`).
+4. If CLI commands are unavailable, run `pip install -e .` to register entrypoints.
+
+## Standard Command Templates
+
+### 1) Generate layouts from inline task descriptions
+
+```bash
+layout-cli \
+ --task_descs "Place the pen in the mug on the desk" "Put the fruit on the table on the plate" \
+ --bg_list "outputs/example_gen_scenes/scene_part_list.txt" \
+ --output_root "outputs/layouts_gen" \
+ --insert_robot
+```
+
+### 2) Batch generation from task list file (background run)
+
+```bash
+layout-cli \
+ --task_descs "apps/assets/example_layout/task_list.txt" \
+ --bg_list "outputs/example_gen_scenes/scene_part_list.txt" \
+ --n_image_retry 4 --n_asset_retry 3 --n_pipe_retry 3 \
+ --output_root "outputs/layouts_gens" \
+ --insert_robot > layouts_gens.log 2>&1 &
+```
+
+## Common Parameters
+
+- `--task_descs`: task descriptions or a task-list text file path.
+- `--output_root`: root output directory.
+- `--bg_list`: background scene list file (scene retrieval pool).
+- `--insert_robot`: include robot pose in layout generation/simulation output.
+- `--output_iscene`: export composed scene mesh (`Iscene.glb`).
+- `--n_image_retry --n_asset_retry --n_pipe_retry`: retry controls for text-to-3D subpipeline.
+- `--seed_img --seed_3d --seed_layout`: reproducibility controls.
+- `--n_img_sample --text_guidance_scale --img_denoise_step`: text-to-image / asset-generation controls.
+- `--keep_intermediate`: keep intermediate files from generation substeps.
+
+## Output Conventions
+
+Outputs are organized by task index:
+- `/task_0000/layout.json`
+- `/task_0000/scene_tree.jpg`
+- `/task_0000/background/`
+- `/task_0000/asset3d/`
+- Optional: `/task_0000/Iscene.glb` (when `--output_iscene` is enabled)
+
+## Runtime Expectations
+
+- Typical generation time is around 30 minutes per task (depends on retries/GPU/background matching).
+- Batch jobs should use background execution (`nohup`) with log redirection.
+
+## Failure Handling and Retry
+
+1. Missing background candidate: verify `--bg_list` path and referenced scene directories.
+2. OOM or GPU pressure: reduce concurrency and lower retry/sample settings.
+3. Poor asset/layout quality: increase retry counts or refine task text.
+4. Missing outputs: verify output permissions and use absolute paths.
diff --git a/embodied_gen/skills/room-creator/SKILL.md b/embodied_gen/skills/room-creator/SKILL.md
new file mode 100644
index 0000000..286aaa4
--- /dev/null
+++ b/embodied_gen/skills/room-creator/SKILL.md
@@ -0,0 +1,84 @@
+---
+name: room-creator
+description: Generate indoor rooms (single room or house) and export URDF/USD by wrapping embodied_gen/scripts/room_gen/gen_room.py. Use when users ask to create rooms with seed control, choose room type and complexity, run generation/export stages, or run reproducible room generation jobs (batch runs can be done by wrapping this command in an outer loop/script).
+---
+
+# Room Creator
+
+Generate room scenes with `python -m embodied_gen.scripts.room_gen.gen_room` from infinigen(https://github.com/princeton-vl/infinigen) and optionally export URDF/USD.
+
+## Use This Workflow
+
+1. Confirm output root and target room profile.
+2. Choose generation scope:
+- `--gen --urdf --usd` for full pipeline.
+- `--gen --no-urdf --no-usd` for generation only.
+- `--no-gen --urdf --usd` for export from existing blender output.
+3. Run the command from repository root.
+4. Verify output folder: `/_seed/` (if `--seed` is omitted, check the generated seed from logs first).
+
+## Parameters
+
+- `--output-root` (required): base output directory.
+- `--prompt`: natural-language task/scene description; a GPT router infers room type and complexity from it, overriding `--room-type`/`--complexity`. Requires a configured GPT agent.
+- `--room-type`: `Bedroom | LivingRoom | Kitchen | Bathroom | DiningRoom | Office | House`.
+- `--seed`: random seed. For reproducible runs, pass this explicitly; if omitted, a random seed is generated.
+- `--complexity`: `minimalist | simple | medium | detail`.
+- `--custom-params`: gin file copied to Infinigen `custom_solve.gin`.
+- `--large-scene`: only for `House`; enables more rooms.
+- `--gen/--no-gen`, `--urdf/--no-urdf`, `--usd/--no-usd`: pipeline switches.
+
+## Complexity Guidance
+
+- `minimalist`: fastest, sparse furniture.
+- `simple`: default, balanced quality/time.
+- `medium`: richer layout, slower.
+- `detail`: highest detail, longest runtime.
+
+## Command Templates
+
+```bash
+# Full pipeline for one kitchen
+python -m embodied_gen.scripts.room_gen.gen_room \
+ --output-root outputs/rooms \
+ --room-type Kitchen \
+ --seed 42 \
+ --complexity simple
+```
+
+```bash
+# Generation only (no export)
+python -m embodied_gen.scripts.room_gen.gen_room \
+ --output-root outputs/rooms \
+ --room-type LivingRoom \
+ --seed 100 \
+ --complexity medium \
+ --no-urdf --no-usd
+```
+
+```bash
+# Export only from existing blender result
+python -m embodied_gen.scripts.room_gen.gen_room \
+ --output-root outputs/rooms \
+ --room-type Kitchen \
+ --seed 42 \
+ --no-gen --urdf --usd
+```
+
+```bash
+# House generation (use --large-scene for more rooms)
+python -m embodied_gen.scripts.room_gen.gen_room \
+ --output-root outputs/rooms \
+ --room-type House \
+ --seed 7 \
+ --complexity simple \
+ --large-scene
+```
+
+## Runtime Requirements
+
+- Run from repo root so relative paths resolve.
+- Ensure Blender Python exists at `$BLENDER_PYTHON`. If not set, the command will try:
+ `thirdparty/infinigen/blender/4.2/python/bin/python3.11` (must exist on disk).
+- `--no-gen` requires existing blender output at:
+ `/_seed/blender`.
diff --git a/embodied_gen/skills/sim-runner/SKILL.md b/embodied_gen/skills/sim-runner/SKILL.md
new file mode 100644
index 0000000..dbec432
--- /dev/null
+++ b/embodied_gen/skills/sim-runner/SKILL.md
@@ -0,0 +1,72 @@
+---
+name: sim-runner
+description: Run SAPIEN-based simulation rendering from EmbodiedGen layout outputs using sim-cli. Use this skill whenever users ask to load a generated layout.json into simulation, render interactive scene videos, control camera/render settings, or enable robot grasp trajectory rendering.
+---
+
+# Sim Runner
+
+Unified entry for EmbodiedGen simulation rendering via `sim-cli`.
+
+## When To Use
+
+Use this skill when users want to:
+- Load a generated `layout.json` into simulation.
+- Render interactive scene videos (foreground + 3DGS background composition).
+- Adjust camera, rendering, or simulation-step parameters.
+- Include robot grasp trajectory rendering with `--insert_robot`.
+
+## Routing Rule (Core)
+
+Use `sim-cli` when the input is an existing layout result (especially `layout.json`) and the target output is simulation visualization (e.g., `Iscene.mp4`), not generation of new assets/backgrounds/layouts.
+
+## Pre-checks
+
+1. Run commands from the repository root.
+2. Confirm the active environment is `embodiedgen`.
+3. Confirm input `--layout_path` exists and points to a valid layout output.
+4. Ensure referenced background and asset files in the layout directory are present.
+5. If CLI commands are unavailable, run `pip install -e .` to register entrypoints.
+
+## Standard Command Template
+
+```bash
+sim-cli \
+ --layout_path "outputs/layouts_gen/task_0000/layout.json" \
+ --output_dir "outputs/layouts_gen/task_0000/sapien_render" \
+ --insert_robot
+```
+
+## Common Parameters
+
+- `--layout_path`: input layout file path.
+- `--output_dir`: output directory for rendered video.
+- `--insert_robot`: render robot grasp actions for manipulated objects.
+- `--sim_freq --control_freq --sim_step`: simulation/control timing settings.
+- `--render_interval`: render every N simulation steps.
+- `--num_cameras --camera_radius --camera_height --fovy_deg`: camera configuration.
+- `--image_hw`: output frame size.
+- `--render_keys`: render channels (requires `Foreground` for final compositing).
+- `--ray_tracing`: enable/disable ray tracing backend.
+- `--device`: rendering device (e.g., `cuda`).
+
+## Output Conventions
+
+Primary output:
+- `/Iscene.mp4`
+
+Typical input dependencies resolved from layout directory:
+- `layout.json`
+- background `gs_model.ply`
+- per-object assets referenced by layout
+
+## Runtime Expectations
+
+- Runtime depends on `sim_step`, `render_interval`, camera count, and ray-tracing mode.
+- Enabling `--insert_robot` increases render time due to grasp-action rollout.
+
+## Failure Handling and Retry
+
+1. Missing file errors: verify layout-relative asset/background paths exist.
+2. GPU memory pressure: reduce `--num_cameras`, `--image_hw`, or disable heavy settings.
+3. Empty/invalid video output: ensure `Foreground` is included in `--render_keys`.
+4. Slow runtime: reduce `--sim_step` or increase `--render_interval`.
\ No newline at end of file
diff --git a/embodied_gen/skills/spatial-computing/README.md b/embodied_gen/skills/spatial-computing/README.md
new file mode 100644
index 0000000..6bb9f29
--- /dev/null
+++ b/embodied_gen/skills/spatial-computing/README.md
@@ -0,0 +1,59 @@
+## Using with IDE Agent via Natural Language
+
+The Agent will automatically load this skill based on its **description** when you mention URDF, floorplan, indoor scene, object placement, etc. You only need to specify in natural language **what to do** and provide **key information like paths/room names**.
+
+### LLM Environment Configuration (When Using Semantic Matching)
+
+If you want to use natural language descriptions (e.g., "put lamp on bookshelf") instead of exact instance/room names, you need to configure the LLM environment first:
+
+```bash
+# If outputs/env.sh exists, source it first
+source outputs/env.sh
+```
+
+If access to the LLM interface is unavailable, please provide exact instance names (you can check them via `--list_instances`).
+
+### URDF Visualization Only (Generate Floorplan)
+
+**You can say:**
+- "Help me visualize `path_to/scene.urdf` or `path_to/folder_contain/scene.urdf`"
+
+**Agent will:** Use `visualize_floorplan(urdf_path=..., output_path=...)` or the corresponding CLI to generate the floorplan only, without modifying URDF/USD.
+
+### Insert Object and Update Scene (URDF, or URDF+USD)
+
+**You can say:**
+- "Put `chair.obj` into scene.urdf's kitchen room"
+- "Put `bottle.obj` into the URDF at `outputs/rooms/Kitchen_seed3773`, instance name bottle_1, update scene and generate floorplan"
+- "Put a cup on the table in the living room" โ Agent will use `on_instance="table"`, `place_strategy="top"`, etc.
+
+**If you also want to update USD:**
+- "Put a chair in the kitchen, update both URDF and USD, USD path is `xxx/usd/export_scene.usdc`"
+- Note that you need to use **room-cli** to execute (this skill will prompt the Agent), because writing USD requires bpy.
+
+**Agent will:** Use `FloorplanManager` + `insert_object` (or `insert_object_to_scene`), execute according to the paths and room names you provided; when USD is needed, use room-cli to run the CLI.
+
+### View Instances and Rooms in the Scene
+
+Before placing objects, you can first view what instances and rooms are in the scene:
+
+**You can say:**
+- "Help me list all instances and room names in `.../scene.urdf`"
+
+**Agent will:** Execute `--list_instances` to display the instance names and room names in the current scene.
+
+### URDF/USD Output Notes
+
+- **URDF Output**: The updated URDF is written to `*_updated.urdf` by default (e.g., `scene.urdf` โ `scene_updated.urdf`), and **will not overwrite** the original `scene.urdf`
+- **USD Output**: If `usd_path` is specified, the USD file will be written to `*_updated.usdc` following the same rule
+- **Only Update USD**: Requires using **room-cli** to execute, because writing USD needs Blender (bpy)
+
+### What Information to Provide
+
+| Goal | Suggested Information to Provide in Conversation |
+|------|-----------------------------------------------|
+| Visualization only | URDF path, floorplan save path (optional, Agent can default to floorplan.png in same directory) |
+| View instances/rooms | URDF path, let Agent list instance names and room names in current scene |
+| Placement + update | URDF path, object mesh path (.obj), instance name (e.g., chair_1), room name (e.g., kitchen); if placing on table, say "place on table"; if updating USD, also provide USD path and use room-cli |
+
+Example in one go: "Use spatial-computing skill, generate floorplan for `.../scene.urdf` and save to floorplan.png in same directory, then put `path/to/bottle.obj` into kitchen, instance name bottle_1, update URDF only."
diff --git a/embodied_gen/skills/spatial-computing/REFERENCE.md b/embodied_gen/skills/spatial-computing/REFERENCE.md
new file mode 100644
index 0000000..8e1b2cf
--- /dev/null
+++ b/embodied_gen/skills/spatial-computing/REFERENCE.md
@@ -0,0 +1,236 @@
+# Floorplan Skill โ API Reference
+
+This document provides API details, configuration items, errors, and dependencies for reference beyond the usage instructions in [SKILL.md](SKILL.md).
+
+## Contents
+
+- [Floorplan Skill โ API Reference](#floorplan-skill--api-reference)
+ - [Contents](#contents)
+ - [LLM Environment Configuration](#llm-environment-configuration)
+ - [FloorplanManager](#floorplanmanager)
+ - [Constructor](#constructor)
+ - [Methods](#methods)
+ - [Convenience Functions](#convenience-functions)
+ - [CLI Features](#cli-features)
+ - [Command Line Parameters](#command-line-parameters)
+ - [Configuration and Ignore Items](#configuration-and-ignore-items)
+ - [Smart File Naming Strategy](#smart-file-naming-strategy)
+ - [USD and Blender](#usd-and-blender)
+ - [Errors and Return Values](#errors-and-return-values)
+ - [Dependencies](#dependencies)
+ - [Usage Recommendations](#usage-recommendations)
+
+---
+
+## LLM Environment Configuration
+
+Before using semantic matching (`resolve_*` methods), configure the LLM API:
+
+```bash
+# Use the project-provided env (Azure + proxy), if outputs/env.sh exists:
+source outputs/env.sh
+```
+
+If access to the LLM interface is unavailable, prompt the user.
+
+---
+
+## FloorplanManager
+
+### Constructor
+
+```python
+from importlib import import_module
+
+FloorplanManager = import_module(
+ "embodied_gen.skills.spatial-computing.api"
+).FloorplanManager
+
+manager = FloorplanManager(
+ urdf_path="scene.urdf", # Required
+ usd_path=None, # Optional; USD write after insert/delete if provided
+ mesh_sample_num=50000,
+ ignore_items=None, # Default ["ceiling", "light", "exterior"]
+ output_strategy="suffix", # "suffix" (default) / "timestamp" / "overwrite"
+)
+```
+
+### Methods
+
+| Method | Description |
+|--------|-------------|
+| `visualize(output_path)` | Generate floorplan and save as image |
+| `insert_object(asset_path, instance_key, in_room=..., on_instance=..., beside_instance=..., place_strategy=..., n_max_attempt=2000, rotation_rpy=...)` | Place object, automatically write back to URDF/USD on success, return `[x,y,z]` or `None` |
+| `delete_object(instance_key, in_room=..., urdf_output_path=..., usd_output_path=...)` | Delete instance from scene, return `True`/`False`. Supports room constraint via `in_room` |
+| `query_instance_center(instance_key)` | Query instance center coordinates, return `[x,y,z]` or `None` |
+| `update_scene(urdf_output_path=..., usd_output_path=...)` | Manually write back currently placed instances; generally not needed (called inside `insert_object`) |
+| `get_room_names()` | List of room names |
+| `get_instance_names()` | List of instance names (excluding walls/floor) |
+| `get_instance_names_in_room(in_room)` | List of instance names within a specific room |
+| `resolve_on_instance(on_instance, gpt_client=None)` | Resolve user description to exact instance name |
+| `resolve_in_room(in_room, gpt_client=None)` | Resolve user description to exact room name |
+| `resolve_beside_instance(beside_instance, gpt_client=None, in_room=None)` | Resolve user description to exact instance name for beside placement |
+| `resolve_delete_instance(delete_instance, gpt_client=None, in_room=None)` | Resolve user description to exact instance name for deletion |
+| `resolve_and_query_instance(query_instance, gpt_client=None)` | Resolve and query instance center in one call, return `(resolved_name, [x,y,z])` or `(None, None)` |
+| `get_occupied_area()` | Occupied area Shapely geometry |
+| `get_floor_union()` | Floor area union geometry |
+
+**Key parameters**:
+- `on_instance` / `beside_instance` / `delete_instance`: Exact instance name or semantic description (with `gpt_client`)
+- `in_room`: Room constraint for placement/deletion/query
+- `place_strategy`: `"random"` (default) or `"top"` (select highest surface)
+- `beside_distance`: Max distance in meters for beside placement (default 0.5)
+
+---
+
+## Convenience Functions
+
+| Function | Description |
+|----------|-------------|
+| `visualize_floorplan(urdf_path, output_path, ...)` | Generate floorplan only |
+| `insert_object_to_scene(urdf_path, asset_path, instance_key, output_path, ...)` | Insert object and generate floorplan, return `[x,y,z]` or `None` |
+| `delete_object_from_scene(urdf_path, instance_key, in_room=..., output_path=...)` | Delete instance and optionally generate floorplan, return `True`/`False` |
+| `query_instance_position(urdf_path, instance_key)` | Quick query instance center coordinates, return `[x,y,z]` or `None` |
+| `resolve_instance_with_llm(gpt_client, instance_names, user_spec, ...)` | Use LLM to match user description to exact instance name |
+
+---
+
+## CLI Features
+
+### Command Line Parameters
+
+| Parameter | Description |
+|-----------|-------------|
+| `--urdf_path` | Input URDF scene file path (required) |
+| `--usd_path` | Optional USD scene file path, update USD simultaneously if specified |
+| `--asset_path` | Object mesh file path (.obj) for insertion |
+| `--instance_key` | Unique identifier for the new instance, default `inserted_object` |
+| `--in_room` | Limit placement to specified room, supports semantic description |
+| `--on_instance` | Place on top of specified instance, supports semantic description |
+| `--beside_instance` | Place beside specified instance on floor, supports semantic description |
+| `--beside_distance` | Max distance (meters) from target instance, default 0.5 |
+| `--place_strategy` | Placement strategy: `"random"` (default) or `"top"` |
+| `--rotation_rpy` | Initial rotation angle (roll, pitch, yaw radians) |
+| `--output_path` | Floorplan output path |
+| `--output_strategy` | File naming strategy: `"suffix"` (default) / `"timestamp"` / `"overwrite"` |
+| `--list_instances` | List instance names and room names, then exit |
+| `--delete_instance` | Instance name to delete (supports semantic description) |
+| `--delete_in_room` | Room constraint for deletion |
+| `--query_instance` | Instance name to query position (supports semantic description) |
+| `--max_placement_attempts` | Maximum placement attempts, default 2000 |
+
+### CLI Usage Examples
+
+**View scene info**:
+```bash
+python -m embodied_gen.skills.spatial-computing.cli.main \
+ --urdf_path .../scene.urdf --list_instances
+```
+
+**Insert object with semantic matching**:
+```bash
+source outputs/env.sh
+python -m embodied_gen.skills.spatial-computing.cli.main \
+ --urdf_path .../scene.urdf --asset_path .../lamp.obj --instance_key lamp_1 \
+ --on_instance ไนฆๆ
+```
+
+**Delete object with room constraint**:
+```bash
+python -m embodied_gen.skills.spatial-computing.cli.main \
+ --urdf_path .../scene.urdf --delete_instance ๆฒๅ --delete_in_room ๅฎขๅ
+```
+
+**Query instance position**:
+```bash
+python -m embodied_gen.skills.spatial-computing.cli.main \
+ --urdf_path .../scene.urdf --query_instance ๅบ
+```
+
+**Update both URDF and USD (room-cli)**:
+```bash
+room-cli -m embodied_gen.skills.spatial-computing.cli.main \
+ --urdf_path .../scene.urdf --usd_path .../scene.usdc \
+ --delete_instance ๆฒๅ
+```
+
+---
+
+## Configuration and Ignore Items
+
+| Parameter | Default | Description |
+|-----------|---------|-------------|
+| `mesh_sample_num` | 50000 | Number of mesh sampling points |
+| `ignore_items` | `["ceiling", "light", "exterior"]` | Link name patterns to skip during URDF parsing |
+| `output_strategy` | `"suffix"` | File naming strategy for output files |
+
+---
+
+## Smart File Naming Strategy
+
+Default `output_strategy="suffix"` provides intelligent continuous operation support:
+
+| Operation | Input File | Output File | Behavior |
+|-----------|-----------|-------------|----------|
+| First insert | `scene.urdf` | `scene_updated.urdf` | Creates new file |
+| Second insert | `scene_updated.urdf` | `scene_updated.urdf` | **Overwrites** (continuous) |
+| Delete | `scene_updated.urdf` | `scene_updated.urdf` | **Overwrites** (continuous) |
+
+**Key features**:
+- โ
No `*_updated_updated.urdf` accumulation
+- โ
Original `scene.urdf` never modified
+- โ
Seamless insert/delete workflow
+
+**Alternative strategies**:
+- `"timestamp"`: Unique versioning (`scene_20260311_180235.urdf`)
+- `"overwrite"`: Direct overwrite (use with caution)
+
+---
+
+## USD and Blender
+
+- Writing USD requires **Blender (bpy)**. Use **room-cli** environment for USD operations.
+- Without `usd_path`, only URDF is updated (no bpy needed).
+- Assets in `.usd`/`.usdc`/`.usda` format are directly referenced; only `.obj` files are converted via bpy.
+- If `*_collision.obj` exists alongside visual mesh, it will be used for URDF collision.
+
+---
+
+## Errors and Return Values
+
+**Exceptions**
+
+- **ValueError**: Room/instance not found; `update_scene()` called before insertion; `instance_key` already exists; attempting to delete protected instances (`walls`, `*floor*`).
+
+**Return Values**
+
+- `insert_object` / `insert_object_to_scene`: `[x, y, z]` on success, `None` on failure.
+- `delete_object` / `delete_object_from_scene`: `True` on success, `False` on failure.
+- `query_instance_center` / `query_instance_position`: `[x, y, z]` or `None`.
+
+**Exit Codes (CLI)**
+
+- `0`: Success
+- `1`: Instance/room not found, deletion failed, or placement failed
+
+---
+
+## Dependencies
+
+| Type | Package | Description |
+|------|---------|-------------|
+| Core | trimesh, shapely, matplotlib, numpy | Parsing and visualization |
+| USD Writing | pxr, bpy | Required only when using `usd_path`; bpy requires Blender |
+| LLM Semantic Matching | openai, project gpt_config | `resolve_*` methods require `GPTclient` instance |
+| CLI | tyro | Required only for CLI entry point |
+
+---
+
+## Usage Recommendations
+
+- **Upright objects**: Default orientation applies; for special orientations, pass `(roll, pitch, yaw)` radians.
+- **Placing on furniture**: Use `resolve_on_instance()` to get exact name, then `insert_object(..., on_instance=resolved, place_strategy="top")`.
+- **Placing beside furniture**: Use `insert_object(..., beside_instance=resolved, beside_distance=0.5)` for floor placement near target.
+- **Deleting objects**: Use `resolve_delete_instance()` for semantic matching, then `delete_object(..., in_room=room)` for room-specific deletion.
+- **Protected instances**: Cannot delete `walls` or instances containing `floor` in their names.
+- **Continuous editing**: Use `scene_updated.urdf` as input for subsequent operations to maintain changes.
diff --git a/embodied_gen/skills/spatial-computing/SKILL.md b/embodied_gen/skills/spatial-computing/SKILL.md
new file mode 100644
index 0000000..631c2e6
--- /dev/null
+++ b/embodied_gen/skills/spatial-computing/SKILL.md
@@ -0,0 +1,405 @@
+---
+name: spatial-computing
+description: Visualizes floorplans from URDF scene files and inserts/removes 3D assets with collision-aware placement on surfaces. Supports semantic instance matching via LLM (e.g., "put lamp on bookshelf", "delete sofa in living room"). Use when working with URDF/USD indoor scenes, floorplan visualization, object placement/deletion, or room-level scene editing.
+---
+
+# Floorplan & Object Placement/Deletion
+
+## Overview
+
+Parse indoor scenes from URDF, generate 2D floorplans, or place/remove 3D objects in scenes and write back to URDF/USD. After successful insertion/deletion, the corresponding file is automatically updated based on whether `urdf_path`/`usd_path` is provided.
+
+**When to use**: Use this skill when you need to generate floorplans from URDF, place/delete objects on specified rooms/furniture surfaces, or batch update URDF/USD files.
+
+> โ ๏ธ **USD updates require `room-cli`**: To update USD files, you **must** use `room-cli` instead of `python -m`, and specify the USD file via `--usd_path`. `room-cli` runs on Blender Python which includes the `bpy` module for OBJโUSD conversion; using `python -m` with `--usd_path` will fail with `ModuleNotFoundError: No module named 'bpy'`.
+>
+> ```bash
+> # โ
Correct: use room-cli to update both URDF and USD
+> room-cli -m embodied_gen.skills.spatial-computing.cli.main \
+> --urdf_path .../scene.urdf --usd_path .../scene.usdc ...
+
+
+**Smart File Naming Strategy**:
+- **Default behavior**: First operation creates `scene_updated.urdf`, subsequent operations automatically overwrite it
+- **No file bloat**: Prevents `*_updated_updated.urdf` files from accumulating
+- **Safe**: Original `scene.urdf` is never modified unless explicitly requested
+- **Works for both insert and delete**: Seamless continuous scene editing
+
+---
+
+## Best Practices & Constraints
+
+### 1. Workflow for Continuous Scene Editing
+
+**Recommended workflow** for multiple insert/delete operations:
+
+```bash
+# Step 1: View current scene
+python -m embodied_gen.skills.spatial-computing.cli.main \
+ --urdf_path .../scene.urdf --list_instances
+
+# Step 2: First insert โ creates scene_updated.urdf
+python -m embodied_gen.skills.spatial-computing.cli.main \
+ --urdf_path .../scene.urdf \
+ --asset_path .../apple.obj --instance_key apple_1
+
+# Step 3: Second insert โ overwrites scene_updated.urdf
+python -m embodied_gen.skills.spatial-computing.cli.main \
+ --urdf_path .../scene_updated.urdf \
+ --asset_path .../lamp.obj --instance_key lamp_1
+
+# Step 4: Delete operation โ overwrites scene_updated.urdf
+python -m embodied_gen.skills.spatial-computing.cli.main \
+ --urdf_path .../scene_updated.urdf \
+ --delete_instance apple_1
+```
+
+**Key benefits**:
+- โ
No multiple `*_updated_updated.urdf` files
+- โ
Original file `scene.urdf` always preserved
+- โ
Continuous insert/delete operations are seamless
+
+**Result**: Clean workflow with only two files:
+- `scene.urdf` (original, untouched)
+- `scene_updated.urdf` (final state)
+
+### 2. When to Use Different Strategies
+
+| Strategy | Use Case | Example |
+|----------|----------|---------|
+| **suffix** (default) | Standard workflow, continuous editing | Most scenarios |
+| **timestamp** | Version tracking, backup before risky changes | `scene_20260311_180235.urdf` |
+| **overwrite** | Confident single operation, no backup needed | Automated pipelines |
+
+### 3. Performance Optimization: Batch Insert
+
+**Problem**: CLI commands re-parse URDF and process all meshes on every call, leading to slow performance when inserting multiple objects.
+
+**Solution**: Use `--batch_insert_config` with JSON config for 3-4x speedup:
+
+**Step 1**: Create JSON config file (`batch_chairs.json`):
+
+```json
+[
+ {
+ "asset_path": "path/to/chair1.obj",
+ "instance_key": "chair_1",
+ "beside_instance": "table_dining_7178300",
+ "in_room": "dining_room_0_floor"
+ },
+ {
+ "asset_path": "path/to/chair2.obj",
+ "instance_key": "chair_2",
+ "beside_instance": "table_dining_7178300",
+ "in_room": "dining_room_0_floor"
+ },
+ {
+ "asset_path": "path/to/chair3.obj",
+ "instance_key": "chair_3",
+ "beside_instance": "table_dining_7178300",
+ "in_room": "dining_room_0_floor"
+ }
+]
+```
+
+**Step 2**: Run batch insertion:
+
+```bash
+# Update URDF only
+room-cli -m embodied_gen.skills.spatial-computing.cli.main \
+ --urdf_path .../scene.urdf \
+ --batch_insert_config batch_chairs.json
+
+# Update both URDF and USD
+room-cli -m embodied_gen.skills.spatial-computing.cli.main \
+ --urdf_path .../scene.urdf \
+ --usd_path .../scene.usdc \
+ --batch_insert_config batch_chairs.json
+```
+
+**JSON Config Fields**:
+- `asset_path` (required): Path to asset mesh file (.obj)
+- `instance_key` (required): Unique instance identifier
+- `beside_instance`: Place beside target instance (on floor). **Must be exact name**.
+- `on_instance`: Place on top of target instance. **Must be exact name**.
+- `in_room`: Limit placement to specified room. **Must be exact name**.
+- `beside_distance`: Max distance from target (default: 0.5m)
+- `place_strategy`: "random" or "top" (default: "random")
+
+> **โ ๏ธ Batch insert does NOT support fuzzy/semantic matching.**
+> `beside_instance`, `on_instance`, and `in_room` require exact names.
+> Use `--list_instances` to get the exact instance / room names first:
+> ```bash
+> python -m embodied_gen.skills.spatial-computing.cli.main \
+> --urdf_path .../scene.urdf --list_instances
+> ```
+
+**When to Use**:
+- โ
Inserting 2+ objects at once
+- โ
Performance-critical workflows
+- โ
Automated scene generation pipelines
+
+โ ๏ธ **Batch config file cleanup**: The JSON config file for `--batch_insert_config` is a **temporary file** and **must not** be left in the project root directory. Always:
+1. Create the JSON config in the **same directory as the target scene** (e.g., `.../House_seed5/batch_fruits.json`).
+2. **Delete the JSON config file immediately after the batch command finishes**, regardless of success or failure.
+
+### 3. Important Constraints
+
+**USD prim hierarchy**: When updating a USD file, inserted assets must be
+authored under the stage's `defaultPrim` (for existing room exports this is
+usually `/World`), for example `/World/`. Do not write inserted
+objects as pseudo-root children like `/` because USD references in
+IsaacSim load the `defaultPrim` only; root-level siblings outside `defaultPrim`
+will be omitted.
+
+โ **Wrong**: Using `scene.urdf` for all operations (ignores previous changes)
+```bash
+# This will NOT see apple_1 from previous operation
+python -m ... --urdf_path scene.urdf --asset_path lamp.obj
+```
+
+โ
**Right**: Chain operations using `scene_updated.urdf`
+```bash
+# This WILL see apple_1 and add lamp_1
+python -m ... --urdf_path scene_updated.urdf --asset_path lamp.obj
+```
+
+---
+
+## LLM Environment (Required for Semantic Matching)
+
+Before using `resolve_instance_with_llm` for semantic matching in **Python**, configure the LLM API and ensure access to the interface. Prompt the user if access is unavailable.
+
+```bash
+# Use the project-provided env (Azure + proxy, etc.), if outputs/env.sh exists:
+source outputs/env.sh
+```
+
+---
+
+## Core Convention: Placement/Deletion/Query Requests Must Use This Skill's Interface
+
+When users request "put A somewhere", "delete A", "find A", or "visualize urdf", you **must** implement it using this skill's interface:
+
+| User Request Example | Corresponding Parameter & Usage |
+|---------------------|---------------------------------|
+| **Put A on B** (e.g., "put lamp on bookshelf") | `on_instance` (instance name, obtained from `--list_instances`) |
+| **Put A beside B** (e.g., "put chair beside table") | `beside_instance` (instance name, obtained from `--list_instances`); placed on floor near target |
+| **Put A in a room** (e.g., "put table in living room") | `in_room` (room name, obtained from `--list_instances`) |
+| **Put A beside B in a room** (e.g., "put chair beside table in kitchen") | `beside_instance` + `in_room` |
+| **Put A on B in a room** (e.g., "put apple on table in living room") | Decomposed into "apple" and "living room" as `in_room` and `on_instance` |
+| **Delete A** (e.g., "delete lamp") | `delete_instance` (instance name or semantic description, supports fuzzy matching with LLM) |
+| **Delete A in a room** (e.g., "delete sofa in living room") | `delete_instance` + `delete_in_room` (only deletes if instance is in specified room) |
+| **Find A** (e.g., "find lamp", "where is the bed") | `query_instance` (returns center coordinates [x, y, z], supports fuzzy matching with LLM) |
+
+| `output_strategy` | `"suffix"` / `"timestamp"` / `"overwrite"` | File naming strategy for output files. Default is "suffix" (non-destructive). |
+| **Visualize scene.urdf** | `cli.main --urdf_path .../scene.urdf --output_path .../floorplan.png`; output_path defaults to same directory as urdf |
+
+- When no match is found, prompt "The object/room does not exist, please re-enter" and provide the current scene object or room list.
+- Instance names should not use the ` ` from URDF. **Recommended**: Run `--list_instances` before placement/deletion/query to view current instance name list, and select the closest semantic match.
+
+---
+
+## CLI Examples
+
+> **Tip**: The URDF file is typically located at `/urdf/export_scene/scene.urdf` (e.g., `outputs/rooms/Kitchen_seed0/urdf/export_scene/scene.urdf`).
+
+### Example 1: View Instance Names and Room Names in Current Scene
+
+```bash
+# View instance names and room names in current scene (to fill in --on_instance / --in_room)
+python -m embodied_gen.skills.spatial-computing.cli.main \
+ --urdf_path .../scene.urdf --list_instances
+```
+
+### Example 2: Visualize Floorplan Only
+
+```bash
+python -m embodied_gen.skills.spatial-computing.cli.main \
+ --urdf_path .../scene.urdf --output_path .../floorplan.png
+```
+
+### Example 3: Put Lamp on Bookshelf (Place on an Object)
+
+`--on_instance` can be filled with the instance name returned by `--list_instances` or a semantic description.
+
+```bash
+python -m embodied_gen.skills.spatial-computing.cli.main \
+ --urdf_path .../scene.urdf --output_path .../floorplan.png \
+ --asset_path .../lamp.obj --instance_key lamp_on_bookcase --on_instance ไนฆๆ
+```
+
+---
+
+### Example 4: Put Table in Living Room (Place in a Room)
+
+```bash
+python -m embodied_gen.skills.spatial-computing.cli.main \
+ --urdf_path .../scene.urdf --output_path .../floorplan.png \
+ --asset_path .../table.obj --instance_key table_1 \
+ --in_room living_room
+```
+
+---
+
+### Example 5: Put Apple on Table in Living Room (Room + on Object)
+
+```bash
+python -m embodied_gen.skills.spatial-computing.cli.main \
+ --urdf_path .../scene.urdf --output_path .../floorplan.png \
+ --asset_path .../apple.obj --instance_key apple_1 \
+ --in_room living_room --on_instance table --place_strategy top
+```
+
+---
+
+### Example 7: Delete an Object (Exact Name)
+
+```bash
+python -m embodied_gen.skills.spatial-computing.cli.main \
+ --urdf_path .../scene.urdf --output_path .../floorplan.png \
+ --delete_instance bed_192207
+```
+
+---
+
+### Example 8: Delete Object with Fuzzy Matching (Semantic Description)
+
+Requires LLM environment (see "LLM Environment" section).
+
+```bash
+python -m embodied_gen.skills.spatial-computing.cli.main \
+ --urdf_path .../scene.urdf --output_path .../floorplan.png \
+ --delete_instance "ๆฒๅ"
+```
+
+---
+
+### Example 9: Delete Object in Specific Room
+
+Only deletes the instance if it's located in the specified room.
+
+```bash
+python -m embodied_gen.skills.spatial-computing.cli.main \
+ --urdf_path .../scene.urdf --output_path .../floorplan.png \
+ --delete_instance "ๆฒๅ" --delete_in_room "ๅฎขๅ
"
+```
+
+**Update both URDF and USD using room-cli:**
+```bash
+room-cli -m embodied_gen.skills.spatial-computing.cli.main \
+ --urdf_path .../scene.urdf --usd_path .../scene.usdc \
+ --output_path .../floorplan.png \
+ --delete_instance "ๆฒๅ" --delete_in_room "ๅฎขๅ
"
+```
+
+---
+
+### Example 10: Query Instance Position (Exact Name)
+
+```bash
+python -m embodied_gen.skills.spatial-computing.cli.main \
+ --urdf_path .../scene.urdf \
+ --query_instance bed_192207
+```
+
+**Expected output**:
+```
+๐ Instance 'bed_192207' center: (-0.9250, -6.5830, 0.5000)
+```
+
+---
+
+### Example 11: Query Instance Position with Fuzzy Matching
+
+Requires LLM environment (see "LLM Environment" section).
+
+```bash
+python -m embodied_gen.skills.spatial-computing.cli.main \
+ --urdf_path .../scene.urdf \
+ --query_instance "ๅบ"
+```
+
+---
+
+#### **Alternative Strategies**
+
+**Timestamp** - Unique versioning for each operation:
+```bash
+# Output: scene_20260311_180235.urdf
+python -m embodied_gen.skills.spatial-computing.cli.main \
+ --urdf_path .../scene.urdf \
+ --asset_path .../apple.obj --instance_key apple_1 \
+ --output_strategy timestamp
+```
+
+**Overwrite** - Directly overwrite original (use with caution):
+```bash
+# Overwrites: scene.urdf
+python -m embodied_gen.skills.spatial-computing.cli.main \
+ --urdf_path .../scene.urdf \
+ --asset_path .../apple.obj --instance_key apple_1 \
+ --output_strategy overwrite
+```
+
+---
+
+### Query Instance Position
+
+Query the center coordinates of an instance in the scene. Supports fuzzy matching with LLM.
+
+**CLI Interface**:
+```bash
+# Exact instance name
+python -m embodied_gen.skills.spatial-computing.cli.main \
+ --urdf_path scene.urdf \
+ --query_instance bed_192207
+
+# Fuzzy matching (requires GPT)
+source outputs/env.sh
+python -m embodied_gen.skills.spatial-computing.cli.main \
+ --urdf_path scene.urdf \
+ --query_instance "ๅบ"
+```
+
+### 6. Common Parameters
+
+| Parameter | Meaning |
+|-----------|---------|
+| `in_room` | Limit placement to specified room |
+| `on_instance` | Place on top of specified instance; must be **exact instance name** (obtained via `resolve_instance_with_llm`) |
+| `beside_instance` | Place beside specified instance on the floor; must be **exact instance name** (obtained via `resolve_instance_with_llm`). Mutually exclusive with `on_instance` |
+| `beside_distance` | Max distance (meters) from target instance for beside placement. Default `0.5`. Increase if placement fails |
+| `place_strategy` | `"random"` random placement (default, e.g., bookshelf with 3 layers will randomly select one), `"top"` select highest surface |
+| `rotation_rpy` | Not required by default; pass (roll, pitch, yaw) radians for special orientations |
+| `delete_instance` | Instance name or semantic description to delete (supports fuzzy matching with LLM). Cannot delete protected items (walls, floors) |
+| `delete_in_room` | Optional room constraint for deletion - only delete if instance is in this room |
+| `query_instance` | Instance name or semantic description to query center coordinates (supports fuzzy matching with LLM). Returns [x, y, z] position |
+
+---
+
+## Roaming Trajectory Generation (Optional)
+
+Generate a smooth, collision-free robot **roaming trajectory** on a floorplan
+and overlay it on the floorplan image. **Opt-in and off by default**: the
+normal floorplan visualization never draws a trajectory unless you run this
+CLI. Doors are treated as open passages (excluded from obstacles).
+
+**CLI Interface**:
+```bash
+python -m embodied_gen.scripts.room_gen.gen_trajectory \
+ --urdf_path .../scene.urdf \
+ --output_dir .../trajectory \
+ --clearance 0.4 --num_waypoints 8
+```
+
+Outputs `_trajectory.json` (equidistant `{x, y, rot, t}` waypoints at a
+constant speed) and `.png` (red-path overlay). `rot` in degrees: `0ยฐ` = `+Y`
+(12 o'clock), counter-clockwise positive, tangent to the path (forward
+heading); `t` is the timestamp in seconds.
+
+---
+
+## Next Steps
+
+- For complete API, configuration, errors, and dependencies, see [REFERENCE.md](REFERENCE.md).
diff --git a/embodied_gen/skills/spatial-computing/__init__.py b/embodied_gen/skills/spatial-computing/__init__.py
new file mode 100644
index 0000000..0cd773e
--- /dev/null
+++ b/embodied_gen/skills/spatial-computing/__init__.py
@@ -0,0 +1,31 @@
+# Project EmbodiedGen
+#
+# Copyright (c) 2025 Horizon Robotics. All Rights Reserved.
+#
+# 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.
+
+from .core import (
+ UrdfSemanticInfoCollector,
+ get_actionable_surface,
+ points_to_polygon,
+)
+from .core.visualizer import (
+ FloorplanVisualizer,
+)
+
+__all__ = [
+ "FloorplanVisualizer",
+ "UrdfSemanticInfoCollector",
+ "points_to_polygon",
+ "get_actionable_surface",
+]
diff --git a/embodied_gen/skills/spatial-computing/api/__init__.py b/embodied_gen/skills/spatial-computing/api/__init__.py
new file mode 100644
index 0000000..9c492f9
--- /dev/null
+++ b/embodied_gen/skills/spatial-computing/api/__init__.py
@@ -0,0 +1,34 @@
+# Project EmbodiedGen
+#
+# Copyright (c) 2025 Horizon Robotics. All Rights Reserved.
+#
+# 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.
+
+from embodied_gen.utils.llm_resolve import resolve_instance_with_llm
+
+from .floorplan_api import (
+ FloorplanManager,
+ delete_object_from_scene,
+ insert_object_to_scene,
+ query_instance_position,
+ visualize_floorplan,
+)
+
+__all__ = [
+ "FloorplanManager",
+ "visualize_floorplan",
+ "insert_object_to_scene",
+ "delete_object_from_scene",
+ "query_instance_position",
+ "resolve_instance_with_llm",
+]
diff --git a/embodied_gen/skills/spatial-computing/api/floorplan_api.py b/embodied_gen/skills/spatial-computing/api/floorplan_api.py
new file mode 100644
index 0000000..f571d74
--- /dev/null
+++ b/embodied_gen/skills/spatial-computing/api/floorplan_api.py
@@ -0,0 +1,917 @@
+# Project EmbodiedGen
+#
+# Copyright (c) 2025 Horizon Robotics. All Rights Reserved.
+#
+# 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.
+
+from __future__ import annotations
+
+import logging
+import os
+from dataclasses import dataclass, field
+from datetime import datetime
+from typing import Literal
+
+from shapely.geometry import MultiPolygon, Polygon
+from embodied_gen.utils.llm_resolve import resolve_instance_with_llm
+
+from ..core import (
+ UrdfSemanticInfoCollector,
+)
+from ..core.collector import (
+ DEFAULT_BESIDE_DISTANCE,
+ DEFAULT_IGNORE_ITEMS,
+ DEFAULT_MESH_SAMPLE_NUM,
+ DEFAULT_ROTATION_RPY,
+)
+from ..core.visualizer import (
+ FloorplanVisualizer,
+)
+
+# Type aliases
+Geometry = Polygon | MultiPolygon
+logger = logging.getLogger(__name__)
+
+
+@dataclass
+class FloorplanConfig:
+ """Configuration for floorplan operations."""
+
+ urdf_path: str
+ """Path to the input URDF scene file."""
+
+ output_path: str | None = None
+ """Path to save the floorplan visualization image."""
+
+ usd_path: str | None = None
+ """Optional path to the USD scene file for USD export."""
+
+ asset_path: str | None = None
+ """Optional path to the asset mesh file (.obj)."""
+
+ instance_key: str = "inserted_object"
+ """Unique key for the added instance."""
+
+ in_room: str | None = None
+ """Optional room name to constrain asset placement."""
+
+ on_instance: str | None = None
+ """Optional instance name to place the asset on top of (exact key from get_instance_names())."""
+
+ beside_instance: str | None = None
+ """Optional instance name to place the asset beside (on floor, near the target)."""
+
+ beside_distance: float = DEFAULT_BESIDE_DISTANCE
+ """Max distance (meters) from the target instance for beside placement."""
+
+ place_strategy: Literal["top", "random"] = "random"
+ """Placement strategy for the asset."""
+
+ rotation_rpy: tuple[float, float, float] = DEFAULT_ROTATION_RPY
+ """Rotation in roll-pitch-yaw (radians)."""
+
+ ignore_items: list[str] = field(
+ default_factory=lambda: list(DEFAULT_IGNORE_ITEMS)
+ )
+ """List of item name patterns to ignore during parsing."""
+
+ mesh_sample_num: int = DEFAULT_MESH_SAMPLE_NUM
+ """Number of points to sample from meshes."""
+
+ max_placement_attempts: int = 2000
+ """Maximum attempts for asset placement."""
+
+ update_urdf: bool = True
+ """Whether to update and save the URDF file."""
+
+ update_usd: bool = True
+ """Whether to update and save the USD file."""
+
+ list_instances: bool = False
+ """If True, print instance and room names then exit (no placement/visualization)."""
+
+ delete_instance: str | None = None
+ """Optional instance name to delete from the scene (supports fuzzy matching with LLM)."""
+
+ delete_in_room: str | None = None
+ """Optional room constraint for deletion - only delete if instance is in this room."""
+
+ query_instance: str | None = None
+ """Optional instance name to query and return its center coordinates (supports fuzzy matching with LLM)."""
+
+ output_strategy: Literal["suffix", "overwrite", "timestamp"] = "suffix"
+ """File naming strategy for output files.
+
+ - "suffix": Add '_updated' suffix (default, non-destructive)
+ - "overwrite": Overwrite original files (use with caution)
+ - "timestamp": Add timestamp suffix (e.g., '_20260311_171500')
+ """
+
+ batch_insert_config: str | None = None
+ """Path to JSON config file for batch insertion (3-4x faster than multiple CLI calls).
+
+ JSON format example:
+ [
+ {
+ "asset_path": "path/to/chair1.obj",
+ "instance_key": "chair_1",
+ "beside_instance": "table_dining_7178300",
+ "in_room": "dining_room_0_floor"
+ },
+ {
+ "asset_path": "path/to/chair2.obj",
+ "instance_key": "chair_2",
+ "beside_instance": "table_dining_7178300",
+ "in_room": "dining_room_0_floor"
+ }
+ ]
+ """
+
+
+class FloorplanManager:
+ """High-level API for floorplan operations.
+
+ This class provides simplified methods for:
+ - Loading and analyzing URDF scenes
+ - Visualizing floorplans
+ - Inserting objects into scenes
+ - Updating URDF and USD files
+
+ Example:
+ >>> manager = FloorplanManager(urdf_path="scene.urdf", usd_path="scene.usdc")
+ >>> manager.visualize(output_path="floorplan.png")
+ >>> position = manager.insert_object(
+ ... asset_path="chair.obj",
+ ... instance_key="chair_1",
+ ... in_room="kitchen"
+ ... )
+ # URDF/USD are updated automatically after insert
+ """
+
+ def __init__(
+ self,
+ urdf_path: str,
+ usd_path: str | None = None,
+ mesh_sample_num: int = DEFAULT_MESH_SAMPLE_NUM,
+ ignore_items: list[str] | None = None,
+ output_strategy: Literal[
+ "suffix", "overwrite", "timestamp"
+ ] = "suffix",
+ ) -> None:
+ """Initialize the floorplan manager.
+
+ Args:
+ urdf_path: Path to the URDF file.
+ usd_path: Optional path to the USD file for scene updates.
+ mesh_sample_num: Number of points to sample from meshes.
+ ignore_items: List of item name patterns to ignore.
+ output_strategy: File naming strategy for output files.
+
+ """
+ self.urdf_path = urdf_path
+ self.usd_path = usd_path
+ self.output_strategy = output_strategy
+ self.collector = UrdfSemanticInfoCollector(
+ mesh_sample_num=mesh_sample_num,
+ ignore_items=ignore_items,
+ )
+ self.collector.collect(urdf_path)
+ self.pending_instance_data: dict | None = None
+
+ def _get_output_path(
+ self,
+ input_path: str,
+ custom_output_path: str | None = None,
+ ) -> str:
+ """Generate output path based on the naming strategy.
+
+ Smart file naming strategy:
+ - "suffix" (default):
+ * If input already ends with "_updated", overwrite it (continuous operations)
+ * Otherwise, add "_updated" suffix (first operation)
+ - "timestamp": Add timestamp suffix for unique versioning
+ - "overwrite": Always overwrite the input file
+
+ Args:
+ input_path: Original input file path.
+ custom_output_path: Optional custom output path (highest priority).
+
+ Returns:
+ Generated output path based on strategy.
+
+ """
+ if custom_output_path is not None:
+ return custom_output_path
+
+ name, ext = os.path.splitext(input_path)
+
+ if self.output_strategy == "overwrite":
+ return input_path
+ elif self.output_strategy == "timestamp":
+ timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
+ return f"{name}_{timestamp}{ext}"
+ else: # "suffix" (default) - smart continuous operation support
+ # If input already has "_updated" suffix, overwrite it (continuous operation)
+ if name.endswith("_updated"):
+ return input_path
+ # Otherwise, add "_updated" suffix (first operation)
+ else:
+ return f"{name}_updated{ext}"
+
+ def visualize(
+ self,
+ output_path: str,
+ ) -> None:
+ """Generate and save a floorplan visualization.
+
+ Args:
+ output_path: Path to save the output image.
+
+ """
+ FloorplanVisualizer.plot(
+ self.collector.rooms,
+ self.collector.footprints,
+ self.collector.occ_area,
+ output_path,
+ )
+ logger.info(f"โ
Floorplan visualization saved to {output_path}")
+
+ def insert_object(
+ self,
+ asset_path: str,
+ instance_key: str,
+ in_room: str | None = None,
+ on_instance: str | None = None,
+ beside_instance: str | None = None,
+ beside_distance: float = DEFAULT_BESIDE_DISTANCE,
+ rotation_rpy: tuple[float, float, float] = DEFAULT_ROTATION_RPY,
+ n_max_attempt: int = 2000,
+ place_strategy: Literal["top", "random"] = "random",
+ ) -> list[float] | None:
+ """Insert an object into the scene with automatic placement.
+
+ Args:
+ asset_path: Path to the asset mesh file (.obj).
+ instance_key: Unique key for the new instance.
+ in_room: Optional room name to constrain placement.
+ on_instance: Optional instance name to place on top of.
+ beside_instance: Optional instance name to place beside (on floor).
+ beside_distance: Max distance from target for beside placement.
+ rotation_rpy: Initial rotation in roll-pitch-yaw.
+ n_max_attempt: Maximum placement attempts.
+ place_strategy: Either "top" or "random".
+
+ Returns:
+ List [x, y, z] of the placed instance center, or None if failed.
+
+ """
+ center = self.collector.add_instance(
+ asset_path=asset_path,
+ instance_key=instance_key,
+ in_room=in_room,
+ on_instance=on_instance,
+ beside_instance=beside_instance,
+ beside_distance=beside_distance,
+ rotation_rpy=rotation_rpy,
+ n_max_attempt=n_max_attempt,
+ place_strategy=place_strategy,
+ )
+
+ if center is not None:
+ self.pending_instance_data = {
+ "asset_path": asset_path,
+ "instance_key": instance_key,
+ "center": center,
+ "rotation_rpy": rotation_rpy,
+ }
+ self.update_scene()
+
+ return center
+
+ def batch_insert_objects(
+ self,
+ objects: list[dict],
+ defer_update: bool = False,
+ ) -> list[list[float] | None]:
+ """Batch insert multiple objects into the scene efficiently.
+
+ Args:
+ objects: List of object configs, each containing:
+ asset_path: Path to the asset mesh file (.obj).
+ instance_key: Unique key for the new instance.
+ in_room: Optional room name to constrain placement.
+ on_instance: Optional instance name to place on top of.
+ beside_instance: Optional instance name to place beside.
+ beside_distance: Max distance from target (default: 0.5m).
+ rotation_rpy: Initial rotation (default: (0, 0, 0)).
+ place_strategy: Either "top" or "random" (default: "random").
+ defer_update: If True, don't update URDF/USD after each
+ insertion. Useful when inserting many objects at once.
+
+ Returns:
+ List of centers [x, y, z] for each inserted object,
+ or None if failed.
+
+ Example:
+ >>> objects = [
+ ... {"asset_path": "chair1.obj",
+ ... "instance_key": "chair_1",
+ ... "beside_instance": "table"},
+ ... ]
+ >>> centers = manager.batch_insert_objects(objects)
+
+ """
+ centers = []
+ usd_source = self.usd_path
+
+ for i, obj_config in enumerate(objects, 1):
+ logger.info(
+ f"[{i}/{len(objects)}] Inserting '{obj_config.get('instance_key', 'unknown')}'..."
+ )
+
+ center = self.collector.add_instance(
+ asset_path=obj_config["asset_path"],
+ instance_key=obj_config["instance_key"],
+ in_room=obj_config.get("in_room"),
+ on_instance=obj_config.get("on_instance"),
+ beside_instance=obj_config.get("beside_instance"),
+ beside_distance=obj_config.get(
+ "beside_distance", DEFAULT_BESIDE_DISTANCE
+ ),
+ rotation_rpy=obj_config.get(
+ "rotation_rpy", DEFAULT_ROTATION_RPY
+ ),
+ n_max_attempt=obj_config.get("n_max_attempt", 2000),
+ place_strategy=obj_config.get("place_strategy", "random"),
+ )
+
+ if center is not None:
+ # Store instance data for later update
+ collision_path = obj_config["asset_path"].replace(
+ ".obj", "_collision.obj"
+ )
+ if not os.path.exists(collision_path):
+ collision_path = None
+
+ # Update URDF incrementally
+ if self.urdf_path and not defer_update:
+ urdf_out = self._get_output_path(self.urdf_path)
+ self.collector.update_urdf_info(
+ output_path=urdf_out,
+ instance_key=obj_config["instance_key"],
+ visual_mesh_path=obj_config["asset_path"],
+ collision_mesh_path=collision_path,
+ trans_xyz=tuple(center),
+ rot_rpy=obj_config.get(
+ "rotation_rpy", DEFAULT_ROTATION_RPY
+ ),
+ joint_type="fixed",
+ )
+
+ # Update USD incrementally
+ if self.usd_path and not defer_update:
+ usd_out = self._get_output_path(self.usd_path)
+ self.collector.update_usd_info(
+ usd_path=usd_source,
+ output_path=usd_out,
+ instance_key=obj_config["instance_key"],
+ visual_mesh_path=obj_config["asset_path"],
+ trans_xyz=center,
+ rot_rpy=obj_config.get(
+ "rotation_rpy", DEFAULT_ROTATION_RPY
+ ),
+ )
+ usd_source = usd_out
+
+ logger.info(f"โ
Placed at {center}")
+ else:
+ logger.warning(f"โ Failed to place object")
+
+ centers.append(center)
+
+ return centers
+
+ def update_scene(
+ self,
+ urdf_output_path: str | None = None,
+ usd_output_path: str | None = None,
+ ) -> None:
+ """Update URDF and/or USD with inserted instances.
+
+ Updates URDF if self.urdf_path is set, USD if self.usd_path is set.
+ Both are updated when both paths are set. No-op when no instance was inserted.
+
+ Note: USD updates require Blender (bpy) to convert .obj to .usdc format.
+
+ Args:
+ urdf_output_path: Optional custom path for URDF output.
+ usd_output_path: Optional custom path for USD output.
+
+ Raises:
+ ValueError: If no instance has been inserted.
+
+ """
+ if self.pending_instance_data is None:
+ raise ValueError(
+ "No instance to update. Call insert_object() first."
+ )
+
+ data = self.pending_instance_data
+ collision_path = data["asset_path"].replace(".obj", "_collision.obj")
+ if not os.path.exists(collision_path):
+ collision_path = None
+
+ if self.urdf_path:
+ urdf_out = self._get_output_path(self.urdf_path, urdf_output_path)
+ self.collector.update_urdf_info(
+ output_path=urdf_out,
+ instance_key=data["instance_key"],
+ visual_mesh_path=data["asset_path"],
+ collision_mesh_path=collision_path,
+ trans_xyz=tuple(data["center"]),
+ rot_rpy=data["rotation_rpy"],
+ joint_type="fixed",
+ )
+
+ if self.usd_path:
+ usd_out = self._get_output_path(self.usd_path, usd_output_path)
+ self.collector.update_usd_info(
+ usd_path=self.usd_path,
+ output_path=usd_out,
+ instance_key=data["instance_key"],
+ visual_mesh_path=data["asset_path"],
+ trans_xyz=data["center"],
+ rot_rpy=data["rotation_rpy"],
+ )
+
+ def delete_object(
+ self,
+ instance_key: str,
+ in_room: str | None = None,
+ urdf_output_path: str | None = None,
+ usd_output_path: str | None = None,
+ ) -> bool:
+ """Delete an object from the scene.
+
+ Args:
+ instance_key: Exact instance name to delete.
+ in_room: Optional room constraint - only delete if instance is in this room.
+ urdf_output_path: Optional custom path for URDF output.
+ usd_output_path: Optional custom path for USD output.
+
+ Returns:
+ True if deletion succeeded, False otherwise.
+
+ """
+ success = self.collector.remove_instance(
+ instance_key=instance_key,
+ in_room=in_room,
+ )
+
+ if success:
+ # Update URDF
+ if self.urdf_path:
+ urdf_out = self._get_output_path(
+ self.urdf_path, urdf_output_path
+ )
+ self.collector.save_urdf(urdf_out)
+
+ # Update USD
+ if self.usd_path:
+ usd_out = self._get_output_path(self.usd_path, usd_output_path)
+ self.collector.remove_usd_instance(
+ usd_path=self.usd_path,
+ output_path=usd_out,
+ instance_key=instance_key,
+ )
+
+ return success
+
+ def get_instance_names(self) -> list[str]:
+ """Get list of instance names in the scene.
+
+ Returns:
+ List of instance names.
+
+ """
+ return [
+ k
+ for k in self.collector.instances.keys()
+ if k != "walls" and "floor" not in k.lower()
+ ]
+
+ def get_room_names(self) -> list[str]:
+ """Get list of room names in the scene.
+
+ Returns:
+ List of room names.
+
+ """
+ return list(self.collector.rooms.keys())
+
+ def get_instance_names_in_room(self, in_room: str) -> list[str]:
+ """Get instance names that are spatially inside a given room.
+
+ Buffers the room polygon slightly to handle mesh-sampling precision.
+
+ Args:
+ in_room: Exact room key (must exist in get_room_names()).
+
+ Returns:
+ List of instance names within the room.
+
+ """
+ room_poly = self.collector.rooms.get(in_room)
+ if room_poly is None:
+ return self.get_instance_names()
+ room_buffered = room_poly.buffer(0.1)
+ all_names = self.get_instance_names()
+ return [
+ k
+ for k in all_names
+ if room_buffered.contains(
+ self.collector.instances[k].representative_point()
+ )
+ ]
+
+ def resolve_on_instance(
+ self,
+ on_instance: str,
+ gpt_client: object | None = None,
+ ) -> str | None:
+ r"""Resolve on_instance to an exact key (for placement).
+
+ If on_instance is already in get_instance_names(), return it.
+ Otherwise if gpt_client is provided, use LLM to resolve user description
+ (e.g. \"ๆๅญ\", \"ไนฆๆ\") to one exact instance key.
+
+ Args:
+ on_instance: Exact instance key or semantic description.
+ gpt_client: Optional GPT client for semantic resolve (e.g. GPT_CLIENT).
+
+ Returns:
+ Exact instance key, or None if not found / LLM returned NONE.
+ """
+ names = self.get_instance_names()
+ if on_instance in names:
+ return on_instance
+ if gpt_client is not None:
+ return resolve_instance_with_llm(
+ gpt_client, names, on_instance # type: ignore[arg-type]
+ )
+ return None
+
+ def resolve_in_room(
+ self,
+ in_room: str,
+ gpt_client: object | None = None,
+ ) -> str | None:
+ r"""Resolve in_room to an exact room name (for placement).
+
+ If in_room is already in get_room_names(), return it.
+ Otherwise if gpt_client is provided, use LLM to resolve user description
+ (e.g. \"kitchen\", \"the place for cooking\") to one exact room name.
+
+ Args:
+ in_room: Exact room name or semantic description.
+ gpt_client: Optional GPT client for semantic resolve (e.g. GPT_CLIENT).
+
+ Returns:
+ Exact room name, or None if not found / LLM returned NONE.
+ """
+ names = self.get_room_names()
+ if in_room in names:
+ return in_room
+ if gpt_client is not None:
+ return resolve_instance_with_llm(
+ gpt_client, names, in_room # type: ignore[arg-type]
+ )
+ return None
+
+ def resolve_beside_instance(
+ self,
+ beside_instance: str,
+ gpt_client: object | None = None,
+ in_room: str | None = None,
+ ) -> str | None:
+ r"""Resolve beside_instance to an exact key (for beside placement).
+
+ If beside_instance is already in get_instance_names(), return it.
+ Otherwise if gpt_client is provided, use LLM to resolve user description
+ (e.g. "ๆกๅญ", "ๆฒๅ") to one exact instance key.
+
+ When `in_room` is given, only instances spatially inside that room are
+ considered as candidates.
+
+ Args:
+ beside_instance: Exact instance key or semantic description.
+ gpt_client: Optional GPT client for semantic resolve.
+ in_room: Optional resolved room key to restrict candidate scope.
+
+ Returns:
+ Exact instance key, or None if not found / LLM returned NONE.
+ """
+ if in_room is not None:
+ names = self.get_instance_names_in_room(in_room)
+ else:
+ names = self.get_instance_names()
+ if beside_instance in names:
+ return beside_instance
+
+ # Substring matching as fallback
+ query_lower = beside_instance.lower()
+ matches = [n for n in names if query_lower in n.lower()]
+ if len(matches) == 1:
+ logger.info(
+ "Substring match: '%s' -> '%s'", beside_instance, matches[0]
+ )
+ return matches[0]
+ elif len(matches) > 1:
+ logger.warning(
+ "Multiple substring matches for '%s': %s. Using '%s'. "
+ "Use exact name or LLM for better matching.",
+ beside_instance,
+ matches,
+ matches[0],
+ )
+ return matches[0]
+
+ if gpt_client is not None:
+ return resolve_instance_with_llm(
+ gpt_client, names, beside_instance # type: ignore[arg-type]
+ )
+ return None
+
+ def resolve_delete_instance(
+ self,
+ delete_instance: str,
+ gpt_client: object | None = None,
+ in_room: str | None = None,
+ ) -> str | None:
+ r"""Resolve delete_instance to an exact key (for deletion).
+
+ Similar to resolve_beside_instance but specifically for deletion.
+ If delete_instance is already in get_instance_names(), return it.
+ Otherwise if gpt_client is provided, use LLM to resolve user description
+ (e.g. "ๆกๅญ", "ๆฒๅ") to one exact instance key.
+
+ When `in_room` is given, only instances spatially inside that room are
+ considered as candidates.
+
+ Args:
+ delete_instance: Exact instance key or semantic description.
+ gpt_client: Optional GPT client for semantic resolve.
+ in_room: Optional resolved room key to restrict candidate scope.
+
+ Returns:
+ Exact instance key, or None if not found / LLM returned NONE.
+ """
+ if in_room is not None:
+ names = self.get_instance_names_in_room(in_room)
+ else:
+ names = self.get_instance_names()
+
+ if delete_instance in names:
+ return delete_instance
+
+ # Substring matching as fallback
+ query_lower = delete_instance.lower()
+ matches = [n for n in names if query_lower in n.lower()]
+ if len(matches) == 1:
+ logger.info(
+ "Substring match: '%s' -> '%s'", delete_instance, matches[0]
+ )
+ return matches[0]
+ elif len(matches) > 1:
+ logger.warning(
+ "Multiple substring matches for '%s': %s. Using '%s'. "
+ "Use exact name or LLM for better matching.",
+ delete_instance,
+ matches,
+ matches[0],
+ )
+ return matches[0]
+
+ if gpt_client is not None:
+ return resolve_instance_with_llm(
+ gpt_client, names, delete_instance # type: ignore[arg-type]
+ )
+ return None
+
+ def query_instance_center(
+ self,
+ instance_key: str,
+ ) -> list[float] | None:
+ """Query the center coordinates of an instance.
+
+ Args:
+ instance_key: Exact instance name to query.
+
+ Returns:
+ List [x, y, z] of the instance center, or None if not found.
+
+ """
+ return self.collector.get_instance_center(instance_key)
+
+ def resolve_and_query_instance(
+ self,
+ query_instance: str,
+ gpt_client: object | None = None,
+ ) -> tuple[str | None, list[float] | None]:
+ r"""Resolve instance name and return its center coordinates.
+
+ Combines fuzzy matching with coordinate query.
+ If query_instance is already in get_instance_names(), return its center.
+ Otherwise if gpt_client is provided, use LLM to resolve user description
+ (e.g. "ๅบ", "ๆฒๅ") to one exact instance key.
+
+ Args:
+ query_instance: Exact instance key or semantic description.
+ gpt_client: Optional GPT client for semantic resolve.
+
+ Returns:
+ Tuple of (resolved_instance_name, center_coordinates), or (None, None) if not found.
+
+ """
+ names = self.get_instance_names()
+
+ if query_instance in names:
+ center = self.query_instance_center(query_instance)
+ return query_instance, center
+
+ # Substring matching as fallback
+ query_lower = query_instance.lower()
+ matches = [n for n in names if query_lower in n.lower()]
+ if len(matches) == 1:
+ logger.info(
+ "Substring match: '%s' -> '%s'", query_instance, matches[0]
+ )
+ center = self.query_instance_center(matches[0])
+ return matches[0], center
+ elif len(matches) > 1:
+ logger.warning(
+ "Multiple substring matches for '%s': %s. Using '%s'. "
+ "Use exact name or LLM for better matching.",
+ query_instance,
+ matches,
+ matches[0],
+ )
+ center = self.query_instance_center(matches[0])
+ return matches[0], center
+
+ if gpt_client is not None:
+ resolved = resolve_instance_with_llm(
+ gpt_client, names, query_instance # type: ignore[arg-type]
+ )
+ if resolved:
+ center = self.query_instance_center(resolved)
+ return resolved, center
+
+ return None, None
+
+ def get_occupied_area(self) -> Geometry:
+ """Get the union of all occupied areas.
+
+ Returns:
+ Shapely geometry representing occupied areas.
+
+ """
+ return self.collector.occ_area
+
+ def get_floor_union(self) -> Geometry:
+ """Get the union of all floor areas.
+
+ Returns:
+ Shapely geometry representing floor areas.
+
+ """
+ return self.collector.floor_union
+
+
+def visualize_floorplan(
+ urdf_path: str,
+ output_path: str,
+ mesh_sample_num: int = DEFAULT_MESH_SAMPLE_NUM,
+ ignore_items: list[str] | None = None,
+) -> None:
+ """Quick function to visualize a floorplan.
+
+ Args:
+ urdf_path: Path to the URDF file.
+ output_path: Path to save the output image.
+ mesh_sample_num: Number of points to sample from meshes.
+ ignore_items: List of item name patterns to ignore.
+
+ """
+ manager = FloorplanManager(
+ urdf_path=urdf_path,
+ mesh_sample_num=mesh_sample_num,
+ ignore_items=ignore_items,
+ )
+ manager.visualize(output_path=output_path)
+
+
+def insert_object_to_scene(
+ urdf_path: str,
+ asset_path: str,
+ instance_key: str,
+ output_path: str,
+ usd_path: str | None = None,
+ in_room: str | None = None,
+ on_instance: str | None = None,
+ beside_instance: str | None = None,
+ beside_distance: float = DEFAULT_BESIDE_DISTANCE,
+ place_strategy: Literal["top", "random"] = "random",
+ rotation_rpy: tuple[float, float, float] = DEFAULT_ROTATION_RPY,
+) -> list[float] | None:
+ """Quick function to insert an object and generate floorplan.
+
+ Note: USD updates require Blender (bpy) to convert .obj to .usdc format.
+
+ Args:
+ urdf_path: Path to the URDF file.
+ asset_path: Path to the asset mesh file (.obj).
+ instance_key: Unique key for the new instance.
+ output_path: Path to save the floorplan image.
+ usd_path: Optional path to the USD file (requires Blender).
+ in_room: Optional room name to constrain placement.
+ on_instance: Optional instance name to place on top of.
+ beside_instance: Optional instance name to place beside (on floor).
+ beside_distance: Max distance for beside placement (meters).
+ place_strategy: Either "top" or "random".
+ rotation_rpy: Initial rotation in roll-pitch-yaw.
+
+ Returns:
+ List [x, y, z] of the placed instance center, or None if failed.
+
+ """
+ manager = FloorplanManager(urdf_path=urdf_path, usd_path=usd_path)
+ center = manager.insert_object(
+ asset_path=asset_path,
+ instance_key=instance_key,
+ in_room=in_room,
+ on_instance=on_instance,
+ beside_instance=beside_instance,
+ beside_distance=beside_distance,
+ rotation_rpy=rotation_rpy,
+ place_strategy=place_strategy,
+ )
+ if center is not None:
+ manager.visualize(output_path=output_path)
+ return center
+
+
+def delete_object_from_scene(
+ urdf_path: str,
+ instance_key: str,
+ in_room: str | None = None,
+ output_path: str | None = None,
+) -> bool:
+ """Quick function to delete an object from scene.
+
+ Args:
+ urdf_path: Path to the URDF file.
+ instance_key: Exact instance name to delete.
+ in_room: Optional room constraint - only delete if instance is in this room.
+ output_path: Optional path to save the floorplan image after deletion.
+
+ Returns:
+ True if deletion succeeded, False otherwise.
+
+ """
+ manager = FloorplanManager(urdf_path=urdf_path)
+ success = manager.delete_object(
+ instance_key=instance_key,
+ in_room=in_room,
+ )
+ if success and output_path is not None:
+ manager.visualize(output_path=output_path)
+ return success
+
+
+def query_instance_position(
+ urdf_path: str,
+ instance_key: str,
+) -> list[float] | None:
+ """Quick function to query instance center coordinates.
+
+ Args:
+ urdf_path: Path to the URDF file.
+ instance_key: Exact instance name to query.
+
+ Returns:
+ List [x, y, z] of the instance center, or None if not found.
+
+ """
+ manager = FloorplanManager(urdf_path=urdf_path)
+ return manager.query_instance_center(instance_key)
diff --git a/embodied_gen/skills/spatial-computing/cli/__init__.py b/embodied_gen/skills/spatial-computing/cli/__init__.py
new file mode 100644
index 0000000..a5d3075
--- /dev/null
+++ b/embodied_gen/skills/spatial-computing/cli/__init__.py
@@ -0,0 +1,7 @@
+"""Command-line interface for floorplan operations.
+
+This package provides CLI utilities for floorplan visualization and
+scene manipulation.
+"""
+
+__all__: list[str] = []
diff --git a/embodied_gen/skills/spatial-computing/cli/main.py b/embodied_gen/skills/spatial-computing/cli/main.py
new file mode 100644
index 0000000..3c15573
--- /dev/null
+++ b/embodied_gen/skills/spatial-computing/cli/main.py
@@ -0,0 +1,267 @@
+# Project EmbodiedGen
+#
+# Copyright (c) 2025 Horizon Robotics. All Rights Reserved.
+#
+# 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.
+
+from __future__ import annotations
+
+import json
+import logging
+import sys
+import warnings
+
+import tyro
+
+from ..api.floorplan_api import (
+ FloorplanConfig,
+ FloorplanManager,
+)
+
+warnings.filterwarnings("ignore", category=RuntimeWarning)
+
+logging.basicConfig(
+ format="%(asctime)s - %(levelname)s - %(message)s",
+ level=logging.INFO,
+ force=True,
+)
+logger = logging.getLogger(__name__)
+
+
+def _get_gpt_client() -> object | None:
+ """Lazy-import GPT_CLIENT for semantic --on_instance resolution."""
+ try:
+ from embodied_gen.utils.gpt_clients import GPT_CLIENT
+
+ return GPT_CLIENT
+ except Exception:
+ return None
+
+
+def entrypoint(cfg: FloorplanConfig) -> None:
+ """Main entry point for floorplan visualization and scene manipulation.
+
+ Args:
+ cfg: Configuration object with all parameters.
+
+ """
+ manager = FloorplanManager(
+ urdf_path=cfg.urdf_path,
+ usd_path=cfg.usd_path,
+ mesh_sample_num=cfg.mesh_sample_num,
+ ignore_items=cfg.ignore_items,
+ output_strategy=cfg.output_strategy,
+ )
+
+ # List instances/rooms and exit if requested
+ if cfg.list_instances:
+ names = manager.get_instance_names()
+ rooms = manager.get_room_names()
+ logger.info(f"instance_names: {names}")
+ logger.info(f"room_names: {rooms}")
+ return
+
+ # Batch insertion
+ if cfg.batch_insert_config is not None:
+ logger.info(
+ f"Loading batch insert config from {cfg.batch_insert_config}"
+ )
+ with open(cfg.batch_insert_config, 'r') as f:
+ objects = json.load(f)
+
+ logger.info(f"Batch inserting {len(objects)} objects...")
+ centers = manager.batch_insert_objects(objects)
+
+ success_count = len([c for c in centers if c is not None])
+ logger.info(
+ f"โ
Successfully placed {success_count}/{len(objects)} objects"
+ )
+
+ if success_count < len(objects):
+ logger.warning(
+ f"โ ๏ธ Failed to place {len(objects) - success_count} objects"
+ )
+ sys.exit(1)
+
+ # Generate floorplan visualization if requested
+ if cfg.output_path is not None:
+ manager.visualize(output_path=cfg.output_path)
+
+ return
+
+ gpt_client = _get_gpt_client()
+ on_instance = cfg.on_instance
+ if on_instance is not None:
+ resolved = manager.resolve_on_instance(
+ on_instance, gpt_client=gpt_client
+ )
+ if resolved is None:
+ logger.error(
+ "No object matched \"%s\"ใCurrent scene instance name: %sใ",
+ on_instance,
+ manager.get_instance_names(),
+ )
+ sys.exit(1)
+ on_instance = resolved
+ if resolved != cfg.on_instance:
+ logger.info("\"%s\" -> \"%s\"", cfg.on_instance, resolved)
+
+ in_room = cfg.in_room
+ if in_room is not None:
+ resolved = manager.resolve_in_room(in_room, gpt_client=gpt_client)
+ if resolved is None:
+ logger.error(
+ "No room matched \"%s\"ใCurrent scene room names: %sใ",
+ in_room,
+ manager.get_room_names(),
+ )
+ sys.exit(1)
+ in_room = resolved
+ if resolved != cfg.in_room:
+ logger.info("\"%s\" -> \"%s\"", cfg.in_room, resolved)
+
+ beside_instance = cfg.beside_instance
+ if beside_instance is not None:
+ resolved = manager.resolve_beside_instance(
+ beside_instance, gpt_client=gpt_client, in_room=in_room
+ )
+ if resolved is None:
+ candidates = (
+ manager.get_instance_names_in_room(in_room)
+ if in_room
+ else manager.get_instance_names()
+ )
+ logger.error(
+ "No object matched \"%s\"ใCurrent %sinstance name: %sใ",
+ beside_instance,
+ f"room '{in_room}' " if in_room else "",
+ candidates,
+ )
+ sys.exit(1)
+ beside_instance = resolved
+ if resolved != cfg.beside_instance:
+ logger.info("\"%s\" -> \"%s\"", cfg.beside_instance, resolved)
+
+ delete_instance = cfg.delete_instance
+ delete_in_room = cfg.delete_in_room
+ if delete_instance is not None:
+ # Resolve room constraint if provided
+ if delete_in_room is not None:
+ resolved_room = manager.resolve_in_room(
+ delete_in_room, gpt_client=gpt_client
+ )
+ if resolved_room is None:
+ logger.error(
+ "No room matched \"%s\"ใCurrent scene room names: %sใ",
+ delete_in_room,
+ manager.get_room_names(),
+ )
+ sys.exit(1)
+ delete_in_room = resolved_room
+ if resolved_room != cfg.delete_in_room:
+ logger.info(
+ "\"%s\" -> \"%s\"", cfg.delete_in_room, resolved_room
+ )
+
+ # Resolve delete_instance with fuzzy matching
+ resolved = manager.resolve_delete_instance(
+ delete_instance, gpt_client=gpt_client, in_room=delete_in_room
+ )
+ if resolved is None:
+ candidates = (
+ manager.get_instance_names_in_room(delete_in_room)
+ if delete_in_room
+ else manager.get_instance_names()
+ )
+ logger.error(
+ "No object matched \"%s\"ใCurrent %sinstance name: %sใ",
+ delete_instance,
+ f"room '{delete_in_room}' " if delete_in_room else "",
+ candidates,
+ )
+ sys.exit(1)
+ delete_instance = resolved
+ if resolved != cfg.delete_instance:
+ logger.info("\"%s\" -> \"%s\"", cfg.delete_instance, resolved)
+
+ # Perform deletion
+ success = manager.delete_object(
+ instance_key=delete_instance,
+ in_room=delete_in_room,
+ )
+ if success:
+ logger.info(
+ f"โ
Successfully deleted '{delete_instance}' from scene."
+ )
+ else:
+ logger.error(f"โ Failed to delete '{delete_instance}'.")
+ sys.exit(1)
+
+ # Query instance position
+ query_instance = cfg.query_instance
+ if query_instance is not None:
+ # Resolve instance with fuzzy matching
+ resolved_name, center = manager.resolve_and_query_instance(
+ query_instance, gpt_client=gpt_client
+ )
+
+ if resolved_name is None or center is None:
+ logger.error(
+ "No object matched \"%s\"ใCurrent instance names: %sใ",
+ query_instance,
+ manager.get_instance_names(),
+ )
+ sys.exit(1)
+
+ if resolved_name != query_instance:
+ logger.info("\"%s\" -> \"%s\"", query_instance, resolved_name)
+
+ logger.info(
+ f"๐ Instance '{resolved_name}' center: "
+ f"({center[0]:.4f}, {center[1]:.4f}, {center[2]:.4f})"
+ )
+
+ # Add asset instance if specified
+ center = None
+ if cfg.asset_path is not None:
+ center = manager.insert_object(
+ asset_path=cfg.asset_path,
+ instance_key=cfg.instance_key,
+ in_room=in_room,
+ on_instance=on_instance,
+ beside_instance=beside_instance,
+ beside_distance=cfg.beside_distance,
+ rotation_rpy=cfg.rotation_rpy,
+ n_max_attempt=cfg.max_placement_attempts,
+ place_strategy=cfg.place_strategy,
+ )
+
+ if center is not None:
+ logger.info(
+ f"Successfully placed '{cfg.instance_key}' at "
+ f"({center[0]:.3f}, {center[1]:.3f}, {center[2]:.3f})"
+ )
+ else:
+ logger.error(
+ f"โ Failed to place '{cfg.instance_key}' in the scene."
+ )
+ sys.exit(1)
+
+ # Generate floorplan visualization
+ if cfg.output_path is not None:
+ manager.visualize(output_path=cfg.output_path)
+
+
+if __name__ == "__main__":
+ config = tyro.cli(FloorplanConfig)
+ entrypoint(config)
diff --git a/embodied_gen/skills/spatial-computing/core/__init__.py b/embodied_gen/skills/spatial-computing/core/__init__.py
new file mode 100644
index 0000000..f45964e
--- /dev/null
+++ b/embodied_gen/skills/spatial-computing/core/__init__.py
@@ -0,0 +1,31 @@
+"""Floorplan skill core modules.
+
+This package provides core functionality for floorplan visualization
+and object placement in 3D indoor scenes.
+"""
+
+from .collector import (
+ UrdfSemanticInfoCollector,
+)
+from .geometry import (
+ get_actionable_surface,
+ points_to_polygon,
+)
+from .trajectory import (
+ RoamTrajectoryGenerator,
+ TrajectoryResult,
+ heading_to_rot_deg,
+)
+from .visualizer import (
+ FloorplanVisualizer,
+)
+
+__all__ = [
+ "FloorplanVisualizer",
+ "UrdfSemanticInfoCollector",
+ "points_to_polygon",
+ "get_actionable_surface",
+ "RoamTrajectoryGenerator",
+ "TrajectoryResult",
+ "heading_to_rot_deg",
+]
diff --git a/embodied_gen/skills/spatial-computing/core/collector.py b/embodied_gen/skills/spatial-computing/core/collector.py
new file mode 100644
index 0000000..4b7ae0a
--- /dev/null
+++ b/embodied_gen/skills/spatial-computing/core/collector.py
@@ -0,0 +1,1175 @@
+# Project EmbodiedGen
+#
+# Copyright (c) 2025 Horizon Robotics. All Rights Reserved.
+#
+# 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.
+
+
+from __future__ import annotations
+
+import hashlib
+import logging
+import os
+import random
+import re
+import xml.etree.ElementTree as ET
+from concurrent.futures import ProcessPoolExecutor, as_completed
+from shutil import copy2, copytree
+from typing import Any, Literal
+
+import numpy as np
+import trimesh
+from scipy.spatial.transform import Rotation as R
+from shapely.affinity import translate
+from shapely.geometry import MultiPoint, MultiPolygon, Polygon
+from shapely.ops import unary_union
+
+from .geometry import (
+ DEFAULT_MESH_SAMPLE_NUM,
+ get_actionable_surface,
+ points_to_polygon,
+)
+
+logger = logging.getLogger(__name__)
+
+# Type aliases
+Geometry = Polygon | MultiPolygon
+
+# Constants
+DEFAULT_ROTATION_RPY = (1.57, 0.0, 0.0)
+DEFAULT_MAX_PLACEMENT_ATTEMPTS = 3000
+DEFAULT_IGNORE_ITEMS = ("ceiling", "light", "exterior")
+DEFAULT_BESIDE_DISTANCE = 0.5
+DEFAULT_Z_OFFSET = 0.001
+
+
+def _exact_xy_projection(mesh: trimesh.Trimesh) -> Polygon:
+ """Project all mesh triangles onto XY and union them into a footprint.
+
+ Deterministic and shape-accurate (unlike point sampling), at the cost of
+ iterating every triangle.
+
+ Args:
+ mesh: A world-transformed mesh.
+
+ Returns:
+ The projected footprint polygon, or an empty polygon.
+ """
+ triangle_polys = []
+ for triangle in mesh.triangles[:, :, :2]:
+ poly = Polygon(triangle)
+ if poly.is_valid and poly.area > 1e-8:
+ triangle_polys.append(poly)
+ if not triangle_polys:
+ return Polygon()
+ return unary_union(triangle_polys).buffer(0)
+
+
+def _load_mesh_to_poly(
+ mesh_path: str,
+ xyz: np.ndarray,
+ rpy: np.ndarray,
+ mesh_sample_num: int,
+ use_exact_projection: bool = False,
+) -> Polygon:
+ """Load mesh and convert to 2D footprint polygon (process-safe).
+
+ Standalone function for use with ProcessPoolExecutor.
+
+ """
+ if not os.path.exists(mesh_path):
+ return Polygon()
+
+ # Deterministic per-mesh seed so footprint sampling is reproducible
+ # regardless of worker process or task ordering (str hashing is salted
+ # across processes, so use a stable hash of the mesh path).
+ mesh_seed = int.from_bytes(
+ hashlib.md5(os.fspath(mesh_path).encode()).digest()[:4], "little"
+ )
+ np.random.seed(mesh_seed)
+
+ mesh = trimesh.load(mesh_path, force="mesh", skip_materials=True)
+
+ matrix = np.eye(4)
+ matrix[:3, :3] = R.from_euler("xyz", rpy).as_matrix()
+ matrix[:3, 3] = xyz
+ mesh.apply_transform(matrix)
+
+ if use_exact_projection:
+ projected_poly = _exact_xy_projection(mesh)
+ if not projected_poly.is_empty:
+ return projected_poly
+
+ verts = np.asarray(mesh.sample(mesh_sample_num))[:, :2]
+ poly = points_to_polygon(verts)
+
+ # Scanline sampling can collapse for thin/rotated meshes (and is seed
+ # sensitive); when the footprint is far smaller than the sampled-point
+ # convex hull, fall back to the exact, deterministic projection.
+ hull_area = MultiPoint(verts).convex_hull.area
+ if hull_area > 1e-6 and poly.area < 0.5 * hull_area:
+ projected_poly = _exact_xy_projection(mesh)
+ if not projected_poly.is_empty and projected_poly.area > poly.area:
+ return projected_poly
+
+ return poly
+
+
+class UrdfSemanticInfoCollector:
+ """Collector for URDF semantic information.
+
+ Parses URDF files to extract room layouts, object footprints, and
+ provides methods for adding new instances and updating URDF/USD files.
+
+ Attributes:
+ mesh_sample_num: Number of points to sample from meshes.
+ ignore_items: List of item name patterns to ignore.
+ instances: Dictionary of instance name to footprint polygon.
+ instance_meta: Dictionary of instance metadata (mesh path, pose).
+ rooms: Dictionary of room polygons.
+ footprints: Dictionary of object footprints.
+ occ_area: Union of all occupied areas.
+ floor_union: Union of all floor polygons.
+
+ """
+
+ def __init__(
+ self,
+ mesh_sample_num: int = DEFAULT_MESH_SAMPLE_NUM,
+ ignore_items: list[str] | None = None,
+ ) -> None:
+ """Initialize the collector.
+
+ Args:
+ mesh_sample_num: Number of points to sample from meshes.
+ ignore_items: List of item name patterns to ignore during parsing.
+
+ """
+ self.mesh_sample_num = mesh_sample_num
+ self.ignore_items = ignore_items or list(DEFAULT_IGNORE_ITEMS)
+
+ self.instances: dict[str, Polygon] = {}
+ self.instance_meta: dict[str, dict] = {}
+ self.rooms: dict[str, Geometry] = {}
+ self.footprints: dict[str, Geometry] = {}
+ self.occ_area: Geometry = Polygon()
+ self.floor_union: Geometry = Polygon()
+
+ self.urdf_path: str = ""
+ self._tree: ET.ElementTree | None = None
+ self._root: ET.Element | None = None
+
+ def _get_transform(
+ self,
+ joint_elem: ET.Element,
+ ) -> tuple[np.ndarray, np.ndarray]:
+ """Extract transform (xyz, rpy) from a joint element.
+
+ Args:
+ joint_elem: XML Element representing a URDF joint.
+
+ Returns:
+ Tuple of (xyz, rpy) arrays.
+
+ """
+ origin = joint_elem.find("origin")
+ if origin is not None:
+ xyz = np.fromstring(origin.attrib.get("xyz", "0 0 0"), sep=" ")
+ rpy = np.fromstring(origin.attrib.get("rpy", "0 0 0"), sep=" ")
+ else:
+ xyz, rpy = np.zeros(3), np.zeros(3)
+ return xyz, rpy
+
+ def collect(self, urdf_path: str) -> None:
+ """Parse URDF file and collect semantic information.
+
+ Args:
+ urdf_path: Path to the URDF file.
+
+ """
+ logger.info(f"Collecting URDF semantic info from {urdf_path}")
+ self.urdf_path = urdf_path
+ urdf_dir = os.path.dirname(urdf_path)
+
+ self._tree = ET.parse(urdf_path)
+ self._root = self._tree.getroot()
+
+ link_transforms = self._build_link_transforms()
+ self._process_links(urdf_dir, link_transforms)
+ self._update_internal_state()
+
+ def _build_link_transforms(
+ self,
+ ) -> dict[str, tuple[np.ndarray, np.ndarray]]:
+ """Build mapping from link names to their transforms.
+
+ Returns:
+ Dictionary mapping link names to (xyz, rpy) tuples.
+
+ """
+ link_transforms: dict[str, tuple[np.ndarray, np.ndarray]] = {}
+
+ for joint in self._tree.findall("joint"):
+ child = joint.find("child")
+ if child is not None:
+ link_name = child.attrib["link"]
+ link_transforms[link_name] = self._get_transform(joint)
+
+ return link_transforms
+
+ def _process_links(
+ self,
+ urdf_dir: str,
+ link_transforms: dict[str, tuple[np.ndarray, np.ndarray]],
+ ) -> None:
+ """Process all links in the URDF tree (parallel).
+
+ Uses ProcessPoolExecutor to bypass GIL for CPU-bound mesh
+ loading and sampling.
+
+ Args:
+ urdf_dir: Directory containing the URDF file.
+ link_transforms: Dictionary of link transforms.
+
+ """
+ self.instances = {}
+ self.instance_meta = {}
+ wall_polys: list[Polygon] = []
+
+ # Collect tasks for parallel processing
+ tasks: list[dict] = []
+ for link in self._tree.findall("link"):
+ name = link.attrib.get("name", "").lower()
+ if any(ign in name for ign in self.ignore_items):
+ continue
+
+ visual = link.find("visual")
+ if visual is None:
+ continue
+
+ mesh_node = visual.find("geometry/mesh")
+ if mesh_node is None:
+ continue
+
+ mesh_path = os.path.join(urdf_dir, mesh_node.attrib["filename"])
+ default_transform = (np.zeros(3), np.zeros(3))
+ xyz, rpy = link_transforms.get(
+ link.attrib["name"], default_transform
+ )
+ tasks.append(
+ {
+ "link_name": link.attrib["name"],
+ "link_name_lower": name,
+ "mesh_path": mesh_path,
+ "xyz": xyz,
+ "rpy": rpy,
+ }
+ )
+
+ logger.info(
+ "Processing %d URDF links to extract geometry "
+ "(parallel, sample_num=%d)...",
+ len(tasks),
+ self.mesh_sample_num,
+ )
+
+ # ProcessPoolExecutor bypasses GIL for CPU-bound trimesh ops.
+ # Cap workers to balance parallelism vs memory overhead.
+ n_workers = min(len(tasks), os.cpu_count() or 4, 8)
+ futures_map: dict = {}
+ with ProcessPoolExecutor(max_workers=n_workers) as executor:
+ for task in tasks:
+ future = executor.submit(
+ _load_mesh_to_poly,
+ task["mesh_path"],
+ task["xyz"],
+ task["rpy"],
+ self.mesh_sample_num,
+ "_floor" in task["link_name_lower"],
+ )
+ futures_map[future] = task
+
+ for future in as_completed(futures_map):
+ task = futures_map[future]
+ try:
+ poly = future.result()
+ except Exception:
+ logger.warning(
+ "Failed to process link '%s', skipping.",
+ task["link_name"],
+ exc_info=True,
+ )
+ continue
+
+ if poly.is_empty:
+ continue
+
+ if "wall" in task["link_name_lower"]:
+ wall_polys.append(poly)
+ else:
+ key = self._process_safe_key_robust(task["link_name"])
+ self.instances[key] = poly
+ self.instance_meta[key] = {
+ "mesh_path": task["mesh_path"],
+ "xyz": task["xyz"],
+ "rpy": task["rpy"],
+ "original_link_name": task["link_name"],
+ }
+
+ self.instances["walls"] = unary_union(wall_polys)
+
+ def _update_internal_state(self) -> None:
+ """Update derived state (rooms, footprints, occupied area)."""
+ self.rooms = {
+ k: v
+ for k, v in self.instances.items()
+ if "_floor" in k.lower() and not v.is_empty
+ }
+
+ self.footprints = {
+ k: v
+ for k, v in self.instances.items()
+ if k != "walls"
+ and "_floor" not in k.lower()
+ and "rug" not in k.lower()
+ and not v.is_empty
+ }
+ self.occ_area = unary_union(list(self.footprints.values()))
+ self.floor_union = unary_union(list(self.rooms.values()))
+
+ def _process_safe_key_robust(self, name: str) -> str:
+ """Convert a link name to a safe, normalized key.
+
+ Args:
+ name: Original link name.
+
+ Returns:
+ Normalized key string.
+
+ """
+ if name.endswith("_floor"):
+ parts = name.split("_")
+ return "_".join(parts[:-2] + ["floor"])
+
+ if "Factory" in name:
+ # Handle infinigen naming convention
+ prefix = name.split("Factory")[0]
+ suffix = f"_{name.split('_')[-1]}"
+ else:
+ prefix, suffix = name, ""
+
+ res = prefix.replace(" ", "_")
+ res = re.sub(r"([a-z0-9])([A-Z])", r"\1_\2", res)
+ res = res.lower()
+ res = re.sub(r"_+", "_", res).strip("_ ")
+
+ return f"{res}{suffix}"
+
+ def add_instance(
+ self,
+ asset_path: str,
+ instance_key: str,
+ in_room: str | None = None,
+ on_instance: str | None = None,
+ beside_instance: str | None = None,
+ beside_distance: float = DEFAULT_BESIDE_DISTANCE,
+ rotation_rpy: tuple[float, float, float] = DEFAULT_ROTATION_RPY,
+ n_max_attempt: int = DEFAULT_MAX_PLACEMENT_ATTEMPTS,
+ place_strategy: Literal["top", "random"] = "random",
+ ) -> list[float] | None:
+ """Add a new instance to the scene with automatic placement.
+
+ Args:
+ asset_path: Path to the asset mesh file.
+ instance_key: Unique key for the new instance.
+ in_room: Optional room name to constrain placement.
+ on_instance: Optional instance name to place on top of.
+ beside_instance: Optional instance name to place beside (on floor).
+ beside_distance: Initial buffer distance from the target instance
+ for beside placement (meters). Will auto-expand if needed.
+ rotation_rpy: Initial rotation in roll-pitch-yaw.
+ n_max_attempt: Maximum placement attempts.
+ place_strategy: Either "top" or "random".
+
+ Returns:
+ List [x, y, z] of the placed instance center, or None if failed.
+
+ Raises:
+ ValueError: If instance_key already exists or room/instance not found.
+
+ """
+ if instance_key in self.instances:
+ raise ValueError(f"Instance key '{instance_key}' already exists.")
+
+ room_poly = self._resolve_room_polygon(in_room)
+
+ # Load mesh and compute base polygon (needed for all placement modes)
+ mesh = trimesh.load(asset_path, force="mesh")
+ mesh.apply_transform(
+ trimesh.transformations.euler_matrix(*rotation_rpy, "sxyz")
+ )
+
+ verts = np.asarray(mesh.sample(self.mesh_sample_num))[:, :2]
+ base_poly = points_to_polygon(verts)
+ centroid = base_poly.centroid
+ base_poly = translate(base_poly, xoff=-centroid.x, yoff=-centroid.y)
+
+ if beside_instance is not None:
+ placement = self._try_place_beside(
+ base_poly=base_poly,
+ beside_instance=beside_instance,
+ room_poly=room_poly,
+ beside_distance=beside_distance,
+ n_max_attempt=n_max_attempt,
+ multi_match_strategy="first", # Default strategy
+ )
+ base_z = 0.0
+ else:
+ target_area, obstacles, base_z = self._resolve_placement_target(
+ on_instance, room_poly, place_strategy
+ )
+
+ if target_area.is_empty:
+ logger.error("Target area for placement is empty.")
+ return None
+
+ placement = self._try_place_polygon(
+ base_poly, target_area, obstacles, n_max_attempt
+ )
+
+ if placement is None:
+ logger.error(
+ f"Failed to place '{instance_key}' after all attempts."
+ )
+ return None
+
+ x, y, candidate = placement
+ self.instances[instance_key] = candidate
+ final_z = base_z - mesh.bounds[0][2] + DEFAULT_Z_OFFSET
+ self._update_internal_state()
+
+ return [round(v, 4) for v in (x, y, final_z)]
+
+ def _resolve_room_polygon(self, in_room: str | None) -> Geometry | None:
+ """Resolve room name to polygon.
+
+ Args:
+ in_room: Room name query string.
+
+ Returns:
+ Room polygon or None if not specified.
+
+ Raises:
+ ValueError: If room not found.
+
+ """
+ if in_room is None:
+ return None
+
+ query_room = in_room.lower()
+ room_matches = [
+ k for k in self.rooms.keys() if query_room in k.lower()
+ ]
+
+ if not room_matches:
+ raise ValueError(f"Room '{in_room}' not found.")
+
+ return unary_union([self.rooms[k] for k in room_matches])
+
+ def _try_place_beside(
+ self,
+ base_poly: Polygon,
+ beside_instance: str,
+ room_poly: Geometry | None,
+ beside_distance: float = DEFAULT_BESIDE_DISTANCE,
+ n_max_attempt: int = DEFAULT_MAX_PLACEMENT_ATTEMPTS,
+ max_expand_steps: int = 5,
+ expand_factor: float = 1.5,
+ multi_match_strategy: Literal["first", "random", "largest"] = "first",
+ ) -> tuple[float, float, Polygon] | None:
+ """Place object beside target with progressive distance expansion.
+
+ More robust than fixed-distance placement:
+ 1. Ensures minimum distance accommodates the new object's size.
+ 2. Pre-subtracts obstacles from the ring โ sampling only in free area.
+ 3. Progressively expands distance on failure (up to max_expand_steps).
+ 4. Skips steps where the free area is too small for the object.
+
+ Args:
+ base_poly: Object footprint polygon centered at origin.
+ beside_instance: Target instance name to place beside.
+ room_poly: Optional room constraint polygon.
+ beside_distance: Initial buffer distance (meters).
+ n_max_attempt: Total max placement attempts across all steps.
+ max_expand_steps: Max number of distance expansion rounds.
+ expand_factor: Distance multiplier per expansion round.
+ multi_match_strategy: How to pick among multiple matching target
+ instances ("first", "random", or "largest").
+
+ Returns:
+ Tuple (x, y, placed_polygon) on success, or None if all failed.
+
+ Raises:
+ ValueError: If beside_instance not found in scene.
+
+ """
+ # --- Resolve target instance ---
+ query_obj = beside_instance.lower()
+ possible_matches = [
+ k
+ for k in self.instances.keys()
+ if query_obj in k.lower() and k != "walls"
+ ]
+
+ if room_poly is not None:
+ # Check that the object's representative point falls inside
+ # the room (buffered slightly for mesh-sampling tolerance).
+ room_buffered = room_poly.buffer(0.1)
+ possible_matches = [
+ k
+ for k in possible_matches
+ if room_buffered.contains(
+ self.instances[k].representative_point()
+ )
+ ]
+
+ if not possible_matches:
+ location_msg = " in specified room" if room_poly else ""
+ # Log candidate distances for easier debugging
+ all_matches = [
+ k
+ for k in self.instances.keys()
+ if query_obj in k.lower() and k != "walls"
+ ]
+ if all_matches and room_poly is not None:
+ dists = {
+ k: round(self.instances[k].distance(room_poly), 4)
+ for k in all_matches
+ }
+ logger.error("Candidate distances to room polygon: %s", dists)
+ raise ValueError(
+ f"No instance matching '{beside_instance}' "
+ f"found{location_msg}."
+ )
+
+ if len(possible_matches) > 1:
+ # Apply multi-match strategy
+ if multi_match_strategy == "random":
+ target_key = random.choice(possible_matches)
+ elif multi_match_strategy == "largest":
+ target_key = max(
+ possible_matches, key=lambda k: self.instances[k].area
+ )
+ else: # "first"
+ target_key = possible_matches[0]
+ logger.warning(
+ f"Multiple matches for '{beside_instance}': "
+ f"{possible_matches}. Using '{target_key}' "
+ f"(strategy: {multi_match_strategy})."
+ )
+ else:
+ target_key = possible_matches[0]
+
+ target_footprint = self.instances[target_key]
+ floor = room_poly if room_poly is not None else self.floor_union
+
+ # --- Ensure initial distance accommodates the object's size ---
+ obj_bounds = base_poly.bounds # (minx, miny, maxx, maxy)
+ obj_half_diag = (
+ np.hypot(
+ obj_bounds[2] - obj_bounds[0],
+ obj_bounds[3] - obj_bounds[1],
+ )
+ / 2.0
+ )
+ current_distance = max(beside_distance, obj_half_diag * 1.5)
+
+ # Budget attempts across expansion steps
+ attempts_per_step = max(n_max_attempt // (max_expand_steps + 1), 500)
+ empty_obstacle = Polygon() # pre-created; obstacles are pre-subtracted
+
+ for step in range(max_expand_steps + 1):
+ # Build ring: buffer - footprint, intersected with floor
+ buffered = target_footprint.buffer(current_distance)
+ ring_area = buffered.difference(target_footprint)
+ ring_area = ring_area.intersection(floor)
+
+ if ring_area.is_empty:
+ logger.info(
+ f"[beside step {step}] Ring empty at "
+ f"{current_distance:.2f}m, expanding..."
+ )
+ current_distance *= expand_factor
+ continue
+
+ # Pre-subtract all obstacles โ sample only from actual free area
+ free_area = ring_area.difference(self.occ_area)
+
+ if free_area.is_empty or free_area.area < base_poly.area * 0.5:
+ logger.info(
+ f"[beside step {step}] Free area too small at "
+ f"{current_distance:.2f}m "
+ f"(free={free_area.area:.4f}, "
+ f"needโ{base_poly.area:.4f}), expanding..."
+ )
+ current_distance *= expand_factor
+ continue
+
+ # Attempt placement in the free area (obstacles already removed)
+ placement = self._try_place_polygon(
+ base_poly, free_area, empty_obstacle, attempts_per_step
+ )
+
+ if placement is not None:
+ logger.info(
+ f"Placed beside '{target_key}' at distance "
+ f"{current_distance:.2f}m (step {step})"
+ )
+ return placement
+
+ logger.info(
+ f"[beside step {step}] Failed at {current_distance:.2f}m "
+ f"after {attempts_per_step} attempts, expanding..."
+ )
+ current_distance *= expand_factor
+
+ logger.error(
+ f"Failed to place beside '{target_key}' after "
+ f"{max_expand_steps + 1} expansion steps "
+ f"(final distance: {current_distance / expand_factor:.2f}m)."
+ )
+ return None
+
+ def _resolve_placement_target(
+ self,
+ on_instance: str | None,
+ room_poly: Geometry | None,
+ place_strategy: Literal["top", "random"],
+ ) -> tuple[Geometry, Geometry, float]:
+ """Resolve the target placement area and obstacles.
+
+ Args:
+ on_instance: Instance name to place on.
+ room_poly: Room polygon constraint.
+ place_strategy: Placement strategy.
+
+ Returns:
+ Tuple of (target_area, obstacles, base_z_height).
+
+ Raises:
+ ValueError: If on_instance not found.
+
+ """
+ if on_instance is None:
+ if room_poly is not None:
+ return room_poly, self.occ_area, 0.0
+ return self.floor_union, self.occ_area, 0.0
+
+ query_obj = on_instance.lower()
+ possible_matches = [
+ k
+ for k in self.instances.keys()
+ if query_obj in k.lower() and k != "walls"
+ ]
+
+ if room_poly is not None:
+ room_buffered = room_poly.buffer(0.1)
+ possible_matches = [
+ k
+ for k in possible_matches
+ if room_buffered.contains(
+ self.instances[k].representative_point()
+ )
+ ]
+
+ if not possible_matches:
+ location_msg = f" in room '{on_instance}'" if room_poly else ""
+ raise ValueError(
+ f"No instance matching '{on_instance}' found{location_msg}."
+ )
+
+ if place_strategy == "random":
+ target_parent_key = random.choice(possible_matches)
+ else:
+ target_parent_key = possible_matches[0]
+
+ if len(possible_matches) > 1:
+ logger.warning(
+ f"Multiple matches for '{on_instance}': {possible_matches}. "
+ f"Using '{target_parent_key}'."
+ )
+
+ meta = self.instance_meta[target_parent_key]
+ parent_mesh = trimesh.load(meta["mesh_path"], force="mesh")
+ matrix = np.eye(4)
+ matrix[:3, :3] = R.from_euler("xyz", meta["rpy"]).as_matrix()
+ matrix[:3, 3] = meta["xyz"]
+ parent_mesh.apply_transform(matrix)
+
+ best_z, surface_poly = get_actionable_surface(
+ parent_mesh, place_strategy=place_strategy
+ )
+ obstacles = self.occ_area.difference(self.instances[target_parent_key])
+
+ # Re-add footprints of objects inside the parent polygon so they
+ # remain obstacles (difference above removes them).
+ parent_poly = self.instances[target_parent_key]
+ children_on_parent = [
+ poly
+ for key, poly in self.footprints.items()
+ if key != target_parent_key and parent_poly.contains(poly)
+ ]
+ if children_on_parent:
+ obstacles = unary_union([obstacles] + children_on_parent)
+
+ logger.info(f"Placing on '{target_parent_key}' (Z={best_z:.3f})")
+
+ return surface_poly, obstacles, best_z
+
+ def _try_place_polygon(
+ self,
+ base_poly: Polygon,
+ target_area: Geometry,
+ obstacles: Geometry,
+ n_max_attempt: int,
+ ) -> tuple[float, float, Polygon] | None:
+ """Try to place polygon in target area avoiding obstacles.
+
+ Pre-computes the free area (target minus obstacles) so that the
+ containment check alone is sufficient, avoiding redundant
+ intersection tests against obstacles on every iteration.
+
+ Args:
+ base_poly: Polygon to place (centered at origin).
+ target_area: Area where placement is allowed.
+ obstacles: Areas to avoid.
+ n_max_attempt: Maximum attempts.
+
+ Returns:
+ Tuple of (x, y, placed_polygon) or None if failed.
+
+ """
+ if not obstacles.is_empty:
+ free_area = target_area.difference(obstacles)
+ else:
+ free_area = target_area
+
+ if free_area.is_empty:
+ return None
+
+ minx, miny, maxx, maxy = free_area.bounds
+
+ for _ in range(n_max_attempt):
+ x = np.random.uniform(minx, maxx)
+ y = np.random.uniform(miny, maxy)
+ candidate = translate(base_poly, xoff=x, yoff=y)
+
+ if free_area.contains(candidate):
+ return x, y, candidate
+
+ return None
+
+ def update_urdf_info(
+ self,
+ output_path: str,
+ instance_key: str,
+ visual_mesh_path: str,
+ collision_mesh_path: str | None = None,
+ trans_xyz: tuple[float, float, float] = (0, 0, 0),
+ rot_rpy: tuple[float, float, float] = DEFAULT_ROTATION_RPY,
+ joint_type: str = "fixed",
+ ) -> None:
+ """Add a new link to the URDF tree and save.
+
+ Args:
+ output_path: Path to save the updated URDF.
+ instance_key: Name for the new link.
+ visual_mesh_path: Path to the visual mesh file.
+ collision_mesh_path: Optional path to collision mesh.
+ trans_xyz: Translation (x, y, z).
+ rot_rpy: Rotation (roll, pitch, yaw).
+ joint_type: Type of joint (e.g., "fixed").
+
+ """
+ if self._root is None:
+ return
+
+ logger.info(f"Updating URDF for instance '{instance_key}'.")
+ urdf_dir = os.path.dirname(self.urdf_path)
+
+ # Copy mesh files
+ copytree(
+ os.path.dirname(visual_mesh_path),
+ f"{urdf_dir}/{instance_key}",
+ dirs_exist_ok=True,
+ )
+ visual_rel_path = (
+ f"{instance_key}/{os.path.basename(visual_mesh_path)}"
+ )
+
+ collision_rel_path = None
+ if collision_mesh_path is not None:
+ copytree(
+ os.path.dirname(collision_mesh_path),
+ f"{urdf_dir}/{instance_key}",
+ dirs_exist_ok=True,
+ )
+ collision_rel_path = (
+ f"{instance_key}/{os.path.basename(collision_mesh_path)}"
+ )
+
+ # Create link element
+ link = ET.SubElement(self._root, "link", attrib={"name": instance_key})
+
+ visual = ET.SubElement(link, "visual")
+ v_geo = ET.SubElement(visual, "geometry")
+ ET.SubElement(v_geo, "mesh", attrib={"filename": visual_rel_path})
+
+ if collision_rel_path is not None:
+ collision = ET.SubElement(link, "collision")
+ c_geo = ET.SubElement(collision, "geometry")
+ ET.SubElement(
+ c_geo, "mesh", attrib={"filename": collision_rel_path}
+ )
+
+ # Create joint element
+ joint_name = f"joint_{instance_key}"
+ joint = ET.SubElement(
+ self._root,
+ "joint",
+ attrib={"name": joint_name, "type": joint_type},
+ )
+
+ ET.SubElement(joint, "parent", attrib={"link": "base"})
+ ET.SubElement(joint, "child", attrib={"link": instance_key})
+
+ xyz_str = f"{trans_xyz[0]:.4f} {trans_xyz[1]:.4f} {trans_xyz[2]:.4f}"
+ rpy_str = f"{rot_rpy[0]:.4f} {rot_rpy[1]:.4f} {rot_rpy[2]:.4f}"
+ ET.SubElement(joint, "origin", attrib={"xyz": xyz_str, "rpy": rpy_str})
+
+ self.save_urdf(output_path)
+
+ def update_usd_info(
+ self,
+ usd_path: str,
+ output_path: str,
+ instance_key: str,
+ visual_mesh_path: str,
+ trans_xyz: list[float],
+ rot_rpy: tuple[float, float, float] = DEFAULT_ROTATION_RPY,
+ ) -> None:
+ """Add a mesh instance to an existing USD file.
+
+ Uses Blender (bpy) to convert OBJ to USD format.
+
+ Args:
+ usd_path: Path to the source USD file.
+ output_path: Path to save the modified USD.
+ instance_key: Prim path name for the new instance.
+ visual_mesh_path: Path to the visual mesh (OBJ format).
+ trans_xyz: Translation [x, y, z].
+ rot_rpy: Rotation (roll, pitch, yaw).
+
+ Raises:
+ ImportError: If pxr (USD) library or bpy is not available.
+
+ """
+ import bpy
+ from pxr import Gf, Usd, UsdGeom
+
+ out_dir = os.path.dirname(output_path)
+ target_dir = os.path.join(out_dir, instance_key)
+ os.makedirs(target_dir, exist_ok=True)
+
+ mesh_filename = os.path.basename(visual_mesh_path)
+ usdc_filename = os.path.splitext(mesh_filename)[0] + ".usdc"
+ target_usdc_path = os.path.join(target_dir, usdc_filename)
+
+ logger.info(
+ f"Converting with Blender (bpy): "
+ f"{visual_mesh_path} -> {target_usdc_path}"
+ )
+ bpy.ops.wm.read_factory_settings(use_empty=True)
+ bpy.ops.wm.obj_import(
+ filepath=visual_mesh_path,
+ forward_axis="Y",
+ up_axis="Z",
+ )
+ bpy.ops.wm.usd_export(
+ filepath=target_usdc_path,
+ selected_objects_only=False,
+ )
+
+ # Copy texture files
+ src_dir = os.path.dirname(visual_mesh_path)
+ for f in os.listdir(src_dir):
+ if f.lower().endswith((".png", ".jpg", ".jpeg", ".mtl")):
+ copy2(os.path.join(src_dir, f), target_dir)
+
+ final_rel_path = f"./{instance_key}/{usdc_filename}"
+
+ # Update USD stage
+ stage = Usd.Stage.Open(usd_path)
+ prim_path = self._usd_instance_prim_path(stage, instance_key)
+ mesh_prim = UsdGeom.Xform.Define(stage, prim_path)
+
+ ref_prim = UsdGeom.Mesh.Define(stage, f"{prim_path}/Mesh")
+ ref_prim.GetPrim().GetReferences().AddReference(final_rel_path)
+
+ # Build transform matrix
+ translation_mat = Gf.Matrix4d().SetTranslate(
+ Gf.Vec3d(trans_xyz[0], trans_xyz[1], trans_xyz[2])
+ )
+ rx = Gf.Matrix4d().SetRotate(
+ Gf.Rotation(Gf.Vec3d(1, 0, 0), np.degrees(rot_rpy[0]))
+ )
+ ry = Gf.Matrix4d().SetRotate(
+ Gf.Rotation(Gf.Vec3d(0, 1, 0), np.degrees(rot_rpy[1]))
+ )
+ rz = Gf.Matrix4d().SetRotate(
+ Gf.Rotation(Gf.Vec3d(0, 0, 1), np.degrees(rot_rpy[2]))
+ )
+ rotation_mat = rx * ry * rz
+ transform = rotation_mat * translation_mat
+ mesh_prim.AddTransformOp().Set(transform)
+
+ stage.GetRootLayer().Export(output_path)
+ logger.info(f"โ
Saved updated USD to {output_path}")
+
+ @staticmethod
+ def _usd_scene_root_path(stage: Any) -> str:
+ """Return the prim path that should own scene-authored assets."""
+ default_prim = stage.GetDefaultPrim()
+ if default_prim and default_prim.IsValid():
+ return default_prim.GetPath().pathString
+
+ world_prim = stage.GetPrimAtPath("/World")
+ if world_prim and world_prim.IsValid():
+ stage.SetDefaultPrim(world_prim)
+ logger.info(
+ "USD has no defaultPrim; using '/World' as scene root."
+ )
+ return "/World"
+
+ return ""
+
+ @classmethod
+ def _usd_instance_prim_path(cls, stage: Any, instance_key: str) -> str:
+ """Build a USD prim path under the stage defaultPrim when available."""
+ scene_root_path = cls._usd_scene_root_path(stage)
+ if scene_root_path:
+ return f"{scene_root_path}/{instance_key}"
+ return f"/{instance_key}"
+
+ def remove_usd_instance(
+ self,
+ usd_path: str,
+ output_path: str,
+ instance_key: str,
+ ) -> None:
+ """Remove an instance from a USD file.
+
+ Args:
+ usd_path: Path to the source USD file.
+ output_path: Path to save the modified USD.
+ instance_key: Prim path name of the instance to remove.
+
+ Raises:
+ ImportError: If pxr (USD) library is not available.
+
+ """
+ from pxr import Usd
+
+ # Open USD stage
+ stage = Usd.Stage.Open(usd_path)
+
+ # Find and remove the prim. Check the defaultPrim path first, and
+ # keep the old root-level path as a compatibility fallback.
+ prim_paths = [self._usd_instance_prim_path(stage, instance_key)]
+ legacy_prim_path = f"/{instance_key}"
+ if legacy_prim_path not in prim_paths:
+ prim_paths.append(legacy_prim_path)
+
+ removed = False
+ for prim_path in prim_paths:
+ prim = stage.GetPrimAtPath(prim_path)
+ if prim.IsValid():
+ stage.RemovePrim(prim_path)
+ logger.info(f"Removed prim '{prim_path}' from USD.")
+ removed = True
+
+ if not removed:
+ logger.warning(
+ f"Prim '{instance_key}' not found in USD stage under "
+ "defaultPrim or legacy root path."
+ )
+
+ # Export modified stage
+ stage.GetRootLayer().Export(output_path)
+ logger.info(f"โ
Saved updated USD to {output_path}")
+
+ def remove_instance(
+ self,
+ instance_key: str,
+ in_room: str | None = None,
+ ) -> bool:
+ """Remove an instance from the scene.
+
+ Args:
+ instance_key: Exact instance name or semantic description to remove.
+ in_room: Optional room constraint - only remove if instance is in this room.
+
+ Returns:
+ True if instance was removed, False if not found.
+
+ Raises:
+ ValueError: If instance_key is a protected item (walls, floors).
+
+ """
+ # Protect critical items
+ protected = ["walls"] + [
+ k for k in self.instances.keys() if "floor" in k.lower()
+ ]
+ if instance_key in protected:
+ raise ValueError(
+ f"Cannot remove protected instance '{instance_key}'. "
+ f"Protected items: {protected}"
+ )
+
+ # Check if instance exists
+ if instance_key not in self.instances:
+ logger.warning(f"Instance '{instance_key}' not found in scene.")
+ return False
+
+ # Check room constraint if specified
+ if in_room is not None:
+ room_poly = self._resolve_room_polygon(in_room)
+ if room_poly is not None:
+ room_buffered = room_poly.buffer(0.1)
+ instance_point = self.instances[
+ instance_key
+ ].representative_point()
+ if not room_buffered.contains(instance_point):
+ logger.warning(
+ f"Instance '{instance_key}' is not in room '{in_room}'."
+ )
+ return False
+
+ # Remove from URDF XML tree
+ if self._root is not None:
+ self._remove_link_and_joint(instance_key)
+
+ # Remove from instances dict
+ del self.instances[instance_key]
+
+ # Remove from metadata
+ if instance_key in self.instance_meta:
+ del self.instance_meta[instance_key]
+
+ # Update internal state
+ self._update_internal_state()
+
+ logger.info(f"โ
Removed instance '{instance_key}' from scene.")
+ return True
+
+ def _remove_link_and_joint(self, instance_key: str) -> None:
+ """Remove link and joint elements from URDF XML tree.
+
+ Args:
+ instance_key: Key of the instance to remove (simplified key).
+
+ """
+ if self._root is None:
+ return
+
+ # Get original link name from metadata
+ meta = self.instance_meta.get(instance_key, {})
+ original_link_name = meta.get("original_link_name", instance_key)
+
+ # Find and remove the link element
+ link_removed = False
+ for link in self._root.findall("link"):
+ if link.attrib.get("name") == original_link_name:
+ self._root.remove(link)
+ logger.info(f"Removed link '{original_link_name}' from URDF.")
+ link_removed = True
+ break
+
+ if not link_removed:
+ logger.warning(
+ f"Link '{original_link_name}' not found in URDF tree."
+ )
+
+ # Find and remove the joint element
+ joint_removed = False
+ for joint in self._root.findall("joint"):
+ child = joint.find("child")
+ if (
+ child is not None
+ and child.attrib.get("link") == original_link_name
+ ):
+ self._root.remove(joint)
+ logger.info(
+ f"Removed joint for '{original_link_name}' from URDF."
+ )
+ joint_removed = True
+ break
+
+ if not joint_removed:
+ logger.warning(
+ f"Joint for '{original_link_name}' not found in URDF tree."
+ )
+
+ def get_instance_center(self, instance_key: str) -> list[float] | None:
+ """Get the center position of an instance.
+
+ Args:
+ instance_key: Name of the instance to query.
+
+ Returns:
+ List [x, y, z] of the instance center, or None if not found.
+
+ """
+ if instance_key not in self.instances:
+ logger.warning(f"Instance '{instance_key}' not found in scene.")
+ return None
+
+ # Get instance metadata
+ meta = self.instance_meta.get(instance_key, {})
+ xyz = meta.get("xyz", np.zeros(3))
+
+ # Get polygon centroid for 2D position
+ poly = self.instances[instance_key]
+ centroid = poly.centroid
+
+ # Return [x, y, z] where x,y are from polygon centroid, z from metadata
+ center = [round(centroid.x, 4), round(centroid.y, 4), round(xyz[2], 4)]
+
+ logger.info(f"Instance '{instance_key}' center: {center}")
+ return center
+
+ def save_urdf(self, output_path: str) -> None:
+ """Save the current URDF tree to file.
+
+ Args:
+ output_path: Path to save the URDF file.
+
+ """
+ if self._tree is None:
+ return
+
+ if hasattr(ET, "indent"):
+ ET.indent(self._tree, space=" ", level=0)
+
+ self._tree.write(output_path, encoding="utf-8", xml_declaration=True)
+ logger.info(f"โ
Saved updated URDF to {output_path}")
diff --git a/embodied_gen/skills/spatial-computing/core/geometry.py b/embodied_gen/skills/spatial-computing/core/geometry.py
new file mode 100644
index 0000000..51bde8e
--- /dev/null
+++ b/embodied_gen/skills/spatial-computing/core/geometry.py
@@ -0,0 +1,231 @@
+# Project EmbodiedGen
+#
+# Copyright (c) 2025 Horizon Robotics. All Rights Reserved.
+#
+# 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.
+
+
+from __future__ import annotations
+
+import logging
+import random
+from typing import Literal
+
+import numpy as np
+import trimesh
+from shapely.geometry import MultiPoint, MultiPolygon, Polygon
+
+logger = logging.getLogger(__name__)
+
+# Type aliases
+Geometry = Polygon | MultiPolygon
+
+# Constants
+DEFAULT_MESH_SAMPLE_NUM = 10000
+DEFAULT_MAX_PLACEMENT_ATTEMPTS = 2000
+
+
+def points_to_polygon(
+ points: np.ndarray,
+ smooth_thresh: float = 0.2,
+ scanline_step: float = 0.01,
+) -> Polygon:
+ """Convert point clouds into polygon contours using sweep line algorithm.
+
+ Args:
+ points: Array of 2D points with shape (N, 2).
+ smooth_thresh: Buffer threshold for smoothing the polygon.
+ scanline_step: Step size for the scanline sweep.
+
+ Returns:
+ A Shapely Polygon representing the contour of the point cloud.
+
+ """
+ if len(points) == 0:
+ return Polygon()
+
+ ys = points[:, 1]
+ y_min, y_max = ys.min(), ys.max()
+ y_values = np.arange(y_min, y_max + scanline_step, scanline_step)
+
+ upper: list[list[float]] = []
+ lower: list[list[float]] = []
+
+ for y in y_values:
+ pts_in_strip = points[(ys >= y) & (ys < y + scanline_step)]
+ if len(pts_in_strip) == 0:
+ continue
+
+ xs = pts_in_strip[:, 0]
+ upper.append([xs.max(), y])
+ lower.append([xs.min(), y])
+
+ contour = upper + lower[::-1]
+ if len(contour) < 3:
+ return Polygon()
+
+ poly = Polygon(contour)
+ return poly.buffer(smooth_thresh).buffer(-smooth_thresh)
+
+
+def get_actionable_surface(
+ mesh: trimesh.Trimesh,
+ tol_angle: int = 10,
+ tol_z: float = 0.02,
+ area_tolerance: float = 0.15,
+ place_strategy: Literal["top", "random"] = "random",
+) -> tuple[float, Geometry]:
+ """Extract the actionable (placeable) surface from a mesh.
+
+ Finds upward-facing surfaces and returns the best one based on the
+ placement strategy.
+
+ Args:
+ mesh: The input trimesh object.
+ tol_angle: Angle tolerance in degrees for detecting up-facing normals.
+ tol_z: Z-coordinate tolerance for clustering faces.
+ area_tolerance: Tolerance for selecting candidate surfaces by area.
+ place_strategy: Either "top" (highest surface) or "random".
+
+ Returns:
+ A tuple of (z_height, surface_polygon) representing the selected
+ actionable surface.
+
+ """
+ up_vec = np.array([0, 0, 1])
+ dots = np.dot(mesh.face_normals, up_vec)
+ valid_mask = dots > np.cos(np.deg2rad(tol_angle))
+
+ if not np.any(valid_mask):
+ logger.warning(
+ "No up-facing surfaces found. Falling back to bounding box top."
+ )
+ verts = mesh.vertices[:, :2]
+ return mesh.bounds[1][2], MultiPoint(verts).convex_hull
+
+ valid_faces_indices = np.where(valid_mask)[0]
+ face_z = mesh.triangles_center[valid_mask][:, 2]
+ face_areas = mesh.area_faces[valid_mask]
+
+ z_clusters = _cluster_faces_by_z(
+ face_z, face_areas, valid_faces_indices, tol_z
+ )
+
+ if not z_clusters:
+ return mesh.bounds[1][2], MultiPoint(mesh.vertices[:, :2]).convex_hull
+
+ selected_z, selected_data = _select_surface_cluster(
+ z_clusters, area_tolerance, place_strategy
+ )
+
+ # For "top" strategy, use the highest z among all clusters for
+ # base height, while keeping the largest-area polygon for XY placement.
+ if place_strategy == "top":
+ highest_z = max(z_clusters.keys())
+ if highest_z > selected_z:
+ logger.info(
+ f"Overriding base Z from {selected_z:.3f} to "
+ f"highest surface {highest_z:.3f}"
+ )
+ selected_z = highest_z
+
+ cluster_faces = mesh.faces[selected_data["indices"]]
+ temp_mesh = trimesh.Trimesh(vertices=mesh.vertices, faces=cluster_faces)
+ samples, _ = trimesh.sample.sample_surface(temp_mesh, 10000)
+
+ if len(samples) < 3:
+ logger.warning(
+ f"Failed to sample enough points on layer Z={selected_z}. "
+ "Returning empty polygon."
+ )
+ return selected_z, Polygon()
+
+ surface_poly = MultiPoint(samples[:, :2]).convex_hull
+ return selected_z, surface_poly
+
+
+def _cluster_faces_by_z(
+ face_z: np.ndarray,
+ face_areas: np.ndarray,
+ face_indices: np.ndarray,
+ tol_z: float,
+) -> dict[float, dict]:
+ """Cluster mesh faces by their Z coordinate.
+
+ Args:
+ face_z: Z coordinates of face centers.
+ face_areas: Areas of each face.
+ face_indices: Original indices of the faces.
+ tol_z: Tolerance for Z clustering.
+
+ Returns:
+ Dictionary mapping Z values to cluster data (area and indices).
+
+ """
+ z_clusters: dict[float, dict] = {}
+
+ for i, z in enumerate(face_z):
+ key = round(z / tol_z) * tol_z
+
+ if key not in z_clusters:
+ z_clusters[key] = {"area": 0.0, "indices": []}
+
+ z_clusters[key]["area"] += face_areas[i]
+ z_clusters[key]["indices"].append(face_indices[i])
+
+ return z_clusters
+
+
+def _select_surface_cluster(
+ z_clusters: dict[float, dict],
+ area_tolerance: float,
+ place_strategy: Literal["top", "random"],
+) -> tuple[float, dict]:
+ """Select the best surface cluster based on strategy.
+
+ Args:
+ z_clusters: Dictionary of Z clusters with area and indices.
+ area_tolerance: Tolerance for candidate selection by area.
+ place_strategy: Either "top" or "random".
+
+ Returns:
+ Tuple of (selected_z, cluster_data).
+
+ """
+ max_area = max(c["area"] for c in z_clusters.values())
+ candidates = [
+ (z, data)
+ for z, data in z_clusters.items()
+ if data["area"] >= max_area * (1.0 - area_tolerance)
+ ]
+
+ if not candidates:
+ best_item = max(z_clusters.items(), key=lambda x: x[1]["area"])
+ candidates = [best_item]
+
+ if place_strategy == "random":
+ selected_z, selected_data = random.choice(candidates)
+ logger.info(
+ f"Strategy 'random': Selected Z={selected_z:.3f} "
+ f"(Area={selected_data['area']:.3f}) "
+ f"from {len(candidates)} candidates."
+ )
+ else:
+ candidates.sort(key=lambda x: x[0], reverse=True)
+ selected_z, selected_data = candidates[0]
+ logger.info(
+ f"Strategy 'top': Selected highest Z={selected_z:.3f} "
+ f"(Area={selected_data['area']:.3f})"
+ )
+
+ return selected_z, selected_data
diff --git a/embodied_gen/skills/spatial-computing/core/trajectory.py b/embodied_gen/skills/spatial-computing/core/trajectory.py
new file mode 100644
index 0000000..a1e3de5
--- /dev/null
+++ b/embodied_gen/skills/spatial-computing/core/trajectory.py
@@ -0,0 +1,885 @@
+# Project EmbodiedGen
+#
+# Copyright (c) 2025 Horizon Robotics. All Rights Reserved.
+#
+# 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.
+
+
+from __future__ import annotations
+
+import heapq
+import logging
+import math
+from dataclasses import dataclass, field
+
+import numpy as np
+import shapely
+from scipy.ndimage import distance_transform_edt, label
+from shapely.geometry import MultiPolygon, Polygon
+from shapely.ops import unary_union
+
+logger = logging.getLogger(__name__)
+
+# Type aliases
+Geometry = Polygon | MultiPolygon
+
+# Constants
+DEFAULT_CLEARANCE = 0.4
+"""Minimum distance (m) required between the trajectory and any obstacle."""
+
+DEFAULT_RESOLUTION = 0.05
+"""Occupancy-grid cell size in meters."""
+
+DEFAULT_OBSTACLE_IGNORE = ("door",)
+"""Footprint name keywords excluded from obstacles (treated as open)."""
+
+DEFAULT_NUM_WAYPOINTS = 8
+"""Number of roam waypoints sampled across the navigable space."""
+
+DEFAULT_POINT_SPACING = 0.1
+"""Output spacing (m) between consecutive trajectory points."""
+
+DEFAULT_TURN_RADIUS = 0.5
+"""Target turning-arc radius (m) for rounding corners."""
+
+DEFAULT_ENDPOINT_CLEARANCE = 1.5
+"""Minimum distance (m) the start/end keep from walls and objects."""
+
+HAIRPIN_REVERSAL_DOT = -0.85
+"""In/out direction dot product below which a turn is treated as a hairpin
+(near 180 deg) and collapsed; normal roam turns are preserved."""
+
+TURN_FRAME_TRIGGER_DEG = 45.0
+"""Only heading jumps larger than this (a big U-turn at an unavoidable sharp
+corner) get extra in-place rotation frames; gentler turns flow continuously."""
+
+TURN_FRAME_STEP_DEG = 20.0
+"""Heading step (deg) of each inserted in-place rotation frame."""
+
+MIN_FILLET_RADIUS = 0.15
+"""Smallest fillet radius (m) tried when rounding a corner; below this a
+corner that still cannot be rounded is left sharp."""
+
+# 8-connected neighbor offsets (di, dj) with step cost.
+_NEIGHBORS = [
+ (-1, 0, 1.0),
+ (1, 0, 1.0),
+ (0, -1, 1.0),
+ (0, 1, 1.0),
+ (-1, -1, math.sqrt(2)),
+ (-1, 1, math.sqrt(2)),
+ (1, -1, math.sqrt(2)),
+ (1, 1, math.sqrt(2)),
+]
+
+
+@dataclass
+class TrajectoryResult:
+ """Result of a roaming-trajectory generation.
+
+ Attributes:
+ points: Array (N, 3) of (x, y, rot_deg). rot_deg is the heading
+ tangent to the curve: 0 deg points +Y (12 o'clock), increasing
+ counter-clockwise in [0, 360).
+ clearance: Clearance radius (m) used for planning.
+ min_clearance: Minimum obstacle clearance (m) along the trajectory.
+ length: Total arc length (m) of the trajectory.
+ reachable_rooms: Room keys reachable within the planning component.
+ """
+
+ points: np.ndarray
+ clearance: float
+ min_clearance: float
+ length: float
+ reachable_rooms: list[str] = field(default_factory=list)
+
+
+def heading_to_rot_deg(dx: float, dy: float) -> float:
+ """Convert a motion direction to the roaming heading convention.
+
+ The heading is the forward (tangent) direction of travel, where 0 deg
+ points to +Y (12 o'clock) and the angle increases counter-clockwise.
+
+ Args:
+ dx: X component of the motion direction.
+ dy: Y component of the motion direction.
+
+ Returns:
+ Heading in degrees within [0, 360).
+ """
+ return math.degrees(math.atan2(-dx, dy)) % 360.0
+
+
+class RoamTrajectoryGenerator:
+ """Generate smooth, collision-free roaming trajectories on a floorplan.
+
+ The drivable region is the floor area minus furniture footprints. An
+ occupancy grid and its distance transform give per-cell clearance; cells
+ with clearance >= ``clearance`` form the navigable space. Spread waypoints
+ are sampled within one connected navigable component, ordered by polar
+ angle into a non-self-intersecting loop, and linked with a clearance-aware
+ A* planner. Each segment is simplified inside the navigable region, dead-
+ end hairpins are removed, corners are rounded, and a final clearance pass
+ keeps every sample collision-free; the start and end are pushed into open
+ space.
+
+ Doors are treated as open passages by default (excluded from obstacles),
+ matching scenes where doors are removed before rendering.
+ """
+
+ def __init__(
+ self,
+ floor: Geometry,
+ obstacles: Geometry,
+ clearance: float = DEFAULT_CLEARANCE,
+ resolution: float = DEFAULT_RESOLUTION,
+ rooms: dict[str, Geometry] | None = None,
+ ) -> None:
+ """Initialize the generator and build the navigability grid.
+
+ Args:
+ floor: Drivable floor region (e.g. union of all room floors).
+ obstacles: Union of obstacle footprints (e.g. furniture).
+ clearance: Minimum obstacle clearance (m) the path must keep.
+ resolution: Occupancy-grid cell size in meters.
+ rooms: Optional room-name to polygon map, used only to report
+ which rooms the trajectory can reach.
+ """
+ if floor.is_empty:
+ raise ValueError("Floor region is empty; cannot plan a path.")
+
+ self.clearance = clearance
+ self.resolution = resolution
+ self.rooms = rooms or {}
+
+ self.minx, self.miny, maxx, maxy = floor.bounds
+ self.nx = max(1, int(math.ceil((maxx - self.minx) / resolution)))
+ self.ny = max(1, int(math.ceil((maxy - self.miny) / resolution)))
+
+ xs = self.minx + (np.arange(self.nx) + 0.5) * resolution
+ ys = self.miny + (np.arange(self.ny) + 0.5) * resolution
+ grid_x, grid_y = np.meshgrid(xs, ys)
+ flat_x, flat_y = grid_x.ravel(), grid_y.ravel()
+
+ inside = shapely.contains_xy(floor, flat_x, flat_y)
+ if obstacles is not None and not obstacles.is_empty:
+ in_obs = shapely.contains_xy(obstacles, flat_x, flat_y)
+ else:
+ in_obs = np.zeros_like(inside)
+ free = (inside & ~in_obs).reshape(self.ny, self.nx)
+
+ self.clearance_map = distance_transform_edt(free) * resolution
+ self.nav = self.clearance_map >= clearance
+ self.labels, n_comp = label(self.nav, structure=np.ones((3, 3)))
+ logger.info(
+ "Navigability grid %dx%d, %d component(s), max clearance %.2fm.",
+ self.ny,
+ self.nx,
+ n_comp,
+ float(self.clearance_map.max()),
+ )
+
+ @classmethod
+ def from_collector(
+ cls,
+ collector,
+ clearance: float = DEFAULT_CLEARANCE,
+ resolution: float = DEFAULT_RESOLUTION,
+ obstacle_ignore: tuple[str, ...] = DEFAULT_OBSTACLE_IGNORE,
+ obstacle_clearance: float | None = None,
+ ) -> "RoamTrajectoryGenerator":
+ """Build a generator from a ``UrdfSemanticInfoCollector``.
+
+ Args:
+ collector: A collector that has already parsed a URDF scene.
+ clearance: Minimum clearance (m) kept from walls (the floor
+ boundary); keep it small so narrow doorways stay passable.
+ resolution: Occupancy-grid cell size in meters.
+ obstacle_ignore: Footprint name keywords to exclude from
+ obstacles (treated as open passages, e.g. doors).
+ obstacle_clearance: Minimum clearance (m) kept from furniture and
+ objects. When larger than ``clearance``, footprints are
+ inflated by the difference so the path stays this far from
+ them while still only needing ``clearance`` from walls.
+ Defaults to ``clearance`` (no extra furniture margin).
+
+ Returns:
+ A configured ``RoamTrajectoryGenerator``.
+ """
+ obstacle_polys = [
+ poly
+ for key, poly in collector.footprints.items()
+ if not any(kw in key.lower() for kw in obstacle_ignore)
+ ]
+ obstacles = (
+ unary_union(obstacle_polys) if obstacle_polys else Polygon()
+ )
+ margin = (obstacle_clearance or clearance) - clearance
+ if margin > 0 and not obstacles.is_empty:
+ obstacles = obstacles.buffer(margin)
+ return cls(
+ floor=collector.floor_union,
+ obstacles=obstacles,
+ clearance=clearance,
+ resolution=resolution,
+ rooms=collector.rooms,
+ )
+
+ # ----- coordinate helpers ------------------------------------------------
+
+ def _to_cell(self, x: float, y: float) -> tuple[int, int]:
+ """Convert world (x, y) to grid (row, col)."""
+ j = int((x - self.minx) / self.resolution)
+ i = int((y - self.miny) / self.resolution)
+ return i, j
+
+ def _to_world(self, i: int, j: int) -> tuple[float, float]:
+ """Convert grid (row, col) to world (x, y) at the cell center."""
+ x = self.minx + (j + 0.5) * self.resolution
+ y = self.miny + (i + 0.5) * self.resolution
+ return x, y
+
+ def _clearance_at(self, x: float, y: float) -> float:
+ """Return obstacle clearance (m) at the cell nearest to (x, y)."""
+ i, j = self._to_cell(x, y)
+ if 0 <= i < self.ny and 0 <= j < self.nx:
+ return float(self.clearance_map[i, j])
+ return 0.0
+
+ # ----- component / waypoint selection ------------------------------------
+
+ def _select_component(
+ self, start_xy: tuple[float, float] | None
+ ) -> tuple[np.ndarray, np.ndarray]:
+ """Choose the navigable component to roam within.
+
+ Args:
+ start_xy: Optional world start position. The component containing
+ the nearest navigable cell is used; otherwise the largest.
+
+ Returns:
+ Tuple of (component_mask, component_cells) where component_cells
+ is an (M, 2) array of (row, col) indices.
+
+ Raises:
+ ValueError: If no navigable space exists.
+ """
+ nav_cells = np.argwhere(self.nav)
+ if nav_cells.size == 0:
+ raise ValueError(
+ f"No navigable space at clearance {self.clearance}m. "
+ "Lower --clearance or --resolution."
+ )
+
+ if start_xy is not None:
+ i, j = self._to_cell(*start_xy)
+ comp_id = (
+ self.labels[i, j]
+ if 0 <= i < self.ny and 0 <= j < self.nx
+ else 0
+ )
+ if comp_id == 0:
+ # Snap to the nearest navigable cell.
+ deltas = nav_cells - np.array([i, j])
+ nearest = nav_cells[np.argmin((deltas**2).sum(axis=1))]
+ comp_id = self.labels[nearest[0], nearest[1]]
+ else:
+ counts = np.bincount(self.labels.ravel())
+ counts[0] = 0 # background
+ comp_id = int(counts.argmax())
+
+ mask = self.labels == comp_id
+ return mask, np.argwhere(mask)
+
+ def _sample_waypoints(
+ self,
+ comp_cells: np.ndarray,
+ num_waypoints: int,
+ start_xy: tuple[float, float] | None,
+ rng: np.random.Generator,
+ ) -> list[tuple[int, int]]:
+ """Sample spread-out waypoints via farthest-point sampling.
+
+ Args:
+ comp_cells: (M, 2) array of navigable (row, col) indices.
+ num_waypoints: Number of waypoints to select.
+ start_xy: Optional world start; seeds the first waypoint.
+ rng: Random generator for reproducibility.
+
+ Returns:
+ Ordered list of (row, col) waypoints, starting near ``start_xy``.
+ """
+ num_waypoints = max(2, min(num_waypoints, len(comp_cells)))
+
+ if start_xy is not None:
+ i, j = self._to_cell(*start_xy)
+ first = int(
+ np.argmin(((comp_cells - np.array([i, j])) ** 2).sum(axis=1))
+ )
+ else:
+ first = int(rng.integers(len(comp_cells)))
+
+ selected = [first]
+ min_dist = ((comp_cells - comp_cells[first]) ** 2).sum(axis=1)
+ for _ in range(num_waypoints - 1):
+ nxt = int(min_dist.argmax())
+ if min_dist[nxt] == 0:
+ break
+ selected.append(nxt)
+ d = ((comp_cells - comp_cells[nxt]) ** 2).sum(axis=1)
+ min_dist = np.minimum(min_dist, d)
+
+ return [tuple(comp_cells[idx]) for idx in selected]
+
+ @staticmethod
+ def _order_waypoints(
+ waypoints: list[tuple[int, int]],
+ ) -> list[tuple[int, int]]:
+ """Order waypoints into a non-self-intersecting loop by polar angle.
+
+ Sorting the waypoints by their angle around the centroid yields a
+ roughly convex sweep, which avoids the tangled crossings and the
+ collapsing back-and-forth of a nearest-neighbor tour, and keeps the
+ roam length stable. The angularly-first waypoint becomes the start.
+
+ Args:
+ waypoints: List of (row, col) waypoints.
+
+ Returns:
+ Angularly ordered waypoints forming a clean roaming loop.
+ """
+ if len(waypoints) <= 2:
+ return waypoints
+
+ pts = np.array(waypoints, dtype=float)
+ center = pts.mean(axis=0)
+ angles = np.arctan2(pts[:, 0] - center[0], pts[:, 1] - center[1])
+ order = np.argsort(angles)
+ return [waypoints[int(i)] for i in order]
+
+ def _snap_to_open(
+ self,
+ cell: tuple[int, int],
+ mask: np.ndarray,
+ min_clearance: float,
+ ) -> tuple[int, int]:
+ """Move a cell to the nearest navigable cell clear of walls/objects.
+
+ Returns the navigable cell nearest to ``cell`` whose clearance is at
+ least ``min_clearance`` (so the start/end sit comfortably away from
+ obstacles). If the cell already qualifies it is kept; if no cell in the
+ component qualifies, the original is returned with a warning.
+
+ Args:
+ cell: (row, col) to snap.
+ mask: Boolean grid of the navigable component.
+ min_clearance: Required minimum clearance (m) at the endpoint.
+
+ Returns:
+ A (row, col) cell with clearance >= ``min_clearance`` when one
+ exists in the component, else the original cell.
+ """
+ i, j = cell
+ if self.clearance_map[i, j] >= min_clearance:
+ return cell
+ open_cells = np.argwhere(mask & (self.clearance_map >= min_clearance))
+ if open_cells.size == 0:
+ logger.warning(
+ "No cell with clearance >= %.2fm in the component; keeping "
+ "endpoint as is.",
+ min_clearance,
+ )
+ return cell
+ deltas = open_cells - np.array([i, j])
+ nearest = open_cells[(deltas**2).sum(axis=1).argmin()]
+ return (int(nearest[0]), int(nearest[1]))
+
+ def _remove_reversals(
+ self,
+ vertices: list[tuple[float, float]],
+ mask: np.ndarray,
+ min_dot: float = HAIRPIN_REVERSAL_DOT,
+ ) -> list[tuple[float, float]]:
+ """Collapse hairpin reversals in the routed polyline.
+
+ A waypoint inside a dead-end (single-door) room makes the routed path
+ enter and leave through the same doorway, an unroundable U-turn that
+ waypoint-angle pruning cannot see. Here we work on the actual path
+ vertices: any vertex whose incoming and outgoing directions nearly
+ reverse is removed when the straight shortcut across it stays inside
+ the navigable mask, peeling the dead-end excursion back to the door.
+
+ Args:
+ vertices: Routed-and-simplified polyline vertices.
+ mask: Boolean grid of the navigable component.
+ min_dot: Reversal threshold on the in/out direction dot product
+ (โ1 = full reversal); default โ0.85 โ only near-180 deg
+ hairpins, so normal roam turns in open space are preserved.
+
+ Returns:
+ Polyline with hairpin excursions collapsed.
+ """
+ pts = [np.array(v, dtype=float) for v in vertices]
+ changed = True
+ while changed and len(pts) > 2:
+ changed = False
+ for i in range(1, len(pts) - 1):
+ d_in = pts[i] - pts[i - 1]
+ d_out = pts[i + 1] - pts[i]
+ n_in = float(np.hypot(d_in[0], d_in[1]))
+ n_out = float(np.hypot(d_out[0], d_out[1]))
+ if n_in < 1e-9 or n_out < 1e-9:
+ pts.pop(i)
+ changed = True
+ break
+ dot = float(np.dot(d_in / n_in, d_out / n_out))
+ if dot < min_dot and self._segment_in_mask(
+ (pts[i - 1][0], pts[i - 1][1]),
+ (pts[i + 1][0], pts[i + 1][1]),
+ mask,
+ ):
+ pts.pop(i)
+ changed = True
+ break
+ return [(float(p[0]), float(p[1])) for p in pts]
+
+ # ----- A* planning -------------------------------------------------------
+
+ def _astar(
+ self,
+ start: tuple[int, int],
+ goal: tuple[int, int],
+ mask: np.ndarray,
+ center_weight: float = 2.0,
+ ) -> list[tuple[int, int]] | None:
+ """Plan a path between two cells on the navigable component.
+
+ The step cost is scaled up where clearance is low, so the planner
+ prefers well-centered routes away from walls and furniture.
+
+ Args:
+ start: Start (row, col).
+ goal: Goal (row, col).
+ mask: Boolean grid of the navigable component.
+ center_weight: Strength of the centering preference.
+
+ Returns:
+ List of (row, col) cells from start to goal, or None if no path.
+ """
+ if start == goal:
+ return [start]
+
+ target = max(self.clearance * 2.0, self.clearance + 0.1)
+ open_heap: list[tuple[float, tuple[int, int]]] = [(0.0, start)]
+ g_score = {start: 0.0}
+ came_from: dict[tuple[int, int], tuple[int, int]] = {}
+
+ while open_heap:
+ _, current = heapq.heappop(open_heap)
+ if current == goal:
+ return self._reconstruct(came_from, current)
+
+ ci, cj = current
+ for di, dj, step in _NEIGHBORS:
+ ni, nj = ci + di, cj + dj
+ if not (0 <= ni < self.ny and 0 <= nj < self.nx):
+ continue
+ if not mask[ni, nj]:
+ continue
+ deficit = max(0.0, target - self.clearance_map[ni, nj])
+ cost = step * (1.0 + center_weight * deficit / target)
+ tentative = g_score[current] + cost
+ if tentative < g_score.get((ni, nj), math.inf):
+ g_score[(ni, nj)] = tentative
+ came_from[(ni, nj)] = current
+ h = math.hypot(goal[0] - ni, goal[1] - nj)
+ heapq.heappush(open_heap, (tentative + h, (ni, nj)))
+
+ return None
+
+ @staticmethod
+ def _reconstruct(
+ came_from: dict[tuple[int, int], tuple[int, int]],
+ current: tuple[int, int],
+ ) -> list[tuple[int, int]]:
+ """Rebuild a path from the A* came-from map."""
+ path = [current]
+ while current in came_from:
+ current = came_from[current]
+ path.append(current)
+ path.reverse()
+ return path
+
+ # ----- smoothing ---------------------------------------------------------
+
+ def _segment_in_mask(
+ self,
+ a: tuple[float, float],
+ b: tuple[float, float],
+ mask: np.ndarray,
+ ) -> bool:
+ """Check whether the straight segment a-b stays within the mask."""
+ dist = math.hypot(b[0] - a[0], b[1] - a[1])
+ steps = max(2, int(dist / (self.resolution * 0.5)))
+ for t in np.linspace(0.0, 1.0, steps):
+ x = a[0] + (b[0] - a[0]) * t
+ y = a[1] + (b[1] - a[1]) * t
+ i, j = self._to_cell(x, y)
+ if not (0 <= i < self.ny and 0 <= j < self.nx and mask[i, j]):
+ return False
+ return True
+
+ def _simplify_in_mask(
+ self,
+ points: list[tuple[float, float]],
+ mask: np.ndarray,
+ ) -> list[tuple[float, float]]:
+ """Greedily drop points while each chord stays inside the mask.
+
+ Removes the per-cell A* staircase so straight runs collapse to their
+ endpoints, but keeps a vertex wherever skipping it would let a chord
+ leave the navigable region (i.e. cut through a wall). Endpoints are
+ always kept. Applied per waypoint segment, so the roam shape (the
+ sequence of waypoints) is preserved.
+
+ Args:
+ points: Dense polyline vertices of one A* segment.
+ mask: Boolean grid of the navigable component.
+
+ Returns:
+ Simplified vertices whose consecutive chords stay in the mask.
+ """
+ if len(points) <= 2:
+ return list(points)
+ kept = [points[0]]
+ anchor = 0
+ for i in range(1, len(points) - 1):
+ if not self._segment_in_mask(points[anchor], points[i + 1], mask):
+ kept.append(points[i])
+ anchor = i
+ kept.append(points[-1])
+ return kept
+
+ def _round_corners(
+ self,
+ points: list[tuple[float, float]],
+ radius: float,
+ samples_per_corner: int = 12,
+ ) -> list[tuple[float, float]]:
+ """Round each turn with a clearance-safe arc of a target radius.
+
+ At every interior vertex a quadratic Bezier fillet is inserted whose
+ offset from the corner equals ``radius`` (clamped to half of each
+ adjacent segment). A larger ``radius`` yields wider turning arcs.
+ A corner is only rounded when the entire arc keeps the required
+ clearance; otherwise the sharp vertex is kept.
+
+ Args:
+ points: Polyline vertices.
+ radius: Target turn radius (m); larger gives wider arcs.
+ samples_per_corner: Points sampled along each rounded corner.
+
+ Returns:
+ Polyline vertices with rounded turns.
+ """
+ if radius <= 0 or len(points) <= 2:
+ return points
+
+ out: list[tuple[float, float]] = [points[0]]
+ for k in range(1, len(points) - 1):
+ a = np.asarray(points[k - 1], dtype=float)
+ v = np.asarray(points[k], dtype=float)
+ b = np.asarray(points[k + 1], dtype=float)
+ va, vb = a - v, b - v
+ la, lb = np.linalg.norm(va), np.linalg.norm(vb)
+ if la < 1e-6 or lb < 1e-6:
+ out.append((float(v[0]), float(v[1])))
+ continue
+
+ # Round with the largest radius that stays clear; shrink and retry
+ # so constrained corners still get a (smaller) arc rather than a
+ # sharp cusp that snaps the heading.
+ r = min(radius, la * 0.5, lb * 0.5)
+ arc: list[tuple[float, float]] = []
+ while r >= MIN_FILLET_RADIUS:
+ p1 = v + va / la * r
+ p2 = v + vb / lb * r
+ n_samples = max(
+ samples_per_corner, int(2 * r / self.resolution)
+ )
+ candidate = []
+ safe = True
+ for t in np.linspace(0.0, 1.0, n_samples):
+ pt = (1 - t) ** 2 * p1 + 2 * (1 - t) * t * v + t**2 * p2
+ if self._clearance_at(pt[0], pt[1]) < self.clearance:
+ safe = False
+ break
+ candidate.append((float(pt[0]), float(pt[1])))
+ if safe:
+ arc = candidate
+ break
+ r *= 0.6
+ out.extend(arc if arc else [(float(v[0]), float(v[1]))])
+
+ out.append(points[-1])
+ return out
+
+ def _resample(
+ self, points: list[tuple[float, float]], spacing: float
+ ) -> np.ndarray:
+ """Resample a polyline to uniform arc-length spacing (constant speed).
+
+ Args:
+ points: Polyline vertices.
+ spacing: Spacing (m) between consecutive points.
+
+ Returns:
+ (M, 2) array of equidistant points including both endpoints.
+ """
+ pts = np.asarray(points, dtype=float)
+ if len(pts) < 2:
+ return pts
+ seg = np.linalg.norm(np.diff(pts, axis=0), axis=1)
+ cum = np.concatenate([[0.0], np.cumsum(seg)])
+ total = cum[-1]
+ if total <= spacing:
+ return pts
+ n_intervals = max(1, round(total / spacing))
+ targets = np.linspace(0.0, total, n_intervals + 1)
+ x = np.interp(targets, cum, pts[:, 0])
+ y = np.interp(targets, cum, pts[:, 1])
+ return np.column_stack([x, y])
+
+ def _clearance_gradient(self, x: float, y: float) -> tuple[float, float]:
+ """Finite-difference gradient of the clearance field at (x, y)."""
+ step = self.resolution
+ gx = self._clearance_at(x + step, y) - self._clearance_at(x - step, y)
+ gy = self._clearance_at(x, y + step) - self._clearance_at(x, y - step)
+ return gx, gy
+
+ def _enforce_clearance(
+ self, xy: np.ndarray, max_steps: int = 40
+ ) -> np.ndarray:
+ """Push any sample below the clearance bound up the clearance field.
+
+ Smoothing and linear resampling can let a point dip below the
+ required clearance near concave corners; this nudges such points
+ toward higher clearance until the bound is satisfied.
+
+ Args:
+ xy: (N, 2) array of waypoints.
+ max_steps: Maximum gradient-ascent steps per point.
+
+ Returns:
+ The adjusted (N, 2) array.
+ """
+ out = xy.copy()
+ for k in range(len(out)):
+ x, y = out[k]
+ for _ in range(max_steps):
+ if self._clearance_at(x, y) >= self.clearance:
+ break
+ gx, gy = self._clearance_gradient(x, y)
+ norm = math.hypot(gx, gy)
+ if norm < 1e-9:
+ break
+ x += self.resolution * gx / norm
+ y += self.resolution * gy / norm
+ out[k] = (x, y)
+ return out
+
+ @staticmethod
+ def _compute_headings(xy: np.ndarray) -> np.ndarray:
+ """Compute per-point heading (rot_deg) tangent to the curve."""
+ n = len(xy)
+ rots = np.zeros(n)
+ for i in range(n):
+ if i == 0:
+ dx, dy = xy[1] - xy[0]
+ elif i == n - 1:
+ dx, dy = xy[-1] - xy[-2]
+ else:
+ dx, dy = xy[i + 1] - xy[i - 1]
+ rots[i] = heading_to_rot_deg(dx, dy)
+ return rots
+
+ @staticmethod
+ def _limit_heading_rate(
+ points: np.ndarray,
+ trigger_deg: float = TURN_FRAME_TRIGGER_DEG,
+ step_deg: float = TURN_FRAME_STEP_DEG,
+ ) -> np.ndarray:
+ """Add in-place rotation frames only at big U-turns.
+
+ Gentle turns flow continuously; only where the heading jumps more than
+ ``trigger_deg`` between consecutive points (an unavoidable sharp corner)
+ does the robot rotate in place, with a few extra frames stepping the
+ heading by ``step_deg`` so the big turn is not abrupt.
+
+ Args:
+ points: (N, 3) array of (x, y, rot_deg).
+ trigger_deg: Only jumps above this get extra frames.
+ step_deg: Heading step (deg) of each inserted in-place frame.
+
+ Returns:
+ (M, 3) array with M >= N.
+ """
+ if len(points) < 2:
+ return points
+ out = [points[0]]
+ for i in range(1, len(points)):
+ prev_rot = out[-1][2]
+ x, y, rot = points[i]
+ delta = (rot - prev_rot + 180.0) % 360.0 - 180.0
+ if abs(delta) > trigger_deg:
+ n_extra = max(1, int(math.ceil(abs(delta) / step_deg)) - 1)
+ for k in range(1, n_extra + 1):
+ out.append(
+ (x, y, (prev_rot + delta * k / (n_extra + 1)) % 360.0)
+ )
+ out.append((x, y, rot))
+ return np.asarray(out)
+
+ # ----- public API --------------------------------------------------------
+
+ def generate(
+ self,
+ start_xy: tuple[float, float] | None = None,
+ num_waypoints: int = DEFAULT_NUM_WAYPOINTS,
+ point_spacing: float = DEFAULT_POINT_SPACING,
+ turn_radius: float = DEFAULT_TURN_RADIUS,
+ endpoint_clearance: float = DEFAULT_ENDPOINT_CLEARANCE,
+ seed: int | None = None,
+ ) -> TrajectoryResult:
+ """Generate a smooth roaming trajectory.
+
+ Args:
+ start_xy: Optional world (x, y) start. Defaults to a point in the
+ largest navigable component.
+ num_waypoints: Number of roam waypoints to visit.
+ point_spacing: Output sampling spacing in meters.
+ turn_radius: Target turning-arc radius (m); larger gives wider,
+ rounder turns (clamped by segment length and clearance).
+ endpoint_clearance: Minimum clearance (m) kept at the start and
+ end points so they are not placed close to walls.
+ seed: Optional RNG seed for reproducible roaming.
+
+ Returns:
+ A ``TrajectoryResult`` with (x, y, rot_deg) waypoints.
+
+ Raises:
+ ValueError: If no navigable path can be planned.
+ """
+ rng = np.random.default_rng(seed)
+ mask, comp_cells = self._select_component(start_xy)
+
+ waypoints = self._sample_waypoints(
+ comp_cells, num_waypoints, start_xy, rng
+ )
+ waypoints = self._order_waypoints(waypoints)
+
+ # Keep the end (and the auto-chosen start) well away from walls.
+ waypoints[-1] = self._snap_to_open(
+ waypoints[-1], mask, endpoint_clearance
+ )
+ if start_xy is None:
+ waypoints[0] = self._snap_to_open(
+ waypoints[0], mask, endpoint_clearance
+ )
+
+ # Plan each waypoint-to-waypoint segment and simplify it within the
+ # navigable mask. Simplifying per segment (not the merged path) keeps
+ # every chord inside walls AND preserves the waypoints (roam shape),
+ # while collapsing straight runs so corners can use the full radius.
+ vertices: list[tuple[float, float]] = []
+ for a, b in zip(waypoints[:-1], waypoints[1:]):
+ segment = self._astar(a, b, mask)
+ if segment is None:
+ logger.warning("No A* path between %s and %s; skipping.", a, b)
+ continue
+ seg_world = [self._to_world(i, j) for i, j in segment]
+ seg_world = self._simplify_in_mask(seg_world, mask)
+ if vertices and seg_world and vertices[-1] == seg_world[0]:
+ seg_world = seg_world[1:]
+ vertices.extend(seg_world)
+
+ if len(vertices) < 2:
+ raise ValueError(
+ "Failed to plan a roaming path; try more waypoints or a "
+ "lower clearance."
+ )
+
+ # Collapse dead-end hairpins (single-door room excursions) that the
+ # waypoint-angle prune cannot detect.
+ vertices = self._remove_reversals(vertices, mask)
+
+ world_path = self._round_corners(vertices, turn_radius)
+ # Resample to uniform spacing, then enforce clearance last so the
+ # output is collision-free (the final interpolation cannot reintroduce
+ # violations). Spacing stays near-uniform; only the few points pushed
+ # out near tight corners shift, and gen_trajectory derives timestamps
+ # from arc length so constant speed is preserved.
+ xy = self._resample(world_path, point_spacing)
+ xy = self._enforce_clearance(xy)
+
+ rots = self._compute_headings(xy)
+ points = np.column_stack([xy, rots])
+ # Insert in-place rotations at unavoidably sharp corners so the
+ # heading never snaps between consecutive points.
+ points = self._limit_heading_rate(points)
+
+ xy = points[:, :2]
+ seg = np.linalg.norm(np.diff(xy, axis=0), axis=1)
+ length = float(seg.sum())
+ min_clr = min(self._clearance_at(x, y) for x, y in xy)
+ reachable = self._reachable_rooms(mask)
+
+ logger.info(
+ "Generated trajectory: %d points, %.2fm long, min clearance "
+ "%.2fm, reaches %d room(s).",
+ len(points),
+ length,
+ min_clr,
+ len(reachable),
+ )
+ return TrajectoryResult(
+ points=points,
+ clearance=self.clearance,
+ min_clearance=min_clr,
+ length=length,
+ reachable_rooms=reachable,
+ )
+
+ def _reachable_rooms(self, mask: np.ndarray) -> list[str]:
+ """List room keys overlapping the chosen navigable component.
+
+ A room is reachable if any navigable cell of the component falls
+ inside the room polygon.
+
+ Args:
+ mask: Boolean grid of the navigable component.
+
+ Returns:
+ Room keys with at least one navigable cell inside them.
+ """
+ cells = np.argwhere(mask)
+ if cells.size == 0:
+ return []
+ world = np.array([self._to_world(i, j) for i, j in cells])
+ reachable = []
+ for name, poly in self.rooms.items():
+ if poly.is_empty:
+ continue
+ if shapely.contains_xy(poly, world[:, 0], world[:, 1]).any():
+ reachable.append(name)
+ return reachable
diff --git a/embodied_gen/skills/spatial-computing/core/visualizer.py b/embodied_gen/skills/spatial-computing/core/visualizer.py
new file mode 100644
index 0000000..8c474c8
--- /dev/null
+++ b/embodied_gen/skills/spatial-computing/core/visualizer.py
@@ -0,0 +1,362 @@
+# Project EmbodiedGen
+#
+# Copyright (c) 2025 Horizon Robotics. All Rights Reserved.
+#
+# 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.
+
+
+from __future__ import annotations
+
+import logging
+
+import matplotlib.pyplot as plt
+import numpy as np
+from matplotlib.axes import Axes
+from shapely.geometry import MultiPolygon, Polygon
+from shapely.ops import unary_union
+
+# Type aliases
+Geometry = Polygon | MultiPolygon
+
+logger = logging.getLogger(__name__)
+
+
+class FloorplanVisualizer:
+ """Static utility class for visualizing floorplans."""
+
+ @staticmethod
+ def draw_poly(ax: Axes, poly: Geometry, **kwargs) -> None:
+ """Draw a polygon or multi-polygon on matplotlib axes.
+
+ Args:
+ ax: Matplotlib axes object.
+ poly: Shapely Polygon or MultiPolygon to draw.
+ **kwargs: Additional arguments passed to ax.fill().
+
+ """
+ if poly.is_empty:
+ return
+
+ geoms = poly.geoms if hasattr(poly, "geoms") else [poly]
+
+ color = kwargs.pop("color", None)
+ if color is None:
+ cmap = plt.get_cmap("tab10")
+ colors = [cmap(i) for i in range(len(geoms))]
+ else:
+ colors = [color] * len(geoms)
+
+ for i, p in enumerate(geoms):
+ if p.is_empty:
+ continue
+ x, y = p.exterior.xy
+ ax.fill(x, y, facecolor=colors[i], **kwargs)
+
+ @classmethod
+ def plot(
+ cls,
+ rooms: dict[str, Geometry],
+ footprints: dict[str, Geometry],
+ occ_area: Geometry,
+ save_path: str,
+ trajectory: np.ndarray | None = None,
+ arrow_stride: int = 10,
+ current_index: int | None = None,
+ point_markers: bool = True,
+ dpi: int = 300,
+ ) -> None:
+ """Generate and save a floorplan visualization.
+
+ Args:
+ rooms: Dictionary mapping room names to floor polygons.
+ footprints: Dictionary mapping object names to footprint polygons.
+ occ_area: Union of all occupied areas.
+ save_path: Path to save the output image.
+ trajectory: Optional (N, 2) or (N, 3) array of waypoints. When the
+ third column (rot_deg, tangent heading) is present, heading
+ arrows are drawn. Rendered as a red curve overlay.
+ arrow_stride: Draw a heading arrow every ``arrow_stride`` points
+ (0 disables arrows). Ignored when ``current_index`` is set.
+ current_index: Animation frame index. When set, only the traveled
+ path (up to this index) is drawn, with a green dot at the
+ current position and a red heading arrow; the future path is
+ hidden.
+ point_markers: When True, mark every trajectory point with a small
+ red dot (in addition to the curve).
+ dpi: Output image resolution in dots per inch.
+
+ """
+ fig, ax = plt.subplots(figsize=(10, 10))
+ ax.set_aspect("equal")
+ cmap_rooms = plt.get_cmap("Pastel1")
+
+ cls._draw_room_floors(ax, rooms, cmap_rooms)
+ cls._draw_occupied_area(ax, occ_area)
+ cls._draw_footprint_outlines(ax, footprints)
+ cls._draw_footprint_labels(ax, footprints)
+ cls._draw_room_labels(ax, rooms)
+ if trajectory is not None and len(trajectory) > 1:
+ cls._draw_trajectory(
+ ax,
+ np.asarray(trajectory),
+ arrow_stride,
+ current_index,
+ point_markers,
+ )
+ cls._configure_axes(ax, rooms, occ_area)
+
+ ax.set_title("")
+ ax.set_xlabel("")
+ ax.set_ylabel("")
+ ax.set_xticks([])
+ ax.set_yticks([])
+ for spine in ax.spines.values():
+ spine.set_visible(False)
+ fig.subplots_adjust(left=0, right=1, top=1, bottom=0)
+ fig.patch.set_alpha(0)
+ ax.patch.set_alpha(0)
+ plt.savefig(
+ save_path,
+ dpi=dpi,
+ bbox_inches="tight",
+ pad_inches=0,
+ transparent=True,
+ )
+ plt.close(fig)
+
+ @classmethod
+ def _draw_room_floors(
+ cls,
+ ax: Axes,
+ rooms: dict[str, Geometry],
+ cmap: plt.cm.ScalarMappable,
+ ) -> None:
+ """Draw colored room floor polygons (Layer 1)."""
+ for i, (name, poly) in enumerate(rooms.items()):
+ color = cmap(i % cmap.N)
+ cls.draw_poly(
+ ax,
+ poly,
+ color=color,
+ alpha=1.0,
+ edgecolor="black",
+ linestyle="--",
+ zorder=1,
+ )
+
+ @classmethod
+ def _draw_occupied_area(cls, ax: Axes, occ_area: Geometry) -> None:
+ """Draw the occupied area overlay (Layer 2)."""
+ cls.draw_poly(
+ ax,
+ occ_area,
+ color="tab:blue",
+ alpha=0.5,
+ lw=0,
+ zorder=2,
+ )
+
+ @staticmethod
+ def _draw_footprint_outlines(
+ ax: Axes,
+ footprints: dict[str, Geometry],
+ ) -> None:
+ """Draw footprint outlines (Layer 3)."""
+ for poly in footprints.values():
+ if poly.is_empty:
+ continue
+ geoms = poly.geoms if hasattr(poly, "geoms") else [poly]
+ for p in geoms:
+ ax.plot(*p.exterior.xy, "--", lw=0.8, color="gray", zorder=3)
+
+ @staticmethod
+ def _draw_footprint_labels(
+ ax: Axes,
+ footprints: dict[str, Geometry],
+ ) -> None:
+ """Draw footprint text labels (Layer 4)."""
+ import re
+
+ for name, poly in footprints.items():
+ if poly.is_empty:
+ continue
+ label = re.sub(r"_\d+$", "", name)
+ ax.text(
+ poly.centroid.x,
+ poly.centroid.y,
+ label,
+ fontsize=8,
+ ha="center",
+ va="center",
+ bbox={
+ "facecolor": "white",
+ "alpha": 0.5,
+ "edgecolor": "none",
+ "pad": 0.1,
+ },
+ zorder=4,
+ )
+
+ @staticmethod
+ def _draw_room_labels(ax: Axes, rooms: dict[str, Geometry]) -> None:
+ """Draw room text labels (Layer 5)."""
+ for name, poly in rooms.items():
+ if poly.is_empty:
+ continue
+ label = name.replace("_floor", "")
+ ax.text(
+ poly.centroid.x,
+ poly.centroid.y,
+ label,
+ fontsize=9,
+ color="black",
+ weight="bold",
+ ha="center",
+ va="center",
+ bbox={
+ "facecolor": "lightgray",
+ "alpha": 0.7,
+ "edgecolor": "black",
+ "boxstyle": "round,pad=0.3",
+ },
+ zorder=5,
+ )
+
+ @staticmethod
+ def _draw_trajectory(
+ ax: Axes,
+ trajectory: np.ndarray,
+ arrow_stride: int,
+ current_index: int | None = None,
+ point_markers: bool = True,
+ ) -> None:
+ """Draw the roaming trajectory as a red curve with heading arrows.
+
+ Args:
+ ax: Matplotlib axes object.
+ trajectory: (N, 2) or (N, 3) array; the optional third column is
+ the heading rot_deg (0 = +Y, counter-clockwise positive).
+ arrow_stride: Spacing (in points) between heading arrows; 0 to
+ disable arrows. Ignored when ``current_index`` is set.
+ current_index: When set, draw only the traveled path up to this
+ index, a green dot at the current position, and a single red
+ heading arrow; the future path is hidden.
+ point_markers: When True, mark every trajectory point with a small
+ red dot (full-trajectory view only).
+
+ """
+ xs, ys = trajectory[:, 0], trajectory[:, 1]
+ has_heading = trajectory.shape[1] >= 3
+
+ if current_index is not None:
+ ci = int(np.clip(current_index, 0, len(trajectory) - 1))
+ ax.plot(
+ xs[: ci + 1],
+ ys[: ci + 1],
+ color="red",
+ linewidth=2.5,
+ solid_capstyle="round",
+ zorder=6,
+ )
+ ax.scatter(
+ xs[ci], ys[ci], c="lime", s=200, edgecolors="black", zorder=8
+ )
+ if has_heading:
+ rot = np.deg2rad(trajectory[ci, 2])
+ dx, dy = -np.sin(rot), np.cos(rot)
+ # Tail anchored at the dot center; long arrow in data coords.
+ length = 0.9
+ head = (xs[ci] + dx * length, ys[ci] + dy * length)
+ ax.annotate(
+ "",
+ xy=head,
+ xytext=(xs[ci], ys[ci]),
+ arrowprops={
+ "arrowstyle": "-|>",
+ "color": "red",
+ "lw": 2.5,
+ "mutation_scale": 18,
+ },
+ zorder=9,
+ )
+ return
+
+ ax.plot(
+ xs,
+ ys,
+ color="red",
+ linewidth=2.5,
+ solid_capstyle="round",
+ zorder=6,
+ )
+ if point_markers:
+ ax.scatter(xs, ys, c="gray", s=8, zorder=6.5, edgecolors="none")
+ ax.scatter(
+ xs[0],
+ ys[0],
+ c="lime",
+ s=70,
+ edgecolors="black",
+ zorder=7,
+ label="start",
+ )
+ ax.scatter(
+ xs[-1],
+ ys[-1],
+ c="red",
+ s=70,
+ marker="s",
+ edgecolors="black",
+ zorder=7,
+ label="end",
+ )
+
+ if arrow_stride and has_heading:
+ idx = np.arange(0, len(trajectory), arrow_stride)
+ rot = np.deg2rad(trajectory[idx, 2])
+ # Heading convention: 0 deg -> +Y, counter-clockwise positive.
+ u, v = -np.sin(rot), np.cos(rot)
+ ax.quiver(
+ xs[idx],
+ ys[idx],
+ u,
+ v,
+ color="darkred",
+ scale=25,
+ width=0.004,
+ zorder=7,
+ )
+
+ @staticmethod
+ def _configure_axes(
+ ax: Axes,
+ rooms: dict[str, Geometry],
+ occ_area: Geometry,
+ ) -> None:
+ """Configure axes limits and labels."""
+ total_geom = unary_union(list(rooms.values()) + [occ_area])
+
+ if total_geom.is_empty:
+ minx, miny, maxx, maxy = -1, -1, 1, 1
+ else:
+ minx, miny, maxx, maxy = total_geom.bounds
+
+ cx = (minx + maxx) * 0.5
+ cy = (miny + maxy) * 0.5
+ half = max(maxx - minx, maxy - miny) * 0.5 * 1.05
+
+ ax.set_xlim(cx - half, cx + half)
+ ax.set_ylim(cy - half, cy + half)
+ ax.set_title("Floorplan Analysis", fontsize=14)
+ ax.set_xlabel("X (m)")
+ ax.set_ylabel("Y (m)")
diff --git a/embodied_gen/trainer/pono2mesh_trainer.py b/embodied_gen/trainer/pono2mesh_trainer.py
index 6f04435..61b1050 100644
--- a/embodied_gen/trainer/pono2mesh_trainer.py
+++ b/embodied_gen/trainer/pono2mesh_trainer.py
@@ -15,7 +15,7 @@
# permissions and limitations under the License.
-from embodied_gen.utils.monkey_patches import monkey_patch_pano2room
+from embodied_gen.utils.monkey_patch.pano2room import monkey_patch_pano2room
monkey_patch_pano2room()
diff --git a/embodied_gen/utils/general.py b/embodied_gen/utils/general.py
new file mode 100644
index 0000000..848a62e
--- /dev/null
+++ b/embodied_gen/utils/general.py
@@ -0,0 +1,54 @@
+import random
+import logging
+import warnings
+
+import numpy as np
+import torch
+
+__all__ = ["filter_warnings", "set_seed"]
+
+
+def filter_warnings() -> None:
+ warnings.filterwarnings("ignore", category=FutureWarning)
+ warnings.filterwarnings(
+ "ignore",
+ message="To copy construct from a tensor.*",
+ category=UserWarning,
+ )
+ warnings.filterwarnings(
+ "ignore",
+ message="Unable to load pointnet2_ops cpp extension.*",
+ category=UserWarning,
+ )
+ warnings.filterwarnings(
+ "ignore",
+ message="TORCH_CUDA_ARCH_LIST is not set.*",
+ category=UserWarning,
+ )
+ warnings.filterwarnings(
+ "ignore",
+ message="The given buffer is not writable.*",
+ category=UserWarning,
+ )
+ warnings.filterwarnings(
+ "ignore",
+ message="Using torch.cross without specifying the dim arg is deprecated.*",
+ category=UserWarning,
+ )
+ warnings.filterwarnings(
+ "ignore",
+ message="torch.meshgrid: in an upcoming release.*",
+ category=UserWarning,
+ )
+ logging.getLogger("grasp_gen.dataset.renderer").setLevel(logging.ERROR)
+
+
+def set_seed(seed: int) -> None:
+ random.seed(seed)
+ np.random.seed(seed)
+ torch.manual_seed(seed)
+ if torch.cuda.is_available():
+ torch.cuda.manual_seed(seed)
+ torch.cuda.manual_seed_all(seed)
+ torch.backends.cudnn.deterministic = True
+ torch.backends.cudnn.benchmark = False
diff --git a/embodied_gen/utils/geometry.py b/embodied_gen/utils/geometry.py
index c5dbe85..d2ee577 100644
--- a/embodied_gen/utils/geometry.py
+++ b/embodied_gen/utils/geometry.py
@@ -14,12 +14,15 @@
# implied. See the License for the specific language governing
# permissions and limitations under the License.
+from __future__ import annotations
+
import json
import os
import random
from collections import defaultdict, deque
+from dataclasses import dataclass
from functools import wraps
-from typing import Literal
+from typing import TYPE_CHECKING, Literal
import numpy as np
import torch
@@ -32,8 +35,20 @@
from embodied_gen.utils.enum import LayoutInfo, Scene3DItemEnum
from embodied_gen.utils.log import logger
+if TYPE_CHECKING:
+ import sapien.core as sapien
+
__all__ = [
+ "EnclosedFaceLabelFillConfig",
+ "MeshInfo",
+ "SmoothFaceLabelMergeConfig",
"with_seed",
+ "fill_small_enclosed_face_labels",
+ "normalize_mesh",
+ "merge_smooth_face_labels",
+ "nearest_faces",
+ "sample_surface_points",
+ "gripper_finger_center",
"matrix_to_pose",
"pose_to_matrix",
"quaternion_multiply",
@@ -44,6 +59,423 @@
]
+@dataclass
+class MeshInfo:
+ actor_name: str
+ collision_mesh_path: str
+ collision_mesh_scale: np.ndarray
+ collision_local_pose: sapien.Pose
+ visual_mesh_path: str
+ visual_mesh_scale: np.ndarray
+ visual_local_pose: sapien.Pose
+ transformed_mesh: trimesh.Trimesh
+ object_height: float
+ mass: float | None
+ static_friction: float
+ dynamic_friction: float
+
+
+@dataclass(frozen=True)
+class SmoothFaceLabelMergeConfig:
+ max_smooth_angle_deg: float = 18.0
+ vertex_merge_tolerance: float = 0.0
+ min_component_area_ratio: float = 0.0
+ min_component_faces: int = 1
+ preserve_negative: bool = False
+
+
+@dataclass(frozen=True)
+class EnclosedFaceLabelFillConfig:
+ max_component_area_ratio: float = 0.005
+ max_component_faces: int = 100
+ min_enclosure_neighbor_ratio: float = 0.8
+ vertex_merge_tolerance: float = 0.0
+
+
+class _UnionFind:
+ def __init__(self, size: int):
+ self.parent = np.arange(size, dtype=np.int64)
+ self.rank = np.zeros(size, dtype=np.uint8)
+
+ def find(self, item: int) -> int:
+ parent = int(self.parent[item])
+ if parent != item:
+ parent = self.find(parent)
+ self.parent[item] = parent
+ return parent
+
+ def union(self, left: int, right: int) -> None:
+ left_root = self.find(left)
+ right_root = self.find(right)
+ if left_root == right_root:
+ return
+ if self.rank[left_root] < self.rank[right_root]:
+ left_root, right_root = right_root, left_root
+ self.parent[right_root] = left_root
+ if self.rank[left_root] == self.rank[right_root]:
+ self.rank[left_root] += 1
+
+
+def normalize_mesh(mesh: trimesh.Trimesh) -> trimesh.Trimesh:
+ mesh_unit = mesh.copy()
+ scale = float(np.ptp(mesh_unit.vertices, axis=0).max())
+ if scale <= 0.0:
+ raise ValueError("mesh has zero extent and cannot be normalized")
+ mesh_unit.apply_scale(1.0 / scale)
+ return mesh_unit
+
+
+def sample_surface_points(
+ mesh: trimesh.Trimesh, num_sample_points: int
+) -> np.ndarray:
+ points, _ = trimesh.sample.sample_surface(mesh, num_sample_points)
+ points = np.asarray(points, dtype=np.float32)
+
+ if len(points) > num_sample_points:
+ indices = np.random.choice(
+ len(points), num_sample_points, replace=False
+ )
+ points = points[indices]
+
+ return points
+
+
+def nearest_faces(mesh: trimesh.Trimesh, points: np.ndarray) -> np.ndarray:
+ points = np.asarray(points, dtype=np.float64)
+ if not isinstance(mesh, trimesh.Trimesh) or len(mesh.faces) == 0:
+ return np.full(len(points), -1, dtype=np.int64)
+
+ try:
+ _, _, face_ids = trimesh.proximity.closest_point(mesh, points)
+ return np.asarray(face_ids, dtype=np.int64)
+ except Exception as exc:
+ logger.warning(
+ "Failed to query closest mesh faces with trimesh proximity, "
+ f"falling back to triangle-center KDTree: {exc}"
+ )
+ from scipy.spatial import cKDTree
+
+ _, face_ids = cKDTree(mesh.triangles_center).query(points)
+ return np.asarray(face_ids, dtype=np.int64)
+
+
+def _auto_vertex_merge_tolerance(mesh: trimesh.Trimesh) -> float:
+ bounds = np.asarray(mesh.bounds, dtype=np.float64)
+ if bounds.shape != (2, 3) or not np.all(np.isfinite(bounds)):
+ return 1e-8
+ diag = float(np.linalg.norm(bounds[1] - bounds[0]))
+ return max(diag * 1e-8, 1e-10)
+
+
+def _vertex_key(vertex: np.ndarray, tolerance: float) -> tuple[int, int, int]:
+ return tuple(np.rint(vertex / tolerance).astype(np.int64).tolist())
+
+
+def _edge_face_groups_by_position(
+ mesh: trimesh.Trimesh,
+ vertex_merge_tolerance: float = 0.0,
+) -> dict[tuple[tuple[int, int, int], tuple[int, int, int]], list[int]]:
+ faces = np.asarray(mesh.faces, dtype=np.int64)
+ vertices = np.asarray(mesh.vertices, dtype=np.float64)
+ if len(faces) == 0:
+ return {}
+
+ tolerance = (
+ float(vertex_merge_tolerance)
+ if vertex_merge_tolerance > 0.0
+ else _auto_vertex_merge_tolerance(mesh)
+ )
+ edge_to_faces: dict[
+ tuple[tuple[int, int, int], tuple[int, int, int]], list[int]
+ ] = {}
+ edge_offsets = ((0, 1), (1, 2), (2, 0))
+ for face_idx, face in enumerate(faces):
+ for left_offset, right_offset in edge_offsets:
+ left_key = _vertex_key(vertices[face[left_offset]], tolerance)
+ right_key = _vertex_key(vertices[face[right_offset]], tolerance)
+ edge_key = tuple(sorted((left_key, right_key)))
+ edge_to_faces.setdefault(edge_key, []).append(face_idx)
+ return edge_to_faces
+
+
+def _face_adjacency_by_position(
+ mesh: trimesh.Trimesh,
+ vertex_merge_tolerance: float = 0.0,
+) -> np.ndarray:
+ edge_to_faces = _edge_face_groups_by_position(mesh, vertex_merge_tolerance)
+ adjacent_pairs = []
+ for face_indices in edge_to_faces.values():
+ if len(face_indices) < 2:
+ continue
+ for idx in range(len(face_indices) - 1):
+ adjacent_pairs.append((face_indices[idx], face_indices[idx + 1]))
+
+ if not adjacent_pairs:
+ return np.empty((0, 2), dtype=np.int64)
+ return np.asarray(adjacent_pairs, dtype=np.int64)
+
+
+def _face_adjacency_lists(
+ n_faces: int,
+ adjacency: np.ndarray,
+) -> list[list[int]]:
+ adjacency_lists = [[] for _ in range(n_faces)]
+ for left, right in adjacency:
+ adjacency_lists[int(left)].append(int(right))
+ adjacency_lists[int(right)].append(int(left))
+ return adjacency_lists
+
+
+def _smooth_face_components(
+ mesh: trimesh.Trimesh,
+ max_smooth_angle_deg: float,
+ vertex_merge_tolerance: float = 0.0,
+) -> list[np.ndarray]:
+ if len(mesh.faces) == 0:
+ return []
+
+ face_normals = np.asarray(mesh.face_normals, dtype=np.float64)
+ adjacency = _face_adjacency_by_position(mesh, vertex_merge_tolerance)
+ union_find = _UnionFind(len(mesh.faces))
+ if len(adjacency) > 0:
+ normal_dot = np.einsum(
+ "ij,ij->i",
+ face_normals[adjacency[:, 0]],
+ face_normals[adjacency[:, 1]],
+ )
+ normal_dot = np.clip(normal_dot, -1.0, 1.0)
+ angles = np.degrees(np.arccos(normal_dot))
+ smooth_pairs = adjacency[angles <= max_smooth_angle_deg]
+ for left, right in smooth_pairs:
+ union_find.union(int(left), int(right))
+
+ groups: dict[int, list[int]] = {}
+ for face_idx in range(len(mesh.faces)):
+ groups.setdefault(union_find.find(face_idx), []).append(face_idx)
+ return [np.asarray(indices, dtype=np.int64) for indices in groups.values()]
+
+
+def _same_label_components(
+ face_labels: np.ndarray,
+ adjacency_lists: list[list[int]],
+) -> list[np.ndarray]:
+ visited = np.zeros(len(face_labels), dtype=bool)
+ components = []
+ for face_idx, label in enumerate(face_labels):
+ if visited[face_idx] or label < 0:
+ continue
+
+ visited[face_idx] = True
+ stack = [face_idx]
+ component = []
+ while stack:
+ current = stack.pop()
+ component.append(current)
+ for neighbor in adjacency_lists[current]:
+ if visited[neighbor] or face_labels[neighbor] != label:
+ continue
+ visited[neighbor] = True
+ stack.append(neighbor)
+ components.append(np.asarray(component, dtype=np.int64))
+ return components
+
+
+def _dominant_face_label(
+ face_labels: np.ndarray,
+ face_areas: np.ndarray,
+ face_indices: np.ndarray,
+) -> int | None:
+ labels = face_labels[face_indices]
+ valid_mask = labels >= 0
+ if not np.any(valid_mask):
+ return None
+
+ valid_labels = labels[valid_mask]
+ valid_areas = face_areas[face_indices][valid_mask]
+ area_by_label: dict[int, float] = {}
+ for label, area in zip(valid_labels, valid_areas):
+ area_by_label[int(label)] = area_by_label.get(int(label), 0.0) + float(
+ area
+ )
+ return max(area_by_label.items(), key=lambda item: item[1])[0]
+
+
+def fill_small_enclosed_face_labels(
+ mesh: trimesh.Trimesh,
+ face_labels: np.ndarray,
+ cfg: EnclosedFaceLabelFillConfig | None = None,
+) -> tuple[np.ndarray, dict[str, int]]:
+ if cfg is None:
+ cfg = EnclosedFaceLabelFillConfig()
+ if len(face_labels) != len(mesh.faces):
+ raise ValueError(
+ "face_labels length must match mesh faces, got "
+ f"{len(face_labels)} and {len(mesh.faces)}"
+ )
+
+ new_face_labels = np.asarray(face_labels, dtype=np.int64).copy()
+ edge_to_faces = _edge_face_groups_by_position(
+ mesh,
+ cfg.vertex_merge_tolerance,
+ )
+ adjacency = _face_adjacency_by_position(mesh, cfg.vertex_merge_tolerance)
+ adjacency_lists = _face_adjacency_lists(len(mesh.faces), adjacency)
+ open_boundary_faces = {
+ face_indices[0]
+ for face_indices in edge_to_faces.values()
+ if len(face_indices) == 1
+ }
+
+ face_areas = np.asarray(mesh.area_faces, dtype=np.float64)
+ total_area = float(np.sum(face_areas))
+ max_area = max(0.0, cfg.max_component_area_ratio) * total_area
+ max_faces = int(cfg.max_component_faces)
+
+ component_mask = np.zeros(len(mesh.faces), dtype=bool)
+ filled_components = 0
+ skipped_components = 0
+ for face_indices in _same_label_components(face_labels, adjacency_lists):
+ source_label = int(face_labels[face_indices[0]])
+ component_area = float(np.sum(face_areas[face_indices]))
+ if max_area <= 0.0 or component_area > max_area:
+ skipped_components += 1
+ continue
+ if max_faces > 0 and len(face_indices) > max_faces:
+ skipped_components += 1
+ continue
+ if any(
+ int(face_idx) in open_boundary_faces for face_idx in face_indices
+ ):
+ skipped_components += 1
+ continue
+
+ component_mask[face_indices] = True
+ outside_labels = []
+ for face_idx in face_indices:
+ for neighbor in adjacency_lists[int(face_idx)]:
+ if component_mask[neighbor]:
+ continue
+ outside_labels.append(int(face_labels[neighbor]))
+
+ target_counts: dict[int, int] = {}
+ for label in outside_labels:
+ if label >= 0 and label != source_label:
+ target_counts[label] = target_counts.get(label, 0) + 1
+
+ target_label = None
+ if outside_labels and target_counts:
+ target_label, target_count = max(
+ target_counts.items(),
+ key=lambda item: item[1],
+ )
+ enclosure_ratio = target_count / len(outside_labels)
+ if enclosure_ratio < cfg.min_enclosure_neighbor_ratio:
+ target_label = None
+
+ if target_label is None:
+ skipped_components += 1
+ else:
+ new_face_labels[face_indices] = target_label
+ filled_components += 1
+ component_mask[face_indices] = False
+
+ stats = {
+ "components_filled": filled_components,
+ "components_skipped": skipped_components,
+ "faces_changed": int(np.count_nonzero(new_face_labels != face_labels)),
+ }
+ return new_face_labels, stats
+
+
+def merge_smooth_face_labels(
+ mesh: trimesh.Trimesh,
+ face_labels: np.ndarray,
+ cfg: SmoothFaceLabelMergeConfig | None = None,
+) -> tuple[np.ndarray, dict[str, int]]:
+ if cfg is None:
+ cfg = SmoothFaceLabelMergeConfig()
+ if len(face_labels) != len(mesh.faces):
+ raise ValueError(
+ "face_labels length must match mesh faces, got "
+ f"{len(face_labels)} and {len(mesh.faces)}"
+ )
+
+ new_face_labels = np.asarray(face_labels, dtype=np.int64).copy()
+ face_areas = np.asarray(mesh.area_faces, dtype=np.float64)
+ total_area = float(np.sum(face_areas))
+ min_area = max(0.0, cfg.min_component_area_ratio) * total_area
+
+ changed_components = 0
+ skipped_components = 0
+ for face_indices in _smooth_face_components(
+ mesh,
+ cfg.max_smooth_angle_deg,
+ cfg.vertex_merge_tolerance,
+ ):
+ if len(face_indices) < cfg.min_component_faces:
+ skipped_components += 1
+ continue
+ component_area = float(np.sum(face_areas[face_indices]))
+ if component_area < min_area:
+ skipped_components += 1
+ continue
+
+ labels = new_face_labels[face_indices]
+ valid_unique = np.unique(labels[labels >= 0])
+ if len(valid_unique) <= 1:
+ continue
+
+ target_label = _dominant_face_label(
+ new_face_labels,
+ face_areas,
+ face_indices,
+ )
+ if target_label is None:
+ continue
+
+ if cfg.preserve_negative:
+ assign_mask = new_face_labels[face_indices] >= 0
+ assign_faces = face_indices[assign_mask]
+ else:
+ assign_faces = face_indices
+
+ new_face_labels[assign_faces] = target_label
+ changed_components += 1
+
+ stats = {
+ "components_changed": changed_components,
+ "components_skipped": skipped_components,
+ "faces_changed": int(np.count_nonzero(new_face_labels != face_labels)),
+ }
+ return new_face_labels, stats
+
+
+def gripper_finger_center(
+ gripper_urdf_path: str,
+ geometry_type: Literal["collision", "visual"] = "collision",
+) -> np.ndarray:
+ from embodied_gen.utils.io_utils import URDFFile
+
+ gripper_urdf = URDFFile(gripper_urdf_path)
+ finger_link_names = gripper_urdf.get_child_link_names(
+ name_contains="finger"
+ )
+ if len(finger_link_names) != 2:
+ raise ValueError(
+ "expected exactly two finger links in gripper URDF, got "
+ f"{finger_link_names}: {gripper_urdf_path}"
+ )
+
+ link_transforms = gripper_urdf.get_link_transforms()
+ finger_centers = []
+ for link_name in finger_link_names:
+ mesh = gripper_urdf.load_link_geometry_mesh(link_name, geometry_type)
+ mesh.apply_transform(link_transforms[link_name])
+ finger_centers.append(np.asarray(mesh.centroid, dtype=np.float64))
+
+ return np.mean(np.stack(finger_centers, axis=0), axis=0)
+
+
def matrix_to_pose(matrix: np.ndarray) -> list[float]:
"""Converts a 4x4 transformation matrix to a pose (x, y, z, qx, qy, qz, qw).
diff --git a/embodied_gen/utils/gpt_clients.py b/embodied_gen/utils/gpt_clients.py
index 830dc77..0794e99 100644
--- a/embodied_gen/utils/gpt_clients.py
+++ b/embodied_gen/utils/gpt_clients.py
@@ -17,6 +17,7 @@
import base64
import logging
+import math
import os
from io import BytesIO
from typing import Optional
@@ -29,9 +30,9 @@
retry,
retry_if_not_exception_type,
stop_after_attempt,
+ stop_after_delay,
wait_random_exponential,
)
-from embodied_gen.utils.process_media import combine_images_to_grid
logging.getLogger("httpx").setLevel(logging.WARNING)
logging.basicConfig(level=logging.WARNING)
@@ -44,6 +45,42 @@
_CURRENT_DIR = os.path.dirname(os.path.abspath(__file__))
CONFIG_FILE = os.path.join(_CURRENT_DIR, "gpt_config.yaml")
+DEFAULT_GPT_TIMEOUT = float(os.environ.get("GPT_TIMEOUT", 120))
+# GPT-5.x counts reasoning tokens against this cap, so it must be high
+# enough to leave room for both reasoning and the visible reply.
+GPT5_DEFAULT_MAX_COMPLETION_TOKENS = 8192
+
+
+def combine_images_to_grid(
+ images: list[str | Image.Image],
+ cat_row_col: tuple[int, int] = None,
+ target_wh: tuple[int, int] = (512, 512),
+ image_mode: str = "RGB",
+) -> list[Image.Image]:
+ n_images = len(images)
+ if n_images == 1:
+ return images
+
+ if cat_row_col is None:
+ n_col = math.ceil(math.sqrt(n_images))
+ n_row = math.ceil(n_images / n_col)
+ else:
+ n_row, n_col = cat_row_col
+
+ images = [
+ Image.open(p).convert(image_mode) if isinstance(p, str) else p
+ for p in images
+ ]
+ images = [img.resize(target_wh) for img in images]
+
+ grid_w, grid_h = n_col * target_wh[0], n_row * target_wh[1]
+ grid = Image.new(image_mode, (grid_w, grid_h), (0, 0, 0))
+
+ for idx, img in enumerate(images):
+ row, col = divmod(idx, n_col)
+ grid.paste(img, (col * target_wh[0], row * target_wh[1]))
+
+ return [grid]
class GPTclient:
@@ -58,6 +95,7 @@ class GPTclient:
api_version (str, optional): API version (for Azure).
check_connection (bool, optional): Whether to check API connection.
verbose (bool, optional): Enable verbose logging.
+ timeout (float, optional): Max seconds for a single GPT request.
Example:
```sh
@@ -85,21 +123,27 @@ def __init__(
api_version: str = None,
check_connection: bool = True,
verbose: bool = False,
+ timeout: float = DEFAULT_GPT_TIMEOUT,
):
if api_version is not None:
self.client = AzureOpenAI(
azure_endpoint=endpoint,
api_key=api_key,
api_version=api_version,
+ timeout=timeout,
+ max_retries=0,
)
else:
self.client = OpenAI(
base_url=endpoint,
api_key=api_key,
+ timeout=timeout,
+ max_retries=0,
)
self.endpoint = endpoint
self.model_name = model_name
+ self.timeout = timeout
self.image_formats = {".png", ".jpg", ".jpeg", ".webp", ".bmp", ".gif"}
self.verbose = verbose
if check_connection:
@@ -107,10 +151,15 @@ def __init__(
logger.info(f"Using GPT model: {self.model_name}.")
+ @staticmethod
+ def _is_gpt5_model(model_name: str) -> bool:
+ name = (model_name or "").lower()
+ return "gpt-5" in name or "gpt5" in name
+
@retry(
retry=retry_if_not_exception_type(openai.BadRequestError),
wait=wait_random_exponential(min=1, max=10),
- stop=stop_after_attempt(5),
+ stop=stop_after_attempt(5) | stop_after_delay(DEFAULT_GPT_TIMEOUT),
)
def completion_with_backoff(self, **kwargs):
"""Performs a chat completion request with retry/backoff."""
@@ -174,21 +223,52 @@ def query(
}
)
- payload = {
- "messages": [
- {"role": "system", "content": system_role},
- {"role": "user", "content": content_user},
- ],
- "temperature": 0.1,
- "max_tokens": 500,
- "top_p": 0.1,
- "frequency_penalty": 0,
- "presence_penalty": 0,
- "stop": None,
- "model": self.model_name,
- }
+ is_gpt5 = self._is_gpt5_model(self.model_name)
+ if is_gpt5:
+ # GPT-5.x only supports default temperature/top_p and uses
+ # `max_completion_tokens` instead of `max_tokens`.
+ payload = {
+ "messages": [
+ {"role": "system", "content": system_role},
+ {"role": "user", "content": content_user},
+ ],
+ "max_completion_tokens": GPT5_DEFAULT_MAX_COMPLETION_TOKENS,
+ "model": self.model_name,
+ }
+ else:
+ payload = {
+ "messages": [
+ {"role": "system", "content": system_role},
+ {"role": "user", "content": content_user},
+ ],
+ "temperature": 0.1,
+ "max_tokens": 500,
+ "top_p": 0.1,
+ "frequency_penalty": 0,
+ "presence_penalty": 0,
+ "stop": None,
+ "model": self.model_name,
+ }
if params:
+ params = dict(params)
+ if is_gpt5:
+ # GPT-5.x rejects custom temperature/top_p/penalty/stop and
+ # uses `max_completion_tokens` instead of `max_tokens`.
+ if (
+ "max_tokens" in params
+ and "max_completion_tokens" not in params
+ ):
+ params["max_completion_tokens"] = params.pop("max_tokens")
+ for k in (
+ "temperature",
+ "top_p",
+ "frequency_penalty",
+ "presence_penalty",
+ "stop",
+ "max_tokens",
+ ):
+ params.pop(k, None)
payload.update(params)
response = None
@@ -212,18 +292,22 @@ def check_connection(self) -> None:
ConnectionError: If connection fails.
"""
try:
- response = self.completion_with_backoff(
+ probe_kwargs = dict(
messages=[
{"role": "system", "content": "You are a test system."},
{"role": "user", "content": "Hello"},
],
model=self.model_name,
- temperature=0,
- max_tokens=100,
)
- content = response.choices[0].message.content
- logger.info(f"Connection check success.")
- except Exception as e:
+ if self._is_gpt5_model(self.model_name):
+ probe_kwargs["max_completion_tokens"] = 100
+ else:
+ probe_kwargs["temperature"] = 0
+ probe_kwargs["max_tokens"] = 100
+ response = self.completion_with_backoff(**probe_kwargs)
+ response.choices[0].message.content
+ logger.info("Connection check success.")
+ except Exception:
raise ConnectionError(
f"Failed to connect to GPT API at {self.endpoint}, "
f"please check setting in `{CONFIG_FILE}` and `README`."
@@ -241,6 +325,7 @@ def check_connection(self) -> None:
api_key = os.environ.get("API_KEY", agent_config.get("api_key"))
api_version = os.environ.get("API_VERSION", agent_config.get("api_version"))
model_name = os.environ.get("MODEL_NAME", agent_config.get("model_name"))
+timeout = DEFAULT_GPT_TIMEOUT
GPT_CLIENT = GPTclient(
endpoint=endpoint,
@@ -248,6 +333,7 @@ def check_connection(self) -> None:
api_version=api_version,
model_name=model_name,
check_connection=False,
+ timeout=timeout,
)
diff --git a/embodied_gen/utils/gpt_config.yaml b/embodied_gen/utils/gpt_config.yaml
index 67b78b3..3b1302b 100644
--- a/embodied_gen/utils/gpt_config.yaml
+++ b/embodied_gen/utils/gpt_config.yaml
@@ -1,5 +1,5 @@
# config.yaml
-agent_type: "qwen2.5-vl" # gpt-4o or qwen2.5-vl
+agent_type: "gpt-5.4" # gpt-4o, gpt-5.4 or qwen2.5-vl
gpt-4o:
endpoint: https://xxx.openai.azure.com
@@ -7,6 +7,12 @@ gpt-4o:
api_version: 2025-xx-xx
model_name: yfb-gpt-4o
+gpt-5.4:
+ endpoint: https://yfb-openai-sweden.openai.azure.com/
+ api_key: xxx
+ api_version: 2024-12-01-preview
+ model_name: gpt-5.4
+
qwen2.5-vl:
endpoint: https://openrouter.ai/api/v1
api_key: sk-or-v1-xxx
diff --git a/embodied_gen/utils/inference.py b/embodied_gen/utils/inference.py
index 5e19e93..a0144b3 100644
--- a/embodied_gen/utils/inference.py
+++ b/embodied_gen/utils/inference.py
@@ -1,4 +1,21 @@
-from embodied_gen.utils.monkey_patches import monkey_path_trellis
+# Project EmbodiedGen
+#
+# Copyright (c) 2025 Horizon Robotics. All Rights Reserved.
+#
+# 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.
+
+
+from embodied_gen.utils.monkey_patch.trellis import monkey_path_trellis
monkey_path_trellis()
import random
@@ -21,6 +38,7 @@ def image3d_model_infer(
seed: int = None,
**kwargs: dict,
) -> dict[str, any]:
+ """Execute 3D generation using Trellis or SAM3D pipeline on input image."""
if isinstance(pipe, TrellisImageTo3DPipeline):
pipe.cuda()
seg_image = trellis_preprocess(seg_image)
diff --git a/embodied_gen/utils/io_utils.py b/embodied_gen/utils/io_utils.py
new file mode 100644
index 0000000..8ebb0b1
--- /dev/null
+++ b/embodied_gen/utils/io_utils.py
@@ -0,0 +1,730 @@
+import json
+import os
+import xml.etree.ElementTree as ET
+from collections.abc import Iterable, Mapping
+from typing import TYPE_CHECKING, Any, Literal
+
+import numpy as np
+import trimesh
+import trimesh.transformations as tra
+
+if TYPE_CHECKING:
+ from embodied_gen.utils.geometry import MeshInfo
+
+__all__ = [
+ "URDFFile",
+ "load_json",
+ "load_mesh",
+ "load_mesh_info",
+ "save_mesh",
+ "write_json",
+]
+
+DEFAULT_URDF_ORIGIN_XYZ = (0.0, 0.0, 0.0)
+DEFAULT_URDF_ORIGIN_RPY = (1.5708, 0.0, 0.0)
+DEFAULT_URDF_SCALE = (1.0, 1.0, 1.0)
+
+
+def load_json(path: str | os.PathLike) -> dict:
+ with open(path, "r", encoding="utf-8") as file:
+ payload = json.load(file)
+ return payload
+
+
+def write_json(payload: dict, path: str | os.PathLike) -> None:
+ output_path = os.fspath(path)
+ output_dir = os.path.dirname(output_path)
+ if output_dir:
+ os.makedirs(output_dir, exist_ok=True)
+ with open(output_path, "w", encoding="utf-8") as file:
+ json.dump(payload, file, indent=4)
+
+
+def _sapien_pose_from_mesh_transform(mesh_transform: Mapping[str, str]):
+ import sapien.core as sapien
+ from scipy.spatial.transform import Rotation as R
+
+ origin_xyz = [
+ float(value) for value in mesh_transform["origin_xyz"].split()
+ ]
+ origin_rpy = [
+ float(value) for value in mesh_transform["origin_rpy"].split()
+ ]
+
+ rotation = R.from_euler("xyz", origin_rpy)
+ quat_xyzw = rotation.as_quat()
+ local_pose = sapien.Pose(
+ p=origin_xyz,
+ q=[
+ float(quat_xyzw[3]),
+ float(quat_xyzw[0]),
+ float(quat_xyzw[1]),
+ float(quat_xyzw[2]),
+ ],
+ )
+ return local_pose
+
+
+def _mesh_scale_from_transform(
+ mesh_transform: Mapping[str, str],
+) -> np.ndarray:
+ return np.array(
+ [float(v) for v in mesh_transform["scale"].split()],
+ dtype=np.float64,
+ )
+
+
+def load_mesh_info(urdf_path: str | os.PathLike) -> "MeshInfo":
+ from embodied_gen.utils.geometry import MeshInfo
+
+ urdf = URDFFile(urdf_path)
+ collision_mesh_path = urdf.get_mesh_path("collision")
+ collision_mesh_transform = urdf.get_mesh_transform("collision")
+ collision_mesh_scale = _mesh_scale_from_transform(collision_mesh_transform)
+ collision_local_pose = _sapien_pose_from_mesh_transform(
+ collision_mesh_transform
+ )
+
+ try:
+ visual_mesh_path = urdf.get_mesh_path("visual")
+ visual_mesh_transform = urdf.get_mesh_transform("visual")
+ except ValueError:
+ visual_mesh_path = collision_mesh_path
+ visual_mesh_transform = collision_mesh_transform
+ visual_mesh_scale = _mesh_scale_from_transform(visual_mesh_transform)
+ visual_local_pose = _sapien_pose_from_mesh_transform(visual_mesh_transform)
+
+ mesh = load_mesh(
+ collision_mesh_path,
+ apply_origin=True,
+ **collision_mesh_transform,
+ )
+ object_height = float(mesh.bounds[1, 2] - mesh.bounds[0, 2])
+ static_friction, dynamic_friction = urdf.get_collision_friction()
+
+ return MeshInfo(
+ actor_name=urdf.get_robot_name(),
+ collision_mesh_path=collision_mesh_path,
+ collision_mesh_scale=collision_mesh_scale,
+ collision_local_pose=collision_local_pose,
+ visual_mesh_path=visual_mesh_path,
+ visual_mesh_scale=visual_mesh_scale,
+ visual_local_pose=visual_local_pose,
+ transformed_mesh=mesh,
+ object_height=object_height,
+ mass=urdf.get_mass(),
+ static_friction=static_friction,
+ dynamic_friction=dynamic_friction,
+ )
+
+
+class URDFFile:
+ """Small XML helper for reading and writing fields inside one URDF file."""
+
+ def __init__(self, urdf_path: str | os.PathLike):
+ self.urdf_path = os.fspath(urdf_path)
+ self.urdf_dir = os.path.dirname(self.urdf_path)
+ self.tree = ET.parse(self.urdf_path)
+ self.root = self.tree.getroot()
+
+ def reload(self) -> None:
+ self.tree = ET.parse(self.urdf_path)
+ self.root = self.tree.getroot()
+
+ def save(
+ self,
+ urdf_path: str | os.PathLike | None = None,
+ *,
+ indent: bool = True,
+ indent_space: str = " ",
+ ) -> None:
+ output_path = (
+ os.fspath(urdf_path) if urdf_path is not None else self.urdf_path
+ )
+ if indent:
+ ET.indent(self.tree, space=indent_space)
+ self.tree.write(output_path, encoding="utf-8", xml_declaration=True)
+
+ def read(
+ self,
+ path: str,
+ *,
+ attr: str | None = None,
+ default: Any = None,
+ required: bool = False,
+ all_matches: bool = False,
+ strip: bool = True,
+ ) -> Any:
+ nodes = (
+ self.root.findall(path) if all_matches else [self.root.find(path)]
+ )
+ if not all_matches and nodes[0] is None:
+ if required:
+ raise ValueError(
+ f"URDF path not found: {path} in {self.urdf_path}"
+ )
+ return default
+
+ values = [
+ self._read_node_value(
+ node, attr=attr, default=default, strip=strip
+ )
+ for node in nodes
+ if node is not None
+ ]
+ if all_matches:
+ if required and not values:
+ raise ValueError(
+ f"URDF path not found: {path} in {self.urdf_path}"
+ )
+ return values
+ return values[0]
+
+ def get_mesh_path(self, mesh_type: str = "visual") -> str:
+ mesh_path = self.read(
+ f".//{mesh_type}/geometry/mesh",
+ attr="filename",
+ required=True,
+ )
+ return self._resolve_path(mesh_path)
+
+ def get_robot_name(self, default: str = "dropped_object") -> str:
+ return self.read(".", attr="name", default=default)
+
+ def get_category(self) -> str:
+ category = self.read(".//extra_info/category", required=True)
+ if not category:
+ raise ValueError(f"Empty category in {self.urdf_path}")
+ return category
+
+ def get_mesh_part_seg_path(self) -> str:
+ mesh_path = self.read(
+ ".//custom_data/affordance/visual_seg/geometry/mesh",
+ attr="filename",
+ required=True,
+ )
+ return self._resolve_path(mesh_path)
+
+ def get_affordance_annot_path(self) -> str:
+ annot_path = self.read(
+ ".//custom_data/affordance/affordance_annot",
+ required=True,
+ )
+ return self._resolve_path(annot_path)
+
+ def get_mesh_transform(self, mesh_type: str = "visual") -> dict:
+ return {
+ "origin_xyz": self.read(
+ f".//{mesh_type}/origin",
+ attr="xyz",
+ default="0 0 0",
+ ),
+ "origin_rpy": self.read(
+ f".//{mesh_type}/origin",
+ attr="rpy",
+ default="0 0 0",
+ ),
+ "scale": self.read(
+ f".//{mesh_type}/geometry/mesh",
+ attr="scale",
+ default="1 1 1",
+ ),
+ }
+
+ def get_mass(self) -> float | None:
+ mass = self.read(".//inertial/mass", attr="value")
+ return None if mass is None else float(mass)
+
+ def get_collision_friction(
+ self,
+ *,
+ default_static: float = 0.7,
+ default_dynamic: float = 0.6,
+ ) -> tuple[float, float]:
+ static_friction = self.read(
+ ".//collision/gazebo/mu1",
+ default=str(default_static),
+ )
+ dynamic_friction = self.read(
+ ".//collision/gazebo/mu2",
+ default=str(default_dynamic),
+ )
+ return float(static_friction), float(dynamic_friction)
+
+ def get_prismatic_joint_control_info(self) -> dict[str, float]:
+ lower_limits = []
+ upper_limits = []
+ effort_limits = []
+ damping_values = []
+ for joint_node in self.root.findall(".//joint"):
+ if joint_node.get("type") != "prismatic":
+ continue
+ limit_node = joint_node.find("limit")
+ if limit_node is None:
+ continue
+ lower_limits.append(float(limit_node.get("lower", "0.0")))
+ upper_limits.append(float(limit_node.get("upper", "0.0")))
+ effort_limits.append(float(limit_node.get("effort", "0.0")))
+ dynamics_node = joint_node.find("dynamics")
+ if dynamics_node is not None:
+ damping_values.append(
+ float(dynamics_node.get("damping", "0.0"))
+ )
+
+ if not lower_limits or not upper_limits:
+ raise ValueError(
+ f"No prismatic joint limits found in {self.urdf_path}"
+ )
+
+ valid_efforts = [effort for effort in effort_limits if effort > 0.0]
+ valid_damping = [
+ damping for damping in damping_values if damping > 0.0
+ ]
+ return {
+ "open_qpos": min(upper_limits),
+ "close_qpos": max(lower_limits),
+ "drive_damping": max(valid_damping) if valid_damping else 0.0,
+ "force_limit": min(valid_efforts) if valid_efforts else 0.0,
+ }
+
+ def get_link_names(self) -> list[str]:
+ return [
+ link_name
+ for link_name in (
+ link_node.get("name")
+ for link_node in self.root.findall("link")
+ )
+ if link_name
+ ]
+
+ def get_child_link_names(
+ self,
+ *,
+ name_contains: str | None = None,
+ ) -> list[str]:
+ child_link_names = []
+ name_query = (
+ name_contains.lower() if name_contains is not None else None
+ )
+ for joint_node in self.root.findall("joint"):
+ child_node = joint_node.find("child")
+ link_name = (
+ child_node.get("link") if child_node is not None else None
+ )
+ if not link_name:
+ continue
+ if name_query is not None and name_query not in link_name.lower():
+ continue
+ child_link_names.append(link_name)
+ return list(dict.fromkeys(child_link_names))
+
+ def get_link_transforms(self) -> dict[str, np.ndarray]:
+ child_to_joint = {}
+ for joint_node in self.root.findall("joint"):
+ child_node = joint_node.find("child")
+ parent_node = joint_node.find("parent")
+ child_name = (
+ child_node.get("link") if child_node is not None else None
+ )
+ parent_name = (
+ parent_node.get("link") if parent_node is not None else None
+ )
+ if child_name and parent_name:
+ child_to_joint[child_name] = joint_node
+
+ transforms: dict[str, np.ndarray] = {}
+
+ def link_transform(link_name: str) -> np.ndarray:
+ if link_name in transforms:
+ return transforms[link_name]
+ joint_node = child_to_joint.get(link_name)
+ if joint_node is None:
+ transforms[link_name] = np.eye(4, dtype=np.float64)
+ return transforms[link_name]
+
+ parent_name = joint_node.find("parent").get("link")
+ transforms[link_name] = link_transform(
+ parent_name
+ ) @ self._joint_transform(joint_node)
+ return transforms[link_name]
+
+ for link_name in self.get_link_names():
+ link_transform(link_name)
+ return transforms
+
+ def load_link_geometry_mesh(
+ self,
+ link_name: str,
+ geometry_type: Literal["collision", "visual"] = "collision",
+ ) -> trimesh.Trimesh:
+ link_node = next(
+ (
+ node
+ for node in self.root.findall("link")
+ if node.get("name") == link_name
+ ),
+ None,
+ )
+ if link_node is None:
+ raise ValueError(
+ f"URDF link not found: {link_name} in {self.urdf_path}"
+ )
+
+ geom_node = link_node.find(geometry_type)
+ if geom_node is None and geometry_type == "collision":
+ geom_node = link_node.find("visual")
+ if geom_node is None:
+ raise ValueError(
+ f"link {link_name} does not contain {geometry_type} geometry"
+ )
+
+ mesh_node = geom_node.find("geometry/mesh")
+ mesh_filename = (
+ mesh_node.get("filename") if mesh_node is not None else None
+ )
+ if mesh_filename is None:
+ raise ValueError(
+ f"link {link_name} does not contain a mesh geometry"
+ )
+
+ origin_node = geom_node.find("origin")
+ origin_xyz = (
+ origin_node.get("xyz", "0 0 0")
+ if origin_node is not None
+ else None
+ )
+ origin_rpy = (
+ origin_node.get("rpy", "0 0 0")
+ if origin_node is not None
+ else None
+ )
+ return load_mesh(
+ self._resolve_path(mesh_filename),
+ origin_xyz=origin_xyz,
+ origin_rpy=origin_rpy,
+ scale=mesh_node.get("scale", "1.0 1.0 1.0"),
+ apply_origin=True,
+ apply_scale=True,
+ )
+
+ def write(
+ self,
+ updates: Mapping[str, Any] | Iterable[Mapping[str, Any]],
+ *,
+ save: bool = True,
+ urdf_path: str | os.PathLike | None = None,
+ indent: bool = True,
+ indent_space: str = " ",
+ ) -> None:
+ for update in self._normalize_updates(updates):
+ node = self._find_or_create(update["path"])
+ if update.get("clear_attrs", False):
+ node.attrib.clear()
+ if update.get("clear_children", False):
+ node.clear()
+ if "text" in update:
+ node.text = (
+ None if update["text"] is None else str(update["text"])
+ )
+ for key, value in update.get("attrs", {}).items():
+ node.set(key, str(value))
+
+ if save:
+ self.save(urdf_path, indent=indent, indent_space=indent_space)
+
+ @staticmethod
+ def _read_node_value(
+ node: ET.Element,
+ *,
+ attr: str | None,
+ default: Any,
+ strip: bool,
+ ) -> Any:
+ value = node.get(attr) if attr is not None else node.text
+ if value is None:
+ return default
+ if strip and isinstance(value, str):
+ return value.strip()
+ return value
+
+ def _resolve_path(self, path: str | os.PathLike) -> str:
+ path = os.fspath(path)
+ if not os.path.isabs(path):
+ path = os.path.join(self.urdf_dir, path)
+ return os.path.normpath(path)
+
+ @staticmethod
+ def _origin_transform(origin_node: ET.Element | None) -> np.ndarray:
+ if origin_node is None:
+ return np.eye(4, dtype=np.float64)
+
+ xyz = _parse_xyz_rpy(
+ origin_node.get("xyz", "0 0 0"), (0.0, 0.0, 0.0), "origin xyz"
+ )
+ rpy = _parse_xyz_rpy(
+ origin_node.get("rpy", "0 0 0"), (0.0, 0.0, 0.0), "origin rpy"
+ )
+ transform = tra.euler_matrix(*rpy, axes="sxyz")
+ transform[:3, 3] = xyz
+ return transform.astype(np.float64, copy=False)
+
+ @staticmethod
+ def _joint_axis(joint_node: ET.Element) -> np.ndarray:
+ axis_node = joint_node.find("axis")
+ axis = np.asarray(
+ _parse_xyz_rpy(
+ axis_node.get("xyz", "1 0 0")
+ if axis_node is not None
+ else None,
+ (1.0, 0.0, 0.0),
+ "joint axis",
+ ),
+ dtype=np.float64,
+ )
+ norm = np.linalg.norm(axis)
+ if norm <= 1e-12:
+ raise ValueError(
+ f"joint axis must be non-zero, got {axis.tolist()}"
+ )
+ return axis / norm
+
+ @staticmethod
+ def _joint_default_position(joint_node: ET.Element) -> float:
+ if joint_node.get("type") != "prismatic":
+ return 0.0
+
+ limit_node = joint_node.find("limit")
+ if limit_node is None:
+ return 0.0
+ return float(limit_node.get("upper", limit_node.get("lower", "0.0")))
+
+ @classmethod
+ def _joint_transform(cls, joint_node: ET.Element) -> np.ndarray:
+ transform = cls._origin_transform(joint_node.find("origin"))
+ joint_position = cls._joint_default_position(joint_node)
+ if joint_position == 0.0:
+ return transform
+
+ motion = np.eye(4, dtype=np.float64)
+ motion[:3, 3] = cls._joint_axis(joint_node) * joint_position
+ return transform @ motion
+
+ @staticmethod
+ def _normalize_updates(
+ updates: Mapping[str, Any] | Iterable[Mapping[str, Any]],
+ ) -> list[dict[str, Any]]:
+ if isinstance(updates, Mapping):
+ normalized = []
+ for path, value in updates.items():
+ if isinstance(value, Mapping) and any(
+ key in value
+ for key in (
+ "text",
+ "attrs",
+ "clear_attrs",
+ "clear_children",
+ )
+ ):
+ item = {"path": path, **dict(value)}
+ else:
+ item = {"path": path, "text": value}
+ normalized.append(item)
+ return normalized
+
+ normalized = []
+ for update in updates:
+ if "path" not in update:
+ raise ValueError(f"URDF write update missing path: {update}")
+ normalized.append(dict(update))
+ return normalized
+
+ def _find_or_create(self, path: str) -> ET.Element:
+ existing = self.root.find(path)
+ if existing is not None:
+ return existing
+
+ node = self.root
+ for tag in self._creation_path_parts(path):
+ child = node.find(tag)
+ if child is None:
+ child = ET.SubElement(node, tag)
+ node = child
+ return node
+
+ def _creation_path_parts(self, path: str) -> list[str]:
+ clean_path = path.strip()
+ if clean_path in ("", "."):
+ return []
+ if clean_path.startswith(".//"):
+ clean_path = clean_path[3:]
+ elif clean_path.startswith("./"):
+ clean_path = clean_path[2:]
+ if clean_path.startswith(f"{self.root.tag}/"):
+ clean_path = clean_path[len(self.root.tag) + 1 :]
+
+ parts = [part for part in clean_path.split("/") if part]
+ if any(
+ any(token in part for token in ("[", "]", "@", "*"))
+ for part in parts
+ ):
+ raise ValueError(
+ f"URDF write can only create simple element paths, got: {path}"
+ )
+ return parts
+
+
+def _normalize_scale(scale) -> tuple[float, float, float]:
+ if isinstance(scale, str):
+ values = [float(value) for value in scale.split()]
+ elif isinstance(scale, (int, float, np.number)):
+ values = [float(scale)]
+ else:
+ values = [float(value) for value in scale]
+
+ if len(values) == 1:
+ values = values * 3
+ if len(values) != 3:
+ raise ValueError(f"scale must have 1 or 3 values, got {scale}")
+ return tuple(values)
+
+
+def _parse_xyz_rpy(
+ values: Iterable[float] | str | None,
+ default: tuple[float, float, float],
+ name: str,
+) -> list[float]:
+ if values is None:
+ parsed = list(default)
+ elif isinstance(values, str):
+ parsed = [float(value) for value in values.split()]
+ else:
+ parsed = [float(value) for value in values]
+
+ if len(parsed) != 3:
+ raise ValueError(f"{name} must have 3 values, got {parsed}")
+ return parsed
+
+
+def _apply_mesh_scale(
+ mesh: trimesh.Trimesh,
+ scale: tuple[float, float, float] | str | Iterable[float] | float,
+) -> None:
+ scale_array = np.asarray(_normalize_scale(scale), dtype=np.float64)
+ if np.allclose(scale_array, scale_array[0]):
+ mesh.apply_scale(float(scale_array[0]))
+ return
+
+ transform = np.eye(4, dtype=np.float64)
+ transform[:3, :3] = np.diag(scale_array)
+ mesh.apply_transform(transform)
+
+
+def _apply_inverse_mesh_scale(
+ mesh: trimesh.Trimesh,
+ scale: tuple[float, float, float] | str | Iterable[float] | float,
+) -> None:
+ scale_array = np.asarray(_normalize_scale(scale), dtype=np.float64)
+ if np.any(np.isclose(scale_array, 0.0)):
+ raise ValueError(f"scale must be non-zero to invert, got {scale}")
+
+ inverse_scale = 1.0 / scale_array
+ if np.allclose(inverse_scale, inverse_scale[0]):
+ mesh.apply_scale(float(inverse_scale[0]))
+ return
+
+ transform = np.eye(4, dtype=np.float64)
+ transform[:3, :3] = np.diag(inverse_scale)
+ mesh.apply_transform(transform)
+
+
+def _apply_origin_transform(
+ mesh: trimesh.Trimesh,
+ origin_xyz: Iterable[float] | str | None,
+ origin_rpy: Iterable[float] | str | None,
+) -> None:
+ xyz = _parse_xyz_rpy(origin_xyz, (0.0, 0.0, 0.0), "origin_xyz")
+ rpy = _parse_xyz_rpy(origin_rpy, (0.0, 0.0, 0.0), "origin_rpy")
+ transform = tra.euler_matrix(*rpy, axes="sxyz")
+ transform[:3, 3] = xyz
+ mesh.apply_transform(transform.astype(np.float64, copy=False))
+
+
+def _apply_inverse_origin_transform(
+ mesh: trimesh.Trimesh,
+ origin_xyz: Iterable[float] | str | None,
+ origin_rpy: Iterable[float] | str | None,
+) -> None:
+ xyz = _parse_xyz_rpy(origin_xyz, (0.0, 0.0, 0.0), "origin_xyz")
+ rpy = _parse_xyz_rpy(origin_rpy, (0.0, 0.0, 0.0), "origin_rpy")
+ transform = tra.euler_matrix(*rpy, axes="sxyz")
+ transform[:3, 3] = xyz
+ mesh.apply_transform(
+ np.linalg.inv(transform).astype(np.float64, copy=False)
+ )
+
+
+def load_mesh(
+ mesh_path: str | os.PathLike,
+ *,
+ origin_xyz: Iterable[float] | str | None = None,
+ origin_rpy: Iterable[float] | str | None = None,
+ scale: tuple[float, float, float]
+ | str
+ | Iterable[float]
+ | float
+ | None = None,
+ apply_origin: bool = True,
+ apply_scale: bool = True,
+) -> trimesh.Trimesh:
+ """Load a mesh and optionally apply URDF mesh scale and origin transform."""
+
+ mesh = trimesh.load(os.fspath(mesh_path), force="mesh", process=False)
+ if isinstance(mesh, trimesh.Scene):
+ mesh = mesh.dump(concatenate=True)
+ if not isinstance(mesh, trimesh.Trimesh):
+ raise ValueError(f"mesh is not a triangle mesh: {mesh_path}")
+
+ if apply_scale and scale is not None:
+ _apply_mesh_scale(mesh, scale)
+ if apply_origin:
+ _apply_origin_transform(mesh, origin_xyz, origin_rpy)
+
+ if "face_ids" in mesh.metadata:
+ return mesh, np.asarray(mesh.metadata["face_ids"], dtype=np.int64)
+ return mesh
+
+
+def save_mesh(
+ mesh: trimesh.Trimesh,
+ output_path: str | os.PathLike,
+ *,
+ origin_xyz: Iterable[float] | str | None = DEFAULT_URDF_ORIGIN_XYZ,
+ origin_rpy: Iterable[float] | str | None = DEFAULT_URDF_ORIGIN_RPY,
+ scale: tuple[float, float, float]
+ | str
+ | Iterable[float]
+ | float
+ | None = DEFAULT_URDF_SCALE,
+ apply_origin: bool = True,
+ apply_scale: bool = True,
+ copy: bool = True,
+) -> str:
+ """Save a mesh by optionally undoing the default URDF scale and origin."""
+
+ if not isinstance(mesh, trimesh.Trimesh):
+ raise ValueError(
+ f"mesh must be a trimesh.Trimesh, got {type(mesh).__name__}"
+ )
+
+ mesh_to_save = mesh.copy() if copy else mesh
+ if apply_origin:
+ _apply_inverse_origin_transform(mesh_to_save, origin_xyz, origin_rpy)
+ if apply_scale and scale is not None:
+ _apply_inverse_mesh_scale(mesh_to_save, scale)
+
+ output_path = os.fspath(output_path)
+ output_dir = os.path.dirname(output_path)
+ if output_dir:
+ os.makedirs(output_dir, exist_ok=True)
+ mesh_to_save.export(output_path)
+ return output_path
diff --git a/embodied_gen/utils/llm_resolve.py b/embodied_gen/utils/llm_resolve.py
new file mode 100644
index 0000000..97cc6b3
--- /dev/null
+++ b/embodied_gen/utils/llm_resolve.py
@@ -0,0 +1,108 @@
+# Project EmbodiedGen
+#
+# Copyright (c) 2025 Horizon Robotics. All Rights Reserved.
+#
+# 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.
+
+
+from __future__ import annotations
+
+import logging
+
+from embodied_gen.utils.gpt_clients import GPTclient
+
+logger = logging.getLogger(__name__)
+
+DEFAULT_RESOLVE_PROMPT = """You are matching a user's description to exactly one object in a 3D scene.
+
+Scene instance list (each is an identifier, may contain IDs like "kitchen_cabinet_9197760", "banana_001"):
+{instance_list}
+
+User description (what they want to refer to, e.g. "้ป่ฒๆฐดๆ", "the yellow fruit", "ๆๅญ", "oven"):
+"{user_spec}"
+
+Rules:
+1. Pick the ONE instance from the list that best matches the user's description (semantic match: e.g. "้ป่ฒๆฐดๆ" -> banana, "ๆๅญ" -> cabinet).
+2. If no instance matches, reply with exactly: NONE
+3. Otherwise reply with the EXACT instance name from the list, nothing else (no quotes, no explanation).
+
+Your reply (one line, exact instance name or NONE):"""
+
+
+def resolve_instance_with_llm(
+ gpt_client: GPTclient,
+ instance_names: list[str],
+ user_spec: str,
+ prompt_template: str | None = None,
+) -> str | None:
+ """Map a user description to a single scene instance name via LLM semantic matching.
+
+ E.g. user says "yellow fruit" and the scene has "banana_001" -> returns "banana_001".
+ Returns None when there is no match or the LLM replies NONE; the caller should
+ prompt the user that the object does not exist and ask for re-entry.
+
+ Args:
+ gpt_client: GPT client instance, e.g. embodied_gen.utils.gpt_clients.GPT_CLIENT.
+ instance_names: List of scene instance names from FloorplanManager.get_instance_names().
+ user_spec: User input, e.g. "yellow fruit", "ๆๅญ", "the table".
+ prompt_template: Optional custom prompt; placeholders {instance_list} and {user_spec}.
+
+ Returns:
+ The matched instance name (exactly one of instance_names), or None if no match.
+ """
+ if not user_spec or not instance_names:
+ return None
+
+ template = prompt_template or DEFAULT_RESOLVE_PROMPT
+ instance_list_str = "\n".join(f"- {n}" for n in instance_names)
+ prompt = template.format(
+ instance_list=instance_list_str,
+ user_spec=(user_spec or "").strip(),
+ )
+
+ try:
+ response = gpt_client.query(text_prompt=prompt)
+ except Exception as e:
+ logger.warning("LLM `resolve_instance_with_llm` query failed: %s", e)
+ return None
+
+ if not response:
+ return None
+
+ first_line = response.strip().split("\n")[0].strip()
+ if first_line.upper() == "NONE":
+ return None
+ candidate = first_line.strip('"\'')
+
+ if not candidate:
+ return None
+
+ names_lower = {n.lower(): n for n in instance_names}
+ candidate_lower = candidate.lower()
+
+ if candidate in instance_names:
+ return candidate
+
+ if candidate_lower in names_lower:
+ return names_lower[candidate_lower]
+
+ matches = [n for n in instance_names if candidate_lower in n.lower()]
+ if len(matches) == 1:
+ return matches[0]
+
+ logger.debug(
+ "resolve_instance_with_llm: LLM reply %r did not match any of %s",
+ first_line,
+ instance_names[:5],
+ )
+ return None
diff --git a/embodied_gen/utils/monkey_patch/gradio.py b/embodied_gen/utils/monkey_patch/gradio.py
new file mode 100644
index 0000000..584c9cb
--- /dev/null
+++ b/embodied_gen/utils/monkey_patch/gradio.py
@@ -0,0 +1,140 @@
+# Project EmbodiedGen
+#
+# Copyright (c) 2025 Horizon Robotics. All Rights Reserved.
+#
+# 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.
+
+
+import fileinput
+import site
+
+import gradio_client.utils as gradio_client_utils
+
+
+def _patch_gradio_schema_bool_bug() -> None:
+ """Patch schema parser for bool-style for gradio<5.33."""
+ original_get_type = gradio_client_utils.get_type
+ original_json_schema_to_python_type = (
+ gradio_client_utils._json_schema_to_python_type
+ )
+
+ def _safe_get_type(schema):
+ if isinstance(schema, bool):
+ return {}
+ return original_get_type(schema)
+
+ def _safe_json_schema_to_python_type(schema, defs):
+ if isinstance(schema, bool):
+ return "Any"
+ return original_json_schema_to_python_type(schema, defs)
+
+ gradio_client_utils.get_type = _safe_get_type
+ gradio_client_utils._json_schema_to_python_type = (
+ _safe_json_schema_to_python_type
+ )
+
+
+def _patch_open3d_cuda_device_count_bug() -> None:
+ """Patch open3d to avoid cuda device count bug."""
+ with fileinput.FileInput(
+ f'{site.getsitepackages()[0]}/open3d/__init__.py', inplace=True
+ ) as file:
+ for line in file:
+ print(
+ line.replace(
+ '_pybind_cuda.open3d_core_cuda_device_count()', '1'
+ ),
+ end='',
+ )
+
+
+def _neutralize_warp_in_parent() -> None:
+ """Prevent NVIDIA Warp from calling cuInit() in the ZeroGPU parent.
+
+ Root cause of @spaces.GPU silent hangs (spaces>=0.50): kaolin imports
+ warp at module top-level. When any kaolin module triggers warp.init(),
+ Warp's `init_cuda_driver` dlopens libcuda.so + calls cuInit() in the
+ parent process. After spaces forks the worker, torch.init(nvidia_uuid)
+ in the worker hangs forever because the inherited CUDA driver state is
+ poisoned (parent never had a real GPU; ZeroGPU exposes one only post-fork).
+
+ Fix: stub warp.init / warp.context.runtime_init with a pid-aware no-op.
+ The parent-resident pid skips init; the forked worker (different pid)
+ runs the real init so warp keeps working inside @spaces.GPU code paths.
+
+ Must be called BEFORE any import that pulls kaolin (e.g. embodied_gen.data,
+ thirdparty.TRELLIS).
+ """
+ import os
+ import sys
+
+ try:
+ import warp # noqa: F401 -- pure python import, no cuInit
+ except ImportError:
+ return
+
+ parent_pid = os.getpid()
+
+ def _make_pid_safe(orig):
+ def _wrapped(*args, **kwargs):
+ if os.getpid() == parent_pid:
+ sys.stderr.write(
+ f"[warp-neutralize] skip {orig.__name__} in parent pid={parent_pid}\n"
+ )
+ sys.stderr.flush()
+ return None
+ return orig(*args, **kwargs)
+
+ _wrapped.__wrapped__ = orig
+ _wrapped.__name__ = getattr(orig, "__name__", "wrapped")
+ return _wrapped
+
+ if hasattr(warp, "init") and not hasattr(warp.init, "__wrapped__"):
+ warp.init = _make_pid_safe(warp.init)
+
+ try:
+ from warp import context as _wctx
+
+ if hasattr(_wctx, "runtime_init") and not hasattr(
+ _wctx.runtime_init, "__wrapped__"
+ ):
+ _wctx.runtime_init = _make_pid_safe(_wctx.runtime_init)
+ except Exception:
+ pass
+
+
+def _disable_xformers_flash3() -> None:
+ """Force xformers dispatcher to skip Flash-Attention v3 (Hopper-only).
+
+ sm_120 (Blackwell) has no FA3 kernel binary; the dispatcher still picks
+ flash3 and the launch aborts with:
+ `CUDA error ... hopper/flash_fwd_launch_template.h:188: invalid argument`
+ Env vars `XFORMERS_FLASH3_ATTENTION_DISABLED=1` are silently ignored in
+ xformers 0.0.32.post2, so we patch `not_supported_reasons` directly.
+ Cutlass and FA2 both work on sm_120, so removing flash3 from candidates
+ is enough.
+ """
+ try:
+ from xformers.ops.fmha import flash3 as _f3
+ except Exception:
+ return
+
+ _disabled = ["disabled by EmbodiedGen: no FA3 kernel for sm_120"]
+
+ def _ns(cls, d): # noqa: ARG001
+ return list(_disabled)
+
+ if hasattr(_f3, "FwOp"):
+ _f3.FwOp.not_supported_reasons = classmethod(_ns)
+ if hasattr(_f3, "BwOp"):
+ _f3.BwOp.not_supported_reasons = classmethod(_ns)
diff --git a/embodied_gen/utils/monkey_patch/infinigen.py b/embodied_gen/utils/monkey_patch/infinigen.py
new file mode 100644
index 0000000..6119e7f
--- /dev/null
+++ b/embodied_gen/utils/monkey_patch/infinigen.py
@@ -0,0 +1,812 @@
+# Project EmbodiedGen
+#
+# Copyright (c) 2025 Horizon Robotics. All Rights Reserved.
+#
+# 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.
+
+
+import os
+import sys
+
+import numpy as np
+
+
+def _infinigen_path():
+ current_file = os.path.abspath(__file__)
+ current_dir = os.path.dirname(current_file)
+ return os.path.abspath(
+ os.path.join(current_dir, "../../..", "thirdparty", "infinigen")
+ )
+
+
+def _ensure_infinigen_on_path():
+ path = _infinigen_path()
+ if path not in sys.path:
+ sys.path.insert(0, path)
+
+
+def patch_material_assignments():
+ """Replace ceramic.tile with ceramic.Tile in utility_floor assignments."""
+ _ensure_infinigen_on_path()
+ from infinigen.assets.composition import material_assignments
+ from infinigen.assets.materials import ceramic
+
+ # utility_floor: ceramic.tile -> ceramic.Tile
+ material_assignments.utility_floor = [
+ (ceramic.Concrete, 1.0),
+ (ceramic.Plaster, 1.0),
+ (ceramic.Tile, 1.0),
+ ]
+
+
+def patch_concrete():
+ """Filter Concrete.generate kwargs to supported keys."""
+ _ensure_infinigen_on_path()
+ from infinigen.assets.materials.ceramic import concrete
+ from infinigen.core import surface
+
+ shader_concrete = concrete.shader_concrete
+
+ def patched_generate(self, **kwargs):
+ # Filter out unsupported keywords and pass remaining arguments
+ # Concrete.shader_concrete accepts: scale, base_color_hsv, seed, roughness, crack_amount, crack_scale, snake_crack
+ supported_kwargs = {
+ 'scale',
+ 'base_color_hsv',
+ 'seed',
+ 'roughness',
+ 'crack_amount',
+ 'crack_scale',
+ 'snake_crack',
+ }
+ filtered_kwargs = {
+ k: v for k, v in kwargs.items() if k in supported_kwargs
+ }
+ return surface.shaderfunc_to_material(
+ shader_concrete, **filtered_kwargs
+ )
+
+ concrete.Concrete.generate = patched_generate
+ concrete.Concrete.__call__ = patched_generate
+
+
+def patch_room_constants():
+ """Add Office to RoomConstants.home_room_types."""
+ _ensure_infinigen_on_path()
+ from infinigen.core import tags as t
+ from infinigen.core.constraints.constraint_language.constants import (
+ RoomConstants,
+ )
+
+ _original_home_room_types = RoomConstants.home_room_types.fget
+
+ @property
+ def patched_home_room_types(self):
+ return _original_home_room_types(self) | {t.Semantics.Office}
+
+ RoomConstants.home_room_types = patched_home_room_types
+
+
+def patch_doors_base_simple():
+ """Override BaseDoorFactory init to customize door dimensions and handles."""
+ _ensure_infinigen_on_path()
+ from infinigen.assets import colors
+ from infinigen.assets.composition import material_assignments
+ from infinigen.assets.objects.elements.doors.base import BaseDoorFactory
+ from infinigen.core.constraints.constraint_language.constants import (
+ RoomConstants,
+ )
+ from infinigen.core.util.math import FixedSeed
+ from infinigen.core.util.random import weighted_sample
+ from numpy.random import uniform
+
+ _orig_init = BaseDoorFactory.__init__
+
+ def patched_init(self, factory_seed, coarse=False, constants=None):
+ _orig_init(self, factory_seed, coarse=coarse, constants=constants)
+ with FixedSeed(self.factory_seed):
+ if constants is None:
+ constants = RoomConstants()
+ self.width = constants.door_width - 0.02
+ # Force a rectangular full frame so generated doors can close
+ # cleanly against the wall opening.
+ self.door_frame_style = "full_frame_square"
+ self.door_frame_width = 0.02
+ handle_types = ["knob", "lever", "pull", "none", "bar"]
+ if self.door_frame_style != "single_column":
+ self.width += -0.02
+ self.height += -0.04
+ self.handle_type = np.random.choice(handle_types)
+ if self.handle_type == "bar":
+ self.surface = weighted_sample(material_assignments.metals)()
+ if self.handle_type == "bar":
+ self.handle_info_dict = {
+ "handle_type": self.handle_type,
+ "bar_length": uniform(0.7, 0.9) * self.width,
+ "bar_thickness": uniform(0.025, 0.045) * self.height,
+ "bar_aspect_ratio": uniform(0.4, 0.6),
+ "bar_height_ratio": uniform(0.7, 0.9),
+ "bar_length_ratio": uniform(0.5, 0.8),
+ "bar_end_length_ratio": uniform(0.1, 0.15),
+ "bar_end_height_ratio": uniform(1.8, 3.0),
+ "bar_overall_z_offset": -uniform(0.0, 0.1) * self.height,
+ "shader": weighted_sample(material_assignments.metals)(),
+ "color": colors.hsv2rgba(colors.metal_natural_hsv()),
+ }
+ else:
+ self.handle_info_dict = {"handle_type": self.handle_type}
+ if self.handle_type in ["knob", "lever"]:
+ self.handle_joint = "hinge"
+ elif self.handle_type == "bar":
+ self.handle_joint = "slide"
+ elif self.handle_type == "pull":
+ self.handle_joint = "rigid"
+ else:
+ self.handle_joint = "none"
+
+ BaseDoorFactory.__init__ = patched_init
+
+
+def patch_kitchen_cabinet():
+ """Add kitchen_space_bottom support to kitchen cabinet factories."""
+ _ensure_infinigen_on_path()
+ from infinigen.assets.objects.shelves.kitchen_cabinet import (
+ KitchenCabinetBaseFactory,
+ KitchenCabinetFactory,
+ )
+ from numpy.random import uniform
+
+ _orig_base_init = KitchenCabinetBaseFactory.__init__
+
+ def patched_base_init(
+ self,
+ factory_seed,
+ params=None,
+ coarse=False,
+ kitchen_space_bottom=False,
+ ):
+ if params is None:
+ params = {}
+ _orig_base_init(self, factory_seed, params=params, coarse=coarse)
+ self.bottom_mid = kitchen_space_bottom
+
+ KitchenCabinetBaseFactory.__init__ = patched_base_init
+
+ def patched_factory_init(
+ self,
+ factory_seed,
+ params=None,
+ coarse=False,
+ dimensions=None,
+ drawer_only=False,
+ kitchen_space_bottom=False,
+ ):
+ if params is None:
+ params = {}
+ self.dimensions = dimensions
+ KitchenCabinetBaseFactory.__init__(
+ self,
+ factory_seed,
+ params=params,
+ coarse=coarse,
+ kitchen_space_bottom=kitchen_space_bottom,
+ )
+ self.drawer_only = drawer_only
+
+ KitchenCabinetFactory.__init__ = patched_factory_init
+
+ _orig_sample_params = KitchenCabinetFactory.sample_params
+
+ def patched_sample_params(self):
+ params = dict()
+ if self.dimensions is None:
+ dimensions = (
+ uniform(0.25, 0.35),
+ uniform(0.5, 1.0),
+ uniform(0.5, 1.3),
+ )
+ self.dimensions = dimensions
+ else:
+ dimensions = self.dimensions
+ params["Dimensions"] = dimensions
+ # Copy frame_params logic from original
+ params["shelf_depth"] = params["Dimensions"][0] - 0.01
+ num_h = int((params["Dimensions"][2] - 0.06) / 0.3)
+ params["shelf_cell_height"] = [
+ (params["Dimensions"][2] - 0.06) / num_h for _ in range(num_h)
+ ]
+ params["side_board_thickness"] = 0.02
+ params["division_board_thickness"] = 0.02
+ params["bottom_board_height"] = 0.06
+ self.frame_params = params
+ n_cells = max(int(params["Dimensions"][1] / 0.45), 1)
+ intervals = np.random.uniform(0.55, 1.0, size=(n_cells,))
+ intervals = intervals / intervals.sum() * params["Dimensions"][1]
+ self.cabinet_widths = intervals.tolist()
+ if getattr(self, "bottom_mid", False):
+ self.cabinet_widths = [params["Dimensions"][1]]
+
+ KitchenCabinetFactory.sample_params = patched_sample_params
+
+
+def patch_kitchen_space():
+ """Customize kitchen space/island creation with sink and layout tweaks."""
+ _ensure_infinigen_on_path()
+ from infinigen.assets.objects.shelves.kitchen_cabinet import (
+ KitchenCabinetFactory,
+ )
+
+ # Need to import geometry_nodes_add_cabinet_top and nodegroup_tag_cube from same module
+ from infinigen.assets.objects.shelves.kitchen_space import (
+ KitchenIslandFactory,
+ KitchenSpaceFactory,
+ geometry_nodes_add_cabinet_top,
+ )
+ from infinigen.assets.objects.table_decorations import SinkFactory
+ from infinigen.assets.objects.wall_decorations.range_hood import (
+ RangeHoodFactory,
+ )
+ from infinigen.core import surface, tagging
+ from infinigen.core.util import blender as butil
+ from infinigen.core.util.math import FixedSeed
+ from mathutils import Vector
+ from numpy.random import choice, uniform
+
+ _orig_ks_init = KitchenSpaceFactory.__init__
+
+ def patched_ks_init(
+ self,
+ factory_seed,
+ coarse=False,
+ dimensions=None,
+ island=False,
+ has_sink=False,
+ ):
+ KitchenSpaceFactory.__bases__[0].__init__(
+ self, factory_seed, coarse=coarse
+ )
+ with FixedSeed(factory_seed):
+ if dimensions is None:
+ dimensions = Vector(
+ (uniform(0.7, 1), uniform(1.7, 5), uniform(2.3, 2.5))
+ )
+ self.island = island
+ if self.island:
+ dimensions.x *= uniform(1.5, 2)
+ dimensions.y = uniform(1, 2)
+ self.dimensions = dimensions
+ self.params = self.sample_parameters(dimensions)
+ self.has_sink = has_sink
+
+ KitchenSpaceFactory.__init__ = patched_ks_init
+
+ _orig_create_asset = KitchenSpaceFactory.create_asset
+
+ def patched_create_asset(self, **params):
+ x, y, z = self.dimensions
+ parts = []
+ cabinet_bottom_height = self.cabinet_bottom_height
+ cabinet_top_height = self.cabinet_top_height
+ mid_width = uniform(1.0, 1.3)
+ other_width = (y - mid_width) / 2.0
+ offset_bm = 0.04
+ offset_tm = 0.08
+ offset = 0.04
+ if other_width >= 0.98:
+ offset = 0.08
+ elif 0.98 > other_width >= 0.9:
+ other_width += -0.04
+ mid_width += 0.08
+ if other_width >= 1.47:
+ offset = 0.12
+ elif 1.47 > other_width >= 1.35:
+ other_width += -0.04
+ mid_width += 0.08
+ if other_width >= 1.96:
+ offset = 0.16
+ elif 1.96 > other_width >= 1.8:
+ other_width += -0.04
+ mid_width += 0.08
+
+ if self.island and other_width <= 0.3:
+ num_cells = False
+ offset = 0.08
+ if getattr(self, "has_sink", False) or y < 1.35:
+ num_cells = True
+ offset = 0.04
+ island_factory = KitchenCabinetFactory(
+ self.factory_seed,
+ dimensions=(x, y - offset, cabinet_bottom_height),
+ drawer_only=True,
+ kitchen_space_bottom=num_cells,
+ )
+ cabinet_bottom = island_factory(i=0)
+ else:
+ cabinet_bottom_factory = KitchenCabinetFactory(
+ self.factory_seed,
+ dimensions=(x, other_width - offset, cabinet_bottom_height),
+ drawer_only=True,
+ )
+ cabinet_bottom_left = cabinet_bottom_factory(i=0)
+ cabinet_bottom_right = cabinet_bottom_factory(i=1)
+ cabinet_bottom_left.location = (0.0, 0.0, 0.0)
+ cabinet_bottom_right.location = (0.0, y - other_width, 0.0)
+ cabinet_bottom_mid_factory = KitchenCabinetFactory(
+ self.factory_seed,
+ dimensions=(x, mid_width - offset_bm, cabinet_bottom_height),
+ drawer_only=True,
+ kitchen_space_bottom=True,
+ )
+ bottom_mid = cabinet_bottom_mid_factory(i=0)
+ bottom_mid.location = (0.0, y - other_width - mid_width, 0.0)
+ cabinet_bottom = butil.join_objects(
+ [cabinet_bottom_left, cabinet_bottom_right, bottom_mid]
+ )
+ parts.append(cabinet_bottom)
+ surface.add_geomod(
+ cabinet_bottom, geometry_nodes_add_cabinet_top, apply=True
+ )
+
+ if getattr(self, "has_sink", False):
+ sink_factory = SinkFactory(
+ factory_seed=self.factory_seed,
+ dimensions=[
+ mid_width * 0.7,
+ min(x * 0.7, 0.4),
+ cabinet_bottom_height * 0.3,
+ ],
+ )
+ sink = sink_factory(i=0)
+ sink.location = (
+ (x / 2.0) - 0.3,
+ y / 2.0,
+ cabinet_bottom_height * 0.7 + 0.12,
+ )
+ sink.parent = cabinet_bottom
+
+ if not self.island:
+ cabinet_top_factory = KitchenCabinetFactory(
+ self.factory_seed,
+ dimensions=(x / 2.0, other_width - offset, cabinet_top_height),
+ drawer_only=False,
+ )
+ cabinet_top_left = cabinet_top_factory(i=0)
+ cabinet_top_right = cabinet_top_factory(i=1)
+ cabinet_top_left.location = (-x / 4.0, 0.0, z - cabinet_top_height)
+ cabinet_top_right.location = (
+ -x / 4.0,
+ y - other_width,
+ z - cabinet_top_height,
+ )
+ mid_style = choice(["cabinet"])
+ if mid_style == "range_hood":
+ range_hood_factory = RangeHoodFactory(
+ self.factory_seed,
+ dimensions=(
+ x * 0.66,
+ mid_width + 0.15,
+ cabinet_top_height,
+ ),
+ )
+ top_mid = range_hood_factory(i=0)
+ top_mid.location = (
+ -x * 0.5,
+ y / 2.0,
+ z - cabinet_top_height + 0.05,
+ )
+ elif mid_style == "cabinet":
+ cabinet_top_mid_factory = KitchenCabinetFactory(
+ self.factory_seed,
+ dimensions=(
+ x / 2.0,
+ mid_width - offset_tm,
+ cabinet_top_height,
+ ),
+ drawer_only=False,
+ )
+ top_mid = cabinet_top_mid_factory(i=0)
+ top_mid.location = (
+ -x / 4.0,
+ (y / 2.0) - (mid_width / 2.0),
+ z - cabinet_top_height,
+ )
+ else:
+ raise NotImplementedError
+ parts += [cabinet_top_left, cabinet_top_right, top_mid]
+
+ kitchen_space = butil.join_objects(parts)
+ if not self.island:
+ kitchen_space.dimensions = self.dimensions
+ butil.apply_transform(kitchen_space)
+ tagging.tag_system.relabel_obj(kitchen_space)
+ return kitchen_space
+
+ KitchenSpaceFactory.create_asset = patched_create_asset
+
+ def patched_island_init(self, factory_seed):
+ KitchenSpaceFactory.__init__(
+ self, factory_seed=factory_seed, island=True, has_sink=False
+ )
+
+ KitchenIslandFactory.__init__ = patched_island_init
+
+
+def patch_sink():
+ """Simplify SinkFactory.sample_parameters with fixed sampling ranges."""
+ _ensure_infinigen_on_path()
+ from infinigen.assets.objects.table_decorations.sink import SinkFactory
+ from numpy.random import uniform as U
+
+ def patched_sample_parameters(
+ dimensions, upper_height, use_default=False, open=False
+ ):
+ if not dimensions:
+ width = U(0.4, 1.0)
+ depth = U(0.4, 0.5)
+ upper_height = U(0.2, 0.4)
+ else:
+ width, depth, upper_height = dimensions
+ curvature = U(1.0, 1.0)
+ lower_height = U(0.00, 0.01)
+ hole_radius = U(0.02, 0.05)
+ margin = U(0.02, 0.05)
+ watertap_margin = U(0.1, 0.12)
+ params = {
+ "Width": width,
+ "Depth": depth,
+ "Curvature": curvature,
+ "Upper Height": upper_height,
+ "Lower Height": lower_height,
+ "HoleRadius": hole_radius,
+ "Margin": margin,
+ "WaterTapMargin": watertap_margin,
+ "ProtrudeAboveCounter": U(0.01, 0.025),
+ }
+ return params
+
+ SinkFactory.sample_parameters = staticmethod(patched_sample_parameters)
+
+
+def patch_generate_indoors():
+ """Force populate_doors to use all_open=True by default."""
+ _ensure_infinigen_on_path()
+ from infinigen.core.constraints.example_solver.room import (
+ decorate as room_dec,
+ )
+
+ _orig_populate_doors = room_dec.populate_doors
+
+ def patched_populate_doors(
+ placeholders,
+ constants,
+ n_doors=3,
+ door_chance=1,
+ casing_chance=0.0,
+ all_open=False,
+ **kwargs,
+ ):
+ return _orig_populate_doors(
+ placeholders,
+ constants,
+ n_doors=n_doors,
+ door_chance=door_chance,
+ casing_chance=casing_chance,
+ all_open=True,
+ **kwargs,
+ )
+
+ room_dec.populate_doors = patched_populate_doors
+
+
+def patch_room_types():
+ """Include Office in util.room_types."""
+ _ensure_infinigen_on_path()
+ from infinigen.core import tags as t
+ from infinigen_examples.constraints import util as cu
+
+ cu.room_types.add(t.Semantics.Office)
+
+
+def patch_home_constraints():
+ """Add office-only room constraints and desk/chair furniture rules."""
+ _ensure_infinigen_on_path()
+ from collections import OrderedDict
+
+ import gin
+ from infinigen.assets.objects import seating, shelves
+ from infinigen.core.constraints import constraint_language as cl
+ from infinigen.core.constraints.constraint_language.constants import (
+ RoomConstants,
+ )
+ from infinigen.core.tags import Semantics
+ from infinigen_examples.constraints import home as home_module
+ from infinigen_examples.constraints import util as cu
+
+ gin.enter_interactive_mode()
+ _orig_home_room_constraints = home_module.home_room_constraints
+
+ def _office_room_constraints():
+ constraints = OrderedDict()
+ score_terms = OrderedDict()
+ constants = RoomConstants(
+ fixed_contour=False, room_type={Semantics.Office}
+ )
+ rooms = cl.scene()[Semantics.RoomContour]
+ constraints["node_gen"] = rooms[Semantics.Root].all(
+ lambda r: (
+ rooms[Semantics.Office]
+ .related_to(r, cl.Traverse())
+ .count()
+ .in_range(1, 1, mean=1)
+ )
+ )
+ constraints["node"] = (
+ rooms[Semantics.Office].count().in_range(1, 1, mean=1)
+ * (rooms[Semantics.Entrance].count() >= 0)
+ * (rooms[Semantics.StaircaseRoom].count() == 0)
+ )
+ all_rooms = cl.scene()[Semantics.RoomContour]
+ rooms_filtered = all_rooms[-Semantics.Exterior][-Semantics.Staircase]
+ score_terms["room"] = (
+ rooms_filtered[Semantics.Office]
+ .sum(lambda r: (r.area() / 25).log().hinge(0, 0.4).pow(2))
+ .minimize(weight=500.0)
+ )
+ return cl.Problem(
+ constraints=constraints,
+ score_terms=score_terms,
+ constants=constants,
+ )
+
+ @gin.configurable(
+ "home_room_constraints", module="infinigen_examples.constraints.home"
+ )
+ def patched_home_room_constraints(
+ has_fewer_rooms=False, office_only=False
+ ):
+ if office_only:
+ return _office_room_constraints()
+ return _orig_home_room_constraints(has_fewer_rooms=has_fewer_rooms)
+
+ home_module.home_room_constraints = patched_home_room_constraints
+
+ # --- home_furniture_constraints: Office room (1-2 desks, 1-2 chairs each) ---
+ _orig_home_furniture_constraints = home_module.home_furniture_constraints
+
+ def patched_home_furniture_constraints():
+ problem = _orig_home_furniture_constraints()
+ constraints = OrderedDict(problem.constraints)
+ score_terms = OrderedDict(problem.score_terms)
+ rooms = cl.scene()[{Semantics.Room, -Semantics.Object}]
+ obj = cl.scene()[{Semantics.Object, -Semantics.Room}]
+ furniture = obj[Semantics.Furniture].related_to(rooms, cu.on_floor)
+ wallfurn = furniture.related_to(rooms, cu.against_wall)
+ desks = wallfurn[shelves.SimpleDeskFactory]
+ deskchair = furniture[seating.OfficeChairFactory].related_to(
+ desks, cu.front_to_front
+ )
+ offices = rooms[Semantics.Office]
+ constraints["office_desks"] = offices.all(
+ lambda r: desks.related_to(r).count().in_range(1, 2, mean=1.5)
+ )
+ constraints["office_desk_chairs"] = offices.all(
+ lambda r: desks.related_to(r).all(
+ lambda t: (
+ deskchair.related_to(r)
+ .related_to(t)
+ .count()
+ .in_range(1, 2, mean=1.5)
+ )
+ )
+ )
+ score_terms["office_desks"] = offices.mean(
+ lambda r: desks.related_to(r).mean(
+ lambda d: (
+ cl.accessibility_cost(d, furniture.related_to(r)).minimize(
+ weight=3
+ )
+ + cl.accessibility_cost(d, r).minimize(weight=3)
+ + deskchair.related_to(r)
+ .distance(rooms, cu.walltags)
+ .maximize(weight=1)
+ )
+ )
+ )
+ return cl.Problem(constraints=constraints, score_terms=score_terms)
+
+ home_module.home_furniture_constraints = patched_home_furniture_constraints
+
+
+def patch_floor_plan_solver():
+ """Guard swap_room against layouts without swap targets."""
+ _ensure_infinigen_on_path()
+ from infinigen.core.constraints.example_solver.room import (
+ solver as solver_module,
+ )
+
+ _orig_swap_room = solver_module.FloorPlanMoves.swap_room
+
+ def patched_swap_room(self, state, k):
+ candidates = [
+ r.target_name for r in state[k].relations if r.value.length > 0
+ ]
+ if not candidates:
+ raise NotImplementedError(
+ "No valid swap targets (e.g. single-room layout)"
+ )
+ return _orig_swap_room(self, state, k)
+
+ solver_module.FloorPlanMoves.swap_room = patched_swap_room
+
+
+def patch_room_graph_root():
+ """Allow single-room graphs to select a valid root without StaircaseRoom."""
+ _ensure_infinigen_on_path()
+ from infinigen.core.constraints.example_solver.room import (
+ base as base_module,
+ )
+ from infinigen.core.tags import Semantics
+
+ @property
+ def patched_root(self):
+ if self.entrance is None:
+ if self[Semantics.StaircaseRoom]:
+ return self.names[self[Semantics.StaircaseRoom][0]]
+ if self[Semantics.Root]:
+ return self.names[self[Semantics.Root][0]]
+ for i, n in enumerate(self.names):
+ if base_module.room_type(n) != Semantics.Exterior:
+ return self.names[i]
+ raise IndexError(
+ "Graph has no StaircaseRoom, Root, or interior room for root"
+ )
+ return self.names[self._entrance]
+
+ base_module.RoomGraph.root = patched_root
+
+
+def patch_removed_object_tree() -> None:
+ """Skip Blender objects whose RNA handles were removed."""
+ _ensure_infinigen_on_path()
+ from infinigen.core.util import blender as butil
+
+ def is_removed(obj) -> bool:
+ try:
+ obj.name
+ except ReferenceError:
+ return True
+ return False
+
+ def patched_iter_object_tree(obj):
+ if is_removed(obj):
+ return
+
+ yield obj
+
+ try:
+ children = list(obj.children)
+ except ReferenceError:
+ return
+
+ for child in children:
+ yield from patched_iter_object_tree(child)
+
+ butil.iter_object_tree = patched_iter_object_tree
+
+
+def _make_run_main_impl():
+ def _run_main_impl():
+ import argparse
+ from pathlib import Path
+
+ import infinigen_examples.generate_indoors as gi
+ from infinigen.core import init
+
+ parser = argparse.ArgumentParser()
+ parser.add_argument("--output_folder", type=Path)
+ parser.add_argument("--input_folder", type=Path, default=None)
+ parser.add_argument("-s", "--seed", default=None)
+ parser.add_argument(
+ "-t",
+ "--task",
+ nargs="+",
+ default=["coarse"],
+ choices=[
+ "coarse",
+ "populate",
+ "fine_terrain",
+ "ground_truth",
+ "render",
+ "mesh_save",
+ "export",
+ ],
+ )
+ parser.add_argument("-g", "--configs", nargs="+", default=["base"])
+ parser.add_argument("-p", "--overrides", nargs="+", default=[])
+ parser.add_argument("--task_uniqname", type=str, default=None)
+ parser.add_argument("-d", "--debug", type=str, nargs="*", default=None)
+
+ args = init.parse_args_blender(parser)
+
+ import logging
+
+ logging.getLogger("infinigen").setLevel(logging.INFO)
+ logging.getLogger("infinigen.core.nodes.node_wrangler").setLevel(
+ logging.CRITICAL
+ )
+ if args.debug is not None:
+ for name in logging.root.manager.loggerDict:
+ if not name.startswith("infinigen"):
+ continue
+ if len(args.debug) == 0 or any(
+ name.endswith(x) for x in args.debug
+ ):
+ logging.getLogger(name).setLevel(logging.DEBUG)
+
+ gi.main(args)
+
+ return _run_main_impl
+
+
+def add_run_main_to_module(module):
+ """Inject _run_main into generate_indoors module. Call after 'import infinigen_examples.generate_indoors as gi'."""
+ module._run_main = _make_run_main_impl()
+
+
+def patch_generate_indoors_run_main():
+ """Legacy: add _run_main if module already in sys.modules (e.g. when patch runs from generate_indoors top)."""
+ mod = sys.modules.get("infinigen_examples.generate_indoors")
+ if mod is not None:
+ add_run_main_to_module(mod)
+
+
+def monkey_patch_infinigen(
+ *,
+ material_assignments=True,
+ concrete=True,
+ room_constants=True,
+ room_types=True,
+ home_constraints=True,
+ doors=True,
+ kitchen_cabinet=True,
+ kitchen_space=True,
+ sink=True,
+ generate_indoors=True,
+ removed_object_tree=True,
+):
+ """Apply selected monkey patches to Infinigen."""
+ if material_assignments:
+ patch_material_assignments()
+ if concrete:
+ patch_concrete()
+ if room_constants:
+ patch_room_constants()
+ if room_types:
+ patch_room_types()
+ if home_constraints:
+ patch_home_constraints()
+ if doors:
+ patch_doors_base_simple()
+ if kitchen_cabinet:
+ patch_kitchen_cabinet()
+ if kitchen_space:
+ patch_kitchen_space()
+ if sink:
+ patch_sink()
+ if generate_indoors:
+ patch_generate_indoors()
+ patch_floor_plan_solver()
+ patch_room_graph_root()
+ if removed_object_tree:
+ patch_removed_object_tree()
+ patch_generate_indoors_run_main()
diff --git a/embodied_gen/utils/monkey_patch/maniskill.py b/embodied_gen/utils/monkey_patch/maniskill.py
new file mode 100644
index 0000000..bd9b276
--- /dev/null
+++ b/embodied_gen/utils/monkey_patch/maniskill.py
@@ -0,0 +1,88 @@
+# Project EmbodiedGen
+#
+# Copyright (c) 2025 Horizon Robotics. All Rights Reserved.
+#
+# 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.
+
+
+import numpy as np
+import torch
+
+
+def monkey_patch_maniskill():
+ """Monkey patches ManiSkillScene to support sensor image retrieval and RGBA rendering."""
+ from mani_skill.envs.scene import ManiSkillScene
+
+ def get_sensor_images(
+ self, obs: dict[str, any]
+ ) -> dict[str, dict[str, torch.Tensor]]:
+ """Retrieve images from all sensors based on observations."""
+ sensor_data = dict()
+ for name, sensor in self.sensors.items():
+ sensor_data[name] = sensor.get_images(obs[name])
+ return sensor_data
+
+ def get_human_render_camera_images(
+ self, camera_name: str = None, return_alpha: bool = False
+ ) -> dict[str, torch.Tensor]:
+ """Render images from human-view cameras, optionally generating alpha channel from segmentation."""
+
+ def get_rgba_tensor(camera, return_alpha):
+ color = camera.get_obs(
+ rgb=True, depth=False, segmentation=False, position=False
+ )["rgb"]
+ if return_alpha:
+ seg_labels = camera.get_obs(
+ rgb=False, depth=False, segmentation=True, position=False
+ )["segmentation"]
+ masks = np.where((seg_labels.cpu() > 1), 255, 0).astype(
+ np.uint8
+ )
+ masks = torch.tensor(masks).to(color.device)
+ color = torch.concat([color, masks], dim=-1)
+
+ return color
+
+ image_data = dict()
+ if self.gpu_sim_enabled:
+ if self.parallel_in_single_scene:
+ for name, camera in self.human_render_cameras.items():
+ camera.camera._render_cameras[0].take_picture()
+ rgba = get_rgba_tensor(camera, return_alpha)
+ image_data[name] = rgba
+ else:
+ for name, camera in self.human_render_cameras.items():
+ if camera_name is not None and name != camera_name:
+ continue
+ assert camera.config.shader_config.shader_pack not in [
+ "rt",
+ "rt-fast",
+ "rt-med",
+ ], "ray tracing shaders do not work with parallel rendering"
+ camera.capture()
+ rgba = get_rgba_tensor(camera, return_alpha)
+ image_data[name] = rgba
+ else:
+ for name, camera in self.human_render_cameras.items():
+ if camera_name is not None and name != camera_name:
+ continue
+ camera.capture()
+ rgba = get_rgba_tensor(camera, return_alpha)
+ image_data[name] = rgba
+
+ return image_data
+
+ ManiSkillScene.get_sensor_images = get_sensor_images
+ ManiSkillScene.get_human_render_camera_images = (
+ get_human_render_camera_images
+ )
diff --git a/embodied_gen/utils/monkey_patch/p3sam.py b/embodied_gen/utils/monkey_patch/p3sam.py
new file mode 100644
index 0000000..0d6d719
--- /dev/null
+++ b/embodied_gen/utils/monkey_patch/p3sam.py
@@ -0,0 +1,111 @@
+import importlib
+import os
+import sys
+
+import torch
+import torch.nn as nn
+
+
+def monkey_patch_p3sam():
+ """Patch P3-SAM model construction to use the shared Hugging Face cache."""
+ current_file_path = os.path.abspath(__file__)
+ current_dir = os.path.dirname(current_file_path)
+ hunyuan_part_root = os.path.abspath(
+ os.path.join(current_dir, "../../../thirdparty/Hunyuan3D-Part")
+ )
+ p3sam_root = os.path.join(hunyuan_part_root, "P3-SAM")
+ partgen_root = os.path.join(hunyuan_part_root, "XPart/partgen")
+
+ for path in [p3sam_root, partgen_root]:
+ if path not in sys.path:
+ sys.path.insert(0, path)
+
+ from models import sonata
+
+ p3sam_model = importlib.import_module("model")
+
+ def build_P3SAM(self):
+ self.sonata = sonata.load(
+ "sonata",
+ repo_id="facebook/sonata",
+ download_root=os.path.expanduser(
+ "~/.cache/huggingface/hub/sonata"
+ ),
+ )
+ self.mlp = nn.Sequential(
+ nn.Linear(1232, 512),
+ nn.GELU(),
+ nn.Linear(512, 512),
+ nn.GELU(),
+ nn.Linear(512, 512),
+ )
+ self.transform = sonata.transform.default()
+
+ self.seg_mlp_1 = nn.Sequential(
+ nn.Linear(512 + 3 + 3, 512),
+ nn.GELU(),
+ nn.Linear(512, 512),
+ nn.GELU(),
+ nn.Linear(512, 1),
+ )
+ self.seg_mlp_2 = nn.Sequential(
+ nn.Linear(512 + 3 + 3, 512),
+ nn.GELU(),
+ nn.Linear(512, 512),
+ nn.GELU(),
+ nn.Linear(512, 1),
+ )
+ self.seg_mlp_3 = nn.Sequential(
+ nn.Linear(512 + 3 + 3, 512),
+ nn.GELU(),
+ nn.Linear(512, 512),
+ nn.GELU(),
+ nn.Linear(512, 1),
+ )
+
+ self.seg_s2_mlp_g = nn.Sequential(
+ nn.Linear(512 + 3 + 3 + 3, 256),
+ nn.GELU(),
+ nn.Linear(256, 256),
+ nn.GELU(),
+ nn.Linear(256, 256),
+ )
+ self.seg_s2_mlp_1 = nn.Sequential(
+ nn.Linear(512 + 3 + 3 + 3 + 256, 256),
+ nn.GELU(),
+ nn.Linear(256, 256),
+ nn.GELU(),
+ nn.Linear(256, 1),
+ )
+ self.seg_s2_mlp_2 = nn.Sequential(
+ nn.Linear(512 + 3 + 3 + 3 + 256, 256),
+ nn.GELU(),
+ nn.Linear(256, 256),
+ nn.GELU(),
+ nn.Linear(256, 1),
+ )
+ self.seg_s2_mlp_3 = nn.Sequential(
+ nn.Linear(512 + 3 + 3 + 3 + 256, 256),
+ nn.GELU(),
+ nn.Linear(256, 256),
+ nn.GELU(),
+ nn.Linear(256, 1),
+ )
+
+ self.iou_mlp = nn.Sequential(
+ nn.Linear(512 + 3 + 3 + 3 + 256, 256),
+ nn.GELU(),
+ nn.Linear(256, 256),
+ nn.GELU(),
+ nn.Linear(256, 256),
+ )
+ self.iou_mlp_out = nn.Sequential(
+ nn.Linear(256, 256),
+ nn.GELU(),
+ nn.Linear(256, 256),
+ nn.GELU(),
+ nn.Linear(256, 3),
+ )
+ self.iou_criterion = torch.nn.MSELoss()
+
+ p3sam_model.build_P3SAM = build_P3SAM
diff --git a/embodied_gen/utils/monkey_patch/pano2room.py b/embodied_gen/utils/monkey_patch/pano2room.py
new file mode 100644
index 0000000..9cbec26
--- /dev/null
+++ b/embodied_gen/utils/monkey_patch/pano2room.py
@@ -0,0 +1,159 @@
+# Project EmbodiedGen
+#
+# Copyright (c) 2025 Horizon Robotics. All Rights Reserved.
+#
+# 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.
+
+import os
+import sys
+import zipfile
+
+import torch
+from huggingface_hub import hf_hub_download
+from omegaconf import OmegaConf
+from PIL import Image
+from torchvision import transforms
+
+
+def monkey_patch_pano2room():
+ """Monkey patches pano2room components with custom initializers and model paths."""
+ current_file_path = os.path.abspath(__file__)
+ current_dir = os.path.dirname(current_file_path)
+ sys.path.append(os.path.join(current_dir, "../../.."))
+ sys.path.append(os.path.join(current_dir, "../../../thirdparty/pano2room"))
+ from thirdparty.pano2room.modules.geo_predictors.omnidata.omnidata_normal_predictor import (
+ OmnidataNormalPredictor,
+ )
+ from thirdparty.pano2room.modules.geo_predictors.omnidata.omnidata_predictor import (
+ OmnidataPredictor,
+ )
+
+ def patched_omni_depth_init(self):
+ """Initialize Omnidata depth predictor with explicit model loading."""
+ self.img_size = 384
+ self.model = torch.hub.load(
+ 'alexsax/omnidata_models', 'depth_dpt_hybrid_384'
+ )
+ self.model.eval()
+ self.trans_totensor = transforms.Compose(
+ [
+ transforms.Resize(self.img_size, interpolation=Image.BILINEAR),
+ transforms.CenterCrop(self.img_size),
+ transforms.Normalize(mean=0.5, std=0.5),
+ ]
+ )
+
+ OmnidataPredictor.__init__ = patched_omni_depth_init
+
+ def patched_omni_normal_init(self):
+ """Initialize Omnidata normal predictor with explicit model loading."""
+ self.img_size = 384
+ self.model = torch.hub.load(
+ 'alexsax/omnidata_models', 'surface_normal_dpt_hybrid_384'
+ )
+ self.model.eval()
+ self.trans_totensor = transforms.Compose(
+ [
+ transforms.Resize(self.img_size, interpolation=Image.BILINEAR),
+ transforms.CenterCrop(self.img_size),
+ transforms.Normalize(mean=0.5, std=0.5),
+ ]
+ )
+
+ OmnidataNormalPredictor.__init__ = patched_omni_normal_init
+
+ def patched_panojoint_init(self, save_path=None):
+ """Initialize PanoJointPredictor using patched depth/normal predictors."""
+ self.depth_predictor = OmnidataPredictor()
+ self.normal_predictor = OmnidataNormalPredictor()
+ self.save_path = save_path
+
+ from modules.geo_predictors import PanoJointPredictor
+
+ PanoJointPredictor.__init__ = patched_panojoint_init
+
+ # NOTE: We use gsplat instead.
+ # import depth_diff_gaussian_rasterization_min as ddgr
+ # from dataclasses import dataclass
+ # @dataclass
+ # class PatchedGaussianRasterizationSettings:
+ # image_height: int
+ # image_width: int
+ # tanfovx: float
+ # tanfovy: float
+ # bg: torch.Tensor
+ # scale_modifier: float
+ # viewmatrix: torch.Tensor
+ # projmatrix: torch.Tensor
+ # sh_degree: int
+ # campos: torch.Tensor
+ # prefiltered: bool
+ # debug: bool = False
+ # ddgr.GaussianRasterizationSettings = PatchedGaussianRasterizationSettings
+
+ # disable get_has_ddp_rank print in `BaseInpaintingTrainingModule`
+ os.environ["NODE_RANK"] = "0"
+
+ from thirdparty.pano2room.modules.inpainters.lama.saicinpainting.training.trainers import (
+ load_checkpoint,
+ )
+ from thirdparty.pano2room.modules.inpainters.lama_inpainter import (
+ LamaInpainter,
+ )
+
+ def patched_lama_inpaint_init(self):
+ """Initialize LamaInpainter by downloading and setting up Big-Lama model."""
+ zip_path = hf_hub_download(
+ repo_id="smartywu/big-lama",
+ filename="big-lama.zip",
+ repo_type="model",
+ )
+ extract_dir = os.path.splitext(zip_path)[0]
+
+ if not os.path.exists(extract_dir):
+ os.makedirs(extract_dir, exist_ok=True)
+ with zipfile.ZipFile(zip_path, "r") as zip_ref:
+ zip_ref.extractall(extract_dir)
+
+ config_path = os.path.join(extract_dir, 'big-lama', 'config.yaml')
+ checkpoint_path = os.path.join(
+ extract_dir, 'big-lama/models/best.ckpt'
+ )
+ train_config = OmegaConf.load(config_path)
+ train_config.training_model.predict_only = True
+ train_config.visualizer.kind = 'noop'
+
+ self.model = load_checkpoint(
+ train_config, checkpoint_path, strict=False, map_location='cpu'
+ )
+ self.model.freeze()
+
+ LamaInpainter.__init__ = patched_lama_inpaint_init
+
+ from diffusers import StableDiffusionInpaintPipeline
+ from thirdparty.pano2room.modules.inpainters.SDFT_inpainter import (
+ SDFTInpainter,
+ )
+
+ def patched_sd_inpaint_init(self, subset_name=None):
+ """Initialize SDFTInpainter with Stable Diffusion 2 Inpainting pipeline."""
+ super(SDFTInpainter, self).__init__()
+ pipe = StableDiffusionInpaintPipeline.from_pretrained(
+ # "stabilityai/stable-diffusion-2-inpainting",
+ "sd2-community/stable-diffusion-2-inpainting",
+ torch_dtype=torch.float16,
+ ).to("cuda")
+ pipe.enable_model_cpu_offload()
+ self.inpaint_pipe = pipe
+
+ SDFTInpainter.__init__ = patched_sd_inpaint_init
diff --git a/embodied_gen/utils/monkey_patches.py b/embodied_gen/utils/monkey_patch/sam3d.py
similarity index 60%
rename from embodied_gen/utils/monkey_patches.py
rename to embodied_gen/utils/monkey_patch/sam3d.py
index 44d55c3..8f74717 100644
--- a/embodied_gen/utils/monkey_patches.py
+++ b/embodied_gen/utils/monkey_patch/sam3d.py
@@ -16,295 +16,32 @@
import os
import sys
-import zipfile
+from copy import deepcopy
+from typing import Optional, Union
import numpy as np
import torch
-from huggingface_hub import hf_hub_download
-from omegaconf import OmegaConf
from PIL import Image
-from torchvision import transforms
-
-__all__ = [
- "monkey_patch_pano2room",
- "monkey_patch_maniskill",
- "monkey_patch_sam3d",
-]
-
-
-def monkey_path_trellis():
- import torch.nn.functional as F
-
- current_file_path = os.path.abspath(__file__)
- current_dir = os.path.dirname(current_file_path)
- sys.path.append(os.path.join(current_dir, "../.."))
-
- from thirdparty.TRELLIS.trellis.representations import Gaussian
- from thirdparty.TRELLIS.trellis.representations.gaussian.general_utils import (
- build_scaling_rotation,
- inverse_sigmoid,
- strip_symmetric,
- )
-
- os.environ["TORCH_EXTENSIONS_DIR"] = os.path.expanduser(
- "~/.cache/torch_extensions"
- )
- os.environ["SPCONV_ALGO"] = "auto" # Can be 'native' or 'auto'
- os.environ['ATTN_BACKEND'] = (
- "xformers" # Can be 'flash-attn' or 'xformers'
- )
- from thirdparty.TRELLIS.trellis.modules.sparse import set_attn
-
- set_attn("xformers")
-
- def patched_setup_functions(self):
- def inverse_softplus(x):
- return x + torch.log(-torch.expm1(-x))
-
- def build_covariance_from_scaling_rotation(
- scaling, scaling_modifier, rotation
- ):
- L = build_scaling_rotation(scaling_modifier * scaling, rotation)
- actual_covariance = L @ L.transpose(1, 2)
- symm = strip_symmetric(actual_covariance)
- return symm
-
- if self.scaling_activation_type == "exp":
- self.scaling_activation = torch.exp
- self.inverse_scaling_activation = torch.log
- elif self.scaling_activation_type == "softplus":
- self.scaling_activation = F.softplus
- self.inverse_scaling_activation = inverse_softplus
-
- self.covariance_activation = build_covariance_from_scaling_rotation
- self.opacity_activation = torch.sigmoid
- self.inverse_opacity_activation = inverse_sigmoid
- self.rotation_activation = F.normalize
-
- self.scale_bias = self.inverse_scaling_activation(
- torch.tensor(self.scaling_bias)
- ).to(self.device)
- self.rots_bias = torch.zeros((4)).to(self.device)
- self.rots_bias[0] = 1
- self.opacity_bias = self.inverse_opacity_activation(
- torch.tensor(self.opacity_bias)
- ).to(self.device)
-
- Gaussian.setup_functions = patched_setup_functions
-
-
-def monkey_patch_pano2room():
- current_file_path = os.path.abspath(__file__)
- current_dir = os.path.dirname(current_file_path)
- sys.path.append(os.path.join(current_dir, "../.."))
- sys.path.append(os.path.join(current_dir, "../../thirdparty/pano2room"))
- from thirdparty.pano2room.modules.geo_predictors.omnidata.omnidata_normal_predictor import (
- OmnidataNormalPredictor,
- )
- from thirdparty.pano2room.modules.geo_predictors.omnidata.omnidata_predictor import (
- OmnidataPredictor,
- )
-
- def patched_omni_depth_init(self):
- self.img_size = 384
- self.model = torch.hub.load(
- 'alexsax/omnidata_models', 'depth_dpt_hybrid_384'
- )
- self.model.eval()
- self.trans_totensor = transforms.Compose(
- [
- transforms.Resize(self.img_size, interpolation=Image.BILINEAR),
- transforms.CenterCrop(self.img_size),
- transforms.Normalize(mean=0.5, std=0.5),
- ]
- )
-
- OmnidataPredictor.__init__ = patched_omni_depth_init
-
- def patched_omni_normal_init(self):
- self.img_size = 384
- self.model = torch.hub.load(
- 'alexsax/omnidata_models', 'surface_normal_dpt_hybrid_384'
- )
- self.model.eval()
- self.trans_totensor = transforms.Compose(
- [
- transforms.Resize(self.img_size, interpolation=Image.BILINEAR),
- transforms.CenterCrop(self.img_size),
- transforms.Normalize(mean=0.5, std=0.5),
- ]
- )
-
- OmnidataNormalPredictor.__init__ = patched_omni_normal_init
-
- def patched_panojoint_init(self, save_path=None):
- self.depth_predictor = OmnidataPredictor()
- self.normal_predictor = OmnidataNormalPredictor()
- self.save_path = save_path
-
- from modules.geo_predictors import PanoJointPredictor
-
- PanoJointPredictor.__init__ = patched_panojoint_init
-
- # NOTE: We use gsplat instead.
- # import depth_diff_gaussian_rasterization_min as ddgr
- # from dataclasses import dataclass
- # @dataclass
- # class PatchedGaussianRasterizationSettings:
- # image_height: int
- # image_width: int
- # tanfovx: float
- # tanfovy: float
- # bg: torch.Tensor
- # scale_modifier: float
- # viewmatrix: torch.Tensor
- # projmatrix: torch.Tensor
- # sh_degree: int
- # campos: torch.Tensor
- # prefiltered: bool
- # debug: bool = False
- # ddgr.GaussianRasterizationSettings = PatchedGaussianRasterizationSettings
-
- # disable get_has_ddp_rank print in `BaseInpaintingTrainingModule`
- os.environ["NODE_RANK"] = "0"
-
- from thirdparty.pano2room.modules.inpainters.lama.saicinpainting.training.trainers import (
- load_checkpoint,
- )
- from thirdparty.pano2room.modules.inpainters.lama_inpainter import (
- LamaInpainter,
- )
-
- def patched_lama_inpaint_init(self):
- zip_path = hf_hub_download(
- repo_id="smartywu/big-lama",
- filename="big-lama.zip",
- repo_type="model",
- )
- extract_dir = os.path.splitext(zip_path)[0]
-
- if not os.path.exists(extract_dir):
- os.makedirs(extract_dir, exist_ok=True)
- with zipfile.ZipFile(zip_path, "r") as zip_ref:
- zip_ref.extractall(extract_dir)
-
- config_path = os.path.join(extract_dir, 'big-lama', 'config.yaml')
- checkpoint_path = os.path.join(
- extract_dir, 'big-lama/models/best.ckpt'
- )
- train_config = OmegaConf.load(config_path)
- train_config.training_model.predict_only = True
- train_config.visualizer.kind = 'noop'
-
- self.model = load_checkpoint(
- train_config, checkpoint_path, strict=False, map_location='cpu'
- )
- self.model.freeze()
-
- LamaInpainter.__init__ = patched_lama_inpaint_init
-
- from diffusers import StableDiffusionInpaintPipeline
- from thirdparty.pano2room.modules.inpainters.SDFT_inpainter import (
- SDFTInpainter,
- )
-
- def patched_sd_inpaint_init(self, subset_name=None):
- super(SDFTInpainter, self).__init__()
- pipe = StableDiffusionInpaintPipeline.from_pretrained(
- # "stabilityai/stable-diffusion-2-inpainting",
- "sd2-community/stable-diffusion-2-inpainting",
- torch_dtype=torch.float16,
- ).to("cuda")
- pipe.enable_model_cpu_offload()
- self.inpaint_pipe = pipe
-
- SDFTInpainter.__init__ = patched_sd_inpaint_init
-
-
-def monkey_patch_maniskill():
- from mani_skill.envs.scene import ManiSkillScene
-
- def get_sensor_images(
- self, obs: dict[str, any]
- ) -> dict[str, dict[str, torch.Tensor]]:
- sensor_data = dict()
- for name, sensor in self.sensors.items():
- sensor_data[name] = sensor.get_images(obs[name])
- return sensor_data
-
- def get_human_render_camera_images(
- self, camera_name: str = None, return_alpha: bool = False
- ) -> dict[str, torch.Tensor]:
- def get_rgba_tensor(camera, return_alpha):
- color = camera.get_obs(
- rgb=True, depth=False, segmentation=False, position=False
- )["rgb"]
- if return_alpha:
- seg_labels = camera.get_obs(
- rgb=False, depth=False, segmentation=True, position=False
- )["segmentation"]
- masks = np.where((seg_labels.cpu() > 1), 255, 0).astype(
- np.uint8
- )
- masks = torch.tensor(masks).to(color.device)
- color = torch.concat([color, masks], dim=-1)
-
- return color
-
- image_data = dict()
- if self.gpu_sim_enabled:
- if self.parallel_in_single_scene:
- for name, camera in self.human_render_cameras.items():
- camera.camera._render_cameras[0].take_picture()
- rgba = get_rgba_tensor(camera, return_alpha)
- image_data[name] = rgba
- else:
- for name, camera in self.human_render_cameras.items():
- if camera_name is not None and name != camera_name:
- continue
- assert camera.config.shader_config.shader_pack not in [
- "rt",
- "rt-fast",
- "rt-med",
- ], "ray tracing shaders do not work with parallel rendering"
- camera.capture()
- rgba = get_rgba_tensor(camera, return_alpha)
- image_data[name] = rgba
- else:
- for name, camera in self.human_render_cameras.items():
- if camera_name is not None and name != camera_name:
- continue
- camera.capture()
- rgba = get_rgba_tensor(camera, return_alpha)
- image_data[name] = rgba
-
- return image_data
-
- ManiSkillScene.get_sensor_images = get_sensor_images
- ManiSkillScene.get_human_render_camera_images = (
- get_human_render_camera_images
- )
def monkey_patch_sam3d():
- from typing import Optional, Union
-
+ """Monkey patches SAM3D inference pipelines with custom initialization and execution logic."""
from embodied_gen.data.utils import model_device_ctx
from embodied_gen.utils.log import logger
os.environ["LIDRA_SKIP_INIT"] = "true"
+ os.environ['ATTN_BACKEND'] = "xformers"
current_file_path = os.path.abspath(__file__)
current_dir = os.path.dirname(current_file_path)
sam3d_root = os.path.abspath(
- os.path.join(current_dir, "../../thirdparty/sam3d")
+ os.path.join(current_dir, "../../../thirdparty/sam3d")
)
if sam3d_root not in sys.path:
sys.path.insert(0, sam3d_root)
def patch_pointmap_infer_pipeline():
- from copy import deepcopy
-
+ """Patches InferencePipelinePointMap.run to handle pointmap generation and 3D structure sampling."""
try:
from sam3d_objects.pipeline.inference_pipeline_pointmap import (
InferencePipelinePointMap,
@@ -333,6 +70,7 @@ def patch_run(
decode_formats=None,
estimate_plane=False,
) -> dict:
+ """Execute the inference pipeline: process image/mask, generate layouts (SS), and decode 3D shapes (SLAT)."""
image = self.merge_image_and_mask(image, mask)
with self.device:
pointmap_dict = self.compute_pointmap(image, pointmap)
@@ -465,6 +203,7 @@ def patch_run(
InferencePipelinePointMap.run = patch_run
def patch_infer_init():
+ """Patches InferencePipeline.__init__ to allow CPU offloading during model initialization."""
import torch
try:
@@ -525,6 +264,7 @@ def patch_init(
slat_mean=SLAT_MEAN,
slat_std=SLAT_STD,
):
+ """Initialize pipeline components on CPU first to save GPU memory, then move necessary parts later."""
self.rendering_engine = rendering_engine
self.device = torch.device(device)
self.compile_model = compile_model
@@ -640,7 +380,7 @@ def patch_init(
InferencePipeline.__init__ = patch_init
- patch_pointmap_infer_pipeline()
- patch_infer_init()
+ patch_pointmap_infer_pipeline() # patch
+ patch_infer_init() # patch
return
diff --git a/embodied_gen/utils/monkey_patch/trellis.py b/embodied_gen/utils/monkey_patch/trellis.py
new file mode 100644
index 0000000..73fc803
--- /dev/null
+++ b/embodied_gen/utils/monkey_patch/trellis.py
@@ -0,0 +1,83 @@
+# Project EmbodiedGen
+#
+# Copyright (c) 2025 Horizon Robotics. All Rights Reserved.
+#
+# 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.
+
+import os
+import sys
+
+import torch
+import torch.nn.functional as F
+
+
+def monkey_path_trellis():
+ """Monkey patches TRELLIS with specific environment settings and Gaussian setup functions."""
+ current_file_path = os.path.abspath(__file__)
+ current_dir = os.path.dirname(current_file_path)
+ sys.path.append(os.path.join(current_dir, "../../.."))
+
+ from thirdparty.TRELLIS.trellis.representations import Gaussian
+ from thirdparty.TRELLIS.trellis.representations.gaussian.general_utils import (
+ build_scaling_rotation,
+ inverse_sigmoid,
+ strip_symmetric,
+ )
+
+ os.environ["TORCH_EXTENSIONS_DIR"] = os.path.expanduser(
+ "~/.cache/torch_extensions"
+ )
+ os.environ["SPCONV_ALGO"] = "native" # Can be 'native' or 'auto'
+ os.environ['ATTN_BACKEND'] = (
+ "xformers" # Can be 'flash-attn' or 'xformers'
+ )
+ from thirdparty.TRELLIS.trellis.modules.sparse import set_attn
+
+ set_attn("xformers")
+
+ def patched_setup_functions(self):
+ """Configure activation functions and biases for Gaussian representation."""
+
+ def inverse_softplus(x):
+ return x + torch.log(-torch.expm1(-x))
+
+ def build_covariance_from_scaling_rotation(
+ scaling, scaling_modifier, rotation
+ ):
+ L = build_scaling_rotation(scaling_modifier * scaling, rotation)
+ actual_covariance = L @ L.transpose(1, 2)
+ symm = strip_symmetric(actual_covariance)
+ return symm
+
+ if self.scaling_activation_type == "exp":
+ self.scaling_activation = torch.exp
+ self.inverse_scaling_activation = torch.log
+ elif self.scaling_activation_type == "softplus":
+ self.scaling_activation = F.softplus
+ self.inverse_scaling_activation = inverse_softplus
+
+ self.covariance_activation = build_covariance_from_scaling_rotation
+ self.opacity_activation = torch.sigmoid
+ self.inverse_opacity_activation = inverse_sigmoid
+ self.rotation_activation = F.normalize
+
+ self.scale_bias = self.inverse_scaling_activation(
+ torch.tensor(self.scaling_bias)
+ ).to(self.device)
+ self.rots_bias = torch.zeros((4)).to(self.device)
+ self.rots_bias[0] = 1
+ self.opacity_bias = self.inverse_opacity_activation(
+ torch.tensor(self.opacity_bias)
+ ).to(self.device)
+
+ Gaussian.setup_functions = patched_setup_functions
diff --git a/embodied_gen/utils/process_media.py b/embodied_gen/utils/process_media.py
index 3a68ca1..d4bf011 100644
--- a/embodied_gen/utils/process_media.py
+++ b/embodied_gen/utils/process_media.py
@@ -61,6 +61,7 @@ def render_asset3d(
num_images: int = 1,
elevation: list[float] = (0.0,),
pbr_light_factor: float = 1.2,
+ pbr_metallic: bool = False,
return_key: str = "image_color/*",
output_subdir: str = "renders",
gen_color_mp4: bool = False,
@@ -78,6 +79,7 @@ def render_asset3d(
num_images (int, optional): Number of views to render.
elevation (list[float], optional): Camera elevation angles.
pbr_light_factor (float, optional): PBR lighting factor.
+ pbr_metallic (bool, optional): Keep metallic material properties for PBR rendering.
return_key (str, optional): Glob pattern for output images.
output_subdir (str, optional): Subdirectory for outputs.
gen_color_mp4 (bool, optional): Generate color MP4 video.
@@ -111,6 +113,7 @@ def render_asset3d(
num_images=num_images,
elevation=elevation,
pbr_light_factor=pbr_light_factor,
+ pbr_metallic=pbr_metallic,
with_mtl=with_mtl,
gen_color_mp4=gen_color_mp4,
gen_viewnormal_mp4=gen_viewnormal_mp4,
diff --git a/embodied_gen/utils/simulation.py b/embodied_gen/utils/simulation.py
index 7f828fb..369f4d2 100644
--- a/embodied_gen/utils/simulation.py
+++ b/embodied_gen/utils/simulation.py
@@ -55,10 +55,19 @@
__all__ = [
"SIM_COORD_ALIGN",
"FrankaPandaGrasper",
+ "capture_frame",
+ "create_panda_agent",
+ "create_recording_camera",
+ "estimate_grasp_width",
+ "get_actor_bottom_z",
+ "get_actor_mesh",
+ "is_urdf_articulated",
"load_assets_from_layout_file",
+ "load_collision_mesh_from_urdf",
"load_mani_skill_robot",
+ "quat_from_yaw",
"render_images",
- "is_urdf_articulated",
+ "set_ground_base_color",
]
@@ -722,6 +731,8 @@ def move_to_pose(
result[action_key] = result[action_key][::sample_ratio]
n_step = len(result[action_key])
+ if n_step == 0:
+ return None
actions = []
for i in range(n_step):
qpos = result[action_key][i]
@@ -805,10 +816,14 @@ def compute_grasp_action(
gripper_state=1,
env_idx=env_idx,
)
+ if grasp_actions is None:
+ logger.warning(
+ f"Failed to move from reach pose to grasp pose for `{actor.name}`."
+ )
+ return None
actions.append(grasp_actions)
close_actions = self.control_gripper(
gripper_state=-1,
- env_idx=env_idx,
)
actions.append(close_actions)
back_actions = self.move_to_pose(
@@ -817,6 +832,181 @@ def compute_grasp_action(
gripper_state=-1,
env_idx=env_idx,
)
+ if back_actions is None:
+ logger.warning(
+ f"Failed to retreat after grasping `{actor.name}`."
+ )
+ return None
actions.append(back_actions)
return np.concatenate(actions, axis=0)
+
+
+def load_collision_mesh_from_urdf(urdf_path: str) -> trimesh.Trimesh:
+ """Load the collision mesh referenced by a URDF in its link frame.
+
+ Applies the optional collision/origin transform so the returned mesh sits
+ in the same frame the simulator will use; required for correct spawn-z
+ estimation downstream.
+ """
+ root = ET.parse(urdf_path).getroot()
+ collision_mesh = root.find(".//collision/geometry/mesh")
+ if collision_mesh is None:
+ raise ValueError(f"Collision mesh not found in URDF: {urdf_path}")
+
+ collision_file = collision_mesh.get("filename")
+ if not collision_file:
+ raise ValueError(f"Collision mesh filename missing in {urdf_path}")
+
+ scale_attr = collision_mesh.get("scale", "1.0 1.0 1.0")
+ mesh_scale = np.array([float(x) for x in scale_attr.split()])
+ mesh_path = os.path.join(os.path.dirname(urdf_path), collision_file)
+ mesh = trimesh.load(mesh_path)
+ if isinstance(mesh, trimesh.Scene):
+ mesh = mesh.dump(concatenate=True)
+ mesh.apply_scale(mesh_scale)
+
+ collision_origin = root.find(".//collision/origin")
+ if collision_origin is not None:
+ xyz = [float(v) for v in collision_origin.get("xyz", "0 0 0").split()]
+ rpy = [float(v) for v in collision_origin.get("rpy", "0 0 0").split()]
+ transform = np.eye(4, dtype=np.float64)
+ transform[:3, :3] = R.from_euler("xyz", rpy, degrees=False).as_matrix()
+ transform[:3, 3] = np.array(xyz, dtype=np.float64)
+ mesh.apply_transform(transform)
+
+ return mesh
+
+
+def estimate_grasp_width(mesh: trimesh.Trimesh) -> float:
+ """Estimate a conservative top-down grasp width from OBB extents."""
+ extents = np.sort(mesh.bounding_box_oriented.extents)
+ return float(extents[1])
+
+
+def get_actor_mesh(actor: sapien.Entity) -> trimesh.Trimesh:
+ """Get the actor collision mesh in world coordinates."""
+ physx_rigid = actor.components[1]
+ mesh = get_component_mesh(physx_rigid, to_world_frame=True)
+ if mesh is None or mesh.is_empty:
+ raise ValueError(f"Actor `{actor.name}` has no valid collision mesh.")
+
+ return mesh
+
+
+def get_actor_bottom_z(actor: sapien.Entity) -> float:
+ """Get the actor world-space bottom z from its collision mesh."""
+ return float(get_actor_mesh(actor).bounds[0, 2])
+
+
+def quat_from_yaw(yaw_deg: float) -> list[float]:
+ """Convert z-axis yaw angle (degrees) to a SAPIEN quaternion (w,x,y,z)."""
+ yaw = np.deg2rad(yaw_deg)
+ return [float(np.cos(yaw / 2)), 0.0, 0.0, float(np.sin(yaw / 2))]
+
+
+def set_ground_base_color(scene: sapien.Scene, rgba: list[float]) -> None:
+ """Update the default ground plane material color for this scene."""
+ for actor in scene.get_all_actors():
+ if actor.name != "ground":
+ continue
+ for component in actor.components:
+ render_shapes = getattr(component, "render_shapes", None)
+ if render_shapes is None:
+ continue
+ for render_shape in render_shapes:
+ render_shape.material.set_base_color(rgba)
+ return
+
+ raise ValueError("Ground actor not found in the scene.")
+
+
+def capture_frame(
+ scene: sapien.Scene,
+ camera: sapien.render.RenderCameraComponent,
+) -> np.ndarray:
+ """Capture a single RGB frame from the camera (updates render first)."""
+ scene.update_render()
+ camera.take_picture()
+ return np.array(render_images(camera, ["Color"])["Color"])
+
+
+def create_recording_camera(
+ scene_manager: "SapienSceneManager",
+ eye_pos: list[float],
+ target_pt: list[float],
+ image_hw: tuple[int, int],
+ fovy_deg: float = 45.0,
+ cam_name: str = "recording_camera",
+) -> sapien.render.RenderCameraComponent:
+ """Create a camera looking from eye_pos at target_pt for video capture."""
+ eye_pos = np.array(eye_pos, dtype=np.float32)
+ target_pt = np.array(target_pt, dtype=np.float32)
+ world_up_vec = np.array([0.0, 0.0, 1.0], dtype=np.float32)
+ forward_vec = target_pt - eye_pos
+ forward_vec = forward_vec / np.linalg.norm(forward_vec)
+ temp_right_vec = np.cross(forward_vec, world_up_vec)
+ if np.linalg.norm(temp_right_vec) < 1e-6:
+ temp_right_vec = np.array([1.0, 0.0, 0.0], dtype=np.float32)
+ right_vec = temp_right_vec / np.linalg.norm(temp_right_vec)
+ up_vec = np.cross(right_vec, forward_vec)
+ rotation_matrix = np.array([forward_vec, -right_vec, up_vec]).T
+ scipy_quat = R.from_matrix(rotation_matrix).as_quat()
+ quat = [
+ float(scipy_quat[3]),
+ float(scipy_quat[0]),
+ float(scipy_quat[1]),
+ float(scipy_quat[2]),
+ ]
+
+ return scene_manager.create_camera(
+ cam_name,
+ pose=sapien.Pose(p=eye_pos.tolist(), q=quat),
+ image_hw=image_hw,
+ fovy_deg=fovy_deg,
+ )
+
+
+def create_panda_agent(
+ scene: sapien.Scene,
+ control_freq: int,
+ sim_backend: str,
+ render_backend: str,
+ initial_qpos: np.ndarray | None = None,
+ control_mode: str = "pd_joint_pos",
+) -> BaseAgent:
+ """Create a ManiSkill Panda agent attached to a SAPIEN scene."""
+ from mani_skill.agents import REGISTERED_AGENTS
+ from mani_skill.envs.utils.system.backend import (
+ parse_sim_and_render_backend,
+ )
+
+ backend = parse_sim_and_render_backend(sim_backend, render_backend)
+ ms_scene = ManiSkillScene([scene], device=sim_backend, backend=backend)
+ robot_cls = REGISTERED_AGENTS["panda"].agent_cls
+ agent = robot_cls(
+ scene=ms_scene,
+ control_freq=control_freq,
+ control_mode=control_mode,
+ initial_pose=sapien.Pose([0, 0, 0], [1, 0, 0, 0]),
+ )
+ if initial_qpos is None:
+ initial_qpos = np.array(
+ [
+ 0.0,
+ np.pi / 8,
+ 0.0,
+ -np.pi * 3 / 8,
+ 0.0,
+ np.pi * 3 / 4,
+ np.pi / 4,
+ 0.04,
+ 0.04,
+ ],
+ dtype=np.float32,
+ )
+ agent.reset(initial_qpos[None, ...].copy())
+ agent.init_qpos = agent.robot.qpos
+ agent.controller.controllers["gripper"].reset()
+
+ return agent
diff --git a/embodied_gen/utils/tags.py b/embodied_gen/utils/tags.py
index b03d010..d20b058 100644
--- a/embodied_gen/utils/tags.py
+++ b/embodied_gen/utils/tags.py
@@ -1 +1 @@
-VERSION = "v0.1.7"
+VERSION = "v2.0.0"
diff --git a/embodied_gen/utils/trender.py b/embodied_gen/utils/trender.py
index f2a845f..17e5bc4 100644
--- a/embodied_gen/utils/trender.py
+++ b/embodied_gen/utils/trender.py
@@ -19,7 +19,6 @@
from collections import defaultdict
import numpy as np
-import spaces
import torch
from easydict import EasyDict as edict
from tqdm import tqdm
@@ -43,7 +42,6 @@
]
-@spaces.GPU
def render_mesh_frames(sample, extrinsics, intrinsics, options={}, **kwargs):
renderer = MeshRenderer()
renderer.rendering_options.resolution = options.get("resolution", 512)
@@ -66,7 +64,6 @@ def render_mesh_frames(sample, extrinsics, intrinsics, options={}, **kwargs):
return rets
-@spaces.GPU
def render_gs_frames(
sample,
extrinsics,
@@ -117,7 +114,6 @@ def to_numpy(tensor):
return dict(outputs)
-@spaces.GPU
def render_video(
sample,
resolution=512,
@@ -149,7 +145,6 @@ def render_video(
return result
-@spaces.GPU
def pack_state(gs: Gaussian, mesh: MeshExtractResult) -> dict:
return {
"gaussian": {
diff --git a/embodied_gen/utils/vis_utils.py b/embodied_gen/utils/vis_utils.py
new file mode 100644
index 0000000..5625b61
--- /dev/null
+++ b/embodied_gen/utils/vis_utils.py
@@ -0,0 +1,614 @@
+import os
+import sys
+import textwrap
+
+import imageio
+import numpy as np
+import trimesh
+from PIL import Image, ImageDraw, ImageFont
+from embodied_gen.utils.log import logger
+
+__all__ = [
+ "PALETTE",
+ "collect_colors",
+ "render_grid",
+ "visualize_partsemantics",
+ "show_grasps",
+]
+
+PALETTE = [
+ (0, [230, 25, 75], "Red"),
+ (1, [60, 180, 75], "Green"),
+ (2, [0, 130, 200], "Blue"),
+ (3, [255, 225, 25], "Yellow"),
+ (4, [245, 130, 48], "Orange"),
+ (5, [145, 30, 180], "Purple"),
+ (6, [70, 240, 240], "Cyan"),
+ (7, [240, 50, 230], "Magenta"),
+ (8, [250, 190, 212], "Pink"),
+ (9, [210, 245, 60], "Lime Green"),
+ (10, [0, 128, 128], "Teal"),
+ (11, [170, 110, 40], "Brown"),
+ (12, [128, 0, 0], "Maroon"),
+ (13, [0, 0, 128], "Navy"),
+ (14, [107, 142, 35], "Olive"),
+ (15, [128, 128, 128], "Gray"),
+ (16, [220, 20, 60], "Crimson"),
+ (17, [255, 255, 255], "White"),
+ (18, [204, 85, 0], "Burnt Orange"),
+ (19, [0, 153, 143], "Jade"),
+ (20, [128, 0, 128], "Violet"),
+ (21, [255, 215, 0], "Gold"),
+ (22, [0, 191, 255], "Deep Sky Blue"),
+ (23, [255, 105, 180], "Hot Pink"),
+ (24, [124, 252, 0], "Lawn Green"),
+ (25, [75, 0, 130], "Indigo"),
+ (26, [255, 140, 0], "Dark Orange"),
+ (27, [46, 139, 87], "Sea Green"),
+ (28, [220, 220, 220], "Gainsboro"),
+ (29, [139, 0, 0], "Dark Red"),
+ (30, [0, 100, 0], "Dark Green"),
+ (31, [25, 25, 112], "Midnight Blue"),
+ (32, [255, 20, 147], "Deep Pink"),
+ (33, [154, 205, 50], "Yellow Green"),
+ (34, [72, 61, 139], "Dark Slate Blue"),
+ (35, [255, 160, 122], "Light Salmon"),
+ (36, [32, 178, 170], "Light Sea Green"),
+ (37, [218, 112, 214], "Orchid"),
+ (38, [176, 196, 222], "Light Steel Blue"),
+ (39, [189, 183, 107], "Dark Khaki"),
+ (40, [255, 99, 71], "Tomato"),
+ (41, [64, 224, 208], "Turquoise"),
+ (42, [148, 0, 211], "Dark Violet"),
+ (43, [240, 128, 128], "Light Coral"),
+ (44, [60, 179, 113], "Medium Sea Green"),
+ (45, [123, 104, 238], "Medium Slate Blue"),
+ (46, [255, 228, 181], "Moccasin"),
+ (47, [199, 21, 133], "Medium Violet Red"),
+ (48, [112, 128, 144], "Slate Gray"),
+ (49, [127, 255, 212], "Aquamarine"),
+ (50, [255, 69, 0], "Orange Red"),
+ (51, [95, 158, 160], "Cadet Blue"),
+ (52, [173, 255, 47], "Green Yellow"),
+ (53, [186, 85, 211], "Medium Orchid"),
+ (54, [205, 92, 92], "Indian Red"),
+ (55, [0, 206, 209], "Dark Turquoise"),
+ (56, [255, 182, 193], "Light Pink"),
+ (57, [85, 107, 47], "Dark Olive Green"),
+ (58, [30, 144, 255], "Dodger Blue"),
+ (59, [210, 105, 30], "Chocolate"),
+]
+
+
+def collect_colors(face_ids: np.ndarray) -> str:
+ active_ids = {
+ int(part_id)
+ for part_id in np.unique(np.asarray(face_ids))
+ if int(part_id) >= 0
+ }
+ ordered_colors = [
+ color_name
+ for palette_id, _, color_name in PALETTE
+ if palette_id in active_ids
+ ]
+ return ", ".join(ordered_colors)
+
+
+def show_grasps(
+ mesh: trimesh.Trimesh,
+ grasps: np.ndarray,
+ confidences: np.ndarray,
+ gripper_name: str,
+ model_name: str,
+ *,
+ grasp_center_points: np.ndarray | None = None,
+ visualizer=None,
+ visualizer_port: int = 8080,
+ gripper_info=None,
+ gripper_mesh: trimesh.Trimesh | None = None,
+):
+ if model_name == "graspgen":
+ sys.path.append("thirdparty/GraspGen")
+ from grasp_gen.utils.viser_utils import (
+ create_visualizer,
+ get_color_from_score,
+ visualize_grasp,
+ visualize_mesh,
+ visualize_pointcloud,
+ )
+ elif model_name == "graspgenx":
+ sys.path.append("thirdparty/GraspGenX")
+ from graspgenx.utils.viser_utils import (
+ create_visualizer,
+ get_color_from_score,
+ visualize_mesh,
+ visualize_pointcloud,
+ )
+ from graspgenx.utils.viser_utils import (
+ visualize_x_grasp as visualize_grasp,
+ )
+
+ best_idx = int(confidences.argmax())
+
+ if visualizer is None:
+ visualizer = create_visualizer(port=visualizer_port)
+ else:
+ visualizer.scene.reset()
+
+ transform = np.eye(4, dtype=np.float64)
+
+ if grasp_center_points is not None:
+ grasp_point_colors = np.zeros(
+ (len(grasp_center_points), 3), dtype=np.uint8
+ )
+ visualize_pointcloud(
+ visualizer,
+ "grasp_center_points",
+ grasp_center_points,
+ grasp_point_colors,
+ transform=transform,
+ size=0.0025,
+ )
+
+ visualize_mesh(
+ visualizer,
+ "object_mesh",
+ mesh,
+ color=[169, 169, 169],
+ transform=transform,
+ )
+
+ colors = get_color_from_score(confidences, use_255_scale=True)
+ for idx, grasp in enumerate(grasps):
+ color = [0, 100, 255] if idx == best_idx else colors[idx]
+ lw = 5.0 if idx == best_idx else 3.0
+ if model_name == "graspgen":
+ visualize_grasp(
+ visualizer,
+ f"grasps/grasp_{idx:03d}",
+ grasp,
+ color=color,
+ gripper_name=gripper_name,
+ linewidth=lw,
+ )
+ if model_name == "graspgenx":
+ visualize_grasp(
+ visualizer,
+ f"grasps/grasp_{idx:03d}",
+ grasp,
+ color=color,
+ gripper_info=gripper_info,
+ linewidth=lw,
+ )
+
+ if gripper_mesh is not None:
+ visualize_mesh(
+ visualizer,
+ "top_grasp_mesh",
+ gripper_mesh,
+ color=[0, 100, 255],
+ transform=grasps[best_idx],
+ )
+
+ logger.info(
+ "Grasp visualization running at http://localhost:{}".format(
+ visualizer_port,
+ )
+ )
+ return visualizer
+
+
+def render_grid(
+ mesh_path: str,
+ output_dir: str,
+ output_subdir: str = "renders",
+ *,
+ num_images: int = 6,
+ grid_rows: int = 2,
+ grid_cols: int = 3,
+ view_size: int = 512,
+) -> tuple[str, list[str]]:
+ from embodied_gen.utils.process_media import (
+ combine_images_to_grid,
+ render_asset3d,
+ )
+
+ image_paths = render_asset3d(
+ mesh_path,
+ output_root=output_dir,
+ distance=5.0,
+ num_images=num_images,
+ elevation=(45.0, -45.0),
+ output_subdir=output_subdir,
+ pbr_light_factor=1.5,
+ pbr_metallic=False,
+ no_index_file=True,
+ )
+ image_paths = sorted(image_paths)
+ if len(image_paths) != num_images:
+ raise ValueError(
+ "grid rendering expected "
+ f"{num_images} images, got {len(image_paths)}."
+ )
+
+ grid = combine_images_to_grid(
+ image_paths,
+ cat_row_col=(grid_rows, grid_cols),
+ target_wh=(view_size, view_size),
+ image_mode="RGB",
+ )[0]
+ grid_path = os.path.join(output_dir, output_subdir, "affordance_grid.png")
+ grid.save(grid_path)
+ return grid_path, image_paths
+
+
+def _build_fixed_camera_projection(
+ vertices: np.ndarray, image_size: int
+) -> tuple[np.ndarray, np.ndarray]:
+ center = (vertices.min(axis=0) + vertices.max(axis=0)) / 2.0
+ extent = float(np.linalg.norm(vertices.max(axis=0) - vertices.min(axis=0)))
+ if extent <= 0:
+ extent = 1.0
+
+ view_dir = np.asarray([1.0, 1.0, 0.0], dtype=np.float64)
+ view_dir /= np.linalg.norm(view_dir)
+ eye = center + view_dir * extent * 1.8
+ forward = center - eye
+ forward /= np.linalg.norm(forward)
+ up = np.asarray([0.0, 0.0, 1.0], dtype=np.float64)
+ right = np.cross(forward, up)
+ if np.linalg.norm(right) < 1e-6:
+ right = np.asarray([1.0, 0.0, 0.0], dtype=np.float64)
+ right /= np.linalg.norm(right)
+ up = np.cross(right, forward)
+ up /= np.linalg.norm(up)
+ right, up = -up, right
+
+ relative_vertices = vertices - eye
+ camera_points = np.stack(
+ [
+ relative_vertices @ right,
+ relative_vertices @ up,
+ relative_vertices @ forward,
+ ],
+ axis=1,
+ )
+ depth = np.maximum(camera_points[:, 2], 1e-6)
+ focal = image_size * 1.35
+ projected = np.empty((len(vertices), 2), dtype=np.float64)
+ projected[:, 0] = image_size * 0.5 + focal * camera_points[:, 0] / depth
+ projected[:, 1] = image_size * 0.5 - focal * camera_points[:, 1] / depth
+
+ xy_min = projected.min(axis=0)
+ xy_max = projected.max(axis=0)
+ projected_center = (xy_min + xy_max) / 2.0
+ projected_extent = float(np.max(xy_max - xy_min))
+ if projected_extent > 1e-6:
+ scale = image_size * 0.82 / projected_extent
+ projected = (projected - projected_center) * scale + image_size * 0.5
+ return projected, camera_points[:, 2]
+
+
+def _build_affordance_text_lines(affordance: dict) -> list[str]:
+ title = f"{affordance.get('part_name', 'unknown part')} id={affordance.get('id')}"
+ lines = [
+ title,
+ f"mask color: {affordance.get('mask_color')}",
+ f"graspable: {affordance.get('graspable')}",
+ ]
+
+ scenarios = affordance.get("grasp_scenarios") or []
+ if scenarios:
+ lines.append("grasp scenarios:")
+ for item in scenarios[:3]:
+ scenario = item.get("scenario", "")
+ confidence = item.get("confidence")
+ lines.append(f"- {scenario} ({confidence})")
+
+ labels = affordance.get("functional_labels") or []
+ if labels:
+ lines.append("functions:")
+ lines.extend([f"- {label}" for label in labels[:6]])
+
+ semantic = affordance.get("semantic_description")
+ if semantic:
+ lines.append("semantic:")
+ lines.extend(textwrap.wrap(str(semantic), width=42))
+ return lines
+
+
+def _load_visualization_fonts(
+ image_size: int,
+) -> tuple[ImageFont.ImageFont, ImageFont.ImageFont]:
+ scale = max(1.0, image_size / 512)
+ title_font_paths = [
+ "/usr/share/fonts/truetype/dejavu/DejaVuSans-Bold.ttf",
+ "/usr/share/fonts/truetype/freefont/FreeSansBold.ttf",
+ "/usr/share/fonts/opentype/noto/NotoSerifCJK-Bold.ttc",
+ ]
+ body_font_paths = [
+ "/usr/share/fonts/truetype/dejavu/DejaVuSans.ttf",
+ "/usr/share/fonts/truetype/freefont/FreeSans.ttf",
+ "/usr/share/fonts/truetype/noto/NotoSansMono-Regular.ttf",
+ ]
+
+ def _load_font(font_paths: list[str], size: int) -> ImageFont.ImageFont:
+ for font_path in font_paths:
+ if os.path.exists(font_path):
+ return ImageFont.truetype(font_path, size=size)
+ return ImageFont.load_default()
+
+ return (
+ _load_font(title_font_paths, int(24 * scale)),
+ _load_font(body_font_paths, int(18 * scale)),
+ )
+
+
+def _draw_affordance_text_panel(
+ draw: ImageDraw.ImageDraw,
+ panel_x: int,
+ panel_width: int,
+ height: int,
+ lines: list[str],
+ visible_chars: int,
+) -> None:
+ title_font, body_font = _load_visualization_fonts(height)
+ scale = max(1.0, height / 512)
+ left_margin = int(24 * scale)
+ title_line_height = int(30 * scale)
+ body_line_height = int(24 * scale)
+ draw.rectangle(
+ [panel_x, 0, panel_x + panel_width, height], fill=(248, 248, 240)
+ )
+ draw.rectangle(
+ [panel_x, 0, panel_x + int(1 * scale), height], fill=(0, 105, 160)
+ )
+ y = int(28 * scale)
+ remaining_chars = visible_chars
+ for idx, line in enumerate(lines):
+ font = title_font if idx == 0 else body_font
+ line_height = title_line_height if idx == 0 else body_line_height
+ wrap_width = 30 if idx == 0 else 38
+ wrapped_lines = textwrap.wrap(str(line), width=wrap_width) or [""]
+ for wrapped in wrapped_lines:
+ if remaining_chars <= 0:
+ return
+ visible_text = wrapped[:remaining_chars]
+ fill = (28, 31, 34) if idx != 0 else (0, 82, 125)
+ draw.text(
+ (panel_x + left_margin, y), visible_text, fill=fill, font=font
+ )
+ remaining_chars -= len(wrapped)
+ y += line_height
+ y += int(10 * scale) if idx == 0 else int(7 * scale)
+
+
+def _draw_affordance_frame(
+ mesh: trimesh.Trimesh,
+ face_part_ids: np.ndarray,
+ face_colors: np.ndarray,
+ screen_vertices: np.ndarray,
+ vertex_depths: np.ndarray,
+ affordance: dict,
+ blink_alpha: int,
+ text_lines: list[str],
+ visible_chars: int,
+ image_size: int,
+ text_panel_width: int,
+) -> Image.Image:
+ canvas = Image.new(
+ "RGBA",
+ (image_size + text_panel_width, image_size),
+ (255, 255, 255, 255),
+ )
+ mesh_layer = Image.new(
+ "RGBA", (image_size, image_size), (255, 255, 255, 0)
+ )
+ base_draw = ImageDraw.Draw(mesh_layer, "RGBA")
+ highlight_layer = Image.new(
+ "RGBA", (image_size, image_size), (255, 255, 255, 0)
+ )
+ highlight_draw = ImageDraw.Draw(highlight_layer, "RGBA")
+
+ faces = np.asarray(mesh.faces)
+ face_depths = vertex_depths[faces].mean(axis=1)
+ draw_order = np.argsort(face_depths)[::-1]
+ target_part_id = affordance.get("id")
+ highlight_rgb = tuple(
+ next(
+ (
+ color
+ for palette_id, color, _ in PALETTE
+ if palette_id == target_part_id
+ ),
+ [255, 40, 40],
+ )
+ )
+ bright_highlight_rgb = tuple(
+ min(255, int(channel * 1.35 + 35)) for channel in highlight_rgb
+ )
+
+ for face_idx in draw_order:
+ polygon = [
+ tuple(screen_vertices[vertex_idx])
+ for vertex_idx in faces[face_idx]
+ ]
+ if face_part_ids[face_idx] == target_part_id:
+ fill = (*bright_highlight_rgb, blink_alpha)
+ outline = (255, 255, 255, 255)
+ highlight_draw.polygon(polygon, fill=fill, outline=outline)
+ else:
+ base_rgb = tuple(
+ max(0, int(channel * 0.58))
+ for channel in face_colors[face_idx][:3]
+ )
+ base_draw.polygon(
+ polygon,
+ fill=(*base_rgb, 48),
+ outline=(*base_rgb, 12),
+ )
+
+ canvas.alpha_composite(mesh_layer, (0, 0))
+ canvas.alpha_composite(highlight_layer, (0, 0))
+ draw = ImageDraw.Draw(canvas)
+ _draw_affordance_text_panel(
+ draw,
+ image_size,
+ text_panel_width,
+ image_size,
+ text_lines,
+ visible_chars,
+ )
+ return canvas.convert("RGB")
+
+
+def _get_face_base_colors(mesh: trimesh.Trimesh) -> np.ndarray:
+ fallback = np.full((len(mesh.faces), 3), 170, dtype=np.uint8)
+ visual = getattr(mesh, "visual", None)
+ if visual is None:
+ return fallback
+
+ face_colors = getattr(visual, "face_colors", None)
+ if face_colors is not None and len(face_colors) == len(mesh.faces):
+ return np.asarray(face_colors, dtype=np.uint8)[:, :3]
+
+ vertex_colors = getattr(visual, "vertex_colors", None)
+ if vertex_colors is not None and len(vertex_colors) == len(mesh.vertices):
+ vertex_colors = np.asarray(vertex_colors, dtype=np.float32)[:, :3]
+ return (
+ vertex_colors[np.asarray(mesh.faces)].mean(axis=1).astype(np.uint8)
+ )
+
+ material = getattr(visual, "material", None)
+ texture = getattr(material, "baseColorTexture", None)
+ if texture is None:
+ texture = getattr(material, "image", None)
+ uv = getattr(visual, "uv", None)
+ if (
+ texture is not None
+ and uv is not None
+ and len(uv) == len(mesh.vertices)
+ ):
+ texture_arr = np.asarray(texture.convert("RGB"), dtype=np.uint8)
+ height, width = texture_arr.shape[:2]
+ face_uv = np.asarray(uv, dtype=np.float64)[
+ np.asarray(mesh.faces)
+ ].mean(axis=1)
+ u = np.mod(face_uv[:, 0], 1.0)
+ v = np.mod(face_uv[:, 1], 1.0)
+ x = np.clip((u * (width - 1)).round().astype(np.int64), 0, width - 1)
+ y = np.clip(
+ ((1.0 - v) * (height - 1)).round().astype(np.int64), 0, height - 1
+ )
+ return texture_arr[y, x, :3]
+
+ diffuse = getattr(material, "diffuse", None)
+ if diffuse is not None and len(diffuse) >= 3:
+ color = np.asarray(diffuse[:3], dtype=np.uint8)
+ return np.tile(color, (len(mesh.faces), 1))
+
+ base_color = getattr(material, "baseColorFactor", None)
+ if base_color is not None and len(base_color) >= 3:
+ color = np.asarray(base_color[:3], dtype=np.uint8)
+ return np.tile(color, (len(mesh.faces), 1))
+
+ return fallback
+
+
+def visualize_partsemantics(
+ mesh_path: str,
+ seg_mesh_path: str,
+ affordance_annot_path: str,
+ *,
+ fps: int = 12,
+ frames_per_part: int = 36,
+ view_size: int = 1080,
+ output_path: str | None = None,
+) -> str | None:
+
+ from embodied_gen.utils.io_utils import load_json
+
+ affordance_payload = load_json(affordance_annot_path)
+ affordances = [
+ item
+ for item in affordance_payload.get("affordances", [])
+ if isinstance(item, dict) and item.get("id") is not None
+ ]
+ if not affordances:
+ logger.warning(
+ f"No affordance entries to visualize: {affordance_annot_path}"
+ )
+ return None
+
+ mesh = trimesh.load(mesh_path, force="mesh", process=False)
+ if isinstance(mesh, trimesh.Scene):
+ mesh = trimesh.util.concatenate(tuple(mesh.geometry.values()))
+ if not isinstance(mesh, trimesh.Trimesh):
+ raise ValueError(
+ f"Expected mesh at {mesh_path}, got {type(mesh).__name__}"
+ )
+
+ face_colors = _get_face_base_colors(mesh)
+ seg_mesh = trimesh.load(seg_mesh_path, force="mesh", process=False)
+ face_part_ids = np.asarray(seg_mesh.metadata["face_ids"], dtype=np.int64)
+ if len(face_part_ids) != len(mesh.faces):
+ raise ValueError(
+ "face_ids length must match mesh faces, got "
+ f"{len(face_part_ids)} and {len(mesh.faces)}"
+ )
+
+ palette_ids = {palette_id for palette_id, _, _ in PALETTE}
+ part_ids = np.unique(face_part_ids[face_part_ids >= 0])
+ if any(int(part_id) not in palette_ids for part_id in part_ids):
+ if len(part_ids) > len(PALETTE):
+ raise ValueError(
+ "number of unique part ids "
+ f"{len(part_ids)} exceeds color palette size {len(PALETTE)}"
+ )
+ palette_part_ids = np.full(face_part_ids.shape, -1, dtype=np.int64)
+ for palette_idx, part_id in enumerate(part_ids):
+ palette_part_ids[face_part_ids == part_id] = PALETTE[palette_idx][
+ 0
+ ]
+ face_part_ids = palette_part_ids
+
+ image_size = view_size
+ text_panel_width = int(view_size * 0.75)
+ screen_vertices, vertex_depths = _build_fixed_camera_projection(
+ np.asarray(mesh.vertices, dtype=np.float64),
+ image_size,
+ )
+
+ frames = []
+ frames_per_affordance = max(1, frames_per_part)
+ for affordance in affordances:
+ text_lines = _build_affordance_text_lines(affordance)
+ total_chars = sum(len(line) for line in text_lines)
+ for frame_idx in range(frames_per_affordance):
+ phase = frame_idx / max(1, frames_per_affordance - 1)
+ blink_strength = 1.0 - abs(2.0 * phase - 1.0)
+ blink_alpha = int(130 + 125 * blink_strength)
+ visible_chars = int(total_chars * min(1.0, phase * 1.25))
+ frame = _draw_affordance_frame(
+ mesh,
+ face_part_ids,
+ face_colors,
+ screen_vertices,
+ vertex_depths,
+ affordance,
+ blink_alpha,
+ text_lines,
+ visible_chars,
+ image_size,
+ text_panel_width,
+ )
+ frames.append(np.asarray(frame))
+
+ if output_path is None:
+ output_path = os.path.join(
+ os.path.dirname(affordance_annot_path),
+ "affordance_annot_vis.mp4",
+ )
+ imageio.mimsave(output_path, frames, fps=fps)
+ logger.info(f"Affordance visualization saved to {output_path}")
+ return output_path
diff --git a/embodied_gen/validators/quality_checkers.py b/embodied_gen/validators/quality_checkers.py
index 3cec795..dd90daf 100644
--- a/embodied_gen/validators/quality_checkers.py
+++ b/embodied_gen/validators/quality_checkers.py
@@ -30,6 +30,8 @@
__all__ = [
"MeshGeoChecker",
"ImageSegChecker",
+ "PartSemanticsChecker",
+ "PartSegChecker",
"ImageAestheticChecker",
"SemanticConsistChecker",
"TextGenAlignChecker",
@@ -231,6 +233,163 @@ def query(self, image_paths: list[str]) -> str:
)
+class PartSemanticsChecker(BaseChecker):
+ """An affordance annotation quality checker using GPT-based reasoning.
+
+ This checker validates whether an affordance annotation result is
+ consistent with the RGB multi-view grid and the aligned colored part-mask
+ grid.
+
+ Attributes:
+ gpt_client (GPTclient): GPT client used for multi-modal analysis.
+ prompt (str): Prompt used to evaluate affordance annotations.
+ verbose (bool): Whether to enable verbose logging.
+ """
+
+ def __init__(
+ self,
+ gpt_client: GPTclient,
+ prompt: str = None,
+ verbose: bool = False,
+ ) -> None:
+ super().__init__(prompt, verbose)
+ self.gpt_client = gpt_client
+ if self.prompt is None:
+ self.prompt = """
+ Task: Check whether the candidate affordance annotation is correct.
+
+ You will receive:
+ - Image 1: an RGB multi-view grid of the object.
+ - Image 2: part mask multi-view grid aligned with Image 1. Different colors indicate different segmented parts.
+ - Candidate affordance annotation JSON/text.
+
+ Evaluate every annotated part against both images. The annotation is acceptable only when all important fields are reasonable.
+
+ For each part, check:
+ - part_name: it must accurately describe the corresponding physical part shown in the RGB image and selected by the mask. It should be specific enough to distinguish that part from other parts.
+ - graspable: the true/false label must be realistic for robotic gripper use, considering whether the part is a useful place to hold, pick up, stabilize, pull, press, or manipulate the object.
+ - grasp_scenarios: each scenario must be concrete and plausible for the part. Confidence values must be consistent with how likely or useful that grasp is; high confidence should be reserved for common, robust grasps, while marginal or delicate grasps should have low confidence.
+ - functional_labels: labels must describe real functions, roles, or possible uses of that physical part. They should not be generic, unrelated, or copied from another part.
+ - semantic_description: it must match the RGB appearance of the same part, including visible color, material, surface finish, texture, shape, size, position, and markings when these can be inferred. The visible color means the real color in Image 1, not the segmentation mask color in Image 2.
+
+ Be strict about clear mismatches in part identity, mask color, graspability, function, or visual description. Accept minor uncertainty only when the images are genuinely ambiguous or the annotation is still broadly correct.
+
+ Output Rules:
+ - If the annotation is acceptable, respond exactly with: YES
+ - If the annotation is not acceptable, respond with one valid JSON object only:
+ {"status": "NO", "reason": "brief reason, max 30 words", "modified_response": }
+ - The modified_response must preserve the required annotation schema and fix the identified issue.
+ - In a failure response, do not include the word YES anywhere.
+ - Do not use Markdown, code fences, bullets, or bold text outside the JSON object.
+ """
+
+ def query(
+ self,
+ result: str,
+ grid_rgb_path: str | Image.Image,
+ grid_mask_path: str | Image.Image,
+ ) -> str:
+ text_prompt = (
+ f"{self.prompt}\n\n"
+ "Candidate affordance annotation result:\n"
+ f"{result}"
+ )
+ return self.gpt_client.query(
+ text_prompt=text_prompt,
+ image_base64=[grid_rgb_path, grid_mask_path],
+ params={"max_tokens": 10240},
+ )
+
+ def __call__(self, *args, **kwargs) -> tuple[bool, str]:
+ response = self.query(*args, **kwargs)
+ if self.verbose:
+ logger.info(response)
+
+ if response is None:
+ return None, (
+ "Error when calling GPT api, check config in "
+ "`embodied_gen/utils/gpt_config.yaml` or net connection."
+ )
+
+ response = response.strip()
+ if response == "YES":
+ return True, "YES"
+ return False, response
+
+
+class PartSegChecker(BaseChecker):
+ """A part segmentation quality checker using GPT-based reasoning.
+
+ This checker validates whether a colored part-mask multi-view grid
+ correctly segments the object parts shown in an aligned RGB multi-view grid.
+ """
+
+ def __init__(
+ self,
+ gpt_client: GPTclient,
+ prompt: str = None,
+ verbose: bool = False,
+ ) -> None:
+ super().__init__(prompt, verbose)
+ self.gpt_client = gpt_client
+ if self.prompt is None:
+ self.prompt = """
+ Task: Check whether a functional part segmentation result is accurate and actionable.
+
+ You will receive:
+ - Object category: {category}
+ - Mask color list: {color_names}
+ - Image 1: an RGB multi-view grid of the object.
+ - Image 2: part mask multi-view grid aligned with Image 1. Different colors indicate different segmented parts.
+
+ The goal is to judge whether Image 2 correctly segments the object into functional physical parts. A functional part is a region that has a distinct physical role or affordance for the object category, such as a handle, lid, wheel, leg, blade, screen, button, base, stem, cap, seat, backrest, or main body.
+
+ Criteria:
+ - Use Image 1 to understand the real object shape and functional parts.
+ - Use Image 2 and the mask color list to identify which segmented regions exist. Only refer to colors from the provided color list.
+ - Meaningful functional parts should be separated when their boundary is visually clear.
+ - For simple single-piece categories with few mechanical parts, accept stable fine-grained regions that follow visible geometry or usable surfaces, even if they share the same broad function.
+ - A single functional part should not appear as many small fragments or several colors unless those pieces are genuinely separate repeated parts.
+ - Different functional parts should not be merged into one color when they have clear boundaries and different roles.
+ - Ignore tiny boundary noise, minor holes, occluded-view ambiguity, and reasonable simplification for smooth objects with few natural parts.
+
+ When the result is too fragmented, give modification advice that can be applied by code. The only supported modification is merging multiple mask colors into one functional part:
+ - merge colors that belong to the same functional part.
+ - merge small noisy fragments into the neighboring major color only when they are not meaningful functional parts.
+ - For simple single-piece objects, prefer keeping coherent fine regions separate unless the colors are clearly accidental fragments of the same visible surface.
+ - keep repeated symmetric parts separate only if the downstream task needs individual instances; otherwise they may share one color when they have the same function.
+ - Do not suggest any operation other than color merging.
+
+ Output Rules:
+ - If the part segmentation is accurate enough, respond exactly with: YES
+ - If the part segmentation is not accurate enough, respond with exactly one line starting with "NO: " followed by valid compact JSON.
+ - The JSON must follow this schema:
+ {{"summary":"brief reason","merge_groups":[{{"colors":["ColorName"],"target_part":"functional part name","reason":"why these colors should be merged"}}]}}
+ - Each merge group must contain at least two colors from the provided mask color list.
+ - Only include merge groups that are clearly supported by the images.
+ - If the segmentation has problems that cannot be fixed by merging colors, still output the closest useful merge groups if any; otherwise use an empty merge_groups list and explain the unsupported issue in summary.
+ - Keep the JSON concise, with at most 3 merge groups and reasons under 20 words each.
+ - In a failure response, do not include the word YES anywhere.
+ - Do not use Markdown, code fences, bullets, or bold text.
+ """
+
+ def query(
+ self,
+ category: str,
+ color_names: str,
+ grid_rgb_path: str | Image.Image,
+ grid_mask_path: str | Image.Image,
+ ) -> str:
+ text_prompt = self.prompt.format(
+ category=category, color_names=color_names
+ )
+ return self.gpt_client.query(
+ text_prompt=text_prompt,
+ image_base64=[grid_rgb_path, grid_mask_path],
+ params={"max_tokens": 10240},
+ )
+
+
class ImageAestheticChecker(BaseChecker):
"""Evaluates the aesthetic quality of images using a CLIP-based predictor.
diff --git a/embodied_gen/validators/urdf_convertor.py b/embodied_gen/validators/urdf_convertor.py
index 6c770f6..2830d7c 100644
--- a/embodied_gen/validators/urdf_convertor.py
+++ b/embodied_gen/validators/urdf_convertor.py
@@ -28,7 +28,6 @@
from embodied_gen.data.convex_decomposer import decompose_convex_mesh
from embodied_gen.utils.gpt_clients import GPT_CLIENT, GPTclient
from embodied_gen.utils.process_media import (
- combine_images_to_grid,
render_asset3d,
)
from embodied_gen.utils.tags import VERSION
@@ -75,7 +74,7 @@
0.0
0.0
"-1"
- ""
+
@@ -400,7 +399,7 @@ def get_attr_from_urdf(
mesh_attr = scale_element.text
try:
mesh_attr = float(mesh_attr)
- except ValueError as e:
+ except ValueError:
pass
return mesh_attr
diff --git a/install.sh b/install.sh
index ffb9460..9011b7e 100644
--- a/install.sh
+++ b/install.sh
@@ -1,31 +1,40 @@
#!/bin/bash
set -e
-STAGE=$1 # "basic" | "extra" | "all"
-STAGE=${STAGE:-all}
+STAGE=$1 # "basic" | "scene3d" | "room" | "affordance" | "cu126" | "all"
+STAGE=${STAGE:-basic}
-SCRIPT_DIR=$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)
-
-source "$SCRIPT_DIR/install/_utils.sh"
+REPO_ROOT=$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)
+source "$REPO_ROOT/install/_utils.sh"
git config --global http.postBuffer 524288000
log_info "===== Starting installation stage: $STAGE ====="
if [[ "$STAGE" == "basic" || "$STAGE" == "all" ]]; then
- bash "$SCRIPT_DIR/install/install_basic.sh"
+ bash "$REPO_ROOT/install/install_basic.sh"
fi
-if [[ "$STAGE" == "extra" || "$STAGE" == "all" ]]; then
- # Patch submodule .gitignore to ignore __pycache__, if submodule exists
- PANO2ROOM_PATH="$SCRIPT_DIR/thirdparty/pano2room"
+if [[ "$STAGE" == "scene3d" || "$STAGE" == "all" ]]; then
+ PANO2ROOM_PATH="$REPO_ROOT/thirdparty/pano2room"
if [ -d "$PANO2ROOM_PATH" ]; then
echo "__pycache__/" > "$PANO2ROOM_PATH/.gitignore"
log_info "Added .gitignore to ignore __pycache__ in $PANO2ROOM_PATH"
fi
+ bash "$REPO_ROOT/install/install_scene3d.sh"
+fi
- bash "$SCRIPT_DIR/install/install_extra.sh"
+if [[ "$STAGE" == "room" || "$STAGE" == "all" ]]; then
+ bash "$REPO_ROOT/install/install_room.sh"
fi
-pip install triton==2.1.0 numpy==1.26.4
+if [[ "$STAGE" == "affordance" || "$STAGE" == "all" ]]; then
+ bash "$REPO_ROOT/install/install_affordance.sh"
+fi
+
+if [[ "$STAGE" == "cu126" ]]; then
+ bash "$REPO_ROOT/install/install_cu126.sh"
+fi
+# Global constraints for all stages
+pip install numpy==1.26.4
log_info "===== Installation completed successfully. ====="
diff --git a/install/init_submodules.sh b/install/init_submodules.sh
new file mode 100644
index 0000000..00fe5ce
--- /dev/null
+++ b/install/init_submodules.sh
@@ -0,0 +1,81 @@
+#!/bin/bash
+set -e
+
+REPO_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
+
+echo "Repo root: $REPO_ROOT"
+
+echo "Initializing submodules..."
+HUNYUAN_PART_PATH="thirdparty/Hunyuan3D-Part"
+
+while IFS= read -r path; do
+ if [[ "$path" == "$HUNYUAN_PART_PATH" && "${ASSET3D_HUNYUAN_FULL:-0}" != "1" ]]; then
+ full_path="$REPO_ROOT/$path"
+ commit="$(git -C "$REPO_ROOT" rev-parse "HEAD:$path")"
+ url="$(git -C "$REPO_ROOT" config -f .gitmodules --get "submodule.$path.url")"
+ branch="$(git -C "$REPO_ROOT" config -f .gitmodules --get "submodule.$path.branch" || true)"
+
+ echo "Initializing $path with sparse checkout (skipping GLB assets)..."
+ git -C "$REPO_ROOT" submodule init "$path"
+ if [[ ! -d "$full_path/.git" && ! -f "$full_path/.git" ]]; then
+ rm -rf "$full_path"
+ git clone --filter=blob:none --depth 1 --no-checkout --branch "${branch:-main}" "$url" "$full_path"
+ fi
+ git -C "$full_path" sparse-checkout init --no-cone
+ git -C "$full_path" sparse-checkout set --no-cone '/*' '!*.glb'
+ if ! git -C "$full_path" checkout --progress "$commit"; then
+ git -C "$full_path" fetch --depth 1 origin "$commit"
+ git -C "$full_path" checkout --progress "$commit"
+ fi
+ git -C "$REPO_ROOT" submodule absorbgitdirs "$path"
+ continue
+ fi
+
+ if [[ "$path" == "$HUNYUAN_PART_PATH" && -e "$REPO_ROOT/$path/.git" ]]; then
+ git -C "$REPO_ROOT/$path" sparse-checkout disable || true
+ fi
+
+ if [[ "$path" == "thirdparty/infinigen" ]]; then
+ INFI_PATH="$REPO_ROOT/$path"
+ echo "Initializing $path..."
+ git -C "$REPO_ROOT" submodule update --init --progress "$path"
+
+ echo "Filtering .gitmodules in $INFI_PATH..."
+ git -C "$INFI_PATH" config -f .gitmodules --get-regexp '^submodule\..*\.path$' \
+ | while read -r key value; do
+ name=$(echo "$key" | sed 's/^submodule\.//;s/\.path$//')
+ if [[ "$name" != "infinigen/infinigen_gpl" \
+ && "$name" != "infinigen/OcMesher" \
+ && "$name" != "infinigen/datagen/customgt/dependencies/glm" ]]; then
+ git -C "$INFI_PATH" config -f .gitmodules --remove-section "submodule.$name" || true
+ fi
+ done
+
+ echo "Recursively initializing infinigen_gpl..."
+ git -C "$INFI_PATH" submodule update --init --recursive --progress infinigen/infinigen_gpl
+
+ echo "Recursively initializing OcMesher..."
+ git -C "$INFI_PATH" submodule update --init --recursive --progress infinigen/OcMesher
+
+ echo "Recursively initializing glm..."
+ git -C "$INFI_PATH" submodule update --init --recursive --progress infinigen/datagen/customgt/dependencies/glm
+
+ echo "Applying patches to infinigen..."
+ sed -i.bak \
+ "s|'infinigen_examples|'thirdparty/infinigen/infinigen_examples|g" \
+ "$INFI_PATH/infinigen_examples/configs_indoor/base_indoors.gin"
+ rm -f "$INFI_PATH/infinigen_examples/configs_indoor/base_indoors.gin.bak"
+
+ echo "Updating scikit-image constraint..."
+ sed -i.bak \
+ 's|"scikit-image<0.20.0",|"scikit-image",|g' \
+ "$INFI_PATH/pyproject.toml"
+ rm -f "$INFI_PATH/pyproject.toml.bak"
+
+ continue
+ fi
+
+ echo "Recursively initializing $path..."
+ git -C "$REPO_ROOT" submodule update --init --recursive --progress "$path"
+
+done < <(git -C "$REPO_ROOT" config -f .gitmodules --get-regexp '^submodule\..*\.path$' | awk '{ print $2 }')
diff --git a/install/install_affordance.sh b/install/install_affordance.sh
new file mode 100644
index 0000000..49d2012
--- /dev/null
+++ b/install/install_affordance.sh
@@ -0,0 +1,53 @@
+#!/bin/bash
+set -e
+SCRIPT_DIR=$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)
+PROJECT_ROOT=$(cd "$SCRIPT_DIR/.." && pwd)
+source "$SCRIPT_DIR/_utils.sh"
+
+PIP_INSTALL_PACKAGES=(
+ "fpsample==1.0.2"
+ "viser==1.0.30"
+ "pickle5==0.0.11"
+ "webdataset==1.0.2"
+ "yourdfpy==0.0.56"
+ "sharedarray==3.2.4"
+ "meshcat==0.3.2"
+)
+
+if [[ -n "${CONDA_PREFIX:-}" ]]; then
+ if [[ -f "$CONDA_PREFIX/etc/conda/activate.d/cuda126.sh" ]]; then
+ source "$CONDA_PREFIX/etc/conda/activate.d/cuda126.sh"
+ fi
+fi
+
+export MAX_JOBS="${MAX_JOBS:-8}"
+log_info "Using TORCH_CUDA_ARCH_LIST=${TORCH_CUDA_ARCH_LIST:-unset} for affordance CUDA extensions."
+
+try_install "Installing torch-scatter==2.1.2 from source..." \
+ "FORCE_CUDA=1 pip install --no-cache-dir --no-build-isolation --no-binary torch-scatter --force-reinstall --no-deps torch-scatter==2.1.2" \
+ "torch-scatter source installation failed."
+
+try_install "Installing flash-attn==2.8.2 from source..." \
+ "FLASH_ATTENTION_FORCE_BUILD=TRUE pip install --no-cache-dir --no-build-isolation --no-binary flash-attn --force-reinstall --no-deps flash-attn==2.8.2" \
+ "flash-attn source installation failed."
+
+for pkg in "${PIP_INSTALL_PACKAGES[@]}"; do
+ try_install "Installing $pkg..." \
+ "pip install ${pkg}" \
+ "$pkg installation failed."
+done
+
+POINTNET2_OPS_DIR="$PROJECT_ROOT/thirdparty/GraspGen/pointnet2_ops"
+
+if [[ ! -d "$POINTNET2_OPS_DIR" ]]; then
+ log_error "GraspGen pointnet2_ops directory not found: $POINTNET2_OPS_DIR"
+ exit 1
+fi
+
+try_install "Installing GraspGen pointnet2_ops..." \
+ "pip install --no-build-isolation $POINTNET2_OPS_DIR" \
+ "GraspGen pointnet2_ops installation failed."
+
+rm -r "$POINTNET2_OPS_DIR/build" 2>/dev/null || true
+
+pip install opencv-python==4.13.0.92
diff --git a/install/install_agent_plugin.sh b/install/install_agent_plugin.sh
new file mode 100755
index 0000000..7f4f315
--- /dev/null
+++ b/install/install_agent_plugin.sh
@@ -0,0 +1,48 @@
+#!/usr/bin/env bash
+
+set -euo pipefail
+
+REPO_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
+PLUGIN_ROOT="$REPO_ROOT/embodied_gen/skills/claude_plugin"
+PLUGIN_PACKAGE_ROOT="$PLUGIN_ROOT/plugins/embodiedgen"
+CLAUDE_BIN="claude"
+MARKETPLACE_NAME="embodiedgen-local"
+PLUGIN_NAME="embodiedgen"
+
+if [[ "${1:-}" != "" ]]; then
+ echo "Usage: bash install/install_agent_plugin.sh" >&2
+ exit 1
+fi
+
+if [[ ! -f "$PLUGIN_PACKAGE_ROOT/.claude-plugin/plugin.json" ]]; then
+ echo "Plugin manifest not found: $PLUGIN_PACKAGE_ROOT/.claude-plugin/plugin.json" >&2
+ exit 1
+fi
+
+if [[ ! -f "$PLUGIN_ROOT/.claude-plugin/marketplace.json" ]]; then
+ echo "Marketplace manifest not found: $PLUGIN_ROOT/.claude-plugin/marketplace.json" >&2
+ exit 1
+fi
+
+if ! command -v "$CLAUDE_BIN" >/dev/null 2>&1; then
+ echo "Claude CLI not found in PATH: $CLAUDE_BIN" >&2
+ exit 1
+fi
+
+echo "Adding local Claude marketplace from:"
+echo " $PLUGIN_ROOT"
+echo
+if "$CLAUDE_BIN" plugin marketplace list | grep -q "$MARKETPLACE_NAME"; then
+ echo "Marketplace already exists, updating:"
+ echo " $CLAUDE_BIN plugin marketplace update \"$MARKETPLACE_NAME\""
+ "$CLAUDE_BIN" plugin marketplace update "$MARKETPLACE_NAME"
+else
+ echo "Running:"
+ echo " $CLAUDE_BIN plugin marketplace add \"$PLUGIN_ROOT\" --scope local"
+ "$CLAUDE_BIN" plugin marketplace add "$PLUGIN_ROOT" --scope local
+fi
+
+echo
+echo "Installing plugin:"
+echo " $CLAUDE_BIN plugin install \"$PLUGIN_NAME@$MARKETPLACE_NAME\" --scope local"
+"$CLAUDE_BIN" plugin install "$PLUGIN_NAME@$MARKETPLACE_NAME" --scope local
diff --git a/install/install_basic.sh b/install/install_basic.sh
index ccbf861..4a54cb9 100644
--- a/install/install_basic.sh
+++ b/install/install_basic.sh
@@ -5,34 +5,32 @@ source "$SCRIPT_DIR/_utils.sh"
PIP_INSTALL_PACKAGES=(
"pip==22.3.1"
- "torch==2.4.0 torchvision==0.19.0 --index-url https://download.pytorch.org/whl/cu118"
- "xformers==0.0.27.post2 --index-url https://download.pytorch.org/whl/cu118"
+ "setuptools==80.10.2 wheel packaging 'Cython>=0.29.37'"
+ "torch==2.8.0 torchvision==0.23.0 --index-url https://download.pytorch.org/whl/cu126"
+ "xformers==0.0.32.post2 --index-url https://download.pytorch.org/whl/cu126"
"-r requirements.txt --use-deprecated=legacy-resolver"
- # "flash-attn==2.7.0.post2"
"utils3d@git+https://github.com/EasternJournalist/utils3d.git@9a4eb15"
"clip@git+https://github.com/openai/CLIP.git"
"segment-anything@git+https://github.com/facebookresearch/segment-anything.git@dca509f"
"nvdiffrast@git+https://github.com/NVlabs/nvdiffrast.git@729261d"
"kolors@git+https://github.com/HochCC/Kolors.git"
- "kaolin@git+https://github.com/NVIDIAGameWorks/kaolin.git@v0.16.0"
- "git+https://github.com/nerfstudio-project/gsplat.git@v1.5.3"
- "git+https://github.com/facebookresearch/pytorch3d.git@stable"
+ "--no-build-isolation kaolin@git+https://github.com/NVIDIAGameWorks/kaolin.git@v0.18.0"
+ "--no-build-isolation git+https://github.com/nerfstudio-project/gsplat.git@v1.5.3"
+ "--no-build-isolation git+https://github.com/facebookresearch/pytorch3d.git@stable"
"MoGe@git+https://github.com/microsoft/MoGe.git@a8c3734"
)
for pkg in "${PIP_INSTALL_PACKAGES[@]}"; do
try_install "Installing $pkg..." \
- "pip install $pkg" \
+ "pip install ${pkg}" \
"$pkg installation failed."
done
log_info "Installing diff-gaussian-rasterization..."
-TMP_DIR="/tmp/mip-splatting"
-rm -rf "$TMP_DIR"
-git clone --recursive https://github.com/autonomousvision/mip-splatting.git "$TMP_DIR"
-pip install "$TMP_DIR/submodules/diff-gaussian-rasterization"
-rm -rf "$TMP_DIR"
+pip install --no-build-isolation diff-gaussian-rasterization@git+https://github.com/autonomousvision/mip-splatting.git#subdirectory=submodules/diff-gaussian-rasterization
try_install "Installing EmbodiedGen..." \
"pip install -e .[dev]" \
"EmbodiedGen installation failed."
+
+pre-commit install
\ No newline at end of file
diff --git a/install/install_cu126.sh b/install/install_cu126.sh
new file mode 100755
index 0000000..8adbc0b
--- /dev/null
+++ b/install/install_cu126.sh
@@ -0,0 +1,67 @@
+#!/bin/bash
+set -e
+SCRIPT_DIR=$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)
+source "$SCRIPT_DIR/_utils.sh"
+
+CONDA_CMD="${CONDA_EXE:-}"
+
+if [[ -n "$CONDA_CMD" && ! -x "$CONDA_CMD" ]]; then
+ CONDA_CMD=""
+fi
+
+if [[ -z "$CONDA_CMD" ]]; then
+ CONDA_CMD=$(command -v conda || true)
+fi
+
+if [[ -z "$CONDA_CMD" ]]; then
+ log_error "conda is required to install CUDA 12.6 into the active environment."
+ exit 1
+fi
+
+if [[ -z "${CONDA_PREFIX:-}" ]]; then
+ log_error "No active conda environment detected. Please run 'conda activate ' first."
+ exit 1
+fi
+
+log_info "Installing CUDA 12.6 toolkit into conda environment: $CONDA_PREFIX"
+log_info "Using conda executable: $CONDA_CMD"
+"$CONDA_CMD" install \
+ -p "$CONDA_PREFIX" \
+ --override-channels \
+ -c nvidia \
+ -c conda-forge \
+ cuda-toolkit=12.6 \
+ cuda-nvcc=12.6 \
+ -y
+
+log_info "Writing CUDA 12.6 activation hook into the conda environment..."
+mkdir -p "$CONDA_PREFIX/etc/conda/activate.d"
+cat > "$CONDA_PREFIX/etc/conda/activate.d/cuda126.sh" <<'HOOK'
+export CUDA_HOME="$CONDA_PREFIX"
+export CUDA_PATH="$CONDA_PREFIX"
+export PATH="$CONDA_PREFIX/bin:$PATH"
+export CUDA_TARGET_LIB="$CONDA_PREFIX/targets/x86_64-linux/lib"
+_cuda_conda_lib="$CONDA_PREFIX/lib"
+_cuda_conda_lib64="$CONDA_PREFIX/lib64"
+_cuda_ld_path=":${LD_LIBRARY_PATH:-}:"
+_cuda_ld_path="${_cuda_ld_path//:$_cuda_conda_lib:/:}"
+_cuda_ld_path="${_cuda_ld_path//:$_cuda_conda_lib64:/:}"
+_cuda_ld_path="${_cuda_ld_path#:}"
+_cuda_ld_path="${_cuda_ld_path%:}"
+export LD_LIBRARY_PATH="$CUDA_TARGET_LIB${_cuda_ld_path:+:$_cuda_ld_path}"
+export LIBRARY_PATH="$CUDA_TARGET_LIB:${LIBRARY_PATH:-}"
+unset _cuda_ld_path _cuda_conda_lib _cuda_conda_lib64
+export CPATH="$CONDA_PREFIX/targets/x86_64-linux/include:${CPATH:-}"
+export TORCH_CUDA_ARCH_LIST="${TORCH_CUDA_ARCH_LIST:-8.9}"
+HOOK
+
+log_info "Verifying CUDA 12.6 compiler from the active conda environment..."
+source "$CONDA_PREFIX/etc/conda/activate.d/cuda126.sh"
+
+which nvcc
+nvcc --version
+
+log_info "CUDA 12.6 toolkit installation finished."
+log_info "Future install.sh stages will load CUDA 12.6 variables automatically."
+ENV_NAME="${CONDA_PREFIX##*/}"
+log_info "For interactive nvcc in this terminal, run: conda activate $ENV_NAME"
diff --git a/install/install_room.sh b/install/install_room.sh
new file mode 100755
index 0000000..6edbfd8
--- /dev/null
+++ b/install/install_room.sh
@@ -0,0 +1,67 @@
+#!/bin/bash
+
+set -e
+
+PROJECT_ROOT="$(pwd)"
+INFINIGEN_DIR="${PROJECT_ROOT}/thirdparty/infinigen"
+
+BLENDER_WGET_LINK='https://download.blender.org/release/Blender4.2/blender-4.2.0-linux-x64.tar.xz'
+BLENDER_WGET_FILE="${INFINIGEN_DIR}/blender-4.2.0-linux-x64.tar.xz"
+BLENDER_UNTAR_DIR_NAME='blender-4.2.0-linux-x64'
+BLENDER_UNTAR_PATH="${INFINIGEN_DIR}/${BLENDER_UNTAR_DIR_NAME}"
+BLENDER_DIR="${INFINIGEN_DIR}/blender"
+BLENDER_PYTHON="${BLENDER_DIR}/4.2/python/bin/python3.11"
+
+if [ ! -d "${BLENDER_DIR}" ]; then
+ echo "Blender not found in ${BLENDER_DIR}, install..."
+
+ # Download Blender to specific path
+ if [ ! -f "${BLENDER_WGET_FILE}" ]; then
+ wget -O "${BLENDER_WGET_FILE}" "${BLENDER_WGET_LINK}"
+ fi
+
+ # Unzip Blender in Linux
+ tar -xf "${BLENDER_WGET_FILE}" -C "${INFINIGEN_DIR}"
+ mv "${BLENDER_UNTAR_PATH}" "${BLENDER_DIR}"
+ rm "${BLENDER_WGET_FILE}"
+fi
+
+
+ENV_NAME="build_311"
+if ! conda info --envs | grep -q "^$ENV_NAME "; then
+ conda create -n "$ENV_NAME" python=3.11 -y
+ conda install -n "$ENV_NAME" -y -c conda-forge gxx_linux-64=11 mesalib glew glm menpo::glfw3
+fi
+
+# Set environment variables
+TARGET_ENV=$(conda info --envs | awk -v env="$ENV_NAME" '$1 == env {print $NF; exit}')
+if [ -z "$TARGET_ENV" ]; then
+ CONDA_BASE=$(conda info --base)
+ TARGET_ENV="$CONDA_BASE/envs/$ENV_NAME"
+fi
+export C_INCLUDE_PATH=$TARGET_ENV/include:$C_INCLUDE_PATH
+export CPLUS_INCLUDE_PATH=$TARGET_ENV/include:$CPLUS_INCLUDE_PATH
+export LIBRARY_PATH=$TARGET_ENV/lib:$LIBRARY_PATH
+export LD_LIBRARY_PATH=$TARGET_ENV/lib:$LD_LIBRARY_PATH
+export CC=$TARGET_ENV/bin/x86_64-conda-linux-gnu-gcc
+export CXX=$TARGET_ENV/bin/x86_64-conda-linux-gnu-g++
+
+CUDA_HOST_CXX=
+if [ -x "$TARGET_ENV/bin/x86_64-conda-linux-gnu-g++" ]; then
+ CUDA_HOST_CXX="$TARGET_ENV/bin/x86_64-conda-linux-gnu-g++"
+elif [ -x /usr/bin/g++-11 ]; then
+ CUDA_HOST_CXX=/usr/bin/g++-11
+fi
+if [ -n "$CUDA_HOST_CXX" ]; then
+ export CUDAHOSTCXX="$CUDA_HOST_CXX"
+ export NVCC_PREPEND_FLAGS="-ccbin=$CUDA_HOST_CXX"
+fi
+
+"${BLENDER_PYTHON}" -m ensurepip
+BUILD_PYTHONPATH="$TARGET_ENV/lib/python3.11/site-packages${PYTHONPATH:+:$PYTHONPATH}"
+CFLAGS="-I$TARGET_ENV/include/python3.11 $CFLAGS" PYTHONPATH="$BUILD_PYTHONPATH" "${BLENDER_PYTHON}" -m pip install --no-build-isolation -e "${INFINIGEN_DIR}[sim]"
+
+"${BLENDER_PYTHON}" -m pip install pyyaml tyro colorlog openai tenacity json-repair
+"${BLENDER_PYTHON}" -m pip install -e "${PROJECT_ROOT}"
+
+echo "Setup room env complete."
diff --git a/install/install_extra.sh b/install/install_scene3d.sh
old mode 100644
new mode 100755
similarity index 100%
rename from install/install_extra.sh
rename to install/install_scene3d.sh
diff --git a/mkdocs.yml b/mkdocs.yml
index d5a421c..54c7591 100644
--- a/mkdocs.yml
+++ b/mkdocs.yml
@@ -1,35 +1,44 @@
-site_name: Documentation
-site_url: https://horizonrobotics.github.io/EmbodiedGen/
+site_name: EmbodiedGen Documentation
+site_url: https://horizonrobotics.github.io/EmbodiedGen/docs/
+docs_dir: docs/documentation
repo_name: "EmbodiedGen"
repo_url: https://github.com/HorizonRobotics/EmbodiedGen
-copyright: "Copyright (c) 2025 Horizon Robotics"
+copyright: "Copyright (c) 2026 Horizon Robotics"
use_directory_urls: false
nav:
- ๐ Home: index.md
- ๐ Installation: install.md
+ - ๐ Tutorials:
+ - Overview: tutorials/index.md
+ - ๐งฑ Asset Generation:
+ - Image-to-3D: tutorials/image_to_3d.md
+ - Text-to-3D: tutorials/text_to_3d.md
+ - Texture Generation: tutorials/texture_edit.md
+ - Soft-Body Simulation: tutorials/soft_body.md
+ - Articulated Objects (DIPO): tutorials/articulated_gen.md
+ - Affordance: tutorials/affordance.md
+ - ๐ Scene Generation:
+ - Room Generation: tutorials/room_gen.md
+ - 3D Scene Generation: tutorials/scene_gen.md
+ - ๐ World Composition:
+ - Layout Generation: tutorials/layout_gen.md
+ - Digital Twin Creation: tutorials/digital_twin.md
+ - ๐ฌ 3D Vibe Coding: tutorials/vibe_coding.md
+ - ๐ฆ Any Simulators: tutorials/any_simulators.md
+ - ๐ค Robot Learning: tutorials/robot_learning.md
- ๐งฉ Services:
- Overview: services/index.md
- Image-to-3D: services/image_to_3d.md
- Text-to-3D: services/text_to_3d.md
- Texture Generation: services/texture_edit.md
- Asset Visualizer: services/visualize_asset.md
- - ๐ Tutorials:
- - Overview: tutorials/index.md
- - Image-to-3D: tutorials/image_to_3d.md
- - Text-to-3D: tutorials/text_to_3d.md
- - Texture Generation: tutorials/texture_edit.md
- # - Articulated Object Generation: tutorials/articulated_gen.md
- - 3D Scene Generation: tutorials/scene_gen.md
- - Interactive 3D Scenes: tutorials/layout_gen.md
- - Gym Parallel Envs: tutorials/gym_env.md
- - Any Simulators: tutorials/any_simulators.md
- - Digital Twin Creation: tutorials/digital_twin.md
- ๐ API Reference:
- Overview: api/index.md
- Data: api/data.md
- Envs: api/envs.md
- Models: api/models.md
+ - Skills: api/skills.md
- Trainer: api/trainer.md
- Utilities: api/utils.md
- Validators: api/validators.md
@@ -52,7 +61,10 @@ theme:
name: material
language: en
logo: assets/logo.png
- favicon: assets/logo.png
+ favicon: assets/favicon.svg
+ font:
+ text: Hanken Grotesk
+ code: JetBrains Mono
icon:
repo: fontawesome/brands/github
features:
@@ -68,15 +80,15 @@ theme:
palette:
- media: "(prefers-color-scheme: light)"
scheme: default
- primary: brown
- accent: red
+ primary: indigo
+ accent: indigo
toggle:
icon: material/weather-sunny
name: Switch to dark mode
- media: "(prefers-color-scheme: dark)"
scheme: slate
- primary: brown
- accent: red
+ primary: indigo
+ accent: indigo
toggle:
icon: material/weather-night
name: Switch to light mode
@@ -114,4 +126,6 @@ markdown_extensions:
- pymdownx.highlight
- pymdownx.superfences
- admonition
+ - attr_list
+ - md_in_html
diff --git a/pyproject.toml b/pyproject.toml
index 8ed7bc7..13c657f 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -7,7 +7,7 @@ packages = ["embodied_gen"]
[project]
name = "embodied_gen"
-version = "v0.1.7"
+version = "v2.0.0"
readme = "README.md"
license = "Apache-2.0"
license-files = ["LICENSE", "NOTICE"]
@@ -19,15 +19,14 @@ requires-python = ">=3.10"
dev = [
"cpplint",
"pre-commit",
- "pydocstyle",
- "black",
- "isort",
+ "ruff",
"pytest",
"pytest-mock",
"mkdocs",
"mkdocs-material",
"mkdocstrings[python]",
"mkdocs-git-revision-date-localized-plugin",
+ "pycodestyle",
]
[project.scripts]
@@ -39,25 +38,37 @@ texture-cli = "embodied_gen.scripts.gen_texture:entrypoint"
scene3d-cli = "embodied_gen.scripts.gen_scene3d:entrypoint"
layout-cli = "embodied_gen.scripts.gen_layout:entrypoint"
sim-cli = "embodied_gen.scripts.simulate_sapien:entrypoint"
+room-cli = "embodied_gen.scripts.gen_room:entrypoint"
+affordance-cli = "embodied_gen.scripts.affordance_annot.affordance_annot:entrypoint"
-[tool.pydocstyle]
-match = '(?!test_).*(?!_pb2)\.py'
-match-dir = '^(?!(raw|projects|tools|k8s_submit|thirdparty)$)[\w.-]+$'
+[tool.ruff]
+line-length = 79
+target-version = "py310"
+extend-exclude = ["thirdparty"]
+
+[tool.ruff.lint]
+select = ["D", "E", "F", "I", "W"]
+ignore = [
+ "D100",
+ "D101",
+ "D102",
+ "D103",
+ "D104",
+ "D105",
+ "D107",
+ "D202",
+ "E203",
+ "E251",
+ "E402",
+ "E501",
+]
+
+[tool.ruff.lint.pydocstyle]
convention = "google"
-add-ignore = 'D104,D107,D202,D105,D100,D102,D103,D101,E203'
-[tool.pycodestyle]
-max-line-length = 79
-ignore = "E203"
+[tool.ruff.lint.isort]
+known-first-party = ["embodied_gen"]
+no-lines-before = ["first-party"]
-[tool.black]
-line-length = 79
-exclude = "thirdparty"
-skip-string-normalization = true
-
-[tool.isort]
-line_length = 79
-profile = 'black'
-no_lines_before = 'FIRSTPARTY'
-known_first_party = ['embodied_gen']
-skip = "thirdparty/"
\ No newline at end of file
+[tool.ruff.format]
+quote-style = "preserve"
diff --git a/requirements.txt b/requirements.txt
index c72fa6b..6b733ea 100644
--- a/requirements.txt
+++ b/requirements.txt
@@ -1,10 +1,10 @@
-torch==2.4.0
-torchvision==0.19.0
-xformers==0.0.27.post2
+torch==2.8.0
+torchvision==0.23.0
+xformers==0.0.32.post2
pytorch-lightning==2.4.0
-spconv-cu120==2.3.6
+spconv-cu126==2.3.7
numpy==1.26.4
-triton==2.1.0
+triton==3.4.0
dataclasses_json
easydict
opencv-python>4.5
@@ -18,7 +18,8 @@ igraph==0.11.8
pyvista==0.36.1
openai==1.58.1
transformers==4.42.4
-gradio==5.12.0
+gradio==5.48.0
+spaces
sentencepiece==0.2.0
diffusers==0.34.0
xatlas
@@ -29,7 +30,6 @@ basicsr==1.4.2
realesrgan==0.3.0
pydantic==2.9.2
vtk==9.3.1
-spaces
colorlog
json-repair
scikit-learn
diff --git a/scripts/autoformat.sh b/scripts/autoformat.sh
deleted file mode 100644
index bf12f78..0000000
--- a/scripts/autoformat.sh
+++ /dev/null
@@ -1,10 +0,0 @@
-#!/bin/bash
-
-ROOT_DIR=${1}
-
-set -e
-
-black --config=${ROOT_DIR}pyproject.toml ${ROOT_DIR}./
-isort --settings-file=${ROOT_DIR}pyproject.toml ${ROOT_DIR}./
-pycodestyle --show-source --config=${ROOT_DIR}setup.cfg ${ROOT_DIR}./
-pydocstyle --config=${ROOT_DIR}pyproject.toml ${ROOT_DIR}./
diff --git a/scripts/check_lint.py b/scripts/check_lint.py
deleted file mode 100644
index 991ad1b..0000000
--- a/scripts/check_lint.py
+++ /dev/null
@@ -1,61 +0,0 @@
-import argparse
-import os
-import subprocess
-import sys
-
-
-def get_root():
- current_file_path = os.path.abspath(__file__)
- root_path = os.path.dirname(current_file_path)
- for _ in range(2):
- root_path = os.path.dirname(root_path)
- return root_path
-
-
-def cpp_lint(root_path: str):
- # run external python file to lint cpp
- subprocess.check_call(
- " ".join(
- [
- "python3",
- f"{root_path}/scripts/lint_src/lint.py",
- "--project=asset_recons",
- "--path",
- f"{root_path}/src/",
- f"{root_path}/include/",
- f"{root_path}/module/",
- "--exclude_path",
- f"{root_path}/module/web_viz/front_end/",
- ]
- ),
- shell=True,
- )
-
-
-def python_lint(root_path: str, auto_format: bool = False):
- # run external python file to lint python
- subprocess.check_call(
- " ".join(
- [
- "bash",
- (
- f"{root_path}/scripts/lint/check_pylint.sh"
- if not auto_format
- else f"{root_path}/scripts/lint/autoformat.sh"
- ),
- f"{root_path}/",
- ]
- ),
- shell=True,
- )
-
-
-if __name__ == "__main__":
- parser = argparse.ArgumentParser(description="check format.")
- parser.add_argument(
- "--auto_format", action="store_true", help="auto format python"
- )
- parser = parser.parse_args()
- root_path = get_root()
- cpp_lint(root_path)
- python_lint(root_path, parser.auto_format)
diff --git a/scripts/check_pylint.sh b/scripts/check_pylint.sh
deleted file mode 100644
index 8938332..0000000
--- a/scripts/check_pylint.sh
+++ /dev/null
@@ -1,11 +0,0 @@
-#!/bin/bash
-
-ROOT_DIR=${1}
-
-set -e
-
-
-pycodestyle --show-source --config=${ROOT_DIR}setup.cfg ${ROOT_DIR}./
-pydocstyle --config=${ROOT_DIR}pyproject.toml ${ROOT_DIR}./
-black --check --diff --config=${ROOT_DIR}pyproject.toml ${ROOT_DIR}./
-isort --diff --settings-file=${ROOT_DIR}pyproject.toml ${ROOT_DIR}./
diff --git a/scripts/lint/autoformat.sh b/scripts/lint/autoformat.sh
index bf12f78..85a9045 100644
--- a/scripts/lint/autoformat.sh
+++ b/scripts/lint/autoformat.sh
@@ -4,7 +4,5 @@ ROOT_DIR=${1}
set -e
-black --config=${ROOT_DIR}pyproject.toml ${ROOT_DIR}./
-isort --settings-file=${ROOT_DIR}pyproject.toml ${ROOT_DIR}./
-pycodestyle --show-source --config=${ROOT_DIR}setup.cfg ${ROOT_DIR}./
-pydocstyle --config=${ROOT_DIR}pyproject.toml ${ROOT_DIR}./
+ruff check --fix --config=${ROOT_DIR}pyproject.toml ${ROOT_DIR}./
+ruff format --config=${ROOT_DIR}pyproject.toml ${ROOT_DIR}./
diff --git a/scripts/lint/check_lint.py b/scripts/lint/check_lint.py
index 991ad1b..7e7ebb2 100644
--- a/scripts/lint/check_lint.py
+++ b/scripts/lint/check_lint.py
@@ -1,7 +1,6 @@
import argparse
import os
import subprocess
-import sys
def get_root():
@@ -19,7 +18,7 @@ def cpp_lint(root_path: str):
[
"python3",
f"{root_path}/scripts/lint_src/lint.py",
- "--project=asset_recons",
+ "--project=embodied_gen",
"--path",
f"{root_path}/src/",
f"{root_path}/include/",
diff --git a/scripts/lint/check_pylint.sh b/scripts/lint/check_pylint.sh
index 8938332..afef109 100644
--- a/scripts/lint/check_pylint.sh
+++ b/scripts/lint/check_pylint.sh
@@ -5,7 +5,5 @@ ROOT_DIR=${1}
set -e
-pycodestyle --show-source --config=${ROOT_DIR}setup.cfg ${ROOT_DIR}./
-pydocstyle --config=${ROOT_DIR}pyproject.toml ${ROOT_DIR}./
-black --check --diff --config=${ROOT_DIR}pyproject.toml ${ROOT_DIR}./
-isort --diff --settings-file=${ROOT_DIR}pyproject.toml ${ROOT_DIR}./
+ruff check --config=${ROOT_DIR}pyproject.toml ${ROOT_DIR}./
+ruff format --check --diff --config=${ROOT_DIR}pyproject.toml ${ROOT_DIR}./
diff --git a/scripts/lint_src/cpplint.hook b/scripts/lint_src/cpplint.hook
index a9d87ee..7a9319c 100755
--- a/scripts/lint_src/cpplint.hook
+++ b/scripts/lint_src/cpplint.hook
@@ -6,5 +6,5 @@ if [[ ! $(which cpplint) ]]; then
fi
# diff files on local machine.
files=$(git diff --cached --name-status | awk '$1 != "D" {print $2}')
-python3 scripts/lint_src/lint.py --project=asset_recons --path $files --exclude_path thirdparty patch_files;
+python3 scripts/lint_src/lint.py --project=embodied_gen --path $files --exclude_path thirdparty patch_files;
diff --git a/setup.cfg b/setup.cfg
deleted file mode 100644
index 257629f..0000000
--- a/setup.cfg
+++ /dev/null
@@ -1,2 +0,0 @@
-[pycodestyle]
-ignore = E203,W503,E402,E501,E251
diff --git a/tests/test_examples/test_genesis_cloth_demo.py b/tests/test_examples/test_genesis_cloth_demo.py
new file mode 100644
index 0000000..0f87a5f
--- /dev/null
+++ b/tests/test_examples/test_genesis_cloth_demo.py
@@ -0,0 +1,120 @@
+# Project EmbodiedGen
+#
+# Copyright (c) 2025 Horizon Robotics. All Rights Reserved.
+#
+# 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.
+
+"""Smoke tests for the Genesis cloth demo helpers."""
+
+import importlib.util
+import json
+
+import pytest
+import trimesh
+from embodied_gen.scripts.flexible_obj.genesis_cloth_converter import (
+ convert_cloth_to_genesis,
+)
+from embodied_gen.scripts.flexible_obj.simulate_genesis_cloth import (
+ simulate_genesis_cloth,
+)
+
+
+def _write_square_cloth(path):
+ mesh = trimesh.Trimesh(
+ vertices=[
+ [-0.25, -0.25, 0.0],
+ [0.25, -0.25, 0.0],
+ [0.25, 0.25, 0.0],
+ [-0.25, 0.25, 0.0],
+ ],
+ faces=[[0, 1, 2], [0, 2, 3]],
+ process=False,
+ )
+ mesh.export(path)
+
+
+def _write_cloth_urdf(path):
+ path.write_text(
+ """
+
+
+
+
+
+
+
+
+
+"""
+ )
+
+
+def test_convert_cloth_to_genesis(tmp_path):
+ mesh_dir = tmp_path / "mesh"
+ mesh_dir.mkdir()
+ _write_square_cloth(mesh_dir / "sample.obj")
+
+ urdf_path = tmp_path / "sample.urdf"
+ output_dir = tmp_path / "converted"
+ _write_cloth_urdf(urdf_path)
+
+ manifest_path = convert_cloth_to_genesis(
+ str(urdf_path), output_dir=str(output_dir), overwrite=True
+ )
+ manifest = json.loads(
+ (output_dir / "genesis" / "manifest.json").read_text()
+ )
+
+ assert manifest_path == str(output_dir / "genesis" / "manifest.json")
+ assert (output_dir / manifest["cloth_mesh"]).exists()
+ assert (output_dir / manifest["material"]).exists()
+ assert manifest["source_urdf"].endswith("sample.urdf")
+ assert manifest["urdf_visual_origin"]["rpy"] == [1.5708, 0.0, 0.0]
+ assert manifest["mesh_stats"]["vertices"] == 4
+ assert manifest["mesh_stats"]["faces"] == 2
+
+
+@pytest.mark.skipif(
+ importlib.util.find_spec("genesis") is None,
+ reason="Genesis is not installed",
+)
+def test_simulate_genesis_cloth_smoke(tmp_path):
+ mesh_dir = tmp_path / "mesh"
+ mesh_dir.mkdir()
+ _write_square_cloth(mesh_dir / "sample.obj")
+ urdf_path = tmp_path / "sample.urdf"
+ _write_cloth_urdf(urdf_path)
+ output_dir = tmp_path / "converted"
+ convert_cloth_to_genesis(
+ str(urdf_path), output_dir=str(output_dir), overwrite=True
+ )
+
+ video_path = simulate_genesis_cloth(
+ str(output_dir),
+ output_dir=str(tmp_path / "genesis_sim"),
+ init_height=0.5,
+ duration_seconds=0.4,
+ sim_steps=2,
+ shake=True,
+ shake_steps=2,
+ shake_amplitude=0.001,
+ device="cpu",
+ headless=True,
+ camera_res=(80, 80),
+ render_interval=1,
+ fps=5,
+ )
+
+ assert (tmp_path / "genesis_sim" / "run_summary.json").exists()
+ assert (tmp_path / "genesis_sim" / "sim_config.json").exists()
+ assert video_path.endswith("video.mp4")
diff --git a/tests/test_examples/test_urdf_convertor.py b/tests/test_examples/test_urdf_convertor.py
index 3795e3b..ca806ac 100644
--- a/tests/test_examples/test_urdf_convertor.py
+++ b/tests/test_examples/test_urdf_convertor.py
@@ -14,6 +14,9 @@
# implied. See the License for the specific language governing
# permissions and limitations under the License.
+import os
+
+import pytest
from embodied_gen.utils.gpt_clients import GPT_CLIENT
from embodied_gen.validators.urdf_convertor import URDFGenerator
@@ -41,15 +44,18 @@ def test_urdf_convertor():
"outputs/test_urdf/pen2/result/mesh/pen.obj",
"outputs/test_urdf/pen3/result/mesh/pen.obj",
]
+ mesh_paths = [p for p in mesh_paths if os.path.exists(p)]
+ if not mesh_paths:
+ pytest.skip("No mesh test assets available under outputs/test_urdf/")
+
for idx, mesh_path in enumerate(mesh_paths):
filename = mesh_path.split("/")[-1].split(".")[0]
urdf_path = urdf_gen(
mesh_path=mesh_path,
output_root=f"outputs/test_urdf2/sample_{idx}",
category=filename,
- # min_height=1.0,
- # max_height=1.2,
)
+ assert urdf_path is not None and os.path.exists(urdf_path)
def test_decompose_convex_mesh():
@@ -61,7 +67,7 @@ def test_decompose_convex_mesh():
]
for idx, mesh_path in enumerate(mesh_paths):
filename = mesh_path.split("/")[-1].split(".")[0]
- urdf_path = urdf_gen(
+ urdf_gen(
mesh_path=mesh_path,
output_root=f"outputs/test_urdf3/sample_{idx}",
category=filename,
diff --git a/tests/test_unit/test_asset_process.py b/tests/test_unit/test_asset_process.py
new file mode 100644
index 0000000..c19dd21
--- /dev/null
+++ b/tests/test_unit/test_asset_process.py
@@ -0,0 +1,407 @@
+# Project EmbodiedGen
+#
+# Copyright (c) 2025 Horizon Robotics. All Rights Reserved.
+#
+# 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.
+
+import importlib
+import xml.etree.ElementTree as ET
+from pathlib import Path
+from types import SimpleNamespace
+
+import numpy as np
+import pytest
+import trimesh
+from plyfile import PlyData, PlyElement
+
+asset_process = importlib.import_module(
+ "embodied_gen.skills.asset-process.asset_process"
+)
+
+
+def _create_asset(
+ root: Path,
+ mesh_format: str = "obj",
+ include_gaussian: bool = False,
+) -> Path:
+ asset_dir = root / "sample"
+ result_dir = asset_dir / "result"
+ mesh_dir = result_dir / "mesh"
+ mesh_dir.mkdir(parents=True)
+
+ mesh = trimesh.creation.box(extents=(2.0, 4.0, 6.0))
+ if mesh_format == "obj":
+ mesh.export(mesh_dir / "sample.obj")
+ elif mesh_format == "glb":
+ mesh.export(mesh_dir / "sample.glb")
+ elif mesh_format == "collision":
+ mesh.export(mesh_dir / "sample_collision.obj")
+ else:
+ raise ValueError(f"Unsupported test mesh format: {mesh_format}")
+
+ if include_gaussian:
+ _write_gaussian_ply(mesh_dir / "sample_gs.ply")
+
+ urdf_path = result_dir / "sample.urdf"
+ urdf_path.write_text(
+ """
+
+
+
+
+
+
+
+
+
+
+
+ 0.15
+ 0.20
+ 0.175
+
+
+
+""",
+ encoding="utf-8",
+ )
+ return urdf_path
+
+
+def _write_gaussian_ply(path: Path) -> None:
+ vertex = np.zeros(
+ 1,
+ dtype=[
+ ("x", "f4"),
+ ("y", "f4"),
+ ("z", "f4"),
+ ("scale_0", "f4"),
+ ("scale_1", "f4"),
+ ("scale_2", "f4"),
+ ("rot_0", "f4"),
+ ("rot_1", "f4"),
+ ("rot_2", "f4"),
+ ("rot_3", "f4"),
+ ],
+ )
+ vertex["y"] = 1.0
+ vertex["rot_0"] = 1.0
+ PlyData([PlyElement.describe(vertex, "vertex")]).write(str(path))
+
+
+def _create_flat_asset(root: Path) -> Path:
+ asset_dir = root / "sample"
+ mesh_dir = asset_dir / "mesh"
+ mesh_dir.mkdir(parents=True)
+ trimesh.creation.box(extents=(2.0, 4.0, 6.0)).export(
+ mesh_dir / "sample.obj"
+ )
+
+ urdf_path = asset_dir / "sample.urdf"
+ urdf_path.write_text(
+ """
+
+
+
+
+
+
+
+ 0.15
+ 0.20
+ 0.175
+
+
+
+""",
+ encoding="utf-8",
+ )
+ return urdf_path
+
+
+def _read_heights(urdf_path: Path) -> dict[str, float]:
+ root = ET.parse(urdf_path).getroot()
+ extra_info = root.find("link/extra_info")
+ assert extra_info is not None
+ return {
+ field: float(extra_info.findtext(field))
+ for field in asset_process.URDF_HEIGHT_FIELDS
+ }
+
+
+def _read_height_text(urdf_path: Path) -> dict[str, str]:
+ root = ET.parse(urdf_path).getroot()
+ extra_info = root.find("link/extra_info")
+ assert extra_info is not None
+ return {
+ field: extra_info.findtext(field)
+ for field in asset_process.URDF_HEIGHT_FIELDS
+ }
+
+
+def test_process_asset_scales_rotates_and_recalculates_height(
+ tmp_path: Path,
+) -> None:
+ source_urdf = _create_asset(tmp_path / "source")
+ output_root = tmp_path / "output"
+
+ output_urdf = asset_process.process_asset(
+ urdf_path=source_urdf,
+ scale_factor=2.0,
+ rot_xyz=(90.0, 0.0, 0.0),
+ output_dir=output_root,
+ )
+
+ assert output_urdf == output_root / "result/sample.urdf"
+ output_mesh = trimesh.load(output_root / "result/mesh/sample.obj")
+ np.testing.assert_allclose(output_mesh.extents, (4.0, 12.0, 8.0))
+ assert _read_heights(output_urdf) == {
+ "min_height": 12.0,
+ "max_height": 12.0,
+ "real_height": 12.0,
+ }
+ assert _read_height_text(output_urdf) == {
+ "min_height": "12.0000",
+ "max_height": "12.0000",
+ "real_height": "12.0000",
+ }
+
+ root = ET.parse(output_urdf).getroot()
+ assert root.find("link/visual/origin").get("rpy") == "1.5708 0 0"
+ source_mesh = trimesh.load(source_urdf.parent / "mesh/sample.obj")
+ np.testing.assert_allclose(source_mesh.extents, (2.0, 4.0, 6.0))
+
+
+@pytest.mark.parametrize("mesh_format", ["glb", "collision"])
+def test_height_calculation_falls_back_to_available_mesh(
+ tmp_path: Path, mesh_format: str
+) -> None:
+ urdf_path = _create_asset(tmp_path / mesh_format, mesh_format)
+
+ output_urdf = asset_process.process_asset(
+ urdf_path=urdf_path,
+ inplace=True,
+ )
+
+ assert output_urdf == urdf_path
+ assert _read_heights(output_urdf) == {
+ "min_height": 4.0,
+ "max_height": 4.0,
+ "real_height": 4.0,
+ }
+
+
+def test_height_calculation_requires_a_mesh(tmp_path: Path) -> None:
+ urdf_path = _create_asset(tmp_path / "missing")
+ (urdf_path.parent / "mesh/sample.obj").unlink()
+
+ with pytest.raises(FileNotFoundError, match="height calculation"):
+ asset_process.process_asset(urdf_path=urdf_path, inplace=True)
+
+
+def test_process_asset_supports_scale_only(tmp_path: Path) -> None:
+ urdf_path = _create_asset(tmp_path / "scale_only")
+
+ output_urdf = asset_process.process_asset(
+ urdf_path=urdf_path,
+ scale_factor=0.5,
+ inplace=True,
+ )
+
+ assert output_urdf == urdf_path
+ mesh = trimesh.load(urdf_path.parent / "mesh/sample.obj")
+ np.testing.assert_allclose(mesh.extents, (1.0, 2.0, 3.0))
+ assert _read_heights(output_urdf) == {
+ "min_height": 2.0,
+ "max_height": 2.0,
+ "real_height": 2.0,
+ }
+
+
+def test_gaussian_processing_extends_legacy_scale_flow(
+ tmp_path: Path, monkeypatch: pytest.MonkeyPatch
+) -> None:
+ urdf_path = _create_asset(
+ tmp_path / "gaussian",
+ include_gaussian=True,
+ )
+ calls: list[tuple[str, object]] = []
+
+ class FakeGaussian:
+ @classmethod
+ def load_from_ply(cls, path: str) -> "FakeGaussian":
+ calls.append(("load", path))
+ return cls()
+
+ @staticmethod
+ def trans_to_quatpose(matrix: np.ndarray) -> np.ndarray:
+ calls.append(("pose", matrix.copy()))
+ return np.array([0.0, 0.0, 0.0, 1.0])
+
+ def rescale(self, scale: float) -> None:
+ calls.append(("rescale", scale))
+
+ def get_gaussians(self, instance_pose: np.ndarray) -> "FakeGaussian":
+ calls.append(("rotate", instance_pose.copy()))
+ return self
+
+ def save_to_ply(self, path: str) -> None:
+ calls.append(("save", path))
+
+ monkeypatch.setattr(
+ asset_process,
+ "import_module",
+ lambda name: SimpleNamespace(GaussianOperator=FakeGaussian),
+ )
+
+ asset_process.process_asset(
+ urdf_path=urdf_path,
+ scale_factor=2.0,
+ rot_xyz=(90.0, 0.0, 0.0),
+ inplace=True,
+ )
+
+ assert [name for name, _ in calls] == [
+ "load",
+ "rescale",
+ "pose",
+ "rotate",
+ "save",
+ ]
+ assert calls[1] == ("rescale", 2.0)
+ np.testing.assert_allclose(
+ calls[2][1],
+ asset_process.Rotation.from_euler(
+ "xyz", (90.0, 0.0, 0.0), degrees=True
+ ).as_matrix(),
+ )
+
+
+def test_collision_loader_preserves_multiple_obj_groups(
+ tmp_path: Path,
+) -> None:
+ collision_path = tmp_path / "multi_collision.obj"
+ collision_path.write_text(
+ """v 0 0 0
+v 1 0 0
+v 0 1 0
+v 0 0 1
+v 1 0 1
+v 0 1 1
+o lower
+f 1 2 3
+o upper
+f 4 5 6
+""",
+ encoding="utf-8",
+ )
+
+ meshes = asset_process.AssetProcessor._load_collision_obj(
+ str(collision_path)
+ )
+
+ assert len(meshes) == 2
+ assert all(len(mesh.faces) == 1 for mesh in meshes)
+
+
+def test_gaussian_file_is_optional(
+ tmp_path: Path, monkeypatch: pytest.MonkeyPatch
+) -> None:
+ urdf_path = _create_asset(tmp_path / "without_gaussian")
+
+ def fail_import(name: str) -> None:
+ raise AssertionError(f"Unexpected GS module import: {name}")
+
+ monkeypatch.setattr(asset_process, "import_module", fail_import)
+
+ output_urdf = asset_process.process_asset(
+ urdf_path=urdf_path,
+ scale_factor=0.5,
+ inplace=True,
+ )
+
+ assert output_urdf == urdf_path
+
+
+def test_process_asset_supports_flat_asset_layout(tmp_path: Path) -> None:
+ urdf_path = _create_flat_asset(tmp_path / "source")
+ output_root = tmp_path / "processed"
+
+ output_urdf = asset_process.process_asset(
+ urdf_path=urdf_path,
+ rot_xyz=(90.0, 0.0, 0.0),
+ output_dir=output_root,
+ )
+
+ assert output_urdf == output_root / "sample.urdf"
+ output_mesh = trimesh.load(output_root / "mesh/sample.obj")
+ np.testing.assert_allclose(output_mesh.extents, (2.0, 6.0, 4.0))
+ assert _read_heights(output_urdf) == {
+ "min_height": 6.0,
+ "max_height": 6.0,
+ "real_height": 6.0,
+ }
+
+
+def test_keep_urdf_raw_rot_composes_inverse_rotation(tmp_path: Path) -> None:
+ urdf_path = _create_asset(tmp_path / "source")
+ output_dir = tmp_path / "processed"
+ baked_rotation = asset_process.Rotation.from_euler(
+ "xyz",
+ (20.0, 35.0, 10.0),
+ degrees=True,
+ )
+ original_rotation = asset_process.Rotation.from_euler(
+ "xyz",
+ (1.5708, 0.0, 0.0),
+ degrees=False,
+ )
+
+ output_urdf = asset_process.process_asset(
+ urdf_path=urdf_path,
+ rot_xyz=(20.0, 35.0, 10.0),
+ keep_urdf_raw_rot=True,
+ output_dir=output_dir,
+ )
+
+ root = ET.parse(output_urdf).getroot()
+ for path in ("link/visual/origin", "link/collision/origin"):
+ origin = root.find(path)
+ rpy_text = origin.get("rpy")
+ assert all(
+ len(component.rsplit(".", maxsplit=1)[-1]) == 4
+ for component in rpy_text.split()
+ )
+ updated_rpy = np.fromstring(rpy_text, sep=" ")
+ updated_rotation = asset_process.Rotation.from_euler(
+ "xyz",
+ updated_rpy,
+ degrees=False,
+ )
+ np.testing.assert_allclose(
+ (updated_rotation * baked_rotation).as_matrix(),
+ original_rotation.as_matrix(),
+ atol=1e-4,
+ )
+
+
+def test_output_dir_cannot_replace_source_without_inplace(
+ tmp_path: Path,
+) -> None:
+ urdf_path = _create_flat_asset(tmp_path / "source")
+
+ with pytest.raises(ValueError, match="use inplace=True"):
+ asset_process.AssetProcessor(
+ urdf_path=urdf_path,
+ output_dir=urdf_path.parent,
+ )
diff --git a/tests/test_unit/test_gpt_client.py b/tests/test_unit/test_gpt_client.py
index e7b8180..f2787c0 100644
--- a/tests/test_unit/test_gpt_client.py
+++ b/tests/test_unit/test_gpt_client.py
@@ -15,6 +15,7 @@
# permissions and limitations under the License.
import os
+from types import SimpleNamespace
from unittest.mock import patch
import pytest
@@ -92,3 +93,142 @@ def test_gptclient_query(gpt_client, text_prompt, image_base64):
mock_query.assert_called_once_with(
text_prompt=text_prompt, image_base64=image_base64
)
+
+
+def _make_client(model_name: str) -> GPTclient:
+ return GPTclient(
+ endpoint="https://yfb-openai-sweden.openai.azure.com/",
+ api_key="fake_key",
+ api_version="2024-12-01-preview",
+ model_name=model_name,
+ check_connection=False,
+ )
+
+
+def _fake_response(text: str = "ok"):
+ return SimpleNamespace(
+ choices=[SimpleNamespace(message=SimpleNamespace(content=text))]
+ )
+
+
+def test_is_gpt5_model_detection():
+ assert GPTclient._is_gpt5_model("gpt-5.4") is True
+ assert GPTclient._is_gpt5_model("GPT-5") is True
+ assert GPTclient._is_gpt5_model("gpt5-turbo") is True
+ assert GPTclient._is_gpt5_model("gpt-4o") is False
+ assert (
+ GPTclient._is_gpt5_model("qwen/qwen2.5-vl-72b-instruct:free") is False
+ )
+
+
+def test_gpt5_text_payload_uses_max_completion_tokens():
+ client = _make_client("gpt-5.4")
+ with patch.object(
+ client, "completion_with_backoff", return_value=_fake_response("hi")
+ ) as mock_call:
+ out = client.query("Hello world")
+ assert out == "hi"
+ payload = mock_call.call_args.kwargs
+ assert payload["model"] == "gpt-5.4"
+ assert payload["max_completion_tokens"] == 8192
+ # GPT-5 path should NOT include legacy sampling params.
+ for forbidden in (
+ "max_tokens",
+ "temperature",
+ "top_p",
+ "frequency_penalty",
+ "presence_penalty",
+ "stop",
+ ):
+ assert forbidden not in payload, (
+ f"{forbidden} leaked into gpt-5 payload"
+ )
+ # Text-only: a single user content block of type text.
+ user_content = payload["messages"][-1]["content"]
+ assert user_content[0]["type"] == "text"
+ assert all(c["type"] != "image_url" for c in user_content)
+
+
+def test_gpt5_text_image_payload():
+ client = _make_client("gpt-5.4")
+ img1 = Image.new("RGB", (32, 32), "red")
+ img2 = Image.new("RGB", (32, 32), "blue")
+ with patch.object(
+ client, "completion_with_backoff", return_value=_fake_response("ok")
+ ) as mock_call:
+ client.query("describe", image_base64=[img1, img2])
+ payload = mock_call.call_args.kwargs
+ user_content = payload["messages"][-1]["content"]
+ image_blocks = [c for c in user_content if c["type"] == "image_url"]
+ assert len(image_blocks) == 2
+ for b in image_blocks:
+ assert b["image_url"]["url"].startswith("data:image/png;base64,")
+ assert payload["max_completion_tokens"] == 8192
+
+
+def test_non_gpt5_payload_keeps_legacy_params():
+ client = _make_client("yfb-gpt-4o")
+ with patch.object(
+ client, "completion_with_backoff", return_value=_fake_response("ok")
+ ) as mock_call:
+ client.query("Hello")
+ payload = mock_call.call_args.kwargs
+ assert payload["max_tokens"] == 500
+ assert payload["temperature"] == 0.1
+ assert "max_completion_tokens" not in payload
+
+
+def test_params_override():
+ client = _make_client("gpt-5.4")
+ with patch.object(
+ client, "completion_with_backoff", return_value=_fake_response("ok")
+ ) as mock_call:
+ client.query("Hello", params={"max_completion_tokens": 256})
+ payload = mock_call.call_args.kwargs
+ assert payload["max_completion_tokens"] == 256
+
+
+def test_gpt5_filters_unsupported_params():
+ client = _make_client("gpt-5.4")
+ with patch.object(
+ client, "completion_with_backoff", return_value=_fake_response("ok")
+ ) as mock_call:
+ client.query(
+ "Hello",
+ params={
+ "temperature": 1.0,
+ "top_p": 0.95,
+ "frequency_penalty": 0,
+ "presence_penalty": 0,
+ "stop": None,
+ "max_tokens": 256,
+ },
+ )
+ payload = mock_call.call_args.kwargs
+ for forbidden in (
+ "temperature",
+ "top_p",
+ "frequency_penalty",
+ "presence_penalty",
+ "stop",
+ "max_tokens",
+ ):
+ assert forbidden not in payload
+ # max_tokens should be migrated to max_completion_tokens.
+ assert payload["max_completion_tokens"] == 256
+
+
+def test_image_path_loaded_from_disk(tmp_path):
+ img_path = tmp_path / "tiny.png"
+ Image.new("RGB", (8, 8), "green").save(img_path)
+ client = _make_client("gpt-5.4")
+ with patch.object(
+ client, "completion_with_backoff", return_value=_fake_response("ok")
+ ) as mock_call:
+ client.query("describe", image_base64=str(img_path))
+ user_content = mock_call.call_args.kwargs["messages"][-1]["content"]
+ image_blocks = [c for c in user_content if c["type"] == "image_url"]
+ assert len(image_blocks) == 1
+ assert image_blocks[0]["image_url"]["url"].startswith(
+ "data:image/png;base64,"
+ )