laser-cutter 0.2.0

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.
@@ -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