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.
Files changed (119) hide show
  1. checksums.yaml +5 -5
  2. data/.envrc +2 -0
  3. data/.github/workflows/lint.yml +22 -0
  4. data/.github/workflows/rspec.yml +22 -0
  5. data/.gitignore +5 -5
  6. data/.plans/001.00-dry-cli-migration/plan.md +29 -0
  7. data/.plans/002.00-lid-options/plan.md +45 -0
  8. data/.plans/003.00-ruby-api/plan.md +37 -0
  9. data/.relaxed_rubocop.yml +153 -0
  10. data/.rspec +1 -0
  11. data/.rubocop.yml +31 -0
  12. data/.rubocop_todo.yml +90 -0
  13. data/.ruby-version +1 -0
  14. data/CLAUDE.md +75 -0
  15. data/CONTRIBUTING.md +2 -4
  16. data/Gemfile +14 -0
  17. data/README.md +207 -79
  18. data/Rakefile +27 -13
  19. data/docs/badges/coverage_badge.svg +21 -0
  20. data/docs/images/box-lid-back.avif +0 -0
  21. data/docs/images/box-lid-full.avif +0 -0
  22. data/docs/images/box-lid-plain.avif +0 -0
  23. data/docs/images/box-metric.avif +0 -0
  24. data/docs/images/box-tray.avif +0 -0
  25. data/exe/laser-cutter +8 -0
  26. data/exe/lc +1 -0
  27. data/justfile +97 -0
  28. data/laser-cutter.gemspec +27 -14
  29. data/lib/laser/cutter/aggregator.rb +101 -0
  30. data/lib/laser/cutter/box.rb +251 -0
  31. data/lib/laser/cutter/cli/command.rb +46 -0
  32. data/lib/laser/cutter/cli/completion.rb +21 -0
  33. data/lib/laser/cutter/cli/config_file.rb +36 -0
  34. data/lib/laser/cutter/cli/examples.rb +47 -0
  35. data/lib/laser/cutter/cli/generate.rb +95 -0
  36. data/lib/laser/cutter/cli/help.rb +20 -0
  37. data/lib/laser/cutter/cli/page_sizes.rb +19 -0
  38. data/lib/laser/cutter/cli/version.rb +15 -0
  39. data/lib/laser/cutter/cli.rb +49 -0
  40. data/lib/laser/cutter/configuration.rb +107 -0
  41. data/lib/{laser-cutter → laser/cutter}/geometry/dimensions.rb +2 -3
  42. data/lib/{laser-cutter/geometry/shape → laser/cutter/geometry}/line.rb +19 -18
  43. data/lib/{laser-cutter → laser/cutter}/geometry/point.rb +4 -2
  44. data/lib/{laser-cutter/geometry/shape → laser/cutter/geometry}/rect.rb +6 -7
  45. data/lib/{laser-cutter → laser/cutter}/geometry/shape.rb +7 -7
  46. data/lib/{laser-cutter → laser/cutter}/geometry/tuple.rb +40 -36
  47. data/lib/laser/cutter/invalid_option.rb +8 -0
  48. data/lib/laser/cutter/launcher.rb +40 -0
  49. data/lib/laser/cutter/missing_option.rb +7 -0
  50. data/lib/laser/cutter/notching/base.rb +19 -0
  51. data/lib/{laser-cutter → laser/cutter}/notching/edge.rb +36 -20
  52. data/lib/laser/cutter/notching/infinite_iterator.rb +27 -0
  53. data/lib/laser/cutter/notching/path_generator.rb +187 -0
  54. data/lib/laser/cutter/notching/shift.rb +17 -0
  55. data/lib/laser/cutter/options.rb +154 -0
  56. data/lib/laser/cutter/page_manager.rb +50 -0
  57. data/lib/{laser-cutter → laser/cutter}/renderer/base.rb +10 -3
  58. data/lib/laser/cutter/renderer/box_renderer.rb +40 -0
  59. data/lib/laser/cutter/renderer/layout_renderer.rb +61 -0
  60. data/lib/laser/cutter/renderer/line_renderer.rb +21 -0
  61. data/lib/laser/cutter/renderer/meta_renderer.rb +83 -0
  62. data/lib/{laser-cutter → laser/cutter}/renderer/rect_renderer.rb +3 -1
  63. data/lib/laser/cutter/renderer/svg_renderer.rb +65 -0
  64. data/lib/laser/cutter/renderer.rb +24 -0
  65. data/lib/laser/cutter/types.rb +57 -0
  66. data/lib/laser/cutter/units_converter.rb +15 -0
  67. data/lib/laser/cutter/version.rb +7 -0
  68. data/lib/laser/cutter/zero_value_not_allowed.rb +7 -0
  69. data/lib/laser/cutter.rb +67 -0
  70. data/lib/laser-cutter.rb +2 -14
  71. data/lib/laser_cutter.rb +3 -0
  72. data/spec/laser/cutter/aggregator_spec.rb +70 -0
  73. data/spec/laser/cutter/box_lid_spec.rb +142 -0
  74. data/spec/laser/cutter/box_spec.rb +61 -0
  75. data/spec/laser/cutter/cli/config_file_spec.rb +31 -0
  76. data/spec/laser/cutter/cli_spec.rb +213 -0
  77. data/spec/laser/cutter/configuration_spec.rb +118 -0
  78. data/spec/{dimensions_spec.rb → laser/cutter/dimensions_spec.rb} +8 -6
  79. data/spec/laser/cutter/edge_spec.rb +64 -0
  80. data/spec/{line_spec.rb → laser/cutter/line_spec.rb} +26 -20
  81. data/spec/laser/cutter/options_spec.rb +120 -0
  82. data/spec/{page_manager_spec.rb → laser/cutter/page_manager_spec.rb} +27 -11
  83. data/spec/{path_generator_spec.rb → laser/cutter/path_generator_spec.rb} +18 -14
  84. data/spec/{point_spec.rb → laser/cutter/point_spec.rb} +27 -22
  85. data/spec/{rect_spec.rb → laser/cutter/rect_spec.rb} +9 -5
  86. data/spec/laser/cutter/renderer/layout_renderer_spec.rb +85 -0
  87. data/spec/laser/cutter/renderer/svg_renderer_spec.rb +51 -0
  88. data/spec/laser/cutter/renderer_spec.rb +19 -0
  89. data/spec/laser/cutter_spec.rb +114 -0
  90. data/spec/spec_helper.rb +35 -22
  91. data/spec/support/aruba.rb +10 -0
  92. data/spec/support/outline.rb +53 -0
  93. metadata +230 -76
  94. data/.travis.yml +0 -26
  95. data/BOXMAKER.md +0 -51
  96. data/LICENSE +0 -22
  97. data/bin/laser-cutter +0 -19
  98. data/lib/laser-cutter/aggregator.rb +0 -57
  99. data/lib/laser-cutter/box.rb +0 -183
  100. data/lib/laser-cutter/cli/opt_parser.rb +0 -131
  101. data/lib/laser-cutter/cli/serializer.rb +0 -51
  102. data/lib/laser-cutter/configuration.rb +0 -83
  103. data/lib/laser-cutter/face.rb +0 -18
  104. data/lib/laser-cutter/geometry.rb +0 -11
  105. data/lib/laser-cutter/notching/path_generator.rb +0 -234
  106. data/lib/laser-cutter/notching.rb +0 -9
  107. data/lib/laser-cutter/page_manager.rb +0 -43
  108. data/lib/laser-cutter/renderer/box_renderer.rb +0 -36
  109. data/lib/laser-cutter/renderer/layout_renderer.rb +0 -59
  110. data/lib/laser-cutter/renderer/line_renderer.rb +0 -17
  111. data/lib/laser-cutter/renderer/meta_renderer.rb +0 -77
  112. data/lib/laser-cutter/renderer.rb +0 -14
  113. data/lib/laser-cutter/version.rb +0 -5
  114. data/spec/aggregator_spec.rb +0 -65
  115. data/spec/box_spec.rb +0 -39
  116. data/spec/configuration_spec.rb +0 -81
  117. data/spec/edge_spec.rb +0 -43
  118. data/spec/renderer_spec.rb +0 -72
  119. /data/docs/{comparison.jpg → images/comparison.jpg} +0 -0
data/README.md CHANGED
@@ -1,138 +1,266 @@
1
- [![Gem Version](https://badge.fury.io/rb/laser-cutter.svg)](http://badge.fury.io/rb/laser-cutter)
2
- [![Build status](https://secure.travis-ci.org/kigster/laser-cutter.png)](http://travis-ci.org/kigster/laser-cutter)
3
- [![Maintainability](https://api.codeclimate.com/v1/badges/bea3225fd93ee84d078a/maintainability)](https://codeclimate.com/github/kigster/laser-cutter/maintainability)[![Test Coverage](https://api.codeclimate.com/v1/badges/bea3225fd93ee84d078a/test_coverage)](https://codeclimate.com/github/kigster/laser-cutter/test_coverage)
1
+ [![Gem Version](https://badge.fury.io/rb/laser-cutter.svg)](http://badge.fury.io/rb/laser-cutter) ![coverage](docs/badges/coverage_badge.svg)
4
2
 
5
- [![Join the chat at https://gitter.im/kigster/laser-cutter](https://badges.gitter.im/kigster/laser-cutter.svg)](https://gitter.im/kigster/laser-cutter?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge)
6
- [![GitHub issues](https://img.shields.io/github/issues/kigster/laser-cutter.svg)](https://github.com/kigster/laser-cutter/issues)
7
- [![GitHub forks](https://img.shields.io/github/forks/kigster/laser-cutter.svg)](https://github.com/kigster/laser-cutter/network)
8
- [![GitHub stars](https://img.shields.io/github/stars/kigster/laser-cutter.svg)](https://github.com/kigster/laser-cutter/stargazers)
9
- [![GitHub license](https://img.shields.io/github/license/kigster/laser-cutter.svg)](https://github.com/kigster/laser-cutter/blob/master/LICENSE)
3
+ ## LaserCutter and Make-A-Box.io
10
4
 
11
- [![Donate](https://www.paypalobjects.com/en_US/i/btn/btn_donate_SM.gif)](https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=FSFYYNEQ8RKWU)
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
- [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.
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
- > NOTE: Please read our [feature comparison guide](BOXMAKER.md) of LaserCutter against an older tool called [BoxMaker](https://github.com/rahulbot/boxmaker).
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
- gem 'laser-cutter'
27
+ ```
28
+ gem 'laser-cutter'
29
+ ```
34
30
 
35
31
  And then execute:
36
32
 
37
- $ bundle
33
+ ```
34
+ $ bundle
35
+ ```
38
36
 
39
- Or install it manually:
37
+ Or install it yourself as:
40
38
 
41
- $ gem install laser-cutter
39
+ ```
40
+ $ gem install laser-cutter
41
+ ```
42
42
 
43
43
  ## Usage
44
44
 
45
- We'll start with some examples:
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
- Create a box defined in inches, with kerf (cut width) set to `0.005in`, and open PDF in preview right after:
75
+ A box in inches, with the kerf set to 0.008", opened once it is written:
50
76
 
51
77
  ```bash
52
- laser-cutter -z 3x2x2/0.125 -k 0.005 -O -o box.pdf
53
- ```
78
+ laser-cutter generate -b 3x2x2/0.125 -k 0.008 -O -o box.pdf
79
+ ```
54
80
 
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`:
81
+ A box with a lid that lifts off:
56
82
 
57
83
  ```bash
58
- laser-cutter -u mm -w70 -h20 -d50 -t4.3 -n5 -iA3 -l landscape -s0.5 -v -O -o box.pdf
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
- List all possible page sizes in metric system:
97
+ | `--lid full` | `--lid back` | `--lid plain` |
98
+ | :------------------------------------------------------ | :--------------------------------------------------------------------- | :-------------------------------------------------------- |
99
+ | ![A box with a full lid](docs/images/box-lid-full.avif) | ![A box with a lid notched at the back](docs/images/box-lid-back.avif) | ![A box with a plain lid](docs/images/box-lid-plain.avif) |
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
- laser-cutter -L -u mm
65
- ```
108
+ laser-cutter generate -b 3x2x2/0.125 -f svg -o box.svg
109
+ ```
66
110
 
67
- Create a box with provided dimensions, and save the config to a file for later use:
111
+ A box in millimeters on a landscape A3 page, with a 0.5mm stroke:
68
112
 
69
113
  ```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
- ```
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
- Read settings from a previously saved file:
117
+ Every page size, in millimeters:
74
118
 
75
119
  ```bash
76
- laser-cutter -O -o box.pdf -R box-settings.json
77
- cat box-settings.json | laser-cutter -O -o box.pdf -R -
120
+ laser-cutter page-sizes -u mm
78
121
  ```
79
122
 
80
- ### Complete Help
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
- Usage: laser-cutter [options] -o filename.pdf
85
- eg: laser-cutter -z 1x1.5x2/0.125 -O -o box.pdf
131
+ ### More boxes
86
132
 
87
- Specific Options:
88
- -w, --width WIDTH Internal width of the box
89
- -h, --height HEIGHT Internal height of the box
90
- -d, --depth DEPTH Internal depth of the box
91
- -t, --thickness THICKNESS Thickness of the box material
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
- -m, --margin MARGIN Margins from the edge of the document
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
+ ![A 70 by 20 by 50 millimeter box](docs/images/box-metric.avif)
100
140
 
101
- -O, --open Open generated file with system viewer before exiting
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
- -L, --list-all-page-sizes Print all available page sizes with dimensions and exit
106
- -M, --no-metadata Do not print box metadata on the PDF
107
- -v, --[no-]verbose Run verbosely
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
- --examples Show detailed usage examples
112
- --help Show this message
113
- --version Show version
147
+ ![A 120 by 25 by 80 millimeter tray with a plain lid](docs/images/box-tray.avif)
114
148
 
115
- Common Options:
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
- -u, --units UNITS Either 'in' for inches (default) or 'mm'
151
+ ```bash
152
+ magick -density 96 -background white box.svg -flatten box.avif
121
153
  ```
122
154
 
123
- ## Wish List
155
+ ## Using it from Ruby
124
156
 
125
- * Create T-style joins, using various standard sizes of nuts and bolts (such as common #4-40 and M2 sizes)
126
- * Extensibility with various layout strategies, notch drawing strategies, basically plug and play
127
- model for adding new algorithms for path creation and box joining
128
- * Support more shapes than just box, such as prisms
129
- * Supporting lids and front panels, that are larger than the box itself and have holes for notches.
130
- * Your brilliant idea can be here too! Please see [contributing](CONTRIBUTING.md) for more info.
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
+ ![LaserCutter Comparison](docs/images/comparison.jpg).
131
253
 
132
254
  ## Contributing
133
255
 
134
- 1. Fork it ( https://github.com/kigster/laser-cutter/fork )
135
- 2. Create your feature branch (`git checkout -b my-new-feature`)
136
- 3. Commit your changes (`git commit -am 'Add some feature'`)
137
- 4. Push to the branch (`git push origin my-new-feature`)
138
- 5. Create a new Pull Request
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
- require 'bundler/gem_tasks'
2
- require 'rspec/core/rake_task'
3
- require 'yard'
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('rm -rf pkg/ tmp/ coverage/' )
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 :permissions => [ :clean ] do
15
- shell("chmod -v o+r,g+r * */* */*/* */*/*/* */*/*/*/* */*/*/*/*/*")
16
- shell("find . -type d -exec chmod o+x,g+x {} \\;")
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 :build => :permissions
29
+ task build: :permissions
20
30
 
21
31
  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') }
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 :default => :spec
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
- # coding: utf-8
2
- lib = File.expand_path('../lib', __FILE__)
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
- require 'laser-cutter/version'
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 = %q{Creates notched box outlines for laser-cut boxes which are geometrically symmetric and pleasing to the eye.}
12
- spec.description = %q{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
+ 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.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
18
- spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
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 'prawn'
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 'colored'
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.add_development_dependency 'yard'
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