laser-cutter 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,59 @@
1
+ module Laser
2
+ module Cutter
3
+ module Geometry
4
+ class Rect < Line
5
+
6
+ attr_accessor :sides, :vertices
7
+
8
+ def self.[] p1, p2
9
+ Rect.new(p1, p2)
10
+ end
11
+
12
+ def self.create(point, w, h, name = nil)
13
+ r = Rect.new(point, Point[point.x + w, point.y + h])
14
+ r.name = name
15
+ r
16
+ end
17
+
18
+ def self.from_line(line)
19
+ Rect.new(line.p1, line.p2)
20
+ end
21
+
22
+ def initialize(*args)
23
+ super(*args)
24
+ relocate!
25
+ end
26
+
27
+ def relocate!
28
+ super
29
+ self.vertices = []
30
+ vertices << p1
31
+ vertices << p1.move_by(w, 0)
32
+ vertices << p2
33
+ vertices << p1.move_by(0, h)
34
+ self.sides = []
35
+ vertices.each_with_index do |v, index|
36
+ sides << Line.new(v, vertices[(index + 1) % vertices.size])
37
+ end
38
+ self
39
+ end
40
+
41
+ def w
42
+ p2.x - p1.x
43
+ end
44
+
45
+ def h
46
+ p2.y - p1.y
47
+ end
48
+
49
+
50
+ def to_s
51
+ "#{sprintf "%3d", w}(w)x#{sprintf "%3d", h}(h) @ #{position.to_s}"
52
+ end
53
+
54
+ end
55
+
56
+ end
57
+
58
+ end
59
+ end
@@ -0,0 +1,89 @@
1
+ module Laser
2
+ module Cutter
3
+ module Geometry
4
+ class Tuple
5
+ attr_accessor :coords
6
+ PRECISION = 0.000001
7
+
8
+ def initialize(*args)
9
+ args = customize_args(args)
10
+ x = args.first
11
+ if x.is_a?(String)
12
+ parse_string(x)
13
+ elsif x.is_a?(Hash)
14
+ parse_hash(x)
15
+ elsif x.is_a?(Array)
16
+ self.coords = x.clone
17
+ else
18
+ self.coords = args.clone
19
+ end
20
+
21
+ self.coords.map!(&:to_f)
22
+ end
23
+
24
+ def customize_args(args)
25
+ args
26
+ end
27
+
28
+ def separator
29
+ raise NotImplementedError
30
+ # 'x'
31
+ end
32
+
33
+ def hash_keys
34
+ raise NotImplementedError
35
+ # [:x, :y, :z] or [:h, :w, :d]
36
+ end
37
+
38
+ def to_a
39
+ self.coords
40
+ end
41
+
42
+ def to_s
43
+ "{#{coords.map { |a| sprintf("%.5f", a) }.join(separator)}}"
44
+ end
45
+
46
+ def valid?
47
+ raise "Have nil value: #{self.inspect}" if coords.any? { |c| c.nil? }
48
+ true
49
+ end
50
+
51
+ def eql?(other)
52
+ return false unless other.respond_to?(:coords)
53
+ equal = true
54
+ self.coords.each_with_index do |c, i|
55
+ if (c - other.coords[i])**2 > PRECISION
56
+ equal = false
57
+ break
58
+ end
59
+ end
60
+ equal
61
+ end
62
+
63
+ def clone
64
+ clone = super
65
+ clone.coords = self.coords.clone
66
+ clone
67
+ end
68
+
69
+ private
70
+
71
+ #
72
+ # convert from, eg "100,50" to [100.0, 50.0],
73
+ # and then to a new instance.
74
+ #
75
+ def parse_string string
76
+ self.coords = string.split(separator).map(&:to_f)
77
+ end
78
+
79
+ def parse_hash hash
80
+ self.coords = []
81
+ hash_keys.each { |k| self.coords << hash[k] }
82
+ end
83
+
84
+ end
85
+
86
+
87
+ end
88
+ end
89
+ end
@@ -0,0 +1,19 @@
1
+ module Laser
2
+ module Cutter
3
+ module Renderer
4
+ class AbstractRenderer < Struct.new(:subject, :options)
5
+ def render pdf = nil
6
+ raise 'Abstract method'
7
+ end
8
+
9
+ def units
10
+ options.units.to_sym || :mm
11
+ end
12
+ end
13
+ end
14
+ end
15
+ end
16
+
17
+ require 'laser-cutter/renderer/line_renderer'
18
+ require 'laser-cutter/renderer/rect_renderer'
19
+ require 'laser-cutter/renderer/box_renderer'
@@ -0,0 +1,69 @@
1
+ require 'json'
2
+ module Laser
3
+ module Cutter
4
+ module Renderer
5
+ class BoxRenderer < AbstractRenderer
6
+
7
+ def box
8
+ subject
9
+ end
10
+
11
+ def render pdf = nil
12
+ pdf = Prawn::Document.new(:margin => options.margin.send(options.units),
13
+ :page_size => options.page_size,
14
+ :page_layout => options.page_layout.to_sym)
15
+
16
+ header = <<-EOF
17
+
18
+ Produced with Laser Cutter Ruby Gem (v#{Laser::Cutter::VERSION})
19
+ Credits to Prawn (for ruby PDF generation),
20
+ and BoxMaker (for the inspiration).
21
+ © 2014 Konstantin Gredeskoul, MIT license.
22
+ https://github.com/kigster/laser-cutter
23
+ Generated at #{Time.new}.
24
+ EOF
25
+
26
+ renderer = self
27
+
28
+ pdf.instance_eval do
29
+ self.line_width = renderer.options.stroke.send(renderer.options.units.to_sym)
30
+ float do
31
+ bounding_box([0, 50], :width => 150, :height => 40) do
32
+ stroke_color '0080FF'
33
+ stroke_bounds
34
+
35
+ indent 10 do
36
+ font('Courier', :size => 5) do
37
+ text header, :color => "0080FF"
38
+ end
39
+ end
40
+ end
41
+ bounding_box([480, 120], :width => 110, :height => 110) do
42
+ stroke_color '00DF20'
43
+ stroke_bounds
44
+ indent 10 do
45
+ font('Courier', :size => 6) do
46
+ out = JSON.pretty_generate(renderer.options.to_hash).gsub(/[\{\}",]/,'')
47
+ text out, :color => "00DF20"
48
+ end
49
+ end
50
+ end
51
+ end
52
+
53
+ stroke_color "000000"
54
+ renderer.box.notches.each do |notch|
55
+ LineRenderer.new(notch, renderer.options).render(self)
56
+ end
57
+
58
+ render_file(renderer.options.file)
59
+ end
60
+
61
+ if options.verbose
62
+ puts "file #{options.file} has been written."
63
+ end
64
+ end
65
+
66
+ end
67
+ end
68
+ end
69
+ end
@@ -0,0 +1,16 @@
1
+ module Laser
2
+ module Cutter
3
+ module Renderer
4
+ class LineRenderer < AbstractRenderer
5
+ def line
6
+ subject
7
+ end
8
+ def render pdf = nil
9
+ pdf.stroke { pdf.line [line.p1.x, line.p1.y].map{ |p| p.send(units) },
10
+ [line.p2.x, line.p2.y].map{ |p| p.send(units) }}
11
+
12
+ end
13
+ end
14
+ end
15
+ end
16
+ end
@@ -0,0 +1,17 @@
1
+ module Laser
2
+ module Cutter
3
+ module Renderer
4
+ class RectRenderer < AbstractRenderer
5
+ def rect
6
+ subject
7
+ end
8
+
9
+ def render pdf
10
+ rect.sides.each do |side|
11
+ LineRenderer.new(side, options).render(pdf)
12
+ end
13
+ end
14
+ end
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,5 @@
1
+ module Laser
2
+ module Cutter
3
+ VERSION = "0.2.0"
4
+ end
5
+ end
@@ -0,0 +1,31 @@
1
+ require_relative 'spec_helper'
2
+
3
+ module Laser
4
+ module Cutter
5
+ describe Box do
6
+ let(:config) { {'width' => 50, 'height' => 60, 'depth' => 70, 'margin' => 5, 'padding' => 3 } }
7
+ let(:box1) { Box.new(config.merge('thickness' => 6, 'notch' => 10)) }
8
+ let(:box2) { Box.new(config.merge('thickness' => 6, )) }
9
+
10
+ context '#initialize' do
11
+ it 'should initialize with passed in parameters' do
12
+ expect(box1.w).to eq(50.0)
13
+ expect(box1.thickness).to eq(6.0)
14
+ expect(box1.notch_width).to eq(10.0)
15
+ end
16
+
17
+ it 'should initialize with default notch' do
18
+ expect(box2.notch_width).to eq(70.0 / 5.0)
19
+ end
20
+ end
21
+
22
+ context '#notches' do
23
+ it 'should generate notches' do
24
+ expect(box1.notches).to_not be_nil
25
+ expect(box1.notches.size).to eql(320)
26
+ end
27
+ end
28
+
29
+ end
30
+ end
31
+ end
@@ -0,0 +1,28 @@
1
+ require_relative 'spec_helper'
2
+
3
+ module Laser
4
+ module Cutter
5
+ describe Configuration do
6
+ let(:config) { Laser::Cutter::Configuration.new(opts)}
7
+
8
+ context 'option parsing' do
9
+ let(:opts) { { "size" => "2x3x2/0.125/0.5", "inches" => true} }
10
+ it 'should be able to parse size options' do
11
+ expect(config.width).to eql(2.0)
12
+ expect(config.height).to eql(3.0)
13
+ expect(config.depth).to eql(2.0)
14
+ expect(config.thickness).to eql(0.125)
15
+ expect(config.notch).to eql(0.5)
16
+ end
17
+ end
18
+ context 'validate' do
19
+ let(:opts) {{ "height" => "23" }}
20
+ it 'should be able to validate missing options' do
21
+ expect(config.height).to eql(23.0)
22
+ expect { config.validate! } .to raise_error(RuntimeError)
23
+ end
24
+ end
25
+
26
+ end
27
+ end
28
+ end
@@ -0,0 +1,28 @@
1
+ require_relative 'spec_helper'
2
+
3
+ module Laser
4
+ module Cutter
5
+ module Geometry
6
+
7
+ describe Dimensions do
8
+ let(:dim1) { Dimensions.new(20, 10, 50) }
9
+
10
+ context 'creation' do
11
+ context 'from string' do
12
+ let(:dim2) { Dimensions.new "20x10x50" }
13
+ it 'should instantiate correctly from a string' do
14
+ expect(dim2).to_not be_nil
15
+ expect(dim2).to eql(dim1)
16
+ end
17
+ end
18
+
19
+ context 'from hash' do
20
+ it 'should instantiate correctly from a hash' do
21
+ expect(Dimensions.new(h: 10, w: 20, d: 50)).to eql(dim1)
22
+ end
23
+ end
24
+ end
25
+ end
26
+ end
27
+ end
28
+ end
@@ -0,0 +1,79 @@
1
+ require_relative 'spec_helper'
2
+
3
+ module Laser
4
+ module Cutter
5
+ module Geometry
6
+ describe Line do
7
+ let(:p1) { Point.new(1, 1) }
8
+ let(:p2) { Point.new(7, 11) }
9
+ let(:center) { Point.new( (7 + 1) / 2, (11 + 1) / 2) }
10
+ let(:line) { Line.new(p1, p2) }
11
+
12
+ context '#center' do
13
+ it 'should calculate' do
14
+ expect(line.center).to eql(center)
15
+ end
16
+ end
17
+
18
+ context '#initialize' do
19
+ let(:line2) { Line.new(from: [1,1], to: [7,11])}
20
+ let(:line3) { Line.new(from: Point.new(1,1), to: Point.new(7,11))}
21
+ it 'should create' do
22
+ expect(line2.p1).to eql(Point.new(1,1))
23
+ expect(line2.p2).to eql(Point.new(7,11))
24
+ end
25
+ it 'should properly equal identical line' do
26
+ expect(line).to eql(line2)
27
+ expect(line).to eql(line3)
28
+ end
29
+
30
+ end
31
+
32
+ context '#length' do
33
+ let(:line1) { Line.new(Point.new(0, 0), Point.new(0, 10)) }
34
+ let(:line2) { Line.new(Point.new(0, 0), Point.new(-10, 0)) }
35
+ let(:line3) { Line.new(Point.new(0, 0), Point.new(3, 4)) }
36
+ it 'should calculate' do
37
+ expect(line1.length).to be_within(0.001).of(10)
38
+ expect(line2.length).to be_within(0.001).of(10)
39
+ expect(line3.length).to be_within(0.001).of(5)
40
+ end
41
+ end
42
+
43
+ context 'ordering and equality' do
44
+ let(:l1) { Line.new(Point[0,0], Point[10,10]) }
45
+ let(:l2) { Line.new(Point[0,1], Point[10,10]) }
46
+ let(:l3) { Line.new(Point[0,0], Point[11,10]) }
47
+ let(:l4) { Line.new(Point[20,20], Point[1,1]) }
48
+ let(:l5) { Line.new(Point[11,10], Point[0,0]) }
49
+ it 'should detect equality' do
50
+ expect(l1).to eql(Line.new(l1.p1, l1.p2))
51
+ expect(l1).to_not eql(Line.new(l1.p1, Point[2,4]))
52
+ expect(l5).to eql(l3)
53
+ expect(l5.hash).to eql(l3.hash)
54
+ end
55
+
56
+ it 'should properly compare' do
57
+ list = [l4,l3,l1,l2]
58
+ list.sort!
59
+ expect(list).to eql([l1,l3,l2,l4])
60
+ end
61
+
62
+ it 'should properly uniq' do
63
+ list = [l4, l1, l4, l2, l3, l3, l2, l1]
64
+ list.sort!.uniq!
65
+ expect(list).to eql([l1,l3,l2,l4])
66
+ end
67
+
68
+ it 'should properly deduplicate' do
69
+ list = [l1, l4, l2, l3, l5, l3, l1, l3, l3, l2]
70
+ new_list = PathGenerator.deduplicate(list)
71
+ expect(new_list).to eql([l4])
72
+ end
73
+
74
+ end
75
+
76
+ end
77
+ end
78
+ end
79
+ end