from __future__ import annotations import math from typing import Any def parse_gcode_preview_geometry( gcode: str, scale_factor: float = 0.001, scale_length: float = 1.0, show_e_0: bool = False, ) -> dict[str, Any]: """ Parses G-code text and extracts vertices, edges, and e_rates for mesh preview generation. Returns a dict with: vertices: list[tuple[float, float, float]] edges: list[tuple[int, int]] e_rates: list[float] default_layer_height: float prep_translation: list[float] prep_rotation: list[float] prep_scale: list[float] """ vertices: list[tuple[float, float, float]] = [] edges: list[tuple[int, int]] = [] e_rates: list[float] = [] last_x, last_y, last_z = 0.0, 0.0, 0.0 last_e = 0.0 offset_x, offset_y = 0.0, 0.0 custom_default_extrusion_rate = 0.0 default_layer_height = 0.2 default_layer_width = 0.4 last_v_idx = -1 last_p2 = None prep_translation = [0.0, 0.0, 0.0] prep_rotation = [0.0, 0.0, 0.0] prep_scale = [1.0, 1.0, 1.0] for line in gcode.splitlines(): # Parse metadata from comments if ';' in line: comment = line.split(';', 1)[1].strip() if comment.startswith("OFFSET_X="): try: offset_x = float(comment.split('=')[1]) except ValueError: pass elif comment.startswith("OFFSET_Y="): try: offset_y = float(comment.split('=')[1]) except ValueError: pass elif comment.startswith("DEFAULT_EXTRUSION_RATE="): try: custom_default_extrusion_rate = float(comment.split('=')[1]) except ValueError: pass elif comment.startswith("DEFAULT_LAYER_HEIGHT="): try: default_layer_height = float(comment.split('=')[1]) except ValueError: pass elif comment.startswith("DEFAULT_LAYER_WIDTH="): try: default_layer_width = float(comment.split('=')[1]) except ValueError: pass elif comment.startswith("PREPARATION_TOTAL_TRANSLATION="): try: vals = [float(v) for v in comment.split('=')[1].split(',')] if len(vals) >= 3: prep_translation = vals[:3] except ValueError: pass elif comment.startswith("PREPARATION_TOTAL_ROTATION="): try: vals = [float(v) for v in comment.split('=')[1].split(',')] if len(vals) >= 3: prep_rotation = vals[:3] except ValueError: pass elif comment.startswith("PREPARATION_TOTAL_SCALE="): try: vals = [float(v) for v in comment.split('=')[1].split(',')] if len(vals) >= 3: prep_scale = vals[:3] except ValueError: pass line_no_comment = line.split(';')[0].strip() if not line_no_comment: continue parts = line_no_comment.split() if not parts: continue cmd = parts[0].upper() params = {} for p in parts[1:]: if len(p) < 2: continue try: val = float(p[1:]) if p[0].upper() in 'XYZ': val *= scale_factor params[p[0].upper()] = val except ValueError: continue new_x = params.get('X', last_x) new_y = params.get('Y', last_y) new_z = params.get('Z', last_z) new_e = params.get('E', last_e) show = False if show_e_0: show = True if cmd == 'G1' and new_e > 0: show = True if show: ox, oy = offset_x * scale_factor, offset_y * scale_factor p1 = (last_x - ox, last_y - oy, last_z) p2 = (new_x - ox, new_y - oy, new_z) seg_len_mm = math.dist(p1, p2) * (1000.0 * scale_length) if seg_len_mm > 0 and custom_default_extrusion_rate > 0: e_rate = (new_e / seg_len_mm) / custom_default_extrusion_rate else: e_rate = 1.0 if last_v_idx != -1 and p1 == last_p2: v2_idx = len(vertices) vertices.append(p2) edges.append((last_v_idx, v2_idx)) last_v_idx = v2_idx else: v1_idx = len(vertices) v2_idx = v1_idx + 1 vertices.extend([p1, p2]) edges.append((v1_idx, v2_idx)) last_v_idx = v2_idx e_rates.append(e_rate) last_p2 = p2 else: last_v_idx = -1 last_p2 = None last_x, last_y, last_z = new_x, new_y, new_z last_e = new_e return { "vertices": vertices, "edges": edges, "e_rates": e_rates, "default_layer_height": default_layer_height, "prep_translation": prep_translation, "prep_rotation": prep_rotation, "prep_scale": prep_scale, }