3
3
import numpy as np
4
4
from attrs import asdict , define , field
5
5
from ...utils .common import BaseAttrs , base_validators
6
- from ...utils .geometry import convert_to_plus_minus_pi
7
6
8
7
import omplpy as ompl
9
- from ...models import Robot , RobotState
8
+ from ...models import Robot , RobotGeometry
10
9
from omplpy import base , geometric
10
+ from kompass_cpp .planning import OMPL2DGeometricPlanner
11
11
12
- from ..fcl .collisions import FCL
13
- from ..fcl .config import FCLConfig
14
12
from .config import create_config_class , initializePlanners , optimization_objectives
15
13
16
14
@@ -54,7 +52,6 @@ def __init__(
54
52
use_fcl : bool = True ,
55
53
config : Optional [OMPLGeometricConfig ] = None ,
56
54
config_file : Optional [str ] = None ,
57
- map_3d : Optional [np .ndarray ] = None ,
58
55
):
59
56
"""
60
57
Init Open Motion Planning Lib geometric handler
@@ -93,45 +90,29 @@ def __init__(
93
90
self .available_planners = ompl .geometric .planners .getPlanners ()
94
91
95
92
# create a simple setup object
96
- self .setup = geometric .SimpleSetup (self .state_space )
97
-
98
- # Configure FCL
99
- if use_fcl :
100
- self .__init_fcl (map_3d )
101
- is_state_valid = self ._validity_checker_with_fcl
93
+ self ._ompl_setup = geometric .SimpleSetup (self .state_space )
102
94
103
- else :
104
- is_state_valid = self ._validity_checker
95
+ # Set the selected planner
96
+ self ._set_planner ()
105
97
106
- # Add validity method to ompl setup without custom validity checkers
107
- self .setup .setStateValidityChecker (is_state_valid )
98
+ # Setup Path Optimization Objective
99
+ optimization_objective = getattr (base , self ._config .optimization_objective )(
100
+ self ._ompl_setup .getSpaceInformation ()
101
+ )
108
102
109
- # Set the selected planner
110
- self .set_planner ()
103
+ self ._ompl_setup .setOptimizationObjective (optimization_objective )
111
104
112
105
if config_file :
113
106
self .configure (config_file )
114
107
115
- def __init_fcl (self , map_3d : Optional [np .ndarray ] = None ):
116
- """
117
- Setup FCL
118
-
119
- :param map_3d: Map data
120
- :type map_3d: np.ndarray
121
- """
122
- fcl_config = FCLConfig (
108
+ self ._cpp_planner : Optional [OMPL2DGeometricPlanner ] = OMPL2DGeometricPlanner (
109
+ robot_shape = RobotGeometry .Type .to_kompass_cpp_lib (
110
+ self ._robot .geometry_type
111
+ ),
112
+ robot_dimensions = self ._robot .geometry_params ,
113
+ ompl_setup = self ._ompl_setup ,
123
114
map_resolution = self ._config .map_resolution ,
124
- robot_geometry_type = self ._robot .geometry_type ,
125
- robot_geometry_params = self ._robot .geometry_params .tolist (),
126
115
)
127
- if not hasattr (self , "fcl" ):
128
- # setup collision check
129
- if map_3d is not None :
130
- self .fcl = FCL (fcl_config , map_3d )
131
- else :
132
- self .fcl = FCL (fcl_config )
133
- else :
134
- self .fcl ._setup_from_config ()
135
116
136
117
def configure (
137
118
self ,
@@ -170,59 +151,19 @@ def configure(
170
151
171
152
planner_params .from_file (config_file , nested_root_name + "." + planner_name )
172
153
173
- self .set_planner (planner_params , planner_id )
154
+ self ._set_planner (planner_params , planner_id )
174
155
175
156
self .start = False
176
157
177
- # configure FCL if it is used
178
- if self ._use_fcl :
179
- self .__init_fcl ()
180
-
181
- def clear (self ):
182
- """
183
- Clears planning setup
184
- """
185
- self .setup .clear ()
186
-
187
158
@property
188
- def path_cost (self ) -> Optional [ float ] :
159
+ def path_cost (self ) -> float :
189
160
"""
190
161
Getter of solution path cost using the configured optimization objective
191
162
192
163
:return: Path cost
193
164
:rtype: float | None
194
165
"""
195
- if self .solution :
196
- optimization_objective = getattr (base , self ._config .optimization_objective )(
197
- self .setup .getSpaceInformation ()
198
- )
199
- cost = self .solution .cost (optimization_objective )
200
- return cost .value ()
201
- return None
202
-
203
- def get_cost_using_objective (self , objective_key : str ) -> Optional [float ]:
204
- """
205
- Get solution cost using a specific objective
206
- This is used to get the cost using an objective other than the configured objective
207
- To get the cost using the default objective use self.path_cost directly
208
-
209
- :param objective_key: _description_
210
- :type objective_key: str
211
- :raises KeyError: Unknown objective name
212
- :return: _description_
213
- :rtype: _type_
214
- """
215
- if not self .solution :
216
- return None
217
- if objective_key in optimization_objectives :
218
- optimization_objective = getattr (base , objective_key )(
219
- self .setup .getSpaceInformation ()
220
- )
221
- cost = self .solution .cost (optimization_objective )
222
- return cost .value ()
223
- raise KeyError (
224
- f"Unknown optimization objective. Available optimization objectives are: { optimization_objectives .keys ()} "
225
- )
166
+ return self ._cpp_planner .get_cost ()
226
167
227
168
def setup_problem (
228
169
self ,
@@ -236,7 +177,7 @@ def setup_problem(
236
177
map_3d : Optional [np .ndarray ] = None ,
237
178
):
238
179
"""
239
- Setup a new planning problem with a map, start and goal infromation
180
+ Setup a new planning problem with a map, start and goal information
240
181
241
182
:param map_meta_data: Global map meta data as a dictionary
242
183
:type map_meta_data: Dict
@@ -255,132 +196,16 @@ def setup_problem(
255
196
:param map_3d: 3D array for map PointCloud data, defaults to None
256
197
:type map_3d: np.ndarray | None, optional
257
198
"""
258
- # Clear previous setup
259
- self .setup .clear ()
260
-
261
- self .set_space_bounds (map_meta_data )
262
-
263
- scoped_start_state = base .ScopedState (self .state_space )
264
- start_state = scoped_start_state .get ()
265
- start_state .setX (start_x )
266
- start_state .setY (start_y )
267
-
268
- # SE2 takes angle in [-pi,+pi]
269
- yaw = convert_to_plus_minus_pi (start_yaw )
270
- start_state .setYaw (yaw )
271
-
272
- scoped_goal_state = base .ScopedState (self .state_space )
273
- goal_state = scoped_goal_state .get ()
274
- goal_state .setX (goal_x )
275
- goal_state .setY (goal_y )
276
-
277
- # SE2 takes angle in [-pi,+pi]
278
- yaw = convert_to_plus_minus_pi (goal_yaw )
279
- goal_state .setYaw (yaw )
280
-
281
- self .setup .setStartAndGoalStates (
282
- start = scoped_start_state , goal = scoped_goal_state
283
- )
284
-
285
- # Setup Path Optimization Objective
286
- optimization_objective = getattr (base , self ._config .optimization_objective )(
287
- self .setup .getSpaceInformation ()
288
- )
289
-
290
- self .setup .setOptimizationObjective (optimization_objective )
291
-
292
- if self ._use_fcl and map_3d is None :
293
- raise ValueError (
294
- "OMPL is started with collision check -> Map should be provided"
295
- )
296
- elif self ._use_fcl :
297
- # TODO: Add an option to update the map periodically or just at the start, or after a time interval
298
- if not self .start :
299
- self .fcl .set_map (map_3d )
300
- self .start = True
301
- self .fcl .update_state (
302
- robot_state = RobotState (x = start_x , y = start_y , yaw = start_yaw )
303
- )
304
-
305
- def add_validity_check (self , name : str , validity_function : Callable ) -> None :
306
- """
307
- Add method for state validity check during planning
308
-
309
- :param name: Method key name
310
- :type name: str
311
- :param validity_function: Validity check method
312
- :type validity_function: Callable
313
-
314
- :raises TypeError: If validity check is not callable
315
- :raises TypeError: If validity check method does not return a boolean
316
- """
317
- # Check that validity function is callable
318
- if callable (validity_function ) and callable (validity_function ):
319
- # Check if the function returns a boolean value
320
- args = (None ,) * validity_function .__code__ .co_argcount
321
- if isinstance (validity_function (* args ), bool ):
322
- self ._custom_validity_check [name ] = validity_function
323
- else :
324
- raise TypeError ("Validity check function needs to return a boolean" )
325
- else :
326
- raise TypeError ("Validity check function must be callable" )
327
-
328
- def remove_validity_check (self , name : str ) -> bool :
329
- """
330
- Removes an added validity chec!= Nonek
331
-
332
- :param name: Validity check name key
333
- :type name: str
334
-
335
- :raises ValueError: If given key does not correspond to an added validity check
336
-
337
- :return: If validity check is removed
338
- :rtype: bool
339
- """
340
- deleted_method = self ._custom_validity_check .pop (name , None )
341
- if deleted_method is not None :
342
- return True
343
- else :
344
- raise ValueError (
345
- f"Cannot remove validity check titled { name } as it does not exist"
346
- )
347
-
348
- def _validity_checker (self , state , ** _ ) -> bool :
349
- """
350
- State validity checker method
351
-
352
- :param state: Robot state
353
- :type state: SE2State
354
-
355
- :return: If state is valid
356
- :rtype: bool
357
- """
358
- # Run bounds and state check
359
- return self .setup .getSpaceInformation ().satisfiesBounds (state )
360
-
361
- def _validity_checker_with_fcl (self , state , ** _ ) -> bool :
362
- """
363
- State validity checker method
364
-
365
- :param state: Robot state
366
- :type state: SE2State
367
-
368
- :return: If state is valid
369
- :rtype: bool
370
- """
371
- # Run bounds and state check
372
- state_space_valid : bool = self .setup .getSpaceInformation ().satisfiesBounds (
373
- state
199
+ self ._set_space_bounds (map_meta_data )
200
+ self ._cpp_planner .setup_problem (
201
+ start_x = start_x ,
202
+ start_y = start_y ,
203
+ start_yaw = start_yaw ,
204
+ goal_x = goal_x ,
205
+ goal_y = goal_y ,
206
+ goal_yaw = goal_yaw ,
207
+ map_3d = map_3d ,
374
208
)
375
- if self .fcl .got_map :
376
- self .fcl .update_state (
377
- RobotState (x = state .getX (), y = state .getY (), yaw = state .getYaw ())
378
- )
379
- is_collision = self .fcl .check_collision ()
380
- else :
381
- is_collision = False
382
-
383
- return state_space_valid and not is_collision
384
209
385
210
@property
386
211
def planner_params (self ) -> Optional [ompl .base .ParamSet ]:
@@ -420,7 +245,7 @@ def planner_id(self) -> str:
420
245
"""
421
246
return self ._config .planner_id
422
247
423
- def set_planner (
248
+ def _set_planner (
424
249
self ,
425
250
planner_config : Optional [BaseAttrs ] = None ,
426
251
planner_id : Optional [str ] = None ,
@@ -441,40 +266,32 @@ def set_planner(
441
266
else :
442
267
self ._config .planner_id = planner_id
443
268
444
- self .planner = eval ("%s(self.setup.getSpaceInformation())" % planner_id )
269
+ self .planner = eval (
270
+ "%s(self._ompl_setup.getSpaceInformation())" % planner_id
271
+ )
445
272
446
273
if planner_config :
447
274
self .planner_params = planner_config
448
275
449
- self .setup .setPlanner (self .planner )
276
+ self ._ompl_setup .setPlanner (self .planner )
450
277
451
278
except Exception :
452
279
raise
453
280
454
- def set_space_bounds (self , map_meta_data : Dict ):
281
+ def _set_space_bounds (self , map_meta_data : Dict ):
455
282
"""
456
283
Set planning space bounds from map data
457
284
458
285
:param map_meta_data: Map meta data
459
286
:type map_meta_data: Dict
460
287
"""
461
- # X-axis bounds
462
- x_lower = map_meta_data ["origin_x" ]
463
- x_upper = x_lower + map_meta_data ["resolution" ] * map_meta_data ["width" ]
464
-
465
- # Y-axis bounds
466
- y_lower = map_meta_data ["origin_y" ]
467
- y_upper = y_lower + map_meta_data ["resolution" ] * map_meta_data ["height" ]
468
-
469
- # set lower and upper bounds
470
- bounds = base .RealVectorBounds (2 )
471
-
472
- bounds .setLow (index = 0 , value = x_lower )
473
- bounds .setLow (index = 1 , value = y_lower )
474
- bounds .setHigh (index = 0 , value = x_upper )
475
- bounds .setHigh (index = 1 , value = y_upper )
476
-
477
- self .state_space .setBounds (bounds )
288
+ self ._cpp_planner .set_space_bounds_from_map (
289
+ origin_x = map_meta_data ["origin_x" ],
290
+ origin_y = map_meta_data ["origin_y" ],
291
+ width = map_meta_data ["width" ],
292
+ height = map_meta_data ["height" ],
293
+ resolution = map_meta_data ["resolution" ],
294
+ )
478
295
479
296
def solve (self ) -> ompl .geometric .PathGeometric :
480
297
"""
@@ -483,23 +300,9 @@ def solve(self) -> ompl.geometric.PathGeometric:
483
300
:return: Solution (Path points) or None if no solution is found
484
301
:rtype: ompl.geometric.PathGeometric
485
302
"""
486
- solved = self .setup .solve (self ._config .planning_timeout )
303
+ solved = self ._cpp_planner .solve (self ._config .planning_timeout )
487
304
488
305
if solved :
489
- self .solution = self .setup .getSolutionPath ()
490
-
491
- return self .solution
492
- return None
493
-
494
- def simplify_solution (self ) -> ompl .geometric .PathGeometric :
495
- """
496
- Simplify the path
497
-
498
- :return: Simplified path
499
- :rtype: ompl.geometric.PathGeometric
500
- """
501
- if self .solution :
502
- self .setup .simplifySolution (self ._config .simplification_timeout )
503
- self .solution = self .setup .getSolutionPath ()
306
+ self .solution = self ._cpp_planner .get_solution ()
504
307
return self .solution
505
308
return None
0 commit comments