Model removal option
This commit is contained in:
@@ -3,6 +3,7 @@
|
||||
import * as THREE from "three";
|
||||
import { STLLoader } from "three/examples/jsm/loaders/STLLoader.js";
|
||||
import { OBJLoader } from "three/examples/jsm/loaders/OBJLoader.js";
|
||||
import { GLTFLoader } from "three/examples/jsm/loaders/GLTFLoader.js";
|
||||
import { OrbitControls } from "three/examples/jsm/controls/OrbitControls.js";
|
||||
|
||||
interface Props {
|
||||
@@ -95,6 +96,8 @@
|
||||
|
||||
if (extension === "obj") {
|
||||
loadOBJ();
|
||||
} else if (extension === "gltf" || extension === "glb") {
|
||||
loadGLTF();
|
||||
} else {
|
||||
loadSTL();
|
||||
}
|
||||
@@ -161,6 +164,56 @@
|
||||
);
|
||||
}
|
||||
|
||||
function loadGLTF() {
|
||||
const loader = new GLTFLoader();
|
||||
|
||||
loader.load(
|
||||
modelPath,
|
||||
(gltf) => {
|
||||
const model = gltf.scene;
|
||||
|
||||
// Center the object
|
||||
const box = new THREE.Box3().setFromObject(model);
|
||||
const center = box.getCenter(new THREE.Vector3());
|
||||
model.position.sub(center);
|
||||
|
||||
// Scale to fit
|
||||
const size = box.getSize(new THREE.Vector3());
|
||||
const maxDim = Math.max(size.x, size.y, size.z);
|
||||
const scale = 50 / maxDim;
|
||||
model.scale.set(scale, scale, scale);
|
||||
|
||||
// glTF models may have their own materials, apply default if missing
|
||||
model.traverse((child) => {
|
||||
if (child instanceof THREE.Mesh) {
|
||||
if (
|
||||
!child.material ||
|
||||
(child.material as THREE.Material).type ===
|
||||
"MeshBasicMaterial"
|
||||
) {
|
||||
child.material = new THREE.MeshPhongMaterial({
|
||||
color: 0x3b82f6,
|
||||
specular: 0x111111,
|
||||
shininess: 50,
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
scene.add(model);
|
||||
|
||||
// Position camera
|
||||
const distance = maxDim * scale * 2.5;
|
||||
camera.position.set(distance, distance, distance);
|
||||
controls.update();
|
||||
},
|
||||
undefined,
|
||||
(err) => {
|
||||
console.error("Error loading glTF:", err);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
function addGeometryToScene(geometry: THREE.BufferGeometry) {
|
||||
// Center the geometry
|
||||
geometry.computeBoundingBox();
|
||||
|
||||
@@ -101,8 +101,12 @@
|
||||
stlFile = input.files[0];
|
||||
uploadStatus = stlFile.name;
|
||||
uploadProgress = 0;
|
||||
removeModel = false; // Reset remove flag if selecting new file
|
||||
}
|
||||
}
|
||||
|
||||
// Track if user wants to remove the model
|
||||
let removeModel = $state(false);
|
||||
</script>
|
||||
|
||||
<Modal title="Edit Print Log" {open} onclose={handleClose}>
|
||||
@@ -121,6 +125,11 @@
|
||||
}
|
||||
}
|
||||
|
||||
// Handle model removal
|
||||
if (removeModel) {
|
||||
formData.set("remove_model", "true");
|
||||
}
|
||||
|
||||
// Convert hours + minutes to total minutes
|
||||
const hours = Number(formData.get("duration_hours") || 0);
|
||||
const mins = Number(formData.get("duration_mins") || 0);
|
||||
@@ -222,8 +231,9 @@
|
||||
3D Model {print.stl_file ? "" : "(Optional)"}
|
||||
</label>
|
||||
|
||||
{#if print.stl_file && browser && !stlFile}
|
||||
{#if print.stl_file && browser && !stlFile && !removeModel}
|
||||
<!-- Show existing STL viewer -->
|
||||
<div class="relative">
|
||||
<div
|
||||
class="flex justify-center bg-slate-900 rounded-lg p-2"
|
||||
>
|
||||
@@ -235,9 +245,39 @@
|
||||
/>
|
||||
{/await}
|
||||
</div>
|
||||
<!-- Remove button overlay -->
|
||||
<button
|
||||
type="button"
|
||||
class="absolute top-4 right-4 p-2 bg-red-500/80 hover:bg-red-500 text-white rounded-lg transition-colors"
|
||||
onclick={() => (removeModel = true)}
|
||||
title="Remove 3D model"
|
||||
>
|
||||
<Icon icon="mdi:delete" class="w-5 h-5" />
|
||||
</button>
|
||||
</div>
|
||||
<p class="text-xs text-slate-500 text-center">
|
||||
Click below to replace with a new model
|
||||
</p>
|
||||
{:else if removeModel && print.stl_file}
|
||||
<!-- Removal confirmation -->
|
||||
<div
|
||||
class="p-4 rounded-lg bg-red-500/10 border border-red-500/30 text-center"
|
||||
>
|
||||
<Icon
|
||||
icon="mdi:file-remove"
|
||||
class="w-10 h-10 text-red-400 mx-auto mb-2"
|
||||
/>
|
||||
<p class="text-sm text-red-300 mb-3">
|
||||
Model will be removed when you save
|
||||
</p>
|
||||
<button
|
||||
type="button"
|
||||
class="text-xs text-slate-400 hover:text-white underline"
|
||||
onclick={() => (removeModel = false)}
|
||||
>
|
||||
Cancel removal
|
||||
</button>
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
<!-- Upload button or progress -->
|
||||
@@ -280,7 +320,7 @@
|
||||
</div>
|
||||
<input
|
||||
type="file"
|
||||
accept=".stl,.obj"
|
||||
accept=".stl,.obj,.gltf,.glb"
|
||||
class="sr-only"
|
||||
onchange={handleFileSelect}
|
||||
/>
|
||||
|
||||
@@ -239,7 +239,7 @@
|
||||
</div>
|
||||
<input
|
||||
type="file"
|
||||
accept=".stl,.obj"
|
||||
accept=".stl,.obj,.gltf,.glb"
|
||||
class="sr-only"
|
||||
onchange={handleFileSelect}
|
||||
/>
|
||||
|
||||
@@ -5,7 +5,7 @@ import { existsSync } from 'fs';
|
||||
import path from 'path';
|
||||
|
||||
const UPLOAD_DIR = 'static/uploads/models';
|
||||
const ALLOWED_EXTENSIONS = ['.stl', '.obj'];
|
||||
const ALLOWED_EXTENSIONS = ['.stl', '.obj', '.gltf', '.glb'];
|
||||
|
||||
export const POST: RequestHandler = async ({ request, locals }) => {
|
||||
if (!locals.user) {
|
||||
@@ -24,7 +24,7 @@ export const POST: RequestHandler = async ({ request, locals }) => {
|
||||
const extension = '.' + fileName.split('.').pop();
|
||||
|
||||
if (!ALLOWED_EXTENSIONS.includes(extension)) {
|
||||
throw error(400, 'Only STL and OBJ files are allowed');
|
||||
throw error(400, 'Only STL, OBJ, GLTF, and GLB files are allowed');
|
||||
}
|
||||
|
||||
// Create upload directory if it doesn't exist
|
||||
|
||||
@@ -5,6 +5,9 @@ import { User } from '$lib/models/User';
|
||||
import { connectDB } from '$lib/server/db';
|
||||
import type { PageServerLoad, Actions } from './$types';
|
||||
import { fail, redirect } from '@sveltejs/kit';
|
||||
import { unlink } from 'fs/promises';
|
||||
import { existsSync } from 'fs';
|
||||
import path from 'path';
|
||||
|
||||
export const load: PageServerLoad = async ({ locals }) => {
|
||||
if (!locals.user) throw redirect(303, '/login');
|
||||
@@ -142,6 +145,7 @@ export const actions: Actions = {
|
||||
const printer_id = formData.get('printer_id');
|
||||
const spool_id = formData.get('spool_id');
|
||||
const stl_file = formData.get('stl_file');
|
||||
const remove_model = formData.get('remove_model');
|
||||
|
||||
if (!id || !name) {
|
||||
return fail(400, { missing: true });
|
||||
@@ -218,9 +222,31 @@ export const actions: Actions = {
|
||||
if (spool_id) {
|
||||
updateData.spool_id = spool_id;
|
||||
}
|
||||
// Update STL file if provided
|
||||
// Handle STL file: update if new one provided, or remove if requested
|
||||
if (stl_file) {
|
||||
// If replacing an existing model, delete the old file
|
||||
if (printJob.stl_file) {
|
||||
const oldFilePath = path.join('static', printJob.stl_file);
|
||||
if (existsSync(oldFilePath)) {
|
||||
try {
|
||||
await unlink(oldFilePath);
|
||||
} catch (e) {
|
||||
console.error('Failed to delete old model file:', e);
|
||||
}
|
||||
}
|
||||
}
|
||||
updateData.stl_file = stl_file;
|
||||
} else if (remove_model === 'true' && printJob.stl_file) {
|
||||
// Delete the file from disk
|
||||
const filePath = path.join('static', printJob.stl_file);
|
||||
if (existsSync(filePath)) {
|
||||
try {
|
||||
await unlink(filePath);
|
||||
} catch (e) {
|
||||
console.error('Failed to delete model file:', e);
|
||||
}
|
||||
}
|
||||
updateData.stl_file = null;
|
||||
}
|
||||
|
||||
await PrintJob.findOneAndUpdate(
|
||||
@@ -246,6 +272,21 @@ export const actions: Actions = {
|
||||
await connectDB();
|
||||
|
||||
try {
|
||||
// Find the print first to get the model file path
|
||||
const printJob = await PrintJob.findOne({ _id: id, user_id: locals.user.id });
|
||||
|
||||
if (printJob?.stl_file) {
|
||||
// Delete the model file from disk
|
||||
const filePath = path.join('static', printJob.stl_file);
|
||||
if (existsSync(filePath)) {
|
||||
try {
|
||||
await unlink(filePath);
|
||||
} catch (e) {
|
||||
console.error('Failed to delete model file:', e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
await PrintJob.findOneAndDelete({ _id: id, user_id: locals.user.id });
|
||||
return { success: true };
|
||||
} catch (error) {
|
||||
|
||||
Reference in New Issue
Block a user