diff --git a/.gitattributes b/.gitattributes index 33e7dae9..4d5d0dce 100644 --- a/.gitattributes +++ b/.gitattributes @@ -11,3 +11,4 @@ *.stl filter=lfs diff=lfs merge=lfs -text *.npz filter=lfs diff=lfs merge=lfs -text *.onnx filter=lfs diff=lfs merge=lfs -text +src/reachy_mini/daemon/app/wasm/bin/** filter=lfs diff=lfs merge=lfs -text diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 00000000..41692988 --- /dev/null +++ b/.gitmodules @@ -0,0 +1,4 @@ +[submodule "src/reachy_mini/daemon/app/wasm/mujoco_web"] + path = src/reachy_mini/daemon/app/wasm/mujoco_web + url = git@github.com:pollen-robotics/mujoco_web.git + branch = mirror diff --git a/src/reachy_mini/daemon/app/dashboard/style.css b/src/reachy_mini/daemon/app/dashboard/style.css index d59674bd..5bf89468 100644 --- a/src/reachy_mini/daemon/app/dashboard/style.css +++ b/src/reachy_mini/daemon/app/dashboard/style.css @@ -34,4 +34,97 @@ ul#examples-list li a { ul#examples-list li a:hover { text-decoration: underline; +} + +/* Tab styles */ +.tab-container { + margin: 0 2em; +} + +.tab-nav { + display: flex; + border-bottom: 2px solid #ddd; + margin-bottom: 1em; +} + +.tab-button { + padding: 0.8em 1.5em; + border: none; + background: #f0f0f0; + cursor: pointer; + margin-right: 2px; + border-top-left-radius: 8px; + border-top-right-radius: 8px; + font-size: 0.9em; + transition: background-color 0.2s; +} + +.tab-button:hover { + background: #e0e0e0; +} + +.tab-button.active { + background: #2d3e50; + color: white; +} + +.tab-content { + display: none; + padding: 1em 0; +} + +.tab-content.active { + display: block; +} + +/* MuJoCo container styles */ +#mujoco-container { + background: #000; + border-radius: 8px; + overflow: hidden; + border: 1px solid #ddd; +} + +/* Daemon control styles */ +#daemon-control { + background: #fff; + padding: 1.5em; + border-radius: 8px; + box-shadow: 0 2px 6px rgba(0, 0, 0, 0.07); + margin-bottom: 1em; +} + +#daemon-control h2 { + margin-top: 0; + color: #2d3e50; +} + +#daemon-control button { + padding: 0.5em 1em; + margin-right: 0.5em; + margin-bottom: 0.5em; + border: none; + border-radius: 4px; + cursor: pointer; + font-size: 0.9em; +} + +#start-daemon { + background: #27ae60; + color: white; +} + +#stop-daemon { + background: #e74c3c; + color: white; +} + +#restart-daemon { + background: #f39c12; + color: white; +} + +#daemon-control label { + display: block; + margin: 0.5em 0; } \ No newline at end of file diff --git a/src/reachy_mini/daemon/app/main.py b/src/reachy_mini/daemon/app/main.py index 60be7a45..a83aa0f8 100644 --- a/src/reachy_mini/daemon/app/main.py +++ b/src/reachy_mini/daemon/app/main.py @@ -119,6 +119,85 @@ async def list_examples(request: Request): name="dashboard", ) + # Mount WASM files - always use bin directory + wasm_bin_dir = Path(__file__).parent / "wasm" / "bin" + original_mjcf_dir = Path(__file__).parent.parent.parent / "descriptions" / "reachy_mini" / "mjcf" + + if wasm_bin_dir.exists(): + # Mount original model files from descriptions directory + if original_mjcf_dir.exists(): + app.mount( + "/wasm/dist/examples/scenes/reachy", + StaticFiles(directory=str(original_mjcf_dir)), + name="wasm_original_models", + ) + + # Mount general path last + app.mount( + "/wasm/dist", + StaticFiles(directory=str(wasm_bin_dir)), + name="wasm", + ) + + # Add redirects for React app assets when accessed from iframe + from fastapi.responses import RedirectResponse + + @app.get("/assets/{file_path}") + async def get_asset_redirect(file_path: str): + # Handle versioned files by mapping to generic names + if file_path.startswith("mujoco_wasm-") and file_path.endswith(".wasm"): + return RedirectResponse(url="/wasm/dist/assets/mujoco_wasm.wasm") + elif file_path.startswith("mujoco_wasm-") and file_path.endswith(".js"): + return RedirectResponse(url="/wasm/dist/assets/mujoco_wasm.js") + elif file_path.startswith("index-") and file_path.endswith(".js"): + return RedirectResponse(url="/wasm/dist/assets/index.js") + elif file_path.startswith("index-") and file_path.endswith(".css"): + return RedirectResponse(url="/wasm/dist/assets/index.css") + else: + return RedirectResponse(url=f"/wasm/dist/assets/{file_path}") + + @app.get("/vite.svg") + async def get_vite_svg(): + return RedirectResponse(url="/wasm/dist/vite.svg") + + # Also add direct redirects for the full paths + @app.get("/wasm/dist/assets/mujoco_wasm-{hash}.wasm") + async def get_versioned_mujoco_wasm(hash: str): + return RedirectResponse(url="/wasm/dist/assets/mujoco_wasm.wasm") + + @app.get("/wasm/dist/assets/mujoco_wasm-{hash}.js") + async def get_versioned_mujoco_js(hash: str): + return RedirectResponse(url="/wasm/dist/assets/mujoco_wasm.js") + + @app.get("/wasm/dist/assets/index-{hash}.js") + async def get_versioned_index_js(hash: str): + return RedirectResponse(url="/wasm/dist/assets/index.js") + + @app.get("/wasm/dist/assets/index-{hash}.css") + async def get_versioned_index_css(hash: str): + return RedirectResponse(url="/wasm/dist/assets/index.css") + + # Add proper MIME types and COOP/COEP headers for WASM + @app.middleware("http") + async def add_wasm_headers(request: Request, call_next): + response = await call_next(request) + + # Add MIME types + if request.url.path.endswith('.wasm'): + response.headers["content-type"] = "application/wasm" + elif request.url.path.endswith('.js') and '/wasm/' in request.url.path: + response.headers["content-type"] = "text/javascript" + + # Add Cross-Origin headers for WASM Web Workers + response.headers["Cross-Origin-Opener-Policy"] = "same-origin" + response.headers["Cross-Origin-Embedder-Policy"] = "require-corp" + + return response + + else: + print(f"Warning: WASM bin directory not found at {wasm_bin_dir}") + print("Run the build script to generate the bin directory with generic asset names") + return app diff --git a/src/reachy_mini/daemon/app/templates/dashboard.html b/src/reachy_mini/daemon/app/templates/dashboard.html index f667c514..a35c841c 100644 --- a/src/reachy_mini/daemon/app/templates/dashboard.html +++ b/src/reachy_mini/daemon/app/templates/dashboard.html @@ -9,32 +9,141 @@

Dashboard

-
-

Daemon Control

-
Loading daemon status...
- - - - - -
-
-

Video stream

-
-
- + + +
+
+ + + + +
+ + +
+
+

Daemon Control

+
Loading daemon status...
+ + + + + +
+
+ + +
+

MuJoCo Renderer

+ +
+
Loading MuJoCo Renderer...
+
+
+ + +
+

Video Stream

+
+
+ +
+
+ + +
+ + +
+

Examples

+
    + {% for fname in files %} +
  • {{ fname }}
  • + {% endfor %} +
- - -

Examples

- + \ No newline at end of file diff --git a/src/reachy_mini/daemon/app/wasm/.gitattributes b/src/reachy_mini/daemon/app/wasm/.gitattributes new file mode 100644 index 00000000..a6344aac --- /dev/null +++ b/src/reachy_mini/daemon/app/wasm/.gitattributes @@ -0,0 +1,35 @@ +*.7z filter=lfs diff=lfs merge=lfs -text +*.arrow filter=lfs diff=lfs merge=lfs -text +*.bin filter=lfs diff=lfs merge=lfs -text +*.bz2 filter=lfs diff=lfs merge=lfs -text +*.ckpt filter=lfs diff=lfs merge=lfs -text +*.ftz filter=lfs diff=lfs merge=lfs -text +*.gz filter=lfs diff=lfs merge=lfs -text +*.h5 filter=lfs diff=lfs merge=lfs -text +*.joblib filter=lfs diff=lfs merge=lfs -text +*.lfs.* filter=lfs diff=lfs merge=lfs -text +*.mlmodel filter=lfs diff=lfs merge=lfs -text +*.model filter=lfs diff=lfs merge=lfs -text +*.msgpack filter=lfs diff=lfs merge=lfs -text +*.npy filter=lfs diff=lfs merge=lfs -text +*.npz filter=lfs diff=lfs merge=lfs -text +*.onnx filter=lfs diff=lfs merge=lfs -text +*.ot filter=lfs diff=lfs merge=lfs -text +*.parquet filter=lfs diff=lfs merge=lfs -text +*.pb filter=lfs diff=lfs merge=lfs -text +*.pickle filter=lfs diff=lfs merge=lfs -text +*.pkl filter=lfs diff=lfs merge=lfs -text +*.pt filter=lfs diff=lfs merge=lfs -text +*.pth filter=lfs diff=lfs merge=lfs -text +*.rar filter=lfs diff=lfs merge=lfs -text +*.safetensors filter=lfs diff=lfs merge=lfs -text +saved_model/**/* filter=lfs diff=lfs merge=lfs -text +*.tar.* filter=lfs diff=lfs merge=lfs -text +*.tar filter=lfs diff=lfs merge=lfs -text +*.tflite filter=lfs diff=lfs merge=lfs -text +*.tgz filter=lfs diff=lfs merge=lfs -text +*.wasm filter=lfs diff=lfs merge=lfs -text +*.xz filter=lfs diff=lfs merge=lfs -text +*.zip filter=lfs diff=lfs merge=lfs -text +*.zst filter=lfs diff=lfs merge=lfs -text +*tfevents* filter=lfs diff=lfs merge=lfs -text diff --git a/src/reachy_mini/daemon/app/wasm/.gitignore b/src/reachy_mini/daemon/app/wasm/.gitignore new file mode 100644 index 00000000..7a09779c --- /dev/null +++ b/src/reachy_mini/daemon/app/wasm/.gitignore @@ -0,0 +1,41 @@ +# Build outputs (ignore dist but keep bin for production) +dist/ +dist +# Keep bin directory for production deployment +!bin/ + +# Node.js dependencies +node_modules/ +package-lock.json +pnpm-lock.yaml + +# Build artifacts +build_workspace/ +emsdk/ + +# MuJoCo web fork build artifacts +fork/mujoco_web/build/ +fork/mujoco_web/dist/ +fork/mujoco_web/node_modules/ +fork/mujoco_web/pnpm-lock.yaml + +# WASM build artifacts +*.wasm +*.wasm.map +# But keep WASM files in bin directory for production +!bin/**/*.wasm + +# CMake build artifacts +CMakeCache.txt +CMakeFiles/ +cmake_install.cmake +Makefile + +# Temporary files +*.tmp +*.temp +.DS_Store +Thumbs.db + +# Generated package files +package.json \ No newline at end of file diff --git a/src/reachy_mini/daemon/app/wasm/bin/assets/index.css b/src/reachy_mini/daemon/app/wasm/bin/assets/index.css new file mode 100644 index 00000000..86dc9570 --- /dev/null +++ b/src/reachy_mini/daemon/app/wasm/bin/assets/index.css @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:f3e1e54eabccff6e471ba2f174dbc89618c4895dcc260cf26c7368cfab702e7e +size 6428 diff --git a/src/reachy_mini/daemon/app/wasm/bin/assets/index.js b/src/reachy_mini/daemon/app/wasm/bin/assets/index.js new file mode 100644 index 00000000..92d29d3c --- /dev/null +++ b/src/reachy_mini/daemon/app/wasm/bin/assets/index.js @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:4bf617a36adcbf15c6a412bf32a656f526773105346320c0d4dd15b8c9a58878 +size 1171845 diff --git a/src/reachy_mini/daemon/app/wasm/bin/assets/mujoco_wasm.js b/src/reachy_mini/daemon/app/wasm/bin/assets/mujoco_wasm.js new file mode 100644 index 00000000..ef563a21 --- /dev/null +++ b/src/reachy_mini/daemon/app/wasm/bin/assets/mujoco_wasm.js @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:ff660a3616cbadd83bf78f94e695a594840b9d8b1548f4f1336b55b5b2a4bb07 +size 103458 diff --git a/src/reachy_mini/daemon/app/wasm/bin/assets/mujoco_wasm.wasm b/src/reachy_mini/daemon/app/wasm/bin/assets/mujoco_wasm.wasm new file mode 100644 index 00000000..bd4d4de6 --- /dev/null +++ b/src/reachy_mini/daemon/app/wasm/bin/assets/mujoco_wasm.wasm @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:2846e4293b399265c943e23ca703c666730354806a3b5f084ce032d50d228775 +size 6750430 diff --git a/src/reachy_mini/daemon/app/wasm/bin/index.html b/src/reachy_mini/daemon/app/wasm/bin/index.html new file mode 100644 index 00000000..111da470 --- /dev/null +++ b/src/reachy_mini/daemon/app/wasm/bin/index.html @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:4b029085610e71ec9f4346ef4f69c6b24bc97d0b498868f673cc61b39f28011d +size 655 diff --git a/src/reachy_mini/daemon/app/wasm/bin/mujoco.js b/src/reachy_mini/daemon/app/wasm/bin/mujoco.js new file mode 100644 index 00000000..0301b28f --- /dev/null +++ b/src/reachy_mini/daemon/app/wasm/bin/mujoco.js @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:59125e3ef20b0cae66935844d86dab0f3354e52f9fded17a9bb6425cbab29f25 +size 270512 diff --git a/src/reachy_mini/daemon/app/wasm/bin/mujoco.wasm b/src/reachy_mini/daemon/app/wasm/bin/mujoco.wasm new file mode 100755 index 00000000..bd4d4de6 --- /dev/null +++ b/src/reachy_mini/daemon/app/wasm/bin/mujoco.wasm @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:2846e4293b399265c943e23ca703c666730354806a3b5f084ce032d50d228775 +size 6750430 diff --git a/src/reachy_mini/daemon/app/wasm/bin/vite.svg b/src/reachy_mini/daemon/app/wasm/bin/vite.svg new file mode 100644 index 00000000..bf6ee755 --- /dev/null +++ b/src/reachy_mini/daemon/app/wasm/bin/vite.svg @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:4a748afd443918bb16591c834c401dae33e87861ab5dbad0811c3a3b4a9214fb +size 1497 diff --git a/src/reachy_mini/daemon/app/wasm/build.sh b/src/reachy_mini/daemon/app/wasm/build.sh new file mode 100755 index 00000000..ecc3eb18 --- /dev/null +++ b/src/reachy_mini/daemon/app/wasm/build.sh @@ -0,0 +1,272 @@ +#!/bin/bash + +# Build script for MuJoCo WASM React App +# Replicates the multi-stage Docker build process +# Usage: ./build.sh [MODEL_PATH] +# Example: ./build.sh /home/user/reachy_mini/descriptions/reachy_mini/mjcf + +set -e # Exit on any error + +# Check if model path is provided +if [[ -z "$1" ]]; then + echo "โŒ Error: Model path is required" + echo "Usage: ./build.sh MODEL_PATH" + echo "Example: ./build.sh /home/cdussieux/dev/reachy_mini/reachy_mini/src/reachy_mini/descriptions/reachy_mini/mjcf" + exit 1 +fi + +MODEL_PATH="$1" + +# Validate model path exists +if [[ ! -d "$MODEL_PATH" ]]; then + echo "โŒ Error: Model path does not exist: $MODEL_PATH" + exit 1 +fi + +echo "๐Ÿš€ Starting MuJoCo WASM React App build..." +echo "๐Ÿ“ Using model path: $MODEL_PATH" + +# Colors for output +RED='\033[0;31m' +GREEN='\033[0;32m' +YELLOW='\033[1;33m' +BLUE='\033[0;34m' +NC='\033[0m' # No Color + +# Function to print colored output +print_step() { + echo -e "${BLUE}๐Ÿ“‹ $1${NC}" +} + +print_success() { + echo -e "${GREEN}โœ… $1${NC}" +} + +print_warning() { + echo -e "${YELLOW}โš ๏ธ $1${NC}" +} + +print_error() { + echo -e "${RED}โŒ $1${NC}" +} + +# Check prerequisites +print_step "Checking prerequisites..." + +# Check if we're in the right directory +if [[ ! -f "server.js" || ! -d "mujoco_web" ]]; then + print_error "This script must be run from the directory containing server.js and mujoco_web submodule" + exit 1 +fi + +# Check if required tools are installed +command -v node >/dev/null 2>&1 || { print_error "Node.js is required but not installed. Aborting."; exit 1; } +command -v npm >/dev/null 2>&1 || { print_error "npm is required but not installed. Aborting."; exit 1; } +command -v cmake >/dev/null 2>&1 || { print_error "cmake is required but not installed. Aborting."; exit 1; } +command -v git >/dev/null 2>&1 || { print_error "git is required but not installed. Aborting."; exit 1; } + +print_success "Prerequisites check passed" + +# Install pnpm if not available +if ! command -v pnpm >/dev/null 2>&1; then + print_step "Installing pnpm..." + npm install -g pnpm + print_success "pnpm installed" +fi + +# Check for Emscripten SDK +if [[ ! -d "/emsdk" ]]; then + print_step "Installing Emscripten SDK..." + + # Clone emsdk if it doesn't exist + if [[ ! -d "emsdk" ]]; then + git clone https://github.com/emscripten-core/emsdk.git emsdk + fi + + cd emsdk + ./emsdk install latest + ./emsdk activate latest + cd .. + + export EMSDK="$(pwd)/emsdk" + export PATH="$EMSDK:$EMSDK/node/$(ls $EMSDK/node)/bin:$EMSDK/upstream/emscripten:$EMSDK/upstream/bin:$PATH" + + print_success "Emscripten SDK installed and configured" +else + print_step "Using existing Emscripten SDK..." + export EMSDK=/emsdk + export PATH="/emsdk:/emsdk/node/$(ls /emsdk/node)/bin:/emsdk/upstream/emscripten:/emsdk/upstream/bin:$PATH" +fi + +# Create workspace directory +WORKSPACE_DIR="build_workspace" +print_step "Setting up workspace: $WORKSPACE_DIR" + +rm -rf "$WORKSPACE_DIR" +mkdir -p "$WORKSPACE_DIR" + +# Copy mujoco_web submodule +print_step "Copying MuJoCo web submodule..." +cp -r mujoco_web "$WORKSPACE_DIR/" +print_success "MuJoCo web submodule copied" + +# Copy model files from provided path +print_step "Copying model files from: $MODEL_PATH" +mkdir -p "$WORKSPACE_DIR/models" +cp -r "$MODEL_PATH"/* "$WORKSPACE_DIR/models/" +print_success "Model files copied" + +# Build MuJoCo WASM +print_step "Building MuJoCo WASM..." +cd "$WORKSPACE_DIR/mujoco_web" + +# Build using emscripten +emcmake cmake -B build -H. -DCMAKE_BUILD_TYPE=Release +cmake --build build -j"$(nproc)" + +print_success "MuJoCo WASM build completed" + +# Copy WASM outputs to public directory +print_step "Copying WASM files to public directory..." +mkdir -p public +cp src/wasm/mujoco_wasm.wasm public/mujoco.wasm +cp src/wasm/mujoco_wasm.js public/mujoco.js + +print_success "WASM files copied" + +# Copy model files to public directory +print_step "Copying models to public directory..." +mkdir -p public/models/reachy +cp -r ../models/* public/models/reachy/ + +print_success "Models copied to public directory" + +# Build React app +print_step "Installing React dependencies..." +pnpm install + +print_success "Dependencies installed" + +# Backup WASM files for light builds (save to main directory) +print_step "Backing up WASM files for light builds..." +mkdir -p ../../mujoco_web/wasm_backup +cp src/wasm/mujoco_wasm.wasm ../../mujoco_web/wasm_backup/ +cp src/wasm/mujoco_wasm.js ../../mujoco_web/wasm_backup/ +print_success "WASM files backed up to main directory" + +print_step "Patching mujoco_wasm.js for Vite..." +sed -i 's/, {/, \/* @vite-ignore *\/ {/' src/wasm/mujoco_wasm.js + +print_step "Building React application..." +pnpm run build + +print_success "React build completed" + +# Go back to original directory +cd ../.. + +# Set up production files +print_step "Setting up production files..." +rm -rf dist +mkdir -p dist + +# Copy built React app +cp -r "$WORKSPACE_DIR/mujoco_web/dist"/* dist/ + +# Copy model scenes to the correct location +print_step "Copying model scenes to dist..." +mkdir -p dist/examples/scenes/reachy +cp -r "$WORKSPACE_DIR/models"/* dist/examples/scenes/reachy/ + +print_success "Production files ready" + +# Create or update package.json for production server +print_step "Creating production package.json..." +cat > package.json << EOF +{ + "name": "mujoco_web_server", + "version": "1.0.0", + "main": "server.js", + "scripts": { + "start": "node server.js", + "build": "./build.sh" + }, + "dependencies": { + "express": "^4.18.2" + } +} +EOF + +# Install production dependencies +if [[ ! -d "node_modules" ]]; then + print_step "Installing production dependencies..." + npm install --production + print_success "Production dependencies installed" +fi + +# Create bin directory with essential files for FastAPI app +print_step "Creating bin directory with essential app files..." +rm -rf bin +mkdir -p bin/assets + +# Copy essential files for FastAPI integration +cp dist/vite.svg bin/ +cp dist/mujoco.js bin/ +cp dist/mujoco.wasm bin/ + +# Create index.html with generic asset references +sed 's|/assets/index-[^.]*\.js|/assets/index.js|g; s|/assets/index-[^.]*\.css|/assets/index.css|g' dist/index.html > bin/index.html +print_success "Created index.html with generic asset references" + +# Copy React app assets with generic names +INDEX_JS=$(ls dist/assets/index-*.js 2>/dev/null | head -1) +INDEX_CSS=$(ls dist/assets/index-*.css 2>/dev/null | head -1) + +if [[ -n "$INDEX_JS" ]]; then + cp "$INDEX_JS" bin/assets/index.js + print_success "Copied $(basename "$INDEX_JS") as index.js" +fi + +if [[ -n "$INDEX_CSS" ]]; then + cp "$INDEX_CSS" bin/assets/index.css + print_success "Copied $(basename "$INDEX_CSS") as index.css" +fi + +# Copy MuJoCo WASM assets with generic names +MUJOCO_WASM=$(ls dist/assets/mujoco_wasm-*.wasm 2>/dev/null | head -1) +MUJOCO_JS=$(ls dist/assets/mujoco_wasm-*.js 2>/dev/null | head -1) + +if [[ -n "$MUJOCO_WASM" ]]; then + cp "$MUJOCO_WASM" bin/assets/mujoco_wasm.wasm + print_success "Copied $(basename "$MUJOCO_WASM") as mujoco_wasm.wasm" +fi + +if [[ -n "$MUJOCO_JS" ]]; then + cp "$MUJOCO_JS" bin/assets/mujoco_wasm.js + print_success "Copied $(basename "$MUJOCO_JS") as mujoco_wasm.js" +fi + +# Copy model files and examples directory structure +print_step "Copying model files and examples..." +if [[ -d "dist/examples" ]]; then + cp -r dist/examples bin/ + print_success "Examples and model files copied" +fi + +if [[ -d "dist/models" ]]; then + cp -r dist/models bin/ + print_success "Model files copied" +fi + +print_success "Essential app files copied to bin directory with generic names" + +# Cleanup workspace +print_step "Cleaning up workspace..." +rm -rf "$WORKSPACE_DIR" +print_success "Workspace cleaned up" + +print_success "๐ŸŽ‰ Build completed successfully!" +echo "" +echo -e "${GREEN}๐Ÿ“ Built files are in: ./dist${NC}" +echo -e "${GREEN}๐Ÿš€ To start the server: npm start${NC}" +echo -e "${GREEN}๐ŸŒ Server will run on: http://localhost:7860${NC}" \ No newline at end of file diff --git a/src/reachy_mini/daemon/app/wasm/clean.sh b/src/reachy_mini/daemon/app/wasm/clean.sh new file mode 100755 index 00000000..7d64ab50 --- /dev/null +++ b/src/reachy_mini/daemon/app/wasm/clean.sh @@ -0,0 +1,174 @@ +#!/bin/bash + +# Clean script for MuJoCo WASM React App +# Removes ALL build artifacts and returns to clean state + +set -e # Exit on any error + +echo "๐Ÿงน Starting complete cleanup..." + +# Colors for output +RED='\033[0;31m' +GREEN='\033[0;32m' +YELLOW='\033[1;33m' +BLUE='\033[0;34m' +NC='\033[0m' # No Color + +# Function to print colored output +print_step() { + echo -e "${BLUE}๐Ÿ“‹ $1${NC}" +} + +print_success() { + echo -e "${GREEN}โœ… $1${NC}" +} + +print_warning() { + echo -e "${YELLOW}โš ๏ธ $1${NC}" +} + +print_error() { + echo -e "${RED}โŒ $1${NC}" +} + +print_warning "Removing ALL build artifacts and dependencies..." +echo "Files being removed:" +echo " - dist/" +echo " - node_modules/" +echo " - package.json" +echo " - package-lock.json" +echo " - pnpm-lock.yaml" +echo " - build_workspace/" +echo " - emsdk/" +echo " - Any CMake build directories" +echo "" + +print_step "Removing distribution files..." +if [[ -d "dist" ]]; then + rm -rf dist + print_success "Removed dist/" +else + print_warning "dist/ not found" +fi + +print_step "Removing Node.js dependencies..." +if [[ -d "node_modules" ]]; then + rm -rf node_modules + print_success "Removed node_modules/" +else + print_warning "node_modules/ not found" +fi + +print_step "Removing package files..." +if [[ -f "package.json" ]]; then + rm -f package.json + print_success "Removed package.json" +else + print_warning "package.json not found" +fi + +if [[ -f "package-lock.json" ]]; then + rm -f package-lock.json + print_success "Removed package-lock.json" +else + print_warning "package-lock.json not found" +fi + +if [[ -f "pnpm-lock.yaml" ]]; then + rm -f pnpm-lock.yaml + print_success "Removed pnpm-lock.yaml" +else + print_warning "pnpm-lock.yaml not found" +fi + +print_step "Removing build workspace..." +if [[ -d "build_workspace" ]]; then + rm -rf build_workspace + print_success "Removed build_workspace/" +else + print_warning "build_workspace/ not found" +fi + +print_step "Removing Emscripten SDK..." +if [[ -d "emsdk" ]]; then + rm -rf emsdk + print_success "Removed emsdk/" +else + print_warning "emsdk/ not found" +fi + +print_step "Cleaning MuJoCo web fork build artifacts..." +if [[ -d "fork/mujoco_web/build" ]]; then + rm -rf fork/mujoco_web/build + print_success "Removed fork/mujoco_web/build/" +else + print_warning "fork/mujoco_web/build/ not found" +fi + +if [[ -d "fork/mujoco_web/dist" ]]; then + rm -rf fork/mujoco_web/dist + print_success "Removed fork/mujoco_web/dist/" +else + print_warning "fork/mujoco_web/dist/ not found" +fi + +if [[ -d "fork/mujoco_web/node_modules" ]]; then + rm -rf fork/mujoco_web/node_modules + print_success "Removed fork/mujoco_web/node_modules/" +else + print_warning "fork/mujoco_web/node_modules/ not found" +fi + +if [[ -d "fork/mujoco_web/wasm_backup" ]]; then + rm -rf fork/mujoco_web/wasm_backup + print_success "Removed fork/mujoco_web/wasm_backup/" +else + print_warning "fork/mujoco_web/wasm_backup/ not found" +fi + +if [[ -d "fork/mujoco_web/public" ]]; then + rm -rf fork/mujoco_web/public + print_success "Removed fork/mujoco_web/public/" +else + print_warning "fork/mujoco_web/public/ not found" +fi + +if [[ -f "fork/mujoco_web/pnpm-lock.yaml" ]]; then + rm -f fork/mujoco_web/pnpm-lock.yaml + print_success "Removed fork/mujoco_web/pnpm-lock.yaml" +else + print_warning "fork/mujoco_web/pnpm-lock.yaml not found" +fi + +print_step "Cleaning WASM build artifacts..." +if [[ -d "fork/mujoco_web/src/wasm" ]]; then + find fork/mujoco_web/src/wasm -name "*.wasm" -delete 2>/dev/null || true + find fork/mujoco_web/src/wasm -name "*.js" -delete 2>/dev/null || true + find fork/mujoco_web/src/wasm -name "*.wasm.map" -delete 2>/dev/null || true + print_success "Cleaned WASM artifacts from fork/mujoco_web/src/wasm/" +fi + +print_step "Removing any temporary files..." +find . -name "*.tmp" -delete 2>/dev/null || true +find . -name "*.temp" -delete 2>/dev/null || true +find . -name ".DS_Store" -delete 2>/dev/null || true +find . -name "Thumbs.db" -delete 2>/dev/null || true + +print_step "Cleaning CMake caches..." +find . -name "CMakeCache.txt" -delete 2>/dev/null || true +find . -name "CMakeFiles" -type d -exec rm -rf {} + 2>/dev/null || true +find . -name "cmake_install.cmake" -delete 2>/dev/null || true +find . -name "Makefile" -not -path "./fork/mujoco_web/Makefile" -delete 2>/dev/null || true + +print_success "Temporary files cleaned" + +# Reset any environment variables that might have been set +print_step "Resetting environment variables..." +unset EMSDK 2>/dev/null || true +print_success "Environment variables reset" + +print_success "๐ŸŽ‰ Complete cleanup finished!" +echo "" +echo -e "${GREEN}๐Ÿงผ Repository is now in clean state${NC}" +echo -e "${GREEN}๐Ÿ“ Only source files remain (Dockerfile, server.js, fork/, stewart_little_control/, etc.)${NC}" +echo -e "${GREEN}๐Ÿš€ Ready for fresh build with: ./build.sh${NC}" \ No newline at end of file diff --git a/src/reachy_mini/daemon/app/wasm/light_build.sh b/src/reachy_mini/daemon/app/wasm/light_build.sh new file mode 100755 index 00000000..43859ba5 --- /dev/null +++ b/src/reachy_mini/daemon/app/wasm/light_build.sh @@ -0,0 +1,211 @@ +#!/bin/bash + +# Light Build script for MuJoCo WASM React App +# Only rebuilds the React app part, skipping MuJoCo WASM compilation + +set -e # Exit on any error + +echo "๐Ÿš€ Starting light build (React app only)..." + +# Colors for output +RED='\033[0;31m' +GREEN='\033[0;32m' +YELLOW='\033[1;33m' +BLUE='\033[0;34m' +NC='\033[0m' # No Color + +# Function to print colored output +print_step() { + echo -e "${BLUE}๐Ÿ“‹ $1${NC}" +} + +print_success() { + echo -e "${GREEN}โœ… $1${NC}" +} + +print_warning() { + echo -e "${YELLOW}โš ๏ธ $1${NC}" +} + +print_error() { + echo -e "${RED}โŒ $1${NC}" +} + +# Check if model path is provided +if [[ -z "$1" ]]; then + echo "โŒ Error: Model path is required" + echo "Usage: ./light_build.sh MODEL_PATH" + echo "Example: ./light_build.sh /home/cdussieux/dev/reachy_mini/reachy_mini/src/reachy_mini/descriptions/reachy_mini/mjcf" + exit 1 +fi + +MODEL_PATH="$1" + +# Validate model path exists +if [[ ! -d "$MODEL_PATH" ]]; then + echo "โŒ Error: Model path does not exist: $MODEL_PATH" + exit 1 +fi + +echo "๐Ÿ“ Using model path: $MODEL_PATH" + +# Check if MuJoCo WASM files exist and restore them if needed +print_step "Checking for MuJoCo WASM files..." + +# Check if backup files exist (from full build) +if [[ -f "mujoco_web/wasm_backup/mujoco_wasm.wasm" ]] && [[ -f "mujoco_web/wasm_backup/mujoco_wasm.js" ]]; then + print_step "Restoring WASM files from backup..." + + # Restore original WASM files from backup + mkdir -p mujoco_web/src/wasm + cp mujoco_web/wasm_backup/mujoco_wasm.wasm mujoco_web/src/wasm/ + cp mujoco_web/wasm_backup/mujoco_wasm.js mujoco_web/src/wasm/ + + print_success "WASM files restored from backup" +# Check if files already exist in source directory +elif [[ -f "mujoco_web/src/wasm/mujoco_wasm.wasm" ]] && [[ -f "mujoco_web/src/wasm/mujoco_wasm.js" ]]; then + print_success "WASM files found in source directory" +else + print_error "MuJoCo WASM files not found! Please run full build first: ./build.sh" + print_error "Checked locations:" + echo " - mujoco_web/wasm_backup/" + echo " - mujoco_web/src/wasm/" + exit 1 +fi + +# Check for pnpm +if ! command -v pnpm >/dev/null 2>&1; then + print_step "Installing pnpm..." + npm install -g pnpm + print_success "pnpm installed" +fi + +# Work in the mujoco_web directory +cd mujoco_web + +print_step "Updating model files in public directory..." + +# Ensure public directory exists +mkdir -p public/models/reachy + +# Copy model files to public directory +cp -r "$MODEL_PATH"/* public/models/reachy/ +print_success "Model files updated in public directory" + +# Copy WASM files to public directory (in case they're missing) +print_step "Ensuring WASM files are in public directory..." +mkdir -p public +cp src/wasm/mujoco_wasm.wasm public/mujoco.wasm +cp src/wasm/mujoco_wasm.js public/mujoco.js +print_success "WASM files ready" + +# Install dependencies if node_modules doesn't exist +if [[ ! -d "node_modules" ]]; then + print_step "Installing React dependencies..." + pnpm install + print_success "Dependencies installed" +else + print_step "Dependencies already installed, skipping..." +fi + +print_step "Patching mujoco_wasm.js for Vite..." +sed -i 's/, {/, \/* @vite-ignore *\/ {/' src/wasm/mujoco_wasm.js + +print_step "Building React application..." +pnpm run build +print_success "React build completed" + +# Go back to original directory +cd ../.. + +# Set up production files +print_step "Setting up production files..." +rm -rf dist +mkdir -p dist + +# Copy built React app +cp -r mujoco_web/dist/* dist/ + +# Copy model scenes to the correct location +print_step "Copying model scenes to dist..." +mkdir -p dist/examples/scenes/reachy +cp -r "$MODEL_PATH"/* dist/examples/scenes/reachy/ +print_success "Production files ready" + +# Create or update package.json for production server +print_step "Creating production package.json..." +cat > package.json << EOF +{ + "name": "mujoco_web_server", + "version": "1.0.0", + "main": "server.js", + "scripts": { + "start": "node server.js", + "build": "./build.sh", + "light-build": "./light_build.sh" + }, + "dependencies": { + "express": "^4.18.2" + } +} +EOF + +# Install production dependencies if needed +if [[ ! -d "node_modules" ]]; then + print_step "Installing production dependencies..." + npm install --production + print_success "Production dependencies installed" +fi + +# Create bin directory with essential files for FastAPI app +print_step "Creating bin directory with essential app files..." +rm -rf bin +mkdir -p bin/assets + +# Copy essential files for FastAPI integration +cp dist/vite.svg bin/ +cp dist/mujoco.js bin/ +cp dist/mujoco.wasm bin/ + +# Create index.html with generic asset references +sed 's|/assets/index-[^.]*\.js|/assets/index.js|g; s|/assets/index-[^.]*\.css|/assets/index.css|g' dist/index.html > bin/index.html +print_success "Created index.html with generic asset references" + +# Copy React app assets with generic names +INDEX_JS=$(ls dist/assets/index-*.js 2>/dev/null | head -1) +INDEX_CSS=$(ls dist/assets/index-*.css 2>/dev/null | head -1) + +if [[ -n "$INDEX_JS" ]]; then + cp "$INDEX_JS" bin/assets/index.js + print_success "Copied $(basename "$INDEX_JS") as index.js" +fi + +if [[ -n "$INDEX_CSS" ]]; then + cp "$INDEX_CSS" bin/assets/index.css + print_success "Copied $(basename "$INDEX_CSS") as index.css" +fi + +# Copy MuJoCo WASM assets with generic names +MUJOCO_WASM=$(ls dist/assets/mujoco_wasm-*.wasm 2>/dev/null | head -1) +MUJOCO_JS=$(ls dist/assets/mujoco_wasm-*.js 2>/dev/null | head -1) + +if [[ -n "$MUJOCO_WASM" ]]; then + cp "$MUJOCO_WASM" bin/assets/mujoco_wasm.wasm + print_success "Copied $(basename "$MUJOCO_WASM") as mujoco_wasm.wasm" +fi + +if [[ -n "$MUJOCO_JS" ]]; then + cp "$MUJOCO_JS" bin/assets/mujoco_wasm.js + print_success "Copied $(basename "$MUJOCO_JS") as mujoco_wasm.js" +fi + +print_success "Essential app files copied to bin directory with generic names" + +print_success "๐ŸŽ‰ Light build completed successfully!" +echo "" +echo -e "${GREEN}๐Ÿ“ Built files are in: ./dist${NC}" +echo -e "${GREEN}๐Ÿš€ To start the server: npm start${NC}" +echo -e "${GREEN}๐ŸŒ Server will run on: http://localhost:7860${NC}" +echo "" +echo -e "${YELLOW}๐Ÿ’ก Note: This was a light build (React only)${NC}" +echo -e "${YELLOW}๐Ÿ’ก If you need to rebuild MuJoCo WASM, use: ./build.sh${NC}" \ No newline at end of file diff --git a/src/reachy_mini/daemon/app/wasm/server.js b/src/reachy_mini/daemon/app/wasm/server.js new file mode 100644 index 00000000..85eafedf --- /dev/null +++ b/src/reachy_mini/daemon/app/wasm/server.js @@ -0,0 +1,26 @@ +// server.js +const express = require('express'); +const path = require('path'); + +const app = express(); + +// Inject COOP/COEP headers +app.use((req, res, next) => { + res.setHeader('Cross-Origin-Opener-Policy', 'same-origin'); + res.setHeader('Cross-Origin-Embedder-Policy', 'require-corp'); + res.setHeader('Cross-Origin-Resource-Policy', 'cross-origin'); + + next(); +}); + +// Static serve +app.use(express.static(path.join(process.cwd(), 'dist'))); + +// SPA fallback +app.get('*', (req, res) => { + res.sendFile(path.join(process.cwd(), 'dist', 'index.html')); +}); + +const port = process.env.PORT || 7860; +console.log(`Listening on port ${port}`); +app.listen(port);