add E axis

This commit is contained in:
Nikolay Khabarov
2017-05-27 18:47:01 +03:00
parent 665ba2ad1e
commit a87e2a379b
11 changed files with 238 additions and 147 deletions
+3
View File
@@ -6,6 +6,7 @@ STEPPER_MAX_ACCELERATION_MM_PER_S2 = 200 # mm per sec^2
STEPPER_PULSES_PER_MM_X = 400 STEPPER_PULSES_PER_MM_X = 400
STEPPER_PULSES_PER_MM_Y = 400 STEPPER_PULSES_PER_MM_Y = 400
STEPPER_PULSES_PER_MM_Z = 400 STEPPER_PULSES_PER_MM_Z = 400
STEPPER_PULSES_PER_MM_E = 80
TABLE_SIZE_X_MM = 200 TABLE_SIZE_X_MM = 200
TABLE_SIZE_Y_MM = 300 TABLE_SIZE_Y_MM = 300
@@ -17,10 +18,12 @@ SPINDLE_MAX_RPM = 10000
STEPPER_STEP_PIN_X = 16 STEPPER_STEP_PIN_X = 16
STEPPER_STEP_PIN_Y = 20 STEPPER_STEP_PIN_Y = 20
STEPPER_STEP_PIN_Z = 21 STEPPER_STEP_PIN_Z = 21
STEPPER_STEP_PIN_E = 25
STEPPER_DIR_PIN_X = 13 STEPPER_DIR_PIN_X = 13
STEPPER_DIR_PIN_Y = 19 STEPPER_DIR_PIN_Y = 19
STEPPER_DIR_PIN_Z = 26 STEPPER_DIR_PIN_Z = 26
STEPPER_DIR_PIN_E = 8
SPINDLE_PWM_PIN = 7 SPINDLE_PWM_PIN = 7
+24 -15
View File
@@ -6,7 +6,7 @@ class Coordinates(object):
""" This object represent machine coordinates. """ This object represent machine coordinates.
Machine supports 3 axis, so there are X, Y and Z. Machine supports 3 axis, so there are X, Y and Z.
""" """
def __init__(self, x, y, z): def __init__(self, x, y, z, e):
""" Create object. """ Create object.
:param x: x coordinated. :param x: x coordinated.
:param y: y coordinated. :param y: y coordinated.
@@ -15,16 +15,18 @@ class Coordinates(object):
self.x = round(x, 10) self.x = round(x, 10)
self.y = round(y, 10) self.y = round(y, 10)
self.z = round(z, 10) self.z = round(z, 10)
self.e = round(e, 10)
def is_zero(self): def is_zero(self):
""" Check if all coordinates are zero. """ Check if all coordinates are zero.
:return: boolean value. :return: boolean value.
""" """
return self.x == 0.0 and self.y == 0.0 and self.z == 0.0 return self.x == 0.0 and self.y == 0.0 and self.z == 0.0 and \
self.e == 0.0
def is_in_aabb(self, p1, p2): def is_in_aabb(self, p1, p2):
""" Check coordinates are in aabb(Axis-Aligned Bounding Box). """ Check coordinates are in aabb(Axis-Aligned Bounding Box).
aabb is specified with two points. aabb is specified with two points. E is ignored.
:param p1: First point in Coord object. :param p1: First point in Coord object.
:param p2: Second point in Coord object. :param p2: Second point in Coord object.
:return: boolean value. :return: boolean value.
@@ -42,46 +44,53 @@ class Coordinates(object):
""" Calculate the length of vector. """ Calculate the length of vector.
:return: Vector length. :return: Vector length.
""" """
return math.sqrt(self.x * self.x + self.y * self.y + self.z * self.z) return math.sqrt(self.x * self.x + self.y * self.y + self.z * self.z
+ self.e * self.e)
def round(self, base_x, base_y, base_z): def round(self, base_x, base_y, base_z, base_e):
""" Round values to specified base, ie 0.49 with base 0.25 will be 0.5. """ Round values to specified base, ie 0.49 with base 0.25 will be 0.5.
:param base_x: Base for x axis. :param base_x: Base for x axis.
:param base_y: Base for y axis. :param base_y: Base for y axis.
:param base_z: Base for z axis. :param base_z: Base for z axis.
:param base_e: Base for e axis.
:return: New rounded object. :return: New rounded object.
""" """
return Coordinates(round(self.x / base_x) * base_x, return Coordinates(round(self.x / base_x) * base_x,
round(self.y / base_y) * base_y, round(self.y / base_y) * base_y,
round(self.z / base_z) * base_z) round(self.z / base_z) * base_z,
round(self.e / base_e) * base_e)
def find_max(self): def find_max(self):
""" Find a maximum value of all values. """ Find a maximum value of all values.
:return: maximum value. :return: maximum value.
""" """
return max(self.x, self.y, self.z) return max(self.x, self.y, self.z, self.e)
# build in function implementation # build in function implementation
def __add__(self, other): def __add__(self, other):
return Coordinates(self.x + other.x, self.y + other.y, self.z + other.z) return Coordinates(self.x + other.x, self.y + other.y,
self.z + other.z, self.e + other.e)
def __sub__(self, other): def __sub__(self, other):
return Coordinates(self.x - other.x, self.y - other.y, self.z - other.z) return Coordinates(self.x - other.x, self.y - other.y,
self.z - other.z, self.e - other.e)
def __mul__(self, v): def __mul__(self, v):
return Coordinates(self.x * v, self.y * v, self.z * v) return Coordinates(self.x * v, self.y * v, self.z * v, self.e * v)
def __div__(self, v): def __div__(self, v):
return Coordinates(self.x / v, self.y / v, self.z / v) return Coordinates(self.x / v, self.y / v, self.z / v, self.e / v)
def __truediv__(self, v): def __truediv__(self, v):
return Coordinates(self.x / v, self.y / v, self.z / v) return Coordinates(self.x / v, self.y / v, self.z / v, self.e / v)
def __eq__(self, other): def __eq__(self, other):
return self.x == other.x and self.y == other.y and self.z == other.z return self.x == other.x and self.y == other.y and self.z == other.z \
and self.e == other.e
def __str__(self): def __str__(self):
return '(' + str(self.x) + ', ' + str(self.y) + ', ' + str(self.z) + ')' return '(' + str(self.x) + ', ' + str(self.y) + ', ' + str(self.z) \
+ ', ' + str(self.e) + ')'
def __abs__(self): def __abs__(self):
return Coordinates(abs(self.x), abs(self.y), abs(self.z)) return Coordinates(abs(self.x), abs(self.y), abs(self.z), abs(self.e))
+5 -3
View File
@@ -44,13 +44,15 @@ class GCode(object):
x = self.get('X', default.x, multiply) x = self.get('X', default.x, multiply)
y = self.get('Y', default.y, multiply) y = self.get('Y', default.y, multiply)
z = self.get('Z', default.z, multiply) z = self.get('Z', default.z, multiply)
return Coordinates(x, y, z) e = self.get('E', default.e, multiply)
return Coordinates(x, y, z, e)
def has_coordinates(self): def has_coordinates(self):
""" Check if at least one of the coordinates is present. """ Check if at least one of the coordinates is present.
:return: Boolean value. :return: Boolean value.
""" """
return 'X' in self.params or 'Y' in self.params or 'Z' in self.params return 'X' in self.params or 'Y' in self.params or 'Z' in self.params \
or 'E' in self.params
def radius(self, default, multiply): def radius(self, default, multiply):
""" Get radius for circular interpolation(I, J, K or R). """ Get radius for circular interpolation(I, J, K or R).
@@ -61,7 +63,7 @@ class GCode(object):
i = self.get('I', default.x, multiply) i = self.get('I', default.x, multiply)
j = self.get('J', default.y, multiply) j = self.get('J', default.y, multiply)
k = self.get('K', default.z, multiply) k = self.get('K', default.z, multiply)
return Coordinates(i, j, k) return Coordinates(i, j, k, 0)
def command(self): def command(self):
""" Get value from gcode line. """ Get value from gcode line.
+20 -16
View File
@@ -22,7 +22,7 @@ class GMachine(object):
def __init__(self): def __init__(self):
""" Initialization. """ Initialization.
""" """
self._position = Coordinates(0.0, 0.0, 0.0) self._position = Coordinates(0.0, 0.0, 0.0, 0.0)
# init variables # init variables
self._velocity = 0 self._velocity = 0
self._spindle_rpm = 0 self._spindle_rpm = 0
@@ -48,11 +48,11 @@ class GMachine(object):
self._velocity = 1000 self._velocity = 1000
self._spindle_rpm = 1000 self._spindle_rpm = 1000
self._pause = 0 self._pause = 0
self._local = Coordinates(0.0, 0.0, 0.0) self._local = Coordinates(0.0, 0.0, 0.0, 0.0)
self._convertCoordinates = 1.0 self._convertCoordinates = 1.0
self._absoluteCoordinates = True self._absoluteCoordinates = True
self._plane = PLANE_XY self._plane = PLANE_XY
self._radius = Coordinates(0.0, 0.0, 0.0) self._radius = Coordinates(0.0, 0.0, 0.0, 0.0)
def _spindle(self, spindle_speed): def _spindle(self, spindle_speed):
hal.join() hal.join()
@@ -60,14 +60,15 @@ class GMachine(object):
def __check_delta(self, delta): def __check_delta(self, delta):
pos = self._position + delta pos = self._position + delta
if not pos.is_in_aabb(Coordinates(0.0, 0.0, 0.0), if not pos.is_in_aabb(Coordinates(0.0, 0.0, 0.0, 0.0),
Coordinates(TABLE_SIZE_X_MM, TABLE_SIZE_Y_MM, TABLE_SIZE_Z_MM)): Coordinates(TABLE_SIZE_X_MM, TABLE_SIZE_Y_MM, TABLE_SIZE_Z_MM, 0)):
raise GMachineException("out of effective area") raise GMachineException("out of effective area")
def _move_linear(self, delta, velocity): def _move_linear(self, delta, velocity):
delta = delta.round(1.0 / STEPPER_PULSES_PER_MM_X, delta = delta.round(1.0 / STEPPER_PULSES_PER_MM_X,
1.0 / STEPPER_PULSES_PER_MM_Y, 1.0 / STEPPER_PULSES_PER_MM_Y,
1.0 / STEPPER_PULSES_PER_MM_Z) 1.0 / STEPPER_PULSES_PER_MM_Z,
1.0 / STEPPER_PULSES_PER_MM_E)
if delta.is_zero(): if delta.is_zero():
return return
self.__check_delta(delta) self.__check_delta(delta)
@@ -130,10 +131,11 @@ class GMachine(object):
def _circular(self, delta, radius, velocity, direction): def _circular(self, delta, radius, velocity, direction):
delta = delta.round(1.0 / STEPPER_PULSES_PER_MM_X, delta = delta.round(1.0 / STEPPER_PULSES_PER_MM_X,
1.0 / STEPPER_PULSES_PER_MM_Y, 1.0 / STEPPER_PULSES_PER_MM_Y,
1.0 / STEPPER_PULSES_PER_MM_Z) 1.0 / STEPPER_PULSES_PER_MM_Z,
1.0 / STEPPER_PULSES_PER_MM_E)
self.__check_delta(delta) self.__check_delta(delta)
# get delta vector and put it on circle # get delta vector and put it on circle
circle_end = Coordinates(0,0,0) circle_end = Coordinates(0, 0, 0, 0)
if self._plane == PLANE_XY: if self._plane == PLANE_XY:
circle_end.x, circle_end.y = self.__adjust_circle(delta.x, delta.y, circle_end.x, circle_end.y = self.__adjust_circle(delta.x, delta.y,
radius.x, radius.y, direction, radius.x, radius.y, direction,
@@ -152,9 +154,11 @@ class GMachine(object):
self._position.z, self._position.x, self._position.z, self._position.x,
TABLE_SIZE_Z_MM, TABLE_SIZE_X_MM) TABLE_SIZE_Z_MM, TABLE_SIZE_X_MM)
circle_end.y = delta.y circle_end.y = delta.y
circle_end.e = delta.e
circle_end = circle_end.round(1.0 / STEPPER_PULSES_PER_MM_X, circle_end = circle_end.round(1.0 / STEPPER_PULSES_PER_MM_X,
1.0 / STEPPER_PULSES_PER_MM_Y, 1.0 / STEPPER_PULSES_PER_MM_Y,
1.0 / STEPPER_PULSES_PER_MM_Z) 1.0 / STEPPER_PULSES_PER_MM_Z,
1.0 / STEPPER_PULSES_PER_MM_E)
hal.move_circular(circle_end, radius, self._plane, velocity, direction) hal.move_circular(circle_end, radius, self._plane, velocity, direction)
# if finish coords is not on circle, move some distance linearly # if finish coords is not on circle, move some distance linearly
linear_delta = delta - circle_end linear_delta = delta - circle_end
@@ -168,9 +172,9 @@ class GMachine(object):
def home(self): def home(self):
""" Move head to park position """ Move head to park position
""" """
d = Coordinates(0, 0, -self._position.z) d = Coordinates(0, 0, -self._position.z, 0)
self._move_linear(d, STEPPER_MAX_VELOCITY_MM_PER_MIN) self._move_linear(d, STEPPER_MAX_VELOCITY_MM_PER_MIN)
d = Coordinates(-self._position.x, -self._position.y, 0) d = Coordinates(-self._position.x, -self._position.y, 0, 0)
self._move_linear(d, STEPPER_MAX_VELOCITY_MM_PER_MIN) self._move_linear(d, STEPPER_MAX_VELOCITY_MM_PER_MIN)
def position(self): def position(self):
@@ -207,7 +211,8 @@ class GMachine(object):
coord = coord + self._local coord = coord + self._local
delta = coord - self._position delta = coord - self._position
else: else:
delta = gcode.coordinates(Coordinates(0.0, 0.0, 0.0), self._convertCoordinates) delta = gcode.coordinates(Coordinates(0.0, 0.0, 0.0, 0.0),
self._convertCoordinates)
coord = self._position + delta coord = self._position + delta
velocity = gcode.get('F', self._velocity) velocity = gcode.get('F', self._velocity)
spindle_rpm = gcode.get('S', self._spindle_rpm) spindle_rpm = gcode.get('S', self._spindle_rpm)
@@ -245,14 +250,14 @@ class GMachine(object):
elif c == 'G28': # home elif c == 'G28': # home
self.home() self.home()
elif c == 'G53': # switch to machine coords elif c == 'G53': # switch to machine coords
self._local = Coordinates(0.0, 0.0, 0.0) self._local = Coordinates(0.0, 0.0, 0.0, 0.0)
elif c == 'G90': # switch to absolute coords elif c == 'G90': # switch to absolute coords
self._absoluteCoordinates = True self._absoluteCoordinates = True
elif c == 'G91': # switch to relative coords elif c == 'G91': # switch to relative coords
self._absoluteCoordinates = False self._absoluteCoordinates = False
elif c == 'G92': # switch to local coords elif c == 'G92': # switch to local coords
self._local = self._position - \ self._local = self._position - \
gcode.coordinates(Coordinates(0.0, 0.0, 0.0), gcode.coordinates(Coordinates(0.0, 0.0, 0.0, 0.0),
self._convertCoordinates) self._convertCoordinates)
elif c == 'M3': # spinle on elif c == 'M3': # spinle on
self._spindle(spindle_rpm) self._spindle(spindle_rpm)
@@ -271,5 +276,4 @@ class GMachine(object):
self._spindle_rpm = spindle_rpm self._spindle_rpm = spindle_rpm
self._pause = pause self._pause = pause
self._radius = radius self._radius = radius
logging.debug("position {}, {}, {}".format( logging.debug("position {}".format(self._position))
self._position.x, self._position.y, self._position.z))
+11 -2
View File
@@ -27,6 +27,7 @@ pwm = rpgpio.DMAPWM()
STEP_PIN_MASK_X = 1 << STEPPER_STEP_PIN_X STEP_PIN_MASK_X = 1 << STEPPER_STEP_PIN_X
STEP_PIN_MASK_Y = 1 << STEPPER_STEP_PIN_Y STEP_PIN_MASK_Y = 1 << STEPPER_STEP_PIN_Y
STEP_PIN_MASK_Z = 1 << STEPPER_STEP_PIN_Z STEP_PIN_MASK_Z = 1 << STEPPER_STEP_PIN_Z
STEP_PIN_MASK_E = 1 << STEPPER_STEP_PIN_E
def init(): def init():
""" Initialize GPIO pins and machine itself, including callibration if """ Initialize GPIO pins and machine itself, including callibration if
@@ -35,9 +36,11 @@ def init():
gpio.init(STEPPER_STEP_PIN_X, rpgpio.GPIO.MODE_OUTPUT) gpio.init(STEPPER_STEP_PIN_X, rpgpio.GPIO.MODE_OUTPUT)
gpio.init(STEPPER_STEP_PIN_Y, rpgpio.GPIO.MODE_OUTPUT) gpio.init(STEPPER_STEP_PIN_Y, rpgpio.GPIO.MODE_OUTPUT)
gpio.init(STEPPER_STEP_PIN_Z, rpgpio.GPIO.MODE_OUTPUT) gpio.init(STEPPER_STEP_PIN_Z, rpgpio.GPIO.MODE_OUTPUT)
gpio.init(STEPPER_STEP_PIN_E, rpgpio.GPIO.MODE_OUTPUT)
gpio.init(STEPPER_DIR_PIN_X, rpgpio.GPIO.MODE_OUTPUT) gpio.init(STEPPER_DIR_PIN_X, rpgpio.GPIO.MODE_OUTPUT)
gpio.init(STEPPER_DIR_PIN_Y, rpgpio.GPIO.MODE_OUTPUT) gpio.init(STEPPER_DIR_PIN_Y, rpgpio.GPIO.MODE_OUTPUT)
gpio.init(STEPPER_DIR_PIN_Z, rpgpio.GPIO.MODE_OUTPUT) gpio.init(STEPPER_DIR_PIN_Z, rpgpio.GPIO.MODE_OUTPUT)
gpio.init(STEPPER_DIR_PIN_E, rpgpio.GPIO.MODE_OUTPUT)
gpio.init(ENDSTOP_PIN_X, rpgpio.GPIO.MODE_INPUT_PULLUP) gpio.init(ENDSTOP_PIN_X, rpgpio.GPIO.MODE_INPUT_PULLUP)
gpio.init(ENDSTOP_PIN_X, rpgpio.GPIO.MODE_INPUT_PULLUP) gpio.init(ENDSTOP_PIN_X, rpgpio.GPIO.MODE_INPUT_PULLUP)
gpio.init(ENDSTOP_PIN_X, rpgpio.GPIO.MODE_INPUT_PULLUP) gpio.init(ENDSTOP_PIN_X, rpgpio.GPIO.MODE_INPUT_PULLUP)
@@ -130,15 +133,19 @@ def move_linear(delta, velocity):
gpio.clear(STEPPER_DIR_PIN_Z) gpio.clear(STEPPER_DIR_PIN_Z)
else: else:
gpio.set(STEPPER_DIR_PIN_Z) gpio.set(STEPPER_DIR_PIN_Z)
if delta.e > 0.0:
gpio.clear(STEPPER_DIR_PIN_E)
else:
gpio.set(STEPPER_DIR_PIN_E)
# prepare and run dma # prepare and run dma
dma.clear() dma.clear()
prev = 0 prev = 0
is_ran = False is_ran = False
st = time.time() st = time.time()
for tx, ty, tz in generator: for tx, ty, tz, te in generator:
pins = 0 pins = 0
k = int(round(min(x for x in (tx, ty, tz) if x is not None) k = int(round(min(x for x in (tx, ty, tz, te) if x is not None)
* US_IN_SECONDS)) * US_IN_SECONDS))
if tx is not None: if tx is not None:
pins |= STEP_PIN_MASK_X pins |= STEP_PIN_MASK_X
@@ -146,6 +153,8 @@ def move_linear(delta, velocity):
pins |= STEP_PIN_MASK_Y pins |= STEP_PIN_MASK_Y
if tz is not None: if tz is not None:
pins |= STEP_PIN_MASK_Z pins |= STEP_PIN_MASK_Z
if te is not None:
pins |= STEP_PIN_MASK_E
if k - prev > 0: if k - prev > 0:
dma.add_delay(k - prev) dma.add_delay(k - prev)
dma.add_pulse(pins, STEPPER_PULSE_LINGTH_US) dma.add_pulse(pins, STEPPER_PULSE_LINGTH_US)
+21 -9
View File
@@ -31,13 +31,13 @@ def move_linear(delta, velocity):
:param velocity: velocity in mm per min :param velocity: velocity in mm per min
""" """
logging.info("move {} with velocity {}".format(delta, velocity)) logging.info("move {} with velocity {}".format(delta, velocity))
ix = iy = iz = 0 ix = iy = iz = ie = 0
generator = PulseGeneratorLinear(delta, velocity) generator = PulseGeneratorLinear(delta, velocity)
lx, ly, lz = None, None, None lx, ly, lz, le = None, None, None, None
dx, dy, dz = 0, 0, 0 dx, dy, dz, de = 0, 0, 0, 0
mx, my, mz = 0, 0, 0 mx, my, mz, me = 0, 0, 0, 0
st = time.time() st = time.time()
for tx, ty, tz in generator: for tx, ty, tz, te in generator:
if tx is not None: if tx is not None:
if tx > mx: if tx > mx:
mx = tx mx = tx
@@ -71,16 +71,28 @@ def move_linear(delta, velocity):
lz = tz lz = tz
else: else:
dz = None dz = None
if te is not None:
if te > me:
me = te
te = int(round(te * 1000000))
ie += 1
if le is not None:
de = te - le
assert de > 0, "negative or zero time delta detected for e"
le = te
else:
de = None
# very verbose, uncomment on demand # very verbose, uncomment on demand
# logging.debug("Iteration {} is {} {} {}".format(max(ix, iy, iz), tx, ty, tz)) # logging.debug("Iteration {} is {} {} {} {}".format(max(ix, iy, iz, ie), tx, ty, tz, te))
f = list(x for x in (tx, ty, tz) if x is not None) f = list(x for x in (tx, ty, tz, te) if x is not None)
assert f.count(f[0]) == len(f), "fast forwarded pulse detected" assert f.count(f[0]) == len(f), "fast forwarded pulse detected"
pt = time.time() pt = time.time()
assert ix / STEPPER_PULSES_PER_MM_X == abs(delta.x), "x wrong number of pulses" assert ix / STEPPER_PULSES_PER_MM_X == abs(delta.x), "x wrong number of pulses"
assert iy / STEPPER_PULSES_PER_MM_Y == abs(delta.y), "y wrong number of pulses" assert iy / STEPPER_PULSES_PER_MM_Y == abs(delta.y), "y wrong number of pulses"
assert iz / STEPPER_PULSES_PER_MM_Z == abs(delta.z), "z wrong number of pulses" assert iz / STEPPER_PULSES_PER_MM_Z == abs(delta.z), "z wrong number of pulses"
assert max(mx, my, mz) <= generator.total_time_s(), "interpolation time or pulses wrong" assert ie / STEPPER_PULSES_PER_MM_E == abs(delta.e), "e wrong number of pulses"
logging.debug("Did {}, {}, {} iterations".format(ix, iy, iz)) assert max(mx, my, mz, me) <= generator.total_time_s(), "interpolation time or pulses wrong"
logging.debug("Did {}, {}, {}, {} iterations".format(ix, iy, iz, ie))
logging.info("prepared in " + str(round(pt - st, 2)) \ logging.info("prepared in " + str(round(pt - st, 2)) \
+ "s, estimated " + str(round(generator.total_time_s(), 2)) + "s") + "s, estimated " + str(round(generator.total_time_s(), 2)) + "s")
+29 -15
View File
@@ -41,6 +41,7 @@ class PulseGenerator(object):
self._iteration_x = 0 self._iteration_x = 0
self._iteration_y = 0 self._iteration_y = 0
self._iteration_z = 0 self._iteration_z = 0
self._iteration_e = 0
self._acceleration_time_s = 0.0 self._acceleration_time_s = 0.0
self._linear_time_s = 0.0 self._linear_time_s = 0.0
self._2Vmax_per_a = 0.0 self._2Vmax_per_a = 0.0
@@ -60,12 +61,15 @@ class PulseGenerator(object):
""" """
raise NotImplemented raise NotImplemented
def _interpolation_function(self, pulse_number): def _interpolation_function(self, ix, iy, iz, ie):
""" Get function for interpolation path. This function should returned """ Get function for interpolation path. This function should returned
values as it is uniform movement. There is only one trick, function values as it is uniform movement. There is only one trick, function
must be expressed in terms of position, i.e. t = S / V for linear, must be expressed in terms of position, i.e. t = S / V for linear,
where S - distance would be increment on motor minimum step. where S - distance would be increment on motor minimum step.
:param pulse_number: number of pulse. :param ix: number of pulse for X axis.
:param iy: number of pulse for X axis.
:param iz: number of pulse for X axis.
:param ie: number of pulse for X axis.
:return: time for each axis or None if movement for axis is finished. :return: time for each axis or None if movement for axis is finished.
""" """
raise NotImplemented raise NotImplemented
@@ -82,6 +86,7 @@ class PulseGenerator(object):
self._iteration_x = 0 self._iteration_x = 0
self._iteration_y = 0 self._iteration_y = 0
self._iteration_z = 0 self._iteration_z = 0
self._iteration_e = 0
logging.debug(', '.join("%s: %s" % i for i in vars(self).items())) logging.debug(', '.join("%s: %s" % i for i in vars(self).items()))
return self return self
@@ -123,15 +128,16 @@ class PulseGenerator(object):
the next pulse. If there is no pulses left None will be the next pulse. If there is no pulses left None will be
returned. returned.
""" """
tx, ty, tz = self._interpolation_function(self._iteration_x, tx, ty, tz, te = self._interpolation_function(self._iteration_x,
self._iteration_y, self._iteration_y,
self._iteration_z) self._iteration_z,
self._iteration_e)
# check condition to stop # check condition to stop
if tx is None and ty is None and tz is None: if tx is None and ty is None and tz is None and te is None:
raise StopIteration raise StopIteration
# convert to real time # convert to real time
m = min(x for x in (tx, ty, tz) if x is not None) m = min(x for x in (tx, ty, tz, te) if x is not None)
am = self._to_accelerated_time(m) am = self._to_accelerated_time(m)
# sort pulses in time # sort pulses in time
if tx is not None: if tx is not None:
@@ -152,8 +158,14 @@ class PulseGenerator(object):
else: else:
tz = am tz = am
self._iteration_z += 1 self._iteration_z += 1
if te is not None:
if te > m:
te = None
else:
te = am
self._iteration_e += 1
return tx, ty, tz return tx, ty, tz, te
def total_time_s(self): def total_time_s(self):
""" Get total time for movement. """ Get total time for movement.
@@ -173,24 +185,24 @@ class PulseGeneratorLinear(PulseGenerator):
# this class doesn't care about direction # this class doesn't care about direction
self._distance_mm = abs(delta_mm) self._distance_mm = abs(delta_mm)
# velocity of each axis # velocity of each axis
distance_xyz_mm = self._distance_mm.length() distance_total_mm = self._distance_mm.length()
self.max_velocity_mm_per_sec = self._distance_mm * ( self.max_velocity_mm_per_sec = self._distance_mm * (
velocity_mm_per_min / SECONDS_IN_MINUTE / distance_xyz_mm) velocity_mm_per_min / SECONDS_IN_MINUTE / distance_total_mm)
# acceleration time # acceleration time
self.acceleration_time_s = self.max_velocity_mm_per_sec.find_max() \ self.acceleration_time_s = self.max_velocity_mm_per_sec.find_max() \
/ STEPPER_MAX_ACCELERATION_MM_PER_S2 / STEPPER_MAX_ACCELERATION_MM_PER_S2
# check if there is enough space to accelerate and brake, adjust time # check if there is enough space to accelerate and brake, adjust time
# S = a * t^2 / 2 # S = a * t^2 / 2
if STEPPER_MAX_ACCELERATION_MM_PER_S2 * self.acceleration_time_s ** 2 \ if STEPPER_MAX_ACCELERATION_MM_PER_S2 * self.acceleration_time_s ** 2 \
> distance_xyz_mm: > distance_total_mm:
self.acceleration_time_s = math.sqrt(distance_xyz_mm / self.acceleration_time_s = math.sqrt(distance_total_mm /
STEPPER_MAX_ACCELERATION_MM_PER_S2) STEPPER_MAX_ACCELERATION_MM_PER_S2)
self.linear_time_s = 0.0 self.linear_time_s = 0.0
# V = a * t -> V = 2 * S / t, take half of total distance for acceleration and braking # V = a * t -> V = 2 * S / t, take half of total distance for acceleration and braking
self.max_velocity_mm_per_sec = self._distance_mm / self.acceleration_time_s self.max_velocity_mm_per_sec = self._distance_mm / self.acceleration_time_s
else: else:
# calculate linear time # calculate linear time
linear_distance_mm = distance_xyz_mm\ linear_distance_mm = distance_total_mm\
- self.acceleration_time_s ** 2 \ - self.acceleration_time_s ** 2 \
* STEPPER_MAX_ACCELERATION_MM_PER_S2 * STEPPER_MAX_ACCELERATION_MM_PER_S2
self.linear_time_s = linear_distance_mm \ self.linear_time_s = linear_distance_mm \
@@ -212,7 +224,7 @@ class PulseGeneratorLinear(PulseGenerator):
# Linear movement, S = V * t -> t = S / V # Linear movement, S = V * t -> t = S / V
return position_mm / velocity_mm_per_sec return position_mm / velocity_mm_per_sec
def _interpolation_function(self, ix, iy, iz): def _interpolation_function(self, ix, iy, iz, ie):
""" Calculate interpolation values for linear movement, see super class """ Calculate interpolation values for linear movement, see super class
for details. for details.
""" """
@@ -222,4 +234,6 @@ class PulseGeneratorLinear(PulseGenerator):
self.max_velocity_mm_per_sec.y) self.max_velocity_mm_per_sec.y)
t_z = self.__linear(iz / STEPPER_PULSES_PER_MM_Z, self._distance_mm.z, t_z = self.__linear(iz / STEPPER_PULSES_PER_MM_Z, self._distance_mm.z,
self.max_velocity_mm_per_sec.z) self.max_velocity_mm_per_sec.z)
return t_x, t_y, t_z t_e = self.__linear(ie / STEPPER_PULSES_PER_MM_E, self._distance_mm.e,
self.max_velocity_mm_per_sec.e)
return t_x, t_y, t_z, t_e
+45 -26
View File
@@ -5,7 +5,7 @@ from cnc.coordinates import *
class TestCoordinates(unittest.TestCase): class TestCoordinates(unittest.TestCase):
def setUp(self): def setUp(self):
self.default = Coordinates(96, 102, 150) self.default = Coordinates(96, 102, 150, 228)
def tearDown(self): def tearDown(self):
pass pass
@@ -13,110 +13,129 @@ class TestCoordinates(unittest.TestCase):
def test_constructor(self): def test_constructor(self):
# constructor rounds values to 10 digits after the point # constructor rounds values to 10 digits after the point
self.assertRaises(TypeError, Coordinates) self.assertRaises(TypeError, Coordinates)
c = Coordinates(1.00000000005, 2.00000000004, -3.5000000009) c = Coordinates(1.00000000005, 2.00000000004, -3.5000000009, 0.0)
self.assertEqual(c.x, 1.0000000001) self.assertEqual(c.x, 1.0000000001)
self.assertEqual(c.y, 2.0) self.assertEqual(c.y, 2.0)
self.assertEqual(c.z, -3.5000000009) self.assertEqual(c.z, -3.5000000009)
self.assertEqual(c.e, 0.0)
def test_zero(self): def test_zero(self):
c = Coordinates(0, 0, 0) c = Coordinates(0, 0, 0, 0)
self.assertTrue(c.is_zero()) self.assertTrue(c.is_zero())
def test_aabb(self): def test_aabb(self):
# aabb - Axis Aligned Bounded Box. # aabb - Axis Aligned Bounded Box.
# original method checks if point belongs aabb. # original method checks if point belongs aabb.
p1 = Coordinates(0, 0, 0) p1 = Coordinates(0, 0, 0, 0)
p2 = Coordinates(2, 2, 2) p2 = Coordinates(2, 2, 2, 0)
c = Coordinates(1, 1, 1) c = Coordinates(1, 1, 1, 0)
self.assertTrue(c.is_in_aabb(p1, p2)) self.assertTrue(c.is_in_aabb(p1, p2))
self.assertTrue(c.is_in_aabb(p2, p1)) self.assertTrue(c.is_in_aabb(p2, p1))
c = Coordinates(0, 0, 0) c = Coordinates(0, 0, 0, 0)
self.assertTrue(c.is_in_aabb(p1, p2)) self.assertTrue(c.is_in_aabb(p1, p2))
c = Coordinates(2, 2, 2) c = Coordinates(2, 2, 2, 0)
self.assertTrue(c.is_in_aabb(p1, p2)) self.assertTrue(c.is_in_aabb(p1, p2))
c = Coordinates(2, 3, 2) c = Coordinates(2, 3, 2, 0)
self.assertFalse(c.is_in_aabb(p1, p2)) self.assertFalse(c.is_in_aabb(p1, p2))
c = Coordinates(-1, 1, 1) c = Coordinates(-1, 1, 1, 0)
self.assertFalse(c.is_in_aabb(p1, p2)) self.assertFalse(c.is_in_aabb(p1, p2))
c = Coordinates(1, 1, 3) c = Coordinates(1, 1, 3, 0)
self.assertFalse(c.is_in_aabb(p1, p2)) self.assertFalse(c.is_in_aabb(p1, p2))
def test_length(self): def test_length(self):
c = Coordinates(-1, 0, 0) c = Coordinates(-1, 0, 0, 0)
self.assertEqual(c.length(), 1) self.assertEqual(c.length(), 1)
c = Coordinates(0, 3, -4) c = Coordinates(0, 3, -4, 0)
self.assertEqual(c.length(), 5) self.assertEqual(c.length(), 5)
c = Coordinates(3, 4, 12) c = Coordinates(3, 4, 12, 0)
self.assertEqual(c.length(), 13) self.assertEqual(c.length(), 13)
c = Coordinates(1, 1, 1, 1)
self.assertEqual(c.length(), 2)
def test_round(self): def test_round(self):
# round works in another way then Python's round. # round works in another way then Python's round.
# This round() rounds digits with specified step. # This round() rounds digits with specified step.
c = Coordinates(1.5, -1.4, 3.05) c = Coordinates(1.5, -1.4, 3.05, 3.5)
r = c.round(1, 1, 1) r = c.round(1, 1, 1, 1)
self.assertEqual(r.x, 2.0) self.assertEqual(r.x, 2.0)
self.assertEqual(r.y, -1.0) self.assertEqual(r.y, -1.0)
self.assertEqual(r.z, 3.0) self.assertEqual(r.z, 3.0)
r = c.round(0.25, 0.25, 0.25) self.assertEqual(r.e, 4.0)
r = c.round(0.25, 0.25, 0.25, 0.25)
self.assertEqual(r.x, 1.5) self.assertEqual(r.x, 1.5)
self.assertEqual(r.y, -1.5) self.assertEqual(r.y, -1.5)
self.assertEqual(r.z, 3.0) self.assertEqual(r.z, 3.0)
self.assertEqual(r.e, 3.5)
def test_max(self): def test_max(self):
self.assertEqual(self.default.find_max(), max(self.default.x, self.assertEqual(self.default.find_max(), max(self.default.x,
self.default.y, self.default.y,
self.default.z)) self.default.z,
self.default.e))
# build-in function overriding tests # build-in function overriding tests
def test_add(self): def test_add(self):
r = self.default + Coordinates(1, 2, 3) r = self.default + Coordinates(1, 2, 3, 4)
self.assertEqual(r.x, self.default.x + 1) self.assertEqual(r.x, self.default.x + 1)
self.assertEqual(r.y, self.default.y + 2) self.assertEqual(r.y, self.default.y + 2)
self.assertEqual(r.z, self.default.z + 3) self.assertEqual(r.z, self.default.z + 3)
self.assertEqual(r.e, self.default.e + 4)
def test_sub(self): def test_sub(self):
r = self.default - Coordinates(1, 2, 3) r = self.default - Coordinates(1, 2, 3, 4)
self.assertEqual(r.x, self.default.x - 1) self.assertEqual(r.x, self.default.x - 1)
self.assertEqual(r.y, self.default.y - 2) self.assertEqual(r.y, self.default.y - 2)
self.assertEqual(r.z, self.default.z - 3) self.assertEqual(r.z, self.default.z - 3)
self.assertEqual(r.e, self.default.e - 4)
def test_mul(self): def test_mul(self):
r = self.default * 2 r = self.default * 2
self.assertEqual(r.x, self.default.x * 2) self.assertEqual(r.x, self.default.x * 2)
self.assertEqual(r.y, self.default.y * 2) self.assertEqual(r.y, self.default.y * 2)
self.assertEqual(r.z, self.default.z * 2) self.assertEqual(r.z, self.default.z * 2)
self.assertEqual(r.e, self.default.e * 2)
def test_div(self): def test_div(self):
r = self.default / 2 r = self.default / 2
self.assertEqual(r.x, self.default.x / 2) self.assertEqual(r.x, self.default.x / 2)
self.assertEqual(r.y, self.default.y / 2) self.assertEqual(r.y, self.default.y / 2)
self.assertEqual(r.z, self.default.z / 2) self.assertEqual(r.z, self.default.z / 2)
self.assertEqual(r.e, self.default.e / 2)
def test_truediv(self): def test_truediv(self):
r = self.default / 3.0 r = self.default / 3.0
self.assertEqual(r.x, self.default.x / 3.0) self.assertEqual(r.x, self.default.x / 3.0)
self.assertEqual(r.y, self.default.y / 3.0) self.assertEqual(r.y, self.default.y / 3.0)
self.assertEqual(r.z, self.default.z / 3.0) self.assertEqual(r.z, self.default.z / 3.0)
self.assertEqual(r.e, self.default.e / 3.0)
def test_eq(self): def test_eq(self):
a = Coordinates(self.default.x, self.default.y, self.default.z) a = Coordinates(self.default.x, self.default.y, self.default.z,
self.default.e)
self.assertTrue(a == self.default) self.assertTrue(a == self.default)
a = Coordinates(-self.default.x, self.default.y, self.default.z) a = Coordinates(-self.default.x, self.default.y, self.default.z,
self.default.e)
self.assertFalse(a == self.default) self.assertFalse(a == self.default)
a = Coordinates(self.default.x, -self.default.y, self.default.z) a = Coordinates(self.default.x, -self.default.y, self.default.z,
self.default.e)
self.assertFalse(a == self.default) self.assertFalse(a == self.default)
a = Coordinates(self.default.x, self.default.y, -self.default.z) a = Coordinates(self.default.x, self.default.y, -self.default.z,
self.default.e)
self.assertFalse(a == self.default)
a = Coordinates(self.default.x, self.default.y, self.default.z,
-self.default.e)
self.assertFalse(a == self.default) self.assertFalse(a == self.default)
def test_str(self): def test_str(self):
self.assertTrue(isinstance(str(self.default), str)) self.assertTrue(isinstance(str(self.default), str))
def test_abs(self): def test_abs(self):
c = Coordinates(-1, -2.5, -99) c = Coordinates(-1, -2.5, -99, -23)
r = abs(c) r = abs(c)
self.assertEqual(r.x, 1.0) self.assertEqual(r.x, 1.0)
self.assertEqual(r.y, 2.5) self.assertEqual(r.y, 2.5)
self.assertEqual(r.z, 99.0) self.assertEqual(r.z, 99.0)
self.assertEqual(r.e, 23.0)
if __name__ == '__main__': if __name__ == '__main__':
+13 -6
View File
@@ -7,7 +7,7 @@ from cnc.gcode import *
class TestGCode(unittest.TestCase): class TestGCode(unittest.TestCase):
def setUp(self): def setUp(self):
self.default = Coordinates(-7, 8, 9) self.default = Coordinates(-7, 8, 9, -10)
def tearDown(self): def tearDown(self):
pass pass
@@ -16,28 +16,31 @@ class TestGCode(unittest.TestCase):
# GCode shouldn't be created with constructor, but since it uses # GCode shouldn't be created with constructor, but since it uses
# internally, let's check it. # internally, let's check it.
self.assertRaises(TypeError, GCode) self.assertRaises(TypeError, GCode)
gc = GCode({"X": "1", "Y": "-2", "Z":"0", "G": "1"}) gc = GCode({"X": "1", "Y": "-2", "Z":"0", "E": 99, "G": "1"})
self.assertEqual(gc.coordinates(self.default, 1).x, 1.0) self.assertEqual(gc.coordinates(self.default, 1).x, 1.0)
self.assertEqual(gc.coordinates(self.default, 1).y, -2.0) self.assertEqual(gc.coordinates(self.default, 1).y, -2.0)
self.assertEqual(gc.coordinates(self.default, 1).z, 0.0) self.assertEqual(gc.coordinates(self.default, 1).z, 0.0)
self.assertEqual(gc.coordinates(self.default, 1).e, 99.0)
def test_parser(self): def test_parser(self):
gc = GCode.parse_line("G1X2Y-3Z4") gc = GCode.parse_line("G1X2Y-3Z4E1.5")
self.assertEqual(gc.command(), "G1") self.assertEqual(gc.command(), "G1")
self.assertEqual(gc.coordinates(self.default, 1).x, 2.0) self.assertEqual(gc.coordinates(self.default, 1).x, 2.0)
self.assertEqual(gc.coordinates(self.default, 1).y, -3.0) self.assertEqual(gc.coordinates(self.default, 1).y, -3.0)
self.assertEqual(gc.coordinates(self.default, 1).z, 4.0) self.assertEqual(gc.coordinates(self.default, 1).z, 4.0)
self.assertEqual(gc.coordinates(self.default, 1).e, 1.5)
gc = GCode.parse_line("") gc = GCode.parse_line("")
self.assertIsNone(gc) self.assertIsNone(gc)
def test_defaults(self): def test_defaults(self):
# defaults are values which should be returned if corresponding # defaults are values which should be returned if corresponding
# value doesn't exist in gcode. # value doesn't exist in gcode.
default = Coordinates(11, -12, 14) default = Coordinates(11, -12, 14, -10)
gc = GCode.parse_line("G1") gc = GCode.parse_line("G1")
self.assertEqual(gc.coordinates(default, 1).x, 11.0) self.assertEqual(gc.coordinates(default, 1).x, 11.0)
self.assertEqual(gc.coordinates(default, 1).y, -12.0) self.assertEqual(gc.coordinates(default, 1).y, -12.0)
self.assertEqual(gc.coordinates(default, 1).z, 14.0) self.assertEqual(gc.coordinates(default, 1).z, 14.0)
self.assertEqual(gc.coordinates(default, 1).e, -10.0)
def test_commands(self): def test_commands(self):
gc = GCode({"G": "1"}) gc = GCode({"G": "1"})
@@ -48,11 +51,12 @@ class TestGCode(unittest.TestCase):
def test_case_sensitivity(self): def test_case_sensitivity(self):
gc = GCode.parse_line("m111") gc = GCode.parse_line("m111")
self.assertEqual(gc.command(), "M111") self.assertEqual(gc.command(), "M111")
gc = GCode.parse_line("g2X3y-4Z5") gc = GCode.parse_line("g2X3y-4Z5e6")
self.assertEqual(gc.command(), "G2") self.assertEqual(gc.command(), "G2")
self.assertEqual(gc.coordinates(self.default, 1).x, 3.0) self.assertEqual(gc.coordinates(self.default, 1).x, 3.0)
self.assertEqual(gc.coordinates(self.default, 1).y, -4.0) self.assertEqual(gc.coordinates(self.default, 1).y, -4.0)
self.assertEqual(gc.coordinates(self.default, 1).z, 5.0) self.assertEqual(gc.coordinates(self.default, 1).z, 5.0)
self.assertEqual(gc.coordinates(self.default, 1).e, 6.0)
def test_has_coordinates(self): def test_has_coordinates(self):
gc = GCode.parse_line("X2Y-3Z4") gc = GCode.parse_line("X2Y-3Z4")
@@ -65,6 +69,8 @@ class TestGCode(unittest.TestCase):
self.assertTrue(gc.has_coordinates()) self.assertTrue(gc.has_coordinates())
gc = GCode.parse_line("Z1") gc = GCode.parse_line("Z1")
self.assertTrue(gc.has_coordinates()) self.assertTrue(gc.has_coordinates())
gc = GCode.parse_line("E1")
self.assertTrue(gc.has_coordinates())
def test_radius(self): def test_radius(self):
gc = GCode.parse_line("G2I1J2K3") gc = GCode.parse_line("G2I1J2K3")
@@ -78,10 +84,11 @@ class TestGCode(unittest.TestCase):
def test_multiply(self): def test_multiply(self):
# getting coordinates could modify value be specified multiplier. # getting coordinates could modify value be specified multiplier.
gc = GCode.parse_line("X2 Y-3 Z4") gc = GCode.parse_line("X2 Y-3 Z4 E5")
self.assertEqual(gc.coordinates(self.default, 25.4).x, 50.8) self.assertEqual(gc.coordinates(self.default, 25.4).x, 50.8)
self.assertEqual(gc.coordinates(self.default, 2).y, -6) self.assertEqual(gc.coordinates(self.default, 2).y, -6)
self.assertEqual(gc.coordinates(self.default, 0).y, 0) self.assertEqual(gc.coordinates(self.default, 0).y, 0)
self.assertEqual(gc.coordinates(self.default, 5).e, 25)
def test_whitespaces(self): def test_whitespaces(self):
gc = GCode.parse_line("X1 Y2") gc = GCode.parse_line("X1 Y2")
+39 -38
View File
@@ -20,28 +20,28 @@ class TestGMachine(unittest.TestCase):
m.do_command(GCode.parse_line("G91")) m.do_command(GCode.parse_line("G91"))
m.do_command(GCode.parse_line("X1Y1Z1")) m.do_command(GCode.parse_line("X1Y1Z1"))
m.reset() m.reset()
m.do_command(GCode.parse_line("X3Y4Z5")) m.do_command(GCode.parse_line("X3Y4Z5E6"))
self.assertEqual(m.position(), Coordinates(3, 4, 5)) self.assertEqual(m.position(), Coordinates(3, 4, 5, 6))
def test_release(self): def test_release(self):
# release homes head. # release homes head.
m = GMachine() m = GMachine()
m.do_command(GCode.parse_line("X1Y2Z3")) m.do_command(GCode.parse_line("X1Y2Z3E4"))
m.release() m.release()
self.assertEqual(m.position(), Coordinates(0, 0, 0)) self.assertEqual(m.position(), Coordinates(0, 0, 0, 4))
def test_home(self): def test_home(self):
m = GMachine() m = GMachine()
m.do_command(GCode.parse_line("X1Y2Z3")) m.do_command(GCode.parse_line("X1Y2Z3E4"))
m.home() m.home()
self.assertEqual(m.position(), Coordinates(0, 0, 0)) self.assertEqual(m.position(), Coordinates(0, 0, 0, 4))
def test_none(self): def test_none(self):
# GMachine must ignore None commands, since GCode.parse_line() # GMachine must ignore None commands, since GCode.parse_line()
# returns None if no gcode found in line. # returns None if no gcode found in line.
m = GMachine() m = GMachine()
m.do_command(None) m.do_command(None)
self.assertEqual(m.position(), Coordinates(0, 0, 0)) self.assertEqual(m.position(), Coordinates(0, 0, 0, 0))
def test_unknown(self): def test_unknown(self):
# Test commands which doesn't exists # Test commands which doesn't exists
@@ -54,10 +54,10 @@ class TestGMachine(unittest.TestCase):
# Test gcode commands. # Test gcode commands.
def test_g0_g1(self): def test_g0_g1(self):
m = GMachine() m = GMachine()
m.do_command(GCode.parse_line("G0X3Y2Z1")) m.do_command(GCode.parse_line("G0X3Y2Z1E-2"))
self.assertEqual(m.position(), Coordinates(3, 2, 1)) self.assertEqual(m.position(), Coordinates(3, 2, 1, -2))
m.do_command(GCode.parse_line("G1X1Y2Z3")) m.do_command(GCode.parse_line("G1X1Y2Z3E4"))
self.assertEqual(m.position(), Coordinates(1, 2, 3)) self.assertEqual(m.position(), Coordinates(1, 2, 3, 4))
self.assertRaises(GMachineException, self.assertRaises(GMachineException,
m.do_command, GCode.parse_line("G1F-1")) m.do_command, GCode.parse_line("G1F-1"))
self.assertRaises(GMachineException, self.assertRaises(GMachineException,
@@ -86,7 +86,7 @@ class TestGMachine(unittest.TestCase):
m.do_command, GCode.parse_line("G2X99999999Y99999999I1J1")) m.do_command, GCode.parse_line("G2X99999999Y99999999I1J1"))
self.assertRaises(GMachineException, self.assertRaises(GMachineException,
m.do_command, GCode.parse_line("G2X2Y2Z99999999I1J1")) m.do_command, GCode.parse_line("G2X2Y2Z99999999I1J1"))
self.assertEqual(m.position(), Coordinates(0, 0, 0)) self.assertEqual(m.position(), Coordinates(0, 0, 0, 0))
self.assertRaises(GMachineException, self.assertRaises(GMachineException,
m.do_command, GCode.parse_line("G2X4Y4I2J2")) m.do_command, GCode.parse_line("G2X4Y4I2J2"))
self.assertRaises(GMachineException, self.assertRaises(GMachineException,
@@ -94,10 +94,10 @@ class TestGMachine(unittest.TestCase):
m.do_command(GCode.parse_line("G1X1")) m.do_command(GCode.parse_line("G1X1"))
m.do_command(GCode.parse_line("G2J1")) m.do_command(GCode.parse_line("G2J1"))
m.do_command(GCode.parse_line("G3J1")) m.do_command(GCode.parse_line("G3J1"))
self.assertEqual(m.position(), Coordinates(1, 0, 0)) self.assertEqual(m.position(), Coordinates(1, 0, 0, 0))
m.do_command(GCode.parse_line("G1X5Y5")) m.do_command(GCode.parse_line("G1X5Y5"))
m.do_command(GCode.parse_line("G2X0Y0Z5I-2J-2")) m.do_command(GCode.parse_line("G2X0Y0Z5I-2J-2"))
self.assertEqual(m.position(), Coordinates(0, 0, 5)) self.assertEqual(m.position(), Coordinates(0, 0, 5, 0))
def test_g4(self): def test_g4(self):
@@ -120,47 +120,48 @@ class TestGMachine(unittest.TestCase):
def test_g20_g21(self): def test_g20_g21(self):
m = GMachine() m = GMachine()
m.do_command(GCode.parse_line("G20")) m.do_command(GCode.parse_line("G20"))
m.do_command(GCode.parse_line("X3Y2Z1")) m.do_command(GCode.parse_line("X3Y2Z1E0.5"))
self.assertEqual(m.position(), Coordinates(76.2, 50.8, 25.4)) self.assertEqual(m.position(), Coordinates(76.2, 50.8, 25.4, 12.7))
m.do_command(GCode.parse_line("G21")) m.do_command(GCode.parse_line("G21"))
m.do_command(GCode.parse_line("X3Y2Z1")) m.do_command(GCode.parse_line("X3Y2Z1E0.5"))
self.assertEqual(m.position(), Coordinates(3, 2, 1)) self.assertEqual(m.position(), Coordinates(3, 2, 1, 0.5))
def test_g90_g91(self): def test_g90_g91(self):
m = GMachine() m = GMachine()
m.do_command(GCode.parse_line("G91")) m.do_command(GCode.parse_line("G91"))
m.do_command(GCode.parse_line("X1Y1Z1E1"))
m.do_command(GCode.parse_line("X1Y1Z1")) m.do_command(GCode.parse_line("X1Y1Z1"))
m.do_command(GCode.parse_line("X1Y1")) m.do_command(GCode.parse_line("X1Y1"))
m.do_command(GCode.parse_line("X1")) m.do_command(GCode.parse_line("X1"))
self.assertEqual(m.position(), Coordinates(3, 2, 1)) self.assertEqual(m.position(), Coordinates(4, 3, 2, 1))
m.do_command(GCode.parse_line("X-1Y-1Z-1")) m.do_command(GCode.parse_line("X-1Y-1Z-1E-1"))
m.do_command(GCode.parse_line("G90")) m.do_command(GCode.parse_line("G90"))
m.do_command(GCode.parse_line("X1Y1Z1")) m.do_command(GCode.parse_line("X1Y1Z1E1"))
self.assertEqual(m.position(), Coordinates(1, 1, 1)) self.assertEqual(m.position(), Coordinates(1, 1, 1, 1))
def test_g90_g92(self): def test_g90_g92(self):
m = GMachine() m = GMachine()
m.do_command(GCode.parse_line("G92X100Y100Z100")) m.do_command(GCode.parse_line("G92X100Y100Z100E100"))
m.do_command(GCode.parse_line("X101Y102Z103")) m.do_command(GCode.parse_line("X101Y102Z103E104"))
self.assertEqual(m.position(), Coordinates(1, 2, 3)) self.assertEqual(m.position(), Coordinates(1, 2, 3, 4))
m.do_command(GCode.parse_line("G92X-1Y-1Z-1")) m.do_command(GCode.parse_line("G92X-1Y-1Z-1E-1"))
m.do_command(GCode.parse_line("X1Y1Z1")) m.do_command(GCode.parse_line("X1Y1Z1E1"))
self.assertEqual(m.position(), Coordinates(3, 4, 5)) self.assertEqual(m.position(), Coordinates(3, 4, 5, 6))
m.do_command(GCode.parse_line("G92X3Y4Z5")) m.do_command(GCode.parse_line("G92X3Y4Z5E6"))
m.do_command(GCode.parse_line("X0Y0Z0")) m.do_command(GCode.parse_line("X0Y0Z0E0"))
self.assertEqual(m.position(), Coordinates(0, 0, 0)) self.assertEqual(m.position(), Coordinates(0, 0, 0, 0))
m.do_command(GCode.parse_line("G90")) m.do_command(GCode.parse_line("G90"))
m.do_command(GCode.parse_line("X6Y7Z8")) m.do_command(GCode.parse_line("X6Y7Z8E9"))
self.assertEqual(m.position(), Coordinates(6, 7, 8)) self.assertEqual(m.position(), Coordinates(6, 7, 8, 9))
def test_g53_g91_g92(self): def test_g53_g91_g92(self):
m = GMachine() m = GMachine()
m.do_command(GCode.parse_line("G92X-50Y-60Z-70")) m.do_command(GCode.parse_line("G92X-50Y-60Z-70E-80"))
m.do_command(GCode.parse_line("X-45Y-55Z-65")) m.do_command(GCode.parse_line("X-45Y-55Z-65E-75"))
self.assertEqual(m.position(), Coordinates(5, 5, 5)) self.assertEqual(m.position(), Coordinates(5, 5, 5, 5))
m.do_command(GCode.parse_line("G91")) m.do_command(GCode.parse_line("G91"))
m.do_command(GCode.parse_line("X-1Y-2Z-3")) m.do_command(GCode.parse_line("X-1Y-2Z-3E-4"))
self.assertEqual(m.position(), Coordinates(4, 3, 2)) self.assertEqual(m.position(), Coordinates(4, 3, 2, 1))
def test_m3_m5(self): def test_m3_m5(self):
m = GMachine() m = GMachine()
+28 -17
View File
@@ -18,90 +18,101 @@ class TestPulses(unittest.TestCase):
# PulseGenerator should never receive empty movement. # PulseGenerator should never receive empty movement.
self.assertRaises(ZeroDivisionError, self.assertRaises(ZeroDivisionError,
PulseGeneratorLinear, PulseGeneratorLinear,
Coordinates(0, 0, 0), self.v) Coordinates(0, 0, 0, 0), self.v)
def test_step(self): def test_step(self):
# Check if PulseGenerator returns correctly single step movement. # Check if PulseGenerator returns correctly single step movement.
g = PulseGeneratorLinear(Coordinates(1.0 / STEPPER_PULSES_PER_MM_X, 0, 0), g = PulseGeneratorLinear(Coordinates(1.0 / STEPPER_PULSES_PER_MM_X,
0, 0, 0),
self.v) self.v)
i = 0 i = 0
for px, py, pz in g: for px, py, pz, pe in g:
i += 1 i += 1
self.assertEqual(px, 0) self.assertEqual(px, 0)
self.assertEqual(py, None) self.assertEqual(py, None)
self.assertEqual(pz, None) self.assertEqual(pz, None)
self.assertEqual(pe, None)
self.assertEqual(i, 1) self.assertEqual(i, 1)
g = PulseGeneratorLinear(Coordinates( g = PulseGeneratorLinear(Coordinates(
1.0 / STEPPER_PULSES_PER_MM_X, 1.0 / STEPPER_PULSES_PER_MM_X,
1.0 / STEPPER_PULSES_PER_MM_Y, 1.0 / STEPPER_PULSES_PER_MM_Y,
1.0 / STEPPER_PULSES_PER_MM_Z), 1.0 / STEPPER_PULSES_PER_MM_Z,
1.0 / STEPPER_PULSES_PER_MM_E),
self.v) self.v)
i = 0 i = 0
for px, py, pz in g: for px, py, pz, pe in g:
i += 1 i += 1
self.assertEqual(px, 0) self.assertEqual(px, 0)
self.assertEqual(py, 0) self.assertEqual(py, 0)
self.assertEqual(pz, 0) self.assertEqual(pz, 0)
self.assertEqual(pe, 0)
self.assertEqual(i, 1) self.assertEqual(i, 1)
def test_linear_with_hal_virtual(self): def test_linear_with_hal_virtual(self):
# Using hal_virtual module for this test, it already contains plenty # Using hal_virtual module for this test, it already contains plenty
# of asserts for wrong number of pulses, pulse timing issues etc # of asserts for wrong number of pulses, pulse timing issues etc
hal_virtual.move_linear(Coordinates(1, 0, 0), self.v) hal_virtual.move_linear(Coordinates(1, 0, 0, 0), self.v)
hal_virtual.move_linear(Coordinates(25.4, 0, 0), self.v) hal_virtual.move_linear(Coordinates(25.4, 0, 0, 0), self.v)
hal_virtual.move_linear(Coordinates(25.4, 0, 0), self.v) hal_virtual.move_linear(Coordinates(25.4, 0, 0, 0), self.v)
hal_virtual.move_linear(Coordinates(25.4, 0, 0), self.v) hal_virtual.move_linear(Coordinates(25.4, 0, 0, 0), self.v)
hal_virtual.move_linear(Coordinates(TABLE_SIZE_X_MM, hal_virtual.move_linear(Coordinates(TABLE_SIZE_X_MM,
TABLE_SIZE_Y_MM, TABLE_SIZE_Y_MM,
TABLE_SIZE_Z_MM), self.v) TABLE_SIZE_Z_MM,
100.0), self.v)
def test_twice_faster(self): def test_twice_faster(self):
# Checks if one axis moves exactly twice faster, pulses are correct. # Checks if one axis moves exactly twice faster, pulses are correct.
m = Coordinates(2, 4, 0) m = Coordinates(2, 4, 0, 0)
g = PulseGeneratorLinear(m, self.v) g = PulseGeneratorLinear(m, self.v)
i = 0 i = 0
for px, py, pz in g: for px, py, pz, pe in g:
if i % 2 == 0: if i % 2 == 0:
self.assertNotEqual(px, None) self.assertNotEqual(px, None)
else: else:
self.assertEqual(px, None) self.assertEqual(px, None)
self.assertNotEqual(py, None) self.assertNotEqual(py, None)
self.assertEqual(pz, None) self.assertEqual(pz, None)
self.assertEqual(pe, None)
i += 1 i += 1
self.assertEqual(m.find_max() * STEPPER_PULSES_PER_MM_Y, i) self.assertEqual(m.find_max() * STEPPER_PULSES_PER_MM_Y, i)
def test_pulses_count_and_timings(self): def test_pulses_count_and_timings(self):
# Check if number of pulses is equal to specified distance. # Check if number of pulses is equal to specified distance.
m = Coordinates(TABLE_SIZE_X_MM, TABLE_SIZE_Y_MM, TABLE_SIZE_Z_MM) m = Coordinates(TABLE_SIZE_X_MM, TABLE_SIZE_Y_MM, TABLE_SIZE_Z_MM,
100.0)
g = PulseGeneratorLinear(m, self.v) g = PulseGeneratorLinear(m, self.v)
ix = 0 ix = 0
iy = 0 iy = 0
iz = 0 iz = 0
ie = 0
t = -1 t = -1
for px, py, pz in g: for px, py, pz, pe in g:
if px is not None: if px is not None:
ix += 1 ix += 1
if py is not None: if py is not None:
iy += 1 iy += 1
if pz is not None: if pz is not None:
iz += 1 iz += 1
v = list(x for x in (px, py, pz) if x is not None) if pe is not None:
ie += 1
v = list(x for x in (px, py, pz, pe) if x is not None)
self.assertEqual(min(v), max(v)) self.assertEqual(min(v), max(v))
self.assertLess(t, min(v)) self.assertLess(t, min(v))
t = max(v) t = max(v)
self.assertEqual(m.x * STEPPER_PULSES_PER_MM_X, ix) self.assertEqual(m.x * STEPPER_PULSES_PER_MM_X, ix)
self.assertEqual(m.y * STEPPER_PULSES_PER_MM_Y, iy) self.assertEqual(m.y * STEPPER_PULSES_PER_MM_Y, iy)
self.assertEqual(m.z * STEPPER_PULSES_PER_MM_Z, iz) self.assertEqual(m.z * STEPPER_PULSES_PER_MM_Z, iz)
self.assertEqual(m.e * STEPPER_PULSES_PER_MM_E, ie)
self.assertLessEqual(t, g.total_time_s()) self.assertLessEqual(t, g.total_time_s())
def test_acceleration_velocity(self): def test_acceleration_velocity(self):
# Check if acceleration present in pulses sequence and if velocity # Check if acceleration present in pulses sequence and if velocity
# is correct # is correct
m = Coordinates(TABLE_SIZE_X_MM, 0, 0) m = Coordinates(TABLE_SIZE_X_MM, 0, 0, 0)
g = PulseGeneratorLinear(m, self.v) g = PulseGeneratorLinear(m, self.v)
i = 0 i = 0
lx = 0 lx = 0
for px, py, pz in g: for px, py, pz, pe in g:
if i == 2: if i == 2:
at = px - lx at = px - lx
if i == TABLE_SIZE_X_MM * STEPPER_PULSES_PER_MM_X / 2: if i == TABLE_SIZE_X_MM * STEPPER_PULSES_PER_MM_X / 2: