@richardmcquiston01/unofficial-xcs-writer 0.7.0 → 0.8.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/CHANGELOG.md CHANGED
@@ -5,6 +5,28 @@ All notable changes to this project will be documented in this file.
5
5
  The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
6
6
  and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
7
 
8
+ ## [Unreleased]
9
+
10
+ ## [0.8.0] - 2026-09-21
11
+
12
+ ### Added
13
+
14
+ - **Machine catalog** (`src/machines.ts`): `XCSGeneratorOptions.deviceId`/`createXCS`'s argument
15
+ now accepts a `XTOOL_MACHINES` key -- currently 13 machines verified from real exports in this
16
+ repo (`P2S`, `F2 Ultra UV`, `S1`, `P3`, `M2`, `F2`, `F2 Ultra (Single)`, `F2 Ultra`, `M1 Ultra`,
17
+ `F1`, `F1 Lite`, `F1 Ultra`, `MetalFab`) -- and resolves that machine's real `extId`/`extName`/
18
+ `deviceCode` (`.xs` only)/default power, instead of reusing whatever string you pass for all of
19
+ them. A `MachineProfile` object can also be supplied directly for a machine not yet in the
20
+ catalog. Existing raw device id strings still behave exactly as before (unaffected, since they
21
+ simply don't match a catalog key).
22
+
23
+ ### Changed
24
+
25
+ - `createXCS()`/`new XCSGenerator()` with no `deviceId` (defaulting to `"P2S"`) now embeds the
26
+ real `deviceCode: "ZY013"` in `.toXsBytes()` output, instead of the previous placeholder
27
+ (`file.device.id`, i.e. `"P2S"`). `.xcs` output (`.toBytes()`) is unaffected -- it has no
28
+ `deviceCode` field.
29
+
8
30
  ## [0.7.0] - 2026-09-21
9
31
 
10
32
  ### Added
package/README.md CHANGED
@@ -113,6 +113,27 @@ and undocumented beyond real exports — see `xs_samples/*.xs`'s `profiles.json`
113
113
  option behave as before: a generated `.xs` file's power/speed may need to be set manually in
114
114
  xTool Studio before cutting/engraving.
115
115
 
116
+ `createXCS('P2S')`'s argument (and `XCSGeneratorOptions.deviceId`) accepts a known machine name
117
+ from `XTOOL_MACHINES` — currently 13 machines verified from real exports in this repo (`"P2S"`,
118
+ `"F2 Ultra UV"`, `"S1"`, `"P3"`, `"M2"`, `"F2"`, `"F2 Ultra (Single)"`, `"F2 Ultra"`,
119
+ `"M1 Ultra"`, `"F1"`, `"F1 Lite"`, `"F1 Ultra"`, `"MetalFab"`) — and resolves the real
120
+ `extId`/`extName`/`deviceCode` (`.xs` only)/default power for that machine, instead of reusing
121
+ whatever string you pass for all of them. Export the same design for multiple machines by
122
+ building it once and calling `.toXsBytes()`/`.toBytes()` once per `createXCS(machineName)`:
123
+
124
+ ```ts
125
+ import { createXCS, XTOOL_MACHINES } from '@richardmcquiston01/unofficial-xcs-writer';
126
+
127
+ for (const machine of Object.keys(XTOOL_MACHINES)) {
128
+ const bytes = createXCS(machine).addPath('M0 0L10 0L10 10L0 10Z', 0, 0, 10, 10).toXsBytes();
129
+ // write `bytes` to e.g. `design-${machine}.xs`
130
+ }
131
+ ```
132
+
133
+ A device id not in the catalog still works exactly as before (no `.xs` `deviceCode`, `extId`/
134
+ `extName`/`device.id` all set to that string, default power `55`); you can also pass a
135
+ `MachineProfile` object directly for a machine not yet in the catalog.
136
+
116
137
  ## API
117
138
 
118
139
  ### `assertXcsFormat(buffer: ArrayBuffer): void`
@@ -175,9 +196,9 @@ Packages an already-`generate()`d project (see Building, below) into a `.xs` v2
175
196
 
176
197
  Lower-level glyph-extraction primitives everything else in this library is built on, exported for direct use. See `src/glyphs.ts` for the exact xTool JSON conventions these were reverse-engineered against.
177
198
 
178
- ### Building (`src/builder.ts`, `src/layout.ts`)
199
+ ### Building (`src/builder.ts`, `src/layout.ts`, `src/machines.ts`)
179
200
 
180
- `createXCS(deviceId?)` / `new XCSGenerator(options?)` — starts a new project (one canvas, one default layer). Chainable methods:
201
+ `createXCS(deviceId?)` / `new XCSGenerator(options?)` — starts a new project (one canvas, one default layer). `deviceId` (and `options.deviceId`) accepts a `XTOOL_MACHINES` key, a `MachineProfile` object, or a raw device id string — see "Building a project from scratch" above. Chainable methods:
181
202
 
182
203
  | Method | Adds |
183
204
  |--------|------|
package/dist/index.cjs CHANGED
@@ -31,6 +31,7 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
31
31
  var index_exports = {};
32
32
  __export(index_exports, {
33
33
  XCSGenerator: () => XCSGenerator,
34
+ XTOOL_MACHINES: () => XTOOL_MACHINES,
34
35
  assertXcsFormat: () => assertXcsFormat,
35
36
  assertXsFormat: () => assertXsFormat,
36
37
  buildXsArchive: () => buildXsArchive,
@@ -416,7 +417,7 @@ function coverResourceEntries(coverDataUrl) {
416
417
  })
417
418
  };
418
419
  }
419
- function buildXsArchive(file, processingBindings = []) {
420
+ function buildXsArchive(file, processingBindings = [], deviceCode = file.device.id) {
420
421
  const canvas = file.canvas[0];
421
422
  if (!canvas) {
422
423
  throw new Error("Cannot build a .xs archive from a project with no canvas.");
@@ -478,7 +479,7 @@ function buildXsArchive(file, processingBindings = []) {
478
479
  }),
479
480
  [`devices/device-${file.device.id}.json`]: encodeJson({
480
481
  id: file.device.id,
481
- deviceCode: file.device.id,
482
+ deviceCode,
482
483
  extId: file.extId,
483
484
  extName: file.extName,
484
485
  power: [file.device.power],
@@ -766,6 +767,101 @@ function layoutCurvedGlyphText(font, text, emSizeMm, curveDeg, boxWidthMm, boxHe
766
767
  };
767
768
  }
768
769
 
770
+ // src/machines.ts
771
+ var XTOOL_MACHINES = {
772
+ P2S: {
773
+ name: "P2S",
774
+ extId: "P2S",
775
+ extName: "P2S",
776
+ deviceCode: "ZY013",
777
+ defaultPower: 55
778
+ },
779
+ "F2 Ultra UV": {
780
+ name: "F2 Ultra UV",
781
+ extId: "GS009-CLASS-4",
782
+ extName: "F2 Ultra UV",
783
+ deviceCode: "GS009-CLASS-4",
784
+ defaultPower: 5
785
+ },
786
+ S1: {
787
+ name: "S1",
788
+ extId: "S1",
789
+ extName: "S1",
790
+ deviceCode: "MD2",
791
+ defaultPower: 40
792
+ },
793
+ P3: {
794
+ name: "P3",
795
+ extId: "P3",
796
+ extName: "P3",
797
+ deviceCode: "ZY015",
798
+ defaultPower: 80
799
+ },
800
+ M2: {
801
+ name: "M2",
802
+ extId: "JS002",
803
+ extName: "M2",
804
+ deviceCode: "JS002",
805
+ defaultPower: 10
806
+ },
807
+ F2: {
808
+ name: "F2",
809
+ extId: "GS006",
810
+ extName: "F2",
811
+ deviceCode: "GS006",
812
+ defaultPower: 5
813
+ },
814
+ "F2 Ultra (Single)": {
815
+ name: "F2 Ultra (Single)",
816
+ extId: "GS007-CLASS-4",
817
+ extName: "F2 Ultra (Single)",
818
+ deviceCode: "GS007-CLASS-4",
819
+ defaultPower: 60
820
+ },
821
+ "F2 Ultra": {
822
+ name: "F2 Ultra",
823
+ extId: "GS004-CLASS-4",
824
+ extName: "F2 Ultra",
825
+ deviceCode: "GS004-CLASS-4",
826
+ defaultPower: 60
827
+ },
828
+ "M1 Ultra": {
829
+ name: "M1 Ultra",
830
+ extId: "M1Ultra",
831
+ extName: "M1 Ultra",
832
+ deviceCode: "ZH009",
833
+ defaultPower: 20
834
+ },
835
+ F1: {
836
+ name: "F1",
837
+ extId: "F1",
838
+ extName: "F1",
839
+ deviceCode: "MF1",
840
+ defaultPower: 10
841
+ },
842
+ "F1 Lite": {
843
+ name: "F1 Lite",
844
+ extId: "GS005",
845
+ extName: "F1 Lite",
846
+ deviceCode: "GS005",
847
+ defaultPower: 10
848
+ },
849
+ "F1 Ultra": {
850
+ name: "F1 Ultra",
851
+ extId: "F1Ultra",
852
+ extName: "F1 Ultra",
853
+ deviceCode: "GS002",
854
+ defaultPower: 20
855
+ },
856
+ MetalFab: {
857
+ name: "MetalFab",
858
+ extId: "HJ003",
859
+ extName: "MetalFab CNC Cutter",
860
+ deviceCode: "HJ003",
861
+ defaultPower: 1200
862
+ }
863
+ };
864
+
769
865
  // src/builder.ts
770
866
  var XCSGenerator = class {
771
867
  canvasId;
@@ -773,12 +869,20 @@ var XCSGenerator = class {
773
869
  displays = [];
774
870
  layers = /* @__PURE__ */ new Map();
775
871
  options;
872
+ /** From `XTOOL_MACHINES`/a supplied `MachineProfile`; `deviceId` itself when the machine isn't known. */
873
+ extName;
874
+ /** `.xs`-only; unset when the machine's real `deviceCode` isn't verified (see `src/machines.ts`). */
875
+ deviceCode;
776
876
  /** Keyed by `JSON.stringify([processingType, values])` to merge displays sharing identical settings into one profile. */
777
877
  processingBindings = /* @__PURE__ */ new Map();
778
878
  constructor(options = {}) {
879
+ const machine = this.resolveMachine(options.deviceId);
880
+ const deviceId = machine?.extId ?? (typeof options.deviceId === "string" ? options.deviceId : "P2S");
881
+ this.extName = machine?.extName ?? deviceId;
882
+ this.deviceCode = machine?.deviceCode;
779
883
  this.options = {
780
- deviceId: options.deviceId || "P2S",
781
- devicePower: options.devicePower || 55,
884
+ deviceId,
885
+ devicePower: options.devicePower || machine?.defaultPower || 55,
782
886
  canvasWidth: options.canvasWidth || 400,
783
887
  canvasHeight: options.canvasHeight || 400
784
888
  };
@@ -786,6 +890,11 @@ var XCSGenerator = class {
786
890
  this.canvas = this.createCanvas();
787
891
  this.addLayer("#00befe", "{Cyan}", 1);
788
892
  }
893
+ /** Looks `deviceId` up in `XTOOL_MACHINES` by name, passes a supplied `MachineProfile` through, or returns `undefined` for a raw/unknown device id (including the "no option given" default of `"P2S"`). */
894
+ resolveMachine(deviceId) {
895
+ if (typeof deviceId === "object") return deviceId;
896
+ return XTOOL_MACHINES[deviceId ?? "P2S"];
897
+ }
789
898
  /**
790
899
  * Add a text object to the canvas. Pass `layout` (built via
791
900
  * `layoutGlyphText`/`layoutMultilineGlyphText`/`layoutCurvedGlyphText`
@@ -914,7 +1023,7 @@ var XCSGenerator = class {
914
1023
  canvasId: this.canvasId,
915
1024
  canvas: [this.canvas],
916
1025
  extId: this.options.deviceId,
917
- extName: this.options.deviceId,
1026
+ extName: this.extName,
918
1027
  device: {
919
1028
  id: this.options.deviceId,
920
1029
  power: this.options.devicePower,
@@ -952,13 +1061,19 @@ var XCSGenerator = class {
952
1061
  }
953
1062
  /**
954
1063
  * Export as a `.xs` (v2 workspace) ZIP archive instead of `.xcs` --
955
- * see `buildXsArchive` in `src/xs.ts` for the container format and
956
- * its limitations (no processing profiles/device bindings yet).
1064
+ * see `buildXsArchive` in `src/xs.ts` for the container format.
957
1065
  * Display objects themselves carry over unchanged; only the
958
- * container differs from `toBytes()`.
1066
+ * container differs from `toBytes()`. `deviceCode` comes from
1067
+ * `XTOOL_MACHINES`/a supplied `MachineProfile` (see
1068
+ * `XCSGeneratorOptions.deviceId`) when known, else `buildXsArchive`
1069
+ * falls back to the device id itself, as before.
959
1070
  */
960
1071
  toXsBytes() {
961
- return buildXsArchive(this.generate(), Array.from(this.processingBindings.values()));
1072
+ return buildXsArchive(
1073
+ this.generate(),
1074
+ Array.from(this.processingBindings.values()),
1075
+ this.deviceCode
1076
+ );
962
1077
  }
963
1078
  /** Merges a display into an existing profile when another display already shares its exact `processing` settings, else starts a new one. */
964
1079
  registerProcessing(displayId, processing) {
@@ -1208,6 +1323,7 @@ function createXCS(deviceId = "P2S") {
1208
1323
  // Annotate the CommonJS export names for ESM import in node:
1209
1324
  0 && (module.exports = {
1210
1325
  XCSGenerator,
1326
+ XTOOL_MACHINES,
1211
1327
  assertXcsFormat,
1212
1328
  assertXsFormat,
1213
1329
  buildXsArchive,