mirror of
https://github.com/maziggy/bambuddy.git
synced 2026-10-17 19:36:28 +02:00
feat(slicing): build-plate override in the SliceModal (#1337)
Slicing an STL via the integrated slicer always defaulted to whatever
curr_bed_type lived in the chosen process preset (typically "Cool
Plate"), which the slicer CLI rejected for high-temp filaments with
"Plate 1: Cool Plate does not support filament 1". The user had no
way to switch plates without cloning the preset in BambuStudio.
The Slice modal now exposes a Build plate dropdown with the six
canonical BambuStudio / OrcaSlicer plates (Cool Plate, Cool Plate
SuperTack, Engineering Plate, High Temp Plate, Textured PEI Plate,
Smooth PEI Plate) plus an "Auto (use process preset)" option that
preserves the previous behavior. Positioned between Process profile
and Filament rows so a long filament list never pushes it off the
modal's scrolled viewport, and always enabled regardless of whether
the user picked a Printer Preset Bundle.
A new bed_type field on SliceRequest flows through both dispatch
paths:
- Resolved-preset path: _patch_process_bed_type overwrites
curr_bed_type on the process JSON before forwarding to the sidecar.
Works end-to-end today, no sidecar change needed.
- Bundle dispatch path: slice_with_bundle adds a bedType form field
to the sidecar multipart. The sidecar (maziggy/orca-slicer-api
fork) needs a matching change to honor it as --curr_bed_type on
the CLI invocation; until then the field is silently ignored and
the slice runs with the bundle's default plate.
This commit is contained in:
@@ -234,6 +234,86 @@ class TestSliceLibraryFile:
|
||||
assert final["result"]["print_time_seconds"] == 656
|
||||
assert captured["url"].endswith("/slice")
|
||||
|
||||
@pytest.mark.asyncio
|
||||
@pytest.mark.integration
|
||||
async def test_bed_type_override_patches_process_profile(self, async_client: AsyncClient, slice_test_setup):
|
||||
"""#1337: when SliceRequest.bed_type is set, the process JSON sent to
|
||||
the sidecar must carry curr_bed_type with that exact value. Without
|
||||
the patch, slicing high-temp filaments on a "Cool Plate" process
|
||||
preset fails inside the slicer CLI with "does not support filament 1"
|
||||
and the user has no way to switch plates from the SliceModal."""
|
||||
captured: dict = {}
|
||||
|
||||
def handler(request: httpx.Request) -> httpx.Response:
|
||||
captured["body"] = bytes(request.content)
|
||||
return httpx.Response(
|
||||
status_code=200,
|
||||
content=b"PK\x03\x04 fake",
|
||||
headers={
|
||||
"x-print-time-seconds": "10",
|
||||
"x-filament-used-g": "0.1",
|
||||
"x-filament-used-mm": "1.0",
|
||||
},
|
||||
)
|
||||
|
||||
_install_mock_sidecar(handler)
|
||||
response = await async_client.post(
|
||||
f"/api/v1/library/files/{slice_test_setup['src_file_id']}/slice",
|
||||
json={
|
||||
"printer_preset_id": slice_test_setup["printer_id"],
|
||||
"process_preset_id": slice_test_setup["process_id"],
|
||||
"filament_preset_id": slice_test_setup["filament_id"],
|
||||
"bed_type": "Textured PEI Plate",
|
||||
},
|
||||
)
|
||||
assert response.status_code == 202
|
||||
final = await _wait_for_job(async_client, response.json()["job_id"])
|
||||
assert final["status"] == "completed", final
|
||||
|
||||
# The presetProfile part of the multipart upload now carries the
|
||||
# override. Searching the raw body avoids parsing the multipart by
|
||||
# hand — the substring is unique enough since we control the JSON
|
||||
# being patched.
|
||||
assert b'"curr_bed_type": "Textured PEI Plate"' in captured["body"], (
|
||||
"bed_type override must appear in the process JSON sent to the sidecar"
|
||||
)
|
||||
|
||||
@pytest.mark.asyncio
|
||||
@pytest.mark.integration
|
||||
async def test_bed_type_omitted_leaves_process_profile_untouched(self, async_client: AsyncClient, slice_test_setup):
|
||||
"""Companion to the override test: the patch must NOT fire when the
|
||||
client omits bed_type, so the process preset's own curr_bed_type
|
||||
(or absence thereof) is forwarded to the sidecar unchanged."""
|
||||
captured: dict = {}
|
||||
|
||||
def handler(request: httpx.Request) -> httpx.Response:
|
||||
captured["body"] = bytes(request.content)
|
||||
return httpx.Response(
|
||||
status_code=200,
|
||||
content=b"PK\x03\x04 fake",
|
||||
headers={
|
||||
"x-print-time-seconds": "10",
|
||||
"x-filament-used-g": "0.1",
|
||||
"x-filament-used-mm": "1.0",
|
||||
},
|
||||
)
|
||||
|
||||
_install_mock_sidecar(handler)
|
||||
response = await async_client.post(
|
||||
f"/api/v1/library/files/{slice_test_setup['src_file_id']}/slice",
|
||||
json={
|
||||
"printer_preset_id": slice_test_setup["printer_id"],
|
||||
"process_preset_id": slice_test_setup["process_id"],
|
||||
"filament_preset_id": slice_test_setup["filament_id"],
|
||||
},
|
||||
)
|
||||
assert response.status_code == 202
|
||||
final = await _wait_for_job(async_client, response.json()["job_id"])
|
||||
assert final["status"] == "completed", final
|
||||
assert b"curr_bed_type" not in captured["body"], (
|
||||
"bed_type must stay out of the process JSON when no override is set"
|
||||
)
|
||||
|
||||
@pytest.mark.asyncio
|
||||
@pytest.mark.integration
|
||||
async def test_invalid_preset_id_surfaces_as_failed_job_with_status_400(
|
||||
@@ -513,6 +593,84 @@ class TestSliceWithBundle:
|
||||
assert b'name="presetProfile"' not in body
|
||||
assert b'name="filamentProfile"' not in body
|
||||
|
||||
@pytest.mark.asyncio
|
||||
@pytest.mark.integration
|
||||
async def test_bundle_dispatch_forwards_bed_type_when_set(self, async_client: AsyncClient, slice_test_setup):
|
||||
"""#1337 follow-up: bed-type override flows through the bundle path
|
||||
as a `bedType` form field so the sidecar can pass
|
||||
`--curr_bed_type` to the CLI. Bambuddy can't patch the bundle's
|
||||
process JSON locally — the sidecar materialises it from the stored
|
||||
.bbscfg — so the form field is the only handle."""
|
||||
captured: dict = {}
|
||||
|
||||
def handler(request: httpx.Request) -> httpx.Response:
|
||||
captured["body"] = bytes(request.content)
|
||||
return httpx.Response(
|
||||
status_code=200,
|
||||
content=b"PK\x03\x04 fake",
|
||||
headers={
|
||||
"x-print-time-seconds": "10",
|
||||
"x-filament-used-g": "0.1",
|
||||
"x-filament-used-mm": "1.0",
|
||||
},
|
||||
)
|
||||
|
||||
_install_mock_sidecar(handler)
|
||||
response = await async_client.post(
|
||||
f"/api/v1/library/files/{slice_test_setup['src_file_id']}/slice",
|
||||
json={
|
||||
"bundle": {
|
||||
"bundle_id": "abc",
|
||||
"printer_name": "# X1C",
|
||||
"process_name": "# 0.20mm",
|
||||
"filament_names": ["# Bambu PLA"],
|
||||
},
|
||||
"bed_type": "Engineering Plate",
|
||||
},
|
||||
)
|
||||
assert response.status_code == 202
|
||||
final = await _wait_for_job(async_client, response.json()["job_id"])
|
||||
assert final["status"] == "completed", final
|
||||
body = captured["body"]
|
||||
assert b'name="bedType"' in body
|
||||
assert b"Engineering Plate" in body
|
||||
|
||||
@pytest.mark.asyncio
|
||||
@pytest.mark.integration
|
||||
async def test_bundle_dispatch_omits_bed_type_when_unset(self, async_client: AsyncClient, slice_test_setup):
|
||||
"""Companion test: no bed_type ⇒ no bedType form field, so the
|
||||
bundle's own curr_bed_type is preserved end-to-end."""
|
||||
captured: dict = {}
|
||||
|
||||
def handler(request: httpx.Request) -> httpx.Response:
|
||||
captured["body"] = bytes(request.content)
|
||||
return httpx.Response(
|
||||
status_code=200,
|
||||
content=b"PK\x03\x04 fake",
|
||||
headers={
|
||||
"x-print-time-seconds": "10",
|
||||
"x-filament-used-g": "0.1",
|
||||
"x-filament-used-mm": "1.0",
|
||||
},
|
||||
)
|
||||
|
||||
_install_mock_sidecar(handler)
|
||||
response = await async_client.post(
|
||||
f"/api/v1/library/files/{slice_test_setup['src_file_id']}/slice",
|
||||
json={
|
||||
"bundle": {
|
||||
"bundle_id": "abc",
|
||||
"printer_name": "# X1C",
|
||||
"process_name": "# 0.20mm",
|
||||
"filament_names": ["# Bambu PLA"],
|
||||
},
|
||||
},
|
||||
)
|
||||
assert response.status_code == 202
|
||||
final = await _wait_for_job(async_client, response.json()["job_id"])
|
||||
assert final["status"] == "completed", final
|
||||
assert b'name="bedType"' not in captured["body"]
|
||||
|
||||
@pytest.mark.asyncio
|
||||
@pytest.mark.integration
|
||||
async def test_bundle_dispatch_3mf_falls_back_to_embedded_on_5xx(
|
||||
|
||||
Reference in New Issue
Block a user