@@ -181,51 +181,204 @@ def GetTrajectoryManipulators(self, traj):
181
181
182
182
return active_manipulators
183
183
184
- def ExecutePath (self , path , simplify = True , smooth = True , defer = False ,
185
- timeout = 1. , ** kwargs ):
184
+ def PostProcessPath (self , path , defer = False , executor = None ,
185
+ constrained = None , smooth = None , default_timelimit = 0.5 ,
186
+ shortcut_options = None , smoothing_options = None ,
187
+ retiming_options = None ):
188
+ """ Post-process a geometric path to prepare it for execution.
189
+
190
+ This method post-processes a geometric path by (optionally) optimizing
191
+ it and timing it. Three different post-processing pipelines are used:
192
+
193
+ 1. For constrained trajectories, we do not modify the geometric path
194
+ and retime the path to be time-optimal. This trajectory must stop
195
+ at every waypoint. The only exception is for...
196
+ 2. For smooth trajectories, we attempt to fit a time-optimal smooth
197
+ curve through the waypoints (not implemented). If this curve is
198
+ not collision free, then we fall back on...
199
+ 3. By default, we run a smoother that jointly times and smooths the
200
+ path. This algorithm can change the geometric path to optimize
201
+ runtime.
202
+
203
+ The behavior in (1) and (2) can be forced by passing constrained=True
204
+ or smooth=True. By default, the case is inferred by the tag(s) attached
205
+ to the trajectory: (1) is triggered by the CONSTRAINED tag and (2) is
206
+ tiggered by the SMOOTH tag.
207
+
208
+ Options an be passed to each post-processing routine using the
209
+ shortcut-options, smoothing_options, and retiming_options **kwargs
210
+ dictionaries. If no "timelimit" is specified in any of these
211
+ dictionaries, it defaults to default_timelimit seconds.
212
+
213
+ @param path un-timed OpenRAVE trajectory
214
+ @param defer return immediately with a future trajectory
215
+ @param executor executor to use when defer = True
216
+ @param constrained the path is constrained; do not change it
217
+ @param smooth the path is smooth; attempt to execute it directly
218
+ @param default_timelimit timelimit for all operations, if not set
219
+ @param shortcut_options kwargs to ShortcutPath for shortcutting
220
+ @param smoothing_options kwargs to RetimeTrajectory for smoothing
221
+ @param retiming_options kwargs to RetimeTrajectory for timing
222
+ @return trajectory ready for execution
223
+ """
224
+ from ..planning .base import Tags
225
+ from ..util import GetTrajectoryTags , CopyTrajectory
226
+
227
+ # Default parameters.
228
+ if shortcut_options is None :
229
+ shortcut_options = dict ()
230
+ if smoothing_options is None :
231
+ smoothing_options = dict ()
232
+ if retiming_options is None :
233
+ retiming_options = dict ()
234
+
235
+ shortcut_options .setdefault ('timelimit' , default_timelimit )
236
+ smoothing_options .setdefault ('timelimit' , default_timelimit )
237
+ retiming_options .setdefault ('timelimit' , default_timelimit )
238
+
239
+ # Read default parameters from the trajectory's tags.
240
+ tags = GetTrajectoryTags (path )
241
+
242
+ if constrained is None :
243
+ constrained = tags .get (Tags .CONSTRAINED , False )
244
+ logger .debug ('Detected "%s" tag on trajectory: Setting'
245
+ ' constrained = True.' , Tags .CONSTRAINED )
246
+
247
+ if smooth is None :
248
+ smooth = tags .get (Tags .SMOOTH , False )
249
+ logger .debug ('Detected "%s" tag on trajectory: Setting smooth'
250
+ ' = True' , Tags .SMOOTH )
251
+
252
+ def do_postprocess ():
253
+ with Clone (self .GetEnv ()) as cloned_env :
254
+ cloned_robot = cloned_env .Cloned (self )
186
255
187
- logger .debug ('Begin ExecutePath' )
256
+ # Planners only operate on the active DOFs. We'll set any DOFs
257
+ # in the trajectory as active.
258
+ env = path .GetEnv ()
259
+ cspec = path .GetConfigurationSpecification ()
260
+ used_bodies = cspec .ExtractUsedBodies (env )
261
+ if self not in used_bodies :
262
+ raise ValueError (
263
+ 'Robot "{:s}" is not in the trajectory.' .format (
264
+ self .GetName ()))
265
+
266
+ dof_indices , _ = cspec .ExtractUsedIndices (self )
267
+ cloned_robot .SetActiveDOFs (dof_indices )
268
+ logger .debug (
269
+ 'Setting robot "%s" DOFs %s as active for post-processing.' ,
270
+ cloned_robot .GetName (), list (dof_indices ))
271
+
272
+ # TODO: Handle a affine DOF trajectories for the base.
273
+
274
+ # Directly compute a timing of smooth trajectories.
275
+ if smooth :
276
+ logger .warning (
277
+ 'Post-processing smooth paths is not supported.'
278
+ ' Using the default post-processing logic; this may'
279
+ ' significantly change the geometric path.'
280
+ )
188
281
189
- def do_execute (path , simplify , smooth , timeout , ** kwargs ):
282
+ # The trajectory is constrained. Retime it without changing the
283
+ # geometric path.
284
+ if constrained :
285
+ logger .debug ('Retiming a constrained path. The output'
286
+ ' trajectory will stop at every waypoint.' )
287
+ traj = self .retimer .RetimeTrajectory (
288
+ cloned_robot , path , defer = False , ** retiming_options )
289
+ else :
290
+ # The trajectory is not constrained, so we can shortcut it
291
+ # before execution.
292
+ logger .debug ('Shortcutting an unconstrained path.' )
293
+ shortcut_path = self .simplifier .ShortcutPath (
294
+ cloned_robot , path , defer = False , ** shortcut_options )
295
+
296
+ logger .debug ('Smoothing an unconstrained path.' )
297
+ traj = self .smoother .RetimeTrajectory (
298
+ cloned_robot , shortcut_path , defer = False ,
299
+ ** smoothing_options )
300
+
301
+ return CopyTrajectory (traj , env = self .GetEnv ())
190
302
191
- with Clone (self .GetEnv ()) as cloned_env :
192
- traj_dofs = util .GetTrajectoryIndices (path )
193
- cloned_env .Cloned (self ).SetActiveDOFs (traj_dofs )
194
- cloned_robot = cloned_env .Cloned (self )
303
+ if defer :
304
+ from trollius .executor import get_default_executor
305
+ from trollius .futures import wrap_future
195
306
196
- if simplify :
197
- path = self .simplifier .ShortcutPath (cloned_robot , path , defer = False ,
198
- timeout = timeout , ** kwargs )
307
+ if executor is None :
308
+ executor = get_default_executor ()
199
309
200
- retimer = self .smoother if smooth else self .retimer
201
- cloned_timed_traj = retimer .RetimeTrajectory (cloned_robot , path , defer = False , ** kwargs )
310
+ return wrap_future (executor .submit (do_postprocess ))
311
+ else :
312
+ return do_postprocess ()
313
+
314
+ def ExecutePath (self , path , defer = False , executor = None , ** kwargs ):
315
+ """ Post-process and execute an un-timed path.
316
+
317
+ This method calls PostProcessPath, then passes the result to
318
+ ExecuteTrajectory. Any extra **kwargs are forwarded to both of these
319
+ methods. This function returns the timed trajectory that was executed
320
+ on the robot.
321
+
322
+ @param path OpenRAVE trajectory representing an un-timed path
323
+ @param defer execute asynchronously and return a future
324
+ @param executor if defer = True, which executor to use
325
+ @param **kwargs forwarded to PostProcessPath and ExecuteTrajectory
326
+ @return timed trajectory executed on the robot
327
+ """
202
328
203
- # Copy the trajectory back to the original environment.
204
- from .. util import CopyTrajectory
205
- timed_traj = CopyTrajectory ( cloned_timed_traj , env = self . GetEnv () )
329
+ def do_execute ():
330
+ logger . debug ( 'Post-processing path to compute a timed trajectory.' )
331
+ traj = self . PostProcessPath ( path , defer = False , ** kwargs )
206
332
207
- return self .ExecuteTrajectory (timed_traj , defer = False , ** kwargs )
333
+ logger .debug ('Executing timed trajectory.' )
334
+ return self .ExecuteTrajectory (traj , defer = False , ** kwargs )
208
335
209
336
if defer :
210
337
from trollius .executor import get_default_executor
211
338
from trollius .futures import wrap_future
212
339
213
- executor = kwargs .get ('executor' ) or get_default_executor ()
214
- return wrap_future (
215
- executor .submit (do_execute ,
216
- path , simplify = simplify , smooth = smooth , timeout = timeout ,
217
- ** kwargs
218
- )
219
- )
340
+ if executor is None :
341
+ executor = get_default_executor ()
342
+
343
+ return wrap_future (executor .submit (do_execute ))
220
344
else :
221
- return do_execute (path , simplify = simplify , smooth = smooth ,
222
- timeout = timeout , ** kwargs )
345
+ return do_execute ()
346
+
347
+ def ExecuteTrajectory (self , traj , defer = False , timeout = None , period = 0.01 ):
348
+ """ Executes a time trajectory on the robot.
349
+
350
+ This function directly executes a timed OpenRAVE trajectory on the
351
+ robot. If you have a geometric path, such as those returned by a
352
+ geometric motion planner, you should first time the path using
353
+ PostProcessPath. Alternatively, you could use the ExecutePath helper
354
+ function to time and execute the path in one function call.
355
+
356
+ If timeout = None (the default), this function does not return until
357
+ execution has finished. Termination occurs if the trajectory is
358
+ successfully executed or if a fault occurs (in this case, an exception
359
+ will be raised). If timeout is a float (including timeout = 0), this
360
+ function will return None once the timeout has ellapsed, even if the
361
+ trajectory is still being executed.
362
+
363
+ NOTE: We suggest that you either use timeout=None or defer=True. If
364
+ trajectory execution times out, there is no way to tell whether
365
+ execution was successful or not. Other values of timeout are only
366
+ supported for legacy reasons.
367
+
368
+ This function returns the trajectory that was actually executed on the
369
+ robot, including controller error. If this is not available, the input
370
+ trajectory will be returned instead.
371
+
372
+ @param traj timed OpenRAVE trajectory to be executed
373
+ @param defer execute asynchronously and return a trajectory Future
374
+ @param timeout maximum time to wait for execution to finish
375
+ @param period poll rate, in seconds, for checking trajectory status
376
+ @return trajectory executed on the robot
377
+ """
223
378
224
- def ExecuteTrajectory (self , traj , defer = False , timeout = None , period = 0.01 , ** kw_args ):
225
379
# TODO: Verify that the trajectory is timed.
226
380
# TODO: Check if this trajectory contains the base.
227
381
228
- logger .debug ('Begin ExecuteTrajectory' )
229
382
needs_base = util .HasAffineDOFs (traj .GetConfigurationSpecification ())
230
383
231
384
self .GetController ().SetPath (traj )
@@ -238,8 +391,13 @@ def ExecuteTrajectory(self, traj, defer=False, timeout=None, period=0.01, **kw_a
238
391
]
239
392
240
393
if needs_base :
241
- if hasattr (self , 'base' ) and hasattr (self .base , 'controller' ):
394
+ if (hasattr (self , 'base' ) and hasattr (self .base , 'controller' )
395
+ and self .base .controller is not None ):
242
396
active_controllers .append (self .base .controller )
397
+ else :
398
+ logger .warning (
399
+ 'Trajectory includes the base, but no base controller is'
400
+ ' available. Is self.base.controller set?' )
243
401
244
402
if defer :
245
403
import time
0 commit comments