pini-print-bot / engine /optimizer.js
dotandru's picture
Phase 1.3 Final Hardening - Transparency & Business Truth
6a25ae8
Raw
History Blame Contribute Delete
2.99 kB
/**
* engine/optimizer.js
* ืžื ื•ืข ืื™ืžืคื•ื–ื™ืฆื™ื” (Imposition Engine) - V2 (Hardened)
* ====================================
* ืชืคืงื™ื“: ืœื—ืฉื‘ ืžืชืžื˜ื™ืช ื›ืžื” ื™ื—ื™ื“ื•ืช ื ื›ื ืกื•ืช ื‘ื’ื™ืœื™ื•ืŸ SRA3.
* ืชื•ืงืŸ: ื”ื•ืกืคืช Epsilon Guard ืœืžื ื™ืขืช ื—ื™ืชื•ืš ืฉื’ื•ื™ ื‘ื’ืœืœ Floating Point (ืœืžืฉืœ ืžื•ื ืข ืž-2.99999998 ืœื”ืคื•ืš ืœ-2).
*/
const MACHINE_SPECS = {
// HP Indigo 7K specs (SRA3)
sheetWidth: 320, // mm
sheetHeight: 450, // mm
margins: 5, // mm (safety margin for gripper/bleed)
gutter: 2 // mm (space between cuts)
};
/**
* ืคื•ื ืงืฆื™ื™ืช ืขื–ืจ ืœื—ืœื•ืงื” ืžื•ื’ื ืช ืžืคื ื™ ืฉื’ื™ืื•ืช Floating Point
*/
function safeFloorDivision(total, part) {
if (part <= 0) return 0;
// Number.EPSILON ืžื—ืคื” ื‘ื“ื™ื•ืง ืขืœ ื”ืžืกืคืจื™ื ืฉื™ื•ืจื“ื™ื ื‘-0.000000001
return Math.floor((total / part) + Number.EPSILON);
}
/**
* ื—ื™ืฉื•ื‘ ืคืจื™ืกื” ืื•ืคื˜ื™ืžืœื™ืช (Best Fit)
* @param {number} prodWidth - ืจื•ื—ื‘ ืžื•ืฆืจ ื‘ืž"ืž
* @param {number} prodHeight - ื’ื•ื‘ื” ืžื•ืฆืจ ื‘ืž"ืž
* @returns {object} { ups, layout, efficiency }
*/
function calculateImposition(prodWidth, prodHeight) {
// ืฉื˜ื— ื ื˜ื• ืœื”ื“ืคืกื” (ืœืื—ืจ ื”ืคื—ืชืช ืฉื•ืœื™ ืžื›ื•ื ื”)
const safeW = MACHINE_SPECS.sheetWidth - (MACHINE_SPECS.margins * 2);
const safeH = MACHINE_SPECS.sheetHeight - (MACHINE_SPECS.margins * 2);
// ื”ื’ื ื” ืžืคื ื™ ืงืœื˜ ืœื ืชืงื™ืŸ
if (!prodWidth || !prodHeight || prodWidth <= 0 || prodHeight <= 0) {
return { ups: 0, layout: 'error', efficiency: 0 };
}
// ืื•ืคืฆื™ื” ื': ื™ืฉืจ (Portrait)
const fitW_A = safeFloorDivision(safeW, prodWidth + MACHINE_SPECS.gutter);
const fitH_A = safeFloorDivision(safeH, prodHeight + MACHINE_SPECS.gutter);
const total_A = fitW_A * fitH_A;
// ืื•ืคืฆื™ื” ื‘': ืžืกื•ื‘ื‘ (Landscape)
const fitW_B = safeFloorDivision(safeW, prodHeight + MACHINE_SPECS.gutter);
const fitH_B = safeFloorDivision(safeH, prodWidth + MACHINE_SPECS.gutter);
const total_B = fitW_B * fitH_B;
// ื‘ื—ื™ืจืช ื”ืžื ืฆื— (ืื™ืคื” ื ื›ื ืกื™ื ื™ื•ืชืจ?)
const maxUps = Math.max(total_A, total_B);
const bestLayout = total_A >= total_B ? 'portrait' : 'landscape';
// ื˜ื™ืคื•ืœ ืืœื’ื ื˜ื™ ื‘ืžื•ืฆืจ ื’ื“ื•ืœ ืžื“ื™ (ื”ื’ื ื” ืขืœ ื”ืžืขืจื›ืช ืžืงืจืฉ ืขืœ ื—ืœื•ืงื” ื‘ืืคืก)
if (maxUps === 0) {
return { ups: 0, layout: 'error', efficiency: "0.0%" };
}
// ื—ื™ืฉื•ื‘ ืื—ื•ื– ื ื™ืฆื•ืœ ื”ื ื™ื™ืจ (Efficiency) - ื”ืขืจื”: ืžื—ื•ืฉื‘ ืžื•ืœ ืฉื˜ื— ื‘ืจื•ื˜ื• ื‘ื›ื•ื•ื ื” ืชื—ื™ืœื”
const usedArea = maxUps * prodWidth * prodHeight;
const totalArea = MACHINE_SPECS.sheetWidth * MACHINE_SPECS.sheetHeight;
const efficiencyNum = ((usedArea / totalArea) * 100).toFixed(1);
return {
ups: maxUps,
layout: bestLayout,
efficiency: efficiencyNum + "%"
};
}
module.exports = { calculateImposition };