@rulecad/shapes 0.1.2 → 0.2.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/README.md +27 -0
- package/enclosure.d.mts +59 -0
- package/enclosure.mjs +83 -0
- package/package.json +4 -2
- package/shapes.d.mts +1 -0
- package/shapes.mjs +3 -1
package/README.md
CHANGED
|
@@ -22,3 +22,30 @@ const cap = cut(body, inlay); //
|
|
|
22
22
|
- `cut(a, b, margin)` is `subtract(a, b)`, but only the faces near `b` are cut. JSCAD's subtract splits every face of
|
|
23
23
|
`a` along `b`'s planes, however far away, and those splits can leave hairline gaps that slicers dislike.
|
|
24
24
|
- `size(geom2)` gives a 2D outline's `[width, depth]`.
|
|
25
|
+
|
|
26
|
+
## Enclosures
|
|
27
|
+
|
|
28
|
+
The pieces cases are made of, drawn one way everywhere, so a family of parts looks like one system. Footprints are in
|
|
29
|
+
x and y, centred on 0, z up from the bed; every slope is 45 degrees, so they print without supports as drawn.
|
|
30
|
+
|
|
31
|
+
```js
|
|
32
|
+
import { shell, lid, dovetail, gabled } from '@rulecad/shapes';
|
|
33
|
+
|
|
34
|
+
const box = shell({ width: 72, height: 72, depth: 17, round: 6, wall: 2.1, floor: 1.6, bottom: 0.4 }); // open at the top
|
|
35
|
+
const front = lid({ width: 72, height: 72, round: 6, thick: 2.4, edge: 2, wall: 2.1, fit: 0.2, lip: 2, ring: 1.26 }); // printed face down
|
|
36
|
+
const rail = extrudeLinear({ height: 17 }, dovetail({ base: 6, height: 1.6 })); // and its slot: dovetail({ ..., gap: 0.4 })
|
|
37
|
+
const window = gabled([-1, 1], [-1, 4], [10, 13]); // an opening in an upright wall
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
- `outline(w, h, { round, bevel })`: a rectangle with rounded or cut corners (2D).
|
|
41
|
+
- `slab({ width, height, depth, round, bevel, bottom, top })`: a block on that outline, its lower and upper edges
|
|
42
|
+
sloped in by `bottom` (against elephant's foot) and `top` (a bevelled front edge).
|
|
43
|
+
- `shell({ ...slab, wall, floor })`: a slab hollowed from `floor` up, open at the top.
|
|
44
|
+
- `lid({ width, height, round, bevel, thick, edge, wall, fit, lip, ring })`: a lid printed face down, its front edge
|
|
45
|
+
sloped by `edge`, with a lip `ring` wide that drops `lip` deep inside a shell's walls, `fit` away. Place it turned
|
|
46
|
+
over (an assembly item's `"rotate": [180, 0, 0]`).
|
|
47
|
+
- `dovetail({ base, height, gap })`: a rail's profile rising from y = 0, flanks at 45 degrees; with `gap`, the slot
|
|
48
|
+
that takes it, `gap` wider each side and deeper.
|
|
49
|
+
- `gabled([x0, x1], [y0, y1], [z0, z1], axis)`: a box with a 45 degree gabled roof, for openings in upright walls.
|
|
50
|
+
- `diamond(size, axis, length)`: a square hole turned 45 degrees, for small holes in upright walls.
|
|
51
|
+
- `vents({ count, width, pitch, length, depth })`: slots side by side along x.
|
package/enclosure.d.mts
ADDED
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
/** A rectangle with rounded or cut corners, centred on 0. @returns {object} geom2 */
|
|
2
|
+
export function outline(w: any, h: any, { round, bevel, segments }?: {
|
|
3
|
+
round?: number;
|
|
4
|
+
bevel?: number;
|
|
5
|
+
segments?: number;
|
|
6
|
+
}): object;
|
|
7
|
+
/** A block on an outline, its lower and upper edges sloped in at 45 degrees by bottom and top. @returns {object} geom3 */
|
|
8
|
+
export function slab({ width, height, depth, round, bevel, bottom, top, segments }: {
|
|
9
|
+
width: any;
|
|
10
|
+
height: any;
|
|
11
|
+
depth: any;
|
|
12
|
+
round?: number;
|
|
13
|
+
bevel?: number;
|
|
14
|
+
bottom?: number;
|
|
15
|
+
top?: number;
|
|
16
|
+
segments?: number;
|
|
17
|
+
}): object;
|
|
18
|
+
/** A slab hollowed from floor up, open at the top; the walls wall thick. @returns {object} geom3 */
|
|
19
|
+
export function shell({ wall, floor, ...o }: {
|
|
20
|
+
[x: string]: any;
|
|
21
|
+
wall: any;
|
|
22
|
+
floor: any;
|
|
23
|
+
}): object;
|
|
24
|
+
/**
|
|
25
|
+
* A lid printed face down: a plate thick deep whose front edge slopes in by edge (on the bed), and on top a lip, ring
|
|
26
|
+
* wide, that drops lip deep inside a shell's walls (wall thick) with fit to spare. @returns {object} geom3
|
|
27
|
+
*/
|
|
28
|
+
export function lid({ width, height, round, bevel, thick, edge, wall, fit, lip, ring, segments }: {
|
|
29
|
+
width: any;
|
|
30
|
+
height: any;
|
|
31
|
+
round?: number;
|
|
32
|
+
bevel?: number;
|
|
33
|
+
thick: any;
|
|
34
|
+
edge?: number;
|
|
35
|
+
wall: any;
|
|
36
|
+
fit: any;
|
|
37
|
+
lip: any;
|
|
38
|
+
ring: any;
|
|
39
|
+
segments?: number;
|
|
40
|
+
}): object;
|
|
41
|
+
/** A dovetail rail's profile (gap 0) or the slot that takes it (gap > 0), rising from y = 0, flanks at 45 degrees. */
|
|
42
|
+
export function dovetail({ base, height, gap }: {
|
|
43
|
+
base: any;
|
|
44
|
+
height: any;
|
|
45
|
+
gap?: number;
|
|
46
|
+
}): jscad.geometries.geom2.Geom2;
|
|
47
|
+
/** A box [x0, x1] × [y0, y1] × [z0, z1] with a 45 degree gabled roof above z1, its ridge along axis ('x' or 'y'). */
|
|
48
|
+
export function gabled([x0, x1]: [any, any], [y0, y1]: [any, any], [z0, z1]: [any, any], axis?: string): jscad.geometries.geom3.Geom3;
|
|
49
|
+
/** A square hole size wide turned 45 degrees about axis, through length, centred on 0. */
|
|
50
|
+
export function diamond(size: any, axis?: string, length?: number): jscad.geometries.geom3.Geom3;
|
|
51
|
+
/** Vent slots side by side along x, centred on 0: count of them, width wide, pitch apart, length long in z, depth in y. */
|
|
52
|
+
export function vents({ count, width, pitch, length, depth }: {
|
|
53
|
+
count: any;
|
|
54
|
+
width: any;
|
|
55
|
+
pitch: any;
|
|
56
|
+
length: any;
|
|
57
|
+
depth: any;
|
|
58
|
+
}): jscad.geometries.geom3.Geom3;
|
|
59
|
+
import jscad from '@jscad/modeling';
|
package/enclosure.mjs
ADDED
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
// Enclosure helpers: the pieces printed cases are made of, drawn the same way everywhere, so a family of parts looks
|
|
2
|
+
// like one system. Footprints are in x and y, centred on 0; z is up, from the bed. Every slope is 45 degrees, so
|
|
3
|
+
// everything prints without supports when the part lies as drawn.
|
|
4
|
+
// outline(w, h, { round, bevel }) a rectangle with rounded (round: radius) or cut (bevel: along each side) corners
|
|
5
|
+
// slab({ width, height, depth, round, bevel, bottom, top }) a block on that outline; bottom and top slope its lower
|
|
6
|
+
// and upper edges in at 45 degrees (bottom: against elephant's foot; top: a bevelled front edge)
|
|
7
|
+
// shell({ ...slab, wall, floor }) a slab hollowed out from floor up, open at the top
|
|
8
|
+
// lid({ width, height, round, bevel, thick, edge, wall, fit, lip, ring }) a lid printed face down: its front edge
|
|
9
|
+
// sloped (edge), a lip on top that drops inside a shell's walls (fit away) to locate it
|
|
10
|
+
// dovetail({ base, height, gap }) a rail's 2D profile, rising from y = 0 (gap > 0: the slot that takes it, gap wider
|
|
11
|
+
// each side and deeper)
|
|
12
|
+
// gabled([x0, x1], [y0, y1], [z0, z1], axis) a box with a 45 degree gabled roof, for openings in upright walls
|
|
13
|
+
// diamond(size, axis) a square hole turned 45 degrees about axis ('x' | 'y'), for small holes in upright walls
|
|
14
|
+
// vents({ count, width, pitch, length, depth }) slots side by side along x, centred, each length long in z
|
|
15
|
+
import jscad from '@jscad/modeling';
|
|
16
|
+
|
|
17
|
+
const { polygon, roundedRectangle, rectangle, cuboid } = jscad.primitives, { extrudeLinear } = jscad.extrusions;
|
|
18
|
+
const { translate, rotateX, rotateY } = jscad.transforms, { subtract, union } = jscad.booleans, { hull } = jscad.hulls;
|
|
19
|
+
const THIN = 1e-3; // the height of a slice that hulls join into slopes
|
|
20
|
+
|
|
21
|
+
/** A rectangle with rounded or cut corners, centred on 0. @returns {object} geom2 */
|
|
22
|
+
export function outline(w, h, { round = 0, bevel = 0, segments = 32 } = {}) {
|
|
23
|
+
if (w <= 0 || h <= 0) throw new Error(`outline: ${w} × ${h} mm is not a shape`);
|
|
24
|
+
if (round > 0) return roundedRectangle({ size: [w, h], roundRadius: Math.min(round, w / 2 - THIN, h / 2 - THIN), segments });
|
|
25
|
+
if (bevel > 0) { const b = Math.min(bevel, w / 2, h / 2); return polygon({ points: [[-w / 2 + b, -h / 2], [w / 2 - b, -h / 2], [w / 2, -h / 2 + b], [w / 2, h / 2 - b], [w / 2 - b, h / 2], [-w / 2 + b, h / 2], [-w / 2, h / 2 - b], [-w / 2, -h / 2 + b]] }); }
|
|
26
|
+
return rectangle({ size: [w, h] });
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
// The outline moved in by d all round: a rounded corner keeps its centre, a cut corner its angle
|
|
30
|
+
const inset = (o, d) => ({ ...o, width: o.width - d * 2, height: o.height - d * 2, round: o.round ? Math.max(o.round - d, 0) : 0, bevel: o.bevel ? Math.max(o.bevel - d * (2 - Math.SQRT2), 0) : 0 });
|
|
31
|
+
const slice = (o, d, z0, z1 = z0 + THIN) => translate([0, 0, z0], extrudeLinear({ height: z1 - z0 }, outline(o.width - d * 2, o.height - d * 2, inset(o, d))));
|
|
32
|
+
|
|
33
|
+
/** A block on an outline, its lower and upper edges sloped in at 45 degrees by bottom and top. @returns {object} geom3 */
|
|
34
|
+
export function slab({ width, height, depth, round = 0, bevel = 0, bottom = 0, top = 0, segments = 32 }) {
|
|
35
|
+
const o = { width, height, round, bevel, segments };
|
|
36
|
+
if (bottom + top >= depth) throw new Error(`slab: slopes of ${bottom} and ${top} mm need more than ${depth} mm of depth`);
|
|
37
|
+
if (!bottom && !top) return slice(o, 0, 0, depth);
|
|
38
|
+
return hull(slice(o, bottom, 0), slice(o, 0, bottom, depth - top), slice(o, top, depth - THIN));
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/** A slab hollowed from floor up, open at the top; the walls wall thick. @returns {object} geom3 */
|
|
42
|
+
export function shell({ wall, floor, ...o }) {
|
|
43
|
+
const room = inset({ ...o, round: o.round || 0, bevel: o.bevel || 0 }, wall);
|
|
44
|
+
return subtract(slab(o), translate([0, 0, floor], extrudeLinear({ height: o.depth }, outline(room.width, room.height, room))));
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* A lid printed face down: a plate thick deep whose front edge slopes in by edge (on the bed), and on top a lip, ring
|
|
49
|
+
* wide, that drops lip deep inside a shell's walls (wall thick) with fit to spare. @returns {object} geom3
|
|
50
|
+
*/
|
|
51
|
+
export function lid({ width, height, round = 0, bevel = 0, thick, edge = 0, wall, fit, lip, ring, segments = 32 }) {
|
|
52
|
+
const o = { width, height, round, bevel, segments };
|
|
53
|
+
const plate = slab({ ...o, depth: thick, bottom: edge });
|
|
54
|
+
if (!lip) return plate;
|
|
55
|
+
const out = inset(o, wall + fit), hole = inset(o, wall + fit + ring);
|
|
56
|
+
const band = subtract(outline(out.width, out.height, out), outline(hole.width, hole.height, hole));
|
|
57
|
+
return union(plate, translate([0, 0, thick], extrudeLinear({ height: lip }, band)));
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/** A dovetail rail's profile (gap 0) or the slot that takes it (gap > 0), rising from y = 0, flanks at 45 degrees. */
|
|
61
|
+
export function dovetail({ base, height, gap = 0 }) {
|
|
62
|
+
const b = base / 2 + gap, t = b + height + (gap ? gap : 0), y = height + gap;
|
|
63
|
+
return polygon({ points: [[-b, gap ? -1 : 0], [b, gap ? -1 : 0], [b, 0], [t, y], [-t, y], [-b, 0]] });
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/** A box [x0, x1] × [y0, y1] × [z0, z1] with a 45 degree gabled roof above z1, its ridge along axis ('x' or 'y'). */
|
|
67
|
+
export function gabled([x0, x1], [y0, y1], [z0, z1], axis = 'y') {
|
|
68
|
+
const box = (a0, a1, b0, b1, c0, c1) => translate([(a0 + a1) / 2, (b0 + b1) / 2, (c0 + c1) / 2], cuboid({ size: [a1 - a0, b1 - b0, c1 - c0] }));
|
|
69
|
+
const half = axis === 'y' ? (x1 - x0) / 2 : (y1 - y0) / 2, mx = (x0 + x1) / 2, my = (y0 + y1) / 2;
|
|
70
|
+
const ridge = axis === 'y' ? box(mx - THIN, mx + THIN, y0, y1, z1, z1 + half) : box(x0, x1, my - THIN, my + THIN, z1, z1 + half);
|
|
71
|
+
return hull(box(x0, x1, y0, y1, z0, z1), ridge);
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/** A square hole size wide turned 45 degrees about axis, through length, centred on 0. */
|
|
75
|
+
export function diamond(size, axis = 'y', length = 10) {
|
|
76
|
+
const a = size / Math.SQRT2;
|
|
77
|
+
return axis === 'y' ? rotateY(Math.PI / 4, cuboid({ size: [a, length, a] })) : rotateX(Math.PI / 4, cuboid({ size: [length, a, a] }));
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/** Vent slots side by side along x, centred on 0: count of them, width wide, pitch apart, length long in z, depth in y. */
|
|
81
|
+
export function vents({ count, width, pitch, length, depth }) {
|
|
82
|
+
return union(...Array.from({ length: count }, (_, i) => translate([(i - (count - 1) / 2) * pitch, 0, 0], cuboid({ size: [width, depth, length] }))));
|
|
83
|
+
}
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@rulecad/shapes",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"description": "Shape helpers for printed JSCAD designs: text that prints cleanly, anchors on a face,
|
|
3
|
+
"version": "0.2.0",
|
|
4
|
+
"description": "Shape helpers for printed JSCAD designs: text that prints cleanly, anchors on a face, clean local cuts, and enclosure pieces (shells, lids, rails, print-safe holes).",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"exports": {
|
|
7
7
|
"types": "./shapes.d.mts",
|
|
@@ -10,6 +10,8 @@
|
|
|
10
10
|
"files": [
|
|
11
11
|
"shapes.mjs",
|
|
12
12
|
"shapes.d.mts",
|
|
13
|
+
"enclosure.mjs",
|
|
14
|
+
"enclosure.d.mts",
|
|
13
15
|
"README.md",
|
|
14
16
|
"LICENSE"
|
|
15
17
|
],
|
package/shapes.d.mts
CHANGED
|
@@ -24,3 +24,4 @@ export function cut(a: object, b: object, margin?: number): object;
|
|
|
24
24
|
export function size(g: any): number[];
|
|
25
25
|
export const scale: typeof jscad.transforms.scale;
|
|
26
26
|
import jscad from '@jscad/modeling';
|
|
27
|
+
export { outline, slab, shell, lid, dovetail, gabled, diamond, vents } from "./enclosure.mjs";
|
package/shapes.mjs
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
// Shape helpers for JSCAD designs that are printed: text that prints,
|
|
1
|
+
// Shape helpers for JSCAD designs that are printed: text that prints, placing things on a face, and the pieces of
|
|
2
|
+
// enclosures (enclosure.mjs: outlines, slabs, shells, lids, dovetail rails, print-safe holes, vents).
|
|
2
3
|
// text({ text, height, stroke, segments }) -> a 2D outline of the text, centred on 0: JSCAD's single-line
|
|
3
4
|
// font, scaled so a capital is height tall, each line thickened to stroke wide (whole print lines, so it prints).
|
|
4
5
|
// The arrows the font lacks (up, down, left, right) are drawn in the same stroke.
|
|
@@ -80,3 +81,4 @@ export function cut(a, b, margin = 0.05) {
|
|
|
80
81
|
|
|
81
82
|
export const size = (g) => { const [lo, hi] = measureBoundingBox(g); return [hi[0] - lo[0], hi[1] - lo[1]]; };
|
|
82
83
|
export { scale };
|
|
84
|
+
export { outline, slab, shell, lid, dovetail, gabled, diamond, vents } from './enclosure.mjs';
|