g-coder 0.1
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +18 -0
- data/Gemfile +4 -0
- data/LICENSE +20 -0
- data/README.md +4 -0
- data/Rakefile +1 -0
- data/gcoder.gemspec +23 -0
- data/lib/gcoder.rb +4 -0
- data/lib/gcoder/dialects/default.rb +95 -0
- data/lib/gcoder/gcode.rb +321 -0
- data/lib/gcoder/parser.rb +45 -0
- data/lib/gcoder/version.rb +6 -0
- data/spec/default_dialect_spec.rb +13 -0
- data/spec/gcode_program_spec.rb +56 -0
- data/spec/parser_spec.rb +54 -0
- data/spec/spec_helper.rb +4 -0
- metadata +98 -0
data/.gitignore
ADDED
data/Gemfile
ADDED
data/LICENSE
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
The MIT License (MIT)
|
2
|
+
|
3
|
+
Copyright (c) 2014 32leaves
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy of
|
6
|
+
this software and associated documentation files (the "Software"), to deal in
|
7
|
+
the Software without restriction, including without limitation the rights to
|
8
|
+
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
|
9
|
+
the Software, and to permit persons to whom the Software is furnished to do so,
|
10
|
+
subject to the following conditions:
|
11
|
+
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
13
|
+
copies or substantial portions of the Software.
|
14
|
+
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
|
17
|
+
FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
18
|
+
COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
|
19
|
+
IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
20
|
+
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
data/Rakefile
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
require "bundler/gem_tasks"
|
data/gcoder.gemspec
ADDED
@@ -0,0 +1,23 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
lib = File.expand_path('../lib', __FILE__)
|
3
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
|
+
require 'gcoder/version'
|
5
|
+
|
6
|
+
Gem::Specification.new do |spec|
|
7
|
+
spec.name = "g-coder"
|
8
|
+
spec.version = GCoder::VERSION
|
9
|
+
spec.authors = ["32leaves"]
|
10
|
+
spec.email = ["info@32leav.es"]
|
11
|
+
spec.summary = %q{GCoder is a Ruby library to deal with GCode in various flavours.}
|
12
|
+
spec.description = %q{GCoder is designed to make scripting GCode filters easy, and to work with Opal to enable web-based GCode editors/viewers.}
|
13
|
+
spec.homepage = ""
|
14
|
+
spec.license = "MIT"
|
15
|
+
|
16
|
+
spec.files = `git ls-files -z`.split("\x0")
|
17
|
+
spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
|
18
|
+
spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
|
19
|
+
spec.require_paths = ["lib"]
|
20
|
+
|
21
|
+
spec.add_development_dependency "bundler", "~> 1.5"
|
22
|
+
spec.add_development_dependency "rake"
|
23
|
+
end
|
data/lib/gcoder.rb
ADDED
@@ -0,0 +1,95 @@
|
|
1
|
+
|
2
|
+
module GCoder
|
3
|
+
|
4
|
+
module Dialects
|
5
|
+
|
6
|
+
def self.default
|
7
|
+
map = {}
|
8
|
+
map[:C] = {}
|
9
|
+
map[:G] = {}
|
10
|
+
map[:M] = {}
|
11
|
+
map[:G][0] = :MoveRapid
|
12
|
+
map[:G][1] = :MoveByFeedrate
|
13
|
+
map[:G][2] = :ClockwiseCircularArcAtFeedrate
|
14
|
+
map[:G][3] = :CounterClockwiseCircularArcAtFeedrate
|
15
|
+
map[:G][4] = :Dwell
|
16
|
+
map[:G][9] = :ExactStopCheck
|
17
|
+
map[:G][10] = :ProgrammableParameterInput
|
18
|
+
map[:G][15] = :TurnPolarCoordinatesOffReturnToCartesianCoordinates
|
19
|
+
map[:G][16] = :TurnPolarCoordinatesOn
|
20
|
+
map[:G][17] = :SelectXYPlane
|
21
|
+
map[:G][18] = :SelectXZPlane
|
22
|
+
map[:G][19] = :SelectYZPlane
|
23
|
+
map[:G][20] = :ProgramCoordinatesAreInches
|
24
|
+
map[:G][21] = :ProgramCoordinatesAreMm
|
25
|
+
map[:G][27] = :ReferencePointReturnCheck
|
26
|
+
map[:G][28] = :ReturnToHomePosition
|
27
|
+
map[:G][29] = :ReturnFromTheReferencePosition
|
28
|
+
map[:G][30] = :ReturnToThe2nd3rdAnd4thReferencePoint
|
29
|
+
map[:G][32] = :ConstantLeadThreading
|
30
|
+
map[:G][40] = :ToolCutterCompensationOff
|
31
|
+
map[:G][41] = :ToolCutterCompensationLeft
|
32
|
+
map[:G][42] = :ToolCutterCompensationRight
|
33
|
+
map[:G][43] = :ApplyToolLengthCompensationPlus
|
34
|
+
map[:G][44] = :ApplyToolLengthCompensationMinus
|
35
|
+
map[:G][49] = :ToolLengthCompensationCancel
|
36
|
+
map[:G][50] = :ResetAllScaleFactorsTo1
|
37
|
+
map[:G][51] = :TurnOnScaleFactors
|
38
|
+
map[:G][53] = :MachineCoordinateSystem
|
39
|
+
map[:G][54] = :WorkCoordinateSystem1stWorkpiece
|
40
|
+
map[:G][55] = :WorkCoordinateSystem2ndWorkpiece
|
41
|
+
map[:G][56] = :WorkCoordinateSystem3rdWorkpiece
|
42
|
+
map[:G][57] = :WorkCoordinateSystem4thWorkpiece
|
43
|
+
map[:G][58] = :WorkCoordinateSystem5thWorkpiece
|
44
|
+
map[:G][59] = :WorkCoordinateSystem6thWorkpiece
|
45
|
+
map[:G][61] = :ExactStopCheckMode
|
46
|
+
map[:G][62] = :AutomaticCornerOverride
|
47
|
+
map[:G][63] = :TappingMode
|
48
|
+
map[:G][64] = :BestSpeedPath
|
49
|
+
map[:G][65] = :CustomMacroSimpleCall
|
50
|
+
map[:G][68] = :CoordinateSystemRotation
|
51
|
+
map[:G][69] = :CancelCoordinateSystemRotation
|
52
|
+
map[:G][73] = :HighSpeedDrillingCycle
|
53
|
+
map[:G][74] = :LeftHandTappingCycle
|
54
|
+
map[:G][76] = :FineBoringCyle
|
55
|
+
map[:G][80] = :CancelCannedCycle
|
56
|
+
map[:G][81] = :SimpleDrillingCycle
|
57
|
+
map[:G][82] = :DrillingCycleWithDwell
|
58
|
+
map[:G][83] = :PeckDrillingCycle
|
59
|
+
map[:G][84] = :TappingCycle
|
60
|
+
map[:G][85] = :BoringCannedCycleNoDwellFeedOut
|
61
|
+
map[:G][86] = :BoringCannedCycleSpindleStopRapidOut
|
62
|
+
map[:G][87] = :BackBoringCannedCycle
|
63
|
+
map[:G][88] = :BoringCannedCycleSpindleStopManualOut
|
64
|
+
map[:G][89] = :BoringCannedCycleDwellFeedOut
|
65
|
+
map[:G][90] = :AbsoluteProgrammingOfXYZ
|
66
|
+
map[:G][91] = :IncrementalProgrammingOfXYZ
|
67
|
+
map[:G][92] = :OffsetCoordinateSystemAndSaveParameters
|
68
|
+
map[:G][921] = :CancelOffsetAndZeroParameters
|
69
|
+
map[:G][922] = :CancelOffsetAndRetainParameters
|
70
|
+
map[:G][923] = :OffsetCoordinateSystemWithSavedParameters
|
71
|
+
map[:G][94] = :UnitsPerMinuteFeedModeUnitsInInchesOrMm
|
72
|
+
map[:G][95] = :UnitsPerRevolutionFeedModeUnitsInInchesOrMm
|
73
|
+
map[:G][96] = :ConstantSurfaceSpeed
|
74
|
+
map[:G][97] = :CancelConstantSurfaceSpeed
|
75
|
+
map[:G][98] = :ReturnToInitialZPlaneAfterCannedCycle
|
76
|
+
map[:G][99] = :ReturnToInitialRPlaneAfterCannedCycle
|
77
|
+
map[:M][0] = :ProgramStop
|
78
|
+
map[:M][1] = :ProgramStopOptional
|
79
|
+
map[:M][2] = :EndOfProgram
|
80
|
+
map[:M][3] = :SpindleOnCwRotation
|
81
|
+
map[:M][4] = :SpindleOnCcwRotation
|
82
|
+
map[:M][5] = :SpindleStop
|
83
|
+
map[:M][6] = :ToolChange
|
84
|
+
map[:M][7] = :MistCoolantOn
|
85
|
+
map[:M][8] = :FloodCoolantOn
|
86
|
+
map[:M][9] = :CoolantOff
|
87
|
+
map[:M][30] = :EndOfProgramRewindAndResetModes
|
88
|
+
map[:M][98] = :SubprogramCall
|
89
|
+
map[:M][99] = :ReturnFromSubprogram
|
90
|
+
map
|
91
|
+
end
|
92
|
+
|
93
|
+
end
|
94
|
+
|
95
|
+
end
|
data/lib/gcoder/gcode.rb
ADDED
@@ -0,0 +1,321 @@
|
|
1
|
+
require 'delegate'
|
2
|
+
|
3
|
+
class Object
|
4
|
+
|
5
|
+
#
|
6
|
+
# A combination of to_i and to_f.
|
7
|
+
# If this object is nil?, nil is returned.
|
8
|
+
# If this object is an integer, to_i is returned.
|
9
|
+
# If this object is a floating point number, to_f is returned.
|
10
|
+
#
|
11
|
+
def to_nif
|
12
|
+
if nil?
|
13
|
+
nil
|
14
|
+
elsif to_i.to_s == self
|
15
|
+
to_i
|
16
|
+
else
|
17
|
+
to_f
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
end
|
22
|
+
|
23
|
+
module GCoder
|
24
|
+
|
25
|
+
module GCode
|
26
|
+
|
27
|
+
class ProgramContext < SimpleDelegator
|
28
|
+
attr_accessor :position, :feedrate, :units, :absolute
|
29
|
+
|
30
|
+
def initialize(position = [0,0,0], feedrate = 0, units = :mm, absolute = false)
|
31
|
+
super({})
|
32
|
+
|
33
|
+
@position = position
|
34
|
+
@feedrate = feedrate
|
35
|
+
@units = units
|
36
|
+
@absolute = absolute
|
37
|
+
end
|
38
|
+
|
39
|
+
def absolute?; absolute; end
|
40
|
+
|
41
|
+
def update_position(pos)
|
42
|
+
if absolute
|
43
|
+
@position = @position.each_with_index.map {|e, i| pos[i] || @position[i] }
|
44
|
+
else
|
45
|
+
@position = @position.each_with_index.map {|e, i| @position[i] + (pos[i] || 0) }
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
def update_feedrate(feedrate)
|
50
|
+
@feedrate = feedrate unless feedrate.nil?
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
class Program < SimpleDelegator
|
55
|
+
include GCoder::GCode
|
56
|
+
|
57
|
+
def initialize(commands)
|
58
|
+
super(commands)
|
59
|
+
end
|
60
|
+
|
61
|
+
#
|
62
|
+
# Interpretes aspects of the GCode program, namely position, feedrate, unit of measure
|
63
|
+
# and absolute/incremental positioning. This information is made available to the optional
|
64
|
+
# block as second parameter, through a ProgramContext instance.
|
65
|
+
#
|
66
|
+
# Returns an array with two elements, the mapping result and context.
|
67
|
+
#
|
68
|
+
#
|
69
|
+
def map_with_context(&block)
|
70
|
+
ctx = ProgramContext.new
|
71
|
+
|
72
|
+
map_result = self.map do |cmd|
|
73
|
+
if block_given?
|
74
|
+
r = yield(cmd, ctx)
|
75
|
+
else
|
76
|
+
r = cmd
|
77
|
+
end
|
78
|
+
|
79
|
+
if cmd.is_a? MoveRapid or cmd.is_a? MoveByFeedrate
|
80
|
+
ctx.update_position cmd.position
|
81
|
+
ctx.update_feedrate cmd.feedrate
|
82
|
+
elsif cmd.is_a? ProgramCoordinatesAreMm
|
83
|
+
ctx.units = :mm
|
84
|
+
elsif cmd.is_a? ProgramCoordinatesAreInches
|
85
|
+
ctx.units = :inch
|
86
|
+
elsif cmd.is_a? AbsoluteProgrammingOfXYZ
|
87
|
+
ctx.absolute = true
|
88
|
+
elsif cmd.is_a? IncrementalProgrammingOfXYZ
|
89
|
+
ctx.absolute = false
|
90
|
+
end
|
91
|
+
|
92
|
+
r
|
93
|
+
end
|
94
|
+
|
95
|
+
[map_result, ctx]
|
96
|
+
end
|
97
|
+
|
98
|
+
|
99
|
+
end
|
100
|
+
|
101
|
+
class Command
|
102
|
+
attr_reader :code, :args
|
103
|
+
|
104
|
+
def initialize(code, args)
|
105
|
+
@code = code
|
106
|
+
@args = args
|
107
|
+
end
|
108
|
+
|
109
|
+
def to_s
|
110
|
+
( [code] + args.map {|e| e.join } ).join(" ")
|
111
|
+
end
|
112
|
+
|
113
|
+
end
|
114
|
+
|
115
|
+
class Comment < Command
|
116
|
+
# Returns the text of the comment without the parentheses
|
117
|
+
def text
|
118
|
+
code
|
119
|
+
end
|
120
|
+
|
121
|
+
def to_s
|
122
|
+
"(#{code})"
|
123
|
+
end
|
124
|
+
end
|
125
|
+
|
126
|
+
class CategoryCommand < Command
|
127
|
+
end
|
128
|
+
class MotionCommand < Command
|
129
|
+
|
130
|
+
def position
|
131
|
+
[ args[:X], args[:Y], args[:Z] ].map {|x| x.to_nif }
|
132
|
+
end
|
133
|
+
|
134
|
+
def feedrate
|
135
|
+
args[:F].to_nif
|
136
|
+
end
|
137
|
+
|
138
|
+
end
|
139
|
+
class CompensationCommand < Command
|
140
|
+
end
|
141
|
+
class CoordinateCommand < Command
|
142
|
+
end
|
143
|
+
class MotionCommand < Command
|
144
|
+
end
|
145
|
+
class CannedCommand < Command
|
146
|
+
end
|
147
|
+
class CompensationCommand < Command
|
148
|
+
end
|
149
|
+
class CoordinateCommand < Command
|
150
|
+
end
|
151
|
+
class OtherCommand < Command
|
152
|
+
end
|
153
|
+
class CannedCommand < Command
|
154
|
+
end
|
155
|
+
class MCodeCommand < Command
|
156
|
+
end
|
157
|
+
|
158
|
+
class MoveRapid < MotionCommand
|
159
|
+
end
|
160
|
+
class MoveByFeedrate < MotionCommand
|
161
|
+
end
|
162
|
+
class ClockwiseCircularArcAtFeedrate < MotionCommand
|
163
|
+
end
|
164
|
+
class CounterClockwiseCircularArcAtFeedrate < MotionCommand
|
165
|
+
end
|
166
|
+
class Dwell < MotionCommand
|
167
|
+
end
|
168
|
+
class ExactStopCheck < MotionCommand
|
169
|
+
end
|
170
|
+
class ProgrammableParameterInput < CompensationCommand
|
171
|
+
end
|
172
|
+
class TurnPolarCoordinatesOffReturnToCartesianCoordinates < CoordinateCommand
|
173
|
+
end
|
174
|
+
class TurnPolarCoordinatesOn < CoordinateCommand
|
175
|
+
end
|
176
|
+
class SelectXYPlane < CoordinateCommand
|
177
|
+
end
|
178
|
+
class SelectXZPlane < CoordinateCommand
|
179
|
+
end
|
180
|
+
class SelectYZPlane < CoordinateCommand
|
181
|
+
end
|
182
|
+
class ProgramCoordinatesAreInches < CoordinateCommand
|
183
|
+
end
|
184
|
+
class ProgramCoordinatesAreMm < CoordinateCommand
|
185
|
+
end
|
186
|
+
class AbsoluteProgrammingOfXYZ < CoordinateCommand
|
187
|
+
end
|
188
|
+
class IncrementalProgrammingOfXYZ < CoordinateCommand
|
189
|
+
end
|
190
|
+
class ReferencePointReturnCheck < MotionCommand
|
191
|
+
end
|
192
|
+
class ReturnToHomePosition < MotionCommand
|
193
|
+
end
|
194
|
+
class ReturnFromTheReferencePosition < MotionCommand
|
195
|
+
end
|
196
|
+
class ReturnToThe2nd3rdAnd4thReferencePoint < MotionCommand
|
197
|
+
end
|
198
|
+
class ConstantLeadThreading < CannedCommand
|
199
|
+
end
|
200
|
+
class ToolCutterCompensationOff < CompensationCommand
|
201
|
+
end
|
202
|
+
class ToolCutterCompensationLeft < CompensationCommand
|
203
|
+
end
|
204
|
+
class ToolCutterCompensationRight < CompensationCommand
|
205
|
+
end
|
206
|
+
class ApplyToolLengthCompensationPlus < CompensationCommand
|
207
|
+
end
|
208
|
+
class ApplyToolLengthCompensationMinus < CompensationCommand
|
209
|
+
end
|
210
|
+
class ToolLengthCompensationCancel < CompensationCommand
|
211
|
+
end
|
212
|
+
class ResetAllScaleFactorsTo1 < CompensationCommand
|
213
|
+
end
|
214
|
+
class TurnOnScaleFactors < CompensationCommand
|
215
|
+
end
|
216
|
+
class MachineCoordinateSystem < CoordinateCommand
|
217
|
+
end
|
218
|
+
class WorkCoordinateSystem1stWorkpiece < CoordinateCommand
|
219
|
+
end
|
220
|
+
class WorkCoordinateSystem2ndWorkpiece < CoordinateCommand
|
221
|
+
end
|
222
|
+
class WorkCoordinateSystem3rdWorkpiece < CoordinateCommand
|
223
|
+
end
|
224
|
+
class WorkCoordinateSystem4thWorkpiece < CoordinateCommand
|
225
|
+
end
|
226
|
+
class WorkCoordinateSystem5thWorkpiece < CoordinateCommand
|
227
|
+
end
|
228
|
+
class WorkCoordinateSystem6thWorkpiece < CoordinateCommand
|
229
|
+
end
|
230
|
+
class ExactStopCheckMode < OtherCommand
|
231
|
+
end
|
232
|
+
class AutomaticCornerOverride < OtherCommand
|
233
|
+
end
|
234
|
+
class TappingMode < OtherCommand
|
235
|
+
end
|
236
|
+
class BestSpeedPath < OtherCommand
|
237
|
+
end
|
238
|
+
class CustomMacroSimpleCall < OtherCommand
|
239
|
+
end
|
240
|
+
class CoordinateSystemRotation < CoordinateCommand
|
241
|
+
end
|
242
|
+
class CancelCoordinateSystemRotation < CoordinateCommand
|
243
|
+
end
|
244
|
+
class HighSpeedDrillingCycle < CannedCommand
|
245
|
+
end
|
246
|
+
class LeftHandTappingCycle < CannedCommand
|
247
|
+
end
|
248
|
+
class FineBoringCyle < CannedCommand
|
249
|
+
end
|
250
|
+
class CancelCannedCycle < CannedCommand
|
251
|
+
end
|
252
|
+
class SimpleDrillingCycle < CannedCommand
|
253
|
+
end
|
254
|
+
class DrillingCycleWithDwell < CannedCommand
|
255
|
+
end
|
256
|
+
class PeckDrillingCycle < CannedCommand
|
257
|
+
end
|
258
|
+
class TappingCycle < CannedCommand
|
259
|
+
end
|
260
|
+
class BoringCannedCycleNoDwellFeedOut < CannedCommand
|
261
|
+
end
|
262
|
+
class BoringCannedCycleSpindleStopRapidOut < CannedCommand
|
263
|
+
end
|
264
|
+
class BackBoringCannedCycle < CannedCommand
|
265
|
+
end
|
266
|
+
class BoringCannedCycleSpindleStopManualOut < CannedCommand
|
267
|
+
end
|
268
|
+
class BoringCannedCycleDwellFeedOut < CannedCommand
|
269
|
+
end
|
270
|
+
class OffsetCoordinateSystemAndSaveParameters < CoordinateCommand
|
271
|
+
end
|
272
|
+
class ClampOfMaximumSpindleSpeed < MotionCommand
|
273
|
+
end
|
274
|
+
class CancelOffsetAndZeroParameters < CoordinateCommand
|
275
|
+
end
|
276
|
+
class CancelOffsetAndRetainParameters < CoordinateCommand
|
277
|
+
end
|
278
|
+
class OffsetCoordinateSystemWithSavedParameters < CoordinateCommand
|
279
|
+
end
|
280
|
+
class UnitsPerMinuteFeedModeUnitsInInchesOrMm < MotionCommand
|
281
|
+
end
|
282
|
+
class UnitsPerRevolutionFeedModeUnitsInInchesOrMm < MotionCommand
|
283
|
+
end
|
284
|
+
class ConstantSurfaceSpeed < MotionCommand
|
285
|
+
end
|
286
|
+
class CancelConstantSurfaceSpeed < MotionCommand
|
287
|
+
end
|
288
|
+
class ReturnToInitialZPlaneAfterCannedCycle < CannedCommand
|
289
|
+
end
|
290
|
+
class ReturnToInitialRPlaneAfterCannedCycle < CannedCommand
|
291
|
+
end
|
292
|
+
class ProgramStop < MCodeCommand
|
293
|
+
end
|
294
|
+
class ProgramStopOptional < MCodeCommand
|
295
|
+
end
|
296
|
+
class EndOfProgram < MCodeCommand
|
297
|
+
end
|
298
|
+
class SpindleOnCwRotation < MCodeCommand
|
299
|
+
end
|
300
|
+
class SpindleOnCcwRotation < MCodeCommand
|
301
|
+
end
|
302
|
+
class SpindleStop < MCodeCommand
|
303
|
+
end
|
304
|
+
class ToolChange < MCodeCommand
|
305
|
+
end
|
306
|
+
class MistCoolantOn < MCodeCommand
|
307
|
+
end
|
308
|
+
class FloodCoolantOn < MCodeCommand
|
309
|
+
end
|
310
|
+
class CoolantOff < MCodeCommand
|
311
|
+
end
|
312
|
+
class EndOfProgramRewindAndResetModes < MCodeCommand
|
313
|
+
end
|
314
|
+
class SubprogramCall < MCodeCommand
|
315
|
+
end
|
316
|
+
class ReturnFromSubprogram < MCodeCommand
|
317
|
+
end
|
318
|
+
|
319
|
+
end
|
320
|
+
|
321
|
+
end
|
@@ -0,0 +1,45 @@
|
|
1
|
+
|
2
|
+
module GCoder
|
3
|
+
|
4
|
+
class Parser
|
5
|
+
|
6
|
+
#
|
7
|
+
# Rudimentary GCode parser that also maps the commands to their respective classes according to the dialect.
|
8
|
+
#
|
9
|
+
# Returns a GCode::Program containing the commands.
|
10
|
+
#
|
11
|
+
def parse(code, dialect = GCoder::Dialects::default)
|
12
|
+
code = code.split("\n") unless code.is_a? Array
|
13
|
+
|
14
|
+
commands = code.map do |caa|
|
15
|
+
r = nil
|
16
|
+
caa.strip!
|
17
|
+
unless caa.empty?
|
18
|
+
cmd, *args = caa.strip.split(" ")
|
19
|
+
root_code = cmd[0...1]
|
20
|
+
child_code = cmd[1..-1]
|
21
|
+
args = args.inject({}) {|m,e| m[e[0...1].to_sym] = e[1..-1]; m }
|
22
|
+
|
23
|
+
if root_code == '('
|
24
|
+
r = GCoder::GCode::Comment.new(caa.strip[1...-1], {})
|
25
|
+
else
|
26
|
+
commands = dialect[root_code.to_sym]
|
27
|
+
throw "Dialect does not support #{root_code} commands" if commands.nil?
|
28
|
+
|
29
|
+
unless child_code.nil? or child_code.empty?
|
30
|
+
cmd_class = commands[child_code.to_i]
|
31
|
+
throw "Dialect does not support command: #{child_code}" if cmd_class.nil?
|
32
|
+
|
33
|
+
r = eval("GCoder::GCode::#{cmd_class}").new(cmd, args)
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
37
|
+
r
|
38
|
+
end.compact
|
39
|
+
|
40
|
+
GCode::Program.new(commands)
|
41
|
+
end
|
42
|
+
|
43
|
+
end
|
44
|
+
|
45
|
+
end
|
@@ -0,0 +1,13 @@
|
|
1
|
+
require 'rspec'
|
2
|
+
require 'spec_helper'
|
3
|
+
|
4
|
+
describe 'Default dialect' do
|
5
|
+
|
6
|
+
it 'should use only classes that exist in GCoder' do
|
7
|
+
GCoder::Dialects::default.values.map {|e| e.values }.flatten.each do |classname|
|
8
|
+
GCoder::GCode.const_defined?(classname).should be_true, "Undefined class #{classname}"
|
9
|
+
GCoder::GCode.const_get(classname).new("Code", {})
|
10
|
+
end
|
11
|
+
end
|
12
|
+
|
13
|
+
end
|
@@ -0,0 +1,56 @@
|
|
1
|
+
require 'rspec'
|
2
|
+
require 'spec_helper'
|
3
|
+
|
4
|
+
describe 'GCode program' do
|
5
|
+
|
6
|
+
it 'should maintain the correct position (in absolute mode) in map_with_context' do
|
7
|
+
program = GCoder::Parser.new.parse("""
|
8
|
+
G90
|
9
|
+
G0 X0 Y0 Z0 F140
|
10
|
+
G1 X42 Y13 Z22.2 F100.001
|
11
|
+
G0 X11.1 Y0 Z102
|
12
|
+
M0
|
13
|
+
""")
|
14
|
+
|
15
|
+
program.map_with_context.last.absolute?.should eq true
|
16
|
+
program.map_with_context {|cmd, ctx| ctx.position }.first.should eq [
|
17
|
+
[ 0, 0, 0 ],
|
18
|
+
[ 0, 0, 0 ],
|
19
|
+
[ 0, 0, 0 ],
|
20
|
+
[ 42, 13, 22.2 ],
|
21
|
+
[ 11.1, 0, 102 ]
|
22
|
+
]
|
23
|
+
end
|
24
|
+
|
25
|
+
it 'should maintain the correct position (in relative mode) in map_with_context' do
|
26
|
+
program = GCoder::Parser.new.parse("""
|
27
|
+
G91
|
28
|
+
G0 X0 Y0 Z0 F140
|
29
|
+
G1 X42 Y13 Z22.2 F100.001
|
30
|
+
G0 X11.1 Y0 Z102
|
31
|
+
M0
|
32
|
+
""")
|
33
|
+
|
34
|
+
program.map_with_context.last.absolute?.should eq false
|
35
|
+
program.map_with_context {|cmd, ctx| ctx.position }.first.should eq [
|
36
|
+
[ 0, 0, 0 ],
|
37
|
+
[ 0, 0, 0 ],
|
38
|
+
[ 0, 0, 0 ],
|
39
|
+
[ 42, 13, 22.2 ],
|
40
|
+
[ 53.1, 13, 124.2 ]
|
41
|
+
]
|
42
|
+
end
|
43
|
+
|
44
|
+
it 'should maintain the correct feedrate in map_with_context' do
|
45
|
+
program = GCoder::Parser.new.parse("""
|
46
|
+
G90
|
47
|
+
G0 X0 Y0 Z0 F140
|
48
|
+
G1 X42 Y13 Z22.2 F100.001
|
49
|
+
G0 X11.1 Y0 Z102
|
50
|
+
M0
|
51
|
+
""")
|
52
|
+
|
53
|
+
program.map_with_context {|cmd, ctx| ctx.feedrate }.first.should eq [ 0, 0, 140, 100.001, 100.001 ]
|
54
|
+
end
|
55
|
+
|
56
|
+
end
|
data/spec/parser_spec.rb
ADDED
@@ -0,0 +1,54 @@
|
|
1
|
+
require 'rspec'
|
2
|
+
require 'spec_helper'
|
3
|
+
|
4
|
+
describe 'GCode parser' do
|
5
|
+
|
6
|
+
before do
|
7
|
+
@parser = GCoder::Parser.new
|
8
|
+
end
|
9
|
+
|
10
|
+
it 'should return a GCode program' do
|
11
|
+
@parser.parse("G0 X0 Y0 Z0 F1").should be_an_instance_of GCoder::GCode::Program
|
12
|
+
end
|
13
|
+
|
14
|
+
it 'should parse rapid motion GCode' do
|
15
|
+
cmd = @parser.parse("G0 X1 Y22.22 Z33.3 F12").first
|
16
|
+
|
17
|
+
cmd.should be_an_instance_of GCoder::GCode::MoveRapid
|
18
|
+
cmd.feedrate.should eq 12
|
19
|
+
cmd.position.should eq [ 1, 22.22, 33.3 ]
|
20
|
+
end
|
21
|
+
|
22
|
+
it 'should parse feedrate motion GCode' do
|
23
|
+
cmd = @parser.parse("G1 X1 Y22.22 Z33.3 F12").first
|
24
|
+
|
25
|
+
cmd.should be_an_instance_of GCoder::GCode::MoveByFeedrate
|
26
|
+
cmd.feedrate.should eq 12
|
27
|
+
cmd.position.should eq [ 1, 22.22, 33.3 ]
|
28
|
+
end
|
29
|
+
|
30
|
+
it 'should omit empty lines' do
|
31
|
+
prog = @parser.parse("""
|
32
|
+
G0 X1 Y2 Z3
|
33
|
+
|
34
|
+
M0
|
35
|
+
""")
|
36
|
+
prog.length.should eq 2
|
37
|
+
end
|
38
|
+
|
39
|
+
it 'should parse comments' do
|
40
|
+
comments = [
|
41
|
+
"Hello World, this is a comment",
|
42
|
+
"Let's try another comment"
|
43
|
+
]
|
44
|
+
|
45
|
+
prog = @parser.parse(comments.map {|c| "(#{c})\n" }.join("\n"))
|
46
|
+
|
47
|
+
prog.length.should eq comments.length
|
48
|
+
prog.each_with_index do |cmd, idx|
|
49
|
+
cmd.should be_an_instance_of GCoder::GCode::Comment
|
50
|
+
cmd.text.should eq comments[idx]
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
end
|
data/spec/spec_helper.rb
ADDED
metadata
ADDED
@@ -0,0 +1,98 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: g-coder
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: '0.1'
|
5
|
+
prerelease:
|
6
|
+
platform: ruby
|
7
|
+
authors:
|
8
|
+
- 32leaves
|
9
|
+
autorequire:
|
10
|
+
bindir: bin
|
11
|
+
cert_chain: []
|
12
|
+
date: 2014-02-11 00:00:00.000000000 Z
|
13
|
+
dependencies:
|
14
|
+
- !ruby/object:Gem::Dependency
|
15
|
+
name: bundler
|
16
|
+
requirement: !ruby/object:Gem::Requirement
|
17
|
+
none: false
|
18
|
+
requirements:
|
19
|
+
- - ~>
|
20
|
+
- !ruby/object:Gem::Version
|
21
|
+
version: '1.5'
|
22
|
+
type: :development
|
23
|
+
prerelease: false
|
24
|
+
version_requirements: !ruby/object:Gem::Requirement
|
25
|
+
none: false
|
26
|
+
requirements:
|
27
|
+
- - ~>
|
28
|
+
- !ruby/object:Gem::Version
|
29
|
+
version: '1.5'
|
30
|
+
- !ruby/object:Gem::Dependency
|
31
|
+
name: rake
|
32
|
+
requirement: !ruby/object:Gem::Requirement
|
33
|
+
none: false
|
34
|
+
requirements:
|
35
|
+
- - ! '>='
|
36
|
+
- !ruby/object:Gem::Version
|
37
|
+
version: '0'
|
38
|
+
type: :development
|
39
|
+
prerelease: false
|
40
|
+
version_requirements: !ruby/object:Gem::Requirement
|
41
|
+
none: false
|
42
|
+
requirements:
|
43
|
+
- - ! '>='
|
44
|
+
- !ruby/object:Gem::Version
|
45
|
+
version: '0'
|
46
|
+
description: GCoder is designed to make scripting GCode filters easy, and to work
|
47
|
+
with Opal to enable web-based GCode editors/viewers.
|
48
|
+
email:
|
49
|
+
- info@32leav.es
|
50
|
+
executables: []
|
51
|
+
extensions: []
|
52
|
+
extra_rdoc_files: []
|
53
|
+
files:
|
54
|
+
- .gitignore
|
55
|
+
- Gemfile
|
56
|
+
- LICENSE
|
57
|
+
- README.md
|
58
|
+
- Rakefile
|
59
|
+
- gcoder.gemspec
|
60
|
+
- lib/gcoder.rb
|
61
|
+
- lib/gcoder/dialects/default.rb
|
62
|
+
- lib/gcoder/gcode.rb
|
63
|
+
- lib/gcoder/parser.rb
|
64
|
+
- lib/gcoder/version.rb
|
65
|
+
- spec/default_dialect_spec.rb
|
66
|
+
- spec/gcode_program_spec.rb
|
67
|
+
- spec/parser_spec.rb
|
68
|
+
- spec/spec_helper.rb
|
69
|
+
homepage: ''
|
70
|
+
licenses:
|
71
|
+
- MIT
|
72
|
+
post_install_message:
|
73
|
+
rdoc_options: []
|
74
|
+
require_paths:
|
75
|
+
- lib
|
76
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
77
|
+
none: false
|
78
|
+
requirements:
|
79
|
+
- - ! '>='
|
80
|
+
- !ruby/object:Gem::Version
|
81
|
+
version: '0'
|
82
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
83
|
+
none: false
|
84
|
+
requirements:
|
85
|
+
- - ! '>='
|
86
|
+
- !ruby/object:Gem::Version
|
87
|
+
version: '0'
|
88
|
+
requirements: []
|
89
|
+
rubyforge_project:
|
90
|
+
rubygems_version: 1.8.23
|
91
|
+
signing_key:
|
92
|
+
specification_version: 3
|
93
|
+
summary: GCoder is a Ruby library to deal with GCode in various flavours.
|
94
|
+
test_files:
|
95
|
+
- spec/default_dialect_spec.rb
|
96
|
+
- spec/gcode_program_spec.rb
|
97
|
+
- spec/parser_spec.rb
|
98
|
+
- spec/spec_helper.rb
|