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.
- checksums.yaml +7 -0
- data/.gitignore +17 -0
- data/Gemfile +4 -0
- data/LICENSE.md +596 -0
- data/README.md +29 -0
- data/Rakefile +1 -0
- data/gcode.gemspec +24 -0
- data/lib/gcode.rb +8 -0
- data/lib/gcode/codes.rb +90 -0
- data/lib/gcode/line.rb +268 -0
- data/lib/gcode/object.rb +404 -0
- data/lib/gcode/pretty_output.rb +17 -0
- data/lib/gcode/version.rb +3 -0
- metadata +99 -0
data/README.md
ADDED
|
@@ -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
|
data/Rakefile
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
require 'bundler/gem_tasks'
|
data/gcode.gemspec
ADDED
|
@@ -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
|
data/lib/gcode.rb
ADDED
data/lib/gcode/codes.rb
ADDED
|
@@ -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
|
data/lib/gcode/line.rb
ADDED
|
@@ -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
|
data/lib/gcode/object.rb
ADDED
|
@@ -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
|