laser-cutter 1.0.3 → 1.0.5
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 +4 -4
- data/.gitignore +1 -0
- data/.rspec +0 -1
- data/.travis.yml +23 -8
- data/BOXMAKER.md +51 -0
- data/README.md +55 -106
- data/Rakefile +28 -1
- data/{doc → docs}/comparison.jpg +0 -0
- data/laser-cutter.gemspec +3 -3
- data/lib/laser-cutter/box.rb +70 -68
- data/lib/laser-cutter/face.rb +18 -0
- data/lib/laser-cutter/notching.rb +0 -1
- data/lib/laser-cutter/notching/edge.rb +4 -4
- data/lib/laser-cutter/notching/path_generator.rb +48 -63
- data/lib/laser-cutter/version.rb +1 -1
- data/spec/box_spec.rb +0 -1
- data/spec/spec_helper.rb +2 -6
- metadata +15 -15
- data/lib/laser-cutter/notching/base.rb +0 -13
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 3a3d7b723ed7171e84a93783545f037adeeeb122
|
4
|
+
data.tar.gz: d3571947154cda05176e1d1bbb84d8d2ee140c02
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 870c36a4b07b2d4190076c524917f4322d599c58f5ff9b226b47e00a011dd63f722a7153ed885c192cbe0caefbda17fb150269a7822f4e36a001f4913a435d19
|
7
|
+
data.tar.gz: 67c264109139865d62292cb8cea2573ec5dbc569fec92316b00a14f942c0ea15d096d0391d1a5a275e3eaf78c7f9f8f179e95331f93859df982f7c7a80589e08
|
data/.gitignore
CHANGED
data/.rspec
CHANGED
data/.travis.yml
CHANGED
@@ -1,11 +1,26 @@
|
|
1
|
+
env:
|
2
|
+
global:
|
3
|
+
- CC_TEST_REPORTER_ID=6520104decf84e2f6c2c169d30d67976bfcdaa48cd89b4ec34f6e637f2a0ae02
|
4
|
+
sudo: false
|
1
5
|
language: ruby
|
6
|
+
cache: bundler
|
2
7
|
rvm:
|
3
|
-
|
4
|
-
|
8
|
+
- 2.4.1
|
9
|
+
- 2.3.5
|
10
|
+
before_install: gem install bundler -v 1.15.4
|
11
|
+
before_script:
|
12
|
+
- curl -L https://codeclimate.com/downloads/test-reporter/test-reporter-latest-linux-amd64
|
13
|
+
> ./cc-test-reporter
|
14
|
+
- chmod +x ./cc-test-reporter
|
15
|
+
- "./cc-test-reporter before-build"
|
16
|
+
script:
|
17
|
+
- bundle exec rspec
|
18
|
+
after_script:
|
19
|
+
- "./cc-test-reporter after-build --exit-code $TRAVIS_TEST_RESULT"
|
5
20
|
notifications:
|
6
|
-
|
7
|
-
|
8
|
-
-
|
9
|
-
on_success:
|
10
|
-
on_failure:
|
11
|
-
|
21
|
+
webhooks:
|
22
|
+
urls:
|
23
|
+
- secure: fgpv34jAm3rDNjiWuQtuijE0p9B2bpsCWdoCQ82pDZwktSITmNjanYCoZC0hlEw/kqMBDKRN78HfIIidy8dS664sLz8KyaFXq7RWAFsMP9fn/GMG/r9B+S3YumifQ/Zy+3OGa+0Qz76075q291QfNSPv0Y5QDMoAU5VljIVashC/qJS6DRO+vekDaDKIhi4Dr/+JpHPnjchqS4VaMB8gEIrDpeSYNlhrj1oAI/8GwgqZ8UR4sZGxNpIzUg+XGJSYz5/cAB3P18ENSQvOwaNefJa7qIVNjMD7YwXKmfX9cyr5RHM5sibq/S5fHH/N7pig+aveTquGGVxInJr1IEDiyr2TsFARjPitp9KJSFLHLsA2jm1cZfNW0/d6ii+51HdLPKorHom3fPjb0jkIe1LPa9CrrjnfSrAE9a/w4qpSnDv7OjAzkieepz+VQCtpQGIzQxJcHWaFX32iZ6RQ6pmKc/22E0OZwsNM2FHrB5V1b+rfczl8Ej7/R1wAOtHWkbLbfwZ9Ux2N0N0EKW8r8cOjEbsxmN8FFQubTwl68uqjJLhEtrOUbM28JM/NkX+Ue1KRw+NJAdMFEZQKPb/b/X6s3nwPD4/4yLyUDCGyr8aGpzSBD5uIu5uSJNiWdNDjcnXtJJ6TlGGZqj91r7FckjA8FuSA6S3yA8epMNG3FTUxPVE=
|
24
|
+
on_success: always
|
25
|
+
on_failure: onchange
|
26
|
+
on_start: never
|
data/BOXMAKER.md
ADDED
@@ -0,0 +1,51 @@
|
|
1
|
+
# LaserCutter versus BoxMaker
|
2
|
+
|
3
|
+
Another developer [Rahulbot](https://github.com/rahulbot/) created a similar app [BoxMaker](https://github.com/rahulbot/boxmaker/) in Java, which was an inspiration to LaserCutter.
|
4
|
+
|
5
|
+
Laser-Cutter library attempts to further advance the concept of programmatically creating
|
6
|
+
laser-cut box designs, provides additional fine tuning, many more options, strategies and most
|
7
|
+
importantly – extensibility.
|
8
|
+
|
9
|
+
Unlike `BoxMaker` this gem has a suit of automated tests (rspecs) around the core functionality.
|
10
|
+
In addition, new feature contributions are highly encouraged, and in that
|
11
|
+
regard having existing test suit offers confidence against regressions, and thus welcomes colaboration.
|
12
|
+
|
13
|
+
Finally, BoxMaker's notch-drawing algorithm generates non-symmetric and sometimes purely broken designs
|
14
|
+
(see picture below).
|
15
|
+
|
16
|
+
`laser-cutter`'s algorithm will create a _symmetric design for most panels_, but it might sacrifice
|
17
|
+
identical notch length. Depending on the box dimensions you may end up with a slightly different notch
|
18
|
+
length on each side of the box.
|
19
|
+
|
20
|
+
The choice ultimately comes down to the preference and feature set, so here I show you two boxes made with
|
21
|
+
each program, so you can pick what you prefer.
|
22
|
+
|
23
|
+
### Example Outputs
|
24
|
+
|
25
|
+
Below are two examples of boxes with identical dimensions produced with `laser-cutter` and `boxmaker`:
|
26
|
+
|
27
|
+
This is how you would make a box with Adam Phelp's fork of BoxMaker (which adds flags and a lot of
|
28
|
+
niceties):
|
29
|
+
|
30
|
+
```bash
|
31
|
+
git clone https://github.com/aphelps/boxmaker && cd boxmaker && ant
|
32
|
+
java -cp BOX.jar com.rahulbotics.boxmaker.BoxMaker \
|
33
|
+
-W 1 -H 2 -D 1.5 -T 0.125 -n 0.125 -o box.pdf
|
34
|
+
```
|
35
|
+
|
36
|
+
And laser-cutter:
|
37
|
+
|
38
|
+
```bash
|
39
|
+
gem install laser-cutter
|
40
|
+
laser-cutter -z 1x1.5x2/0.125/0.125 -O -o box.pdf
|
41
|
+
```
|
42
|
+
|
43
|
+
.
|
44
|
+
|
45
|
+
## Contributing
|
46
|
+
|
47
|
+
1. Fork it ( https://github.com/kigster/laser-cutter/fork )
|
48
|
+
2. Create your feature branch (`git checkout -b my-new-feature`)
|
49
|
+
3. Commit your changes (`git commit -am 'Add some feature'`)
|
50
|
+
4. Push to the branch (`git push origin my-new-feature`)
|
51
|
+
5. Create a new Pull Request
|
data/README.md
CHANGED
@@ -1,34 +1,27 @@
|
|
1
1
|
[](http://badge.fury.io/rb/laser-cutter)
|
2
2
|
[](http://travis-ci.org/kigster/laser-cutter)
|
3
|
-
[](https://codeclimate.com/github/kigster/laser-cutter)
|
3
|
+
[](https://codeclimate.com/github/kigster/laser-cutter/maintainability)[](https://codeclimate.com/github/kigster/laser-cutter/test_coverage)
|
5
4
|
|
6
|
-
|
5
|
+
[](https://gitter.im/kigster/laser-cutter?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge)
|
6
|
+
[](https://github.com/kigster/laser-cutter/issues)
|
7
|
+
[](https://github.com/kigster/laser-cutter/network)
|
8
|
+
[](https://github.com/kigster/laser-cutter/stargazers)
|
9
|
+
[](https://github.com/kigster/laser-cutter/blob/master/LICENSE)
|
7
10
|
|
8
|
-
|
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
|
+
[](https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=FSFYYNEQ8RKWU)
|
12
12
|
|
13
|
-
|
14
|
-
install it as a gem, and use command line to generate PDFs.
|
13
|
+
# LaserCutter
|
15
14
|
|
16
|
-
|
17
|
-
and provides a straight-forward user interface for generating PDF designs without the need to install
|
18
|
-
the gem or use command line.
|
15
|
+
**LaserCutter** is a ruby library for generating PDF designs for boxes of custom dimensions that suit your project, that are meant to be used as a cut template on a laser-cutter. The sides of the box snap together using alternating notches, that are deliberately laid out in a symmetric form.
|
19
16
|
|
20
|
-
|
17
|
+
To use `laser-cutter` you need to have a recent version of ruby interpreter, install it as a gem, and use command line to generate PDFs.
|
18
|
+
|
19
|
+
[MakeABox.IO](http://makeabox.io) is an online web application that uses `laser-cutter` library and provides a straight-forward user interface for generating PDF designs without the need to install the gem or use command line.
|
21
20
|
|
22
|
-
|
21
|
+
Use whatever suites you better.
|
23
22
|
|
24
|
-
|
25
|
-
laser-cut designs, where alternative strategies can be added over time, and supported by various
|
26
|
-
command line options, and perhaps a light weight web application. If you are interested in
|
27
|
-
contributing to the project, please see [contributing](CONTRIBUTING.md) for more details.
|
23
|
+
> NOTE: Please read our [feature comparison guide](BOXMAKER.md) of LaserCutter against an older tool called [BoxMaker](https://github.com/rahulbot/boxmaker).
|
28
24
|
|
29
|
-
```laser-cutter``` supports many flexible command line options that allow setting dimensions,
|
30
|
-
stroke width, page size, layout, margins, padding (spacing between the boxes), and many more.
|
31
|
-
|
32
25
|
## Dependencies
|
33
26
|
|
34
27
|
The gem depends primarily on [Prawn](http://prawnpdf.org) – a fantastic PDF generation library.
|
@@ -43,12 +36,49 @@ And then execute:
|
|
43
36
|
|
44
37
|
$ bundle
|
45
38
|
|
46
|
-
Or install it
|
39
|
+
Or install it manually:
|
47
40
|
|
48
41
|
$ gem install laser-cutter
|
49
42
|
|
50
43
|
## Usage
|
51
44
|
|
45
|
+
We'll start with some examples:
|
46
|
+
|
47
|
+
### Examples
|
48
|
+
|
49
|
+
Create a box defined in inches, with kerf (cut width) set to `0.005in`, and open PDF in preview right after:
|
50
|
+
|
51
|
+
```bash
|
52
|
+
laser-cutter -z 3x2x2/0.125 -k 0.005 -O -o box.pdf
|
53
|
+
```
|
54
|
+
|
55
|
+
Create a box defined in millimeters, print verbose info, and set page size to A3, and layout to landscape, and stroke width to `1/2mm`:
|
56
|
+
|
57
|
+
```bash
|
58
|
+
laser-cutter -u mm -w70 -h20 -d50 -t4.3 -n5 -iA3 -l landscape -s0.5 -v -O -o box.pdf
|
59
|
+
```
|
60
|
+
|
61
|
+
List all possible page sizes in metric system:
|
62
|
+
|
63
|
+
```bash
|
64
|
+
laser-cutter -L -u mm
|
65
|
+
```
|
66
|
+
|
67
|
+
Create a box with provided dimensions, and save the config to a file for later use:
|
68
|
+
|
69
|
+
```bash
|
70
|
+
laser-cutter -z 1.1x2.5x1.5/0.125/0.125 -p 0.1 -O -o box.pdf -W box-settings.json
|
71
|
+
```
|
72
|
+
|
73
|
+
Read settings from a previously saved file:
|
74
|
+
|
75
|
+
```bash
|
76
|
+
laser-cutter -O -o box.pdf -R box-settings.json
|
77
|
+
cat box-settings.json | laser-cutter -O -o box.pdf -R -
|
78
|
+
```
|
79
|
+
|
80
|
+
### Complete Help
|
81
|
+
|
52
82
|
```bash
|
53
83
|
|
54
84
|
Usage: laser-cutter [options] -o filename.pdf
|
@@ -60,7 +90,7 @@ Specific Options:
|
|
60
90
|
-d, --depth DEPTH Internal depth of the box
|
61
91
|
-t, --thickness THICKNESS Thickness of the box material
|
62
92
|
-n, --notch NOTCH Optional notch length (aka "tab width"), guide only
|
63
|
-
-k, --kerf KERF Kerf - cut width (default is 0.
|
93
|
+
-k, --kerf KERF Kerf - cut width (default is 0.0024in)
|
64
94
|
|
65
95
|
-m, --margin MARGIN Margins from the edge of the document
|
66
96
|
-p, --padding PADDING Space between the boxes on the page
|
@@ -90,41 +120,7 @@ Common Options:
|
|
90
120
|
-u, --units UNITS Either 'in' for inches (default) or 'mm'
|
91
121
|
```
|
92
122
|
|
93
|
-
|
94
|
-
|
95
|
-
Create a box defined in inches, with kerf (cut width) set to 0.008", and open PDF in preview right after:
|
96
|
-
|
97
|
-
```bash
|
98
|
-
laser-cutter -z 3x2x2/0.125 -k 0.008 -O -o box.pdf
|
99
|
-
```
|
100
|
-
|
101
|
-
Create a box defined in millimeters, print verbose info, and set
|
102
|
-
page size to A3, and layout to landscape, and stroke width to 1/2mm:
|
103
|
-
|
104
|
-
```bash
|
105
|
-
laser-cutter -u mm -w70 -h20 -d50 -t4.3 -n5 -iA3 -l landscape -s0.5 -v -O -o box.pdf
|
106
|
-
```
|
107
|
-
|
108
|
-
List all possible page sizes in metric system:
|
109
|
-
|
110
|
-
```bash
|
111
|
-
laser-cutter -L -u mm
|
112
|
-
```
|
113
|
-
|
114
|
-
Create a box with provided dimensions, and save the config to a file for later use:
|
115
|
-
|
116
|
-
```bash
|
117
|
-
laser-cutter -z 1.1x2.5x1.5/0.125/0.125 -p 0.1 -O -o box.pdf -W box-settings.json
|
118
|
-
```
|
119
|
-
|
120
|
-
Read settings from a previously saved file:
|
121
|
-
|
122
|
-
```bash
|
123
|
-
laser-cutter -O -o box.pdf -R box-settings.json
|
124
|
-
cat box-settings.json | laser-cutter -O -o box.pdf -R -
|
125
|
-
```
|
126
|
-
|
127
|
-
## Feature Wish List
|
123
|
+
## Wish List
|
128
124
|
|
129
125
|
* Create T-style joins, using various standard sizes of nuts and bolts (such as common #4-40 and M2 sizes)
|
130
126
|
* Extensibility with various layout strategies, notch drawing strategies, basically plug and play
|
@@ -133,56 +129,9 @@ Read settings from a previously saved file:
|
|
133
129
|
* Supporting lids and front panels, that are larger than the box itself and have holes for notches.
|
134
130
|
* Your brilliant idea can be here too! Please see [contributing](CONTRIBUTING.md) for more info.
|
135
131
|
|
136
|
-
## LaserCutter vs BoxMaker
|
137
|
-
|
138
|
-
[Rahulbot](https://github.com/rahulbot/)-made [BoxMaker](https://github.com/rahulbot/boxmaker/) is a
|
139
|
-
functional generator of notched designs, similar to ```laser-cutter```, and generously open sourced
|
140
|
-
by the author, and so in no way this project disputes BoxMaker's viability. In fact BoxMaker was an
|
141
|
-
inspiration for this project.
|
142
|
-
|
143
|
-
Laser-Cutter library attempts to further advance the concept of programmatically creating
|
144
|
-
laser-cut box designs, provides additional fine tuning, many more options, strategies and most
|
145
|
-
importantly – extensibility.
|
146
|
-
|
147
|
-
Unlike ```BoxMaker```, this gem has a suit of automated tests (rspecs) around the core functionality.
|
148
|
-
In addition, new feature contributions are highly encouraged, and in that
|
149
|
-
regard having existing test suit offers confidence against regressions, and thus welcomes colaboration.
|
150
|
-
|
151
|
-
Finally, BoxMaker's notch-drawing algorithm generates non-symmetric and sometimes purely broken designs
|
152
|
-
(see picture below).
|
153
|
-
|
154
|
-
```laser-cutter```'s algorithm will create a _symmetric design for most panels_, but it might sacrifice
|
155
|
-
identical notch length. Depending on the box dimensions you may end up with a slightly different notch
|
156
|
-
length on each side of the box.
|
157
|
-
|
158
|
-
The choice ultimately comes down to the preference and feature set, so here I show you two boxes made with
|
159
|
-
each program, so you can pick what you prefer.
|
160
|
-
|
161
|
-
### Example Outputs
|
162
|
-
|
163
|
-
Below are two examples of boxes with identical dimensions produced with ```laser-cutter``` and ```boxmaker```:
|
164
|
-
|
165
|
-
This is how you would make a box with Adam Phelp's fork of BoxMaker (which adds flags and a lot of
|
166
|
-
niceties):
|
167
|
-
|
168
|
-
```bash
|
169
|
-
git clone https://github.com/aphelps/boxmaker && cd boxmaker && ant
|
170
|
-
java -cp BOX.jar com.rahulbotics.boxmaker.BoxMaker \
|
171
|
-
-W 1 -H 2 -D 1.5 -T 0.125 -n 0.125 -o box.pdf
|
172
|
-
```
|
173
|
-
|
174
|
-
And laser-cutter:
|
175
|
-
|
176
|
-
```bash
|
177
|
-
gem install laser-cutter
|
178
|
-
laser-cutter -z 1x1.5x2/0.125/0.125 -O -o box.pdf
|
179
|
-
```
|
180
|
-
|
181
|
-
.
|
182
|
-
|
183
132
|
## Contributing
|
184
133
|
|
185
|
-
1. Fork it ( https://github.com/
|
134
|
+
1. Fork it ( https://github.com/kigster/laser-cutter/fork )
|
186
135
|
2. Create your feature branch (`git checkout -b my-new-feature`)
|
187
136
|
3. Commit your changes (`git commit -am 'Add some feature'`)
|
188
137
|
4. Push to the branch (`git push origin my-new-feature`)
|
data/Rakefile
CHANGED
@@ -1,2 +1,29 @@
|
|
1
|
-
require
|
1
|
+
require 'bundler/gem_tasks'
|
2
|
+
require 'rspec/core/rake_task'
|
3
|
+
require 'yard'
|
2
4
|
|
5
|
+
def shell(*args)
|
6
|
+
puts "running: #{args.join(' ')}"
|
7
|
+
system(args.join(' '))
|
8
|
+
end
|
9
|
+
|
10
|
+
task :clean do
|
11
|
+
shell('rm -rf pkg/ tmp/ coverage/' )
|
12
|
+
end
|
13
|
+
|
14
|
+
task :permissions => [ :clean ] do
|
15
|
+
shell("chmod -v o+r,g+r * */* */*/* */*/*/* */*/*/*/* */*/*/*/*/*")
|
16
|
+
shell("find . -type d -exec chmod o+x,g+x {} \\;")
|
17
|
+
end
|
18
|
+
|
19
|
+
task :build => :permissions
|
20
|
+
|
21
|
+
YARD::Rake::YardocTask.new(:doc) do |t|
|
22
|
+
t.files = %w(lib/**/*.rb bin/* - README.md LICENSE.txt BOXMAKER.md)
|
23
|
+
t.options.unshift('--title','LaserCutter Library')
|
24
|
+
t.after = ->() { exec('open doc/index.html') }
|
25
|
+
end
|
26
|
+
|
27
|
+
RSpec::Core::RakeTask.new(:spec)
|
28
|
+
|
29
|
+
task :default => :spec
|
data/{doc → docs}/comparison.jpg
RENAMED
File without changes
|
data/laser-cutter.gemspec
CHANGED
@@ -22,9 +22,9 @@ Gem::Specification.new do |spec|
|
|
22
22
|
spec.add_dependency 'hashie'
|
23
23
|
spec.add_dependency 'colored'
|
24
24
|
|
25
|
-
spec.add_development_dependency '
|
25
|
+
spec.add_development_dependency 'yard'
|
26
|
+
spec.add_development_dependency 'simplecov'
|
27
|
+
spec.add_development_dependency 'bundler'
|
26
28
|
spec.add_development_dependency 'rake'
|
27
29
|
spec.add_development_dependency 'rspec'
|
28
|
-
spec.add_development_dependency 'rspec-legacy_formatters'
|
29
|
-
spec.add_development_dependency 'codeclimate-test-reporter'
|
30
30
|
end
|
data/lib/laser-cutter/box.rb
CHANGED
@@ -1,9 +1,10 @@
|
|
1
|
+
require 'forwardable'
|
1
2
|
module Laser
|
2
3
|
module Cutter
|
3
|
-
# Note: this class badly needs refactoring and tests. Both are coming.
|
4
|
-
|
5
4
|
class Box
|
6
5
|
# Everything is in millimeters
|
6
|
+
extend Forwardable
|
7
|
+
def_delegators :@dim, :w, :h, :d
|
7
8
|
|
8
9
|
attr_accessor :dim, :thickness, :notch_width, :kerf
|
9
10
|
attr_accessor :padding, :units, :inside_box
|
@@ -13,33 +14,43 @@ module Laser
|
|
13
14
|
attr_accessor :metadata, :notches
|
14
15
|
|
15
16
|
def initialize(config = {})
|
16
|
-
self.dim
|
17
|
+
self.dim = Geometry::Dimensions.new(config['width'], config['height'], config['depth'])
|
17
18
|
self.thickness = config['thickness']
|
18
19
|
|
19
20
|
self.notch_width = config['notch'] || (1.0 * self.longest / 5.0)
|
20
|
-
self.kerf
|
21
|
-
self.padding
|
22
|
-
self.units
|
23
|
-
self.inside_box
|
21
|
+
self.kerf = config['kerf'] || 0.0
|
22
|
+
self.padding = config['padding']
|
23
|
+
self.units = config['units']
|
24
|
+
self.inside_box = config['inside_box']
|
24
25
|
|
25
26
|
self.notches = []
|
26
27
|
|
27
28
|
self.metadata = Geometry::Point[config['metadata_width'] || 0, config['metadata_height'] || 0]
|
28
29
|
|
29
30
|
create_faces! # generates dimensions for each side
|
30
|
-
self.faces
|
31
|
-
|
32
|
-
self.conf
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
31
|
+
self.faces = [top, front, bottom, back, left, right]
|
32
|
+
|
33
|
+
self.conf = {
|
34
|
+
valign: [:out, :out, :out, :out, :in, :in],
|
35
|
+
halign: [:in, :out, :in, :out, :in, :in],
|
36
|
+
corners: {
|
37
|
+
top: [:yes, :no, :yes, :no, :no, :no], # 2nd choice, has to work if 1st doesn't
|
38
|
+
front: [:no, :yes, :no, :yes, :no, :no], # our default choice, but may not work
|
39
|
+
},
|
39
40
|
}
|
40
41
|
self
|
41
42
|
end
|
42
43
|
|
44
|
+
def generate_notches
|
45
|
+
position_faces!
|
46
|
+
self.corner_face = pick_corners_face
|
47
|
+
self.notches = []
|
48
|
+
faces.each_with_index do |face, face_index|
|
49
|
+
create_face_edges(face, face_index)
|
50
|
+
end
|
51
|
+
self.notches.flatten!
|
52
|
+
end
|
53
|
+
|
43
54
|
def enclosure
|
44
55
|
generate_notches if self.notches.empty?
|
45
56
|
p1 = notches.first.p1.to_a
|
@@ -47,55 +58,15 @@ module Laser
|
|
47
58
|
|
48
59
|
notches.each do |notch|
|
49
60
|
n = notch.normalized
|
50
|
-
n.p1.to_a.each_with_index {|c, i| p1[i] = c if c < p1[i]
|
51
|
-
n.p2.to_a.each_with_index {|c, i| p2[i] = c if c > p2[i]
|
61
|
+
n.p1.to_a.each_with_index {|c, i| p1[i] = c if c < p1[i]}
|
62
|
+
n.p2.to_a.each_with_index {|c, i| p2[i] = c if c > p2[i]}
|
52
63
|
end
|
53
64
|
|
54
65
|
Geometry::Rect[Geometry::Point.new(p1), Geometry::Point.new(p2)]
|
55
66
|
end
|
56
67
|
|
57
|
-
def generate_notches
|
58
|
-
position_faces!
|
59
|
-
corner_face = pick_corners_face
|
60
|
-
self.notches = []
|
61
|
-
faces.each_with_index do |face, face_index|
|
62
|
-
bound = face_bounding_rect(face)
|
63
|
-
side_lines = []
|
64
|
-
edges = []
|
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?)
|
67
|
-
key = side_index.odd? ? :valign : :halign
|
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
|
-
end
|
77
|
-
|
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!
|
91
|
-
end
|
92
|
-
|
93
|
-
def w; dim.w; end
|
94
|
-
def h; dim.h; end
|
95
|
-
def d; dim.d; end
|
96
|
-
|
97
68
|
def longest
|
98
|
-
[w, h, d].max
|
69
|
+
[w, h, d].max
|
99
70
|
end
|
100
71
|
|
101
72
|
def to_s
|
@@ -104,6 +75,37 @@ module Laser
|
|
104
75
|
|
105
76
|
private
|
106
77
|
|
78
|
+
def create_face_edges(face, face_index)
|
79
|
+
bound = face_bounding_rect(face)
|
80
|
+
|
81
|
+
side_lines = []
|
82
|
+
edges = []
|
83
|
+
bound.sides.each_with_index do |bounding_side, side_index|
|
84
|
+
include_corners = (self.conf[:corners][corner_face][face_index] == :yes && side_index.odd?)
|
85
|
+
key = side_index.odd? ? :valign : :halign
|
86
|
+
center_out = (self.conf[key][face_index] == :out)
|
87
|
+
edges << Notching::Edge.new(bounding_side, face.sides[side_index],
|
88
|
+
{ :notch_width => notch_width,
|
89
|
+
:thickness => thickness,
|
90
|
+
:kerf => kerf,
|
91
|
+
:center_out => center_out,
|
92
|
+
:corners => include_corners
|
93
|
+
})
|
94
|
+
end
|
95
|
+
|
96
|
+
if edges.any? {|e| e.corners} && !edges.all? {|e| e.first_notch_out?}
|
97
|
+
edges.each {|e| e.adjust_corners = true}
|
98
|
+
end
|
99
|
+
|
100
|
+
edges.each do |edge|
|
101
|
+
side_lines << Notching::PathGenerator.new(edge).generate
|
102
|
+
end
|
103
|
+
|
104
|
+
aggregator = Aggregator.new(side_lines.flatten)
|
105
|
+
aggregator.dedup!.deoverlap!.dedup!
|
106
|
+
self.notches << aggregator.lines
|
107
|
+
end
|
108
|
+
|
107
109
|
def face_bounding_rect(face)
|
108
110
|
b = face.clone
|
109
111
|
b.move_to(b.position.plus(-thickness, -thickness))
|
@@ -141,39 +143,39 @@ module Laser
|
|
141
143
|
left.x = offset_x - d - 2 * thickness - padding
|
142
144
|
right.x = offset_x + w + 2 * thickness + padding
|
143
145
|
|
144
|
-
[bottom, front, top, back].each {
|
146
|
+
[bottom, front, top, back].each {|s| s.x = offset_x}
|
145
147
|
|
146
148
|
# Y Coordinate
|
147
149
|
top.y = offset_y - d - 2 * thickness - padding
|
148
150
|
bottom.y = offset_y + h + 2 * thickness + padding
|
149
151
|
back.y = bottom.y + d + 2 * thickness + padding
|
150
152
|
|
151
|
-
[left, front, right].each {
|
153
|
+
[left, front, right].each {|s| s.y = offset_y}
|
152
154
|
|
153
155
|
faces.each(&:relocate!)
|
154
156
|
end
|
155
157
|
|
156
158
|
def create_faces!
|
157
|
-
zero
|
159
|
+
zero = Geometry::Point.new(0, 0)
|
158
160
|
self.front = Geometry::Rect.create(zero, dim.w, dim.h, "front")
|
159
|
-
self.back
|
161
|
+
self.back = Geometry::Rect.create(zero, dim.w, dim.h, "back")
|
160
162
|
|
161
|
-
self.top
|
163
|
+
self.top = Geometry::Rect.create(zero, dim.w, dim.d, "top")
|
162
164
|
self.bottom = Geometry::Rect.create(zero, dim.w, dim.d, "bottom")
|
163
165
|
|
164
|
-
self.left
|
166
|
+
self.left = Geometry::Rect.create(zero, dim.d, dim.h, "left")
|
165
167
|
self.right = Geometry::Rect.create(zero, dim.d, dim.h, "right")
|
166
168
|
end
|
167
169
|
|
168
170
|
# Choose which face will be responsible for filling out the little square overlap
|
169
171
|
# in the corners. Only one of the 3 possible sides need to be picked.
|
170
172
|
def pick_corners_face
|
171
|
-
b
|
173
|
+
b = face_bounding_rect(front)
|
172
174
|
edges = []
|
173
175
|
front.sides[0..1].each_with_index do |face, index|
|
174
|
-
edges << Notching::Edge.new(b.sides[index], face, :notch_width => notch_width, :kerf => kerf
|
176
|
+
edges << Notching::Edge.new(b.sides[index], face, :notch_width => notch_width, :kerf => kerf)
|
175
177
|
end
|
176
|
-
edges.map(&:notch_count).all?{|c| c % 4 == 3} ? :top : :front
|
178
|
+
edges.map(&:notch_count).all? {|c| c % 4 == 3} ? :top : :front
|
177
179
|
end
|
178
180
|
|
179
181
|
end
|
@@ -0,0 +1,18 @@
|
|
1
|
+
require 'forwardable'
|
2
|
+
module Laser
|
3
|
+
module Cutter
|
4
|
+
# Note: this class badly needs refactoring and tests. Both are coming.
|
5
|
+
|
6
|
+
|
7
|
+
class Face
|
8
|
+
attr_accessor :edges, :rect
|
9
|
+
extend Forwardable
|
10
|
+
def_delegators :@rect, *((Geometry::Rect.new(Geometry::Point.new(0,0), Geometry::Point.new(1,1))).methods - Object.methods)
|
11
|
+
def initialize(rect, edges = [])
|
12
|
+
self.rect = rect
|
13
|
+
self.edges = edges
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
end
|
18
|
+
end
|
@@ -32,8 +32,8 @@ module Laser
|
|
32
32
|
self.notch_width = options[:notch_width]
|
33
33
|
self.adjust_corners = options[:adjust_corners]
|
34
34
|
|
35
|
-
adjust_for_kerf!
|
36
35
|
calculate_notch_width!
|
36
|
+
adjust_for_kerf!
|
37
37
|
end
|
38
38
|
|
39
39
|
def adjust_for_kerf!
|
@@ -68,11 +68,11 @@ module Laser
|
|
68
68
|
private
|
69
69
|
|
70
70
|
def calculate_notch_width!
|
71
|
-
|
72
|
-
count = (length / notch_width).to_f.ceil + 1
|
71
|
+
count = ((self.inside.length) / notch_width).to_f.ceil + 1
|
73
72
|
count = (count / 2 * 2) + 1 # make count always an odd number
|
74
73
|
count = [MINIMUM_NOTCHES_PER_SIDE, count].max
|
75
|
-
|
74
|
+
|
75
|
+
self.notch_width = 1.0 * (self.inside.length) / count
|
76
76
|
self.notch_count = count
|
77
77
|
end
|
78
78
|
|
@@ -32,6 +32,17 @@ module Laser
|
|
32
32
|
end
|
33
33
|
end
|
34
34
|
|
35
|
+
# One of the key "tricks" that this algorithm applies, is that it converts everything into
|
36
|
+
# pure set of lines in the end. It then tries to find all intersections of the lines so that
|
37
|
+
# we can remove duplicates. So any segment of any line that is covered by 2 lines or more is removed,
|
38
|
+
# cleared completely for an empty space. This turns out to be very useful indeed, because we can
|
39
|
+
# paint with wide brush strokes to get the carcass, and then fine tune it by adding or removing line
|
40
|
+
# segments. Some of the lines below are added to actually remove the lines that might have otherwise
|
41
|
+
# been there.
|
42
|
+
#
|
43
|
+
# This comes especially handy when drawing corner boxes, which are deliberately made not to match the notch
|
44
|
+
# width, but to match thickness of the material. The corner notces for these sides will therefore have
|
45
|
+
# length equal to the thickness + regular notch length.
|
35
46
|
class PathGenerator
|
36
47
|
|
37
48
|
extend ::Forwardable
|
@@ -84,81 +95,23 @@ module Laser
|
|
84
95
|
end
|
85
96
|
end
|
86
97
|
|
98
|
+
# These two boxes occupy the corners of the 3D box. They do not match
|
99
|
+
# in width to our notches because they are usually merged with them. Their
|
100
|
+
# size is equal to the thickness of the material (adjusted for kerf)
|
101
|
+
# It's just an aesthetic choice I guess.
|
87
102
|
def corner_box_sides
|
88
103
|
boxes = []
|
89
104
|
extra_lines = []
|
90
|
-
sides = []
|
91
105
|
|
92
|
-
# These two boxes occupy the corners of the 3D box. They do not match
|
93
|
-
# in width to our notches because they are usually merged with them.
|
94
|
-
# It's just an aesthetic choice I guess.
|
95
106
|
boxes << Geometry::Rect[edge.inside.p1.clone, edge.outside.p1.clone]
|
96
107
|
boxes << Geometry::Rect[edge.inside.p2.clone, edge.outside.p2.clone]
|
97
108
|
|
98
|
-
if kerf?
|
99
|
-
if adjust_corners
|
100
|
-
if first_notch_out?
|
101
|
-
k = 2
|
102
|
-
direction = -1
|
103
|
-
dim_index = 1
|
104
|
-
extra_lines << add_corners_when_out(dim_index, direction, k)
|
105
|
-
else
|
106
|
-
k = -2
|
107
|
-
direction = 1
|
108
|
-
dim_index = 0
|
109
|
-
extra_lines << add_boxes_when_in(dim_index, direction, k)
|
110
|
-
end
|
111
|
-
end
|
112
|
-
end
|
109
|
+
extra_lines << add_corners if adjust_corners && kerf?
|
113
110
|
sides = boxes.flatten.map(&:relocate!).map(&:sides)
|
114
111
|
sides << extra_lines if !extra_lines.empty?
|
115
112
|
sides.flatten
|
116
113
|
end
|
117
114
|
|
118
|
-
def add_boxes_when_in(dim_index, direction, k)
|
119
|
-
v1 = k * direction * shift_vector(1, dim_index)
|
120
|
-
v2 = k * direction * shift_vector(2, dim_index)
|
121
|
-
p1 = edge.inside.p1.plus(v1)
|
122
|
-
coords = []
|
123
|
-
coords[d_index_along] = edge.inside.p1[d_index_along]
|
124
|
-
coords[d_index_across] = edge.outside.p1[d_index_across]
|
125
|
-
p2 = Geometry::Point[*coords]
|
126
|
-
r1 = Geometry::Rect[p1, p2]
|
127
|
-
|
128
|
-
p1 = edge.inside.p2.plus(v2)
|
129
|
-
coords = []
|
130
|
-
coords[d_index_along] = edge.inside.p2[d_index_along]
|
131
|
-
coords[d_index_across] = edge.outside.p2[d_index_across]
|
132
|
-
p2 = Geometry::Point[*coords]
|
133
|
-
r2 = Geometry::Rect[p1, p2]
|
134
|
-
lines = [r1, r2].map(&:sides).flatten
|
135
|
-
lines << Geometry::Line[edge.inside.p1.plus(v1), edge.inside.p1.clone]
|
136
|
-
lines << Geometry::Line[edge.inside.p2.plus(v2), edge.inside.p2.clone]
|
137
|
-
lines
|
138
|
-
end
|
139
|
-
|
140
|
-
def add_corners_when_out(dim_index, direction, k)
|
141
|
-
v1 = direction * k * shift_vector(1, dim_index)
|
142
|
-
v2 = direction * k * shift_vector(2, dim_index)
|
143
|
-
p1 = edge.inside.p1.plus(v1)
|
144
|
-
coords = []
|
145
|
-
coords[d_index_along] = edge.outside.p1[d_index_along]
|
146
|
-
coords[d_index_across] = edge.inside.p1[d_index_across]
|
147
|
-
p2 = Geometry::Point[*coords]
|
148
|
-
r1 = Geometry::Rect[p1, p2]
|
149
|
-
|
150
|
-
p1 = edge.inside.p2.plus(v2)
|
151
|
-
coords = []
|
152
|
-
coords[d_index_along] = edge.outside.p2[d_index_along]
|
153
|
-
coords[d_index_across] = edge.inside.p2[d_index_across]
|
154
|
-
p2 = Geometry::Point[*coords]
|
155
|
-
r2 = Geometry::Rect[p1, p2]
|
156
|
-
lines = [r1, r2].map(&:sides).flatten
|
157
|
-
lines << Geometry::Line[edge.inside.p1.plus(v1), edge.inside.p1.clone]
|
158
|
-
lines << Geometry::Line[edge.inside.p2.plus(v2), edge.inside.p2.clone]
|
159
|
-
lines
|
160
|
-
end
|
161
|
-
|
162
115
|
def shift_vector(index, dim_shift = 0)
|
163
116
|
shift = []
|
164
117
|
shift[(d_index_across + dim_shift) % 2] = 0
|
@@ -189,6 +142,38 @@ module Laser
|
|
189
142
|
end
|
190
143
|
|
191
144
|
private
|
145
|
+
# Helper method to calculate dimensions of our corners.
|
146
|
+
def add_corners
|
147
|
+
k, direction, dim_index, edge_along, edge_across = if first_notch_out?
|
148
|
+
[2, -1, 1, :inside, :outside]
|
149
|
+
else
|
150
|
+
[-2, 1, 0, :outside, :inside]
|
151
|
+
end
|
152
|
+
v1 = direction * k * shift_vector(1, dim_index)
|
153
|
+
v2 = direction * k * shift_vector(2, dim_index)
|
154
|
+
|
155
|
+
r1 = define_corner_rect(:p1, v1, edge_along, edge_across)
|
156
|
+
r2 = define_corner_rect(:p2, v2, edge_along, edge_across)
|
157
|
+
|
158
|
+
lines = [r1, r2].map(&:sides).flatten
|
159
|
+
|
160
|
+
# Our clever algorithm removes automatically duplicate lines. These lines
|
161
|
+
# below are added to actually clear out this space and remove the existing
|
162
|
+
# lines that are already there.
|
163
|
+
lines << Geometry::Line[edge.inside.p1.plus(v1), edge.inside.p1.clone]
|
164
|
+
lines << Geometry::Line[edge.inside.p2.plus(v2), edge.inside.p2.clone]
|
165
|
+
lines
|
166
|
+
end
|
167
|
+
|
168
|
+
def define_corner_rect(point, delta, edge_along, edge_across)
|
169
|
+
p1 = edge.inside.send(point).plus(delta)
|
170
|
+
coords = []
|
171
|
+
coords[d_index_along] = edge.send(edge_along).send(point)[d_index_along]
|
172
|
+
coords[d_index_across] = edge.send(edge_across).send(point)[d_index_across]
|
173
|
+
p2 = Geometry::Point[*coords]
|
174
|
+
Geometry::Rect[p1, p2]
|
175
|
+
end
|
176
|
+
|
192
177
|
|
193
178
|
# This method has the bulk of the logic: we create the list of path deltas
|
194
179
|
# to be applied when we walk the edge next.
|
data/lib/laser-cutter/version.rb
CHANGED
data/spec/box_spec.rb
CHANGED
data/spec/spec_helper.rb
CHANGED
@@ -5,16 +5,12 @@
|
|
5
5
|
#
|
6
6
|
# See http://rubydoc.info/gems/rspec-core/RSpec/Core/Configuration
|
7
7
|
|
8
|
-
if ENV['CODECLIMATE_REPO_TOKEN']
|
9
|
-
require 'codeclimate-test-reporter'
|
10
|
-
CodeClimate::TestReporter.start
|
11
|
-
end
|
12
|
-
|
13
8
|
ENV['BUNDLE_GEMFILE'] ||= File.expand_path('../Gemfile', __FILE__)
|
14
9
|
require 'rubygems'
|
15
10
|
require 'bundler/setup' if File.exists?(ENV['BUNDLE_GEMFILE'])
|
11
|
+
require 'simplecov'
|
12
|
+
SimpleCov.start
|
16
13
|
require 'laser-cutter'
|
17
|
-
require "codeclimate-test-reporter"
|
18
14
|
|
19
15
|
|
20
16
|
|
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: 1.0.
|
4
|
+
version: 1.0.5
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Konstantin Gredeskoul
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date:
|
11
|
+
date: 2018-01-01 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: prawn
|
@@ -53,21 +53,21 @@ dependencies:
|
|
53
53
|
- !ruby/object:Gem::Version
|
54
54
|
version: '0'
|
55
55
|
- !ruby/object:Gem::Dependency
|
56
|
-
name:
|
56
|
+
name: yard
|
57
57
|
requirement: !ruby/object:Gem::Requirement
|
58
58
|
requirements:
|
59
|
-
- - "
|
59
|
+
- - ">="
|
60
60
|
- !ruby/object:Gem::Version
|
61
|
-
version: '
|
61
|
+
version: '0'
|
62
62
|
type: :development
|
63
63
|
prerelease: false
|
64
64
|
version_requirements: !ruby/object:Gem::Requirement
|
65
65
|
requirements:
|
66
|
-
- - "
|
66
|
+
- - ">="
|
67
67
|
- !ruby/object:Gem::Version
|
68
|
-
version: '
|
68
|
+
version: '0'
|
69
69
|
- !ruby/object:Gem::Dependency
|
70
|
-
name:
|
70
|
+
name: simplecov
|
71
71
|
requirement: !ruby/object:Gem::Requirement
|
72
72
|
requirements:
|
73
73
|
- - ">="
|
@@ -81,7 +81,7 @@ dependencies:
|
|
81
81
|
- !ruby/object:Gem::Version
|
82
82
|
version: '0'
|
83
83
|
- !ruby/object:Gem::Dependency
|
84
|
-
name:
|
84
|
+
name: bundler
|
85
85
|
requirement: !ruby/object:Gem::Requirement
|
86
86
|
requirements:
|
87
87
|
- - ">="
|
@@ -95,7 +95,7 @@ dependencies:
|
|
95
95
|
- !ruby/object:Gem::Version
|
96
96
|
version: '0'
|
97
97
|
- !ruby/object:Gem::Dependency
|
98
|
-
name:
|
98
|
+
name: rake
|
99
99
|
requirement: !ruby/object:Gem::Requirement
|
100
100
|
requirements:
|
101
101
|
- - ">="
|
@@ -109,7 +109,7 @@ dependencies:
|
|
109
109
|
- !ruby/object:Gem::Version
|
110
110
|
version: '0'
|
111
111
|
- !ruby/object:Gem::Dependency
|
112
|
-
name:
|
112
|
+
name: rspec
|
113
113
|
requirement: !ruby/object:Gem::Requirement
|
114
114
|
requirements:
|
115
115
|
- - ">="
|
@@ -136,6 +136,7 @@ files:
|
|
136
136
|
- ".gitignore"
|
137
137
|
- ".rspec"
|
138
138
|
- ".travis.yml"
|
139
|
+
- BOXMAKER.md
|
139
140
|
- CONTRIBUTING.md
|
140
141
|
- Gemfile
|
141
142
|
- LICENSE
|
@@ -143,7 +144,7 @@ files:
|
|
143
144
|
- README.md
|
144
145
|
- Rakefile
|
145
146
|
- bin/laser-cutter
|
146
|
-
-
|
147
|
+
- docs/comparison.jpg
|
147
148
|
- laser-cutter.gemspec
|
148
149
|
- lib/laser-cutter.rb
|
149
150
|
- lib/laser-cutter/aggregator.rb
|
@@ -151,6 +152,7 @@ files:
|
|
151
152
|
- lib/laser-cutter/cli/opt_parser.rb
|
152
153
|
- lib/laser-cutter/cli/serializer.rb
|
153
154
|
- lib/laser-cutter/configuration.rb
|
155
|
+
- lib/laser-cutter/face.rb
|
154
156
|
- lib/laser-cutter/geometry.rb
|
155
157
|
- lib/laser-cutter/geometry/dimensions.rb
|
156
158
|
- lib/laser-cutter/geometry/point.rb
|
@@ -159,7 +161,6 @@ files:
|
|
159
161
|
- lib/laser-cutter/geometry/shape/rect.rb
|
160
162
|
- lib/laser-cutter/geometry/tuple.rb
|
161
163
|
- lib/laser-cutter/notching.rb
|
162
|
-
- lib/laser-cutter/notching/base.rb
|
163
164
|
- lib/laser-cutter/notching/edge.rb
|
164
165
|
- lib/laser-cutter/notching/path_generator.rb
|
165
166
|
- lib/laser-cutter/page_manager.rb
|
@@ -203,7 +204,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
203
204
|
version: '0'
|
204
205
|
requirements: []
|
205
206
|
rubyforge_project:
|
206
|
-
rubygems_version: 2.
|
207
|
+
rubygems_version: 2.6.11
|
207
208
|
signing_key:
|
208
209
|
specification_version: 4
|
209
210
|
summary: Creates notched box outlines for laser-cut boxes which are geometrically
|
@@ -221,4 +222,3 @@ test_files:
|
|
221
222
|
- spec/rect_spec.rb
|
222
223
|
- spec/renderer_spec.rb
|
223
224
|
- spec/spec_helper.rb
|
224
|
-
has_rdoc:
|