# Bounds and framing

from pygrbl_build import SvgProfile, generate_framing_gcode, get_bounding_box, svg_gcode

# From a file path, opened and streamed in C...
min_x, max_x, min_y, max_y = get_bounding_box("job.nc")

# ...or from G-code already in memory, no file needed:
gcode = "\n".join(svg_gcode("logo.svg", SvgProfile()))
min_x, max_x, min_y, max_y = get_bounding_box(gcode)           # str
min_x, max_x, min_y, max_y = get_bounding_box(gcode.encode())  # or bytes

frame = generate_framing_gcode(min_x, max_x, min_y, max_y, power=10.0, speed=1000)

get_bounding_box returns (min_x, max_x, min_y, max_y), computed by a C parser. It accepts a file path (str or Path, opened and streamed in C) or the G-code content itself (bytes, bytearray or a multi-line str), so the G-code never has to exist on disk. A str is a path when it names an existing file, and G-code text otherwise. Only X and Y are considered (Z is ignored), and rapid moves to the origin (G0 with X0 and Y0) are skipped so the generators' own home moves don't expand the box. G-code without X/Y coordinates raises ValueError.

generate_framing_gcode returns the perimeter trace as a list of lines (power is 0 to 100, speed in mm/min), ending with the beam off and a rapid move to the origin. Run it before the job so the operator can confirm where the work lands on the material.