create-rulecad 0.0.0-stage → 0.1.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.
- package/LICENSE +21 -0
- package/README.md +10 -2
- package/index.mjs +27 -0
- package/package.json +29 -4
- package/template/README.md +19 -0
- package/template/cad.config.json +4 -0
- package/template/gitignore +6 -0
- package/template/projects/home/objects/soap-tray.js +19 -0
- package/template/projects/home/objects/soap-tray.json +20 -0
- package/template/projects/home/project.json +6 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 JoniBach and contributors
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
CHANGED
|
@@ -1,3 +1,11 @@
|
|
|
1
|
-
#
|
|
1
|
+
# create-rulecad
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
```sh
|
|
4
|
+
npm create rulecad my-designs
|
|
5
|
+
cd my-designs
|
|
6
|
+
npx cad check home/soap-tray
|
|
7
|
+
```
|
|
8
|
+
|
|
9
|
+
Makes a [rulecad](https://github.com/JoniBach/rulecad) workspace with one design to start from: a soap tray with
|
|
10
|
+
ribs and drain slots, every measurement a variable that names its rule. `--no-install` makes the files without
|
|
11
|
+
running `npm install`.
|
package/index.mjs
ADDED
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// npm create rulecad [folder]: a new rulecad workspace with a design to start from (a soap tray), then npm install.
|
|
3
|
+
// --no-install make the files only
|
|
4
|
+
import fs from 'node:fs';
|
|
5
|
+
import path from 'node:path';
|
|
6
|
+
import { execSync } from 'node:child_process';
|
|
7
|
+
|
|
8
|
+
const args = process.argv.slice(2), install = !args.includes('--no-install');
|
|
9
|
+
const dir = path.resolve(args.find((a) => !a.startsWith('--')) || 'my-designs'), name = path.basename(dir);
|
|
10
|
+
if (fs.existsSync(dir) && fs.readdirSync(dir).length) { console.error(`${dir} isn't empty: name a new folder (npm create rulecad my-designs)`); process.exit(1); }
|
|
11
|
+
|
|
12
|
+
const TEMPLATE = new URL('./template/', import.meta.url).pathname;
|
|
13
|
+
fs.cpSync(TEMPLATE, dir, { recursive: true });
|
|
14
|
+
fs.renameSync(path.join(dir, 'gitignore'), path.join(dir, '.gitignore')); // npm drops files named .gitignore from packages
|
|
15
|
+
const readme = path.join(dir, 'README.md');
|
|
16
|
+
fs.writeFileSync(readme, fs.readFileSync(readme, 'utf8').replaceAll('NAME', name));
|
|
17
|
+
// $RULECAD_LOCAL: a rulecad checkout to use instead of npm (to try unpublished changes)
|
|
18
|
+
const from = (pkg) => (process.env.RULECAD_LOCAL ? `file:${path.join(path.resolve(process.env.RULECAD_LOCAL), 'packages', pkg)}` : '^0.1.0');
|
|
19
|
+
fs.writeFileSync(path.join(dir, 'package.json'), `${JSON.stringify({
|
|
20
|
+
name, private: true, type: 'module',
|
|
21
|
+
scripts: { check: 'cad check home/soap-tray' },
|
|
22
|
+
dependencies: { '@rulecad/cad': from('cad'), '@jscad/modeling': '^2.12.0' },
|
|
23
|
+
}, null, 2)}\n`);
|
|
24
|
+
|
|
25
|
+
console.log(`made ${path.relative(process.cwd(), dir) || '.'}: a soap tray to start from, in projects/home`);
|
|
26
|
+
if (install) execSync('npm install', { cwd: dir, stdio: 'inherit' });
|
|
27
|
+
console.log(`\n cd ${path.relative(process.cwd(), dir)}\n npx cad check home/soap-tray\n npx cad preview home/soap-tray\n\nREADME.md says what's where.`);
|
package/package.json
CHANGED
|
@@ -1,6 +1,31 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "create-rulecad",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"
|
|
5
|
-
"
|
|
6
|
-
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Start a rulecad project: npm create rulecad my-designs. A workspace with a soap tray to start from, ready to check, preview, export and share.",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"bin": {
|
|
7
|
+
"create-rulecad": "index.mjs"
|
|
8
|
+
},
|
|
9
|
+
"files": [
|
|
10
|
+
"index.mjs",
|
|
11
|
+
"template",
|
|
12
|
+
"README.md",
|
|
13
|
+
"LICENSE"
|
|
14
|
+
],
|
|
15
|
+
"license": "MIT",
|
|
16
|
+
"keywords": [
|
|
17
|
+
"rulecad",
|
|
18
|
+
"create",
|
|
19
|
+
"3d-printing",
|
|
20
|
+
"jscad",
|
|
21
|
+
"scaffold"
|
|
22
|
+
],
|
|
23
|
+
"engines": {
|
|
24
|
+
"node": ">=20.6"
|
|
25
|
+
},
|
|
26
|
+
"repository": {
|
|
27
|
+
"type": "git",
|
|
28
|
+
"url": "git+https://github.com/JoniBach/rulecad.git",
|
|
29
|
+
"directory": "packages/create-rulecad"
|
|
30
|
+
}
|
|
31
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
# NAME
|
|
2
|
+
|
|
3
|
+
Designs for 3D printing, made with [rulecad](https://github.com/JoniBach/rulecad). Every measurement is a variable,
|
|
4
|
+
and every variable says where it comes from.
|
|
5
|
+
|
|
6
|
+
```sh
|
|
7
|
+
npx cad check home/soap-tray # is it printable? (and: no raw numbers in the design)
|
|
8
|
+
npx cad check home/soap-tray --set length=140 # try another size (each tweakable variable has a range)
|
|
9
|
+
npx cad preview home/soap-tray # six views, in .preview/
|
|
10
|
+
npx cad export home/soap-tray # a 3MF for your slicer, in exports/
|
|
11
|
+
npx cad link home/soap-tray --md # a link that opens the model in a browser: nothing is uploaded
|
|
12
|
+
npx cad new home/hook # your own design
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
- `projects/home/objects/soap-tray.json` holds the variables, `soap-tray.js` the shape ([JSCAD](https://openjscad.xyz)).
|
|
16
|
+
- `rules/` holds the facts designs use, such as your printer's (`x1c.json`, copied in on first use). Measure your own
|
|
17
|
+
printer and correct them: every design that uses them follows.
|
|
18
|
+
- `cad.config.json` names the printer. For another printer, copy `rules/x1c.json` under a new name and change the
|
|
19
|
+
values.
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
// A soap tray: a shallow box with ribs across the floor to hold the soap up, and drain slots between them.
|
|
2
|
+
// Every measurement is a variable in soap-tray.json; this file only says how they combine. Z is up, the bed is z = 0.
|
|
3
|
+
import jscad from '@jscad/modeling'
|
|
4
|
+
const { cuboid } = jscad.primitives
|
|
5
|
+
const { translate } = jscad.transforms
|
|
6
|
+
const { subtract, union } = jscad.booleans
|
|
7
|
+
|
|
8
|
+
// A box sitting on z = z0, centred in x and y
|
|
9
|
+
const box = (x, y, z0, h, cx = 0) => translate([cx, 0, z0 + h / 2], cuboid({ size: [x, y, h] }))
|
|
10
|
+
|
|
11
|
+
export function main (v) {
|
|
12
|
+
const inside = [v.length - v.wall * 2, v.width - v.wall * 2]
|
|
13
|
+
const count = Math.floor(inside[0] / v.pitch) // ribs, centred along the length
|
|
14
|
+
const at = (i) => (i - (count - 1) / 2) * v.pitch
|
|
15
|
+
const ribs = Array.from({ length: count }, (_, i) => box(v.rib_width, inside[1], v.floor / 2, v.rib_height + v.floor / 2, at(i)))
|
|
16
|
+
const slots = Array.from({ length: count - 1 }, (_, i) => box(v.slot, inside[1] - v.margin * 2, -v.floor / 2, v.floor * 2, (at(i) + at(i + 1)) / 2))
|
|
17
|
+
const tray = subtract(box(v.length, v.width, 0, v.height), box(inside[0], inside[1], v.floor, v.height))
|
|
18
|
+
return subtract(union(tray, ...ribs), ...slots)
|
|
19
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "soap-tray",
|
|
3
|
+
"title": "Soap tray",
|
|
4
|
+
"version": "0.1.0",
|
|
5
|
+
"uses": ["x1c"],
|
|
6
|
+
"material": "PLA",
|
|
7
|
+
"orientation": "Flat side down on the bed. Walls are straight up, so it prints without supports.",
|
|
8
|
+
"variables": {
|
|
9
|
+
"length": { "value": 110, "unit": "mm", "rule": "Long enough for a bar of soap", "label": "Length", "show": true, "tweak": { "min": 70, "max": 180 } },
|
|
10
|
+
"width": { "value": 75, "unit": "mm", "rule": "Wide enough for a bar of soap", "label": "Width", "show": true, "tweak": { "min": 50, "max": 120 } },
|
|
11
|
+
"height": { "value": "x1c.layer * 60", "unit": "mm", "rule": "A whole number of layers", "label": "Height", "show": true },
|
|
12
|
+
"wall": { "value": "x1c.line * 4", "unit": "mm", "rule": "Four print lines: stiff, and no gaps between lines", "label": "Wall" },
|
|
13
|
+
"floor": { "value": "x1c.layer * 8", "unit": "mm", "rule": "Eight layers under the ribs" },
|
|
14
|
+
"rib_height": { "value": "x1c.layer * 15", "unit": "mm", "rule": "Holds the soap up out of the water", "label": "Rib height", "show": true },
|
|
15
|
+
"rib_width": { "value": "x1c.line * 4", "unit": "mm", "rule": "Four print lines" },
|
|
16
|
+
"pitch": { "value": 12, "unit": "mm", "rule": "Ribs this far apart: soap rests on several, water runs between", "label": "Rib spacing", "show": true, "tweak": { "min": 8, "max": 20 } },
|
|
17
|
+
"slot": { "value": "3 + x1c.hole_comp", "unit": "mm", "rule": "Drain slots between the ribs, widened for how holes print small" },
|
|
18
|
+
"margin": { "value": 6, "unit": "mm", "rule": "Solid floor between the slots and the walls" }
|
|
19
|
+
}
|
|
20
|
+
}
|