mirror of
https://github.com/sinseman44/PyCNC.git
synced 2026-01-11 02:30:05 +00:00
refactoring
This commit is contained in:
11
.idea/codeStyleSettings.xml
generated
Normal file
11
.idea/codeStyleSettings.xml
generated
Normal file
@@ -0,0 +1,11 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="ProjectCodeStyleSettingsManager">
|
||||
<option name="PER_PROJECT_SETTINGS">
|
||||
<value>
|
||||
<option name="ALIGN_MULTILINE_PARAMETERS_IN_CALLS" value="true" />
|
||||
</value>
|
||||
</option>
|
||||
<option name="PREFERRED_PROJECT_CODE_STYLE" value="Default" />
|
||||
</component>
|
||||
</project>
|
||||
12
.idea/inspectionProfiles/Project_Default.xml
generated
12
.idea/inspectionProfiles/Project_Default.xml
generated
@@ -1,6 +1,9 @@
|
||||
<component name="InspectionProjectProfileManager">
|
||||
<profile version="1.0">
|
||||
<option name="myName" value="Project Default" />
|
||||
<inspection_tool class="PyChainedComparisonsInspection" enabled="true" level="WEAK WARNING" enabled_by_default="true">
|
||||
<option name="ignoreConstantInTheMiddle" value="true" />
|
||||
</inspection_tool>
|
||||
<inspection_tool class="PyCompatibilityInspection" enabled="true" level="WARNING" enabled_by_default="true">
|
||||
<option name="ourVersions">
|
||||
<value>
|
||||
@@ -11,5 +14,14 @@
|
||||
</value>
|
||||
</option>
|
||||
</inspection_tool>
|
||||
<inspection_tool class="PyPackageRequirementsInspection" enabled="true" level="WARNING" enabled_by_default="true">
|
||||
<option name="ignoredPackages">
|
||||
<value>
|
||||
<list size="1">
|
||||
<item index="0" class="java.lang.String" itemvalue="pypandoc" />
|
||||
</list>
|
||||
</value>
|
||||
</option>
|
||||
</inspection_tool>
|
||||
</profile>
|
||||
</component>
|
||||
18
README.md
18
README.md
@@ -30,11 +30,13 @@ perfect choice for easy development of this project.
|
||||
|
||||
Video demo - [YouTube video](https://youtu.be/vcedo59raS4)
|
||||
|
||||
# Current command support
|
||||
G0, G1, G2, G3, G4, G17, G18, G19, G20, G21, G28, G53, G90, G91, G92, M2, M3,
|
||||
M5, M30
|
||||
Commands can be easily added, see [gmachine.py](./cnc/gmachine.py) file.
|
||||
Four axis are supported - X, Y, Z, E
|
||||
# Current gcode support
|
||||
Commands G0, G1, G2, G3, G4, G17, G18, G19, G20, G21, G28, G53, G90, G91, G92, M2, M3,
|
||||
M5, M30 are supported. Commands can be easily added, see
|
||||
[gmachine.py](./cnc/gmachine.py) file.
|
||||
Four axis are supported - X, Y, Z, E.
|
||||
Spindle with rpm control is supported.
|
||||
Circular interpolation for XY, ZX, YZ planes is supported.
|
||||
|
||||
# Config
|
||||
All configs are stored in [config.py](./cnc/config.py) and contain hardware
|
||||
@@ -47,8 +49,8 @@ So having Raspberry Pi connected this way, there is no need to configure
|
||||
pin map for project.
|
||||
|
||||
# Hardware
|
||||
Currently, this project supports Raspberry Pi 1-3. Tested with RPI2 and RPI3.
|
||||
But there is a way to add new boards. See [hal.py](./cnc/hal.py) file.
|
||||
Currently, this project supports Raspberry Pi 1-3. Developed and tested with
|
||||
RPI3. And there is a way to add new boards. See [hal.py](./cnc/hal.py) file.
|
||||
_Note: Current Raspberry Pi implementation uses the same resources as on board
|
||||
3.5 mm jack(PWM module), so do not use it. HDMI audio works._
|
||||
|
||||
@@ -67,7 +69,7 @@ sudo pip remove pycnc
|
||||
```
|
||||
|
||||
# Performance notice
|
||||
Pure Python interpreter wouldn't provide great performance for high speed
|
||||
Pure Python interpreter would not provide great performance for high speed
|
||||
machines. Overspeeding setting causes motors mispulses and probably lose of
|
||||
trajectory. According to my tests, Raspberry Pi 2 can handle axises with 400
|
||||
pulses on mm with top velocity ~800 mm per min. There is always way out! :)
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
# Hardware limitations config
|
||||
STEPPER_PULSE_LINGTH_US = 2
|
||||
STEPPER_PULSE_LENGTH_US = 2
|
||||
STEPPER_MAX_VELOCITY_MM_PER_MIN = 1800 # mm per min
|
||||
STEPPER_MAX_ACCELERATION_MM_PER_S2 = 200 # mm per sec^2
|
||||
|
||||
|
||||
@@ -21,8 +21,8 @@ class Coordinates(object):
|
||||
""" Check if all coordinates are zero.
|
||||
:return: boolean value.
|
||||
"""
|
||||
return self.x == 0.0 and self.y == 0.0 and self.z == 0.0 and \
|
||||
self.e == 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).
|
||||
@@ -31,12 +31,12 @@ class Coordinates(object):
|
||||
:param p2: Second point in Coord object.
|
||||
:return: boolean value.
|
||||
"""
|
||||
minx, maxx = sorted((p1.x, p2.x))
|
||||
miny, maxy = sorted((p1.y, p2.y))
|
||||
minz, maxz = sorted((p1.z, p2.z))
|
||||
if self.x < minx or self.y < miny or self.z < minz:
|
||||
min_x, max_x = sorted((p1.x, p2.x))
|
||||
min_y, max_y = sorted((p1.y, p2.y))
|
||||
min_z, max_z = sorted((p1.z, p2.z))
|
||||
if self.x < min_x or self.y < min_y or self.z < min_z:
|
||||
return False
|
||||
if self.x > maxx or self.y > maxy or self.z > maxz:
|
||||
if self.x > max_x or self.y > max_y or self.z > max_z:
|
||||
return False
|
||||
return True
|
||||
|
||||
@@ -76,12 +76,21 @@ class Coordinates(object):
|
||||
self.z - other.z, self.e - other.e)
|
||||
|
||||
def __mul__(self, v):
|
||||
"""
|
||||
@rtype: Coordinates
|
||||
"""
|
||||
return Coordinates(self.x * v, self.y * v, self.z * v, self.e * v)
|
||||
|
||||
def __div__(self, v):
|
||||
"""
|
||||
@rtype: Coordinates
|
||||
"""
|
||||
return Coordinates(self.x / v, self.y / v, self.z / v, self.e / v)
|
||||
|
||||
def __truediv__(self, v):
|
||||
"""
|
||||
@rtype: Coordinates
|
||||
"""
|
||||
return Coordinates(self.x / v, self.y / v, self.z / v, self.e / v)
|
||||
|
||||
def __eq__(self, other):
|
||||
|
||||
23
cnc/gcode.py
23
cnc/gcode.py
@@ -1,11 +1,11 @@
|
||||
import re
|
||||
import math
|
||||
|
||||
from cnc.coordinates import Coordinates
|
||||
from cnc.enums import Plane
|
||||
|
||||
gpattern = re.compile('([A-Z])([-+]?[0-9.]+)')
|
||||
cleanpattern = re.compile('\s+|\(.*?\)|;.*') # white spaces and comments start with ';' and in '()'
|
||||
# extract letter-digit pairs
|
||||
g_pattern = re.compile('([A-Z])([-+]?[0-9.]+)')
|
||||
# white spaces and comments start with ';' and in '()'
|
||||
clean_pattern = re.compile('\s+|\(.*?\)|;.*')
|
||||
|
||||
|
||||
class GCodeException(Exception):
|
||||
@@ -24,20 +24,20 @@ class GCode(object):
|
||||
"""
|
||||
self.params = params
|
||||
|
||||
def get(self, argname, default=None, multiply=1.0):
|
||||
def get(self, arg_name, default=None, multiply=1.0):
|
||||
""" Get value from gcode line.
|
||||
:param argname: Value name.
|
||||
:param arg_name: Value name.
|
||||
:param default: Default value if value doesn't exist.
|
||||
:param multiply: if value exist, multiply it by this value.
|
||||
:return: Value if exists or default otherwise.
|
||||
"""
|
||||
if argname not in self.params:
|
||||
if arg_name not in self.params:
|
||||
return default
|
||||
return float(self.params[argname]) * multiply
|
||||
return float(self.params[arg_name]) * multiply
|
||||
|
||||
def coordinates(self, default, multiply):
|
||||
""" Get X, Y and Z values as Coord object.
|
||||
:param default: Default values, if any of coords is not specified.
|
||||
:param default: Default values, if any of coordinates is not specified.
|
||||
:param multiply: If value exist, multiply it by this value.
|
||||
:return: Coord object.
|
||||
"""
|
||||
@@ -82,16 +82,17 @@ class GCode(object):
|
||||
:return: gcode objects.
|
||||
"""
|
||||
line = line.upper()
|
||||
line = re.sub(cleanpattern, '', line)
|
||||
line = re.sub(clean_pattern, '', line)
|
||||
if len(line) == 0:
|
||||
return None
|
||||
if line[0] == '%':
|
||||
return None
|
||||
m = gpattern.findall(line)
|
||||
m = g_pattern.findall(line)
|
||||
if not m:
|
||||
raise GCodeException('gcode not found')
|
||||
if len(''.join(["%s%s" % i for i in m])) != len(line):
|
||||
raise GCodeException('extra characters in line')
|
||||
# noinspection PyTypeChecker
|
||||
params = dict(m)
|
||||
if len(params) != len(m):
|
||||
raise GCodeException('duplicated gcode entries')
|
||||
|
||||
@@ -1,13 +1,10 @@
|
||||
from __future__ import division
|
||||
import time
|
||||
import logging
|
||||
import math
|
||||
|
||||
import cnc.logging_config as logging_config
|
||||
from cnc import hal
|
||||
from cnc.coordinates import Coordinates
|
||||
from cnc.enums import *
|
||||
from cnc.config import *
|
||||
from cnc.pulses import *
|
||||
from cnc.coordinates import *
|
||||
|
||||
|
||||
class GMachineException(Exception):
|
||||
@@ -53,6 +50,7 @@ class GMachine(object):
|
||||
self._absoluteCoordinates = True
|
||||
self._plane = PLANE_XY
|
||||
|
||||
# noinspection PyMethodMayBeStatic
|
||||
def _spindle(self, spindle_speed):
|
||||
hal.join()
|
||||
hal.spindle_control(100.0 * spindle_speed / SPINDLE_MAX_RPM)
|
||||
@@ -60,7 +58,8 @@ 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, 0.0),
|
||||
Coordinates(TABLE_SIZE_X_MM, TABLE_SIZE_Y_MM, TABLE_SIZE_Z_MM, 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):
|
||||
@@ -78,7 +77,8 @@ class GMachine(object):
|
||||
# save position
|
||||
self._position = self._position + delta
|
||||
|
||||
def __quarter(self, pa, pb):
|
||||
@staticmethod
|
||||
def __quarter(pa, pb):
|
||||
if pa >= 0 and pb >= 0:
|
||||
return 1
|
||||
if pa < 0 and pb >= 0:
|
||||
@@ -88,7 +88,7 @@ class GMachine(object):
|
||||
if pa >= 0 and pb < 0:
|
||||
return 4
|
||||
|
||||
def __adjust_circle(self, da, db, ra, rb, dir, pa, pb, ma, mb):
|
||||
def __adjust_circle(self, da, db, ra, rb, direction, pa, pb, ma, mb):
|
||||
r = math.sqrt(ra * ra + rb * rb)
|
||||
if r == 0:
|
||||
raise GMachineException("circle radius is zero")
|
||||
@@ -108,7 +108,7 @@ class GMachine(object):
|
||||
q = sq
|
||||
pq = q
|
||||
for _ in range(0, 4):
|
||||
if dir == CW:
|
||||
if direction == CW:
|
||||
q -= 1
|
||||
else:
|
||||
q += 1
|
||||
@@ -120,7 +120,7 @@ class GMachine(object):
|
||||
break
|
||||
is_raise = False
|
||||
if (pq == 1 and q == 4) or (pq == 4 and q == 1):
|
||||
is_raise = (pa + ra + r > ma)
|
||||
is_raise = (pa + ra + r > ma)
|
||||
elif (pq == 1 and q == 2) or (pq == 2 and q == 1):
|
||||
is_raise = (pb + rb + r > mb)
|
||||
elif (pq == 2 and q == 3) or (pq == 3 and q == 2):
|
||||
@@ -145,22 +145,25 @@ class GMachine(object):
|
||||
# get delta vector and put it on circle
|
||||
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,
|
||||
self._position.x, self._position.y,
|
||||
TABLE_SIZE_X_MM, TABLE_SIZE_Y_MM)
|
||||
circle_end.x, circle_end.y = \
|
||||
self.__adjust_circle(delta.x, delta.y, radius.x, radius.y,
|
||||
direction, self._position.x,
|
||||
self._position.y, TABLE_SIZE_X_MM,
|
||||
TABLE_SIZE_Y_MM)
|
||||
circle_end.z = delta.z
|
||||
elif self._plane == PLANE_YZ:
|
||||
circle_end.y, circle_end.z = self.__adjust_circle(delta.y, delta.z,
|
||||
radius.y, radius.z, direction,
|
||||
self._position.y, self._position.z,
|
||||
TABLE_SIZE_Y_MM, TABLE_SIZE_Z_MM)
|
||||
circle_end.y, circle_end.z = \
|
||||
self.__adjust_circle(delta.y, delta.z, radius.y, radius.z,
|
||||
direction, self._position.y,
|
||||
self._position.z, TABLE_SIZE_Y_MM,
|
||||
TABLE_SIZE_Z_MM)
|
||||
circle_end.x = delta.x
|
||||
elif self._plane == PLANE_ZX:
|
||||
circle_end.z, circle_end.x = self.__adjust_circle(delta.z, delta.x,
|
||||
radius.z, radius.x, direction,
|
||||
self._position.z, self._position.x,
|
||||
TABLE_SIZE_Z_MM, TABLE_SIZE_X_MM)
|
||||
circle_end.z, circle_end.x = \
|
||||
self.__adjust_circle(delta.z, delta.x, radius.z, radius.x,
|
||||
direction, 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,
|
||||
@@ -168,8 +171,8 @@ class GMachine(object):
|
||||
1.0 / STEPPER_PULSES_PER_MM_Z,
|
||||
1.0 / STEPPER_PULSES_PER_MM_E)
|
||||
logging.info("Moving circularly {} {} {} with radius {}"
|
||||
" and velocity {}".
|
||||
format(self._plane, circle_end, direction, radius, velocity))
|
||||
" and velocity {}".format(self._plane, circle_end,
|
||||
direction, radius, velocity))
|
||||
gen = PulseGeneratorCircular(circle_end, radius, self._plane, direction,
|
||||
velocity)
|
||||
hal.move(gen)
|
||||
@@ -227,7 +230,7 @@ class GMachine(object):
|
||||
else:
|
||||
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)
|
||||
spindle_rpm = gcode.get('S', self._spindle_rpm)
|
||||
pause = gcode.get('P', self._pause)
|
||||
@@ -274,14 +277,14 @@ class GMachine(object):
|
||||
self._local = self._position - \
|
||||
gcode.coordinates(Coordinates(0.0, 0.0, 0.0, 0.0),
|
||||
self._convertCoordinates)
|
||||
elif c == 'M3': # spinle on
|
||||
elif c == 'M3': # spindle on
|
||||
self._spindle(spindle_rpm)
|
||||
elif c == 'M5': # spindle off
|
||||
self._spindle(0)
|
||||
elif c == 'M2' or c == 'M30': # program finish, reset everything.
|
||||
self.reset()
|
||||
elif c == 'M111': # enable debug
|
||||
logging.getLogger().setLevel(logging.DEBUG)
|
||||
logging_config.debug_enable()
|
||||
elif c is None: # command not specified(for example, just F was passed)
|
||||
pass
|
||||
else:
|
||||
|
||||
@@ -1,23 +1,9 @@
|
||||
import logging
|
||||
import time
|
||||
|
||||
from cnc.hal_raspberry import rpgpio
|
||||
|
||||
from cnc.pulses import PulseGeneratorLinear
|
||||
from cnc.coordinates import Coordinates
|
||||
from cnc.pulses import *
|
||||
from cnc.config import *
|
||||
|
||||
# Stepper motors channel for RPIO
|
||||
STEPPER_CHANNEL = 0
|
||||
# Since there is no way to add pulses and then start cycle in RPIO,
|
||||
# use this delay to start adding pulses to cycle. It can be easily
|
||||
# solved by modifying RPIO in a way of adding method to start cycle
|
||||
# explicitly.
|
||||
RPIO_START_DELAY_US = 200000
|
||||
# Since RPIO generate cycles in loop, use this delay to stop RPIO
|
||||
# It can be removed if RPIO would allow to run single shot cycle.
|
||||
RPIO_STOP_DELAY_US = 5000000
|
||||
|
||||
US_IN_SECONDS = 1000000
|
||||
|
||||
gpio = rpgpio.GPIO()
|
||||
@@ -29,8 +15,9 @@ 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
|
||||
""" Initialize GPIO pins and machine itself, including calibration if
|
||||
needed. Do not return till all procedures are completed.
|
||||
"""
|
||||
gpio.init(STEPPER_STEP_PIN_X, rpgpio.GPIO.MODE_OUTPUT)
|
||||
@@ -53,7 +40,7 @@ def init():
|
||||
gpio.set(STEPPER_DIR_PIN_Z)
|
||||
pins = STEP_PIN_MASK_X | STEP_PIN_MASK_Y | STEP_PIN_MASK_Z
|
||||
dma.clear()
|
||||
dma.add_pulse(pins, STEPPER_PULSE_LINGTH_US)
|
||||
dma.add_pulse(pins, STEPPER_PULSE_LENGTH_US)
|
||||
st = time.time()
|
||||
max_pulses_left = int(1.2 * max(STEPPER_PULSES_PER_MM_X,
|
||||
STEPPER_PULSES_PER_MM_Y,
|
||||
@@ -66,15 +53,15 @@ def init():
|
||||
if (STEP_PIN_MASK_X & pins) != 0 and gpio.read(ENDSTOP_PIN_X) == 0:
|
||||
pins &= ~STEP_PIN_MASK_X
|
||||
dma.clear()
|
||||
dma.add_pulse(pins, STEPPER_PULSE_LINGTH_US)
|
||||
dma.add_pulse(pins, STEPPER_PULSE_LENGTH_US)
|
||||
if (STEP_PIN_MASK_Y & pins) != 0 and gpio.read(ENDSTOP_PIN_Y) == 0:
|
||||
pins &= ~STEP_PIN_MASK_Y
|
||||
dma.clear()
|
||||
dma.add_pulse(pins, STEPPER_PULSE_LINGTH_US)
|
||||
dma.add_pulse(pins, STEPPER_PULSE_LENGTH_US)
|
||||
if (STEP_PIN_MASK_Z & pins) != 0 and gpio.read(ENDSTOP_PIN_Z) == 0:
|
||||
pins &= ~STEP_PIN_MASK_Z
|
||||
dma.clear()
|
||||
dma.add_pulse(pins, STEPPER_PULSE_LINGTH_US)
|
||||
dma.add_pulse(pins, STEPPER_PULSE_LENGTH_US)
|
||||
if pins == 0:
|
||||
break
|
||||
dma.run(False)
|
||||
@@ -122,8 +109,8 @@ def move(generator):
|
||||
is_ran = False
|
||||
instant = INSTANT_RUN
|
||||
st = time.time()
|
||||
for dir, tx, ty, tz, te in generator:
|
||||
if dir: # set up directions
|
||||
for direction, tx, ty, tz, te in generator:
|
||||
if direction: # set up directions
|
||||
pins_to_set = 0
|
||||
pins_to_clear = 0
|
||||
if tx > 0:
|
||||
@@ -160,11 +147,11 @@ def move(generator):
|
||||
pins |= STEP_PIN_MASK_E
|
||||
if k - prev > 0:
|
||||
dma.add_delay(k - prev)
|
||||
dma.add_pulse(pins, STEPPER_PULSE_LINGTH_US)
|
||||
dma.add_pulse(pins, STEPPER_PULSE_LENGTH_US)
|
||||
# TODO not a precise way! pulses will set in queue, instead of crossing
|
||||
# if next pulse start during pulse length. Though it almost doesn't
|
||||
# matter for pulses with 1-2us length.
|
||||
prev = k + STEPPER_PULSE_LINGTH_US
|
||||
prev = k + STEPPER_PULSE_LENGTH_US
|
||||
# instant run handling
|
||||
if not is_ran and instant:
|
||||
if k > 500000: # wait at least 500 ms is uploaded
|
||||
|
||||
@@ -20,7 +20,7 @@ class GPIO(object):
|
||||
"""
|
||||
self._mem = PhysicalMemory(PERI_BASE + GPIO_REGISTER_BASE)
|
||||
|
||||
def _pullupdn(self, pin, mode):
|
||||
def _pull_up_dn(self, pin, mode):
|
||||
p = self._mem.read_int(GPIO_PULLUPDN_OFFSET)
|
||||
p &= ~3
|
||||
if mode == self.MODE_INPUT_PULLUP:
|
||||
@@ -28,49 +28,49 @@ class GPIO(object):
|
||||
elif mode == self.MODE_INPUT_PULLDOWN:
|
||||
p |= 1
|
||||
self._mem.write_int(GPIO_PULLUPDN_OFFSET, p)
|
||||
addr = 4 * int(pin / 32) + GPIO_PULLUPDNCLK_OFFSET
|
||||
self._mem.write_int(addr, 1 << (pin % 32))
|
||||
address = 4 * int(pin / 32) + GPIO_PULLUPDNCLK_OFFSET
|
||||
self._mem.write_int(address, 1 << (pin % 32))
|
||||
p = self._mem.read_int(GPIO_PULLUPDN_OFFSET)
|
||||
p &= ~3
|
||||
self._mem.write_int(GPIO_PULLUPDN_OFFSET, p)
|
||||
self._mem.write_int(addr, 0)
|
||||
self._mem.write_int(address, 0)
|
||||
|
||||
def init(self, pin, mode):
|
||||
""" Initialize or re-initialize GPIO pin.
|
||||
:param pin: pin number.
|
||||
:param mode: one of MODE_* variables in this class.
|
||||
"""
|
||||
addr = 4 * int(pin / 10) + GPIO_FSEL_OFFSET
|
||||
v = self._mem.read_int(addr)
|
||||
address = 4 * int(pin / 10) + GPIO_FSEL_OFFSET
|
||||
v = self._mem.read_int(address)
|
||||
v &= ~(7 << ((pin % 10) * 3)) # input value
|
||||
if mode == self.MODE_OUTPUT:
|
||||
v |= (1 << ((pin % 10) * 3)) # output value, base on input
|
||||
self._mem.write_int(addr, v)
|
||||
self._mem.write_int(address, v)
|
||||
else:
|
||||
self._mem.write_int(addr, v)
|
||||
self._pullupdn(pin, mode)
|
||||
self._mem.write_int(address, v)
|
||||
self._pull_up_dn(pin, mode)
|
||||
|
||||
def set(self, pin):
|
||||
""" Set pin to HIGH state.
|
||||
:param pin: pin number.
|
||||
"""
|
||||
addr = 4 * int(pin / 32) + GPIO_SET_OFFSET
|
||||
self._mem.write_int(addr, 1 << (pin % 32))
|
||||
address = 4 * int(pin / 32) + GPIO_SET_OFFSET
|
||||
self._mem.write_int(address, 1 << (pin % 32))
|
||||
|
||||
def clear(self, pin):
|
||||
""" Set pin to LOW state.
|
||||
:param pin: pin number.
|
||||
"""
|
||||
addr = 4 * int(pin / 32) + GPIO_CLEAR_OFFSET
|
||||
self._mem.write_int(addr, 1 << (pin % 32))
|
||||
address = 4 * int(pin / 32) + GPIO_CLEAR_OFFSET
|
||||
self._mem.write_int(address, 1 << (pin % 32))
|
||||
|
||||
def read(self, pin):
|
||||
""" Read pin current value.
|
||||
:param pin: pin number.
|
||||
:return: integer value 0 or 1.
|
||||
"""
|
||||
addr = 4 * int(pin / 32) + GPIO_INPUT_OFFSET
|
||||
v = self._mem.read_int(addr)
|
||||
address = 4 * int(pin / 32) + GPIO_INPUT_OFFSET
|
||||
v = self._mem.read_int(address)
|
||||
v &= 1 << (pin % 32)
|
||||
if v == 0:
|
||||
return 0
|
||||
@@ -102,20 +102,20 @@ class DMAGPIO(DMAProto):
|
||||
self._clock = PhysicalMemory(PERI_BASE + CM_BASE)
|
||||
|
||||
# pre calculated variables for control blocks
|
||||
self._delay_info = DMA_TI_NO_WIDE_BURSTS | DMA_SRC_IGNORE \
|
||||
| DMA_TI_PER_MAP(DMA_TI_PER_MAP_PWM) \
|
||||
| DMA_TI_DEST_DREQ
|
||||
self._delay_info = (DMA_TI_NO_WIDE_BURSTS | DMA_SRC_IGNORE
|
||||
| DMA_TI_PER_MAP(DMA_TI_PER_MAP_PWM)
|
||||
| DMA_TI_DEST_DREQ)
|
||||
self._delay_destination = PHYSICAL_PWM_BUS + PWM_FIFO
|
||||
self._delay_stride = 0
|
||||
|
||||
self._pulse_info = DMA_TI_NO_WIDE_BURSTS | DMA_TI_TDMODE \
|
||||
| DMA_TI_WAIT_RESP
|
||||
self._pulse_info = (DMA_TI_NO_WIDE_BURSTS | DMA_TI_TDMODE
|
||||
| DMA_TI_WAIT_RESP)
|
||||
self._pulse_destination = PHYSICAL_GPIO_BUS + GPIO_SET_OFFSET
|
||||
# YLENGTH is transfers count and XLENGTH size of each transfer
|
||||
self._pulse_length = DMA_TI_TXFR_LEN_YLENGTH(2) \
|
||||
| DMA_TI_TXFR_LEN_XLENGTH(4)
|
||||
self._pulse_stride = DMA_TI_STRIDE_D_STRIDE(12) \
|
||||
| DMA_TI_STRIDE_S_STRIDE(4)
|
||||
self._pulse_length = (DMA_TI_TXFR_LEN_YLENGTH(2)
|
||||
| DMA_TI_TXFR_LEN_XLENGTH(4))
|
||||
self._pulse_stride = (DMA_TI_STRIDE_D_STRIDE(12)
|
||||
| DMA_TI_STRIDE_S_STRIDE(4))
|
||||
|
||||
def add_pulse(self, pins_mask, length_us):
|
||||
""" Add single pulse at the current position.
|
||||
@@ -126,9 +126,9 @@ class DMAGPIO(DMAProto):
|
||||
:param length_us: length in us.
|
||||
"""
|
||||
next_cb = self.__current_address + 3 * self._DMA_CONTROL_BLOCK_SIZE
|
||||
if next_cb > self._physmem.get_size():
|
||||
if next_cb > self._phys_memory.get_size():
|
||||
raise MemoryError("Out of allocated memory.")
|
||||
next3 = next_cb + self._physmem.get_bus_address()
|
||||
next3 = next_cb + self._phys_memory.get_bus_address()
|
||||
next2 = next3 - self._DMA_CONTROL_BLOCK_SIZE
|
||||
next1 = next2 - self._DMA_CONTROL_BLOCK_SIZE
|
||||
|
||||
@@ -137,16 +137,17 @@ class DMAGPIO(DMAProto):
|
||||
source3 = next3 - 8
|
||||
|
||||
data = (
|
||||
# control block 1 - set
|
||||
self._pulse_info, source1, self._pulse_destination,
|
||||
self._pulse_length,
|
||||
self._pulse_stride, next1, pins_mask, 0,
|
||||
self._pulse_length, self._pulse_stride, next1, pins_mask, 0,
|
||||
# control block 2 - delay
|
||||
self._delay_info, 0, self._delay_destination, length2,
|
||||
self._delay_stride, next2, 0, 0,
|
||||
# control block 3 - clear
|
||||
self._pulse_info, source3, self._pulse_destination,
|
||||
self._pulse_length,
|
||||
self._pulse_stride, next3, 0, pins_mask
|
||||
self._pulse_length, self._pulse_stride, next3, 0, pins_mask
|
||||
)
|
||||
self._physmem.write(self.__current_address, "24I", data)
|
||||
self._phys_memory.write(self.__current_address, "24I", data)
|
||||
self.__current_address = next_cb
|
||||
|
||||
def add_delay(self, delay_us):
|
||||
@@ -154,16 +155,16 @@ class DMAGPIO(DMAProto):
|
||||
:param delay_us: delay in us.
|
||||
"""
|
||||
next_cb = self.__current_address + self._DMA_CONTROL_BLOCK_SIZE
|
||||
if next_cb > self._physmem.get_size():
|
||||
if next_cb > self._phys_memory.get_size():
|
||||
raise MemoryError("Out of allocated memory.")
|
||||
next1 = self._physmem.get_bus_address() + next_cb
|
||||
next1 = self._phys_memory.get_bus_address() + next_cb
|
||||
source = next1 - 8 # last 8 bytes are padding, use it to store data
|
||||
length = delay_us << 4 # * 16
|
||||
data = (
|
||||
self._delay_info, source, self._delay_destination, length,
|
||||
self._delay_stride, next1, 0, 0
|
||||
)
|
||||
self._physmem.write(self.__current_address, "8I", data)
|
||||
self._phys_memory.write(self.__current_address, "8I", data)
|
||||
self.__current_address = next_cb
|
||||
|
||||
def add_set_clear(self, pins_to_set, pins_to_clear):
|
||||
@@ -172,23 +173,23 @@ class DMAGPIO(DMAProto):
|
||||
:param pins_to_clear: bitwise mask which pins should be clear.
|
||||
"""
|
||||
next_cb = self.__current_address + self._DMA_CONTROL_BLOCK_SIZE
|
||||
if next_cb > self._physmem.get_size():
|
||||
if next_cb > self._phys_memory.get_size():
|
||||
raise MemoryError("Out of allocated memory.")
|
||||
next1 = self._physmem.get_bus_address() + next_cb
|
||||
next1 = self._phys_memory.get_bus_address() + next_cb
|
||||
source = next1 - 8 # last 8 bytes are padding, use it to store data
|
||||
data = (
|
||||
self._pulse_info, source, self._pulse_destination,
|
||||
self._pulse_length,
|
||||
self._pulse_stride, next1, pins_to_set, pins_to_clear
|
||||
self._pulse_length, self._pulse_stride, next1,
|
||||
pins_to_set, pins_to_clear
|
||||
)
|
||||
self._physmem.write(self.__current_address, "8I", data)
|
||||
self._phys_memory.write(self.__current_address, "8I", data)
|
||||
self.__current_address = next_cb
|
||||
|
||||
def finalize_stream(self):
|
||||
""" Mark last added block as the last one.
|
||||
"""
|
||||
self._physmem.write_int(self.__current_address + 20
|
||||
- self._DMA_CONTROL_BLOCK_SIZE, 0)
|
||||
self._phys_memory.write_int(self.__current_address + 20
|
||||
- self._DMA_CONTROL_BLOCK_SIZE, 0)
|
||||
logging.info("DMA took {}MB of memory".
|
||||
format(round(self.__current_address / 1024.0 / 1024.0, 2)))
|
||||
|
||||
@@ -201,9 +202,10 @@ class DMAGPIO(DMAProto):
|
||||
self._clock.write_int(CM_PWM_CNTL, CM_PASSWORD | CM_SRC_PLLD) # disable
|
||||
while (self._clock.read_int(CM_PWM_CNTL) & CM_CNTL_BUSY) != 0:
|
||||
time.sleep(0.00001) # 10 us, wait until BUSY bit is clear
|
||||
self._clock.write_int(CM_PWM_DIV, CM_PASSWORD | CM_DIV_VALUE(5)) # 100MHz
|
||||
self._clock.write_int(CM_PWM_CNTL, CM_PASSWORD | CM_SRC_PLLD |
|
||||
CM_CNTL_ENABLE)
|
||||
self._clock.write_int(CM_PWM_DIV,
|
||||
CM_PASSWORD | CM_DIV_VALUE(5)) # 100MHz
|
||||
self._clock.write_int(CM_PWM_CNTL,
|
||||
CM_PASSWORD | CM_SRC_PLLD | CM_CNTL_ENABLE)
|
||||
|
||||
self._pwm.write_int(PWM_RNG1, 100)
|
||||
self._pwm.write_int(PWM_DMAC, PWM_DMAC_ENAB
|
||||
@@ -220,9 +222,9 @@ class DMAGPIO(DMAProto):
|
||||
raise RuntimeError("Nothing was added.")
|
||||
# fix 'next' field in previous control block
|
||||
if loop:
|
||||
self._physmem.write_int(self.__current_address + 20
|
||||
- self._DMA_CONTROL_BLOCK_SIZE,
|
||||
self._physmem.get_bus_address())
|
||||
self._phys_memory.write_int(self.__current_address + 20
|
||||
- self._DMA_CONTROL_BLOCK_SIZE,
|
||||
self._phys_memory.get_bus_address())
|
||||
else:
|
||||
self.finalize_stream()
|
||||
self.run_stream()
|
||||
@@ -267,17 +269,17 @@ class DMAPWM(DMAProto):
|
||||
self.__add_control_block(i * self._DMA_CONTROL_BLOCK_SIZE,
|
||||
GPIO_CLEAR_OFFSET)
|
||||
# loop
|
||||
self._physmem.write_int((self._TOTAL_NUMBER_OF_BLOCKS - 1)
|
||||
* self._DMA_CONTROL_BLOCK_SIZE + 20,
|
||||
self._physmem.get_bus_address())
|
||||
self._phys_memory.write_int((self._TOTAL_NUMBER_OF_BLOCKS - 1)
|
||||
* self._DMA_CONTROL_BLOCK_SIZE + 20,
|
||||
self._phys_memory.get_bus_address())
|
||||
self._gpio = PhysicalMemory(PERI_BASE + GPIO_REGISTER_BASE)
|
||||
|
||||
def __add_control_block(self, address, offset):
|
||||
ba = self._physmem.get_bus_address() + address
|
||||
ba = self._phys_memory.get_bus_address() + address
|
||||
data = (
|
||||
DMA_TI_NO_WIDE_BURSTS | DMA_TI_WAIT_RESP
|
||||
| DMA_TI_DEST_INC | DMA_TI_SRC_INC, # info
|
||||
ba + self._DMA_DATA_OFFSET, # source, last 8 bytes are padding, use it to store data
|
||||
ba + self._DMA_DATA_OFFSET, # source, use padding for storing data
|
||||
PHYSICAL_GPIO_BUS + offset, # destination
|
||||
4, # length
|
||||
0, # stride
|
||||
@@ -285,7 +287,7 @@ class DMAPWM(DMAProto):
|
||||
0, # padding, uses as data storage
|
||||
0 # padding
|
||||
)
|
||||
self._physmem.write(address, "8I", data)
|
||||
self._phys_memory.write(address, "8I", data)
|
||||
|
||||
def add_pin(self, pin, duty_cycle):
|
||||
""" Add pin to PMW with specified duty cycle.
|
||||
@@ -302,14 +304,14 @@ class DMAPWM(DMAProto):
|
||||
self._gpio.write_int(GPIO_SET_OFFSET, 1 << pin)
|
||||
self._clear_pins[pin] = self._DMA_DATA_OFFSET
|
||||
else:
|
||||
value = self._physmem.read_int(self._DMA_DATA_OFFSET)
|
||||
value = self._phys_memory.read_int(self._DMA_DATA_OFFSET)
|
||||
value |= 1 << pin
|
||||
self._physmem.write_int(self._DMA_DATA_OFFSET, value)
|
||||
clear_address = block_number * self._DMA_CONTROL_BLOCK_SIZE \
|
||||
+ self._DMA_DATA_OFFSET
|
||||
value = self._physmem.read_int(clear_address)
|
||||
self._phys_memory.write_int(self._DMA_DATA_OFFSET, value)
|
||||
clear_address = (block_number * self._DMA_CONTROL_BLOCK_SIZE
|
||||
+ self._DMA_DATA_OFFSET)
|
||||
value = self._phys_memory.read_int(clear_address)
|
||||
value |= 1 << pin
|
||||
self._physmem.write_int(clear_address, value)
|
||||
self._phys_memory.write_int(clear_address, value)
|
||||
self._clear_pins[pin] = clear_address
|
||||
if not self.is_active():
|
||||
super(DMAPWM, self)._run_dma()
|
||||
@@ -321,12 +323,12 @@ class DMAPWM(DMAProto):
|
||||
assert 0 <= pin < 32
|
||||
if pin in self._clear_pins.keys():
|
||||
address = self._clear_pins[pin]
|
||||
value = self._physmem.read_int(address)
|
||||
value = self._phys_memory.read_int(address)
|
||||
value &= ~(1 << pin)
|
||||
self._physmem.write_int(address, value)
|
||||
value = self._physmem.read_int(self._DMA_DATA_OFFSET)
|
||||
self._phys_memory.write_int(address, value)
|
||||
value = self._phys_memory.read_int(self._DMA_DATA_OFFSET)
|
||||
value &= ~(1 << pin)
|
||||
self._physmem.write_int(self._DMA_DATA_OFFSET, value)
|
||||
self._phys_memory.write_int(self._DMA_DATA_OFFSET, value)
|
||||
del self._clear_pins[pin]
|
||||
self._gpio.write_int(GPIO_CLEAR_OFFSET, 1 << pin)
|
||||
if len(self._clear_pins) == 0 and self.is_active():
|
||||
|
||||
@@ -57,32 +57,18 @@ DMA_CS_END = 1 << 1
|
||||
DMA_CS_ACTIVE = 1 << 0
|
||||
DMA_TI_PER_MAP_PWM = 5
|
||||
DMA_TI_PER_MAP_PCM = 2
|
||||
|
||||
def DMA_TI_PER_MAP(x):
|
||||
return x << 16
|
||||
|
||||
def DMA_TI_TXFR_LEN_YLENGTH(y):
|
||||
return (y & 0x3fff) << 16
|
||||
|
||||
def DMA_TI_TXFR_LEN_XLENGTH(x):
|
||||
return x & 0xffff
|
||||
|
||||
def DMA_TI_STRIDE_D_STRIDE(x):
|
||||
return (x & 0xffff) << 16
|
||||
|
||||
def DMA_TI_STRIDE_S_STRIDE(x):
|
||||
return x & 0xffff
|
||||
|
||||
def DMA_CS_PRIORITY(x):
|
||||
return (x & 0xf) << 16
|
||||
|
||||
def DMA_CS_PANIC_PRIORITY(x):
|
||||
return (x & 0xf) << 20
|
||||
DMA_TI_PER_MAP = (lambda x: x << 16)
|
||||
DMA_TI_TXFR_LEN_YLENGTH = (lambda y: (y & 0x3fff) << 16)
|
||||
DMA_TI_TXFR_LEN_XLENGTH = (lambda x: x & 0xffff)
|
||||
DMA_TI_STRIDE_D_STRIDE = (lambda x: (x & 0xffff) << 16)
|
||||
DMA_TI_STRIDE_S_STRIDE = (lambda x: x & 0xffff)
|
||||
DMA_CS_PRIORITY = (lambda x: (x & 0xf) << 16)
|
||||
DMA_CS_PANIC_PRIORITY = (lambda x: (x & 0xf) << 20)
|
||||
|
||||
# hardware PWM controller registers
|
||||
PWM_BASE = 0x0020C000
|
||||
PHYSICAL_PWM_BUS = 0x7E000000 + PWM_BASE
|
||||
PWM_CTL= 0x00
|
||||
PWM_CTL = 0x00
|
||||
PWM_DMAC = 0x08
|
||||
PWM_RNG1 = 0x10
|
||||
PWM_RNG2 = 0x20
|
||||
@@ -95,12 +81,8 @@ PWM_CTL_CLRF = 1 << 6
|
||||
PWM_CTL_USEF1 = 1 << 5
|
||||
PWM_CTL_USEF2 = 1 << 13
|
||||
PWM_DMAC_ENAB = 1 << 31
|
||||
|
||||
def PWM_DMAC_PANIC(x):
|
||||
return x << 8
|
||||
|
||||
def PWM_DMAC_DREQ(x):
|
||||
return x
|
||||
PWM_DMAC_PANIC = (lambda x: x << 8)
|
||||
PWM_DMAC_DREQ = (lambda x: x)
|
||||
|
||||
# clock manager module
|
||||
CM_BASE = 0x00101000
|
||||
@@ -113,14 +95,13 @@ CM_CNTL_ENABLE = 1 << 4
|
||||
CM_CNTL_BUSY = 1 << 7
|
||||
CM_SRC_OSC = 1 # 19.2 MHz
|
||||
CM_SRC_PLLC = 5 # 1000 MHz
|
||||
CM_SRC_PLLD = 6 # 500 MHz
|
||||
CM_SRC_HDMI = 7 # 216 MHz
|
||||
|
||||
def CM_DIV_VALUE(x):
|
||||
return x << 12
|
||||
CM_SRC_PLLD = 6 # 500 MHz
|
||||
CM_SRC_HDMI = 7 # 216 MHz
|
||||
CM_DIV_VALUE = (lambda x: x << 12)
|
||||
|
||||
|
||||
class PhysicalMemory(object):
|
||||
# noinspection PyArgumentList,PyArgumentList
|
||||
def __init__(self, phys_address, size=PAGE_SIZE):
|
||||
""" Create object which maps physical memory to Python's mmap object.
|
||||
:param phys_address: based address of physical memory
|
||||
@@ -128,14 +109,14 @@ class PhysicalMemory(object):
|
||||
self._size = size
|
||||
phys_address -= phys_address % PAGE_SIZE
|
||||
fd = self._open_dev("/dev/mem")
|
||||
self._rmap = mmap.mmap(fd, size, flags=mmap.MAP_SHARED,
|
||||
prot=mmap.PROT_READ | mmap.PROT_WRITE,
|
||||
offset=phys_address)
|
||||
self._memmap = mmap.mmap(fd, size, flags=mmap.MAP_SHARED,
|
||||
prot=mmap.PROT_READ | mmap.PROT_WRITE,
|
||||
offset=phys_address)
|
||||
self._close_dev(fd)
|
||||
atexit.register(self.cleanup)
|
||||
|
||||
def cleanup(self):
|
||||
self._rmap.close()
|
||||
self._memmap.close()
|
||||
|
||||
@staticmethod
|
||||
def _open_dev(name):
|
||||
@@ -149,13 +130,13 @@ class PhysicalMemory(object):
|
||||
os.close(fd)
|
||||
|
||||
def write_int(self, address, int_value):
|
||||
ctypes.c_uint32.from_buffer(self._rmap, address).value = int_value
|
||||
ctypes.c_uint32.from_buffer(self._memmap, address).value = int_value
|
||||
|
||||
def write(self, address, fmt, data):
|
||||
struct.pack_into(fmt, self._rmap, address, *data)
|
||||
struct.pack_into(fmt, self._memmap, address, *data)
|
||||
|
||||
def read_int(self, address):
|
||||
return ctypes.c_uint32.from_buffer(self._rmap, address).value
|
||||
return ctypes.c_uint32.from_buffer(self._memmap, address).value
|
||||
|
||||
def get_size(self):
|
||||
return self._size
|
||||
@@ -177,8 +158,8 @@ class CMAPhysicalMemory(PhysicalMemory):
|
||||
if self._handle == 0:
|
||||
raise OSError("No memory to allocate with /dev/vcio")
|
||||
# lock memory
|
||||
self._busmem = self._send_data(0x3000d, [self._handle])
|
||||
if self._busmem == 0:
|
||||
self._bus_memory = self._send_data(0x3000d, [self._handle])
|
||||
if self._bus_memory == 0:
|
||||
# memory should be freed in __del__
|
||||
raise OSError("Failed to lock memory with /dev/vcio")
|
||||
# print("allocate {} at {} (bus {})".format(size,
|
||||
@@ -206,10 +187,10 @@ class CMAPhysicalMemory(PhysicalMemory):
|
||||
return data[5]
|
||||
|
||||
def get_bus_address(self):
|
||||
return self._busmem
|
||||
return self._bus_memory
|
||||
|
||||
def get_phys_address(self):
|
||||
return self._busmem & ~0xc0000000
|
||||
return self._bus_memory & ~0xc0000000
|
||||
|
||||
|
||||
class DMAProto(object):
|
||||
@@ -219,7 +200,7 @@ class DMAProto(object):
|
||||
"""
|
||||
self._DMA_CHANNEL = dma_channel
|
||||
# allocate buffer for control blocks
|
||||
self._physmem = CMAPhysicalMemory(memory_size)
|
||||
self._phys_memory = CMAPhysicalMemory(memory_size)
|
||||
# prepare dma registers memory map
|
||||
self._dma = PhysicalMemory(PERI_BASE + DMA_BASE)
|
||||
|
||||
@@ -228,7 +209,8 @@ class DMAProto(object):
|
||||
"""
|
||||
address = 0x100 * self._DMA_CHANNEL
|
||||
self._dma.write_int(address + DMA_CS, DMA_CS_END)
|
||||
self._dma.write_int(address + DMA_CONBLK_AD, self._physmem.get_bus_address())
|
||||
self._dma.write_int(address + DMA_CONBLK_AD,
|
||||
self._phys_memory.get_bus_address())
|
||||
cs = DMA_CS_PRIORITY(7) | DMA_CS_PANIC_PRIORITY(7) | DMA_CS_DISDEBUG
|
||||
self._dma.write_int(address + DMA_CS, cs)
|
||||
cs |= DMA_CS_ACTIVE
|
||||
|
||||
@@ -1,10 +1,8 @@
|
||||
from __future__ import division
|
||||
import logging
|
||||
import time
|
||||
|
||||
from cnc.pulses import PulseGeneratorLinear, PulseGeneratorCircular
|
||||
from cnc.pulses import *
|
||||
from cnc.config import *
|
||||
from cnc.coordinates import Coordinates
|
||||
|
||||
""" This is virtual device class which is very useful for debugging.
|
||||
It checks PulseGenerator with some tests.
|
||||
@@ -25,6 +23,7 @@ def spindle_control(percent):
|
||||
logging.info("spindle control: {}%".format(percent))
|
||||
|
||||
|
||||
# noinspection PyUnusedLocal
|
||||
def move(generator):
|
||||
""" Move head to specified position.
|
||||
:param generator: PulseGenerator object.
|
||||
@@ -35,28 +34,28 @@ def move(generator):
|
||||
dx, dy, dz, de = 0, 0, 0, 0
|
||||
mx, my, mz, me = 0, 0, 0, 0
|
||||
cx, cy, cz, ce = 0, 0, 0, 0
|
||||
dirx, diry, dirz, dire = 1, 1, 1, 1
|
||||
direction_x, direction_y, direction_z, dire = 1, 1, 1, 1
|
||||
st = time.time()
|
||||
direction_found = False
|
||||
for dir, tx, ty, tz, te in generator:
|
||||
if dir:
|
||||
for direction, tx, ty, tz, te in generator:
|
||||
if direction:
|
||||
direction_found = True
|
||||
dirx, diry, dirz, dire = tx, ty, tz, te
|
||||
direction_x, direction_y, direction_z, dire = tx, ty, tz, te
|
||||
if isinstance(generator, PulseGeneratorLinear):
|
||||
assert (tx < 0 and delta.x < 0) or (tx > 0 and delta.x > 0) \
|
||||
or delta.x == 0
|
||||
assert (ty < 0 and delta.y < 0) or (ty > 0 and delta.y > 0) \
|
||||
or delta.y == 0
|
||||
assert (tz < 0 and delta.z < 0) or (tz > 0 and delta.z > 0) \
|
||||
or delta.z == 0
|
||||
assert (te < 0 and delta.e < 0) or (te > 0 and delta.e > 0) \
|
||||
or delta.e == 0
|
||||
assert ((tx < 0 and delta.x < 0) or (tx > 0 and delta.x > 0)
|
||||
or delta.x == 0)
|
||||
assert ((ty < 0 and delta.y < 0) or (ty > 0 and delta.y > 0)
|
||||
or delta.y == 0)
|
||||
assert ((tz < 0 and delta.z < 0) or (tz > 0 and delta.z > 0)
|
||||
or delta.z == 0)
|
||||
assert ((te < 0 and delta.e < 0) or (te > 0 and delta.e > 0)
|
||||
or delta.e == 0)
|
||||
continue
|
||||
if tx is not None:
|
||||
if tx > mx:
|
||||
mx = tx
|
||||
tx = int(round(tx * 1000000))
|
||||
ix += dirx
|
||||
ix += direction_x
|
||||
cx += 1
|
||||
if lx is not None:
|
||||
dx = tx - lx
|
||||
@@ -68,7 +67,7 @@ def move(generator):
|
||||
if ty > my:
|
||||
my = ty
|
||||
ty = int(round(ty * 1000000))
|
||||
iy += diry
|
||||
iy += direction_y
|
||||
cy += 1
|
||||
if ly is not None:
|
||||
dy = ty - ly
|
||||
@@ -80,7 +79,7 @@ def move(generator):
|
||||
if tz > mz:
|
||||
mz = tz
|
||||
tz = int(round(tz * 1000000))
|
||||
iz += dirz
|
||||
iz += direction_z
|
||||
cz += 1
|
||||
if lz is not None:
|
||||
dz = tz - lz
|
||||
@@ -101,7 +100,8 @@ def move(generator):
|
||||
else:
|
||||
de = None
|
||||
# very verbose, uncomment on demand
|
||||
# logging.debug("Iteration {} is {} {} {} {}".format(max(ix, iy, iz, ie), tx, ty, tz, te))
|
||||
# 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()
|
||||
@@ -110,10 +110,11 @@ def move(generator):
|
||||
assert iy / STEPPER_PULSES_PER_MM_Y == delta.y, "y wrong number of pulses"
|
||||
assert iz / STEPPER_PULSES_PER_MM_Z == delta.z, "z wrong number of pulses"
|
||||
assert ie / STEPPER_PULSES_PER_MM_E == delta.e, "e wrong number of pulses"
|
||||
assert max(mx, my, mz, me) <= generator.total_time_s(), "interpolation time or pulses wrong"
|
||||
assert max(mx, my, mz, me) <= generator.total_time_s(), \
|
||||
"interpolation time or pulses wrong"
|
||||
logging.debug("Moved {}, {}, {}, {} 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")
|
||||
logging.info("prepared in " + str(round(pt - st, 2)) + "s, estimated "
|
||||
+ str(round(generator.total_time_s(), 2)) + "s")
|
||||
|
||||
|
||||
def join():
|
||||
|
||||
12
cnc/logging_config.py
Normal file
12
cnc/logging_config.py
Normal file
@@ -0,0 +1,12 @@
|
||||
import logging
|
||||
|
||||
logging.basicConfig(level=logging.CRITICAL,
|
||||
format='[%(levelname)s] %(message)s')
|
||||
|
||||
|
||||
def debug_enable():
|
||||
logging.getLogger().setLevel(logging.DEBUG)
|
||||
|
||||
|
||||
def debug_disable():
|
||||
logging.getLogger().setLevel(logging.CRITICAL)
|
||||
21
cnc/main.py
21
cnc/main.py
@@ -1,19 +1,29 @@
|
||||
#!/usr/bin/env python
|
||||
|
||||
import os
|
||||
import sys
|
||||
import readline
|
||||
import logging
|
||||
logging.basicConfig(level=logging.CRITICAL,
|
||||
format='[%(levelname)s] %(message)s')
|
||||
import atexit
|
||||
|
||||
import cnc.logging_config as logging_config
|
||||
from cnc.gcode import GCode, GCodeException
|
||||
from cnc.gmachine import GMachine, GMachineException
|
||||
|
||||
try: # python3 compatibility
|
||||
try: # python3 compatibility
|
||||
type(raw_input)
|
||||
except NameError:
|
||||
# noinspection PyShadowingBuiltins
|
||||
raw_input = input
|
||||
|
||||
# configure history file for interactive mode
|
||||
history_file = os.path.join(os.environ['HOME'], '.pycnc_history')
|
||||
try:
|
||||
readline.read_history_file(history_file)
|
||||
except IOError:
|
||||
pass
|
||||
readline.set_history_length(1000)
|
||||
atexit.register(readline.write_history_file, history_file)
|
||||
|
||||
machine = GMachine()
|
||||
|
||||
|
||||
@@ -29,6 +39,7 @@ def do_line(line):
|
||||
|
||||
|
||||
def main():
|
||||
logging_config.debug_disable()
|
||||
try:
|
||||
if len(sys.argv) > 1:
|
||||
# Read file with gcode
|
||||
@@ -53,6 +64,6 @@ def main():
|
||||
print("\r\nExiting...")
|
||||
machine.release()
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
|
||||
|
||||
205
cnc/pulses.py
205
cnc/pulses.py
@@ -1,10 +1,9 @@
|
||||
from __future__ import division
|
||||
import math
|
||||
import logging
|
||||
|
||||
from cnc.config import *
|
||||
from cnc.coordinates import *
|
||||
from cnc.enums import *
|
||||
from cnc.coordinates import Coordinates
|
||||
|
||||
SECONDS_IN_MINUTE = 60.0
|
||||
|
||||
@@ -33,8 +32,8 @@ class PulseGenerator(object):
|
||||
"""
|
||||
|
||||
def __init__(self, delta):
|
||||
""" Create object. Do not create directly this object, inherit this class
|
||||
and implement interpolation function and related methods.
|
||||
""" Create object. Do not create directly this object, inherit this
|
||||
class and implement interpolation function and related methods.
|
||||
All child have to call this method ( super().__init__() ).
|
||||
:param delta: overall movement delta in mm, uses for debug purpose.
|
||||
"""
|
||||
@@ -84,11 +83,11 @@ class PulseGenerator(object):
|
||||
""" Get iterator.
|
||||
:return: iterable object.
|
||||
"""
|
||||
self._acceleration_time_s, self._linear_time_s, \
|
||||
max_axis_velocity_mm_per_sec = self._get_movement_parameters()
|
||||
(self._acceleration_time_s, self._linear_time_s,
|
||||
max_axis_velocity_mm_per_sec) = self._get_movement_parameters()
|
||||
# helper variable
|
||||
self._2Vmax_per_a = 2.0 * max_axis_velocity_mm_per_sec \
|
||||
/ STEPPER_MAX_ACCELERATION_MM_PER_S2
|
||||
self._2Vmax_per_a = (2.0 * max_axis_velocity_mm_per_sec
|
||||
/ STEPPER_MAX_ACCELERATION_MM_PER_S2)
|
||||
self._iteration_x = 0
|
||||
self._iteration_y = 0
|
||||
self._iteration_z = 0
|
||||
@@ -147,14 +146,13 @@ class PulseGenerator(object):
|
||||
not be earlier in time then current. If there is no pulses
|
||||
left StopIteration will be raised.
|
||||
"""
|
||||
dir, (tx, ty, tz, te) = self._interpolation_function(self._iteration_x,
|
||||
self._iteration_y,
|
||||
self._iteration_z,
|
||||
self._iteration_e)
|
||||
direction, (tx, ty, tz, te) = \
|
||||
self._interpolation_function(self._iteration_x, self._iteration_y,
|
||||
self._iteration_z, self._iteration_e)
|
||||
# check if direction update:
|
||||
if dir != self._iteration_direction:
|
||||
self._iteration_direction = dir
|
||||
return (True,) + dir
|
||||
if direction != self._iteration_direction:
|
||||
self._iteration_direction = direction
|
||||
return (True,) + direction
|
||||
# check condition to stop
|
||||
if tx is None and ty is None and tz is None and te is None:
|
||||
raise StopIteration
|
||||
@@ -215,45 +213,47 @@ class PulseGeneratorLinear(PulseGenerator):
|
||||
"""
|
||||
super(PulseGeneratorLinear, self).__init__(delta_mm)
|
||||
# this class doesn't care about direction
|
||||
self._distance_mm = abs(delta_mm)
|
||||
self._distance_mm = abs(delta_mm) # type: Coordinates
|
||||
# velocity of each axis
|
||||
distance_total_mm = self._distance_mm.length()
|
||||
self.max_velocity_mm_per_sec = self._distance_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
|
||||
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_total_mm:
|
||||
self.acceleration_time_s = math.sqrt(distance_total_mm /
|
||||
STEPPER_MAX_ACCELERATION_MM_PER_S2)
|
||||
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
|
||||
self.max_velocity_mm_per_sec = (self._distance_mm
|
||||
/ self.acceleration_time_s)
|
||||
else:
|
||||
# calculate linear time
|
||||
linear_distance_mm = distance_total_mm \
|
||||
- self.acceleration_time_s ** 2 \
|
||||
* STEPPER_MAX_ACCELERATION_MM_PER_S2
|
||||
self.linear_time_s = linear_distance_mm \
|
||||
/ self.max_velocity_mm_per_sec.length()
|
||||
self._direction = math.copysign(1, delta_mm.x), \
|
||||
math.copysign(1, delta_mm.y), \
|
||||
math.copysign(1, delta_mm.z), \
|
||||
math.copysign(1, delta_mm.e)
|
||||
self.linear_time_s = (linear_distance_mm
|
||||
/ self.max_velocity_mm_per_sec.length())
|
||||
self._direction = (math.copysign(1, delta_mm.x),
|
||||
math.copysign(1, delta_mm.y),
|
||||
math.copysign(1, delta_mm.z),
|
||||
math.copysign(1, delta_mm.e))
|
||||
|
||||
def _get_movement_parameters(self):
|
||||
""" Return movement parameters, see super class for details.
|
||||
"""
|
||||
return self.acceleration_time_s, \
|
||||
self.linear_time_s, \
|
||||
self.max_velocity_mm_per_sec.find_max()
|
||||
return (self.acceleration_time_s,
|
||||
self.linear_time_s,
|
||||
self.max_velocity_mm_per_sec.find_max())
|
||||
|
||||
def __linear(self, position_mm, distance_mm, velocity_mm_per_sec):
|
||||
@staticmethod
|
||||
def __linear(position_mm, distance_mm, velocity_mm_per_sec):
|
||||
""" Helper function for linear movement.
|
||||
"""
|
||||
# check if need to calculate for this axis
|
||||
@@ -326,16 +326,18 @@ class PulseGeneratorCircular(PulseGenerator):
|
||||
eb = sb + delta.x
|
||||
apm = STEPPER_PULSES_PER_MM_Z
|
||||
bpm = STEPPER_PULSES_PER_MM_X
|
||||
else:
|
||||
raise ValueError("Unknown plane")
|
||||
# adjust radius to fit into axises step.
|
||||
radius = round(math.sqrt(sa * sa + sb * sb) * min(apm, bpm)) \
|
||||
/ min(apm, bpm)
|
||||
radius = (round(math.sqrt(sa * sa + sb * sb) * min(apm, bpm))
|
||||
/ min(apm, bpm))
|
||||
self._radius2 = radius * radius
|
||||
self._radius_a_pulses = int(radius * apm)
|
||||
self._radius_b_pulses = int(radius * bpm)
|
||||
self._start_a_pulses = int(sa * apm)
|
||||
self._start_b_pulses = int(sb * bpm)
|
||||
assert round(math.sqrt(ea * ea + eb * eb) * min(apm, bpm)) \
|
||||
/ min(apm, bpm) == radius, "Wrong end point"
|
||||
assert (round(math.sqrt(ea * ea + eb * eb) * min(apm, bpm))
|
||||
/ min(apm, bpm) == radius), "Wrong end point"
|
||||
|
||||
# Calculate angles and directions.
|
||||
start_angle = self.__angle(sa, sb)
|
||||
@@ -355,7 +357,7 @@ class PulseGeneratorCircular(PulseGenerator):
|
||||
else:
|
||||
self._dir_a = 1
|
||||
elif direction == CCW:
|
||||
if 0 < start_angle <= math.pi:
|
||||
if 0.0 < start_angle <= math.pi:
|
||||
self._dir_b = 1
|
||||
else:
|
||||
self._dir_b = -1
|
||||
@@ -363,8 +365,10 @@ class PulseGeneratorCircular(PulseGenerator):
|
||||
self._dir_a = -1
|
||||
else:
|
||||
self._dir_a = 1
|
||||
self._side_a = self._start_b_pulses < 0 or (self._start_b_pulses == 0 and self._dir_b < 0)
|
||||
self._side_b = self._start_a_pulses < 0 or (self._start_a_pulses == 0 and self._dir_a < 0)
|
||||
self._side_a = (self._start_b_pulses < 0
|
||||
or (self._start_b_pulses == 0 and self._dir_b < 0))
|
||||
self._side_b = (self._start_a_pulses < 0
|
||||
or (self._start_a_pulses == 0 and self._dir_a < 0))
|
||||
self._start_angle = start_angle
|
||||
logging.debug("start angle {}, end angle {}, delta {}".format(
|
||||
start_angle * 180.0 / math.pi,
|
||||
@@ -381,38 +385,42 @@ class PulseGeneratorCircular(PulseGenerator):
|
||||
end_angle_m = end_angle
|
||||
if start_angle >= end_angle:
|
||||
end_angle_m += 2 * math.pi
|
||||
rstart = int(start_angle / (math.pi / 2.0))
|
||||
rend = int(end_angle_m / (math.pi / 2.0))
|
||||
if rend - rstart >= 4:
|
||||
quarter_start = int(start_angle / (math.pi / 2.0))
|
||||
quarter_end = int(end_angle_m / (math.pi / 2.0))
|
||||
if quarter_end - quarter_start >= 4:
|
||||
self._iterations_a = 4 * int(radius * apm)
|
||||
self._iterations_b = 4 * int(radius * apm)
|
||||
else:
|
||||
if rstart == rend:
|
||||
if quarter_start == quarter_end:
|
||||
self._iterations_a = int(abs(sa - ea) * apm)
|
||||
self._iterations_b = int(abs(sb - eb) * bpm)
|
||||
else:
|
||||
for r in range(rstart, rend + 1):
|
||||
for r in range(quarter_start, quarter_end + 1):
|
||||
i = r
|
||||
if i >= 4:
|
||||
i -= 4
|
||||
if r == rstart:
|
||||
if r == quarter_start:
|
||||
if i == 0 or i == 2:
|
||||
self._iterations_a += int(radius * apm) - int(abs(sa) * apm)
|
||||
self._iterations_a += int(radius * apm) \
|
||||
- int(abs(sa) * apm)
|
||||
else:
|
||||
self._iterations_a += int(abs(sa) * apm)
|
||||
if i == 1 or i == 3:
|
||||
self._iterations_b += int(radius * bpm) - int(abs(sb) * bpm)
|
||||
self._iterations_b += int(radius * bpm) \
|
||||
- int(abs(sb) * bpm)
|
||||
else:
|
||||
self._iterations_b += int(abs(sb) * bpm)
|
||||
elif r == rend:
|
||||
elif r == quarter_end:
|
||||
if i == 0 or i == 2:
|
||||
self._iterations_a += int(abs(ea) * apm)
|
||||
else:
|
||||
self._iterations_a += int(radius * apm) - int(abs(ea) * apm)
|
||||
self._iterations_a += int(radius * apm) \
|
||||
- int(abs(ea) * apm)
|
||||
if i == 1 or i == 3:
|
||||
self._iterations_b += int(abs(eb) * bpm)
|
||||
else:
|
||||
self._iterations_b += int(radius * bpm) - int(abs(eb) * bpm)
|
||||
self._iterations_b += int(radius * bpm) \
|
||||
- int(abs(eb) * bpm)
|
||||
else:
|
||||
self._iterations_a += int(radius * apm)
|
||||
self._iterations_b += int(radius * bpm)
|
||||
@@ -437,20 +445,22 @@ class PulseGeneratorCircular(PulseGenerator):
|
||||
l = math.sqrt(arc * arc + delta.y * delta.y + e2)
|
||||
self._velocity_3rd = abs(delta.y) / l * velocity
|
||||
self._third_dir = math.copysign(1, delta.y)
|
||||
else:
|
||||
raise ValueError("Unknown plane")
|
||||
self._iterations_e = abs(delta.e) * STEPPER_PULSES_PER_MM_E
|
||||
# Velocity splits with corresponding distance.
|
||||
cV = arc / l * velocity
|
||||
self._RdivV = radius / cV
|
||||
circular_velocity = arc / l * velocity
|
||||
self._r_div_v = radius / circular_velocity
|
||||
self._e_velocity = abs(delta.e) / l * velocity
|
||||
self._e_dir = math.copysign(1, delta.e)
|
||||
self.max_velocity_mm_per_sec = max(cV, self._velocity_3rd,
|
||||
self._e_velocity)
|
||||
self.acceleration_time_s = self.max_velocity_mm_per_sec \
|
||||
/ STEPPER_MAX_ACCELERATION_MM_PER_S2
|
||||
self.max_velocity_mm_per_sec = max(circular_velocity,
|
||||
self._velocity_3rd, self._e_velocity)
|
||||
self.acceleration_time_s = (self.max_velocity_mm_per_sec
|
||||
/ STEPPER_MAX_ACCELERATION_MM_PER_S2)
|
||||
if STEPPER_MAX_ACCELERATION_MM_PER_S2 * self.acceleration_time_s ** 2 \
|
||||
> l:
|
||||
self.acceleration_time_s = math.sqrt(l /
|
||||
STEPPER_MAX_ACCELERATION_MM_PER_S2)
|
||||
self.acceleration_time_s = \
|
||||
math.sqrt(l / STEPPER_MAX_ACCELERATION_MM_PER_S2)
|
||||
self.linear_time_s = 0.0
|
||||
self.max_velocity_mm_per_sec = l / self.acceleration_time_s
|
||||
else:
|
||||
@@ -458,7 +468,8 @@ class PulseGeneratorCircular(PulseGenerator):
|
||||
* STEPPER_MAX_ACCELERATION_MM_PER_S2
|
||||
self.linear_time_s = linear_distance_mm / velocity
|
||||
|
||||
def __angle(self, a, b):
|
||||
@staticmethod
|
||||
def __angle(a, b):
|
||||
# Calculate angle of entry point (a, b) of circle with center in (0,0)
|
||||
angle = math.acos(b / math.sqrt(a * a + b * b))
|
||||
if a < 0:
|
||||
@@ -468,27 +479,28 @@ class PulseGeneratorCircular(PulseGenerator):
|
||||
def _get_movement_parameters(self):
|
||||
""" Return movement parameters, see super class for details.
|
||||
"""
|
||||
return self.acceleration_time_s, \
|
||||
self.linear_time_s, \
|
||||
self.max_velocity_mm_per_sec
|
||||
return (self.acceleration_time_s,
|
||||
self.linear_time_s,
|
||||
self.max_velocity_mm_per_sec)
|
||||
|
||||
def __circularHelper(self, start, i, radius, side, dir):
|
||||
np = start + dir * i
|
||||
@staticmethod
|
||||
def __circular_helper(start, i, radius, side, direction):
|
||||
np = start + direction * i
|
||||
if np > radius:
|
||||
np -= 2 * (np - radius)
|
||||
dir = -dir
|
||||
direction = -direction
|
||||
side = not side
|
||||
if np < -radius:
|
||||
np -= 2 * (np + radius)
|
||||
dir = -dir
|
||||
direction = -direction
|
||||
side = not side
|
||||
if np > radius:
|
||||
np -= 2 * (np - radius)
|
||||
dir = -dir
|
||||
direction = -direction
|
||||
side = not side
|
||||
return np, dir, side
|
||||
return np, direction, side
|
||||
|
||||
def __circularFindTime(self, a, b):
|
||||
def __circular_find_time(self, a, b):
|
||||
angle = self.__angle(a, b)
|
||||
if self._direction == CW:
|
||||
delta_angle = angle - self._start_angle
|
||||
@@ -496,39 +508,40 @@ class PulseGeneratorCircular(PulseGenerator):
|
||||
delta_angle = self._start_angle - angle
|
||||
if delta_angle <= 0:
|
||||
delta_angle += 2 * math.pi
|
||||
return self._RdivV * delta_angle
|
||||
return self._r_div_v * delta_angle
|
||||
|
||||
def __circularA(self, i, pulses_per_mm):
|
||||
def __circular_a(self, i, pulses_per_mm):
|
||||
if i >= self._iterations_a:
|
||||
return self._dir_a, None
|
||||
a, dir, side = self.__circularHelper(self._start_a_pulses, i + 1,
|
||||
self._radius_a_pulses,
|
||||
self._side_a, self._dir_a)
|
||||
a, direction, side = self.__circular_helper(self._start_a_pulses, i + 1,
|
||||
self._radius_a_pulses,
|
||||
self._side_a, self._dir_a)
|
||||
a /= pulses_per_mm
|
||||
# last item can be slightly more then end angle due to float precision
|
||||
if i + 1 == self._iterations_a:
|
||||
return dir, self._RdivV * self._delta_angle
|
||||
return direction, self._r_div_v * self._delta_angle
|
||||
b = math.sqrt(self._radius2 - a * a)
|
||||
if side:
|
||||
b = -b
|
||||
return dir, self.__circularFindTime(a, b)
|
||||
return direction, self.__circular_find_time(a, b)
|
||||
|
||||
def __circularB(self, i, pulses_per_mm):
|
||||
def __circular_b(self, i, pulses_per_mm):
|
||||
if i >= self._iterations_b:
|
||||
return self._dir_b, None
|
||||
b, dir, side = self.__circularHelper(self._start_b_pulses, i + 1,
|
||||
self._radius_b_pulses,
|
||||
self._side_b, self._dir_b)
|
||||
b, direction, side = self.__circular_helper(self._start_b_pulses, i + 1,
|
||||
self._radius_b_pulses,
|
||||
self._side_b, self._dir_b)
|
||||
b /= pulses_per_mm
|
||||
# last item can be slightly more then end angle due to float precision
|
||||
if i + 1 == self._iterations_b:
|
||||
return dir, self._RdivV * self._delta_angle
|
||||
return direction, self._r_div_v * self._delta_angle
|
||||
a = math.sqrt(self._radius2 - b * b)
|
||||
if side:
|
||||
a = -a
|
||||
return dir, self.__circularFindTime(a, b)
|
||||
return direction, self.__circular_find_time(a, b)
|
||||
|
||||
def __linear(self, i, total_i, pulses_per_mm, velocity):
|
||||
@staticmethod
|
||||
def __linear(i, total_i, pulses_per_mm, velocity):
|
||||
if i >= total_i:
|
||||
return None
|
||||
return i / pulses_per_mm / velocity
|
||||
@@ -538,22 +551,22 @@ class PulseGeneratorCircular(PulseGenerator):
|
||||
for details.
|
||||
"""
|
||||
if self._plane == PLANE_XY:
|
||||
dx, tx = self.__circularA(ix, STEPPER_PULSES_PER_MM_X)
|
||||
dy, ty = self.__circularB(iy, STEPPER_PULSES_PER_MM_Y)
|
||||
tz = self.__linear(iz, self._iterations_3rd, STEPPER_PULSES_PER_MM_Z,
|
||||
self._velocity_3rd)
|
||||
dx, tx = self.__circular_a(ix, STEPPER_PULSES_PER_MM_X)
|
||||
dy, ty = self.__circular_b(iy, STEPPER_PULSES_PER_MM_Y)
|
||||
tz = self.__linear(iz, self._iterations_3rd,
|
||||
STEPPER_PULSES_PER_MM_Z, self._velocity_3rd)
|
||||
dz = self._third_dir
|
||||
elif self._plane == PLANE_YZ:
|
||||
dy, ty = self.__circularA(iy, STEPPER_PULSES_PER_MM_Y)
|
||||
dz, tz = self.__circularB(iz, STEPPER_PULSES_PER_MM_Z)
|
||||
tx = self.__linear(ix, self._iterations_3rd, STEPPER_PULSES_PER_MM_X,
|
||||
self._velocity_3rd)
|
||||
dy, ty = self.__circular_a(iy, STEPPER_PULSES_PER_MM_Y)
|
||||
dz, tz = self.__circular_b(iz, STEPPER_PULSES_PER_MM_Z)
|
||||
tx = self.__linear(ix, self._iterations_3rd,
|
||||
STEPPER_PULSES_PER_MM_X, self._velocity_3rd)
|
||||
dx = self._third_dir
|
||||
elif self._plane == PLANE_ZX:
|
||||
dz, tz = self.__circularA(iz, STEPPER_PULSES_PER_MM_Z)
|
||||
dx, tx = self.__circularB(ix, STEPPER_PULSES_PER_MM_X)
|
||||
ty = self.__linear(iy, self._iterations_3rd, STEPPER_PULSES_PER_MM_Y,
|
||||
self._velocity_3rd)
|
||||
else: # self._plane == PLANE_ZX:
|
||||
dz, tz = self.__circular_a(iz, STEPPER_PULSES_PER_MM_Z)
|
||||
dx, tx = self.__circular_b(ix, STEPPER_PULSES_PER_MM_X)
|
||||
ty = self.__linear(iy, self._iterations_3rd,
|
||||
STEPPER_PULSES_PER_MM_Y, self._velocity_3rd)
|
||||
dy = self._third_dir
|
||||
te = self.__linear(ie, self._iterations_e, STEPPER_PULSES_PER_MM_E,
|
||||
self._e_velocity)
|
||||
|
||||
16
deploy.sh
16
deploy.sh
@@ -1,11 +1,15 @@
|
||||
#!/bin/sh
|
||||
set -e
|
||||
PASS=raspberry
|
||||
ADDR=pi@192.168.0.208
|
||||
ADDRESS=pi@192.168.0.211
|
||||
if [ ! -z $1 ]; then
|
||||
ADDR=pi@$1
|
||||
if [[ $1 == *"@"* ]]; then
|
||||
ADDRESS=$1
|
||||
else
|
||||
ADDRESS=pi@$1
|
||||
fi
|
||||
fi
|
||||
find . -name "*.py" -o -name "pycnc" | tar -cjf $(dirname "$0")/pycnc.tar.bz2 -T -
|
||||
sshpass -p${PASS} scp $(dirname "$0")/pycnc.tar.bz2 "${ADDR}:~/pycnc"
|
||||
sshpass -p${PASS} ssh -t ${ADDR} "(cd ~/pycnc && tar xvf pycnc.tar.bz2) > /dev/null" &> /dev/null
|
||||
sshpass -p${PASS} ssh -t ${ADDR} "sudo pypy ~/pycnc/pycnc"
|
||||
find . -name "*.py" -o -name "pycnc" -o -name "*.gcode" | tar -cjf $(dirname "$0")/pycnc.tar.bz2 -T -
|
||||
sshpass -p${PASS} scp $(dirname "$0")/pycnc.tar.bz2 "${ADDRESS}:~/pycnc"
|
||||
sshpass -p${PASS} ssh -t ${ADDRESS} "(cd ~/pycnc && tar xvf pycnc.tar.bz2) > /dev/null" &> /dev/null
|
||||
sshpass -p${PASS} ssh -t ${ADDRESS} "sudo pypy ~/pycnc/pycnc"
|
||||
|
||||
10
runtests.sh
10
runtests.sh
@@ -14,14 +14,14 @@ echo '---------------------------Unit tests---------------------------------'
|
||||
python -m unittest discover "$@" --pattern="test_*.py"
|
||||
echo '-------------------------Integration tests----------------------------'
|
||||
app="pycnc"
|
||||
if ! which $app &> /dev/null; then
|
||||
if ! which ${app} &> /dev/null; then
|
||||
echo "WARNING pycnc not found in path. Not installed? Using './pycnc'."
|
||||
app="./pycnc"
|
||||
fi
|
||||
res="$($app tests/rects.gcode 2>&1)"
|
||||
res="$res$($app tests/circles.gcode 2>&1)"
|
||||
res="$res$($app tests/test_parser.gcode 2>&1)"
|
||||
if echo "$res" | grep -q -i error; then
|
||||
res="$(${app} tests/rects.gcode 2>&1)"
|
||||
res="${res}$(${app} tests/circles.gcode 2>&1)"
|
||||
res="${res}$(${app} tests/test_parser.gcode 2>&1)"
|
||||
if echo "${res}" | grep -q -i error; then
|
||||
echo "FAILED"
|
||||
echo "$res"
|
||||
exit 1
|
||||
|
||||
2
setup.py
2
setup.py
@@ -9,7 +9,7 @@ except(IOError, ImportError):
|
||||
from setuptools import setup, find_packages
|
||||
setup(
|
||||
name="pycnc",
|
||||
version="0.1.2",
|
||||
version="0.1.3",
|
||||
packages=find_packages(),
|
||||
scripts=['pycnc'],
|
||||
|
||||
|
||||
@@ -1,11 +1,15 @@
|
||||
#!/bin/sh
|
||||
set -e
|
||||
PASS=raspberry
|
||||
ADDR=pi@192.168.0.208
|
||||
ADDRESS=pi@192.168.0.211
|
||||
if [ ! -z $1 ]; then
|
||||
ADDR=pi@$1
|
||||
if [[ $1 == *"@"* ]]; then
|
||||
ADDRESS=$1
|
||||
else
|
||||
ADDRESS=pi@$1
|
||||
fi
|
||||
fi
|
||||
find cnc/hal_raspberry -name "rpgpio*.py" -o -name "pycnc" | tar -cjf $(dirname "$0")/../pycnc.tar.bz2 -T -
|
||||
sshpass -p${PASS} scp $(dirname "$0")/../pycnc.tar.bz2 "${ADDR}:~/pycnc"
|
||||
sshpass -p${PASS} ssh -t ${ADDR} "(cd ~/pycnc && tar xvf pycnc.tar.bz2) > /dev/null" &> /dev/null
|
||||
sshpass -p${PASS} ssh -t ${ADDR} "(cd ~/pycnc && sudo pypy -m cnc.hal_raspberry.rpgpio)"
|
||||
sshpass -p${PASS} scp $(dirname "$0")/../pycnc.tar.bz2 "${ADDRESS}:~/pycnc"
|
||||
sshpass -p${PASS} ssh -t ${ADDRESS} "(cd ~/pycnc && tar xvf pycnc.tar.bz2) > /dev/null" &> /dev/null
|
||||
sshpass -p${PASS} ssh -t ${ADDRESS} "(cd ~/pycnc && sudo pypy -m cnc.hal_raspberry.rpgpio)"
|
||||
|
||||
@@ -131,6 +131,7 @@ class TestCoordinates(unittest.TestCase):
|
||||
|
||||
def test_abs(self):
|
||||
c = Coordinates(-1, -2.5, -99, -23)
|
||||
# noinspection PyTypeChecker
|
||||
r = abs(c)
|
||||
self.assertEqual(r.x, 1.0)
|
||||
self.assertEqual(r.y, 2.5)
|
||||
|
||||
@@ -1,7 +1,5 @@
|
||||
import unittest
|
||||
import math
|
||||
|
||||
from cnc.coordinates import *
|
||||
from cnc.gcode import *
|
||||
|
||||
|
||||
@@ -16,7 +14,7 @@ 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", "E": 99, "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)
|
||||
@@ -122,11 +120,12 @@ class TestGCode(unittest.TestCase):
|
||||
gc = GCode.parse_line("X2 Y(inline comment)7")
|
||||
self.assertEqual(gc.coordinates(self.default, 1).x, 2.0)
|
||||
self.assertEqual(gc.coordinates(self.default, 1).y, 7.0)
|
||||
gc = GCode.parse_line("X2 Y(inline comment)3 \t(one more comment) \tz4 ; multi comment test")
|
||||
gc = GCode.parse_line("X2 Y(inline comment)3 \t(one more comment) "
|
||||
"\tz4 ; multi comment test")
|
||||
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)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
unittest.main()
|
||||
unittest.main()
|
||||
|
||||
@@ -1,9 +1,8 @@
|
||||
import unittest
|
||||
import time
|
||||
|
||||
from cnc.coordinates import *
|
||||
from cnc.gcode import *
|
||||
from cnc.gmachine import *
|
||||
from cnc.coordinates import *
|
||||
|
||||
|
||||
class TestGMachine(unittest.TestCase):
|
||||
@@ -83,7 +82,8 @@ class TestGMachine(unittest.TestCase):
|
||||
self.assertRaises(GMachineException,
|
||||
m.do_command, GCode.parse_line("G3I0J0K1"))
|
||||
self.assertRaises(GMachineException,
|
||||
m.do_command, GCode.parse_line("G2X99999999Y99999999I1J1"))
|
||||
m.do_command, GCode.parse_line("G2X99999999Y99999999"
|
||||
"I1J1"))
|
||||
self.assertRaises(GMachineException,
|
||||
m.do_command, GCode.parse_line("G2X2Y2Z99999999I1J1"))
|
||||
self.assertEqual(m.position(), Coordinates(0, 0, 0, 0))
|
||||
@@ -194,4 +194,4 @@ class TestGMachine(unittest.TestCase):
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
unittest.main()
|
||||
unittest.main()
|
||||
|
||||
@@ -24,5 +24,5 @@ g90
|
||||
g92x100y100z100
|
||||
m111
|
||||
g1x98y98z98
|
||||
(head should be in zero position, and last movent with 500 mm/min velocity)
|
||||
(head should be in zero position, and last movement with 500 mm/min velocity)
|
||||
m2
|
||||
|
||||
@@ -1,9 +1,8 @@
|
||||
import unittest
|
||||
import time
|
||||
|
||||
from cnc.coordinates import *
|
||||
from cnc.pulses import *
|
||||
from cnc.config import *
|
||||
from cnc.coordinates import *
|
||||
from cnc import hal_virtual
|
||||
|
||||
|
||||
@@ -35,8 +34,8 @@ class TestPulses(unittest.TestCase):
|
||||
0, 0, 0),
|
||||
self.v)
|
||||
i = 0
|
||||
for dir, px, py, pz, pe in g:
|
||||
if dir:
|
||||
for direction, px, py, pz, pe in g:
|
||||
if direction:
|
||||
continue
|
||||
i += 1
|
||||
self.assertEqual(px, 0)
|
||||
@@ -51,8 +50,8 @@ class TestPulses(unittest.TestCase):
|
||||
1.0 / STEPPER_PULSES_PER_MM_E),
|
||||
self.v)
|
||||
i = 0
|
||||
for dir, px, py, pz, pe in g:
|
||||
if dir:
|
||||
for direction, px, py, pz, pe in g:
|
||||
if direction:
|
||||
continue
|
||||
i += 1
|
||||
self.assertEqual(px, 0)
|
||||
@@ -61,20 +60,19 @@ class TestPulses(unittest.TestCase):
|
||||
self.assertEqual(pe, 0)
|
||||
self.assertEqual(i, 1)
|
||||
|
||||
|
||||
def __check_circular(self, delta, radius, plane, direction = CW):
|
||||
def __check_circular(self, delta, radius, plane, direction=CW):
|
||||
g = PulseGeneratorCircular(delta, radius, plane, direction, self.v)
|
||||
x, y, z, e = 0, 0, 0, 0
|
||||
dx, dy, dz, de = None, None, None, None
|
||||
dir_changed = 0
|
||||
dir_requested = False
|
||||
t = -1
|
||||
for dir, px, py, pz, pe in g:
|
||||
if dir:
|
||||
for direction_i, px, py, pz, pe in g:
|
||||
if direction_i:
|
||||
dx, dy, dz, de = px, py, pz, pe
|
||||
dir_requested = True
|
||||
continue
|
||||
if dir_requested: # ignore last change
|
||||
if dir_requested: # ignore last change
|
||||
dir_requested = False
|
||||
dir_changed += 1
|
||||
if px is not None:
|
||||
@@ -97,53 +95,43 @@ class TestPulses(unittest.TestCase):
|
||||
def test_single_radius_circles(self):
|
||||
# Check if PulseGenerator returns correctly single radius movement in
|
||||
# both direction.
|
||||
_, pos = self.__check_circular(Coordinates(0, 0, 0, 0),
|
||||
Coordinates(1.0 / STEPPER_PULSES_PER_MM_X, 0, 0, 0),
|
||||
zero_delta = Coordinates(0, 0, 0, 0)
|
||||
radius = Coordinates(1.0 / STEPPER_PULSES_PER_MM_X, 0, 0, 0)
|
||||
_, pos = self.__check_circular(zero_delta, radius, PLANE_XY, CW)
|
||||
self.assertEqual(pos, Coordinates(0, 0, 0, 0))
|
||||
radius = Coordinates(-1.0 / STEPPER_PULSES_PER_MM_X, 0, 0, 0)
|
||||
_, pos = self.__check_circular(zero_delta, radius,
|
||||
PLANE_XY, CW)
|
||||
self.assertEqual(pos, Coordinates(0, 0, 0, 0))
|
||||
_, pos = self.__check_circular(Coordinates(0, 0, 0, 0),
|
||||
Coordinates(-1.0 / STEPPER_PULSES_PER_MM_X, 0, 0, 0),
|
||||
PLANE_XY, CW)
|
||||
radius = Coordinates(0, 1.0 / STEPPER_PULSES_PER_MM_Y, 0, 0)
|
||||
_, pos = self.__check_circular(zero_delta, radius, PLANE_YZ, CW)
|
||||
self.assertEqual(pos, Coordinates(0, 0, 0, 0))
|
||||
_, pos = self.__check_circular(Coordinates(0, 0, 0, 0),
|
||||
Coordinates(0, 1.0 / STEPPER_PULSES_PER_MM_Y, 0, 0),
|
||||
PLANE_YZ, CW)
|
||||
radius = Coordinates(0, -1.0 / STEPPER_PULSES_PER_MM_Y, 0, 0)
|
||||
_, pos = self.__check_circular(zero_delta, radius, PLANE_YZ, CW)
|
||||
self.assertEqual(pos, Coordinates(0, 0, 0, 0))
|
||||
_, pos = self.__check_circular(Coordinates(0, 0, 0, 0),
|
||||
Coordinates(0, -1.0 / STEPPER_PULSES_PER_MM_Y, 0, 0),
|
||||
PLANE_YZ, CW)
|
||||
radius = Coordinates(0, 0, 1.0 / STEPPER_PULSES_PER_MM_Z, 0)
|
||||
_, pos = self.__check_circular(zero_delta, radius, PLANE_ZX, CW)
|
||||
self.assertEqual(pos, Coordinates(0, 0, 0, 0))
|
||||
_, pos = self.__check_circular(Coordinates(0, 0, 0, 0),
|
||||
Coordinates(0, 0, 1.0 / STEPPER_PULSES_PER_MM_Z, 0),
|
||||
PLANE_ZX, CW)
|
||||
radius = Coordinates(0, 0, -1.0 / STEPPER_PULSES_PER_MM_Z, 0)
|
||||
_, pos = self.__check_circular(zero_delta, radius, PLANE_ZX, CW)
|
||||
self.assertEqual(pos, Coordinates(0, 0, 0, 0))
|
||||
_, pos = self.__check_circular(Coordinates(0, 0, 0, 0),
|
||||
Coordinates(0, 0, -1.0 / STEPPER_PULSES_PER_MM_Z, 0),
|
||||
PLANE_ZX, CW)
|
||||
radius = Coordinates(1.0 / STEPPER_PULSES_PER_MM_X, 0, 0, 0)
|
||||
_, pos = self.__check_circular(zero_delta, radius, PLANE_XY, CCW)
|
||||
self.assertEqual(pos, Coordinates(0, 0, 0, 0))
|
||||
_, pos = self.__check_circular(Coordinates(0, 0, 0, 0),
|
||||
Coordinates(1.0 / STEPPER_PULSES_PER_MM_X, 0, 0, 0),
|
||||
PLANE_XY, CCW)
|
||||
radius = Coordinates(-1.0 / STEPPER_PULSES_PER_MM_X, 0, 0, 0)
|
||||
_, pos = self.__check_circular(zero_delta, radius, PLANE_XY, CCW)
|
||||
self.assertEqual(pos, Coordinates(0, 0, 0, 0))
|
||||
_, pos = self.__check_circular(Coordinates(0, 0, 0, 0),
|
||||
Coordinates(-1.0 / STEPPER_PULSES_PER_MM_X, 0, 0, 0),
|
||||
PLANE_XY, CCW)
|
||||
radius = Coordinates(0, 1.0 / STEPPER_PULSES_PER_MM_Y, 0, 0)
|
||||
_, pos = self.__check_circular(zero_delta, radius, PLANE_YZ, CCW)
|
||||
self.assertEqual(pos, Coordinates(0, 0, 0, 0))
|
||||
_, pos = self.__check_circular(Coordinates(0, 0, 0, 0),
|
||||
Coordinates(0, 1.0 / STEPPER_PULSES_PER_MM_Y, 0, 0),
|
||||
PLANE_YZ, CCW)
|
||||
radius = Coordinates(0, -1.0 / STEPPER_PULSES_PER_MM_Y, 0, 0)
|
||||
_, pos = self.__check_circular(zero_delta, radius, PLANE_YZ, CCW)
|
||||
self.assertEqual(pos, Coordinates(0, 0, 0, 0))
|
||||
_, pos = self.__check_circular(Coordinates(0, 0, 0, 0),
|
||||
Coordinates(0, -1.0 / STEPPER_PULSES_PER_MM_Y, 0, 0),
|
||||
PLANE_YZ, CCW)
|
||||
radius = Coordinates(0, 0, 1.0 / STEPPER_PULSES_PER_MM_Z, 0)
|
||||
_, pos = self.__check_circular(zero_delta, radius, PLANE_ZX, CCW)
|
||||
self.assertEqual(pos, Coordinates(0, 0, 0, 0))
|
||||
_, pos = self.__check_circular(Coordinates(0, 0, 0, 0),
|
||||
Coordinates(0, 0, 1.0 / STEPPER_PULSES_PER_MM_Z, 0),
|
||||
PLANE_ZX, CCW)
|
||||
self.assertEqual(pos, Coordinates(0, 0, 0, 0))
|
||||
_, pos = self.__check_circular(Coordinates(0, 0, 0, 0),
|
||||
Coordinates(0, 0, -1.0 / STEPPER_PULSES_PER_MM_Z, 0),
|
||||
PLANE_ZX, CCW)
|
||||
radius = Coordinates(0, 0, -1.0 / STEPPER_PULSES_PER_MM_Z, 0)
|
||||
_, pos = self.__check_circular(zero_delta, radius, PLANE_ZX, CCW)
|
||||
self.assertEqual(pos, Coordinates(0, 0, 0, 0))
|
||||
|
||||
def test_with_hal_virtual(self):
|
||||
@@ -158,30 +146,29 @@ class TestPulses(unittest.TestCase):
|
||||
hal_virtual.move(PulseGeneratorLinear(Coordinates(25.4, 0, 0, 0),
|
||||
self.v))
|
||||
hal_virtual.move(PulseGeneratorLinear(Coordinates(TABLE_SIZE_X_MM,
|
||||
TABLE_SIZE_Y_MM,
|
||||
TABLE_SIZE_Z_MM,
|
||||
100.0), self.v))
|
||||
TABLE_SIZE_Y_MM,
|
||||
TABLE_SIZE_Z_MM,
|
||||
100.0), self.v))
|
||||
hal_virtual.move(PulseGeneratorCircular(Coordinates(0, 20, 0, 0),
|
||||
Coordinates(-10, 10, 0, 0),
|
||||
PLANE_XY, CW, self.v))
|
||||
hal_virtual.move(PulseGeneratorCircular(Coordinates(-4, -4, 0, 0),
|
||||
Coordinates(-2, -2, 0, 0),
|
||||
PLANE_XY, CW, self.v))
|
||||
hal_virtual.move(PulseGeneratorCircular(Coordinates(- 2.0 / STEPPER_PULSES_PER_MM_X,
|
||||
- 2.0 / STEPPER_PULSES_PER_MM_Y,
|
||||
0, 0),
|
||||
Coordinates(- 1.0 / STEPPER_PULSES_PER_MM_X,
|
||||
- 1.0 / STEPPER_PULSES_PER_MM_Y,
|
||||
0, 0),
|
||||
PLANE_XY, CW, self.v))
|
||||
delta = Coordinates(- 2.0 / STEPPER_PULSES_PER_MM_X,
|
||||
- 2.0 / STEPPER_PULSES_PER_MM_Y, 0, 0)
|
||||
radius = Coordinates(- 1.0 / STEPPER_PULSES_PER_MM_X,
|
||||
- 1.0 / STEPPER_PULSES_PER_MM_Y, 0, 0)
|
||||
hal_virtual.move(PulseGeneratorCircular(delta, radius, PLANE_XY, CW,
|
||||
self.v))
|
||||
|
||||
def test_twice_faster_linear(self):
|
||||
# Checks if one axis moves exactly twice faster, pulses are correct.
|
||||
m = Coordinates(2, 4, 0, 0)
|
||||
g = PulseGeneratorLinear(m, self.v)
|
||||
i = 0
|
||||
for dir, px, py, pz, pe in g:
|
||||
if dir:
|
||||
for direction, px, py, pz, pe in g:
|
||||
if direction:
|
||||
continue
|
||||
if i % 2 == 0:
|
||||
self.assertNotEqual(px, None)
|
||||
@@ -203,8 +190,8 @@ class TestPulses(unittest.TestCase):
|
||||
iz = 0
|
||||
ie = 0
|
||||
t = -1
|
||||
for dir, px, py, pz, pe in g:
|
||||
if dir:
|
||||
for direction, px, py, pz, pe in g:
|
||||
if direction:
|
||||
continue
|
||||
if px is not None:
|
||||
ix += 1
|
||||
@@ -244,8 +231,9 @@ class TestPulses(unittest.TestCase):
|
||||
g = PulseGeneratorLinear(m, self.v)
|
||||
i = 0
|
||||
lx = 0
|
||||
for dir, px, py, pz, pe in g:
|
||||
if dir:
|
||||
lt, at, bt = None, None, None
|
||||
for direction, px, py, pz, pe in g:
|
||||
if direction:
|
||||
continue
|
||||
if i == 2:
|
||||
at = px - lx
|
||||
@@ -254,7 +242,8 @@ class TestPulses(unittest.TestCase):
|
||||
bt = px - lx
|
||||
lx = px
|
||||
i += 1
|
||||
self.assertEqual(round(60.0 / lt / STEPPER_PULSES_PER_MM_X), round(self.v))
|
||||
self.assertEqual(round(60.0 / lt / STEPPER_PULSES_PER_MM_X),
|
||||
round(self.v))
|
||||
self.assertGreater(at, lt)
|
||||
self.assertGreater(bt, lt)
|
||||
|
||||
@@ -263,8 +252,8 @@ class TestPulses(unittest.TestCase):
|
||||
m = Coordinates(1, -2, 3, -4)
|
||||
g = PulseGeneratorLinear(m, self.v)
|
||||
dir_found = False
|
||||
for dir, px, py, pz, pe in g:
|
||||
if dir:
|
||||
for direction, px, py, pz, pe in g:
|
||||
if direction:
|
||||
# should be once
|
||||
self.assertFalse(dir_found)
|
||||
dir_found = True
|
||||
@@ -273,8 +262,8 @@ class TestPulses(unittest.TestCase):
|
||||
m = Coordinates(-1, 2, -3, 4)
|
||||
g = PulseGeneratorLinear(m, self.v)
|
||||
dir_found = False
|
||||
for dir, px, py, pz, pe in g:
|
||||
if dir:
|
||||
for direction, px, py, pz, pe in g:
|
||||
if direction:
|
||||
# should be once
|
||||
self.assertFalse(dir_found)
|
||||
dir_found = True
|
||||
|
||||
Reference in New Issue
Block a user