Files
POPS/app/templates/bracket_detail.html
Martin Hohenberg 4c7acb1c52 Analyse STL/3MF on upload: bounding box + volume, cached in stl_cache
- StlCache model: filename, cube_x/y/z (mm), volume_ccm
- Migration 0007: stl_cache table
- app/stl_analysis.py: trimesh-based analyse() + get_cache_map()
  - Runs immediately after upload in both /jobs and /brackets/{id}/jobs
  - Skips .gcode (no geometry), caches per filename, idempotent
- Library selector in both modals now shows dimensions and volume
- requirements: trimesh==4.5.3

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-18 22:04:11 +02:00

166 lines
7.2 KiB
HTML
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
{% extends "base.html" %}
{% block title %}{{ bracket.name }} — POPS{% endblock %}
{% block content %}
{% set total = bracket.jobs | length %}
{% set done = bracket.jobs | selectattr('status.value', 'equalto', 'done') | list | length %}
{% set printing = bracket.jobs | selectattr('status.value', 'equalto', 'printing') | list | length %}
{% set pct = (done / total * 100) | int if total else 0 %}
<div class="d-flex align-items-center gap-3 mb-4">
<a href="/brackets" class="text-secondary text-decoration-none"><i class="bi bi-arrow-left"></i></a>
<div class="flex-grow-1">
<h5 class="text-white mb-0">{{ bracket.name }}</h5>
{% if bracket.customer %}<small class="text-secondary">{{ bracket.customer }}</small>{% endif %}
</div>
<button class="btn btn-sm btn-primary" data-bs-toggle="modal" data-bs-target="#addJobsModal">
<i class="bi bi-plus-lg me-1"></i>Add Job(s)
</button>
</div>
{# ── Progress ── #}
{% if total %}
<div class="card mb-4 p-3">
<div class="d-flex justify-content-between mb-2">
<span class="text-secondary small">{{ done }} done · {{ printing }} printing · {{ total - done - printing }} queued</span>
<span class="text-secondary small">{{ pct }}%</span>
</div>
<div class="progress" style="height:8px">
<div class="progress-bar bg-success" style="width:{{ pct }}%"></div>
</div>
</div>
{% endif %}
{# ── Jobs table ── #}
<div class="card">
{% if bracket.jobs %}
<table class="table table-dark table-hover mb-0 align-middle" style="font-size:.875rem">
<thead>
<tr class="text-secondary small">
<th class="fw-normal">UUID</th>
<th class="fw-normal">File</th>
<th class="fw-normal">Material</th>
<th class="fw-normal">Printer</th>
<th class="fw-normal">Status</th>
<th class="fw-normal text-end">Created</th>
</tr>
</thead>
<tbody>
{% for j in bracket.jobs %}
<tr>
<td class="font-monospace text-secondary" style="font-size:.75rem">
{{ j.job_uuid[:8] if j.job_uuid else '—' }}
</td>
<td class="text-white">{{ j.file_name or j.name }}</td>
<td class="text-secondary">
{% if j.filament %}{{ j.filament.material.value }}{% if j.filament.color %} · {{ j.filament.color }}{% endif %}{% else %}—{% endif %}
</td>
<td class="text-secondary">{{ j.printer.name if j.printer else '—' }}</td>
<td>
{% set s = j.status.value %}
{% if s == 'printing' %}<span class="badge bg-primary">printing</span>
{% elif s == 'done' %}<span class="badge bg-success">done</span>
{% elif s == 'failed' %}<span class="badge bg-danger">failed</span>
{% elif s == 'cancelled' %}<span class="badge bg-warning text-dark">cancelled</span>
{% else %}<span class="badge bg-secondary">queued</span>
{% endif %}
</td>
<td class="text-secondary text-end">{{ j.created_at.strftime('%d.%m %H:%M') }}</td>
</tr>
{% endfor %}
</tbody>
</table>
{% else %}
<div class="card-body text-secondary">No jobs in this bracket yet.</div>
{% endif %}
</div>
{# ── Add Jobs Modal ── #}
<div class="modal fade" id="addJobsModal" tabindex="-1">
<div class="modal-dialog modal-lg">
<div class="modal-content bg-dark border-secondary">
<form method="POST" action="/brackets/{{ bracket.id }}/jobs" enctype="multipart/form-data">
<div class="modal-header border-secondary">
<h6 class="modal-title text-white">Add Job(s) to Bracket</h6>
<button type="button" class="btn-close btn-close-white" data-bs-dismiss="modal"></button>
</div>
<div class="modal-body d-flex flex-column gap-3">
{# STL file #}
<div>
<label class="form-label text-secondary small">STL / File</label>
<ul class="nav nav-pills mb-2" role="tablist">
<li class="nav-item">
<button class="nav-link active py-1 px-3" style="font-size:.8rem"
data-bs-toggle="pill" data-bs-target="#bUploadPane" type="button">
<i class="bi bi-upload me-1"></i>Upload
</button>
</li>
<li class="nav-item">
<button class="nav-link py-1 px-3" style="font-size:.8rem"
data-bs-toggle="pill" data-bs-target="#bLibraryPane" type="button">
<i class="bi bi-folder2-open me-1"></i>Library
<span class="badge bg-secondary ms-1">{{ stl_files | length }}</span>
</button>
</li>
</ul>
<div class="tab-content">
<div class="tab-pane fade show active" id="bUploadPane">
<input type="file" name="stl_file"
class="form-control bg-dark border-secondary text-white"
accept=".stl,.3mf,.gcode">
</div>
<div class="tab-pane fade" id="bLibraryPane">
{% if stl_files %}
<select name="stl_select" class="form-select bg-dark border-secondary text-white">
<option value="">— select file —</option>
{% for f in stl_files %}
{% set c = stl_cache.get(f) %}
<option value="{{ f }}">{{ f }}{% if c %} — {{ c.cube_x|round(1) }}×{{ c.cube_y|round(1) }}×{{ c.cube_z|round(1) }} mm · {{ c.volume_ccm }} cm³{% endif %}</option>
{% endfor %}
</select>
{% else %}
<p class="text-secondary small mb-0">No files in library yet.</p>
{% endif %}
</div>
</div>
</div>
<div class="row g-3">
<div class="col-md-6">
<label class="form-label text-secondary small">Material & Color</label>
<select name="filament_id" class="form-select bg-dark border-secondary text-white">
<option value="">— none —</option>
{% for f in filaments %}
<option value="{{ f.id }}">{{ f.material.value }}{% if f.color %} · {{ f.color }}{% endif %}{% if f.brand %} ({{ f.brand }}){% endif %}</option>
{% endfor %}
</select>
</div>
<div class="col-md-6">
<label class="form-label text-secondary small">
Quantity
<span class="text-secondary fw-normal">(copies to create)</span>
</label>
<input type="number" name="quantity"
class="form-control bg-dark border-secondary text-white"
value="1" min="1" max="500">
</div>
</div>
<div>
<label class="form-label text-secondary small">Notes</label>
<textarea name="notes" class="form-control bg-dark border-secondary text-white"
rows="2" placeholder="Optional notes (applied to all copies)"></textarea>
</div>
</div>
<div class="modal-footer border-secondary">
<button type="button" class="btn btn-secondary btn-sm" data-bs-dismiss="modal">Cancel</button>
<button type="submit" class="btn btn-primary btn-sm">Add Jobs</button>
</div>
</form>
</div>
</div>
</div>
{% endblock %}