Diff of /URBasic/urScriptExt.py [000000] .. [c1b1c5]

Switch to unified view

a b/URBasic/urScriptExt.py
1
'''
2
Python 3.x library to control an UR robot through its TCP/IP interfaces
3
Copyright (C) 2017  Martin Huus Bjerge, Rope Robotics ApS, Denmark
4
5
Permission is hereby granted, free of charge, to any person obtaining a copy of this software
6
and associated documentation files (the "Software"), to deal in the Software without restriction,
7
including without limitation the rights to use, copy, modify, merge, publish, distribute,
8
sublicense, and/or sell copies of the Software, and to permit persons to whom the Software
9
is furnished to do so, subject to the following conditions:
10
11
The above copyright notice and this permission notice shall be included in all copies
12
or substantial portions of the Software.
13
14
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
15
INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
16
PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL "Rope Robotics ApS" BE LIABLE FOR ANY CLAIM,
17
DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
19
20
Except as contained in this notice, the name of "Rope Robotics ApS" shall not be used
21
in advertising or otherwise to promote the sale, use or other dealings in this Software
22
without prior written authorization from "Rope Robotics ApS".
23
'''
24
__author__ = "Martin Huus Bjerge"
25
__copyright__ = "Copyright 2017, Rope Robotics ApS, Denmark"
26
__license__ = "MIT License"
27
28
import URBasic
29
import numpy as np
30
import time
31
32
33
class UrScriptExt(URBasic.urScript.UrScript):
34
    '''
35
    Interface to remote access UR script commands, and add some extended features as well.
36
    For more details see the script manual at this site:
37
    http://www.universal-robots.com/download/
38
39
    Beside the implementation of the script interface, this class also inherits from the
40
    Real Time Client and RTDE interface and thereby also open a connection to these data interfaces.
41
    The Real Time Client in this version is only used to send program and script commands
42
    to the robot, not to read data from the robot, all data reading is done via the RTDE interface.
43
44
    This class also opens a connection to the UR Dashboard server and enables you to
45
    e.g. reset error and warnings from the UR controller.
46
47
    The constructor takes a UR robot hostname as input, and a RTDE configuration file, and optional a logger object.
48
49
    Input parameters:
50
    host (string):  hostname or IP of UR Robot (RT CLient server)
51
    rtde_conf_filename (string):  Path to xml file describing what channels to activate
52
    logger (URBasis_DataLogging obj): A instance if a logger object if common logging is needed.
53
54
55
    Example:
56
    rob = URBasic.urScriptExt.UrScriptExt('192.168.56.101', rtde_conf_filename='rtde_configuration.xml')
57
    self.close_rtc()
58
    '''
59
60
    def __init__(self, host, robotModel, hasForceTorque=False, conf_filename=None):
61
        if host is None:  # Only for enable code completion
62
            return
63
        super(UrScriptExt, self).__init__(host, robotModel, hasForceTorque, conf_filename)
64
        logger = URBasic.dataLogging.DataLogging()
65
        name = logger.AddEventLogging(__name__, log2Consol=False)
66
        self.__logger = logger.__dict__[name]
67
        self.print_actual_tcp_pose()
68
        self.print_actual_joint_positions()
69
        self.__logger.info('Init done')
70
71
    def close(self):
72
        self.print_actual_tcp_pose()
73
        self.print_actual_joint_positions()
74
        self.robotConnector.close()
75
76
    def reset_error(self):
77
        '''
78
        Check if the UR controller is powered on and ready to run.
79
        If controller isn't power on it will be power up.
80
        If there is a safety error, it will be tried rest it once.
81
82
        Return Value:
83
        state (boolean): True of power is on and no safety errors active.
84
        '''
85
86
        if not self.robotConnector.RobotModel.RobotStatus().PowerOn:
87
            # self.robotConnector.DashboardClient.PowerOn()
88
            self.robotConnector.DashboardClient.ur_power_on()
89
            self.robotConnector.DashboardClient.wait_dbs()
90
            # self.robotConnector.DashboardClient.BrakeRelease()
91
            self.robotConnector.DashboardClient.ur_brake_release()
92
            self.robotConnector.DashboardClient.wait_dbs()
93
94
        if self.robotConnector.RobotModel.SafetyStatus().StoppedDueToSafety:  # self.get_safety_status()['StoppedDueToSafety']:
95
            # self.robotConnector.DashboardClient.UnlockProtectiveStop()
96
            self.robotConnector.DashboardClient.ur_unlock_protective_stop()
97
            self.robotConnector.DashboardClient.wait_dbs()
98
            # self.robotConnector.DashboardClient.CloseSafetyPopup()
99
            self.robotConnector.DashboardClient.ur_close_safety_popup()
100
            self.robotConnector.DashboardClient.wait_dbs()
101
            # self.robotConnector.DashboardClient.BrakeRelease()
102
            self.robotConnector.DashboardClient.ur_brake_release()
103
            self.robotConnector.DashboardClient.wait_dbs()
104
105
            # ADDED: If there was a safety stop -> reupload the realtime control program
106
            self.init_realtime_control()
107
108
        # return self.get_robot_status()['PowerOn'] & (not self.get_safety_status()['StoppedDueToSafety'])
109
        return self.robotConnector.RobotModel.RobotStatus().PowerOn and not self.robotConnector.RobotModel.SafetyStatus().StoppedDueToSafety
110
111
    def get_in(self, port, wait=True):
112
        '''
113
        Get input signal level
114
115
        Parameters:
116
        port (HW profile str): Hardware profile tag
117
        wait (bool): True if wait for next RTDE sample, False, to get the latest sample
118
119
        Return Value:
120
        out (bool or float), The signal level.
121
        '''
122
        if 'BCI' == port[:3]:
123
            return self.get_configurable_digital_in(int(port[4:]), wait)
124
        elif 'BDI' == port[:3]:
125
            return self.get_standard_digital_in(int(port[4:]), wait)
126
        elif 'BAI' == port[:3]:
127
            return self.get_standard_analog_in(int(port[4:]), wait)
128
129
    def set_output(self, port, value):
130
        '''
131
        Get output signal level
132
133
        Parameters:
134
        port (HW profile str): Hardware profile tag
135
        value (bool or float): The output value to be set
136
137
        Return Value:
138
        Status (bool): Status, True if signal set successfully.
139
        '''
140
141
        if 'BCO' == port[:3]:
142
            self.set_configurable_digital_out(int(port[4:]), value)
143
        elif 'BDO' == port[:3]:
144
            self.set_standard_digital_out(int(port[4:]), value)
145
        elif 'BAO' == port[:3]:
146
            pass
147
        elif 'TDO' == port[:3]:
148
            pass
149
150
            # if self.sendData():
151
            #    return True
152
            return True  # Vi har sendt det .. vi checker ikke
153
        else:
154
            return False
155
156
    def init_force_remote(self, task_frame=[0.0, 0.0, 0.0, 0.0, 0.0, 0.0], f_type=2):
157
        '''
158
        The Force Remote function enables changing the force settings dynamically,
159
        without sending new programs to the robot, and thereby exit and enter force mode again.
160
        As the new settings are send via RTDE, the force can be updated every 8ms.
161
        This function initializes the remote force function,
162
        by sending a program to the robot that can receive new force settings.
163
164
        See "force_mode" for more details on force functions
165
166
        Parameters:
167
        task_frame (6D-vector): Initial task frame (can be changed via the update function)
168
        f_type (int): Initial force type (can be changed via the update function)
169
170
        Return Value:
171
        Status (bool): Status, True if successfully initialized.
172
        '''
173
174
        if not self.robotConnector.RTDE.isRunning():
175
            self.__logger.error('RTDE need to be running to use force remote')
176
            return False
177
178
        selection_vector = [0, 0, 0, 0, 0, 0]
179
        wrench = [0.0, 0.0, 0.0, 0.0, 0.0, 0.0]
180
        limits = [0.1, 0.1, 0.1, 0.1, 0.1, 0.1]
181
182
        self.robotConnector.RTDE.setData('input_int_register_0', selection_vector[0])
183
        self.robotConnector.RTDE.setData('input_int_register_1', selection_vector[1])
184
        self.robotConnector.RTDE.setData('input_int_register_2', selection_vector[2])
185
        self.robotConnector.RTDE.setData('input_int_register_3', selection_vector[3])
186
        self.robotConnector.RTDE.setData('input_int_register_4', selection_vector[4])
187
        self.robotConnector.RTDE.setData('input_int_register_5', selection_vector[5])
188
189
        self.robotConnector.RTDE.setData('input_double_register_0', wrench[0])
190
        self.robotConnector.RTDE.setData('input_double_register_1', wrench[1])
191
        self.robotConnector.RTDE.setData('input_double_register_2', wrench[2])
192
        self.robotConnector.RTDE.setData('input_double_register_3', wrench[3])
193
        self.robotConnector.RTDE.setData('input_double_register_4', wrench[4])
194
        self.robotConnector.RTDE.setData('input_double_register_5', wrench[5])
195
196
        self.robotConnector.RTDE.setData('input_double_register_6', limits[0])
197
        self.robotConnector.RTDE.setData('input_double_register_7', limits[1])
198
        self.robotConnector.RTDE.setData('input_double_register_8', limits[2])
199
        self.robotConnector.RTDE.setData('input_double_register_9', limits[3])
200
        self.robotConnector.RTDE.setData('input_double_register_10', limits[4])
201
        self.robotConnector.RTDE.setData('input_double_register_11', limits[5])
202
203
        self.robotConnector.RTDE.setData('input_double_register_12', task_frame[0])
204
        self.robotConnector.RTDE.setData('input_double_register_13', task_frame[1])
205
        self.robotConnector.RTDE.setData('input_double_register_14', task_frame[2])
206
        self.robotConnector.RTDE.setData('input_double_register_15', task_frame[3])
207
        self.robotConnector.RTDE.setData('input_double_register_16', task_frame[4])
208
        self.robotConnector.RTDE.setData('input_double_register_17', task_frame[5])
209
210
        self.robotConnector.RTDE.setData('input_int_register_6', f_type)
211
        self.robotConnector.RTDE.sendData()
212
213
        prog = '''def force_remote():
214
    while (True):
215
216
        global task_frame =  p[read_input_float_register(12),
217
                              read_input_float_register(13),
218
                              read_input_float_register(14),
219
                              read_input_float_register(15),
220
                              read_input_float_register(16),
221
                              read_input_float_register(17)]
222
223
224
        global selection_vector = [ read_input_integer_register(0),
225
                                    read_input_integer_register(1),
226
                                    read_input_integer_register(2),
227
                                    read_input_integer_register(3),
228
                                    read_input_integer_register(4),
229
                                    read_input_integer_register(5)]
230
231
        global wrench = [ read_input_float_register(0),
232
                          read_input_float_register(1),
233
                          read_input_float_register(2),
234
                          read_input_float_register(3),
235
                          read_input_float_register(4),
236
                          read_input_float_register(5)]
237
238
        global limits = [ read_input_float_register(6),
239
                          read_input_float_register(7),
240
                          read_input_float_register(8),
241
                          read_input_float_register(9),
242
                          read_input_float_register(10),
243
                          read_input_float_register(11)]
244
245
        global f_type = read_input_integer_register(6)
246
247
        force_mode(task_frame, selection_vector, wrench, f_type , limits)
248
        sync()
249
    end
250
end
251
'''
252
        self.robotConnector.RealTimeClient.SendProgram(prog.format(**locals()))
253
        self.robotConnector.RobotModel.forceRemoteActiveFlag = True
254
255
    def set_force_remote(self, task_frame=[0.0, 0.0, 0.0, 0.0, 0.0, 0.0], selection_vector=[0, 0, 0, 0, 0, 0],
256
                         wrench=[0.0, 0.0, 0.0, 0.0, 0.0, 0.0], limits=[0.1, 0.1, 0.1, 0.1, 0.1, 0.1], f_type=2):
257
        '''
258
        Update/set remote force, see "init_force_remote" for more details.
259
260
        Parameters:
261
        task frame: A pose vector that defines the force frame relative to the base frame.
262
263
        selection vector: A 6d vector that may only contain 0 or 1. 1 means that the robot will be
264
                          compliant in the corresponding axis of the task frame, 0 means the robot is
265
                          not compliant along/about that axis.
266
267
        wrench: The forces/torques the robot is to apply to its environment. These values
268
                have different meanings whether they correspond to a compliant axis or not.
269
                Compliant axis: The robot will adjust its position along/about the axis in order
270
                to achieve the specified force/torque. Non-compliant axis: The robot follows
271
                the trajectory of the program but will account for an external force/torque
272
                of the specified value.
273
274
        limits: A 6d vector with float values that are interpreted differently for
275
                compliant/non-compliant axes:
276
                Compliant axes: The limit values for compliant axes are the maximum
277
                                allowed tcp speed along/about the axis.
278
                Non-compliant axes: The limit values for non-compliant axes are the
279
                                    maximum allowed deviation along/about an axis between the
280
                                    actual tcp position and the one set by the program.
281
282
        f_type: An integer specifying how the robot interprets the force frame.
283
                1: The force frame is transformed in a way such that its y-axis is aligned with a vector
284
                   pointing from the robot tcp towards the origin of the force frame.
285
                2: The force frame is not transformed.
286
                3: The force frame is transformed in a way such that its x-axis is the projection of
287
                   the robot tcp velocity vector onto the x-y plane of the force frame.
288
                All other values of f_type are invalid.
289
290
        Return Value:
291
        Status (bool): Status, True if parameters successfully updated.
292
        '''
293
        if not self.robotConnector.RobotModel.forceRemoteActiveFlag:
294
            self.init_force_remote(task_frame, f_type)
295
296
        if self.robotConnector.RTDE.isRunning() and self.robotConnector.RobotModel.forceRemoteActiveFlag:
297
            self.robotConnector.RTDE.setData('input_int_register_0', selection_vector[0])
298
            self.robotConnector.RTDE.setData('input_int_register_1', selection_vector[1])
299
            self.robotConnector.RTDE.setData('input_int_register_2', selection_vector[2])
300
            self.robotConnector.RTDE.setData('input_int_register_3', selection_vector[3])
301
            self.robotConnector.RTDE.setData('input_int_register_4', selection_vector[4])
302
            self.robotConnector.RTDE.setData('input_int_register_5', selection_vector[5])
303
304
            self.robotConnector.RTDE.setData('input_double_register_0', wrench[0])
305
            self.robotConnector.RTDE.setData('input_double_register_1', wrench[1])
306
            self.robotConnector.RTDE.setData('input_double_register_2', wrench[2])
307
            self.robotConnector.RTDE.setData('input_double_register_3', wrench[3])
308
            self.robotConnector.RTDE.setData('input_double_register_4', wrench[4])
309
            self.robotConnector.RTDE.setData('input_double_register_5', wrench[5])
310
311
            self.robotConnector.RTDE.setData('input_double_register_6', limits[0])
312
            self.robotConnector.RTDE.setData('input_double_register_7', limits[1])
313
            self.robotConnector.RTDE.setData('input_double_register_8', limits[2])
314
            self.robotConnector.RTDE.setData('input_double_register_9', limits[3])
315
            self.robotConnector.RTDE.setData('input_double_register_10', limits[4])
316
            self.robotConnector.RTDE.setData('input_double_register_11', limits[5])
317
318
            self.robotConnector.RTDE.setData('input_double_register_12', task_frame[0])
319
            self.robotConnector.RTDE.setData('input_double_register_13', task_frame[1])
320
            self.robotConnector.RTDE.setData('input_double_register_14', task_frame[2])
321
            self.robotConnector.RTDE.setData('input_double_register_15', task_frame[3])
322
            self.robotConnector.RTDE.setData('input_double_register_16', task_frame[4])
323
            self.robotConnector.RTDE.setData('input_double_register_17', task_frame[5])
324
325
            self.robotConnector.RTDE.setData('input_int_register_6', f_type)
326
327
            self.robotConnector.RTDE.sendData()
328
            return True
329
330
        else:
331
            if not self.robotConnector.RobotModel.forceRemoteActiveFlag:
332
                self.__logger.warning('Force Remote not initialized')
333
            else:
334
                self.__logger.warning('RTDE is not running')
335
336
            return False
337
338
    def init_realtime_control(self):
339
        '''
340
        The realtime control mode enables continuous updates to a servoj program which is
341
        initialized by this function. This way no new program has to be sent to the robot
342
        and the robot can perform a smooth trajectory.
343
        Sending new servoj commands is done by utilizing RTDE of this library
344
        
345
        Parameters:
346
        sample_time: time of one sample, standard is 8ms as this is the thread-cycle time of UR
347
        
348
        Return Value:
349
        Status (bool): Status, True if successfully initialized.
350
        '''
351
352
        if not self.robotConnector.RTDE.isRunning():
353
            self.__logger.error('RTDE needs to be running to use realtime control')
354
            return False
355
356
        # get current tcp pos
357
        init_pose = self.get_actual_tcp_pose()
358
359
        self.robotConnector.RTDE.setData('input_double_register_0', init_pose[0])
360
        self.robotConnector.RTDE.setData('input_double_register_1', init_pose[1])
361
        self.robotConnector.RTDE.setData('input_double_register_2', init_pose[2])
362
        self.robotConnector.RTDE.setData('input_double_register_3', init_pose[3])
363
        self.robotConnector.RTDE.setData('input_double_register_4', init_pose[4])
364
        self.robotConnector.RTDE.setData('input_double_register_5', init_pose[5])
365
366
        self.robotConnector.RTDE.sendData()
367
368
        prog = '''def realtime_control():
369
    
370
    
371
    while (True):
372
        
373
        new_pose = p[read_input_float_register(0),
374
                    read_input_float_register(1),
375
                    read_input_float_register(2),
376
                    read_input_float_register(3),
377
                    read_input_float_register(4),
378
                    read_input_float_register(5)]
379
           
380
        servoj(get_inverse_kin(new_pose), t=0.2, lookahead_time= 0.1, gain=350)
381
            
382
        sync()
383
    end
384
end
385
'''
386
        # , t=0.1
387
388
        self.robotConnector.RealTimeClient.SendProgram(prog.format(**locals()))
389
        self.robotConnector.RobotModel.realtimeControlFlag = True
390
391
    def set_realtime_pose(self, pose):
392
        """
393
        Update/Set realtime_pose after sample_time seconds.
394
395
        Parameters
396
        pose: pose to transition to in sample_time seconds
397
        sample_time: time to take to perform servoj to next pose. 0.008 = thread cycle time of Universal Robot
398
        """
399
400
        if not self.robotConnector.RobotModel.realtimeControlFlag:
401
            print("Realtime control not initialized!")
402
            self.init_realtime_control()
403
            print("Realtime control initialized!")
404
405
        if self.robotConnector.RTDE.isRunning() and self.robotConnector.RobotModel.realtimeControlFlag:
406
            self.robotConnector.RTDE.setData('input_double_register_0', pose[0])
407
            self.robotConnector.RTDE.setData('input_double_register_1', pose[1])
408
            self.robotConnector.RTDE.setData('input_double_register_2', pose[2])
409
            self.robotConnector.RTDE.setData('input_double_register_3', pose[3])
410
            self.robotConnector.RTDE.setData('input_double_register_4', pose[4])
411
            self.robotConnector.RTDE.setData('input_double_register_5', pose[5])
412
            self.robotConnector.RTDE.sendData()
413
            return True
414
        else:
415
            if not self.robotConnector.RobotModel.realtimeControlFlag:
416
                self.__logger.warning('Realtime Remote Control not initialized')
417
            else:
418
                self.__logger.warning('RTDE is not running')
419
420
            return False
421
422
    def move_force_2stop(self, start_tolerance=0.01,
423
                         stop_tolerance=0.01,
424
                         wrench_gain=[1.0, 1.0, 1.0, 1.0, 1.0, 1.0],
425
                         timeout=10,
426
                         task_frame=[0.0, 0.0, 0.0, 0.0, 0.0, 0.0],
427
                         selection_vector=[0, 0, 0, 0, 0, 0],
428
                         wrench=[0.0, 0.0, 0.0, 0.0, 0.0, 0.0],
429
                         limits=[0.1, 0.1, 0.1, 0.1, 0.1, 0.1],
430
                         f_type=2):
431
        '''
432
        Move force will set the robot in force mode (see force_mode) and move the TCP until it meets an object making the TCP stand still.
433
434
        Parameters:
435
        start_tolerance (float): sum of all elements in a pose vector defining a robot has started moving (60 samples)
436
437
        stop_tolerance (float): sum of all elements in a pose vector defining a standing still robot (60 samples)
438
439
        wrench_gain (6D vector): Gain multiplied with wrench each 8ms sample
440
441
        timeout (float): Seconds to timeout if tolerance not reached
442
443
        task frame: A pose vector that defines the force frame relative to the base frame.
444
445
        selection vector: A 6d vector that may only contain 0 or 1. 1 means that the robot will be
446
                          compliant in the corresponding axis of the task frame, 0 means the robot is
447
                          not compliant along/about that axis.
448
449
        wrench: The forces/torques the robot is to apply to its environment. These values
450
                have different meanings whether they correspond to a compliant axis or not.
451
                Compliant axis: The robot will adjust its position along/about the axis in order
452
                to achieve the specified force/torque. Non-compliant axis: The robot follows
453
                the trajectory of the program but will account for an external force/torque
454
                of the specified value.
455
456
        limits: A 6d vector with float values that are interpreted differently for
457
                compliant/non-compliant axes:
458
                Compliant axes: The limit values for compliant axes are the maximum
459
                                allowed tcp speed along/about the axis.
460
                Non-compliant axes: The limit values for non-compliant axes are the
461
                                    maximum allowed deviation along/about an axis between the
462
                                    actual tcp position and the one set by the program.
463
464
        f_type: An integer specifying how the robot interprets the force frame.
465
                1: The force frame is transformed in a way such that its y-axis is aligned with a vector
466
                   pointing from the robot tcp towards the origin of the force frame.
467
                2: The force frame is not transformed.
468
                3: The force frame is transformed in a way such that its x-axis is the projection of
469
                   the robot tcp velocity vector onto the x-y plane of the force frame.
470
                All other values of f_type are invalid.
471
472
        Return Value:
473
        Status (bool): Status, True if signal set successfully.
474
475
        '''
476
477
        timeoutcnt = 125 * timeout
478
        wrench = np.array(wrench)
479
        wrench_gain = np.array(wrench_gain)
480
        self.set_force_remote(task_frame, selection_vector, wrench, limits, f_type)
481
482
        dist = np.array(range(60), float)
483
        dist.fill(0.)
484
        cnt = 0
485
        old_pose = self.get_actual_tcp_pose() * np.array(selection_vector)
486
        while np.sum(dist) < start_tolerance and cnt < timeoutcnt:
487
            new_pose = self.get_actual_tcp_pose() * np.array(selection_vector)
488
            wrench = wrench * wrench_gain  # Need a max wrencd check
489
            self.set_force_remote(task_frame, selection_vector, wrench, limits, f_type)
490
            dist[np.mod(cnt, 60)] = np.abs(np.sum(new_pose - old_pose))
491
            old_pose = new_pose
492
            cnt += 1
493
494
        # Check if robot started to move
495
        if cnt < timeoutcnt:
496
            dist.fill(stop_tolerance)
497
            cnt = 0
498
            while np.sum(dist) > stop_tolerance and cnt < timeoutcnt:
499
                new_pose = self.get_actual_tcp_pose() * np.array(selection_vector)
500
                dist[np.mod(cnt, 60)] = np.abs(np.sum(new_pose - old_pose))
501
                old_pose = new_pose
502
                cnt += 1
503
504
        self.set_force_remote(task_frame, selection_vector, [0, 0, 0, 0, 0, 0], limits, f_type)
505
        self.end_force_mode()
506
        if cnt >= timeoutcnt:
507
            return False
508
        else:
509
            return True
510
511
    def move_force(self, pose=None,
512
                   a=1.2,
513
                   v=0.25,
514
                   t=0,
515
                   r=0.0,
516
                   movetype='l',
517
                   task_frame=[0.0, 0.0, 0.0, 0.0, 0.0, 0.0],
518
                   selection_vector=[0, 0, 0, 0, 0, 0],
519
                   wrench=[0.0, 0.0, 0.0, 0.0, 0.0, 0.0],
520
                   limits=[0.1, 0.1, 0.1, 0.1, 0.1, 0.1],
521
                   f_type=2,
522
                   wait=True,
523
                   q=None):
524
525
        """
526
        Concatenate several move commands and applies a blending radius
527
        pose or q is a list of pose or joint-pose, and apply a force in a direction
528
529
        Parameters:
530
        pose: list of target pose (pose can also be specified as joint
531
              positions, then forward kinematics is used to calculate the corresponding pose see q)
532
533
        a:    tool acceleration [m/s^2]
534
535
        v:    tool speed [m/s]
536
537
        t:    time [S]
538
539
        r:    blend radius [m]
540
541
        movetype: (str): 'j', 'l', 'p', 'c'
542
543
        task frame: A pose vector that defines the force frame relative to the base frame.
544
545
        selection vector: A 6d vector that may only contain 0 or 1. 1 means that the robot will be
546
                          compliant in the corresponding axis of the task frame, 0 means the robot is
547
                          not compliant along/about that axis.
548
549
        wrench: The forces/torques the robot is to apply to its environment. These values
550
                have different meanings whether they correspond to a compliant axis or not.
551
                Compliant axis: The robot will adjust its position along/about the axis in order
552
                to achieve the specified force/torque. Non-compliant axis: The robot follows
553
                the trajectory of the program but will account for an external force/torque
554
                of the specified value.
555
556
        limits: A 6d vector with float values that are interpreted differently for
557
                compliant/non-compliant axes:
558
                Compliant axes: The limit values for compliant axes are the maximum
559
                                allowed tcp speed along/about the axis.
560
                Non-compliant axes: The limit values for non-compliant axes are the
561
                                    maximum allowed deviation along/about an axis between the
562
                                    actual tcp position and the one set by the program.
563
564
        f_type: An integer specifying how the robot interprets the force frame.
565
                1: The force frame is transformed in a way such that its y-axis is aligned with a vector
566
                   pointing from the robot tcp towards the origin of the force frame.
567
                2: The force frame is not transformed.
568
                3: The force frame is transformed in a way such that its x-axis is the projection of
569
                   the robot tcp velocity vector onto the x-y plane of the force frame.
570
                All other values of f_type are invalid.
571
572
        wait: function return when movement is finished
573
574
        q:    list of target joint positions
575
576
577
        Return Value:
578
        Status (bool): Status, True if signal set successfully.
579
580
        """
581
        task_frame = np.array(task_frame)
582
        if np.size(task_frame.shape) == 2:
583
            prefix = "p"
584
            t_val = ''
585
            if pose is None:
586
                prefix = ""
587
                pose = q
588
            pose = np.array(pose)
589
            if movetype == 'j' or movetype == 'l':
590
                tval = 't={t},'.format(**locals())
591
592
            prg = 'def move_force():\n'
593
            for idx in range(np.size(pose, 0)):
594
                posex = np.round(pose[idx], 4)
595
                posex = posex.tolist()
596
                task_framex = np.round(task_frame[idx], 4)
597
                task_framex = task_framex.tolist()
598
                if (np.size(pose, 0) - 1) == idx:
599
                    r = 0
600
                prg += '    force_mode(p{task_framex}, {selection_vector}, {wrench}, {f_type}, {limits})\n'.format(
601
                    **locals())
602
                prg += '    move{movetype}({prefix}{posex}, a={a}, v={v}, {t_val} r={r})\n'.format(**locals())
603
604
            prg += '    stopl({a})\n'.format(**locals())
605
            prg += '    end_force_mode()\nend\n'
606
607
        else:
608
            prg = '''def move_force():
609
    force_mode(p{task_frame}, {selection_vector}, {wrench}, {f_type}, {limits})
610
{movestr}
611
    end_force_mode()
612
end
613
'''
614
            task_frame = task_frame.tolist()
615
            movestr = self._move(movetype, pose, a, v, t, r, wait, q)
616
617
        self.robotConnector.RealTimeClient.SendProgram(prg.format(**locals()))
618
        if (wait):
619
            self.waitRobotIdleOrStopFlag()
620
621
    def movej_waypoints(self, waypoints, wait=True):
622
        '''
623
        Movej along multiple waypoints. By configuring a blend radius continuous movements can be enabled.
624
625
        Parameters:
626
        waypoints: List waypoint dictionaries {pose: [6d], a, v, t, r}
627
        '''
628
629
        prg = '''def move_waypoints():
630
{exec_str}
631
end
632
'''
633
        exec_str = ""
634
        for waypoint in waypoints:
635
            movestr = self._move(movetype='j', **waypoint)
636
            exec_str += movestr + "\n"
637
638
        programString = prg.format(**locals())
639
640
        self.robotConnector.RealTimeClient.SendProgram(programString)
641
        if (wait):
642
            self.waitRobotIdleOrStopFlag()
643
644
    def movel_waypoints(self, waypoints, wait=True):
645
        '''
646
        Movel along multiple waypoints. By configuring a blend radius continuous movements can be enabled.
647
648
        Parameters:
649
        waypoints: List waypoint dictionaries {pose: [6d], a, v, t, r}
650
        '''
651
652
        prg = '''def move_waypoints():
653
{exec_str}
654
end
655
'''
656
        exec_str = ""
657
        for waypoint in waypoints:
658
            movestr = self._move(movetype='l', **waypoint)
659
            exec_str += movestr + "\n"
660
661
        programString = prg.format(**locals())
662
663
        self.robotConnector.RealTimeClient.SendProgram(programString)
664
        if (wait):
665
            self.waitRobotIdleOrStopFlag()
666
667
    def print_actual_tcp_pose(self):
668
        '''
669
        print the actual TCP pose
670
        '''
671
        self.print_pose(self.get_actual_tcp_pose())
672
673
    def print_actual_joint_positions(self):
674
        '''
675
        print the actual TCP pose
676
        '''
677
        self.print_pose(q=self.get_actual_joint_positions())
678
679
    def print_pose(self, pose=None, q=None):
680
        '''
681
        print a pose
682
        '''
683
        if q is None:
684
            print('Robot Pose: [{: 06.4f}, {: 06.4f}, {: 06.4f},   {: 06.4f}, {: 06.4f}, {: 06.4f}]'.format(*pose))
685
        else:
686
            print('Robot joint positions: [{: 06.4f}, {: 06.4f}, {: 06.4f},   {: 06.4f}, {: 06.4f}, {: 06.4f}]'.format(
687
                *q))