mirror of
https://github.com/sinseman44/PyCNC.git
synced 2026-01-12 02:40:04 +00:00
add E axis
This commit is contained in:
@@ -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_Y = 400
|
||||
STEPPER_PULSES_PER_MM_Z = 400
|
||||
STEPPER_PULSES_PER_MM_E = 80
|
||||
|
||||
TABLE_SIZE_X_MM = 200
|
||||
TABLE_SIZE_Y_MM = 300
|
||||
@@ -17,10 +18,12 @@ SPINDLE_MAX_RPM = 10000
|
||||
STEPPER_STEP_PIN_X = 16
|
||||
STEPPER_STEP_PIN_Y = 20
|
||||
STEPPER_STEP_PIN_Z = 21
|
||||
STEPPER_STEP_PIN_E = 25
|
||||
|
||||
STEPPER_DIR_PIN_X = 13
|
||||
STEPPER_DIR_PIN_Y = 19
|
||||
STEPPER_DIR_PIN_Z = 26
|
||||
STEPPER_DIR_PIN_E = 8
|
||||
|
||||
SPINDLE_PWM_PIN = 7
|
||||
|
||||
|
||||
@@ -6,7 +6,7 @@ class Coordinates(object):
|
||||
""" This object represent machine coordinates.
|
||||
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.
|
||||
:param x: x coordinated.
|
||||
:param y: y coordinated.
|
||||
@@ -15,16 +15,18 @@ class Coordinates(object):
|
||||
self.x = round(x, 10)
|
||||
self.y = round(y, 10)
|
||||
self.z = round(z, 10)
|
||||
self.e = round(e, 10)
|
||||
|
||||
def is_zero(self):
|
||||
""" Check if all coordinates are zero.
|
||||
: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):
|
||||
""" 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 p2: Second point in Coord object.
|
||||
:return: boolean value.
|
||||
@@ -42,46 +44,53 @@ class Coordinates(object):
|
||||
""" Calculate the length of vector.
|
||||
: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.
|
||||
:param base_x: Base for x axis.
|
||||
:param base_y: Base for y axis.
|
||||
:param base_z: Base for z axis.
|
||||
:param base_e: Base for e axis.
|
||||
:return: New rounded object.
|
||||
"""
|
||||
return Coordinates(round(self.x / base_x) * base_x,
|
||||
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):
|
||||
""" Find a maximum value of all values.
|
||||
: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
|
||||
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):
|
||||
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):
|
||||
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):
|
||||
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):
|
||||
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):
|
||||
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):
|
||||
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):
|
||||
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))
|
||||
|
||||
@@ -44,13 +44,15 @@ class GCode(object):
|
||||
x = self.get('X', default.x, multiply)
|
||||
y = self.get('Y', default.y, 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):
|
||||
""" Check if at least one of the coordinates is present.
|
||||
: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):
|
||||
""" Get radius for circular interpolation(I, J, K or R).
|
||||
@@ -61,7 +63,7 @@ class GCode(object):
|
||||
i = self.get('I', default.x, multiply)
|
||||
j = self.get('J', default.y, multiply)
|
||||
k = self.get('K', default.z, multiply)
|
||||
return Coordinates(i, j, k)
|
||||
return Coordinates(i, j, k, 0)
|
||||
|
||||
def command(self):
|
||||
""" Get value from gcode line.
|
||||
|
||||
@@ -22,7 +22,7 @@ class GMachine(object):
|
||||
def __init__(self):
|
||||
""" Initialization.
|
||||
"""
|
||||
self._position = Coordinates(0.0, 0.0, 0.0)
|
||||
self._position = Coordinates(0.0, 0.0, 0.0, 0.0)
|
||||
# init variables
|
||||
self._velocity = 0
|
||||
self._spindle_rpm = 0
|
||||
@@ -48,11 +48,11 @@ class GMachine(object):
|
||||
self._velocity = 1000
|
||||
self._spindle_rpm = 1000
|
||||
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._absoluteCoordinates = True
|
||||
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):
|
||||
hal.join()
|
||||
@@ -60,14 +60,15 @@ class GMachine(object):
|
||||
|
||||
def __check_delta(self, delta):
|
||||
pos = self._position + delta
|
||||
if not pos.is_in_aabb(Coordinates(0.0, 0.0, 0.0),
|
||||
Coordinates(TABLE_SIZE_X_MM, TABLE_SIZE_Y_MM, TABLE_SIZE_Z_MM)):
|
||||
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, 0)):
|
||||
raise GMachineException("out of effective area")
|
||||
|
||||
def _move_linear(self, delta, velocity):
|
||||
delta = delta.round(1.0 / STEPPER_PULSES_PER_MM_X,
|
||||
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():
|
||||
return
|
||||
self.__check_delta(delta)
|
||||
@@ -130,10 +131,11 @@ class GMachine(object):
|
||||
def _circular(self, delta, radius, velocity, direction):
|
||||
delta = delta.round(1.0 / STEPPER_PULSES_PER_MM_X,
|
||||
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)
|
||||
# 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:
|
||||
circle_end.x, circle_end.y = self.__adjust_circle(delta.x, delta.y,
|
||||
radius.x, radius.y, direction,
|
||||
@@ -152,9 +154,11 @@ class GMachine(object):
|
||||
self._position.z, self._position.x,
|
||||
TABLE_SIZE_Z_MM, TABLE_SIZE_X_MM)
|
||||
circle_end.y = delta.y
|
||||
circle_end.e = delta.e
|
||||
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_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)
|
||||
# if finish coords is not on circle, move some distance linearly
|
||||
linear_delta = delta - circle_end
|
||||
@@ -168,9 +172,9 @@ class GMachine(object):
|
||||
def home(self):
|
||||
""" 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)
|
||||
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)
|
||||
|
||||
def position(self):
|
||||
@@ -207,7 +211,8 @@ class GMachine(object):
|
||||
coord = coord + self._local
|
||||
delta = coord - self._position
|
||||
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
|
||||
velocity = gcode.get('F', self._velocity)
|
||||
spindle_rpm = gcode.get('S', self._spindle_rpm)
|
||||
@@ -245,14 +250,14 @@ class GMachine(object):
|
||||
elif c == 'G28': # home
|
||||
self.home()
|
||||
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
|
||||
self._absoluteCoordinates = True
|
||||
elif c == 'G91': # switch to relative coords
|
||||
self._absoluteCoordinates = False
|
||||
elif c == 'G92': # switch to local coords
|
||||
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)
|
||||
elif c == 'M3': # spinle on
|
||||
self._spindle(spindle_rpm)
|
||||
@@ -271,5 +276,4 @@ class GMachine(object):
|
||||
self._spindle_rpm = spindle_rpm
|
||||
self._pause = pause
|
||||
self._radius = radius
|
||||
logging.debug("position {}, {}, {}".format(
|
||||
self._position.x, self._position.y, self._position.z))
|
||||
logging.debug("position {}".format(self._position))
|
||||
|
||||
@@ -27,6 +27,7 @@ pwm = rpgpio.DMAPWM()
|
||||
STEP_PIN_MASK_X = 1 << STEPPER_STEP_PIN_X
|
||||
STEP_PIN_MASK_Y = 1 << STEPPER_STEP_PIN_Y
|
||||
STEP_PIN_MASK_Z = 1 << STEPPER_STEP_PIN_Z
|
||||
STEP_PIN_MASK_E = 1 << STEPPER_STEP_PIN_E
|
||||
|
||||
def init():
|
||||
""" 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_Y, 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_Y, 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)
|
||||
@@ -130,15 +133,19 @@ def move_linear(delta, velocity):
|
||||
gpio.clear(STEPPER_DIR_PIN_Z)
|
||||
else:
|
||||
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
|
||||
dma.clear()
|
||||
prev = 0
|
||||
is_ran = False
|
||||
st = time.time()
|
||||
for tx, ty, tz in generator:
|
||||
for tx, ty, tz, te in generator:
|
||||
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))
|
||||
if tx is not None:
|
||||
pins |= STEP_PIN_MASK_X
|
||||
@@ -146,6 +153,8 @@ def move_linear(delta, velocity):
|
||||
pins |= STEP_PIN_MASK_Y
|
||||
if tz is not None:
|
||||
pins |= STEP_PIN_MASK_Z
|
||||
if te is not None:
|
||||
pins |= STEP_PIN_MASK_E
|
||||
if k - prev > 0:
|
||||
dma.add_delay(k - prev)
|
||||
dma.add_pulse(pins, STEPPER_PULSE_LINGTH_US)
|
||||
|
||||
@@ -31,13 +31,13 @@ def move_linear(delta, velocity):
|
||||
:param velocity: velocity in mm per min
|
||||
"""
|
||||
logging.info("move {} with velocity {}".format(delta, velocity))
|
||||
ix = iy = iz = 0
|
||||
ix = iy = iz = ie = 0
|
||||
generator = PulseGeneratorLinear(delta, velocity)
|
||||
lx, ly, lz = None, None, None
|
||||
dx, dy, dz = 0, 0, 0
|
||||
mx, my, mz = 0, 0, 0
|
||||
lx, ly, lz, le = None, None, None, None
|
||||
dx, dy, dz, de = 0, 0, 0, 0
|
||||
mx, my, mz, me = 0, 0, 0, 0
|
||||
st = time.time()
|
||||
for tx, ty, tz in generator:
|
||||
for tx, ty, tz, te in generator:
|
||||
if tx is not None:
|
||||
if tx > mx:
|
||||
mx = tx
|
||||
@@ -71,16 +71,28 @@ def move_linear(delta, velocity):
|
||||
lz = tz
|
||||
else:
|
||||
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
|
||||
# logging.debug("Iteration {} is {} {} {}".format(max(ix, iy, iz), tx, ty, tz))
|
||||
f = list(x for x in (tx, ty, tz) if x is not None)
|
||||
# logging.debug("Iteration {} is {} {} {} {}".format(max(ix, iy, iz, ie), tx, ty, tz, te))
|
||||
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"
|
||||
pt = time.time()
|
||||
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 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"
|
||||
logging.debug("Did {}, {}, {} iterations".format(ix, iy, iz))
|
||||
assert ie / STEPPER_PULSES_PER_MM_E == abs(delta.e), "e wrong number of pulses"
|
||||
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)) \
|
||||
+ "s, estimated " + str(round(generator.total_time_s(), 2)) + "s")
|
||||
|
||||
|
||||
@@ -41,6 +41,7 @@ class PulseGenerator(object):
|
||||
self._iteration_x = 0
|
||||
self._iteration_y = 0
|
||||
self._iteration_z = 0
|
||||
self._iteration_e = 0
|
||||
self._acceleration_time_s = 0.0
|
||||
self._linear_time_s = 0.0
|
||||
self._2Vmax_per_a = 0.0
|
||||
@@ -60,12 +61,15 @@ class PulseGenerator(object):
|
||||
"""
|
||||
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
|
||||
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,
|
||||
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.
|
||||
"""
|
||||
raise NotImplemented
|
||||
@@ -82,6 +86,7 @@ class PulseGenerator(object):
|
||||
self._iteration_x = 0
|
||||
self._iteration_y = 0
|
||||
self._iteration_z = 0
|
||||
self._iteration_e = 0
|
||||
logging.debug(', '.join("%s: %s" % i for i in vars(self).items()))
|
||||
return self
|
||||
|
||||
@@ -123,15 +128,16 @@ class PulseGenerator(object):
|
||||
the next pulse. If there is no pulses left None will be
|
||||
returned.
|
||||
"""
|
||||
tx, ty, tz = self._interpolation_function(self._iteration_x,
|
||||
self._iteration_y,
|
||||
self._iteration_z)
|
||||
tx, ty, tz, te = self._interpolation_function(self._iteration_x,
|
||||
self._iteration_y,
|
||||
self._iteration_z,
|
||||
self._iteration_e)
|
||||
# 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
|
||||
|
||||
# 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)
|
||||
# sort pulses in time
|
||||
if tx is not None:
|
||||
@@ -152,8 +158,14 @@ class PulseGenerator(object):
|
||||
else:
|
||||
tz = am
|
||||
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):
|
||||
""" Get total time for movement.
|
||||
@@ -173,24 +185,24 @@ class PulseGeneratorLinear(PulseGenerator):
|
||||
# this class doesn't care about direction
|
||||
self._distance_mm = abs(delta_mm)
|
||||
# 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 * (
|
||||
velocity_mm_per_min / SECONDS_IN_MINUTE / distance_xyz_mm)
|
||||
velocity_mm_per_min / SECONDS_IN_MINUTE / distance_total_mm)
|
||||
# acceleration time
|
||||
self.acceleration_time_s = self.max_velocity_mm_per_sec.find_max() \
|
||||
/ STEPPER_MAX_ACCELERATION_MM_PER_S2
|
||||
# check if there is enough space to accelerate and brake, adjust time
|
||||
# S = a * t^2 / 2
|
||||
if STEPPER_MAX_ACCELERATION_MM_PER_S2 * self.acceleration_time_s ** 2 \
|
||||
> distance_xyz_mm:
|
||||
self.acceleration_time_s = math.sqrt(distance_xyz_mm /
|
||||
> distance_total_mm:
|
||||
self.acceleration_time_s = math.sqrt(distance_total_mm /
|
||||
STEPPER_MAX_ACCELERATION_MM_PER_S2)
|
||||
self.linear_time_s = 0.0
|
||||
# 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
|
||||
else:
|
||||
# calculate linear time
|
||||
linear_distance_mm = distance_xyz_mm\
|
||||
linear_distance_mm = distance_total_mm\
|
||||
- self.acceleration_time_s ** 2 \
|
||||
* STEPPER_MAX_ACCELERATION_MM_PER_S2
|
||||
self.linear_time_s = linear_distance_mm \
|
||||
@@ -212,7 +224,7 @@ class PulseGeneratorLinear(PulseGenerator):
|
||||
# Linear movement, S = V * t -> t = S / V
|
||||
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
|
||||
for details.
|
||||
"""
|
||||
@@ -222,4 +234,6 @@ class PulseGeneratorLinear(PulseGenerator):
|
||||
self.max_velocity_mm_per_sec.y)
|
||||
t_z = self.__linear(iz / STEPPER_PULSES_PER_MM_Z, self._distance_mm.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
|
||||
|
||||
@@ -5,7 +5,7 @@ from cnc.coordinates import *
|
||||
|
||||
class TestCoordinates(unittest.TestCase):
|
||||
def setUp(self):
|
||||
self.default = Coordinates(96, 102, 150)
|
||||
self.default = Coordinates(96, 102, 150, 228)
|
||||
|
||||
def tearDown(self):
|
||||
pass
|
||||
@@ -13,110 +13,129 @@ class TestCoordinates(unittest.TestCase):
|
||||
def test_constructor(self):
|
||||
# constructor rounds values to 10 digits after the point
|
||||
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.y, 2.0)
|
||||
self.assertEqual(c.z, -3.5000000009)
|
||||
self.assertEqual(c.e, 0.0)
|
||||
|
||||
def test_zero(self):
|
||||
c = Coordinates(0, 0, 0)
|
||||
c = Coordinates(0, 0, 0, 0)
|
||||
self.assertTrue(c.is_zero())
|
||||
|
||||
def test_aabb(self):
|
||||
# aabb - Axis Aligned Bounded Box.
|
||||
# original method checks if point belongs aabb.
|
||||
p1 = Coordinates(0, 0, 0)
|
||||
p2 = Coordinates(2, 2, 2)
|
||||
c = Coordinates(1, 1, 1)
|
||||
p1 = Coordinates(0, 0, 0, 0)
|
||||
p2 = Coordinates(2, 2, 2, 0)
|
||||
c = Coordinates(1, 1, 1, 0)
|
||||
self.assertTrue(c.is_in_aabb(p1, p2))
|
||||
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))
|
||||
c = Coordinates(2, 2, 2)
|
||||
c = Coordinates(2, 2, 2, 0)
|
||||
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))
|
||||
c = Coordinates(-1, 1, 1)
|
||||
c = Coordinates(-1, 1, 1, 0)
|
||||
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))
|
||||
|
||||
def test_length(self):
|
||||
c = Coordinates(-1, 0, 0)
|
||||
c = Coordinates(-1, 0, 0, 0)
|
||||
self.assertEqual(c.length(), 1)
|
||||
c = Coordinates(0, 3, -4)
|
||||
c = Coordinates(0, 3, -4, 0)
|
||||
self.assertEqual(c.length(), 5)
|
||||
c = Coordinates(3, 4, 12)
|
||||
c = Coordinates(3, 4, 12, 0)
|
||||
self.assertEqual(c.length(), 13)
|
||||
c = Coordinates(1, 1, 1, 1)
|
||||
self.assertEqual(c.length(), 2)
|
||||
|
||||
def test_round(self):
|
||||
# round works in another way then Python's round.
|
||||
# This round() rounds digits with specified step.
|
||||
c = Coordinates(1.5, -1.4, 3.05)
|
||||
r = c.round(1, 1, 1)
|
||||
c = Coordinates(1.5, -1.4, 3.05, 3.5)
|
||||
r = c.round(1, 1, 1, 1)
|
||||
self.assertEqual(r.x, 2.0)
|
||||
self.assertEqual(r.y, -1.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.y, -1.5)
|
||||
self.assertEqual(r.z, 3.0)
|
||||
self.assertEqual(r.e, 3.5)
|
||||
|
||||
def test_max(self):
|
||||
self.assertEqual(self.default.find_max(), max(self.default.x,
|
||||
self.default.y,
|
||||
self.default.z))
|
||||
self.default.y,
|
||||
self.default.z,
|
||||
self.default.e))
|
||||
|
||||
# build-in function overriding tests
|
||||
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.y, self.default.y + 2)
|
||||
self.assertEqual(r.z, self.default.z + 3)
|
||||
self.assertEqual(r.e, self.default.e + 4)
|
||||
|
||||
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.y, self.default.y - 2)
|
||||
self.assertEqual(r.z, self.default.z - 3)
|
||||
self.assertEqual(r.e, self.default.e - 4)
|
||||
|
||||
def test_mul(self):
|
||||
r = self.default * 2
|
||||
self.assertEqual(r.x, self.default.x * 2)
|
||||
self.assertEqual(r.y, self.default.y * 2)
|
||||
self.assertEqual(r.z, self.default.z * 2)
|
||||
self.assertEqual(r.e, self.default.e * 2)
|
||||
|
||||
def test_div(self):
|
||||
r = self.default / 2
|
||||
self.assertEqual(r.x, self.default.x / 2)
|
||||
self.assertEqual(r.y, self.default.y / 2)
|
||||
self.assertEqual(r.z, self.default.z / 2)
|
||||
self.assertEqual(r.e, self.default.e / 2)
|
||||
|
||||
def test_truediv(self):
|
||||
r = self.default / 3.0
|
||||
self.assertEqual(r.x, self.default.x / 3.0)
|
||||
self.assertEqual(r.y, self.default.y / 3.0)
|
||||
self.assertEqual(r.z, self.default.z / 3.0)
|
||||
self.assertEqual(r.e, self.default.e / 3.0)
|
||||
|
||||
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)
|
||||
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)
|
||||
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)
|
||||
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)
|
||||
|
||||
def test_str(self):
|
||||
self.assertTrue(isinstance(str(self.default), str))
|
||||
|
||||
def test_abs(self):
|
||||
c = Coordinates(-1, -2.5, -99)
|
||||
c = Coordinates(-1, -2.5, -99, -23)
|
||||
r = abs(c)
|
||||
self.assertEqual(r.x, 1.0)
|
||||
self.assertEqual(r.y, 2.5)
|
||||
self.assertEqual(r.z, 99.0)
|
||||
self.assertEqual(r.e, 23.0)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
|
||||
@@ -7,7 +7,7 @@ from cnc.gcode import *
|
||||
|
||||
class TestGCode(unittest.TestCase):
|
||||
def setUp(self):
|
||||
self.default = Coordinates(-7, 8, 9)
|
||||
self.default = Coordinates(-7, 8, 9, -10)
|
||||
|
||||
def tearDown(self):
|
||||
pass
|
||||
@@ -16,28 +16,31 @@ class TestGCode(unittest.TestCase):
|
||||
# GCode shouldn't be created with constructor, but since it uses
|
||||
# internally, let's check it.
|
||||
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).y, -2.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):
|
||||
gc = GCode.parse_line("G1X2Y-3Z4")
|
||||
gc = GCode.parse_line("G1X2Y-3Z4E1.5")
|
||||
self.assertEqual(gc.command(), "G1")
|
||||
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).z, 4.0)
|
||||
self.assertEqual(gc.coordinates(self.default, 1).e, 1.5)
|
||||
gc = GCode.parse_line("")
|
||||
self.assertIsNone(gc)
|
||||
|
||||
def test_defaults(self):
|
||||
# defaults are values which should be returned if corresponding
|
||||
# value doesn't exist in gcode.
|
||||
default = Coordinates(11, -12, 14)
|
||||
default = Coordinates(11, -12, 14, -10)
|
||||
gc = GCode.parse_line("G1")
|
||||
self.assertEqual(gc.coordinates(default, 1).x, 11.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).e, -10.0)
|
||||
|
||||
def test_commands(self):
|
||||
gc = GCode({"G": "1"})
|
||||
@@ -48,11 +51,12 @@ class TestGCode(unittest.TestCase):
|
||||
def test_case_sensitivity(self):
|
||||
gc = GCode.parse_line("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.coordinates(self.default, 1).x, 3.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).e, 6.0)
|
||||
|
||||
def test_has_coordinates(self):
|
||||
gc = GCode.parse_line("X2Y-3Z4")
|
||||
@@ -65,6 +69,8 @@ class TestGCode(unittest.TestCase):
|
||||
self.assertTrue(gc.has_coordinates())
|
||||
gc = GCode.parse_line("Z1")
|
||||
self.assertTrue(gc.has_coordinates())
|
||||
gc = GCode.parse_line("E1")
|
||||
self.assertTrue(gc.has_coordinates())
|
||||
|
||||
def test_radius(self):
|
||||
gc = GCode.parse_line("G2I1J2K3")
|
||||
@@ -78,10 +84,11 @@ class TestGCode(unittest.TestCase):
|
||||
|
||||
def test_multiply(self):
|
||||
# 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, 2).y, -6)
|
||||
self.assertEqual(gc.coordinates(self.default, 0).y, 0)
|
||||
self.assertEqual(gc.coordinates(self.default, 5).e, 25)
|
||||
|
||||
def test_whitespaces(self):
|
||||
gc = GCode.parse_line("X1 Y2")
|
||||
|
||||
@@ -20,28 +20,28 @@ class TestGMachine(unittest.TestCase):
|
||||
m.do_command(GCode.parse_line("G91"))
|
||||
m.do_command(GCode.parse_line("X1Y1Z1"))
|
||||
m.reset()
|
||||
m.do_command(GCode.parse_line("X3Y4Z5"))
|
||||
self.assertEqual(m.position(), Coordinates(3, 4, 5))
|
||||
m.do_command(GCode.parse_line("X3Y4Z5E6"))
|
||||
self.assertEqual(m.position(), Coordinates(3, 4, 5, 6))
|
||||
|
||||
def test_release(self):
|
||||
# release homes head.
|
||||
m = GMachine()
|
||||
m.do_command(GCode.parse_line("X1Y2Z3"))
|
||||
m.do_command(GCode.parse_line("X1Y2Z3E4"))
|
||||
m.release()
|
||||
self.assertEqual(m.position(), Coordinates(0, 0, 0))
|
||||
self.assertEqual(m.position(), Coordinates(0, 0, 0, 4))
|
||||
|
||||
def test_home(self):
|
||||
m = GMachine()
|
||||
m.do_command(GCode.parse_line("X1Y2Z3"))
|
||||
m.do_command(GCode.parse_line("X1Y2Z3E4"))
|
||||
m.home()
|
||||
self.assertEqual(m.position(), Coordinates(0, 0, 0))
|
||||
self.assertEqual(m.position(), Coordinates(0, 0, 0, 4))
|
||||
|
||||
def test_none(self):
|
||||
# GMachine must ignore None commands, since GCode.parse_line()
|
||||
# returns None if no gcode found in line.
|
||||
m = GMachine()
|
||||
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):
|
||||
# Test commands which doesn't exists
|
||||
@@ -54,10 +54,10 @@ class TestGMachine(unittest.TestCase):
|
||||
# Test gcode commands.
|
||||
def test_g0_g1(self):
|
||||
m = GMachine()
|
||||
m.do_command(GCode.parse_line("G0X3Y2Z1"))
|
||||
self.assertEqual(m.position(), Coordinates(3, 2, 1))
|
||||
m.do_command(GCode.parse_line("G1X1Y2Z3"))
|
||||
self.assertEqual(m.position(), Coordinates(1, 2, 3))
|
||||
m.do_command(GCode.parse_line("G0X3Y2Z1E-2"))
|
||||
self.assertEqual(m.position(), Coordinates(3, 2, 1, -2))
|
||||
m.do_command(GCode.parse_line("G1X1Y2Z3E4"))
|
||||
self.assertEqual(m.position(), Coordinates(1, 2, 3, 4))
|
||||
self.assertRaises(GMachineException,
|
||||
m.do_command, GCode.parse_line("G1F-1"))
|
||||
self.assertRaises(GMachineException,
|
||||
@@ -86,7 +86,7 @@ class TestGMachine(unittest.TestCase):
|
||||
m.do_command, GCode.parse_line("G2X99999999Y99999999I1J1"))
|
||||
self.assertRaises(GMachineException,
|
||||
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,
|
||||
m.do_command, GCode.parse_line("G2X4Y4I2J2"))
|
||||
self.assertRaises(GMachineException,
|
||||
@@ -94,10 +94,10 @@ class TestGMachine(unittest.TestCase):
|
||||
m.do_command(GCode.parse_line("G1X1"))
|
||||
m.do_command(GCode.parse_line("G2J1"))
|
||||
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("G2X0Y0Z5I-2J-2"))
|
||||
self.assertEqual(m.position(), Coordinates(0, 0, 5))
|
||||
self.assertEqual(m.position(), Coordinates(0, 0, 5, 0))
|
||||
|
||||
|
||||
def test_g4(self):
|
||||
@@ -120,47 +120,48 @@ class TestGMachine(unittest.TestCase):
|
||||
def test_g20_g21(self):
|
||||
m = GMachine()
|
||||
m.do_command(GCode.parse_line("G20"))
|
||||
m.do_command(GCode.parse_line("X3Y2Z1"))
|
||||
self.assertEqual(m.position(), Coordinates(76.2, 50.8, 25.4))
|
||||
m.do_command(GCode.parse_line("X3Y2Z1E0.5"))
|
||||
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("X3Y2Z1"))
|
||||
self.assertEqual(m.position(), Coordinates(3, 2, 1))
|
||||
m.do_command(GCode.parse_line("X3Y2Z1E0.5"))
|
||||
self.assertEqual(m.position(), Coordinates(3, 2, 1, 0.5))
|
||||
|
||||
def test_g90_g91(self):
|
||||
m = GMachine()
|
||||
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("X1Y1"))
|
||||
m.do_command(GCode.parse_line("X1"))
|
||||
self.assertEqual(m.position(), Coordinates(3, 2, 1))
|
||||
m.do_command(GCode.parse_line("X-1Y-1Z-1"))
|
||||
self.assertEqual(m.position(), Coordinates(4, 3, 2, 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("X1Y1Z1"))
|
||||
self.assertEqual(m.position(), Coordinates(1, 1, 1))
|
||||
m.do_command(GCode.parse_line("X1Y1Z1E1"))
|
||||
self.assertEqual(m.position(), Coordinates(1, 1, 1, 1))
|
||||
|
||||
def test_g90_g92(self):
|
||||
m = GMachine()
|
||||
m.do_command(GCode.parse_line("G92X100Y100Z100"))
|
||||
m.do_command(GCode.parse_line("X101Y102Z103"))
|
||||
self.assertEqual(m.position(), Coordinates(1, 2, 3))
|
||||
m.do_command(GCode.parse_line("G92X-1Y-1Z-1"))
|
||||
m.do_command(GCode.parse_line("X1Y1Z1"))
|
||||
self.assertEqual(m.position(), Coordinates(3, 4, 5))
|
||||
m.do_command(GCode.parse_line("G92X3Y4Z5"))
|
||||
m.do_command(GCode.parse_line("X0Y0Z0"))
|
||||
self.assertEqual(m.position(), Coordinates(0, 0, 0))
|
||||
m.do_command(GCode.parse_line("G92X100Y100Z100E100"))
|
||||
m.do_command(GCode.parse_line("X101Y102Z103E104"))
|
||||
self.assertEqual(m.position(), Coordinates(1, 2, 3, 4))
|
||||
m.do_command(GCode.parse_line("G92X-1Y-1Z-1E-1"))
|
||||
m.do_command(GCode.parse_line("X1Y1Z1E1"))
|
||||
self.assertEqual(m.position(), Coordinates(3, 4, 5, 6))
|
||||
m.do_command(GCode.parse_line("G92X3Y4Z5E6"))
|
||||
m.do_command(GCode.parse_line("X0Y0Z0E0"))
|
||||
self.assertEqual(m.position(), Coordinates(0, 0, 0, 0))
|
||||
m.do_command(GCode.parse_line("G90"))
|
||||
m.do_command(GCode.parse_line("X6Y7Z8"))
|
||||
self.assertEqual(m.position(), Coordinates(6, 7, 8))
|
||||
m.do_command(GCode.parse_line("X6Y7Z8E9"))
|
||||
self.assertEqual(m.position(), Coordinates(6, 7, 8, 9))
|
||||
|
||||
def test_g53_g91_g92(self):
|
||||
m = GMachine()
|
||||
m.do_command(GCode.parse_line("G92X-50Y-60Z-70"))
|
||||
m.do_command(GCode.parse_line("X-45Y-55Z-65"))
|
||||
self.assertEqual(m.position(), Coordinates(5, 5, 5))
|
||||
m.do_command(GCode.parse_line("G92X-50Y-60Z-70E-80"))
|
||||
m.do_command(GCode.parse_line("X-45Y-55Z-65E-75"))
|
||||
self.assertEqual(m.position(), Coordinates(5, 5, 5, 5))
|
||||
m.do_command(GCode.parse_line("G91"))
|
||||
m.do_command(GCode.parse_line("X-1Y-2Z-3"))
|
||||
self.assertEqual(m.position(), Coordinates(4, 3, 2))
|
||||
m.do_command(GCode.parse_line("X-1Y-2Z-3E-4"))
|
||||
self.assertEqual(m.position(), Coordinates(4, 3, 2, 1))
|
||||
|
||||
def test_m3_m5(self):
|
||||
m = GMachine()
|
||||
|
||||
@@ -18,90 +18,101 @@ class TestPulses(unittest.TestCase):
|
||||
# PulseGenerator should never receive empty movement.
|
||||
self.assertRaises(ZeroDivisionError,
|
||||
PulseGeneratorLinear,
|
||||
Coordinates(0, 0, 0), self.v)
|
||||
Coordinates(0, 0, 0, 0), self.v)
|
||||
|
||||
def test_step(self):
|
||||
# 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)
|
||||
i = 0
|
||||
for px, py, pz in g:
|
||||
for px, py, pz, pe in g:
|
||||
i += 1
|
||||
self.assertEqual(px, 0)
|
||||
self.assertEqual(py, None)
|
||||
self.assertEqual(pz, None)
|
||||
self.assertEqual(pe, None)
|
||||
self.assertEqual(i, 1)
|
||||
g = PulseGeneratorLinear(Coordinates(
|
||||
1.0 / STEPPER_PULSES_PER_MM_X,
|
||||
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)
|
||||
i = 0
|
||||
for px, py, pz in g:
|
||||
for px, py, pz, pe in g:
|
||||
i += 1
|
||||
self.assertEqual(px, 0)
|
||||
self.assertEqual(py, 0)
|
||||
self.assertEqual(pz, 0)
|
||||
self.assertEqual(pe, 0)
|
||||
self.assertEqual(i, 1)
|
||||
|
||||
def test_linear_with_hal_virtual(self):
|
||||
# Using hal_virtual module for this test, it already contains plenty
|
||||
# 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(25.4, 0, 0), self.v)
|
||||
hal_virtual.move_linear(Coordinates(25.4, 0, 0), self.v)
|
||||
hal_virtual.move_linear(Coordinates(25.4, 0, 0), self.v)
|
||||
hal_virtual.move_linear(Coordinates(1, 0, 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, 0), self.v)
|
||||
hal_virtual.move_linear(Coordinates(25.4, 0, 0, 0), self.v)
|
||||
hal_virtual.move_linear(Coordinates(TABLE_SIZE_X_MM,
|
||||
TABLE_SIZE_Y_MM,
|
||||
TABLE_SIZE_Z_MM), self.v)
|
||||
TABLE_SIZE_Z_MM,
|
||||
100.0), self.v)
|
||||
|
||||
def test_twice_faster(self):
|
||||
# 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)
|
||||
i = 0
|
||||
for px, py, pz in g:
|
||||
for px, py, pz, pe in g:
|
||||
if i % 2 == 0:
|
||||
self.assertNotEqual(px, None)
|
||||
else:
|
||||
self.assertEqual(px, None)
|
||||
self.assertNotEqual(py, None)
|
||||
self.assertEqual(pz, None)
|
||||
self.assertEqual(pe, None)
|
||||
i += 1
|
||||
self.assertEqual(m.find_max() * STEPPER_PULSES_PER_MM_Y, i)
|
||||
|
||||
def test_pulses_count_and_timings(self):
|
||||
# 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)
|
||||
ix = 0
|
||||
iy = 0
|
||||
iz = 0
|
||||
ie = 0
|
||||
t = -1
|
||||
for px, py, pz in g:
|
||||
for px, py, pz, pe in g:
|
||||
if px is not None:
|
||||
ix += 1
|
||||
if py is not None:
|
||||
iy += 1
|
||||
if pz is not None:
|
||||
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.assertLess(t, min(v))
|
||||
t = max(v)
|
||||
self.assertEqual(m.x * STEPPER_PULSES_PER_MM_X, ix)
|
||||
self.assertEqual(m.y * STEPPER_PULSES_PER_MM_Y, iy)
|
||||
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())
|
||||
|
||||
def test_acceleration_velocity(self):
|
||||
# Check if acceleration present in pulses sequence and if velocity
|
||||
# is correct
|
||||
m = Coordinates(TABLE_SIZE_X_MM, 0, 0)
|
||||
m = Coordinates(TABLE_SIZE_X_MM, 0, 0, 0)
|
||||
g = PulseGeneratorLinear(m, self.v)
|
||||
i = 0
|
||||
lx = 0
|
||||
for px, py, pz in g:
|
||||
for px, py, pz, pe in g:
|
||||
if i == 2:
|
||||
at = px - lx
|
||||
if i == TABLE_SIZE_X_MM * STEPPER_PULSES_PER_MM_X / 2:
|
||||
|
||||
Reference in New Issue
Block a user