diff --git a/GCode_Interpreterdc.py b/GCode_Interpreterdc.py index 1791b16..91104cb 100644 --- a/GCode_Interpreterdc.py +++ b/GCode_Interpreterdc.py @@ -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) diff --git a/controller.py b/controller.py index a1cb4d2..b8d0ded 100644 --- a/controller.py +++ b/controller.py @@ -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) diff --git a/hardware.py b/hardware.py index ac5f392..d5cdd25 100644 --- a/hardware.py +++ b/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) diff --git a/main.py b/main.py index fffd298..8142d5e 100644 --- a/main.py +++ b/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") diff --git a/shell.nix b/shell.nix index 530ecc6..9e71168 100644 --- a/shell.nix +++ b/shell.nix @@ -7,6 +7,7 @@ pkgs.mkShell { ps.numpy ps.pyaudio ps.matplotlib - ps.scipy])) + ps.scipy + ])) ]; } diff --git a/simulator.py b/simulator.py index 8984d17..7ce3cb7 100755 --- a/simulator.py +++ b/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") diff --git a/svgtogcode.py b/svgtogcode.py new file mode 100644 index 0000000..fffd298 --- /dev/null +++ b/svgtogcode.py @@ -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")