diff --git a/PythonicGcodeMachine/Gcode/Rs274/Coordinate.py b/PythonicGcodeMachine/Gcode/Rs274/Coordinate.py new file mode 100644 index 0000000000000000000000000000000000000000..260979aa63bb1855806a0fc9658d7922c452a104 --- /dev/null +++ b/PythonicGcodeMachine/Gcode/Rs274/Coordinate.py @@ -0,0 +1,92 @@ +#################################################################################################### +# +# PythonicGcodeMachine - A Python G-code Toolkit +# Copyright (C) 2018 Fabrice Salvaire +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . +# +#################################################################################################### + +"""Module to implement a machine coordinate. +""" + +#################################################################################################### + +__all__ = [ + 'Coordinate', +] + +#################################################################################################### + +import numpy as np + +#################################################################################################### + +class Coordinate: + + ############################################## + + def __init__(self, *args, dimension=None): + + if args: + self._v = numpy.array(args) + else: + self._v = numpy.zeros(dimension) + + ############################################## + + def clone(self): + return self.__class__(self._v) + + ############################################## + + @property + def dimension(self): + return self._v.shape[0] + + ############################################## + + def __len__(self): + return self.dimension + + def __iter__(self): + return iter(self._v) + + def __getitem__(self, slice_): + return self._v[slice_] + + ############################################## + + def set(self, v): + if isintance(self, Coordinate): + self._v = self._v + else: + self._v = v + + ############################################## + + def __eq__(self, v): + return self._v == self._v + + ############################################## + + def __iadd__(self, v): + self._v += self._v + return self + + ############################################## + + def __isub__(self, v): + self._v -= self._v + return self diff --git a/PythonicGcodeMachine/Gcode/Rs274/MachineState.py b/PythonicGcodeMachine/Gcode/Rs274/MachineState.py new file mode 100644 index 0000000000000000000000000000000000000000..18154bbc67b8aa8d15981f494caebbbf391aed7a --- /dev/null +++ b/PythonicGcodeMachine/Gcode/Rs274/MachineState.py @@ -0,0 +1,199 @@ +#################################################################################################### +# +# PythonicGcodeMachine - A Python G-code Toolkit +# Copyright (C) 2018 Fabrice Salvaire +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . +# +#################################################################################################### + +"""Module to implement a basic G-code machine. +""" + +#################################################################################################### + +__all__ = [ + 'MachineState', +] + +#################################################################################################### + +from enum import Enum, auto + +from .Coordinate import Coordinate +from .ToolSet import ToolSet + +#################################################################################################### + +class PlaneSelection: + XY = auto() + XZ = auto() + YZ = auto() + +class FeedRateMode: + UNITS_PER_MINUTE = auto() + INVERSE_TIME = auto() + +#################################################################################################### + +class MachineState: + + NUMBER_OF_COORDINATE_SYSTEMS = 9 + + ############################################## + + def __init__(self, + number_of_axes, + is_metric=True, + ): + + self._number_of_axes = int(number_of_axes) + self._coordinate = Coordinate(dimension=self._number_of_axes) + + self._tool_set = ToolSet() + self._tool = None # T + + self._is_metric = bool(is_metric) + self._use_metric = self._is_metric # G20 inch / G21 mm + self._use_absolut = True # G90 absolut / G91 incremental + + # G17 XY-plane selection + # G18 XZ-plane selection + # G19 YZ-plane selection + self._plane = None + + # G54 G55 G56 G57 G58 G59 G59.1 G59.2 G59.3 + self._coordinate_system = None + + self._feed_rate = 0 # F + # G93 units per minute + # G94 inverse time + self._feed_rate_mode = FeedRateMode.UNITS_PER_MINUTE + + self._spindle_rate = 0 # S + + ### 4 M0 M1 M2 M30 M60 stopping + ### 6 M6 tool change + ### 7 M3 M4 M5 spindle turning + ### 8 M7 M8 M9 coolant (special case: M7 and M8 may be active at the same time) + ### 9 M48 M49 enable/disable feed and speed override switches + ### 10 G98 G99 return mode in canned cycles + ### 13 G61 G61.1 G64 path control mode + + ############################################## + + @property + def number_of_axes(self): + return self._number_of_axes + + @property + def coordinate(self): + return self._coordinate + + ############################################## + + @property + def is_metric(self): + return self._is_metric + + @property + def use_metric(self): + return self._use_metric + + @use_metric.setter + def use_metric(self, value): + self._use_metric = bool(value) + + ############################################## + + @property + def use_absolut(self): + return self._use_absolut + + @use_absolut.setter + def use_absolut(self, value): + self._use_absolut = bool(value) + + ############################################## + + @property + def plane(self): + return self._plane + + @plane.setter + def plane(self, value): + self._plane = PlaneSelection(value) + + ############################################## + + @property + def coordinate_system(self): + return self._coordinate_system + + @coordinate_system.setter + def coordinate_system(self, value): + _value = int(value) + if not(1 <= _value <= self.NUMBER_OF_COORDINATE_SYSTEMS): + raise ValueError('Invalid coordinate system {}'.format(value)) + self._coordinate_system = _value + + ############################################## + + @property + def tool_set(self): + return self._tool_set + + def load_tool(self, pocket): + """Load the tool at the given carousel pocket. + + Raise ValueError if KeyError. + """ + self._tool.toggle_loaded() + try: + self._tool = self._tool_set[pocket].toggle_loaded() + except KeyError: + raise ValueError('Invalid carousel pocket {}'.format(pocket)) + + ############################################## + + @property + def feed_rate(self): + return self._feed_rate + + @feed_rate.setter + def feed_rate(self, value): + # negative feedback ? + self._feed_rate = float(value) + + @property + def feed_rate_mode(self): + return self._feed_rate_mode + + @feed_rate_mode.setter + def feed_rate_mode(self, value): + self._feed_rate_mode = FeedRateMode(value) + + ############################################## + + @property + def spindle_rate(self): + return self._spindle_rate + + @spindle_rate.setter + def spindle_rate(self, value): + _value = float(value) + if _value < 0: + raise ValueError('Negative spindle rate {}'.format(value)) + self._spindle_rate = _value + diff --git a/PythonicGcodeMachine/Gcode/Rs274/Tool.py b/PythonicGcodeMachine/Gcode/Rs274/Tool.py new file mode 100644 index 0000000000000000000000000000000000000000..88aa837b19232600a278a72bcaa3f7a7fda6451d --- /dev/null +++ b/PythonicGcodeMachine/Gcode/Rs274/Tool.py @@ -0,0 +1,268 @@ +#################################################################################################### +# +# PythonicGcodeMachine - A Python G-code Toolkit +# Copyright (C) 2018 Fabrice Salvaire +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . +# +#################################################################################################### + +"""Module to implement a basic tool set. +""" + +#################################################################################################### + +__all__ = [ + 'Tool', + 'LatheTool', + 'ToolSet', +] + +#################################################################################################### + +import yaml + +#################################################################################################### + +class Tool: + + """Class to define a tool""" + + ############################################## + + def __init__(self, tool_id, offset, diameter=None, comment=None): + + self._id = tool_id + self._tool_offset = offset + self._tool_diameter = diameter + self._comment = comment + + self._tool_set = None + self._pocket = None + + self._loaded = False + + ############################################## + + @property + def id(self): + return self._id + + @tool_id.setter + def id(self, value): + self._id = str(value) + + @property + def offset(self): + return self._offset + + @offset.setter + def offset(self, value): + self._offset = value + + @property + def diameter(self): + return self._diameter + + @diameter.setter + def diameter(self, value): + self._diameter = float(value) + + @property + def comment(self): + return self._comment + + @comment.setter + def comment(self, value): + self._comment = str(value) + + ############################################## + + @property + def tool_set(self): + return self._tool_set + + @tool_set.setter + def tool_set(self, value): + self._tool_set = value + + @property + def pocket(self): + return self._pocket + + @pocket.setter + def pocket(self, value): + self._pocket = int(value) + + ############################################## + + @property + def loaded(self): + return self._loaded + + @loaded.setter + def loaded(self, value): + self._loaded = bool(value) + + def toggle_loaded(self): + self._loaded = not self._loaded + return self + + ############################################## + + def _to_dict(self, d, keys): + for key in keys: + d[key] = geattr(self, key) + + ############################################## + + def to_dict(self): + + keys = ( + 'id', + 'tool_offset', + 'tool_diameter', + 'comment', + 'pocket', + ) + + return self._to_dict({}, keys) + +#################################################################################################### + +class LatheTool(Tool): + + """Class to define a lathe tool""" + + ############################################## + + def __init__(self, tool_id, offset, front_angle, back_angle, orientation, + diameter=None, comment=None): + + super(). __init__(tool_id, offset, diameter, comment) + + self._front_angle = front_angle + self._back_angle = back_angle + self._orientation = orientation + + ############################################## + + @property + def front_angle(self): + return self._front_angle + + @front_angle.setter + def front_angle(self, value): + self._front_angle = float(value) + + @property + def back_angle(self): + return self._back_angle + + @back_angle.setter + def back_angle(self, value): + self._back_angle = float(value) + + @property + def orientation(self): + return self._orientation + + @orientation.setter + def orientation(self, value): + self._orientation = int(value) + + ############################################## + + def to_dict(self): + + d = super().to_dict() + keys = ( + 'front_angle', + 'back_angle', + 'orientation', + ) + self._to_dict(d, keys) + + return d + +#################################################################################################### + +class ToolSet: + + """Class to define a tool set""" + + ############################################## + + def __init__(self): + + self._tools = {} # Free pocket implementation + + ############################################## + + def __len__(self): + return len(self._tools) + + def __iter__(self): + return iter(self._tools.values()) + + def __getitem__(self, pocket): + return self._tools[pocket] + + ############################################## + + def remove_tool(self, pocket): + if isinstance(pocket, Tool): + pocket = pocket.pocket + tool = self._tools.pop(pocket) + tool.tool_set = None + tool.pocket = None + return tool + + ############################################## + + def add_tool(self, tool, pocket): + old_tool = self.remove_tool(pocket) + self._tools[pocket] = tool + tool.tool_set = self + tool.pocket = pocket + return old_tool + + ############################################## + + def load_yaml(self, path): + + with (open(path, 'r')) as fh: + yaml_data = yaml.load(fh.read()) + + for pocket, d in yaml_data.items(): + if 'front_angle' in d: + cls = LatheTool + else: + cls = Tool + tool = cls(*d) + self.add_tool(tool, pocket) + + ############################################## + + def write_yaml(self, path): + + data = {} + for tool in self: + d = tool.to_dict() + del d['pocket'] + data[tool.pocket] = d + + yaml_data = yaml.dump(data, default_flow_style=False) + with (open(path, 'w')) as fh: + fh.write(yaml_data) diff --git a/requirements.txt b/requirements.txt index 6e4516b3dc77fc40edd23c647522fb412356bb1e..85b64e2fc5178850a7395fc98b73cd4919cdf9e9 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,3 +1,4 @@ ansicolors +numpy ply>=3.11 PyYAML>=3.13