Fix a bug: A1 refuses to print when packing any non first plate G-code
This commit is contained in:
@@ -15,4 +15,5 @@ Thumbs.db
|
|||||||
|
|
||||||
*.bak
|
*.bak
|
||||||
/settings.json
|
/settings.json
|
||||||
|
/reference
|
||||||
codec.py
|
codec.py
|
||||||
@@ -17,7 +17,13 @@ from .gcode import (
|
|||||||
read_swap_gcode_file,
|
read_swap_gcode_file,
|
||||||
resolve_swap_gcode_path,
|
resolve_swap_gcode_path,
|
||||||
)
|
)
|
||||||
from .metadata import read_filament_metadata, read_slice_plate_metadata, safe_float, update_first_slice_info
|
from .metadata import (
|
||||||
|
read_filament_metadata,
|
||||||
|
read_model_settings_gcode_members,
|
||||||
|
read_slice_plate_metadata,
|
||||||
|
safe_float,
|
||||||
|
update_first_slice_info,
|
||||||
|
)
|
||||||
from .models import BuildOptions, BuildResult, GcodePatchConfig, PlateJob, PlateSource
|
from .models import BuildOptions, BuildResult, GcodePatchConfig, PlateJob, PlateSource
|
||||||
from .patches import apply_gcode_patches, parse_patch_config
|
from .patches import apply_gcode_patches, parse_patch_config
|
||||||
|
|
||||||
@@ -128,9 +134,35 @@ def update_preview_label(png_bytes: bytes, label: str, small: bool = False) -> b
|
|||||||
return output.getvalue()
|
return output.getvalue()
|
||||||
|
|
||||||
|
|
||||||
|
def resolve_output_gcode_member(archive: zipfile.ZipFile, fallback_member: str) -> str:
|
||||||
|
configured_members = read_model_settings_gcode_members(archive)
|
||||||
|
if fallback_member in configured_members:
|
||||||
|
return fallback_member
|
||||||
|
existing_members = set(list_gcode_members(archive))
|
||||||
|
for member in configured_members:
|
||||||
|
if member in existing_members:
|
||||||
|
return member
|
||||||
|
if configured_members:
|
||||||
|
return configured_members[0]
|
||||||
|
return fallback_member
|
||||||
|
|
||||||
|
|
||||||
|
def preview_members_for_gcode_member(gcode_member: str) -> set[str]:
|
||||||
|
match = GCODE_MEMBER_RE.match(gcode_member)
|
||||||
|
if not match:
|
||||||
|
return set()
|
||||||
|
plate_index = match.group(1)
|
||||||
|
return {
|
||||||
|
f"Metadata/plate_{plate_index}.png",
|
||||||
|
f"Metadata/plate_{plate_index}_small.png",
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
def write_output_3mf(base_3mf: Path, output_3mf: Path, gcode_bytes: bytes, sources: list[PlateSource], options: BuildOptions) -> str:
|
def write_output_3mf(base_3mf: Path, output_3mf: Path, gcode_bytes: bytes, sources: list[PlateSource], options: BuildOptions) -> str:
|
||||||
md5 = hashlib.md5(gcode_bytes).hexdigest()
|
md5 = hashlib.md5(gcode_bytes).hexdigest()
|
||||||
with zipfile.ZipFile(base_3mf, "r") as src, zipfile.ZipFile(output_3mf, "w", compression=zipfile.ZIP_DEFLATED, compresslevel=1) as dst:
|
with zipfile.ZipFile(base_3mf, "r") as src, zipfile.ZipFile(output_3mf, "w", compression=zipfile.ZIP_DEFLATED, compresslevel=1) as dst:
|
||||||
|
gcode_member = resolve_output_gcode_member(src, sources[0].member_name)
|
||||||
|
preview_members = preview_members_for_gcode_member(gcode_member)
|
||||||
for item in src.infolist():
|
for item in src.infolist():
|
||||||
name = item.filename
|
name = item.filename
|
||||||
if GCODE_MEMBER_RE.match(name) or MD5_MEMBER_RE.match(name):
|
if GCODE_MEMBER_RE.match(name) or MD5_MEMBER_RE.match(name):
|
||||||
@@ -138,11 +170,11 @@ def write_output_3mf(base_3mf: Path, output_3mf: Path, gcode_bytes: bytes, sourc
|
|||||||
data = src.read(name)
|
data = src.read(name)
|
||||||
if name == "Metadata/slice_info.config":
|
if name == "Metadata/slice_info.config":
|
||||||
data = update_first_slice_info(data, sources, options)
|
data = update_first_slice_info(data, sources, options)
|
||||||
elif options.add_preview_label and name in {"Metadata/plate_1.png", "Metadata/plate_1_small.png"}:
|
elif options.add_preview_label and name in preview_members:
|
||||||
data = update_preview_label(data, f"{len(sources)} plates", small=name.endswith("_small.png"))
|
data = update_preview_label(data, f"{len(sources)} plates", small=name.endswith("_small.png"))
|
||||||
dst.writestr(item, data)
|
dst.writestr(item, data)
|
||||||
dst.writestr("Metadata/plate_1.gcode", gcode_bytes)
|
dst.writestr(gcode_member, gcode_bytes)
|
||||||
dst.writestr("Metadata/plate_1.gcode.md5", md5)
|
dst.writestr(f"{gcode_member}.md5", md5)
|
||||||
return md5
|
return md5
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -54,6 +54,27 @@ def read_filament_metadata(archive: zipfile.ZipFile) -> dict[int, dict[str, str]
|
|||||||
return result
|
return result
|
||||||
|
|
||||||
|
|
||||||
|
def read_model_settings_gcode_members(archive: zipfile.ZipFile) -> list[str]:
|
||||||
|
try:
|
||||||
|
data = archive.read("Metadata/model_settings.config")
|
||||||
|
except KeyError:
|
||||||
|
return []
|
||||||
|
try:
|
||||||
|
root = ET.fromstring(data.decode("utf-8-sig", errors="replace"))
|
||||||
|
except Exception:
|
||||||
|
return []
|
||||||
|
members: list[str] = []
|
||||||
|
for plate in root.findall("plate"):
|
||||||
|
for metadata in plate.findall("metadata"):
|
||||||
|
if metadata.attrib.get("key") != "gcode_file":
|
||||||
|
continue
|
||||||
|
value = metadata.attrib.get("value", "").strip().replace("\\", "/").lstrip("/")
|
||||||
|
if value and GCODE_MEMBER_RE.match(value):
|
||||||
|
members.append(value)
|
||||||
|
break
|
||||||
|
return members
|
||||||
|
|
||||||
|
|
||||||
def safe_float(value: str | None) -> float | None:
|
def safe_float(value: str | None) -> float | None:
|
||||||
if value is None:
|
if value is None:
|
||||||
return None
|
return None
|
||||||
|
|||||||
@@ -0,0 +1,90 @@
|
|||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import hashlib
|
||||||
|
import tempfile
|
||||||
|
import unittest
|
||||||
|
import zipfile
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
from a1_swap_mod_packer.builder import (
|
||||||
|
preview_members_for_gcode_member,
|
||||||
|
resolve_output_gcode_member,
|
||||||
|
write_output_3mf,
|
||||||
|
)
|
||||||
|
from a1_swap_mod_packer.metadata import read_model_settings_gcode_members
|
||||||
|
from a1_swap_mod_packer.models import BuildOptions, PlateSource
|
||||||
|
|
||||||
|
|
||||||
|
class ActivePlateMemberTest(unittest.TestCase):
|
||||||
|
def test_write_output_preserves_configured_plate_member(self) -> None:
|
||||||
|
with tempfile.TemporaryDirectory() as directory:
|
||||||
|
root = Path(directory)
|
||||||
|
source_3mf = root / "plate2.3mf"
|
||||||
|
output_3mf = root / "out.3mf"
|
||||||
|
self.write_plate2_archive(source_3mf)
|
||||||
|
|
||||||
|
with zipfile.ZipFile(source_3mf, "r") as archive:
|
||||||
|
self.assertEqual(read_model_settings_gcode_members(archive), ["Metadata/plate_2.gcode"])
|
||||||
|
self.assertEqual(resolve_output_gcode_member(archive, "Metadata/plate_2.gcode"), "Metadata/plate_2.gcode")
|
||||||
|
self.assertEqual(
|
||||||
|
preview_members_for_gcode_member("Metadata/plate_2.gcode"),
|
||||||
|
{"Metadata/plate_2.png", "Metadata/plate_2_small.png"},
|
||||||
|
)
|
||||||
|
|
||||||
|
gcode_bytes = b"G1 X1\n"
|
||||||
|
sources = [
|
||||||
|
PlateSource(
|
||||||
|
source_3mf=source_3mf,
|
||||||
|
member_name="Metadata/plate_2.gcode",
|
||||||
|
gcode_text="G1 X0\n",
|
||||||
|
)
|
||||||
|
]
|
||||||
|
options = BuildOptions(
|
||||||
|
swap_gcode="",
|
||||||
|
output_3mf=output_3mf,
|
||||||
|
add_preview_label=False,
|
||||||
|
apply_gcode_patches=False,
|
||||||
|
)
|
||||||
|
md5 = write_output_3mf(source_3mf, output_3mf, gcode_bytes, sources, options)
|
||||||
|
|
||||||
|
with zipfile.ZipFile(output_3mf, "r") as archive:
|
||||||
|
names = set(archive.namelist())
|
||||||
|
self.assertIn("Metadata/plate_2.gcode", names)
|
||||||
|
self.assertIn("Metadata/plate_2.gcode.md5", names)
|
||||||
|
self.assertNotIn("Metadata/plate_1.gcode", names)
|
||||||
|
self.assertEqual(archive.read("Metadata/plate_2.gcode"), gcode_bytes)
|
||||||
|
self.assertEqual(archive.read("Metadata/plate_2.gcode.md5").decode(), md5)
|
||||||
|
self.assertEqual(md5, hashlib.md5(gcode_bytes).hexdigest())
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def write_plate2_archive(path: Path) -> None:
|
||||||
|
model_settings = """<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<config>
|
||||||
|
<plate>
|
||||||
|
<metadata key="plater_id" value="1"/>
|
||||||
|
<metadata key="gcode_file" value=""/>
|
||||||
|
</plate>
|
||||||
|
<plate>
|
||||||
|
<metadata key="plater_id" value="2"/>
|
||||||
|
<metadata key="gcode_file" value="Metadata/plate_2.gcode"/>
|
||||||
|
</plate>
|
||||||
|
</config>
|
||||||
|
"""
|
||||||
|
slice_info = """<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<config>
|
||||||
|
<plate>
|
||||||
|
<metadata key="index" value="2"/>
|
||||||
|
</plate>
|
||||||
|
</config>
|
||||||
|
"""
|
||||||
|
with zipfile.ZipFile(path, "w") as archive:
|
||||||
|
archive.writestr("Metadata/model_settings.config", model_settings)
|
||||||
|
archive.writestr("Metadata/slice_info.config", slice_info)
|
||||||
|
archive.writestr("Metadata/plate_2.gcode", "G1 X0\n")
|
||||||
|
archive.writestr("Metadata/plate_2.gcode.md5", "old")
|
||||||
|
archive.writestr("Metadata/plate_2.png", b"png")
|
||||||
|
archive.writestr("Metadata/plate_2_small.png", b"png")
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
unittest.main()
|
||||||
Reference in New Issue
Block a user