g-coder 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.
- 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
|