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
|
||||
/settings.json
|
||||
/reference
|
||||
codec.py
|
||||
@@ -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
|
||||
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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