laser-cutter 1.0.5 → 2.0.1
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 +5 -5
- data/.envrc +2 -0
- data/.github/workflows/lint.yml +22 -0
- data/.github/workflows/rspec.yml +22 -0
- data/.gitignore +5 -5
- data/.plans/001.00-dry-cli-migration/plan.md +29 -0
- data/.plans/002.00-lid-options/plan.md +45 -0
- data/.plans/003.00-ruby-api/plan.md +37 -0
- data/.relaxed_rubocop.yml +153 -0
- data/.rspec +1 -0
- data/.rubocop.yml +31 -0
- data/.rubocop_todo.yml +90 -0
- data/.ruby-version +1 -0
- data/CLAUDE.md +75 -0
- data/CONTRIBUTING.md +2 -4
- data/Gemfile +14 -0
- data/README.md +207 -79
- data/Rakefile +27 -13
- data/docs/badges/coverage_badge.svg +21 -0
- data/docs/images/box-lid-back.avif +0 -0
- data/docs/images/box-lid-full.avif +0 -0
- data/docs/images/box-lid-plain.avif +0 -0
- data/docs/images/box-metric.avif +0 -0
- data/docs/images/box-tray.avif +0 -0
- data/exe/laser-cutter +8 -0
- data/exe/lc +1 -0
- data/justfile +97 -0
- data/laser-cutter.gemspec +27 -14
- data/lib/laser/cutter/aggregator.rb +101 -0
- data/lib/laser/cutter/box.rb +251 -0
- data/lib/laser/cutter/cli/command.rb +46 -0
- data/lib/laser/cutter/cli/completion.rb +21 -0
- data/lib/laser/cutter/cli/config_file.rb +36 -0
- data/lib/laser/cutter/cli/examples.rb +47 -0
- data/lib/laser/cutter/cli/generate.rb +95 -0
- data/lib/laser/cutter/cli/help.rb +20 -0
- data/lib/laser/cutter/cli/page_sizes.rb +19 -0
- data/lib/laser/cutter/cli/version.rb +15 -0
- data/lib/laser/cutter/cli.rb +49 -0
- data/lib/laser/cutter/configuration.rb +107 -0
- data/lib/{laser-cutter → laser/cutter}/geometry/dimensions.rb +2 -3
- data/lib/{laser-cutter/geometry/shape → laser/cutter/geometry}/line.rb +19 -18
- data/lib/{laser-cutter → laser/cutter}/geometry/point.rb +4 -2
- data/lib/{laser-cutter/geometry/shape → laser/cutter/geometry}/rect.rb +6 -7
- data/lib/{laser-cutter → laser/cutter}/geometry/shape.rb +7 -7
- data/lib/{laser-cutter → laser/cutter}/geometry/tuple.rb +40 -36
- data/lib/laser/cutter/invalid_option.rb +8 -0
- data/lib/laser/cutter/launcher.rb +40 -0
- data/lib/laser/cutter/missing_option.rb +7 -0
- data/lib/laser/cutter/notching/base.rb +19 -0
- data/lib/{laser-cutter → laser/cutter}/notching/edge.rb +36 -20
- data/lib/laser/cutter/notching/infinite_iterator.rb +27 -0
- data/lib/laser/cutter/notching/path_generator.rb +187 -0
- data/lib/laser/cutter/notching/shift.rb +17 -0
- data/lib/laser/cutter/options.rb +154 -0
- data/lib/laser/cutter/page_manager.rb +50 -0
- data/lib/{laser-cutter → laser/cutter}/renderer/base.rb +10 -3
- data/lib/laser/cutter/renderer/box_renderer.rb +40 -0
- data/lib/laser/cutter/renderer/layout_renderer.rb +61 -0
- data/lib/laser/cutter/renderer/line_renderer.rb +21 -0
- data/lib/laser/cutter/renderer/meta_renderer.rb +83 -0
- data/lib/{laser-cutter → laser/cutter}/renderer/rect_renderer.rb +3 -1
- data/lib/laser/cutter/renderer/svg_renderer.rb +65 -0
- data/lib/laser/cutter/renderer.rb +24 -0
- data/lib/laser/cutter/types.rb +57 -0
- data/lib/laser/cutter/units_converter.rb +15 -0
- data/lib/laser/cutter/version.rb +7 -0
- data/lib/laser/cutter/zero_value_not_allowed.rb +7 -0
- data/lib/laser/cutter.rb +67 -0
- data/lib/laser-cutter.rb +2 -14
- data/lib/laser_cutter.rb +3 -0
- data/spec/laser/cutter/aggregator_spec.rb +70 -0
- data/spec/laser/cutter/box_lid_spec.rb +142 -0
- data/spec/laser/cutter/box_spec.rb +61 -0
- data/spec/laser/cutter/cli/config_file_spec.rb +31 -0
- data/spec/laser/cutter/cli_spec.rb +213 -0
- data/spec/laser/cutter/configuration_spec.rb +118 -0
- data/spec/{dimensions_spec.rb → laser/cutter/dimensions_spec.rb} +8 -6
- data/spec/laser/cutter/edge_spec.rb +64 -0
- data/spec/{line_spec.rb → laser/cutter/line_spec.rb} +26 -20
- data/spec/laser/cutter/options_spec.rb +120 -0
- data/spec/{page_manager_spec.rb → laser/cutter/page_manager_spec.rb} +27 -11
- data/spec/{path_generator_spec.rb → laser/cutter/path_generator_spec.rb} +18 -14
- data/spec/{point_spec.rb → laser/cutter/point_spec.rb} +27 -22
- data/spec/{rect_spec.rb → laser/cutter/rect_spec.rb} +9 -5
- data/spec/laser/cutter/renderer/layout_renderer_spec.rb +85 -0
- data/spec/laser/cutter/renderer/svg_renderer_spec.rb +51 -0
- data/spec/laser/cutter/renderer_spec.rb +19 -0
- data/spec/laser/cutter_spec.rb +114 -0
- data/spec/spec_helper.rb +35 -22
- data/spec/support/aruba.rb +10 -0
- data/spec/support/outline.rb +53 -0
- metadata +230 -76
- data/.travis.yml +0 -26
- data/BOXMAKER.md +0 -51
- data/LICENSE +0 -22
- data/bin/laser-cutter +0 -19
- data/lib/laser-cutter/aggregator.rb +0 -57
- data/lib/laser-cutter/box.rb +0 -183
- data/lib/laser-cutter/cli/opt_parser.rb +0 -131
- data/lib/laser-cutter/cli/serializer.rb +0 -51
- data/lib/laser-cutter/configuration.rb +0 -83
- data/lib/laser-cutter/face.rb +0 -18
- data/lib/laser-cutter/geometry.rb +0 -11
- data/lib/laser-cutter/notching/path_generator.rb +0 -234
- data/lib/laser-cutter/notching.rb +0 -9
- data/lib/laser-cutter/page_manager.rb +0 -43
- data/lib/laser-cutter/renderer/box_renderer.rb +0 -36
- data/lib/laser-cutter/renderer/layout_renderer.rb +0 -59
- data/lib/laser-cutter/renderer/line_renderer.rb +0 -17
- data/lib/laser-cutter/renderer/meta_renderer.rb +0 -77
- data/lib/laser-cutter/renderer.rb +0 -14
- data/lib/laser-cutter/version.rb +0 -5
- data/spec/aggregator_spec.rb +0 -65
- data/spec/box_spec.rb +0 -39
- data/spec/configuration_spec.rb +0 -81
- data/spec/edge_spec.rb +0 -43
- data/spec/renderer_spec.rb +0 -72
- /data/docs/{comparison.jpg → images/comparison.jpg} +0 -0
data/README.md
CHANGED
|
@@ -1,138 +1,266 @@
|
|
|
1
|
-
[](http://badge.fury.io/rb/laser-cutter)
|
|
2
|
-
[](http://travis-ci.org/kigster/laser-cutter)
|
|
3
|
-
[](https://codeclimate.com/github/kigster/laser-cutter/maintainability)[](https://codeclimate.com/github/kigster/laser-cutter/test_coverage)
|
|
1
|
+
[](http://badge.fury.io/rb/laser-cutter) 
|
|
4
2
|
|
|
5
|
-
|
|
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)
|
|
3
|
+
## LaserCutter and Make-A-Box.io
|
|
10
4
|
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
# LaserCutter
|
|
14
|
-
|
|
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.
|
|
5
|
+
`laser-cutter` is a ruby library for generating PDF designs for boxes of custom dimensions that suit your project, that can be cut from wood or acrylic using a laser-cutter. The sides of the box snap together using alternating notches, that are deliberately layed out in a symmetric form.
|
|
16
6
|
|
|
17
7
|
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
8
|
|
|
19
|
-
[
|
|
9
|
+
[Make-A-Box](http://makeabox.io) is a 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.
|
|
20
10
|
|
|
21
11
|
Use whatever suites you better.
|
|
22
12
|
|
|
23
|
-
|
|
13
|
+
### Design Goals
|
|
14
|
+
|
|
15
|
+
One of the design goals of this project is to provide a highly extensible platform for creating laser-cut designs, where alternative strategies can be added over time, and supported by various command line options, and perhaps a light weight web application. If you are interested in contributing to the project, please see [contributing](CONTRIBUTING.md) for more details.
|
|
16
|
+
|
|
17
|
+
`laser-cutter` supports many flexible command line options that allow setting dimensions, stroke width, page size, layout, margins, padding (spacing between the boxes), and many more.
|
|
24
18
|
|
|
25
19
|
## Dependencies
|
|
26
20
|
|
|
27
|
-
The gem depends primarily on [Prawn](http://prawnpdf.org) – a fantastic PDF generation library.
|
|
21
|
+
The gem depends primarily on [Prawn](http://prawnpdf.org) – a fantastic PDF generation library. SVG output uses [Victor](https://github.com/DannyBen/victor), and the command line is built on [dry-cli](https://dry-cli.tools/). It needs Ruby 4.0 or newer.
|
|
28
22
|
|
|
29
23
|
## Installation
|
|
30
24
|
|
|
31
25
|
Add this line to your application's Gemfile:
|
|
32
26
|
|
|
33
|
-
|
|
27
|
+
```
|
|
28
|
+
gem 'laser-cutter'
|
|
29
|
+
```
|
|
34
30
|
|
|
35
31
|
And then execute:
|
|
36
32
|
|
|
37
|
-
|
|
33
|
+
```
|
|
34
|
+
$ bundle
|
|
35
|
+
```
|
|
38
36
|
|
|
39
|
-
Or install it
|
|
37
|
+
Or install it yourself as:
|
|
40
38
|
|
|
41
|
-
|
|
39
|
+
```
|
|
40
|
+
$ gem install laser-cutter
|
|
41
|
+
```
|
|
42
42
|
|
|
43
43
|
## Usage
|
|
44
44
|
|
|
45
|
-
|
|
45
|
+
```text
|
|
46
|
+
laser-cutter COMMAND [OPTIONS]
|
|
47
|
+
|
|
48
|
+
generate, g Draw the panels of a box into a PDF or an SVG file
|
|
49
|
+
page-sizes List every page size, with its dimensions
|
|
50
|
+
examples Show detailed usage examples
|
|
51
|
+
help Show help, for the program or for one command
|
|
52
|
+
version Print the version
|
|
53
|
+
completion Print a bash or zsh completion script
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
`laser-cutter help generate` lists every option. The common ones:
|
|
57
|
+
|
|
58
|
+
| Option | Meaning |
|
|
59
|
+
| :--------------------- | :------------------------------------------------------------- |
|
|
60
|
+
| `-b`, `--box` | `WxHxD/T[/N]`: width, height, depth, thickness, optional notch |
|
|
61
|
+
| `-w`, `-H`, `-d`, `-t` | Width, height, depth and thickness, one at a time |
|
|
62
|
+
| `-n`, `--notch` | Notch length, a guide only |
|
|
63
|
+
| `-k`, `--kerf` | Kerf, the width of the cut |
|
|
64
|
+
| `-L`, `--lid` | `full` (default), `back` or `plain`, see below |
|
|
65
|
+
| `-u`, `--units` | `in` (default) or `mm` |
|
|
66
|
+
| `-o`, `--file` | File to write, required |
|
|
67
|
+
| `-f`, `--format` | `pdf` (default) or `svg`, in either case |
|
|
68
|
+
| `-B`, `--inside-box` | Also draw the box without kerf, in red |
|
|
69
|
+
| `-W`, `-R` | Save the configuration to a file, or read it from one |
|
|
70
|
+
|
|
71
|
+
Height is `-H`, because `-h` prints help.
|
|
46
72
|
|
|
47
73
|
### Examples
|
|
48
74
|
|
|
49
|
-
|
|
75
|
+
A box in inches, with the kerf set to 0.008", opened once it is written:
|
|
50
76
|
|
|
51
77
|
```bash
|
|
52
|
-
|
|
53
|
-
```
|
|
78
|
+
laser-cutter generate -b 3x2x2/0.125 -k 0.008 -O -o box.pdf
|
|
79
|
+
```
|
|
54
80
|
|
|
55
|
-
|
|
81
|
+
A box with a lid that lifts off:
|
|
56
82
|
|
|
57
83
|
```bash
|
|
58
|
-
|
|
59
|
-
```
|
|
84
|
+
laser-cutter generate -b 3x2x2/0.125 --lid plain -o box.pdf
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
### The lid
|
|
88
|
+
|
|
89
|
+
The lid is the top panel. `--lid` sets how it joins the walls:
|
|
90
|
+
|
|
91
|
+
| `--lid` | The lid | The walls under it |
|
|
92
|
+
| :------ | :----------------------------------- | :------------------------------------------------ |
|
|
93
|
+
| `full` | Notched on all four sides | Notched; the box is glued shut |
|
|
94
|
+
| `back` | Notched where it meets the back wall | The back is notched, the other three are straight |
|
|
95
|
+
| `plain` | A rectangle, no notches | All four are straight |
|
|
60
96
|
|
|
61
|
-
|
|
97
|
+
| `--lid full` | `--lid back` | `--lid plain` |
|
|
98
|
+
| :------------------------------------------------------ | :--------------------------------------------------------------------- | :-------------------------------------------------------- |
|
|
99
|
+
|  |  |  |
|
|
100
|
+
|
|
101
|
+
Each is `laser-cutter generate -b 3x2x2/0.125 --lid …`. The lid is the panel at the bottom of the page, the back wall the one at the top.
|
|
102
|
+
|
|
103
|
+
A lid edge without notches reaches the outside of the wall under it, and that wall ends at the internal height. So a `plain` lid is `W + 2T` by `D + 2T` and lies on top of the box, and the space inside is still `W` by `H` by `D`.
|
|
104
|
+
|
|
105
|
+
The same box as an SVG:
|
|
62
106
|
|
|
63
107
|
```bash
|
|
64
|
-
|
|
65
|
-
```
|
|
108
|
+
laser-cutter generate -b 3x2x2/0.125 -f svg -o box.svg
|
|
109
|
+
```
|
|
66
110
|
|
|
67
|
-
|
|
111
|
+
A box in millimeters on a landscape A3 page, with a 0.5mm stroke:
|
|
68
112
|
|
|
69
113
|
```bash
|
|
70
|
-
|
|
71
|
-
```
|
|
114
|
+
laser-cutter generate -u mm -w 70 -H 20 -d 50 -t 4.3 -n 5 -i A3 -l landscape -s 0.5 -o box.pdf
|
|
115
|
+
```
|
|
72
116
|
|
|
73
|
-
|
|
117
|
+
Every page size, in millimeters:
|
|
74
118
|
|
|
75
119
|
```bash
|
|
76
|
-
|
|
77
|
-
cat box-settings.json | laser-cutter -O -o box.pdf -R -
|
|
120
|
+
laser-cutter page-sizes -u mm
|
|
78
121
|
```
|
|
79
122
|
|
|
80
|
-
|
|
123
|
+
Save the settings of a box, and use them again:
|
|
81
124
|
|
|
82
125
|
```bash
|
|
126
|
+
laser-cutter generate -b 1.1x2.5x1.5/0.125/0.125 -p 0.1 -o box.pdf -W box-settings.json
|
|
127
|
+
laser-cutter generate -o box.pdf -R box-settings.json
|
|
128
|
+
cat box-settings.json | laser-cutter generate -o box.pdf -R -
|
|
129
|
+
```
|
|
83
130
|
|
|
84
|
-
|
|
85
|
-
eg: laser-cutter -z 1x1.5x2/0.125 -O -o box.pdf
|
|
131
|
+
### More boxes
|
|
86
132
|
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
-n, --notch NOTCH Optional notch length (aka "tab width"), guide only
|
|
93
|
-
-k, --kerf KERF Kerf - cut width (default is 0.0024in)
|
|
133
|
+
A shallow box in millimeters, glued shut:
|
|
134
|
+
|
|
135
|
+
```bash
|
|
136
|
+
laser-cutter generate -u mm -w 70 -H 20 -d 50 -t 4.3 -n 5 -f svg -o box.svg
|
|
137
|
+
```
|
|
94
138
|
|
|
95
|
-
|
|
96
|
-
-p, --padding PADDING Space between the boxes on the page
|
|
97
|
-
-s, --stroke WIDTH Numeric stroke width of the line
|
|
98
|
-
-i, --page_size LETTER Document page size, default is autofit the box.
|
|
99
|
-
-l, --page_layout portrait Page layout, other option is 'landscape'
|
|
139
|
+

|
|
100
140
|
|
|
101
|
-
|
|
102
|
-
-W, --write CONFIG_FILE Save provided configuration to a file, use '-' for STDOUT
|
|
103
|
-
-R, --read CONFIG_FILE Read configuration from a file, or use '-' for STDIN
|
|
141
|
+
A tray with a lid that lifts off, from 3mm material with wide notches:
|
|
104
142
|
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
-B, --inside-box Draw the inside boxes (helpful to verify kerfing)
|
|
109
|
-
-D, --debug Show full exception stack trace on error
|
|
143
|
+
```bash
|
|
144
|
+
laser-cutter generate -u mm -w 120 -H 25 -d 80 -t 3 -n 12 --lid plain -f svg -o tray.svg
|
|
145
|
+
```
|
|
110
146
|
|
|
111
|
-
|
|
112
|
-
--help Show this message
|
|
113
|
-
--version Show version
|
|
147
|
+

|
|
114
148
|
|
|
115
|
-
|
|
116
|
-
-o, --file FILE Required output filename of the PDF
|
|
117
|
-
-z, --size WxHxD/T[/N] Combined internal dimensions: W = width, H = height,
|
|
118
|
-
D = depth, T = thickness, and optional N = notch length
|
|
149
|
+
The pictures on this page are the SVG files themselves, drawn with a thicker stroke (`-s 0.5`) and converted:
|
|
119
150
|
|
|
120
|
-
|
|
151
|
+
```bash
|
|
152
|
+
magick -density 96 -background white box.svg -flatten box.avif
|
|
121
153
|
```
|
|
122
154
|
|
|
123
|
-
##
|
|
155
|
+
## Using it from Ruby
|
|
124
156
|
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
157
|
+
A Ruby program draws a box in its own process; nothing runs the command line. It needs three things:
|
|
158
|
+
|
|
159
|
+
| Call | What it does |
|
|
160
|
+
| :------------------------------ | :------------------------------------------------ |
|
|
161
|
+
| `Laser::Cutter::Options.new` | Every setting of a box, typed and checked |
|
|
162
|
+
| `Laser::Cutter.render(options)` | Returns the PDF or the SVG as a String |
|
|
163
|
+
| `Laser::Cutter.write(options)` | Writes it to `options.file`, and returns the path |
|
|
164
|
+
|
|
165
|
+
```ruby
|
|
166
|
+
require "laser-cutter"
|
|
167
|
+
|
|
168
|
+
options = Laser::Cutter::Options.new(
|
|
169
|
+
width: 70, height: 20, depth: 50, thickness: 4.3,
|
|
170
|
+
units: :mm, lid: :plain, format: :svg
|
|
171
|
+
)
|
|
172
|
+
|
|
173
|
+
svg = Laser::Cutter.render(options) # a String, no file written
|
|
174
|
+
Laser::Cutter.write(options.new(file: "box.pdf", format: nil)) # the extension picks the format
|
|
175
|
+
```
|
|
176
|
+
|
|
177
|
+
`Options` has an attribute for each option of `generate`:
|
|
178
|
+
|
|
179
|
+
| Attribute | Type | When left out |
|
|
180
|
+
| :-------------------------------------- | :---------------------- | :----------------------------- |
|
|
181
|
+
| `width`, `height`, `depth`, `thickness` | Float above zero | `MissingOption` is raised |
|
|
182
|
+
| `notch` | Float above zero | Three times the thickness |
|
|
183
|
+
| `kerf`, `margin`, `padding` | Float, zero or more | The default for the units |
|
|
184
|
+
| `stroke` | Float above zero | The default for the units |
|
|
185
|
+
| `units` | `in` or `mm` | `in` |
|
|
186
|
+
| `lid` | `full`, `back`, `plain` | `full` |
|
|
187
|
+
| `format` | `pdf` or `svg` | `pdf`, or the file's extension |
|
|
188
|
+
| `file` | String | Only `write` needs it |
|
|
189
|
+
| `page_size` | A name such as `A4` | The page fits the box |
|
|
190
|
+
| `page_layout` | `portrait`, `landscape` | `portrait` |
|
|
191
|
+
| `metadata` | Boolean | `true` |
|
|
192
|
+
| `inside_box` | Boolean | `false` |
|
|
193
|
+
|
|
194
|
+
- Values are coerced, so the Strings a web form sends will do, and a blank String counts as left out. Keys may be Strings or Symbols.
|
|
195
|
+
- A value it cannot use, or a key that is not an option, raises `Laser::Cutter::InvalidOption` with a message such as `lid cannot be "sliding", but must be one of: full, back, plain.` Both errors descend from `Laser::Cutter::Error`.
|
|
196
|
+
- An `Options` cannot be changed; `options.new(lid: :back)` returns a changed copy.
|
|
197
|
+
|
|
198
|
+
In a Rails controller:
|
|
199
|
+
|
|
200
|
+
```ruby
|
|
201
|
+
def create
|
|
202
|
+
box = params.require(:box).permit(*Laser::Cutter::Options.attribute_names)
|
|
203
|
+
send_data Laser::Cutter.render(box.to_h), type: "application/pdf", filename: "box.pdf"
|
|
204
|
+
rescue Laser::Cutter::Error => e
|
|
205
|
+
redirect_to new_box_path, alert: e.message
|
|
206
|
+
end
|
|
207
|
+
```
|
|
208
|
+
|
|
209
|
+
`Laser::Cutter::Box::LIDS` lists the lids, for a select. `Configuration`, `Renderer::LayoutRenderer#render` and `PageManager#page_size_values`, which MakeABox.io called in 1.0.3, still work.
|
|
210
|
+
|
|
211
|
+
## Feature Wish List
|
|
212
|
+
|
|
213
|
+
- Create T-style joins, using various standard sizes of nuts and bolts (such as common #4-40 and M2 sizes)
|
|
214
|
+
- Extensibility with various layout strategies, notch drawing strategies, basically plug and play model for adding new algorithms for path creation and box joining
|
|
215
|
+
- Support more shapes than just box, such as prisms
|
|
216
|
+
- Supporting lids and front panels, that are larger than the box itself and have holes for notches.
|
|
217
|
+
- Your brilliant idea can be here too! Please see [contributing](CONTRIBUTING.md) for more info.
|
|
218
|
+
|
|
219
|
+
## LaserCutter vs BoxMaker
|
|
220
|
+
|
|
221
|
+
[Rahulbot](https://github.com/rahulbot/)-made [BoxMaker](https://github.com/rahulbot/boxmaker/) is a functional generator of notched designs, similar to `laser-cutter`, and generously open sourced by the author, and so in no way this project disputes BoxMaker's viability. In fact BoxMaker was an inspiration for this project.
|
|
222
|
+
|
|
223
|
+
Laser-Cutter library attempts to further advance the concept of programmatically creating laser-cut box designs, provides additional fine tuning, many more options, strategies and most importantly – extensibility.
|
|
224
|
+
|
|
225
|
+
Unlike `BoxMaker`, this gem has a suit of automated tests (rspecs) around the core functionality. In addition, new feature contributions are highly encouraged, and in that regard having existing test suit offers confidence against regressions, and thus welcomes colaboration.
|
|
226
|
+
|
|
227
|
+
Finally, BoxMaker's notch-drawing algorithm generates non-symmetric and sometimes purely broken designs (see picture below).
|
|
228
|
+
|
|
229
|
+
`laser-cutter`'s algorithm will create a _symmetric design for most panels_, but it might sacrifice identical notch length. Depending on the box dimensions you may end up with a slightly different notch length on each side of the box.
|
|
230
|
+
|
|
231
|
+
The choice ultimately comes down to the preference and feature set, so here I show you two boxes made with each program, so you can pick what you prefer.
|
|
232
|
+
|
|
233
|
+
### Example Outputs
|
|
234
|
+
|
|
235
|
+
Below are two examples of boxes with identical dimensions produced with `laser-cutter` and `boxmaker`:
|
|
236
|
+
|
|
237
|
+
This is how you would make a box with Adam Phelp's fork of BoxMaker (which adds flags and a lot of niceties):
|
|
238
|
+
|
|
239
|
+
```bash
|
|
240
|
+
git clone https://github.com/aphelps/boxmaker && cd boxmaker && ant
|
|
241
|
+
java -cp BOX.jar com.rahulbotics.boxmaker.BoxMaker \
|
|
242
|
+
-W 1 -H 2 -D 1.5 -T 0.125 -n 0.125 -o box.pdf
|
|
243
|
+
```
|
|
244
|
+
|
|
245
|
+
And laser-cutter:
|
|
246
|
+
|
|
247
|
+
```bash
|
|
248
|
+
gem install laser-cutter
|
|
249
|
+
laser-cutter generate -b 1x1.5x2/0.125/0.125 -O -o box.pdf
|
|
250
|
+
```
|
|
251
|
+
|
|
252
|
+
.
|
|
131
253
|
|
|
132
254
|
## Contributing
|
|
133
255
|
|
|
134
|
-
1. Fork it ( https://github.com/
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
256
|
+
1. Fork it ( https://github.com/[my-github-username]/laser-cutter/fork )
|
|
257
|
+
1. Create your feature branch (`git checkout -b my-new-feature`)
|
|
258
|
+
1. Commit your changes (`git commit -am 'Add some feature'`)
|
|
259
|
+
1. Create a new Pull Request
|
|
260
|
+
1. Push to the branch (`git push origin my-new-feature`)
|
|
261
|
+
|
|
262
|
+
## License
|
|
263
|
+
|
|
264
|
+
MIT License (MIT). Please see [LICENSE](LICENSE) for more information.
|
|
265
|
+
|
|
266
|
+
Author: © 2015-2024 Konstantin Gredeskoul [@kigster](https://github.com/kigster)
|
data/Rakefile
CHANGED
|
@@ -1,29 +1,43 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
require
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "bundler/gem_tasks"
|
|
4
|
+
require "rspec/core/rake_task"
|
|
5
|
+
require "timeout"
|
|
6
|
+
require "yard"
|
|
4
7
|
|
|
5
8
|
def shell(*args)
|
|
6
9
|
puts "running: #{args.join(' ')}"
|
|
7
|
-
system(args.join(
|
|
10
|
+
system(args.join(" "))
|
|
8
11
|
end
|
|
9
12
|
|
|
10
13
|
task :clean do
|
|
11
|
-
shell(
|
|
14
|
+
shell("rm -rf pkg/ tmp/ coverage/ doc/ ")
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
task gem: [:build] do
|
|
18
|
+
shell("gem install pkg/*")
|
|
12
19
|
end
|
|
13
20
|
|
|
14
|
-
task :
|
|
15
|
-
|
|
16
|
-
|
|
21
|
+
task permissions: [:clean] do
|
|
22
|
+
# One traversal replaces a six-level glob chain that printed "No such file
|
|
23
|
+
# or directory" for every level this project does not have, skipped dotfiles
|
|
24
|
+
# entirely, and silently stopped at depth six. .git is pruned: its objects
|
|
25
|
+
# have no business being group-readable.
|
|
26
|
+
shell("find . -path ./.git -prune -o -type d -exec chmod o+rx,g+rx {} + -o -type f -exec chmod o+r,g+r {} +")
|
|
17
27
|
end
|
|
18
28
|
|
|
19
|
-
task :
|
|
29
|
+
task build: :permissions
|
|
20
30
|
|
|
21
31
|
YARD::Rake::YardocTask.new(:doc) do |t|
|
|
22
|
-
t.files = %w
|
|
23
|
-
t.options.unshift(
|
|
24
|
-
t.after = ->
|
|
32
|
+
t.files = %w[lib/**/*.rb - README.md LICENSE.txt CHANGELOG.md]
|
|
33
|
+
t.options.unshift("--title", '"dry-cli-ui: runtime terminal UI for dry-cli commands"')
|
|
34
|
+
t.after = -> { exec("open doc/index.html") } if RUBY_PLATFORM =~ /darwin/
|
|
35
|
+
|
|
36
|
+
require "fileutils"
|
|
37
|
+
FileUtils.mkdir_p("doc/docs/badges")
|
|
38
|
+
FileUtils.cp("docs/badges/coverage_badge.svg", "doc/docs/badges")
|
|
25
39
|
end
|
|
26
40
|
|
|
27
41
|
RSpec::Core::RakeTask.new(:spec)
|
|
28
42
|
|
|
29
|
-
task :
|
|
43
|
+
task default: :spec
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
<?xml version="1.0" encoding="UTF-8"?>
|
|
2
|
+
<svg xmlns="http://www.w3.org/2000/svg" width="99" height="20">
|
|
3
|
+
<linearGradient id="b" x2="0" y2="100%">
|
|
4
|
+
<stop offset="0" stop-color="#bbb" stop-opacity=".1"/>
|
|
5
|
+
<stop offset="1" stop-opacity=".1"/>
|
|
6
|
+
</linearGradient>
|
|
7
|
+
<mask id="a">
|
|
8
|
+
<rect width="99" height="20" rx="3" fill="#fff"/>
|
|
9
|
+
</mask>
|
|
10
|
+
<g mask="url(#a)">
|
|
11
|
+
<path fill="#555" d="M0 0h63v20H0z"/>
|
|
12
|
+
<path fill="#4c1" d="M63 0h36v20H63z"/>
|
|
13
|
+
<path fill="url(#b)" d="M0 0h99v20H0z"/>
|
|
14
|
+
</g>
|
|
15
|
+
<g fill="#fff" text-anchor="middle" font-family="DejaVu Sans,Verdana,Geneva,sans-serif" font-size="11">
|
|
16
|
+
<text x="31.5" y="15" fill="#010101" fill-opacity=".3">coverage</text>
|
|
17
|
+
<text x="31.5" y="14">coverage</text>
|
|
18
|
+
<text x="80" y="15" fill="#010101" fill-opacity=".3">97%</text>
|
|
19
|
+
<text x="80" y="14">97%</text>
|
|
20
|
+
</g>
|
|
21
|
+
</svg>
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
data/exe/laser-cutter
ADDED
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
#!/usr/bin/env ruby
|
|
2
|
+
# frozen_string_literal: true
|
|
3
|
+
|
|
4
|
+
# No Bundler here: a Gemfile in the user's directory must not decide which gems load.
|
|
5
|
+
$LOAD_PATH.unshift(File.expand_path('../lib', __dir__))
|
|
6
|
+
require 'laser/cutter'
|
|
7
|
+
|
|
8
|
+
Laser::Cutter::Launcher.new(ARGV).execute!
|
data/exe/lc
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
laser-cutter
|
data/justfile
ADDED
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
set shell := ["bash", "-c"]
|
|
2
|
+
|
|
3
|
+
version := `gawk -F'"' '/VERSION/ { printf "%s", $2 }' lib/laser/cutter/version.rb`
|
|
4
|
+
rbenv := 'eval "$(rbenv init - bash 2>/dev/null || true)"; bundle exec '
|
|
5
|
+
repo := 'git@github.com:kigster/laser-cutter.git'
|
|
6
|
+
|
|
7
|
+
gem_name := 'laser-cutter'
|
|
8
|
+
gem_file := 'pkg/' + gem_name + '-' + version + '.gem'
|
|
9
|
+
gem_url := 'https://rubygems.org/gems/' + gem_name
|
|
10
|
+
|
|
11
|
+
[no-exit-message]
|
|
12
|
+
recipes:
|
|
13
|
+
just --choose
|
|
14
|
+
|
|
15
|
+
# Lint Ruby
|
|
16
|
+
lint:
|
|
17
|
+
{{ rbenv }} rubocop
|
|
18
|
+
|
|
19
|
+
# Autocorrect Ruby (pass -A for unsafe corrections) and format Markdown
|
|
20
|
+
format *args: format-markdown
|
|
21
|
+
{{ rbenv }} rubocop -a {{ args }}
|
|
22
|
+
|
|
23
|
+
# Format every Markdown file
|
|
24
|
+
format-markdown:
|
|
25
|
+
fd .md -X mdformat --wrap no
|
|
26
|
+
|
|
27
|
+
# Run all the tests; a full run enforces 100% line and branch coverage
|
|
28
|
+
test *args:
|
|
29
|
+
{{ rbenv }} rspec {{ args }}
|
|
30
|
+
|
|
31
|
+
# Run all tests with --documentation
|
|
32
|
+
test-docs *args:
|
|
33
|
+
{{ rbenv }} rspec --format documentation {{ args }}
|
|
34
|
+
|
|
35
|
+
# Run tests and measure coverage even for a partial run
|
|
36
|
+
test-coverage *args:
|
|
37
|
+
export COVERAGE=true; {{ rbenv }} rspec {{ args }}
|
|
38
|
+
|
|
39
|
+
ci: lint test-coverage
|
|
40
|
+
|
|
41
|
+
alias check-all := ci
|
|
42
|
+
|
|
43
|
+
# Remove .DS_Store files and tmp/
|
|
44
|
+
clean:
|
|
45
|
+
fd --hidden --no-ignore --type file --glob .DS_Store --exec rm -v
|
|
46
|
+
rm -rf tmp coverage
|
|
47
|
+
|
|
48
|
+
# Run all lefthook pre-commit hooks against every file
|
|
49
|
+
lefthook:
|
|
50
|
+
lefthook run pre-commit --all-files
|
|
51
|
+
|
|
52
|
+
# Print current gem version
|
|
53
|
+
version:
|
|
54
|
+
@echo "{{ version }}"
|
|
55
|
+
|
|
56
|
+
# Remove every generated file
|
|
57
|
+
clobber:
|
|
58
|
+
{{ rbenv }} rake clobber
|
|
59
|
+
|
|
60
|
+
# Generate YARD documentation
|
|
61
|
+
doc:
|
|
62
|
+
{{ rbenv }} rake doc
|
|
63
|
+
|
|
64
|
+
# Builds the gem for distribution
|
|
65
|
+
build:
|
|
66
|
+
{{ rbenv }} rake build
|
|
67
|
+
|
|
68
|
+
# `gem push` rather than `rake release`: release also tags and pushes git,
|
|
69
|
+
# which `just release` does separately, and it gives no way to pass a 2FA code.
|
|
70
|
+
#
|
|
71
|
+
# just publish # gem push prompts for the code if 2FA needs one
|
|
72
|
+
# just publish 123456 # use this code
|
|
73
|
+
#
|
|
74
|
+
# Build the .gem and push it to RubyGems
|
|
75
|
+
publish otp="": build
|
|
76
|
+
#!/usr/bin/env bash
|
|
77
|
+
set -euo pipefail
|
|
78
|
+
eval "$(rbenv init - bash 2>/dev/null || true)"
|
|
79
|
+
|
|
80
|
+
otp="{{ otp }}"
|
|
81
|
+
if [[ -n "${otp}" ]]; then
|
|
82
|
+
gem push "{{ gem_file }}" --otp "${otp}"
|
|
83
|
+
else
|
|
84
|
+
gem push "{{ gem_file }}"
|
|
85
|
+
fi
|
|
86
|
+
|
|
87
|
+
# Only reachable when the push succeeded: `set -e` aborts on a failed push.
|
|
88
|
+
echo "published {{ gem_name }} {{ version }} → {{ gem_url }}"
|
|
89
|
+
open "{{ gem_url }}" 2>/dev/null || xdg-open "{{ gem_url }}" 2>/dev/null || true
|
|
90
|
+
|
|
91
|
+
# Tag v{{ version }} and publish the GitHub release
|
|
92
|
+
release:
|
|
93
|
+
git fetch --tags
|
|
94
|
+
git tag -f "v{{ version }}"
|
|
95
|
+
git push -f --tags
|
|
96
|
+
gh release delete -y "v{{ version }}" --repo {{ repo }} 2>/dev/null || true
|
|
97
|
+
gh release create "v{{ version }}" --generate-notes --repo {{ repo }}
|
data/laser-cutter.gemspec
CHANGED
|
@@ -1,30 +1,43 @@
|
|
|
1
|
-
#
|
|
2
|
-
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
lib = File.expand_path('./lib', __dir__)
|
|
3
4
|
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
|
4
|
-
|
|
5
|
+
|
|
6
|
+
require_relative 'lib/laser/cutter/version'
|
|
5
7
|
|
|
6
8
|
Gem::Specification.new do |spec|
|
|
7
9
|
spec.name = "laser-cutter"
|
|
8
10
|
spec.version = Laser::Cutter::VERSION
|
|
9
11
|
spec.authors = ['Konstantin Gredeskoul']
|
|
10
12
|
spec.email = ["kigster@gmail.com"]
|
|
11
|
-
spec.summary =
|
|
12
|
-
spec.description =
|
|
13
|
+
spec.summary = 'Creates notched box outlines for laser-cut boxes which are geometrically symmetric and pleasing to the eye.'
|
|
14
|
+
spec.description = 'Similar to the older BoxMaker, this ruby gem generates PDFs that can be used as a basis for cutting boxes on a typical laser cutter. The intention was to create an extensible, well tested, and modern ruby gem for generating PDF templates used in laser cutting.'
|
|
13
15
|
spec.homepage = "https://github.com/kigster/laser-cutter"
|
|
14
16
|
spec.license = "MIT"
|
|
15
17
|
|
|
16
18
|
spec.files = `git ls-files -z`.split("\x0")
|
|
17
|
-
spec.
|
|
18
|
-
spec.
|
|
19
|
+
spec.bindir = 'exe'
|
|
20
|
+
spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
|
|
19
21
|
spec.require_paths = ["lib"]
|
|
22
|
+
spec.required_ruby_version = '>= 4.0'
|
|
20
23
|
|
|
21
|
-
spec.add_dependency '
|
|
24
|
+
spec.add_dependency 'dry-cli', '~> 1.4.1'
|
|
25
|
+
spec.add_dependency 'dry-cli-autocomplete'
|
|
26
|
+
spec.add_dependency 'dry-cli-help'
|
|
27
|
+
spec.add_dependency 'dry-cli-ui'
|
|
28
|
+
spec.add_dependency 'dry-struct', '~> 1.6'
|
|
22
29
|
spec.add_dependency 'hashie'
|
|
23
|
-
spec.add_dependency '
|
|
30
|
+
spec.add_dependency 'matrix'
|
|
31
|
+
spec.add_dependency 'pastel'
|
|
32
|
+
spec.add_dependency 'prawn'
|
|
33
|
+
spec.add_dependency 'tty-cursor'
|
|
34
|
+
spec.add_dependency 'tty-progressbar'
|
|
35
|
+
spec.add_dependency 'tty-prompt'
|
|
36
|
+
spec.add_dependency 'tty-screen'
|
|
37
|
+
spec.add_dependency 'tty-spinner'
|
|
38
|
+
spec.add_dependency 'tty-table'
|
|
39
|
+
spec.add_dependency 'victor'
|
|
40
|
+
spec.add_dependency 'zeitwerk'
|
|
24
41
|
|
|
25
|
-
spec.
|
|
26
|
-
spec.add_development_dependency 'simplecov'
|
|
27
|
-
spec.add_development_dependency 'bundler'
|
|
28
|
-
spec.add_development_dependency 'rake'
|
|
29
|
-
spec.add_development_dependency 'rspec'
|
|
42
|
+
spec.metadata['rubygems_mfa_required'] = 'true'
|
|
30
43
|
end
|