| | |
| |
|
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| |
|
| | import os |
| | import socket |
| | import sys |
| | from typing import Any, Dict, Optional |
| |
|
| | from Path.Post.Processor import PostProcessor |
| |
|
| | import Path |
| | import FreeCAD |
| |
|
| | translate = FreeCAD.Qt.translate |
| |
|
| | DEBUG = False |
| | if DEBUG: |
| | Path.Log.setLevel(Path.Log.Level.DEBUG, Path.Log.thisModule()) |
| | Path.Log.trackModule(Path.Log.thisModule()) |
| | else: |
| | Path.Log.setLevel(Path.Log.Level.INFO, Path.Log.thisModule()) |
| |
|
| | |
| | |
| | |
| | Values = Dict[str, Any] |
| |
|
| |
|
| | class Smoothie(PostProcessor): |
| | """ |
| | The SmoothieBoard post processor class. |
| | |
| | This postprocessor outputs G-code suitable for SmoothieBoard controllers. |
| | It supports direct network upload to the SmoothieBoard via TCP/IP. |
| | """ |
| |
|
| | def __init__( |
| | self, |
| | job, |
| | tooltip=translate("CAM", "Refactored SmoothieBoard post processor"), |
| | tooltipargs=["ip-addr", "verbose"], |
| | units="Metric", |
| | ) -> None: |
| | super().__init__( |
| | job=job, |
| | tooltip=tooltip, |
| | tooltipargs=tooltipargs, |
| | units=units, |
| | ) |
| | Path.Log.debug("Refactored SmoothieBoard post processor initialized.") |
| | self.ip_addr: Optional[str] = None |
| | self.verbose: bool = False |
| |
|
| | def init_values(self, values: Values) -> None: |
| | """Initialize values that are used throughout the postprocessor.""" |
| | |
| | super().init_values(values) |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | values["PARAMETER_ORDER"] = [ |
| | "X", |
| | "Y", |
| | "Z", |
| | "A", |
| | "B", |
| | "I", |
| | "J", |
| | "F", |
| | "S", |
| | "T", |
| | "Q", |
| | "R", |
| | "L", |
| | ] |
| | |
| | |
| | |
| | values["MACHINE_NAME"] = "SmoothieBoard" |
| | |
| | |
| | |
| | |
| | values[ |
| | "POSTAMBLE" |
| | ] = """M05 |
| | G17 G90 |
| | M2""" |
| | values["POSTPROCESSOR_FILE_NAME"] = __name__ |
| | |
| | |
| | |
| | |
| | values["PREAMBLE"] = """G17 G90""" |
| |
|
| | def init_arguments(self, values, argument_defaults, arguments_visible): |
| | """Initialize command-line arguments, including SmoothieBoard-specific options.""" |
| | parser = super().init_arguments(values, argument_defaults, arguments_visible) |
| |
|
| | |
| | smoothie_group = parser.add_argument_group("SmoothieBoard-specific arguments") |
| |
|
| | smoothie_group.add_argument( |
| | "--ip-addr", help="IP address for direct upload to SmoothieBoard (e.g., 192.168.1.100)" |
| | ) |
| |
|
| | smoothie_group.add_argument( |
| | "--verbose", |
| | action="store_true", |
| | help="Enable verbose output for network transfer debugging", |
| | ) |
| |
|
| | return parser |
| |
|
| | def process_arguments(self): |
| | """Process arguments and update values, including SmoothieBoard-specific settings.""" |
| | flag, args = super().process_arguments() |
| |
|
| | if flag and args: |
| | |
| | if hasattr(args, "ip_addr") and args.ip_addr: |
| | self.ip_addr = args.ip_addr |
| | Path.Log.info(f"SmoothieBoard IP address set to: {self.ip_addr}") |
| |
|
| | if hasattr(args, "verbose"): |
| | self.verbose = args.verbose |
| | if self.verbose: |
| | Path.Log.info("Verbose mode enabled") |
| |
|
| | return flag, args |
| |
|
| | def export(self): |
| | """Override export to handle network upload to SmoothieBoard.""" |
| | |
| | gcode_sections = super().export() |
| |
|
| | if gcode_sections is None: |
| | return None |
| |
|
| | |
| | if self.ip_addr: |
| | |
| | gcode = "" |
| | for section_name, section_gcode in gcode_sections: |
| | if section_gcode: |
| | gcode += section_gcode |
| |
|
| | |
| | filename = self._job.PostProcessorOutputFile |
| | if not filename or filename == "-": |
| | filename = "output.nc" |
| |
|
| | self._send_to_smoothie(self.ip_addr, gcode, filename) |
| |
|
| | |
| | return gcode_sections |
| |
|
| | |
| | return gcode_sections |
| |
|
| | def _send_to_smoothie(self, ip: str, gcode: str, fname: str) -> None: |
| | """ |
| | Send G-code directly to SmoothieBoard via network. |
| | |
| | Args: |
| | ip: IP address of the SmoothieBoard |
| | gcode: G-code string to send |
| | fname: Filename to use on the SmoothieBoard SD card |
| | """ |
| | fname = os.path.basename(fname) |
| | FreeCAD.Console.PrintMessage(f"Sending to SmoothieBoard: {fname}\n") |
| |
|
| | gcode = gcode.rstrip() |
| | filesize = len(gcode) |
| |
|
| | try: |
| | |
| | s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) |
| | s.settimeout(4.0) |
| | s.connect((ip, 115)) |
| | tn = s.makefile(mode="rw") |
| |
|
| | |
| | ln = tn.readline() |
| | if not ln.startswith("+"): |
| | FreeCAD.Console.PrintError(f"Failed to connect with SFTP: {ln}\n") |
| | return |
| |
|
| | if self.verbose: |
| | print("RSP: " + ln.strip()) |
| |
|
| | |
| | tn.write(f"STOR OLD /sd/{fname}\n") |
| | tn.flush() |
| |
|
| | ln = tn.readline() |
| | if not ln.startswith("+"): |
| | FreeCAD.Console.PrintError(f"Failed to create file: {ln}\n") |
| | return |
| |
|
| | if self.verbose: |
| | print("RSP: " + ln.strip()) |
| |
|
| | |
| | tn.write(f"SIZE {filesize}\n") |
| | tn.flush() |
| |
|
| | ln = tn.readline() |
| | if not ln.startswith("+"): |
| | FreeCAD.Console.PrintError(f"Failed: {ln}\n") |
| | return |
| |
|
| | if self.verbose: |
| | print("RSP: " + ln.strip()) |
| |
|
| | |
| | cnt = 0 |
| | for line in gcode.splitlines(True): |
| | tn.write(line) |
| | if self.verbose: |
| | cnt += len(line) |
| | print("SND: " + line.strip()) |
| | print(f"{cnt}/{filesize}\r", end="") |
| |
|
| | tn.flush() |
| |
|
| | ln = tn.readline() |
| | if not ln.startswith("+"): |
| | FreeCAD.Console.PrintError(f"Failed to save file: {ln}\n") |
| | return |
| |
|
| | if self.verbose: |
| | print("RSP: " + ln.strip()) |
| |
|
| | |
| | tn.write("DONE\n") |
| | tn.flush() |
| | tn.close() |
| |
|
| | FreeCAD.Console.PrintMessage("Upload complete\n") |
| |
|
| | except socket.timeout: |
| | FreeCAD.Console.PrintError(f"Connection timeout while connecting to {ip}:115\n") |
| | except ConnectionRefusedError: |
| | FreeCAD.Console.PrintError( |
| | f"Connection refused by {ip}:115. Is the SmoothieBoard running?\n" |
| | ) |
| | except Exception as e: |
| | FreeCAD.Console.PrintError(f"Error sending to SmoothieBoard: {str(e)}\n") |
| |
|
| | @property |
| | def tooltip(self): |
| | tooltip: str = """ |
| | This is a postprocessor file for the CAM workbench. |
| | It is used to take a pseudo-gcode fragment from a CAM object |
| | and output 'real' GCode suitable for a SmoothieBoard controller. |
| | |
| | This postprocessor supports direct network upload to SmoothieBoard |
| | via the --ip-addr argument. |
| | """ |
| | return tooltip |
| |
|