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
|
# HARDWARE.realize() applies the current commands to the actual hardware
|
||||||
|
|
||||||
def move(self):
|
def move(self):
|
||||||
|
print(self.target_units)
|
||||||
|
print(self.feedrate)
|
||||||
self.HARDWARE.probe(self)
|
self.HARDWARE.probe(self)
|
||||||
while not self.CONTROLLER(self): # Allow controller to alter self
|
while not self.CONTROLLER(self): # Allow controller to alter self
|
||||||
self.HARDWARE.realize(self)
|
self.HARDWARE.realize(self)
|
||||||
time.sleep(self.ctrl_step)
|
print("realized?")
|
||||||
|
time.sleep(self.ctrl_step)
|
||||||
|
self.HARDWARE.probe(self)
|
||||||
|
|
||||||
self.calculate_deltas()
|
self.calculate_deltas()
|
||||||
|
|
||||||
|
@ -150,7 +154,7 @@ class GCodeToMotors:
|
||||||
or has_command('Z', instruction):
|
or has_command('Z', instruction):
|
||||||
code = search_string('G', instruction)
|
code = search_string('G', instruction)
|
||||||
match code:
|
match code:
|
||||||
case 0, 1, 2, 3:
|
case 0 | 1 | 2 | 3:
|
||||||
if self.abs_mode:
|
if self.abs_mode:
|
||||||
if has_command('X', instruction):
|
if has_command('X', instruction):
|
||||||
fp[0] = search_string('X', instruction)
|
fp[0] = search_string('X', instruction)
|
||||||
|
@ -172,17 +176,17 @@ class GCodeToMotors:
|
||||||
fp[2] = self.current_units[2] + search_string('Z', instruction)
|
fp[2] = self.current_units[2] + search_string('Z', instruction)
|
||||||
case _:
|
case _:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
match code:
|
match code:
|
||||||
case 0, 1:
|
case 0 | 1:
|
||||||
self.set_position(fp[0], fp[1], fp[2])
|
self.set_target(fp[0], fp[1], fp[2])
|
||||||
if has_command('G', instruction) and code == 1:
|
if has_command('G', instruction) and code == 1:
|
||||||
self.feedrate = search_string('F', instruction)
|
self.feedrate = search_string('F', instruction)
|
||||||
if self.feedrate == 0:
|
if self.feedrate == 0:
|
||||||
self.feedrate = self.get_max_speed()
|
self.feedrate = self.get_max_speed()
|
||||||
self.move()
|
self.move()
|
||||||
|
|
||||||
case 2, 3:
|
case 2 | 3:
|
||||||
center = [0., 0., 0.]
|
center = [0., 0., 0.]
|
||||||
center[0] = search_string('I', instruction) + self.current_units[0]
|
center[0] = search_string('I', instruction) + self.current_units[0]
|
||||||
center[1] = search_string('J', instruction) + self.current_units[1]
|
center[1] = search_string('J', instruction) + self.current_units[1]
|
||||||
|
@ -258,7 +262,7 @@ class GCodeToMotors:
|
||||||
case 5.1:
|
case 5.1:
|
||||||
raise NotImplementedError("PAS DE SPLINE QUADRATIQUE J'AI LA FLEMME")
|
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")
|
raise NotImplementedError("Experimental Unimplemented Feature")
|
||||||
|
|
||||||
case 6:
|
case 6:
|
||||||
|
@ -338,9 +342,11 @@ def has_command(key: str, instruction: str) -> bool:
|
||||||
return key in instruction
|
return key in instruction
|
||||||
|
|
||||||
|
|
||||||
def search_string(key: str, instruction: str) -> int:
|
def search_string(key: str, instruction: str) -> float:
|
||||||
index = instruction.find(key)
|
index = instruction.find(key)
|
||||||
|
if(index==-1):
|
||||||
|
return float(0.)
|
||||||
tmp = instruction[index+1:].split(' ')[0]
|
tmp = instruction[index+1:].split(' ')[0]
|
||||||
return int(tmp)
|
return float(tmp)
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
|
import numpy as np
|
||||||
class Controller:
|
class Controller:
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
pass
|
pass
|
||||||
|
@ -58,23 +58,27 @@ class DummyController(Controller):
|
||||||
def __call__(self, gtm, *args, **kwargs):
|
def __call__(self, gtm, *args, **kwargs):
|
||||||
print("Call Me")
|
print("Call Me")
|
||||||
|
|
||||||
feed = self.feedrate
|
feed = gtm.feedrate
|
||||||
dx, dy, dz = self.delta_units
|
dx, dy, dz = gtm.delta_units
|
||||||
feed_x = feed * dx/(dx**2 + dy**2 + dz**2)
|
feed_x = feed * dx/(dx**2 + dy**2 + dz**2)
|
||||||
feed_y = feed * dy/(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_z = feed * dz/(dz**2 + dy**2 + dz**2)
|
||||||
|
|
||||||
gtm.x_direction = 1 if gtm.delta_steps[0] > 0 else 0
|
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.y_direction = 1 if gtm.delta_steps[1] > 0 else 0
|
||||||
gtm.z_direction = 1 if gtm.delta_steps[2] > 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)
|
feed_x,feed_y,feed_z = abs(feed_x), abs(feed_y), abs(feed_z)
|
||||||
gtm.throttle_y = floor(gtm.FAST_XY_FEEDRATE/feed_y)
|
|
||||||
gtm.throttle_z = floor(gtm.FAST_Z_FEEDRATE/feed_z)
|
|
||||||
|
|
||||||
gtm.throttle_x = 0 if gtm.throttle_x >= 65536 else gtm.throttle_x
|
gtm.x_throttle = 0 if feed_x == 0. else np.floor(gtm.FAST_XY_FEEDRATE/feed_x)
|
||||||
gtm.throttle_y = 0 if gtm.throttle_y >= 65536 else gtm.throttle_y
|
gtm.y_throttle = 0 if feed_y == 0. else np.floor(gtm.FAST_XY_FEEDRATE/feed_y)
|
||||||
gtm.throttle_z = 0 if gtm.throttle_z >= 65536 else gtm.throttle_z
|
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
|
gtm.x_throttle = 0 if gtm.x_throttle >= 65536 else gtm.x_throttle
|
||||||
return (gtm.ctrl_step*(feed*1000./60) >= 3.*(dx**2+dy**2+dz**2)**.5):
|
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 socket
|
||||||
|
|
||||||
import GCode_Interpreterdc
|
import GCode_Interpreterdc
|
||||||
|
from controller import DummyController
|
||||||
|
|
||||||
class Hardware:
|
class Hardware:
|
||||||
def __init__(self, bind):
|
def __init__(self, bind):
|
||||||
|
@ -20,11 +21,11 @@ class SimuHardware(Hardware):
|
||||||
|
|
||||||
|
|
||||||
def probe(self, gtm):
|
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_x = int(socket.SocketIO(self.s, "rw").readline())
|
||||||
encoder_y = 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())
|
encoder_z = int(socket.SocketIO(self.s, "rw").readline())
|
||||||
|
print("HAS PROBED")
|
||||||
last_x, last_y, last_z = gtm.current_steps
|
last_x, last_y, last_z = gtm.current_steps
|
||||||
# We are now at some position which realizes the encoder positions
|
# We are now at some position which realizes the encoder positions
|
||||||
# curr_x = encoder_x + k*X_MOTOR_STEPS
|
# 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
|
# Note that if we don't probe regularly enough, we lose track of the position
|
||||||
# Really, we should be able to set_steps
|
# Really, we should be able to set_steps
|
||||||
gtm.set_position([curr_x / gtm.X_STEPS_PER_MM,
|
gtm.set_position(curr_x / gtm.X_STEPS_PER_MM,
|
||||||
curr_y / gtm.Y_STEPS_PER_MM,
|
curr_y / gtm.Y_STEPS_PER_MM,
|
||||||
curr_z / gtm.Z_STEPS_PER_MM])
|
curr_z / gtm.Z_STEPS_PER_MM)
|
||||||
|
|
||||||
def realize(self, gtm):
|
def realize(self, gtm):
|
||||||
self.s.send("realize".encode())
|
self.s.send("realize\n".encode())
|
||||||
|
|
||||||
if gtm.throttle_x == 0:
|
if gtm.x_throttle == 0.:
|
||||||
self.s.send(gtm.x_direction + " 0")
|
self.s.send(str(gtm.x_direction).encode() + b" 0\n")
|
||||||
else:
|
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:
|
if gtm.y_throttle == 0.:
|
||||||
self.s.send(gtm.y_direction + " 0")
|
self.s.send(str(gtm.y_direction).encode() + b" 0\n")
|
||||||
else:
|
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:
|
if gtm.z_throttle == 0.:
|
||||||
self.s.send(gtm.z_direction + " 0")
|
self.s.send(str(gtm.z_direction).encode() + b" 0\n")
|
||||||
else:
|
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__':
|
if __name__ == '__main__':
|
||||||
b = "tmp.txt"
|
b = "socket.sock"
|
||||||
# sim = simulator.Simulator(b)
|
|
||||||
sh = SimuHardware(b)
|
sh = SimuHardware(b)
|
||||||
gtm1 = GCode_Interpreterdc.GCodeToMotors
|
ctrl=DummyController()
|
||||||
|
gtm1 = GCode_Interpreterdc.GCodeToMotors(ctrl, sh)
|
||||||
sh.probe(gtm1)
|
sh.probe(gtm1)
|
||||||
|
print(gtm1.current_units)
|
||||||
|
|
133
main.py
133
main.py
|
@ -1,126 +1,9 @@
|
||||||
import svgpathtools
|
import hardware as hw
|
||||||
import math
|
import controller as ctl
|
||||||
from typing import List, Optional
|
import GCode_Interpreterdc as gci
|
||||||
|
|
||||||
class SVGToGCodeConverter:
|
b = "socket.sock"
|
||||||
"""
|
sh = hw.SimuHardware(b)
|
||||||
General SVG to GCode converter, parametrized with the available functions.
|
ctrl= ctl.DummyController()
|
||||||
"""
|
gtm1 = gci.GCodeToMotors(ctrl, sh)
|
||||||
|
gtm1.instruction_converter("G0 X2.000 Y30.000 F1.000")
|
||||||
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")
|
|
||||||
|
|
|
@ -7,6 +7,7 @@ pkgs.mkShell {
|
||||||
ps.numpy
|
ps.numpy
|
||||||
ps.pyaudio
|
ps.pyaudio
|
||||||
ps.matplotlib
|
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.pos += self.speed * self.time_step
|
||||||
self.speed += 1./self.J * (gamma - self.f*self.speed) * self.time_step
|
self.speed += 1./self.J * (gamma - self.f*self.speed) * self.time_step
|
||||||
print("timestep ", self.time_step)
|
#print("timestep ", self.time_step)
|
||||||
print("gamma ", gamma)
|
#print("gamma ", gamma)
|
||||||
print("speed ", self.speed)
|
#print("speed ", self.speed)
|
||||||
print("position ", self.pos)
|
#print("position ", self.pos)
|
||||||
|
|
||||||
self.steps = np.ceil(self.pos * np.array([self.X_STEPS_PER_MM,
|
self.steps = np.ceil(self.pos * np.array([self.X_STEPS_PER_MM,
|
||||||
self.Y_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])
|
self.add_point(self.pos[0], self.pos[1])
|
||||||
|
|
||||||
def request(self):
|
def request(self):
|
||||||
self.s.send(f"{self.steps[0]}".encode())
|
self.s.send(f"{int(self.steps[0])}\n".encode())
|
||||||
self.s.send(f"{self.steps[1]}".encode())
|
self.s.send(f"{int(self.steps[1])}\n".encode())
|
||||||
self.s.send(f"{self.steps[2]}".encode())
|
self.s.send(f"{int(self.steps[2])}\n".encode())
|
||||||
|
|
||||||
def realize(self):
|
def realize(self):
|
||||||
print("I JUST REALIZED: MA VIE C'EST DE LA MERDE")
|
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())
|
z_v = parse_speed(socket.SocketIO(self.s, 'r').readline())
|
||||||
print("Everything parsed")
|
print("Everything parsed")
|
||||||
self.command_spd = np.array([x_v, y_v, z_v])
|
self.command_spd = np.array([x_v, y_v, z_v])
|
||||||
|
print("New speeds : ", self.command_spd)
|
||||||
self.s.send("Realized".encode())
|
#self.s.send("Realized\n".encode())
|
||||||
|
|
||||||
simu = NaiveSimulator("socket.sock")
|
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