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.
298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 |
# File 'lib/graster.rb', line 298 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 |
# File 'lib/graster.rb', line 174 def build_tiled_rows forward = false @tiled_rows = @image.size[1].times.map {|y| tiled_row_spans y, (forward = !forward) } end |
#config_to_yaml ⇒ Object
290 291 292 |
# File 'lib/graster.rb', line 290 def config_to_yaml @config.map {|k,v| "#{k}: #{v.inspect}\n" }.join end |
#debug(msg) ⇒ Object
294 295 296 |
# File 'lib/graster.rb', line 294 def debug msg STDERR.puts msg if @debug end |
#generate_all_files ⇒ Object
278 279 280 281 282 283 284 285 286 287 288 |
# File 'lib/graster.rb', line 278 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
180 181 182 |
# File 'lib/graster.rb', line 180 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
274 275 276 |
# File 'lib/graster.rb', line 274 def open_cut_file &block io = GcodeFile.open "#{@image.filename}.cut.ngc", "w", &block end |
#open_gcode_file(&block) ⇒ Object
266 267 268 |
# File 'lib/graster.rb', line 266 def open_gcode_file &block io = GcodeFile.open "#{@image.filename}.raster.ngc", "w", &block end |
#open_gmask_file(&block) ⇒ Object
270 271 272 |
# File 'lib/graster.rb', line 270 def open_gmask_file &block io = GmaskFile.open "#{@image.filename}.raster.gmask", "w", &block end |
#render_all(gcode, gmask, cuts) ⇒ Object
261 262 263 264 |
# File 'lib/graster.rb', line 261 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
251 252 253 254 255 256 257 258 259 |
# File 'lib/graster.rb', line 251 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
220 221 222 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 |
# File 'lib/graster.rb', line 220 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
185 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 |
# File 'lib/graster.rb', line 185 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}" @tiled_rows.each_with_index do |spans, ypix| 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? 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 @config[:repeat][0].times.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 |