Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
57 changes: 54 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,13 @@ npm install empf-generator
# Example

```js
const {EmpfGenerator, InkModeEnum, PrintBedEnum} = require("empf-generator");
const {
CraftModeEnum,
EmpfExportFormatEnum,
EmpfGenerator,
InkModeEnum,
PrintBedEnum
} = require("empf-generator");
const fs = require("fs");

const imagePath = "./image.png";
Expand All @@ -22,6 +28,8 @@ const outPath = "./output.empf";
(async () => {
const image = fs.readFileSync(imagePath);
const generator = new EmpfGenerator({
craftMode: CraftModeEnum.reliefTexture,
exportFormat: EmpfExportFormatEnum.studioEncrypted,
printBed: PrintBedEnum.standardFlatbed,
projectName: "Test Project"
});
Expand Down Expand Up @@ -60,6 +68,8 @@ Creates a new EmpfGenerator.
| [options.printBed] | <code>PrintBedEnum</code> | <code>PrintBedEnum.standardFlatbed</code> | The print bed to use. |
| [options.projectName] | <code>string</code> | <code>&quot;Untitled Design&quot;</code> | The name of the project. |
| [options.canvasBackground] | <code>string</code> | <code>&quot;#ffffff&quot;</code> | The canvas background color, as a hex code. |
| [options.craftMode] | <code>CraftModeEnum</code> | <code>CraftModeEnum.flat</code> | The craft mode to mark in the project metadata. |
| [options.exportFormat] | <code>EmpfExportFormatEnum</code> | <code>EmpfExportFormatEnum.zip</code> | The EMPF container format to export. |

<a name="EmpfGenerator+addImage"></a>

Expand Down Expand Up @@ -91,14 +101,55 @@ Adds an image to the canvas.

<a name="EmpfGenerator+export"></a>

### empfGenerator.export(outPath)
### empfGenerator.export(outPath, [options])
Exports the canvas to a .empf file.

**Kind**: instance method of [<code>EmpfGenerator</code>](#EmpfGenerator)

| Param | Type | Description |
| --- | --- | --- |
| outPath | <code>string</code> | The output path to export to. |
| [options] | <code>Object</code> | Export options. |
| [options.exportFormat] | <code>EmpfExportFormatEnum</code> | Override the EMPF container format for this export. |

<a name="CraftModeEnum"></a>

## CraftModeEnum : <code>enum</code>
Enum for eufyMake craft modes.

**Kind**: global enum
**Read only**: true
**Properties**

| Name | Type | Default |
| --- | --- | --- |
| flat | <code>number</code> | <code>-1</code> |
| reliefTexture | <code>number</code> | <code>0</code> |
| texture | <code>number</code> | <code>1</code> |
| textureRelief | <code>number</code> | <code>2</code> |
| brushStrokes | <code>number</code> | <code>3</code> |
| poster | <code>number</code> | <code>4</code> |
| sticker | <code>number</code> | <code>5</code> |
| gild | <code>number</code> | <code>6</code> |
| raised | <code>number</code> | <code>7</code> |

<a name="EmpfExportFormatEnum"></a>

## EmpfExportFormatEnum : <code>enum</code>
Enum for EMPF container formats.

**Kind**: global enum
**Read only**: true
**Properties**

| Name | Type | Default |
| --- | --- | --- |
| zip | <code>string</code> | <code>&quot;zip&quot;</code> |
| studioEncrypted | <code>string</code> | <code>&quot;studioEncrypted&quot;</code> |

`studioEncrypted` wraps the generated ZIP in the `eufyMake` AES-GCM envelope
observed in current eufyMake Studio exports. Older Studio builds may still
accept the default plain ZIP-style EMPF files.

<a name="InkModeEnum"></a>

Expand Down Expand Up @@ -133,4 +184,4 @@ Enum for print beds.
| Name | Type | Default |
| --- | --- | --- |
| standardFlatbed | <code>string</code> | <code>&quot;standardFlatbed&quot;</code> |
| miniFlatbed | <code>string</code> | <code>&quot;miniFlatbed&quot;</code> |
| miniFlatbed | <code>string</code> | <code>&quot;miniFlatbed&quot;</code> |
86 changes: 79 additions & 7 deletions src/classes/EmpfGenerator.js
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,8 @@ const JSZip = require("jszip");
const {v4: uuidv4} = require("uuid");

const CanvasObjectEnum = require("../enums/CanvasObjectEnum.js");
const CraftModeEnum = require("../enums/CraftModeEnum.js");
const EmpfExportFormatEnum = require("../enums/EmpfExportFormatEnum.js");
const InkModeEnum = require("../enums/InkModeEnum.js");
const PrintBedConstants = require("../constants/PrintBedConstants.js");
const PrintBedEnum = require("../enums/PrintBedEnum.js");
Expand All @@ -35,6 +37,25 @@ const mmToE1Units = require("../util/mmToE1Units.js");

// For ease of development, all units are kept in mm until export, when they are converted to E1 units.

const STUDIO_AES_GCM_KEY = Buffer.from("ab24ba760a896cd89eb9e15a9caec7fa");
const STUDIO_AES_GCM_NONCE_LENGTH = 12;
const STUDIO_WRAPPER_FIELDS = [
[1, Buffer.from([1])],
[3, Buffer.from([1])],
[4, Buffer.concat([Buffer.from("offline"), Buffer.alloc(33)])],
[6, Buffer.from("application/zip")],
[5, Buffer.from("file")]
];

const CraftModeTagType = {
[CraftModeEnum.flat]: 0,
[CraftModeEnum.texture]: 1,
[CraftModeEnum.reliefTexture]: 2,
[CraftModeEnum.textureRelief]: 3,
[CraftModeEnum.raised]: 4,
[CraftModeEnum.gild]: 8
};

/**
* Class for generating .empf files
*/
Expand All @@ -46,14 +67,20 @@ class EmpfGenerator {
* @param {PrintBedEnum} [options.printBed=PrintBedEnum.standardFlatbed] - The print bed to use.
* @param {string} [options.projectName=Untitled Design] - The name of the project.
* @param {string} [options.canvasBackground=#ffffff] - The canvas background color, as a hex code.
* @param {CraftModeEnum} [options.craftMode=CraftModeEnum.flat] - The craft mode to mark in the project metadata.
* @param {EmpfExportFormatEnum} [options.exportFormat=EmpfExportFormatEnum.zip] - The EMPF container format to export.
*/
constructor({
printBed = PrintBedEnum.standardFlatbed,
projectName = "Untitled Design",
canvasBackground = "#ffffff"
canvasBackground = "#ffffff",
craftMode = CraftModeEnum.flat,
exportFormat = EmpfExportFormatEnum.zip
}) {
this._printBed = printBed;
this._projectName = projectName;
this._craftMode = craftMode;
this._exportFormat = exportFormat;

this._version = "5.3.0";
this._canvasObjects = [];
Expand Down Expand Up @@ -146,15 +173,26 @@ class EmpfGenerator {
/**
* Exports the canvas to a .empf file.
* @param {string} outPath - The output path to export to.
* @param {Object} [options] - Export options.
* @param {EmpfExportFormatEnum} [options.exportFormat] - Override the EMPF container format for this export.
*/
async export(outPath) {
async export(outPath, options = {}) {
const exportFormat = options.exportFormat ?? this._exportFormat;
const zipBuf = await this._generateZipBuffer();
const outputBuf = exportFormat === EmpfExportFormatEnum.studioEncrypted
? encryptStudioPayload(zipBuf)
: zipBuf;

await fs.writeFile(outPath, outputBuf);
}

async _generateZipBuffer() {
const zip = new JSZip();
zip.file("Asset/font/font_mapping.json", "{}");
zip.file(`Asset/project_file/canvas_${this._canvasID}.json`, JSON.stringify(this._generateCanvasJSON()));
zip.file("Metadata/project_info.json", JSON.stringify(this._generateProjectInfoJSON()));

const zipBuf = await zip.generateAsync({type: "nodebuffer"});
await fs.writeFile(outPath, zipBuf);
return zip.generateAsync({type: "nodebuffer"});
}

get _printBedData() {
Expand Down Expand Up @@ -245,6 +283,7 @@ class EmpfGenerator {

_generateProjectInfoJSON() {
const currentTimestamp = Math.floor(Date.now() / 1000);
const tagType = craftModeToTagType(this._craftMode);
return {
canvases: [{
base_map: this._printBedData.e1Data.base_map,
Expand All @@ -257,14 +296,16 @@ class EmpfGenerator {
is_standard_product: this._printBedData.e1Data.is_standard_product,
create_time: currentTimestamp,
update_time: currentTimestamp,
extra: '{"cutData":"","appCavas":"","pcCavas":"","canvasShape":null,"bleedingLine":null}',
tag_type: tagType,
extra: '{"cutData":"","appCavas":"","pcCavas":"","canvasShape":null,"bleedingLine":null,"new_func_tag":"00"}',
material_list: [],
model_link: "",
project_id: this._projectID,
print_param: JSON.stringify({
printModel: 2,
imgQuality: 300,
printLayerData: [],
subPrintType: this._craftMode,
format_size_w: Math.round(e1UnitsToMM(this._printBedData.e1Data.base_map_width)),
format_size_h: Math.round(e1UnitsToMM(this._printBedData.e1Data.base_map_height)),
format_size_w_non: Math.round(e1UnitsToMM(this._printBedData.e1Data.base_map_width)),
Expand Down Expand Up @@ -293,12 +334,43 @@ class EmpfGenerator {
works_status: 0,
project_desc: "",
project_type: 1,
tag_type: 0,
tag_type: tagType,
thumb_file: null // this is inconsistent with eufyMake Studio, hopefully it doesn't break things
},
canvasesIndex: 0
};
}
}

module.exports = EmpfGenerator;
function craftModeToTagType(craftMode) {
return CraftModeTagType[craftMode] ?? 0;
}

function encryptStudioPayload(zipBuf) {
const nonce = crypto.randomBytes(STUDIO_AES_GCM_NONCE_LENGTH);
const cipher = crypto.createCipheriv("aes-256-gcm", STUDIO_AES_GCM_KEY, nonce);
const ciphertext = Buffer.concat([cipher.update(zipBuf), cipher.final()]);
return Buffer.concat([
createStudioWrapperHeader(),
nonce,
ciphertext,
cipher.getAuthTag()
]);
}

function createStudioWrapperHeader() {
const fieldBuffers = STUDIO_WRAPPER_FIELDS.map(([tag, value]) => {
const field = Buffer.alloc(3 + value.length);
field.writeUInt8(tag, 0);
field.writeUInt16BE(value.length, 1);
value.copy(field, 3);
return field;
});
const headerLength = 12 + fieldBuffers.reduce((sum, field) => sum + field.length, 0);
const header = Buffer.alloc(12);
header.write("eufyMake", 0, "ascii");
header.writeUInt32BE(headerLength, 8);
return Buffer.concat([header, ...fieldBuffers]);
}

module.exports = EmpfGenerator;
22 changes: 22 additions & 0 deletions src/enums/CraftModeEnum.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
"use strict";

// Corresponds to subPrintType in Metadata/project_info.json canvas print_param.

/**
* Enum for eufyMake craft modes.
* @readonly
* @enum {number}
*/
const CraftModeEnum = {
flat: -1,
reliefTexture: 0,
texture: 1,
textureRelief: 2,
brushStrokes: 3,
poster: 4,
sticker: 5,
gild: 6,
raised: 7
};

module.exports = CraftModeEnum;
13 changes: 13 additions & 0 deletions src/enums/EmpfExportFormatEnum.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
"use strict";

/**
* Enum for EMPF container formats.
* @readonly
* @enum {string}
*/
const EmpfExportFormatEnum = {
zip: "zip",
studioEncrypted: "studioEncrypted"
};

module.exports = EmpfExportFormatEnum;
4 changes: 3 additions & 1 deletion src/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,8 @@ along with this program. If not, see <https://www.gnu.org/licenses/>.
module.exports = {
EmpfGenerator: require("./classes/EmpfGenerator.js"),

CraftModeEnum: require("./enums/CraftModeEnum.js"),
EmpfExportFormatEnum: require("./enums/EmpfExportFormatEnum.js"),
InkModeEnum: require("./enums/InkModeEnum.js"),
PrintBedEnum: require("./enums/PrintBedEnum.js")
};
};