gcode 0.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,29 @@
1
+ # Gcode
2
+
3
+ TODO: Write a gem description
4
+
5
+ ## Installation
6
+
7
+ Add this line to your application's Gemfile:
8
+
9
+ gem 'gcode'
10
+
11
+ And then execute:
12
+
13
+ $ bundle
14
+
15
+ Or install it yourself as:
16
+
17
+ $ gem install gcode
18
+
19
+ ## Usage
20
+
21
+ TODO: Write usage instructions here
22
+
23
+ ## Contributing
24
+
25
+ 1. Fork it
26
+ 2. Create your feature branch (`git checkout -b my-new-feature`)
27
+ 3. Commit your changes (`git commit -am 'Add some feature'`)
28
+ 4. Push to the branch (`git push origin my-new-feature`)
29
+ 5. Create new Pull Request
@@ -0,0 +1 @@
1
+ require 'bundler/gem_tasks'
@@ -0,0 +1,24 @@
1
+ # coding: utf-8
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'gcode/version'
5
+
6
+ Gem::Specification.new do |gem|
7
+ gem.name = 'gcode'
8
+ gem.version = Gcode::VERSION
9
+ gem.authors = ['Kaz Walker']
10
+ gem.email = ['kaz@printtopeer.com']
11
+ gem.description = %q{A basic Gcode parser and modifier.}
12
+ gem.summary = %q{This gem includes classes to evaluate, analyze and do simple modifications to RepRap flavoured Gcode and some MakerBot flavoured Gcode.}
13
+ gem.homepage = 'https://github.com/PrintToPeer/gcode'
14
+ gem.license = 'GPLv3'
15
+
16
+ gem.files = `git ls-files`.split($/)
17
+ gem.executables = gem.files.grep(%r{^bin/}) { |f| File.basename(f) }
18
+ gem.test_files = gem.files.grep(%r{^(test|spec|features)/})
19
+ gem.require_paths = ['lib']
20
+
21
+ gem.add_development_dependency 'bundler', '~> 1.3'
22
+ gem.add_development_dependency 'rake'
23
+ gem.add_development_dependency 'yard'
24
+ end
@@ -0,0 +1,8 @@
1
+ require 'gcode/version'
2
+ require 'gcode/codes'
3
+ require 'gcode/object'
4
+ require 'gcode/line'
5
+
6
+ module Gcode
7
+ # Your code goes here...
8
+ end
@@ -0,0 +1,90 @@
1
+ module Gcode
2
+ # Contains GCodes.
3
+ module Codes
4
+ # Do a rapid move.
5
+ RAPID_MOVE = 'G0'
6
+ # Do a move at the given or previously given acceleration (F).
7
+ CONTROLLED_MOVE = 'G1'
8
+ # Pause for (P) a given number of milliseconds.
9
+ DWELL = 'G4'
10
+ # Set head offset (for multiple extruders).
11
+ HEAD_OFFSET = 'G10'
12
+ # Set units in following commands to be imperial.
13
+ USE_INCHES = 'G20'
14
+ # Set units in following commands to be metric (default).
15
+ USE_MILLIMETRES = 'G21'
16
+ # Home axes.
17
+ HOME = 'G28'
18
+ # Set following commands to use absolute coordinates.
19
+ ABS_POSITIONING = 'G90'
20
+ # Set following commands to use relative coordinates.
21
+ REL_POSITIONING = 'G91'
22
+ # Set current position.
23
+ SET_POSITION = 'G92'
24
+ # Finish moves, then shutdown (reset required to wake machine).
25
+ STOP = 'M0'
26
+ # Finish moves the shutdown (sending commands will wake machine).
27
+ SLEEP = 'M1'
28
+ # Enable motors.
29
+ ENABLE_MOTORS = 'M17'
30
+ # Disable motors.
31
+ DISABLE_MOTORS = 'M18'
32
+ # List contents of SD card.
33
+ LIST_SD = 'M20'
34
+ # Initialize SD card (needed if card wasn't present at bootup).
35
+ INIT_SD = 'M21'
36
+ # Release SD (safe removal of SD).
37
+ RELEASE_SD = 'M22'
38
+ # Select SD file (require to print from SD).
39
+ SELECT_SD_FILE = 'M23'
40
+ # Print selected file from SD (requires file to be selected).
41
+ START_SD_PRINT = 'M24'
42
+ # Pause printing from SD card.
43
+ PAUSE_SD_PRINT = 'M25'
44
+ # Set SD position in bytes.
45
+ SET_SD_POSITION = 'M26'
46
+ # Report SD printing status.
47
+ SD_PRINT_STATUS = 'M27'
48
+ # Write following GCodes to given file (requires 8.3 file name).
49
+ START_SD_WRITE = 'M28'
50
+ # Signal end of SD write, following commands will be executed as normal.
51
+ STOP_SD_WRITE = 'M29'
52
+ # Power on.
53
+ POWER_ON = 'M80'
54
+ # Power off.
55
+ POWER_OFF = 'M81'
56
+ # Set extrusion units in following commands to absolute coordinates.
57
+ ABS_EXT_MODE = 'M82'
58
+ # Set extrusion units in following commands to relative coordinates.
59
+ REL_EXT_MODE = 'M83'
60
+ # Trun off powered holding of motors when idle.
61
+ IDLE_HOLD_OFF = 'M84'
62
+ # Set Extruder tmeperature and return control to host.
63
+ SET_EXT_TEMP_NW = 'M104'
64
+ # Report temperatures
65
+ GET_EXT_TEMP = 'M105'
66
+ # Trun fans on to given value (S, 0-255).
67
+ FAN_ON = 'M106'
68
+ # Turn off fans
69
+ FAN_OFF = 'M107'
70
+ # Set extruder temperature and wait for it to reach temperature.
71
+ SET_EXT_TEMP_W = 'M109'
72
+ # Reset the line number for the following commands.
73
+ SET_LINE_NUM = 'M110'
74
+ # Emergency stop.
75
+ EMRG_STOP = 'M112'
76
+ # Report position.
77
+ GET_POSITION = 'M114'
78
+ # Report firmware details.
79
+ GET_FW_DETAILS = 'M115'
80
+ # Wait for temperature (all extruders and bed) to reach the temerature they were set to.
81
+ WIAT_FOR_TEMP = 'M116'
82
+ # Set bed temperature and return control to host.
83
+ SET_BED_TEMP_NW = 'M140'
84
+ # Set bed temperature and wait for it to reach temperature.
85
+ SET_BED_TEMP_W = 'M190'
86
+ # Comment symbol
87
+ # @todo Move this to a configurable option.
88
+ COMMENT_SYMBOL = ';'
89
+ end
90
+ end
@@ -0,0 +1,268 @@
1
+ require 'gcode/codes'
2
+
3
+ module Gcode
4
+ # Represents a single line in a GCode file, parse expression tester: {http://rubular.com/}
5
+ class Line
6
+ include Codes
7
+
8
+ # @!macro attr_accessor
9
+ # @!attribute [rw] $1
10
+ # @param speed_multiplier [Float] number speed (F) will be multiplied by.
11
+ # @return [nil] if the speed multiplier is not set.
12
+ # @return [Float] the speed multiplier (print moves only).
13
+ # @!attribute [rw] $2
14
+ # @param extrusion_multiplier [Float] number extrusions (E) will be multiplied by.
15
+ # @return [nil] if the extrusion multiplier is not set.
16
+ # @return [Float] the extrusion multiplier.
17
+ # @!attribute [rw] $3
18
+ # @param travel_multiplier [Float] number travel move speeds (F) will be multiplied by.
19
+ # @return [nil] if the travel multiplier is not set.
20
+ # @return [Float] the travel multiplier.
21
+ # @!attribute [rw] $4
22
+ # @param tool_number [Fixnum] the tool used in the command.
23
+ # @return [Fixnum] the tool used in the command.
24
+ # @!attribute [rw] $5
25
+ # @param f [Float] speed of the command (in mm/minute).
26
+ # @return [Float] the speed of the command (in mm/minute).
27
+ attr_accessor :speed_multiplier, :extrusion_multiplier,
28
+ :travel_multiplier, :tool_number, :f, :x_add,
29
+ :y_add, :z_add
30
+
31
+ # @!macro attr_reader
32
+ # @!attribute [r] $1
33
+ # @return [String] the line, upcased and stripped of whitespace.
34
+ # @!attribute [r] $2
35
+ # @return [nil] if the line wasn't valid GCode.
36
+ # @return [MatchData] the raw matches from the regular expression evaluation.
37
+ attr_reader :raw, :matches
38
+
39
+ # GCode matching pattern
40
+ GCODE_PATTERN = /^(?<line>(?<command>((?<command_letter>[G|M|T])(?<command_number>\d{1,3}))) ?(?<regular_data>([S](?<s_data>\d*))? ?([P](?<p_data>\d*))? ?([X](?<x_data>[-]?\d+\.?\d*))? ?([Y](?<y_data>[-]?\d+\.?\d*))? ?([Z](?<z_data>[-]?\d+\.?\d*))? ?([F](?<f_data>\d+\.?\d*))? ?([E|A](?<e_data>[-]?\d+\.?\d*))?)? ?(?<string_data>[^;]*)?)? ?;?(?<comment>.*)?$/
41
+
42
+ # Creates a {Line}
43
+ # @param line [String] a line of GCode.
44
+ # @return [false] if line is empty or doesn't match the evaluation expression.
45
+ # @return [Line]
46
+ def initialize(line)
47
+ return false if line.nil? || line.empty?
48
+ @raw = line
49
+ @matches = @raw.match(GCODE_PATTERN)
50
+ return false if @matches.nil?
51
+ # assign_values
52
+ @f = @matches[:f_data].to_f unless @matches[:f_data].nil?
53
+ @tool_number = command_number if !command_letter.nil? && command_letter == 'T'
54
+ end
55
+
56
+ # Checks if the given line is more than just a comment.
57
+ # @return [Boolean] true if empty/invalid
58
+ def empty?
59
+ command.nil?
60
+ end
61
+
62
+ # Checks if the command in the line causes movement.
63
+ # @return [Boolean] true if command moves printer, false otherwise.
64
+ def is_move?
65
+ command == RAPID_MOVE || command == CONTROLLED_MOVE
66
+ end
67
+
68
+ # Checks whether the line is a travel move or not.
69
+ # @return [Boolean] true if line is a travel move, false otherwise.
70
+ def travel_move?
71
+ is_move? && e.nil?
72
+ end
73
+
74
+ # Checks whether the line is as extrusion move or not.
75
+ # @return [Boolean] true if line is an extrusion move, false otherwise.
76
+ def extrusion_move?
77
+ is_move? && !e.nil? && e > 0
78
+ end
79
+
80
+ # Checks wether the line is a full home or not.
81
+ # @return [Boolean] true if line is full home, false otherwise.
82
+ def full_home?
83
+ command == HOME && !x.nil? && !y.nil? && !z.nil?
84
+ end
85
+
86
+ # Returns the line, modified if multipliers are set and a line number is given.
87
+ # @return [String] the line.
88
+ def to_s(line_number = nil)
89
+ # return line if line_number.nil? || !line_number.is_a?(Fixnum)
90
+ # return prefix_line(line, line_number) if @extrusion_multiplier.nil? && @speed_multiplier.nil?
91
+
92
+ new_f = multiplied_speed
93
+ new_e = multiplied_extrusion
94
+
95
+ x_string = !x.nil? ? " X#{x+@x_add.to_f}" : ''
96
+ y_string = !y.nil? ? " Y#{y+@y_add.to_f}" : ''
97
+ z_string = !z.nil? ? " Z#{z+@z_add.to_f}" : ''
98
+ e_string = !e.nil? ? " E#{new_e}" : ''
99
+ f_string = !f.nil? ? " F#{new_f}" : ''
100
+ p_string = !p.nil? ? " P#{p}" : ""
101
+ s_string = !s.nil? ? " S#{s}" : ""
102
+ string = !string_data.nil? ? " #{string_data}" : ''
103
+
104
+ prefix_line("#{command}#{s_string}#{p_string}#{x_string}#{y_string}#{z_string}#{f_string}#{e_string}#{string}".strip, line_number)
105
+ end
106
+
107
+ ## Line value functions
108
+
109
+ # Striped version of the input GCode, or nil if not valid GCode
110
+ # @return [String] striped line of GCode.
111
+ # @return [nil] if no GCode was present .
112
+ def line
113
+ if @line.nil? && !@matches[:line].nil?
114
+ @line = @matches[:line].strip
115
+ else
116
+ @line
117
+ end
118
+ end
119
+
120
+ # The command in the line, nil if no command is present.
121
+ # @return [String] command in the line.
122
+ # @return [nil] if no command is present.
123
+ def command
124
+ @matches[:command]
125
+ end
126
+
127
+ # The command letter of the line, nil if no command is present.
128
+ # @return [String] command letter of the line.
129
+ # @return [nil] if no command is present.
130
+ def command_letter
131
+ @matches[:command_letter]
132
+ end
133
+
134
+ # The command number of the line, nil if no command is present.
135
+ # @return [Fixnum] command number of the line.
136
+ # @return [nil] if no command is present.
137
+ def command_number
138
+ if @command_number.nil? && !@matches[:command_number].nil?
139
+ @command_number = @matches[:command_number].to_i
140
+ else
141
+ @command_number
142
+ end
143
+ end
144
+
145
+ # The X value of the line, nil if no X value is present.
146
+ # @return [Float] X value of the line.
147
+ # @return [nil] if no X value is present.
148
+ def x
149
+ if @x.nil? && !@matches[:x_data].nil?
150
+ @x = @matches[:x_data].to_f
151
+ else
152
+ @x
153
+ end
154
+ end
155
+
156
+ # The Y value of the line, nil if no Y value is present.
157
+ # @return [Float] Y value of the line.
158
+ # @return [nil] if no Y value is present.
159
+ def y
160
+ if @y.nil? && !@matches[:y_data].nil?
161
+ @y = @matches[:y_data].to_f
162
+ else
163
+ @y
164
+ end
165
+ end
166
+
167
+ # The Z value of the line, nil if no Z value is present.
168
+ # @return [Float] Z value of the line.
169
+ # @return [nil] if no Z value is present.
170
+ def z
171
+ if @z.nil? && !@matches[:z_data].nil?
172
+ @z = @matches[:z_data].to_f
173
+ else
174
+ @z
175
+ end
176
+ end
177
+
178
+ # The E value of the line, nil if no E value is present.
179
+ # @return [Float] E value of the line.
180
+ # @return [nil] if no E value is present.
181
+ def e
182
+ if @e.nil? && !@matches[:e_data].nil?
183
+ @e = @matches[:e_data].to_f
184
+ else
185
+ @e
186
+ end
187
+ end
188
+
189
+ # The S value of the line, nil if no S value is present.
190
+ # @return [Fixnum] S value of the line.
191
+ # @return [nil] if no S value is present.
192
+ def s
193
+ if @s.nil? && !@matches[:s_data].nil?
194
+ @s = @matches[:s_data].to_i
195
+ else
196
+ @s
197
+ end
198
+ end
199
+
200
+ # The P value of the line, nil if no P value is present.
201
+ # @return [Fixnum] P value of the line.
202
+ # @return [nil] if no P value is present
203
+ def p
204
+ if @p.nil? && !@matches[:p_data].nil?
205
+ @p = @matches[:p_data].to_i
206
+ else
207
+ @p
208
+ end
209
+ end
210
+
211
+ # The string data of the line, nil if no string data is present.
212
+ # @return [String] string data of the line.
213
+ # @return [nil] if no string data is present
214
+ def string_data
215
+ if @string_data.nil? && (!@matches[:string_data].nil? || !@matches[:string_data].empty?)
216
+ @string_data = @matches[:string_data].strip
217
+ else
218
+ @string_data
219
+ end
220
+ end
221
+
222
+ # The comment of the line, nil if no comment is present.
223
+ # @return [String] comment of the line.
224
+ # @return [nil] if no comment is present
225
+ def comment
226
+ if @comment.nil? && !@matches[:comment].nil?
227
+ @comment ||= @matches[:comment].strip
228
+ else
229
+ @comment
230
+ end
231
+ end
232
+
233
+ private
234
+
235
+ def multiplied_extrusion
236
+ if !e.nil? && valid_multiplier?(@extrusion_multiplier)
237
+ return e * @extrusion_multiplier
238
+ else
239
+ e
240
+ end
241
+ end
242
+
243
+ def multiplied_speed
244
+ if travel_move? && valid_multiplier?(@travel_multiplier)
245
+ return f * @travel_multiplier
246
+ elsif extrusion_move? && valid_multiplier?(@speed_multiplier)
247
+ return f * @speed_multiplier
248
+ else
249
+ return f
250
+ end
251
+ end
252
+
253
+ def valid_multiplier?(multiplier)
254
+ !multiplier.nil? && (multiplier.class == Fixnum || multiplier.class == Float) && multiplier > 0
255
+ end
256
+
257
+ def get_checksum(command)
258
+ command.bytes.inject{|a,b| a^b}.to_s
259
+ end
260
+
261
+ def prefix_line(command, line_number)
262
+ return command if line_number.nil?
263
+ prefix = 'N' + line_number.to_s + ' ' + command
264
+ (prefix+'*'+get_checksum(prefix))
265
+ end
266
+
267
+ end
268
+ end
@@ -0,0 +1,404 @@
1
+ require 'gcode/codes'
2
+ require 'gcode/line'
3
+ require 'gcode/pretty_output'
4
+
5
+ module Gcode
6
+ # A class that represents a processed Gcode file.
7
+ class Object
8
+ include Codes
9
+ include PrettyOutput
10
+
11
+ # An array of the raw Gcode with each line as an element.
12
+ # @return [Array] of raw Gcode without the comments stripped out.
13
+ attr_accessor :raw_data
14
+ # @!macro attr_reader
15
+ # @!attribute [r] $1
16
+ # @return [Array<Line>] an array of {Line}s.
17
+ # @!attribute [r] $2
18
+ # @return [Float] the smallest X coordinate of an extrusion line.
19
+ # @!attribute [r] $3
20
+ # @return [Float] the biggest X coordinate of an extrusion line.
21
+ # @!attribute [r] $4
22
+ # @return [Float] the smallest Y coordinate of an extrusion line.
23
+ # @!attribute [r] $5
24
+ # @return [Float] the biggest Y coordinate of an extrusion line.
25
+ # @!attribute [r] $6
26
+ # @return [Float] the smallest Z coordinate.
27
+ # @!attribute [r] $7
28
+ # @return [Float] the biggest Z coordinate.
29
+ # @!attribute [r] $8
30
+ # @return [Array<Float>] the amount in mm of fliament extruded with the index representing the extruder.
31
+ # @!attribute [r] $9
32
+ # @return [Float] the distance in total that the X axis will travel in mm.
33
+ # @!attribute [r] $10
34
+ # @return [Float] the distance in total that the Y axis will travel in mm.
35
+ # @!attribute [r] $11
36
+ # @return [Float] the distance in total that the Z axis will travel in mm.
37
+ # @!attribute [r] $12
38
+ # @return [Float] the distance in total that the E axis will travel in mm.
39
+ # @todo implement this
40
+ # @!attribute [r] $13
41
+ # @return [Float] the width of the print.
42
+ # @!attribute [r] $14
43
+ # @return [Float] the depth of the print.
44
+ # @!attribute [r] $15
45
+ # @return [Float] the height of the print.
46
+ # @!attribute [r] $16
47
+ # @return [Fixnum] the number of layers in the print.
48
+ # @!attribute [r] $17
49
+ # @return [Float] the estimated durration of the print in seconds.
50
+ # @!attribute [r] $18
51
+ # @return [Array] of the lines that only contained comments found in the file.
52
+ # @!attribute [r] $19
53
+ # @return [Array<Hash<Fixnum>>] ranges of commands with their respective layer represented by the array index.
54
+ attr_reader :lines, :x_min, :x_max, :y_min, :y_max, :z_min, :z_max,
55
+ :filament_used, :x_travel, :y_travel, :z_travel, :e_travel,
56
+ :width, :depth, :height, :layers, :total_duration, :comments,
57
+ :layer_ranges
58
+
59
+ # Creates a GCode {Object}.
60
+ # @param data [String] path to a GCode file on the system.
61
+ # @param data [Array] with each element being a line of GCode.
62
+ # @param auto_process [Boolean] enable/disable auto processing.
63
+ # @param default_speed [Float] the default speed (in mm/minute) for moves that don't have one declared.
64
+ # @param acceleration [Float] the acceleration rate set in the printer' firmware.
65
+ # @return [Object] if data is valid, returns a GCode {Object}.
66
+ # @return [false] if data is not an array, path, didn't contain GCode or default_speed wasn't a number grater than 0.
67
+ def initialize(options = {})
68
+ if options.is_a?(Array)
69
+ temp_data = options
70
+ options = {}
71
+ options[:data] = temp_data
72
+ temp_data = nil
73
+ end
74
+ options = default_options.merge!(options)
75
+ return false unless positive_number?(options[:default_speed])
76
+ return false unless positive_number?(options[:acceleration])
77
+ if options[:data].class == String && self.class.is_file?(options[:data])
78
+ options[:data] = self.class.get_file(options[:data])
79
+ end
80
+ return false if options[:data].nil? || options[:data].class != Array
81
+ @options = options
82
+ set_variables
83
+ @raw_data.each do |line|
84
+ line = set_line_properties(Line.new(line))
85
+ if line
86
+ unless line.empty?
87
+ @lines << line
88
+ else
89
+ @comments << line.comment
90
+ end
91
+ end
92
+ end
93
+ process if options[:auto_process]
94
+ return false if empty?
95
+ end
96
+
97
+ # Checks if the given string is a file and if it exists.
98
+ # @param file [String] path to a file on the system.
99
+ # @return [Boolean] true if is a file that exists on the system, false otherwise.
100
+ def self.is_file?(file)
101
+ !file.nil? && !file.empty? && File.exist?(file) && File.file?(file)
102
+ end
103
+
104
+ # Returns an array of the lines of the file if it exists.
105
+ # @param file [String] path to a file on the system.
106
+ # @return [Array] containting the lines of the given file as elements.
107
+ # @return [false] if given string isn't a file or doesn't exist.
108
+ def self.get_file(file)
109
+ return false unless self.is_file?(file)
110
+ IO.readlines(file)
111
+ end
112
+
113
+ # alias for {#empty?}.
114
+ # @see #empty?
115
+ def blank?
116
+ empty?
117
+ end
118
+
119
+ # Checks if there are any {Line}s in {#lines}.
120
+ # @return [Boolean] true if no lines, false otherwise.
121
+ def empty?
122
+ @lines.empty?
123
+ end
124
+
125
+ # Opposite of {#empty?}.
126
+ # @see #empty?
127
+ def present?
128
+ !empty?
129
+ end
130
+
131
+ # Checks if the GCode object contains multiple materials.
132
+ # @return [nil] if processing hasn't been done.
133
+ # @return [Boolean] true if multiple extruders used, false otherwise.
134
+ def multi_material?
135
+ return nil unless @width
136
+ @filament_used.length > 1
137
+ end
138
+
139
+ # Returns estimated durration of the print in a human readable format.
140
+ # @return [String] human readable estimated durration of the print.
141
+ def durration_in_words
142
+ seconds_to_words(@total_duration)
143
+ end
144
+
145
+ # Get the layer number the given command number is in.
146
+ # @param command_number [Fixnum] number of the command who's layer number you'd lke to know.
147
+ # @return [Fixnum] layer number for the given command number.
148
+ # @return [nil] if the given command number is invalid or if the object wasn't processed.
149
+ def in_what_layer?(command_number)
150
+ return nil if @width.nil? || !command_number.is_a?(Fixnum) || command_number < 0 || command_number > @lines.length
151
+ layer = 1
152
+ @layers.times do
153
+ return layer if (@layer_ranges[layer][:lower]..@layer_ranges[layer][:upper]).include?(command_number)
154
+ layer += 1
155
+ end
156
+ nil
157
+ end
158
+
159
+ def write_to_file(output_file, encoding = "us-ascii")
160
+ begin
161
+ fd = File.open(output_file, 'w+')
162
+ @lines.each do |line|
163
+ fd.write (line.to_s+"; #{line.comment.to_s}"+"\n").encode(encoding)
164
+ end
165
+ ensure
166
+ fd.close
167
+ end
168
+ end
169
+
170
+ private
171
+
172
+ def default_options
173
+ {default_speed: 2400, auto_process: true, acceleration: 1500, add_speed: false}
174
+ end
175
+
176
+ def process
177
+ set_processing_variables
178
+
179
+ @lines.each do |line|
180
+ case line.command
181
+ when USE_INCHES
182
+ @imperial = true
183
+ when USE_MILLIMETRES
184
+ @imperial = false
185
+ when ABS_POSITIONING
186
+ @relative = false
187
+ when REL_POSITIONING
188
+ @relative = true
189
+ when ABS_EXT_MODE
190
+ @relative_extrusion = false
191
+ when REL_EXT_MODE
192
+ @relative_extrusion = true
193
+ when SET_POSITION
194
+ @set_position_called = true
195
+ set_positions(line)
196
+ when HOME
197
+ home_axes(line)
198
+ when RAPID_MOVE
199
+ movement_line(line)
200
+ when CONTROLLED_MOVE
201
+ count_layers(line)
202
+ movement_line(line)
203
+ calculate_time(line)
204
+ when DWELL
205
+ @total_duration += line.p/1000 unless line.p.nil?
206
+ end
207
+ @current_line += 1
208
+ end
209
+ @layer_ranges[@layers][:upper] = @current_line
210
+ set_dimensions
211
+ end
212
+
213
+ def set_dimensions
214
+ @width = @x_max - @x_min
215
+ @depth = @y_max - @y_min
216
+ @height = @z_max - @z_min
217
+ end
218
+
219
+ def calculate_time(line)
220
+ line.f.nil? ? @speed_per_second = @last_speed_per_second : @speed_per_second = line.f / 60
221
+ current_travel = hypot3d(@current_x, @current_y, @current_z, @last_x, @last_y, @last_z)
222
+ distance = (2*((@last_speed_per_second+@speed_per_second)*(@speed_per_second-@last_speed_per_second)*0.5)/@acceleration).abs
223
+ if distance <= current_travel && !(@last_speed_per_second+@speed_per_second).zero? && !@speed_per_second.zero?
224
+ move_duration = (2*distance/(@last_speed_per_second+@speed_per_second))+((current_travel-distance)/@speed_per_second)
225
+ else
226
+ move_duration = Math.sqrt(2*distance/@acceleration)
227
+ end
228
+ @total_duration += move_duration
229
+ end
230
+
231
+ def count_layers(line)
232
+ if !line.z.nil? && line.z > @current_z
233
+ @layer_ranges[@layers][:upper] = @current_line
234
+ @layers += 1
235
+ @layer_ranges[@layers] = {}
236
+ @layer_ranges[@layers][:lower] = @current_line
237
+ end
238
+ end
239
+
240
+ def hypot3d(x1, y1, z1, x2 = 0.0, y2 = 0.0, z2 = 0.0)
241
+ Math.hypot(x2-x1, Math.hypot(y2-y1, z2-z1))
242
+ end
243
+
244
+ def movement_line(line)
245
+ measure_travel(line)
246
+ set_last_values
247
+ set_current_position(line)
248
+ calculate_filament_usage(line)
249
+ set_limits(line)
250
+ end
251
+
252
+ def measure_travel(line)
253
+ if @relative
254
+ @x_travel += to_mm(line.x).abs unless line.x.nil?
255
+ @y_travel += to_mm(line.y).abs unless line.y.nil?
256
+ @z_travel += to_mm(line.z).abs unless line.z.nil?
257
+ else
258
+ @x_travel += (@current_x - to_mm(line.x)).abs unless line.x.nil?
259
+ @y_travel += (@current_y - to_mm(line.y)).abs unless line.y.nil?
260
+ @z_travel += (@current_z - to_mm(line.z)).abs unless line.z.nil?
261
+ end
262
+ end
263
+
264
+ def home_axes(line)
265
+ if !line.x.nil? || line.full_home?
266
+ @x_travel += @current_x
267
+ @current_x = 0
268
+ end
269
+ if !line.y.nil? || line.full_home?
270
+ @y_travel += @current_y
271
+ @current_y = 0
272
+ end
273
+ if !line.z.nil? || line.full_home?
274
+ @z_travel += @current_z
275
+ @current_z = 0
276
+ end
277
+ end
278
+
279
+ def positive_number?(number, grater_than = 0)
280
+ number.is_a?(Numeric) && number >= grater_than
281
+ end
282
+
283
+ def set_last_values
284
+ @last_x = @current_x
285
+ @last_y = @current_y
286
+ @last_z = @current_z
287
+ @last_e = @current_e
288
+ @last_speed_per_second = @speed_per_second
289
+ end
290
+
291
+ def set_positions(line)
292
+ @current_x = to_mm(line.x) unless line.x.nil?
293
+ @current_y = to_mm(line.y) unless line.y.nil?
294
+ @current_z = to_mm(line.z) unless line.z.nil?
295
+ unless @relative_extrusion
296
+ @filament_used[line.tool_number] = 0 if @filament_used[line.tool_number].nil?
297
+ @filament_used[line.tool_number] += @current_e
298
+ end
299
+ @current_e = to_mm(line.e) unless line.e.nil?
300
+ end
301
+
302
+ def set_current_position(line)
303
+ if @relative
304
+ @current_x += to_mm(line.x) unless line.x.nil?
305
+ @current_y += to_mm(line.y) unless line.y.nil?
306
+ @current_z += to_mm(line.z) unless line.z.nil?
307
+ else
308
+ @current_x = to_mm(line.x) unless line.x.nil?
309
+ @current_y = to_mm(line.y) unless to_mm(line.y).nil?
310
+ @current_z = to_mm(line.z) unless line.z.nil?
311
+ end
312
+ if @relative_extrusion
313
+ @current_e += to_mm(line.e) unless line.e.nil?
314
+ else
315
+ @current_e = to_mm(line.e) unless line.e.nil?
316
+ end
317
+ end
318
+
319
+ def calculate_filament_usage(line)
320
+ return if @set_position_called
321
+ @filament_used[line.tool_number] = 0 if @filament_used[line.tool_number].nil?
322
+ @filament_used[line.tool_number] = @current_e
323
+ end
324
+
325
+ def set_limits(line)
326
+ if line.extrusion_move?
327
+ unless line.x.nil?
328
+ @x_min = @current_x if @current_x < @x_min
329
+ @x_max = @current_x if @current_x > @x_max
330
+ end
331
+ unless line.y.nil?
332
+ @y_min = @current_y if @current_y < @y_min
333
+ @y_max = @current_y if @current_y > @y_max
334
+ end
335
+ end
336
+ unless line.z.nil?
337
+ @z_min = @current_z if @current_z < @z_min
338
+ @z_max = @current_z if @current_z > @z_max
339
+ end
340
+ end
341
+
342
+ def set_line_properties(line)
343
+ return false unless line
344
+ return line if line.command.nil?
345
+ @tool_number = line.tool_number unless line.tool_number.nil?
346
+ line.tool_number = @tool_number if line.tool_number.nil?
347
+ @speed = line.f unless line.f.nil?
348
+ line.f = @speed if line.f.nil? && @options[:add_speed]
349
+ line.x_add = @options[:x_add]
350
+ line.y_add = @options[:y_add]
351
+ line.z_add = @options[:z_add]
352
+ line
353
+ end
354
+
355
+ def set_processing_variables
356
+ @x_travel = 0
357
+ @y_travel = 0
358
+ @z_travel = 0
359
+ @current_x = 0
360
+ @current_y = 0
361
+ @current_z = 0
362
+ @current_e = 0
363
+ @last_x = 0
364
+ @last_y = 0
365
+ @last_z = 0
366
+ @last_e = 0
367
+ @x_min = 999999999
368
+ @y_min = 999999999
369
+ @z_min = 0
370
+ @x_max = -999999999
371
+ @y_max = -999999999
372
+ @z_max = -999999999
373
+ @filament_used = []
374
+ @layers = 1
375
+ @layer_ranges = []
376
+ @layer_ranges[1] = {}
377
+ @layer_ranges[1][:lower] = 0
378
+ @current_line = 0
379
+ # Time
380
+ @speed_per_second = 0.0
381
+ @last_speed_per_second = 0.0
382
+ @move_duration = 0.0
383
+ @total_duration = 0.0
384
+ @acceleration = 1500.0 #mm/s/s ASSUMING THE DEFAULT FROM SPRINTER !!!!
385
+ end
386
+
387
+ def set_variables
388
+ @raw_data = @options[:data]
389
+ @imperial = false
390
+ @relative = false
391
+ @tool_number = 0
392
+ @speed = @options[:default_speed].to_f
393
+ @acceleration = @options[:acceleration]
394
+ @lines = []
395
+ @comments = []
396
+ end
397
+
398
+ def to_mm(number)
399
+ return number unless @imperial
400
+ number *= 25.4 if !number.nil?
401
+ end
402
+
403
+ end
404
+ end