rintcore 0.0.3 → 0.0.4

Sign up to get free protection for your applications and to get access to all the features.
@@ -1,81 +1,176 @@
1
1
  require 'rint_core/g_code/codes'
2
- require 'active_support/core_ext/object/blank'
3
2
 
4
3
  module RintCore
5
4
  module GCode
5
+ # Represents a single line in a GCode file, parse expression tester: {http://rubular.com/}
6
6
  class Line
7
7
  include RintCore::GCode::Codes
8
8
 
9
- attr_accessor :imperial, :relative, :f
10
- attr_reader :raw
11
- attr_writer :x, :y, :z, :e
12
-
9
+ # @!macro attr_accessor
10
+ # @!attribute [rw] $1
11
+ # @param speed_multiplier [Float] number speed (F) will be multiplied by.
12
+ # @return [nil] if the speed multiplier is not set.
13
+ # @return [Float] the speed multiplier (print moves only).
14
+ # @!attribute [rw] $2
15
+ # @param extrusion_multiplier [Float] number extrusions (E) will be multiplied by.
16
+ # @return [nil] if the extrusion multiplier is not set.
17
+ # @return [Float] the extrusion multiplier.
18
+ # @!attribute [rw] $3
19
+ # @param travel_multiplier [Float] number travel move speeds (F) will be multiplied by.
20
+ # @return [nil] if the travel multiplier is not set.
21
+ # @return [Float] the travel multiplier.
22
+ # @!attribute [rw] $4
23
+ # @param tool_number [Fixnum] the tool used in the command.
24
+ # @return [Fixnum] the tool used in the command.
25
+ # @!attribute [rw] $5
26
+ # @param f [Float] speed of the command (in mm/minute).
27
+ # @return [Float] the speed of the command (in mm/minute).
28
+ attr_accessor :speed_multiplier, :extrusion_multiplier,
29
+ :travel_multiplier, :tool_number, :f
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 evaluation expression.
37
+ # @!attribute [r] $3
38
+ # @return [String] ready to send version of the original line (no multipliers, call {#to_s} to apply multipliers).
39
+ # @!attribute [r] $4
40
+ # @return [String] command portion of the line.
41
+ # @!attribute [r] $5
42
+ # @return [String] command letter of the line (G,M,T).
43
+ # @!attribute [r] $6
44
+ # @return [Fixnum] command number of the line (ex. 28 for "G28").
45
+ # @!attribute [r] $7
46
+ # @return [nil] if there's no S data for the command.
47
+ # @return [Fixnum] S data for the command.
48
+ # @!attribute [r] $8
49
+ # @return [nil] if there's no P data for the command.
50
+ # @return [Fixnum] P data for the command.
51
+ # @!attribute [r] $9
52
+ # @return [nil] if there's no X coordinate of the command.
53
+ # @return [Float] X coordinate of the command.
54
+ # @!attribute [r] $10
55
+ # @return [nil] if there's no Y coordinate of the command.
56
+ # @return [Float] Y coordinate of the command.
57
+ # @!attribute [r] $11
58
+ # @return [nil] if there's no Z coordinate of the command.
59
+ # @return [Float] Z coordinate of the command.
60
+ # @!attribute [r] $12
61
+ # @return [nil] if there's no E parameter of the command.
62
+ # @return [Float] E parameter of the command.
63
+ attr_reader :raw, :matches, :line, :command, :command_letter, :command_number,
64
+ :s_data, :p_data, :x, :y, :z, :e
65
+
66
+ # Creates a {Line}
67
+ # @param line [String] a line of GCode.
68
+ # @return [false] if line is empty or doesn't match the evaluation expression.
69
+ # @return [Line]
13
70
  def initialize(line)
14
- @coordinates = ['X','Y','Z','E','F']
15
- @number_pattern = /[-]?\d+[.]?\d*/
16
- @raw = line.upcase.strip
17
- @raw = @raw.split(COMMENT_SYMBOL).first.strip if line.include?(COMMENT_SYMBOL)
18
-
19
- parse_coordinates
71
+ return false if line.nil? || line.empty?
72
+ @raw = line
73
+ @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](?<e_data>[-]?\d+\.?\d*))?)? (?<string_data>[^;]*)?)? ?;?(?<comment>.*)$/
74
+ @matches = @raw.match(@gcode_pattern)
75
+ return false if @matches.nil?
76
+ assign_values unless @matches.nil?
20
77
  end
21
78
 
22
- def to_mm(number)
23
- number *= 25.4 if number.present? && @imperial
24
- number
79
+ # Checks if the given line is more than just a comment.
80
+ # @return [Boolean] true if empty/invalid
81
+ def empty?
82
+ @command.nil?
25
83
  end
26
84
 
27
- def x
28
- to_mm @x
85
+ # Checks if the command in the line causes movement.
86
+ # @return [Boolean] true if command moves printer, false otherwise.
87
+ def is_move?
88
+ @command == RAPID_MOVE || @command == CONTROLLED_MOVE
29
89
  end
30
90
 
31
- def y
32
- to_mm @y
91
+ # Checks whether the line is a travel move or not.
92
+ # @return [Boolean] true if line is a travel move, false otherwise.
93
+ def travel_move?
94
+ is_move? && @e.nil?
33
95
  end
34
96
 
35
- def z
36
- to_mm @z
97
+ # Checks whether the line is as extrusion move or not.
98
+ # @return [Boolean] true if line is an extrusion move, false otherwise.
99
+ def extrusion_move?
100
+ is_move? && !@e.nil? && @e > 0
37
101
  end
38
102
 
39
- def e
40
- to_mm @e
103
+ # Checks wether the line is a full home or not.
104
+ # @return [Boolean] true if line is full home, false otherwise.
105
+ def full_home?
106
+ @command == HOME && !@x.nil? && !@y.nil? && !@z.nil?
41
107
  end
42
108
 
43
- def command
44
- if @raw.present?
45
- @raw.split(' ').first
46
- else
47
- ''
48
- end
49
- end
109
+ # Returns the line, modified if multipliers are set.
110
+ # @return [String] the line.
111
+ def to_s
112
+ return @line if @extrusion_multiplier.nil? && @speed_multiplier.nil?
113
+
114
+ new_f = multiplied_speed unless is_move?
115
+ new_e = multiplied_extrusion unless @e.nil?
116
+
117
+ x_string = !@x.nil? ? " X#{@x}" : ''
118
+ y_string = !@y.nil? ? " Y#{@y}" : ''
119
+ z_string = !@z.nil? ? " Z#{@z}" : ''
120
+ e_string = !@e.nil? ? " E#{new_e}" : ''
121
+ f_string = is_move? ? " F#{new_f}" : ''
122
+ string = !@string_data.nil? ? " #{@string_data}" : ''
50
123
 
51
- def get_float(axis)
52
- @raw.split(axis).last.scan(@number_pattern).first.to_f
124
+ "#{@command}#{x_string}#{y_string}#{z_string}#{f_string}#{e_string}#{string}"
53
125
  end
54
126
 
55
- def parse_coordinates
56
- @coordinates.each do |axis|
57
- send(axis.downcase+'=', get_float(axis)) if @raw.include?(axis)
58
- end
127
+ private
128
+
129
+ def assign_values
130
+ command_assignments
131
+ coordinate_assignments
132
+ @s = @matches[:s_data].to_i unless @matches[:s_data].nil?
133
+ @p = @matches[:p_data].to_i unless @matches[:p_data].nil?
134
+ @string_data = @matches[:string_data]
135
+ @comment = @matches[:comment].strip unless @matches[:comment].nil?
59
136
  end
60
137
 
61
- def is_move?
62
- @raw.start_with?(RAPID_MOVE) || @raw.start_with?(CONTROLLED_MOVE)
138
+ def command_assignments
139
+ @command = @matches[:command]
140
+ @line = @matches[:line] unless @matches[:line].nil?
141
+ @command_letter = @matches[:command_letter]
142
+ @command_number = @matches[:command_number].to_i unless @matches[:command_number].nil?
143
+ @tool_number = @command_number if !@matches[:command_letter].nil? && @matches[:command_letter] == 'T'
63
144
  end
64
145
 
65
- def travel_move?
66
- is_move? && !@e.present?
146
+ def coordinate_assignments
147
+ @x = @matches[:x_data].to_f unless @matches[:x_data].nil?
148
+ @y = @matches[:y_data].to_f unless @matches[:y_data].nil?
149
+ @z = @matches[:z_data].to_f unless @matches[:z_data].nil?
150
+ @f = @matches[:f_data].to_f unless @matches[:f_data].nil?
151
+ @e = @matches[:e_data].to_f unless @matches[:e_data].nil?
67
152
  end
68
153
 
69
- def extrusion_move?
70
- is_move? && @e.present? && @e > 0
154
+ def multiplied_extrusion
155
+ if valid_multiplier?(@extrusion_multiplier)
156
+ return @e * @extrusion_multiplier
157
+ else
158
+ @e
159
+ end
71
160
  end
72
161
 
73
- def full_home?
74
- command == HOME && @x.blank? && @y.blank? && @z.blank?
162
+ def multiplied_speed
163
+ if travel_move? && valid_multiplier?(@travel_multiplier)
164
+ return @f * @travel_multiplier
165
+ elsif extrusion_move? && valid_multiplier?(@speed_multiplier)
166
+ return @f * @speed_multiplier
167
+ else
168
+ return @f
169
+ end
75
170
  end
76
171
 
77
- def to_s
78
- @raw
172
+ def valid_multiplier?(multiplier)
173
+ !multiplier.nil? && (multiplier.class == Fixnum || multiplier.class == Float) && multiplier > 0
79
174
  end
80
175
 
81
176
  end
@@ -1,45 +1,121 @@
1
1
  require 'rint_core/g_code/codes'
2
2
  require 'rint_core/g_code/line'
3
- require 'active_support/core_ext/object/blank'
4
3
 
5
4
  module RintCore
6
5
  module GCode
6
+ # A class that represents a processed GCode file.
7
7
  class Object
8
8
  include RintCore::GCode::Codes
9
9
 
10
- attr_accessor :raw_data, :layers
10
+ # An array of the raw Gcode with each line as an element.
11
+ # @return [Array] of raw GCode without the comments stripped out.
12
+ attr_accessor :raw_data
13
+ # @!macro attr_reader
14
+ # @!attribute [r] $1
15
+ # @return [Array<Line>] an array of {Line}s.
16
+ # @!attribute [r] $2
17
+ # @return [Float] the smallest X coordinate of an extrusion line.
18
+ # @!attribute [r] $3
19
+ # @return [Float] the biggest X coordinate of an extrusion line.
20
+ # @!attribute [r] $4
21
+ # @return [Float] the smallest Y coordinate of an extrusion line.
22
+ # @!attribute [r] $5
23
+ # @return [Float] the biggest Y coordinate of an extrusion line.
24
+ # @!attribute [r] $6
25
+ # @return [Float] the smallest Z coordinate.
26
+ # @!attribute [r] $7
27
+ # @return [Float] the biggest Z coordinate.
28
+ # @!attribute [r] $8
29
+ # @return [Array<Float>] the amount in mm of fliament extruded with the index representing the extruder.
30
+ # @!attribute [r] $9
31
+ # @return [Float] the distance in total that the X axis will travel in mm.
32
+ # @!attribute [r] $10
33
+ # @return [Float] the distance in total that the Y axis will travel in mm.
34
+ # @!attribute [r] $11
35
+ # @return [Float] the distance in total that the Z axis will travel in mm.
36
+ # @!attribute [r] $12
37
+ # @return [Float] the distance in total that the E axis will travel in mm.
38
+ # @todo implement this
39
+ # @!attribute [r] $13
40
+ # @return [Float] the width of the print.
41
+ # @!attribute [r] $14
42
+ # @return [Float] the depth of the print.
43
+ # @!attribute [r] $15
44
+ # @return [Float] the height of the print.
45
+ # @!attribute [r] $16
46
+ # @return [Fixnum] the number of layers in the print.
11
47
  attr_reader :lines, :x_min, :x_max, :y_min, :y_max, :z_min, :z_max,
12
48
  :filament_used, :x_travel, :y_travel, :z_travel, :e_travel,
13
- :width, :depth, :height
49
+ :width, :depth, :height, :layers
14
50
 
15
- def initialize(data = nil)
51
+ # Creates a GCode {Object}.
52
+ # @param data [String] path to a GCode file on the system.
53
+ # @param data [Array] with each element being a line of GCode.
54
+ # @param default_speed [Float] the default speed (in mm/minute) for moves that don't have one declared.
55
+ # @param auto_process [Boolean] enable/disable auto processing.
56
+ # @return [Object] if data is valid, returns a GCode {Object}.
57
+ # @return [false] if data is not an array, path, didn't contain GCode or default_speed wasn't a number grater than 0.
58
+ def initialize(data = nil, default_speed = 2400, auto_process = true)
59
+ return false if default_speed.nil? && (default_speed.class == Fixnum || default_speed.class == Float) && default_speed <= 0
16
60
  if data.class == String && self.class.is_file?(data)
17
61
  data = self.class.get_file(data)
18
62
  end
19
- return false if data.blank? || data.class != Array
20
- @raw_data = data
21
- @imperial = false
22
- @relative = false
23
- @lines = []
63
+ return false if data.nil? || data.class != Array
64
+ set_variables(data, default_speed)
24
65
  data.each do |line|
25
66
  line = RintCore::GCode::Line.new(line)
26
- @lines << line if line.raw.present?
67
+ @lines << set_line_properties(line) if line && !line.command.nil?
27
68
  end
28
- process
69
+ process if auto_process
70
+ return false if empty?
29
71
  end
30
72
 
73
+ # Checks if the given string is a file and if it exists.
74
+ # @param file [String] path to a file on the system.
75
+ # @return [Boolean] true if is a file that exists on the system, false otherwise.
31
76
  def self.is_file?(file)
32
- file.present? && File.exist?(file) && File.file?(file)
77
+ !file.nil? && !file.empty? && File.exist?(file) && File.file?(file)
33
78
  end
34
79
 
80
+ # Returns an array of the lines of the file if it exists.
81
+ # @param file [String] path to a file on the system.
82
+ # @return [Array] containting the lines of the given file as elements.
83
+ # @return [false] if given string isn't a file or doesn't exist.
35
84
  def self.get_file(file)
85
+ return false unless self.is_file?(file)
36
86
  IO.readlines(file)
37
87
  end
38
88
 
89
+ # Checks if there are any {Line}s in {#lines}.
90
+ # @return [Boolean] true if no lines, false otherwise.
91
+ def blank?
92
+ empty?
93
+ end
94
+
95
+ # Checks if there are any {Line}s in {#lines}.
96
+ # @return [Boolean] true if no lines, false otherwise.
97
+ def empty?
98
+ @lines.empty?
99
+ end
100
+
101
+ # Opposite of {#empty?}.
102
+ # @see #empty?
103
+ def present?
104
+ !empty?
105
+ end
106
+
107
+ # Checks if the GCode object contains multiple materials.
108
+ # @return [nil] if processing hasn't been done.
109
+ # @return [Boolean] true if multiple extruders used, false otherwise.
110
+ def multi_material?
111
+ return nil unless @width
112
+ @filament_used.length > 1
113
+ end
114
+
39
115
  private
40
116
 
41
117
  def process
42
- set_variables
118
+ set_processing_variables
43
119
 
44
120
  @lines.each do |line|
45
121
  case line.command
@@ -63,94 +139,105 @@ private
63
139
  end
64
140
  end
65
141
 
142
+ set_dimensions
143
+ end
144
+
145
+ def set_dimensions
66
146
  @width = @x_max - @x_min
67
147
  @depth = @y_max - @y_min
68
148
  @height = @z_max - @z_min
69
149
  end
70
150
 
71
151
  def count_layers(line)
72
- if line.z.present? && line.z > @current_z
152
+ if !line.z.nil? && line.z > @current_z
73
153
  @layers += 1
74
154
  end
75
155
  end
76
156
 
77
157
  def movement_line(line)
78
- line.imperial = @imperial
79
- line.relative = @relative
80
158
  measure_travel(line)
81
159
  set_current_position(line)
82
160
  set_limits(line)
83
161
  end
84
162
 
85
163
  def measure_travel(line)
86
- if line.relative
87
- @x_travel += line.x.abs if line.x.present?
88
- @y_travel += line.y.abs if line.y.present?
89
- @z_travel += line.z.abs if line.z.present?
164
+ if @relative
165
+ @x_travel += to_mm(line.x).abs unless line.x.nil?
166
+ @y_travel += to_mm(line.y).abs unless line.y.nil?
167
+ @z_travel += to_mm(line.z).abs unless line.z.nil?
90
168
  else
91
- @x_travel += (@current_x - line.x).abs if line.x.present?
92
- @y_travel += (@current_y - line.y).abs if line.y.present?
93
- @z_travel += (@current_z - line.z).abs if line.z.present?
169
+ @x_travel += (@current_x - to_mm(line.x)).abs unless line.x.nil?
170
+ @y_travel += (@current_y - to_mm(line.y)).abs unless line.y.nil?
171
+ @z_travel += (@current_z - to_mm(line.z)).abs unless line.z.nil?
94
172
  end
95
173
  end
96
174
 
97
175
  def home_axes(line)
98
- if line.x.present? || line.full_home?
176
+ if !line.x.nil? || line.full_home?
99
177
  @x_travel += @current_x
100
178
  @current_x = 0
101
179
  end
102
- if line.y.present? || line.full_home?
180
+ if !line.y.nil? || line.full_home?
103
181
  @y_travel += @current_y
104
182
  @current_y = 0
105
183
  end
106
- if line.z.present? || line.full_home?
184
+ if !line.z.nil? || line.full_home?
107
185
  @z_travel += @current_z
108
186
  @current_z = 0
109
187
  end
110
188
  end
111
189
 
112
190
  def set_positions(line)
113
- @current_x = line.x if line.x.present?
114
- @current_y = line.y if line.y.present?
115
- @current_z = line.z if line.z.present?
116
- if line.e.present?
117
- @filament_used += @current_e
118
- @current_e = line.e
191
+ @current_x = to_mm(line.x) unless line.x.nil?
192
+ @current_y = to_mm(line.y) unless line.y.nil?
193
+ @current_z = to_mm(line.z) unless line.z.nil?
194
+ unless line.e.nil?
195
+ @filament_used[line.tool_number] = 0 if @filament_used[line.tool_number].nil?
196
+ @filament_used[line.tool_number] += @current_e
197
+ @current_e = to_mm(line.e)
119
198
  end
120
199
  end
121
200
 
122
201
  def set_current_position(line)
123
- if line.relative
124
- @current_x += line.x if line.x.present?
125
- @current_y += line.y if line.y.present?
126
- @current_z += line.z if line.z.present?
127
- @current_e += line.e if line.e.present?
202
+ if @relative
203
+ @current_x += to_mm(line.x) unless line.x.nil?
204
+ @current_y += to_mm(line.y) unless line.y.nil?
205
+ @current_z += to_mm(line.z) unless line.z.nil?
206
+ @current_e += to_mm(line.e) unless line.e.nil?
128
207
  else
129
- @current_x = line.x if line.x.present?
130
- @current_y = line.y if line.y.present?
131
- @current_z = line.z if line.z.present?
132
- @current_e = line.e if line.e.present?
208
+ @current_x = to_mm(line.x) unless line.x.nil?
209
+ @current_y = to_mm(line.y) unless to_mm(line.y).nil?
210
+ @current_z = to_mm(line.z) unless line.z.nil?
211
+ @current_e = to_mm(line.e) unless line.e.nil?
133
212
  end
134
213
  end
135
214
 
136
215
  def set_limits(line)
137
216
  if line.extrusion_move?
138
- if line.x.present? && !line.x.zero?
217
+ unless line.x.nil?
139
218
  @x_min = @current_x if @current_x < @x_min
140
219
  @x_max = @current_x if @current_x > @x_max
141
220
  end
142
- if line.y.present? && !line.y.zero?
221
+ unless line.y.nil?
143
222
  @y_min = @current_y if @current_y < @y_min
144
223
  @y_max = @current_y if @current_y > @y_max
145
224
  end
146
225
  end
147
- if line.z.present?
226
+ unless line.z.nil?
148
227
  @z_min = @current_z if @current_z < @z_min
149
228
  @z_max = @current_z if @current_z > @z_max
150
229
  end
151
230
  end
152
231
 
153
- def set_variables
232
+ def set_line_properties(line)
233
+ @tool_number = line.tool_number unless line.tool_number.nil?
234
+ line.tool_number = @tool_number if line.tool_number.nil?
235
+ @speed = line.f unless line.f.nil?
236
+ line.f = @speed if line.f.nil?
237
+ line
238
+ end
239
+
240
+ def set_processing_variables
154
241
  @x_travel = 0
155
242
  @y_travel = 0
156
243
  @z_travel = 0
@@ -164,10 +251,24 @@ private
164
251
  @x_max = -999999999
165
252
  @y_max = -999999999
166
253
  @z_max = -999999999
167
- @filament_used = 0
254
+ @filament_used = []
168
255
  @layers = 0
169
256
  end
170
257
 
258
+ def set_variables(data, default_speed)
259
+ @raw_data = data
260
+ @imperial = false
261
+ @relative = false
262
+ @tool_number = 0
263
+ @speed = default_speed.to_f
264
+ @lines = []
265
+ end
266
+
267
+ def to_mm(number)
268
+ return number unless @imperial
269
+ number *= 25.4 if !number.nil?
270
+ end
271
+
171
272
  end
172
273
  end
173
274
  end