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 +4 -4
- data/README.md +32 -29
- data/bin/laser-cutter +5 -5
- data/lib/laser-cutter/box.rb +24 -6
- data/lib/laser-cutter/configuration.rb +5 -4
- data/lib/laser-cutter/geometry/path_generator.rb +1 -4
- data/lib/laser-cutter/geometry/shape/rect.rb +3 -1
- data/lib/laser-cutter/geometry/tuple.rb +1 -1
- data/lib/laser-cutter/renderer/base.rb +3 -0
- data/lib/laser-cutter/renderer/box_renderer.rb +3 -0
- data/lib/laser-cutter/renderer/layout_renderer.rb +10 -2
- data/lib/laser-cutter/renderer/meta_renderer.rb +2 -1
- data/lib/laser-cutter/version.rb +1 -1
- data/spec/box_spec.rb +6 -1
- data/spec/configuration_spec.rb +10 -1
- metadata +2 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 869598bd65fbb14370dcce5b969a19748f246ce2
|
4
|
+
data.tar.gz: 4ce9cc14002201a598b5df25f1ae1a474b4269f6
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
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
|
-
|
9
|
-
|
10
|
-
|
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
|
-
|
13
|
-
|
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
|
-
##
|
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
|
-
##
|
132
|
+
## LaserCutter vs BoxMaker
|
131
133
|
|
132
|
-
|
133
|
-
|
134
|
-
|
135
|
-
|
136
|
-
|
137
|
-
|
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
|
140
|
-
In addition,
|
141
|
-
regard
|
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
|
144
|
-
|
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
|
data/bin/laser-cutter
CHANGED
@@ -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
|
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
|
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", "
|
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 }
|
data/lib/laser-cutter/box.rb
CHANGED
@@ -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
|
-
|
41
|
-
|
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:
|
21
|
-
padding:
|
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
|
|
@@ -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 ||
|
20
|
+
page_size = config.page_size || calculate_image_boundary(box_renderer, margin)
|
19
21
|
|
20
|
-
pdf = Prawn::Document.new(:margin =>
|
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.
|
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
|
data/lib/laser-cutter/version.rb
CHANGED
data/spec/box_spec.rb
CHANGED
@@ -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
|
data/spec/configuration_spec.rb
CHANGED
@@ -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",
|
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.
|
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-
|
11
|
+
date: 2014-10-05 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: prawn
|