Grotti 2.2.6: Wand-Vorschau, dichterer 3D-Pfad, ASCII-Dateinamen.

Kacheln zeigen Moonraker-Thumbnails; Toolpath-Thinning behält ganze Schichten; Nicht-ASCII-Namen vermeiden 10115 beim Senden.
This commit is contained in:
Dominic Gundel committed 2026-10-17 11:22:43 +02:00
1 parent 7da895bbcd
commit b277ca48aa
16 files changed
+302 -50

No files matched your search

+4 -1
View File
@@ -910,7 +910,10 @@ def create_app(cfg):
pass
data["file_id"] = file_id
data["name"] = row["name"]
return jsonify(data)
data["version"] = TOOLPATH_VERSION
resp = jsonify(data)
resp.headers["Cache-Control"] = "no-store"
return resp
@app.patch("/api/v1/dateien/<int:file_id>")
def file_patch(file_id):
+2 -2
View File
@@ -10,8 +10,8 @@ window.GrottiGcodeViewer = (function () {
const COLOR_OBJ_EX = 0xef4444;
const COLOR_OBJ_CUR = 0xfbbf24;
const GRID_STEP = 10;
// Nur leichte Doppelung — starke Offsets + Punkte wirkten wie Vollkörper mit Löchern
const FAT_OFFSETS = [0, -0.22, 0.22];
// Keine Parallel-Offsets: bei ausgedünnten Pfaden wirkten sie gesprenkelt
const FAT_OFFSETS = [0];
function create(canvas) {
const wrap = document.createElement("div");
+34 -3
View File
@@ -620,12 +620,44 @@ article.machine.offline:hover { opacity: 1; }
.job-line {
display: flex;
align-items: center;
gap: 0.45rem;
gap: 0.55rem;
min-width: 0;
color: var(--muted);
font-size: var(--t-sm);
padding: 0.45rem 0.5rem;
border-radius: 10px;
background: rgba(255, 255, 255, 0.03);
border: 1px solid var(--line);
}
.job-thumb {
width: 4.5rem;
height: 4.5rem;
object-fit: contain;
border-radius: 9px;
border: 1px solid var(--line);
background: var(--card-2);
flex: none;
}
.job-thumb[hidden] { display: none; }
.job-ico { display: inline-flex; flex: none; color: var(--faint); }
.job-ico .ico { width: 1.1rem; height: 1.1rem; }
.job-line.has-thumb .job-ico { display: none; }
.job-meta {
min-width: 0;
flex: 1;
display: flex;
flex-direction: column;
gap: 0.3rem;
align-items: flex-start;
}
.job-line .name {
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
max-width: 100%;
color: var(--ink);
font-weight: 650;
}
.job-line .name { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; flex: 1; min-width: 0; color: var(--ink); }
.job-line .swatches { padding: 0; min-height: 0; flex: none; }
.badge {
font-size: 0.65rem;
@@ -1874,7 +1906,6 @@ body.desk-on main {
.summary-text b { color: var(--ink); }
.page-head-side { display: flex; align-items: center; gap: 0.75rem 1rem; flex-wrap: wrap; }
.page-head-side .check { margin: 0; font-size: var(--t-sm); color: var(--muted); }
.job-line > .ico { color: var(--faint); }
.job-line[hidden] { display: none; }
article.machine.flash { animation: flash 2s var(--ease); }
@keyframes flash { 0%, 40% { box-shadow: 0 0 0 2px var(--accent), var(--shadow); } }
+2 -2
View File
@@ -146,7 +146,7 @@
{% endblock %}
{% block script %}
<script src="{{ url_for('static', filename='three.min.js') }}?v=1"></script>
<script src="{{ url_for('static', filename='gcode-viewer.js') }}?v=8"></script>
<script src="{{ url_for('static', filename='gcode-viewer.js') }}?v=10"></script>
<script src="{{ url_for('static', filename='sweep-viewer.js') }}?v=1"></script>
<script>
const families = { kobra_s1: "Kobra S1", kobra_3: "Kobra 3" };
@@ -198,7 +198,7 @@ async function showToolpath(file, progress) {
if (viewerFileId !== file.id) {
meta.textContent = "Pfad wird gelesen …";
try {
const data = await api("/dateien/" + file.id + "/pfad");
const data = await api("/dateien/" + file.id + "/pfad?tp=8");
viewer.setPath(data);
viewerFileId = file.id;
meta.textContent = (data.count || 0).toLocaleString("de-DE") + " Punkte";
+1 -1
View File
@@ -9,7 +9,7 @@
<link rel="icon" type="image/png" sizes="32x32" href="{{ url_for('static', filename='brand/favicon-32.png') }}">
<link rel="icon" type="image/png" sizes="512x512" href="{{ url_for('static', filename='brand/favicon.png') }}">
<link rel="apple-touch-icon" href="{{ url_for('static', filename='brand/apple-touch-icon.png') }}">
<link rel="stylesheet" href="{{ url_for('static', filename='grotti.css') }}?v=39">
<link rel="stylesheet" href="{{ url_for('static', filename='grotti.css') }}?v=41">
<script>
(function () {
try {
+41 -5
View File
@@ -86,7 +86,7 @@
{% endblock %}
{% block script %}
<script src="{{ url_for('static', filename='three.min.js') }}?v=1"></script>
<script src="{{ url_for('static', filename='gcode-viewer.js') }}?v=8"></script>
<script src="{{ url_for('static', filename='gcode-viewer.js') }}?v=10"></script>
<script>
const labels = STATE_LABELS;
const rank = { printing: 0, paused: 1, error: 2, heating: 3, complete: 4, cancelled: 5, standby: 6, offline: 7 };
@@ -490,7 +490,14 @@ function buildCard(printer) {
<div data-role="bar"></div>
<div class="stats" data-role="stats"></div>
<div data-role="temps"></div>
<div class="job-line" data-role="jobline">${icon("cube")}<span class="name" data-role="job"></span><span class="swatches" data-role="swatches"></span></div>
<div class="job-line" data-role="jobline">
<img class="job-thumb" data-role="thumb" alt="" hidden>
<span class="job-ico" data-role="jobico">${icon("cube")}</span>
<div class="job-meta">
<span class="name" data-role="job"></span>
<span class="swatches" data-role="swatches"></span>
</div>
</div>
<div data-role="flow"></div>
<div data-role="strips"></div>
<div class="card-actions" data-role="controls"></div>
@@ -551,7 +558,36 @@ function patchCard(article, printer) {
setHtml(role("stats"), statsHtml(printer, state));
setHtml(role("temps"), tempbarsHtml(printer, state));
const jobline = role("jobline");
if (jobline) jobline.hidden = !jobLineVisible(printer, state);
const jobVisible = jobLineVisible(printer, state);
const hasThumb = !!(jobVisible && printer.preview);
if (jobline) {
jobline.hidden = !jobVisible;
jobline.classList.toggle("has-thumb", hasThumb);
}
const thumb = role("thumb");
const jobico = role("jobico");
if (thumb) {
if (hasThumb) {
const next = withToken(printer.preview);
if (thumb.dataset.srcKey !== next) {
thumb.dataset.srcKey = next;
thumb.src = next;
}
thumb.hidden = false;
thumb.onerror = () => {
thumb.hidden = true;
thumb.dataset.srcKey = "";
if (jobline) jobline.classList.remove("has-thumb");
if (jobico) jobico.hidden = false;
};
if (jobico) jobico.hidden = true;
} else {
thumb.hidden = true;
thumb.removeAttribute("src");
thumb.dataset.srcKey = "";
if (jobico) jobico.hidden = false;
}
}
const job = role("job");
if (job) {
job.textContent = jobName(printer.filename) || "";
@@ -762,10 +798,10 @@ async function syncDeskViewer(printer) {
}
try {
let data = toolpathCache.get(file.id);
const needLoad = !data || data.version !== 5 || deskViewerFileId !== file.id;
const needLoad = !data || data.version !== 8 || deskViewerFileId !== file.id;
if (needLoad) {
if (caption) caption.textContent = "wird gelesen …";
data = await api("/dateien/" + file.id + "/pfad");
data = await api("/dateien/" + file.id + "/pfad?tp=8");
toolpathCache.set(file.id, data);
const sameFile = deskViewerFileId === file.id;
deskViewer.setPath(data, { keepView: sameFile });