프로젝트

일반

사용자정보

개정판 0a9e44a7

ID0a9e44a70b9620527f296a60c71ab4ecd36114a6
상위 fc72e116
하위 4d73f27f

백흠경이(가) 6년 이상 전에 추가함

fixed issue #629:
- 프로그램 실행시 발생하는 로그 표시
- 로그 삭제 기능

차이점 보기:

DTI_PID/DTI_PID/App.py
7 7
from PyQt5.QtGui import *
8 8
from PyQt5.QtWidgets import *
9 9
from PyQt5.QtSvg import *
10
from PyQt5 import QtWidgets
10 11

  
11 12
from AppDocData import AppDocData
12 13

  
......
17 18
        self.setStyle(appStyle)
18 19
        self.loadStyleSheet(os.path.dirname(os.path.realpath(__file__)) + '\\coffee')
19 20

  
20
        self.mainWnd = None
21
        self._mainWnd = None
22

  
23
        QtWidgets.qApp = self
21 24

  
22 25
    '''
23 26
        @brief  load application style sheet
......
36 39
        finally:
37 40
            file.close()
38 41

  
42
    '''
43
        @brief      create hmb data from database record
44
        @author     humkyung
45
        @date       2018.07.12
46
    '''
47
    @staticmethod
48
    def mainWnd():
49
        return QtWidgets.qApp._mainWnd 
50

  
39 51
'''
40 52
    @history    18.04.23    Jeongwoo    Change method to execute ProjectDialog(dlg.exec_()→dlg.showDialog())
41 53
'''
......
56 68
        selectedProject = dlg.showDialog()
57 69
        if selectedProject is not None:
58 70
            AppDocData.instance().setCurrentProject(selectedProject)
59
            app.mainWnd = MainWindow.instance()
60
            app.mainWnd.show()
71
            app._mainWnd = MainWindow.instance()
72
            app._mainWnd.show()
61 73
    except Exception as ex:
62 74
        print('에러가 발생했습니다.\n', ex)
63 75

  
DTI_PID/DTI_PID/AppDocData.py
2 2

  
3 3
import sys
4 4
import os
5
from PyQt5.QtCore import *
6 5
import sqlite3
7 6
import datetime
8 7
from PIL import PngImagePlugin, JpegImagePlugin
......
10 9
from PIL.ImageQt import ImageQt
11 10

  
12 11
try:
13
    from PyQt5.QtCore import QBuffer
14
    from PyQt5.QtGui import QImage, QPixmap
12
    from PyQt5.QtCore import *
13
    from PyQt5.QtGui import *
14
    from PyQt5 import QtWidgets
15 15
except ImportError:
16
    from PyQt4.QtCore import QBuffer
17
    from PyQt4.QtGui import QImage, QPixmap
16
    from PyQt4.QtCore import *
17
    from PyQt4.QtGui import *
18 18
import numpy as np
19
from enum import Enum
19 20

  
20 21
from SingletonInstance import SingletonInstane
21 22
import Project
......
123 124
        self._property = _property
124 125
        self.value = value
125 126

  
127
'''
128
    @brief      MessageType
129
    @author     humkyung 
130
    @date       2018.07.31
131
'''
132
class MessageType(Enum):
133
    Normal = 1
134
    Error = 2
135

  
126 136
class AppDocData(SingletonInstane):
127 137
    def __init__(self):
128 138
        self._imgFilePath = None
......
177 187
            except Exception as ex:
178 188
                # Roll back any change if something goes wrong
179 189
                conn.rollback()
180
                print('error occured({}) in {}:{}'.format(ex, sys.exc_info()[-1].tb_frame.f_code.co_filename, sys.exc_info()[-1].tb_lineno))
190

  
191
                from App import App 
192
                message = 'error occured({}) in {}:{}'.format(ex, sys.exc_info()[-1].tb_frame.f_code.co_filename, sys.exc_info()[-1].tb_lineno)
193
                App.mainWnd().addMessage(MessageType.Error, message)
181 194
            finally:
182 195
                conn.close()
183 196

  
DTI_PID/DTI_PID/DTI_PID.py
20 20
import XmlGenerator as xg
21 21
import pytesseract
22 22
import tesseract_ocr_module as TOCR
23
import potrace
24 23
import sys
25 24
from PyQt5.QtCore import *
26 25
from PyQt5.QtGui import *
27 26
from PyQt5.QtWidgets import *
28 27
from PyQt5.QtSvg import *
29 28
import QtImageViewer
29
from MainWindow import MainWindow
30
from AppDocData import AppDocData
30 31

  
31 32
sys.path.insert(0, os.path.dirname(os.path.realpath(__file__)) + '\\Commands')
32 33
import CreateCommand
......
37 38
from EngineeringLineItem import QEngineeringLineItem
38 39
from SymbolSvgItem import SymbolSvgItem
39 40
from QGraphicsBoundingBoxItem import QGraphicsBoundingBoxItem
40
from AppDocData import AppDocData
41
#endregion
41
from AppDocData import *
42 42

  
43 43
## Tesseract path
44 44
pytesseract.pytesseract.tesseract_cmd = os.path.join(os.environ['TESSERACT_HOME'], 'tesseract.exe')
......
276 276

  
277 277
    matchCount = count
278 278

  
279
    #print("match Count : " + str(matchCount))
280 279
    return matchCount
281 280

  
282 281
'''
......
298 297
                Jeongwoo 2018.06.27 Remove part to split P&ID image and for loop
299 298
                humkyung 2018.07.07 return searched symbols
300 299
'''
301
def detectSymbolOnPid(mainRes, targetSymbol, listWidget, updateProgressSignal):
300
def detectSymbolOnPid(mainRes, targetSymbol, listWidget, worker):
302 301
    global ocrCompletedSrc
303
    global afterDenoising
304 302
    global threadLock
305 303
    global searchedSymbolList
306 304
    global maxProgressValue
......
446 444
                                                                    , isDetectOnOrigin, symbolRotateCount, symbolOcrOption, isContainChild
447 445
                                                                    , ','.join(str(x) for x in originalPoint), '/'.join('{},{}'.format(x[0],x[1]) for x in connectionPoint), baseSymbol, additionalSymbol,isExceptDetect)
448 446
                                ## DEBUG
449
                                #print('//// {}:{}-{} ////'.format(symbolName, searchedItemSp, hitRate))
447
                                ##message = '//// {}:{}-{} ////'.format(symbolName, searchedItemSp, hitRate)
448
                                ##worker.displayLog.emit(MessageType.Normal, message)
450 449
                                ## up to here
451 450

  
452 451
                                threadLock.release()
453 452
                        ## 현재 심볼과 검출된 심볼이 같지 않을 경우 (포함)
454 453
                        elif appDocData.isEquipmentType(searchedSymbol.getType()):
455 454
                            ## DEBUG
456
                            print('{}->{}:{}-{}'.format(searchedSymbol.getName(), symbolName, searchedItemSp, hitRate))
455
                            message = '{}->{}:{}-{}'.format(searchedSymbol.getName(), symbolName, searchedItemSp, hitRate)
456
                            worker.displayLog.emit(MessageType.Normal, message)
457 457
                            ## up to here
458 458

  
459 459
                            threadLock.acquire()
......
475 475
        listWidget.addItem('Found Symbol   : ' + os.path.splitext(os.path.basename(symbolPath))[0] + ' - (' + str(foundSymbolCount) + ')')
476 476
        threadLock.release()
477 477

  
478
        updateProgressSignal.emit(maxProgressValue)
478
        worker.updateProgress.emit(maxProgressValue)
479 479

  
480 480
        return [symbol for symbol in searchedSymbolList if symbol.getName() == symbolName]
481 481
    except Exception as ex:
482
        print('error occured({}) in {}:{}'.format(ex, sys.exc_info()[-1].tb_frame.f_code.co_filename, sys.exc_info()[-1].tb_lineno))
482
        message = 'error occured({}) in {}:{}'.format(ex, sys.exc_info()[-1].tb_frame.f_code.co_filename, sys.exc_info()[-1].tb_lineno)
483
        worker.displayLog.emit(MessageType.Error, message)
483 484
    
484 485
    return []
485 486

  
......
489 490
    @date       2018.07.07
490 491
    @history    humkyhung 2018.07.17 pass equpment as parameter instead of image
491 492
'''
492
def detectNozzleOnPid(equipment, nozzle, listWidget, updateProgressSignal):
493
def detectNozzleOnPid(equipment, nozzle, listWidget, worker):
493 494
    global src
494 495
    global threadLock
495 496
    global searchedSymbolList
......
645 646
        listWidget.addItem('Found Symbol   : ' + os.path.splitext(os.path.basename(symbolPath))[0] + ' - (' + str(foundSymbolCount) + ')')
646 647
        threadLock.release()
647 648

  
648
        updateProgressSignal.emit(maxProgressValue)
649
        worker.updateProgress.emit(maxProgressValue)
649 650

  
650 651
        return [symbol for symbol in searchedSymbolList if symbol.getName() == symbolName]
651 652
    except Exception as ex:
652
        print('error occured({}) in {}:{}'.format(ex, sys.exc_info()[-1].tb_frame.f_code.co_filename, sys.exc_info()[-1].tb_lineno))
653
        message = 'error occured({}) in {}:{}'.format(ex, sys.exc_info()[-1].tb_frame.f_code.co_filename, sys.exc_info()[-1].tb_lineno)
654
        worker.displayLog(MessageType.Error, message)
653 655

  
654 656
    return []
655 657

  
......
658 660
    @author humkyung
659 661
    @date   2018.07.07
660 662
'''
661
def detectEquipmentOnPid(mainRes, targetSymbol, listWidget, updateProgressSignal):
663
def detectEquipmentOnPid(mainRes, targetSymbol, listWidget, worker):
662 664
    try:
663
        equipments = detectSymbolOnPid(mainRes, targetSymbol, listWidget, updateProgressSignal)
665
        equipments = detectSymbolOnPid(mainRes, targetSymbol, listWidget, worker)
664 666
        for equipment in equipments:
665 667
            # detect nozzles around equimpent
666 668
            for nozzle in targetSymbolList[1]:
667
                detectNozzleOnPid(equipment, nozzle, listWidget, updateProgressSignal)
669
                detectNozzleOnPid(equipment, nozzle, listWidget, worker)
668 670
            # up to here
669 671
    except Exception as ex:
670
        print('error occured({}) in {}:{}'.format(ex, sys.exc_info()[-1].tb_frame.f_code.co_filename, sys.exc_info()[-1].tb_lineno))
672
        message = 'error occured({}) in {}:{}'.format(ex, sys.exc_info()[-1].tb_frame.f_code.co_filename, sys.exc_info()[-1].tb_lineno)
673
        worker.displayLog(MessageType.Error, message)
671 674
            
672 675
'''
673 676
    @history    2018.05.17  Jeongwoo    Bitwise_not target changed (Original Image → Symbol Image)
......
758 761

  
759 762
        cv2.imwrite(os.path.join(project.getTempPath(), "FOUND_" + os.path.basename(drawingPath)), canvas)
760 763
    except Exception as ex:
761
        print('error occured({}) in {}:{}'.format(ex, sys.exc_info()[-1].tb_frame.f_code.co_filename, sys.exc_info()[-1].tb_lineno))
764
        message = 'error occured({}) in {}:{}'.format(ex, sys.exc_info()[-1].tb_frame.f_code.co_filename, sys.exc_info()[-1].tb_lineno)
765
        MainWindow.instance().displayMessage.emit(MessageType.Error, message)
762 766
        
763 767
'''
764 768
    @history    2018.04.24  Jeongwoo    Add isExceptDetect Field
......
801 805
            if area > minArea and area < maxArea: selectedContours.append(contour)
802 806
        contourImage = cv2.drawContours(image, selectedContours, -1, (255,255,255), -1); # draw contour with white color
803 807
    except Exception as ex:
804
        print('error occured({}) in {}:{}'.format(ex, sys.exc_info()[-1].tb_frame.f_code.co_filename, sys.exc_info()[-1].tb_lineno))
808
        message = 'error occured({}) in {}:{}'.format(ex, sys.exc_info()[-1].tb_frame.f_code.co_filename, sys.exc_info()[-1].tb_lineno)
809
        MainWindow.instance().displayMessage.emit(MessageType.Error, message)
805 810

  
806 811
    return contourImage
807 812

  
......
856 861
                    for symbol in matches:
857 862
                        detector.connectLineToSymbol(line, (round(area.x), round(area.y)), symbol)
858 863
            except Exception as ex:
859
                print('error occured({}) in {}:{}'.format(ex, sys.exc_info()[-1].tb_frame.f_code.co_filename, sys.exc_info()[-1].tb_lineno))
864
                message = 'error occured({}) in {}:{}'.format(ex, sys.exc_info()[-1].tb_frame.f_code.co_filename, sys.exc_info()[-1].tb_lineno)
865
                MainWindow.instance().displayMessage.emit(MessageType.Error, message)
860 866
            # up to here
861 867

  
862 868
            # connect line to line
......
877 883
                    for match in selected:
878 884
                        detector.connectLineToLine(match, line, toler)
879 885
            except Exception as ex:
880
                print('error occured({}) in {}:{}'.format(ex, sys.exc_info()[-1].tb_frame.f_code.co_filename, sys.exc_info()[-1].tb_lineno))
886
                message = 'error occured({}) in {}:{}'.format(ex, sys.exc_info()[-1].tb_frame.f_code.co_filename, sys.exc_info()[-1].tb_lineno)
887
                MainWindow.instance().displayMessage.emit(MessageType.Error, message)
881 888
            # up to here
882 889

  
883 890
        lines = []
......
893 900
        arrangeLinePosition(lines, symbols, listWidget)
894 901
        # up to here
895 902
    except Exception as ex:
896
        print('error occured({}) in {}:{}'.format(ex, sys.exc_info()[-1].tb_frame.f_code.co_filename, sys.exc_info()[-1].tb_lineno))
903
        message = 'error occured({}) in {}:{}'.format(ex, sys.exc_info()[-1].tb_frame.f_code.co_filename, sys.exc_info()[-1].tb_lineno)
904
        MainWindow.instance().displayMessage.emit(MessageType.Error, message)
897 905
    finally:
898 906
        listWidget.addItem('Finished line recognization')
899 907
    
......
945 953
            # up to here
946 954
        # up to here
947 955
    except Exception as ex:
948
        print('error occured({}) in {}:{}'.format(ex, sys.exc_info()[-1].tb_frame.f_code.co_filename, sys.exc_info()[-1].tb_lineno))
956
        message = 'error occured({}) in {}:{}'.format(ex, sys.exc_info()[-1].tb_frame.f_code.co_filename, sys.exc_info()[-1].tb_lineno)
957
        MainWindow.instance().displayMessage.emit(MessageType.Error, message)
949 958

  
950 959
'''
951 960
    @brief      Main function
......
962 971
                humkyung 2018.06.11 get difference between original and recognized image
963 972
                Jeongwoo 2018.06.21 If noteTextInfoList is None, change from None to empty list
964 973
'''
965
def executeRecognition(signal, updateProgressSignal, path, listWidget, isSymbolTextChecked):
974
def executeRecognition(signal, path, listWidget, isSymbolTextChecked, worker):
966 975
    import re
967 976
    from TextDetector import TextDetector
968 977

  
......
1032 1041
                # detect equipments
1033 1042
                pool = futures.ThreadPoolExecutor(max_workers = THREAD_MAX_WORKER)
1034 1043
                for symbol in targetSymbolList[0]:
1035
                    pool.submit(detectEquipmentOnPid, mainRes, symbol, listWidget, updateProgressSignal)
1044
                    pool.submit(detectEquipmentOnPid, mainRes, symbol, listWidget, worker)
1036 1045
                pool.shutdown(wait = True)
1037 1046
                # up to here
1038 1047

  
1039 1048
                pool = futures.ThreadPoolExecutor(max_workers = THREAD_MAX_WORKER)
1040 1049
                for symbol in targetSymbolList[2]:
1041 1050
                    if type(symbol) is list:
1042
                        pool.submit(detectSymbolsOnPid, mainRes, symbol, listWidget, updateProgressSignal)
1051
                        pool.submit(detectSymbolsOnPid, mainRes, symbol, listWidget, worker)
1043 1052
                    else:
1044
                        pool.submit(detectSymbolOnPid, mainRes, symbol, listWidget, updateProgressSignal)
1053
                        pool.submit(detectSymbolOnPid, mainRes, symbol, listWidget, worker)
1045 1054
                pool.shutdown(wait = True)
1046 1055

  
1047 1056
                ## DEBUG
......
1050 1059
                    cv2.imwrite(os.path.join(project.getTempPath(), 'Tile', item.getName()+'.png'), _img)
1051 1060
                ## up to here
1052 1061

  
1053
                textDetector.recognizeText(appDocData.imgSrc, offset, textAreas, searchedSymbolList, updateProgressSignal, listWidget, maxProgressValue)
1062
                textDetector.recognizeText(appDocData.imgSrc, offset, textAreas, searchedSymbolList, worker, listWidget, maxProgressValue)
1054 1063
                textInfoList = textDetector.textInfoList.copy() if textDetector.textInfoList is not None else None
1055 1064
                noteTextInfoList = textDetector.noteTextInfoList.copy() if textDetector.noteTextInfoList is not None else None
1056 1065

  
......
1090 1099
                
1091 1100
                signal.emit(searchedSymbolList, textInfoList, noteTextInfoList if noteTextInfoList is not None else [])
1092 1101
    except Exception as ex:
1093
        print('error occured({}) in {}:{}'.format(ex, sys.exc_info()[-1].tb_frame.f_code.co_filename, sys.exc_info()[-1].tb_lineno))
1102
        message = 'error occured({}) in {}:{}'.format(ex, sys.exc_info()[-1].tb_frame.f_code.co_filename, sys.exc_info()[-1].tb_lineno)
1103
        worker.displayLog.emit(MessageType.Error, message)
1094 1104
 
1095 1105
'''
1096 1106
    @brief      draw contour to image
......
1149 1159
            project = appDocData.getCurrentProject()
1150 1160
            cv2.imwrite(os.path.join(project.getTempPath(), "DIFF_" + os.path.basename(orgImagePath)), imgDiff)
1151 1161
    except Exception as ex:
1152
        print('error occured({}) in {}:{}'.format(ex, sys.exc_info()[-1].tb_frame.f_code.co_filename, sys.exc_info()[-1].tb_lineno))
1162
        message = 'error occured({}) in {}:{}'.format(ex, sys.exc_info()[-1].tb_frame.f_code.co_filename, sys.exc_info()[-1].tb_lineno)
1163
        MainWindow.instance().displayMessage.emit(MessageType.Error, message)
1153 1164
   
1154 1165
if __name__ == '__main__':
1155 1166
    import DTI_PID_UI
DTI_PID/DTI_PID/DTI_PID.pyproj
75 75
    <Compile Include="QOcrResultDialog.py" />
76 76
    <Compile Include="PropertyTableWidget.py" />
77 77
    <Compile Include="QRecognitionDialog.py" />
78
    <Compile Include="ResultPropertyTableWidget.py" />
79
    <Compile Include="ResultTreeWidget.py" />
78
    <Compile Include="ItemPropertyTableWidget.py" />
79
    <Compile Include="ItemTreeWidget.py" />
80 80
    <Compile Include="QSymbolDisplayDialog.py" />
81 81
    <Compile Include="QSymbolEditorDialog.py" />
82 82
    <Compile Include="QtImageViewer.py">
DTI_PID/DTI_PID/HMBDialog.py
277 277
            textDetector = TextDetector()
278 278
            img = appDocData.imgSrc[round(worker.area[1]):round(worker.area[1]+worker.area[3]), round(worker.area[0]):round(worker.area[0]+worker.area[2])]
279 279
            texts = textDetector.getTextAreaInfo(img, worker.area[0], worker.area[1])
280
            textDetector.recognizeText(appDocData.imgSrc, (worker.area[0], worker.area[1]), texts, [], worker.updateProgress, None, len(texts))
280
            textDetector.recognizeText(appDocData.imgSrc, (worker.area[0], worker.area[1]), texts, [], worker, None, len(texts))
281 281
            worker.textInfoList = textDetector.textInfoList.copy()
282 282
        except Exception as ex:
283 283
            print('error occured({}) in {}:{}'.format(ex, sys.exc_info()[-1].tb_frame.f_code.co_filename, sys.exc_info()[-1].tb_lineno))
DTI_PID/DTI_PID/ItemPropertyTableWidget.py
1
# coding: utf-8
2

  
3
try:
4
    from PyQt5.QtCore import *
5
    from PyQt5.QtGui import *
6
    from PyQt5.QtWidgets import *
7
except ImportError:
8
    try:
9
        from PyQt4.QtCore import *
10
        from PyQt4.QtGui import *
11
    except ImportError:
12
        raise ImportError("ImageViewerQt: Requires PyQt5 or PyQt4.")
13

  
14
import os
15
import sys
16
import math
17
from SymbolSvgItem import SymbolSvgItem
18
from QEngineeringLineNoTextItem import QEngineeringLineNoTextItem
19
from EngineeringLineItem import QEngineeringLineItem
20
from QEngineeringNoteItem import QEngineeringNoteItem
21
from AppDocData import AppDocData
22
from Drawing import Drawing
23
from enum import Enum
24

  
25
'''
26
    @brief      ItemType
27
    @author     Jeongwoo
28
    @date       2018.04.27
29
    @history    2018.05.10  Jeongwoo    Add LINE_NO
30
'''
31
class ItemType(Enum):
32
    SYMBOL = 1
33
    NOTE = 2
34
    LINE_NO = 3
35

  
36
class QItemPropertyTableWidget(QTableWidget):
37
    def __init__(self, mainWindow):
38
        QTableWidget.__init__(self)
39
        self.symData = None
40
        self.initResultPropertyTableWidget()
41
        self.mainWindow = mainWindow
42

  
43
    '''
44
        @brief  show item's property
45
        @author humkyung
46
        @date   2018.07.03
47
    '''
48
    def showItemProperty(self, item):
49
        if type(item) is QEngineeringLineItem:
50
            self.initTitleCell(item)
51
            self.setItem(0, 1, QTableWidgetItem(str(item.uid)))
52
            self.setItem(1, 1, QTableWidgetItem(item.lineType))
53
            pt = item.startPoint()
54
            self.setItem(2, 1, QTableWidgetItem('({},{})'.format(pt[0], pt[1])))
55
            pt = item.endPoint()
56
            self.setItem(3, 1, QTableWidgetItem('({},{})'.format(pt[0], pt[1])))
57
            self.setItem(4, 1, QTableWidgetItem('{}'.format('None' if item.connectors[0].connectedItem is None else item.connectors[0].connectedItem.uid)))
58
            self.setItem(5, 1, QTableWidgetItem('{}'.format('None' if item.connectors[1].connectedItem is None else item.connectors[1].connectedItem.uid)))
59
        elif issubclass(type(item), SymbolSvgItem):
60
            self.onSymbolClicked(item)
61
        elif type(item) is QEngineeringLineNoTextItem:
62
            self.onLineNoClicked(item)
63
        elif type(item) is QEngineeringNoteItem:
64
            self.onNoteClicked(item)
65

  
66
    '''
67
        @brief      Initialize TableWidget
68
        @author     Jeongwoo
69
        @date       18.04.13
70
        @history    humkyung 2018.07.08 show column header
71
    '''
72
    def initResultPropertyTableWidget(self):
73
        self.setColumnCount(2)
74
        self.setHorizontalHeaderLabels(['Name', 'Value'])
75
        self.horizontalHeaderItem(0).setSizeHint(QSize(25, 25))
76
        self.setRowCount(13)
77
        self.verticalHeader().hide()
78
        self.horizontalHeader().setStretchLastSection(True)
79
        self.setEditTriggers(QAbstractItemView.NoEditTriggers)
80
        
81
    '''
82
        @brief      Slot to accept item click event
83
        @author     Jeongwoo
84
        @date       18.04.13
85
        @history    humkyung 2018.04.17 check if given symbol type is SymbolSvgItem
86
    '''
87
    @pyqtSlot(SymbolSvgItem)
88
    def onSymbolClicked(self, symbol):
89
        if issubclass(type(symbol), SymbolSvgItem):
90
            self.symData = symbol
91
            self.symbolChanged(symbol)
92

  
93
    '''
94
        @brief      show drawing' attributes
95
        @author     humkyung 
96
        @date       2018.07.07
97
    '''
98
    @pyqtSlot(Drawing)
99
    def onDrawingClicked(self, drawing):
100
        self.setRowCount(len(drawing.attrs))
101

  
102
        row = 0
103
        for attr in drawing.attrs:
104
            name = attr[0]
105
            item = QTableWidgetItem(name)
106
            item.setFlags(Qt.ItemIsEnabled)
107
            item.setBackground(QColor(220, 220, 220))
108
            self.setItem(row, 0, item)
109

  
110
            value = attr[1]
111
            item = QTableWidgetItem(value)
112
            item.setFlags(Qt.ItemIsEnabled)
113
            self.setItem(row, 1, item)
114

  
115
            row = row + 1
116

  
117
    '''
118
        @brief      Slot to accept Note item click event
119
        @author     Jeongwoo
120
        @date       18.04.27
121
        @history    humkyung 2018.07.08 change method name to onNoteClicked
122
    '''
123
    @pyqtSlot(str, list)
124
    def onNoteClicked(self, noteNoStr, noteContentsList):
125
        self.noteChanged(noteNoStr, noteContentsList)
126

  
127
    '''
128
        @brief      Slot to accept Line No Item Click event
129
        @author     Jeongwoo
130
        @date       18.05.10
131
        @hisotry    humkyung 2018.07.08 change method name to onLineNoClicked
132
    '''
133
    @pyqtSlot(QEngineeringLineNoTextItem)
134
    def onLineNoClicked(self, item):
135
        self.lineNoChanged(item)
136
        
137
    '''
138
        @brief      Reset table with new SymbolSvgItem
139
        @author     Jeongwoo
140
        @date       18.04.13
141
        @history    .
142
    '''
143
    def symbolChanged(self, item):
144
        self.initTitleCell(item)
145
        self.initContentsCell()
146

  
147
    '''
148
        @brief      Reset table with note info
149
        @author     Jeongwoo
150
        @date       18.04.27
151
    '''
152
    def noteChanged(self, noteNoStr, noteContentsList):
153
        self.initTitleCell(QEngineeringNoteItem())
154
        self.initNoteCell(noteNoStr, noteContentsList)
155

  
156
    '''
157
        @brief      Reset table with line no item
158
        @author     Jeongwoo
159
        @date       18.05.10
160
    '''
161
    def lineNoChanged(self, item):
162
        from EngineeringRunItem import QEngineeringRunItem
163

  
164
        if type(item) is QEngineeringLineNoTextItem:
165
            self.initTitleCell(item)
166
            self.initLineNoCell(item)
167
        elif type(item) is QEngineeringRunItem:
168
            self.initLineRunCell(item)
169

  
170
    '''
171
        @brief      Initialize Title Cell
172
        @author     Jeongwoos
173
        @date       18.04.13
174
        @history    2018.04.27 Jeongwoo Add if-statement by ItemType
175
                    2018.05.10 Jeongwoo Add if-statement LINE_NO ItemType
176
    '''
177
    def initTitleCell(self, item):
178
        try:
179
            if issubclass(type(item), SymbolSvgItem):
180
                self.setRowCount(6)
181
                
182
                self.setItem(0, 0, QTableWidgetItem("UID"))
183
                self.setItem(1, 0, QTableWidgetItem("심볼명"))
184
                self.setItem(2, 0, QTableWidgetItem("타입"))
185
                self.setItem(3, 0, QTableWidgetItem("각도"))
186
                self.setItem(4, 0, QTableWidgetItem("원점"))
187
                self.setItem(5, 0, QTableWidgetItem("OWNER"))
188
            elif type(item) is QEngineeringNoteItem:
189
                self.setRowCount(1)
190

  
191
                widgetItem = QTableWidgetItem("노트번호")
192
                self.setItem(0, 0, widgetItem)
193
            elif type(item) is QEngineeringLineNoTextItem:
194
                '''
195
                DO NOTHING / initLineNoCell
196
                '''
197
            elif type(item) is QEngineeringLineItem:
198
                self.setRowCount(6)
199

  
200
                self.setItem(0, 0, QTableWidgetItem('UID'))
201
                self.setItem(1, 0, QTableWidgetItem("타입"))
202
                self.setItem(2, 0, QTableWidgetItem("시작점"))
203
                self.setItem(3, 0, QTableWidgetItem("끝점"))
204
                self.setItem(4, 0, QTableWidgetItem("CONN1"))
205
                self.setItem(5, 0, QTableWidgetItem("CONN2"))
206

  
207
            for index in range(self.rowCount()):
208
                item = self.item(index, 0)
209
                if item is not None:
210
                    item.setFlags(Qt.ItemIsEnabled)
211
                    item.setBackground(QColor(220, 220, 220))
212
        except Exception as ex:
213
            print('error occured({}) in {}:{}'.format(ex, sys.exc_info()[-1].tb_frame.f_code.co_filename, sys.exc_info()[-1].tb_lineno))
214
                
215
    '''
216
        @brief      Initialize Contents Cell
217
        @author     Jeongwoo
218
        @date       18.04.13
219
        @history    humkyung 2018.06.14 display symbol attributes 
220
                    humkyung 2018.07.05 display connectivity
221
    '''
222
    def initContentsCell(self):
223
        from QEngineeringInstrumentItem import QEngineeringInstrumentItem
224
        
225
        if self.symData is not None:
226

  
227
            self.setItem(0, 1, QTableWidgetItem(str(self.symData.uid)))
228
            self.setItem(1, 1, QTableWidgetItem(self.symData.name))
229
            self.setItem(2, 1, QTableWidgetItem(self.symData.type))
230
            self.setItem(3, 1, QTableWidgetItem(str(round(math.degrees(self.symData.angle)))))
231
            self.setItem(4, 1, QTableWidgetItem(str(self.symData.origin)))
232
            self.setItem(5, 1, QTableWidgetItem('{}'.format(self.symData.owner)))
233

  
234
            row = self.rowCount()
235
            self.setRowCount(row + len(self.symData.getAttributes()) + len(self.symData.connectors))
236
            # display attributes of symbol
237
            attrs = self.symData.getAttributes()
238
            if attrs is not None:
239
                attrItems = list(attrs.items())
240
                for index in range(len(attrItems)):
241
                    key = attrItems[index][0]
242
                    value = attrItems[index][1]
243
                    keyItem = QTableWidgetItem(key)
244
                    keyItem.setBackground(QColor(220, 220, 220))
245
                    docData = AppDocData.instance()
246
                    valueItem = QTableWidgetItem(value)
247
                    if docData.checkAttribute(key):
248
                        from PyQt5 import QtGui
249
                        icon = QtGui.QIcon()
250
                        icon.addPixmap(QtGui.QPixmap(":/newPrefix/doubleclick.png"), QtGui.QIcon.Normal, QtGui.QIcon.Off)
251
                        valueItem.setIcon(icon)
252
                    self.setItem(row, 0, keyItem)
253
                    self.setItem(row, 1, valueItem)
254
                    row = row + 1
255
            # up to here
256

  
257
            # display connectivity
258
            count = 1
259
            for connector in self.symData.connectors:
260
                item = QTableWidgetItem('CONN{}'.format(count))
261
                item.setFlags(Qt.ItemIsEnabled)
262
                item.setBackground(QColor(220, 220, 220))
263
                self.setItem(row, 0, item)
264

  
265
                item = QTableWidgetItem('{}'.format('None' if connector.connectedItem is None else str(connector.connectedItem.uid)))
266
                item.setFlags(Qt.ItemIsEnabled)
267
                self.setItem(row, 1, item)
268
                row = row + 1
269
                count = count + 1
270
            # up to here
271
        else:
272
            self.setRowCount(0)
273

  
274
    '''
275
        @brief      Initialize Note Contents Cell
276
        @author     Jeongwoo
277
        @date       18.04.27
278
    '''
279
    def initNoteCell(self, noteNoStr, noteContentsList):
280
        self.setItem(0, 1, QTableWidgetItem(noteNoStr))
281
        
282
        row = self.rowCount()
283
        count = 1
284
        self.setRowCount(row + len(noteContentsList))
285
        for index in range(len(noteContentsList)):
286
            item = QTableWidgetItem("노트내용{}".format(count))
287
            item.setFlags(Qt.ItemIsEnabled)
288
            self.setItem(row, 0, item)
289
            self.setItem(row, 1, QTableWidgetItem(noteContentsList[index]))
290
            row = row + 1
291
            count = count + 1
292

  
293
    '''
294
        @brief      Initialize Line No Contents Cell
295
        @author     Jeongwoo
296
        @date       18.05.10
297
        @history    humkyung 2018.07.20 display combobox when key is 'Stream No'
298
    '''
299
    def initLineNoCell(self, lineNoItem):
300
        self.setRowCount(1 + len(lineNoItem.getLineNoAttributes()))
301

  
302
        lineNoTitleItem = QTableWidgetItem("Line No.")
303
        lineNoTitleItem.setBackground(QColor(220, 220, 220))
304
        lineNoTitleItem.setFlags(Qt.ItemIsEnabled)
305
        self.setItem(0, 0, lineNoTitleItem)
306
        self.setItem(0, 1, QTableWidgetItem(lineNoItem.text()))
307

  
308
        attrItems = lineNoItem.getLineNoAttributes()
309
        if attrItems is not None:
310
            for index in range(len(attrItems)):
311
                key = attrItems[index][0]
312
                value = attrItems[index][1]
313
                item = QTableWidgetItem(key)
314
                item.setFlags(Qt.ItemIsEnabled)
315
                item.setBackground(QColor(220, 220, 220))
316
                self.setItem(1 + index, 0, item)
317
                if key == 'Stream No':
318
                    self.streamNoComboBox = QComboBox()
319
                    self.streamNoComboBox.tag = lineNoItem
320
                    self.streamNoComboBox.currentIndexChanged.connect(self.onStreamNoChanged)
321

  
322
                    appDocData = AppDocData.instance()
323
                    streamNos = sorted(list(appDocData.hmbTable.streamNos()))
324
                    for streamNo in streamNos:
325
                        self.streamNoComboBox.addItem(streamNo)
326
                    self.setCellWidget(1 + index, 1, self.streamNoComboBox)
327
                    self.streamNoComboBox.setCurrentText(value)
328
                else:
329
                    self.setItem(1 + index, 1, QTableWidgetItem(value))
330

  
331
    '''
332
        @brief  change selected lines' type by selected line type
333
        @author humkyung
334
        @date   2018.07.20
335
    '''
336
    def onStreamNoChanged(self, param):
337
        streamNo = self.streamNoComboBox.itemText(param)
338
        self.streamNoComboBox.tag.setAttribute('Stream No', streamNo)
339

  
340
    '''
341
        @brief      Initialize Run Contents Cell
342
        @author     humkyung 
343
        @date       2018.05.27
344
    '''
345
    def initLineRunCell(self, item):
346
        self.setRowCount(1)
347

  
348
        lineTypeItem = QTableWidgetItem("라인 타입")
349
        lineTypeItem.setBackground(QColor(220, 220, 220))
350
        lineTypeItem.setFlags(Qt.ItemIsEnabled)
351
        self.setItem(0, 0, lineTypeItem)
352
        self.setItem(0, 1, QTableWidgetItem(item.lineType))
353

  
354
    '''
355
        @brief      Key Press Event
356
        @author     kyouho
357
        @date       2018.07.19
358
    '''
359
    def keyPressEvent(self, event):
360
        if event.key() == Qt.Key_Delete:
361
            items = self.mainWindow.graphicsView.scene.selectedItems()
362
            selectedIndexes = self.selectedIndexes()
363
            if len(items) == 1 and len(selectedIndexes) == 1:
364
                if selectedIndexes[0].column() == 1:
365
                    attributeStr = self.item(selectedIndexes[0].row(), 0).text()
366
                    items[0].removeSelfAttr(attributeStr)
367
                    self.mainWindow.refreshResultPropertyTableWidget()
DTI_PID/DTI_PID/ItemTreeWidget.py
1
# coding: utf-8
2

  
3
import os
4
import re
5
import sys
6

  
7
try:
8
    from PyQt5.QtCore import *
9
    from PyQt5.QtGui import *
10
    from PyQt5.QtWidgets import *
11
except ImportError:
12
    try:
13
        from PyQt4.QtCore import *
14
        from PyQt4.QtGui import *
15
    except ImportError:
16
        raise ImportError("ImageViewerQt: Requires PyQt5 or PyQt4.")
17
from SymbolSvgItem import SymbolSvgItem
18
from QEngineeringTextItem import QEngineeringTextItem
19
from QEngineeringNoteItem import QEngineeringNoteItem
20
from QEngineeringLineNoTextItem import QEngineeringLineNoTextItem
21
from QEngineeringTrimLineNoTextItem import QEngineeringTrimLineNoTextItem
22
from QEngineeringUnknownItem import QEngineeringUnknownItem
23
from AppDocData import AppDocData
24
from Drawing import Drawing
25

  
26
class QItemTreeWidget(QTreeWidget):
27
    TREE_DATA_ROLE = Qt.UserRole
28

  
29
    #Add Signal
30
    singleClicked = pyqtSignal(SymbolSvgItem)
31
    noteNoSingleClicked = pyqtSignal(str, list)
32
    lineNoSingleClicked = pyqtSignal(QEngineeringLineNoTextItem)
33
    drawingClicked = pyqtSignal(Drawing)
34

  
35
    '''
36
        @history    2018.05.11  Jeongwoo    Add Context Menu settings
37
    '''
38
    def __init__(self, imageViewer):
39
        QTreeWidget.__init__(self)
40
        self.itemClicked.connect(self.itemClickEvent)
41
        self.imageViewer = imageViewer
42
        self.scene = imageViewer.scene
43
        self.root = None
44
        self.setContextMenuPolicy(Qt.CustomContextMenu)
45
        self.customContextMenuRequested.connect(self.openContextMenu)
46

  
47
    '''
48
        @brief      delete selected symbol
49
        @author     humkyung
50
        @date       2018.07.20
51
    '''
52
    def keyPressEvent(self, event):
53
        try:
54
            if event.key() == Qt.Key_Delete:
55
                if self.selectedItems():
56
                    for item in self.selectedItems():
57
                        data = item.data(0, self.TREE_DATA_ROLE)
58
                        if data is not None:
59
                            self.imageViewer.scene.removeItem(data)
60
                            item.parent().removeChild(item)
61
                event.accept()
62
            elif event.key() == Qt.Key_Up:
63
                if self.selectedItems():
64
                    item = self.selectedItems()[0]
65
                    aboveItem = self.itemAbove(item)
66
                    if aboveItem is not None:
67
                        self.setCurrentItem(aboveItem)
68
                        self.scrollToItem(aboveItem, QAbstractItemView.EnsureVisible)
69
                        self.itemClickEvent(aboveItem, 0)
70
                event.accept()
71
            elif event.key() == Qt.Key_Down:
72
                if self.selectedItems():
73
                    item = self.selectedItems()[0]
74
                    belowItem = self.itemBelow(item)
75
                    if belowItem is not None:
76
                        self.setCurrentItem(belowItem)
77
                        self.scrollToItem(belowItem, QAbstractItemView.EnsureVisible)
78
                        self.itemClickEvent(belowItem, 0)
79
                event.accept()
80
            else:
81
                event.ignore()
82
        except Exception as ex:
83
            print('error occured({}) in {}:{}'.format(ex, sys.exc_info()[-1].tb_frame.f_code.co_filename, sys.exc_info()[-1].tb_lineno))
84

  
85
    '''
86
        @brief      Show Context Menu
87
        @author     Jeongwoo
88
        @date       18.05.11
89
        @history    18.06.14    Jeongwoo    Add item None check
90
                    humkyung 2018.06.27 add menu of run item for selecting line type
91
    '''
92
    def openContextMenu(self, position):
93
        from EngineeringRunItem import QEngineeringRunItem
94

  
95
        indexes = self.selectedIndexes()
96
        itemPosition = self.mapTo(self, position)
97
        item = self.itemAt(itemPosition)
98
        if item is not None:
99
            data = item.data(0, self.TREE_DATA_ROLE)
100
            if len(indexes) > 0 and data is not None and issubclass(type(data), QEngineeringLineNoTextItem):
101
                index = indexes[0]
102
                menu = QMenu()
103
                pickColorAction = QAction(self.tr("색상 설정"))
104
                pickColorAction.triggered.connect(lambda : self.pickColorClickEvent(item))
105
                menu.addAction(pickColorAction)
106
                menu.exec_(self.viewport().mapToGlobal(position))
107
            elif len(indexes) > 0 and data is not None and issubclass(type(data), QEngineeringRunItem):
108
                index = indexes[0]
109
                menu = QMenu()
110
                lineTypeAction = QAction(self.tr("라인 타입 설정"))
111
                lineTypeAction.triggered.connect(lambda : self.lineTypeClickEvent(item))
112
                menu.addAction(lineTypeAction)
113
                menu.exec_(self.viewport().mapToGlobal(position))
114

  
115
    '''
116
        @brief      Pick Color for Line No
117
        @author     Jeongwoo
118
        @date       18.05.11
119
        @history    18.05.14    Jeongwoo    Change method to change color by changeTreeWidgetItemColorRecursively()
120
    '''
121
    def pickColorClickEvent(self, lineNoTreeWidgetItem):
122
        ''''''
123
        color = QColorDialog.getColor() # Dialog returns QColor
124
        
125
        if color is not None:
126
            lineNoTreeWidgetItem.setForeground(0, QBrush(color))
127
            lineNoTreeWidgetItem.setFont(0, lineNoTreeWidgetItem.font(0))
128
            lineNoItem = lineNoTreeWidgetItem.data(0, self.TREE_DATA_ROLE)
129
            lineNoItem.setColor(color.name())
130
            connectedItems = lineNoItem.getConnectedItems()
131
            if connectedItems is not None:
132
                for connectedItem in connectedItems:
133
                    connectedItem.setColor(color.name())
134
                self.changeTreeWidgetItemColorRecursively(lineNoTreeWidgetItem, color)
135

  
136
    '''
137
        @brief      pick line type
138
        @author     humkyung 
139
        @date       2018.06.27
140
    '''
141
    def lineTypeClickEvent(self, item):
142
        pass
143

  
144
    '''
145
        @brief      Change Color recursively
146
        @author     Jeongwoo
147
        @date       18.05.14
148
    '''
149
    def changeTreeWidgetItemColorRecursively(self, item, color):
150
        for index in range(item.childCount()):
151
            child = item.child(index)
152
            child.setForeground(0, QBrush(color))
153
            child.setFont(0, child.font(0))
154
            self.changeTreeWidgetItemColorRecursively(child, color)
155

  
156
    '''
157
        @brief      Clear TreeWidget and Set Current PID
158
        @author     Jeongwoo
159
        @date       18.04.11
160
        @history    2018.04.26  Jeongwoo    Add Child [SYMBOLS, NOTES] into root item
161
                    2018.05.09  Jeongwoo    Change method to add default tree items
162
                    humkyung 2018.06.10 add tree item for Line No and Unknown Item
163
    '''
164
    def setCurrentPID(self, drawingName):
165
        appDocData = AppDocData.instance()
166

  
167
        self.clear()
168
        self.root = QTreeWidgetItem(self, [drawingName])
169
        self.root.setData(0, self.TREE_DATA_ROLE, appDocData.activeDrawing)
170
        self.root.addChild(QTreeWidgetItem(['LINE NO']))
171
        self.LineNoTreeItem = self.root.child(self.root.childCount() - 1)
172
        self.root.addChild(QTreeWidgetItem(['EQUIPMENTS']))
173
        self.root.addChild(QTreeWidgetItem(['SYMBOLS']))
174
        self.root.addChild(QTreeWidgetItem(['NOTES']))
175
        self.root.addChild(QTreeWidgetItem(['UNKNOWN']))
176
        self.UnknownTreeItem = self.root.child(self.root.childCount() - 1)
177

  
178
    '''
179
        @brief  create tree item for pipe run if need
180
        @author humkyung
181
        @date   2018.05.15
182
        @history    2018.05.17  Jeongwoo    Change run item color with parent(LineNo Item) color
183
    '''
184
    def addPipeRunTreeItemIfNeed(self, lineNoTreeItem, pipeRun):
185
        for index in range(lineNoTreeItem.childCount()):
186
            treeItem = lineNoTreeItem.child(index)
187
            itemData = treeItem.data(0, self.TREE_DATA_ROLE)
188
            if itemData is not None and itemData == pipeRun.uid:
189
                return treeItem
190

  
191
        runItem = QTreeWidgetItem(['RUNS {}'.format(lineNoTreeItem.childCount()+1)])
192
        runItem.setData(0, self.TREE_DATA_ROLE, pipeRun.uid)
193

  
194
        brush = lineNoTreeItem.foreground(0)
195
        runItem.setForeground(0, brush)
196
        runItem.setFont(0, runItem.font(0))
197

  
198
        lineNoTreeItem.addChild(runItem)
199

  
200
        return runItem
201

  
202
    '''
203
        @brief      add a tree item
204
        @author     humkyung
205
        @date       2018.04.17
206
        @history    humkyung 2018.04.19 treat a case parent is changed
207
                    Jeongwoo 2018.04.26 QEngineeringTextItem → QEngineeringLineNoTextItem
208
                                        Insert if-statement for QEngineeringNoteItem
209
                                        Add if-statement for QEngineeringLineNoTextItem
210
                    Jeongwoo 2018.05.11 Set color when SymbolSvgItem moved into new parent(Line No)
211
                    Jeongwoo 2018.05.15 Add break keyword on if-statement
212
                    Jeongwoo 2018.05.17 Set TreeWidgetItem's color with data's color And Set Data's color on LineNoTextItem Setting
213
                    humkyung 2018.06.12 add unknown item
214
                    humkyung 2018.07.20 append nozzle to equipment
215
    '''
216
    def addTreeItem(self, parent, child):
217
        item = None
218
        if (not hasattr(child, 'treeItem')) or (child.treeItem is None):
219
            if issubclass(type(child), SymbolSvgItem):
220
                if AppDocData.instance().isEquipmentType(child.type):
221
                    symbolsRootItem = self.findItems('EQUIPMENTS', Qt.MatchExactly|Qt.MatchRecursive, 0)
222
                    symbolsRootItem = symbolsRootItem[0]
223
                    item = QTreeWidgetItem(symbolsRootItem, [child.name])
224
                    item.setData(0, self.TREE_DATA_ROLE, child)
225
                elif child.type == 'Nozzles':
226
                    eqpRootTreeItem = self.findItems('EQUIPMENTS', Qt.MatchExactly|Qt.MatchRecursive, 0)
227
                    for i in range(eqpRootTreeItem[0].childCount()):
228
                        eqpTreeItem = eqpRootTreeItem[0].child(i)
229
                        eqpSymbol = eqpTreeItem.data(0, self.TREE_DATA_ROLE)
230
                        if child.owner is eqpSymbol:
231
                            symbolsRootItem = eqpTreeItem
232
                            item = QTreeWidgetItem(eqpTreeItem, [child.name])
233
                            item.setData(0, self.TREE_DATA_ROLE, child)
234
                            break
235
                    
236
                    if item is None:
237
                        symbolsRootItem = self.findItems('SYMBOLS', Qt.MatchExactly|Qt.MatchRecursive, 0)
238
                        symbolsRootItem = symbolsRootItem[0]
239
                        item = QTreeWidgetItem(symbolsRootItem, [child.name])
240
                else:
241
                    symbolsRootItem = self.findItems('SYMBOLS', Qt.MatchExactly|Qt.MatchRecursive, 0)
242
                    symbolsRootItem = symbolsRootItem[0]
243
                    item = QTreeWidgetItem(symbolsRootItem, [child.name])
244
                
245
                if item is not None:
246
                    iconPath = os.path.join(AppDocData.instance().getCurrentProject().getSvgFilePath(), child.type , child.name + ".svg")
247
                    item.setIcon(0, QIcon(iconPath))
248
                    item.setData(0, self.TREE_DATA_ROLE, child)
249
                    brush = QBrush()
250
                    brush.setColor(QColor(child.getColor()))
251
                    item.setForeground(0, brush)
252
                    item.setFont(0, item.font(0))
253
                    child.treeItem = item
254
                    symbolsRootItem.addChild(item)
255
                    symbolsRootItem.sortChildren(0, Qt.AscendingOrder)  # sort childrens
256
            elif type(child) is QEngineeringLineNoTextItem:
257
                item = QTreeWidgetItem([child.text()])
258
                item.setFlags(item.flags() | Qt.ItemIsUserCheckable)
259
                item.setData(0, self.TREE_DATA_ROLE, child)
260
                item.setCheckState(0, Qt.Unchecked)
261
                brush = QBrush()
262
                brush.setColor(QColor(child.getColor()))
263
                item.setForeground(0, brush)
264
                item.setFont(0, item.font(0))
265
                child.treeItem = item
266
                self.LineNoTreeItem.addChild(item)
267
            elif type(child) is QEngineeringTrimLineNoTextItem:
268
                item = QTreeWidgetItem(['Trim Line'])
269
                item.setFlags(item.flags() | Qt.ItemIsUserCheckable)
270
                item.setData(0, self.TREE_DATA_ROLE, child)
271
                item.setCheckState(0, Qt.Unchecked)
272
                brush = QBrush()
273
                brush.setColor(QColor(child.getColor()))
274
                item.setForeground(0, brush)
275
                item.setFont(0, item.font(0))
276
                child.treeItem = item
277
                self.LineNoTreeItem.addChild(item)
278
            elif (type(child) is QEngineeringNoteItem):
279
                item = QTreeWidgetItem([child.text()])
280
                item.setData(0, self.TREE_DATA_ROLE, child)
281
                child.treeItem = item
282
                if parent is not None:
283
                    parent.addChild(item)
284
            elif (type(child) is QEngineeringUnknownItem):
285
                item = QTreeWidgetItem(['Unknown'])
286
                item.setData(0, self.TREE_DATA_ROLE, child)
287
                child.treeItem = item
288
                self.UnknownTreeItem.addChild(item)
289
        elif issubclass(type(child), SymbolSvgItem):  # change item's parent
290
            foundItems = self.findItems(child.name, Qt.MatchExactly|Qt.MatchRecursive, 0)
291
            if foundItems is not None:
292
                for item in foundItems:
293
                    data = item.data(0, self.TREE_DATA_ROLE)
294
                    if data is not None and (data == child) and (parent is not item.parent()):
295
                        parentData = parent.data(0, self.TREE_DATA_ROLE)
296
                        if issubclass(type(parentData), QEngineeringLineNoTextItem):
297
                            for index in range(len(parentData.runs)):
298
                                runGroup = parentData.runs[index]
299
                                if data in runGroup.items:
300
                                    item.parent().removeChild(item) # remove item from original parent
301
                                    runItem = self.addPipeRunTreeItemIfNeed(parent, runGroup) #parent.child(index)
302
                                    brush = QBrush()
303
                                    brush.setColor(QColor(item.data(0, self.TREE_DATA_ROLE).getColor()))
304
                                    item.setForeground(0, brush)
305
                                    #item.data(0, self.TREE_DATA_ROLE).setColor(brush.color().name())
306
                                    item.setFont(0, item.font(0))
307
                                    runItem.addChild(item)
308
                                    break
309
                                else:
310
                                    pass
311
                        break
312
        elif type(child) is QEngineeringLineNoTextItem:
313
            foundItems = self.findItems(child.text(), Qt.MatchExactly|Qt.MatchRecursive, 0)
314
            if foundItems is not None:
315
                for item in foundItems:
316
                    data = item.data(0, self.TREE_DATA_ROLE)
317
                    if data is not None and (data == child):
318
                        connectedItems = data.getConnectedItems()
319
                        color = data.getColor()
320
                        for connectedItem in connectedItems:
321
                            connectedItem.setColor(color)
322
                        return item
323

  
324
        return item
325

  
326
    '''
327
        @brief      Scene Changed Listener
328
        @author     Jeongwoo
329
        @date       18.04.11
330
        @history    Jeongwoo 2018.04.25 Add QEngineeringNoteItem to list for loop  / NOT QEngineeringTextItem
331
                    Jeongwoo 2018.04.26 Change changedSceneItems conditions in loop (QEngineeringTextItem → QEngineeringLineNoTextItem)
332
                    humkyung 2018.07.20 put items which's owner is None before item which's owner is not None
333
    '''
334
    lastSceneItems = None
335
    def sceneChanged(self, sceneItems):
336
        changedSceneItems = [item for item in sceneItems if ((issubclass(type(item), SymbolSvgItem)) or (type(item) is QEngineeringNoteItem) or (type(item) is QEngineeringLineNoTextItem) 
337
                            or (type(item) is QEngineeringUnknownItem)) and (not hasattr(item, 'treeItem') or item.treeItem is None)] # Sublist includes SymbolSvgItem
338
        first = [item for item in changedSceneItems if item.owner is None]
339
        second = [item for item in changedSceneItems if item.owner is not None]
340
        self.initResultTreeWidget(first + second)
341
            
342
    '''
343
        @brief      Initialize TreeWidget
344
                    Tree Item Clear and Add Item
345
        @author     Jeongwoo
346
        @date       2018.04.11
347
        @history    2018.04.12 Jeongwoo    Declare self.TREE_DATA_ROLE for QTreeWidgetItem.data(column, role)
348
        @history    2018.04.17 humkyung NOT remove child of root
349
                    2018.04.25 Jeongwoo     Add QEngineeringNoteItem Child
350
                    2018.04.26 Jeongwoo     Change method to insert child
351
    '''
352
    def initResultTreeWidget(self, items):
353
        for item in items:
354
            if (type(item) is QEngineeringNoteItem):
355
                notesRootItem = self.findItems('NOTES', Qt.MatchExactly|Qt.MatchRecursive, 0)
356
                notesRootItem = notesRootItem[0]
357

  
358
                self.addTreeItem(notesRootItem, item)
359
            else:
360
                self.addTreeItem(self.root, item)
361
                    
362
        notesRootItem = self.findItems('NOTES', Qt.MatchExactly|Qt.MatchRecursive, 0)
363
        if notesRootItem is not None and len(notesRootItem) > 0:
364
            notesRootItem[0].sortChildren(0, Qt.AscendingOrder)
365
        self.expandAll()
366
        
367
    '''
368
        @brief      TreeWidget Item click listener
369
        @author     Jeongwoo
370
        @date       18.04.11
371
        @history    18.04.12    Jeongwoo    Declare self.TREE_DATA_ROLE for QTreeWidgetItem.data(column, role)
372
                    18.04.13    Jeongwoo    Signal 'singleClicked' emit SymbolSvgItem
373
                                            CenterOn() with Symbol's center
374
                    18.04.25    Jeongwoo    Add QEngineeringNoteItem Click event
375
                    18.04.26    Jeongwoo    QEngineeringTextItem → QEngineeringLineNoTextItem
376
                    18.04.27    Jeongwoo    Find NOTE Contents when NOTE No. Clicked
377
                                            Move singClicked to if-statement type(itemData) is SymbolSvgItem:
378
                    18.05.10    Jeongwoo    Add lineNoSingleClicked emit
379
                    humkyung 2018.07.07 emit singleClicked signal when user click drawing name
380
    '''
381
    def itemClickEvent(self, item, columnNo, isSvgClick = False):
382
        from Drawing import Drawing
383
        from EngineeringRunItem import QEngineeringRunItem
384

  
385
        hilightColor = QColor(255, 0, 0, 127)
386

  
387
        itemData = item.data(0, self.TREE_DATA_ROLE)
388
        
389
        if itemData is not None: ## Not PID Name
390
            if issubclass(type(itemData), SymbolSvgItem):
391
                ## Draw rectangle on selected symbol
392
                rect = itemData.sceneBoundingRect()
393
                if isSvgClick == False:
394
                    self.imageViewer.centerOn(rect.center())
395
                ## Send new event to imageViewer's zoomImage Method
396
                self.imageViewer.zoomImage(True, QMouseEvent(QEvent.MouseButtonPress, self.imageViewer.mapFromScene(QPointF(rect.left(), rect.top())), Qt.LeftButton, Qt.LeftButton, Qt.NoModifier), 3)
397
                
398
                self.singleClicked.emit(itemData)
399
            elif (type(itemData) is QEngineeringLineNoTextItem) or (type(itemData) is QEngineeringRunItem):
400
                if type(itemData) is QEngineeringLineNoTextItem:
401
                    rect = itemData.sceneBoundingRect()
402
                    if isSvgClick == False:
403
                        self.imageViewer.centerOn(rect.center())
404
                    ## Send new event to imageViewer's zoomImage Method
405
                    self.imageViewer.zoomImage(True, QMouseEvent(QEvent.MouseButtonPress, self.imageViewer.mapFromScene(QPointF(rect.left(), rect.top())), Qt.LeftButton, Qt.LeftButton, Qt.NoModifier), 3)
406
                else:
407
                    pass
408

  
409
                self.lineNoSingleClicked.emit(itemData)
410
            elif type(itemData) is QEngineeringNoteItem:
411
                rect = itemData.sceneBoundingRect()
412
                if isSvgClick == False:
413
                    self.imageViewer.centerOn(rect.center())
414
                ## Send new event to imageViewer's zoomImage Method
415
                self.imageViewer.zoomImage(True, QMouseEvent(QEvent.MouseButtonPress, self.imageViewer.mapFromScene(QPointF(rect.left(), rect.top())), Qt.LeftButton, Qt.LeftButton, Qt.NoModifier), 3)
416
                noteContentsList = itemData.findNoteContents(itemData.text())
417

  
418
                self.noteNoSingleClicked.emit(itemData.text(), noteContentsList)
419
            elif type(itemData) is QEngineeringUnknownItem:
420
                rect = itemData.sceneBoundingRect()
421
                self.imageViewer.centerOn(rect.center())
422
                self.imageViewer.zoomImage(True, QMouseEvent(QEvent.MouseButtonPress, self.imageViewer.mapFromScene(QPointF(rect.left(), rect.top())), Qt.LeftButton, Qt.LeftButton, Qt.NoModifier), 3)
423
            elif type(itemData) is Drawing:
424
                self.drawingClicked.emit(itemData)
425
                
426
            if issubclass(type(itemData), QGraphicsItem):
427
                itemData.setSelected(True)
428
                itemData.update()
429

  
430

  
431
    '''
432
        @brief  find item which has data is given item
433
        @author humkyung
434
        @date   2018.04.23
435
        @history    2018.06.18  Jeongwoo    Add if-statement for QEngineeringUnknownItem
436
    '''
437
    def findItemByData(self, item):
438
        if issubclass(type(item), SymbolSvgItem):
439
            foundItems = self.findItems(item.name, Qt.MatchExactly|Qt.MatchRecursive, 0)
440
            for foundItem in foundItems:
441
                data = foundItem.data(0, self.TREE_DATA_ROLE)
442
                if data is not None and data is item:
443
                    return foundItem
444
        elif type(item) is QEngineeringLineNoTextItem:
445
            foundItems = self.findItems(item.text() , Qt.MatchExactly|Qt.MatchRecursive, 0)
446
            for foundItem in foundItems:
447
                data = foundItem.data(0, self.TREE_DATA_ROLE)
448
                if data is not None and data is item:
449
                    return foundItem
450
        elif type(item) is QEngineeringUnknownItem:
451
            foundItems = self.findItems('Unknown' , Qt.MatchExactly|Qt.MatchRecursive, 0)
452
            for foundItem in foundItems:
453
                data = foundItem.data(0, self.TREE_DATA_ROLE)
454
                if data is not None and data is item:
455
                    return foundItem
456
        
457
        return None
458

  
459
    '''
460
        @brief      Find QTreeWidgetItem by SymbolSvgItem object
461
        @author     Jeongwoo
462
        @date       18.04.11
463
        @history    18.04.12    Jeongwoo    Change Flags (Qt.MatchExactly → Qt.MatchExactly|Qt.MatchRecursive)
464
                                            Declare self.TREE_DATA_ROLE for QTreeWidgetItem.data(column, role)
465
    '''
466
    @pyqtSlot(SymbolSvgItem)
467
    def findItem(self, item):
468
        if issubclass(type(item), SymbolSvgItem):
469
            foundItems = self.findItems(item.name, Qt.MatchExactly|Qt.MatchRecursive, 0)
470
            if foundItems is not None:
471
                for fItem in foundItems:
472
                    data = fItem.data(0, self.TREE_DATA_ROLE)
473
                    if data is not None and data == item:
474
                        self.setCurrentItem(fItem)
475
                        return
476
        elif type(item) is QEngineeringLineNoTextItem:
477
            foundItems = self.findItems(item.text(), Qt.MatchExactly|Qt.MatchRecursive, 0)
478
            if foundItems is not None:
479
                for fItem in foundItems:
480
                    data = fItem.data(0, self.TREE_DATA_ROLE)
481
                    if data is not None and data == item:
482
                        self.setCurrentItem(fItem)
483
                        return
484

  
485
        ## Not found
486
        msg = QMessageBox()
487
        msg.setIcon(QMessageBox.Warning)
488
        msg.setText('선택하신 심볼의 데이터를 찾을 수 없습니다.')
489
    
490
    '''
491
        @brief      remove given item
492
        @author     humkyung
493
        @date       2018.04.23
494
        @history    Jeongwoo 2018.05.25 Remove Highlighting when item removed
495
                    humkyung 2018.07.22 removed code to Remove highlighting
496
    '''
497
    @pyqtSlot(QGraphicsItem)
498
    def itemRemoved(self, item):
499
        foundItem = self.findItemByData(item)
500
        if foundItem is not None:
501
            foundItem.parent().removeChild(foundItem)
DTI_PID/DTI_PID/MainWindow.py
36 36
from QEngineeringNoteItem import QEngineeringNoteItem
37 37
from QEngineeringSizeTextItem import QEngineeringSizeTextItem
38 38
from QEngineeringUnknownItem import QEngineeringUnknownItem
39
from AppDocData import AppDocData
39
from AppDocData import *
40 40
import SymbolTreeWidget, PropertyTableWidget
41 41
import QSymbolEditorDialog
42
import ResultTreeWidget
43
import ResultPropertyTableWidget
42
import ItemTreeWidget
43
import ItemPropertyTableWidget
44 44
from TextItemFactory import TextItemFactory
45 45

  
46 46
class MainWindow(QMainWindow, MainWindow_UI.Ui_MainWindow, SingletonInstane):
......
97 97
        # up to here
98 98

  
99 99
        # Add Custom Result Tree Widget (Symbol Explorer)
100
        self.resultTreeWidget = ResultTreeWidget.QResultTreeWidget(self.graphicsView)
100
        self.resultTreeWidget = ItemTreeWidget.QItemTreeWidget(self.graphicsView)
101 101
        self.resultTreeWidget.header().hide()
102 102
        self.symbolExplorerVerticalLayout.addWidget(self.resultTreeWidget)
103 103

  
104 104
        # Add Empty Widget
105
        self.resultPropertyTableWidget = ResultPropertyTableWidget.QResultPropertyTableWidget(self)
105
        self.resultPropertyTableWidget = ItemPropertyTableWidget.QItemPropertyTableWidget(self)
106 106
        self.symbolExplorerVerticalLayout.addWidget(self.resultPropertyTableWidget)
107 107
        self.resultTreeWidget.singleClicked.connect(self.resultPropertyTableWidget.onSymbolClicked)
108 108
        self.resultTreeWidget.noteNoSingleClicked.connect(self.resultPropertyTableWidget.onNoteClicked)
......
137 137
        self.actionOCR.triggered.connect(self.areaOcr)
138 138
        self.actionGenerateOutput.triggered.connect(self.generateOutput)
139 139
        self.pushButtonCreateSymbol.clicked.connect(self.createSymbol)
140
        self.pushButtonClearLog.clicked.connect(self.onClearLog)
140 141
        self.actionHMB_DATA.triggered.connect(self.onHMBData)
141 142
        self.actionEquipment_Data_List.triggered.connect(self.showEquipmentDataList)
142 143
        self.actionLine_Data_List.triggered.connect(self.showLineDataList)
......
193 194
                        icon.addPixmap(QtGui.QPixmap(":/newPrefix/doubleclick.png"), QtGui.QIcon.Normal, QtGui.QIcon.Off)
194 195
                        cellItem.setIcon(icon)
195 196
                        self.resultPropertyTableWidget.setItem(row, 1, cellItem)
196
                        
197
    
198
    '''
199
        @brief  add message listwidget
200
        @author humkyung
201
        @date   2018.07.31
202
    '''
203
    def addMessage(self, messageType, message):
204
        from AppDocData import MessageType
205

  
206
        try:
207
            current = QDateTime.currentDateTime()
208

  
209
            item = QListWidgetItem('{}: {}'.format(current.toString('hh:mm:ss'), message))
210
            if messageType == MessageType.Error:
211
                item.setBackground(Qt.red)
212

  
213
            self.listWidgetLog.insertItem(0, item)
214
        except Exception as ex:
215
            print('error occured({}) in {}:{}'.format(ex, sys.exc_info()[-1].tb_frame.f_code.co_filename, sys.exc_info()[-1].tb_lineno))
216

  
217
    '''
218
        @brief      clear log
219
        @author     humkyung
220
        @date       2018.08.01
221
    '''
222
    def onClearLog(self):
223
        self.listWidgetLog.clear()
224

  
197 225
    '''
198 226
        @brief      Area Zoom
199 227
        @author     Jeongwoo
......
452 480
                path = os.path.join(appDocData.getCurrentProject().getTempPath(), appDocData.imgName + '.xml')
453 481
                if os.path.isfile(path): self.loadRecognitionResultFromXml(path)
454 482
        except Exception as ex:
455
            print('error occured({}) in {}:{}'.format(ex, sys.exc_info()[-1].tb_frame.f_code.co_filename, sys.exc_info()[-1].tb_lineno))
483
            message = 'error occured({}) in {}:{}'.format(ex, sys.exc_info()[-1].tb_frame.f_code.co_filename, sys.exc_info()[-1].tb_lineno)
484
            self.addMessage(MessageType.Error, message)
456 485

  
457 486
        return self.path
458 487

  
......
646 675
            return
647 676

  
648 677
        try:
678
            self.onClearLog()
649 679
            self.dlg = QRecognitionDialog(self, self.path)
650 680
            self.dlg.exec_()
651 681
            if self.dlg.isAccepted == True:
652 682
                '''DO NOTHING'''
653 683
        except Exception as ex:
654
            print('error occured({}) in {}:{}'.format(ex, sys.exc_info()[-1].tb_frame.f_code.co_filename, sys.exc_info()[-1].tb_lineno))
684
            message = 'error occured({}) in {}:{}'.format(ex, sys.exc_info()[-1].tb_frame.f_code.co_filename, sys.exc_info()[-1].tb_lineno)
685
            self.addMessage(MessageType.Error, message)
655 686

  
656 687
    '''
657 688
        @brief
......
697 728
                    if issubclass(type(connectedItem), SymbolSvgItem): self.resultTreeWidget.addTreeItem(item, connectedItem)
698 729
            # up to here
699 730
        except Exception as ex:
700
            print('error occured({}) in {}:{}'.format(ex, sys.exc_info()[-1].tb_frame.f_code.co_filename, sys.exc_info()[-1].tb_lineno))
731
            message = 'error occured({}) in {}:{}'.format(ex, sys.exc_info()[-1].tb_frame.f_code.co_filename, sys.exc_info()[-1].tb_lineno)
732
            self.addMessage(MessageType.Error, message)
701 733

  
702 734
    '''
703 735
        @history    2018.05.25  Jeongwoo    Moved from MainWindow
......
769 801
                    self.graphicsView.scene.addItem(item)
770 802
            # up to here
771 803
        except Exception as ex:
772
            print('error occured({}) in {}:{}'.format(ex, sys.exc_info()[-1].tb_frame.f_code.co_filename, sys.exc_info()[-1].tb_lineno))
804
            message = 'error occured({}) in {}:{}'.format(ex, sys.exc_info()[-1].tb_frame.f_code.co_filename, sys.exc_info()[-1].tb_lineno)
805
            self.addMessage(MessageType.Error, message)
773 806

  
774 807
    '''
775 808
        @history    2018.06.08  Jeongwoo    Add parameter on round method
......
811 844
                    item.transfer.onRemoved.connect(self.itemRemoved)
812 845
                    self.addTextItemToScene(item)
813 846
        except Exception as ex:
814
            print('error occured({}) in {}:{}'.format(ex, sys.exc_info()[-1].tb_frame.f_code.co_filename, sys.exc_info()[-1].tb_lineno))
847
            message = 'error occured({}) in {}:{}'.format(ex, sys.exc_info()[-1].tb_frame.f_code.co_filename, sys.exc_info()[-1].tb_lineno)
848
            self.addMessage(MessageType.Error, message)
815 849

  
816 850
    def drawDetectedNoteItem(self, noteTextInfoList):
817 851
        from TextItemFactory import TextItemFactory
......
832 866
                item.setPlainText(text)
833 867
                self.addTextItemToScene(item)
834 868
        except Exception as ex:
835
            print('error occured({}) in {}:{}'.format(ex, sys.exc_info()[-1].tb_frame.f_code.co_filename, sys.exc_info()[-1].tb_lineno))
869
            message = 'error occured({}) in {}:{}'.format(ex, sys.exc_info()[-1].tb_frame.f_code.co_filename, sys.exc_info()[-1].tb_lineno)
870
            self.addMessage(MessageType.Error, message)
836 871

  
837 872
    '''
838 873
        @brief  draw unknown items 
......
884 919
            notFilePath = os.path.join(project.getTempPath(), "NOT_" + os.path.basename(self.path))
885 920
            cv2.imwrite(notFilePath, imgNot)
886 921
        except Exception as ex:
887
            print('error occured({}) in {}:{}'.format(ex, sys.exc_info()[-1].tb_frame.f_code.co_filename, sys.exc_info()[-1].tb_lineno))
922
            message = 'error occured({}) in {}:{}'.format(ex, sys.exc_info()[-1].tb_frame.f_code.co_filename, sys.exc_info()[-1].tb_lineno)
923
            self.addMessage(MessageType.Error, message)
888 924

  
889 925
    '''
890 926
        @brief      load recognition result
......
1021 1057
            # Update Scene
1022 1058
            self.graphicsView.scene.update(self.graphicsView.sceneRect())
1023 1059
        except Exception as ex:
1024
            print('error occured({}) in {}:{}'.format(ex, sys.exc_info()[-1].tb_frame.f_code.co_filename, sys.exc_info()[-1].tb_lineno))
1060
            message = 'error occured({}) in {}:{}'.format(ex, sys.exc_info()[-1].tb_frame.f_code.co_filename, sys.exc_info()[-1].tb_lineno)
1061
            self.addMessage(MessageType.Error, message)
1025 1062

  
1026 1063
    '''
1027 1064
        @brief      Remove added item on same place and Add GraphicsItem
......
1091 1128
        @history    2018.06.18  Jeongwoo    Set Z-index
1092 1129
    '''
1093 1130
    def addUnknownItemToScene(self, unknownItem):
1094
        unknownRect = unknownItem.boundingRectOnScene()
1095
        items = self.graphicsView.scene.items(unknownRect)
1096
        if items is not None:
1097
            for item in items:
1098
                if issubclass(type(item), QEngineeringUnknownItem):
1099
                    itemRect = item.boundingRectOnScene()
1100
                    if itemRect.topLeft() == unknownRect.topLeft() and itemRect.size() == unknownRect.size(): # Equal
1101
                        item.deleteUnknownItemFromScene()
1102
        unknownItem.setZValue(-10)
1103
        unknownItem.addUnknownItemToScene(self.graphicsView.scene)
1131
        try:
1132
            unknownRect = unknownItem.boundingRectOnScene()
1133
            items = self.graphicsView.scene.items(unknownRect)
1134
            if items is not None:
1135
                for item in items:
1136
                    if issubclass(type(item), QEngineeringUnknownItem):
1137
                        itemRect = item.boundingRectOnScene()
1138
                        if itemRect.topLeft() == unknownRect.topLeft() and itemRect.size() == unknownRect.size(): # Equal
1139
                            item.deleteUnknownItemFromScene()
1140
            unknownItem.setZValue(-10)
1141
            unknownItem.addUnknownItemToScene(self.graphicsView.scene)
1142
        except Exception as ex:
1143
            message = 'error occured({}) in {}:{}'.format(ex, sys.exc_info()[-1].tb_frame.f_code.co_filename, sys.exc_info()[-1].tb_lineno)
1144
            self.addMessage(MessageType.Error, message)
1104 1145

  
1105 1146
    '''
1106 1147
        @brief      generate output xml file
......
1135 1176
            project = docData.getCurrentProject()
1136 1177
            cv2.imwrite(os.path.join(project.getTempPath() , 'OUTPUT.png') , docData.imgOutput)
1137 1178
        except Exception as ex:
1138
            print('error occured({}) in {}:{}'.format(ex, sys.exc_info()[-1].tb_frame.f_code.co_filename, sys.exc_info()[-1].tb_lineno))
1179
            message = 'error occured({}) in {}:{}'.format(ex, sys.exc_info()[-1].tb_frame.f_code.co_filename, sys.exc_info()[-1].tb_lineno)
1180
            self.addMessage(MessageType.Error, message)
1139 1181
            
1140 1182
if __name__ == '__main__':
1141 1183
    '''
......
1164 1206
        selectedProject = dlg.showDialog()
1165 1207
        if selectedProject is not None:
1166 1208
            AppDocData.instance().setCurrentProject(selectedProject)
1167
            app.mainWnd = MainWindow.instance()
1168
            app.mainWnd.show()
1209
            app._mainWnd = MainWindow.instance()
1210
            app._mainWnd.show()
1169 1211
    except Exception as ex:
1170 1212
        print('에러가 발생했습니다.\n', ex)
1171 1213

  
DTI_PID/DTI_PID/MainWindow_UI.py
11 11
class Ui_MainWindow(object):
12 12
    def setupUi(self, MainWindow):
13 13
        MainWindow.setObjectName("MainWindow")
14
        MainWindow.resize(1280, 720)
14
        MainWindow.resize(1280, 888)
15 15
        font = QtGui.QFont()
... 이 차이점은 표시할 수 있는 최대 줄수를 초과해서 이 차이점은 잘렸습니다.

내보내기 Unified diff

클립보드 이미지 추가 (최대 크기: 500 MB)