@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 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.
@@ -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.1.2",
4
- "description": "Shape helpers for printed JSCAD designs: text that prints cleanly, anchors on a face, and clean local cuts.",
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, and placing things on a face.
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';