laser-cutter 0.5.1 → 0.5.3

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 06711815bb7563755b270427de4acb6dc2208777
4
- data.tar.gz: 8daa7da0e30ed501f04b5786cf0b31c8b62ec823
3
+ metadata.gz: 869598bd65fbb14370dcce5b969a19748f246ce2
4
+ data.tar.gz: 4ce9cc14002201a598b5df25f1ae1a474b4269f6
5
5
  SHA512:
6
- metadata.gz: 267e0cc220e9e83a0f8a8570429d3f11207d677b5d876831544ac33871fef9ce14daaab7f2594784d4d0a6da1e5161e8299d205d177543a3408ad3ccec42a2b5
7
- data.tar.gz: f5c49c5f68fc71df0e84af028086f9a231cf9abfd42c07350bdfc175f3df31bf40469032c30c33ca49a000f5e82764d1e5788db5d42c90de65e6ed91936e71d9
6
+ metadata.gz: c3b2164cedb8d7af10dfc1beb1482d902f5ce56ae916d32f35cdc289d42d87943567a0d8729f3e793d2844a40c99b27428b5a942b3ddb0b57e4055cc3a7e0266
7
+ data.tar.gz: 9983e63d47f490f0354159f0ba96ddb086d30c2dc44b62e76f0541529bd1b546693b01100366ac7b0f68c44359b6431c873a4b4ec7fa5a0821dfddc2965edc85
data/README.md CHANGED
@@ -3,14 +3,23 @@
3
3
  [![Code Climate](https://codeclimate.com/github/kigster/laser-cutter.png)](https://codeclimate.com/github/kigster/laser-cutter)
4
4
  [![Test Coverage](https://codeclimate.com/github/kigster/laser-cutter/badges/coverage.svg)](https://codeclimate.com/github/kigster/laser-cutter)
5
5
 
6
- ## LaserCutter
6
+ ## LaserCutter and Make-A-Box.io
7
7
 
8
- Similar to [BoxMaker](https://github.com/rahulbot/boxmaker/) (which is written in Java a long time ago),
9
- this ruby gem generates PDFs that can be used as a basis for creating a "snap-in" boxes with notched
10
- sides on a typical laser cutter, by providing dimensions, material thickness and output file name.
8
+ ```laser-cutter``` is a ruby library for generating PDF designs for boxes of
9
+ custom dimensions that suit your project, that can be cut from wood or acrylic
10
+ using a laser-cutter. The sides of the box snap together using alternating notches,
11
+ that are deliberately layed out in a symmetric form.
11
12
 
12
- For more detailed comparison with BoxMaker and motivation behind this project, please see the section
13
- at the bottom of this README.
13
+ To use ```laser-cutter``` you need to have a recent version of ruby interpreter,
14
+ install it as a gem, and use command line to generate PDFs.
15
+
16
+ [Make-A-Box](http://makeabox.io) is a online web application that uses ```laser-cutter``` library
17
+ and provides a straight-forward user interface for generating PDF designs without the need to install
18
+ the gem or use command line.
19
+
20
+ Use whatever suites you better.
21
+
22
+ ### Design Goals
14
23
 
15
24
  One of the design goals of this project is to provide a highly extensible platform for creating
16
25
  laser-cut designs, where alternative strategies can be added over time, and supported by various
@@ -20,13 +29,6 @@ contributing to the project, please see [contributing](CONTRIBUTING.md) for more
20
29
  ```laser-cutter``` supports many flexible command line options that allow setting dimensions,
21
30
  stroke width, page size, layout, margins, padding (spacing between the boxes), and many more.
22
31
 
23
- ## Web Front-End
24
-
25
- There is a web online application that uses this gem and allows you to generate PDFs with
26
- a friendly UI.
27
-
28
- Please visit [http://makeabox.io](http://makeabox.io).
29
-
30
32
  ## Dependencies
31
33
 
32
34
  The gem depends primarily on [Prawn](http://prawnpdf.org) – a fantastic PDF generation library.
@@ -118,31 +120,32 @@ Read settings from a previously saved file:
118
120
  cat box-settings.json | laser-cutter -O -o box.pdf -R -
119
121
  ```
120
122
 
121
- ## Future Features
123
+ ## Feature Wish List
122
124
 
125
+ * Create T-style joins, using various standard sizes of nuts and bolts (such as common #4-40 and M2 sizes)
123
126
  * Extensibility with various layout strategies, notch drawing strategies, basically plug and play
124
127
  model for adding new algorithms for path creation and box joining
125
- * Support more shapes than just box
126
- * Create T-style joins, using various standard sizes of nuts and bolts (such as common #4-40 and M2 sizes)
128
+ * Support more shapes than just box, such as prisms
127
129
  * Supporting lids and front panels, that are larger than the box itself and have holes for notches.
128
130
  * Your brilliant idea can be here too! Please see [contributing](CONTRIBUTING.md) for more info.
129
131
 
130
- ## Comparison with BoxMaker
132
+ ## LaserCutter vs BoxMaker
131
133
 
132
- It's important to note that the author believes that BoxMaker is a greatly useful piece of software
133
- generously open sourced by the author, and so in no way this project disputes BoxMaker's viability.
134
-
135
- In fact BoxMaker was an inspiration for this project. Laser-Cutter library attempts to further advance
136
- concept of programmatically creating laser-cut boxes, provide additional tuning, options, strategies
137
- and most importantly extensibility.
134
+ [Rahulbot](https://github.com/rahulbot/)-made [BoxMaker](https://github.com/rahulbot/boxmaker/) is a
135
+ functional generator of notched designs, similar to ```laser-cutter```, and generously open sourced
136
+ by the author, and so in no way this project disputes BoxMaker's viability. In fact BoxMaker was an
137
+ inspiration for this project.
138
+
139
+ Laser-Cutter library attempts to further advance the concept of programmatically creating
140
+ laser-cut box designs, provides additional fine tuning, many more options, strategies and most
141
+ importantly – extensibility.
138
142
 
139
- Unlike ```BoxMaker```, this gem has a suit of automated tests (rspecs) around creating the geometry.
140
- In addition, we welcome new feature contributions, or bug fixes from other developers, and in that
141
- regard rspecs offer confidence that functionality still works.
143
+ Unlike ```BoxMaker```, this gem has a suit of automated tests (rspecs) around the core functionality.
144
+ In addition, new feature contributions are highly encouraged, and in that
145
+ regard having existing test suit offers confidence against regressions, and thus welcomes colaboration.
142
146
 
143
- BoxMaker's algorithm _tries to ensures that the same notch length is across all sides, but sacrifices
144
- symmetry as a result_. So you may have a front panel's left and right edges be simply non symmetric.
145
- And that might be entirely OK with you :)
147
+ Finally, BoxMaker's notch-drawing algorithm generates non-symmetric and sometimes purely broken designs
148
+ (see picture below).
146
149
 
147
150
  ```laser-cutter```'s algorithm will create a _symmetric design for most panels_, but it might sacrifice
148
151
  identical notch length. Depending on the box dimensions you may end up with a slightly different notch
@@ -18,7 +18,7 @@ module Laser
18
18
  #{('Laser-Cutter v'+ Laser::Cutter::VERSION).bold}
19
19
 
20
20
  Usage: laser-cutter [options] -o filename.pdf
21
- eg: laser-cutter -s 1x1.5x2/0.125/0.125 -O -o box.pdf
21
+ eg: laser-cutter -s 1x1.5x2/0.125 -O -o box.pdf
22
22
  EOF
23
23
 
24
24
  examples = <<-EOF
@@ -26,7 +26,7 @@ Usage: laser-cutter [options] -o filename.pdf
26
26
  Examples:
27
27
  1. Create a box defined in inches, and open PDF in preview right after:
28
28
 
29
- laser-cutter -s 3x2x2/0.125/0.5 -O -o box.pdf
29
+ laser-cutter -s 3x2x2/0.125 -O -o box.pdf
30
30
 
31
31
  2. Create a box defined in millimeters, print verbose info, and set
32
32
  page size to A3, and layout to landscape, and stroke width to 1/2mm:
@@ -59,7 +59,7 @@ Examples:
59
59
  opts.on("-h", "--height HEIGHT", "Internal height of the box") { |value| options.height = value }
60
60
  opts.on("-d", "--depth DEPTH", "Internal depth of the box") { |value| options.depth= value }
61
61
  opts.on("-t", "--thickness THICKNESS", "Thickness of the box material") { |value| options.thickness = value }
62
- opts.on("-n", "--notch NOTCH", "Preferred notch length (used only as a guide)") { |value| options.notch = value }
62
+ opts.on("-n", "--notch NOTCH", "Optional notch length (used as a guide)") { |value| options.notch = value }
63
63
  opts.separator ""
64
64
  opts.on("-m", "--margin MARGIN", "Margins from the edge of the document") { |value| options.margin = value }
65
65
  opts.on("-p", "--padding PADDING", "Space between the boxes on the page") { |value| options.padding = value }
@@ -81,8 +81,8 @@ Examples:
81
81
  opts.separator ""
82
82
  opts.separator "Common Options:"
83
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, N = notch length\n\n") do |size|
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
86
  options.size = size
87
87
  end
88
88
  opts.on_tail("-u", "--units UNITS", "Either 'in' for inches (default) or 'mm'") { |value| options.units = value }
@@ -10,7 +10,7 @@ module Laser
10
10
 
11
11
  attr_accessor :front, :back, :top, :bottom, :left, :right
12
12
  attr_accessor :faces, :bounds, :conf, :corner_face
13
- attr_accessor :metadata
13
+ attr_accessor :metadata, :notches
14
14
 
15
15
  def initialize(config = {})
16
16
  self.dim = Geometry::Dimensions.new(config['width'], config['height'], config['depth'])
@@ -20,6 +20,8 @@ module Laser
20
20
  self.padding = config['padding']
21
21
  self.units = config['units']
22
22
 
23
+ @notches = []
24
+
23
25
  self.metadata = Geometry::Point[config['metadata_width'] || 0, config['metadata_height'] || 0]
24
26
 
25
27
  create_faces! # generates dimensions for each side
@@ -36,15 +38,31 @@ module Laser
36
38
  self
37
39
  end
38
40
 
41
+ def enclosure
42
+ p1 = notches.first.p1.to_a
43
+ p2 = notches.first.p2.to_a
44
+
45
+ notches.each do |notch|
46
+ n = notch.normalized
47
+ n.p1.to_a.each_with_index {|c, i| p1[i] = c if c < p1[i] }
48
+ n.p2.to_a.each_with_index {|c, i| p2[i] = c if c > p2[i] }
49
+ end
39
50
 
40
- # Returns an flattened array of lines representing notched
41
- # rectangle.
51
+ Geometry::Rect[Geometry::Point.new(p1), Geometry::Point.new(p2)]
52
+ end
53
+
54
+ # Returns an flattened array of lines representing notched lines
42
55
  def notches
56
+ generate_notches! if @notches.empty?
57
+ @notches
58
+ end
59
+
60
+ def generate_notches!
43
61
  position_faces!
44
62
 
45
63
  corner_face = pick_corners_face
46
64
 
47
- notches = []
65
+ @notches = []
48
66
  faces.each_with_index do |face, face_index|
49
67
  bound = face_bounding_rect(face)
50
68
  bound.sides.each_with_index do |bounding_side, side_index |
@@ -54,11 +72,11 @@ module Laser
54
72
  :fill_corners => (self.conf[:corners][corner_face][face_index] == :yes && side_index.odd?),
55
73
  :thickness => thickness
56
74
  ).path(Geometry::Edge.new(bounding_side, face.sides[side_index], self.notch_width))
57
- notches << path.create_lines
75
+ @notches << path.create_lines
58
76
  end
59
77
  end
60
78
 
61
- Geometry::PathGenerator.deduplicate(notches.flatten.sort)
79
+ @notches = Geometry::PathGenerator.deduplicate(@notches.flatten.sort)
62
80
  end
63
81
 
64
82
  def w; dim.w; end
@@ -17,8 +17,8 @@ module Laser
17
17
 
18
18
  UNIT_SPECIFIC_DEFAULTS = {
19
19
  'mm' => {
20
- margin: 5,
21
- padding: 5,
20
+ margin: 3,
21
+ padding: 2,
22
22
  stroke: 0.0254,
23
23
  },
24
24
  'in' => {
@@ -28,8 +28,7 @@ module Laser
28
28
  }
29
29
  }
30
30
 
31
-
32
- SIZE_REGEXP = /[\d\.]+x[\d\.]+x[\d\.]+\/[\d\.]+\/[\d\.]+/
31
+ SIZE_REGEXP = /[\d\.]+x[\d\.]+x[\d\.]+\/[\d\.]+(\/[\d\.]+)?/
33
32
 
34
33
  FLOATS = %w(width height depth thickness notch margin padding stroke)
35
34
  NON_ZERO = %w(width height depth thickness stroke)
@@ -51,6 +50,7 @@ module Laser
51
50
  self[k] = self[k].to_f if (self[k] && self[k].is_a?(String))
52
51
  end
53
52
  self.merge!(UNIT_SPECIFIC_DEFAULTS[self['units']].merge(self))
53
+ self['notch'] = self['thickness'] * 3.0 if self['thickness'] && self['notch'].nil?
54
54
  end
55
55
 
56
56
  def validate!
@@ -67,6 +67,7 @@ module Laser
67
67
  return if (self.units.eql?(new_units) || !UNIT_SPECIFIC_DEFAULTS.keys.include?(new_units))
68
68
  k = (self.units == 'in') ? 25.4 : 0.039370079
69
69
  FLOATS.each do |field|
70
+ next if self.send(field.to_sym).nil?
70
71
  self.send("#{field}=".to_sym, (self.send(field.to_sym) * k).round(5))
71
72
  end
72
73
  self.units = new_units
@@ -34,7 +34,7 @@ module Laser
34
34
  # Removes the items from the list that appear more than once
35
35
  # Unlike uniq-ing which keeps all elements, just ensures that are not
36
36
  # repeated, here we remove elements completely if they are seen more than once.
37
- # This is used to remove lines
37
+ # This is used to remove lines that join the same two points.
38
38
  def self.deduplicate list
39
39
  new_list = []
40
40
  list.sort!
@@ -53,9 +53,6 @@ module Laser
53
53
  @notch_width = options[:notch_width] # only desired, will be adapted for each line
54
54
  @center_out = options[:center_out] # when true, the notch in the middle of the edge is out, not in.
55
55
  @thickness = options[:thickness]
56
-
57
- # 2D array of booleans. If true first or second end of the edge is
58
- # created with a corner filled in.
59
56
  @fill_corners = options[:fill_corners]
60
57
  end
61
58
 
@@ -46,11 +46,13 @@ module Laser
46
46
  p2.y - p1.y
47
47
  end
48
48
 
49
-
50
49
  def to_s
51
50
  "#{sprintf "%3d", w}(w)x#{sprintf "%3d", h}(h) @ #{position.to_s}"
52
51
  end
53
52
 
53
+ def to_a
54
+ [[p1.x, p1.y], [p2.x, p2.y]]
55
+ end
54
56
  end
55
57
 
56
58
  end
@@ -36,7 +36,7 @@ module Laser
36
36
  end
37
37
 
38
38
  def to_a
39
- self.coords
39
+ self.coords.clone
40
40
  end
41
41
 
42
42
  def to_s
@@ -1,6 +1,9 @@
1
1
  module Laser
2
2
  module Cutter
3
3
  module Renderer
4
+ # subject is what we are rendering
5
+ # enclosure is the rectangle enclosing our subject's rendered image
6
+ # page_manager contains access to units and page sizes
4
7
  class Base
5
8
  BLACK = "000000"
6
9
  BLUE = "4090E0"
@@ -16,6 +16,9 @@ module Laser
16
16
  box.metadata = Geometry::Point.new(coords)
17
17
  end
18
18
 
19
+ def enclosure
20
+ box.enclosure
21
+ end
19
22
 
20
23
  def render pdf = nil
21
24
  renderer = self
@@ -6,18 +6,20 @@ module Laser
6
6
  class LayoutRenderer < Base
7
7
  def initialize(config)
8
8
  self.config = config
9
+ super(config)
9
10
  end
10
11
 
11
12
  def render
12
13
  renderer = self
13
14
 
15
+ margin = config.margin.to_f.send(config.units.to_sym)
14
16
  meta_renderer = MetaRenderer.new(config)
15
17
  box_renderer = BoxRenderer.new(config)
16
18
  box_renderer.ensure_space_for(meta_renderer.enclosure) if config.metadata
17
19
 
18
- page_size = config.page_size || 'LETTER' # TODO: auto-detect page size based on box dimensions.
20
+ page_size = config.page_size || calculate_image_boundary(box_renderer, margin)
19
21
 
20
- pdf = Prawn::Document.new(:margin => config.margin.to_f.send(config.units.to_sym),
22
+ pdf = Prawn::Document.new(:margin => margin,
21
23
  :page_size => page_size,
22
24
  :page_layout => config.page_layout.to_sym)
23
25
 
@@ -34,6 +36,12 @@ module Laser
34
36
  end
35
37
  end
36
38
 
39
+ def calculate_image_boundary(box_renderer, margin)
40
+ box_renderer.enclosure.to_a[1].map do |c|
41
+ c.send(config.units.to_sym) + 2 * margin
42
+ end
43
+ end
44
+
37
45
  end
38
46
  end
39
47
  end
@@ -23,11 +23,12 @@ class Laser::Cutter::Renderer::MetaRenderer < Laser::Cutter::Renderer::Base
23
23
 
24
24
  metadata = config.to_hash
25
25
  metadata.delete_if { |k| %w(verbose metadata open file).include?(k) }
26
+ metadata['page_size'] ||= 'custom'
26
27
 
27
28
  rect = self.enclosure
28
29
 
29
30
  pdf.instance_eval do
30
- self.line_width = 0.5.mm
31
+ self.line_width = 0.2.mm
31
32
  float do
32
33
  bounding_box([rect.p1.x, rect.h + rect.p1.y], :width => rect.w, :height => rect.h) do
33
34
  stroke_color meta_color
@@ -1,5 +1,5 @@
1
1
  module Laser
2
2
  module Cutter
3
- VERSION = "0.5.1"
3
+ VERSION = "0.5.3"
4
4
  end
5
5
  end
@@ -3,7 +3,7 @@ require_relative 'spec_helper'
3
3
  module Laser
4
4
  module Cutter
5
5
  describe Box do
6
- let(:config) { {'width' => 50, 'height' => 60, 'depth' => 70, 'margin' => 5, 'padding' => 3 } }
6
+ let(:config) { {'width' => 50, 'height' => 60, 'depth' => 70, 'margin' => 5, 'padding' => 3, 'units' => 'mm'} }
7
7
  let(:box1) { Box.new(config.merge('thickness' => 6, 'notch' => 10)) }
8
8
  let(:box2) { Box.new(config.merge('thickness' => 6, )) }
9
9
 
@@ -24,6 +24,11 @@ module Laser
24
24
  expect(box1.notches).to_not be_nil
25
25
  expect(box1.notches.size).to eql(320)
26
26
  end
27
+
28
+ it 'should properly calculate enclosure' do
29
+ expect(box1.enclosure.to_a.flatten.map(&:round)).to eql([0,0, 232, 317])
30
+ expect(box2.enclosure.to_a.flatten.map(&:round)).to eql([0,0, 232, 317])
31
+ end
27
32
  end
28
33
 
29
34
  end
@@ -24,7 +24,7 @@ module Laser
24
24
  end
25
25
  end
26
26
  context 'zero options' do
27
- let(:opts) { {"size" => "2.0x0.0x2/0.125/0.5", "inches" => true, 'file' => '/tmp/a'} }
27
+ let(:opts) { {"size" => "2.0x0.0x2/0.125/0.5", 'file' => '/tmp/a'} }
28
28
  it 'should be able to validate missing options' do
29
29
  expect(config.height).to eql(0.0)
30
30
  expect { config.validate! }.to raise_error(Laser::Cutter::ZeroValueNotAllowed)
@@ -32,6 +32,15 @@ module Laser
32
32
  end
33
33
  end
34
34
 
35
+ context 'default values' do
36
+ let(:opts) { {"size" => "2.0x1.0x2/0.125", 'file' => '/tmp/a'} }
37
+ it 'should correctly default notch based on thickness' do
38
+ config.validate!
39
+ expect(config.thickness).to eql(0.125)
40
+ expect(config.notch).to eql(config.thickness * 3.0)
41
+ end
42
+ end
43
+
35
44
  context 'when invalid units are provided' do
36
45
  let(:opts) { {"size" => "2x3x2/0.125/0.5", "units" => 'xx'} }
37
46
  it 'should default to inches' do
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: laser-cutter
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.5.1
4
+ version: 0.5.3
5
5
  platform: ruby
6
6
  authors:
7
7
  - Konstantin Gredeskoul
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2014-09-30 00:00:00.000000000 Z
11
+ date: 2014-10-05 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: prawn