프로젝트

일반

사용자정보

통계
| 개정판:

hytos / DTI_PID / DTI_PID / QtImageViewer.py @ f5fc3a88

이력 | 보기 | 이력해설 | 다운로드 (41.5 KB)

1
# coding: utf-8
2
import sys
3
import os.path
4

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

    
16
sys.path.insert(0, os.path.dirname(os.path.realpath(__file__)) + '\\Commands')
17
import DefaultCommand
18

    
19
sys.path.insert(0, os.path.dirname(os.path.realpath(__file__)) + '\\Shapes')
20
from EngineeringLineItem import QEngineeringLineItem
21
from EngineeringTextItem import QEngineeringTextItem
22
from EngineeringSpecBreakItem import QEngineeringSpecBreakItem
23
from SymbolSvgItem import SymbolSvgItem
24

    
25
__author__ = "Marcel Goldschen-Ohm <marcel.goldschen@gmail.com>"
26
__version__ = '0.9.0'
27

    
28

    
29
class QtImageViewer(QGraphicsView):
30
    """ PyQt image viewer widget for a QPixmap in a QGraphicsView scene with mouse zooming and panning.
31
    Displays a QImage or QPixmap (QImage is internally converted to a QPixmap).
32
    To display any other image format, you must first convert it to a QImage or QPixmap.
33
    Some useful image format conversion utilities:
34
        qimage2ndarray: NumPy ndarray <==> QImage    (https://github.com/hmeine/qimage2ndarray)
35
        ImageQt: PIL Image <==> QImage  (https://github.com/python-pillow/Pillow/blob/master/PIL/ImageQt.py)
36
    Mouse interaction:
37
        Left mouse button drag: Pan image.
38
        Right mouse button drag: Zoom box.
39
        Right mouse button doubleclick: Zoom to show entire image.
40
    """
41

    
42
    # Mouse button signals emit image scene (x, y) coordinates.
43
    # !!! For image (row, column) matrix indexing, row = y and column = x.
44
    leftMouseButtonPressed = pyqtSignal(float, float)
45
    rightMouseButtonPressed = pyqtSignal(float, float)
46
    leftMouseButtonMoved = pyqtSignal(float, float)
47
    rightMouseButtonMoved = pyqtSignal(float, float)
48
    leftMouseButtonReleased = pyqtSignal(float, float)
49
    rightMouseButtonReleased = pyqtSignal(float, float)
50
    leftMouseButtonDoubleClicked = pyqtSignal(float, float)
51
    rightMouseButtonDoubleClicked = pyqtSignal(float, float)
52
    # itemRemoved = pyqtSignal(QGraphicsItem)
53
    startPointChanged = pyqtSignal(float, float)
54

    
55
    '''
56
        @history    2018.06.27  Jeongwoo    Change zoom rule (Qt.KeepAspectRatioByExpanding → Qt.KeepAspectRatio)
57
    '''
58

    
59
    def __init__(self, mainWindow=None):
60
        QGraphicsView.__init__(self)
61

    
62
        self.mainWindow = mainWindow
63
        # Image is displayed as a QPixmap in a QGraphicsScene attached to this QGraphicsView.
64
        self.command = None
65

    
66
        self.scaleFactor = 1.0
67
        self.numScheduledScalings = 0
68
        self.isOriginalPointSelected = False
69

    
70
        # Store a local handle to the scene's current image pixmap.
71
        self._pixmapHandle = None
72

    
73
        # Image aspect ratio mode.
74
        # !!! ONLY applies to full image. Aspect ratio is always ignored when zooming.
75
        #   Qt.IgnoreAspectRatio: Scale image to fit viewport.
76
        #   Qt.KeepAspectRatio: Scale image to fit inside viewport, preserving aspect ratio.
77
        #   Qt.KeepAspectRatioByExpanding: Scale image to fill the viewport, preserving aspect ratio.
78
        self.aspectRatioMode = Qt.KeepAspectRatio
79

    
80
        # Scroll bar behaviour.
81
        #   Qt.ScrollBarAlwaysOff: Never shows a scroll bar.
82
        #   Qt.ScrollBarAlwaysOn: Always shows a scroll bar.
83
        #   Qt.ScrollBarAsNeeded: Shows a scroll bar only when zoomed.
84
        self.setHorizontalScrollBarPolicy(Qt.ScrollBarAsNeeded)
85
        self.setVerticalScrollBarPolicy(Qt.ScrollBarAsNeeded)
86
        # self.setHorizontalScrollBarPolicy(Qt.ScrollBarAlwaysOff)
87
        # self.setVerticalScrollBarPolicy(Qt.ScrollBarAlwaysOff)
88

    
89
        # Stack of QRectF zoom boxes in scene coordinates.
90
        self.zoomStack = []
91

    
92
        self.setRenderHint(QPainter.Antialiasing)
93

    
94
        self.setAcceptDrops(True)  # enable drop
95

    
96
        # Flags for enabling/disabling mouse interaction.
97
        self.canZoom = True
98
        self.canPan = True
99
        self.setMouseTracking(True)
100
        self.command = None
101

    
102
        # set currentAttribute
103
        self.currentAttribute = ''
104

    
105
        # image stack for symboleditor
106
        self._image_stack = []
107
        self._image_stack_index = -1
108

    
109
    '''
110
        @brief      Return Pixmap Handler
111
        @author     Jeongwoo
112
        @date       2018.06.11
113
    '''
114

    
115
    def getPixmapHandle(self):
116
        return self._pixmapHandle
117

    
118
    '''
119
        @brief      Use Default ImageViewer Command
120
        @author     Jeongwoo
121
        @date       18.04.10
122
        @history    .
123
    '''
124

    
125
    def useDefaultCommand(self):
126
        """ Use Default Command
127
        """
128
        self.command = DefaultCommand.DefaultCommand(self)
129

    
130
    def hasImage(self):
131
        """ Returns whether or not the scene contains an image pixmap.
132
        """
133
        return self._pixmapHandle is not None
134

    
135
    def clearImage(self):
136
        """ Removes the current image pixmap from the scene if it exists.
137
        """
138
        if self.hasImage():
139
            self.scene().removeItem(self._pixmapHandle)
140
            self._pixmapHandle = None
141

    
142
    def pixmap(self):
143
        """ Returns the scene's current image pixmap as a QPixmap, or else None if no image exists.
144
        :rtype: QPixmap | None
145
        """
146
        if self.hasImage():
147
            return self._pixmapHandle.pixmap()
148
        return None
149

    
150
    def image(self):
151
        """ Returns the scene's current image pixmap as a QImage, or else None if no image exists.
152
        :rtype: QImage | None
153
        """
154
        if self.hasImage():
155
            return self._pixmapHandle.pixmap().toImage()
156
        return None
157

    
158
    def setImage(self, image, clear_object=True):
159
        """ Set the scene's current image pixmap to the input QImage or QPixmap.
160
        Raises a RuntimeError if the input image has type other than QImage or QPixmap.
161
        :type image: QImage | QPixmap
162
        """
163
        try:
164
            if type(image) is QPixmap:
165
                pixmap = image
166
            elif type(image) is QImage:
167
                pixmap = QPixmap.fromImage(image)
168
            else:
169
                raise RuntimeError("ImageViewer.setImage: Argument must be a QImage or QPixmap.")
170

    
171
            self.clearImage()
172
            if clear_object:
173
                self.scene().clear()
174

    
175
            if self.hasImage():
176
                self._pixmapHandle.setPixmap(pixmap)
177
            else:
178
                self._pixmapHandle = self.scene().addPixmap(pixmap)
179
                self._pixmapHandle.setFlags(QGraphicsItem.ItemClipsChildrenToShape)
180
                self._pixmapHandle.setZValue(-1)
181

    
182
            self.setSceneRect(QRectF(pixmap.rect()))  # Set scene size to image size.
183
            self.updateViewer()
184
        except Exception as ex:
185
            print('error occurred({}) in {}:{}'.format(ex, sys.exc_info()[-1].tb_frame.f_code.co_filename,
186
                                                       sys.exc_info()[-1].tb_lineno))
187

    
188
    '''
189
        @brief  open a image file selected by user
190
        @author 
191
        @date
192
    '''
193
    def loadImageFromFile(self, drawing):
194
        import cv2
195
        import numpy as np
196
        from AppDocData import AppDocData
197
        """ Load an image from file.
198
        Without any arguments, loadImageFromFile() will popup a file dialog to choose the image file.
199
        With a fileName argument, loadImageFromFile(fileName) will attempt to load the specified image file directly.
200
        """
201

    
202
        file_path = None
203
        try:
204
            app_doc_data = AppDocData.instance()
205

    
206
            cvImg = None
207
            if drawing:
208
                file_path = drawing.file_path
209
                cvImg = drawing.image
210
            else:
211
                options = QFileDialog.Options()
212
                options |= QFileDialog.DontUseNativeDialog
213
                if QT_VERSION_STR[0] == '4':
214
                    file_path = QFileDialog.getOpenFileName(self, "Open image file",
215
                                                            app_doc_data.project.getDrawingFilePath(),
216
                                                            "Image files(*.png *.jpg)", options=options)
217
                elif QT_VERSION_STR[0] == '5':
218
                    file_path, _ = QFileDialog.getOpenFileName(self, "Open image file",
219
                                                               app_doc_data.project.getDrawingFilePath(),
220
                                                               "Image files(*.png *.jpg)", options=options)
221

    
222
                _bytes = None
223
                with open(file_path.encode('utf-8'), 'rb') as stream:
224
                    _bytes = stream.read()
225

    
226
                numpyArray = np.asarray(bytearray(_bytes), dtype=np.uint8)
227
                image = cv2.imdecode(numpyArray, cv2.IMREAD_UNCHANGED)
228
                cvImg = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
229
                cvImg = cv2.threshold(cvImg, 0, 255, cv2.THRESH_BINARY + cv2.THRESH_OTSU)[1]
230

    
231
                configs = app_doc_data.getConfigs('Filter', 'DilateSize')
232
                if 1 == len(configs) and int(configs[0].value) is not 0:
233
                    size = int(configs[0].value)
234
                    kernel = np.ones((size, size), np.uint8)
235
                    cvImg = cv2.erode(cvImg, kernel, iterations=1)
236

    
237
                configs = app_doc_data.getConfigs('Filter', 'FlatSize')
238
                if 1 == len(configs) and int(configs[0].value) is not 0:
239
                    size = int(configs[0].value)
240
                    kernel = np.ones((size, size), np.uint8)
241
                    cvImg = cv2.morphologyEx(cvImg, cv2.MORPH_CLOSE, kernel)
242
                    cvImg = cv2.morphologyEx(cvImg, cv2.MORPH_OPEN, kernel)
243

    
244
            bytesPerLine = cvImg.shape[1]
245
            image = QImage(cvImg.data, cvImg.shape[1], cvImg.shape[0], bytesPerLine, QImage.Format_Indexed8)
246
            self.setImage(image)
247
        except Exception as ex:
248
            from App import App
249
            from AppDocData import MessageType
250

    
251
            message = 'error occurred({}) in {}:{}'.format(repr(ex), sys.exc_info()[-1].tb_frame.f_code.co_filename,
252
                                                           sys.exc_info()[-1].tb_lineno)
253
            App.mainWnd().addMessage.emit(MessageType.Error, message)
254

    
255
        return file_path
256

    
257
    def zoom_rect(self, rect: QRectF) -> None:
258
        """zoom window within given rectangle"""
259
        self.fitInView(rect, Qt.KeepAspectRatio)
260
        if not self.zoomStack:
261
            self.zoomStack = []
262

    
263
        self.zoomStack.append(rect)
264

    
265

    
266
    '''
267
        @history    2018.06.27  Jeongwoo    Change zoom rule (Qt.KeepAspectRatioByExpanding → Qt.KeepAspectRatio)
268
    '''
269

    
270
    def updateViewer(self, zoomNewRect=None):
271
        """Show current zoom (if showing entire image, apply current aspect ratio mode)."""
272
        if not self.hasImage():
273
            return
274

    
275
        if zoomNewRect is not None:
276
            self.fitInView(zoomNewRect, Qt.KeepAspectRatio)
277
        else:
278
            if self.zoomStack:
279
                if zoomNewRect is None:
280
                    self.fitInView(self.zoomStack[-1], Qt.KeepAspectRatio)  # Show zoomed rect (ignore aspect ratio).
281
            else:
282
                self.zoomStack = []  # Clear the zoom stack (in case we got here because of an invalid zoom).
283
                self.fitInView(self.sceneRect(),
284
                               self.aspectRatioMode)  # Show entire image (use current aspect ratio mode).
285

    
286
    def zoomImageInit(self):
287
        if self.hasImage():
288
            self.zoomStack = []
289
            self.updateViewer()
290
            self.setCursor(QCursor(Qt.ArrowCursor))
291

    
292
    '''
293
        @brief      Zoom in & out image
294
        @author     Jeongwoo
295
        @date       -
296
        @history    18.04.11    Jeongwoo    add parameter 'adjust' (@ref ResultTreeWidget.itemClickEvent(self, item, columnNo))
297
    '''
298
    def zoomImage(self, isZoomIn, event, adjust=1):
299
        """Zoom in & out """
300

    
301
        HALF_SIZE = 300
302
        clickPos = event.pos()
303
        if isZoomIn:
304
            left_top = self.mapToScene(clickPos.x() - HALF_SIZE // adjust, clickPos.y() - HALF_SIZE // adjust)
305
            right_bottom = self.mapToScene(clickPos.x() + HALF_SIZE // adjust, clickPos.y() + HALF_SIZE // adjust)
306

    
307
            zoomArea = QRectF(left_top, right_bottom)
308
            self.scene().setSelectionArea(QPainterPath())  # Clear current selection area.
309
            if zoomArea.isValid():
310
                self.zoomStack.append(zoomArea)
311
                self.updateViewer(zoomArea)
312
        else:
313
            zoomNewRect = None
314
            self.scene().clearSelection()
315
            if self.zoomStack:
316
                clickPos = self.mapToScene(clickPos.x(), clickPos.y())
317
                zoomNewRect = self.zoomStack.pop()
318
                zoomNewRect = QRectF(clickPos.x() - zoomNewRect.width() / 2, clickPos.y() - zoomNewRect.height() / 2, zoomNewRect.width(), zoomNewRect.height())
319
            else:
320
                left_top = self.mapToScene(clickPos.x() - HALF_SIZE // adjust, clickPos.y() - HALF_SIZE // adjust)
321
                right_bottom = self.mapToScene(clickPos.x() + HALF_SIZE // adjust, clickPos.y() + HALF_SIZE // adjust)
322

    
323
                zoomArea = QRectF(left_top, right_bottom)
324
                self.scene().clearSelection()
325
                if zoomArea.isValid():
326
                    zoomNewRect = zoomArea
327

    
328
            self.updateViewer(zoomNewRect)
329

    
330
    '''
331
        @brief  mouse move event
332
    '''
333
    def mouseMoveEvent(self, event):
334
        try:
335
            scenePos = self.mapToScene(event.pos())
336
            if self.command is not None:
337
                self.command.execute(['mouseMoveEvent', event, scenePos])
338
                if self.command.name == "SelectAttribute":
339
                    QGraphicsView.mouseMoveEvent(self, event)
340
                if self.command.isTreated:
341
                    event.accept()
342
                    return
343
        except Exception as ex:
344
            print('error occurred({}) in {}:{}'.format(ex, sys.exc_info()[-1].tb_frame.f_code.co_filename,
345
                                                       sys.exc_info()[-1].tb_lineno))
346

    
347
        if self.scene().guidesEnabled:
348
            self.scene().coords = self.mapToScene(event.pos())
349
            self.scene().invalidate()
350

    
351
        QGraphicsView.mouseMoveEvent(self, event)
352

    
353
    '''
354
        @brief      
355
        @author     
356
        @date       
357
        @history    block clear selection when right mouse button is clicked
358
    '''
359
    def mousePressEvent(self, event):
360
        try:
361
            if self.command is not None:
362
                # for symboleditor
363
                if self.window().objectName() == 'SymbolEditorDialog':
364
                    self._image_stack_index = self._image_stack_index + 1
365
                    self._image_stack.insert(self._image_stack_index, self.image().copy())
366

    
367
                scenePos = self.mapToScene(event.pos())
368
                self.command.execute(['mousePressEvent', event, scenePos])
369
                if self.command.isTreated:
370
                    event.accept()
371
                    return
372
        except Exception as ex:
373
            from App import App
374
            from AppDocData import MessageType
375

    
376
            message = 'error occurred({}) in {}:{}'.format(repr(ex), sys.exc_info()[-1].tb_frame.f_code.co_filename,
377
                                                           sys.exc_info()[-1].tb_lineno)
378
            App.mainWnd().addMessage.emit(MessageType.Error, message)
379

    
380
        if event.button() != Qt.RightButton:
381
            QGraphicsView.mousePressEvent(self, event)
382

    
383
    '''
384
        @brief      
385
        @author     
386
        @date       
387
    '''
388

    
389
    def mouseReleaseEvent(self, event):
390
        try:
391
            if self.command is not None:
392
                scenePos = self.mapToScene(event.pos())
393
                instance = self.command.execute(['mouseReleaseEvent', event, scenePos])
394
                if instance is not None:
395
                    self.scene().addItem(instance)
396

    
397
                if self.command is not None and self.command.isTreated == True:
398
                    if self.command.name == 'Default' and self.command.isCopy:
399
                        return
400
                    self.command = DefaultCommand.DefaultCommand(self)
401
                    cursor = QCursor(Qt.ArrowCursor)
402
                    QApplication.instance().setOverrideCursor(cursor)
403
                    return
404
            else:
405
                QGraphicsView.mouseReleaseEvent(self, event)
406
        except Exception as ex:
407
            from App import App
408
            from AppDocData import MessageType
409

    
410
            message = 'error occurred({}) in {}:{}'.format(repr(ex), sys.exc_info()[-1].tb_frame.f_code.co_filename,
411
                                                           sys.exc_info()[-1].tb_lineno)
412
            App.mainWnd().addMessage.emit(MessageType.Error, message)
413

    
414
    """
415
        @brief      Show entire image.
416
    """
417

    
418
    def mouseDoubleClickEvent(self, event):
419
        scenePos = self.mapToScene(event.pos())
420
        if self.command is not None:
421
            instance = self.command.execute(['mouseDoubleClickEvent', event, scenePos])
422
            if self.command.isTreated == True: return
423

    
424
        if event.button() == Qt.LeftButton:
425
            self.leftMouseButtonDoubleClicked.emit(scenePos.x(), scenePos.y())
426
        """
427
        elif event.button() == Qt.RightButton:
428
            if self.canZoom:
429
                self.zoomStack = []  # Clear zoom stack.
430
                self.updateViewer()
431
            self.rightMouseButtonDoubleClicked.emit(scenePos.x(), scenePos.y())
432
        """
433

    
434
        QGraphicsView.mouseDoubleClickEvent(self, event)
435

    
436
    '''
437
        @brief      key press event
438
        @author     Jeongwoo
439
        @date       2018.??.??
440
        @history    send escape key event to command
441
    '''
442

    
443
    def keyPressEvent(self, event):
444
        from TrainingEditorDialog import QTrainingEditorDialog
445
        from TrainingSymbolEditorDialog import QTrainingSymbolEditorDialog
446

    
447
        try:
448
            #print('view : ' + str(event.key()))
449
            if event.key() == Qt.Key_Escape or event.key() == Qt.Key_Return:
450
                if self.command is not None:
451
                    self.command.execute(['keyPressEvent', event, []])
452
                    if self.command.isTreated:
453
                        self.command = DefaultCommand.DefaultCommand(self)
454
                        return
455
            else:
456
                if self.command is not None:
457
                    self.command.execute(['keyPressEvent', event, []])
458
                    if self.command.isTreated: return
459
            if type(self.mainWindow) is QTrainingEditorDialog or type(self.mainWindow) is QTrainingSymbolEditorDialog:
460
                self.mainWindow.keyPressEvent(event)
461

    
462
            #print(QApplication.keyboardModifiers() == Qt.ControlModifier)
463
            QGraphicsView.keyPressEvent(self, event)
464
        except Exception as ex:
465
            message = f"error occurred({repr(ex)}) in {sys.exc_info()[-1].tb_frame.f_code.co_filename}:" \
466
                      f"{sys.exc_info()[-1].tb_lineno}"
467

    
468
    '''
469
        @brief  key release event
470
        @author Jeongwoo
471
        @date   2018.??.??
472
    '''
473

    
474
    def keyReleaseEvent(self, event):
475
        if event.key() == Qt.Key_Delete:
476
            pass
477

    
478
        QGraphicsView.keyReleaseEvent(self, event)
479

    
480
    '''
481
        @brief      mouse wheel event
482
        @autor      humkyung
483
        @date       
484
    '''
485

    
486
    def wheelEvent(self, event):
487
        if event.modifiers() == Qt.ControlModifier:
488
            if self.canZoom and self.hasImage():
489
                numDegrees = event.angleDelta() / 8
490
                if numDegrees is not None:
491
                    if numDegrees.y() > 0:
492
                        self.zoomImage(True, event, 1.5)
493
                    elif numDegrees.y() < 0:
494
                        self.zoomImage(False, event, 0.5)
495
        else:
496
            super().wheelEvent(event)
497

    
498
    '''
499
        @brief      draw background
500
        @author     humkyung
501
        @date       2018.07.23
502
    '''
503

    
504
    """
505
    def drawBackground(self, painter, rect):
506
        QGraphicsView.drawBackground(self, painter, rect)
507
    """
508

    
509
    '''
510
        @history    2018.06.11  Jeongwoo    Change method to manage guideline items
511
                    humkyung 2018.08.28 remove guide lines before drawing
512
    '''
513
    GUIDELINE_ITEMS = []
514

    
515
    def showGuideline(self, pos, isShow):
516
        image = self.image()
517
        width = image.width()
518
        height = image.height()
519
        pen = QPen()
520
        pen.setColor(QColor(180, 180, 180))
521
        pen.setStyle(Qt.DashLine)
522
        pen.setWidthF(0.5)
523
        if isShow:
524
            items = self.scene().items()
525
            for item in self.GUIDELINE_ITEMS:
526
                if item in items:
527
                    self.scene().removeItem(item)
528
            self.GUIDELINE_ITEMS.clear()
529

    
530
            if pos is not None:
531
                verticalLine = self.scene().addLine(pos.x(), 0, pos.x(), height, pen)
532
                horizontalLine = self.scene().addLine(0, pos.y(), width, pos.y(), pen)
533
            else:
534
                verticalLine = self.scene().addLine(round(width * 0.5), 0, round(width * 0.5), height, pen)
535
                horizontalLine = self.scene().addLine(0, round(height * 0.5), width, round(height * 0.5), pen)
536

    
537
            self.GUIDELINE_ITEMS.append(verticalLine)
538
            self.GUIDELINE_ITEMS.append(horizontalLine)
539
        else:
540
            items = self.scene().items()
541
            for item in self.GUIDELINE_ITEMS:
542
                if item in items:
543
                    self.scene().removeItem(item)
544
            self.GUIDELINE_ITEMS.clear()
545

    
546
    '''
547
        @brief  drag enter event
548
        @author humkyung
549
        @date   2018.04.17
550
    '''
551

    
552
    def dragEnterEvent(self, event):
553
        event.acceptProposedAction()
554

    
555
    '''
556
        @brief      drag move event
557
        @author     humkyung
558
        @date       2018.04.17
559
        @history    humkyung 2018.08.21 highlight item under mouse
560
    '''
561

    
562
    def dragMoveEvent(self, event):
563
        scenePos = self.mapToScene(event.pos())
564
        items = [item for item in self.scene().items(scenePos) if
565
                 type(item) is not QGraphicsPixmapItem and type(item) is not QGraphicsTextItem]
566
        if len(items) > 0:
567
            if not hasattr(self, '_underItem') or self._underItem is not items[0]:
568
                if hasattr(self, '_underItem') and self._underItem is not None:
569
                    if hasattr(self._underItem, 'highlight') and self._underItem in self.scene().items():
570
                        self._underItem.highlight(False)
571
                    else:
572
                        self._underItem = None
573

    
574
                self._underItem = items[0]
575
                if hasattr(self._underItem, 'highlight'):
576
                    self._underItem.highlight(True)
577
        # elif hasattr(self, '_underItem') and self._underItem is not None:
578
        #    self._underItem.hoverLeaveEvent(event)
579
        #    self._underItem = None
580

    
581
        event.acceptProposedAction()
582

    
583
    def dropEvent(self, event):
584
        """drop a symbol"""
585
        from AppDocData import AppDocData
586
        from EngineeringEndBreakItem import QEngineeringEndBreakItem
587
        from EngineeringSpecBreakItem import QEngineeringSpecBreakItem
588

    
589
        if len(self.scene().items()) is 0:
590
            return
591
        if hasattr(self, '_underItem') and self._underItem is not None and self._underItem in self.scene().items():
592
            self._underItem.hoverLeaveEvent(None)
593
            self._underItem = None
594
        else:
595
            self._underItem = None
596

    
597
        for item in self.scene().selectedItems():
598
            item.clearFocus()
599
            item.setSelected(False)
600

    
601
        scenePos = self.mapToScene(event.pos())
602
        name = event.mimeData().text()
603
        svg = QtImageViewer.createSymbolObject(name, event.mimeData().tag.getScale())
604
        fixedAngle = 0.0 if QApplication.keyboardModifiers() == Qt.ControlModifier else None
605
        QtImageViewer.matchSymbolToLine(self.scene(), svg, scenePos, angle=fixedAngle)
606
        if svg:
607
            if type(svg) is QEngineeringEndBreakItem or type(svg) is QEngineeringSpecBreakItem:
608
                svg.set_property('Freeze', True)
609

    
610
            svg.setSelected(True)
611
            svg.setFocus()
612
            self.setFocus()  # set focus to graphicview
613

    
614
        event.acceptProposedAction()
615

    
616
    '''
617
        @brief  drop create Symbol
618
        @author kyouho
619
        @date   2018.07.27
620
    '''
621
    @staticmethod
622
    def createSymbolObject(name, scale=[1.0, 1.0]):
623
        """create a symbol object has given uid"""
624
        from AppDocData import AppDocData
625

    
626
        app_doc_data = AppDocData.instance()
627

    
628
        symbol = app_doc_data.getSymbolByQuery('name', name)
629
        if symbol:
630
            svg_file_name = symbol.sName
631
            svgFilePath = os.path.join(app_doc_data.getCurrentProject().getSvgFilePath(), symbol.getType(),
632
                                    svg_file_name + '.svg')
633
            svg = SymbolSvgItem.createItem(symbol.getType(), None, svgFilePath)
634
            connPts = None
635
            strConnPts = symbol.getConnectionPoint()
636
            if strConnPts is not None and strConnPts != '':
637
                connPts = [(float(x.split(',')[0]), float(x.split(',')[1])) if len(x.split(',')) == 2 else (
638
                    x.split(',')[0], float(x.split(',')[1]), float(x.split(',')[2])) \
639
                        for x in strConnPts.split('/')]
640

    
641
            svg.buildItem(svg_file_name, symbol.getType(), 0, None, [symbol.width, symbol.height], None, connPts, symbol.getBaseSymbol(),
642
                        symbol.getAdditionalSymbol(), symbol.getHasInstrumentLabel(), None, scale)
643

    
644
            return svg
645
        else:
646
            return None
647

    
648
    '''
649
        @brief      match symbol to line
650
        @author     kyouho
651
        @date       2018.07.27
652
        @history    humkyung 2018.08.23 change scenePos to connector's center when symbol is placed on connector
653
    '''
654
    @staticmethod
655
    def matchSymbolToLine(scene, svg, scenePos, angle=None, flip=None, strict=False, auto=False, move=False):
656
        from EngineeringConnectorItem import QEngineeringConnectorItem
657
        from EngineeringLineItem import QEngineeringLineItem
658
        from SymbolSvgItem import SymbolSvgItem
659
        from EngineeringUnknownItem import QEngineeringUnknownItem
660
        from EngineeringAbstractItem import QEngineeringAbstractItem
661
        
662
        import math
663
        from App import App
664
        from AppDocData import AppDocData
665

    
666
        try:
667
            if not svg.scene():
668
                svg.transfer.onRemoved.connect(App.mainWnd().itemRemoved)
669

    
670
            items = [item for item in scene.items(scenePos) if type(item) is not QEngineeringUnknownItem and \
671
                    type(item) is not QGraphicsPixmapItem and type(item) is not QGraphicsTextItem and item is not svg]
672
            connectors = []
673
            if len(items) > 0 and type(items[0]) is QEngineeringConnectorItem:
674
                scenePos = QPointF(items[0].center()[0], items[0].center()[1])
675
                connectors = [connector for connector in items if issubclass(type(connector.parentItem()), SymbolSvgItem) \
676
                                and type(connector) is QEngineeringConnectorItem]
677
                if not connectors:
678
                    connectors = [connector for connector in items if type(connector.parentItem()) is QEngineeringLineItem \
679
                                        and type(connector) is QEngineeringConnectorItem]
680

    
681
            #matches = [item for item in scene.items() if
682
            #        (type(item) is QEngineeringLineItem) and (item.distanceTo((scenePos.x(), scenePos.y())) < 20)]
683
            allowed_error = 0.0001
684
            if len(connectors) == 1 and len(svg.connectors) >= 2 and len(connectors[0].parentItem().connectors):
685
                if move:
686
                    svg.scene().removeItem(svg)
687
                    svg.resetPosition()
688

    
689
                # item assistant with connection
690
                xl = connectors[0].parentItem().symbolOrigin[0] - connectors[0].connectPoint[0]
691
                yl = connectors[0].parentItem().symbolOrigin[1] - connectors[0].connectPoint[1]
692
                length = math.sqrt(xl * xl + yl * yl)
693
                if length < allowed_error:
694
                    return
695
                ddx = (connectors[0].sceneBoundingRect().center().x() - connectors[0].parentItem().origin[0]) / length
696
                ddy = (connectors[0].sceneBoundingRect().center().y() - connectors[0].parentItem().origin[1]) / length
697
                dx, dy = abs(svg.connectors[1].connectPoint[0] - svg.symbolOrigin[0]), abs(svg.connectors[1].connectPoint[1] - svg.symbolOrigin[1])
698
                length = math.sqrt(dx * dx + dy * dy)
699
                if length < allowed_error:
700
                    return
701
                dx, dy = length * ddx, length * ddy
702

    
703
                # if abs(connectors[0].parentItem().angle - math.pi / 2) < allowed_error or abs(connectors[0].parentItem().angle - math.pi / 2 * 3) < allowed_error:
704
                #    dx, dy = ddx * dy, ddy * dx
705
                # else:
706
                #    dx, dy = ddx * dx, ddy * dy
707

    
708
                xxl = connectors[0].parentItem().origin[0] - connectors[0].center()[0]
709
                yyl = connectors[0].parentItem().origin[1] - connectors[0].center()[1]
710
                rAngle = -math.atan2(yyl, xxl)# if flip == 0 else math.atan2(yl, xl)
711
                rAngle = abs(rAngle) if rAngle < 0 + allowed_error else 2 * math.pi - rAngle
712
                if angle is not None:
713
                    rAngle = angle
714
                svg.angle = rAngle
715

    
716
                x, y = connectors[0].sceneBoundingRect().center().x() + dx, \
717
                    connectors[0].sceneBoundingRect().center().y() + dy
718
                svg.loc = [x - svg.symbolOrigin[0], y - svg.symbolOrigin[1]]
719
                svg.origin = [x, y]
720
                svg.addSvgItemToScene(scene, True if not auto else False)
721

    
722
                items = [item for item in scene.items(scenePos) if
723
                        type(item) is not QGraphicsPixmapItem and type(item) is not QGraphicsTextItem]
724
                items = [item for item in items if item.parentItem() is svg and type(item) is QEngineeringConnectorItem]
725
                if items and connectors[0].connectedItem and type(connectors[0].connectedItem) is QEngineeringLineItem:
726
                    items[0].connect(connectors[0].parentItem())
727
                    anotherConns = [conn for conn in svg.connectors if conn is not items[0]]
728
                    anotherConns[0].connect(connectors[0].connectedItem)
729
                    for lineConn in connectors[0].connectedItem.connectors:
730
                        if lineConn.connectedItem is connectors[0].parentItem():
731
                            lineConn.connect(svg)
732
                            lineConn.setPos(anotherConns[0].center())
733
                            break
734
                    connectors[0].connect(svg)
735
                    lineConn.transfer.onPosChanged.emit(lineConn)
736
                elif items:
737
                    items[0].connect(connectors[0].parentItem())
738
                    #items[0].highlight(False)
739
                    if connectors[0].connectedItem:
740
                        for conn in connectors[0].connectedItem.connectors:
741
                            if conn.connectedItem is connectors[0].parentItem():
742
                                conn.connect(None)
743
                                #conn.highlight(False)
744
                                break
745
                    connectors[0].connect(svg)
746
                    #connectors[0].highlight(False)
747
            elif not strict and len(items) == 1 and type(items[0]) is QEngineeringLineItem and items[0].length() > svg.size[0] and items[0].length() > svg.size[1]:
748
                if move:
749
                    svg.scene().removeItem(svg)
750
                    svg.resetPosition()
751
                    
752
                vec = items[0].perpendicular()
753
                line = [(scenePos.x() - vec[0] * 20, scenePos.y() - vec[1] * 20), (scenePos.x() + vec[0] * 20, scenePos.y() + vec[1] * 20)]
754
                origin = items[0].intersection(line)
755
                configs = AppDocData.instance().getConfigs('Data', 'Grid')
756
                grid = int(configs[0].value) if 1 == len(configs) else -1
757
                if grid == 1:
758
                    svg.origin = [round(origin.x), round(origin.y)]
759
                    svg.loc = [round(origin.x - svg.symbolOrigin[0]), round(origin.y - svg.symbolOrigin[1])]
760
                    '''
761
                    if not svg.symbolConvertingOrigin:
762
                        svg.origin = [round(origin.x), round(origin.y)]
763
                        svg.loc = [round(origin.x - svg.symbolOrigin[0]), round(origin.y - svg.symbolOrigin[1])]
764
                    else:
765
                        svg.origin = [round(origin.x - svg.symbolConvertingOrigin[0] + svg.symbolOrigin[0]), round(origin.y - svg.symbolConvertingOrigin[1] + svg.symbolOrigin[1])]
766
                        svg.loc = [round(origin.x - svg.symbolConvertingOrigin[0]), round(origin.y - svg.symbolConvertingOrigin[1])]
767
                    '''
768
                else:
769
                    svg.origin = [round(origin.x, 1), round(origin.y, 1)]
770
                    svg.loc = [round(origin.x - svg.symbolOrigin[0], 1), round(origin.y - svg.symbolOrigin[1], 1)]
771
                    '''
772
                    if not svg.symbolConvertingOrigin:
773
                        svg.origin = [round(origin.x, 1), round(origin.y, 1)]
774
                        svg.loc = [round(origin.x - svg.symbolOrigin[0], 1), round(origin.y - svg.symbolOrigin[1], 1)]
775
                    else:
776
                        svg.origin = [round(origin.x - svg.symbolConvertingOrigin[0] + svg.symbolOrigin[0], 1), round(origin.y - svg.symbolConvertingOrigin[1] + svg.symbolOrigin[1], 1)]
777
                        svg.loc = [round(origin.x - svg.symbolConvertingOrigin[0], 1), round(origin.y - svg.symbolConvertingOrigin[1], 1)]
778
                    '''
779
                svg.angle = items[0].angle()
780
                if items[0].start_point()[1] - items[0].end_point()[1] > 0:
781
                    svg.angle = math.pi - svg.angle
782

    
783
                svg.addSvgItemToScene(scene, True if not auto else False, True)
784

    
785
                if len(svg.connectors) > 1:
786
                    connectors = [svg.connectors[0], svg.connectors[1]]
787
                    connectors.sort(key=lambda x: abs(x.center()[0] - items[0].connectors[0].center()[0]) + abs(x.center()[1] - items[0].connectors[0].center()[1]))
788

    
789
                    savedConnectors = []
790
                    for _connector in items[0].connectors:
791
                        if _connector.connectedItem and _connector._connected_at == QEngineeringAbstractItem.CONNECTED_AT_PT:
792
                            _connectors2 = [_connector2 for _connector2 in _connector.connectedItem.connectors if _connector2.connectedItem is items[0] and _connector2._connected_at == QEngineeringAbstractItem.CONNECTED_AT_PT]
793
                            if _connectors2:
794
                                savedConnectors.append(_connectors2[0])
795
                                continue
796
                        savedConnectors.append(None)
797

    
798
                    inLine = QEngineeringLineItem(vertices=[items[0].start_point(), connectors[0].center()])
799
                    inLine.transfer.onRemoved.connect(App.mainWnd().itemRemoved)
800
                    inLine.lineType = items[0].lineType
801

    
802
                    outLine = QEngineeringLineItem(vertices=[connectors[1].center(), items[0].end_point()])
803
                    outLine.transfer.onRemoved.connect(App.mainWnd().itemRemoved)
804
                    outLine.lineType = items[0].lineType
805

    
806
                    inLine.connectors[0].connect(items[0].connectors[0].connectedItem, items[0].connectors[0]._connected_at)
807
                    inLine.connectors[1].connect(svg)
808
                    connectors[0].connect(inLine)
809
                    outLine.connectors[0].connect(svg)
810
                    outLine.connectors[1].connect(items[0].connectors[1].connectedItem, items[0].connectors[1]._connected_at)
811
                    connectors[1].connect(outLine)
812

    
813
                    if savedConnectors[0]:
814
                        savedConnectors[0].connect(inLine)
815
                    if savedConnectors[1]:
816
                        savedConnectors[1].connect(outLine)
817

    
818
                    scene.addItem(inLine)
819
                    scene.addItem(outLine)
820
                    items[0].transfer.onRemoved.emit(items[0])
821
                
822
            elif not strict and not move:
823
                svg.angle = angle if angle else 0.0
824
                svg.flip = flip if flip else 0
825
                configs = AppDocData.instance().getConfigs('Data', 'Grid')
826
                grid = int(configs[0].value) if 1 == len(configs) else -1
827
                if grid == 1:
828
                    svg.origin = [round(scenePos.x()), round(scenePos.y())]
829
                    svg.loc = [round(scenePos.x() - svg.symbolOrigin[0]), round(scenePos.y() - svg.symbolOrigin[1])]
830
                    '''
831
                    if not svg.symbolConvertingOrigin:
832
                        svg.origin = [round(scenePos.x()), round(scenePos.y())]
833
                        svg.loc = [round(scenePos.x() - svg.symbolOrigin[0]), round(scenePos.y() - svg.symbolOrigin[1])]
834
                    else:
835
                        svg.origin = [round(scenePos.x() - svg.symbolConvertingOrigin[0] + svg.symbolOrigin[0]), round(scenePos.y() - svg.symbolConvertingOrigin[1] + svg.symbolOrigin[1])]
836
                        svg.loc = [round(scenePos.x() - svg.symbolConvertingOrigin[0]), round(scenePos.y() - svg.symbolConvertingOrigin[1])]
837
                    '''
838
                else:
839
                    svg.origin = [round(scenePos.x(), 1), round(scenePos.y(), 1)]
840
                    svg.loc = [round(scenePos.x() - svg.symbolOrigin[0], 1), round(scenePos.y() - svg.symbolOrigin[1], 1)]
841
                    '''
842
                    if not svg.symbolConvertingOrigin:
843
                        svg.origin = [round(scenePos.x(), 1), round(scenePos.y(), 1)]
844
                        svg.loc = [round(scenePos.x() - svg.symbolOrigin[0], 1), round(scenePos.y() - svg.symbolOrigin[1], 1)]
845
                    else:
846
                        svg.origin = [round(scenePos.x() - svg.symbolConvertingOrigin[0] + svg.symbolOrigin[0], 1), round(scenePos.y() - svg.symbolConvertingOrigin[1] + svg.symbolOrigin[1], 1)]
847
                        svg.loc = [round(scenePos.x() - svg.symbolConvertingOrigin[0], 1), round(scenePos.y() - svg.symbolConvertingOrigin[1], 1)]
848
                    '''
849

    
850
                if len(svg.connectors) == 1:
851
                    # single connection item assistant
852
                    connectors = [connector for connector in connectors if connector.parentItem() is not svg and not connector.connectedItem]
853

    
854
                    if len(connectors) == 1:
855
                        xxl = connectors[0].parentItem().origin[0] - connectors[0].center()[0]
856
                        yyl = connectors[0].parentItem().origin[1] - connectors[0].center()[1]
857
                        rAngle = -math.atan2(yyl, xxl)
858
                        rAngle = abs(rAngle) if rAngle < 0 + allowed_error else 2 * math.pi - rAngle
859
                        rAngle = rAngle + math.pi if rAngle + math.pi < 2 * math.pi else rAngle - math.pi
860
                        svg.angle = rAngle
861

    
862

    
863
                        svg.connectors[0].connect(connectors[0].parentItem())
864
                        #svg.connectors[0].highlight(False)
865
                        connectors[0].connect(svg)
866
                        #items[0].highlight(False)
867

    
868
                svg.addSvgItemToScene(scene, True if not auto else False, True)
869

    
870
            # svg.reSettingConnetors()
871

    
872
            '''
873
            if not strict:
874
                # need fix
875
                App.mainWnd().symbolTreeWidget.clearFocus()
876
                scene.setFocus()
877
                #scene.clearFocus()
878
                for item in scene.selectedItems():
879
                    item.setSelected(False)
880

881
                svg.setSelected(True)
882
                scene.setFocusItem(svg)
883
            '''
884
        except Exception as ex:
885
            from App import App 
886
            from AppDocData import MessageType
887

    
888
            message = 'error occurred({}) in {}:{}'.format(ex, sys.exc_info()[-1].tb_frame.f_code.co_filename, sys.exc_info()[-1].tb_lineno)
889
            App.mainWnd().addMessage.emit(MessageType.Error, message)
890

    
891
    '''
892
        @brief  find item by uid (SymbolSvgItem 기반, QEngineeringConnectorItem 제외, QEngineeringLineItem 포함)
893
        @author kyouho
894
        @date   2018.07.31
895
    '''
896

    
897
    def findItemByUid(self, uid):
898
        from EngineeringConnectorItem import QEngineeringConnectorItem
899

    
900
        items = [item for item in self.scene().items() if hasattr(item, 'uid') and str(item.uid) == str(uid)]
901
        return items[0] if items else None
902

    
903
    def convertQImageToMat(self, incomingImage):
904
        '''Converts a QImage into an opencv MAT format'''
905
        import numpy as np
906

    
907
        try:
908
            incomingImage = incomingImage.convertToFormat(QImage.Format_RGBA8888)
909

    
910
            width = incomingImage.width()
911
            height = incomingImage.height()
912

    
913
            ptr = incomingImage.bits()
914
            ptr.setsize(incomingImage.byteCount())
915
            arr = np.array(ptr).reshape(height, width, 4)  # Copies the data
916
            return arr
917
        except Exception as ex:
918
            print('error occurred({}) in {}:{}'.format(ex, sys.exc_info()[-1].tb_frame.f_code.co_filename,
919
                                                       sys.exc_info()[-1].tb_lineno))
920

    
921

    
922
if __name__ == '__main__':
923
    import sys
924

    
925
    try:
926
        from PyQt5.QtWidgets import QApplication
927
    except ImportError:
928
        try:
929
            from PyQt4.QtGui import QApplication
930
        except ImportError:
931
            raise ImportError("ImageViewerQt: Requires PyQt5 or PyQt4.")
932
    print('Using Qt ' + QT_VERSION_STR)
933

    
934

    
935
    def handleLeftClick(x, y):
936
        row = int(y)
937
        column = int(x)
938
        print("Clicked on image pixel (row=" + str(row) + ", column=" + str(column) + ")")
939

    
940

    
941
    # Create the application.
942
    app = QApplication(sys.argv)
943

    
944
    # Create image viewer and load an image file to display.
945
    viewer = QtImageViewer(None)
946
    viewer.loadImageFromFile()  # Pops up file dialog.
947

    
948
    # Handle left mouse clicks with custom slot.
949
    viewer.leftMouseButtonPressed.connect(handleLeftClick)
950

    
951
    # Show viewer and run application.
952
    viewer.show()
953
    sys.exit(app.exec_())
클립보드 이미지 추가 (최대 크기: 500 MB)