Added grbl planner Matlab simulator for test reference. Updated line number compile-time option.
- Added a grbl planner simulation tool that was written in Matlab and Python. It was used to visualize the inner workings of the planner as a program is streamed to it. The simulation assumes that the planner buffer is empty, then filled, and kept filled. This is mainly for users to see how the planner works. - Updated some of the compile-time ifdefs when enabling line numbers. The leaving the un-used line numbers in the function calls eats a non-neglible amount of flash memory. So the new if-defs remove them.
This commit is contained in:
		
							
								
								
									
										437
									
								
								doc/test/grbl_sim.m
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										437
									
								
								doc/test/grbl_sim.m
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,437 @@ | ||||
| % ---------------------------------------------------------------------------------------- | ||||
| % The MIT License (MIT) | ||||
| %  | ||||
| % Copyright (c) 2014 Sungeun K. Jeon | ||||
| %  | ||||
| % Permission is hereby granted, free of charge, to any person obtaining a copy | ||||
| % of this software and associated documentation files (the "Software"), to deal | ||||
| % in the Software without restriction, including without limitation the rights | ||||
| % to use, copy, modify, merge, publish, distribute, sublicense, and/or sell | ||||
| % copies of the Software, and to permit persons to whom the Software is | ||||
| % furnished to do so, subject to the following conditions: | ||||
| %  | ||||
| % The above copyright notice and this permission notice shall be included in | ||||
| % all copies or substantial portions of the Software. | ||||
| %  | ||||
| % THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | ||||
| % IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | ||||
| % FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE | ||||
| % AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER | ||||
| % LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, | ||||
| % OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN | ||||
| % THE SOFTWARE. | ||||
| % ---------------------------------------------------------------------------------------- | ||||
|  | ||||
| % This MATLAB script was written for the purpose of being a GRBL planner simulator. This | ||||
| % simulator is a rough representation of the actual workings of Grbl on the Arduino, but | ||||
| % was used to hone and proof the actual planner by providing quick visual feedback on its  | ||||
| % functionality when experimented on. This script should be considered for educational  | ||||
| % purposes only. This script requires and executes a pre-parsed g-code file from the  | ||||
| % matlab_convert.py script that is in a specific non-g-code format.  | ||||
|  | ||||
| % There will be two figures plotted. The first is the line motion paths of the complete | ||||
| % g-code program. The second is a representation of Grbl's planner buffer as new line | ||||
| % motions are fed to it, plotting the velocity profiles the stepper motors will execute. | ||||
| % Every time the user inputs an <Enter>, this feeds the simulator planner one line motion | ||||
| % block. The left side is the first block in the buffer and the one that will be executed | ||||
| % by the stepper module first. The right side is the end of the planner buffer, where the | ||||
| % most recent streamed block is appended onto the planner buffer. Grbl's planner  | ||||
| % optimizes the velocity profiles between the beginning and end of the buffer based on  | ||||
| % the acceleration limits, intended velocity/feedrate, and line motion junction angles | ||||
| % with their corresponding velocity limits (i.e. junctions with acute angles needs to come | ||||
| % to a complete stop vs straight junctions can continue through at full speed.) | ||||
|  | ||||
| % ---------------------------------------------------------------------------------------- | ||||
|  | ||||
|  | ||||
| % Main function  | ||||
| % NOTE: This is just a way to keep all functions in one place, but all non-global variables  | ||||
| %   are cleared as soon as this script completes. | ||||
| function main() | ||||
|  | ||||
| % Load pre-parsed gcode moves. | ||||
| close all; | ||||
| warning off; | ||||
| clearvars -global | ||||
| fid = fopen('matlab.gcode','r'); | ||||
| gcode = textscan(fid,'%d8%f32%f32%f32%f32'); | ||||
| nblock = length(gcode{1}); | ||||
|  | ||||
| % Plot all g-code moves. | ||||
| figure | ||||
| line(gcode{3},gcode{4},gcode{5}); | ||||
| axis equal; | ||||
| % axis([min(gcode{3}) max(gcode{3}) min(gcode{4}) max(gcode{4}) min(gcode{5}) max(gcode{5})]); | ||||
| title('G-code programming line motions'); | ||||
| view(3); | ||||
|  | ||||
| % Set up figure for planner queue | ||||
| figure | ||||
|  | ||||
| % Print help. | ||||
| disp('<NOTE: Press Enter to Advance One G-Code Line Motion>'); | ||||
| disp('   BLUE line indicates completed planner blocks that require no recalculation.'); | ||||
| disp('   RED line indicates planner blocks that have been recalculated.'); | ||||
| disp('   GREEN line indicates the location of the BPLANNED pointer. Always a recalculated block.'); | ||||
| disp('   BLACK dotted-line and ''x'' indicates block nominal speed and max junction velocity, respectively.'); | ||||
| disp('   CYAN ''.'' indicates block initial entry speed.'); | ||||
|  | ||||
| % Define Grbl settings. | ||||
| BUFFER_SIZE = 18; % Number of planner blocks in its ring buffer. | ||||
| steps_per_mm = 200; | ||||
| seekrate = 2500; % mm/min | ||||
| acceleration = [100 100 100]; % mm/sec^2 [ X Y Z ] axes | ||||
| junction_deviation = 0.1; % mm. See Grbl documentation on this parameter. | ||||
| inch_2_mm = 25.4; | ||||
| ACCELERATION_TICKS_PER_SECOND = 100; | ||||
|  | ||||
| gcode{2} = gcode{2}; | ||||
| gcode{2} = inch_2_mm*gcode{2}; | ||||
| gcode{3} = inch_2_mm*gcode{3}; | ||||
| gcode{4} = inch_2_mm*gcode{4}; | ||||
| gcode{5} = inch_2_mm*gcode{5}; | ||||
|  | ||||
| % Initialize blocks | ||||
| block.steps = []; | ||||
| block.step_event_count = []; | ||||
| block.delta_mm = []; | ||||
| block.millimeters = []; | ||||
| block.acceleration = []; | ||||
| block.speed = []; | ||||
| block.nominal_speed = []; | ||||
| block.max_entry_speed = []; | ||||
| block.entry_speed = []; | ||||
| block.recalculate_flag = false; | ||||
| for i = 2:BUFFER_SIZE | ||||
|   block(i) = block(1); | ||||
| end | ||||
|  | ||||
| % Initialize planner | ||||
| position = [0 0 0]; | ||||
| prev_unit_vec = [0 0 0]; | ||||
| previous_nominal_speed = 0; | ||||
| pos = 0; | ||||
|  | ||||
| % BHEAD and BTAIL act as pointers to the block head and tail.  | ||||
| % BPLANNED acts as a pointer of the location of the end of a completed/optimized plan. | ||||
| bhead = 1; | ||||
| btail = 1; | ||||
| bplanned = 1; | ||||
|  | ||||
| global block bhead btail bplanned nind acceleration BUFFER_SIZE pos ACCELERATION_TICKS_PER_SECOND | ||||
|  | ||||
| % Main loop. Simulates plan_buffer_line(). All of the precalculations for the newest incoming | ||||
| % block occurs here. Anything independent of the planner changes. | ||||
| for i = 1:nblock | ||||
|      | ||||
|   target = round([gcode{3}(i) gcode{4}(i) gcode{5}(i)].*steps_per_mm); | ||||
|   if gcode{1}(i) == 1 | ||||
|     feedrate = gcode{2}(i); | ||||
|   else | ||||
|     feedrate = seekrate; | ||||
|   end | ||||
|    | ||||
|   nind = next_block_index(bhead); | ||||
|   if nind == btail | ||||
|     % Simulate a constantly full buffer. Move buffer tail.  | ||||
|     bind = next_block_index(btail); | ||||
|     % Push planned pointer if encountered. Prevents it from looping back around the ring buffer.     | ||||
|     if btail == bplanned; bplanned = bind; end | ||||
|     btail = bind; | ||||
|   end | ||||
|  | ||||
|   block(bhead).steps = abs(target-position); | ||||
|   block(bhead).step_event_count = max(block(bhead).steps); | ||||
|  | ||||
|   % Bail if this is a zero-length block | ||||
|   if block(bhead).step_event_count == 0 | ||||
|     disp(['Zero-length block in line ',int2str(i)]); | ||||
|   else | ||||
|  | ||||
|     % Compute path vector in terms of absolute step target and current positions   | ||||
|     delta_mm = single((target-position)./steps_per_mm); | ||||
|     block(bhead).millimeters = single(norm(delta_mm)); | ||||
|     inverse_millimeters = single(1/block(bhead).millimeters); | ||||
|   | ||||
|     % Compute path unit vector                             | ||||
|     unit_vec = delta_mm/block(bhead).millimeters; | ||||
|   | ||||
|     % Calculate speed in mm/minute for each axis | ||||
|     inverse_minute = single(feedrate * inverse_millimeters); | ||||
|     block(bhead).speed = delta_mm*inverse_minute; | ||||
|     block(bhead).nominal_speed = block(bhead).millimeters*inverse_minute; | ||||
|      | ||||
|     % Calculate block acceleration. Operates on absolute value of unit vector. | ||||
|     [max_acc,ind] = max(abs(unit_vec)./acceleration); % Determine limiting acceleration | ||||
|     block(bhead).acceleration = acceleration(ind)/abs(unit_vec(ind));    | ||||
|      | ||||
|     % Compute maximum junction speed | ||||
|     block(bhead).max_entry_speed = 0.0; | ||||
|     if previous_nominal_speed > 0.0  | ||||
|       cos_theta = dot(-previous_unit_vec,unit_vec);                            | ||||
|       if (cos_theta < 0.95)  | ||||
|           block(bhead).max_entry_speed = min([block(bhead).nominal_speed,previous_nominal_speed]); | ||||
|         if (cos_theta > -0.95)  | ||||
|           sin_theta_d2 = sqrt(0.5*(1.0-cos_theta)); | ||||
|           block(bhead).max_entry_speed = min([block(bhead).max_entry_speed,sqrt(block(bhead).acceleration*3600*junction_deviation*sin_theta_d2/(1.0-sin_theta_d2))]); | ||||
|         end | ||||
|       end | ||||
|     end | ||||
|      | ||||
|     block(bhead).entry_speed = 0; % Just initialize. Set accurately in the replanning function.   | ||||
|     block(bhead).recalculate_flag = true; % Plotting flag to indicate this block has been updated. | ||||
|      | ||||
|     previous_unit_vec = unit_vec; | ||||
|     previous_nominal_speed = block(bhead).nominal_speed; | ||||
|     position = target; | ||||
|      | ||||
|     bhead = nind; % Block complete. Push buffer pointer. | ||||
|     planner_recalculate();    | ||||
|       | ||||
|     plot_buffer_velocities(); | ||||
|   end | ||||
| end | ||||
| return | ||||
|  | ||||
| % Computes the next block index in the planner ring buffer | ||||
| function block_index = next_block_index(block_index)  | ||||
| global BUFFER_SIZE | ||||
|   block_index = block_index + 1; | ||||
|   if block_index > BUFFER_SIZE | ||||
|     block_index = 1; | ||||
|   end | ||||
| return | ||||
|  | ||||
| % Computes the previous block index in the planner ring buffer | ||||
| function block_index = prev_block_index(block_index)  | ||||
| global BUFFER_SIZE | ||||
|   block_index = block_index-1; | ||||
|   if block_index < 1 | ||||
|     block_index = BUFFER_SIZE; | ||||
|   end | ||||
| return | ||||
|  | ||||
|  | ||||
| % Planner recalculate function. The magic happens here. | ||||
| function planner_recalculate(block)  | ||||
|  | ||||
|   global block bhead btail bplanned acceleration | ||||
|  | ||||
|   bind = prev_block_index(bhead); | ||||
|   if bind == bplanned; return; end  % Bail, if only one block in buffer. Can't be operated on. | ||||
|    | ||||
|   % Reverse Pass: Coarsely maximize all possible deceleration curves back-planning from the last | ||||
|   % block in buffer. Cease planning when the last optimal planned or tail pointer is reached. | ||||
|   % NOTE: Forward pass will later refine and correct the reverse pass to create an optimal plan. | ||||
|   next = []; | ||||
|   curr = bind;  % Last block in buffer. | ||||
|  | ||||
|   % Calculate maximum entry speed for last block in buffer, where the exit speed is always zero. | ||||
|   block(curr).entry_speed = min([block(curr).max_entry_speed,sqrt(2*block(curr).acceleration*60*60*block(curr).millimeters)]); | ||||
|  | ||||
|   bind = prev_block_index(bind); % Btail or second to last block | ||||
|   if (bind == bplanned) | ||||
|      % Only two plannable blocks in buffer. Reverse pass complete. | ||||
|      % Check if the first block is the tail. If so, notify stepper module to update its current parameters. | ||||
|      % if bind == btail; update_tail_block; end | ||||
|   else     | ||||
|     % Three or more plannable blocks in buffer. Loop it. | ||||
|     while bind ~= bplanned % Loop until bplanned point hits. Replans to last plan point. | ||||
|       next = curr;  | ||||
|       curr = bind; | ||||
|       bind = prev_block_index( bind ); % Previous block pointer.   | ||||
|              | ||||
|       % Check if the first block is the tail. If so, notify stepper module to update its current parameters. | ||||
|       % if bind == btail; update_tail_block; end | ||||
|                     | ||||
|       % Compute maximum entry speed decelerating over the current block from its exit speed. | ||||
|       if block(curr).entry_speed ~= block(curr).max_entry_speed   | ||||
|         block(curr).recalculate_flag = true; % Plotting flag to indicate this block has been updated. | ||||
|         block(curr).entry_speed = min([ block(curr).max_entry_speed,... | ||||
|                                         sqrt(block(next).entry_speed^2 + 2*block(curr).acceleration*60*60*block(curr).millimeters)]);                                         | ||||
|       end     | ||||
|  | ||||
|     end | ||||
|   end | ||||
|  | ||||
|   % For two blocks, reverse pass is skipped, but forward pass plans second block entry speed | ||||
|   % onward. This prevents the first, or the potentially executing block, from being over-written. | ||||
|   % NOTE: Can never be bhead, since bsafe is always in active buffer. | ||||
|   next = bplanned;   | ||||
|   bind = next_block_index(bplanned); % Start at bplanned | ||||
|   while bind ~= bhead | ||||
|     curr = next; | ||||
|     next = bind; | ||||
|      | ||||
|     % An acceleration block is always an optimally planned block since it starts from the first  | ||||
|     % block's current speed or a maximum junction speed. Compute accelerations from this block  | ||||
|     % and update the next block's entry speed. | ||||
|     if (block(curr).entry_speed < block(next).entry_speed) | ||||
|       % Once speed is set by forward planner, the plan for this block is finished and optimal. | ||||
|       % Increment the planner pointer forward one block.       | ||||
|          | ||||
|       entry_speed = sqrt(block(curr).entry_speed^2 + 2*block(curr).acceleration*60*60*block(curr).millimeters); | ||||
|       if (block(next).entry_speed > entry_speed) | ||||
|         block(next).entry_speed = entry_speed; | ||||
|         bplanned = bind; | ||||
|       end | ||||
|  | ||||
|     end | ||||
|      | ||||
|     % Check if the next block entry speed is at max_entry_speed. If so, move the planned pointer, since | ||||
|     % this entry speed cannot be improved anymore and all prior blocks have been completed and optimally planned. | ||||
|     if block(next).entry_speed == block(next).max_entry_speed | ||||
|       bplanned = bind; | ||||
|     end | ||||
|      | ||||
|     % Recalculate trapezoid can be installed here, since it scans through all of the plannable blocks. | ||||
|     % NOTE: Eventually this will only be computed when being executed. | ||||
|      | ||||
|     bind = next_block_index( bind ); | ||||
|  | ||||
|   end | ||||
|    | ||||
| return | ||||
|  | ||||
| % ---------------------------------------------------------------------------------------- | ||||
| % PLOTTING FUNCTIONS | ||||
|  | ||||
| % Plots the entire buffer plan into a MATLAB figure to visual the plan. | ||||
| %   BLUE line indicates completed planner blocks that require no recalculation. | ||||
| %   RED line indicates planner blocks that have been recalculated. | ||||
| %   GREEN line indicates the location of the BPLANNED pointer. Always a recalculated block. | ||||
| %   BLACK dotted-line and 'x' indicates block nominal speed and max junction velocity, respectively. | ||||
| %   CYAN '.' indicates block initial entry speed. | ||||
| function plot_buffer_velocities()  | ||||
|   global block bhead btail bplanned acceleration pos ACCELERATION_TICKS_PER_SECOND | ||||
|   bind = btail; | ||||
|   curr = []; | ||||
|   next = []; | ||||
|    | ||||
|   pos_initial = 0; | ||||
|   pos = 0; | ||||
|   while bind ~= bhead | ||||
|     curr = next; | ||||
|     next = bind; | ||||
|     hold on; | ||||
|     if ~isempty(curr)  | ||||
|       accel_d = estimate_acceleration_distance(block(curr).entry_speed, block(curr).nominal_speed, block(curr).acceleration*60*60); | ||||
|       decel_d = estimate_acceleration_distance(block(curr).nominal_speed, block(next).entry_speed,-block(curr).acceleration*60*60); | ||||
|       plateau_d = block(curr).millimeters-accel_d-decel_d; | ||||
|       if plateau_d < 0 | ||||
|         accel_d = intersection_distance(block(curr).entry_speed, block(next).entry_speed, block(curr).acceleration*60*60, block(curr).millimeters); | ||||
|         if accel_d < 0 | ||||
|           accel_d = 0; | ||||
|         elseif accel_d > block(curr).millimeters | ||||
|           accel_d = block(curr).millimeters; | ||||
|         end | ||||
|         plateau_d = 0; | ||||
|       end  | ||||
|       color = 'b';  | ||||
|       if (block(curr).recalculate_flag || block(next).recalculate_flag)  | ||||
|         block(curr).recalculate_flag = false; | ||||
|         color = 'r';  | ||||
|       end | ||||
|       if bplanned == curr | ||||
|         color = 'g';  | ||||
|       end | ||||
|        | ||||
|       plot_trap(pos,block(curr).entry_speed,block(next).entry_speed,block(curr).nominal_speed,block(curr).acceleration,accel_d,plateau_d,block(curr).millimeters,color) | ||||
|       plot([pos pos+block(curr).millimeters],block(curr).nominal_speed*[1 1],'k:') % BLACK dotted indicates | ||||
|       plot(pos,block(curr).max_entry_speed,'kx') | ||||
|  | ||||
|       pos = pos + block(curr).millimeters; | ||||
|       plot(pos,block(next).entry_speed,'c.'); | ||||
|     end | ||||
|     bind = next_block_index( bind ); | ||||
|   end | ||||
|  | ||||
|   accel_d = estimate_acceleration_distance(block(next).entry_speed, block(next).nominal_speed, block(next).acceleration*60*60); | ||||
|   decel_d = estimate_acceleration_distance(block(next).nominal_speed, 0, -block(next).acceleration*60*60); | ||||
|   plateau_d = block(next).millimeters-accel_d-decel_d; | ||||
|   if plateau_d < 0 | ||||
|      accel_d = intersection_distance(block(next).entry_speed, 0, block(next).acceleration*60*60, block(next).millimeters); | ||||
|      if accel_d < 0 | ||||
|        accel_d = 0; | ||||
|      elseif accel_d > block(next).millimeters | ||||
|        accel_d = block(next).millimeters; | ||||
|      end | ||||
|      plateau_d = 0; | ||||
|    end  | ||||
|    block(next).recalculate_flag = false; | ||||
|    color = 'r'; | ||||
|    if bplanned == next | ||||
|      color= 'g'; | ||||
|    end | ||||
|     | ||||
|   plot_trap(pos,block(next).entry_speed,0,block(next).nominal_speed,block(next).acceleration,accel_d,plateau_d,block(next).millimeters,color) | ||||
|   plot([pos pos+block(next).millimeters],block(next).nominal_speed*[1 1],'k:') | ||||
|   plot(pos,block(next).max_entry_speed,'kx') | ||||
|  | ||||
|   plot(pos,block(next).entry_speed,'.'); | ||||
|   pos = pos + block(next).millimeters; | ||||
|   plot(pos,0,'rx'); | ||||
|   xlabel('mm'); | ||||
|   ylabel('mm/sec'); | ||||
|   xlim([pos_initial pos]) | ||||
|   title('Planner buffer optimized velocity profile'); | ||||
|   pause(); | ||||
|   hold off; | ||||
|    | ||||
|   plot(pos,0) | ||||
| return | ||||
|  | ||||
|  | ||||
| function d_a = estimate_acceleration_distance(initial_rate, target_rate, acceleration,rate_delta) | ||||
|   d_a = (target_rate*target_rate-initial_rate*initial_rate)/(2*acceleration); | ||||
| return | ||||
|  | ||||
| function d_i = intersection_distance(initial_rate, final_rate, acceleration, distance, rate_delta) | ||||
|   d_i = (2*acceleration*distance-initial_rate*initial_rate+final_rate*final_rate)/(4*acceleration); | ||||
| return | ||||
|  | ||||
|  | ||||
| % Simply plots the ac/de-celeration curves and plateaus of a trapezoid. | ||||
| function plot_trap(pos,initial_rate,final_rate,rate,accel,accel_d,plateau_d,millimeters,color) | ||||
|  | ||||
|   dx = 1.0; % Line segment length | ||||
|   linex = [pos]; liney = [initial_rate]; | ||||
|  | ||||
|   % Acceleration | ||||
|   np = floor(accel_d/dx); | ||||
|   if np | ||||
|     v = initial_rate; | ||||
|     for i = 1:np | ||||
|       v = sqrt(v^2+2*accel*60*60*dx); | ||||
|       linex = [linex pos+i*dx]; | ||||
|       liney = [liney v]; | ||||
|     end | ||||
|   end | ||||
|    | ||||
|   % Plateau | ||||
|   v = sqrt(initial_rate^2 + 2*accel*60*60*accel_d); | ||||
|   if v < rate | ||||
|     rate = v; | ||||
|   end | ||||
|   linex = [linex pos+[accel_d accel_d+plateau_d]]; | ||||
|   liney = [liney [rate rate]]; | ||||
|    | ||||
|   % Deceleration | ||||
|   np = floor((millimeters-accel_d-plateau_d)/dx); | ||||
|   if np | ||||
|     v = rate; | ||||
|     for i = 1:np | ||||
|       v = sqrt(v^2-2*accel*60*60*dx); | ||||
|       linex = [linex pos+i*dx+accel_d+plateau_d]; | ||||
|       liney = [liney v]; | ||||
|     end | ||||
|   end         | ||||
|          | ||||
|   linex = [linex pos+millimeters]; | ||||
|   liney = [ liney final_rate]; | ||||
|   plot(linex,liney,color); | ||||
|  | ||||
| return | ||||
|  | ||||
|  | ||||
|  | ||||
							
								
								
									
										2362
									
								
								doc/test/matlab.gcode
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										2362
									
								
								doc/test/matlab.gcode
									
									
									
									
									
										Normal file
									
								
							
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							
							
								
								
									
										270
									
								
								doc/test/matlab_convert.py
									
									
									
									
									
										Executable file
									
								
							
							
						
						
									
										270
									
								
								doc/test/matlab_convert.py
									
									
									
									
									
										Executable file
									
								
							| @@ -0,0 +1,270 @@ | ||||
| #!/usr/bin/env python | ||||
| """\ | ||||
| The MIT License (MIT) | ||||
|  | ||||
| Copyright (c) 2014 Sungeun K. Jeon | ||||
|  | ||||
| Permission is hereby granted, free of charge, to any person obtaining a copy | ||||
| of this software and associated documentation files (the "Software"), to deal | ||||
| in the Software without restriction, including without limitation the rights | ||||
| to use, copy, modify, merge, publish, distribute, sublicense, and/or sell | ||||
| copies of the Software, and to permit persons to whom the Software is | ||||
| furnished to do so, subject to the following conditions: | ||||
|  | ||||
| The above copyright notice and this permission notice shall be included in | ||||
| all copies or substantial portions of the Software. | ||||
|  | ||||
| THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | ||||
| IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | ||||
| FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE | ||||
| AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER | ||||
| LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, | ||||
| OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN | ||||
| THE SOFTWARE. | ||||
| ---------------------------------------------------------------------------------------- | ||||
| """ | ||||
| """\ | ||||
| G-code preprocessor for the grbl_sim.m MATLAB script. Parses the g-code program to a | ||||
| specific file format for the MATLAB script to use. Based on PreGrbl by @chamnit. | ||||
|  | ||||
| How to use: When running this python script, it will process the g-code program under | ||||
| the filename "test.gcode" (may be changed below) and produces a file called "matlab.gcode" | ||||
| that the grbl_sim.m MATLAB script will search for and execute. | ||||
| """ | ||||
|  | ||||
| import re | ||||
| from math import * | ||||
| from copy import * | ||||
|  | ||||
| # -= SETTINGS =- | ||||
| filein = 'test.gcode'   # Input file name | ||||
| fileout = 'matlab.gcode'  # Output file name | ||||
| ndigits_in = 4 # inch significant digits after '.' | ||||
| ndigits_mm = 2 # mm significant digits after '.' | ||||
| # mm_per_arc_segment = 0.38 # mm per arc segment       | ||||
| arc_tolerance = 0.00005*25.4 | ||||
| n_arc_correction = 20 | ||||
| inch2mm = 25.4 # inch to mm conversion scalar | ||||
| verbose = False  # Verbose flag to show all progress | ||||
| remove_unsupported = True   # Removal flag for all unsupported statements | ||||
|  | ||||
| # Initialize parser state | ||||
| gc = { 'current_xyz' : [0,0,0],  | ||||
|        'feed_rate' : 0,         # F0 | ||||
|        'motion_mode' : 'SEEK',  # G00 | ||||
|        'plane_axis' : [0,1,2],  # G17 | ||||
|        'inches_mode' : False,   # G21 | ||||
|        'inverse_feedrate_mode' : False, # G94 | ||||
|        'absolute_mode' : True}  # G90 | ||||
|  | ||||
| def unit_conv(val) : # Converts value to mm | ||||
|     if gc['inches_mode'] : val *= inch2mm | ||||
|     return(val) | ||||
|  | ||||
| def fout_conv(val) : # Returns converted value as rounded string for output file. | ||||
|     if gc['inches_mode'] : return( str(round(val/inch2mm,ndigits_in)) ) | ||||
|     else : return( str(round(val,ndigits_mm)) ) | ||||
|  | ||||
| # Open g-code file | ||||
| fin = open(filein,'r'); | ||||
| fout = open(fileout,'w'); | ||||
|  | ||||
| # Iterate through g-code file | ||||
| l_count = 0 | ||||
| for line in fin: | ||||
|     l_count += 1 # Iterate line counter | ||||
|      | ||||
|     # Strip comments/spaces/tabs/new line and capitalize. Comment MSG not supported. | ||||
|     block = re.sub('\s|\(.*?\)','',line).upper()  | ||||
|     block = re.sub('\\\\','',block) # Strip \ block delete character | ||||
|     block = re.sub('%','',block) # Strip % program start/stop character | ||||
|      | ||||
|     if len(block) == 0 :  # Ignore empty blocks | ||||
|          | ||||
|         print "Skipping: " + line.strip() | ||||
|          | ||||
|     else :  # Process valid g-code clean block. Assumes no block delete characters or comments | ||||
|          | ||||
|         g_cmd = re.findall(r'[^0-9\.\-]+',block) # Extract block command characters | ||||
|         g_num = re.findall(r'[0-9\.\-]+',block) # Extract block numbers | ||||
|          | ||||
|         # G-code block error checks | ||||
|         # if len(g_cmd) != len(g_num) : print block; raise Exception('Invalid block. Unbalanced word and values.') | ||||
|         if 'N' in g_cmd : | ||||
|             if g_cmd[0]!='N': raise Exception('Line number must be first command in line.') | ||||
|             if g_cmd.count('N') > 1: raise Exception('More than one line number in block.') | ||||
|             g_cmd = g_cmd[1:]  # Remove line number word | ||||
|             g_num = g_num[1:] | ||||
|         # Block item repeat checks? (0<=n'M'<5, G/M modal groups) | ||||
|          | ||||
|         # Initialize block state | ||||
|         blk = { 'next_action' : 'DEFAULT', | ||||
|                 'absolute_override' : False, | ||||
|                 'target_xyz' : deepcopy(gc['current_xyz']), | ||||
|                 'offset_ijk' : [0,0,0], | ||||
|                 'radius_mode' : False,  | ||||
|                 'unsupported': [] } | ||||
|  | ||||
|         # Pass 1 | ||||
|         for cmd,num in zip(g_cmd,g_num) : | ||||
|             fnum = float(num) | ||||
|             inum = int(fnum) | ||||
|             if cmd is 'G' : | ||||
|                 if   inum is 0 : gc['motion_mode'] = 'SEEK' | ||||
|                 elif inum is 1 : gc['motion_mode'] = 'LINEAR' | ||||
|                 elif inum is 2 : gc['motion_mode'] = 'CW_ARC' | ||||
|                 elif inum is 3 : gc['motion_mode'] = 'CCW_ARC' | ||||
|                 elif inum is 4 : blk['next_action'] = 'DWELL' | ||||
|                 elif inum is 17 : gc['plane_axis'] = [0,1,2]    # Select XY Plane | ||||
|                 elif inum is 18 : gc['plane_axis'] = [0,2,1]    # Select XZ Plane | ||||
|                 elif inum is 19 : gc['plane_axis'] = [1,2,0]    # Select YZ Plane | ||||
|                 elif inum is 20 : gc['inches_mode'] = True       | ||||
|                 elif inum is 21 : gc['inches_mode'] = False | ||||
|                 elif inum in [28,30] : blk['next_action'] = 'GO_HOME' | ||||
|                 elif inum is 53 : blk['absolute_override'] = True | ||||
|                 elif inum is 54 : pass | ||||
|                 elif inum is 80 : gc['motion_mode'] = 'MOTION_CANCEL' | ||||
|                 elif inum is 90 : gc['absolute_mode'] = True | ||||
|                 elif inum is 91 : gc['absolute_mode'] = False | ||||
|                 elif inum is 92 : blk['next_action'] = 'SET_OFFSET' | ||||
|                 elif inum is 93 : gc['inverse_feedrate_mode'] = True | ||||
|                 elif inum is 94 : gc['inverse_feedrate_mode'] = False | ||||
|                 else :  | ||||
|                     print 'Unsupported command ' + cmd + num + ' on line ' + str(l_count)  | ||||
|                     if remove_unsupported : blk['unsupported'].append(zip(g_cmd,g_num).index((cmd,num))) | ||||
|             elif cmd is 'M' : | ||||
|                 if   inum in [0,1] : pass   # Program Pause | ||||
|                 elif inum in [2,30,60] : pass   # Program Completed | ||||
|                 elif inum is 3 : pass   # Spindle Direction 1 | ||||
|                 elif inum is 4 : pass   # Spindle Direction -1 | ||||
|                 elif inum is 5 : pass   # Spindle Direction 0 | ||||
|                 else :  | ||||
|                     print 'Unsupported command ' + cmd + num + ' on line ' + str(l_count)  | ||||
|                     if remove_unsupported : blk['unsupported'].append(zip(g_cmd,g_num).index((cmd,num))) | ||||
|             elif cmd is 'T' : pass      # Tool Number | ||||
|              | ||||
|         # Pass 2 | ||||
|         for cmd,num in zip(g_cmd,g_num) : | ||||
|             fnum = float(num)          | ||||
|             if   cmd is 'F' : gc['feed_rate'] = unit_conv(fnum)   # Feed Rate | ||||
|             elif cmd in ['I','J','K'] : blk['offset_ijk'][ord(cmd)-ord('I')] = unit_conv(fnum) # Arc Center Offset | ||||
|             elif cmd is 'N' : pass | ||||
|             elif cmd is 'P' : p = fnum  # Misc value parameter | ||||
|             elif cmd is 'R' : r = unit_conv(fnum); blk['radius_mode'] = True    # Arc Radius Mode | ||||
|             elif cmd is 'S' : pass      # Spindle Speed | ||||
|             elif cmd in ['X','Y','Z'] : # Target Coordinates | ||||
|                 if (gc['absolute_mode'] | blk['absolute_override']) : | ||||
|                     blk['target_xyz'][ord(cmd)-ord('X')] = unit_conv(fnum) | ||||
|                 else : | ||||
|                     blk['target_xyz'][ord(cmd)-ord('X')] += unit_conv(fnum) | ||||
|  | ||||
|         # Execute actions | ||||
|         if   blk['next_action'] is 'GO_HOME' :  | ||||
|             gc['current_xyz'] = deepcopy(blk['target_xyz']) # Update position       | ||||
|         elif blk['next_action'] is 'SET_OFFSET' :  | ||||
|             pass  | ||||
|         elif blk['next_action'] is 'DWELL' : | ||||
|             if p < 0 : raise Exception('Dwell time negative.') | ||||
|         else : # 'DEFAULT' | ||||
|             if gc['motion_mode'] is 'SEEK' :  | ||||
|                 fout.write('0 '+fout_conv(gc['feed_rate'])) | ||||
|                 fout.write(' '+fout_conv(blk['target_xyz'][0])) | ||||
|                 fout.write(' '+fout_conv(blk['target_xyz'][1])) | ||||
|                 fout.write(' '+fout_conv(blk['target_xyz'][2])) | ||||
|                 fout.write('\n')   | ||||
|                 gc['current_xyz'] = deepcopy(blk['target_xyz']) # Update position | ||||
|             elif gc['motion_mode'] is 'LINEAR' : | ||||
|                 fout.write('1 '+fout_conv(gc['feed_rate'])) | ||||
|                 fout.write(' '+fout_conv(blk['target_xyz'][0])) | ||||
|                 fout.write(' '+fout_conv(blk['target_xyz'][1])) | ||||
|                 fout.write(' '+fout_conv(blk['target_xyz'][2])) | ||||
|                 fout.write('\n')   | ||||
|                 gc['current_xyz'] = deepcopy(blk['target_xyz']) # Update position | ||||
|             elif gc['motion_mode'] in ['CW_ARC','CCW_ARC'] : | ||||
|                 axis = gc['plane_axis'] | ||||
|                  | ||||
|                 # Convert radius mode to ijk mode | ||||
|                 if blk['radius_mode'] : | ||||
|                     x = blk['target_xyz'][axis[0]]-gc['current_xyz'][axis[0]] | ||||
|                     y = blk['target_xyz'][axis[1]]-gc['current_xyz'][axis[1]] | ||||
|                     if not (x==0 and y==0) : raise Exception('Same target and current XYZ not allowed in arc radius mode.')  | ||||
|                     h_x2_div_d = -sqrt(4 * r*r - x*x - y*y)/hypot(x,y) | ||||
|                     if isnan(h_x2_div_d) : raise Exception('Floating point error in arc conversion') | ||||
|                     if gc['motion_mode'] is 'CCW_ARC' : h_x2_div_d = -h_x2_div_d | ||||
|                     if r < 0 : h_x2_div_d = -h_x2_div_d | ||||
|                     blk['offset_ijk'][axis[0]] = (x-(y*h_x2_div_d))/2; | ||||
|                     blk['offset_ijk'][axis[1]] = (y+(x*h_x2_div_d))/2; | ||||
|                 else : | ||||
|                     radius = sqrt(blk['offset_ijk'][axis[0]]**2+blk['offset_ijk'][axis[1]]**2) | ||||
|  | ||||
|                 center_axis0 = gc['current_xyz'][axis[0]]+blk['offset_ijk'][axis[0]] | ||||
|                 center_axis1 = gc['current_xyz'][axis[1]]+blk['offset_ijk'][axis[1]] | ||||
|                 linear_travel = blk['target_xyz'][axis[2]]-gc['current_xyz'][axis[2]] | ||||
|                 r_axis0 = -blk['offset_ijk'][axis[0]] | ||||
|                 r_axis1 = -blk['offset_ijk'][axis[1]] | ||||
|                 rt_axis0 = blk['target_xyz'][axis[0]] - center_axis0; | ||||
|                 rt_axis1 = blk['target_xyz'][axis[1]] - center_axis1; | ||||
|                 clockwise_sign = 1 | ||||
|                 if gc['motion_mode'] is 'CW_ARC' : clockwise_sign = -1 | ||||
|  | ||||
|                 angular_travel = atan2(r_axis0*rt_axis1-r_axis1*rt_axis0, r_axis0*rt_axis0+r_axis1*rt_axis1) | ||||
|                 if gc['motion_mode'] is 'CW_ARC' : | ||||
|                     if angular_travel >= 0 : | ||||
|                         angular_travel -= 2*pi | ||||
|                 else : | ||||
|                     if angular_travel <= 0 : | ||||
|                         angular_travel += 2*pi | ||||
|                 | ||||
|                 millimeters_of_travel = sqrt((angular_travel*radius)**2 + abs(linear_travel)**2) | ||||
|  | ||||
|                 mm_per_arc_segment = sqrt(4*(2*radius*arc_tolerance-arc_tolerance**2)) | ||||
|                 segments = int(millimeters_of_travel/mm_per_arc_segment) | ||||
|                 print segments | ||||
|                 print l_count | ||||
|                 theta_per_segment = angular_travel/segments | ||||
|                 linear_per_segment = linear_travel/segments | ||||
|                 cos_T = 1-0.5*theta_per_segment*theta_per_segment | ||||
|                 sin_T = theta_per_segment-theta_per_segment**3/6 | ||||
|                 print(fout_conv(mm_per_arc_segment))   | ||||
|                 print theta_per_segment*180/pi | ||||
|                                  | ||||
|                 arc_target = [0,0,0] | ||||
|                 arc_target[axis[2]] = gc['current_xyz'][axis[2]] | ||||
|  | ||||
|                 count = 0 | ||||
|                 for i in range(1,segments+1) : | ||||
|                     if i < segments : | ||||
|                         if count < n_arc_correction : | ||||
|                             r_axisi = r_axis0*sin_T + r_axis1*cos_T        | ||||
|                             r_axis0 = r_axis0*cos_T - r_axis1*sin_T | ||||
|                             r_axis1 = deepcopy(r_axisi) | ||||
|                             count += 1 | ||||
|                         else : | ||||
|                             cos_Ti = cos((i-1)*theta_per_segment) | ||||
|                             sin_Ti = sin((i-1)*theta_per_segment) | ||||
|                             print n_arc_correction*(r_axis0 -( -blk['offset_ijk'][axis[0]]*cos_Ti + blk['offset_ijk'][axis[1]]*sin_Ti)) | ||||
|                             print n_arc_correction*(r_axis1 -( -blk['offset_ijk'][axis[0]]*sin_Ti - blk['offset_ijk'][axis[1]]*cos_Ti)) | ||||
|                             cos_Ti = cos(i*theta_per_segment) | ||||
|                             sin_Ti = sin(i*theta_per_segment) | ||||
|                             r_axis0 = -blk['offset_ijk'][axis[0]]*cos_Ti + blk['offset_ijk'][axis[1]]*sin_Ti | ||||
|                             r_axis1 = -blk['offset_ijk'][axis[0]]*sin_Ti - blk['offset_ijk'][axis[1]]*cos_Ti | ||||
|                             count = 0 | ||||
|                         arc_target[axis[0]] = center_axis0 + r_axis0 | ||||
|                         arc_target[axis[1]] = center_axis1 + r_axis1 | ||||
|                         arc_target[axis[2]] += linear_per_segment | ||||
|                     else :  | ||||
|                         arc_target = deepcopy(blk['target_xyz']) # Last segment at target_xyz | ||||
|                     # Write only changed variables.  | ||||
|                     fout.write('1 '+fout_conv(gc['feed_rate'])) | ||||
|                     fout.write(' '+fout_conv(arc_target[0])) | ||||
|                     fout.write(' '+fout_conv(arc_target[1])) | ||||
|                     fout.write(' '+fout_conv(arc_target[2])) | ||||
|                     fout.write('\n')             | ||||
|                     gc['current_xyz'] = deepcopy(arc_target) # Update position | ||||
|  | ||||
|  | ||||
| print 'Done!' | ||||
|  | ||||
| # Close files | ||||
| fin.close() | ||||
| fout.close() | ||||
							
								
								
									
										2363
									
								
								doc/test/test.gcode
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										2363
									
								
								doc/test/test.gcode
									
									
									
									
									
										Normal file
									
								
							
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							
		Reference in New Issue
	
	Block a user