askuric HF Staff commited on
Commit
79ee8ac
·
1 Parent(s): 23ca5de

inital upload

Browse files
Files changed (6) hide show
  1. Dockerfile +28 -0
  2. app.py +57 -0
  3. nginx.conf +28 -0
  4. requirements.txt +5 -0
  5. robots.py +66 -0
  6. 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