From 0bb5f9f4174625abdc5bf738e05c3139d32183a7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Balogh=20Barnab=C3=A1s?= Date: Mon, 29 Dec 2025 03:25:19 +0100 Subject: [PATCH 1/2] Refactor print admin to use G-code for filament usage --- .../dashboard/admin/print/+page.server.ts | 28 +++++++++++++++---- src/routes/dashboard/admin/print/+page.svelte | 20 +++++++++---- .../admin/print/[id]/+page.server.ts | 26 +++++++++++------ .../dashboard/admin/print/[id]/+page.svelte | 12 ++++---- 4 files changed, 59 insertions(+), 27 deletions(-) diff --git a/src/routes/dashboard/admin/print/+page.server.ts b/src/routes/dashboard/admin/print/+page.server.ts index 3b91488..97f7dda 100644 --- a/src/routes/dashboard/admin/print/+page.server.ts +++ b/src/routes/dashboard/admin/print/+page.server.ts @@ -13,15 +13,23 @@ export async function load({ locals }) { throw error(403, { message: 'oi get out' }); } - const projects = await getProjects(['t1_approved'], [], []); + + const allowedStatuses: (typeof project.status._.data)[] = ['printing', 't1_approved']; + const projects = await getProjects(allowedStatuses, [], []); const allProjects = await db .select({ id: project.id, - name: project.name + name: project.name, + status: project.status, + userId: project.userId }) .from(project) - .where(and(eq(project.deleted, false))); + .where(and( + eq(project.deleted, false), + inArray(project.status, ['printing', 't1_approved', 'printed']), + sql`(${project.status} != 'printed' OR ${project.userId} = ${locals.user.id})` + )); const users = await db .select({ @@ -37,7 +45,8 @@ export async function load({ locals }) { allProjects, projects, users, - currentlyPrinting + currentlyPrinting, + currentUserId: locals.user.id }; } @@ -51,7 +60,10 @@ export const actions = { } const data = await request.formData(); - const statusFilter = data.getAll('status') as (typeof project.status._.data)[]; + const allowedStatuses: (typeof project.status._.data)[] = ['printing', 't1_approved', 'printed']; + const statusFilter = data.getAll('status') + .map((s) => s.toString()) + .filter((s): s is typeof project.status._.data => allowedStatuses.includes(s as typeof project.status._.data)) as (typeof project.status._.data)[]; const projectFilter = data.getAll('project').map((projectId) => { const parsedInt = parseInt(projectId.toString()); @@ -59,12 +71,16 @@ export const actions = { return parseInt(projectId.toString()); }); - const userFilter = data.getAll('user').map((userId) => { + let userFilter = data.getAll('user').map((userId) => { const parsedInt = parseInt(userId.toString()); if (!parsedInt) throw error(400, { message: 'malformed user filter' }); return parseInt(userId.toString()); }); + if (statusFilter.length === 1 && statusFilter[0] === 'printed') { + userFilter = [locals.user.id]; + } + const projects = await getProjects(statusFilter, projectFilter, userFilter); return { diff --git a/src/routes/dashboard/admin/print/+page.svelte b/src/routes/dashboard/admin/print/+page.svelte index fc8301f..cfa5535 100644 --- a/src/routes/dashboard/admin/print/+page.svelte +++ b/src/routes/dashboard/admin/print/+page.svelte @@ -7,15 +7,23 @@ let { data, form } = $props(); + type AllProject = { id: number; name: string | null; status: string; userId: number }; + const allProjects: AllProject[] = data.allProjects; + const currentUserId: number = data.currentUserId; + let projectSearch = $state(''); let userSearch = $state(''); let projects = $derived(form?.projects ?? data.projects); let filteredProjects = $derived( - data.allProjects.filter((project) => - project.name?.toLowerCase().includes(projectSearch.toLowerCase()) - ) + allProjects + .filter((project) => { + if (project.status === 'printing') return true; + if (project.status === 'printed' && project.userId === currentUserId) return true; + return false; + }) + .filter((project) => project.name?.toLowerCase().includes(projectSearch.toLowerCase())) ); let filteredUsers = $derived( data.users.filter((user) => user.name.toLowerCase().includes(userSearch.toLowerCase())) @@ -64,9 +72,9 @@ value={form?.fields.status ?? ['t1_approved']} multiple > - {#each Object.entries(projectStatuses) as [status, longStatus]} - - {/each} + + + diff --git a/src/routes/dashboard/admin/print/[id]/+page.server.ts b/src/routes/dashboard/admin/print/[id]/+page.server.ts index a59be0a..ed42017 100644 --- a/src/routes/dashboard/admin/print/[id]/+page.server.ts +++ b/src/routes/dashboard/admin/print/[id]/+page.server.ts @@ -224,26 +224,34 @@ export const actions = { } const data = await request.formData(); - const filamentUsed = data.get('filament'); + const gcodeFile = data.get('gcode'); const notes = data.get('notes')?.toString(); const feedback = data.get('feedback')?.toString(); - if (notes === null || feedback === null) { + if (!gcodeFile || typeof gcodeFile === 'string' || notes === null || feedback === null) { return error(400); } - if ( - !filamentUsed || - isNaN(parseFloat(filamentUsed.toString())) || - parseFloat(filamentUsed.toString()) < 0 - ) { - return error(400, { message: 'invalid filament used' }); + const gcodeText = await gcodeFile.text(); + let filamentUsed: number | undefined; + const match1 = gcodeText.match(/;\s*filament used\s*=\s*([\d.]+)g/i); + const match2 = gcodeText.match(/;\s*total filament weight \[g\]\s*:\s*([\d.]+)/i); + const match3 = gcodeText.match(/;[^\n]*filament[^\n]*([\d.]+)\s*g/i); + if (match1) { + filamentUsed = parseFloat(match1[1]); + } else if (match2) { + filamentUsed = parseFloat(match2[1]); + } else if (match3) { + filamentUsed = parseFloat(match3[1]); + } + if (typeof filamentUsed !== 'number' || isNaN(filamentUsed) || filamentUsed < 0) { + return error(400, { message: 'Could not find valid filament usage in G-code file' }); } await db.insert(legionReview).values({ projectId: id, userId: locals.user.id, - filamentUsed: parseFloat(filamentUsed.toString()), + filamentUsed: filamentUsed, notes, feedback, action: 'print' diff --git a/src/routes/dashboard/admin/print/[id]/+page.svelte b/src/routes/dashboard/admin/print/[id]/+page.svelte index cf8ad38..30e3021 100644 --- a/src/routes/dashboard/admin/print/[id]/+page.svelte +++ b/src/routes/dashboard/admin/print/[id]/+page.svelte @@ -163,6 +163,7 @@
{ printFormPending = true; @@ -175,15 +176,14 @@ return confirm('really submit?'); }} > + From 28026f8c07d81b17ca265e62ada33035c6823ae5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Balogh=20Barnab=C3=A1s?= Date: Tue, 30 Dec 2025 01:32:08 +0100 Subject: [PATCH 2/2] Refactor filament usage calculation --- .../admin/print/[id]/+page.server.ts | 18 +++--------- .../dashboard/admin/print/utils.server.ts | 29 +++++++++++++++++++ 2 files changed, 33 insertions(+), 14 deletions(-) diff --git a/src/routes/dashboard/admin/print/[id]/+page.server.ts b/src/routes/dashboard/admin/print/[id]/+page.server.ts index ed42017..dc5368a 100644 --- a/src/routes/dashboard/admin/print/[id]/+page.server.ts +++ b/src/routes/dashboard/admin/print/[id]/+page.server.ts @@ -5,7 +5,7 @@ import { eq, and, asc, sql } from 'drizzle-orm'; import type { Actions } from './$types'; import { sendSlackDM } from '$lib/server/slack.js'; import { getReviewHistory } from '../../getReviewHistory.server'; -import { getCurrentlyPrinting } from '../utils.server'; +import { getCurrentlyPrinting, calculateFilamentUsage } from '../utils.server'; export async function load({ locals, params }) { if (!locals.user) { @@ -233,19 +233,9 @@ export const actions = { } const gcodeText = await gcodeFile.text(); - let filamentUsed: number | undefined; - const match1 = gcodeText.match(/;\s*filament used\s*=\s*([\d.]+)g/i); - const match2 = gcodeText.match(/;\s*total filament weight \[g\]\s*:\s*([\d.]+)/i); - const match3 = gcodeText.match(/;[^\n]*filament[^\n]*([\d.]+)\s*g/i); - if (match1) { - filamentUsed = parseFloat(match1[1]); - } else if (match2) { - filamentUsed = parseFloat(match2[1]); - } else if (match3) { - filamentUsed = parseFloat(match3[1]); - } - if (typeof filamentUsed !== 'number' || isNaN(filamentUsed) || filamentUsed < 0) { - return error(400, { message: 'Could not find valid filament usage in G-code file' }); + const filamentUsed = calculateFilamentUsage(gcodeText); + if (typeof filamentUsed !== 'number' || isNaN(filamentUsed) || filamentUsed <= 0) { + return error(400, { message: 'Could not calculate valid filament usage from G-code file' }); } await db.insert(legionReview).values({ diff --git a/src/routes/dashboard/admin/print/utils.server.ts b/src/routes/dashboard/admin/print/utils.server.ts index ac78a1a..1c5318b 100644 --- a/src/routes/dashboard/admin/print/utils.server.ts +++ b/src/routes/dashboard/admin/print/utils.server.ts @@ -20,3 +20,32 @@ export async function getCurrentlyPrinting(user: { id: number | SQLWrapper }) { return currentlyPrinting; } + +export function calculateFilamentUsage(gcodeText: string): number { + const FILAMENT_DIAMETER = 1.75; + const FILAMENT_DENSITY = 1.24; + const lines = gcodeText.split('\n'); + let totalExtruded = 0; + let currentE = 0; + for (const line of lines) { + const trimmed = line.trim().toUpperCase(); + if (trimmed.startsWith('G92')) { + const eMatch = trimmed.match(/E([0-9.-]+)/); + if (eMatch) { + currentE = parseFloat(eMatch[1]); + } + } else if (trimmed.startsWith('G1')) { + const eMatch = trimmed.match(/E([0-9.-]+)/); + if (eMatch) { + const newE = parseFloat(eMatch[1]); + if (newE > currentE) { + totalExtruded += newE - currentE; + } + currentE = newE; + } + } + } + const area = Math.PI * (FILAMENT_DIAMETER / 2) ** 2; + const volumeCm3 = (totalExtruded * area) / 1000; + return volumeCm3 * FILAMENT_DENSITY; +}