Gap
@@ -87,10 +83,10 @@ export default function Page() {
From LPI
- {num(lpi) > 0 ? (UM_PER_INCH / num(lpi) / 1000).toFixed(4) : "0.0000"} mm
+ {(UM_PER_INCH / Math.max(1, num(lpi)) / 1000).toFixed(4)} mm
- {num(lpi) > 0 ? (UM_PER_INCH / num(lpi)).toFixed(1) : "0.0"} µm
+ {(UM_PER_INCH / Math.max(1, num(lpi))).toFixed(1)} µm
@@ -98,3 +94,4 @@ export default function Page() {
);
}
+
diff --git a/app/components/utilities/laser-toolkit/job-time-estimator/page.tsx b/app/components/utilities/laser-toolkit/job-time-estimator/page.tsx
index ec28886..24a1fd2 100644
--- a/app/components/utilities/laser-toolkit/job-time-estimator/page.tsx
+++ b/app/components/utilities/laser-toolkit/job-time-estimator/page.tsx
@@ -44,7 +44,7 @@ export default function Page() {
if (w <= 0 || h <= 0 || D <= 0 || v <= 0) return { t: 0, gapMm: 0, gapUm: 0, rows: 0 };
const gapMm = 25.4 / D;
const gapUm = gapMm * 1000;
- const rows = Math.max(1, Math.ceil(h / gapMm));
+ const rows = h / gapMm;
const t = rows * (w / v) * p * k;
return { t, gapMm, gapUm, rows };
} else {
@@ -159,7 +159,7 @@ export default function Page() {
{/* Footnote */}
Overhead factor* accounts for real-world slowdowns:
- acceleration/deceleration, jump moves, polygon delays, laser on/off timing, overscan,
+ acceleration/decelleration, jump moves, polygon delays, laser on/off timing, overscan,
bidirectional settle time, and controller latency.{" "}
Typical values: Vector cuts/marks{" "}
1.05–1.15 (simple paths, long runs closer to 1.05; tiny
@@ -171,3 +171,4 @@ export default function Page() {
);
}
+
diff --git a/app/components/utilities/laser-toolkit/power-lens-scaler/page.tsx b/app/components/utilities/laser-toolkit/power-lens-scaler/page.tsx
index 3fdb31b..6113608 100644
--- a/app/components/utilities/laser-toolkit/power-lens-scaler/page.tsx
+++ b/app/components/utilities/laser-toolkit/power-lens-scaler/page.tsx
@@ -18,10 +18,33 @@ function clamp(v: number, lo: number, hi: number) {
return Math.max(lo, Math.min(hi, v));
}
-/** Circular spot-area ratio; pi/4 cancels. */
-function areaFactorFromDiameter(spotSrc: number, spotDst: number) {
- if (spotSrc <= 0 || spotDst <= 0) return 1;
- const r = spotDst / spotSrc;
+/** Default curve parameters based on rated power (very rough, editable). */
+function defaultCurveForRatedW(W: number) {
+ // Peak frequency guess (kHz). Tune these to your hardware fleet.
+ let fPeak = 50;
+ if (W <= 35) fPeak = 25;
+ else if (W <= 60) fPeak = 50;
+ else if (W <= 90) fPeak = 75;
+ else fPeak = 100;
+
+ // Log-normal width parameter (dimensionless). Smaller = narrower peak.
+ const sigma = 0.35;
+ return { fPeak, sigma };
+}
+
+/** Log-normal shaped efficiency curve normalized to 1 at fPeak. */
+function etaOfF(f_kHz: number, fPeak_kHz: number, sigma: number) {
+ const f = Math.max(f_kHz, 0.1);
+ const r = Math.log(f / Math.max(fPeak_kHz, 0.1));
+ const eta = Math.exp(-0.5 * (r / Math.max(sigma, 0.05)) ** 2);
+ // Keep within [0.1, 1] to avoid absurd zeros; adjust if you want tails to hit 0.
+ return clamp(eta, 0.1, 1);
+}
+
+/** Area factor from field (proxy for spot area scaling) */
+function areaFactorFromField(fieldSrc: number, fieldDst: number) {
+ if (fieldSrc <= 0 || fieldDst <= 0) return 1;
+ const r = fieldDst / fieldSrc;
return r * r;
}
@@ -36,7 +59,7 @@ export default function Page() {
const [hSrc, setHSrc] = useState('0.1'); // mm (raster line spacing)
const [fSrc, setFSrc] = useState('30'); // kHz
const [tauSrc, setTauSrc] = useState('100'); // ns pulse width
- const [fieldSrc, setFieldSrc] = useState('60'); // µm, 1/e² spot diameter
+ const [fieldSrc, setFieldSrc] = useState('110'); // mm
// DEST machine/lens
const [wDst, setWDst] = useState('50'); // rated W
@@ -44,7 +67,16 @@ export default function Page() {
const [hDst, setHDst] = useState('0.1'); // mm
const [fDst, setFDst] = useState('30'); // kHz
const [tauDst, setTauDst] = useState('100'); // ns
- const [fieldDst, setFieldDst] = useState('40'); // µm
+ const [fieldDst, setFieldDst] = useState('70'); // mm
+
+ // Curve tuning / advanced
+ const [advanced, setAdvanced] = useState(false);
+ const srcDefaults = defaultCurveForRatedW(num(wSrc, 50));
+ const dstDefaults = defaultCurveForRatedW(num(wDst, 50));
+ const [fPeakSrc, setFPeakSrc] = useState(String(srcDefaults.fPeak));
+ const [sigmaSrc, setSigmaSrc] = useState(String(srcDefaults.sigma));
+ const [fPeakDst, setFPeakDst] = useState(String(dstDefaults.fPeak));
+ const [sigmaDst, setSigmaDst] = useState(String(dstDefaults.sigma));
// Prefer adjusting speed/freq instead of exceeding 100% power
const [preferSpeedAdjust, setPreferSpeedAdjust] = useState(true);
@@ -59,11 +91,21 @@ export default function Page() {
const h2 = Math.max(num(hDst, 0), 0.000001);
const f1k = Math.max(num(fSrc, 0), 0.1);
const f2k = Math.max(num(fDst, 0), 0.1);
+ const tau1_ns = Math.max(num(tauSrc, 0), 0.1);
const tau2_ns = Math.max(num(tauDst, 0), 0.1);
- const aFac = areaFactorFromDiameter(num(fieldSrc, 0), num(fieldDst, 0));
+ const aFac = areaFactorFromField(num(fieldSrc, 0), num(fieldDst, 0));
+
+ const fpk1 = Math.max(num(fPeakSrc, defaultCurveForRatedW(W1).fPeak), 0.1);
+ const sig1 = Math.max(num(sigmaSrc, defaultCurveForRatedW(W1).sigma), 0.05);
+ const fpk2 = Math.max(num(fPeakDst, defaultCurveForRatedW(W2).fPeak), 0.1);
+ const sig2 = Math.max(num(sigmaDst, defaultCurveForRatedW(W2).sigma), 0.05);
+
+ // Efficiency factors (0..1)
+ const eta1 = etaOfF(f1k, fpk1, sig1);
+ const eta2 = etaOfF(f2k, fpk2, sig2);
// Effective average power (W) after frequency efficiency
- const P1eff = W1 * p1;
+ const P1eff = W1 * p1 * eta1;
let p2Frac = p1; // destination power fraction (0..1)
let suggestedSpeed: number | undefined;
@@ -72,7 +114,7 @@ export default function Page() {
// Helper: compute required P2eff for each match, then map to power%
const powerPercentFromEff = (P2effReq: number) => {
// P2eff = W2 * p2 * eta2 => p2 = P2eff / (W2*eta2)
- return P2effReq / W2;
+ return P2effReq / (W2 * eta2);
};
if (mode === 'vector') {
@@ -80,7 +122,7 @@ export default function Page() {
const P2effReq = P1eff * (v2 / v1);
p2Frac = powerPercentFromEff(P2effReq);
if (preferSpeedAdjust && p2Frac > 1) {
- suggestedSpeed = v1 * W2 / (W1 * p1); // from p2<=1
+ suggestedSpeed = v1 * (W2 * eta2) / (W1 * eta1 * p1); // from p2<=1
p2Frac = 1;
}
} else if (mode === 'raster') {
@@ -88,7 +130,7 @@ export default function Page() {
const P2effReq = P1eff * ((v2 * h2) / (v1 * h1));
p2Frac = powerPercentFromEff(P2effReq);
if (preferSpeedAdjust && p2Frac > 1) {
- suggestedSpeed = v1 * W2 * (h1 / h2) / (W1 * p1);
+ suggestedSpeed = v1 * (W2 * eta2) * (h1 / h2) / (W1 * eta1 * p1);
p2Frac = 1;
}
} else if (mode === 'irradiance') {
@@ -107,7 +149,7 @@ export default function Page() {
if (preferSpeedAdjust && p2Frac > 1) {
// Suggest lowering f2 to keep p2<=1: P2eff_max = W2*eta2*1
// f2_req = P2eff_max / Ep1
- const f2_req = W2 / Ep1; // Hz
+ const f2_req = (W2 * eta2) / Ep1; // Hz
suggestedFreq_kHz = Math.max(f2_req / 1e3, 0.1);
p2Frac = 1;
}
@@ -115,7 +157,7 @@ export default function Page() {
// Compute pulse metrics (for display) using **destination** settings
const p2Clamped = clamp(p2Frac, 0, 2);
- const P2eff = W2 * p2Clamped;
+ const P2eff = W2 * p2Clamped * eta2;
const f2Hz = f2k * 1e3;
const tau2_s = tau2_ns * 1e-9;
const Ep2 = P2eff / f2Hz; // J
@@ -125,6 +167,8 @@ export default function Page() {
p2Percent: clamp(p2Clamped * 100, 0, 200),
suggestedSpeed,
suggestedFreq_kHz,
+ eta1,
+ eta2,
P1eff,
P2eff,
Ep2,
@@ -133,13 +177,13 @@ export default function Page() {
};
}, [
mode, wSrc, wDst, pSrc, vSrc, vDst, hSrc, hDst, fSrc, fDst, tauSrc, tauDst,
- fieldSrc, fieldDst, preferSpeedAdjust,
+ fieldSrc, fieldDst, preferSpeedAdjust, fPeakSrc, sigmaSrc, fPeakDst, sigmaDst,
]);
return (
@@ -153,7 +197,7 @@ export default function Page() {
Vector: Energy per length (J/mm)
Raster: Energy per area (J/mm²)
- Spot irradiance: W/mm²
+ Irradiance: W/mm² (spot/field)
Pulse energy: J (fiber)
@@ -203,11 +247,32 @@ export default function Page() {
setHSrc(e.target.value)} inputMode="decimal" />