query( "SELECT brand FROM filaments WHERE brand IS NOT NULL AND brand <> '' GROUP BY brand COLLATE NOCASE ORDER BY brand COLLATE NOCASE" )->fetchAll(PDO::FETCH_COLUMN); $id = (int)($_POST['id'] ?? $_GET['id'] ?? 0); // Manufacturers tend to reuse one spool design, so when adding a spool the // empty-spool weight is pre-filled from the brand's most common value. // Keyed by lowercased brand; ties go to the most recently added spool. $spoolWeights = []; if (!$id) { $wrows = db()->query( "SELECT brand, spool_weight_g AS w, COUNT(*) AS n FROM filaments WHERE brand IS NOT NULL AND brand <> '' AND spool_weight_g > 0 GROUP BY brand COLLATE NOCASE, spool_weight_g ORDER BY n, MAX(id)" )->fetchAll(); foreach ($wrows as $r) { $spoolWeights[mb_strtolower(trim($r['brand']))] = ['w' => (float)$r['w'], 'n' => (int)$r['n'], 'brand' => $r['brand']]; } } $spool = null; if ($id) { $st = db()->prepare('SELECT * FROM filaments WHERE id = ?'); $st->execute([$id]); $spool = $st->fetch(); if (!$spool) { flash('Spool not found.'); header('Location: index.php'); exit; } } $error = ''; if ($_SERVER['REQUEST_METHOD'] === 'POST') { check_csrf(); if (($_POST['action'] ?? '') === 'delete' && $spool) { backup_db(); db()->prepare('DELETE FROM filaments WHERE id = ?')->execute([$id]); flash('Deleted ' . spool_label($spool) . '.'); header('Location: index.php'); exit; } $type = trim((string)($_POST['type'] ?? '')); $brand = trim((string)($_POST['brand'] ?? '')); $colorName = trim((string)($_POST['color_name'] ?? '')); $color = (string)($_POST['color_hex'] ?? ''); $color2 = (string)($_POST['color_hex2'] ?? ''); $total = (float)($_POST['total_weight_g'] ?? 0); $sw = (float)($_POST['spool_weight_g'] ?? 0); $origRaw = trim((string)($_POST['original_g'] ?? '')); $orig = $origRaw === '' ? 1000.0 : (float)$origRaw; $cost = trim((string)($_POST['cost'] ?? '')); $cost = $cost === '' ? null : (float)$cost; $notes = trim((string)($_POST['notes'] ?? '')); $abrasive = isset($_POST['abrasive']) ? 1 : 0; $highflow = isset($_POST['highflow']) ? 1 : 0; $silk = isset($_POST['silk']) ? 1 : 0; $matte = isset($_POST['matte']) ? 1 : 0; $rainbow = isset($_POST['rainbow']) ? 1 : 0; $transparent = isset($_POST['transparent']) ? 1 : 0; $twotone = isset($_POST['twotone']) ? 1 : 0; $sparkle = isset($_POST['sparkle']) ? 1 : 0; $small = isset($_POST['small_spool']) ? 1 : 0; if (!preg_match('/^#[0-9a-fA-F]{6}$/', $color)) { $color = '#888888'; } if (!preg_match('/^#[0-9a-fA-F]{6}$/', $color2)) { $color2 = $twotone ? '#888888' : null; } if ($type === '') { $error = 'Filament type is required.'; } elseif ($total <= 0 || $sw < 0 || $total <= $sw) { $error = 'Total weight must be greater than the spool weight.'; } elseif ($orig <= 0) { $error = 'Original spool amount must be greater than zero.'; } else { $net = $total - $sw; backup_db(); if ($spool) { // A changed weight means the spool was re-weighed: reset remaining // to total - spool. Otherwise keep remaining (usage already deducted). $weightsChanged = abs($total - (float)$spool['total_weight_g']) > 0.001 || abs($sw - (float)$spool['spool_weight_g']) > 0.001; $remaining = $weightsChanged ? $net : (float)$spool['remaining_g']; db()->prepare( 'UPDATE filaments SET type=?, brand=?, color_name=?, color_hex=?, color_hex2=?, total_weight_g=?, spool_weight_g=?, original_g=?, remaining_g=?, abrasive=?, highflow=?, silk=?, matte=?, rainbow=?, transparent=?, twotone=?, sparkle=?, small_spool=?, cost=?, notes=?, updated_at=datetime(\'now\') WHERE id=?' )->execute([$type, $brand, $colorName, $color, $color2, $total, $sw, $orig, $remaining, $abrasive, $highflow, $silk, $matte, $rainbow, $transparent, $twotone, $sparkle, $small, $cost, $notes, $id]); flash('Saved ' . spool_label(['brand' => $brand, 'type' => $type, 'color_name' => $colorName]) . '.'); $dest = 'index.php'; } else { db()->prepare( 'INSERT INTO filaments (type, brand, color_name, color_hex, color_hex2, total_weight_g, spool_weight_g, original_g, remaining_g, abrasive, highflow, silk, matte, rainbow, transparent, twotone, sparkle, small_spool, cost, notes) VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)' )->execute([$type, $brand, $colorName, $color, $color2, $total, $sw, $orig, $net, $abrasive, $highflow, $silk, $matte, $rainbow, $transparent, $twotone, $sparkle, $small, $cost, $notes]); flash('Added ' . spool_label(['brand' => $brand, 'type' => $type, 'color_name' => $colorName]) . '.'); // Land on the "recently added" sort so the new spool is at the top. $dest = 'index.php?sort=added'; } header('Location: index.php'); exit; } // Validation failed: re-show submitted values. $spool = array_merge($spool ?? [], [ 'type' => $type, 'brand' => $brand, 'color_name' => $colorName, 'color_hex' => $color, 'color_hex2' => $color2, 'total_weight_g' => $total, 'spool_weight_g' => $sw, 'original_g' => $orig, 'cost' => $cost, 'abrasive' => $abrasive, 'highflow' => $highflow, 'silk' => $silk, 'matte' => $matte, 'rainbow' => $rainbow, 'transparent' => $transparent, 'twotone' => $twotone, 'sparkle' => $sparkle, 'small_spool' => $small, 'notes' => $notes, ]); } page_header($id ? 'Edit spool' : 'Add spool'); ?>

>

Point the camera at the filament — the average color of the center of the frame is sampled.

Cancel