nodeslicer/infra/networking/gcode_utils.py
2026-08-26 12:02:01 +03:00

90 lines
3.5 KiB
Python

import re
import json
from collections import Counter
from typing import Any
def parse_gcode_metadata(gcode: str) -> dict[str, Any]:
"""Extracts metadata fields from G-code comments."""
fields = {}
# 1. Time per print (decimal hours)
time_match = re.search(r"; estimated printing time \(normal mode\) = (.*)", gcode)
if time_match:
time_str = time_match.group(1)
h = re.search(r"(\d+)h", time_str)
m = re.search(r"(\d+)m", time_str)
s = re.search(r"(\d+)s", time_str)
total_hours = 0.0
if h: total_hours += float(h.group(1))
if m: total_hours += float(m.group(1)) / 60.0
if s: total_hours += float(s.group(1)) / 3600.0
fields["Time per print"] = round(total_hours, 2)
# 2. Parts per print
parts_match = re.search(r"; objects_info = (.*)", gcode)
if parts_match:
try:
info = json.loads(parts_match.group(1))
fields["Parts per print"] = len(info.get("objects", []))
fields["objects_info"] = info
except Exception: pass
# 3. Weight per print
weight_match = re.search(r"; total filament used \[g\] = ([\d\.]+)", gcode)
if weight_match:
fields["Weight per print"] = int(float(weight_match.group(1)))
# 4. Profile for reference
profile_match = re.search(r"; printer_settings_id = (.*)", gcode)
if profile_match:
fields["Printer Profile"] = profile_match.group(1)
return fields
def get_gcode_name_from_metadata(gcode: str, default: str = "output") -> str:
"""Generates a descriptive name for the G-code based on its objects_info metadata or object comments."""
parts_match = re.search(r"; objects_info = (.*)", gcode)
filament_match = re.search(r"; filament_type = (.*)", gcode)
filament = filament_match[1] if filament_match else ''
printer_match = re.search(r"; printer_model = (.*)", gcode)
printer = printer_match[1] if printer_match else ''
if parts_match:
try:
gcode_info = json.loads(parts_match.group(1))
names: list[str] = [str(v['name'].split(" id:")[0]) for v in gcode_info['objects']]
counts = Counter(names)
object_names = "-".join(f"{c}x{n}" if c > 1 else n for n, c in counts.items())
parts = [object_names, filament, printer]
return "-".join([str(p) for p in parts if p])
except Exception:
pass
# Fallback search for object name comments (e.g. ; OBJECT_NAME = ..., ; object_name = ..., ; OBJECT: ...)
obj_matches = re.findall(r";\s*(?:OBJECT_NAME|object_name|OBJECT|object)\s*[:=]\s*(.+)", gcode)
if obj_matches:
names = [m.strip().split(" id:")[0] for m in obj_matches if m.strip()]
if names:
counts = Counter(names)
object_names = "-".join(f"{c}x{n}" if c > 1 else n for n, c in counts.items())
parts = [object_names, filament, printer]
return "-".join([str(p) for p in parts if p])
return default
def prepare_gcode_file(gcode: str, name_part: str) -> tuple[bytes, str]:
"""
Checks if the G-code string contains instructions to generate binary BGcode.
Converts to binary if needed.
Returns a tuple of (file_bytes, filename_with_suffix).
"""
is_binary = "; binary_gcode = 1" in gcode
if is_binary:
from .bgcode import convert_to_bgcode
bgcode_bytes = convert_to_bgcode(gcode, name_part, True)
return bgcode_bytes, f"{name_part}.bgcode"
else:
return gcode.encode("utf-8"), f"{name_part}.gcode"