Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with
or
.
Download ZIP
Newer
Older
100644 689 lines (534 sloc) 18.458 kB
754ffa5 added missing file
rtv authored
1 ///////////////////////////////////////////////////////////////////////////
2 //
3 // File: model_gripper.cc
4 // Authors: Richard Vaughan <vaughan@sfu.ca>
5 // Doug Blank
6 // Date: 21 April 2005
7 //
8 // CVS info:
9 // $Source: /home/tcollett/stagecvs/playerstage-cvs/code/stage/src/model_gripper.c,v $
10 // $Author: thjc $
11 // $Revision: 1.26.8.1 $
12 //
13 ///////////////////////////////////////////////////////////////////////////
14
15 /**
16 @ingroup model
17 @defgroup model_gripper Gripper model
18
19 The ranger model simulates a simple two-fingered gripper with two
20 internal break-beams, similar to the the Pioneer gripper.
21
22 <h2>Worldfile properties</h2>
23
24 @par Summary and default values
25
26 @verbatim
27 gripper
28 (
29 # gripper properties
30 <none>
31
32 # model properties
33 size [0.12 0.28]
34 )
35 @endverbatim
36
37 @par Notes
38
39 @par Details
40
41 */
42
43
44 #include <sys/time.h>
45 #include <math.h>
46 #include "stage.hh"
47 #include "worldfile.hh"
48 using namespace Stg;
49
50 // TODO - simulate energy use when moving grippers
51
52 ModelGripper::ModelGripper( World* world,
53 Model* parent )
54 : Model( world, parent, MODEL_TYPE_GRIPPER ),
55 cfg(), // configured below
56 cmd( CMD_NOOP )
57 {
58 // set up a gripper-specific config structure
59 cfg.paddle_size.x = 0.66; // proportion of body length that is paddles
60 cfg.paddle_size.y = 0.1; // proportion of body width that is paddles
61 cfg.paddle_size.z = 0.4; // proportion of body height that is paddles
62
63 cfg.paddles = PADDLE_OPEN;
64 cfg.lift = LIFT_DOWN;
65 cfg.paddle_position = 0.0;
66 cfg.lift_position = 0.0;
67 cfg.paddles_stalled = false;
68 cfg.autosnatch = false;
69 cfg.grip_stack = NULL;
70 cfg.grip_stack_size = 1;
71
72 // place the break beam sensors at 1/4 and 3/4 the length of the paddle
73 cfg.break_beam_inset[0] = 3.0/4.0 * cfg.paddle_size.x;
74 cfg.break_beam_inset[1] = 1.0/4.0 * cfg.paddle_size.x;
75
76 cfg.close_limit = 1.0;
77
78 SetColor( stg_color_pack( 0.3, 0.3, 0.3, 0 ));
79
80 FixBlocks();
81
82 // default size
83 Geom geom;
84 geom.pose.x = 0.0;
85 geom.pose.y = 0.0;
86 geom.pose.a = 0.0;
87 geom.size.x = 0.2;
88 geom.size.y = 0.3;
89 geom.size.z = 0.2;
90 SetGeom( geom );
91
92 //Startup();
93
94 PositionPaddles();
95 }
96
97 ModelGripper::~ModelGripper()
98 {
99 /* do nothing */
100 }
101
102
103 void ModelGripper::Load()
104 {
105 cfg.autosnatch = wf->ReadInt( wf_entity, "autosnatch", cfg.autosnatch );
106
107 cfg.paddle_size.x = wf->ReadTupleFloat( wf_entity, "paddle_size", 0, cfg.paddle_size.x );
108 cfg.paddle_size.y = wf->ReadTupleFloat( wf_entity, "paddle_size", 1, cfg.paddle_size.y );
109 cfg.paddle_size.z = wf->ReadTupleFloat( wf_entity, "paddle_size", 2, cfg.paddle_size.z );
110
111 const char* paddles = wf->ReadTupleString( wf_entity, "paddles", 0, NULL );
112 const char* lift = wf->ReadTupleString( wf_entity, "paddles", 1, NULL );
113
114 if( paddles && strcmp( paddles, "closed" ) == 0 )
115 {
116 cfg.paddle_position = 1.0;
117 cfg.paddles = PADDLE_CLOSED;
118 }
119
120 if( paddles && strcmp( paddles, "open" ) == 0 )
121 {
122 cfg.paddle_position = 0.0;
123 cfg.paddles = PADDLE_OPEN;
124 }
125
126 if( lift && strcmp( lift, "up" ) == 0 )
127 {
128 cfg.lift_position = 1.0;
129 cfg.lift = LIFT_UP;
130 }
131
132 if( lift && strcmp( lift, "down" ) == 0 )
133 {
134 cfg.lift_position = 0.0;
135 cfg.lift = LIFT_DOWN;
136 }
137
138 cfg.grip_stack_size = wf->ReadInt( wf_entity , "stack_size", cfg.grip_stack_size );
139
140 FixBlocks();
141
142 // do this at the end to ensure that the blocks are resize correctly
143 Model::Load();
144 }
145
146 void ModelGripper::Save()
147 {
148 Model::Save();
149
150 wf->WriteTupleFloat( wf_entity, "paddle_size", 0, cfg.paddle_size.x );
151 wf->WriteTupleFloat( wf_entity, "paddle_size", 1, cfg.paddle_size.y );
152 wf->WriteTupleFloat( wf_entity, "paddle_size", 2, cfg.paddle_size.z );
153
154 wf->WriteInt( wf_entity , "stack_size", cfg.grip_stack_size );
155 }
156
157 void ModelGripper::DataVisualize( Camera* cam )
158 {
159 /* do nothing */
160 }
161
162 void ModelGripper::FixBlocks()
163 {
164 // get rid of the default cube
165 ClearBlocks();
166
167 // add three blocks that make the gripper
168 // base
169 AddBlockRect( 0, 0, 1.0-cfg.paddle_size.x, 1.0, 1.0 );
170
171 // left (top) paddle
172 paddle_left = AddBlockRect( 1.0-cfg.paddle_size.x, 0, cfg.paddle_size.x, cfg.paddle_size.y, cfg.paddle_size.z );
173
174 // right (bottom) paddle
175 paddle_right = AddBlockRect( 1.0-cfg.paddle_size.x, 1.0-cfg.paddle_size.y, cfg.paddle_size.x, cfg.paddle_size.y, cfg.paddle_size.z );
176
177 PositionPaddles();
178 }
179
180 // Update the blocks that are the gripper's body
181 void ModelGripper::PositionPaddles()
182 {
183 UnMap();
184 double paddle_center_pos = cfg.paddle_position * (0.5 - cfg.paddle_size.y );
185 paddle_left->SetCenterY( paddle_center_pos + cfg.paddle_size.y/2.0 );
186 paddle_right->SetCenterY( 1.0 - paddle_center_pos - cfg.paddle_size.y/2.0);
187
188 double paddle_bottom = cfg.lift_position * (1.0 - cfg.paddle_size.z);
189 double paddle_top = paddle_bottom + cfg.paddle_size.z;
190
191 paddle_left->SetZ( paddle_bottom, paddle_top );
192 paddle_right->SetZ( paddle_bottom, paddle_top );
193
194 Map();
195 }
196
197
198 void ModelGripper::Update()
199 {
200 // no work to do if we're unsubscribed
201 if( subs < 1 )
202 {
203 Model::Update();
204 return;
205 }
206
207 float start_paddle_position = cfg.paddle_position;
208 float start_lift_position = cfg.lift_position;
209
210 switch( cmd )
211 {
212 case CMD_NOOP:
213 break;
214
215 case CMD_CLOSE:
216 if( cfg.paddles != PADDLE_CLOSED )
217 {
218 //puts( "closing gripper paddles" );
219 cfg.paddles = PADDLE_CLOSING;
220 }
221 break;
222
223 case CMD_OPEN:
224 if( cfg.paddles != PADDLE_OPEN )
225 {
226 //puts( "opening gripper paddles" );
227 cfg.paddles = PADDLE_OPENING;
228 }
229 break;
230
231 case CMD_UP:
232 if( cfg.lift != LIFT_UP )
233 {
234 //puts( "raising gripper lift" );
235 cfg.lift = LIFT_UPPING;
236 }
237 break;
238
239 case CMD_DOWN:
240 if( cfg.lift != LIFT_DOWN )
241 {
242 //puts( "lowering gripper lift" );
243 cfg.lift = LIFT_DOWNING;
244 }
245 break;
246
247 default:
248 printf( "unknown gripper command %d\n",cmd );
249 }
250
251 // // move the paddles
252 if( cfg.paddles == PADDLE_OPENING && !cfg.paddles_stalled )
253 {
254 cfg.paddle_position -= 0.05;
255
256 if( cfg.paddle_position < 0.0 ) // if we're fully open
257 {
258 cfg.paddle_position = 0.0;
259 cfg.paddles = PADDLE_OPEN; // change state
260 }
261
262 // drop the thing at the head of the stack
263 if( cfg.grip_stack &&
264 (cfg.paddle_position == 0.0 || cfg.paddle_position < cfg.close_limit ))
265 {
266 Model* head = (Model*)cfg.grip_stack->data;
267 cfg.grip_stack = g_list_remove( cfg.grip_stack, head );
268
269 // move it to the new location
270 head->SetParent( NULL );
271 head->SetPose( this->GetGlobalPose() );
272
273 cfg.close_limit = 1.0;
274 }
275 }
276
277 else if( cfg.paddles == PADDLE_CLOSING && !cfg.paddles_stalled )
278 {
279 cfg.paddle_position += 0.05;
280 //printf( "paddle position %.2f\n", cfg.paddle_position );
281
282 if( cfg.paddle_position > cfg.close_limit ) // if we're fully closed
283 {
284 cfg.paddle_position = cfg.close_limit;
285 cfg.paddles = PADDLE_CLOSED; // change state
286 }
287 }
288
289 switch( cfg.lift )
290 {
291 case LIFT_DOWNING:
292 cfg.lift_position -= 0.05;
293
294 if( cfg.lift_position < 0.0 ) // if we're fully down
295 {
296 cfg.lift_position = 0.0;
297 cfg.lift = LIFT_DOWN; // change state
298 }
299 break;
300
301 case LIFT_UPPING:
302 cfg.lift_position += 0.05;
303
304 if( cfg.lift_position > 1.0 ) // if we're fully open
305 {
306 cfg.lift_position = 1.0;
307 cfg.lift = LIFT_UP; // change state
308 }
309 break;
310
311 case LIFT_DOWN: // nothing to do for these cases
312 case LIFT_UP:
313 default:
314 break;
315 }
316
317 // if the paddles or lift have changed position
318 if( start_paddle_position != cfg.paddle_position ||
319 start_lift_position != cfg.lift_position )
320 // figure out where the paddles should be
321 PositionPaddles();
322
323
324 UpdateBreakBeams();
325 UpdateContacts();
326
327 Model::Update();
328 }
329
330 ModelGripper::data_t ModelGripper::GetData()
331 {
332 data_t data;
333
334 data.paddles = cfg.paddles;
335 data.paddle_position = cfg.paddle_position;
336 data.lift = cfg.lift;
337 data.lift_position = cfg.lift_position;
338 data.beam[0] = cfg.beam[0];
339 data.beam[1] = cfg.beam[1];
340
341 data.contact[0] = cfg.contact[0];
342 data.contact[1] = cfg.contact[1];
343
344 if( cfg.grip_stack )
345 data.stack_count = g_list_length( cfg.grip_stack );
346 else
347 data.stack_count = 0;
348
349 data.paddles_stalled = cfg.paddles_stalled;
350
351 return data;
352 }
353
354
355 void ModelGripper::SetConfig( config_t & cfg )
356 {
357 this->cfg = cfg;
358 }
359
360
361 static bool gripper_raytrace_match( Model* hit,
362 Model* finder,
363 const void* dummy )
364 {
365 return( (hit != finder) && hit->vis.gripper_return );
366 // can't use the normal relation check, because we may pick things
367 // up and we must still see them.
368 }
369
370 void ModelGripper::UpdateBreakBeams()
371 {
372 for( unsigned int index=0; index < 2; index++ )
373 {
374 Pose pz;
375
376 // x location of break beam origin
377 double inset = cfg.break_beam_inset[index];
378
379 pz.x = (geom.size.x - inset * geom.size.x) - geom.size.x/2.0;
380
381 // y location of break beam origin
382 pz.y = (1.0 - cfg.paddle_position) * ((geom.size.y/2.0)-(geom.size.y*cfg.paddle_size.y));
383
384 pz.z = 0.0; // TODO
385
386 // break beam local heading
387 pz.a = -M_PI/2.0;
388
389 // break beam max range
390 double bbr =
391 (1.0 - cfg.paddle_position) * (geom.size.y - (geom.size.y * cfg.paddle_size.y * 2.0 ));
392
393 stg_raytrace_result_t sample =
394 Raytrace( pz, // ray origin
395 bbr, // range
396 gripper_raytrace_match, // match func
397 NULL, // match arg
398 true ); // ztest
399
400 cfg.beam[index] = sample.mod;
401 }
402
403 // autosnatch grabs anything that breaks the inner beam
404 if( cfg.autosnatch )
405 {
406 if( cfg.beam[0] || cfg.beam[1] )
407 cmd = CMD_CLOSE;
408 else
409 cmd = CMD_OPEN;
410 }
411 }
412
413 void ModelGripper::UpdateContacts()
414 {
415 cfg.paddles_stalled = false; // may be changed below
416
417 Pose lpz, rpz;
418
419 // x location of contact sensor origin
420 lpz.x = ((1.0 - cfg.paddle_size.x) * geom.size.x) - geom.size.x/2.0 ;
421 rpz.x = ((1.0 - cfg.paddle_size.x) * geom.size.x) - geom.size.x/2.0 ;
422
423 // //double inset = beam ? cfg->inner_break_beam_inset : cfg->outer_break_beam_inset;
424 // //pz.x = (geom.size.x - inset * geom.size.x) - geom.size.x/2.0;
425
426 // y location of paddle beam origin
427
428 lpz.y = (1.0 - cfg.paddle_position) *
429 ((geom.size.y/2.0) - (geom.size.y*cfg.paddle_size.y));
430
431 rpz.y = (1.0 - cfg.paddle_position) *
432 -((geom.size.y/2.0) - (geom.size.y*cfg.paddle_size.y));
433
434 lpz.z = 0.0; // todo
435 rpz.z = 0.0;
436
437 // paddle beam local heading
438 lpz.a = 0.0;
439 rpz.a = 0.0;
440
441 // paddle beam max range
442 double bbr = cfg.paddle_size.x * geom.size.x;
443
444 stg_raytrace_result_t leftsample =
445 Raytrace( lpz, // ray origin
446 bbr, // range
447 gripper_raytrace_match, // match func
448 NULL, // match arg
449 true ); // ztest
450
451 cfg.contact[0] = leftsample.mod;
452
453 stg_raytrace_result_t rightsample =
454 Raytrace( rpz, // ray origin
455 bbr, // range
456 gripper_raytrace_match, // match func
457 NULL, // match arg
458 true ); // ztest
459
460 cfg.contact[1] = rightsample.mod;
461
462 if( cfg.contact[0] || cfg.contact[1] )
463 {
464 cfg.paddles_stalled = true;;
465
466 // if( lhit && (lhit == rhit) )
467 // {
468 // //puts( "left and right hit same thing" );
469
470 if( cfg.paddles == PADDLE_CLOSING )
471 {
472 Model* hit = cfg.contact[0];
473 if( !hit )
474 hit = cfg.contact[1];
475
476 if( cfg.grip_stack_size > 0 &&
477 (g_list_length( cfg.grip_stack ) < cfg.grip_stack_size) )
478 {
479 // get the global pose of the gripper for calculations of the gripped object position
480 // and move it to be right between the paddles
481 Geom hitgeom = hit->GetGeom();
482 //Pose hitgpose = hit->GetGlobalPose();
483
484 // stg_pose_t pose = {0,0,0};
485 // stg_model_local_to_global( lhit, &pose );
486 // stg_model_global_to_local( mod, &pose );
487
488 // // grab the model we hit - very simple grip model for now
489 hit->SetParent( this );
490 hit->SetPose( Pose(0,0, -1.0 * geom.size.z ,0) );
491
492 cfg.grip_stack = g_list_prepend( cfg.grip_stack, hit );
493
494 // // calculate how far closed we can get the paddles now
495 double puckw = hitgeom.size.y;
496 double gripperw = geom.size.y;
497 cfg.close_limit = MAX( 0.0, 1.0 - puckw/(gripperw - cfg.paddle_size.y/2.0 ));
498 }
499 }
500 }
501 }
502
503 // int gripper_render_data( stg_model_t* mod, void* userp )
504 // {
505 // //puts( "gripper render data" );
506
507 // // only draw if someone is using the gripper
508 // if( mod->subs < 1 )
509 // return 0;
510
511 // stg_rtk_fig_t* fig = stg_model_get_fig( mod, "gripper_data_fig" );
512
513 // if( ! fig )
514 // {
515 // fig = stg_model_fig_create( mod, "gripper_data_fig", "top", STG_LAYER_GRIPPERDATA );
516 // //stg_rtk_fig_color_rgb32( fig, gripper_col );
517 // stg_rtk_fig_color_rgb32( fig, 0xFF0000 ); // red
518
519 // }
520 // else
521 // stg_rtk_fig_clear( fig );
522
523 // //printf( "SUBS %d\n", mod->subs );
524
525 // stg_gripper_data_t* data = (stg_gripper_data_t*)mod->data;
526 // assert(data);
527
528 // stg_gripper_config_t *cfg = (stg_gripper_config_t*)mod->cfg;
529 // assert(cfg);
530
531 // stg_geom_t *geom = &mod->geom;
532
533 // //stg_rtk_fig_rectangle( gui->data, 0,0,0, geom.size.x, geom.size.y, 0 );
534
535 // // different x location for each beam
536 // double ibbx = (geom->size.x - cfg->inner_break_beam_inset * geom->size.x) - geom->size.x/2.0;
537 // double obbx = (geom->size.x - cfg->outer_break_beam_inset * geom->size.x) - geom->size.x/2.0;
538
539 // // common y position
540 // double bby =
541 // (1.0-data->paddle_position) * ((geom->size.y/2.0)-(geom->size.y*cfg->paddle_size.y));
542
543 // // size of the paddle indicator lights
544 // double led_dx = cfg->paddle_size.y * 0.5 * geom->size.y;
545
546
547 // if( data->inner_break_beam )
548 // {
549 // stg_rtk_fig_rectangle( fig, ibbx, bby+led_dx, 0, led_dx, led_dx, 1 );
550 // stg_rtk_fig_rectangle( fig, ibbx, -bby-led_dx, 0, led_dx, led_dx, 1 );
551 // }
552 // else
553 // {
554 // stg_rtk_fig_line( fig, ibbx, bby, ibbx, -bby );
555 // stg_rtk_fig_rectangle( fig, ibbx, bby+led_dx, 0, led_dx, led_dx, 0 );
556 // stg_rtk_fig_rectangle( fig, ibbx, -bby-led_dx, 0, led_dx, led_dx, 0 );
557 // }
558
559 // if( data->outer_break_beam )
560 // {
561 // stg_rtk_fig_rectangle( fig, obbx, bby+led_dx, 0, led_dx, led_dx, 1 );
562 // stg_rtk_fig_rectangle( fig, obbx, -bby-led_dx, 0, led_dx, led_dx, 1 );
563 // }
564 // else
565 // {
566 // stg_rtk_fig_line( fig, obbx, bby, obbx, -bby );
567 // stg_rtk_fig_rectangle( fig, obbx, bby+led_dx, 0, led_dx, led_dx, 0 );
568 // stg_rtk_fig_rectangle( fig, obbx, -bby-led_dx, 0, led_dx, led_dx, 0 );
569 // }
570
571 // // draw the contact indicators
572 // stg_rtk_fig_rectangle( fig,
573 // ((1.0 - cfg->paddle_size.x/2.0) * geom->size.x) - geom->size.x/2.0,
574 // (1.0 - cfg->paddle_position) * ((geom->size.y/2.0)-(geom->size.y*cfg->paddle_size.y)),
575 // 0.0,
576 // cfg->paddle_size.x * geom->size.x,
577 // cfg->paddle_size.y/6.0 * geom->size.y,
578 // data->paddle_contacts[0] );
579
580 // stg_rtk_fig_rectangle( fig,
581 // ((1.0 - cfg->paddle_size.x/2.0) * geom->size.x) - geom->size.x/2.0,
582 // (1.0 - cfg->paddle_position) * -((geom->size.y/2.0)-(geom->size.y*cfg->paddle_size.y)),
583 // 0.0,
584 // cfg->paddle_size.x * geom->size.x,
585 // cfg->paddle_size.y/6.0 * geom->size.y,
586 // data->paddle_contacts[1] );
587
588 // //stg_rtk_fig_color_rgb32( fig, gripper_col );
589
590 // return 0;
591 // }
592
593
594 // int gripper_render_cfg( stg_model_t* mod, void* user )
595 // {
596 // puts( "gripper render cfg" );
597 // stg_rtk_fig_t* fig = stg_model_get_fig( mod, "gripper_cfg_fig" );
598
599 // if( ! fig )
600 // {
601 // fig = stg_model_fig_create( mod, "gripper_cfg_fig", "top",
602 // STG_LAYER_GRIPPERCONFIG );
603
604 // stg_rtk_fig_color_rgb32( fig, stg_lookup_color( STG_GRIPPER_CFG_COLOR ));
605 // }
606 // else
607 // stg_rtk_fig_clear( fig );
608
609 // stg_geom_t geom;
610 // stg_model_get_geom( mod, &geom );
611
612 // // get the config and make sure it's the right size
613 // stg_gripper_config_t* cfg = (stg_gripper_config_t*)mod->cfg;
614 // assert( mod->cfg_len == sizeof(stg_gripper_config_t) );
615
616 // // different x location for each beam
617 // double ibbx = (cfg->inner_break_beam_inset) * geom.size.x - geom.size.x/2.0;
618 // double obbx = (cfg->outer_break_beam_inset) * geom.size.x - geom.size.x/2.0;
619
620 // // common y position
621 // double bby =
622 // (1.0-cfg->paddle_position) * ((geom.size.y/2.0)-(geom.size.y*cfg->paddle_size.y));
623
624 // // draw the position of the break beam sensors
625 // stg_rtk_fig_rectangle( fig, ibbx, bby, 0, 0.01, 0.01, 0 );
626 // stg_rtk_fig_rectangle( fig, obbx, bby, 0, 0.01, 0.01, 0 );
627
628 // return 0; //ok
629 // }
630
631
632 // int gripper_startup( stg_model_t* mod )
633 // {
634 // PRINT_DEBUG( "gripper startup" );
635 // stg_model_set_watts( mod, STG_GRIPPER_WATTS );
636 // return 0; // ok
637 // }
638
639 // int gripper_shutdown( stg_model_t* mod )
640 // {
641 // PRINT_DEBUG( "gripper shutdown" );
642 // stg_model_set_watts( mod, 0 );
643
644 // // unrender the break beams & lights
645 // stg_model_fig_clear( mod, "gripper_data_fig" );
646 // return 0; // ok
647 // }
648
649 // void stg_print_gripper_config( stg_gripper_config_t* cfg )
650 // {
651 // char* pstate;
652 // switch( cfg->paddles )
653 // {
654 // case STG_GRIPPER_PADDLE_OPEN: pstate = "OPEN"; break;
655 // case STG_GRIPPER_PADDLE_CLOSED: pstate = "CLOSED"; break;
656 // case STG_GRIPPER_PADDLE_OPENING: pstate = "OPENING"; break;
657 // case STG_GRIPPER_PADDLE_CLOSING: pstate = "CLOSING"; break;
658 // default: pstate = "*unknown*";
659 // }
660
661 // char* lstate;
662 // switch( cfg->lift )
663 // {
664 // case STG_GRIPPER_LIFT_UP: lstate = "UP"; break;
665 // case STG_GRIPPER_LIFT_DOWN: lstate = "DOWN"; break;
666 // case STG_GRIPPER_LIFT_DOWNING: lstate = "DOWNING"; break;
667 // case STG_GRIPPER_LIFT_UPPING: lstate = "UPPING"; break;
668 // default: lstate = "*unknown*";
669 // }
670
671 // printf("gripper state: paddles(%s)[%.2f] lift(%s)[%.2f] stall(%s)\n",
672 // pstate, cfg->paddle_position, lstate, cfg->lift_position,
673 // cfg->paddles_stalled ? "true" : "false" );
674 // }
675
676
677 // int gripper_unrender_data( stg_model_t* mod, void* userp )
678 // {
679 // stg_model_fig_clear( mod, "gripper_data_fig" );
680 // return 1; // callback just runs one time
681 // }
682
683 // int gripper_unrender_cfg( stg_model_t* mod, void* userp )
684 // {
685 // stg_model_fig_clear( mod, "gripper_cfg_fig" );
686 // return 1; // callback just runs one time
687 // }
688
Something went wrong with that request. Please try again.