+
+
+
+ {#if print.stl_file && browser && !stlFile}
+
+
+ {#await import("$lib/components/STLViewer.svelte") then { default: STLViewer }}
+
+ {/await}
+
+
+ Click below to replace with a new model
+
+ {/if}
+
+
+ {#if uploadStatus === "Uploading..." && uploadProgress > 0}
+
+
+
+ {stlFile?.name}
+ {uploadProgress}%
+
+
+
+ {:else}
+
+
+ {#if stlFile}
+
+ {/if}
+
+ {/if}
+
+
{#if editStatus === "In Progress"}
diff --git a/src/lib/components/prints/LogPrintModal.svelte b/src/lib/components/prints/LogPrintModal.svelte
index 8b88bb3..9f71b92 100644
--- a/src/lib/components/prints/LogPrintModal.svelte
+++ b/src/lib/components/prints/LogPrintModal.svelte
@@ -1,260 +1,473 @@
-
+
+
+
+
+
+ {#if uploadStatus === "Uploading..." && uploadProgress > 0}
+
+
+
+ {stlFile?.name}
+ {uploadProgress}%
+
+
+
+ {:else}
+
+
+
+ {#if stlFile}
+
+ {/if}
+
+ {/if}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {#if selectedStatus === "In Progress"}
+
+
+
+
+ Enter the expected total print time and how long it's been running.
+
+
+
+
+
+
+
+ {:else}
+
+
+ {/if}
+
+
+
+
+
+
diff --git a/src/lib/models/PrintJob.ts b/src/lib/models/PrintJob.ts
index a25f68e..66566aa 100644
--- a/src/lib/models/PrintJob.ts
+++ b/src/lib/models/PrintJob.ts
@@ -47,6 +47,9 @@ const printJobSchema = new mongoose.Schema({
type: Date
},
notes: String,
+ stl_file: {
+ type: String // Path to uploaded STL file
+ },
date: {
type: Date,
default: Date.now
diff --git a/src/routes/api/upload-stl/+server.ts b/src/routes/api/upload-stl/+server.ts
new file mode 100644
index 0000000..7047419
--- /dev/null
+++ b/src/routes/api/upload-stl/+server.ts
@@ -0,0 +1,54 @@
+import { json, error } from '@sveltejs/kit';
+import type { RequestHandler } from './$types';
+import { writeFile, mkdir } from 'fs/promises';
+import { existsSync } from 'fs';
+import path from 'path';
+
+const UPLOAD_DIR = 'static/uploads/models';
+const ALLOWED_EXTENSIONS = ['.stl', '.obj'];
+
+export const POST: RequestHandler = async ({ request, locals }) => {
+ if (!locals.user) {
+ throw error(401, 'Unauthorized');
+ }
+
+ const formData = await request.formData();
+ const file = formData.get('stl') as File; // Keep 'stl' for backward compatibility
+
+ if (!file || file.size === 0) {
+ throw error(400, 'No file provided');
+ }
+
+ // Validate file type
+ const fileName = file.name.toLowerCase();
+ const extension = '.' + fileName.split('.').pop();
+
+ if (!ALLOWED_EXTENSIONS.includes(extension)) {
+ throw error(400, 'Only STL and OBJ files are allowed');
+ }
+
+ // Create upload directory if it doesn't exist
+ if (!existsSync(UPLOAD_DIR)) {
+ await mkdir(UPLOAD_DIR, { recursive: true });
+ }
+
+ // Generate unique filename
+ const timestamp = Date.now();
+ const sanitizedName = file.name.replace(/[^a-zA-Z0-9.-]/g, '_');
+ const uniqueFileName = `${locals.user.id}_${timestamp}_${sanitizedName}`;
+ const filePath = path.join(UPLOAD_DIR, uniqueFileName);
+
+ // Write file to disk
+ const buffer = Buffer.from(await file.arrayBuffer());
+ await writeFile(filePath, buffer);
+
+ // Return the public path
+ const publicPath = `/uploads/models/${uniqueFileName}`;
+
+ return json({
+ success: true,
+ path: publicPath,
+ fileName: file.name,
+ fileType: extension.slice(1).toUpperCase()
+ });
+};
diff --git a/src/routes/library/+page.server.ts b/src/routes/library/+page.server.ts
new file mode 100644
index 0000000..c0f1285
--- /dev/null
+++ b/src/routes/library/+page.server.ts
@@ -0,0 +1,22 @@
+import { PrintJob } from '$lib/models/PrintJob';
+import { connectDB } from '$lib/server/db';
+import type { PageServerLoad } from './$types';
+import { redirect } from '@sveltejs/kit';
+
+export const load: PageServerLoad = async ({ locals }) => {
+ if (!locals.user) throw redirect(303, '/login');
+
+ await connectDB();
+
+ // Get all prints with STL files
+ const printsWithSTL = await PrintJob.find({
+ user_id: locals.user.id,
+ stl_file: { $exists: true, $nin: [null, ''] }
+ })
+ .sort({ date: -1 })
+ .lean();
+
+ return {
+ models: JSON.parse(JSON.stringify(printsWithSTL))
+ };
+};
diff --git a/src/routes/library/+page.svelte b/src/routes/library/+page.svelte
new file mode 100644
index 0000000..0402fcb
--- /dev/null
+++ b/src/routes/library/+page.svelte
@@ -0,0 +1,227 @@
+
+
+
+
+
Model Library
+
+ Browse your 3D model collection ({models.length} models)
+
+
+
+ {#if models.length === 0}
+
+
+
+ No Models Yet
+
+
+ Upload STL files when logging prints to build your model
+ library. Go to Prints to add your first model.
+
+
+ {:else}
+
+ {#each models as model}
+
+
+
openViewer(model)}
+ >
+
+
+
+
+
+
+
+ View Model
+
+
+
+
+
+
+
+ {model.name}
+
+
+ {formatDate(model.date)}
+
+
+ {#if model.status === "Success"}
+
+
+ Printed
+
+ {:else if model.status === "In Progress"}
+
+
+ Printing
+
+ {:else if model.status === "Fail"}
+
+
+ Failed
+
+ {/if}
+
+
+
+
+ {/each}
+
+ {/if}
+
+
+
+{#if showViewer && selectedModel && browser}
+
+
+
+
+
e.stopPropagation()}
+ >
+
+
+ {selectedModel.name}
+
+
+ {formatDate(selectedModel.date)}
+
+
+
+
+
+
+
e.stopPropagation()}
+ >
+ {#await import("$lib/components/STLViewer.svelte") then { default: STLViewer }}
+
+ {/await}
+
+
+
+
e.stopPropagation()}
+ >
+ {#if selectedModel.filament_used_g}
+
+ Filament:
+ {selectedModel.filament_used_g}g
+
+ {/if}
+ {#if selectedModel.duration_minutes}
+
+ Duration:
+ {Math.floor(selectedModel.duration_minutes / 60)}h {selectedModel.duration_minutes %
+ 60}m
+
+ {/if}
+ {#if selectedModel.calculated_cost_filament}
+
+ Cost:
+ ${selectedModel.calculated_cost_filament.toFixed(2)}
+
+ {/if}
+
+
+{/if}
+
+
diff --git a/src/routes/prints/+page.server.ts b/src/routes/prints/+page.server.ts
index 51f2153..9e7504d 100644
--- a/src/routes/prints/+page.server.ts
+++ b/src/routes/prints/+page.server.ts
@@ -44,6 +44,7 @@ export const actions: Actions = {
const status = formData.get('status');
const manual_cost = formData.get('manual_cost');
const elapsed_minutes = formData.get('elapsed_minutes');
+ const stl_file = formData.get('stl_file');
if (!spool_id || !printer_id || !filament_used_g) {
return fail(400, { missing: true });
@@ -110,6 +111,7 @@ export const actions: Actions = {
calculated_cost_energy: Number(costEnergy.toFixed(2)),
status,
started_at: startedAt,
+ stl_file: stl_file || null,
date: new Date()
});
@@ -139,6 +141,7 @@ export const actions: Actions = {
const elapsed_minutes = formData.get('elapsed_minutes');
const printer_id = formData.get('printer_id');
const spool_id = formData.get('spool_id');
+ const stl_file = formData.get('stl_file');
if (!id || !name) {
return fail(400, { missing: true });
@@ -215,6 +218,10 @@ export const actions: Actions = {
if (spool_id) {
updateData.spool_id = spool_id;
}
+ // Update STL file if provided
+ if (stl_file) {
+ updateData.stl_file = stl_file;
+ }
await PrintJob.findOneAndUpdate(
{ _id: id, user_id: locals.user.id },
diff --git a/src/routes/prints/+page.svelte b/src/routes/prints/+page.svelte
index 4d7b6b9..4562a3e 100644
--- a/src/routes/prints/+page.svelte
+++ b/src/routes/prints/+page.svelte
@@ -75,9 +75,19 @@