Fix a bug: A1 refuses to print when packing any non first plate G-code

This commit is contained in:
hyyz17200
2026-05-08 00:48:13 +08:00
parent 287509411a
commit 614c65b30e
4 changed files with 148 additions and 4 deletions
+1
View File
@@ -15,4 +15,5 @@ Thumbs.db
*.bak
/settings.json
/reference
codec.py
+36 -4
View File
@@ -17,7 +17,13 @@ from .gcode import (
read_swap_gcode_file,
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 .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()
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:
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:
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():
name = item.filename
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)
if name == "Metadata/slice_info.config":
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"))
dst.writestr(item, data)
dst.writestr("Metadata/plate_1.gcode", gcode_bytes)
dst.writestr("Metadata/plate_1.gcode.md5", md5)
dst.writestr(gcode_member, gcode_bytes)
dst.writestr(f"{gcode_member}.md5", md5)
return md5
+21
View File
@@ -54,6 +54,27 @@ def read_filament_metadata(archive: zipfile.ZipFile) -> dict[int, dict[str, str]
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:
if value is None:
return None
+90
View File
@@ -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()