%SerialLink.teach Graphical teach pendant % % R.teach(OPTIONS) drive a graphical robot by means of a graphical slider panel. % If no graphical robot exists one is created in a new window. Otherwise % all current instances of the graphical robot are driven. % % h = R.teach(OPTIONS) as above but returns a handle for the teach window. Can % be used to programmatically delete the teach window. % % Options:: % 'eul' Display tool orientation in Euler angles % 'rpy' Display tool orientation in roll/pitch/yaw angles % 'approach' Display tool orientation as approach vector (z-axis) % 'degrees' Display angles in degrees (default radians) % 'q0',q Set initial joint coordinates % 'callback',C Set a callback function % % GUI:: % % - The record button adds the current joint coordinates as a row to the robot's % qteach property. % - The Quit button destroys the teach window. % % Notes:: % - The slider limits are derived from the joint limit properties. If not % set then for % - a revolute joint they are assumed to be [-pi, +pi] % - a prismatic joint they are assumed unknown and an error occurs. % % See also SerialLink.plot. % Copyright (C) 1993-2011, by Peter I. Corke % % This file is part of The Robotics Toolbox for Matlab (RTB). % % RTB is free software: you can redistribute it and/or modify % it under the terms of the GNU Lesser General Public License as published by % the Free Software Foundation, either version 3 of the License, or % (at your option) any later version. % % RTB is distributed in the hope that it will be useful, % but WITHOUT ANY WARRANTY; without even the implied warranty of % MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the % GNU Lesser General Public License for more details. % % You should have received a copy of the GNU Leser General Public License % along with RTB. If not, see . % % http://www.petercorke.com %% TODO: %% make the sliders change the animation while moving %% http://www.mathworks.com/matlabcentral/newsreader/view_thread/159374 %% 1. download FINDJOBJ from the file exchange: http://www.mathworks.com/matlabcentral/fileexchange/14317 %% 2. hSlider = uicontrol('style','slider', ...); %create the slider, get its Matlab handle %% 3. jSlider = findjobj(hSlider,'nomenu'); %get handle of the underlying java object %% 4. jSlider.AdjustmentValueChangedCallback = @myMatlabFunction; %set callback %% %% Note: you can also use the familiar format: %% set(jSlider,'AdjustmentValueChangedCallback',@myMatlabFunction) %% %% Feel free to explore many other properties (and ~30 callbacks) %% available in jSlider but not in its Matlab front-end interface hSlider. %% %% Warning: FINDJOBJ relies on undocumented and unsupported Matlab/Java %% functionality. function handle = teach(r, varargin) bgcol = [135 206 250]/255; opt.degrees = false; opt.q0 = []; opt.orientation = {'approach', 'eul', 'rpy'}; opt.callback = []; % TODO: options for rpy, or eul angle display opt = tb_optparse(opt, varargin); % drivebot(r, q) % drivebot(r, 'deg') n = r.n; width = 300; height = 40; qlim = r.qlim; if any(isinf(qlim)) error('for prismatic axes must define joint coordinate limits, set qlim properties for prismatic Links'); end if isempty(opt.q0) q = zeros(1,n); else q = opt.q0; end % set up scale factor, from actual limits in radians/metres to display units qscale = ones(r.n,1); for i=1:r.n L=r.links(i); if opt.degrees && L.isrevolute qscale(i) = 180/pi; end end handles.qscale = qscale; handles.orientation = opt.orientation; handles.callback = opt.callback; T6 = r.fkine(q); fig = figure('Units', 'pixels', ... 'Position', [0 -height width height*(n+2)+40], ... 'BusyAction', 'cancel', ... 'HandleVisibility', 'off', ... 'Color', bgcol); set(fig,'MenuBar','none') delete( get(fig, 'Children') ) % first we check to see if there are any graphical robots of % this name, if so we use them, otherwise create a robot plot. rhandles = findobj('Tag', r.name); % attempt to get current joint config of graphical robot if ~isempty(rhandles) h = get(rhandles(1), 'UserData'); if ~isempty(h.q) q = h.q; end end % now make the sliders for i=1:n % slider label uicontrol(fig, 'Style', 'text', ... 'Units', 'pixels', ... 'BackgroundColor', bgcol, ... 'Position', [0 height*(n-i)+40 width*0.1 height*0.4], ... 'String', sprintf('q%d', i)); % slider itself q(i) = max( qlim(i,1), min( qlim(i,2), q(i) ) ); % clip to range handles.slider(i) = uicontrol(fig, 'Style', 'slider', ... 'Units', 'pixels', ... 'Position', [width*0.1 height*(n-i)+40 width*0.7 height*0.4], ... 'Min', qlim(i,1), ... 'Max', qlim(i,2), ... 'Value', q(i), ... 'Tag', sprintf('Slider%d', i)); % text box showing slider value, also editable handles.edit(i) = uicontrol(fig, 'Style', 'edit', ... 'Units', 'pixels', ... 'Position', [width*0.8 height*(n-i-0.1)+40 width*0.2 height*0.7], ... 'String', num2str(qscale(i)*q(i)), ... 'Tag', sprintf('Edit%d', i)); end set(handles.slider(1)) % robot name text box uicontrol(fig, 'Style', 'text', ... 'Units', 'pixels', ... 'FontSize', 20, ... 'HorizontalAlignment', 'left', ... 'Position', [0 height*(n+1.2)+40 0.8*width 0.8*height], ... 'BackgroundColor', 'white', ... 'String', r.name); % X uicontrol(fig, 'Style', 'text', ... 'Units', 'pixels', ... 'BackgroundColor', bgcol, ... 'Position', [0 height*(n+0.5)+40 0.06*width height/2], ... 'BackgroundColor', 'yellow', ... 'FontSize', 10, ... 'HorizontalAlignment', 'right', ... 'String', 'x:'); handles.t6.t(1) = uicontrol(fig, 'Style', 'edit', ... 'Units', 'pixels', ... 'Position', [0.06*width height*(n+0.5)+40 width*0.2 height*0.6], ... 'String', sprintf('%.3f', T6(1,4)), ... 'Tag', 'T6'); % Y uicontrol(fig, 'Style', 'text', ... 'Units', 'pixels', ... 'BackgroundColor', bgcol, ... 'Position', [0.26*width height*(n+0.5)+40 0.06*width height/2], ... 'BackgroundColor', 'yellow', ... 'FontSize', 10, ... 'HorizontalAlignment', 'right', ... 'String', 'y:'); handles.t6.t(2) = uicontrol(fig, 'Style', 'edit', ... 'Units', 'pixels', ... 'Position', [0.32*width height*(n+0.5)+40 width*0.2 height*0.6], ... 'String', sprintf('%.3f', T6(2,4))); % Z uicontrol(fig, 'Style', 'text', ... 'Units', 'pixels', ... 'BackgroundColor', bgcol, ... 'Position', [0.52*width height*(n+0.5)+40 0.06*width height/2], ... 'BackgroundColor', 'yellow', ... 'FontSize', 10, ... 'HorizontalAlignment', 'right', ... 'String', 'z:'); handles.t6.t(3) = uicontrol(fig, 'Style', 'edit', ... 'Units', 'pixels', ... 'Position', [0.58*width height*(n+0.5)+40 width*0.2 height*0.6], ... 'String', sprintf('%.3f', T6(3,4))); % Orientation switch opt.orientation case 'approach' labels = {'ax:', 'ay:', 'az:'}; case 'eul' labels = {'\phi:', '\theta:', '\psi:'}; case'rpy' labels = {'R:', 'P:', 'Y:'}; end % AX uicontrol(fig, 'Style', 'text', ... 'Units', 'pixels', ... 'BackgroundColor', bgcol, ... 'Position', [0 height*(n)+40 0.06*width height/2], ... 'BackgroundColor', 'yellow', ... 'FontSize', 10, ... 'HorizontalAlignment', 'right', ... 'String', labels(1)); handles.t6.r(1) = uicontrol(fig, 'Style', 'edit', ... 'Units', 'pixels', ... 'Position', [0.06*width height*(n)+40 width*0.2 height*0.6], ... 'String', sprintf('%.3f', T6(1,3))); % AY uicontrol(fig, 'Style', 'text', ... 'Units', 'pixels', ... 'BackgroundColor', bgcol, ... 'Position', [0.26*width height*(n)+40 0.06*width height/2], ... 'BackgroundColor', 'yellow', ... 'FontSize', 10, ... 'HorizontalAlignment', 'right', ... 'String', labels(2)); handles.t6.r(2) = uicontrol(fig, 'Style', 'edit', ... 'Units', 'pixels', ... 'Position', [0.32*width height*(n)+40 width*0.2 height*0.6], ... 'String', sprintf('%.3f', T6(2,3))); % AZ uicontrol(fig, 'Style', 'text', ... 'Units', 'pixels', ... 'BackgroundColor', bgcol, ... 'Position', [0.52*width height*(n)+40 0.06*width height/2], ... 'BackgroundColor', 'yellow', ... 'FontSize', 10, ... 'HorizontalAlignment', 'right', ... 'String', labels(2)); handles.t6.r(3) = uicontrol(fig, 'Style', 'edit', ... 'Units', 'pixels', ... 'Position', [0.58*width height*(n)+40 width*0.2 height*0.6], ... 'String', sprintf('%.3f', T6(3,3))); % add buttons uicontrol(fig, 'Style', 'pushbutton', ... 'Units', 'pixels', ... 'FontSize', 16, ... 'Position', [0.8*width height*n+40 0.2*width 2*height], ... 'CallBack', @(src,event) delete(fig), ... 'BackgroundColor', 'red', ... 'String', 'Quit'); % the record button handles.record = []; uicontrol(fig, 'Style', 'pushbutton', ... 'Units', 'pixels', ... 'FontSize', 15, ... 'Position', [0.05*width 8 0.9*width 0.7*height], ... 'CallBack', @(src,event) record_callback(r, handles), ... 'BackgroundColor', 'blue', ... 'ForegroundColor', 'white', ... 'String', 'record'); % now assign the callbacks for i=1:n % text edit box set(handles.edit(i), ... 'Interruptible', 'off', ... 'Callback', @(src,event)teach_callback(src, r.name, i, handles)); % slider set(handles.slider(i), ... 'Interruptible', 'off', ... 'BusyAction', 'queue', ... 'Callback', @(src,event)teach_callback(src, r.name, i, handles)); % if findjobj exists use it, since it lets us get continous callbacks while % a slider moves if (exist('findjobj', 'file')>0) && verLessThan('matlab', '8') && ~ispc disp('using findjobj'); pause(0.1); drawnow jh = findjobj(handles.slider(i), 'nomenu'); %jh.AdjustmentValueChangedCallback = {@sliderCallbackFunc, r.name, i}; jh.AdjustmentValueChangedCallback = @(src,event)sliderCallbackFunc(src, handles.slider(i), r.name, i, handles); end end if isempty(rhandles) figure r.plot(q); end if nargout > 0 handle = fig; end end function teach_callback(src, name, j, handles) % called on changes to a slider or to the edit box showing joint coordinate % % src the object that caused the event % name name of the robot % j the joint index concerned (1..N) % slider true if the qscale = handles.qscale; switch get(src, 'Style') case 'slider' % slider changed, get value and reflect it to edit box newval = get(src, 'Value'); set(handles.edit(j), 'String', num2str(qscale(j)*newval)); case 'edit' % edit box changed, get value and reflect it to slider newval = str2double(get(src, 'String')) / qscale(j); set(handles.slider(j), 'Value', newval); end %fprintf('newval %d %f\n', j, newval); % find all graphical objects tagged with the robot name, this is the % instancs of that robot across all figures rhandles = findobj('Tag', name); for rhandle=rhandles' % for every graphical robot instance h = get(rhandle, 'UserData'); % get the robot object robot = h.robot; q = h.q; % get its current joint angles if isempty(q) q = zeros(1,robot.n); end q(j) = newval; % update the joint angle % update the robot object %robot.q = q; %set(r, 'UserData', robot); robot.plot(q); % plot it drawnow end % compute the robot tool pose T6 = robot.fkine(q); % convert orientation to desired format switch handles.orientation case 'approach' orient = T6(:,3); % approach vector case 'eul' orient = tr2eul(T6); case'rpy' orient = tr2rpy(T6); end % update the display in the teach window for i=1:3 set(handles.t6.t(i), 'String', sprintf('%.3f', T6(i,4))); set(handles.t6.r(i), 'String', sprintf('%.3f', orient(i))); end if ~isempty(handles.callback) handles.callback(q); end robot.T = T6; robot.notify('Moved'); %drawnow end function record_callback(robot, handles) rhandles = findobj('Tag', robot.name); if ~isempty(rhandles) h = get(rhandles(1), 'UserData'); % get the plot graphics h.q robot.record(h.q); end end function sliderCallbackFunc(src, h, name, joint, handles) persistent busy % this is really ugly but it works if busy return end if get(src,'ValueIsAdjusting') == 1 busy = true; try teach_callback(h, name, joint, handles); catch me fprintf('*******\n') busy = false; return end end busy = false; end