laser-cutter 1.0.3 → 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 -4
- data/.plans/001.00-dry-cli-migration/plan.md +29 -0
- data/.relaxed_rubocop.yml +153 -0
- data/.rspec +1 -1
- 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 +83 -111
- data/Rakefile +41 -0
- 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 → laser/cutter}/box.rb +44 -52
- 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 +21 -20
- data/lib/laser/cutter/notching/infinite_iterator.rb +27 -0
- data/lib/{laser-cutter → laser/cutter}/notching/path_generator.rb +27 -60
- 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 +34 -25
- data/spec/support/aruba.rb +10 -0
- metadata +206 -80
- data/.travis.yml +0 -11
- data/LICENSE +0 -22
- data/bin/laser-cutter +0 -19
- data/lib/laser-cutter/aggregator.rb +0 -57
- 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/geometry.rb +0 -11
- data/lib/laser-cutter/notching/base.rb +0 -13
- data/lib/laser-cutter/notching.rb +0 -10
- 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 -40
- data/spec/configuration_spec.rb +0 -81
- data/spec/edge_spec.rb +0 -43
- data/spec/renderer_spec.rb +0 -72
- /data/{doc → docs/images}/comparison.jpg +0 -0
|
@@ -1,10 +1,11 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
1
3
|
module Laser
|
|
2
4
|
module Cutter
|
|
3
5
|
module Geometry
|
|
4
6
|
class Line < Shape
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
self.new *array
|
|
7
|
+
def self.[](*array)
|
|
8
|
+
new(*array)
|
|
8
9
|
end
|
|
9
10
|
|
|
10
11
|
attr_accessor :p1, :p2
|
|
@@ -19,31 +20,33 @@ module Laser
|
|
|
19
20
|
self.p2 = point2.clone
|
|
20
21
|
end
|
|
21
22
|
self.position = p1.clone
|
|
22
|
-
raise 'Both points are required for line definition' unless
|
|
23
|
+
raise 'Both points are required for line definition' unless p1 && p2
|
|
23
24
|
end
|
|
24
25
|
|
|
25
26
|
def overlaps?(another)
|
|
26
27
|
xs, ys = sorted_coords(another)
|
|
27
|
-
return false unless xs.all?{|x| x == xs[0] } || ys.all?{|y| y == ys[0] }
|
|
28
|
+
return false unless xs.all?{ |x| x == xs[0] } || ys.all?{ |y| y == ys[0] }
|
|
28
29
|
return false if xs.first[1] < xs.last[0] || xs.first[0] > xs.last[1]
|
|
29
30
|
return false if ys.first[1] < ys.last[0] || ys.first[0] > ys.last[1]
|
|
31
|
+
|
|
30
32
|
true
|
|
31
33
|
end
|
|
32
34
|
|
|
33
35
|
def sorted_coords(another)
|
|
34
36
|
xs = [[p1.x, p2.x].sort, [another.p1.x, another.p2.x].sort]
|
|
35
37
|
ys = [[p1.y, p2.y].sort, [another.p1.y, another.p2.y].sort]
|
|
36
|
-
|
|
38
|
+
[xs, ys]
|
|
37
39
|
end
|
|
38
40
|
|
|
39
41
|
def xor(another)
|
|
40
42
|
return nil unless overlaps?(another)
|
|
43
|
+
|
|
41
44
|
xs, ys = sorted_coords(another)
|
|
42
45
|
xs.flatten!.sort!
|
|
43
46
|
ys.flatten!.sort!
|
|
44
47
|
|
|
45
|
-
[
|
|
46
|
-
|
|
48
|
+
[Line.new(Point[xs[0], ys[0]], Point[xs[1], ys[1]]),
|
|
49
|
+
Line.new(Point[xs[2], ys[2]], Point[xs[3], ys[3]])]
|
|
47
50
|
end
|
|
48
51
|
|
|
49
52
|
def relocate!
|
|
@@ -60,16 +63,16 @@ module Laser
|
|
|
60
63
|
end
|
|
61
64
|
|
|
62
65
|
def length
|
|
63
|
-
Math.sqrt((p2.x - p1.x)**2 + (p2.y - p1.y)**2)
|
|
66
|
+
Math.sqrt(((p2.x - p1.x)**2) + ((p2.y - p1.y)**2))
|
|
64
67
|
end
|
|
65
68
|
|
|
66
69
|
def to_s
|
|
67
|
-
"#{self.class.name.gsub(/.*::/,'').downcase} #{p1}=>#{p2}"
|
|
70
|
+
"#{self.class.name.gsub(/.*::/, '').downcase} #{p1}=>#{p2}"
|
|
68
71
|
end
|
|
69
72
|
|
|
70
73
|
def eql?(other)
|
|
71
74
|
(other.p1.eql?(p1) && other.p2.eql?(p2)) ||
|
|
72
|
-
|
|
75
|
+
(other.p2.eql?(p1) && other.p1.eql?(p2))
|
|
73
76
|
end
|
|
74
77
|
|
|
75
78
|
def normalized
|
|
@@ -77,17 +80,17 @@ module Laser
|
|
|
77
80
|
end
|
|
78
81
|
|
|
79
82
|
def <=>(other)
|
|
80
|
-
n1 =
|
|
83
|
+
n1 = normalized
|
|
81
84
|
n2 = other.normalized
|
|
82
85
|
n1.p1.eql?(n2.p1) ? n1.p2 <=> n2.p2 : n1.p1 <=> n2.p1
|
|
83
86
|
end
|
|
84
87
|
|
|
85
|
-
def <
|
|
86
|
-
|
|
88
|
+
def <(other)
|
|
89
|
+
p1 == other.p1 ? p2 < other.p2 : p1 < other.p1
|
|
87
90
|
end
|
|
88
91
|
|
|
89
|
-
def >
|
|
90
|
-
|
|
92
|
+
def >(other)
|
|
93
|
+
p1 == other.p1 ? p2 > other.p2 : p1 > other.p1
|
|
91
94
|
end
|
|
92
95
|
|
|
93
96
|
def hash
|
|
@@ -97,9 +100,7 @@ module Laser
|
|
|
97
100
|
def clone
|
|
98
101
|
self.class.new(p1, p2)
|
|
99
102
|
end
|
|
100
|
-
|
|
101
103
|
end
|
|
102
|
-
|
|
103
104
|
end
|
|
104
105
|
end
|
|
105
106
|
end
|
|
@@ -1,11 +1,12 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
1
3
|
module Laser
|
|
2
4
|
module Cutter
|
|
3
5
|
module Geometry
|
|
4
6
|
class Rect < Line
|
|
5
|
-
|
|
6
7
|
attr_accessor :sides, :vertices
|
|
7
8
|
|
|
8
|
-
def self.[]
|
|
9
|
+
def self.[](p1, p2)
|
|
9
10
|
Rect.new(p1, p2)
|
|
10
11
|
end
|
|
11
12
|
|
|
@@ -19,8 +20,8 @@ module Laser
|
|
|
19
20
|
Rect.new(line.p1, line.p2)
|
|
20
21
|
end
|
|
21
22
|
|
|
22
|
-
def initialize(*
|
|
23
|
-
super
|
|
23
|
+
def initialize(*)
|
|
24
|
+
super
|
|
24
25
|
relocate!
|
|
25
26
|
end
|
|
26
27
|
|
|
@@ -47,15 +48,13 @@ module Laser
|
|
|
47
48
|
end
|
|
48
49
|
|
|
49
50
|
def to_s
|
|
50
|
-
"#{sprintf
|
|
51
|
+
"#{sprintf '%3d', w}(w)x#{sprintf '%3d', h}(h) @ #{position}"
|
|
51
52
|
end
|
|
52
53
|
|
|
53
54
|
def to_a
|
|
54
55
|
[[p1.x, p1.y], [p2.x, p2.y]]
|
|
55
56
|
end
|
|
56
57
|
end
|
|
57
|
-
|
|
58
58
|
end
|
|
59
|
-
|
|
60
59
|
end
|
|
61
60
|
end
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
1
3
|
module Laser
|
|
2
4
|
module Cutter
|
|
3
5
|
module Geometry
|
|
@@ -8,27 +10,28 @@ module Laser
|
|
|
8
10
|
@position ||= Point.new(0, 0)
|
|
9
11
|
end
|
|
10
12
|
|
|
11
|
-
def x=
|
|
13
|
+
def x=(value)
|
|
12
14
|
position.x = value
|
|
13
15
|
end
|
|
16
|
+
|
|
14
17
|
def x
|
|
15
18
|
position.x
|
|
16
19
|
end
|
|
17
20
|
|
|
18
|
-
def y=
|
|
21
|
+
def y=(value)
|
|
19
22
|
position.y = value
|
|
20
23
|
end
|
|
24
|
+
|
|
21
25
|
def y
|
|
22
26
|
position.y
|
|
23
27
|
end
|
|
24
28
|
|
|
25
|
-
def move_to
|
|
29
|
+
def move_to(new_point)
|
|
26
30
|
self.position = new_point
|
|
27
31
|
relocate!
|
|
28
32
|
self
|
|
29
33
|
end
|
|
30
34
|
|
|
31
|
-
|
|
32
35
|
# Implement in each shape to move to the new pointd
|
|
33
36
|
def relocate!
|
|
34
37
|
raise 'Abstract method'
|
|
@@ -37,6 +40,3 @@ module Laser
|
|
|
37
40
|
end
|
|
38
41
|
end
|
|
39
42
|
end
|
|
40
|
-
|
|
41
|
-
require_relative 'shape/line'
|
|
42
|
-
require_relative 'shape/rect'
|
|
@@ -1,60 +1,63 @@
|
|
|
1
|
-
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
2
3
|
module Laser
|
|
3
4
|
module Cutter
|
|
4
5
|
module Geometry
|
|
5
6
|
class Tuple
|
|
6
7
|
attr_accessor :coords
|
|
8
|
+
|
|
7
9
|
PRECISION = 0.000001
|
|
8
10
|
|
|
9
11
|
def initialize(*args)
|
|
10
12
|
x = args.first
|
|
11
|
-
coordinates =
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
13
|
+
coordinates = case x
|
|
14
|
+
when String
|
|
15
|
+
parse_string(x)
|
|
16
|
+
when Hash
|
|
17
|
+
parse_hash(x)
|
|
18
|
+
when Array
|
|
19
|
+
x.clone
|
|
20
|
+
when Tuple, Vector
|
|
21
|
+
x.to_a
|
|
22
|
+
else
|
|
23
|
+
args.clone
|
|
24
|
+
end
|
|
22
25
|
coordinates.map!(&:to_f)
|
|
23
26
|
self.coords = Vector.[](*coordinates)
|
|
24
27
|
end
|
|
25
28
|
|
|
26
|
-
def +
|
|
29
|
+
def +(x, y = nil)
|
|
27
30
|
shift = if x.is_a?(Vector)
|
|
28
31
|
x
|
|
29
32
|
elsif x.is_a?(Tuple)
|
|
30
33
|
x.coords
|
|
31
34
|
elsif y
|
|
32
|
-
Vector.[](x,y)
|
|
35
|
+
Vector.[](x, y)
|
|
33
36
|
end
|
|
34
|
-
self.class.new(
|
|
37
|
+
self.class.new(coords + shift)
|
|
35
38
|
end
|
|
36
39
|
|
|
37
|
-
|
|
38
40
|
alias_method :plus, :+
|
|
39
41
|
|
|
40
42
|
def to_a
|
|
41
|
-
|
|
43
|
+
coords.to_a
|
|
42
44
|
end
|
|
43
45
|
|
|
44
46
|
def to_s
|
|
45
|
-
"{#{coords.to_a.map { |a| sprintf(
|
|
47
|
+
"{#{coords.to_a.map { |a| sprintf('%.5f', a) }.join(separator)}}"
|
|
46
48
|
end
|
|
47
49
|
|
|
48
50
|
def valid?
|
|
49
|
-
raise "Have nil value: #{
|
|
51
|
+
raise "Have nil value: #{inspect}" if coords.to_a.any?(&:nil?)
|
|
52
|
+
|
|
50
53
|
true
|
|
51
54
|
end
|
|
52
55
|
|
|
53
|
-
def x=
|
|
56
|
+
def x=(value)
|
|
54
57
|
self.coords = Vector.[](value, coords.[](1))
|
|
55
58
|
end
|
|
56
59
|
|
|
57
|
-
def y=
|
|
60
|
+
def y=(value)
|
|
58
61
|
self.coords = Vector.[](coords.[](0), value)
|
|
59
62
|
end
|
|
60
63
|
|
|
@@ -70,11 +73,10 @@ module Laser
|
|
|
70
73
|
','
|
|
71
74
|
end
|
|
72
75
|
|
|
73
|
-
def []
|
|
76
|
+
def [](value)
|
|
74
77
|
coords.[](value)
|
|
75
78
|
end
|
|
76
79
|
|
|
77
|
-
|
|
78
80
|
# Override in subclasses, eg:
|
|
79
81
|
# def separator
|
|
80
82
|
# ';'
|
|
@@ -90,8 +92,9 @@ module Laser
|
|
|
90
92
|
# Identity, cloning and sorting/ordering
|
|
91
93
|
def eql?(other)
|
|
92
94
|
return false unless other.respond_to?(:coords)
|
|
95
|
+
|
|
93
96
|
equal = true
|
|
94
|
-
|
|
97
|
+
coords.each_with_index do |c, i|
|
|
95
98
|
if (c - other.coords.to_a[i])**2 > PRECISION
|
|
96
99
|
equal = false
|
|
97
100
|
break
|
|
@@ -99,18 +102,22 @@ module Laser
|
|
|
99
102
|
end
|
|
100
103
|
equal
|
|
101
104
|
end
|
|
105
|
+
|
|
102
106
|
def <=>(other)
|
|
103
|
-
|
|
107
|
+
x == other.x ? y <=> other.y : x <=> other.x
|
|
104
108
|
end
|
|
105
|
-
|
|
106
|
-
|
|
109
|
+
|
|
110
|
+
def <(other)
|
|
111
|
+
x == other.x ? y < other.y : x < other.x
|
|
107
112
|
end
|
|
108
|
-
|
|
109
|
-
|
|
113
|
+
|
|
114
|
+
def >(other)
|
|
115
|
+
x == other.x ? y > other.y : x > other.x
|
|
110
116
|
end
|
|
117
|
+
|
|
111
118
|
def clone
|
|
112
119
|
clone = super
|
|
113
|
-
clone.coords =
|
|
120
|
+
clone.coords = coords.clone
|
|
114
121
|
clone
|
|
115
122
|
end
|
|
116
123
|
|
|
@@ -118,18 +125,15 @@ module Laser
|
|
|
118
125
|
|
|
119
126
|
#
|
|
120
127
|
# Convert from, eg "100,50" to [100.0, 50.0],
|
|
121
|
-
def parse_string
|
|
128
|
+
def parse_string(string)
|
|
122
129
|
string.split(separator).map(&:to_f)
|
|
123
130
|
end
|
|
124
131
|
|
|
125
132
|
# Return array of coordinates
|
|
126
|
-
def parse_hash
|
|
127
|
-
hash_keys.map{ |k,
|
|
133
|
+
def parse_hash(hash)
|
|
134
|
+
hash_keys.map{ |k, _v| hash[k] }
|
|
128
135
|
end
|
|
129
|
-
|
|
130
136
|
end
|
|
131
|
-
|
|
132
|
-
|
|
133
137
|
end
|
|
134
138
|
end
|
|
135
139
|
end
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Laser
|
|
4
|
+
module Cutter
|
|
5
|
+
# Starts the CLI. The executable and the Aruba suite both build one, each
|
|
6
|
+
# with its own argv and streams, and it exits only through kernel.exit.
|
|
7
|
+
class Launcher
|
|
8
|
+
def initialize(argv, stdin = $stdin, stdout = $stdout, stderr = $stderr, kernel = Kernel)
|
|
9
|
+
@argv = argv
|
|
10
|
+
@stdin = stdin
|
|
11
|
+
@stdout = stdout
|
|
12
|
+
@stderr = stderr
|
|
13
|
+
@kernel = kernel
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def execute!
|
|
17
|
+
@kernel.exit(run)
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
private
|
|
21
|
+
|
|
22
|
+
# dry-cli exits by itself after help or a usage error; its status is kept.
|
|
23
|
+
def run
|
|
24
|
+
Dry::CLI.new(CLI).call(arguments: @argv, out: @stdout, err: @stderr)
|
|
25
|
+
0
|
|
26
|
+
rescue SystemExit => e
|
|
27
|
+
e.status
|
|
28
|
+
rescue StandardError => e
|
|
29
|
+
ui.error(e.message)
|
|
30
|
+
@stderr.puts(e.backtrace) if @argv.intersect?(%w[-v --verbose])
|
|
31
|
+
1
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
# The same boxes the commands draw, on this run's own streams.
|
|
35
|
+
def ui
|
|
36
|
+
Dry::CLI::UI::Console.new(out: @stdout, err: @stderr, box_width: CLI.help_width)
|
|
37
|
+
end
|
|
38
|
+
end
|
|
39
|
+
end
|
|
40
|
+
end
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Laser
|
|
4
|
+
module Cutter
|
|
5
|
+
module Notching
|
|
6
|
+
class Base
|
|
7
|
+
attr_accessor :edge
|
|
8
|
+
|
|
9
|
+
def initialize(edge)
|
|
10
|
+
@edge = edge
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
def notches
|
|
14
|
+
raise 'Abstract method'
|
|
15
|
+
end
|
|
16
|
+
end
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
end
|
|
@@ -1,29 +1,27 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
1
3
|
module Laser
|
|
2
4
|
module Cutter
|
|
3
5
|
module Notching
|
|
4
6
|
MINIMUM_NOTCHES_PER_SIDE = 3
|
|
7
|
+
# Decimal places kept of length / notch width before counting notches.
|
|
8
|
+
RATIO_DIGITS = 6
|
|
5
9
|
# This class represents a single edge of one side: both inside
|
|
6
10
|
# and outside edge of the material. It's also responsible
|
|
7
11
|
# for calculating the "perfect" notch width.
|
|
8
12
|
class Edge
|
|
9
|
-
|
|
10
|
-
attr_accessor :outside, :inside
|
|
11
|
-
attr_accessor :notch_width
|
|
12
|
-
attr_accessor :thickness, :kerf
|
|
13
|
-
attr_accessor :center_out, :corners, :adjust_corners
|
|
14
|
-
attr_accessor :notch_count, :v1, :v2
|
|
15
|
-
|
|
13
|
+
attr_accessor :outside, :inside, :notch_width, :thickness, :kerf, :center_out, :corners, :adjust_corners, :notch_count, :v1, :v2
|
|
16
14
|
|
|
17
15
|
def initialize(outside, inside, options = {})
|
|
18
16
|
self.outside = outside.clone
|
|
19
17
|
self.inside = inside.clone
|
|
20
18
|
|
|
21
19
|
# two vectors representing directions going from beginning of each inside line to the outside
|
|
22
|
-
self.v1 = [inside.p1.x - outside.p1.x, inside.p1.y - outside.p1.y].map{|e| -(e / e.abs).to_f }
|
|
23
|
-
self.v2 = [inside.p2.x - outside.p2.x, inside.p2.y - outside.p2.y].map{|e| -(e / e.abs).to_f }
|
|
20
|
+
self.v1 = [inside.p1.x - outside.p1.x, inside.p1.y - outside.p1.y].map{ |e| -(e / e.abs).to_f }
|
|
21
|
+
self.v2 = [inside.p2.x - outside.p2.x, inside.p2.y - outside.p2.y].map{ |e| -(e / e.abs).to_f }
|
|
24
22
|
|
|
25
|
-
self.v1 = Vector.[](*
|
|
26
|
-
self.v2 = Vector.[](*
|
|
23
|
+
self.v1 = Vector.[](*v1)
|
|
24
|
+
self.v2 = Vector.[](*v2)
|
|
27
25
|
|
|
28
26
|
self.center_out = options[:center_out] || false
|
|
29
27
|
self.thickness = options[:thickness]
|
|
@@ -37,13 +35,13 @@ module Laser
|
|
|
37
35
|
end
|
|
38
36
|
|
|
39
37
|
def adjust_for_kerf!
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
38
|
+
return unless kerf?
|
|
39
|
+
|
|
40
|
+
self.inside = move_line_for_kerf(inside)
|
|
41
|
+
self.outside = move_line_for_kerf(outside)
|
|
44
42
|
end
|
|
45
43
|
|
|
46
|
-
def move_line_for_kerf
|
|
44
|
+
def move_line_for_kerf(line)
|
|
47
45
|
k = kerf / 2.0
|
|
48
46
|
p1 = line.p1.plus(v1 * k)
|
|
49
47
|
p2 = line.p2.plus(v2 * k)
|
|
@@ -51,7 +49,7 @@ module Laser
|
|
|
51
49
|
end
|
|
52
50
|
|
|
53
51
|
def kerf?
|
|
54
|
-
|
|
52
|
+
kerf > 0.0
|
|
55
53
|
end
|
|
56
54
|
|
|
57
55
|
# face_setting determines if we want that face to have center notch
|
|
@@ -65,17 +63,20 @@ module Laser
|
|
|
65
63
|
def first_notch_out?
|
|
66
64
|
add_across_line?(center_out)
|
|
67
65
|
end
|
|
66
|
+
|
|
68
67
|
private
|
|
69
68
|
|
|
70
69
|
def calculate_notch_width!
|
|
71
|
-
length = kerf? ?
|
|
72
|
-
|
|
70
|
+
length = kerf? ? inside.length - kerf : inside.length
|
|
71
|
+
# Rounded before #ceil: when the notch divides the side exactly,
|
|
72
|
+
# float noise in the coordinates would otherwise decide the count,
|
|
73
|
+
# and the two panels meeting at this joint would disagree.
|
|
74
|
+
count = (length / notch_width).to_f.round(RATIO_DIGITS).ceil + 1
|
|
73
75
|
count = (count / 2 * 2) + 1 # make count always an odd number
|
|
74
76
|
count = [MINIMUM_NOTCHES_PER_SIDE, count].max
|
|
75
77
|
self.notch_width = 1.0 * length / count
|
|
76
78
|
self.notch_count = count
|
|
77
79
|
end
|
|
78
|
-
|
|
79
80
|
end
|
|
80
81
|
end
|
|
81
82
|
end
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Laser
|
|
4
|
+
module Cutter
|
|
5
|
+
module Notching
|
|
6
|
+
# Alternating iterator
|
|
7
|
+
InfiniteIterator = Struct.new(:array) do
|
|
8
|
+
attr_accessor :array, :next_index, :calls
|
|
9
|
+
|
|
10
|
+
def initialize(array)
|
|
11
|
+
self.array = array
|
|
12
|
+
self.calls = 0
|
|
13
|
+
self.next_index = 0
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def next
|
|
17
|
+
item = array[next_index].clone
|
|
18
|
+
self.next_index += 1
|
|
19
|
+
self.next_index %= array.size
|
|
20
|
+
self.calls += 1
|
|
21
|
+
item = yield item, calls if block_given?
|
|
22
|
+
item
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
end
|