askuric HF Staff commited on
Commit
0439049
·
1 Parent(s): 71cf095

inital release

Browse files
Files changed (10) hide show
  1. Dockerfile +44 -0
  2. README.md +53 -3
  3. app.py +269 -0
  4. meshcat_shapes.py +111 -0
  5. nginx.conf +50 -0
  6. planning_utils.py +1118 -0
  7. requirements.txt +15 -0
  8. robot.py +321 -0
  9. run.sh +18 -0
  10. ws_bridge.py +53 -0
Dockerfile ADDED
@@ -0,0 +1,44 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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: # Commented out to allow building ruckig from source
8
+
9
+ # Install build dependencies for ruckig
10
+ RUN apt-get update && apt-get install -y --no-install-recommends \
11
+ libgl1-mesa-dev \
12
+ libglib2.0-0 \
13
+ git \
14
+ build-essential \
15
+ cmake \
16
+ libeigen3-dev \
17
+ && rm -rf /var/lib/apt/lists/*
18
+
19
+ RUN apt-get update && apt-get install -y --no-install-recommends \
20
+ nginx \
21
+ && rm -rf /var/lib/apt/lists/*
22
+
23
+ WORKDIR /workspace
24
+
25
+ # your working versions
26
+ COPY requirements.txt ./
27
+ RUN apt-get update && apt-get install -y --no-install-recommends git && \
28
+ pip install --upgrade pip &&\
29
+ pip install --verbose -r requirements.txt
30
+
31
+ RUN echo ${PWD} && ls -lR
32
+
33
+ # Copy planning utilities first
34
+ COPY meshcat_shapes.py planning_utils.py ./
35
+
36
+ # app + nginx config + entrypoint
37
+ COPY app.py robot.py nginx.conf run.sh ws_bridge.py ./
38
+ RUN chmod +x /workspace/run.sh \
39
+ && rm -f /etc/nginx/nginx.conf \
40
+ && ln -s /workspace/nginx.conf /etc/nginx/nginx.conf
41
+
42
+ EXPOSE 7860
43
+ CMD ["/workspace/run.sh"]
44
+
README.md CHANGED
@@ -1,10 +1,60 @@
1
  ---
2
  title: Capacity Aware Trajectory Planning
3
- emoji: 👀
4
- colorFrom: green
5
  colorTo: green
6
  sdk: docker
7
  pinned: false
8
  ---
9
 
10
- Check out the configuration reference at https://huggingface.co/docs/hub/spaces-config-reference
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
  ---
2
  title: Capacity Aware Trajectory Planning
3
+ emoji: 🤖
4
+ colorFrom: blue
5
  colorTo: green
6
  sdk: docker
7
  pinned: false
8
  ---
9
 
10
+ # Capacity-Aware Trajectory Planning Comparison
11
+
12
+ This space demonstrates a comparison between **capacity-aware real-time trajectory planning** and **TOPPRA** (Time-Optimal Path Parameterization with Reachability Analysis) for robotic manipulators.
13
+
14
+ ## Features
15
+
16
+ - 🎲 **Random Trajectory Generation**: Generate random Cartesian trajectories with configurable length
17
+ - ⚙️ **Capacity Scaling**: Test different robot capacity levels (0.1 to 1.0)
18
+ - 👀 **Dual Robot Visualization**: Watch both algorithms execute simultaneously
19
+ - **Left Robot**: Our capacity-aware approach
20
+ - **Right Robot**: TOPPRA
21
+ - 📊 **Real-time Comparison Plots**:
22
+ - Position vs Time
23
+ - Velocity vs Time (with capacity limits)
24
+ - Acceleration vs Time (with capacity limits)
25
+ - Tracking Error comparison
26
+
27
+ ## How to Use
28
+
29
+ 1. **Configure Trajectory**:
30
+ - Set the trajectory length (0.1m to 1.0m)
31
+ - Set the robot capacity scale (0.1 to 1.0)
32
+
33
+ 2. **Generate**: Click "Generate Random Trajectory" to create a new trajectory and compute both algorithms
34
+
35
+ 3. **Visualize**:
36
+ - View the dual robot animation in the MeshCat viewer
37
+ - Click "Play Animation" to start the synchronized execution
38
+ - Examine the comparison plots below
39
+
40
+ 4. **Compare**: The info box shows timing comparisons between the two approaches
41
+
42
+ ## Requirements
43
+
44
+ To run locally, you need the planning utilities from the catp repository.
45
+
46
+
47
+
48
+ ## To run the app locally
49
+
50
+ 1. Clone this repository.
51
+ 2. build the Docker image using the provided Dockerfile.
52
+ ```
53
+ docker build -t catp .
54
+ ```
55
+ 3. Run the Docker container
56
+ ```
57
+ docker run -p 7860:7860 catp
58
+ ```
59
+ 4. Open your web browser and navigate to `http://localhost:7860` to access the app.
60
+
app.py ADDED
@@ -0,0 +1,269 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import gradio as gr
2
+ import numpy as np
3
+ from robot import TrajectoryPlanner
4
+ import logging
5
+ import plotly.graph_objects as go
6
+ from plotly.subplots import make_subplots
7
+ logging.basicConfig(level=logging.INFO, format="%(asctime)s %(levelname)s %(message)s")
8
+ import time
9
+ import threading
10
+
11
+ # Initialize trajectory planner
12
+ try:
13
+ planner = TrajectoryPlanner()
14
+ PLANNER_AVAILABLE = True
15
+ except Exception as e:
16
+ logging.error(f"Failed to initialize trajectory planner: {e}")
17
+ PLANNER_AVAILABLE = False
18
+
19
+ animation_running = False
20
+ animation_thread = None
21
+
22
+ css = """
23
+ .control-panel .gr-slider {
24
+ margin-top: 4px !important;
25
+ margin-bottom: 4px !important;
26
+ }
27
+ .control-panel .gr-form {
28
+ gap: 6px !important;
29
+ }
30
+ .label-override {
31
+ font-weight: 500 !important;
32
+ font-size: 1.1em !important;
33
+ color: #333 !important;
34
+ }
35
+ """
36
+
37
+ def create_comparison_plots(plot_data):
38
+ """Create comparison plots for the trajectories"""
39
+ if plot_data is None:
40
+ return None
41
+
42
+ ours = plot_data['ours']
43
+ toppra = plot_data['toppra']
44
+
45
+ # Create subplots
46
+ fig = make_subplots(
47
+ rows=3, cols=2,
48
+ subplot_titles=('Position', 'Velocity', 'Acceleration', 'Jerk', 'Position Error', 'Orientation Error'),
49
+ vertical_spacing=0.12,
50
+ horizontal_spacing=0.1
51
+ )
52
+
53
+ # Position
54
+ fig.add_trace(go.Scatter(x=ours['t'], y=ours['x'], name='Ours',
55
+ line=dict(color='#6C8EBF', width=2), showlegend=False), row=1, col=1)
56
+ fig.add_trace(go.Scatter(x=toppra['t'], y=toppra['x'], name='TOPPRA',
57
+ line=dict(color='#B85450', width=2), showlegend=False), row=1, col=1)
58
+
59
+ # Velocity
60
+ fig.add_trace(go.Scatter(x=ours['t'], y=ours['dx'], name='Ours',
61
+ line=dict(color='#6C8EBF', width=2), showlegend=False), row=1, col=2)
62
+ fig.add_trace(go.Scatter(x=ours['t'], y=ours['dx_max'], name='Ours Limits',
63
+ line=dict(color='#6C8EBF', width=1, dash='dash'), showlegend=False), row=1, col=2)
64
+ fig.add_trace(go.Scatter(x=toppra['t'], y=toppra['dx'], name='TOPPRA',
65
+ line=dict(color='#B85450', width=2), showlegend=False), row=1, col=2)
66
+ fig.add_trace(go.Scatter(x=toppra['t'], y=toppra['ds_max'], name='TOPPRA Limits',
67
+ line=dict(color='#B85450', width=1, dash='dash'), showlegend=False), row=1, col=2)
68
+
69
+ # Acceleration
70
+ fig.add_trace(go.Scatter(x=ours['t'], y=ours['ddx'], name='Ours',
71
+ line=dict(color='#6C8EBF', width=2), showlegend=False), row=2, col=1)
72
+ fig.add_trace(go.Scatter(x=ours['t'], y=ours['ddx_max'], name='Ours Limits',
73
+ line=dict(color='#6C8EBF', width=1, dash='dash'), showlegend=False), row=2, col=1)
74
+ fig.add_trace(go.Scatter(x=ours['t'], y=ours['ddx_min'], name='Ours Limits',
75
+ line=dict(color='#6C8EBF', width=1, dash='dash'), showlegend=False), row=2, col=1)
76
+ fig.add_trace(go.Scatter(x=toppra['t'], y=toppra['ddx'], name='TOPPRA',
77
+ line=dict(color='#B85450', width=2), showlegend=False), row=2, col=1)
78
+ fig.add_trace(go.Scatter(x=toppra['t'], y=toppra['dds_max'], name='TOPPRA Limits',
79
+ line=dict(color='#B85450', width=1, dash='dash'), showlegend=False), row=2, col=1)
80
+ fig.add_trace(go.Scatter(x=toppra['t'], y=toppra['dds_min'], name='TOPPRA Limits',
81
+ line=dict(color='#B85450', width=1, dash='dash'), showlegend=False), row=2, col=1)
82
+
83
+ # Jerk
84
+ fig.add_trace(go.Scatter(x=ours['t'], y=ours['dddx'], name='Ours',
85
+ line=dict(color='#6C8EBF', width=2), showlegend=False), row=2, col=2)
86
+ fig.add_trace(go.Scatter(x=ours['t'], y=ours['dddx_max'], name='Ours Limits',
87
+ line=dict(color='#6C8EBF', width=1, dash='dash'), showlegend=False), row=2, col=2)
88
+ fig.add_trace(go.Scatter(x=ours['t'], y=ours['dddx_min'], name='Ours Limits',
89
+ line=dict(color='#6C8EBF', width=1, dash='dash'), showlegend=False), row=2, col=2)
90
+ fig.add_trace(go.Scatter(x=toppra['t'], y=toppra['dddx'], name='TOPPRA',
91
+ line=dict(color='#B85450', width=2), showlegend=False), row=2, col=2)
92
+ fig.add_trace(go.Scatter(x=toppra['t'], y=toppra['ddds_max'], name='TOPPRA Limits',
93
+ line=dict(color='#B85450', width=1, dash='dash'), showlegend=False), row=2, col=2)
94
+ fig.add_trace(go.Scatter(x=toppra['t'], y=toppra['ddds_min'], name='TOPPRA Limits',
95
+ line=dict(color='#B85450', width=1, dash='dash'), showlegend=False), row=2, col=2)
96
+
97
+ # Position Error
98
+ fig.add_trace(go.Scatter(x=ours['t'], y=ours['e_pos'], name='Ours',
99
+ line=dict(color='#6C8EBF', width=2), showlegend=False), row=3, col=1)
100
+ fig.add_trace(go.Scatter(x=toppra['t'], y=toppra['e_pos'], name='TOPPRA',
101
+ line=dict(color='#B85450', width=2), showlegend=False), row=3, col=1)
102
+
103
+ # Orientation Error
104
+ fig.add_trace(go.Scatter(x=ours['t'], y=ours['e_rot'], name='Ours',
105
+ line=dict(color='#6C8EBF', width=2, dash='dot'), showlegend=False), row=3, col=2)
106
+ fig.add_trace(go.Scatter(x=toppra['t'], y=toppra['e_rot'], name='TOPPRA',
107
+ line=dict(color='#B85450', width=2, dash='dot'), showlegend=False), row=3, col=2)
108
+
109
+
110
+ # Update axes
111
+ # fig.update_xaxes(title_text="Time [s]", row=1, col=1)
112
+ # fig.update_xaxes(title_text="Time [s]", row=1, col=2)
113
+ # fig.update_xaxes(title_text="Time [s]", row=2, col=1)
114
+ # fig.update_xaxes(title_text="Time [s]", row=2, col=2)
115
+ fig.update_xaxes(title_text="Time [s]", row=3, col=1)
116
+ fig.update_xaxes(title_text="Time [s]", row=3, col=2)
117
+
118
+ fig.update_yaxes(title_text="s [m]", row=1, col=1)
119
+ fig.update_yaxes(title_text="ds [m/s]", row=1, col=2)
120
+ fig.update_yaxes(title_text="dds [m/s²]", row=2, col=1)
121
+ fig.update_yaxes(title_text="ddds [m/s³]", row=2, col=2)
122
+ fig.update_yaxes(title_text="Position Error [mm]", row=3, col=1)
123
+ fig.update_yaxes(title_text="Orientation Error [deg]", row=3, col=2)
124
+
125
+ fig.update_layout(height=600, showlegend=True, legend=dict(x=0.5, y=1.1, orientation='h'))
126
+
127
+ return fig
128
+
129
+ def animate_trajectory():
130
+ """Background thread to animate the trajectory"""
131
+ global animation_running
132
+
133
+ anim_data = planner.get_animation_data()
134
+ if anim_data is None:
135
+ return
136
+
137
+ t_max = anim_data['t_max']
138
+
139
+ while animation_running:
140
+ t0 = time.time()
141
+
142
+ while animation_running and (time.time() - t0) < t_max:
143
+ t_current = time.time() - t0
144
+ planner.update_animation(t_current)
145
+ time.sleep(0.01)
146
+
147
+ def generate_and_compute(traj_length, capacity_scale, progress=gr.Progress()):
148
+ """Generate trajectory and compute both algorithms"""
149
+ if not PLANNER_AVAILABLE:
150
+ return "Planner not available", None, None
151
+
152
+ progress(0, desc="Generating trajectory...")
153
+
154
+ try:
155
+ result = planner.generate_trajectory(traj_length, capacity_scale, progress)
156
+
157
+ info = f"""
158
+ ✅ **Trajectory Generated Successfully**
159
+
160
+ - **Waypoints**: {result['waypoints']}
161
+ - **Trajectory Length**: {traj_length:.2f} m
162
+ - **Capacity Scale**: {capacity_scale:.2f}
163
+ - **TOPPRA Duration**: {result['toppra_duration']:.3f} s
164
+ - **Ours Duration**: {result['ours_duration']:.3f} s
165
+ - **Speedup**: {(result['toppra_duration'] / result['ours_duration']):.2f}x
166
+ """
167
+
168
+ progress(0.8, desc="Creating plots...")
169
+
170
+ plot_data = planner.get_plot_data()
171
+ plots = create_comparison_plots(plot_data)
172
+
173
+ progress(1.0, desc="Done!")
174
+
175
+ return info, plots, gr.update(interactive=True)
176
+
177
+ except Exception as e:
178
+ logging.error(f"Error generating trajectory: {e}")
179
+ return f"❌ Error: {str(e)}", None, gr.update(interactive=False)
180
+
181
+ def start_animation():
182
+ """Start the animation"""
183
+ global animation_running, animation_thread
184
+
185
+ if not PLANNER_AVAILABLE or planner.data is None:
186
+ return gr.update(value="⚠️ Generate trajectory first")
187
+
188
+ animation_running = True
189
+ animation_thread = threading.Thread(target=animate_trajectory, daemon=True)
190
+ animation_thread.start()
191
+
192
+ return gr.update(value="⏸️ Stop Animation", variant="stop")
193
+
194
+ def stop_animation():
195
+ """Stop the animation"""
196
+ global animation_running
197
+ animation_running = False
198
+
199
+ return gr.update(value="▶️ Play Animation", variant="primary")
200
+
201
+ def toggle_animation(btn_state):
202
+ """Toggle animation on/off"""
203
+ if "Stop" in btn_state:
204
+ return stop_animation()
205
+ else:
206
+ return start_animation()
207
+
208
+ with gr.Blocks(css=css, title="Capacity-Aware Trajectory Planning") as demo:
209
+
210
+ gr.Markdown("""
211
+ # 🤖 Capacity-Aware Trajectory Planning Comparison
212
+
213
+ Compare **capacity-aware real-time trajectory planning** vs **TOPPRA** for the Panda robot.
214
+
215
+ **Left Robot**: Our approach | **Right Robot**: TOPPRA
216
+ """)
217
+
218
+ with gr.Row(equal_height=True):
219
+ # LEFT: MeshCat viewer
220
+ with gr.Column(scale=2, min_width=500):
221
+ if PLANNER_AVAILABLE:
222
+ viewer = gr.HTML(planner.iframe(height=600))
223
+ else:
224
+ viewer = gr.HTML("<div style='height:600px; background:#f0f0f0; display:flex; align-items:center; justify-content:center;'><h2>Planner Not Available</h2></div>")
225
+
226
+ # RIGHT: Controls
227
+ with gr.Column(scale=1, min_width=400, elem_classes="control-panel"):
228
+ gr.Markdown("## 🎛️ Trajectory Configuration")
229
+
230
+ traj_length = gr.Slider(
231
+ 0.1, 1.0, value=0.8, step=0.05,
232
+ label="Trajectory Length (m)",
233
+ info="Distance between start and end points"
234
+ )
235
+
236
+ capacity_scale = gr.Slider(
237
+ 0.1, 1.0, value=0.5, step=0.05,
238
+ label="Robot Capacity Scale",
239
+ info="Fraction of maximum robot capacity (1.0 = full capacity)"
240
+ )
241
+
242
+ generate_btn = gr.Button("🎲 Generate Random Trajectory", variant="primary", size="lg")
243
+
244
+ info_box = gr.Markdown("Click 'Generate' to create a random trajectory")
245
+
246
+ gr.Markdown("## 🎬 Animation Control")
247
+
248
+ play_btn = gr.Button("▶️ Play Animation", variant="primary", interactive=False)
249
+
250
+ with gr.Row():
251
+ with gr.Column():
252
+ gr.Markdown("## 📊 Trajectory Comparison")
253
+ plots = gr.Plot(label="Comparison Plots")
254
+
255
+ # Callbacks
256
+ generate_btn.click(
257
+ generate_and_compute,
258
+ inputs=[traj_length, capacity_scale],
259
+ outputs=[info_box, plots, play_btn]
260
+ )
261
+
262
+ play_btn.click(
263
+ toggle_animation,
264
+ inputs=[play_btn],
265
+ outputs=[play_btn]
266
+ )
267
+
268
+ if __name__ == "__main__":
269
+ demo.launch(server_name="0.0.0.0", server_port=8501)
meshcat_shapes.py ADDED
@@ -0,0 +1,111 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #!/usr/bin/env python3
2
+ # -*- coding: utf-8 -*-
3
+ #
4
+ # SPDX-License-Identifier: Apache-2.0
5
+ # Copyright 2022 Stéphane Caron
6
+
7
+ """Standalone version of meshcat-shapes.
8
+
9
+ See <https://pypi.org/project/meshcat-shapes/>. We keep this copy in examples/
10
+ so that it can be used by examples that need it without making meshcat-shapes
11
+ (and thus meshcat) a dependency of the project.
12
+ """
13
+
14
+ import meshcat
15
+ import numpy as np
16
+ import pinocchio as pin
17
+ import pynocchio as pnc
18
+
19
+ def __attach_axes(
20
+ handle: meshcat.Visualizer,
21
+ length: float = 0.05,
22
+ thickness: float = 0.002,
23
+ opacity: float = 1.0,
24
+ ) -> None:
25
+ """Attach a set of three basis axes to a MeshCat handle.
26
+
27
+ Args:
28
+ handle: MeshCat handle to attach the basis axes to.
29
+ length: Length of axis unit vectors.
30
+ thickness: Thickness of axis unit vectors.
31
+ opacity: Opacity of all three unit vectors.
32
+
33
+ Note:
34
+ As per the de-facto standard (Blender, OpenRAVE, RViz, ...), the
35
+ x-axis is red, the y-axis is green and the z-axis is blue.
36
+ """
37
+ direction_names = ["x", "y", "z"]
38
+ colors = [0xFF0000, 0x00FF00, 0x0000FF]
39
+ rotation_axes = [[0, 0, 1], [0, 1, 0], [1, 0, 0]]
40
+ position_cylinder_in_frame = 0.5 * length * np.eye(3)
41
+ for i in range(3):
42
+ dir_name = direction_names[i]
43
+ material = meshcat.geometry.MeshLambertMaterial(
44
+ color=colors[i], opacity=opacity
45
+ )
46
+ transform_cylinder_to_frame = meshcat.transformations.rotation_matrix(
47
+ np.pi / 2, rotation_axes[i]
48
+ )
49
+ transform_cylinder_to_frame[0:3, 3] = position_cylinder_in_frame[i]
50
+ cylinder = meshcat.geometry.Cylinder(length, thickness)
51
+ handle[dir_name].set_object(cylinder, material)
52
+ handle[dir_name].set_transform(transform_cylinder_to_frame)
53
+
54
+
55
+ def frame(
56
+ handle: meshcat.Visualizer,
57
+ axis_length: float = 0.1,
58
+ axis_thickness: float = 0.005,
59
+ opacity: float = 1.0,
60
+ origin_color: int = 0x000000,
61
+ origin_radius: float = 0.01,
62
+ ) -> None:
63
+ """Set MeshCat handle to a frame, represented by an origin and three axes.
64
+
65
+ Args:
66
+ handle: MeshCat handle to attach the frame to.
67
+ axis_length: Length of axis unit vectors, in [m].
68
+ axis_thickness: Thickness of axis unit vectors, in [m].
69
+ opacity: Opacity of all three unit vectors.
70
+ origin_color: Color of the origin sphere.
71
+ origin_radius: Radius of the frame origin sphere, in [m].
72
+
73
+ Note:
74
+ As per the de-facto standard (Blender, OpenRAVE, RViz, ...), the
75
+ x-axis is red, the y-axis is green and the z-axis is blue.
76
+ """
77
+ material = meshcat.geometry.MeshLambertMaterial(
78
+ color=origin_color, opacity=opacity
79
+ )
80
+ sphere = meshcat.geometry.Sphere(origin_radius)
81
+ handle.set_object(sphere, material)
82
+ __attach_axes(handle, axis_length, axis_thickness, opacity)
83
+
84
+
85
+ def dk(robot, q, tip = None):
86
+ if tip is None:
87
+ tip = robot.model.frames[-1].name
88
+ # joint_id = robot.model.getFrameId(robot.model.frames[-1].name)
89
+ joint_id = robot.model.getFrameId(tip)
90
+ pin.framesForwardKinematics(robot.model, robot.data, q)
91
+ return robot.data.oMf[robot.model.getFrameId(tip)].translation.copy(), robot.data.oMf[robot.model.getFrameId(tip)].rotation
92
+
93
+ def display_frame(viz, name, X_frame):
94
+ frame(viz.viewer[name], opacity=0.5)
95
+ viz.viewer[name].set_transform(X_frame)
96
+
97
+ def display(viz, robot, tip, name, q, T_shift = np.eye(4)):
98
+ if type(robot) == pnc.RobotWrapper:
99
+ robot = robot.robot
100
+ elif type(robot) == pin.RobotWrapper:
101
+ pass
102
+ else:
103
+ raise ValueError("robot must be of type pin.RobotWrapper or pynocchio.RobotWrapper")
104
+ if len(q) != robot.nq:
105
+ q = np.array(list(q)+[0]*(robot.nq-len(q)))
106
+ viz.display(q)
107
+ dk(robot, q, tip=tip)
108
+
109
+
110
+
111
+ display_frame(viz, name, (T_shift@robot.data.oMf[robot.model.getFrameId(tip)]))
nginx.conf ADDED
@@ -0,0 +1,50 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ worker_processes 1;
2
+ events { worker_connections 1024; }
3
+
4
+ http {
5
+ include mime.types;
6
+ default_type application/octet-stream;
7
+ sendfile on;
8
+
9
+ # helper for ws upgrade header
10
+ map $http_upgrade $connection_upgrade { default upgrade; '' close; }
11
+
12
+ # decide if request at "/" is a websocket upgrade:
13
+ map $http_upgrade $root_backend {
14
+ default http://127.0.0.1:8501; # normal HTTP -> Gradio
15
+ "~*upgrade" http://127.0.0.1:8765; # WS upgrade -> bridge
16
+ "~*websocket" http://127.0.0.1:8765;
17
+ }
18
+
19
+ upstream app { server 127.0.0.1:8501; } # Gradio
20
+ upstream meshcat { server 127.0.0.1:7000; } # MeshCat HTTP (/static) & WS at "/"
21
+
22
+ server {
23
+ listen 7860;
24
+
25
+ # MeshCat viewer HTML lives under /static/ — mount at /meshcat/
26
+ location /meshcat/ {
27
+ proxy_pass http://meshcat/static/; # trailing slash matters
28
+ proxy_set_header Host $host;
29
+ }
30
+
31
+ # MeshCat HTML references /static/... absolutely — forward those too
32
+ location /static/ {
33
+ proxy_pass http://meshcat/static/;
34
+ proxy_set_header Host $host;
35
+ }
36
+
37
+ # ROOT "/":
38
+ # - normal HTTP (page loads, XHR, etc.) -> Gradio app
39
+ # - WebSocket upgrade (opened by MeshCat JS to ws://HOST:7860/) -> WS bridge
40
+ location / {
41
+ proxy_http_version 1.1;
42
+ proxy_set_header Upgrade $http_upgrade;
43
+ proxy_set_header Connection $connection_upgrade;
44
+ proxy_set_header Host $host;
45
+ proxy_read_timeout 3600s;
46
+ proxy_send_timeout 3600s;
47
+ proxy_pass $root_backend;
48
+ }
49
+ }
50
+ }
planning_utils.py ADDED
@@ -0,0 +1,1118 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import qpsolvers
2
+ import numpy as np
3
+ try:
4
+ SPATIAL_MATH = True
5
+ from spatialmath import SE3,SO3, base
6
+ import spatialmath
7
+ except ImportError:
8
+ SPATIAL_MATH = False
9
+ print("spatialmath is not installed")
10
+
11
+ import pinocchio as pin
12
+ import time
13
+
14
+ from cvxopt import matrix
15
+ import cvxopt.glpk
16
+
17
+ from scipy.optimize import linprog
18
+ from scipy.linalg import block_diag
19
+
20
+ from pynocchio import RobotWrapper
21
+
22
+ def solve_lp(c, A_eq, b_eq, x_min, x_max):
23
+ # scipy linprog
24
+ # res = linprog(c, A_eq=A_eq, b_eq=b_eq, bounds=np.array([x_min,x_max]).T)
25
+ # return res.x
26
+
27
+ # glpk linprog
28
+ c = matrix(c)
29
+ A = matrix(A_eq)
30
+ b = matrix(b_eq)
31
+ G = matrix(np.vstack((-np.identity(len(x_min)),np.identity(len(x_min)))))
32
+ h = matrix(np.hstack((list(-np.array(x_min)),x_max)))
33
+ solvers_opt={'tm_lim': 100000, 'msg_lev': 'GLP_MSG_OFF', 'it_lim':10000}
34
+ res = cvxopt.glpk.lp(c=c, A=A, b=b, G=G,h=h, options=solvers_opt)
35
+ return np.array(res[1]).reshape((-1,))
36
+
37
+
38
+ def solve_qp(A,s,x_min,x_max, grad = None, reg_w=1e-7, solver=None):
39
+
40
+ if (solver is None) or('cvxopt' in solver ) :
41
+ A =np.matrix(A)
42
+ s = np.matrix(s)
43
+
44
+ P = A.T@A
45
+ if grad is not None:
46
+ grad = np.matrix(grad).reshape(1,-1)
47
+ q = matrix(-A.T@s + reg_w*grad.T)
48
+ P = P + np.eye(len(grad))*reg_w
49
+ else:
50
+ q = matrix(-A.T@s)
51
+ P = matrix(P)
52
+ G = matrix(np.vstack((-np.identity(len(x_max)),np.identity(len(x_min)))))
53
+ h = matrix(np.hstack((list(-np.array(x_min)),x_max)))
54
+ return np.array(cvxopt.solvers.qp(P, q, G, h)['x']).flatten()
55
+
56
+ else:
57
+
58
+ P = A.T@A
59
+ if grad is not None:
60
+ q = -A.T@s + reg_w*grad[:,None]
61
+ P= P+np.eye(len(grad))*reg_w
62
+ else:
63
+ q = -A.T@s
64
+ G = np.vstack((-np.identity(len(x_max)),np.identity(len(x_min))))
65
+ h = np.hstack((list(-np.array(x_min)),x_max))
66
+
67
+ return qpsolvers.solve_qp(P, q.flatten(), G,h, solver=solver)
68
+
69
+ from copy import copy
70
+ from pathlib import Path
71
+ from sys import path
72
+ import time
73
+
74
+ from ruckig import InputParameter, OutputParameter, Result, Ruckig
75
+
76
+ class TrajData():
77
+ name = ''
78
+
79
+
80
+ def compute_capacity_aware_trajectory(X_init, X_final,q0, robot=None, robot_pyn=None, lims=[], scale=1.0, options = None , verbose=False):
81
+
82
+
83
+ # check the type of the X_init and X_final
84
+ if type(X_init) == np.ndarray:
85
+ X_init = pin.SE3(X_init[:3,:3], X_init[:3,3])
86
+ X_final = pin.SE3(X_final[:3,:3], X_final[:3,3])
87
+ elif type(X_init) == pin.SE3:
88
+ pass
89
+ elif SPATIAL_MATH and (type(X_init) == spatialmath.pose3d.SE3):
90
+ X_init = pin.SE3(X_init.R, X_init.t)
91
+ X_final = pin.SE3(X_final.R, X_final.t)
92
+ else:
93
+ print("Unknown type of X_init")
94
+ return
95
+
96
+ if robot_pyn is not None:
97
+ X_r = robot_pyn.forward(q0)
98
+ n_dof = robot_pyn.n
99
+ elif robot is not None:
100
+ X_r = robot.fkine(q0)
101
+ n_dof = robot.n
102
+ else:
103
+ print("Please provide either robot or robot_pyn")
104
+ return
105
+
106
+
107
+ if options is not None and 'uptate_current_position' in options.keys():
108
+ uptate_current_position = options['uptate_current_position']
109
+ else:
110
+ uptate_current_position = False
111
+
112
+ if options is not None and 'qp_form' in options.keys():
113
+ qp_form = options['qp_form']
114
+ else:
115
+ qp_form = 'acceleration'
116
+
117
+
118
+ if options is not None and 'calculate_limits' in options.keys():
119
+ calculate_limits = options['calculate_limits']
120
+ else:
121
+ calculate_limits = True
122
+
123
+ if options is not None and 'scale_limits' in options.keys():
124
+ scale_limits = options['scale_limits']
125
+ else:
126
+ scale_limits = True
127
+
128
+ if options is not None and 'stop_on_singular' in options.keys():
129
+ stop_on_singular = options['stop_on_singular']
130
+ else:
131
+ stop_on_singular = False
132
+
133
+ if options is not None and 'downsampling_ratio' in options.keys():
134
+ n_ruckig = options['downsampling_ratio']
135
+ else:
136
+ n_ruckig = 1.0
137
+
138
+ if options is not None and 'jerk_scale' in options.keys():
139
+ jerk_scale = options['jerk_scale']
140
+ else:
141
+ jerk_scale =scale
142
+
143
+ if lims is None:
144
+ print('no limits specified')
145
+ data = []
146
+ return
147
+ else:
148
+ if scale_limits:
149
+ dq_max = scale*lims['dq_max'].copy()
150
+ ddq_max = scale*lims['ddq_max'].copy()
151
+ dddq_max = jerk_scale*lims['dddq_max'].copy()
152
+ t_max = scale*lims['t_max'].copy()
153
+ else:
154
+ dq_max = lims['dq_max'].copy()
155
+ ddq_max = lims['ddq_max'].copy()
156
+ dddq_max = lims['dddq_max'].copy()
157
+ t_max = lims['t_max'].copy()
158
+ q_min, q_max = lims['q_min'].copy(), lims['q_max'].copy()
159
+ dq_min = -dq_max
160
+ ddq_min = -ddq_max
161
+ dddq_min = -dddq_max
162
+ t_min = -t_max
163
+
164
+ # cartesian space
165
+ dddx_max = scale*lims['dddx_max'].copy()
166
+ dddx_min = -dddx_max
167
+ ddx_max = scale*lims['ddx_max'].copy()
168
+ ddx_min = -ddx_max
169
+ dx_max = scale*lims['dx_max'].copy()
170
+ dx_min = -dx_max
171
+
172
+ if options is not None and 'scaled_qp_limits' in options.keys():
173
+ scaled_qp_limits = options['scaled_qp_limits']
174
+ else:
175
+ scaled_qp_limits = True
176
+
177
+ if options is not None and 'Kp' in options.keys():
178
+ Kp = options['Kp']
179
+ Kd = options['Kd']
180
+ Ki = options['Ki']
181
+ else:
182
+ Kp = 170
183
+ Kd = 40
184
+ Ki = 0
185
+
186
+ if options is not None and 'clamp_velocity' in options.keys():
187
+ clamp_velocity = options['clamp_velocity']
188
+ else:
189
+ clamp_velocity = True
190
+
191
+ if options is not None and 'clamp_min_accel' in options.keys():
192
+ clamp_min_accel = options['clamp_min_accel']
193
+ else:
194
+ clamp_min_accel = False
195
+
196
+
197
+
198
+ if options is not None and 'override_acceleration' in options.keys():
199
+ override_acceleration = options['override_acceleration']
200
+ else:
201
+ override_acceleration = True
202
+
203
+
204
+ if options is not None and 'use_manip_grad' in options.keys():
205
+ use_manip_grad = options['use_manip_grad']
206
+ else:
207
+ use_manip_grad = False
208
+
209
+ if use_manip_grad and robot is None:
210
+ print("We need RTB model to calculate the manipulability gradient")
211
+ return
212
+
213
+ if options is not None and 'manip_grad_w' in options.keys():
214
+ manip_grad_w = options['manip_grad_w']
215
+ else:
216
+ manip_grad_w = 50000.0
217
+
218
+ data = TrajData()
219
+ u = np.zeros(6)
220
+ u[:3] = X_final.translation-X_init.translation
221
+ d = np.linalg.norm(u)
222
+ u = u/d
223
+
224
+ U,S,V = np.linalg.svd(u[:,None])
225
+ V2 = U[:,1:].T
226
+
227
+ U,S,V = np.linalg.svd(u[:3,None])
228
+ V23 = U[:,1:].T
229
+
230
+ if options is not None and 'dt' in options.keys():
231
+ dt = options['dt']
232
+ else:
233
+ dt = 0.001
234
+ # n_ruckig = 10.0
235
+ dt_ruckig = n_ruckig*dt
236
+
237
+
238
+ if options is not None and 'Tf' in options.keys():
239
+ Tf = options['Tf']
240
+ alpha = Tf/(Tf + dt)
241
+ else:
242
+ Tf = 0.00
243
+ alpha = Tf/(Tf + dt)
244
+
245
+ # Create instances: the Ruckig OTG as well as input and output parameters
246
+ otg = Ruckig(1, dt_ruckig) # DoFs, control cycle
247
+ inp = InputParameter(1)
248
+ out = OutputParameter(1)
249
+
250
+ # Set input parameters
251
+ inp.current_position = [0.0]
252
+ inp.current_velocity = [0.0]
253
+ inp.current_acceleration = [0.0]
254
+
255
+ inp.target_position = [d]
256
+ inp.target_velocity = [0.0]
257
+ inp.target_acceleration = [0.0]
258
+
259
+ inp.max_velocity = [dx_max[0]]
260
+ inp.max_acceleration = [ddx_max[0]]
261
+ inp.max_jerk = [dddx_max[0]]
262
+
263
+ print('\t'.join(['t'] + [str(i) for i in range(otg.degrees_of_freedom)]))
264
+
265
+ # Generate the trajectory within the control loop
266
+ data.qr_list = []
267
+ data.x_list, data.dx_list, data.ddx_list, data.dddx_list = [], [], [], []
268
+ data.x_q_list, data.dx_q_list, data.ddx_q_list, data.dddx_q_list = [], [], [], []
269
+ data.dx_max_list, data.ddx_max_list, data.dddx_max_list = [], [], []
270
+ data.dx_min_list, data.ddx_min_list, data.dddx_min_list = [], [], []
271
+ data.e_pos_list_ruckig, data.e_rot_list_ruckig = [], []
272
+ data.x3d_ruckig = []
273
+
274
+ out_list = []
275
+ res = Result.Working
276
+
277
+ s = time.time()
278
+
279
+ if robot_pyn is None:
280
+ sol = robot.ikine_LM(X_final.np, q0) # solve IK
281
+ q_final = sol.q
282
+ J = robot.jacob0(q_final)
283
+ else:
284
+ q_final = robot_pyn.ik(X_final, q0, qlim=True, verbose=False)
285
+ J = robot_pyn.jacobian(q_final)
286
+ c = u@J
287
+ Aeq = V2@J
288
+ beq = np.zeros(5)
289
+ c_ext = np.hstack((c,c,c,-c))
290
+ Aeq_ext=block_diag(Aeq,Aeq,Aeq,Aeq)
291
+ beq_ext = b_eq=np.hstack((beq,beq,beq,beq))
292
+ x_min_ext = np.hstack((dq_min,ddq_min,dddq_min,ddq_min))
293
+ x_max_ext = np.hstack((dq_max,ddq_max,dddq_max,ddq_max))
294
+ q_ext = solve_lp(-c_ext, Aeq_ext, beq_ext, x_min_ext, x_max_ext)
295
+ ds_final = (c@q_ext[:n_dof])
296
+ dds_final = (c@q_ext[n_dof:(2*n_dof)])
297
+ ddds_final = (c@q_ext[(2*n_dof):(3*n_dof)])
298
+ dds_min_final = (c@q_ext[(3*n_dof):(4*n_dof)])
299
+
300
+
301
+ # bias compensaiton
302
+ db_old, dds_min_old, dds_max_old = 0, 0, 0
303
+ alpha_ds = 1.0
304
+
305
+ err_sum = 0
306
+
307
+ data.solved= True
308
+
309
+ ruckig_current_accel, old_accel = [0], 0
310
+
311
+
312
+ if robot_pyn is None:
313
+ sol = robot.ikine_LM(X_init.np, q0) # solve IK
314
+ q_c = sol.q
315
+ else:
316
+ q_c = robot_pyn.ik(X_init, q0, qlim=True, verbose=False)
317
+
318
+ dq_c, ddq_c = np.zeros(7), np.zeros(7)
319
+ dddq_c = np.zeros(7)
320
+ t_sim = 0
321
+ t_sim_ruckig, t_old_ruckig = 0, -1
322
+ out_position = 0.0
323
+ out_velocity = 0.0
324
+ out_acceleration = 0.0
325
+ beq_filt = np.zeros(5)
326
+ t0 = time.time()
327
+ while res == Result.Working :# or data.dx_q_list[-1] > 1e-2:
328
+
329
+ if t_sim > 10000: # more than 10sec
330
+ print("ERROR: 10 sec simulation time exceeded! Stopping!")
331
+ data.solved = False
332
+ break
333
+
334
+ if robot_pyn is None:
335
+ J = robot.jacob0(q_c)
336
+ X_c = robot.fkine(q_c)
337
+ X_c = pin.SE3(X_c.R, X_c.t)
338
+ J_dot = robot.jacob0_dot(q_c, dq_c)
339
+ else:
340
+ J = robot_pyn.jacobian(q_c)
341
+ J_dot = robot_pyn.jacobian_dot(q_c, dq_c)
342
+ X_c = robot_pyn.forward(q_c)
343
+
344
+
345
+ # u[:3] = X_final.translation-X_c.translation
346
+ # d = np.linalg.norm(u)
347
+ # u = u/d
348
+
349
+ # U,S,V = np.linalg.svd(u[:,None])
350
+ # V2 = U[:,1:].T
351
+
352
+ # U,S,V = np.linalg.svd(u[:3,None])
353
+ # V23 = U[:,1:].T
354
+
355
+ data.qr_list.append(q_c)
356
+ data.x_q_list.append(u[:3]@(X_c.translation-X_init.translation))
357
+ data.x3d_ruckig.append(X_c.translation)
358
+ data.dx_q_list.append(u@J@dq_c)
359
+ data.ddx_q_list.append(u@J@ddq_c + u@J_dot@dq_c)
360
+ data.dddx_q_list.append(u@J@dddq_c + u@J_dot@ddq_c)
361
+
362
+
363
+ # data.e_pos_list_ruckig.append(np.linalg.norm(V23@(X_c.translation-X_init.translation)))
364
+ # data.e_rot_list_ruckig.append(np.linalg.norm(pin.log3(X_init.R.T@X_c.R)))
365
+
366
+
367
+ if t_old_ruckig != t_sim_ruckig:
368
+ if len(data.x_list) == 0:
369
+ delta_x = (inp.current_position[0] )/dt_ruckig
370
+ delta_dx = (inp.current_velocity[0])/dt_ruckig
371
+ delta_ddx = (inp.current_acceleration[0])/dt_ruckig
372
+
373
+ data.x_list.append([delta_x*dt])#+delta_dx*dt**2/2+delta_ddx*dt**2/6])
374
+ data.dx_list.append([delta_dx*dt])#+delta_ddx*dt**2/2])
375
+ data.ddx_list.append([delta_ddx*dt])
376
+ data.dddx_list.append([delta_ddx])
377
+ else:
378
+
379
+ delta_x = (inp.current_position[0] - data.x_list[-1][0])/dt_ruckig
380
+ delta_dx = (inp.current_velocity[0] - data.dx_list[-1][0])/dt_ruckig
381
+ delta_ddx = (inp.current_acceleration[0] - data.ddx_list[-1][0])/dt_ruckig
382
+ data.x_list.append([data.x_list[-1][0] + delta_x*dt])
383
+ data.dx_list.append([data.dx_list[-1][0]+ delta_dx*dt])
384
+ data.ddx_list.append([data.ddx_list[-1][0] + delta_ddx*dt])
385
+ data.dddx_list.append([delta_ddx])
386
+
387
+ out_position = inp.current_position[0]
388
+ out_velocity = inp.current_velocity[0]
389
+ out_acceleration = inp.current_acceleration[0]
390
+
391
+ t_old_ruckig = t_sim_ruckig
392
+ else:
393
+
394
+ data.x_list.append([data.x_list[-1][0] + delta_x*dt])
395
+ data.dx_list.append([data.dx_list[-1][0] + delta_dx*dt])
396
+ data.ddx_list.append([data.ddx_list[-1][0] + delta_ddx*dt])
397
+ data.dddx_list.append([delta_ddx])
398
+
399
+ if t_sim % n_ruckig == 0:
400
+ if ds_final > 1e-1: ds_p=[ds_final]
401
+ else: ds_p= []
402
+ if dds_final > 1e-1: dds_p=[dds_final]
403
+ else: dds_p= []
404
+ if ddds_final > 1e-1: ddds_p=[ddds_final]
405
+ else: ddds_p= []
406
+ if dds_min_final < -1e-1: dds_min_p=[dds_min_final]
407
+ else: dds_min_p= []
408
+
409
+ # print(ds_p,dds_p,ddds_p)
410
+ c = u@J
411
+ Aeq = V2@J
412
+ try:
413
+ c_ext = np.hstack((c,c,c,-c))
414
+ Aeq_ext=block_diag(Aeq,Aeq,Aeq,Aeq)
415
+
416
+ #beq_filt = alpha*beq_filt + (1 - alpha)*-V2@J_dot@dq_c
417
+ beq_filt = -V2@J_dot@dq_c
418
+
419
+ # alpha_ds = 1.0
420
+ # db = u@J_dot@dq_c
421
+ # ddb = (db - db_old) # gradient of the bias term
422
+ # dds_min = c@dddq_min
423
+ # if ddb:
424
+ # dt_dds_min_to_zero = dds_min/ddb
425
+ # else:
426
+ # dt_dds_min_to_zero = 1.0
427
+ # if dt_dds_min_to_zero < 0 and dt_dds_min_to_zero >- 1:
428
+ # alpha_ds = 1.0 - np.abs(dt_dds_min_to_zero)
429
+ # else:
430
+ # alpha_ds = 1.0
431
+ # print(alpha_ds)
432
+ # db_old = u@J_dot@dq_c
433
+
434
+ # dds_max = c@dddq_max
435
+ # if ddb:
436
+ # dt_dds_max_to_zero = dds_p[-1]/np.abs(ddb)
437
+ # else:
438
+ # dt_dds_max_to_zero = 1.0
439
+ # if ddb < 0 and dt_dds_max_to_zero > 0:# and dt_dds_max_to_zero < 10:
440
+ # alpha_ds = np.min([alpha_ds, (dt_dds_max_to_zero)/1000.0])
441
+ # print(alpha_ds, dt_dds_max_to_zero)
442
+ # else:
443
+ # alpha_ds = np.min([alpha_ds, 1.0])
444
+
445
+ beq_ext = np.hstack((beq,-V2@J_dot@dq_c,-V2@J_dot@ddq_c,beq_filt))
446
+ #beq_ext = np.hstack((beq,beq,beq,beq))
447
+ x_min_ext = np.hstack((alpha_ds*dq_min,ddq_min,dddq_min,ddq_min))
448
+ x_max_ext = np.hstack((alpha_ds*dq_max,ddq_max,dddq_max,ddq_max))
449
+ q_ext = solve_lp(-c_ext, Aeq_ext, beq_ext, x_min_ext, x_max_ext)
450
+ ds_p.append(c@q_ext[:n_dof])
451
+ dds_p.append(c@q_ext[n_dof:(2*n_dof)] + u@J_dot@dq_c)
452
+ ddds_p.append(c@q_ext[(2*n_dof):(3*n_dof)] + u@J_dot@ddq_c)
453
+ dds_min_p.append(c@q_ext[(3*n_dof):(4*n_dof)] + u@J_dot@dq_c)
454
+
455
+
456
+ dt_to_zero = 1.0
457
+
458
+ # dds_min_grad = dds_min_p[-1] - dds_min_old
459
+ # if dds_min_grad :
460
+ # dt_dds_min_to_zero = np.abs(dds_p[-1])/np.abs(dds_min_grad)
461
+ # else:
462
+ # dt_dds_min_to_zero = 10000.0
463
+ # if dds_min_grad > 0 and dt_dds_min_to_zero > 0 and dt_dds_min_to_zero < dt_to_zero:
464
+ # alpha_ds = np.min([alpha_ds, 1-(dt_dds_min_to_zero)/dt_to_zero])
465
+ # else:
466
+ # alpha_ds = np.min([alpha_ds, 1.0])
467
+
468
+ # dds_max_grad = (dds_p[-1] - dds_max_old)/0.001*dt_to_zero
469
+ # # alpha_ds_new = 1.0
470
+ # if dds_max_grad :
471
+ # dt_dds_max_to_zero = dds_p[-1]/np.abs(dds_max_grad)
472
+ # else:
473
+ # dt_dds_max_to_zero = 10000.0
474
+ # if dds_p[-1] + dds_max_grad < 0 or dds_p[-1] < 0:
475
+ # # alpha_ds_new = np.min([alpha_ds_new, np.max([0.01, 0.1])])
476
+ # alpha_ds = alpha_ds-0.05
477
+ # else:
478
+ # # alpha_ds_new = np.min([alpha_ds_new, 1.0])
479
+ # alpha_ds = alpha_ds+0.005
480
+
481
+ # # alpha = 0.5
482
+ # # alpha_ds = (1-alpha)* alpha_ds + alpha*alpha_ds_new
483
+ # alpha_ds = np.clip(alpha_ds, 0.01, 1)
484
+
485
+ except:
486
+ if verbose: print("except dds")
487
+ # return
488
+ beq = np.zeros(5)
489
+ # ds_p.append(c@solve_lp(-c, A_eq=Aeq, b_eq=np.zeros(5), x_min=dq_min, x_max=dq_max))
490
+ # dds_p.append(c@solve_lp(-c, A_eq=Aeq, b_eq=np.zeros(5), x_min=ddq_min, x_max=ddq_max) + u@J_dot@dq_c)
491
+ # ddds_p.append(c@solve_lp(-c, A_eq=Aeq, b_eq=np.zeros(5), x_min=dddq_min, x_max=dddq_max) + u@J_dot@ddq_c)
492
+ # dds_min_p.append(c@solve_lp(c, A_eq=Aeq, b_eq=np.zeros(5), x_min=ddq_min, x_max=ddq_max) + u@J_dot@dq_c)
493
+
494
+ # ds_p.append(ds_p[-1])
495
+ # dds_p.append(dds_p[-1])
496
+ # ddds_p.append(ddds_p[-1])
497
+ # dds_min_p.append(dds_min_p[-1])
498
+
499
+ # beq_filt = alpha*beq_filt + (1 - alpha)*-V2@J_dot@dq_c
500
+ beq_filt = -V2@J_dot@dq_c
501
+ try:
502
+ ds_p.append(c@solve_lp(-c, A_eq=Aeq, b_eq=beq, x_min=dq_min, x_max=dq_max))
503
+ except:
504
+ ds_p.append(ds_p[-1])
505
+ try:
506
+ dds_p.append(c@solve_lp(-c, A_eq=Aeq, b_eq=-V2@J_dot@dq_c, x_min=ddq_min, x_max=ddq_max) + u@J_dot@dq_c)
507
+ except:
508
+ done = False
509
+ for i in range(1,10):
510
+ try:
511
+ dds_p.append(c@solve_lp(-c, A_eq=Aeq, b_eq=-V2@J_dot@dq_c/i, x_min=ddq_min, x_max=ddq_max) + u@J_dot@dq_c)
512
+ done = True
513
+ break
514
+ except:
515
+ continue
516
+ if not done:
517
+ dds_p.append(dds_p[-1])
518
+ try:
519
+ ddds_p.append(c@solve_lp(-c, A_eq=Aeq, b_eq=-V2@J_dot@ddq_c, x_min=dddq_min, x_max=dddq_max) + u@J_dot@ddq_c)
520
+ except:
521
+ ddds_p.append(ddds_p[-1])
522
+ try:
523
+ dds_min_p.append(c@solve_lp(c, A_eq=Aeq, b_eq=beq_filt, x_min=ddq_min, x_max=ddq_max) + u@J_dot@dq_c)
524
+ except:
525
+ done = False
526
+ for i in range(1,10):
527
+ try:
528
+ dds_min_p.append(c@solve_lp(c, A_eq=Aeq, b_eq=beq_filt/i, x_min=ddq_min, x_max=ddq_max) + u@J_dot@dq_c)
529
+ done = True
530
+ break
531
+ except:
532
+ continue
533
+ if not done:
534
+ dds_min_p.append(dds_min_p[-1])
535
+
536
+
537
+ # if len(data.dx_max_list):
538
+ # ds_p[-1] = 0.85*data.dx_max_list[-1] + 0.15*ds_p[-1]
539
+ # if len(data.ddx_max_list) :
540
+ # dds_p[-1] = 0.85*data.ddx_max_list[-1] + 0.15*dds_p[-1]
541
+ # if len(data.ddx_min_list):
542
+ # dds_min_p[-1] = 0.85*data.ddx_min_list[-1] + 0.15*dds_min_p[-1]
543
+
544
+ data.dx_max_list.append(ds_p[-1])
545
+ data.ddx_max_list.append(dds_p[-1])
546
+ data.dddx_max_list.append(ddds_p[-1])
547
+ data.dx_min_list.append(-ds_p[-1])
548
+ data.ddx_min_list.append(dds_min_p[-1])
549
+ data.dddx_min_list.append(-ddds_p[-1])
550
+
551
+ if ds_p[-1]<=1e-4 :
552
+ if verbose: print("Error - sinularity or infeasible position for ds")
553
+ data = None
554
+ return
555
+ if dds_p[-1]<=1e-4:
556
+ if verbose: print("Error - sinularity or infeasible position for dds")
557
+ if stop_on_singular:
558
+ data = None
559
+ return
560
+ else:
561
+ dds_p[-1] = 0.05
562
+ if ddds_p[-1]<=1e-4:
563
+ if verbose: print("Error - sinularity or infeasible position for ddds")
564
+ if stop_on_singular:
565
+ data = None
566
+ return
567
+ else:
568
+ ddds_p[-1] = 0.05
569
+
570
+ if dds_min_p[-1]>=-1e-4:
571
+ if verbose: print("Error - sinularity or infeasible position for dds_min")
572
+ if stop_on_singular:
573
+ data = None
574
+ return
575
+ else:
576
+ dds_min_p[-1] = -0.05
577
+
578
+
579
+ if uptate_current_position:
580
+ # if current position is less than 2cm from the target one
581
+ # update the current position
582
+ # else dont update
583
+ if np.abs(data.x_q_list[-1] - inp.target_position[0]) > 0.01:
584
+ inp.current_position = [data.x_q_list[-1]]
585
+
586
+ if t_sim % n_ruckig == 0:
587
+
588
+
589
+ tmp_max_vel = inp.max_velocity[0]
590
+ tmp_current_vel = inp.current_velocity[0]
591
+
592
+ if calculate_limits:
593
+ if override_acceleration:
594
+ inp.current_acceleration = ruckig_current_accel
595
+
596
+ clamped = False
597
+ if calculate_limits:
598
+ inp.max_velocity =[(ds_p[-1])]
599
+ inp.max_acceleration=[(dds_p[-1])]
600
+ inp.max_jerk=[(ddds_p[-1])]
601
+ if clamp_min_accel:
602
+ inp.min_acceleration=[np.max(dds_min_p)]
603
+ else:
604
+ inp.min_acceleration=[(dds_min_p[-1])]
605
+
606
+ if clamp_velocity:
607
+ if inp.current_velocity[0] > inp.max_velocity[0]:
608
+ clamped = True
609
+ inp.current_velocity = inp.max_velocity
610
+
611
+ res = otg.update(inp, out)
612
+ out.pass_to_input(inp)
613
+ # print(inp.current_acceleration)
614
+
615
+ if calculate_limits:
616
+ if clamped:
617
+ inp.current_acceleration = [np.max([(inp.max_velocity[0] - tmp_max_vel)/dt_ruckig, inp.min_acceleration[0]])]
618
+
619
+
620
+ if override_acceleration:
621
+ ruckig_current_accel = inp.current_acceleration
622
+ inp.current_acceleration = [(inp.current_velocity[0] - tmp_current_vel)/dt_ruckig]
623
+
624
+ inp.current_acceleration = [(1-alpha)*inp.current_acceleration[0] + alpha*old_accel]
625
+ old_accel = inp.current_acceleration[0]
626
+ t_sim_ruckig = t_sim_ruckig + out.time;
627
+
628
+
629
+ if 'acceleration' in qp_form:
630
+ t_h = 0.01
631
+ if not scaled_qp_limits:
632
+ ddq_ub = np.vstack(
633
+ [
634
+ lims['ddq_max'],
635
+ (t_h*lims['dddq_max'] + ddq_c).flatten(),
636
+ ((lims['dq_max'] - dq_c)/t_h).flatten(),
637
+ (2*(lims['q_max'] - dq_c*t_h - q_c)/t_h**2).flatten()
638
+ ]).min(axis=0)
639
+ ddq_lb = np.vstack(
640
+ [
641
+ -lims['ddq_max'],
642
+ (t_h*-lims['dddq_max'] + ddq_c).flatten(),
643
+ ((-lims['dq_max'] - dq_c)/t_h).flatten(),
644
+ (2*(lims['q_min'] - dq_c*t_h - q_c)/t_h**2).flatten()
645
+ ]).max(axis=0)
646
+ else:
647
+ ddq_ub = np.vstack(
648
+ [
649
+ ddq_max,
650
+ (t_h*dddq_max + ddq_c).flatten(),
651
+ ((dq_max - dq_c)/t_h).flatten(),
652
+ (2*(q_max - dq_c*t_h - q_c)/t_h**2).flatten()
653
+ ]).min(axis=0)
654
+ ddq_lb = np.vstack(
655
+ [
656
+ ddq_min,
657
+ (t_h*dddq_min + ddq_c).flatten(),
658
+ ((dq_min - dq_c)/t_h).flatten(),
659
+ (2*(q_min - dq_c*t_h - q_c)/t_h**2).flatten()
660
+ ]).max(axis=0)
661
+ elif 'velocity' in qp_form:
662
+ t_h = dt
663
+ if not scaled_qp_limits:
664
+ dq_ub = np.vstack(
665
+ [
666
+ lims['dq_max'],
667
+ (t_h*lims['ddq_max'] + dq_c).flatten(),
668
+ ((lims['q_max'] - q_c)/t_h).flatten(),
669
+ (0.5*((t_h*5)**2)*lims['dddq_max'] + t_h*ddq_c + dq_c).flatten()
670
+ ]
671
+ ).min(axis=0)
672
+ dq_lb = np.vstack(
673
+ [
674
+ -lims['dq_max'],
675
+ (t_h*-lims['ddq_max'] + dq_c).flatten(),
676
+ ((lims['q_min'] - q_c)/t_h).flatten(),
677
+ ((0.5*((t_h*5)**2)*-lims['dddq_max'] + t_h*ddq_c + dq_c).flatten())
678
+ ]
679
+ ).max(axis=0)
680
+ else:
681
+ dq_ub = np.vstack(
682
+ [
683
+ dq_max,
684
+ (t_h*ddq_max + dq_c).flatten(),
685
+ ((q_max - q_c)/t_h).flatten(),
686
+ (0.5*((t_h*5)**2)*dddq_max + t_h*ddq_c + dq_c).flatten()
687
+ ]
688
+ ).min(axis=0)
689
+ dq_lb = np.vstack(
690
+ [
691
+ dq_min,
692
+ (t_h*ddq_min + dq_c).flatten(),
693
+ ((q_min - q_c)/t_h).flatten(),
694
+ ((0.5*((t_h*5)**2)*dddq_min + t_h*ddq_c + dq_c).flatten())
695
+ ]
696
+ ).max(axis=0)
697
+ # print('sc',dq_ub, dq_lb)
698
+ # J = robot.jacob0(q_c)
699
+ c = u@J
700
+ q_c_old = q_c.copy()
701
+ dq_c_old = dq_c.copy()
702
+ ddq_c_old = ddq_c.copy()
703
+
704
+ if 'acceleration' in qp_form:
705
+ # acceleration feed-forward + pd
706
+ ddx_des = data.ddx_list[-1]*u[:,None]
707
+ ddx_des = ddx_des + Kd*(data.dx_list[-1]*u - J@dq_c_old)[:,None]
708
+ # # only translation
709
+ # # ddx_des[:3] = ddx_des[:3] + 70*SE3((np.array(data.x_list[-1]*u[:3]) - (robot.fkine(q_c_old).t - X_init.translation)).log(True)[:3,None]
710
+ # translation + rotation
711
+ X_dk = pin.SE3(X_init.rotation, X_init.translation+np.array(data.x_list[-1]*u[:3]))
712
+ X_wa = pin.SE3(X_init.rotation, np.zeros(3))
713
+ X_rk = pin.SE3(X_c.rotation, X_c.translation)
714
+ X_log = X_dk.actInv(X_rk)
715
+ log_dk = pin.log6(X_log)
716
+ err = (-(X_wa.toActionMatrix()@log_dk)[:,None])
717
+ err_sum = err_sum + err*dt
718
+ ddx_des = ddx_des + Kp*err + Ki*err_sum
719
+ ddx_des = ddx_des - (J_dot@dq_c_old)[:,None]
720
+ grad = (q0-q_c_old) + 2*(-dq_c_old)
721
+ if use_manip_grad:
722
+ grad = manip_grad_w*robot.jacobm(q_c).reshape((-1,))
723
+
724
+ ddq_c = solve_qp(J,ddx_des, ddq_lb, ddq_ub, grad=-grad, reg_w=5*0.00001, solver='cvxopt')
725
+ if ddq_c is None:
726
+ print("No QP solution found")
727
+ data.solved= False
728
+ break
729
+
730
+ dq_c = dq_c + ddq_c*dt
731
+ q_c = np.clip(dq_c*dt + ddq_c*(dt**2)/2 + q_c, q_min,q_max)
732
+ dq_c = np.clip(dq_c, dq_min, dq_max)
733
+ dddq_c = (ddq_c - ddq_c_old)/dt
734
+
735
+ elif 'velocity' in qp_form:
736
+ # translation + rotation
737
+ dx_des = data.dx_list[-1]*u[:,None]
738
+ X_dk = pin.SE3(X_init.rotation, X_init.translation+np.array(data.x_list[-1]*u[:3]))
739
+ X_rk = pin.SE3(X_c.rotation, X_c.translation)
740
+ X_log = X_dk.actInv(X_rk)
741
+ log_dk = pin.log6(X_log)
742
+ dx_des = dx_des + Kp/1000.0*(-(X_dk.toActionMatrix()@log_dk)[:,None])
743
+
744
+ grad = ((q_min+q_max)/2-q_c_old) + 0.1*(-dq_c_old)
745
+ if use_manip_grad:
746
+ grad = manip_grad_w*robot.jacobm(q_c).reshape((-1,))
747
+
748
+ dq_c = solve_qp(J, dx_des, dq_lb, dq_ub, grad=-grad, reg_w=5*0.00001, solver='quadprog')
749
+ if dq_c is None:
750
+ print("No QP solution found")
751
+ data.solved= False
752
+ break
753
+
754
+
755
+ # robot simulation
756
+ if np.any(dq_c > dq_max) or np.any(dq_c < dq_min) :
757
+ print("vel outside limits")
758
+ ddq_c = (dq_c - dq_c_old)/dt
759
+ if np.any(ddq_c > 1.01*ddq_max) or np.any(ddq_c < 1.01*ddq_min) :
760
+ #print(ddq_c, ddq_max, ddq_min, ddq_c > ddq_max, ddq_c < ddq_min)
761
+ print("accel outside limits")
762
+ dddq_c = (ddq_c - ddq_c_old)/dt
763
+ if np.any(dddq_c > 1.5*dddq_max) or np.any(dddq_c < 1.5*dddq_min) :
764
+ #print(dddq_c, dddq_max, dddq_min, dddq_c[dddq_c > dddq_max], dddq_c < dddq_min)
765
+ print("jerk outside limits")
766
+ q_c = np.clip(dq_c_old*dt + ddq_c*(dt**2)/2 + q_c_old, q_min,q_max)
767
+
768
+
769
+ t_sim = t_sim+1
770
+
771
+ # data.e_pos_list_ruckig.append(np.linalg.norm(V23@(X_c.t-X_init.translation)))
772
+ data.e_pos_list_ruckig.append(np.linalg.norm((X_c.translation-X_init.translation-np.array(data.x_list[-1]*u[:3]))))
773
+ data.e_rot_list_ruckig.append(np.linalg.norm(pin.log3(X_init.rotation.T@X_c.rotation)))
774
+
775
+ print(f'Calculation duration: {time.time()-s} [s]')
776
+ data.t_ruckig = np.array(range(0,len(data.x_list)))*dt
777
+ print(f'Trajectory duration: {data.t_ruckig[-1]:0.4f} [s]')
778
+ return data
779
+
780
+
781
+
782
+ def rand_num(delta):
783
+ return np.random.rand()*2*delta - delta
784
+
785
+ def find_random_poses_with_distance(distance, robot, q0, iterations = 10, joint_limits=True, verify_line = False , n_waypoints = 10 ,angle =np.pi/6):
786
+ X_r = robot.fkine(q0)
787
+ found = False
788
+ while not found:
789
+ print("searching")
790
+ X_init = None
791
+ while X_init is None or np.linalg.norm(robot.fkine(robot.ikine_LM(X_init, q0, joint_limits=joint_limits).q).t-X_init.t) > 1e-5:
792
+ X_init = SE3(np.random.rand(1,3)*distance-0.5*distance+np.array([0.5,0,0.4]))*SE3(SO3(X_r.R))*SE3(SO3.Rx(rand_num(angle)))*SE3(SO3.Ry(rand_num(angle)))
793
+
794
+ X_final = None
795
+ i = 0
796
+ while (X_final is None or np.linalg.norm(robot.fkine(robot.ikine_LM(X_final, q0, joint_limits=joint_limits).q).t-X_final.t) > 1e-5) and i <= iterations:
797
+ print("not attainable final")
798
+ v= np.random.rand(3)*2-1
799
+ v = v/np.linalg.norm(v)*distance
800
+ X_final = SE3(X_init.t + v)*SE3(SO3(X_init.R))
801
+ i = i+1
802
+ if i < iterations:
803
+ found = True
804
+
805
+ if verify_line:
806
+ q_line = [ robot.ikine_LM(X_init,q0,joint_limits=joint_limits).q ]
807
+ X_i = np.linspace(X_init.t, X_final.t, n_waypoints)
808
+ print(n_waypoints)
809
+ for x in X_i[1:]:
810
+ T = SE3(x)*SE3(SO3(X_init.R))
811
+ sol = robot.ikine_LM(T,q_line[-1],joint_limits=joint_limits) # solve IK
812
+ X_found = robot.fkine(sol.q)
813
+ if np.linalg.norm(robot.fkine(robot.ikine_LM(X_found, joint_limits=joint_limits).q).t-T.t) > 1e-5:
814
+ break
815
+ q_line.append(sol.q)
816
+
817
+ if len(q_line) < len(X_i):
818
+ print("staright line not possible")
819
+ continue
820
+
821
+
822
+
823
+ return X_init, X_final, q_line
824
+
825
+
826
+ def find_random_poses_with_distance_pinocchio(distance, robot, q0, iterations = 10, joint_limits=True, verify_line = False , n_waypoints = 10,angle =np.pi/6):
827
+ X_r = robot.forward(q0)
828
+ found = False
829
+ while not found:
830
+ print("searching")
831
+ X_init = None
832
+ while X_init is None or np.linalg.norm(robot.forward(robot.ik(X_init, q0, qlim=joint_limits, verbose=False)).translation-X_init.translation) > 1e-3:
833
+ X_init = pin.SE3(X_r.rotation, (np.random.rand(1,3)*distance-0.5*distance+np.array([0.5,0,0.4])).reshape((3,)))
834
+ x,y = rand_num(angle),rand_num(angle)
835
+ X_init = X_init*pin.SE3(pin.rpy.rpyToMatrix(x, y,0), np.zeros(3))
836
+
837
+ X_final = None
838
+ i = 0
839
+ while (X_final is None or np.linalg.norm(robot.forward(robot.ik(X_final, q0, qlim=joint_limits, verbose=False)).translation-X_final.translation) > 1e-3) and i <= iterations:
840
+ print("not attainable final")
841
+ v= np.random.rand(3)*2-1
842
+ v = v/np.linalg.norm(v)*distance
843
+ X_final = pin.SE3(X_init.rotation, X_init.translation + v)
844
+ i = i+1
845
+
846
+ if i < iterations:
847
+ found = True
848
+
849
+ if verify_line:
850
+ print("verify straitgh line")
851
+ q_line = [ robot.ik(X_init,q0,qlim=joint_limits, verbose=False) ]
852
+ X_i = np.linspace(X_init.translation, X_final.translation, n_waypoints)
853
+ print(n_waypoints)
854
+ for x in X_i[1:]:
855
+ T = pin.SE3(X_init.rotation, x)
856
+ q = robot.ik(T, q_line[-1], qlim=joint_limits, verbose=False) # solve IK
857
+ X_found = robot.forward(q)
858
+ if np.linalg.norm(robot.forward(robot.ik(X_found, qlim=joint_limits, verbose=False)).translation-T.translation) > 1e-3:
859
+ print(robot.forward(robot.ik(X_found, qlim=joint_limits, verbose=False)).translation, T.translation)
860
+ break
861
+ q_line.append(q)
862
+
863
+ if len(q_line) < len(X_i):
864
+ print("staright line not possible")
865
+ found = False
866
+ continue
867
+
868
+ return X_init, X_final, q_line
869
+
870
+
871
+ def simulate_toppra(X_init, X_final, ts, qs, qds, qdds, q0, lims, scale, robot = None, options=None , robot_pyn = None ):
872
+ s = time.time()
873
+ data = TrajData()
874
+
875
+ print('simulating trajectory')
876
+
877
+ # check the type of the X_init and X_final
878
+ if type(X_init) == np.ndarray:
879
+ X_init = pin.SE3(X_init[:3,:3], X_init[:3,3])
880
+ X_final = pin.SE3(X_final[:3,:3], X_final[:3,3])
881
+ elif type(X_init) == pin.SE3:
882
+ pass
883
+ elif SPATIAL_MATH and (type(X_init) == spatialmath.pose3d.SE3):
884
+ X_init = pin.SE3(X_init.R, X_init.t)
885
+ X_final = pin.SE3(X_final.R, X_final.t)
886
+ else:
887
+ print("Unknown type of X_init")
888
+ return
889
+
890
+ u = np.zeros(6)
891
+ u[:3] = X_final.translation-X_init.translation
892
+ u = u/np.linalg.norm(u)
893
+
894
+ # limtis calculation
895
+ dq_max = scale*lims['dq_max']
896
+ ddq_max = scale*lims['ddq_max']
897
+ dddq_max = scale*lims['dddq_max']
898
+ q_min, q_max = lims['q_min'], lims['q_max']
899
+ dq_min = -dq_max
900
+ ddq_min = -ddq_max
901
+ dddq_min = -dddq_max
902
+
903
+ if options is not None and 'calculate_limits' in options.keys():
904
+ calculate_limits = options['calculate_limits']
905
+ else:
906
+ calculate_limits = True
907
+
908
+
909
+ if calculate_limits:
910
+ U,S,V = np.linalg.svd(u[:,None])
911
+ V2 = U[:,1:].T
912
+
913
+ data.ds_max_list=[]
914
+ data.dds_max_list=[]
915
+ data.ddds_max_list=[]
916
+ data.ds_min_list=[]
917
+ data.dds_min_list=[]
918
+ data.ddds_min_list=[]
919
+
920
+ data.x3d_top = []
921
+ data.x_top = []
922
+ data.dx_top = []
923
+ data.ddx_top = []
924
+ data.dddx_top = []
925
+ data.qr_list = []
926
+
927
+ U,S,V = np.linalg.svd(u[:3,None])
928
+ V23 = U[:,1:].T
929
+ data.e_pos_list, data.e_rot_list = [], []
930
+
931
+ q_c = qs[0]
932
+
933
+ if robot_pyn is not None:
934
+ dq_c, ddq_c, dddq_c = np.zeros(robot_pyn.n), np.zeros(robot_pyn.n), np.zeros(robot_pyn.n)
935
+ elif robot is not None:
936
+ dq_c, ddq_c, dddq_c = np.zeros(robot.n), np.zeros(robot.n), np.zeros(robot.n)
937
+ else:
938
+ print("No robot model provided")
939
+ return
940
+
941
+ t_last = 0
942
+ for t, q,qd,qdd in zip(ts, qs,qds,qdds):
943
+
944
+
945
+ if robot_pyn is not None:
946
+ X_c = robot_pyn.forward(q_c)
947
+ X_dc = robot_pyn.forward(q)
948
+ J =robot_pyn.jacobian(q_c)
949
+ J_dot = robot_pyn.jacobian_dot(q_c,dq_c)
950
+ x_t = X_c.translation
951
+ data.x3d_top.append(x_t)
952
+ data.x_top.append(u[:3]@(x_t-X_init.translation))
953
+ c = u@J
954
+ c_dot = u@J_dot
955
+ data.dx_top.append(c@dq_c)
956
+ data.ddx_top.append(c@ddq_c + c_dot@dq_c)
957
+ data.dddx_top.append(c@dddq_c+ c_dot@ddq_c)
958
+
959
+ data.qr_list.append(q_c)
960
+
961
+ data.e_pos_list.append(np.linalg.norm((X_c.translation-X_dc.translation)))
962
+ # data.e_pos_list.append(np.linalg.norm(V23@(X_c.t-X_init.t)))
963
+ data.e_rot_list.append(np.linalg.norm(pin.log3(X_init.rotation.T@X_c.rotation)))
964
+
965
+ elif robot is not None:
966
+ X_c = robot.fkine(q_c)
967
+ X_dc = robot.fkine(q)
968
+ J =robot.jacob0(q_c)
969
+ J_dot = robot.jacob0_dot(q_c,dq_c)
970
+
971
+ x_t = X_c.t
972
+ data.x3d_top.append(x_t)
973
+ data.x_top.append(u[:3]@(x_t-X_init.translation))
974
+ c = u@J
975
+ c_dot = u@J_dot
976
+ data.dx_top.append(c@dq_c)
977
+ data.ddx_top.append(c@ddq_c + c_dot@dq_c)
978
+ data.dddx_top.append(c@dddq_c+ c_dot@ddq_c)
979
+
980
+ data.qr_list.append(q_c)
981
+
982
+ data.e_pos_list.append(np.linalg.norm((X_c.t-X_dc.t)))
983
+ # data.e_pos_list.append(np.linalg.norm(V23@(X_c.t-X_init.t)))
984
+ data.e_rot_list.append(np.linalg.norm(pin.log3(X_init.rotation.T@X_c.R)))
985
+
986
+ dt = t - t_last
987
+ t_last = t
988
+ if not dt:
989
+ dt =0.001
990
+
991
+ # calculate position + limit
992
+ q_c = q #dq_c*dt + q_c #+ ddq_c*(dt**2)/2 + q_c
993
+ q_c = np.clip(q_c, q_min, q_max)
994
+
995
+ dq_c_old = dq_c
996
+ ddq_c_old = ddq_c
997
+ # calculate velocity + limit
998
+ # dq_c = qd #dq_c + ddq_c*dt
999
+ dq_c = qd#np.clip(qd, dt*ddq_min+dq_c, dt*ddq_max+dq_c)
1000
+ dq_c = np.clip(dq_c, dq_min, dq_max)
1001
+ # limlit jerk
1002
+ ddq_c = qdd#np.clip((dq_c-dq_c_old)/dt, dt*dddq_min+ddq_c, dt*dddq_max+ddq_c)
1003
+ # limlit acceleration
1004
+ # ddq_c = qdd
1005
+ ddq_c = np.clip(ddq_c, ddq_min, ddq_max)
1006
+ # calculate jerk
1007
+ dddq_c = (ddq_c - ddq_c_old)/dt
1008
+
1009
+
1010
+
1011
+ if calculate_limits:
1012
+ Aeq = V2@J
1013
+ beq = np.zeros(5)
1014
+ try:
1015
+ data.ds_max_list.append(c@solve_lp(-c, A_eq=Aeq, b_eq=beq, x_min=dq_min, x_max=dq_max))
1016
+ data.dds_max_list.append(c@solve_lp(-c, A_eq=Aeq, b_eq=-V2@J_dot@dq_c, x_min=ddq_min, x_max=ddq_max) + u@J_dot@dq_c)
1017
+ data.ddds_max_list.append(c@solve_lp(-c, A_eq=Aeq, b_eq=-V2@J_dot@ddq_c, x_min=dddq_min, x_max=dddq_max) + u@J_dot@ddq_c)
1018
+ data.ds_min_list.append(c@solve_lp(c, A_eq=Aeq, b_eq=beq, x_min=dq_min, x_max=dq_max))
1019
+ data.dds_min_list.append(c@solve_lp(c, A_eq=Aeq, b_eq=-V2@J_dot@dq_c, x_min=ddq_min, x_max=ddq_max) + u@J_dot@dq_c)
1020
+ data.ddds_min_list.append(c@solve_lp(c, A_eq=Aeq, b_eq=-V2@J_dot@ddq_c, x_min=dddq_min, x_max=dddq_max) + u@J_dot@ddq_c)
1021
+ except:
1022
+ print("except dds")
1023
+ data.ds_max_list.append(c@solve_lp(-c, A_eq=Aeq, b_eq=np.zeros(5), x_min=dq_min, x_max=dq_max))
1024
+ data.dds_max_list.append(c@solve_lp(-c, A_eq=Aeq, b_eq=np.zeros(5), x_min=ddq_min, x_max=ddq_max) + u@J_dot@dq_c)
1025
+ data.ddds_max_list.append(c@solve_lp(-c, A_eq=Aeq, b_eq=np.zeros(5), x_min=dddq_min, x_max=dddq_max) + u@J_dot@ddq_c)
1026
+ data.ds_min_list.append(c@solve_lp(c, A_eq=Aeq, b_eq=np.zeros(5), x_min=dq_min, x_max=dq_max))
1027
+ data.dds_min_list.append(c@solve_lp(c, A_eq=Aeq, b_eq=np.zeros(5), x_min=ddq_min, x_max=ddq_max) + u@J_dot@dq_c)
1028
+ data.ddds_min_list.append(c@solve_lp(c, A_eq=Aeq, b_eq=np.zeros(5), x_min=dddq_min, x_max=dddq_max) + u@J_dot@ddq_c)
1029
+
1030
+ data.t_toppra = ts
1031
+ print('TOPPRA trajecotry simulation time',time.time() - s)
1032
+ print(f'Trajectory duration: {data.t_toppra[-1]:0.4f} [s]')
1033
+ return data
1034
+
1035
+
1036
+ import toppra as ta
1037
+ import toppra.constraint as constraint
1038
+ import toppra.algorithm as algo
1039
+
1040
+ def caclulate_toppra_trajectory(X_init, X_final, q0, lims, scale, d_waypoint, robot=None, robot_pyn=None, data=None):
1041
+ s = time.time()
1042
+ # ta.setup_logging("INFO")
1043
+
1044
+
1045
+ if robot_pyn is not None:
1046
+ # check the type of the X_init and X_final
1047
+ if type(X_init) == np.ndarray:
1048
+ X_init = pin.SE3(X_init[:3,:3], X_init[:3,3])
1049
+ X_final = pin.SE3(X_final[:3,:3], X_final[:3,3])
1050
+ elif type(X_init) == pin.SE3:
1051
+ pass
1052
+ elif SPATIAL_MATH and (type(X_init) == spatialmath.pose3d.SE3):
1053
+ X_init = pin.SE3(X_init.R, X_init.t)
1054
+ X_final = pin.SE3(X_final.R, X_final.t)
1055
+ else:
1056
+ print("Unknown type of X_init")
1057
+ return
1058
+
1059
+ # limtis calculation
1060
+ dq_max = scale*lims['dq_max']
1061
+ ddq_max = scale*lims['ddq_max']
1062
+
1063
+ # calculate the waypoints
1064
+ if robot_pyn is not None:
1065
+ d = np.linalg.norm(X_final.translation-X_init.translation)
1066
+ else:
1067
+ d = np.linalg.norm(X_final.t-X_init.t)
1068
+ #number of waypoints in joint space
1069
+ n_waypoints = int(d/d_waypoint)
1070
+ # print(f'traj length: {d}, number of waypoints: {n_waypoints}')
1071
+
1072
+ if data is not None:
1073
+ print('using provided data')
1074
+ x_np = np.array(data.x_list)
1075
+ x_v = np.linspace(0,x_np[-1], n_waypoints)
1076
+ inds = []
1077
+ for x_wp in x_v:
1078
+ inds.append(np.where(x_np >= x_wp)[0][0])
1079
+ q_line = [data.qr_list[int(i)] for i in inds]
1080
+
1081
+ else:
1082
+ if robot is not None:
1083
+ # print('calculation waypoints')
1084
+ X_i = np.linspace(X_init.t,X_final.t,n_waypoints)
1085
+ q_line = [ robot.ikine_LM(X_init, q0).q ]
1086
+ for x in X_i[1:]:
1087
+ T = SE3(x)*SE3(SO3(X_init.R))
1088
+ sol = robot.ikine_LM(T,q_line[-1])#,joint_limits=true) # solve IK
1089
+ q_line.append(sol.q)
1090
+ else:
1091
+ # print('calculation waypoints')
1092
+ X_i = np.linspace(X_init.translation,X_final.translation,n_waypoints)
1093
+ q_line = [ robot_pyn.ik(X_init, q0, qlim=True, verbose=False) ]
1094
+ for x in X_i[1:]:
1095
+ T= pin.SE3(X_init.rotation, x)
1096
+ q_line.append(robot_pyn.ik(T,q_line[-1], qlim=True, verbose=False))
1097
+
1098
+
1099
+
1100
+ # print("Waypoints calculation time:",time.time()-s)
1101
+ s1 = time.time()
1102
+ # calculate the traectory
1103
+ ss = np.linspace(0,1,len(q_line))
1104
+ path = ta.SplineInterpolator(ss, q_line)
1105
+ pc_vel = constraint.JointVelocityConstraint(dq_max)
1106
+ pc_acc = constraint.JointAccelerationConstraint(ddq_max)
1107
+ instance = algo.TOPPRA([pc_vel, pc_acc], path, parametrizer="ParametrizeConstAccel")
1108
+ jnt_traj = instance.compute_trajectory()
1109
+
1110
+ ts_sample = np.arange(0, jnt_traj.duration, 0.001)
1111
+ qs_sample = jnt_traj(ts_sample)
1112
+ qds_sample = jnt_traj(ts_sample, 1)
1113
+ qdds_sample = jnt_traj(ts_sample, 2)
1114
+
1115
+ # print("TOPPRA calculation time:",time.time()-s1)
1116
+ # print("Waypoints+TOPPRA calculation time:",time.time()-s)
1117
+ return ts_sample, qs_sample, qds_sample, qdds_sample
1118
+ # return time.time()-s, time.time()-s1
requirements.txt ADDED
@@ -0,0 +1,15 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ gradio==5.49.1
2
+ plotly>=5.0.0
3
+ meshcat==0.3.2
4
+ pin==3.3.1
5
+ numpy
6
+ websockets==15.0.1
7
+
8
+ # Trajectory planning dependencies
9
+ example-robot-data==4.1.0
10
+ qpsolvers==4.4.0
11
+ cvxopt==1.3.2
12
+ scipy==1.14.1
13
+ ruckig==0.14.0
14
+ toppra
15
+ pynocchio @ git+https://github.com/askuric/pynocchio.git
robot.py ADDED
@@ -0,0 +1,321 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import numpy as np
2
+ import time
3
+ import sys
4
+ import os
5
+
6
+ import meshcat
7
+ import meshcat.geometry as g
8
+ import logging
9
+ logging.basicConfig(level=logging.INFO, format="%(asctime)s %(levelname)s %(message)s")
10
+
11
+ import pinocchio as pin
12
+ from pinocchio.visualize import MeshcatVisualizer
13
+
14
+ # Add catp to path if it exists
15
+ catp_path = os.path.expanduser("~/gitlab/catp")
16
+ if os.path.exists(catp_path):
17
+ sys.path.insert(0, catp_path)
18
+
19
+ try:
20
+ from example_robot_data import load
21
+ from pynocchio import RobotWrapper
22
+ PANDA_AVAILABLE = True
23
+ except ImportError:
24
+ PANDA_AVAILABLE = False
25
+ logging.warning("Panda robot libraries not available")
26
+
27
+
28
+ class TrajectoryPlanner:
29
+ """
30
+ Manages Panda robot with dual trajectory planning visualization.
31
+ """
32
+ def __init__(self):
33
+ if not PANDA_AVAILABLE:
34
+ raise ImportError("Panda robot libraries not available")
35
+
36
+ self.vis = meshcat.Visualizer(zmq_url="tcp://127.0.0.1:6001")
37
+ logging.info(f"Zmq URL: {self.vis.window.zmq_url}, web URL: {self.vis.window.web_url}")
38
+
39
+ # Load Panda robot
40
+ self.panda_pin = load('panda')
41
+ self.panda_pin.data = self.panda_pin.model.createData()
42
+
43
+ self.panda_tip_pin = "panda_hand_tcp"
44
+
45
+ # Import here to avoid circular dependency
46
+ from planning_utils import find_random_poses_with_distance_pinocchio
47
+ from planning_utils import compute_capacity_aware_trajectory, caclulate_toppra_trajectory, simulate_toppra
48
+ from meshcat_shapes import display_frame as display_frame_shapes
49
+ from meshcat_shapes import display, display_frame
50
+
51
+ self.find_random_poses = find_random_poses_with_distance_pinocchio
52
+ self.display = display
53
+ self.display_frame = display_frame
54
+ self.compute_capacity_aware_trajectory = compute_capacity_aware_trajectory
55
+ self.caclulate_toppra_trajectory = caclulate_toppra_trajectory
56
+ self.simulate_toppra = simulate_toppra
57
+
58
+ try:
59
+ self.panda_pyn = RobotWrapper(
60
+ robot_wrapper=self.panda_pin,
61
+ tip=self.panda_tip_pin,
62
+ open_viewer=False,
63
+ start_visualisation=True,
64
+ viewer=self.vis,
65
+ fix_joints=[
66
+ self.panda_pin.model.getJointId("panda_finger_joint1"),
67
+ self.panda_pin.model.getJointId("panda_finger_joint2")
68
+ ]
69
+ )
70
+ self.viz = self.panda_pyn.viz
71
+ self.viz.display_collisions = False
72
+ except Exception as e:
73
+ logging.error(f"Error initializing robot: {e}")
74
+ raise
75
+
76
+ # Setup visualization
77
+ self.vis["/Background"].set_property("top_color", [1, 1, 1])
78
+ self.vis["/Background"].set_property("bottom_color", [1, 1, 1])
79
+ self.vis["/Axes"].set_property("visible", False)
80
+ self.vis['/Grid'].set_property("visible", True)
81
+
82
+ # Default limits for Panda
83
+ q_min, q_max = self.panda_pyn.model.lowerPositionLimit, self.panda_pyn.model.upperPositionLimit
84
+ dq_max = self.panda_pyn.model.velocityLimit
85
+ ddq_max = np.array([15, 7.5, 10, 12.5, 15, 20, 20])
86
+ dddq_max = np.array([7500, 3750, 5000, 6250, 7500, 10000, 10000])
87
+ t_max = np.array([87, 87, 87, 87, 20, 20, 20])
88
+
89
+ dddx_max = np.array([6500.0, 6500.0, 6500.0])
90
+ ddx_max = np.array([13.0, 13, 13])
91
+ dx_max = np.array([1.7, 1.7, 1.7])
92
+
93
+ self.q0 = (self.panda_pyn.model.upperPositionLimit + self.panda_pyn.model.lowerPositionLimit) / 2
94
+
95
+ self.limits = {
96
+ 'q_min': q_min, 'q_max': q_max,
97
+ 'dq_max': dq_max, 'ddq_max': ddq_max,
98
+ 'dddq_max': dddq_max, 't_max': t_max,
99
+ 'dx_max': dx_max, 'ddx_max': ddx_max,
100
+ 'dddx_max': dddx_max
101
+ }
102
+
103
+ self.data = None
104
+ self.data_top = None
105
+ self.ts_sample = None
106
+ self.qs_sample = None
107
+ self.X_init = None
108
+ self.X_final = None
109
+ self.T_shift = None
110
+
111
+ # Setup dual robot visualization
112
+ self._setup_dual_robots()
113
+
114
+ def _setup_dual_robots(self):
115
+ """Setup two robots side by side for comparison"""
116
+ self.panda_ours = self.panda_pyn.robot
117
+ self.panda_toppra = self.panda_pyn.robot
118
+
119
+ self.panda_toppra.data = self.panda_toppra.model.createData()
120
+
121
+ self.viz_l = self.panda_pyn.viz
122
+ self.viz_r = MeshcatVisualizer(
123
+ self.panda_toppra.model,
124
+ self.panda_toppra.collision_model,
125
+ self.panda_toppra.visual_model
126
+ )
127
+
128
+ self.viz_r.initViewer(open=False, viewer=self.vis)
129
+ self.viz_r.loadViewerModel("toppra", color=[0.0, 0.0, 0.0, 0.5])
130
+
131
+ # Shift right robot
132
+ self.T_shift = np.eye(4)
133
+ self.T_shift[1, 3] = 0.5 # 0.5 meter along y
134
+ self.vis["toppra"].set_transform(self.T_shift)
135
+
136
+ # Update visualizations
137
+ self.display(self.viz_l, self.panda_ours, self.panda_tip_pin,
138
+ "end_effector_ruc", self.q0)
139
+ self.display(self.viz_r, self.panda_toppra, self.panda_tip_pin,
140
+ "end_effector_toppra", self.q0, self.T_shift)
141
+
142
+ def generate_trajectory(self, traj_length=0.8, scale=0.5, progress=None):
143
+ """Generate a random trajectory and compute both algorithms"""
144
+ logging.info(f"Generating trajectory: length={traj_length}, scale={scale}")
145
+
146
+ n_waypoints = int(traj_length / 0.05)
147
+ q0 = (self.panda_pyn.model.upperPositionLimit + self.panda_pyn.model.lowerPositionLimit) / 2
148
+
149
+ if progress is not None:
150
+ progress(0.1, desc="Finding random trajectory...")
151
+
152
+ # Generate random trajectory
153
+ self.X_init, self.X_final, q_line = self.find_random_poses(
154
+ robot=self.panda_pyn,
155
+ distance=traj_length,
156
+ q0=q0,
157
+ verify_line=True,
158
+ n_waypoints=n_waypoints,
159
+ angle=np.pi/2
160
+ )
161
+
162
+ # Display start and end frames
163
+ self.display(self.viz_l, self.panda_ours, self.panda_tip_pin,
164
+ "end_effector_ruc", q_line[0])
165
+ self.display(self.viz_r, self.panda_toppra, self.panda_tip_pin,
166
+ "end_effector_toppra", q_line[0])
167
+ self.display_frame(self.viz_l, "start", self.X_init.np)
168
+ self.display_frame(self.viz_l, "end", self.X_final.np)
169
+ self.display_frame(self.viz_r, "start1", self.T_shift@self.X_init.np)
170
+ self.display_frame(self.viz_r, "end1", self.T_shift@self.X_final.np)
171
+
172
+ # Draw straight line between start and end frames
173
+ line_start = self.X_init.np[:3, 3]
174
+ line_end = self.X_final.np[:3, 3]
175
+ line_vertices = np.column_stack([line_start, line_end])
176
+
177
+ self.vis["trajectory_line"].set_object(
178
+ g.Line(g.PointsGeometry(line_vertices),
179
+ g.MeshBasicMaterial(color=0x0000ff, linewidth=2))
180
+ )
181
+
182
+ # Also draw shifted line for right visualization
183
+ line_start_shifted = (self.T_shift @ self.X_init.np)[:3, 3]
184
+ line_end_shifted = (self.T_shift @ self.X_final.np)[:3, 3]
185
+ line_vertices_shifted = np.column_stack([line_start_shifted, line_end_shifted])
186
+
187
+ self.vis["trajectory_line_toppra"].set_object(
188
+ g.Line(g.PointsGeometry(line_vertices_shifted),
189
+ g.MeshBasicMaterial(color=0x0000ff, linewidth=2))
190
+ )
191
+
192
+ if progress is not None:
193
+ progress(0.3, desc="Calculating capacity aware trajectory")
194
+
195
+ # Compute trajectories
196
+ options = {
197
+ 'Kp': 600, 'Kd': 150, 'Ki': 0.0, 'Tf': 0.01,
198
+ 'uptate_current_position': True, 'clamp_velocity': True,
199
+ 'clamp_min_accel': True, 'scaled_qp_limits': True,
200
+ 'override_acceleration': True, 'scale_limits': True,
201
+ 'calculate_limits': True, 'downsampling_ratio': 1,
202
+ 'use_manip_grad': False, 'manip_grad_w': 5000.0,
203
+ 'dt': 0.001, 'qp_form': 'acceleration'
204
+ }
205
+
206
+ # Our approach
207
+ logging.info("Computing capacity-aware trajectory...")
208
+ self.data = self.compute_capacity_aware_trajectory(
209
+ self.X_init, self.X_final,
210
+ robot=None, robot_pyn=self.panda_pyn,
211
+ lims=self.limits, scale=scale,
212
+ options=options, q0=q0
213
+ )
214
+
215
+ if progress is not None:
216
+ progress(0.6, desc="Calculating TOPPRA trajectory")
217
+
218
+ # TOPPRA
219
+ logging.info("Computing TOPPRA trajectory...")
220
+ self.ts_sample, self.qs_sample, qds_sample, qdds_sample = self.caclulate_toppra_trajectory(
221
+ self.X_init, self.X_final,
222
+ robot_pyn=self.panda_pyn, q0=q0,
223
+ d_waypoint=0.05, lims=self.limits, scale=scale
224
+ )
225
+
226
+ self.data_top = self.simulate_toppra(
227
+ self.X_init, self.X_final,
228
+ self.ts_sample, self.qs_sample, qds_sample, qdds_sample,
229
+ q0=q0, robot_pyn=self.panda_pyn,
230
+ lims=self.limits, scale=scale, options=options
231
+ )
232
+
233
+ # Setup initial positions
234
+ self.display(self.viz_l, self.panda_ours, self.panda_tip_pin,
235
+ "end_effector_ruc", self.data.qr_list[0])
236
+ self.display(self.viz_r, self.panda_toppra, self.panda_tip_pin,
237
+ "end_effector_toppra", self.data_top.qr_list[0])
238
+
239
+ self.display_frame(self.viz_l, "start", self.X_init.np)
240
+ self.display_frame(self.viz_l, "end", self.X_final.np)
241
+ self.display_frame(self.viz_r, "start1", self.T_shift @ self.X_init)
242
+ self.display_frame(self.viz_r, "end1", self.T_shift @ self.X_final)
243
+
244
+ return {
245
+ 'toppra_duration': float(self.ts_sample[-1]),
246
+ 'ours_duration': float(self.data.t_ruckig[-1]),
247
+ 'waypoints': n_waypoints
248
+ }
249
+
250
+ def get_animation_data(self):
251
+ """Get data needed for animation"""
252
+ if self.data is None or self.data_top is None:
253
+ return None
254
+
255
+ return {
256
+ 't_max': max(self.data.t_ruckig[-1], self.data_top.t_toppra[-1]),
257
+ 't_ruckig': self.data.t_ruckig,
258
+ 't_toppra': self.data_top.t_toppra,
259
+ 'qr_list': self.data.qr_list,
260
+ 'qs_sample': self.qs_sample,
261
+ }
262
+
263
+ def update_animation(self, t_current):
264
+ """Update robot positions for given time"""
265
+ if self.data is None or self.data_top is None:
266
+ return
267
+
268
+ # Find indices
269
+ ind_t = 0
270
+ while ind_t < len(self.data_top.t_toppra) - 1 and self.data_top.t_toppra[ind_t] <= t_current:
271
+ ind_t += 1
272
+
273
+ ind_r = 0
274
+ while ind_r < len(self.data.t_ruckig) - 1 and self.data.t_ruckig[ind_r] <= t_current:
275
+ ind_r += 1
276
+
277
+ # Update visualizations
278
+ self.display(self.viz_l, self.panda_ours, self.panda_tip_pin,
279
+ "end_effector_ruc", self.data.qr_list[ind_r])
280
+ self.display(self.viz_r, self.panda_toppra, self.panda_tip_pin,
281
+ "end_effector_toppra", self.qs_sample[ind_t], self.T_shift)
282
+
283
+ def get_plot_data(self):
284
+ """Get data for plotting"""
285
+ if self.data is None or self.data_top is None:
286
+ return None
287
+
288
+ return {
289
+ 'ours': {
290
+ 't': self.data.t_ruckig[1:],
291
+ 'x': np.array(self.data.x_list[1:]).flatten().tolist(),
292
+ 'dx': self.data.dx_q_list[1:],
293
+ 'ddx': self.data.ddx_q_list[1:],
294
+ 'dddx': self.data.dddx_q_list[1:],
295
+ 'dx_max': self.data.dx_max_list[1:],
296
+ 'ddx_max': self.data.ddx_max_list[1:],
297
+ 'ddx_min': self.data.ddx_min_list[1:],
298
+ 'dddx_max': self.data.dddx_max_list[1:],
299
+ 'dddx_min': self.data.dddx_min_list[1:],
300
+ 'e_pos': (np.array(self.data.e_pos_list_ruckig) * 1e3),
301
+ 'e_rot': (np.rad2deg(np.array(self.data.e_rot_list_ruckig))),
302
+ },
303
+ 'toppra': {
304
+ 't': self.data_top.t_toppra,
305
+ 'x': self.data_top.x_top,
306
+ 'dx': self.data_top.dx_top,
307
+ 'ddx': self.data_top.ddx_top,
308
+ 'dddx': self.data_top.dddx_top,
309
+ 'ds_max': self.data_top.ds_max_list,
310
+ 'dds_min': self.data_top.dds_min_list,
311
+ 'dds_max': self.data_top.dds_max_list,
312
+ 'ddds_max': self.data_top.ddds_max_list,
313
+ 'ddds_min': self.data_top.ddds_min_list,
314
+ 'e_pos': (np.array(self.data_top.e_pos_list) * 1e3),
315
+ 'e_rot': (np.rad2deg(np.array(self.data_top.e_rot_list))),
316
+ }
317
+ }
318
+
319
+ def iframe(self, width="100%", height=640):
320
+ return f'<iframe src="/static/" style="width:{width};height:{height}px;border:0"></iframe>'
321
+
run.sh ADDED
@@ -0,0 +1,18 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #!/usr/bin/env bash
2
+ set -euo pipefail
3
+ set -x
4
+
5
+
6
+ # 1) Start MeshCat server (HTTP on :7000, WS at "/", ZMQ default :6000)
7
+ meshcat-server --zmq-url tcp://127.0.0.1:6001 &
8
+
9
+ # 2) Start the WS bridge (root "/" on 8765 -> 7000)
10
+ exec python ws_bridge.py &
11
+
12
+ # 3) Nginx reverse proxy (serves public port 7860)
13
+ nginx
14
+
15
+ # 4) Start your Gradio app on 8501
16
+ exec python app.py
17
+ # Keep the script running
18
+ # wait
ws_bridge.py ADDED
@@ -0,0 +1,53 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # WebSocket bridge: browser <-> bridge (:8765) <-> MeshCat WS (:7000)
2
+ # Compatible with websockets >= 11 (incl. 15.0.1). One-arg handler.
3
+ import asyncio
4
+ import logging
5
+ import websockets
6
+
7
+ UPSTREAM = "ws://127.0.0.1:7000/"
8
+
9
+ logging.basicConfig(level=logging.INFO, format="%(asctime)s %(levelname)s %(message)s")
10
+
11
+ async def pump(src, dst, label):
12
+ try:
13
+ async for msg in src:
14
+ await dst.send(msg)
15
+ except websockets.ConnectionClosedOK:
16
+ logging.info("%s closed cleanly", label)
17
+ except websockets.ConnectionClosed as e:
18
+ logging.info("%s closed: code=%s reason=%s", label, e.code, e.reason)
19
+ except Exception:
20
+ logging.exception("%s error", label)
21
+
22
+ async def handler(client_ws):
23
+ # Connect to MeshCat’s WS; disable compression to avoid deflate quirks
24
+ async with websockets.connect(
25
+ UPSTREAM,
26
+ ping_interval=20,
27
+ ping_timeout=20,
28
+ max_size=None,
29
+ compression=None,
30
+ ) as upstream_ws:
31
+ logging.info("Bridge connected: client <-> %s", UPSTREAM)
32
+ # bidirectional forwarding
33
+ c2u = asyncio.create_task(pump(client_ws, upstream_ws, "client->upstream"))
34
+ u2c = asyncio.create_task(pump(upstream_ws, client_ws, "upstream->client"))
35
+ done, pending = await asyncio.wait({c2u, u2c}, return_when=asyncio.FIRST_COMPLETED)
36
+ for t in pending:
37
+ t.cancel()
38
+
39
+ async def main():
40
+ async with websockets.serve(
41
+ handler,
42
+ "127.0.0.1",
43
+ 8765,
44
+ ping_interval=20,
45
+ ping_timeout=20,
46
+ max_size=None,
47
+ compression=None,
48
+ ):
49
+ logging.info("WS bridge listening on ws://127.0.0.1:8765/")
50
+ await asyncio.Future() # run forever
51
+
52
+ if __name__ == "__main__":
53
+ asyncio.run(main())