├── .gitignore ├── README.md ├── README ├── AddScreen.JPG ├── FinishScreen.JPG ├── HomeScreen.JPG ├── LoadScreen.JPG ├── MoveScreen.JPG ├── RemoveScreen.JPG └── ViewScreen.JPG ├── dimensionFilter.py ├── dist └── wxGUI.zip ├── draw.py ├── icon.ico ├── logo.png ├── parserFunction.py └── wxGUI.py /.gitignore: -------------------------------------------------------------------------------- 1 | # file: ~/.gitignore_global 2 | .DS_Store 3 | .idea 4 | __pycache__ 5 | dist 6 | *.txt 7 | *.pdf 8 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Engineering-Drawing-Parser 2 | Currently under devlopment. 3 | 4 | The final program will be able to interpret bubbled engineering drawing in .pdf form, as well as automatically and manually bubble existing .pdf engineering drawings. 5 | 6 | This branch is being used to implement a GUI using wxPython. 7 | 8 | ![Home Screen](https://raw.githubusercontent.com/Benjamin-Hu/Engineering-Drawing-Parser/wxpython-development/README/HomeScreen.JPG) 9 | ![Load Screen](https://raw.githubusercontent.com/Benjamin-Hu/Engineering-Drawing-Parser/wxpython-development/README/LoadScreen.JPG) 10 | ![View Screen](https://raw.githubusercontent.com/Benjamin-Hu/Engineering-Drawing-Parser/wxpython-development/README/ViewScreen.JPG) 11 | ![Add Screen](https://raw.githubusercontent.com/Benjamin-Hu/Engineering-Drawing-Parser/wxpython-development/README/AddScreen.JPG) 12 | ![Remove Screen](https://raw.githubusercontent.com/Benjamin-Hu/Engineering-Drawing-Parser/wxpython-development/README/RemoveScreen.JPG) 13 | ![Move Screen](https://raw.githubusercontent.com/Benjamin-Hu/Engineering-Drawing-Parser/wxpython-development/README/MoveScreen.JPG) 14 | ![Finish Screen](https://raw.githubusercontent.com/Benjamin-Hu/Engineering-Drawing-Parser/wxpython-development/README/FinishScreen.JPG) 15 | -------------------------------------------------------------------------------- /README/AddScreen.JPG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Benjamin-Hu/Engineering-Drawing-Parser/c1de977d8ab16b84e9291d0b5d00e48ecf49a893/README/AddScreen.JPG -------------------------------------------------------------------------------- /README/FinishScreen.JPG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Benjamin-Hu/Engineering-Drawing-Parser/c1de977d8ab16b84e9291d0b5d00e48ecf49a893/README/FinishScreen.JPG -------------------------------------------------------------------------------- /README/HomeScreen.JPG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Benjamin-Hu/Engineering-Drawing-Parser/c1de977d8ab16b84e9291d0b5d00e48ecf49a893/README/HomeScreen.JPG -------------------------------------------------------------------------------- /README/LoadScreen.JPG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Benjamin-Hu/Engineering-Drawing-Parser/c1de977d8ab16b84e9291d0b5d00e48ecf49a893/README/LoadScreen.JPG -------------------------------------------------------------------------------- /README/MoveScreen.JPG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Benjamin-Hu/Engineering-Drawing-Parser/c1de977d8ab16b84e9291d0b5d00e48ecf49a893/README/MoveScreen.JPG -------------------------------------------------------------------------------- /README/RemoveScreen.JPG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Benjamin-Hu/Engineering-Drawing-Parser/c1de977d8ab16b84e9291d0b5d00e48ecf49a893/README/RemoveScreen.JPG -------------------------------------------------------------------------------- /README/ViewScreen.JPG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Benjamin-Hu/Engineering-Drawing-Parser/c1de977d8ab16b84e9291d0b5d00e48ecf49a893/README/ViewScreen.JPG -------------------------------------------------------------------------------- /dimensionFilter.py: -------------------------------------------------------------------------------- 1 | import re 2 | from draw import BOX_UNIT 3 | from math import floor 4 | from wx import Point 5 | 6 | ONE_DECIMAL_TOL = 0.5 7 | TWO_DECIMAL_TOL = 0.15 8 | THREE_DECIMAL_TOL = 0.05 9 | LTCURVE_LIMIT = 400 10 | 11 | 12 | class Dimension: 13 | def __init__(self, figure_type, page, string, label, x1, y1, x2, y2, labX, labY, nom, tol, copy=1): 14 | self.type = figure_type 15 | self.page_number = page 16 | self.string = string 17 | self.label = label 18 | self.left = x1 19 | self.top = y1 20 | self.right = x2 21 | self.bottom = y2 22 | self.label_x = labX 23 | self.label_y = labY 24 | self.nominal = nom 25 | self.tolerance = tol 26 | self.copies = copy 27 | 28 | @classmethod 29 | def file_input(cls, figure_type): 30 | return cls(figure_type, 0, "", "", 0, 0, 0, 0, 0, 0, "", "", 1) 31 | 32 | @classmethod 33 | def copy_dim(cls, dim): 34 | return cls(dim.type, dim.page_number, "", "", dim.left, str(float(dim.top)-BOX_UNIT), dim.right, str(float(dim.bottom)-BOX_UNIT), 0, 0, "", "", 1) 35 | 36 | def string_parser(self): 37 | if "±" in self.string: 38 | separator = self.string.index("±") 39 | self.nominal = self.string[0:separator] 40 | self.tolerance = self.string[separator+1:] 41 | elif re.search('[A-E]', self.string): 42 | self.nominal = re.sub("[^0123456789\.]", "", self.string) 43 | self.tolerance = "-" 44 | elif "°" in self.string: 45 | self.nominal = self.string 46 | self.tolerance = '0.5°' 47 | elif "." in self.string: 48 | self.nominal = re.sub("[^0123456789\.]", "", self.string) 49 | decimals = len(self.nominal) - self.nominal.index(".") - 1 50 | if decimals == 1: 51 | self.tolerance = ONE_DECIMAL_TOL 52 | elif decimals == 2: 53 | self.tolerance = TWO_DECIMAL_TOL 54 | elif decimals == 3: 55 | self.tolerance = THREE_DECIMAL_TOL 56 | else: 57 | return 58 | else: 59 | raise SystemExit("Cancelled: string parsing error occurred") 60 | 61 | def validate_dimension(self): 62 | if self.type == "LTTextBoxHorizontal" or self.type == "LTTextBoxVertical": # If a text box 63 | if has_numbers(self.string): # If contains numbers 64 | if re.search('[a-zE-QS-WY-Z]', self.string): 65 | return 0 66 | self.string = re.sub("[^0123456789ABCDEX±°\.]", "", self.string) 67 | if self.string == "": 68 | return 0 69 | elif self.string[-1] == ".": 70 | return 0 71 | elif "0.5" in self.string and "0.15" in self.string and "0.05" in self.string: 72 | return 0 73 | elif "X" in self.string: 74 | if "." in self.string: 75 | self.string = self.string.lstrip() 76 | self.copies = int(self.string[0]) 77 | self.string = self.string[self.string.index("X")+1:] 78 | return 1 79 | else: 80 | number = int(self.string[len(self.string) - len(self.string.lstrip())]) 81 | return number # Return number of dims 82 | elif "." in self.string: 83 | return 1 # Return 1 dim 84 | elif "±" in self.string: 85 | return 1 86 | elif "°" in self.string: 87 | return 1 88 | else: 89 | return 0 90 | else: 91 | return 0 92 | elif self.type == "LTLine" or self.type == "LTFigure" or (abs(float(self.top) - float(self.bottom)) < LTCURVE_LIMIT and abs(float(self.left) - float(self.right)) < LTCURVE_LIMIT) : 93 | return 0 94 | else: 95 | return -1 # Return -1 for invalid dimension 96 | 97 | 98 | def file_input(p_file, autoMode, dim_array=[], objects=[], mapped_matrix=[]): 99 | page_no = False 100 | coordinate = False 101 | next_copy = False 102 | 103 | for line in p_file: # inputs file into array of Dimension objects 104 | line = line.strip() 105 | if page_no: 106 | page_no = False 107 | coordinate = True 108 | dim_array[-1].page_number = int(line) 109 | elif coordinate: 110 | coordinate = False 111 | line = line.strip("( )") 112 | dim_array[-1].left, dim_array[-1].top, dim_array[-1].right, dim_array[-1].bottom = line.split(",") 113 | elif "LTText" in line or "LTLine" in line or "LTFigure" in line or "LTCurve" in line: 114 | dim = Dimension.file_input(line) 115 | dim_array.append(dim) 116 | page_no = True 117 | next_copy = False 118 | elif next_copy: 119 | dim = Dimension.copy_dim(dim_array[-1]) 120 | dim.string = line 121 | dim_array.append(dim) 122 | elif line == "\n": 123 | continue 124 | else: 125 | dim_array[-1].string = line 126 | next_copy = True 127 | 128 | copy_number = 1 129 | for dimension in dim_array[:]: 130 | number = dimension.validate_dimension() 131 | if number > -1 and autoMode: 132 | x = floor(float(dimension.left)/BOX_UNIT) 133 | x_limit = floor(float(dimension.right)/BOX_UNIT) 134 | y_limit = floor(float(dimension.bottom)/BOX_UNIT) 135 | while x <= x_limit: 136 | y = floor(float(dimension.top)/BOX_UNIT) 137 | while y <= y_limit: 138 | try: 139 | mapped_matrix[x][y] = True 140 | print("True") 141 | except IndexError: 142 | print("Index error: ", x, ", ", y) 143 | y += 1 144 | x += 1 145 | if number == -1: 146 | dim_array.remove(dimension) 147 | elif number == 0: # lines or text 148 | objects.append(dimension) 149 | dim_array.remove(dimension) 150 | elif copy_number > 1: 151 | dimension.copies = copy_number 152 | dimension.string_parser() 153 | copy_number = 1 154 | elif number == 1: # dimension 155 | dimension.string_parser() 156 | else: # 2X 157 | copy_number = number 158 | objects.append(dimension) 159 | dim_array.remove(dimension) 160 | 161 | 162 | def has_numbers(input_string): 163 | return any(char.isdigit() for char in input_string) 164 | 165 | 166 | -------------------------------------------------------------------------------- /dist/wxGUI.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Benjamin-Hu/Engineering-Drawing-Parser/c1de977d8ab16b84e9291d0b5d00e48ecf49a893/dist/wxGUI.zip -------------------------------------------------------------------------------- /draw.py: -------------------------------------------------------------------------------- 1 | from string import ascii_uppercase 2 | from reportlab.lib import colors 3 | from reportlab.lib.pagesizes import letter 4 | from reportlab.platypus import Table, TableStyle 5 | from reportlab.pdfgen import canvas 6 | from PyPDF2 import PdfFileWriter, PdfFileReader 7 | import io 8 | 9 | BOX_UNIT = 12 10 | 11 | 12 | def pdf_label(string, file, text_width, text_height, x, y, page_width, page_height): 13 | file.saveState() 14 | data = [[string]] 15 | t = Table(data, 1 * [text_width], 1 * [text_height]) 16 | t.setStyle(TableStyle([('ALIGN', (0, 0), (-1, -1), 'CENTER'), 17 | ('VALIGN', (0, 0), (-1, -1), 'MIDDLE'), 18 | ('TEXTCOLOR', (0, 0), (-1, -1), colors.red), 19 | ('INNERGRID', (0, 0), (-1, -1), 0.25, colors.red), 20 | ('BOX', (0, 0), (-1, -1), 0.25, colors.red), 21 | ])) 22 | 23 | t.wrapOn(file, page_width, page_height) 24 | t.drawOn(file, x, y) 25 | file.restoreState() 26 | 27 | 28 | def empty_space(array, figure, dim_x, dim_y, bubble_width, bubble_height): 29 | bubble_x1 = 1 30 | bubble_y1 = 1 31 | bubble_x2 = -1 32 | bubble_y2 = -1 33 | if figure == "LTTextBoxHorizontal": 34 | while bubble_x1 < 10: 35 | try: 36 | if array[dim_x + bubble_x1][dim_y] == False: 37 | if bubble_width == 1: 38 | array[dim_x + bubble_x1][dim_y] = True 39 | return dim_x + bubble_x1, dim_y 40 | elif bubble_width == 3 and array[dim_x + bubble_x1 + 1][dim_y] == False and array[dim_x + bubble_x1 + 2][dim_y] == False: 41 | array[dim_x + bubble_x1][dim_y] = True 42 | return dim_x + bubble_x1, dim_y 43 | if array[dim_x + bubble_x2][dim_y] == False: 44 | if bubble_width == 1: 45 | array[dim_x + bubble_x2][dim_y] = True 46 | return dim_x + bubble_x2, dim_y 47 | elif bubble_width == 3 and array[dim_x + bubble_x2 + 1][dim_y] == False and array[dim_x + bubble_x2 + 2][dim_y] == False: 48 | array[dim_x + bubble_x2][dim_y] = True 49 | return dim_x + bubble_x2, dim_y 50 | except IndexError: 51 | print("Index out of range") 52 | bubble_x1 += 1 53 | bubble_x2 -= 1 54 | return dim_x, dim_y 55 | elif figure == "LTTextBoxVertical": 56 | while bubble_y1 < 10: 57 | try: 58 | if array[dim_x][dim_y + bubble_y1] == False: 59 | if bubble_width == 1: 60 | array[dim_x][dim_y + bubble_y1] = True 61 | return dim_x, dim_y + bubble_y1 62 | elif bubble_width == 3 and array[dim_x + 1][dim_y + bubble_y1] == False and array[dim_x + 2][dim_y + bubble_y1] == False: 63 | array[dim_x][dim_y + bubble_y1] = True 64 | return dim_x, dim_y + bubble_y1 65 | if array[dim_x][dim_y + bubble_y2] == False: 66 | if bubble_width == 1: 67 | array[dim_x][dim_y + bubble_y2] = True 68 | return dim_x, dim_y + bubble_y2 69 | elif bubble_width == 3 and array[dim_x + 1][dim_y + bubble_y2] == False and array[dim_x + 2][dim_y + bubble_y2] == False: 70 | array[dim_x][dim_y + bubble_y2] = True 71 | return dim_x, dim_y + bubble_y2 72 | except IndexError: 73 | print("Index out of range") 74 | bubble_y1 += 1 75 | bubble_y2 -= 1 76 | return dim_x, dim_y 77 | else: 78 | return dim_x, dim_y 79 | 80 | 81 | def print_dims(dim_array, pdf_file, csv, outputStream): 82 | counter = 1 83 | packet = io.BytesIO() 84 | width, height = letter 85 | last_page = 1 86 | # create a new PDF with Reportlab 87 | c = canvas.Canvas(packet, pagesize=letter) 88 | 89 | for dimension1 in dim_array: 90 | while dimension1.page_number > last_page: 91 | c.showPage() # Ends current page and starts a new one 92 | last_page += 1 93 | if dimension1.copies == 1: 94 | dimension1.label = str(counter) 95 | pdf_label(dimension1.label, c, BOX_UNIT, BOX_UNIT, dimension1.label_x, dimension1.label_y, width, height) 96 | csv.write((str(dimension1.label) + "," + str(dimension1.nominal) + "," + str(dimension1.tolerance)).strip()) 97 | csv.write("\n") 98 | elif dimension1.copies > 1: 99 | duplicate = 0 100 | pdf_label(str(counter) + "A-" + ascii_uppercase[dimension1.copies-1], c, BOX_UNIT*3, BOX_UNIT, dimension1.label_x - BOX_UNIT*1.5, dimension1.label_y, width, height) 101 | while duplicate < dimension1.copies: 102 | dimension1.label = str(counter) + ascii_uppercase[duplicate] 103 | csv.write((str(dimension1.label) + "," + str(dimension1.nominal) + "," + str(dimension1.tolerance)).strip()) 104 | csv.write("\n") 105 | duplicate += 1 106 | counter += 1 107 | last_page = dimension1.page_number 108 | c.save() 109 | # move to the beginning of the StringIO buffer 110 | packet.seek(0) 111 | new_pdf = PdfFileReader(packet) 112 | # read your existing PDF 113 | existing_pdf = PdfFileReader(pdf_file) 114 | max_page = existing_pdf.getNumPages() 115 | output = PdfFileWriter() 116 | page_no = 0 117 | while page_no < max_page: 118 | # add the "watermark" (which is the new pdf) on the existing page 119 | page = existing_pdf.getPage(page_no) 120 | try: 121 | page.mergePage(new_pdf.getPage(page_no)) 122 | except: 123 | print("No further dimensions found") 124 | output.addPage(page) 125 | page_no += 1 126 | # finally, write "output" to a real file 127 | output.write(outputStream) 128 | 129 | -------------------------------------------------------------------------------- /icon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Benjamin-Hu/Engineering-Drawing-Parser/c1de977d8ab16b84e9291d0b5d00e48ecf49a893/icon.ico -------------------------------------------------------------------------------- /logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Benjamin-Hu/Engineering-Drawing-Parser/c1de977d8ab16b84e9291d0b5d00e48ecf49a893/logo.png -------------------------------------------------------------------------------- /parserFunction.py: -------------------------------------------------------------------------------- 1 | from pdfminer.pdfdocument import PDFDocument 2 | from pdfminer.pdfpage import PDFPage 3 | from pdfminer.pdfparser import PDFParser 4 | from pdfminer.pdfinterp import PDFResourceManager, PDFPageInterpreter 5 | from pdfminer.converter import PDFPageAggregator 6 | from pdfminer.layout import LAParams, LTTextBox, LTTextBoxVertical, LTTextBoxHorizontal 7 | import math 8 | 9 | 10 | def parse_layout(page, layout, output): 11 | # Function to parse layout 12 | for lt_obj in layout: 13 | output.write(lt_obj.__class__.__name__) # Print obj name 14 | output.write("\n") # Print new line 15 | output.write(str(page)) # Print page number 16 | output.write("\n") 17 | output.write(str(lt_obj.bbox)) # Print box coords 18 | output.write("\n") # Print new line 19 | if isinstance(lt_obj, LTTextBoxVertical): # Only print text boxes 20 | output.write(lt_obj.get_text()) # Print text 21 | output.write("\n") # Print new line 22 | elif isinstance(lt_obj, LTTextBoxHorizontal): # Only print text boxes 23 | output.write(lt_obj.get_text()) # Print text 24 | output.write("\n") # Print new line 25 | 26 | 27 | def output_txt(file_path, save_file): 28 | fp = open(file_path, 'rb') # Open specified file 29 | parser = PDFParser(fp) # Use PDFParser 30 | doc = PDFDocument(parser) # Use PDFDocument 31 | rsrcmgr = PDFResourceManager() # ??? 32 | laparams = LAParams(1.0/1e256, 2.0, 1.0/1e256, 0.1, 0.5, True, True) # Sets parameters for parsing 33 | device = PDFPageAggregator(rsrcmgr, laparams=laparams) 34 | interpreter = PDFPageInterpreter(rsrcmgr, device) 35 | page_number = 1 36 | for page in PDFPage.create_pages(doc): 37 | sizeArray = page.mediabox 38 | page_width = sizeArray[2] 39 | page_height = sizeArray[3] 40 | print(page_width, page_height) 41 | interpreter.process_page(page) 42 | layout = device.get_result() 43 | parse_layout(page_number, layout, save_file) 44 | page_number += 1 45 | return page_width, page_height 46 | -------------------------------------------------------------------------------- /wxGUI.py: -------------------------------------------------------------------------------- 1 | # run.py forked from wxPython 2 | 3 | import wx 4 | import sys 5 | import os 6 | from wx.lib.pdfviewer import pdfViewer, pdfButtonPanel 7 | import wx.adv 8 | import parserFunction 9 | import dimensionFilter 10 | import draw 11 | import math 12 | 13 | 14 | class Point: 15 | def __init__(self, x, y, page=0): 16 | self.x = x 17 | self.y = y 18 | self.page = page 19 | 20 | # ---------------------------------------------------------------------- 21 | 22 | 23 | class menuPanel(wx.Panel): 24 | def __init__(self, parent): 25 | # Setting up panel and sizers 26 | wx.Panel.__init__(self, parent, -1) 27 | vsizer = wx.BoxSizer(wx.VERTICAL) 28 | hsizer = wx.BoxSizer(wx.HORIZONTAL) 29 | 30 | # Adding relevant buttons and images 31 | image = wx.Image('logo.png', wx.BITMAP_TYPE_ANY) 32 | image.Rescale(200, 200, wx.IMAGE_QUALITY_HIGH) 33 | imageBitmap = wx.StaticBitmap(self, wx.ID_ANY, wx.Bitmap(image)) 34 | vsizer.Add(imageBitmap, 0, wx.ALIGN_CENTER | wx.ALL, 5) 35 | 36 | lineDivider = wx.StaticLine(self, id=wx.ID_ANY, pos=wx.DefaultPosition, size=wx.DefaultSize, style=wx.LI_HORIZONTAL) 37 | vsizer.Add(lineDivider, 0, wx.ALL | wx.EXPAND, 5) 38 | 39 | self.parseButton = wx.Button(self, wx.ID_ANY, "Parse a Bubbled Drawing", wx.DefaultPosition, wx.DefaultSize, 0 ) 40 | vsizer.Add(self.parseButton, 0, wx.ALIGN_CENTER | wx.ALL, 5) 41 | 42 | self.manualButton = wx.Button(self, wx.ID_ANY, "Manually Bubble a Drawing", wx.DefaultPosition, wx.DefaultSize, 0) 43 | vsizer.Add(self.manualButton, 0, wx.ALIGN_CENTER | wx.ALL, 5) 44 | 45 | self.autoButton = wx.Button(self, wx.ID_ANY, "Automatically Bubble a Drawing", wx.DefaultPosition, wx.DefaultSize, 0) 46 | vsizer.Add(self.autoButton, 0, wx.ALIGN_CENTER | wx.ALL, 5) 47 | 48 | self.aboutButton = wx.Button(self, wx.ID_ANY, "About", wx.DefaultPosition, wx.DefaultSize, 0) 49 | vsizer.Add(self.aboutButton, 0, wx.ALIGN_CENTER|wx.ALL, 5) 50 | 51 | # Completing sizing setup 52 | hsizer.Add(vsizer, 1, wx.GROW|wx.ALIGN_CENTER_HORIZONTAL|wx.ALL, 5) 53 | self.SetSizer(hsizer) 54 | self.SetAutoLayout(True) 55 | 56 | # ---------------------------------------------------------------------- 57 | 58 | 59 | class viewerPanel(wx.Panel): 60 | def __init__(self, parent): 61 | # Setting up panel and sizers 62 | wx.Panel.__init__(self, parent, -1) 63 | self.hsizer = wx.BoxSizer(wx.HORIZONTAL) 64 | self.vsizer = wx.BoxSizer(wx.VERTICAL) 65 | 66 | # Special wxpython PDF button panel 67 | self.buttonpanel = pdfButtonPanel(self, wx.ID_ANY, wx.DefaultPosition, wx.DefaultSize, 0) 68 | 69 | # PDF viewer 70 | self.viewer = pdfViewer(self,wx.ID_ANY,wx.DefaultPosition,wx.DefaultSize,wx.HSCROLL|wx.VSCROLL|wx.SUNKEN_BORDER) 71 | 72 | # Adding button panel and viewer to vertical sizer 73 | self.vsizer.Add(self.buttonpanel, 0, wx.GROW|wx.ALIGN_CENTER_VERTICAL|wx.LEFT|wx.RIGHT|wx.TOP, 5) 74 | self.vsizer.Add(self.viewer, 1, wx.GROW|wx.LEFT|wx.RIGHT|wx.BOTTOM, 5) 75 | 76 | # Adding relevant buttons (or none at all) 77 | self.buttonSizerTop = wx.BoxSizer(wx.HORIZONTAL) 78 | self.addbutton = wx.Button(self, wx.ID_ANY, "Add Dimension", wx.DefaultPosition, wx.DefaultSize, 0) 79 | self.buttonSizerTop.Add(self.addbutton, 0, wx.ALIGN_CENTER | wx.ALL, 5) 80 | self.removebutton = wx.Button(self, wx.ID_ANY, "Remove Dimension", wx.DefaultPosition, wx.DefaultSize, 0) 81 | self.buttonSizerTop.Add(self.removebutton, 0, wx.ALIGN_CENTER | wx.ALL, 5) 82 | self.movebutton = wx.Button(self, wx.ID_ANY, "Move Dimension", wx.DefaultPosition, wx.DefaultSize, 0) 83 | self.buttonSizerTop.Add(self.movebutton, 0, wx.ALL | wx.ALIGN_CENTER, 5) 84 | self.refreshbutton = wx.Button(self, wx.ID_ANY, "Apply Changes", wx.DefaultPosition, wx.DefaultSize, 0) 85 | self.buttonSizerTop.Add(self.refreshbutton, 0, wx.ALL | wx.ALIGN_CENTER, 5) 86 | self.vsizer.Add(self.buttonSizerTop, 0, wx.ALIGN_CENTER_HORIZONTAL, 5) 87 | 88 | self.buttonSizerBottom = wx.BoxSizer(wx.HORIZONTAL) 89 | self.backbutton = wx.Button(self, wx.ID_ANY, "Back", wx.DefaultPosition, wx.DefaultSize, 0) 90 | self.buttonSizerBottom.Add(self.backbutton, 0, wx.ALL | wx.ALIGN_LEFT, 5) 91 | self.buttonSizerBottom.Add(2000,0,1) # Proportionate sizer that keeps two buttons apart 92 | self.finishbutton = wx.Button(self, wx.ID_ANY, "Finish", wx.DefaultPosition, wx.DefaultSize, 0) 93 | self.buttonSizerBottom.Add(self.finishbutton, 0, wx.ALL | wx.ALIGN_RIGHT, 5) 94 | self.vsizer.Add(self.buttonSizerBottom, 0, wx.ALIGN_CENTER_HORIZONTAL, 5) 95 | 96 | # Completing sizing setup 97 | self.hsizer.Add(self.vsizer, 1, wx.GROW|wx.ALIGN_CENTER_HORIZONTAL|wx.ALL, 5) 98 | self.SetSizer(self.hsizer) 99 | self.SetAutoLayout(True) 100 | 101 | # Introduce button panel and viewer to each other 102 | self.buttonpanel.viewer = self.viewer 103 | self.viewer.buttonpanel = self.buttonpanel 104 | 105 | # ---------------------------------------------------------------------- 106 | 107 | 108 | class FullFrame(wx.Frame): 109 | def __init__(self): 110 | # Setting up the app 111 | wx.Frame.__init__(self, parent=None, title="Engineering Drawing Parser", size=(1024, 576)) 112 | self.CenterOnScreen() 113 | 114 | # Establishing files 115 | self.created_file = open('parsedPDF.txt', "w+", encoding="utf-8-sig") 116 | self.created_file.truncate(0) 117 | self.csv_text = open('Inspection Standard CSV.txt', 'w') 118 | self.csv_text_path = os.path.abspath('Inspection Standard CSV.txt') 119 | self.bubbled_pdf = None 120 | self.bubbled_pdf_path = os.path.abspath("Result.pdf") 121 | 122 | # Establishing parameters 123 | self.validatedDimensions = [] # List of final dimensions 124 | self.possibleDimensions = [] # List of possible dimensions 125 | self.lineObjects = [] # List of other misc objects 126 | self.coordinateArray = None # Mapped array of dimension locations (only needed in autoMode) 127 | self.pageWidth = None 128 | self.pageHeight = None 129 | 130 | # Setting up program icon 131 | icon = wx.Icon() 132 | icon.CopyFromBitmap(wx.Bitmap("icon.ico", wx.BITMAP_TYPE_ANY)) 133 | self.SetIcon(icon) 134 | 135 | # Creating a menu bar and proper window features 136 | self.CreateStatusBar() 137 | menuBar = wx.MenuBar() 138 | menuOption = wx.Menu() 139 | self.SetMenuBar(menuBar) 140 | 141 | menuBar.Append(menuOption, "&File") 142 | aboutMe = menuOption.Append(wx.ID_ANY, "&About") 143 | self.Bind(wx.EVT_MENU, self.OnAboutBox, aboutMe) 144 | mainMenu = menuOption.Append(wx.ID_ANY, "&Main Menu\tEsc") 145 | self.Bind(wx.EVT_MENU, self.OnMainMenu, mainMenu) 146 | exit = menuOption.Append(wx.ID_EXIT, "Exit\tAlt+F4", "Exit Program") 147 | self.Bind(wx.EVT_MENU, self.OnExitApp, exit) 148 | self.Bind(wx.EVT_CLOSE, self.OnCloseFrame) 149 | 150 | # Setting up main menu and viewer panels 151 | self.menu = menuPanel(self) 152 | self.viewPanel = viewerPanel(self) 153 | self.viewPanel.Hide() 154 | 155 | self.sizer = wx.BoxSizer(wx.VERTICAL) 156 | self.sizer.Add(self.menu, 1, wx.EXPAND) 157 | self.sizer.Add(self.viewPanel, 1, wx.EXPAND) 158 | self.SetSizer(self.sizer) 159 | 160 | # Binding relevant buttons from lower level panels 161 | self.fileName = None 162 | self.filePath = None 163 | 164 | self.Bind(wx.EVT_BUTTON, self.OnMainMenu, self.viewPanel.backbutton) 165 | self.Bind(wx.EVT_BUTTON, self.OnFinish, self.viewPanel.finishbutton) 166 | 167 | self.Bind(wx.EVT_BUTTON, self.OnManualButton, self.menu.manualButton) 168 | self.Bind(wx.EVT_BUTTON, self.OnParseButton, self.menu.parseButton) 169 | self.Bind(wx.EVT_BUTTON, self.OnAboutBox, self.menu.aboutButton) 170 | self.addPoints = [] 171 | self.Bind(wx.EVT_BUTTON, self.OnAddButton, self.viewPanel.addbutton) 172 | self.Bind(wx.EVT_BUTTON, self.OnRemoveButton, self.viewPanel.removebutton) 173 | self.Bind(wx.EVT_BUTTON, self.OnMoveButton, self.viewPanel.movebutton) 174 | self.Bind(wx.EVT_BUTTON, self.OnApplyChanges, self.viewPanel.refreshbutton) 175 | 176 | 177 | 178 | def OnExitApp(self, evt): 179 | self.Close(True) 180 | 181 | def OnCloseFrame(self, evt): 182 | if hasattr(self, "window") and hasattr(self.window, "ShutdownDemo"): 183 | self.window.ShutdownDemo() 184 | evt.Skip() 185 | 186 | def OnAboutBox(self, event): 187 | description = """ 188 | The Engineering Drawing Parser is a cross-platform tool 189 | used to interpret bubbled engineering drawings and specifications, 190 | as well as create bubbled drawings. Features include automatic 191 | recognition of dimensions, auto-bubbling and exporting .csv data. 192 | """ 193 | licence = """ 194 | The Engineering Drawing Parser is free software; you can redistribute 195 | it and/or modify it under the terms of the GNU General Public License as 196 | published by the Free Software Foundation. 197 | """ 198 | info = wx.adv.AboutDialogInfo() 199 | info.SetIcon(wx.Icon('logo.png', wx.BITMAP_TYPE_PNG)) 200 | info.SetName('Engineering Drawing Parser') 201 | info.SetVersion('1.0') 202 | info.SetDescription(description) 203 | info.SetWebSite('https://github.com/Benjamin-Hu/Engineering-Drawing-Parser') 204 | info.SetLicence(licence) 205 | info.AddDeveloper('Benjamin Hu') 206 | 207 | wx.adv.AboutBox(info) 208 | 209 | def OnMainMenu(self, event): 210 | self.validatedDimensions = [] # List of final dimensions 211 | self.possibleDimensions = [] # List of possible dimensions 212 | self.lineObjects = [] # List of other misc objects 213 | self.coordinateArray = None # Mapped array of dimension locations (only needed in autoMode) 214 | self.csv_text.truncate(0) 215 | self.viewPanel.Hide() 216 | self.menu.Show() 217 | self.Layout() 218 | 219 | def OnFinish(self, event): 220 | self.csv_text.close() 221 | self.bubbled_pdf.close() 222 | os.startfile(self.csv_text_path) 223 | os.startfile(self.bubbled_pdf_path) 224 | self.Close(True) 225 | 226 | def OnManualButton(self, event): 227 | self.fileName = None 228 | self.filePath = None 229 | 230 | dlg = wx.FileDialog(self.viewPanel, message="Select an Engineering Drawing", wildcard=r"*.pdf") 231 | if dlg.ShowModal() == wx.ID_OK: 232 | self.filePath = dlg.GetPath() 233 | dlg.Destroy() 234 | if self.filePath is None: 235 | return 236 | 237 | self.fileName = open(self.filePath, 'rb') 238 | self.viewPanel.viewer.LoadFile(self.filePath) 239 | self.viewPanel.addbutton.Show() 240 | self.viewPanel.removebutton.Show() 241 | self.viewPanel.movebutton.Show() 242 | self.viewPanel.refreshbutton.Show() 243 | self.viewPanel.Show() 244 | self.menu.Hide() 245 | self.Layout() 246 | 247 | # Perform backend operations 248 | self.pageWidth, self.pageHeight = parserFunction.output_txt(self.filePath, self.created_file) 249 | self.created_file.seek(0) 250 | dimensionFilter.file_input(self.created_file, False, self.possibleDimensions, self.lineObjects, self.coordinateArray) 251 | 252 | def OnParseButton(self, event, file): 253 | self.fileName = None 254 | self.filePath = None 255 | 256 | dlg = wx.FileDialog(self.viewPanel, message="Select an Engineering Drawing", wildcard=r"*.pdf") 257 | if dlg.ShowModal() == wx.ID_OK: 258 | self.filePath = dlg.GetPath() 259 | dlg.Destroy() 260 | if self.filePath is None: 261 | return 262 | 263 | self.fileName = open(self.filePath, 'rb') 264 | 265 | self.viewPanel.viewer.LoadFile(self.filePath) 266 | self.viewPanel.addbutton.Hide() 267 | self.viewPanel.removebutton.Hide() 268 | self.viewPanel.movebutton.Hide() 269 | self.viewPanel.refreshbutton.Hide() 270 | self.viewPanel.Show() 271 | self.menu.Hide() 272 | self.Layout() 273 | 274 | def OnApplyChanges(self, event): 275 | self.bubbled_pdf = open('Result.pdf', "wb") 276 | self.AddPointsApplier() 277 | self.validatedDimensions = sorted(self.validatedDimensions, key = lambda x: x.page_number) 278 | self.csv_text.truncate(0) 279 | draw.print_dims(self.validatedDimensions, self.fileName, self.csv_text, self.bubbled_pdf) 280 | self.bubbled_pdf.close() 281 | self.viewPanel.viewer.LoadFile(self.bubbled_pdf_path) 282 | 283 | def OnAddButton(self, event): 284 | print("Adding values...") 285 | print(self.viewPanel.viewer.Xpagepixels, self.viewPanel.viewer.Ypagepixels) 286 | 287 | wx.MessageBox("Please click the dimensions you wish to add and click Apply Changes.", "Attention", wx.OK, self) 288 | 289 | self.viewPanel.viewer.Unbind(wx.EVT_LEFT_DOWN) 290 | self.viewPanel.viewer.Bind(wx.EVT_LEFT_DOWN, self.AddLeftClick) 291 | self.viewPanel.viewer.Bind(wx.EVT_RIGHT_DOWN, self.AddRightClick) 292 | 293 | def OnRemoveButton(self, event): 294 | removedIndex = None 295 | dlg = wx.TextEntryDialog(frame, 'Enter the numbered dimension you wish to remove:', 'Remove Dimensions') 296 | if dlg.ShowModal() == wx.ID_OK: 297 | removedIndex = int(dlg.GetValue()) 298 | dlg.Destroy() 299 | try: 300 | self.validatedDimensions.pop(removedIndex - 1) 301 | self.bubbled_pdf = open('Result.pdf', "wb") 302 | draw.print_dims(self.validatedDimensions, self.fileName, self.csv_text, self.bubbled_pdf) 303 | self.bubbled_pdf.close() 304 | self.viewPanel.viewer.LoadFile(self.bubbled_pdf_path) 305 | except: 306 | return 307 | 308 | def OnMoveButton(self, event): 309 | removedIndex = None 310 | dlg = wx.TextEntryDialog(frame, 'Enter the numbered dimension you wish to move:', 'Move Dimensions') 311 | if dlg.ShowModal() == wx.ID_OK: 312 | removedIndex = int(dlg.GetValue()) 313 | dlg.Destroy() 314 | 315 | wx.MessageBox("Please click at the new location of the bubble.", "Attention", wx.OK, self) 316 | 317 | self.viewPanel.viewer.Unbind(wx.EVT_LEFT_DOWN) 318 | self.viewPanel.viewer.Bind(wx.EVT_LEFT_DOWN, lambda event: self.MoveLeftClick(event, removedIndex)) 319 | 320 | def AddLeftClick(self, event): 321 | panel_point = self.viewPanel.viewer.ScreenToClient(wx.GetMousePosition()) 322 | scrolled = self.viewPanel.viewer.CalcUnscrolledPosition(wx.Point(0, 0)) 323 | scrolled.__iadd__(panel_point) 324 | scrolled = wx.RealPoint(scrolled) 325 | if scrolled.x < self.viewPanel.viewer.Xpagepixels and scrolled.y < self.viewPanel.viewer.Ypagepixels*self.viewPanel.viewer.numpages: 326 | page_num = math.floor(scrolled.y/self.viewPanel.viewer.Ypagepixels) + 1 327 | scrolled.x = self.pageWidth*scrolled.x/self.viewPanel.viewer.Xpagepixels 328 | scrolled.y = self.pageHeight - self.pageHeight*(scrolled.y % self.viewPanel.viewer.Ypagepixels)/self.viewPanel.viewer.Ypagepixels 329 | coordinate = Point(scrolled.x, scrolled.y, page_num) 330 | print(coordinate.x, coordinate.y, coordinate.page) 331 | self.addPoints.append(coordinate) 332 | 333 | def AddRightClick(self, event): 334 | self.viewPanel.viewer.Unbind(wx.EVT_LEFT_DOWN) 335 | 336 | def AddPointsApplier(self): 337 | for point in self.addPoints: 338 | minDistance = sys.maxsize 339 | selectedDimension = 0 340 | currentIndex = 0 341 | for dimension in self.possibleDimensions: 342 | if point.page != dimension.page_number: 343 | currentIndex += 1 344 | continue 345 | dist1 = math.hypot(point.x - float(dimension.left), point.y - float(dimension.top)) 346 | dist2 = math.hypot(point.x - float(dimension.left), point.y - float(dimension.bottom)) 347 | dist3 = math.hypot(point.x - float(dimension.right), point.y - float(dimension.top)) 348 | dist4 = math.hypot(point.x - float(dimension.right), point.y - float(dimension.bottom)) 349 | closestDist = min(dist1, dist2, dist3, dist4) 350 | if minDistance > closestDist: 351 | minDistance = closestDist 352 | selectedDimension = currentIndex 353 | currentIndex += 1 354 | self.possibleDimensions[selectedDimension].label_x = point.x - draw.BOX_UNIT/2 355 | self.possibleDimensions[selectedDimension].label_y = point.y - draw.BOX_UNIT/2 356 | self.validatedDimensions.append(self.possibleDimensions[selectedDimension]) 357 | self.possibleDimensions.pop(selectedDimension) 358 | self.addPoints.clear() 359 | 360 | def MoveLeftClick(self, event, index): 361 | panel_point = self.viewPanel.viewer.ScreenToClient(wx.GetMousePosition()) 362 | scrolled = self.viewPanel.viewer.CalcUnscrolledPosition(wx.Point(0, 0)) 363 | scrolled.__iadd__(panel_point) 364 | scrolled = wx.RealPoint(scrolled) 365 | if scrolled.x < self.viewPanel.viewer.Xpagepixels and scrolled.y < self.viewPanel.viewer.Ypagepixels * self.viewPanel.viewer.numpages: 366 | scrolled.x = self.pageWidth * scrolled.x / self.viewPanel.viewer.Xpagepixels 367 | scrolled.y = self.pageHeight - self.pageHeight * (scrolled.y % self.viewPanel.viewer.Ypagepixels) / self.viewPanel.viewer.Ypagepixels 368 | coordinate = Point(scrolled.x, scrolled.y) 369 | try: 370 | self.validatedDimensions[index - 1].label_x = coordinate.x - draw.BOX_UNIT/2 371 | self.validatedDimensions[index - 1].label_y = coordinate.y - draw.BOX_UNIT/2 372 | self.bubbled_pdf = open('Result.pdf', "wb") 373 | draw.print_dims(self.validatedDimensions, self.fileName, self.csv_text, self.bubbled_pdf) 374 | self.bubbled_pdf.close() 375 | self.viewPanel.viewer.LoadFile(self.bubbled_pdf_path) 376 | except: 377 | return 378 | 379 | 380 | # ---------------------------------------------------------------------- 381 | 382 | 383 | # Print versions for debugging purposes 384 | print("Python %s" % sys.version) 385 | print("wx.version: %s" % wx.version()) 386 | 387 | app = wx.App(False) 388 | frame = FullFrame() 389 | frame.Show() 390 | app.MainLoop() --------------------------------------------------------------------------------