diff --git a/.gitignore b/.gitignore index 454ad24..583c4a1 100644 --- a/.gitignore +++ b/.gitignore @@ -2,3 +2,6 @@ *.pyc \.idea/ + +pcbdevice/resources/output/ +pcbdevice/tests/resources/output/ diff --git a/pcbdevice/gcode/GcodeBuilder.py b/pcbdevice/gcode/GcodeBuilder.py index 4042389..603bc16 100644 --- a/pcbdevice/gcode/GcodeBuilder.py +++ b/pcbdevice/gcode/GcodeBuilder.py @@ -1,4 +1,12 @@ def listToGCode(listIndex, pHeight, pWidth): + """ + Convert a list of matrix coordinate in a list of GCode commands + + :param listIndex: List of coordinate + :param pHeight: Pixel height in mm + :param pWidth: Pixel width in mm + :return: List of all the GCode commands + """ gcodeCommand = [] toolUp = True @@ -14,7 +22,7 @@ def listToGCode(listIndex, pHeight, pWidth): gcodeCommand.append('G0 Z0') toolUp = True else: - gcodeCommand.append('G0 X' + str(coord.getX()*pWidth) + ' Y' + str(coord.getY()*pHeight)) + gcodeCommand.append('G0 X' + str(round(coord.getX()*pWidth, 2)) + ' Y' + str(round(coord.getY()*pHeight, 2))) if toolUp: gcodeCommand.append('G0 Z3') toolUp = False diff --git a/pcbdevice/main.py b/pcbdevice/main.py index 637ac52..9959568 100644 --- a/pcbdevice/main.py +++ b/pcbdevice/main.py @@ -1,13 +1,19 @@ -from pcbdevice.utils.path import path -from pcbdevice.utils.plotimg import plotPath +import math + +from pcbdevice.gcode.GcodeBuilder import listToGCode + +from pcbdevice.gcode.GcodeCreator import createSequence +from pcbdevice.gcode.path import path from pcbdevice.utils.FileUtils import FileUtils import argparse if __name__ == "__main__": parser = argparse.ArgumentParser(prog = 'main.py') parser.add_argument('-i', required = True, help = 'PCB image path') + parser.add_argument('-o', required = True, help = 'Gcode output path') parser.add_argument('-wi', required = True, type = int, help = 'Width of the PCB') parser.add_argument('-he', required = True, type = int, help = 'Height of the PCB') + parser.add_argument('-t', required = True, type = int, help = 'Tool\'s radius in mm') parser.add_argument('-u', required = False, help = 'PCB dimension unit') args = parser.parse_args() @@ -17,13 +23,13 @@ if __name__ == "__main__": pxHeight, pxWidth = FileUtils.getPixelSize(height, width, args.he, args.wi, unit = args.u) else: pxHeight, pxWidth = FileUtils.getPixelSize(height, width, args.he, args.wi) - - - resourcesRawPath = 'tests/resources/raw/' - resourcesFormattedPath = 'tests/resources/formatted/' - resourcesPathOutput = 'resources/pathoutput/' - resourcesExpectedPath = 'tests/resources/expected/' - - #FileUtils.saveMatrixToFile(FileUtils.pbmToMatrix(resourcesRawPath + 'test1ascii.pbm'), resourcesFormattedPath + 'test1.csv') - - #plotPath(path(FileUtils.pbmToMatrix(resourcesRawPath + 'test100x100.pbm'), 5)) \ No newline at end of file + + if pxHeight > pxWidth: + rTool = int(math.ceil(args.t * pxHeight)) + else: + rTool = int(math.ceil(args.t * pxWidth)) + + matrixUpdated = path(matrix, rTool) + listIndexes = createSequence(matrixUpdated) + gcode = listToGCode(listIndexes, pxHeight, pxWidth) + FileUtils.saveStringListToFile(gcode, args.o) \ No newline at end of file diff --git a/pcbdevice/tests/gcode/test_listToGcode.py b/pcbdevice/tests/gcode/test_listToGcode.py index 04571a2..3ca9a51 100644 --- a/pcbdevice/tests/gcode/test_listToGcode.py +++ b/pcbdevice/tests/gcode/test_listToGcode.py @@ -5,37 +5,56 @@ from pcbdevice.models.Coordinates import Coordinate class TestListToGCode(TestCase): + + oneTrace = [Coordinate(1, 2), + Coordinate(1, 5), + Coordinate(2, 5), + Coordinate(2, 8)] + + twoTrace = [Coordinate(1, 2), + Coordinate(1, 5), + Coordinate(5, 5), + Coordinate(5, 2), + Coordinate(1, 2), + Coordinate(-1, -1), + Coordinate(5, 4), + Coordinate(8, 4)] + + threeTrace = [Coordinate(1, 2), + Coordinate(1, 5), + Coordinate(5, 5), + Coordinate(5, 2), + Coordinate(1, 2), + Coordinate(-1, -1), + Coordinate(5, 4), + Coordinate(8, 4), + Coordinate(2, 9), + Coordinate(9, 45), + Coordinate(12, 12), + Coordinate(1, 10)] + def test_listToGCodeMultipleTrace(self): - xSize, ySize = 2, 3 + xSize, ySize = 2.0, 3.0 - coords = whenSingleTrace() - self.assertEqual(listToGCode(coords, ySize, xSize), getExpected(coords, ySize, xSize)) - - coords = whenTwoTrace() - self.assertEqual(listToGCode(coords, ySize, xSize), getExpected(coords, ySize, xSize)) - - coords = whenThreeTrace() - self.assertEqual(listToGCode(coords, ySize, xSize), getExpected(coords, ySize, xSize)) + self.assertEqual(listToGCode(self.oneTrace, ySize, xSize), getExpected(self.oneTrace, ySize, xSize)) + self.assertEqual(listToGCode(self.twoTrace, ySize, xSize), getExpected(self.twoTrace, ySize, xSize)) + self.assertEqual(listToGCode(self.threeTrace, ySize, xSize), getExpected(self.threeTrace, ySize, xSize)) def test_listToGCodePixelSize(self): - xSize, ySize = 1, 4 - coords = whenSingleTrace() - self.assertEqual(listToGCode(coords, ySize, xSize), getExpected(coords, ySize, xSize)) + xSize, ySize = 1.0, 4.0 + self.assertEqual(listToGCode(self.oneTrace, ySize, xSize), getExpected(self.oneTrace, ySize, xSize)) - xSize, ySize = 4, 2 - coords = whenSingleTrace() - self.assertEqual(listToGCode(coords, ySize, xSize), getExpected(coords, ySize, xSize)) + xSize, ySize = 4.0, 2.0 + self.assertEqual(listToGCode(self.oneTrace, ySize, xSize), getExpected(self.oneTrace, ySize, xSize)) - xSize, ySize = 8, -1 - coords = whenSingleTrace() - self.assertRaises(RuntimeError, lambda: listToGCode(coords, ySize, xSize)) - + xSize, ySize = 8.0, -1.0 + self.assertRaises(RuntimeError, lambda: listToGCode(self.oneTrace, ySize, xSize)) def getExpected(coords, ySize, xSize): header = ['G28', 'G90\n'] footer = ['\nG0 Z0', 'G28'] - content = ['G0 X' + str(xSize * coords[0].getX()) + ' Y' + str(ySize * coords[0].getY()), + content = ['G0 X' + str(round(xSize * coords[0].getX(), 2)) + ' Y' + str(round(ySize * coords[0].getY(), 2)), 'G0 Z3', ] @@ -48,37 +67,4 @@ def getExpected(coords, ySize, xSize): else: content.append('G0 Z0') - return header + content + footer - - -def whenSingleTrace(): - return [Coordinate(1, 2), - Coordinate(1, 5), - Coordinate(2, 5), - Coordinate(2, 8)] - - -def whenTwoTrace(): - return [Coordinate(1, 2), - Coordinate(1, 5), - Coordinate(5, 5), - Coordinate(5, 2), - Coordinate(1, 2), - Coordinate(-1, -1), - Coordinate(5, 4), - Coordinate(8, 4)] - - -def whenThreeTrace(): - return [Coordinate(1, 2), - Coordinate(1, 5), - Coordinate(5, 5), - Coordinate(5, 2), - Coordinate(1, 2), - Coordinate(-1, -1), - Coordinate(5, 4), - Coordinate(8, 4), - Coordinate(2, 9), - Coordinate(9, 45), - Coordinate(12, 12), - Coordinate(1, 10)] \ No newline at end of file + return header + content + footer \ No newline at end of file diff --git a/pcbdevice/tests/resources/expected/text1.txt b/pcbdevice/tests/resources/expected/text1.txt new file mode 100644 index 0000000..8e78941 --- /dev/null +++ b/pcbdevice/tests/resources/expected/text1.txt @@ -0,0 +1,5 @@ +This +is +a + +test diff --git a/pcbdevice/tests/resources/expected/text2.txt b/pcbdevice/tests/resources/expected/text2.txt new file mode 100644 index 0000000..c7cf44c --- /dev/null +++ b/pcbdevice/tests/resources/expected/text2.txt @@ -0,0 +1,5 @@ +G28 +G90 +G0 Z3 + +G0 X15 Y45 diff --git a/pcbdevice/tests/utils/test_fileUtils.py b/pcbdevice/tests/utils/test_fileUtils.py index 2ecffa6..69054b2 100644 --- a/pcbdevice/tests/utils/test_fileUtils.py +++ b/pcbdevice/tests/utils/test_fileUtils.py @@ -2,6 +2,7 @@ from unittest import TestCase from pcbdevice.utils import TestUtils from pcbdevice.utils.FileUtils import FileUtils +from pcbdevice.utils.TestUtils import readStringFile resources = './pcbdevice/tests/resources/' @@ -9,18 +10,35 @@ class TestFileUtils(TestCase): def test_pbmToMatrix(self): actual, h, w = FileUtils.pbmToMatrix(resources + 'raw/test1.pbm') expected = TestUtils.readIntFile(resources + 'formatted/test1.csv') - assert actual == expected + self.assertEqual(actual, expected) def test_saveMatrixToFile(self): actual, h, w = FileUtils.pbmToMatrix(resources + 'raw/test1.pbm') FileUtils.saveMatrixToFile(actual, resources + 'output/test1.csv') expected = TestUtils.readIntFile(resources + 'output/test1.csv') - assert actual == expected + self.assertEqual(actual, expected) + + def test_saveStringListToFile(self): + stringList = ['This', + 'is', + 'a\n', + 'test'] + + FileUtils.saveStringListToFile(stringList, resources + 'output/text1.txt') + self.assertEqual(readStringFile(resources + 'output/text1.txt'), readStringFile(resources+'expected/text1.txt')) + + stringList = ['G28', + 'G90', + 'G0 Z3\n', + 'G0 X15 Y45'] + + FileUtils.saveStringListToFile(stringList, resources + 'output/text2.txt') + self.assertEqual(readStringFile(resources + 'output/text2.txt'), readStringFile(resources + 'expected/text2.txt')) def test_getPixelSize(self): - assert 10, 10 == FileUtils.getPixelSize(10, 10, 100, 100) - assert 1, 1 == FileUtils.getPixelSize(100, 100, 100, 100) - assert 10, 10 == FileUtils.getPixelSize(10, 10, 10, 10, unit = 'cm') - assert 10, 10 == FileUtils.getPixelSize(10, 10, 1, 1, unit = 'm') - assert 254, 254 == FileUtils.getPixelSize(10, 10, 10, 10, unit = 'in') - assert 10, 5 == FileUtils.getPixelSize(10, 10, 10, 20) \ No newline at end of file + self.assertEqual((10, 10), FileUtils.getPixelSize(10, 10, 100, 100)) + self.assertEqual((1, 1), FileUtils.getPixelSize(100, 100, 100, 100)) + self.assertEqual((10, 10), FileUtils.getPixelSize(10, 10, 10, 10, unit = 'cm')) + self.assertEqual((25.4, 25.4), FileUtils.getPixelSize(10, 10, 10, 10, unit = 'in')) + self.assertEqual((1, 2), FileUtils.getPixelSize(10, 10, 10, 20)) + self.assertRaises(RuntimeError, lambda: FileUtils.getPixelSize(10, 10, 10, 10, 'ft')) \ No newline at end of file diff --git a/pcbdevice/utils/FileUtils.py b/pcbdevice/utils/FileUtils.py index 08caf56..80648fb 100644 --- a/pcbdevice/utils/FileUtils.py +++ b/pcbdevice/utils/FileUtils.py @@ -3,6 +3,16 @@ import math class FileUtils: @staticmethod def pbmToMatrix(pbmFilePath, dimensionLineIndex = 2): + """ + Read a pbm file and convert in an int matrix + + :param str pbmFilePath: Path of the ascii pbm file to convert in a matrix + :param int dimensionLineIndex: Line index containing the dimension of the image + :return matrix: Matrix with image value + :return height: Height of the matrix + :return width: Width of the matrix + """ + completeFile = [] file = open(pbmFilePath, 'r') @@ -22,6 +32,13 @@ class FileUtils: @staticmethod def saveMatrixToFile(matrix, filePath): + """ + Save a matrix in a plain text file + + :param matrix: Matrix to save + :param filePath: Path of the output file + :return: None + """ with open(filePath, 'w') as f: for x in matrix: for y in x: @@ -29,8 +46,34 @@ class FileUtils: f.write('\n') f.close() + @staticmethod + def saveStringListToFile(stringList, filePath): + """ + Save a string list into a plain text file with a carriage return after each entry + + :param stringList: List of string to write + :param filePath: File path to write the text + :return: None + """ + with open(filePath, 'w') as f: + for line in stringList: + f.write('%s\n' % line) + f.close() + @staticmethod def getPixelSize(matHeight, matWidth, pcbHeight, pcbWidth, unit = 'mm'): + """ + Get pixel width and height with the real image size + + :param matHeight: Height of the image matrix (Nb pixels) + :param matWidth: Width of the image matrix (Nb pixels) + :param pcbHeight: True height of the image (PCB) + :param pcbWidth: True width of the image (PCB) + :param unit: Unit of the size of the image, default in mm + :return pixelHeight: Pixel height in mm + :return pixelWidth: Pixel width in mm + + """ if unit == 'mm': return pcbHeight / matHeight, pcbWidth / matWidth elif unit == 'cm': diff --git a/pcbdevice/utils/TestUtils.py b/pcbdevice/utils/TestUtils.py index f5904f4..a8f1dce 100644 --- a/pcbdevice/utils/TestUtils.py +++ b/pcbdevice/utils/TestUtils.py @@ -1,4 +1,10 @@ def readIntFile(filePath): + """ + Read a matrix file + + :param filePath: File path to read from + :return: The matrix in int + """ completeFile = [] file = open(filePath, 'r') lines = file.readlines() @@ -11,4 +17,17 @@ def readIntFile(filePath): completeFile.append(tempArray) - return completeFile \ No newline at end of file + return completeFile + +def readStringFile(filePath): + """ + Read all lines of a file + + :param filePath: File path to read from + :return: Array of all lines in the file + """ + file = open(filePath, 'r') + lines = file.readlines() + file.close() + + return lines \ No newline at end of file