Spaces:
Sleeping
Sleeping
inital upload
Browse files- Dockerfile +28 -0
- app.py +57 -0
- nginx.conf +28 -0
- requirements.txt +5 -0
- robots.py +66 -0
- run.sh +11 -0
Dockerfile
ADDED
|
@@ -0,0 +1,28 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
FROM python:3.11-slim
|
| 2 |
+
|
| 3 |
+
# non-interactive apt + wheels only (faster, avoids abuse flags)
|
| 4 |
+
ENV DEBIAN_FRONTEND=noninteractive
|
| 5 |
+
ENV PIP_DISABLE_PIP_VERSION_CHECK=1
|
| 6 |
+
ENV PIP_NO_CACHE_DIR=1
|
| 7 |
+
ENV PIP_ONLY_BINARY=:all:
|
| 8 |
+
|
| 9 |
+
# only nginx (no supervisor)
|
| 10 |
+
RUN apt-get update && apt-get install -y --no-install-recommends \
|
| 11 |
+
nginx \
|
| 12 |
+
&& rm -rf /var/lib/apt/lists/*
|
| 13 |
+
|
| 14 |
+
WORKDIR /workspace
|
| 15 |
+
|
| 16 |
+
# your working versions
|
| 17 |
+
COPY requirements.txt ./
|
| 18 |
+
RUN pip install -r requirements.txt
|
| 19 |
+
|
| 20 |
+
# app + nginx config + entrypoint
|
| 21 |
+
COPY app.py robots.py nginx.conf run.sh ./
|
| 22 |
+
RUN chmod +x /workspace/run.sh \
|
| 23 |
+
&& rm -f /etc/nginx/nginx.conf \
|
| 24 |
+
&& ln -s /workspace/nginx.conf /etc/nginx/nginx.conf
|
| 25 |
+
|
| 26 |
+
EXPOSE 7860
|
| 27 |
+
CMD ["/workspace/run.sh"]
|
| 28 |
+
|
app.py
ADDED
|
@@ -0,0 +1,57 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import gradio as gr
|
| 2 |
+
import numpy as np
|
| 3 |
+
from robots import LiveRobot
|
| 4 |
+
|
| 5 |
+
ROBOTS = ["ur5", "ur10", "tiago", "talos", "icub"]
|
| 6 |
+
|
| 7 |
+
# single global instance to keep the MeshCat connection alive
|
| 8 |
+
robot = LiveRobot("ur5")
|
| 9 |
+
|
| 10 |
+
with gr.Blocks(title="Pinocchio + MeshCat — Live") as demo:
|
| 11 |
+
gr.Markdown("### 🤖 Pinocchio + MeshCat — Live Viewer\n"
|
| 12 |
+
"Sliders update the robot in real time (no reload).")
|
| 13 |
+
|
| 14 |
+
with gr.Row():
|
| 15 |
+
robot_dd = gr.Dropdown(ROBOTS, value="ur5", label="Robot")
|
| 16 |
+
neutral_btn = gr.Button("Neutral")
|
| 17 |
+
|
| 18 |
+
viewer = gr.HTML(robot.iframe())
|
| 19 |
+
|
| 20 |
+
# sliders are created based on current robot model
|
| 21 |
+
slider_group = gr.Group()
|
| 22 |
+
sliders = []
|
| 23 |
+
for label, (lo, hi), qi in zip(robot.labels, robot.bounds, robot.idxs):
|
| 24 |
+
sliders.append(gr.Slider(minimum=lo, maximum=hi, step=0.01,
|
| 25 |
+
value=float(robot.q[qi]), label=label))
|
| 26 |
+
|
| 27 |
+
def on_sliders(*vals):
|
| 28 |
+
robot.set_joints(vals)
|
| 29 |
+
# return nothing for viewer; iframe stays connected
|
| 30 |
+
return gr.update()
|
| 31 |
+
|
| 32 |
+
for s in sliders:
|
| 33 |
+
s.change(on_sliders, inputs=sliders, outputs=viewer, queue=False)
|
| 34 |
+
|
| 35 |
+
def on_neutral():
|
| 36 |
+
# reset joints and reflect slider values
|
| 37 |
+
values = robot.neutral()
|
| 38 |
+
updates = [gr.update(value=v) for v in values]
|
| 39 |
+
return [gr.update()] + updates
|
| 40 |
+
|
| 41 |
+
neutral_btn.click(on_neutral, outputs=[viewer] + sliders, queue=False)
|
| 42 |
+
|
| 43 |
+
def on_change_robot(name):
|
| 44 |
+
global robot
|
| 45 |
+
robot = LiveRobot(name)
|
| 46 |
+
# rebuild slider values to the new robot's neutral
|
| 47 |
+
values = [float(robot.q[i]) for i in robot.idxs]
|
| 48 |
+
viewer_html = robot.iframe()
|
| 49 |
+
updates = [gr.update(value=0, label=label, minimum=lo, maximum=hi)
|
| 50 |
+
for (label,(lo,hi)) in zip(robot.labels, robot.bounds)]
|
| 51 |
+
return [gr.update(value=viewer_html)] + updates
|
| 52 |
+
|
| 53 |
+
robot_dd.change(on_change_robot, inputs=robot_dd,
|
| 54 |
+
outputs=[viewer] + sliders, queue=False)
|
| 55 |
+
|
| 56 |
+
if __name__ == "__main__":
|
| 57 |
+
demo.launch(server_name="0.0.0.0", server_port=8501)
|
nginx.conf
ADDED
|
@@ -0,0 +1,28 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
worker_processes 1;
|
| 2 |
+
events { worker_connections 1024; }
|
| 3 |
+
http {
|
| 4 |
+
include mime.types;
|
| 5 |
+
default_type application/octet-stream;
|
| 6 |
+
sendfile on;
|
| 7 |
+
map $http_upgrade $connection_upgrade { default upgrade; '' close; }
|
| 8 |
+
|
| 9 |
+
upstream app { server 127.0.0.1:8501; }
|
| 10 |
+
# upstream meshcat { server 127.0.0.1:6001; }
|
| 11 |
+
|
| 12 |
+
server {
|
| 13 |
+
listen 7860;
|
| 14 |
+
|
| 15 |
+
location / {
|
| 16 |
+
proxy_pass http://app;
|
| 17 |
+
proxy_set_header Host $host;
|
| 18 |
+
proxy_set_header X-Real-IP $remote_addr;
|
| 19 |
+
}
|
| 20 |
+
|
| 21 |
+
# location /meshcat/ {
|
| 22 |
+
# proxy_http_version 1.1;
|
| 23 |
+
# proxy_set_header Upgrade $http_upgrade;
|
| 24 |
+
# proxy_set_header Connection $connection_upgrade;
|
| 25 |
+
# proxy_pass http://meshcat/;
|
| 26 |
+
# }
|
| 27 |
+
}
|
| 28 |
+
}
|
requirements.txt
ADDED
|
@@ -0,0 +1,5 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
gradio==5.49.1
|
| 2 |
+
meshcat==0.3.2
|
| 3 |
+
pin==3.8.0 # Pinocchio (package name is "pin")
|
| 4 |
+
example-robot-data==4.1.0
|
| 5 |
+
numpy==2.3.4
|
robots.py
ADDED
|
@@ -0,0 +1,66 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import numpy as np
|
| 2 |
+
import example_robot_data as erd
|
| 3 |
+
import pinocchio as pin
|
| 4 |
+
import meshcat
|
| 5 |
+
from meshcat.servers.zmqserver import start_zmq_server_as_subprocess
|
| 6 |
+
|
| 7 |
+
from pinocchio.visualize import MeshcatVisualizer
|
| 8 |
+
|
| 9 |
+
def _actuated_dof_indices(model):
|
| 10 |
+
idxs, labels, bounds = [], [], []
|
| 11 |
+
for j in range(1, len(model.joints)):
|
| 12 |
+
J = model.joints[j]
|
| 13 |
+
if J.shortname() == "JointModelFreeFlyer":
|
| 14 |
+
continue
|
| 15 |
+
if J.nq == 1:
|
| 16 |
+
i0 = J.idx_q
|
| 17 |
+
lo = float(model.lowerPositionLimit[i0])
|
| 18 |
+
hi = float(model.upperPositionLimit[i0])
|
| 19 |
+
if not np.isfinite(lo): lo = -3.14
|
| 20 |
+
if not np.isfinite(hi): hi = 3.14
|
| 21 |
+
idxs.append(i0)
|
| 22 |
+
labels.append(model.names[j])
|
| 23 |
+
bounds.append((lo, hi))
|
| 24 |
+
return idxs, labels, bounds
|
| 25 |
+
|
| 26 |
+
class LiveRobot:
|
| 27 |
+
"""
|
| 28 |
+
Starts a local MeshCat ZMQ+Web server and keeps a persistent connection.
|
| 29 |
+
The web UI is proxied at /meshcat/ by nginx (see nginx.conf).
|
| 30 |
+
"""
|
| 31 |
+
def __init__(self, robot_name="ur5"):
|
| 32 |
+
# Start one MeshCat server (zmq + web) and connect
|
| 33 |
+
self.proc, self.zmq_url, self.web_url = start_zmq_server_as_subprocess(server_args=[])
|
| 34 |
+
print(f"MeshCat ZMQ server at {self.zmq_url}, web at {self.web_url}")
|
| 35 |
+
self.vis = meshcat.Visualizer(zmq_url=self.zmq_url)
|
| 36 |
+
|
| 37 |
+
self.rdata = erd.load(robot_name)
|
| 38 |
+
self.model = self.rdata.model
|
| 39 |
+
self.data = self.model.createData()
|
| 40 |
+
|
| 41 |
+
self.viz = MeshcatVisualizer(
|
| 42 |
+
self.model, self.rdata.collision_model, self.rdata.visual_model
|
| 43 |
+
)
|
| 44 |
+
self.viz.initViewer(self.vis)
|
| 45 |
+
self.viz.loadViewerModel()
|
| 46 |
+
|
| 47 |
+
self.q = pin.neutral(self.model)
|
| 48 |
+
self.viz.display(self.q)
|
| 49 |
+
|
| 50 |
+
self.idxs, self.labels, self.bounds = _actuated_dof_indices(self.model)
|
| 51 |
+
|
| 52 |
+
def set_joints(self, vals):
|
| 53 |
+
q = self.q.copy()
|
| 54 |
+
for v, qi in zip(vals, self.idxs):
|
| 55 |
+
q[qi] = float(v)
|
| 56 |
+
self.q = q
|
| 57 |
+
self.viz.display(self.q)
|
| 58 |
+
|
| 59 |
+
def neutral(self):
|
| 60 |
+
self.q = pin.neutral(self.model)
|
| 61 |
+
self.viz.display(self.q)
|
| 62 |
+
return [float(self.q[i]) for i in self.idxs]
|
| 63 |
+
|
| 64 |
+
def iframe(self, width="100%", height=640):
|
| 65 |
+
# nginx proxies the MeshCat web app at /meshcat/
|
| 66 |
+
return f'<iframe src="{self.web_url}" style="width:{width};height:{height}px;border:0"></iframe>'
|
run.sh
ADDED
|
@@ -0,0 +1,11 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#!/usr/bin/env bash
|
| 2 |
+
set -euo pipefail
|
| 3 |
+
|
| 4 |
+
# 1) start MeshCat (websocket server) on 6001 in the background
|
| 5 |
+
meshcat-server --host 127.0.0.1 --port 6001 --open False &
|
| 6 |
+
|
| 7 |
+
# 2) start nginx (daemonizes to background by default; uses /workspace/nginx.conf)
|
| 8 |
+
nginx
|
| 9 |
+
|
| 10 |
+
# 3) run Gradio app on 8501 in the foreground (container stays alive on this)
|
| 11 |
+
exec python app.py
|