@bobfrankston/wallplater 1.0.3 → 1.0.5
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 +32 -10
- package/defaults.json +3 -3
- package/geometry.js +5 -2
- package/index.js +15 -6
- package/package.json +1 -1
- package/threemf.d.ts +6 -1
- package/threemf.js +22 -5
package/README.md
CHANGED
|
@@ -14,20 +14,41 @@ individually colourable objects) or per-part STLs.
|
|
|
14
14
|
The manifold engine has no system dependency beyond Node and is the path
|
|
15
15
|
forward; `-scad` is kept for parity/verification.
|
|
16
16
|
|
|
17
|
+
## Build / install
|
|
18
|
+
|
|
19
|
+
TypeScript compiled to co-located `.js` / `.d.ts` / `.map` (no `src`/`dist`
|
|
20
|
+
split). `tsc` is the global install.
|
|
21
|
+
|
|
22
|
+
```sh
|
|
23
|
+
npm install
|
|
24
|
+
npm run build # tsc (or: npm run watch for tsc -w)
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
Installed globally (`npm i -g @bobfrankston/wallplater`) it exposes a
|
|
28
|
+
`wallplater` command. `defaults.json` is resolved relative to the module, so it
|
|
29
|
+
runs correctly from any working directory.
|
|
30
|
+
|
|
17
31
|
## Usage
|
|
18
32
|
|
|
19
|
-
Requires Node 24+ (
|
|
20
|
-
global install; `npm run typecheck` runs `tsc --noEmit` (type-check only — this
|
|
21
|
-
project runs `.ts` and never emits `.js`).
|
|
33
|
+
Requires Node 24+ (ESM). Run the compiled entry:
|
|
22
34
|
|
|
23
35
|
```sh
|
|
24
|
-
node index.
|
|
25
|
-
node index.
|
|
36
|
+
node index.js ../KitchenBack.json # manifold engine -> KitchenBack.3mf
|
|
37
|
+
node index.js ../KitchenBack.json -scad # OpenSCAD engine (same result)
|
|
38
|
+
wallplater ../KitchenBack.json # if installed globally
|
|
26
39
|
```
|
|
27
40
|
|
|
28
41
|
- Outputs are written next to the **config file**, named after the config's `name`.
|
|
29
42
|
- Unknown flags are rejected. `-scad` (or `--scad`) selects the OpenSCAD engine.
|
|
30
43
|
|
|
44
|
+
The 3MF holds up to three named, individually-selectable objects — `<name> plate`
|
|
45
|
+
(filament 1), `<name> labels` (all accent text, filament 2), `<name> breaker`
|
|
46
|
+
(filament 2). The filament assignment is baked in via `Metadata/model_settings.config`
|
|
47
|
+
(Bambu/Orca per-object `extruder`), so the text slices in your second colour
|
|
48
|
+
without manual assignment — provided the project has ≥2 filaments. The label
|
|
49
|
+
objects sit flush in the plate's front-face pockets (printed front-face-down), so
|
|
50
|
+
they look hidden in the viewport until coloured.
|
|
51
|
+
|
|
31
52
|
## Config
|
|
32
53
|
|
|
33
54
|
Each specific config is **deep-merged over `defaults.json`** (in this
|
|
@@ -53,9 +74,10 @@ Lengths are written **CSS-style with a unit suffix**: `"0.375in"`, `"5mm"`,
|
|
|
53
74
|
|
|
54
75
|
- `gangs[].type`: `"decora"` | `"duplex"` | `"blank"`.
|
|
55
76
|
- `gangs[].screws`: `false` to omit that gang's screw holes (default true).
|
|
56
|
-
- `gangs[].legend`: single label centred
|
|
57
|
-
|
|
58
|
-
|
|
77
|
+
- `gangs[].legend`: single label centred above the gang's opening (positioned
|
|
78
|
+
clear of the screw holes; `legendY` / `legendSize`).
|
|
79
|
+
- `gangs[].breaker`: per-gang breaker/circuit label, lower-right of the opening
|
|
80
|
+
and clear of the screws (`gangBreakerY` / `gangBreakerSize`).
|
|
59
81
|
- `breaker`: `{ "switch": "A8", "size": "0.375in", "inset": "0.25in" }` — the
|
|
60
82
|
panel-wide breaker label in the lower-right corner. `switch` is the text;
|
|
61
83
|
`size`/`inset` come from `defaults.json`, so a config usually sets only
|
|
@@ -80,8 +102,8 @@ opening is unchanged — only the labelling differs. Per gang:
|
|
|
80
102
|
```
|
|
81
103
|
|
|
82
104
|
Rockers are spaced evenly down the opening; `left` labels are right-aligned snug
|
|
83
|
-
to the opening, `right` labels left-aligned. `legend` / `header` / `rockers`
|
|
84
|
-
all print in the accent colour (the
|
|
105
|
+
to the opening, `right` labels left-aligned. `legend` / `header` / `rockers` /
|
|
106
|
+
per-gang `breaker` all print in the accent colour (the **labels** object). Relevant defaults:
|
|
85
107
|
`rockerSize` (3.5mm), `rockerGap` (1.5mm), `headerSize` (4mm), `headerGap`
|
|
86
108
|
(5mm). Keep side labels short on multi-gang plates — the side margin is ~18 mm
|
|
87
109
|
on an end/single gang but only ~6 mm between interior gangs; long labels can
|
package/defaults.json
CHANGED
|
@@ -2,9 +2,9 @@
|
|
|
2
2
|
"name": "wallplate",
|
|
3
3
|
"breaker": { "switch": "", "size": "0.375in", "inset": "0.25in" },
|
|
4
4
|
"legendSize": "5mm",
|
|
5
|
-
"legendY": "
|
|
6
|
-
"gangBreakerSize": "
|
|
7
|
-
"gangBreakerY": "-
|
|
5
|
+
"legendY": "39mm",
|
|
6
|
+
"gangBreakerSize": "5mm",
|
|
7
|
+
"gangBreakerY": "-39mm",
|
|
8
8
|
"rockerSize": "3.5mm",
|
|
9
9
|
"rockerGap": "1.5mm",
|
|
10
10
|
"headerSize": "4mm",
|
package/geometry.js
CHANGED
|
@@ -92,10 +92,13 @@ export async function buildMeshes(cfg) {
|
|
|
92
92
|
for (let i = 0; i < N; i++) {
|
|
93
93
|
const g = cfg.gangs[i];
|
|
94
94
|
const gx = gangX(i);
|
|
95
|
+
// Screw holes sit on the gang centreline at y = +/- screwSpacing/2, so
|
|
96
|
+
// labels are kept off-centreline / out of those bands: legend above the
|
|
97
|
+
// opening (legendY > 0), per-gang breaker below-right of the opening.
|
|
95
98
|
if ((g.legend ?? "") !== "")
|
|
96
99
|
accent.push({ txt: g.legend, size: cfg.legendSize, halign: "center", x: gx, y: cfg.legendY });
|
|
97
|
-
if ((g.breaker ?? "") !== "") // per-gang breaker
|
|
98
|
-
accent.push({ txt: g.breaker, size: cfg.gangBreakerSize, halign: "
|
|
100
|
+
if ((g.breaker ?? "") !== "") // per-gang breaker: lower-right of the opening
|
|
101
|
+
accent.push({ txt: g.breaker, size: cfg.gangBreakerSize, halign: "right", x: gx + openHalf, y: cfg.gangBreakerY });
|
|
99
102
|
if ((g.header ?? "") !== "")
|
|
100
103
|
accent.push({ txt: g.header, size: cfg.headerSize, halign: "center", x: gx, y: openTop + cfg.headerGap });
|
|
101
104
|
const rk = g.rockers ?? [];
|
package/index.js
CHANGED
|
@@ -18,6 +18,14 @@ import { loadConfig } from "./config.js";
|
|
|
18
18
|
import { generateScad, runOpenscad, readStl } from "./scad.js";
|
|
19
19
|
import { buildMeshes } from "./geometry.js";
|
|
20
20
|
import { build3mf, writeBinaryStl } from "./threemf.js";
|
|
21
|
+
// Bambu object-list name: "<config> <part>"; the accent text object reads "labels".
|
|
22
|
+
function partName(cfgName, label) {
|
|
23
|
+
return `${cfgName} ${label === "legend" ? "labels" : label}`;
|
|
24
|
+
}
|
|
25
|
+
// Filament/extruder per part: plate -> 1, all accent text -> 2.
|
|
26
|
+
function partExtruder(label) {
|
|
27
|
+
return label === "plate" ? 1 : 2;
|
|
28
|
+
}
|
|
21
29
|
function partList(cfg) {
|
|
22
30
|
const parts = [{ mode: "plate", label: "plate" }];
|
|
23
31
|
if (cfg.gangs.some((g) => (g.legend ?? "") !== ""))
|
|
@@ -41,16 +49,16 @@ function runScadEngine(cfg, dir) {
|
|
|
41
49
|
}
|
|
42
50
|
else {
|
|
43
51
|
const tmp = [];
|
|
44
|
-
const
|
|
52
|
+
const objects = parts.map((p) => {
|
|
45
53
|
const stl = join(dir, `_${cfg.name}_${p.label}.stl`);
|
|
46
54
|
runOpenscad(cfg.openscad, scadPath, stl, p.mode);
|
|
47
55
|
tmp.push(stl);
|
|
48
|
-
return readStl(stl);
|
|
56
|
+
return { name: partName(cfg.name, p.label), mesh: readStl(stl), extruder: partExtruder(p.label) };
|
|
49
57
|
});
|
|
50
|
-
writeFileSync(join(dir, `${cfg.name}.3mf`), build3mf(
|
|
58
|
+
writeFileSync(join(dir, `${cfg.name}.3mf`), build3mf(objects));
|
|
51
59
|
for (const t of tmp)
|
|
52
60
|
unlinkSync(t);
|
|
53
|
-
console.log(" wrote", `${cfg.name}.3mf`, `(${
|
|
61
|
+
console.log(" wrote", `${cfg.name}.3mf`, `(${objects.length} objects: ${objects.map((o) => o.name).join(", ")})`);
|
|
54
62
|
}
|
|
55
63
|
}
|
|
56
64
|
async function runManifoldEngine(cfg, dir) {
|
|
@@ -66,8 +74,9 @@ async function runManifoldEngine(cfg, dir) {
|
|
|
66
74
|
writeFileSync(join(dir, `${cfg.name}_${m.label}.stl`), writeBinaryStl(m.mesh));
|
|
67
75
|
}
|
|
68
76
|
else {
|
|
69
|
-
|
|
70
|
-
|
|
77
|
+
const objects = meshes.map((m) => ({ name: partName(cfg.name, m.label), mesh: m.mesh, extruder: partExtruder(m.label) }));
|
|
78
|
+
writeFileSync(join(dir, `${cfg.name}.3mf`), build3mf(objects));
|
|
79
|
+
console.log(" wrote", `${cfg.name}.3mf`, `(${objects.length} objects: ${objects.map((o) => o.name).join(", ")})`);
|
|
71
80
|
}
|
|
72
81
|
}
|
|
73
82
|
async function main() {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@bobfrankston/wallplater",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.5",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "JSON-driven wall-plate generator (Decora / duplex / blank). Direct-to-3MF via manifold-3d, with an optional OpenSCAD engine.",
|
|
6
6
|
"main": "index.js",
|
package/threemf.d.ts
CHANGED
|
@@ -2,6 +2,11 @@ export interface Mesh {
|
|
|
2
2
|
verts: number[][];
|
|
3
3
|
tris: number[][];
|
|
4
4
|
}
|
|
5
|
-
export
|
|
5
|
+
export interface Object3mf {
|
|
6
|
+
name: string;
|
|
7
|
+
mesh: Mesh;
|
|
8
|
+
extruder?: number;
|
|
9
|
+
}
|
|
10
|
+
export declare function build3mf(objects: Object3mf[]): Buffer;
|
|
6
11
|
export declare function writeBinaryStl(mesh: Mesh): Buffer;
|
|
7
12
|
//# sourceMappingURL=threemf.d.ts.map
|
package/threemf.js
CHANGED
|
@@ -6,26 +6,42 @@
|
|
|
6
6
|
* - writeBinaryStl(): a single binary STL (used for the manifold STL path).
|
|
7
7
|
*/
|
|
8
8
|
import { deflateRawSync } from "zlib";
|
|
9
|
+
function xmlEscape(s) {
|
|
10
|
+
return s.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """);
|
|
11
|
+
}
|
|
9
12
|
// ---------- multi-object 3MF (core spec, no slicer settings) ----------
|
|
10
|
-
export function build3mf(
|
|
11
|
-
// separate top-level objects (each individually colourable
|
|
12
|
-
// at the same transform so the labels stay seated in the
|
|
13
|
+
export function build3mf(objects) {
|
|
14
|
+
// separate, named top-level objects (each individually selectable/colourable
|
|
15
|
+
// in Bambu), all at the same transform so the labels stay seated in the
|
|
16
|
+
// plate's pockets.
|
|
13
17
|
let objs = "";
|
|
14
18
|
let items = "";
|
|
15
|
-
|
|
19
|
+
objects.forEach((o, i) => {
|
|
20
|
+
const m = o.mesh;
|
|
16
21
|
const id = i + 1;
|
|
17
22
|
const vs = m.verts.map((v) => `<vertex x="${v[0]}" y="${v[1]}" z="${v[2]}"/>`).join("");
|
|
18
23
|
const ts = m.tris.map((t) => `<triangle v1="${t[0]}" v2="${t[1]}" v3="${t[2]}"/>`).join("");
|
|
19
|
-
objs += ` <object id="${id}" type="model"><mesh><vertices>${vs}</vertices><triangles>${ts}</triangles></mesh></object>\n`;
|
|
24
|
+
objs += ` <object id="${id}" type="model" name="${xmlEscape(o.name)}"><mesh><vertices>${vs}</vertices><triangles>${ts}</triangles></mesh></object>\n`;
|
|
20
25
|
items += ` <item objectid="${id}" transform="1 0 0 0 1 0 0 0 1 128 128 0"/>\n`;
|
|
21
26
|
});
|
|
22
27
|
const model = `<?xml version="1.0" encoding="UTF-8"?>
|
|
23
28
|
<model unit="millimeter" xml:lang="en-US" xmlns="http://schemas.microsoft.com/3dmanufacturing/core/2015/02">
|
|
29
|
+
<metadata name="Application">wallplater</metadata>
|
|
24
30
|
<resources>
|
|
25
31
|
${objs} </resources>
|
|
26
32
|
<build>
|
|
27
33
|
${items} </build>
|
|
28
34
|
</model>
|
|
35
|
+
`;
|
|
36
|
+
// Bambu/Orca per-object filament assignment lives here (read by path; not
|
|
37
|
+
// declared in [Content_Types].xml, matching how Bambu itself packages it).
|
|
38
|
+
const settings = `<?xml version="1.0" encoding="UTF-8"?>
|
|
39
|
+
<config>
|
|
40
|
+
${objects.map((o, i) => ` <object id="${i + 1}">
|
|
41
|
+
<metadata key="name" value="${xmlEscape(o.name)}"/>
|
|
42
|
+
<metadata key="extruder" value="${o.extruder ?? 1}"/>
|
|
43
|
+
</object>`).join("\n")}
|
|
44
|
+
</config>
|
|
29
45
|
`;
|
|
30
46
|
const ct = `<?xml version="1.0" encoding="UTF-8"?>
|
|
31
47
|
<Types xmlns="http://schemas.openxmlformats.org/package/2006/content-types">
|
|
@@ -42,6 +58,7 @@ ${items} </build>
|
|
|
42
58
|
{ name: "[Content_Types].xml", data: Buffer.from(ct) },
|
|
43
59
|
{ name: "_rels/.rels", data: Buffer.from(rels) },
|
|
44
60
|
{ name: "3D/3dmodel.model", data: Buffer.from(model) },
|
|
61
|
+
{ name: "Metadata/model_settings.config", data: Buffer.from(settings) },
|
|
45
62
|
]);
|
|
46
63
|
}
|
|
47
64
|
// ---------- binary STL (single object) ----------
|