Class: Graster
- Inherits:
-
Object
- Object
- Graster
- Defined in:
- lib/graster.rb,
lib/graster/image.rb,
lib/graster/runner.rb,
lib/graster/gcode_file.rb,
lib/graster/gmask_file.rb
Defined Under Namespace
Classes: GcodeFile, GmaskFile, Image, InvalidConfig, Runner
Constant Summary collapse
- ROOT2 =
Math.sqrt(2)
- OPTIONS =
{ :dpi => [[Float],"X,Y","Dots per inch of your device"], :on_range => [[Float], "MIN,MAX","Luminosity range for which the", "laser should be on"], :overshoot => [Float,"INCHES", "Distance the X axis should travel", "past the outer boundaries of the outer", "images. This needs to be wide enough", "so that the X axis doesn't start", "decelerating until after it has", "cleared the image"], :offset => [[Float],"X,Y", "Location for the bottom left corner", "of the bottom left tile. The X", "component of this setting must be", "equal to or greater than overshoot"], :repeat => [[Integer],"X,Y", "Number of times to repeat the image", "in the X and Y axes, respectively.", "Size of the tile(s) inches. Any nil", "value is calculated from the size of", "the bitmap"], :tile_spacing => [[Float],"X,Y", "X,Y gap between repeated tiles in", "inches"], :feed => [Float,"N", "Speed to move the X axis while", "burning, in inches/minute"], :cut_feed => [Float,"N", "Speed at which to cut out tiles"], :corner_radius => [Float,"N", "Radius of rounded corners for", "cutout, 0 for pointy corners"] }
- DEFAULTS =
{ :dpi => [500,500], # X,Y dots per inch of your device :on_range => [0.0,0.5], # Luminosity range for which the laser should be on :overshoot => 0.5, # Distance the X axis should travel past the outer boundaries of the outer images. # This needs to be wide enough so that the X axis doesn't start decelerating # until after it has cleared the image. :offset => [1.0,1.0], # X,Y location for the bottom left corner of the bottom left tile. # The X component of this setting must be equal to or greater than :overshoot. :repeat => [1,1], # Number of times to repeat the image in the X and Y axes, respectively. :tile_size => [false,false], # Size of the tile(s) inches. Any nil value is calculated from # the size of the bitmap. :tile_spacing => [0.125,0.125], # X,Y gap between repeated tiles in inches :feed => 120, # Speed to move the X axis while burning, in inches/minute :cut_feed => 20, # Speed at which to cut out tiles :corner_radius => 0 # Radius of rounded corners for cutout, 0 for pointy corners }
Instance Attribute Summary collapse
-
#config ⇒ Object
Returns the value of attribute config.
-
#image ⇒ Object
Returns the value of attribute image.
Instance Method Summary collapse
-
#axis_inches(axis, tile, pixel) ⇒ Object
convert tile + pixel coordinates to inches.
- #build_tiled_rows ⇒ Object
- #config_to_yaml ⇒ Object
- #debug(msg) ⇒ Object
- #generate_all_files ⇒ Object
-
#initialize(opts = {}) ⇒ Graster
constructor
A new instance of Graster.
-
#job_hash ⇒ Object
generate a unique id for this job.
- #load_config_file(pn) ⇒ Object
- #load_image_file(pn) ⇒ Object
- #merge_config(h) ⇒ Object
- #open_cut_file(&block) ⇒ Object
- #open_gcode_file(&block) ⇒ Object
- #open_gmask_file(&block) ⇒ Object
- #render_all(gcode, gmask, cuts) ⇒ Object
-
#render_all_cuts(gcode) ⇒ Object
render gcode to cut out the tiles.
-
#render_cut(gcode, x, y) ⇒ Object
cut out the tile with bottom left at x,y.
-
#render_tiled_image(gcode, gmask) ⇒ Object
render a complete tiled image to gcode and gmask streams.
-
#tiled_row_spans(y, forward = true) ⇒ Object
return a complete tiled row of spans converted to inches.
- #try_load_config_file(pn) ⇒ Object
- #try_load_default_config_file ⇒ Object
- #update_config ⇒ Object
- #validate_config ⇒ Object
- #x_inches(tile, pixel) ⇒ Object
- #y_inches(tile, pixel) ⇒ Object
Constructor Details
#initialize(opts = {}) ⇒ Graster
Returns a new instance of Graster.
301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 |
# File 'lib/graster.rb', line 301 def initialize opts={} self.config = DEFAULTS.dup if opts[:config_file] self.merge_config load_config_file opts[:config_file] elsif opts[:default_config_file] && c = try_load_default_config_file self.merge_config c end self.merge_config opts[:config] if opts[:config] @debug = opts[:debug] if opts[:image] image = opts[:image] elsif opts[:image_file] load_image_file opts[:image_file] end end |
Instance Attribute Details
#config ⇒ Object
Returns the value of attribute config.
105 106 107 |
# File 'lib/graster.rb', line 105 def config @config end |
#image ⇒ Object
Returns the value of attribute image.
116 117 118 |
# File 'lib/graster.rb', line 116 def image @image end |
Instance Method Details
#axis_inches(axis, tile, pixel) ⇒ Object
convert tile + pixel coordinates to inches
139 140 141 |
# File 'lib/graster.rb', line 139 def axis_inches axis, tile, pixel @offset[axis] + tile*@tile_interval[axis] + pixel*@scale[axis] end |
#build_tiled_rows ⇒ Object
174 175 176 177 178 |
# File 'lib/graster.rb', line 174 def build_tiled_rows forward = false @tiled_rows = [] @image.size[1].times {|y| @tiled_rows << tiled_row_spans(y, (forward = !forward)) } end |
#config_to_yaml ⇒ Object
293 294 295 |
# File 'lib/graster.rb', line 293 def config_to_yaml @config.map {|k,v| "#{k}: #{v.inspect}\n" }.join end |
#debug(msg) ⇒ Object
297 298 299 |
# File 'lib/graster.rb', line 297 def debug msg STDERR.puts msg if @debug end |
#generate_all_files ⇒ Object
281 282 283 284 285 286 287 288 289 290 291 |
# File 'lib/graster.rb', line 281 def generate_all_files open_gcode_file do |gcode| open_gmask_file do |gmask| render_tiled_image gcode, gmask end end open_cut_file do |cut| render_all_cuts cut end end |
#job_hash ⇒ Object
generate a unique id for this job
181 182 183 |
# File 'lib/graster.rb', line 181 def job_hash [@image,@config].hash end |
#load_config_file(pn) ⇒ Object
130 131 132 |
# File 'lib/graster.rb', line 130 def load_config_file pn try_load_config_file pn or raise "config file not found '#{pn}'" end |
#load_image_file(pn) ⇒ Object
134 135 136 |
# File 'lib/graster.rb', line 134 def load_image_file pn self.image = Image.from_file(pn) end |
#merge_config(h) ⇒ Object
98 99 100 101 102 103 |
# File 'lib/graster.rb', line 98 def merge_config h @config ||= DEFAULTS.dup h.each {|k,v| @config[k] = v if DEFAULTS[k] } update_config return h end |
#open_cut_file(&block) ⇒ Object
277 278 279 |
# File 'lib/graster.rb', line 277 def open_cut_file &block io = GcodeFile.open "#{@image.filename}.cut.ngc", "w", &block end |
#open_gcode_file(&block) ⇒ Object
269 270 271 |
# File 'lib/graster.rb', line 269 def open_gcode_file &block io = GcodeFile.open "#{@image.filename}.raster.ngc", "w", &block end |
#open_gmask_file(&block) ⇒ Object
273 274 275 |
# File 'lib/graster.rb', line 273 def open_gmask_file &block io = GmaskFile.open "#{@image.filename}.raster.gmask", "w", &block end |
#render_all(gcode, gmask, cuts) ⇒ Object
264 265 266 267 |
# File 'lib/graster.rb', line 264 def render_all gcode, gmask, cuts render_tiled_image gcode, gmask render_all_cuts cuts end |
#render_all_cuts(gcode) ⇒ Object
render gcode to cut out the tiles
254 255 256 257 258 259 260 261 262 |
# File 'lib/graster.rb', line 254 def render_all_cuts gcode gcode.preamble :feed => @config[:cut_feed] @config[:repeat][1].times do |ytile| @config[:repeat][0].times do |xtile| render_cut gcode, x_inches(xtile, 0), y_inches(ytile, 0) end end gcode.epilogue end |
#render_cut(gcode, x, y) ⇒ Object
cut out the tile with bottom left at x,y
223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 |
# File 'lib/graster.rb', line 223 def render_cut gcode, x, y radius = @config[:corner_radius] left = x bottom = y right = x+@config[:tile_size][0] top = y+@config[:tile_size][1] gcode.instance_eval do if radius && radius > 0 jog :x => left, :y => bottom+radius move :x => left, :y => top-radius, :laser => true turn_cw :x => left+radius, :y => top, :i => radius move :x => right-radius, :y => top turn_cw :x => right, :y => top-radius, :j => -radius move :x => right, :y => bottom+radius turn_cw :x => right-radius, :y => bottom, :i => -radius move :x => left+radius, :y => bottom turn_cw :x => left, :y => bottom+radius, :j => radius nc :laser => false else jog :x => left, :y => bottom move :x => left, :y => top, :laser => true move :x => right, :y => top move :x => right, :y => bottom move :x => left, :y => bottom nc :laser => false end end end |
#render_tiled_image(gcode, gmask) ⇒ Object
render a complete tiled image to gcode and gmask streams
186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 |
# File 'lib/graster.rb', line 186 def render_tiled_image gcode, gmask debug "rendering tiled image" job_id = job_hash hyst = -@scale[0]/2 gcode.comment "raster gcode for job #{job_id}" gcode.comment "image: #{@image.filename} #{@image.size.inspect}" gcode.comment "config: #{@config.inspect}" gcode.preamble :feed => @config[:feed], :mask => true gmask.preamble @config[:repeat][1].times do |ytile| debug "begin tile row #{ytile}" ypix = 0 (0...@tiled_rows).each do |spans| debug "pixel row #{ypix} is empty" if spans.empty? unless spans.empty? = y_inches(ytile, ypix) forward = spans[0][0] < spans[-1][1] dir = forward ? 1 : -1 debug "pixel row #{ypix} at #{} inches going #{forward ? 'forward' : 'backward'} with #{spans.size} spans" gcode.g0 :x => spans[0][0] - dir*@config[:overshoot], :y => gcode.g1 :x => spans[-1][1] + dir*@config[:overshoot], :y => gmask.begin_row forward spans.each {|span| gmask.span forward, span[0]+hyst, span[1]+hyst } end # unless spans.empty? ypix += 1 end # @image.each_row debug "end tile row #{ytile}" end # @config[:repeat][i].times gcode.epilogue end |
#tiled_row_spans(y, forward = true) ⇒ Object
return a complete tiled row of spans converted to inches
152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 |
# File 'lib/graster.rb', line 152 def tiled_row_spans y, forward=true spans = @image.spans[y] return spans if spans.empty? tiled_spans = [] if forward @config[:repeat][0].times do |tile| spans.each do |span| tiled_spans << [x_inches(tile,span[0]), x_inches(tile,span[1])] end end else (0...@config[:repeat][0]).to_a.reverse.each do |tile| spans.reverse.each do |span| tiled_spans << [x_inches(tile,span[1]), x_inches(tile,span[0])] end end end return tiled_spans end |
#try_load_config_file(pn) ⇒ Object
118 119 120 121 122 123 124 |
# File 'lib/graster.rb', line 118 def try_load_config_file pn if File.exist?(pn) c = {} YAML.load_file(pn).each {|k,v| c[k.intern] = v } return c end end |
#try_load_default_config_file ⇒ Object
126 127 128 |
# File 'lib/graster.rb', line 126 def try_load_default_config_file try_load_config_file './graster.yml' end |
#update_config ⇒ Object
70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 |
# File 'lib/graster.rb', line 70 def update_config @scale = @config[:dpi].map{|n| 1.0/n } @offset = @config[:offset] if @image 2.times {|i| @config[:tile_size][i] ||= @image.size[i]*@scale[i] } @tile_interval = [] 2.times {|i| @tile_interval << @config[:tile_size][i] + @config[:tile_spacing][i] } @tile_interval end @on_range = Range.new Image.f_to_pix(@config[:on_range].first), Image.f_to_pix(@config[:on_range].last) end |
#validate_config ⇒ Object
87 88 89 |
# File 'lib/graster.rb', line 87 def validate_config raise InvalidConfig.new "X offset (#{@config[:offset][0]}) must be greater or equal to overshoot (#{@config[:overshoot]})" end |
#x_inches(tile, pixel) ⇒ Object
143 144 145 |
# File 'lib/graster.rb', line 143 def x_inches tile, pixel axis_inches 0, tile, pixel end |
#y_inches(tile, pixel) ⇒ Object
147 148 149 |
# File 'lib/graster.rb', line 147 def y_inches tile, pixel axis_inches 1, tile, pixel end |