laser-cutter 1.0.3 → 2.0.0
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 -4
- data/.plans/001.00-dry-cli-migration/plan.md +29 -0
- data/.relaxed_rubocop.yml +153 -0
- data/.rspec +1 -1
- data/.rubocop.yml +31 -0
- data/.rubocop_todo.yml +90 -0
- data/.ruby-version +1 -0
- data/CLAUDE.md +62 -0
- data/CONTRIBUTING.md +2 -4
- data/Gemfile +14 -0
- data/README.md +83 -111
- data/Rakefile +41 -0
- data/docs/badges/coverage_badge.svg +21 -0
- data/exe/laser-cutter +8 -0
- data/exe/lc +1 -0
- data/justfile +97 -0
- data/laser-cutter.gemspec +26 -14
- data/lib/laser/cutter/aggregator.rb +101 -0
- data/lib/{laser-cutter → laser/cutter}/box.rb +44 -52
- 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 +42 -0
- data/lib/laser/cutter/cli/generate.rb +90 -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 +101 -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/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 +21 -20
- data/lib/laser/cutter/notching/infinite_iterator.rb +27 -0
- data/lib/{laser-cutter → laser/cutter}/notching/path_generator.rb +27 -60
- data/lib/laser/cutter/notching/shift.rb +17 -0
- data/lib/laser/cutter/page_manager.rb +50 -0
- data/lib/{laser-cutter → laser/cutter}/renderer/base.rb +3 -2
- 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/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 +29 -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_spec.rb +61 -0
- data/spec/laser/cutter/cli/config_file_spec.rb +31 -0
- data/spec/laser/cutter/cli_spec.rb +186 -0
- data/spec/laser/cutter/configuration_spec.rb +101 -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/{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 +102 -0
- data/spec/laser/cutter/renderer/svg_renderer_spec.rb +51 -0
- data/spec/laser/cutter/renderer_spec.rb +19 -0
- data/spec/spec_helper.rb +34 -25
- data/spec/support/aruba.rb +10 -0
- metadata +206 -80
- data/.travis.yml +0 -11
- data/LICENSE +0 -22
- data/bin/laser-cutter +0 -19
- data/lib/laser-cutter/aggregator.rb +0 -57
- 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/geometry.rb +0 -11
- data/lib/laser-cutter/notching/base.rb +0 -13
- data/lib/laser-cutter/notching.rb +0 -10
- 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 -40
- data/spec/configuration_spec.rb +0 -81
- data/spec/edge_spec.rb +0 -43
- data/spec/renderer_spec.rb +0 -72
- /data/{doc → docs/images}/comparison.jpg +0 -0
data/README.md
CHANGED
|
@@ -1,169 +1,135 @@
|
|
|
1
|
-
[](http://badge.fury.io/rb/laser-cutter)
|
|
2
|
-
[](http://travis-ci.org/kigster/laser-cutter)
|
|
3
|
-
[](https://codeclimate.com/github/kigster/laser-cutter)
|
|
4
|
-
[](https://codeclimate.com/github/kigster/laser-cutter)
|
|
1
|
+
[](http://badge.fury.io/rb/laser-cutter) 
|
|
5
2
|
|
|
6
3
|
## LaserCutter and Make-A-Box.io
|
|
7
4
|
|
|
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.
|
|
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.
|
|
12
6
|
|
|
13
|
-
To use
|
|
14
|
-
install it as a gem, and use command line to generate PDFs.
|
|
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.
|
|
15
8
|
|
|
16
|
-
[Make-A-Box](http://makeabox.io) is a online web application that uses
|
|
17
|
-
and provides a straight-forward user interface for generating PDF designs without the need to install
|
|
18
|
-
the gem or use command line.
|
|
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.
|
|
19
10
|
|
|
20
11
|
Use whatever suites you better.
|
|
21
12
|
|
|
22
13
|
### Design Goals
|
|
23
14
|
|
|
24
|
-
One of the design goals of this project is to provide a highly extensible platform for creating
|
|
25
|
-
|
|
26
|
-
command line options,
|
|
27
|
-
contributing to the project, please see [contributing](CONTRIBUTING.md) for more details.
|
|
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.
|
|
28
18
|
|
|
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
19
|
## Dependencies
|
|
33
20
|
|
|
34
|
-
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.
|
|
35
22
|
|
|
36
23
|
## Installation
|
|
37
24
|
|
|
38
25
|
Add this line to your application's Gemfile:
|
|
39
26
|
|
|
40
|
-
|
|
27
|
+
```
|
|
28
|
+
gem 'laser-cutter'
|
|
29
|
+
```
|
|
41
30
|
|
|
42
31
|
And then execute:
|
|
43
32
|
|
|
44
|
-
|
|
33
|
+
```
|
|
34
|
+
$ bundle
|
|
35
|
+
```
|
|
45
36
|
|
|
46
37
|
Or install it yourself as:
|
|
47
38
|
|
|
48
|
-
|
|
39
|
+
```
|
|
40
|
+
$ gem install laser-cutter
|
|
41
|
+
```
|
|
49
42
|
|
|
50
43
|
## Usage
|
|
51
44
|
|
|
52
|
-
```
|
|
45
|
+
```text
|
|
46
|
+
laser-cutter COMMAND [OPTIONS]
|
|
53
47
|
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
-d, --depth DEPTH Internal depth of the box
|
|
61
|
-
-t, --thickness THICKNESS Thickness of the box material
|
|
62
|
-
-n, --notch NOTCH Optional notch length (aka "tab width"), guide only
|
|
63
|
-
-k, --kerf KERF Kerf - cut width (default is 0.007in)
|
|
64
|
-
|
|
65
|
-
-m, --margin MARGIN Margins from the edge of the document
|
|
66
|
-
-p, --padding PADDING Space between the boxes on the page
|
|
67
|
-
-s, --stroke WIDTH Numeric stroke width of the line
|
|
68
|
-
-i, --page_size LETTER Document page size, default is autofit the box.
|
|
69
|
-
-l, --page_layout portrait Page layout, other option is 'landscape'
|
|
70
|
-
|
|
71
|
-
-O, --open Open generated file with system viewer before exiting
|
|
72
|
-
-W, --write CONFIG_FILE Save provided configuration to a file, use '-' for STDOUT
|
|
73
|
-
-R, --read CONFIG_FILE Read configuration from a file, or use '-' for STDIN
|
|
74
|
-
|
|
75
|
-
-L, --list-all-page-sizes Print all available page sizes with dimensions and exit
|
|
76
|
-
-M, --no-metadata Do not print box metadata on the PDF
|
|
77
|
-
-v, --[no-]verbose Run verbosely
|
|
78
|
-
-B, --inside-box Draw the inside boxes (helpful to verify kerfing)
|
|
79
|
-
-D, --debug Show full exception stack trace on error
|
|
80
|
-
|
|
81
|
-
--examples Show detailed usage examples
|
|
82
|
-
--help Show this message
|
|
83
|
-
--version Show version
|
|
84
|
-
|
|
85
|
-
Common Options:
|
|
86
|
-
-o, --file FILE Required output filename of the PDF
|
|
87
|
-
-z, --size WxHxD/T[/N] Combined internal dimensions: W = width, H = height,
|
|
88
|
-
D = depth, T = thickness, and optional N = notch length
|
|
89
|
-
|
|
90
|
-
-u, --units UNITS Either 'in' for inches (default) or 'mm'
|
|
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
|
|
91
54
|
```
|
|
92
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
|
+
| `-u`, `--units` | `in` (default) or `mm` |
|
|
65
|
+
| `-o`, `--file` | File to write, required |
|
|
66
|
+
| `-f`, `--format` | `pdf` (default) or `svg`, in either case |
|
|
67
|
+
| `-B`, `--inside-box` | Also draw the box without kerf, in red |
|
|
68
|
+
| `-W`, `-R` | Save the configuration to a file, or read it from one |
|
|
69
|
+
|
|
70
|
+
Height is `-H`, because `-h` prints help.
|
|
71
|
+
|
|
93
72
|
### Examples
|
|
94
73
|
|
|
95
|
-
|
|
74
|
+
A box in inches, with the kerf set to 0.008", opened once it is written:
|
|
96
75
|
|
|
97
76
|
```bash
|
|
98
|
-
|
|
99
|
-
```
|
|
77
|
+
laser-cutter generate -b 3x2x2/0.125 -k 0.008 -O -o box.pdf
|
|
78
|
+
```
|
|
100
79
|
|
|
101
|
-
|
|
102
|
-
page size to A3, and layout to landscape, and stroke width to 1/2mm:
|
|
80
|
+
The same box as an SVG:
|
|
103
81
|
|
|
104
82
|
```bash
|
|
105
|
-
|
|
106
|
-
```
|
|
83
|
+
laser-cutter generate -b 3x2x2/0.125 -f svg -o box.svg
|
|
84
|
+
```
|
|
107
85
|
|
|
108
|
-
|
|
86
|
+
A box in millimeters on a landscape A3 page, with a 0.5mm stroke:
|
|
109
87
|
|
|
110
88
|
```bash
|
|
111
|
-
|
|
112
|
-
```
|
|
89
|
+
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
|
|
90
|
+
```
|
|
113
91
|
|
|
114
|
-
|
|
92
|
+
Every page size, in millimeters:
|
|
115
93
|
|
|
116
94
|
```bash
|
|
117
|
-
|
|
118
|
-
```
|
|
95
|
+
laser-cutter page-sizes -u mm
|
|
96
|
+
```
|
|
119
97
|
|
|
120
|
-
|
|
98
|
+
Save the settings of a box, and use them again:
|
|
121
99
|
|
|
122
100
|
```bash
|
|
123
|
-
|
|
124
|
-
|
|
101
|
+
laser-cutter generate -b 1.1x2.5x1.5/0.125/0.125 -p 0.1 -o box.pdf -W box-settings.json
|
|
102
|
+
laser-cutter generate -o box.pdf -R box-settings.json
|
|
103
|
+
cat box-settings.json | laser-cutter generate -o box.pdf -R -
|
|
125
104
|
```
|
|
126
105
|
|
|
127
106
|
## Feature Wish List
|
|
128
107
|
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
* Your brilliant idea can be here too! Please see [contributing](CONTRIBUTING.md) for more info.
|
|
108
|
+
- Create T-style joins, using various standard sizes of nuts and bolts (such as common #4-40 and M2 sizes)
|
|
109
|
+
- Extensibility with various layout strategies, notch drawing strategies, basically plug and play model for adding new algorithms for path creation and box joining
|
|
110
|
+
- Support more shapes than just box, such as prisms
|
|
111
|
+
- Supporting lids and front panels, that are larger than the box itself and have holes for notches.
|
|
112
|
+
- Your brilliant idea can be here too! Please see [contributing](CONTRIBUTING.md) for more info.
|
|
135
113
|
|
|
136
114
|
## LaserCutter vs BoxMaker
|
|
137
115
|
|
|
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.
|
|
116
|
+
[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.
|
|
142
117
|
|
|
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.
|
|
118
|
+
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.
|
|
146
119
|
|
|
147
|
-
Unlike
|
|
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.
|
|
120
|
+
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.
|
|
150
121
|
|
|
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.
|
|
122
|
+
Finally, BoxMaker's notch-drawing algorithm generates non-symmetric and sometimes purely broken designs (see picture below).
|
|
157
123
|
|
|
158
|
-
|
|
159
|
-
|
|
124
|
+
`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.
|
|
125
|
+
|
|
126
|
+
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.
|
|
160
127
|
|
|
161
128
|
### Example Outputs
|
|
162
129
|
|
|
163
|
-
Below are two examples of boxes with identical dimensions produced with
|
|
130
|
+
Below are two examples of boxes with identical dimensions produced with `laser-cutter` and `boxmaker`:
|
|
164
131
|
|
|
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):
|
|
132
|
+
This is how you would make a box with Adam Phelp's fork of BoxMaker (which adds flags and a lot of niceties):
|
|
167
133
|
|
|
168
134
|
```bash
|
|
169
135
|
git clone https://github.com/aphelps/boxmaker && cd boxmaker && ant
|
|
@@ -175,15 +141,21 @@ And laser-cutter:
|
|
|
175
141
|
|
|
176
142
|
```bash
|
|
177
143
|
gem install laser-cutter
|
|
178
|
-
laser-cutter -
|
|
144
|
+
laser-cutter generate -b 1x1.5x2/0.125/0.125 -O -o box.pdf
|
|
179
145
|
```
|
|
180
146
|
|
|
181
|
-
.
|
|
182
148
|
|
|
183
149
|
## Contributing
|
|
184
150
|
|
|
185
151
|
1. Fork it ( https://github.com/[my-github-username]/laser-cutter/fork )
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
152
|
+
1. Create your feature branch (`git checkout -b my-new-feature`)
|
|
153
|
+
1. Commit your changes (`git commit -am 'Add some feature'`)
|
|
154
|
+
1. Create a new Pull Request
|
|
155
|
+
1. Push to the branch (`git push origin my-new-feature`)
|
|
156
|
+
|
|
157
|
+
## License
|
|
158
|
+
|
|
159
|
+
MIT License (MIT). Please see [LICENSE](LICENSE) for more information.
|
|
160
|
+
|
|
161
|
+
Author: © 2015-2024 Konstantin Gredeskoul [@kigster](https://github.com/kigster)
|
data/Rakefile
CHANGED
|
@@ -1,2 +1,43 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
1
3
|
require "bundler/gem_tasks"
|
|
4
|
+
require "rspec/core/rake_task"
|
|
5
|
+
require "timeout"
|
|
6
|
+
require "yard"
|
|
7
|
+
|
|
8
|
+
def shell(*args)
|
|
9
|
+
puts "running: #{args.join(' ')}"
|
|
10
|
+
system(args.join(" "))
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
task :clean do
|
|
14
|
+
shell("rm -rf pkg/ tmp/ coverage/ doc/ ")
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
task gem: [:build] do
|
|
18
|
+
shell("gem install pkg/*")
|
|
19
|
+
end
|
|
20
|
+
|
|
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 {} +")
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
task build: :permissions
|
|
30
|
+
|
|
31
|
+
YARD::Rake::YardocTask.new(:doc) do |t|
|
|
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")
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
RSpec::Core::RakeTask.new(:spec)
|
|
2
42
|
|
|
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>
|
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,42 @@
|
|
|
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'
|
|
22
28
|
spec.add_dependency 'hashie'
|
|
23
|
-
spec.add_dependency '
|
|
29
|
+
spec.add_dependency 'matrix'
|
|
30
|
+
spec.add_dependency 'pastel'
|
|
31
|
+
spec.add_dependency 'prawn'
|
|
32
|
+
spec.add_dependency 'tty-cursor'
|
|
33
|
+
spec.add_dependency 'tty-progressbar'
|
|
34
|
+
spec.add_dependency 'tty-prompt'
|
|
35
|
+
spec.add_dependency 'tty-screen'
|
|
36
|
+
spec.add_dependency 'tty-spinner'
|
|
37
|
+
spec.add_dependency 'tty-table'
|
|
38
|
+
spec.add_dependency 'victor'
|
|
39
|
+
spec.add_dependency 'zeitwerk'
|
|
24
40
|
|
|
25
|
-
spec.
|
|
26
|
-
spec.add_development_dependency 'rake'
|
|
27
|
-
spec.add_development_dependency 'rspec'
|
|
28
|
-
spec.add_development_dependency 'rspec-legacy_formatters'
|
|
29
|
-
spec.add_development_dependency 'codeclimate-test-reporter'
|
|
41
|
+
spec.metadata['rubygems_mfa_required'] = 'true'
|
|
30
42
|
end
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Laser
|
|
4
|
+
module Cutter
|
|
5
|
+
# Merges the lines of one face into the outline to cut.
|
|
6
|
+
#
|
|
7
|
+
# Each edge draws its own notch path and its own corner boxes, so where
|
|
8
|
+
# two of them meet the same stretch is drawn twice, once from each side.
|
|
9
|
+
# That stretch runs through the material and must not be cut. So every
|
|
10
|
+
# collinear group of lines is combined as a symmetric difference: a point
|
|
11
|
+
# is cut when an odd number of lines cover it. Two identical lines cancel,
|
|
12
|
+
# two overlapping lines keep only the parts they do not share, and lines
|
|
13
|
+
# that touch or overlap end to end join into one.
|
|
14
|
+
#
|
|
15
|
+
# Lines are grouped by axis and offset, sorted, and swept once, so a face
|
|
16
|
+
# of n lines takes n log n. Lines on neither axis pass through untouched.
|
|
17
|
+
class Aggregator
|
|
18
|
+
# Coordinates this close together count as the same.
|
|
19
|
+
TOLERANCE = Math.sqrt(Geometry::Tuple::PRECISION)
|
|
20
|
+
|
|
21
|
+
# @return [Array<Geometry::Line>] the outline, sorted
|
|
22
|
+
attr_reader :lines
|
|
23
|
+
|
|
24
|
+
# @param lines [Array<Geometry::Line>]
|
|
25
|
+
def initialize(lines = [])
|
|
26
|
+
@lines = (sweep(lines, 0) + sweep(lines, 1) + skewed(lines)).sort
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
private
|
|
30
|
+
|
|
31
|
+
# Lines running along one axis: 0 for horizontal (constant y), 1 for vertical (constant x).
|
|
32
|
+
#
|
|
33
|
+
# @return [Array<Geometry::Line>]
|
|
34
|
+
def sweep(lines, axis)
|
|
35
|
+
across = (axis + 1) % 2
|
|
36
|
+
along = lines.select { |line| along?(line, axis) }
|
|
37
|
+
groups(along, across).flat_map { |offset, group| cut_intervals(group, axis, offset, across) }
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
def skewed(lines)
|
|
41
|
+
lines.reject { |line| along?(line, 0) || along?(line, 1) }
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
def along?(line, axis)
|
|
45
|
+
across = (axis + 1) % 2
|
|
46
|
+
(line.p1[across] - line.p2[across]).abs <= TOLERANCE && (line.p1[axis] - line.p2[axis]).abs > TOLERANCE
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
# Lines on the same row or column, keyed by the offset of the first one seen.
|
|
50
|
+
def groups(lines, across)
|
|
51
|
+
lines.sort_by { |line| line.p1[across] }.slice_when { |a, b| b.p1[across] - a.p1[across] > TOLERANCE }
|
|
52
|
+
.to_h { |group| [group.first.p1[across], group] }
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
# Sweeps the endpoints of one collinear group, keeping the stretches an
|
|
56
|
+
# odd number of lines cover.
|
|
57
|
+
def cut_intervals(group, axis, offset, across)
|
|
58
|
+
events = group.flat_map do |line|
|
|
59
|
+
from, to = [line.p1[axis], line.p2[axis]].sort
|
|
60
|
+
[[from, 1], [to, -1]]
|
|
61
|
+
end
|
|
62
|
+
events.sort_by! { |position, step| [position, step] }
|
|
63
|
+
|
|
64
|
+
intervals = []
|
|
65
|
+
covered = 0
|
|
66
|
+
started = nil
|
|
67
|
+
events.each do |position, step|
|
|
68
|
+
was = covered
|
|
69
|
+
covered += step
|
|
70
|
+
if was.even? && covered.odd?
|
|
71
|
+
started = position
|
|
72
|
+
elsif was.odd? && covered.even?
|
|
73
|
+
extend_or_add(intervals, started, position)
|
|
74
|
+
end
|
|
75
|
+
end
|
|
76
|
+
intervals.map { |from, to| line_between(from, to, axis, offset, across) }
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
# Joins an interval onto the last one when they touch, and drops one with no length.
|
|
80
|
+
def extend_or_add(intervals, from, to)
|
|
81
|
+
return if to - from <= TOLERANCE
|
|
82
|
+
|
|
83
|
+
last = intervals.last
|
|
84
|
+
if last && from - last[1] <= TOLERANCE
|
|
85
|
+
last[1] = to
|
|
86
|
+
else
|
|
87
|
+
intervals << [from, to]
|
|
88
|
+
end
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
def line_between(from, to, axis, offset, across)
|
|
92
|
+
a = []
|
|
93
|
+
b = []
|
|
94
|
+
a[axis] = from
|
|
95
|
+
b[axis] = to
|
|
96
|
+
a[across] = b[across] = offset
|
|
97
|
+
Geometry::Line.new(Geometry::Point[*a], Geometry::Point[*b])
|
|
98
|
+
end
|
|
99
|
+
end
|
|
100
|
+
end
|
|
101
|
+
end
|