@bobfrankston/wallplater 1.0.4 → 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 CHANGED
@@ -41,11 +41,13 @@ wallplater ../KitchenBack.json # if installed globally
41
41
  - Outputs are written next to the **config file**, named after the config's `name`.
42
42
  - Unknown flags are rejected. `-scad` (or `--scad`) selects the OpenSCAD engine.
43
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.
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.
49
51
 
50
52
  ## Config
51
53
 
@@ -72,9 +74,10 @@ Lengths are written **CSS-style with a unit suffix**: `"0.375in"`, `"5mm"`,
72
74
 
73
75
  - `gangs[].type`: `"decora"` | `"duplex"` | `"blank"`.
74
76
  - `gangs[].screws`: `false` to omit that gang's screw holes (default true).
75
- - `gangs[].legend`: single label centred below the gang.
76
- - `gangs[].breaker`: per-gang breaker/circuit label, centred just below that
77
- gang's legend (size/position from `gangBreakerSize` / `gangBreakerY`).
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`).
78
81
  - `breaker`: `{ "switch": "A8", "size": "0.375in", "inset": "0.25in" }` — the
79
82
  panel-wide breaker label in the lower-right corner. `switch` is the text;
80
83
  `size`/`inset` come from `defaults.json`, so a config usually sets only
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": "-42mm",
6
- "gangBreakerSize": "4mm",
7
- "gangBreakerY": "-50mm",
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, below the legend
98
- accent.push({ txt: g.breaker, size: cfg.gangBreakerSize, halign: "center", x: gx, y: cfg.gangBreakerY });
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
@@ -22,6 +22,10 @@ import { build3mf, writeBinaryStl } from "./threemf.js";
22
22
  function partName(cfgName, label) {
23
23
  return `${cfgName} ${label === "legend" ? "labels" : label}`;
24
24
  }
25
+ // Filament/extruder per part: plate -> 1, all accent text -> 2.
26
+ function partExtruder(label) {
27
+ return label === "plate" ? 1 : 2;
28
+ }
25
29
  function partList(cfg) {
26
30
  const parts = [{ mode: "plate", label: "plate" }];
27
31
  if (cfg.gangs.some((g) => (g.legend ?? "") !== ""))
@@ -49,7 +53,7 @@ function runScadEngine(cfg, dir) {
49
53
  const stl = join(dir, `_${cfg.name}_${p.label}.stl`);
50
54
  runOpenscad(cfg.openscad, scadPath, stl, p.mode);
51
55
  tmp.push(stl);
52
- return { name: partName(cfg.name, p.label), mesh: readStl(stl) };
56
+ return { name: partName(cfg.name, p.label), mesh: readStl(stl), extruder: partExtruder(p.label) };
53
57
  });
54
58
  writeFileSync(join(dir, `${cfg.name}.3mf`), build3mf(objects));
55
59
  for (const t of tmp)
@@ -70,7 +74,7 @@ async function runManifoldEngine(cfg, dir) {
70
74
  writeFileSync(join(dir, `${cfg.name}_${m.label}.stl`), writeBinaryStl(m.mesh));
71
75
  }
72
76
  else {
73
- const objects = meshes.map((m) => ({ name: partName(cfg.name, m.label), mesh: m.mesh }));
77
+ const objects = meshes.map((m) => ({ name: partName(cfg.name, m.label), mesh: m.mesh, extruder: partExtruder(m.label) }));
74
78
  writeFileSync(join(dir, `${cfg.name}.3mf`), build3mf(objects));
75
79
  console.log(" wrote", `${cfg.name}.3mf`, `(${objects.length} objects: ${objects.map((o) => o.name).join(", ")})`);
76
80
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@bobfrankston/wallplater",
3
- "version": "1.0.4",
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
@@ -5,6 +5,7 @@ export interface Mesh {
5
5
  export interface Object3mf {
6
6
  name: string;
7
7
  mesh: Mesh;
8
+ extruder?: number;
8
9
  }
9
10
  export declare function build3mf(objects: Object3mf[]): Buffer;
10
11
  export declare function writeBinaryStl(mesh: Mesh): Buffer;
package/threemf.js CHANGED
@@ -26,11 +26,22 @@ export function build3mf(objects) {
26
26
  });
27
27
  const model = `<?xml version="1.0" encoding="UTF-8"?>
28
28
  <model unit="millimeter" xml:lang="en-US" xmlns="http://schemas.microsoft.com/3dmanufacturing/core/2015/02">
29
+ <metadata name="Application">wallplater</metadata>
29
30
  <resources>
30
31
  ${objs} </resources>
31
32
  <build>
32
33
  ${items} </build>
33
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>
34
45
  `;
35
46
  const ct = `<?xml version="1.0" encoding="UTF-8"?>
36
47
  <Types xmlns="http://schemas.openxmlformats.org/package/2006/content-types">
@@ -47,6 +58,7 @@ ${items} </build>
47
58
  { name: "[Content_Types].xml", data: Buffer.from(ct) },
48
59
  { name: "_rels/.rels", data: Buffer.from(rels) },
49
60
  { name: "3D/3dmodel.model", data: Buffer.from(model) },
61
+ { name: "Metadata/model_settings.config", data: Buffer.from(settings) },
50
62
  ]);
51
63
  }
52
64
  // ---------- binary STL (single object) ----------