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.
Files changed (104) 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 -5
  6. data/.plans/001.00-dry-cli-migration/plan.md +29 -0
  7. data/.relaxed_rubocop.yml +153 -0
  8. data/.rspec +1 -0
  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 +110 -87
  16. data/Rakefile +27 -13
  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/box.rb +173 -0
  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 +23 -22
  44. data/lib/laser/cutter/notching/infinite_iterator.rb +27 -0
  45. data/lib/{laser-cutter → laser/cutter}/notching/path_generator.rb +76 -94
  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 +35 -22
  78. data/spec/support/aruba.rb +10 -0
  79. metadata +202 -76
  80. data/.travis.yml +0 -26
  81. data/BOXMAKER.md +0 -51
  82. data/LICENSE +0 -22
  83. data/bin/laser-cutter +0 -19
  84. data/lib/laser-cutter/aggregator.rb +0 -57
  85. data/lib/laser-cutter/box.rb +0 -183
  86. data/lib/laser-cutter/cli/opt_parser.rb +0 -131
  87. data/lib/laser-cutter/cli/serializer.rb +0 -51
  88. data/lib/laser-cutter/configuration.rb +0 -83
  89. data/lib/laser-cutter/face.rb +0 -18
  90. data/lib/laser-cutter/geometry.rb +0 -11
  91. data/lib/laser-cutter/notching.rb +0 -9
  92. data/lib/laser-cutter/page_manager.rb +0 -43
  93. data/lib/laser-cutter/renderer/box_renderer.rb +0 -36
  94. data/lib/laser-cutter/renderer/layout_renderer.rb +0 -59
  95. data/lib/laser-cutter/renderer/line_renderer.rb +0 -17
  96. data/lib/laser-cutter/renderer/meta_renderer.rb +0 -77
  97. data/lib/laser-cutter/renderer.rb +0 -14
  98. data/lib/laser-cutter/version.rb +0 -5
  99. data/spec/aggregator_spec.rb +0 -65
  100. data/spec/box_spec.rb +0 -39
  101. data/spec/configuration_spec.rb +0 -81
  102. data/spec/edge_spec.rb +0 -43
  103. data/spec/renderer_spec.rb +0 -72
  104. /data/docs/{comparison.jpg → images/comparison.jpg} +0 -0
@@ -1,183 +0,0 @@
1
- require 'forwardable'
2
- module Laser
3
- module Cutter
4
- class Box
5
- # Everything is in millimeters
6
- extend Forwardable
7
- def_delegators :@dim, :w, :h, :d
8
-
9
- attr_accessor :dim, :thickness, :notch_width, :kerf
10
- attr_accessor :padding, :units, :inside_box
11
-
12
- attr_accessor :front, :back, :top, :bottom, :left, :right
13
- attr_accessor :faces, :bounds, :conf, :corner_face
14
- attr_accessor :metadata, :notches
15
-
16
- def initialize(config = {})
17
- self.dim = Geometry::Dimensions.new(config['width'], config['height'], config['depth'])
18
- self.thickness = config['thickness']
19
-
20
- self.notch_width = config['notch'] || (1.0 * self.longest / 5.0)
21
- self.kerf = config['kerf'] || 0.0
22
- self.padding = config['padding']
23
- self.units = config['units']
24
- self.inside_box = config['inside_box']
25
-
26
- self.notches = []
27
-
28
- self.metadata = Geometry::Point[config['metadata_width'] || 0, config['metadata_height'] || 0]
29
-
30
- create_faces! # generates dimensions for each side
31
- self.faces = [top, front, bottom, back, left, right]
32
-
33
- self.conf = {
34
- valign: [:out, :out, :out, :out, :in, :in],
35
- halign: [:in, :out, :in, :out, :in, :in],
36
- corners: {
37
- top: [:yes, :no, :yes, :no, :no, :no], # 2nd choice, has to work if 1st doesn't
38
- front: [:no, :yes, :no, :yes, :no, :no], # our default choice, but may not work
39
- },
40
- }
41
- self
42
- end
43
-
44
- def generate_notches
45
- position_faces!
46
- self.corner_face = pick_corners_face
47
- self.notches = []
48
- faces.each_with_index do |face, face_index|
49
- create_face_edges(face, face_index)
50
- end
51
- self.notches.flatten!
52
- end
53
-
54
- def enclosure
55
- generate_notches if self.notches.empty?
56
- p1 = notches.first.p1.to_a
57
- p2 = notches.first.p2.to_a
58
-
59
- notches.each do |notch|
60
- n = notch.normalized
61
- n.p1.to_a.each_with_index {|c, i| p1[i] = c if c < p1[i]}
62
- n.p2.to_a.each_with_index {|c, i| p2[i] = c if c > p2[i]}
63
- end
64
-
65
- Geometry::Rect[Geometry::Point.new(p1), Geometry::Point.new(p2)]
66
- end
67
-
68
- def longest
69
- [w, h, d].max
70
- end
71
-
72
- def to_s
73
- "Box:\nH:#{dim.h} W:#{dim.w} D:#{dim.d}\nThickness:#{thickness}, Notch:#{notch_width}"
74
- end
75
-
76
- private
77
-
78
- def create_face_edges(face, face_index)
79
- bound = face_bounding_rect(face)
80
-
81
- side_lines = []
82
- edges = []
83
- bound.sides.each_with_index do |bounding_side, side_index|
84
- include_corners = (self.conf[:corners][corner_face][face_index] == :yes && side_index.odd?)
85
- key = side_index.odd? ? :valign : :halign
86
- center_out = (self.conf[key][face_index] == :out)
87
- edges << Notching::Edge.new(bounding_side, face.sides[side_index],
88
- { :notch_width => notch_width,
89
- :thickness => thickness,
90
- :kerf => kerf,
91
- :center_out => center_out,
92
- :corners => include_corners
93
- })
94
- end
95
-
96
- if edges.any? {|e| e.corners} && !edges.all? {|e| e.first_notch_out?}
97
- edges.each {|e| e.adjust_corners = true}
98
- end
99
-
100
- edges.each do |edge|
101
- side_lines << Notching::PathGenerator.new(edge).generate
102
- end
103
-
104
- aggregator = Aggregator.new(side_lines.flatten)
105
- aggregator.dedup!.deoverlap!.dedup!
106
- self.notches << aggregator.lines
107
- end
108
-
109
- def face_bounding_rect(face)
110
- b = face.clone
111
- b.move_to(b.position.plus(-thickness, -thickness))
112
- b.p2 = b.p2.plus(2 * thickness, 2 * thickness)
113
- b.relocate!
114
- end
115
-
116
- #___________________________________________________________________
117
- #
118
- # +-----------------+
119
- # | |
120
- # | back: W x H |
121
- # | |
122
- # +-----------------+
123
- # +-----------------+
124
- # | bottom: W x D |
125
- # +-----------------+
126
- # +--------+ +-----------------+ +--------+
127
- # | | | | | |
128
- # | left | | front: W x H | | right |
129
- # | D x H | | | | D x H |
130
- # +--------+ X-----------------+ +--------+
131
- # +-----------------+
132
- # | top : W x D |
133
- # +-----------------+
134
- #
135
- # 0,0
136
- #___________________________________________________________________
137
-
138
- def position_faces!
139
- offset_x = [padding + d + 3 * thickness, metadata.x + 2 * thickness + padding].max
140
- offset_y = [padding + d + 3 * thickness, metadata.y + 2 * thickness + padding].max
141
-
142
- # X Coordinate
143
- left.x = offset_x - d - 2 * thickness - padding
144
- right.x = offset_x + w + 2 * thickness + padding
145
-
146
- [bottom, front, top, back].each {|s| s.x = offset_x}
147
-
148
- # Y Coordinate
149
- top.y = offset_y - d - 2 * thickness - padding
150
- bottom.y = offset_y + h + 2 * thickness + padding
151
- back.y = bottom.y + d + 2 * thickness + padding
152
-
153
- [left, front, right].each {|s| s.y = offset_y}
154
-
155
- faces.each(&:relocate!)
156
- end
157
-
158
- def create_faces!
159
- zero = Geometry::Point.new(0, 0)
160
- self.front = Geometry::Rect.create(zero, dim.w, dim.h, "front")
161
- self.back = Geometry::Rect.create(zero, dim.w, dim.h, "back")
162
-
163
- self.top = Geometry::Rect.create(zero, dim.w, dim.d, "top")
164
- self.bottom = Geometry::Rect.create(zero, dim.w, dim.d, "bottom")
165
-
166
- self.left = Geometry::Rect.create(zero, dim.d, dim.h, "left")
167
- self.right = Geometry::Rect.create(zero, dim.d, dim.h, "right")
168
- end
169
-
170
- # Choose which face will be responsible for filling out the little square overlap
171
- # in the corners. Only one of the 3 possible sides need to be picked.
172
- def pick_corners_face
173
- b = face_bounding_rect(front)
174
- edges = []
175
- front.sides[0..1].each_with_index do |face, index|
176
- edges << Notching::Edge.new(b.sides[index], face, :notch_width => notch_width, :kerf => kerf)
177
- end
178
- edges.map(&:notch_count).all? {|c| c % 4 == 3} ? :top : :front
179
- end
180
-
181
- end
182
- end
183
- end
@@ -1,131 +0,0 @@
1
- require 'optparse'
2
- require 'colored'
3
- require 'json'
4
- require 'hashie/mash'
5
- require 'laser-cutter'
6
- require_relative 'serializer'
7
-
8
- module Laser
9
- module Cutter
10
- module CLI
11
- class OptParser
12
- def self.puts_error(e)
13
- STDERR.puts "Whoops, #{e}".red
14
- STDERR.puts "Try --help or --examples for more info...".yellow
15
- end
16
-
17
- def self.parse(args)
18
- banner_text = <<-EOF
19
- #{('Laser-Cutter v'+ Laser::Cutter::VERSION).bold}
20
-
21
- Usage: laser-cutter [options] -o filename.pdf
22
- eg: laser-cutter -z 1x1.5x2/0.125 -O -o box.pdf
23
- EOF
24
-
25
- examples = <<-EOF
26
-
27
- Examples:
28
- 1. Create a box defined in inches, set kerf to 0.008" and open PDF in preview right after:
29
-
30
- laser-cutter -z 3x2x2/0.125 -k 0.008 -O -o box.pdf
31
-
32
- 2. Create a box defined in millimeters, print verbose info, and set
33
- page size to A3, and layout to landscape, and stroke width to 1/2mm:
34
-
35
- laser-cutter -u mm -w70 -h20 -d50 -t4.3 -n5 -iA3 -l landscape -s0.5 -v -O -o box.pdf
36
-
37
- 3. List all possible page sizes in metric systems:
38
-
39
- laser-cutter -L -u mm
40
-
41
- 4. Create a box with provided dimensions, and save the config to a file
42
- for later use:
43
-
44
- laser-cutter -z 1.1x2.5x1.5/0.125/0.125 -p 0.1 -O -o box.pdf -W box-settings.json
45
-
46
- 5. Read settings from a previously saved file:
47
-
48
- laser-cutter -O -o box.pdf -R box-settings.json
49
- cat box-settings.json | laser-cutter -O -o box.pdf -R -
50
-
51
- EOF
52
- options = Hashie::Mash.new
53
- options.verbose = false
54
- options.units = 'in'
55
-
56
- opt_parser = OptionParser.new do |opts|
57
- opts.banner = banner_text.blue
58
- opts.separator "Specific Options:"
59
- opts.on("-w", "--width WIDTH", "Internal width of the box") { |value| options.width = value }
60
- opts.on("-h", "--height HEIGHT", "Internal height of the box") { |value| options.height = value }
61
- opts.on("-d", "--depth DEPTH", "Internal depth of the box") { |value| options.depth= value }
62
- opts.on("-t", "--thickness THICKNESS", "Thickness of the box material") { |value| options.thickness = value }
63
- opts.on("-n", "--notch NOTCH", "Optional notch length (aka \"tab width\"), guide only") { |value| options.notch = value }
64
- opts.on("-k", "--kerf KERF", "Kerf - cut width (default is #{Laser::Cutter::Configuration::UNIT_SPECIFIC_DEFAULTS['in'][:kerf]}in)") { |value| options.kerf = value }
65
- opts.separator ""
66
- opts.on("-m", "--margin MARGIN", "Margins from the edge of the document") { |value| options.margin = value }
67
- opts.on("-p", "--padding PADDING", "Space between the boxes on the page") { |value| options.padding = value }
68
- opts.on("-s", "--stroke WIDTH", "Numeric stroke width of the line") { |value| options.stroke = value }
69
- opts.on("-i", "--page_size LETTER", "Document page size, default is autofit the box.") { |value| options.page_size = value }
70
- opts.on("-l", "--page_layout portrait", "Page layout, other option is 'landscape' ") { |value| options.page_layout = value }
71
- opts.separator ""
72
- opts.on("-O", "--open", "Open generated file with system viewer before exiting") { |v| options.open = v }
73
- opts.on("-W", "--write CONFIG_FILE", "Save provided configuration to a file, use '-' for STDOUT") { |v| options.write_file = v }
74
- opts.on("-R", "--read CONFIG_FILE", "Read configuration from a file, or use '-' for STDIN") { |v| options.read_file = v }
75
- opts.separator ""
76
- opts.on("-L", "--list-all-page-sizes", "Print all available page sizes with dimensions and exit") { |v| options.list_all_page_sizes = true }
77
- opts.on("-M", "--no-metadata", "Do not print box metadata on the PDF") { |value| options.metadata = value }
78
- opts.on("-v", "--[no-]verbose", "Run verbosely") { |v| options.verbose = v }
79
- opts.on("-B", "--inside-box", "Draw the inside boxes (helpful to verify kerfing)") { |v| options.inside_box = v }
80
- opts.on("-D", "--debug", "Show full exception stack trace on error") { |v| options.debug = v }
81
- opts.separator ""
82
- opts.on("--examples", "Show detailed usage examples") { puts opts; puts examples.yellow; exit }
83
- opts.on("--help", "Show this message") { puts opts; exit }
84
- opts.on("--version", "Show version") { puts Laser::Cutter::VERSION; exit }
85
- opts.separator ""
86
- opts.separator "Common Options:"
87
- opts.on_tail("-o", "--file FILE", "Required output filename of the PDF") { |value| options.file = value }
88
- opts.on_tail("-z", "--size WxHxD/T[/N]",
89
- "Combined internal dimensions: W = width, H = height,\n#{" " * 37}D = depth, T = thickness, and optional N = notch length\n\n") do |size|
90
- options.size = size
91
- end
92
- opts.on_tail("-u", "--units UNITS", "Either 'in' for inches (default) or 'mm'") { |value| options.units = value }
93
- end
94
-
95
- opt_parser.parse!(args)
96
-
97
- if options.read_file
98
- # these options are kept from the command line
99
- override_with = %w(debug verbose read_file)
100
- keep = options.reject{ |k,v| !override_with.include?(k)}
101
- Serializer.new(options).deserialize
102
- options.merge!(keep)
103
- end
104
-
105
- config = Laser::Cutter::Configuration.new(options.to_hash)
106
- if config.list_all_page_sizes
107
- puts PageManager.new(config.units).all_page_sizes
108
- exit 0
109
- end
110
-
111
- if options.verbose
112
- puts "Starting with the following configuration:"
113
- puts JSON.pretty_generate(config.to_hash).green
114
- end
115
-
116
- config.validate!
117
-
118
- if config.write_file
119
- Serializer.new(config).serialize
120
- end
121
-
122
- config
123
- rescue OptionParser::InvalidOption, OptionParser::MissingArgument, Laser::Cutter::MissingOption => e
124
- puts opt_parser.banner.blue
125
- puts_error(e)
126
- exit 1
127
- end
128
- end
129
- end
130
- end
131
- end
@@ -1,51 +0,0 @@
1
- require 'json'
2
- require 'laser-cutter'
3
-
4
- module Laser
5
- module Cutter
6
- module CLI
7
- class Serializer
8
- attr_accessor :options
9
- def initialize(options = {})
10
- self.options = options
11
- end
12
-
13
- def deserialize
14
- string = if options.read_file.eql?('-')
15
- $stdin.read
16
- elsif File.exist?(options.read_file)
17
- File.read(options.read_file)
18
- end
19
- if string
20
- options.replace(JSON.load(string))
21
- end
22
- rescue Exception => e
23
- STDERR.puts "Error reading options from file #{options.read_file}, #{e.message}".red
24
- if options.verbose
25
- STDERR.puts e.backtrace.join("\n").red
26
- end
27
- exit 1
28
- end
29
-
30
- def serialize
31
- output = if options.write_file.eql?('-')
32
- $stdout
33
- elsif options.write_file
34
- File.open(options.write_file, 'w')
35
- else
36
- nil
37
- end
38
- output.puts(JSON.pretty_generate(options))
39
- output.close if output != $stdout
40
- rescue Exception => e
41
- STDERR.puts "Error writing options to file #{options.write_file}, #{e.message}".red
42
- if options.verbose
43
- STDERR.puts e.backtrace.join("\n").red
44
- end
45
- exit 1
46
- end
47
- end
48
- end
49
- end
50
- end
51
-
@@ -1,83 +0,0 @@
1
- require 'hashie/mash'
2
- require 'prawn/measurement_extensions'
3
- require 'pdf/core/page_geometry'
4
-
5
- module Laser
6
- module Cutter
7
- class MissingOption < RuntimeError; end
8
- class ZeroValueNotAllowed < MissingOption; end
9
-
10
- class UnitsConverter
11
- def self.mm2in value
12
- 0.039370079 * value
13
- end
14
- def self.in2mm value
15
- 25.4 * value
16
- end
17
- end
18
-
19
- class Configuration < Hashie::Mash
20
- DEFAULTS = {
21
- units: 'in',
22
- page_layout: 'portrait',
23
- metadata: true
24
- }
25
-
26
- UNIT_SPECIFIC_DEFAULTS = {
27
- 'in' => {
28
- kerf: 0.0024, # smallest kerf for thin material, usually it's more than that.
29
- margin: 0.125,
30
- padding: 0.1,
31
- stroke: 0.001,
32
- }
33
- }
34
-
35
- UNIT_SPECIFIC_DEFAULTS['mm'] = UNIT_SPECIFIC_DEFAULTS['in'].map{|k, v| [k, UnitsConverter.in2mm(v)] }.to_h
36
-
37
- SIZE_REGEXP = /[\d\.]+x[\d\.]+x[\d\.]+\/[\d\.]+(\/[\d\.]+)?/
38
-
39
- FLOATS = %w(width height depth thickness notch margin padding stroke kerf)
40
- NON_ZERO = %w(width height depth thickness stroke)
41
- REQUIRED = %w(width height depth thickness notch file)
42
-
43
- def initialize(options = {})
44
- options.delete_if { |k, v| v.nil? }
45
- if options['units'] && !UNIT_SPECIFIC_DEFAULTS.keys.include?(options['units'])
46
- options.delete('units')
47
- end
48
- self.merge!(DEFAULTS)
49
- self.merge!(options)
50
- if self['size'] && self['size'] =~ SIZE_REGEXP
51
- dim, self['thickness'], self['notch'] = self['size'].split('/')
52
- self['width'], self['height'], self['depth'] = dim.split('x')
53
- delete('size')
54
- end
55
- FLOATS.each do |k|
56
- self[k] = self[k].to_f if (self[k] && self[k].is_a?(String))
57
- end
58
- self.merge!(UNIT_SPECIFIC_DEFAULTS[self['units']].merge(self))
59
- self['notch'] = (self['thickness'] * 3.0).round(5) if self['thickness'] && self['notch'].nil?
60
- end
61
-
62
- def validate!
63
- missing = []
64
- REQUIRED.each { |k| missing << k if self[k].nil? }
65
- raise MissingOption.new("#{missing.join(', ')} #{missing.size > 1 ? 'are' : 'is'} required, but missing.") unless missing.empty?
66
-
67
- zeros = []
68
- NON_ZERO.each { |k| zeros << k if self[k] == 0 }
69
- raise ZeroValueNotAllowed.new("#{zeros.join(', ')} #{zeros.size > 1 ? 'are' : 'is'} required, but is zero.") unless zeros.empty?
70
- end
71
-
72
- def change_units(new_units)
73
- return if (self.units.eql?(new_units) || !UNIT_SPECIFIC_DEFAULTS.keys.include?(new_units))
74
- k = (self.units == 'in') ? UnitsConverter.in2mm(1.0) : UnitsConverter.mm2in(1.0)
75
- FLOATS.each do |field|
76
- next if self.send(field.to_sym).nil?
77
- self.send("#{field}=".to_sym, (self.send(field.to_sym) * k).round(5))
78
- end
79
- self.units = new_units
80
- end
81
- end
82
- end
83
- end
@@ -1,18 +0,0 @@
1
- require 'forwardable'
2
- module Laser
3
- module Cutter
4
- # Note: this class badly needs refactoring and tests. Both are coming.
5
-
6
-
7
- class Face
8
- attr_accessor :edges, :rect
9
- extend Forwardable
10
- def_delegators :@rect, *((Geometry::Rect.new(Geometry::Point.new(0,0), Geometry::Point.new(1,1))).methods - Object.methods)
11
- def initialize(rect, edges = [])
12
- self.rect = rect
13
- self.edges = edges
14
- end
15
- end
16
-
17
- end
18
- end
@@ -1,11 +0,0 @@
1
- module Laser
2
- module Cutter
3
- module Geometry
4
- end
5
- end
6
- end
7
-
8
- require 'laser-cutter/geometry/tuple'
9
- require 'laser-cutter/geometry/dimensions'
10
- require 'laser-cutter/geometry/point'
11
- require 'laser-cutter/geometry/shape'
@@ -1,9 +0,0 @@
1
- module Laser
2
- module Cutter
3
- module Notching
4
- end
5
- end
6
- end
7
-
8
- require_relative 'notching/edge'
9
- require_relative 'notching/path_generator'
@@ -1,43 +0,0 @@
1
- require 'hashie/mash'
2
- require 'prawn/measurement_extensions'
3
- require 'pdf/core/page_geometry'
4
-
5
- class Laser::Cutter::PageManager < Struct.new(:units)
6
- SIZES = PDF::Core::PageGeometry::SIZES.clone.freeze
7
-
8
- def all_page_sizes
9
- output = ""
10
- page_size_values.each do |k|
11
- output << sprintf("\t%10s:\t%6.1f x %6.1f\n", *k)
12
- end
13
- output
14
- end
15
-
16
- # if from_units is nil, we expect it to be in dots per inch (default
17
- # measurements for Prawn
18
- def value_from_units value, from_units = nil
19
- multiplier = if from_units.nil?
20
- if units.eql?('in')
21
- 1.0 / 72.0 # PDF units per inch
22
- else
23
- 25.4 * 1.0 / 72.0
24
- end
25
- elsif self.units.eql?(from_units)
26
- 1.0
27
- elsif self.units.eql?('in') && from_units.eql?('mm')
28
- (1.0 / 25.4)
29
- else
30
- 25.4
31
- end
32
- value.to_f * multiplier
33
- end
34
-
35
- def page_size_values
36
- h = SIZES
37
- array = []
38
- h.keys.sort.each do |k|
39
- array << [k, value_from_units(h[k][0].to_f), value_from_units(h[k][1].to_f)]
40
- end
41
- array
42
- end
43
- end
@@ -1,36 +0,0 @@
1
- # encoding: utf-8
2
- require 'json'
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(config)
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
- def render pdf = nil
24
- renderer = self
25
- pdf.instance_eval do
26
- self.line_width = renderer.config.stroke.send(renderer.config.units.to_sym)
27
- stroke_color renderer.config[:color] || BLACK
28
- renderer.box.generate_notches.each do |notch|
29
- LineRenderer.new(renderer.config, notch).render(self)
30
- end
31
- end
32
- end
33
- end
34
- end
35
- end
36
- end
@@ -1,59 +0,0 @@
1
- # encoding: utf-8
2
- require 'json'
3
- module Laser
4
- module Cutter
5
- module Renderer
6
- class LayoutRenderer < Base
7
- def initialize(config)
8
- self.config = config
9
- super(config)
10
- end
11
-
12
- def render
13
- renderer = self
14
- renderers = []
15
-
16
- box_renderer = BoxRenderer.new(config)
17
- renderers << box_renderer
18
-
19
- if config.metadata
20
- meta_renderer = MetaRenderer.new(config)
21
- renderers << meta_renderer
22
- box_renderer.ensure_space_for(meta_renderer.enclosure)
23
- end
24
-
25
- if config.debug
26
- unkerfed_config = Laser::Cutter::Configuration.new(config.to_hash)
27
- unkerfed_config.merge!(kerf: 0.0, color: 'DD2211')
28
- unkerfed_box_renderer = BoxRenderer.new(unkerfed_config)
29
- unkerfed_box_renderer.ensure_space_for(meta_renderer.enclosure) if meta_renderer
30
- renderers << unkerfed_box_renderer
31
- end
32
-
33
- margin = config.margin.to_f.send(config.units.to_sym)
34
- page_size = config.page_size || calculate_image_boundary(box_renderer, margin)
35
-
36
- pdf = Prawn::Document.new(:margin => margin,
37
- :page_size => page_size,
38
- :page_layout => self.config.page_layout.to_sym)
39
-
40
- pdf.instance_eval do
41
- renderers.each {|r| r.render(self) }
42
- render_file(renderer.config.file)
43
- end
44
-
45
- if config.verbose
46
- puts "PDF saved to #{config.file}."
47
- end
48
- end
49
-
50
- def calculate_image_boundary(box_renderer, margin)
51
- box_renderer.enclosure.to_a[1].map do |c|
52
- c.send(config.units.to_sym) + 2 * margin
53
- end
54
- end
55
-
56
- end
57
- end
58
- end
59
- end
@@ -1,17 +0,0 @@
1
- module Laser
2
- module Cutter
3
- module Renderer
4
- class LineRenderer < Base
5
- alias_method :line, :subject
6
- def render pdf = nil
7
- pdf.stroke { pdf.line [line.p1.x, line.p1.y].map{ |p| p.send(units) },
8
- [line.p2.x, line.p2.y].map{ |p| p.send(units) }}
9
- end
10
-
11
- def enclosure
12
- self.line
13
- end
14
- end
15
- end
16
- end
17
- end