Various bugfixes, moved svgtogcode to its own file
Aussi, on peut exercise toute la communication c'est trop chouette!!!
This commit is contained in:
parent
b43c0b64c3
commit
f06589562c
7 changed files with 198 additions and 176 deletions
|
@ -131,10 +131,14 @@ class GCodeToMotors:
|
|||
# HARDWARE.realize() applies the current commands to the actual hardware
|
||||
|
||||
def move(self):
|
||||
print(self.target_units)
|
||||
print(self.feedrate)
|
||||
self.HARDWARE.probe(self)
|
||||
while not self.CONTROLLER(self): # Allow controller to alter self
|
||||
self.HARDWARE.realize(self)
|
||||
time.sleep(self.ctrl_step)
|
||||
print("realized?")
|
||||
time.sleep(self.ctrl_step)
|
||||
self.HARDWARE.probe(self)
|
||||
|
||||
self.calculate_deltas()
|
||||
|
||||
|
@ -150,7 +154,7 @@ class GCodeToMotors:
|
|||
or has_command('Z', instruction):
|
||||
code = search_string('G', instruction)
|
||||
match code:
|
||||
case 0, 1, 2, 3:
|
||||
case 0 | 1 | 2 | 3:
|
||||
if self.abs_mode:
|
||||
if has_command('X', instruction):
|
||||
fp[0] = search_string('X', instruction)
|
||||
|
@ -172,17 +176,17 @@ class GCodeToMotors:
|
|||
fp[2] = self.current_units[2] + search_string('Z', instruction)
|
||||
case _:
|
||||
pass
|
||||
|
||||
|
||||
match code:
|
||||
case 0, 1:
|
||||
self.set_position(fp[0], fp[1], fp[2])
|
||||
case 0 | 1:
|
||||
self.set_target(fp[0], fp[1], fp[2])
|
||||
if has_command('G', instruction) and code == 1:
|
||||
self.feedrate = search_string('F', instruction)
|
||||
if self.feedrate == 0:
|
||||
self.feedrate = self.get_max_speed()
|
||||
self.move()
|
||||
|
||||
case 2, 3:
|
||||
case 2 | 3:
|
||||
center = [0., 0., 0.]
|
||||
center[0] = search_string('I', instruction) + self.current_units[0]
|
||||
center[1] = search_string('J', instruction) + self.current_units[1]
|
||||
|
@ -258,7 +262,7 @@ class GCodeToMotors:
|
|||
case 5.1:
|
||||
raise NotImplementedError("PAS DE SPLINE QUADRATIQUE J'AI LA FLEMME")
|
||||
|
||||
case 5.2, 5.3:
|
||||
case 5.2 | 5.3:
|
||||
raise NotImplementedError("Experimental Unimplemented Feature")
|
||||
|
||||
case 6:
|
||||
|
@ -338,9 +342,11 @@ def has_command(key: str, instruction: str) -> bool:
|
|||
return key in instruction
|
||||
|
||||
|
||||
def search_string(key: str, instruction: str) -> int:
|
||||
def search_string(key: str, instruction: str) -> float:
|
||||
index = instruction.find(key)
|
||||
if(index==-1):
|
||||
return float(0.)
|
||||
tmp = instruction[index+1:].split(' ')[0]
|
||||
return int(tmp)
|
||||
return float(tmp)
|
||||
|
||||
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
|
||||
import numpy as np
|
||||
class Controller:
|
||||
def __init__(self):
|
||||
pass
|
||||
|
@ -58,23 +58,27 @@ class DummyController(Controller):
|
|||
def __call__(self, gtm, *args, **kwargs):
|
||||
print("Call Me")
|
||||
|
||||
feed = self.feedrate
|
||||
dx, dy, dz = self.delta_units
|
||||
feed_x = feed * dx/(dx**2 + dy**2 + dz**2)
|
||||
feed_y = feed * dy/(dx**2 + dy**2 + dz**2)
|
||||
feed_z = feed * dz/(dz**2 + dy**2 + dz**2)
|
||||
feed = gtm.feedrate
|
||||
dx, dy, dz = gtm.delta_units
|
||||
feed_x = feed * dx/(dx**2 + dy**2 + dz**2)
|
||||
feed_y = feed * dy/(dx**2 + dy**2 + dz**2)
|
||||
feed_z = feed * dz/(dz**2 + dy**2 + dz**2)
|
||||
|
||||
gtm.x_direction = 1 if gtm.delta_steps[0] > 0 else 0
|
||||
gtm.y_direction = 1 if gtm.delta_steps[1] > 0 else 0
|
||||
gtm.z_direction = 1 if gtm.delta_steps[2] > 0 else 0
|
||||
|
||||
gtm.throttle_x = floor(gtm.FAST_XY_FEEDRATE/feed_x)
|
||||
gtm.throttle_y = floor(gtm.FAST_XY_FEEDRATE/feed_y)
|
||||
gtm.throttle_z = floor(gtm.FAST_Z_FEEDRATE/feed_z)
|
||||
feed_x,feed_y,feed_z = abs(feed_x), abs(feed_y), abs(feed_z)
|
||||
|
||||
gtm.throttle_x = 0 if gtm.throttle_x >= 65536 else gtm.throttle_x
|
||||
gtm.throttle_y = 0 if gtm.throttle_y >= 65536 else gtm.throttle_y
|
||||
gtm.throttle_z = 0 if gtm.throttle_z >= 65536 else gtm.throttle_z
|
||||
gtm.x_throttle = 0 if feed_x == 0. else np.floor(gtm.FAST_XY_FEEDRATE/feed_x)
|
||||
gtm.y_throttle = 0 if feed_y == 0. else np.floor(gtm.FAST_XY_FEEDRATE/feed_y)
|
||||
gtm.z_throttle = 0 if feed_z == 0. else np.floor(gtm.FAST_Z_FEEDRATE/feed_z)
|
||||
|
||||
# If we are close enough, we're good
|
||||
return (gtm.ctrl_step*(feed*1000./60) >= 3.*(dx**2+dy**2+dz**2)**.5):
|
||||
gtm.x_throttle = 0 if gtm.x_throttle >= 65536 else gtm.x_throttle
|
||||
gtm.y_throttle = 0 if gtm.y_throttle >= 65536 else gtm.y_throttle
|
||||
gtm.z_throttle = 0 if gtm.z_throttle >= 65536 else gtm.z_throttle
|
||||
|
||||
print("Throttles : ", gtm.x_throttle, gtm.y_throttle, gtm.z_throttle)
|
||||
|
||||
# If we are close enough, we're good
|
||||
return (gtm.ctrl_step*(feed*1000./60) >= 3.*(dx**2+dy**2+dz**2)**.5)
|
||||
|
|
38
hardware.py
38
hardware.py
|
@ -1,6 +1,7 @@
|
|||
import socket
|
||||
|
||||
import GCode_Interpreterdc
|
||||
from controller import DummyController
|
||||
|
||||
class Hardware:
|
||||
def __init__(self, bind):
|
||||
|
@ -20,11 +21,11 @@ class SimuHardware(Hardware):
|
|||
|
||||
|
||||
def probe(self, gtm):
|
||||
self.s.send("request".encode())
|
||||
self.s.send("request\n".encode())
|
||||
encoder_x = int(socket.SocketIO(self.s, "rw").readline())
|
||||
encoder_y = int(socket.SocketIO(self.s, "rw").readline())
|
||||
encoder_z = int(socket.SocketIO(self.s, "rw").readline())
|
||||
|
||||
print("HAS PROBED")
|
||||
last_x, last_y, last_z = gtm.current_steps
|
||||
# We are now at some position which realizes the encoder positions
|
||||
# curr_x = encoder_x + k*X_MOTOR_STEPS
|
||||
|
@ -43,33 +44,34 @@ class SimuHardware(Hardware):
|
|||
|
||||
# Note that if we don't probe regularly enough, we lose track of the position
|
||||
# Really, we should be able to set_steps
|
||||
gtm.set_position([curr_x / gtm.X_STEPS_PER_MM,
|
||||
curr_y / gtm.Y_STEPS_PER_MM,
|
||||
curr_z / gtm.Z_STEPS_PER_MM])
|
||||
gtm.set_position(curr_x / gtm.X_STEPS_PER_MM,
|
||||
curr_y / gtm.Y_STEPS_PER_MM,
|
||||
curr_z / gtm.Z_STEPS_PER_MM)
|
||||
|
||||
def realize(self, gtm):
|
||||
self.s.send("realize".encode())
|
||||
self.s.send("realize\n".encode())
|
||||
|
||||
if gtm.throttle_x == 0:
|
||||
self.s.send(gtm.x_direction + " 0")
|
||||
if gtm.x_throttle == 0.:
|
||||
self.s.send(str(gtm.x_direction).encode() + b" 0\n")
|
||||
else:
|
||||
self.s.send(gtm.x_direction + " " + gtm.FAST_XY_FEEDRATE / gtm.x_throttle)
|
||||
self.s.send(str(gtm.x_direction).encode() + b" " + str(gtm.FAST_XY_FEEDRATE / gtm.x_throttle).encode() + b"\n")
|
||||
|
||||
if gtm.throttle_y == 0:
|
||||
self.s.send(gtm.y_direction + " 0")
|
||||
if gtm.y_throttle == 0.:
|
||||
self.s.send(str(gtm.y_direction).encode() + b" 0\n")
|
||||
else:
|
||||
self.s.send(gtm.y_direction + " " + gtm.FAST_XY_FEEDRATE / gtm.y_throttle)
|
||||
self.s.send(str(gtm.y_direction).encode() + b" " + str(gtm.FAST_XY_FEEDRATE / gtm.y_throttle).encode() + b"\n")
|
||||
|
||||
if gtm.throttle_z == 0:
|
||||
self.s.send(gtm.z_direction + " 0")
|
||||
if gtm.z_throttle == 0.:
|
||||
self.s.send(str(gtm.z_direction).encode() + b" 0\n")
|
||||
else:
|
||||
self.s.send(gtm.z_direction + " " + gtm.FAST_Z_FEEDRATE / gtm.z_throttle)
|
||||
self.s.send(str(gtm.z_direction).encode() + b" " + str(gtm.FAST_Z_FEEDRATE / gtm.z_throttle).encode() + b"\n")
|
||||
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
b = "tmp.txt"
|
||||
# sim = simulator.Simulator(b)
|
||||
b = "socket.sock"
|
||||
sh = SimuHardware(b)
|
||||
gtm1 = GCode_Interpreterdc.GCodeToMotors
|
||||
ctrl=DummyController()
|
||||
gtm1 = GCode_Interpreterdc.GCodeToMotors(ctrl, sh)
|
||||
sh.probe(gtm1)
|
||||
print(gtm1.current_units)
|
||||
|
|
133
main.py
133
main.py
|
@ -1,126 +1,9 @@
|
|||
import svgpathtools
|
||||
import math
|
||||
from typing import List, Optional
|
||||
import hardware as hw
|
||||
import controller as ctl
|
||||
import GCode_Interpreterdc as gci
|
||||
|
||||
class SVGToGCodeConverter:
|
||||
"""
|
||||
General SVG to GCode converter, parametrized with the available functions.
|
||||
"""
|
||||
|
||||
def __init__(self, supported_g_functions: List[str]):
|
||||
"""Initialize the converter with the supported G-functions.
|
||||
|
||||
Args:
|
||||
supported_g_functions (List[str]): List of supported G-code functions (e.g., ["G1", "G2", "G3", "G4", "G5", "G6", "G7"]).
|
||||
"""
|
||||
self.supported_g_functions = supported_g_functions
|
||||
self._warned_about_g5 = False
|
||||
|
||||
def point_to_gcode(self, x: float, y: float, feedrate: Optional[float] = None) -> str:
|
||||
gcode = f"G1 X{x:.4f} Y{y:.4f}"
|
||||
if feedrate is not None:
|
||||
gcode += f" F{feedrate}"
|
||||
return gcode
|
||||
|
||||
@staticmethod
|
||||
def move_to_gcode(x: float, y: float) -> str:
|
||||
return f"G0 X{x:.4f} Y{y:.4f}"
|
||||
|
||||
def line_to_gcode(self, start: complex, end: complex) -> str:
|
||||
return self.point_to_gcode(end.real, end.imag)
|
||||
|
||||
def arc_to_gcode(self, start: complex, end: complex, center: complex, clockwise: bool) -> str:
|
||||
if "G2" not in self.supported_g_functions and "G3" not in self.supported_g_functions:
|
||||
raise NotImplementedError("Arc support requires G2/G3 functions.")
|
||||
i_offset = center.real - start.real
|
||||
j_offset = center.imag - start.imag
|
||||
g_command = "G2" if clockwise else "G3"
|
||||
return f"{g_command} X{end.real:.4f} Y{end.imag:.4f} I{i_offset:.4f} J{j_offset:.4f}"
|
||||
|
||||
def bezier_to_gcode(self, start: complex, control1: complex, control2: complex, end: complex, steps: int = 20) -> List[str]:
|
||||
if "G5" in self.supported_g_functions:
|
||||
return [
|
||||
f"G5 X{end.real:.4f} Y{end.imag:.4f} I{control1.real:.4f} J{control1.imag:.4f} P{control2.real:.4f} Q{control2.imag:.4f}"
|
||||
]
|
||||
else:
|
||||
if not self._warned_about_g5:
|
||||
print("Warning: G5 is not supported. Approximating Bézier curve with linear segments.")
|
||||
self._warned_about_g5 = True
|
||||
gcode_lines = []
|
||||
for t in [i / steps for i in range(1, steps + 1)]:
|
||||
x = (1 - t)**3 * start.real + 3 * (1 - t)**2 * t * control1.real + 3 * (1 - t) * t**2 * control2.real + t**3 * end.real
|
||||
y = (1 - t)**3 * start.imag + 3 * (1 - t)**2 * t * control1.imag + 3 * (1 - t) * t**2 * control2.imag + t**3 * end.imag
|
||||
gcode_lines.append(self.point_to_gcode(x, y))
|
||||
return gcode_lines
|
||||
|
||||
# Following two are not canonical, I don't know where I found those lol
|
||||
def ellipse_to_gcode(self, start: complex, end: complex, center: complex, rx: float, ry: float, rotation: float, clockwise: bool) -> str:
|
||||
if "G7" not in self.supported_g_functions:
|
||||
raise NotImplementedError("Ellipse support requires G7 function.")
|
||||
i_offset = center.real - start.real
|
||||
j_offset = center.imag - start.imag
|
||||
g_command = "G7" # Assuming G7 is used for ellipses
|
||||
return f"{g_command} X{end.real:.4f} Y{end.imag:.4f} I{i_offset:.4f} J{j_offset:.4f} R1={rx:.4f} R2={ry:.4f} ROT={rotation:.4f}"
|
||||
|
||||
def parabola_to_gcode(self, start: complex, vertex: complex, end: complex) -> str:
|
||||
if "G6" not in self.supported_g_functions:
|
||||
raise NotImplementedError("Parabola support requires G6 function.")
|
||||
g_command = "G6" # Assuming G6 is used for parabolas
|
||||
return f"{g_command} X{end.real:.4f} Y{end.imag:.4f} VERTEX_X{vertex.real:.4f} VERTEX_Y{vertex.imag:.4f}"
|
||||
|
||||
def wait_time_gcode(self, seconds: float) -> str:
|
||||
if "G4" not in self.supported_g_functions:
|
||||
raise NotImplementedError("Wait time support requires G4 function.")
|
||||
return f"G4 P{seconds:.3f}"
|
||||
|
||||
def parse_svg_to_gcode(self, svg_path: svgpathtools.Path) -> List[str]:
|
||||
gcode = []
|
||||
|
||||
for segment in svg_path:
|
||||
if isinstance(segment, svgpathtools.Line):
|
||||
gcode.append(self.line_to_gcode(segment.start, segment.end))
|
||||
|
||||
elif isinstance(segment, svgpathtools.CubicBezier):
|
||||
gcode.extend(self.bezier_to_gcode(segment.start, segment.control1, segment.control2, segment.end))
|
||||
|
||||
elif isinstance(segment, svgpathtools.Arc):
|
||||
center = segment.center
|
||||
if "G7" in self.supported_g_functions:
|
||||
rx, ry = segment.radius.real, segment.radius.imag
|
||||
rotation = segment.rotation
|
||||
gcode.append(self.ellipse_to_gcode(segment.start, segment.end, center, rx, ry, rotation, segment.sweep_flag == 0))
|
||||
else:
|
||||
gcode.append(self.arc_to_gcode(segment.start, segment.end, center, segment.sweep_flag == 0))
|
||||
|
||||
elif hasattr(segment, "vertex") and "G6" in self.supported_g_functions:
|
||||
gcode.append(self.parabola_to_gcode(segment.start, segment.vertex, segment.end))
|
||||
|
||||
else:
|
||||
raise ValueError(f"Unsupported path segment: {segment}")
|
||||
|
||||
return gcode
|
||||
|
||||
def svg_to_gcode(self, file_path: str, output_path: str) -> None:
|
||||
"""Convert an SVG file to G-code.
|
||||
|
||||
Args:
|
||||
file_path (str): Path to the input SVG file.
|
||||
output_path (str): Path to save the output G-code file.
|
||||
"""
|
||||
paths, attributes = svgpathtools.svg2paths(file_path)
|
||||
|
||||
gcode = ["G21 ; Set units to mm", "G90 ; Absolute positioning"] # G-code header
|
||||
|
||||
for path in paths:
|
||||
if path:
|
||||
start_point = path[0].start
|
||||
gcode.append(self.move_to_gcode(start_point.real, start_point.imag))
|
||||
gcode.extend(self.parse_svg_to_gcode(path))
|
||||
|
||||
with open(output_path, "w") as gcode_file:
|
||||
gcode_file.write("\n".join(gcode))
|
||||
print(f"G-code saved to {output_path}")
|
||||
|
||||
# Example usage:
|
||||
# converter = SVGToGCodeConverter(["G1", "G2", "G3", "G5", "G6", "G7"])
|
||||
# converter.svg_to_gcode("example.svg", "output.gcode")
|
||||
b = "socket.sock"
|
||||
sh = hw.SimuHardware(b)
|
||||
ctrl= ctl.DummyController()
|
||||
gtm1 = gci.GCodeToMotors(ctrl, sh)
|
||||
gtm1.instruction_converter("G0 X2.000 Y30.000 F1.000")
|
||||
|
|
|
@ -7,6 +7,7 @@ pkgs.mkShell {
|
|||
ps.numpy
|
||||
ps.pyaudio
|
||||
ps.matplotlib
|
||||
ps.scipy]))
|
||||
ps.scipy
|
||||
]))
|
||||
];
|
||||
}
|
||||
|
|
18
simulator.py
18
simulator.py
|
@ -159,10 +159,10 @@ class NaiveSimulator(Simulator, Douche):
|
|||
|
||||
self.pos += self.speed * self.time_step
|
||||
self.speed += 1./self.J * (gamma - self.f*self.speed) * self.time_step
|
||||
print("timestep ", self.time_step)
|
||||
print("gamma ", gamma)
|
||||
print("speed ", self.speed)
|
||||
print("position ", self.pos)
|
||||
#print("timestep ", self.time_step)
|
||||
#print("gamma ", gamma)
|
||||
#print("speed ", self.speed)
|
||||
#print("position ", self.pos)
|
||||
|
||||
self.steps = np.ceil(self.pos * np.array([self.X_STEPS_PER_MM,
|
||||
self.Y_STEPS_PER_MM,
|
||||
|
@ -170,9 +170,9 @@ class NaiveSimulator(Simulator, Douche):
|
|||
self.add_point(self.pos[0], self.pos[1])
|
||||
|
||||
def request(self):
|
||||
self.s.send(f"{self.steps[0]}".encode())
|
||||
self.s.send(f"{self.steps[1]}".encode())
|
||||
self.s.send(f"{self.steps[2]}".encode())
|
||||
self.s.send(f"{int(self.steps[0])}\n".encode())
|
||||
self.s.send(f"{int(self.steps[1])}\n".encode())
|
||||
self.s.send(f"{int(self.steps[2])}\n".encode())
|
||||
|
||||
def realize(self):
|
||||
print("I JUST REALIZED: MA VIE C'EST DE LA MERDE")
|
||||
|
@ -181,7 +181,7 @@ class NaiveSimulator(Simulator, Douche):
|
|||
z_v = parse_speed(socket.SocketIO(self.s, 'r').readline())
|
||||
print("Everything parsed")
|
||||
self.command_spd = np.array([x_v, y_v, z_v])
|
||||
|
||||
self.s.send("Realized".encode())
|
||||
print("New speeds : ", self.command_spd)
|
||||
#self.s.send("Realized\n".encode())
|
||||
|
||||
simu = NaiveSimulator("socket.sock")
|
||||
|
|
126
svgtogcode.py
Normal file
126
svgtogcode.py
Normal file
|
@ -0,0 +1,126 @@
|
|||
import svgpathtools
|
||||
import math
|
||||
from typing import List, Optional
|
||||
|
||||
class SVGToGCodeConverter:
|
||||
"""
|
||||
General SVG to GCode converter, parametrized with the available functions.
|
||||
"""
|
||||
|
||||
def __init__(self, supported_g_functions: List[str]):
|
||||
"""Initialize the converter with the supported G-functions.
|
||||
|
||||
Args:
|
||||
supported_g_functions (List[str]): List of supported G-code functions (e.g., ["G1", "G2", "G3", "G4", "G5", "G6", "G7"]).
|
||||
"""
|
||||
self.supported_g_functions = supported_g_functions
|
||||
self._warned_about_g5 = False
|
||||
|
||||
def point_to_gcode(self, x: float, y: float, feedrate: Optional[float] = None) -> str:
|
||||
gcode = f"G1 X{x:.4f} Y{y:.4f}"
|
||||
if feedrate is not None:
|
||||
gcode += f" F{feedrate}"
|
||||
return gcode
|
||||
|
||||
@staticmethod
|
||||
def move_to_gcode(x: float, y: float) -> str:
|
||||
return f"G0 X{x:.4f} Y{y:.4f}"
|
||||
|
||||
def line_to_gcode(self, start: complex, end: complex) -> str:
|
||||
return self.point_to_gcode(end.real, end.imag)
|
||||
|
||||
def arc_to_gcode(self, start: complex, end: complex, center: complex, clockwise: bool) -> str:
|
||||
if "G2" not in self.supported_g_functions and "G3" not in self.supported_g_functions:
|
||||
raise NotImplementedError("Arc support requires G2/G3 functions.")
|
||||
i_offset = center.real - start.real
|
||||
j_offset = center.imag - start.imag
|
||||
g_command = "G2" if clockwise else "G3"
|
||||
return f"{g_command} X{end.real:.4f} Y{end.imag:.4f} I{i_offset:.4f} J{j_offset:.4f}"
|
||||
|
||||
def bezier_to_gcode(self, start: complex, control1: complex, control2: complex, end: complex, steps: int = 20) -> List[str]:
|
||||
if "G5" in self.supported_g_functions:
|
||||
return [
|
||||
f"G5 X{end.real:.4f} Y{end.imag:.4f} I{control1.real:.4f} J{control1.imag:.4f} P{control2.real:.4f} Q{control2.imag:.4f}"
|
||||
]
|
||||
else:
|
||||
if not self._warned_about_g5:
|
||||
print("Warning: G5 is not supported. Approximating Bézier curve with linear segments.")
|
||||
self._warned_about_g5 = True
|
||||
gcode_lines = []
|
||||
for t in [i / steps for i in range(1, steps + 1)]:
|
||||
x = (1 - t)**3 * start.real + 3 * (1 - t)**2 * t * control1.real + 3 * (1 - t) * t**2 * control2.real + t**3 * end.real
|
||||
y = (1 - t)**3 * start.imag + 3 * (1 - t)**2 * t * control1.imag + 3 * (1 - t) * t**2 * control2.imag + t**3 * end.imag
|
||||
gcode_lines.append(self.point_to_gcode(x, y))
|
||||
return gcode_lines
|
||||
|
||||
# Following two are not canonical, I don't know where I found those lol
|
||||
def ellipse_to_gcode(self, start: complex, end: complex, center: complex, rx: float, ry: float, rotation: float, clockwise: bool) -> str:
|
||||
if "G7" not in self.supported_g_functions:
|
||||
raise NotImplementedError("Ellipse support requires G7 function.")
|
||||
i_offset = center.real - start.real
|
||||
j_offset = center.imag - start.imag
|
||||
g_command = "G7" # Assuming G7 is used for ellipses
|
||||
return f"{g_command} X{end.real:.4f} Y{end.imag:.4f} I{i_offset:.4f} J{j_offset:.4f} R1={rx:.4f} R2={ry:.4f} ROT={rotation:.4f}"
|
||||
|
||||
def parabola_to_gcode(self, start: complex, vertex: complex, end: complex) -> str:
|
||||
if "G6" not in self.supported_g_functions:
|
||||
raise NotImplementedError("Parabola support requires G6 function.")
|
||||
g_command = "G6" # Assuming G6 is used for parabolas
|
||||
return f"{g_command} X{end.real:.4f} Y{end.imag:.4f} VERTEX_X{vertex.real:.4f} VERTEX_Y{vertex.imag:.4f}"
|
||||
|
||||
def wait_time_gcode(self, seconds: float) -> str:
|
||||
if "G4" not in self.supported_g_functions:
|
||||
raise NotImplementedError("Wait time support requires G4 function.")
|
||||
return f"G4 P{seconds:.3f}"
|
||||
|
||||
def parse_svg_to_gcode(self, svg_path: svgpathtools.Path) -> List[str]:
|
||||
gcode = []
|
||||
|
||||
for segment in svg_path:
|
||||
if isinstance(segment, svgpathtools.Line):
|
||||
gcode.append(self.line_to_gcode(segment.start, segment.end))
|
||||
|
||||
elif isinstance(segment, svgpathtools.CubicBezier):
|
||||
gcode.extend(self.bezier_to_gcode(segment.start, segment.control1, segment.control2, segment.end))
|
||||
|
||||
elif isinstance(segment, svgpathtools.Arc):
|
||||
center = segment.center
|
||||
if "G7" in self.supported_g_functions:
|
||||
rx, ry = segment.radius.real, segment.radius.imag
|
||||
rotation = segment.rotation
|
||||
gcode.append(self.ellipse_to_gcode(segment.start, segment.end, center, rx, ry, rotation, segment.sweep_flag == 0))
|
||||
else:
|
||||
gcode.append(self.arc_to_gcode(segment.start, segment.end, center, segment.sweep_flag == 0))
|
||||
|
||||
elif hasattr(segment, "vertex") and "G6" in self.supported_g_functions:
|
||||
gcode.append(self.parabola_to_gcode(segment.start, segment.vertex, segment.end))
|
||||
|
||||
else:
|
||||
raise ValueError(f"Unsupported path segment: {segment}")
|
||||
|
||||
return gcode
|
||||
|
||||
def svg_to_gcode(self, file_path: str, output_path: str) -> None:
|
||||
"""Convert an SVG file to G-code.
|
||||
|
||||
Args:
|
||||
file_path (str): Path to the input SVG file.
|
||||
output_path (str): Path to save the output G-code file.
|
||||
"""
|
||||
paths, attributes = svgpathtools.svg2paths(file_path)
|
||||
|
||||
gcode = ["G21 ; Set units to mm", "G90 ; Absolute positioning"] # G-code header
|
||||
|
||||
for path in paths:
|
||||
if path:
|
||||
start_point = path[0].start
|
||||
gcode.append(self.move_to_gcode(start_point.real, start_point.imag))
|
||||
gcode.extend(self.parse_svg_to_gcode(path))
|
||||
|
||||
with open(output_path, "w") as gcode_file:
|
||||
gcode_file.write("\n".join(gcode))
|
||||
print(f"G-code saved to {output_path}")
|
||||
|
||||
# Example usage:
|
||||
# converter = SVGToGCodeConverter(["G1", "G2", "G3", "G5", "G6", "G7"])
|
||||
# converter.svg_to_gcode("example.svg", "output.gcode")
|
Loading…
Reference in a new issue