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.
Files changed (102) hide show
  1. checksums.yaml +5 -5
  2. data/.envrc +2 -0
  3. data/.github/workflows/lint.yml +22 -0
  4. data/.github/workflows/rspec.yml +22 -0
  5. data/.gitignore +5 -4
  6. data/.plans/001.00-dry-cli-migration/plan.md +29 -0
  7. data/.relaxed_rubocop.yml +153 -0
  8. data/.rspec +1 -1
  9. data/.rubocop.yml +31 -0
  10. data/.rubocop_todo.yml +90 -0
  11. data/.ruby-version +1 -0
  12. data/CLAUDE.md +62 -0
  13. data/CONTRIBUTING.md +2 -4
  14. data/Gemfile +14 -0
  15. data/README.md +83 -111
  16. data/Rakefile +41 -0
  17. data/docs/badges/coverage_badge.svg +21 -0
  18. data/exe/laser-cutter +8 -0
  19. data/exe/lc +1 -0
  20. data/justfile +97 -0
  21. data/laser-cutter.gemspec +26 -14
  22. data/lib/laser/cutter/aggregator.rb +101 -0
  23. data/lib/{laser-cutter → laser/cutter}/box.rb +44 -52
  24. data/lib/laser/cutter/cli/command.rb +46 -0
  25. data/lib/laser/cutter/cli/completion.rb +21 -0
  26. data/lib/laser/cutter/cli/config_file.rb +36 -0
  27. data/lib/laser/cutter/cli/examples.rb +42 -0
  28. data/lib/laser/cutter/cli/generate.rb +90 -0
  29. data/lib/laser/cutter/cli/help.rb +20 -0
  30. data/lib/laser/cutter/cli/page_sizes.rb +19 -0
  31. data/lib/laser/cutter/cli/version.rb +15 -0
  32. data/lib/laser/cutter/cli.rb +49 -0
  33. data/lib/laser/cutter/configuration.rb +101 -0
  34. data/lib/{laser-cutter → laser/cutter}/geometry/dimensions.rb +2 -3
  35. data/lib/{laser-cutter/geometry/shape → laser/cutter/geometry}/line.rb +19 -18
  36. data/lib/{laser-cutter → laser/cutter}/geometry/point.rb +4 -2
  37. data/lib/{laser-cutter/geometry/shape → laser/cutter/geometry}/rect.rb +6 -7
  38. data/lib/{laser-cutter → laser/cutter}/geometry/shape.rb +7 -7
  39. data/lib/{laser-cutter → laser/cutter}/geometry/tuple.rb +40 -36
  40. data/lib/laser/cutter/launcher.rb +40 -0
  41. data/lib/laser/cutter/missing_option.rb +7 -0
  42. data/lib/laser/cutter/notching/base.rb +19 -0
  43. data/lib/{laser-cutter → laser/cutter}/notching/edge.rb +21 -20
  44. data/lib/laser/cutter/notching/infinite_iterator.rb +27 -0
  45. data/lib/{laser-cutter → laser/cutter}/notching/path_generator.rb +27 -60
  46. data/lib/laser/cutter/notching/shift.rb +17 -0
  47. data/lib/laser/cutter/page_manager.rb +50 -0
  48. data/lib/{laser-cutter → laser/cutter}/renderer/base.rb +3 -2
  49. data/lib/laser/cutter/renderer/box_renderer.rb +40 -0
  50. data/lib/laser/cutter/renderer/layout_renderer.rb +61 -0
  51. data/lib/laser/cutter/renderer/line_renderer.rb +21 -0
  52. data/lib/laser/cutter/renderer/meta_renderer.rb +83 -0
  53. data/lib/{laser-cutter → laser/cutter}/renderer/rect_renderer.rb +3 -1
  54. data/lib/laser/cutter/renderer/svg_renderer.rb +65 -0
  55. data/lib/laser/cutter/renderer.rb +24 -0
  56. data/lib/laser/cutter/units_converter.rb +15 -0
  57. data/lib/laser/cutter/version.rb +7 -0
  58. data/lib/laser/cutter/zero_value_not_allowed.rb +7 -0
  59. data/lib/laser/cutter.rb +29 -0
  60. data/lib/laser-cutter.rb +2 -14
  61. data/lib/laser_cutter.rb +3 -0
  62. data/spec/laser/cutter/aggregator_spec.rb +70 -0
  63. data/spec/laser/cutter/box_spec.rb +61 -0
  64. data/spec/laser/cutter/cli/config_file_spec.rb +31 -0
  65. data/spec/laser/cutter/cli_spec.rb +186 -0
  66. data/spec/laser/cutter/configuration_spec.rb +101 -0
  67. data/spec/{dimensions_spec.rb → laser/cutter/dimensions_spec.rb} +8 -6
  68. data/spec/laser/cutter/edge_spec.rb +64 -0
  69. data/spec/{line_spec.rb → laser/cutter/line_spec.rb} +26 -20
  70. data/spec/{page_manager_spec.rb → laser/cutter/page_manager_spec.rb} +27 -11
  71. data/spec/{path_generator_spec.rb → laser/cutter/path_generator_spec.rb} +18 -14
  72. data/spec/{point_spec.rb → laser/cutter/point_spec.rb} +27 -22
  73. data/spec/{rect_spec.rb → laser/cutter/rect_spec.rb} +9 -5
  74. data/spec/laser/cutter/renderer/layout_renderer_spec.rb +102 -0
  75. data/spec/laser/cutter/renderer/svg_renderer_spec.rb +51 -0
  76. data/spec/laser/cutter/renderer_spec.rb +19 -0
  77. data/spec/spec_helper.rb +34 -25
  78. data/spec/support/aruba.rb +10 -0
  79. metadata +206 -80
  80. data/.travis.yml +0 -11
  81. data/LICENSE +0 -22
  82. data/bin/laser-cutter +0 -19
  83. data/lib/laser-cutter/aggregator.rb +0 -57
  84. data/lib/laser-cutter/cli/opt_parser.rb +0 -131
  85. data/lib/laser-cutter/cli/serializer.rb +0 -51
  86. data/lib/laser-cutter/configuration.rb +0 -83
  87. data/lib/laser-cutter/geometry.rb +0 -11
  88. data/lib/laser-cutter/notching/base.rb +0 -13
  89. data/lib/laser-cutter/notching.rb +0 -10
  90. data/lib/laser-cutter/page_manager.rb +0 -43
  91. data/lib/laser-cutter/renderer/box_renderer.rb +0 -36
  92. data/lib/laser-cutter/renderer/layout_renderer.rb +0 -59
  93. data/lib/laser-cutter/renderer/line_renderer.rb +0 -17
  94. data/lib/laser-cutter/renderer/meta_renderer.rb +0 -77
  95. data/lib/laser-cutter/renderer.rb +0 -14
  96. data/lib/laser-cutter/version.rb +0 -5
  97. data/spec/aggregator_spec.rb +0 -65
  98. data/spec/box_spec.rb +0 -40
  99. data/spec/configuration_spec.rb +0 -81
  100. data/spec/edge_spec.rb +0 -43
  101. data/spec/renderer_spec.rb +0 -72
  102. /data/{doc → docs/images}/comparison.jpg +0 -0
@@ -1,40 +1,11 @@
1
- require 'forwardable'
1
+ # frozen_string_literal: true
2
+
2
3
  module Laser
3
4
  module Cutter
4
5
  module Notching
5
- class Shift < Struct.new(:delta, :direction, :dim_index)
6
- def next_point_after point
7
- p = point.clone
8
- shift = []
9
- shift[dim_index] = delta * direction
10
- shift[(dim_index + 1) % 2] = 0
11
- p.plus *shift
12
- end
13
- end
14
-
15
- # Alternating iterator
16
- class InfiniteIterator < Struct.new(:array)
17
- attr_accessor :array, :next_index, :calls
18
-
19
- def initialize(array)
20
- self.array = array
21
- self.calls = 0
22
- self.next_index = 0
23
- end
24
-
25
- def next
26
- item = self.array[next_index].clone
27
- self.next_index += 1
28
- self.next_index %= array.size
29
- self.calls += 1
30
- item = yield item, self.calls if block_given?
31
- item
32
- end
33
- end
34
-
35
6
  class PathGenerator
36
-
37
7
  extend ::Forwardable
8
+
38
9
  %i(center_out thickness corners kerf kerf? notch_width first_notch_out? adjust_corners corners).each do |method_name|
39
10
  def_delegator :@edge, method_name, method_name
40
11
  end
@@ -66,7 +37,7 @@ module Laser
66
37
  point = starting_point
67
38
 
68
39
  vertices << point
69
- adjust_for_kerf(vertices,-1) if adjust_corners && !first_notch_out?
40
+ adjust_for_kerf(vertices, -1) if adjust_corners && !first_notch_out?
70
41
  shifts.each do |shift|
71
42
  point = shift.next_point_after point
72
43
  vertices << point
@@ -77,17 +48,16 @@ module Laser
77
48
  end
78
49
 
79
50
  def adjust_for_kerf(vertices, direction)
80
- if kerf?
81
- point = vertices.pop
82
- point = corners ? point.plus(2 * direction * shift_vector(1)) : point
83
- vertices << point
84
- end
51
+ return unless kerf?
52
+
53
+ point = vertices.pop
54
+ point = point.plus(2 * direction * shift_vector(1)) if corners
55
+ vertices << point
85
56
  end
86
57
 
87
58
  def corner_box_sides
88
59
  boxes = []
89
60
  extra_lines = []
90
- sides = []
91
61
 
92
62
  # These two boxes occupy the corners of the 3D box. They do not match
93
63
  # in width to our notches because they are usually merged with them.
@@ -95,19 +65,17 @@ module Laser
95
65
  boxes << Geometry::Rect[edge.inside.p1.clone, edge.outside.p1.clone]
96
66
  boxes << Geometry::Rect[edge.inside.p2.clone, edge.outside.p2.clone]
97
67
 
98
- if kerf?
99
- if adjust_corners
100
- if first_notch_out?
101
- k = 2
102
- direction = -1
103
- dim_index = 1
104
- extra_lines << add_corners_when_out(dim_index, direction, k)
105
- else
106
- k = -2
107
- direction = 1
108
- dim_index = 0
109
- extra_lines << add_boxes_when_in(dim_index, direction, k)
110
- end
68
+ if kerf? && adjust_corners
69
+ if first_notch_out?
70
+ k = 2
71
+ direction = -1
72
+ dim_index = 1
73
+ extra_lines << add_corners_when_out(dim_index, direction, k)
74
+ else
75
+ k = -2
76
+ direction = 1
77
+ dim_index = 0
78
+ extra_lines << add_boxes_when_in(dim_index, direction, k)
111
79
  end
112
80
  end
113
81
  sides = boxes.flatten.map(&:relocate!).map(&:sides)
@@ -162,18 +130,17 @@ module Laser
162
130
  def shift_vector(index, dim_shift = 0)
163
131
  shift = []
164
132
  shift[(d_index_across + dim_shift) % 2] = 0
165
- shift[(d_index_along + dim_shift) % 2] = kerf / 2.0 * edge.send("v#{index}".to_sym).[]((d_index_along + dim_shift) % 2)
133
+ shift[(d_index_along + dim_shift) % 2] = kerf / 2.0 * edge.send(:"v#{index}").[]((d_index_along + dim_shift) % 2)
166
134
  Vector.[](*shift)
167
135
  end
168
136
 
169
-
170
137
  def starting_point
171
138
  edge.inside.p1.clone # start
172
139
  end
173
140
 
174
141
  # 0 = X, 1 = Y
175
142
  def d_index_along
176
- (edge.inside.p1.x == edge.inside.p2.x) ? 1 : 0
143
+ edge.inside.p1.x == edge.inside.p2.x ? 1 : 0
177
144
  end
178
145
 
179
146
  def d_index_across
@@ -181,11 +148,11 @@ module Laser
181
148
  end
182
149
 
183
150
  def direction_along
184
- (edge.inside.p1.coords.[](d_index_along) < edge.inside.p2.coords.[](d_index_along)) ? 1 : -1
151
+ edge.inside.p1.coords.[](d_index_along) < edge.inside.p2.coords.[](d_index_along) ? 1 : -1
185
152
  end
186
153
 
187
154
  def direction_across
188
- (edge.inside.p1.coords.[](d_index_across) < edge.outside.p1.coords.[](d_index_across)) ? 1 : -1
155
+ edge.inside.p1.coords.[](d_index_across) < edge.outside.p1.coords.[](d_index_across) ? 1 : -1
189
156
  end
190
157
 
191
158
  private
@@ -206,8 +173,8 @@ module Laser
206
173
  end
207
174
 
208
175
  (1..edge.notch_count).to_a.each do |notch_number|
209
- shifts << along_iter.next do |shift, index|
210
- if inner && (notch_number > 1 && notch_number < edge.notch_count)
176
+ shifts << along_iter.next do |shift, _index|
177
+ if inner && notch_number > 1 && notch_number < edge.notch_count
211
178
  shift.delta -= kerf
212
179
  elsif !inner
213
180
  shift.delta += kerf
@@ -238,7 +205,7 @@ module Laser
238
205
  lines = []
239
206
  vertices.each_with_index do |v, i|
240
207
  if v != vertices.last
241
- lines << Geometry::Line.new(v, vertices[i+1])
208
+ lines << Geometry::Line.new(v, vertices[i + 1])
242
209
  end
243
210
  end
244
211
  lines.flatten
@@ -0,0 +1,17 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Laser
4
+ module Cutter
5
+ module Notching
6
+ Shift = Struct.new(:delta, :direction, :dim_index) do
7
+ def next_point_after(point)
8
+ p = point.clone
9
+ shift = []
10
+ shift[dim_index] = delta * direction
11
+ shift[(dim_index + 1) % 2] = 0
12
+ p.plus(*shift)
13
+ end
14
+ end
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,50 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Laser
4
+ module Cutter
5
+ class PageManager
6
+ SIZES = PDF::Core::PageGeometry::SIZES.clone.freeze
7
+
8
+ attr_reader :units
9
+
10
+ # @param units [String, Symbol] 'in' or 'mm'; the configuration's default is a Symbol
11
+ def initialize(units)
12
+ @units = units.to_s
13
+ end
14
+
15
+ def all_page_sizes
16
+ output = +""
17
+ page_size_values.each do |k|
18
+ output << sprintf("\t%10s:\t%6.1f x %6.1f\n", *k)
19
+ end
20
+ output
21
+ end
22
+
23
+ # if from_units is nil, we expect it to be in dots per inch (default
24
+ # measurements for Prawn
25
+ def value_from_units(value, from_units = nil)
26
+ multiplier = if from_units.nil?
27
+ if units.eql?('in')
28
+ 1.0 / 72.0 # PDF units per inch
29
+ else
30
+ 25.4 * 1.0 / 72.0
31
+ end
32
+ elsif units.eql?(from_units)
33
+ 1.0
34
+ elsif units.eql?('in') && from_units.eql?('mm')
35
+ (1.0 / 25.4)
36
+ else
37
+ 25.4
38
+ end
39
+ value.to_f * multiplier
40
+ end
41
+
42
+ def page_size_values
43
+ h = Hashie::Mash.new(SIZES)
44
+ h.keys.sort.map do |k|
45
+ [k, value_from_units(h[k][0].to_f), value_from_units(h[k][1].to_f)]
46
+ end
47
+ end
48
+ end
49
+ end
50
+ end
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Laser
2
4
  module Cutter
3
5
  module Renderer
@@ -21,10 +23,9 @@ module Laser
21
23
  end
22
24
 
23
25
  def units
24
- config.units.to_sym || :mm
26
+ config.units.to_sym
25
27
  end
26
28
  end
27
29
  end
28
30
  end
29
31
  end
30
-
@@ -0,0 +1,40 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Laser
4
+ module Cutter
5
+ module Renderer
6
+ class BoxRenderer < Base
7
+ alias_method :box, :subject
8
+
9
+ def initialize(config)
10
+ super
11
+ self.subject = Laser::Cutter::Box.new(config)
12
+ end
13
+
14
+ def ensure_space_for(rect)
15
+ coords = [rect.p2.x, rect.p2.y].map{ |a| page_manager.value_from_units(a) }
16
+ box.metadata = Geometry::Point.new(coords)
17
+ end
18
+
19
+ def enclosure
20
+ box.enclosure
21
+ end
22
+
23
+ # @return [Array<Geometry::Line>] every line to cut, computed once
24
+ def lines
25
+ @lines ||= box.generate_notches
26
+ end
27
+
28
+ # Draws the box, yielding after each line so a caller can count them.
29
+ def render(pdf = nil)
30
+ pdf.line_width = config.stroke.send(units)
31
+ pdf.stroke_color config[:color] || BLACK
32
+ lines.each do |line|
33
+ LineRenderer.new(config, line).render(pdf)
34
+ yield line if block_given?
35
+ end
36
+ end
37
+ end
38
+ end
39
+ end
40
+ end
@@ -0,0 +1,61 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Laser
4
+ module Cutter
5
+ module Renderer
6
+ # Writes the PDF: the box, its metadata block, and the unkerfed outline
7
+ # when asked for.
8
+ class LayoutRenderer < Base
9
+ UNKERFED_COLOR = 'DD2211'
10
+
11
+ def initialize(config)
12
+ self.config = config
13
+ super
14
+ end
15
+
16
+ # @return [Integer] how many lines {#render} draws, and yields
17
+ def total
18
+ box_renderers.sum { |renderer| renderer.lines.size }
19
+ end
20
+
21
+ # Writes the file, yielding after each line drawn.
22
+ def render(&)
23
+ margin = config.margin.to_f.send(units)
24
+ pdf = Prawn::Document.new(margin: margin,
25
+ page_size: config.page_size || calculate_image_boundary(box_renderers.first, margin),
26
+ page_layout: config.page_layout.to_sym)
27
+
28
+ box_renderers.first.render(pdf, &)
29
+ meta_renderer&.render(pdf)
30
+ box_renderers.drop(1).each { |renderer| renderer.render(pdf, &) }
31
+ pdf.render_file(config.file)
32
+ end
33
+
34
+ def calculate_image_boundary(box_renderer, margin)
35
+ box_renderer.enclosure.to_a[1].map do |c|
36
+ c.send(units) + (2 * margin)
37
+ end
38
+ end
39
+
40
+ private
41
+
42
+ def meta_renderer
43
+ @meta_renderer ||= MetaRenderer.new(config) if config.metadata
44
+ end
45
+
46
+ # The box, then the same box without kerf when config.debug is set.
47
+ def box_renderers
48
+ @box_renderers ||= begin
49
+ configs = [config]
50
+ configs << Configuration.new(config.to_hash).merge!(kerf: 0.0, color: UNKERFED_COLOR) if config.debug
51
+ configs.map do |box_config|
52
+ BoxRenderer.new(box_config).tap do |renderer|
53
+ renderer.ensure_space_for(meta_renderer.enclosure) if meta_renderer
54
+ end
55
+ end
56
+ end
57
+ end
58
+ end
59
+ end
60
+ end
61
+ end
@@ -0,0 +1,21 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Laser
4
+ module Cutter
5
+ module Renderer
6
+ class LineRenderer < Base
7
+ alias_method :line, :subject
8
+ def render(pdf = nil)
9
+ pdf.stroke {
10
+ pdf.line([line.p1.x, line.p1.y].map{ |p| p.send(units) },
11
+ [line.p2.x, line.p2.y].map{ |p| p.send(units) })
12
+ }
13
+ end
14
+
15
+ def enclosure
16
+ line
17
+ end
18
+ end
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,83 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Laser
4
+ module Cutter
5
+ module Renderer
6
+ class MetaRenderer < Laser::Cutter::Renderer::Base
7
+ META_KEYS = %i(units width height depth thickness notch kerf stroke padding margin page_size page_layout).freeze
8
+
9
+ def initialize(config = {})
10
+ self.config = config
11
+ self.enclosure = Laser::Cutter::Geometry::Rect.create(Laser::Cutter::Geometry::Point[1, 1], 140, 150)
12
+ end
13
+
14
+ def render(pdf = nil)
15
+ banner = <<-EOF
16
+
17
+ Made with Laser Cutter Ruby Gem (v#{Laser::Cutter::VERSION})
18
+ Credits to Prawn for ruby PDF generation,
19
+ and BoxMaker for inspiration.
20
+
21
+ Online: http://makeabox.io/
22
+ Source: https://github.com/kigster/laser-cutter
23
+ EOF
24
+
25
+ meta_color = BLUE
26
+ meta_top_height = 55
27
+
28
+ metadata = config.to_hash
29
+ metadata[:page_size] ||= 'custom'
30
+ metadata.delete(:page_layout) if metadata[:page_size].eql?('custom')
31
+
32
+ meta_fields = META_KEYS.find_all{ |k| metadata[k] }.join(": \n") + ": \n"
33
+ meta_values = META_KEYS.find_all{ |k| metadata[k] }.map{ |k| metadata[k].to_s }.join("\n")
34
+
35
+ rect = enclosure
36
+
37
+ pdf.instance_eval do
38
+ self.line_width = 0.2.mm
39
+ float do
40
+ bounding_box([rect.p1.x, rect.h + rect.p1.y], width: rect.w, height: rect.h) do
41
+ stroke_color meta_color
42
+ stroke_bounds
43
+
44
+ # Print banner
45
+ indent 10 do
46
+ font('Helvetica', size: 6) do
47
+ text banner, color: meta_color
48
+ end
49
+ end
50
+
51
+ # print values of the config, in two parts – keys right aligned first, values left aligned second.
52
+ float do
53
+ bounding_box([0, rect.h - meta_top_height],
54
+ width: rect.w,
55
+ height: rect.h - meta_top_height) do
56
+ float do
57
+ bounding_box([0, rect.h - meta_top_height], width: 70, height: rect.h - meta_top_height) do
58
+ indent 10 do
59
+ font('Helvetica', size: 7) do
60
+ text meta_fields, color: meta_color, align: :right
61
+ end
62
+ end
63
+ end
64
+ end
65
+ float do
66
+ bounding_box([60, rect.h - meta_top_height], width: 70, height: rect.h - meta_top_height) do
67
+ indent 10 do
68
+ font('Helvetica', size: 7) do
69
+ text meta_values, color: meta_color
70
+ end
71
+ end
72
+ end
73
+ end
74
+ end
75
+ end
76
+ end
77
+ end
78
+ end
79
+ end
80
+ end
81
+ end
82
+ end
83
+ end
@@ -1,9 +1,11 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Laser
2
4
  module Cutter
3
5
  module Renderer
4
6
  class RectRenderer < Base
5
7
  alias_method :rect, :subject
6
- def render pdf
8
+ def render(pdf)
7
9
  rect.sides.each do |side|
8
10
  LineRenderer.new(config, side).render(pdf)
9
11
  end
@@ -0,0 +1,65 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'victor'
4
+
5
+ module Laser
6
+ module Cutter
7
+ module Renderer
8
+ # Writes the box as an SVG sized in the configuration's own units. It
9
+ # fits the page to the box, and carries the metadata as a <desc>
10
+ # instead of drawing it.
11
+ class SvgRenderer < Base
12
+ def initialize(config)
13
+ super
14
+ self.subject = Laser::Cutter::Box.new(config)
15
+ end
16
+
17
+ # @return [Array<Geometry::Line>] every line to cut, computed once
18
+ def lines
19
+ @lines ||= subject.generate_notches
20
+ end
21
+
22
+ # @return [Integer] how many lines {#render} draws, and yields
23
+ def total
24
+ lines.size
25
+ end
26
+
27
+ # Writes the file, yielding after each line drawn.
28
+ def render
29
+ svg = Victor::SVG.new(width: "#{width}#{units}", height: "#{height}#{units}", viewBox: "0 0 #{width} #{height}")
30
+ svg.element(:desc, description) if config.metadata
31
+ lines.each do |line|
32
+ svg.line(**coordinates(line), stroke: "##{config[:color] || BLACK}", stroke_width: config.stroke)
33
+ yield line if block_given?
34
+ end
35
+ File.write(config.file, svg.render)
36
+ end
37
+
38
+ private
39
+
40
+ def margin
41
+ config.margin.to_f
42
+ end
43
+
44
+ def width
45
+ (subject.enclosure.p2.x + (2 * margin)).round(5)
46
+ end
47
+
48
+ def height
49
+ (subject.enclosure.p2.y + (2 * margin)).round(5)
50
+ end
51
+
52
+ # SVG counts y down from the top, the box geometry up from the bottom.
53
+ def coordinates(line)
54
+ { x1: (line.p1.x + margin).round(5), y1: (height - margin - line.p1.y).round(5),
55
+ x2: (line.p2.x + margin).round(5), y2: (height - margin - line.p2.y).round(5) }
56
+ end
57
+
58
+ def description
59
+ fields = MetaRenderer::META_KEYS.filter_map { |key| "#{key}: #{config[key]}" if config[key] }
60
+ "Made with laser-cutter v#{VERSION}. #{fields.join(', ')}"
61
+ end
62
+ end
63
+ end
64
+ end
65
+ end
@@ -0,0 +1,24 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Laser
4
+ module Cutter
5
+ module Renderer
6
+ # The renderer that writes each output format.
7
+ FORMATS = {
8
+ 'pdf' => 'LayoutRenderer',
9
+ 'svg' => 'SvgRenderer'
10
+ }.freeze
11
+
12
+ # @param format [String, Symbol] 'pdf' or 'svg', in either case
13
+ # @param config [Configuration]
14
+ # @return [LayoutRenderer, SvgRenderer]
15
+ # @raise [Error] for a format nothing here writes
16
+ def self.for(format, config)
17
+ name = FORMATS.fetch(format.to_s.downcase) do
18
+ raise Error, "unknown format #{format.inspect}, expected one of: #{FORMATS.keys.join(', ')}"
19
+ end
20
+ const_get(name).new(config)
21
+ end
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,15 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Laser
4
+ module Cutter
5
+ class UnitsConverter
6
+ def self.mm2in(value)
7
+ (0.039370079 * value).round(5)
8
+ end
9
+
10
+ def self.in2mm(value)
11
+ (25.4 * value).round(5)
12
+ end
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,7 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Laser
4
+ module Cutter
5
+ VERSION = "2.0.0"
6
+ end
7
+ end
@@ -0,0 +1,7 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Laser
4
+ module Cutter
5
+ class ZeroValueNotAllowed < MissingOption; end
6
+ end
7
+ end
@@ -0,0 +1,29 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "forwardable"
4
+ require "json"
5
+ require "matrix"
6
+
7
+ require "hashie/mash"
8
+ require "hashie/extensions/symbolize_keys"
9
+ require "hashie/extensions/mash/symbolize_keys"
10
+ require "pdf/core/page_geometry"
11
+ require "prawn"
12
+ require "prawn/measurement_extensions"
13
+ require "dry/cli"
14
+ require "dry/cli/help"
15
+ require "dry/cli/ui"
16
+ require "dry/cli/autocomplete/command"
17
+ require "tty-screen"
18
+ require "zeitwerk"
19
+
20
+ module Laser
21
+ module Cutter
22
+ class Error < StandardError; end
23
+ end
24
+ end
25
+
26
+ loader = Zeitwerk::Loader.for_gem_extension(Laser)
27
+ loader.inflector.inflect("cli" => "CLI")
28
+ loader.setup
29
+ loader.eager_load
data/lib/laser-cutter.rb CHANGED
@@ -1,15 +1,3 @@
1
- require 'laser-cutter/version'
2
- require 'laser-cutter/configuration'
3
- require 'laser-cutter/geometry'
4
- require 'laser-cutter/box'
5
- require 'laser-cutter/renderer'
6
- require 'laser-cutter/page_manager'
7
- require 'laser-cutter/notching'
8
- require 'laser-cutter/aggregator'
9
- require 'prawn'
10
- require 'prawn/measurement_extensions'
1
+ # frozen_string_literal: true
11
2
 
12
- module Laser
13
- module Cutter
14
- end
15
- end
3
+ require_relative "laser/cutter"
@@ -0,0 +1,3 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "laser/cutter"