laser-cutter 0.5.1 → 0.5.3

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.
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