def test_handle_gcode_inch_switch(self, no_safe_robot): """ Test that the inch mode switch works correctly. :param no_safe_robot: :return: """ activate_inch = GCmd.read_cmd_str("G20") deactivate_inch = GCmd.read_cmd_str("G21") no_safe_robot.assign_task(activate_inch) sleep(0.1) assert no_safe_robot.inch_active no_safe_robot.assign_task(deactivate_inch) sleep(0.1) assert not no_safe_robot.inch_active
def execute_gcode(ip: str, port: int, serial_ids: Tuple[int, int], filepath: str) -> None: """ Executing G-Code commands from a file. :param ip: :param port: :param serial_ids: :param filepath: :return: """ logging.info(f'Reading G-Code from "{filepath}".') with open(filepath, 'r') as f: cmd_raw = f.readlines() commands = [GCmd.read_cmd_str(cmd_str.strip()) for cmd_str in cmd_raw if not cmd_str.startswith(GCmd.COMMENT)] commands = [cmd for cmd in commands if cmd is not None] total_commands = len(commands) # Create printer object printer = GPrinter.default_init(ip, port, serial_ids=serial_ids, safe_return=False) # Executing communication try: for idx, cmd in enumerate(commands): logging.info(f'Executing next ({idx + 1}/{total_commands}): {cmd}') printer.execute(cmd) except MelfaBaseException as e: print(str(e)) except KeyboardInterrupt: print('Program terminated by user.') finally: printer.shutdown()
def interactive_gcode(ip: str, port: int, serial_ids: Tuple[int, int], safe_return: Optional[bool] = False) -> None: """ Launches an interactive shell accepting G-code. :param ip: IPv4 network address of the robot :param port: Port of the robot :param serial_ids: :param safe_return: """ logging.info("Launching interactive G-code shell...") # Create printer object printer = GPrinter.default_init(ip, port, serial_ids=serial_ids, safe_return=safe_return) # Executing communication try: while True: usr_msg = input("G-Code>") if usr_msg.lower() in ["quit"]: raise KeyboardInterrupt if len(usr_msg) > 0: # Parse G-code gcode = GCmd.read_cmd_str(usr_msg) print(str(gcode)) printer.execute(gcode) except MelfaBaseException as e: print(str(e)) except KeyboardInterrupt: print('Program terminated by user.') finally: printer.shutdown()
def test_execute(self, virtual_environ, cmd_str): """ Test that commands are sent to all corresponding components :return: """ printer, dummy_robot_ctrl = virtual_environ cmd = GCmd.read_cmd_str(cmd_str) printer.execute(cmd) printer.shutdown()
def test_str_comment(self): """ Test that a comment is turned to None :return: """ GCmd.DIGITS = 1 GCmd.CMD_REMOVE_LEAD_ZERO = False out_act = GCmd.read_cmd_str("; I am a comment.") assert out_act is None
def test_str_convert_seconds(self): """ Test that seconds are converted to milliseconds :return: """ GCmd.DIGITS = 1 GCmd.CMD_REMOVE_LEAD_ZERO = False out_act = str(GCmd.read_cmd_str("G01 X-50.0 Y30.0 F30.2 E17.1 S20.3")) assert out_act == "G01 X-50.0 Y30.0 F30.2 E17.1 P20300.0"
def test_str_g28_remove_coordinates(self): """ Test that for G28 (homing) only the axes are considered :return: """ GCmd.DIGITS = 1 GCmd.CMD_REMOVE_LEAD_ZERO = False out_act = str(GCmd.read_cmd_str("G28 Y10.0 Z")) assert out_act == "G28 Y Z"
def test_str_remove_lead_zero(self): """ Test that lead zeroes in the command can be removed :return: """ GCmd.DIGITS = 1 GCmd.CMD_REMOVE_LEAD_ZERO = True out_act = str(GCmd.read_cmd_str("G01 X10.3 Z-10.3")) assert out_act == "G1 X10.3 Z-10.3"
def test_str_recurrent(self, test_input): """ Tests the creation of objects from a command string and their conversion back to the string. :return: """ # Define digits to have outcomes as expected. GCmd.DIGITS = 1 GCmd.CMD_REMOVE_LEAD_ZERO = False out_act = str(GCmd.read_cmd_str(test_input)) assert out_act == test_input
def interactive_gcode_robot_only(ip: str, port: int, safe_return: Optional[bool] = False) -> None: """ Launches an interactive shell accepting G-code. :param ip: IPv4 network address of the robot :param port: Port of the robot :param safe_return: """ logging.info("Launching interactive G-code shell (robot only)...") # Create clients and connect tcp_client = TcpClientR3(host=ip, port=port) tcp_client.connect() # Create printer object printer = MelfaRobot(tcp_client, number_axes=6, speed_threshold=10, safe_return=safe_return) printer.boot() # Executing communication try: while True: usr_msg = input("G-Code>") if usr_msg.lower() in ["quit"]: raise KeyboardInterrupt if len(usr_msg) > 0: # Parse G-code gcode = GCmd.read_cmd_str(usr_msg) print(str(gcode)) printer.assign_task(gcode) except MelfaBaseException as e: print(str(e)) except KeyboardInterrupt: print('Program terminated by user.') finally: printer.shutdown()
def gcode_santa(robot: MelfaRobot): gcode = """G1 X-60 Y-60 Z100 F4500 G91 G1 Z-50 G1 X120 G1 Y120 G1 X-120 G1 Y-120 G1 X120 Y120 G1 X-60 Y60 G1 X-60 Y-60 G1 X120 Y-120 G1 X-120 Y0 G02 X0 Y120 J60 G02 X120 I60 G02 Y-120 J-60 G03 X0 Y0 I-60 J60 G1 Z50 G90 G1 X0 Y0""" for command in gcode.split("\n"): cmd = GCmd.read_cmd_str(command) robot.assign_task(cmd)
def test_linear_segment_from_gcode(cmd, ds, is_absolute, curr_vel, curr_acc, ex_points, target): gcode = GCmd.read_cmd_str(cmd) lin_segment = lin_segment_from_gcode(gcode, CURRENT_POSE, ds, is_absolute, curr_vel, curr_acc) common_properties(curr_acc, curr_vel, ds, ex_points, lin_segment, target)
def check_trajectory(config_f='./../config.ini', gcode_f='./../test.gcode', ip: Optional[str] = None, port: Optional[int] = None): """ Validate a trajectory for a given robot setup. :param config_f: File path for the configuration file :param gcode_f: File path for the input G-Code file :param ip: Optional host address to be used to resolve robot parameters directly :param port: Optional port to be used to resolve robot parameters directly :return: """ print(f'Reading G-Code from file {gcode_f}.') with open(gcode_f, 'r') as f: cmd_raw = f.readlines() commands = [GCmd.read_cmd_str(cmd_str.strip()) for cmd_str in cmd_raw if not cmd_str.startswith(GCmd.COMMENT)] commands = [cmd for cmd in commands if cmd is not None] config_parser = ConfigParser() config_parser.read(config_f) read_param_from_robot = False home_position = None cartesian_limits = None joint_limits = None # Parameters that always need to be configured within the config file max_jnt_speed = config_parser.get('prechecks', 'max_joint_speed') joint_velocity_limits = [float(i) for i in max_jnt_speed.split(', ')] # Increments for sampling inc_distance_mm = float(config_parser.get('prechecks', 'ds_mm')) inc_angle_tool_deg = float(config_parser.get('prechecks', 'dphi_deg', fallback=2 * pi)) urdf = config_parser.get('prechecks', 'urdf_path') default_acc = float(config_parser.get('prechecks', 'default_acc')) # Extrusion parameters extrusion_height = float(config_parser.get('prechecks', 'extrusion_height')) extrusion_width = float(config_parser.get('prechecks', 'extrusion_width')) # Tool Center Point offsets tool_offset_x = float(config_parser.get('prechecks', 'tool_offset_x', fallback=0)) tool_offset_y = float(config_parser.get('prechecks', 'tool_offset_y', fallback=0)) tool_offset_z = float(config_parser.get('prechecks', 'tool_offset_z', fallback=0)) # Heat bed offsets x_hb = float(config_parser.get('prechecks', 'bed_origin_x', fallback=0)) y_hb = float(config_parser.get('prechecks', 'bed_origin_y', fallback=0)) z_hb = float(config_parser.get('prechecks', 'bed_origin_z', fallback=0)) # Learning time prm_learning_time_s = int(config_parser.get('prechecks', 'prm_learning_time', fallback=120)) robot_config = melfa_rv_4a(atoff=tool_offset_z, rtoff=tool_offset_x) if ip is not None and port is not None: # Parameters can be read from the robot try: print(f'Attempting to read configuration from {ip}:{port}') tcp_client = TcpClientR3(host=ip, port=port) protocol = R3Protocol(tcp_client, MelfaCoordinateService()) home_position = protocol.get_safe_pos().values cartesian_limits = protocol.get_xyz_borders() joint_limits = protocol.get_joint_borders() except ClientError as e: print(f'Reading parameters from robot failed due to {e}. Falling back to config file.') else: read_param_from_robot = True if not read_param_from_robot: # Parameters that need to be configured in the config file if they are not read from the robot home_pos_str = config_parser.get('prechecks', 'home_joints') home_position = [float(i) for i in home_pos_str.split(', ')] cartesian_limits_str = config_parser.get('prechecks', 'xyz_limits') cartesian_limits = [float(i) for i in cartesian_limits_str.split(', ')] joint_limits_str = config_parser.get('prechecks', 'joint_limits') joint_limits = [float(i) for i in joint_limits_str.split(', ')] print('\nConfiguration parameters:') print(f'Joint home position in deg: {home_position}') home_position = [i / 180 * pi for i in home_position] print(f'Cartesian limits in mm: {cartesian_limits}') print(f'Joint limits in deg: {joint_limits}') joint_limits = [i / 180 * pi for i in joint_limits] print(f'Maximum joint velocities in rad/s: {joint_velocity_limits}') print(f'Checking resolution in mm: {inc_distance_mm}') print(f'URDF filepath: {urdf}') print(f'Default acceleration set to {default_acc} mm/s^2') print('\n') hb_offset = Coordinate([x_hb, y_hb, z_hb], 'XYZ') # Create the constraints traj_constraint = Constraints(cartesian_limits, joint_limits, joint_velocity_limits) # Create the increments incs = Increments(inc_distance_mm, inc_angle_tool_deg / 180 * pi) # Create the extrusion data extr = Extrusion(extrusion_height, extrusion_width) try: # Check the trajectory check_traj(commands, robot_config, traj_constraint, home_position, incs, extr, default_acc, urdf, hb_offset, prm_learning_time_s) except (CartesianLimitViolation, WorkspaceViolation): logging.error('Please verify that the limits are correct and check the positioning of the part.') raise except ConfigurationChangesError: logging.error('Robot configuration transitions are not supported within a coherent segment.') raise except JointVelocityViolation: logging.error('Autoamtically reducing the speed is not yet supported.') raise except NoValidPathFound: logging.error('Recreating graph (partially) with different tool orientation is unsupported.') raise
def test_invalid_command_from_str(self, cmd): GCmd.CMD_REMOVE_LEAD_ZERO = True with pytest.raises(ValueError): GCmd.read_cmd_str(cmd)