laser-cutter 0.5.3 → 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
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