laser-cutter 1.0.5 → 2.0.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.
- checksums.yaml +5 -5
- data/.envrc +2 -0
- data/.github/workflows/lint.yml +22 -0
- data/.github/workflows/rspec.yml +22 -0
- data/.gitignore +5 -5
- data/.plans/001.00-dry-cli-migration/plan.md +29 -0
- data/.relaxed_rubocop.yml +153 -0
- data/.rspec +1 -0
- data/.rubocop.yml +31 -0
- data/.rubocop_todo.yml +90 -0
- data/.ruby-version +1 -0
- data/CLAUDE.md +62 -0
- data/CONTRIBUTING.md +2 -4
- data/Gemfile +14 -0
- data/README.md +110 -87
- data/Rakefile +27 -13
- data/docs/badges/coverage_badge.svg +21 -0
- data/exe/laser-cutter +8 -0
- data/exe/lc +1 -0
- data/justfile +97 -0
- data/laser-cutter.gemspec +26 -14
- data/lib/laser/cutter/aggregator.rb +101 -0
- data/lib/laser/cutter/box.rb +173 -0
- data/lib/laser/cutter/cli/command.rb +46 -0
- data/lib/laser/cutter/cli/completion.rb +21 -0
- data/lib/laser/cutter/cli/config_file.rb +36 -0
- data/lib/laser/cutter/cli/examples.rb +42 -0
- data/lib/laser/cutter/cli/generate.rb +90 -0
- data/lib/laser/cutter/cli/help.rb +20 -0
- data/lib/laser/cutter/cli/page_sizes.rb +19 -0
- data/lib/laser/cutter/cli/version.rb +15 -0
- data/lib/laser/cutter/cli.rb +49 -0
- data/lib/laser/cutter/configuration.rb +101 -0
- data/lib/{laser-cutter → laser/cutter}/geometry/dimensions.rb +2 -3
- data/lib/{laser-cutter/geometry/shape → laser/cutter/geometry}/line.rb +19 -18
- data/lib/{laser-cutter → laser/cutter}/geometry/point.rb +4 -2
- data/lib/{laser-cutter/geometry/shape → laser/cutter/geometry}/rect.rb +6 -7
- data/lib/{laser-cutter → laser/cutter}/geometry/shape.rb +7 -7
- data/lib/{laser-cutter → laser/cutter}/geometry/tuple.rb +40 -36
- data/lib/laser/cutter/launcher.rb +40 -0
- data/lib/laser/cutter/missing_option.rb +7 -0
- data/lib/laser/cutter/notching/base.rb +19 -0
- data/lib/{laser-cutter → laser/cutter}/notching/edge.rb +23 -22
- data/lib/laser/cutter/notching/infinite_iterator.rb +27 -0
- data/lib/{laser-cutter → laser/cutter}/notching/path_generator.rb +76 -94
- data/lib/laser/cutter/notching/shift.rb +17 -0
- data/lib/laser/cutter/page_manager.rb +50 -0
- data/lib/{laser-cutter → laser/cutter}/renderer/base.rb +3 -2
- data/lib/laser/cutter/renderer/box_renderer.rb +40 -0
- data/lib/laser/cutter/renderer/layout_renderer.rb +61 -0
- data/lib/laser/cutter/renderer/line_renderer.rb +21 -0
- data/lib/laser/cutter/renderer/meta_renderer.rb +83 -0
- data/lib/{laser-cutter → laser/cutter}/renderer/rect_renderer.rb +3 -1
- data/lib/laser/cutter/renderer/svg_renderer.rb +65 -0
- data/lib/laser/cutter/renderer.rb +24 -0
- data/lib/laser/cutter/units_converter.rb +15 -0
- data/lib/laser/cutter/version.rb +7 -0
- data/lib/laser/cutter/zero_value_not_allowed.rb +7 -0
- data/lib/laser/cutter.rb +29 -0
- data/lib/laser-cutter.rb +2 -14
- data/lib/laser_cutter.rb +3 -0
- data/spec/laser/cutter/aggregator_spec.rb +70 -0
- data/spec/laser/cutter/box_spec.rb +61 -0
- data/spec/laser/cutter/cli/config_file_spec.rb +31 -0
- data/spec/laser/cutter/cli_spec.rb +186 -0
- data/spec/laser/cutter/configuration_spec.rb +101 -0
- data/spec/{dimensions_spec.rb → laser/cutter/dimensions_spec.rb} +8 -6
- data/spec/laser/cutter/edge_spec.rb +64 -0
- data/spec/{line_spec.rb → laser/cutter/line_spec.rb} +26 -20
- data/spec/{page_manager_spec.rb → laser/cutter/page_manager_spec.rb} +27 -11
- data/spec/{path_generator_spec.rb → laser/cutter/path_generator_spec.rb} +18 -14
- data/spec/{point_spec.rb → laser/cutter/point_spec.rb} +27 -22
- data/spec/{rect_spec.rb → laser/cutter/rect_spec.rb} +9 -5
- data/spec/laser/cutter/renderer/layout_renderer_spec.rb +102 -0
- data/spec/laser/cutter/renderer/svg_renderer_spec.rb +51 -0
- data/spec/laser/cutter/renderer_spec.rb +19 -0
- data/spec/spec_helper.rb +35 -22
- data/spec/support/aruba.rb +10 -0
- metadata +202 -76
- data/.travis.yml +0 -26
- data/BOXMAKER.md +0 -51
- data/LICENSE +0 -22
- data/bin/laser-cutter +0 -19
- data/lib/laser-cutter/aggregator.rb +0 -57
- data/lib/laser-cutter/box.rb +0 -183
- data/lib/laser-cutter/cli/opt_parser.rb +0 -131
- data/lib/laser-cutter/cli/serializer.rb +0 -51
- data/lib/laser-cutter/configuration.rb +0 -83
- data/lib/laser-cutter/face.rb +0 -18
- data/lib/laser-cutter/geometry.rb +0 -11
- data/lib/laser-cutter/notching.rb +0 -9
- data/lib/laser-cutter/page_manager.rb +0 -43
- data/lib/laser-cutter/renderer/box_renderer.rb +0 -36
- data/lib/laser-cutter/renderer/layout_renderer.rb +0 -59
- data/lib/laser-cutter/renderer/line_renderer.rb +0 -17
- data/lib/laser-cutter/renderer/meta_renderer.rb +0 -77
- data/lib/laser-cutter/renderer.rb +0 -14
- data/lib/laser-cutter/version.rb +0 -5
- data/spec/aggregator_spec.rb +0 -65
- data/spec/box_spec.rb +0 -39
- data/spec/configuration_spec.rb +0 -81
- data/spec/edge_spec.rb +0 -43
- data/spec/renderer_spec.rb +0 -72
- /data/docs/{comparison.jpg → images/comparison.jpg} +0 -0
|
@@ -1,39 +1,43 @@
|
|
|
1
|
-
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'spec_helper'
|
|
2
4
|
|
|
3
5
|
module Laser
|
|
4
6
|
module Cutter
|
|
5
7
|
module Geometry
|
|
6
|
-
describe Line do
|
|
8
|
+
RSpec.describe Line do
|
|
7
9
|
let(:p1) { Point.new(1, 1) }
|
|
8
10
|
let(:p2) { Point.new(7, 11) }
|
|
9
11
|
let(:center) { Point.new((7 + 1) / 2, (11 + 1) / 2) }
|
|
10
12
|
let(:line) { Line.new(p1, p2) }
|
|
11
13
|
|
|
12
|
-
|
|
13
|
-
it '
|
|
14
|
+
describe '#center' do
|
|
15
|
+
it 'calculates' do
|
|
14
16
|
expect(line.center).to eql(center)
|
|
15
17
|
end
|
|
16
18
|
end
|
|
17
19
|
|
|
18
|
-
|
|
20
|
+
describe '#initialize' do
|
|
19
21
|
let(:line2) { Line.new(from: [1, 1], to: [7, 11]) }
|
|
20
22
|
let(:line3) { Line.new(from: Point.new(1, 1), to: Point.new(7, 11)) }
|
|
21
|
-
|
|
23
|
+
|
|
24
|
+
it 'creates' do
|
|
22
25
|
expect(line2.p1).to eql(Point.new(1, 1))
|
|
23
26
|
expect(line2.p2).to eql(Point.new(7, 11))
|
|
24
27
|
end
|
|
25
|
-
|
|
28
|
+
|
|
29
|
+
it 'properlies equal identical line' do
|
|
26
30
|
expect(line).to eql(line2)
|
|
27
31
|
expect(line).to eql(line3)
|
|
28
32
|
end
|
|
29
|
-
|
|
30
33
|
end
|
|
31
34
|
|
|
32
|
-
|
|
35
|
+
describe '#length' do
|
|
33
36
|
let(:line1) { Line.new(Point.new(0, 0), Point.new(0, 10)) }
|
|
34
37
|
let(:line2) { Line.new(Point.new(0, 0), Point.new(-10, 0)) }
|
|
35
38
|
let(:line3) { Line.new(Point.new(0, 0), Point.new(3, 4)) }
|
|
36
|
-
|
|
39
|
+
|
|
40
|
+
it 'calculates' do
|
|
37
41
|
expect(line1.length).to be_within(0.001).of(10)
|
|
38
42
|
expect(line2.length).to be_within(0.001).of(10)
|
|
39
43
|
expect(line3.length).to be_within(0.001).of(5)
|
|
@@ -46,25 +50,27 @@ module Laser
|
|
|
46
50
|
let(:l3) { Line.new(Point[0, 0], Point[11, 10]) }
|
|
47
51
|
let(:l4) { Line.new(Point[20, 20], Point[1, 1]) }
|
|
48
52
|
let(:l5) { Line.new(Point[11, 10], Point[0, 0]) }
|
|
49
|
-
|
|
53
|
+
|
|
54
|
+
it 'detects equality' do
|
|
50
55
|
expect(l1).to eql(Line.new(l1.p1, l1.p2))
|
|
51
|
-
expect(l1).
|
|
56
|
+
expect(l1).not_to eql(Line.new(l1.p1, Point[2, 4]))
|
|
52
57
|
expect(l5).to eql(l3)
|
|
53
58
|
expect(l5.hash).to eql(l3.hash)
|
|
54
59
|
end
|
|
55
60
|
|
|
56
|
-
it '
|
|
61
|
+
it 'properlies compare' do
|
|
57
62
|
list = [l4, l3, l1, l2]
|
|
58
63
|
list.sort!
|
|
59
64
|
expect(list).to eql([l1, l3, l2, l4])
|
|
60
65
|
end
|
|
61
66
|
|
|
62
|
-
it '
|
|
67
|
+
it 'properlies uniq' do
|
|
63
68
|
list = [l4, l1, l4, l2, l3, l3, l2, l1]
|
|
64
69
|
list.sort!.uniq!
|
|
65
70
|
expect(list).to eql([l1, l3, l2, l4])
|
|
66
71
|
end
|
|
67
72
|
end
|
|
73
|
+
|
|
68
74
|
context 'overlaps and such' do
|
|
69
75
|
let(:l1) { Line.new(Point[0, 0], Point[10, 0]) }
|
|
70
76
|
let(:l2) { Line.new(Point[7, 0], Point[15, 0]) }
|
|
@@ -72,9 +78,8 @@ module Laser
|
|
|
72
78
|
let(:l4) { Line.new(Point[0, 1], Point[0, 3]) }
|
|
73
79
|
let(:l5) { Line.new(Point[0, -1], Point[0, 2]) }
|
|
74
80
|
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
it 'should detect overlap' do
|
|
81
|
+
describe '#overlaps?' do
|
|
82
|
+
it 'detects overlap' do
|
|
78
83
|
expect(l1.overlaps?(l2)).to be_truthy
|
|
79
84
|
expect(l1.overlaps?(l3)).to be_falsey
|
|
80
85
|
expect(l2.overlaps?(l3)).to be_falsey
|
|
@@ -84,9 +89,10 @@ module Laser
|
|
|
84
89
|
end
|
|
85
90
|
end
|
|
86
91
|
|
|
87
|
-
|
|
88
|
-
let(:xor) { [
|
|
89
|
-
|
|
92
|
+
describe '#xor' do
|
|
93
|
+
let(:xor) { [Line.new(Point[0, 0], Point[7, 0]), Line.new(Point[10, 0], Point[15, 0])] }
|
|
94
|
+
|
|
95
|
+
it 'subtracts lines' do
|
|
90
96
|
expect(l1.xor(l2)).to eql(xor)
|
|
91
97
|
expect(l1.overlaps?(l3)).to be_falsey
|
|
92
98
|
expect(l2.overlaps?(l3)).to be_falsey
|
|
@@ -1,22 +1,35 @@
|
|
|
1
|
-
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'spec_helper'
|
|
2
4
|
|
|
3
5
|
module Laser
|
|
4
6
|
module Cutter
|
|
5
|
-
describe PageManager do
|
|
7
|
+
RSpec.describe PageManager do
|
|
6
8
|
let(:pm) { Laser::Cutter::PageManager.new(units) }
|
|
7
9
|
|
|
8
10
|
context 'a single value' do
|
|
9
11
|
context 'to inches' do
|
|
10
12
|
let(:units) { 'in' }
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
expect(pm.value_from_units(150
|
|
13
|
+
|
|
14
|
+
it 'is correct' do
|
|
15
|
+
expect(pm.value_from_units(150)).to be_within(0.0001).of(150.0 / 72.0)
|
|
16
|
+
expect(pm.value_from_units(150, 'mm')).to be_within(0.0001).of(150.0 / 25.4)
|
|
14
17
|
expect(pm.value_from_units(150, 'in')).to be_within(0.0001).of(150.0)
|
|
15
18
|
end
|
|
16
19
|
end
|
|
20
|
+
|
|
21
|
+
context 'when units are a Symbol, as the configuration defaults them' do
|
|
22
|
+
let(:units) { :in }
|
|
23
|
+
|
|
24
|
+
it 'reads them as inches' do
|
|
25
|
+
expect(pm.value_from_units(72)).to be_within(0.0001).of(1.0)
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
|
|
17
29
|
context 'to mm' do
|
|
18
30
|
let(:units) { 'mm' }
|
|
19
|
-
|
|
31
|
+
|
|
32
|
+
it 'is correct' do
|
|
20
33
|
expect(pm.value_from_units(150)).to be_within(0.0001).of(25.4 * 150.0 / 72.0)
|
|
21
34
|
expect(pm.value_from_units(150, 'in')).to be_within(0.0001).of(150.0 * 25.4)
|
|
22
35
|
expect(pm.value_from_units(150, 'mm')).to be_within(0.0001).of(150.0)
|
|
@@ -24,18 +37,21 @@ module Laser
|
|
|
24
37
|
end
|
|
25
38
|
end
|
|
26
39
|
|
|
27
|
-
|
|
40
|
+
describe '#list_page_sizes' do
|
|
28
41
|
context 'formatting of output' do
|
|
29
42
|
context 'when using inches' do
|
|
30
43
|
let(:units) { "in" }
|
|
31
|
-
|
|
32
|
-
|
|
44
|
+
|
|
45
|
+
it 'returns the list in inches' do
|
|
46
|
+
expect(pm.all_page_sizes).to match /.*B10:\s+1\.2\s+x\s+1\.7/
|
|
33
47
|
end
|
|
34
48
|
end
|
|
49
|
+
|
|
35
50
|
context 'when using mm' do
|
|
36
51
|
let(:units) { "mm" }
|
|
37
|
-
|
|
38
|
-
|
|
52
|
+
|
|
53
|
+
it 'returns the list in mm' do
|
|
54
|
+
expect(pm.all_page_sizes).to match /.*B10:\s+31\.0\s+x\s+44\.0/
|
|
39
55
|
end
|
|
40
56
|
end
|
|
41
57
|
end
|
|
@@ -1,18 +1,22 @@
|
|
|
1
|
-
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'spec_helper'
|
|
2
4
|
|
|
3
5
|
module Laser
|
|
4
6
|
module Cutter
|
|
5
7
|
module Notching
|
|
6
|
-
describe PathGenerator do
|
|
8
|
+
RSpec.describe PathGenerator do
|
|
7
9
|
let(:notch) { 2 }
|
|
8
10
|
let(:thickness) { 1 }
|
|
9
11
|
let(:center_out) { true }
|
|
10
12
|
let(:corners) { true }
|
|
11
13
|
|
|
12
|
-
let(:options) {
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
14
|
+
let(:options) {
|
|
15
|
+
{ notch_width: notch,
|
|
16
|
+
thickness: thickness,
|
|
17
|
+
center_out: center_out,
|
|
18
|
+
corners: corners }
|
|
19
|
+
}
|
|
16
20
|
|
|
17
21
|
let(:outside) { Geometry::Line.new(from: [0, 0], to: [10, 0]) }
|
|
18
22
|
let(:inside) { Geometry::Line.new(from: [1, 1], to: [9, 1]) }
|
|
@@ -20,14 +24,16 @@ module Laser
|
|
|
20
24
|
let(:generator) { PathGenerator.new(edge) }
|
|
21
25
|
|
|
22
26
|
context 'edge' do
|
|
23
|
-
it '
|
|
27
|
+
it 'properlies calculate notch size' do
|
|
24
28
|
expect(edge.notch_width).to be_within(0.001).of(1.6)
|
|
25
29
|
end
|
|
30
|
+
|
|
26
31
|
context 'edge cases with the edge :)' do
|
|
27
32
|
let(:notch) { 15 } # too big
|
|
28
|
-
|
|
33
|
+
|
|
34
|
+
it 'properlies handle edge cases' do
|
|
29
35
|
# 3 is the minimum number of notches we support per side
|
|
30
|
-
expect(edge.notch_width).to be_within(0.001).of(8.0/3.0)
|
|
36
|
+
expect(edge.notch_width).to be_within(0.001).of(8.0 / 3.0)
|
|
31
37
|
end
|
|
32
38
|
end
|
|
33
39
|
end
|
|
@@ -35,7 +41,8 @@ module Laser
|
|
|
35
41
|
context 'alternating iterator' do
|
|
36
42
|
let(:a) { "hello" }
|
|
37
43
|
let(:b) { "again" }
|
|
38
|
-
let(:iterator) { InfiniteIterator.new([a,b]) }
|
|
44
|
+
let(:iterator) { InfiniteIterator.new([a, b]) }
|
|
45
|
+
|
|
39
46
|
it 'returns things in alternating order' do
|
|
40
47
|
expect(iterator.next).to eq(a)
|
|
41
48
|
expect(iterator.next).to eq(b)
|
|
@@ -44,7 +51,6 @@ module Laser
|
|
|
44
51
|
end
|
|
45
52
|
|
|
46
53
|
context 'shift definition' do
|
|
47
|
-
|
|
48
54
|
it 'correctly defines shifts' do
|
|
49
55
|
shifts = generator.send(:define_shifts)
|
|
50
56
|
expect(edge.outside.length).to eql(10.0)
|
|
@@ -55,7 +61,6 @@ module Laser
|
|
|
55
61
|
end
|
|
56
62
|
end
|
|
57
63
|
|
|
58
|
-
|
|
59
64
|
context 'path generation' do
|
|
60
65
|
# let(:outside) { Line.new(
|
|
61
66
|
# from: inside.p1.plus(-thickness, -thickness),
|
|
@@ -63,7 +68,7 @@ module Laser
|
|
|
63
68
|
|
|
64
69
|
context 'center out' do
|
|
65
70
|
it 'generates correct path vertices' do
|
|
66
|
-
expect(inside.p1).
|
|
71
|
+
expect(inside.p1).not_to eql(inside.p2)
|
|
67
72
|
lines = generator.generate
|
|
68
73
|
expect(lines.size).to be > 5
|
|
69
74
|
|
|
@@ -81,7 +86,6 @@ module Laser
|
|
|
81
86
|
end
|
|
82
87
|
end
|
|
83
88
|
end
|
|
84
|
-
|
|
85
89
|
end
|
|
86
90
|
end
|
|
87
91
|
end
|
|
@@ -1,25 +1,28 @@
|
|
|
1
|
-
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'spec_helper'
|
|
2
4
|
|
|
3
5
|
module Laser
|
|
4
6
|
module Cutter
|
|
5
7
|
module Geometry
|
|
6
|
-
describe Point do
|
|
8
|
+
RSpec.describe Point do
|
|
7
9
|
let(:p1) { Point.new(1, 2) }
|
|
10
|
+
|
|
8
11
|
context 'creation' do
|
|
9
12
|
context 'from string' do
|
|
10
|
-
it '
|
|
11
|
-
expect(Point.new
|
|
13
|
+
it 'instantiates correctly' do
|
|
14
|
+
expect(Point.new("1,2")).to eql(p1)
|
|
12
15
|
end
|
|
13
16
|
end
|
|
14
17
|
|
|
15
18
|
context 'from a point' do
|
|
16
|
-
it '
|
|
19
|
+
it 'instantiates correctly' do
|
|
17
20
|
expect(Point.new(Point.new("1,2"))).to eql(p1)
|
|
18
21
|
end
|
|
19
22
|
end
|
|
20
23
|
|
|
21
24
|
context 'from an array' do
|
|
22
|
-
it '
|
|
25
|
+
it 'properlies duplicate underlying coordinates and not bug out' do
|
|
23
26
|
a = [1, 2]
|
|
24
27
|
p2 = Point.new(a)
|
|
25
28
|
expect(p2).to eql(p1)
|
|
@@ -28,25 +31,26 @@ module Laser
|
|
|
28
31
|
expect(p2).to eql(p1)
|
|
29
32
|
end
|
|
30
33
|
|
|
31
|
-
it '
|
|
32
|
-
expect(Point[1,2]).to eql(p1)
|
|
34
|
+
it 'creates point from class method' do
|
|
35
|
+
expect(Point[1, 2]).to eql(p1)
|
|
33
36
|
end
|
|
34
37
|
end
|
|
35
38
|
|
|
36
39
|
context 'from a hash' do
|
|
37
|
-
it '
|
|
40
|
+
it 'instantiates correctly' do
|
|
38
41
|
expect(Point.new(x: 1, y: 2)).to eql(p1)
|
|
39
42
|
end
|
|
40
43
|
end
|
|
41
44
|
end
|
|
42
45
|
|
|
43
46
|
context 'move by' do
|
|
44
|
-
it '
|
|
47
|
+
it 'moves properly' do
|
|
45
48
|
p = p1.plus(10, -2)
|
|
46
49
|
expect(p.x).to be_within(0.001).of(11)
|
|
47
50
|
expect(p.y).to be_within(0.001).of(0)
|
|
48
51
|
end
|
|
49
|
-
|
|
52
|
+
|
|
53
|
+
it 'moves cloned version properly' do
|
|
50
54
|
p2 = p1.clone.plus(10, -2)
|
|
51
55
|
expect(p2.x).to be_within(0.001).of(11)
|
|
52
56
|
expect(p2.y).to be_within(0.001).of(0)
|
|
@@ -54,20 +58,21 @@ module Laser
|
|
|
54
58
|
end
|
|
55
59
|
|
|
56
60
|
context 'ordering and equality' do
|
|
57
|
-
let(:p1) { Point[0,0]}
|
|
58
|
-
let(:p2) { Point[10,0]}
|
|
59
|
-
let(:p3) { Point[0,10]}
|
|
60
|
-
let(:p4) { Point[10,10]}
|
|
61
|
-
let(:p5) { Point[-1
|
|
62
|
-
|
|
63
|
-
|
|
61
|
+
let(:p1) { Point[0, 0] }
|
|
62
|
+
let(:p2) { Point[10, 0] }
|
|
63
|
+
let(:p3) { Point[0, 10] }
|
|
64
|
+
let(:p4) { Point[10, 10] }
|
|
65
|
+
let(:p5) { Point[-1, -1] }
|
|
66
|
+
|
|
67
|
+
it 'propertlies sort' do
|
|
68
|
+
expect([p3, p2, p1, p4, p5].sort).to eql([p5, p1, p3, p2, p4])
|
|
64
69
|
end
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
expect(
|
|
70
|
+
|
|
71
|
+
it 'detects equality' do
|
|
72
|
+
expect(p1).not_to eql(p2)
|
|
73
|
+
expect(p2).to eql(Point[10, 0])
|
|
68
74
|
end
|
|
69
75
|
end
|
|
70
|
-
|
|
71
76
|
end
|
|
72
77
|
end
|
|
73
78
|
end
|
|
@@ -1,10 +1,12 @@
|
|
|
1
|
-
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'spec_helper'
|
|
2
4
|
|
|
3
5
|
module Laser
|
|
4
6
|
module Cutter
|
|
5
7
|
module Geometry
|
|
6
|
-
describe Rect do
|
|
7
|
-
let(:p1) { Point[
|
|
8
|
+
RSpec.describe Rect do
|
|
9
|
+
let(:p1) { Point[1.0, 3.0] }
|
|
8
10
|
let(:p2) { Point[11.0, 23.0] }
|
|
9
11
|
|
|
10
12
|
let(:rect1) { Rect.new(p1, p2) }
|
|
@@ -22,14 +24,16 @@ module Laser
|
|
|
22
24
|
expect(rect1.position).to eql(rect1.vertices[0])
|
|
23
25
|
expect(rect1.p2).to eql(rect1.vertices[2])
|
|
24
26
|
end
|
|
25
|
-
|
|
27
|
+
|
|
28
|
+
it 'generates four side lines' do
|
|
26
29
|
expect(rect1.sides.size).to eql(4)
|
|
27
|
-
expect(rect1.sides.first).to
|
|
30
|
+
expect(rect1.sides.first).to be_a(Line)
|
|
28
31
|
expect(rect1.sides.first.p1).to eql(p1)
|
|
29
32
|
expect(rect1.sides[0].p1.to_s).to eql("{1.00000,3.00000}")
|
|
30
33
|
expect(rect1.sides[1].p1.to_s).to eql("{11.00000,3.00000}")
|
|
31
34
|
expect(rect1.sides[2].p1).to eql(p1.plus(10, 20))
|
|
32
35
|
end
|
|
36
|
+
|
|
33
37
|
it 'can be moved' do
|
|
34
38
|
expect(rect1.sides[0].p1.to_s).to eql("{1.00000,3.00000}")
|
|
35
39
|
rect1.x = 1000
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'spec_helper'
|
|
4
|
+
|
|
5
|
+
module Laser
|
|
6
|
+
module Cutter
|
|
7
|
+
module Renderer
|
|
8
|
+
RSpec.describe LayoutRenderer do
|
|
9
|
+
describe '#render' do
|
|
10
|
+
let(:renderer) { LayoutRenderer.new(config) }
|
|
11
|
+
let(:file) { File.expand_path("../../../laser-cutter-pdf-test.#{$$}.pdf", __FILE__) }
|
|
12
|
+
|
|
13
|
+
def render_file(filename)
|
|
14
|
+
real_file = ENV['RSPEC_SAVE_PDF'] ? true : false
|
|
15
|
+
config.validate!
|
|
16
|
+
expect(!File.exist?(filename)) if real_file
|
|
17
|
+
renderer.render
|
|
18
|
+
expect(File.exist?(filename)) if real_file
|
|
19
|
+
expect(File.size(filename) > 0) if real_file
|
|
20
|
+
rescue Exception => e
|
|
21
|
+
warn e.backtrace.join("\n")
|
|
22
|
+
fail e.message
|
|
23
|
+
ensure
|
|
24
|
+
if real_file
|
|
25
|
+
begin
|
|
26
|
+
File.delete(filename)
|
|
27
|
+
rescue StandardError
|
|
28
|
+
nil
|
|
29
|
+
end
|
|
30
|
+
expect(!File.exist?(filename))
|
|
31
|
+
end
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
before do
|
|
35
|
+
unless ENV['RSPEC_SAVE_PDF']
|
|
36
|
+
expect_any_instance_of(Prawn::Document).to receive(:render_file).once
|
|
37
|
+
end
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
context 'metric' do
|
|
41
|
+
let(:config) {
|
|
42
|
+
Laser::Cutter::Configuration.new(
|
|
43
|
+
'width' => 50,
|
|
44
|
+
'height' => 60,
|
|
45
|
+
'depth' => 20,
|
|
46
|
+
'thickness' => 6,
|
|
47
|
+
'margin' => 5,
|
|
48
|
+
'padding' => 3,
|
|
49
|
+
'notch' => 10,
|
|
50
|
+
'file' => file
|
|
51
|
+
)
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
it 'is able to generate a PDF file' do
|
|
55
|
+
render_file file
|
|
56
|
+
end
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
context 'imperial' do
|
|
60
|
+
context 'margins and padding provided' do
|
|
61
|
+
let(:config) {
|
|
62
|
+
Laser::Cutter::Configuration.new(
|
|
63
|
+
'width' => 2.5,
|
|
64
|
+
'height' => 3.5,
|
|
65
|
+
'depth' => 2.0,
|
|
66
|
+
'thickness' => 0.125,
|
|
67
|
+
'margin' => 0,
|
|
68
|
+
'padding' => 0.125,
|
|
69
|
+
'notch' => 0.25,
|
|
70
|
+
'file' => file,
|
|
71
|
+
'units' => 'in'
|
|
72
|
+
)
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
it 'is able to generate a PDF file' do
|
|
76
|
+
render_file file
|
|
77
|
+
end
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
context 'margins and padding are defaults' do
|
|
81
|
+
let(:config) {
|
|
82
|
+
Laser::Cutter::Configuration.new(
|
|
83
|
+
'width' => 2.5,
|
|
84
|
+
'height' => 2,
|
|
85
|
+
'depth' => 2.0,
|
|
86
|
+
'thickness' => 0.125,
|
|
87
|
+
'notch' => 0.25,
|
|
88
|
+
'file' => file,
|
|
89
|
+
'units' => 'in'
|
|
90
|
+
)
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
it 'is able to generate a PDF file' do
|
|
94
|
+
render_file file
|
|
95
|
+
end
|
|
96
|
+
end
|
|
97
|
+
end
|
|
98
|
+
end
|
|
99
|
+
end
|
|
100
|
+
end
|
|
101
|
+
end
|
|
102
|
+
end
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'tmpdir'
|
|
4
|
+
|
|
5
|
+
RSpec.describe Laser::Cutter::Renderer::SvgRenderer do
|
|
6
|
+
subject(:renderer) { described_class.new(config) }
|
|
7
|
+
|
|
8
|
+
let(:dir) { Dir.mktmpdir }
|
|
9
|
+
let(:file) { File.join(dir, 'box.svg') }
|
|
10
|
+
let(:settings) { { box: '4x3x2/0.125/0.5', file: file } }
|
|
11
|
+
let(:config) { Laser::Cutter::Configuration.new(settings) }
|
|
12
|
+
let(:svg) { File.read(file) }
|
|
13
|
+
|
|
14
|
+
after { FileUtils.remove_entry(dir) }
|
|
15
|
+
|
|
16
|
+
its(:total) { is_expected.to eq(328) }
|
|
17
|
+
|
|
18
|
+
it 'draws every line, yielding each' do
|
|
19
|
+
yielded = 0
|
|
20
|
+
renderer.render { yielded += 1 }
|
|
21
|
+
expect(yielded).to eq(328)
|
|
22
|
+
expect(svg.scan('<line').size).to eq(328)
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
it 'sizes the page in the units of the box' do
|
|
26
|
+
renderer.render
|
|
27
|
+
expect(svg).to include('width="9.2012in" height="11.5512in"')
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
# Kerf moves the outline out by half its width, into the margin.
|
|
31
|
+
it 'keeps every line on the page' do
|
|
32
|
+
renderer.render
|
|
33
|
+
coordinates = svg.scan(/[xy][12]="([\d.-]+)"/).flatten.map(&:to_f)
|
|
34
|
+
expect(coordinates.min).to be_within(0.002).of(0.125)
|
|
35
|
+
expect(coordinates.max).to be_within(0.002).of(11.5512 - 0.125)
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
it 'describes the box' do
|
|
39
|
+
renderer.render
|
|
40
|
+
expect(svg).to match(/<desc>\s*Made with laser-cutter.*thickness: 0\.125/)
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
context 'without metadata' do
|
|
44
|
+
let(:settings) { super().merge(metadata: false) }
|
|
45
|
+
|
|
46
|
+
it 'leaves the description out' do
|
|
47
|
+
renderer.render
|
|
48
|
+
expect(svg).not_to include('<desc>')
|
|
49
|
+
end
|
|
50
|
+
end
|
|
51
|
+
end
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
RSpec.describe Laser::Cutter::Renderer do
|
|
4
|
+
let(:config) { Laser::Cutter::Configuration.new(box: '4x3x2/0.125', file: 'box') }
|
|
5
|
+
|
|
6
|
+
describe '.for' do
|
|
7
|
+
it 'writes a PDF with the layout renderer' do
|
|
8
|
+
expect(described_class.for('PDF', config)).to be_a(described_class::LayoutRenderer)
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
it 'writes an SVG with the SVG renderer' do
|
|
12
|
+
expect(described_class.for(:svg, config)).to be_a(described_class::SvgRenderer)
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
it 'raises on a format it does not write' do
|
|
16
|
+
expect { described_class.for('dxf', config) }.to raise_error(Laser::Cutter::Error, /unknown format/)
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
end
|
data/spec/spec_helper.rb
CHANGED
|
@@ -1,28 +1,41 @@
|
|
|
1
|
-
#
|
|
2
|
-
# specs live under a `spec` directory, which RSpec adds to the `$LOAD_PATH`.
|
|
3
|
-
# Require this file using `require "spec_helper"` to ensure that it is only
|
|
4
|
-
# loaded once.
|
|
5
|
-
#
|
|
6
|
-
# See http://rubydoc.info/gems/rspec-core/RSpec/Core/Configuration
|
|
1
|
+
# frozen_string_literal: true
|
|
7
2
|
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
require
|
|
11
|
-
require
|
|
12
|
-
|
|
13
|
-
require 'laser-cutter'
|
|
3
|
+
# SimpleCov must start before the gem's own code loads, or that code counts as
|
|
4
|
+
# never run. The badge lands in docs/badges/coverage_badge.svg for the README.
|
|
5
|
+
require "simplecov"
|
|
6
|
+
require "coverage/badge"
|
|
7
|
+
require "fileutils"
|
|
14
8
|
|
|
9
|
+
SimpleCov.start do
|
|
10
|
+
# SimpleCov 1.x gives project-relative paths without a leading slash; 0.22 kept it.
|
|
11
|
+
skip %r{\A/?(spec|test)/}
|
|
12
|
+
minimum_coverage 95
|
|
13
|
+
formatter SimpleCov::Formatter::MultiFormatter.new(
|
|
14
|
+
[SimpleCov::Formatter::HTMLFormatter, Coverage::Badge::Formatter]
|
|
15
|
+
)
|
|
16
|
+
end
|
|
15
17
|
|
|
18
|
+
$LOAD_PATH.unshift File.expand_path("../lib", __dir__)
|
|
16
19
|
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
# Run specs in random order to surface order dependencies. If you find an
|
|
23
|
-
# order dependency and want to debug it, you can fix the order by providing
|
|
24
|
-
# the seed, which is printed after each run.
|
|
25
|
-
# --seed 1234
|
|
26
|
-
config.order = 'random'
|
|
20
|
+
SimpleCov.at_exit do
|
|
21
|
+
SimpleCov.result.format!
|
|
22
|
+
puts "Coverage: #{SimpleCov.result.covered_percent.round(2)}%" # rubocop:disable RSpec/Output
|
|
23
|
+
FileUtils.mkdir_p("docs/badges")
|
|
24
|
+
FileUtils.mv("coverage/badge.svg", "docs/badges/coverage_badge.svg")
|
|
27
25
|
end
|
|
28
26
|
|
|
27
|
+
require "rspec/its"
|
|
28
|
+
require "laser/cutter"
|
|
29
|
+
|
|
30
|
+
Dir[File.expand_path("support/**/*.rb", __dir__)].each { |file| require file }
|
|
31
|
+
|
|
32
|
+
RSpec.configure do |config|
|
|
33
|
+
config.example_status_persistence_file_path = ".rspec_status"
|
|
34
|
+
config.disable_monkey_patching!
|
|
35
|
+
config.order = :random
|
|
36
|
+
Kernel.srand config.seed
|
|
37
|
+
|
|
38
|
+
config.expect_with :rspec do |c|
|
|
39
|
+
c.syntax = :expect
|
|
40
|
+
end
|
|
41
|
+
end
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "aruba/rspec"
|
|
4
|
+
|
|
5
|
+
# In-process: the suite builds a Launcher per example instead of forking, so
|
|
6
|
+
# the CLI's own code counts towards coverage.
|
|
7
|
+
Aruba.configure do |config|
|
|
8
|
+
config.command_launcher = :in_process
|
|
9
|
+
config.main_class = Laser::Cutter::Launcher
|
|
10
|
+
end
|