mirror of
https://github.com/maziggy/bambuddy.git
synced 2026-10-17 19:36:28 +02:00
End-to-end live progress, two correctness fixes, and a UX warning around
the upstream OrcaSlicer bugs we discovered while testing.
LIVE PROGRESS
=============
Wire OrcaSlicer / BambuStudio's --pipe progress channel through the
sidecar -> Bambuddy -> persistent toast so a user-initiated slice shows
"{name} -- Generating G-code (75%) -- 47s" instead of just elapsed time.
The same wiring covers the SliceModal's filament-analysis preview slice
(the real slice that fires before profile picking, used to discover
which AMS slots an unsliced plate consumes) and the embedded-settings
fallback path triggered by Orca's --load-settings segfault on complex
H2D models.
- Sidecar (orca-slicer-api/bambuddy/profile-resolver, separate commit):
switch /slice from execFile to spawn, mkfifo per request, parse the
CLI's structured JSON progress events into a per-process
ProgressStore, expose GET /slice/progress/:requestId.
- Bambuddy backend: slicer_api.slice_with_profiles + slice_without_profiles
accept request_id + on_progress, spawn a 1Hz parallel poller that
forwards each snapshot via SliceDispatchService.set_progress(job_id,
...) onto the matching SliceJob; GET /slice-jobs/:id includes the
latest snapshot on every poll. The 404 from the early-race window
(POST fired before sidecar's progressStore.start) is treated as a
retry rather than terminal -- otherwise the poller bailed before any
progress could ever arrive.
- /api/v1/slicer/preview-progress/:requestId proxies the sidecar's
progress endpoint for the modal's filament-discovery flow (the
/filament-requirements call is server-originated; the browser can't
reach the sidecar directly).
- Frontend: SliceJobTrackerContext re-renders the persistent toast with
the new format when a useful progress frame is present, falls back
to elapsed-time-only when the sidecar hasn't emitted yet or doesn't
support progress. SliceModal.FilamentAnalysisSpinner generates a
per-(source, plate) UUID, polls the proxy at 1Hz, and mirrors the
inline spinner contents into a separate persistent toast so the
preview slice doesn't feel silent either.
CORRECTNESS FIXES
=================
- MakerWorld imports were persisting URL-encoded filenames verbatim
("stormtrooper-helmet%20h2d.3mf"). Backend now urllib.parse.unquote
s the manifest-supplied name and the URL path-tail fallback before
passing to save_3mf_bytes_to_library; frontend defensively
decodeURIComponent s in the slice toast / analysis spinner so
already-imported rows display cleanly without a backfill migration.
- The fallback path's slice_without_profiles call now forwards the
same request_id + on_progress as the primary slice_with_profiles
call so the toast keeps updating across the segfault -> embedded-
settings retry boundary instead of going blank.
ORCASLICER WARNING
==================
Verified two upstream OrcaSlicer CLI bugs reproduce on the latest
nightly (2.4.0-dev, 2026-04-28) with the help of an isolated AppImage
extract and a minimal sentinel-value-injected cube fixture:
- OrcaSlicer/OrcaSlicer#12426 -- SIGSEGV in
update_values_to_printer_extruders_for_multiple_filaments on
painted multi-extruder 3MFs (commented on the existing thread,
not a new issue)
- OrcaSlicer/OrcaSlicer#13386 -- CLI strict-validates parameter
values BambuStudio writes by default (solid_infill_filament: 0,
tree_support_wall_count: -1, prime_tower_brim_width: -1) and
rejects with exit 238, even though Orca's own GUI tolerates
them (filed by us alongside this change)
Settings -> Workflow -> Slicer card renders an amber inline warning
under the preferred-slicer dropdown when orcaslicer is selected,
linking both upstream issues and recommending BambuStudio until the
fixes land. Option stays pickable -- users who only slice STLs aren't
affected by either bug.
49 lines
1.9 KiB
Python
49 lines
1.9 KiB
Python
"""Polling endpoint for the in-memory slice-job dispatcher.
|
|
|
|
POST /library/files/{id}/slice and POST /archives/{id}/slice return a
|
|
job_id and a status_url pointing here. The frontend polls this until
|
|
status flips to `completed` or `failed`.
|
|
"""
|
|
|
|
from fastapi import APIRouter, HTTPException
|
|
|
|
from backend.app.core.auth import RequirePermissionIfAuthEnabled
|
|
from backend.app.core.permissions import Permission
|
|
from backend.app.models.user import User
|
|
from backend.app.services.slice_dispatch import slice_dispatch
|
|
|
|
router = APIRouter(prefix="/slice-jobs", tags=["slice-jobs"])
|
|
|
|
|
|
@router.get("/{job_id}")
|
|
async def get_slice_job(
|
|
job_id: int,
|
|
# Job IDs are sequential integers and the body leaks source filenames
|
|
# plus the resulting library_file_id / archive_id. Gate on LIBRARY_READ
|
|
# — same baseline a user needs to see slice sources or results.
|
|
_: User | None = RequirePermissionIfAuthEnabled(Permission.LIBRARY_READ),
|
|
):
|
|
job = slice_dispatch.get(job_id)
|
|
if job is None:
|
|
raise HTTPException(status_code=404, detail="Slice job not found or expired")
|
|
body: dict = {
|
|
"job_id": job.id,
|
|
"status": job.status,
|
|
"kind": job.kind,
|
|
"source_id": job.source_id,
|
|
"source_name": job.source_name,
|
|
"created_at": job.created_at.isoformat(),
|
|
"started_at": job.started_at.isoformat() if job.started_at else None,
|
|
"completed_at": job.completed_at.isoformat() if job.completed_at else None,
|
|
# Live progress fed by the sidecar's --pipe channel. Null when
|
|
# the slicer hasn't emitted yet (early "Initializing" phase) or
|
|
# the sidecar doesn't support progress (older versions).
|
|
"progress": job.progress,
|
|
}
|
|
if job.status == "completed":
|
|
body["result"] = job.result
|
|
elif job.status == "failed":
|
|
body["error_status"] = job.error_status
|
|
body["error_detail"] = job.error_detail
|
|
return body
|