@bobfrankston/wallplater 1.0.3 → 1.0.4

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
@@ -14,20 +14,39 @@ 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+ (native TypeScript + ESM — run `.ts` directly). `tsc` is the
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.ts ../KitchenBack.json # manifold engine -> KitchenBack.3mf
25
- node index.ts ../KitchenBack.json -scad # OpenSCAD engine (same result)
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
+ `<name> labels` (all accent text), `<name> breaker` — so each is easy to find in
46
+ Bambu Studio's object list and assign to a filament. The label objects sit flush
47
+ in the plate's front-face pockets (the plate prints front-face-down), so they
48
+ look hidden until you give them a second colour.
49
+
31
50
  ## Config
32
51
 
33
52
  Each specific config is **deep-merged over `defaults.json`** (in this
@@ -80,8 +99,8 @@ opening is unchanged — only the labelling differs. Per gang:
80
99
  ```
81
100
 
82
101
  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 `legend` object). Relevant defaults:
102
+ to the opening, `right` labels left-aligned. `legend` / `header` / `rockers` /
103
+ per-gang `breaker` all print in the accent colour (the **labels** object). Relevant defaults:
85
104
  `rockerSize` (3.5mm), `rockerGap` (1.5mm), `headerSize` (4mm), `headerGap`
86
105
  (5mm). Keep side labels short on multi-gang plates — the side margin is ~18 mm
87
106
  on an end/single gang but only ~6 mm between interior gangs; long labels can
package/index.js CHANGED
@@ -18,6 +18,10 @@ 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
+ }
21
25
  function partList(cfg) {
22
26
  const parts = [{ mode: "plate", label: "plate" }];
23
27
  if (cfg.gangs.some((g) => (g.legend ?? "") !== ""))
@@ -41,16 +45,16 @@ function runScadEngine(cfg, dir) {
41
45
  }
42
46
  else {
43
47
  const tmp = [];
44
- const meshes = parts.map((p) => {
48
+ const objects = parts.map((p) => {
45
49
  const stl = join(dir, `_${cfg.name}_${p.label}.stl`);
46
50
  runOpenscad(cfg.openscad, scadPath, stl, p.mode);
47
51
  tmp.push(stl);
48
- return readStl(stl);
52
+ return { name: partName(cfg.name, p.label), mesh: readStl(stl) };
49
53
  });
50
- writeFileSync(join(dir, `${cfg.name}.3mf`), build3mf(meshes));
54
+ writeFileSync(join(dir, `${cfg.name}.3mf`), build3mf(objects));
51
55
  for (const t of tmp)
52
56
  unlinkSync(t);
53
- console.log(" wrote", `${cfg.name}.3mf`, `(${meshes.length} objects: ${parts.map((p) => p.label).join(", ")})`);
57
+ console.log(" wrote", `${cfg.name}.3mf`, `(${objects.length} objects: ${objects.map((o) => o.name).join(", ")})`);
54
58
  }
55
59
  }
56
60
  async function runManifoldEngine(cfg, dir) {
@@ -66,8 +70,9 @@ async function runManifoldEngine(cfg, dir) {
66
70
  writeFileSync(join(dir, `${cfg.name}_${m.label}.stl`), writeBinaryStl(m.mesh));
67
71
  }
68
72
  else {
69
- writeFileSync(join(dir, `${cfg.name}.3mf`), build3mf(meshes.map((m) => m.mesh)));
70
- console.log(" wrote", `${cfg.name}.3mf`, `(${meshes.length} objects: ${meshes.map((m) => m.label).join(", ")})`);
73
+ const objects = meshes.map((m) => ({ name: partName(cfg.name, m.label), mesh: m.mesh }));
74
+ writeFileSync(join(dir, `${cfg.name}.3mf`), build3mf(objects));
75
+ console.log(" wrote", `${cfg.name}.3mf`, `(${objects.length} objects: ${objects.map((o) => o.name).join(", ")})`);
71
76
  }
72
77
  }
73
78
  async function main() {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@bobfrankston/wallplater",
3
- "version": "1.0.3",
3
+ "version": "1.0.4",
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,10 @@ export interface Mesh {
2
2
  verts: number[][];
3
3
  tris: number[][];
4
4
  }
5
- export declare function build3mf(meshes: Mesh[]): Buffer;
5
+ export interface Object3mf {
6
+ name: string;
7
+ mesh: Mesh;
8
+ }
9
+ export declare function build3mf(objects: Object3mf[]): Buffer;
6
10
  export declare function writeBinaryStl(mesh: Mesh): Buffer;
7
11
  //# sourceMappingURL=threemf.d.ts.map
package/threemf.js CHANGED
@@ -6,17 +6,22 @@
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, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;");
11
+ }
9
12
  // ---------- multi-object 3MF (core spec, no slicer settings) ----------
10
- export function build3mf(meshes) {
11
- // separate top-level objects (each individually colourable in Bambu), all
12
- // at the same transform so the labels stay seated in the plate's pockets.
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
- meshes.forEach((m, i) => {
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"?>