laser-cutter 0.5.3 → 1.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 (38) hide show
  1. checksums.yaml +4 -4
  2. data/.travis.yml +1 -1
  3. data/README.md +20 -16
  4. data/bin/laser-cutter +4 -158
  5. data/lib/laser-cutter.rb +2 -0
  6. data/lib/laser-cutter/aggregator.rb +57 -0
  7. data/lib/laser-cutter/box.rb +37 -24
  8. data/lib/laser-cutter/cli/opt_parser.rb +131 -0
  9. data/lib/laser-cutter/cli/serializer.rb +51 -0
  10. data/lib/laser-cutter/configuration.rb +3 -2
  11. data/lib/laser-cutter/geometry.rb +0 -3
  12. data/lib/laser-cutter/geometry/dimensions.rb +3 -3
  13. data/lib/laser-cutter/geometry/point.rb +0 -46
  14. data/lib/laser-cutter/geometry/shape/line.rb +40 -1
  15. data/lib/laser-cutter/geometry/shape/rect.rb +2 -2
  16. data/lib/laser-cutter/geometry/tuple.rb +73 -27
  17. data/lib/laser-cutter/notching.rb +10 -0
  18. data/lib/laser-cutter/notching/base.rb +13 -0
  19. data/lib/laser-cutter/notching/edge.rb +87 -0
  20. data/lib/laser-cutter/notching/path_generator.rb +249 -0
  21. data/lib/laser-cutter/renderer/base.rb +1 -1
  22. data/lib/laser-cutter/renderer/box_renderer.rb +2 -2
  23. data/lib/laser-cutter/renderer/layout_renderer.rb +19 -8
  24. data/lib/laser-cutter/renderer/meta_renderer.rb +8 -9
  25. data/lib/laser-cutter/version.rb +1 -1
  26. data/spec/aggregator_spec.rb +65 -0
  27. data/spec/box_spec.rb +5 -1
  28. data/spec/dimensions_spec.rb +0 -1
  29. data/spec/edge_spec.rb +43 -0
  30. data/spec/line_spec.rb +42 -19
  31. data/spec/path_generator_spec.rb +30 -36
  32. data/spec/point_spec.rb +2 -2
  33. data/spec/rect_spec.rb +1 -1
  34. data/spec/renderer_spec.rb +14 -5
  35. metadata +13 -5
  36. data/lib/laser-cutter/geometry/edge.rb +0 -33
  37. data/lib/laser-cutter/geometry/notched_path.rb +0 -46
  38. data/lib/laser-cutter/geometry/path_generator.rb +0 -129
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 869598bd65fbb14370dcce5b969a19748f246ce2
4
- data.tar.gz: 4ce9cc14002201a598b5df25f1ae1a474b4269f6
3
+ metadata.gz: 1e42b32bc2ee9759bffa630c87390941e92400e0
4
+ data.tar.gz: 2c69316654ae0a33f13871be5b401a973db83b76
5
5
  SHA512:
6
- metadata.gz: c3b2164cedb8d7af10dfc1beb1482d902f5ce56ae916d32f35cdc289d42d87943567a0d8729f3e793d2844a40c99b27428b5a942b3ddb0b57e4055cc3a7e0266
7
- data.tar.gz: 9983e63d47f490f0354159f0ba96ddb086d30c2dc44b62e76f0541529bd1b546693b01100366ac7b0f68c44359b6431c873a4b4ec7fa5a0821dfddc2965edc85
6
+ metadata.gz: 5e8a25fc98e3e9d4ca9a57a543fe67978cff1981b9e57744406a5d2a5056f3960672b1902d7af27521a4edc889dea077c8c399f10da130f7751f1324bb784173
7
+ data.tar.gz: cda884dcc0c1328250f3e9dd0542870e4f856d406abd493d1bdd32573460ebc03026a1f88dde37d456bbcdb5fe791a1f6be1f4f4604e56ba1dfd43da33ac608b
@@ -1,6 +1,6 @@
1
1
  language: ruby
2
2
  rvm:
3
- - 1.9.3
3
+ - 2.1.3
4
4
  script: "CODECLIMATE_REPO_TOKEN=6520104decf84e2f6c2c169d30d67976bfcdaa48cd89b4ec34f6e637f2a0ae02 bundle exec rspec"
5
5
  notifications:
6
6
  email:
data/README.md CHANGED
@@ -50,29 +50,33 @@ Or install it yourself as:
50
50
  ## Usage
51
51
 
52
52
  ```bash
53
+
53
54
  Usage: laser-cutter [options] -o filename.pdf
54
- eg: laser-cutter -s 1x1.5x2/0.125/0.125 -O -o box.pdf
55
+ eg: laser-cutter -z 1x1.5x2/0.125 -O -o box.pdf
55
56
 
56
57
  Specific Options:
57
58
  -w, --width WIDTH Internal width of the box
58
59
  -h, --height HEIGHT Internal height of the box
59
60
  -d, --depth DEPTH Internal depth of the box
60
61
  -t, --thickness THICKNESS Thickness of the box material
61
- -n, --notch NOTCH Preferred notch length (used only as a guide)
62
+ -n, --notch NOTCH Optional notch length (aka "tab width"), guide only
63
+ -k, --kerf KERF Kerf - cut width (default is 0.007in)
62
64
 
63
65
  -m, --margin MARGIN Margins from the edge of the document
64
66
  -p, --padding PADDING Space between the boxes on the page
65
- -k, --stroke WIDTH Numeric stroke width of the line
66
- -z, --page_size LETTER Page size, see --list-all-page-sizes for more info.
67
- -y, --page_layout portrait Page layout, other option is 'landscape'
67
+ -s, --stroke WIDTH Numeric stroke width of the line
68
+ -i, --page_size LETTER Document page size, default is autofit the box.
69
+ -l, --page_layout portrait Page layout, other option is 'landscape'
68
70
 
69
71
  -O, --open Open generated file with system viewer before exiting
70
- -W, --write FILE Save provided configuration to a file, use '-' for STDOUT
71
- -R, --read FILE Read configuration from a file, or use '-' for STDIN
72
+ -W, --write CONFIG_FILE Save provided configuration to a file, use '-' for STDOUT
73
+ -R, --read CONFIG_FILE Read configuration from a file, or use '-' for STDIN
72
74
 
73
- -l, --list-all-page-sizes Print all available page sizes with dimensions and exit
75
+ -L, --list-all-page-sizes Print all available page sizes with dimensions and exit
74
76
  -M, --no-metadata Do not print box metadata on the PDF
75
77
  -v, --[no-]verbose Run verbosely
78
+ -B, --inside-box Draw the inside boxes (helpful to verify kerfing)
79
+ -D, --debug Show full exception stack trace on error
76
80
 
77
81
  --examples Show detailed usage examples
78
82
  --help Show this message
@@ -80,37 +84,37 @@ Specific Options:
80
84
 
81
85
  Common Options:
82
86
  -o, --file FILE Required output filename of the PDF
83
- -s, --size WxHxD/T/N Combined internal dimensions: W = width, H = height,
84
- D = depth, T = thickness, N = notch length
87
+ -z, --size WxHxD/T[/N] Combined internal dimensions: W = width, H = height,
88
+ D = depth, T = thickness, and optional N = notch length
85
89
 
86
90
  -u, --units UNITS Either 'in' for inches (default) or 'mm'
87
91
  ```
88
92
 
89
93
  ### Examples
90
94
 
91
- Create a box defined in inches, and open PDF in preview right after:
95
+ Create a box defined in inches, with kerf (cut width) set to 0.008", and open PDF in preview right after:
92
96
 
93
97
  ```bash
94
- laser-cutter -s 3x2x2/0.125/0.5 -O -o box.pdf
98
+ laser-cutter -z 3x2x2/0.125 -k 0.008 -O -o box.pdf
95
99
  ```
96
100
 
97
101
  Create a box defined in millimeters, print verbose info, and set
98
102
  page size to A3, and layout to landscape, and stroke width to 1/2mm:
99
103
 
100
104
  ```bash
101
- laser-cutter -u mm -w70 -h20 -d50 -t4.3 -n5 -zA3 -y landscape -k0.5 -v -O -o box.pdf
105
+ laser-cutter -u mm -w70 -h20 -d50 -t4.3 -n5 -iA3 -l landscape -s0.5 -v -O -o box.pdf
102
106
  ```
103
107
 
104
108
  List all possible page sizes in metric system:
105
109
 
106
110
  ```bash
107
- laser-cutter -l -u mm
111
+ laser-cutter -L -u mm
108
112
  ```
109
113
 
110
114
  Create a box with provided dimensions, and save the config to a file for later use:
111
115
 
112
116
  ```bash
113
- laser-cutter -s 1.1x2.5x1.5/0.125/0.125 -p 0.1 -O -o box.pdf -W box-settings.json
117
+ laser-cutter -z 1.1x2.5x1.5/0.125/0.125 -p 0.1 -O -o box.pdf -W box-settings.json
114
118
  ```
115
119
 
116
120
  Read settings from a previously saved file:
@@ -171,7 +175,7 @@ And laser-cutter:
171
175
 
172
176
  ```bash
173
177
  gem install laser-cutter
174
- laser-cutter -s 1x1.5x2/0.125/0.125 -O -o box.pdf
178
+ laser-cutter -z 1x1.5x2/0.125/0.125 -O -o box.pdf
175
179
  ```
176
180
 
177
181
  ![LaserCutter Comparison](doc/comparison.jpg).
@@ -1,162 +1,8 @@
1
1
  #!/usr/bin/env ruby
2
- require 'laser-cutter'
3
- require 'optparse'
4
- require 'colored'
5
- require 'json'
6
- require 'hashie/mash'
2
+ require File.expand_path('../../lib/laser-cutter', __FILE__)
3
+ require File.expand_path('../../lib/laser-cutter/cli/opt_parser', __FILE__)
7
4
 
8
- module Laser
9
- module Cutter
10
- class OptParse
11
- def self.puts_error(e)
12
- STDERR.puts "Whoops, #{e}".red
13
- STDERR.puts "Try --help or --examples for more info...".yellow
14
- end
15
-
16
- def self.parse(args)
17
- banner_text = <<-EOF
18
- #{('Laser-Cutter v'+ Laser::Cutter::VERSION).bold}
19
-
20
- Usage: laser-cutter [options] -o filename.pdf
21
- eg: laser-cutter -s 1x1.5x2/0.125 -O -o box.pdf
22
- EOF
23
-
24
- examples = <<-EOF
25
-
26
- Examples:
27
- 1. Create a box defined in inches, and open PDF in preview right after:
28
-
29
- laser-cutter -s 3x2x2/0.125 -O -o box.pdf
30
-
31
- 2. Create a box defined in millimeters, print verbose info, and set
32
- page size to A3, and layout to landscape, and stroke width to 1/2mm:
33
-
34
- laser-cutter -u mm -w70 -h20 -d50 -t4.3 -n5 -zA3 -y landscape -k0.5 -v -O -o box.pdf
35
-
36
- 3. List all possible page sizes in metric systems:
37
-
38
- laser-cutter -l -u mm
39
-
40
- 4. Create a box with provided dimensions, and save the config to a file
41
- for later use:
42
-
43
- laser-cutter -s 1.1x2.5x1.5/0.125/0.125 -p 0.1 -O -o box.pdf -W box-settings.json
44
-
45
- 5. Read settings from a previously saved file:
46
-
47
- laser-cutter -O -o box.pdf -R box-settings.json
48
- cat box-settings.json | laser-cutter -O -o box.pdf -R -
49
-
50
- EOF
51
- options = Hashie::Mash.new
52
- options.verbose = false
53
- options.units = 'in'
54
-
55
- opt_parser = OptionParser.new do |opts|
56
- opts.banner = banner_text.blue
57
- opts.separator "Specific Options:"
58
- opts.on("-w", "--width WIDTH", "Internal width of the box") { |value| options.width = value }
59
- opts.on("-h", "--height HEIGHT", "Internal height of the box") { |value| options.height = value }
60
- opts.on("-d", "--depth DEPTH", "Internal depth of the box") { |value| options.depth= value }
61
- opts.on("-t", "--thickness THICKNESS", "Thickness of the box material") { |value| options.thickness = value }
62
- opts.on("-n", "--notch NOTCH", "Optional notch length (used as a guide)") { |value| options.notch = value }
63
- opts.separator ""
64
- opts.on("-m", "--margin MARGIN", "Margins from the edge of the document") { |value| options.margin = value }
65
- opts.on("-p", "--padding PADDING", "Space between the boxes on the page") { |value| options.padding = value }
66
- opts.on("-k", "--stroke WIDTH", "Numeric stroke width of the line") { |value| options.stroke = value }
67
- opts.on("-z", "--page_size LETTER", "Page size, see --list-all-page-sizes for more info.") { |value| options.page_size = value }
68
- opts.on("-y", "--page_layout portrait", "Page layout, other option is 'landscape' ") { |value| options.page_layout = value }
69
- opts.separator ""
70
- opts.on("-O", "--open", "Open generated file with system viewer before exiting") { |v| options.open = v }
71
- opts.on("-W", "--write FILE", "Save provided configuration to a file, use '-' for STDOUT") { |v| options.write_file = v }
72
- opts.on("-R", "--read FILE", "Read configuration from a file, or use '-' for STDIN") { |v| options.read_file = v }
73
- opts.separator ""
74
- opts.on("-l", "--list-all-page-sizes", "Print all available page sizes with dimensions and exit") { |v| options.list_all_page_sizes = true }
75
- opts.on("-M", "--no-metadata", "Do not print box metadata on the PDF") { |value| options.metadata = value }
76
- opts.on("-v", "--[no-]verbose", "Run verbosely") { |v| options.verbose = v }
77
- opts.separator ""
78
- opts.on("--examples", "Show detailed usage examples") { puts opts; puts examples.yellow; exit }
79
- opts.on("--help", "Show this message") { puts opts; exit }
80
- opts.on("--version", "Show version") { puts Laser::Cutter::VERSION; exit }
81
- opts.separator ""
82
- opts.separator "Common Options:"
83
- opts.on_tail("-o", "--file FILE", "Required output filename of the PDF") { |value| options.file = value }
84
- opts.on_tail("-s", "--size WxHxD/T[/N]",
85
- "Combined internal dimensions: W = width, H = height,\n#{" " * 37}D = depth, T = thickness, and optional N = notch length\n\n") do |size|
86
- options.size = size
87
- end
88
- opts.on_tail("-u", "--units UNITS", "Either 'in' for inches (default) or 'mm'") { |value| options.units = value }
89
- end
90
-
91
- opt_parser.parse!(args)
92
-
93
- if options.read_file
94
- read_options_from_file(options)
95
- end
96
-
97
- config = Laser::Cutter::Configuration.new(options.to_hash)
98
- if config.list_all_page_sizes
99
- puts PageManager.new(config.units).all_page_sizes
100
- exit 0
101
- end
102
-
103
- if options.verbose
104
- puts "Starting with the following configuration:"
105
- puts JSON.pretty_generate(config.to_hash).green
106
- end
107
-
108
- config.validate!
109
-
110
- if config.write_file
111
- write_options_to_file(config)
112
- end
113
-
114
- config
115
- rescue OptionParser::InvalidOption, OptionParser::MissingArgument, Laser::Cutter::MissingOption => e
116
- puts opt_parser.banner.blue
117
- puts_error(e)
118
- exit 1
119
- end
120
-
121
- def self.read_options_from_file(options)
122
- string = if options.read_file.eql?('-')
123
- $stdin.read
124
- elsif File.exist?(options.read_file)
125
- File.read(options.read_file)
126
- end
127
- if string
128
- options.replace(JSON.load(string))
129
- end
130
- rescue Exception => e
131
- STDERR.puts "Error reading options from file #{options.read_file}, #{e.message}".red
132
- if options.verbose
133
- STDERR.puts e.backtrace.join("\n").red
134
- end
135
- exit 1
136
- end
137
-
138
- def self.write_options_to_file(options)
139
- output = if options.write_file.eql?('-')
140
- $stdout
141
- elsif options.write_file
142
- File.open(options.write_file, 'w')
143
- else
144
- nil
145
- end
146
- output.puts(JSON.pretty_generate(options))
147
- output.close if output != $stdout
148
- rescue Exception => e
149
- STDERR.puts "Error writing options to file #{options.write_file}, #{e.message}".red
150
- if options.verbose
151
- STDERR.puts e.backtrace.join("\n").red
152
- end
153
- exit 1
154
- end
155
- end
156
- end
157
- end # class OptParse
158
-
159
- config = Laser::Cutter::OptParse.parse(ARGV)
5
+ config = Laser::Cutter::CLI::OptParser.parse(ARGV)
160
6
 
161
7
  begin
162
8
  Laser::Cutter::Renderer::LayoutRenderer.new(config).render
@@ -164,7 +10,7 @@ begin
164
10
  `open #{config.file}`
165
11
  end
166
12
  rescue Exception => e
167
- STDERR.puts "#{e.inspect}".red
13
+ STDERR.puts "Whoops, #{e.message}".red
168
14
  if config.verbose
169
15
  STDERR.puts e.backtrace.join("\n").red
170
16
  end
@@ -4,6 +4,8 @@ require 'laser-cutter/geometry'
4
4
  require 'laser-cutter/box'
5
5
  require 'laser-cutter/renderer'
6
6
  require 'laser-cutter/page_manager'
7
+ require 'laser-cutter/notching'
8
+ require 'laser-cutter/aggregator'
7
9
  require 'prawn'
8
10
  require 'prawn/measurement_extensions'
9
11
 
@@ -0,0 +1,57 @@
1
+ module Laser
2
+ module Cutter
3
+ class Aggregator
4
+ attr_accessor :lines
5
+
6
+ def initialize(array_of_lines = [])
7
+ self.lines = array_of_lines.sort
8
+ end
9
+
10
+ # This method finds lines that are identical (same p1/p2)
11
+ def dedup!
12
+ lines_to_delete = []
13
+ count = lines.size
14
+ for i in 0..(count - 1) do
15
+ for j in (i + 1)..(count - 1) do
16
+ l1 = lines[i]
17
+ l2 = lines[j]
18
+ if l1.eql?(l2)
19
+ lines_to_delete << l1
20
+ lines_to_delete << l2
21
+ end
22
+ end
23
+ end
24
+ self.lines = self.lines - lines_to_delete
25
+ self
26
+ end
27
+
28
+ # Find overlapping (intersecting) sections of lines and
29
+ # remove them.
30
+ def deoverlap!
31
+ lines_to_delete = []
32
+ lines_to_add = []
33
+ count = lines.size
34
+ for i in 0..(count - 1) do
35
+ for j in (i + 1)..(count - 1) do
36
+ l1 = lines[i]
37
+ l2 = lines[j]
38
+ if l1.overlaps?(l2)
39
+ lines_to_delete << l1
40
+ lines_to_delete << l2
41
+ lines_to_add << l1.xor(l2)
42
+ end
43
+ end
44
+ end
45
+
46
+ lines_to_delete.uniq!
47
+ lines_to_delete.flatten!
48
+ lines_to_add.uniq!
49
+ lines_to_add.flatten!
50
+
51
+ self.lines = (self.lines - lines_to_delete + lines_to_add).flatten
52
+ self.lines.sort!
53
+ self
54
+ end
55
+ end
56
+ end
57
+ end
@@ -5,8 +5,8 @@ module Laser
5
5
  class Box
6
6
  # Everything is in millimeters
7
7
 
8
- attr_accessor :dim, :thickness, :notch_width
9
- attr_accessor :padding, :units
8
+ attr_accessor :dim, :thickness, :notch_width, :kerf
9
+ attr_accessor :padding, :units, :inside_box
10
10
 
11
11
  attr_accessor :front, :back, :top, :bottom, :left, :right
12
12
  attr_accessor :faces, :bounds, :conf, :corner_face
@@ -17,10 +17,12 @@ module Laser
17
17
  self.thickness = config['thickness']
18
18
 
19
19
  self.notch_width = config['notch'] || (1.0 * self.longest / 5.0)
20
+ self.kerf = config['kerf'] || 0.0
20
21
  self.padding = config['padding']
21
22
  self.units = config['units']
23
+ self.inside_box = config['inside_box']
22
24
 
23
- @notches = []
25
+ self.notches = []
24
26
 
25
27
  self.metadata = Geometry::Point[config['metadata_width'] || 0, config['metadata_height'] || 0]
26
28
 
@@ -39,6 +41,7 @@ module Laser
39
41
  end
40
42
 
41
43
  def enclosure
44
+ generate_notches if self.notches.empty?
42
45
  p1 = notches.first.p1.to_a
43
46
  p2 = notches.first.p2.to_a
44
47
 
@@ -51,32 +54,40 @@ module Laser
51
54
  Geometry::Rect[Geometry::Point.new(p1), Geometry::Point.new(p2)]
52
55
  end
53
56
 
54
- # Returns an flattened array of lines representing notched lines
55
- def notches
56
- generate_notches! if @notches.empty?
57
- @notches
58
- end
59
-
60
- def generate_notches!
57
+ def generate_notches
61
58
  position_faces!
62
-
63
59
  corner_face = pick_corners_face
64
-
65
- @notches = []
60
+ self.notches = []
66
61
  faces.each_with_index do |face, face_index|
67
62
  bound = face_bounding_rect(face)
63
+ side_lines = []
64
+ edges = []
68
65
  bound.sides.each_with_index do |bounding_side, side_index |
66
+ include_corners = (self.conf[:corners][corner_face][face_index] == :yes && side_index.odd?)
69
67
  key = side_index.odd? ? :valign : :halign
70
- path = Geometry::PathGenerator.new(:notch_width => notch_width,
71
- :center_out => (self.conf[key][face_index] == :out) ,
72
- :fill_corners => (self.conf[:corners][corner_face][face_index] == :yes && side_index.odd?),
73
- :thickness => thickness
74
- ).path(Geometry::Edge.new(bounding_side, face.sides[side_index], self.notch_width))
75
- @notches << path.create_lines
68
+ center_out = (self.conf[key][face_index] == :out)
69
+ edges << Notching::Edge.new(bounding_side, face.sides[side_index],
70
+ {:notch_width => notch_width,
71
+ :thickness => thickness,
72
+ :kerf => kerf,
73
+ :center_out => center_out,
74
+ :corners => include_corners
75
+ })
76
76
  end
77
- end
78
77
 
79
- @notches = Geometry::PathGenerator.deduplicate(@notches.flatten.sort)
78
+ if edges.any?{|e| e.corners} && !edges.all?{|e| e.first_notch_out? }
79
+ edges.each {|e| e.adjust_corners = true }
80
+ end
81
+
82
+ edges.each do |edge|
83
+ side_lines << Notching::PathGenerator.new(edge).generate
84
+ end
85
+
86
+ aggregator = Aggregator.new(side_lines.flatten)
87
+ aggregator.dedup!.deoverlap!.dedup!
88
+ self.notches << aggregator.lines
89
+ end
90
+ self.notches.flatten!
80
91
  end
81
92
 
82
93
  def w; dim.w; end
@@ -95,8 +106,8 @@ module Laser
95
106
 
96
107
  def face_bounding_rect(face)
97
108
  b = face.clone
98
- b.move_to(b.position.move_by(-thickness, -thickness))
99
- b.p2 = b.p2.move_by(2 * thickness, 2 * thickness)
109
+ b.move_to(b.position.plus(-thickness, -thickness))
110
+ b.p2 = b.p2.plus(2 * thickness, 2 * thickness)
100
111
  b.relocate!
101
112
  end
102
113
 
@@ -154,11 +165,13 @@ module Laser
154
165
  self.right = Geometry::Rect.create(zero, dim.d, dim.h, "right")
155
166
  end
156
167
 
168
+ # Choose which face will be responsible for filling out the little square overlap
169
+ # in the corners. Only one of the 3 possible sides need to be picked.
157
170
  def pick_corners_face
158
171
  b = face_bounding_rect(front)
159
172
  edges = []
160
173
  front.sides[0..1].each_with_index do |face, index|
161
- edges << Geometry::Edge.new(b.sides[index], face, notch_width)
174
+ edges << Notching::Edge.new(b.sides[index], face, :notch_width => notch_width, :kerf => kerf )
162
175
  end
163
176
  edges.map(&:notch_count).all?{|c| c % 4 == 3} ? :top : :front
164
177
  end