프로젝트

일반

사용자정보

통계
| 개정판:

hytos / DTI_PID / DTI_PID / Shapes / EngineeringLineItem.py @ d36055d0

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

1
# coding: utf-8
2
""" This is engineering line item module """
3
import sys
4
import cv2
5
import os
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 Qt, QRectF, pyqtSignal, QT_VERSION_STR, QRect
14
        from PyQt4.QtGui import QGraphicsView, QGraphicsScene, QImage, QPixmap, QPainterPath, QFileDialog, \
15
            QGraphicsLineItem
16
    except ImportError:
17
        raise ImportError("ImageViewerQt: Requires PyQt5 or PyQt4.")
18

    
19
from EngineeringAbstractItem import QEngineeringAbstractItem
20
import shapely
21
from LineTypeConditions import LineTypeConditions
22

    
23

    
24
class QEngineeringLineItem(QGraphicsLineItem, QEngineeringAbstractItem):
25
    """ This is engineering line item """
26

    
27
    ARROW_SIZE = 30
28
    ZVALUE = 100
29
    HIGHLIGHT = '#BC4438'
30
    LINE_TYPE_COLORS = {}
31

    
32
    '''
33
        @history    2018.05.11  Jeongwoo    Make Comments self.setPen()
34
                    2018.05.15  Jeongwoo    Change method to call parent's __init__
35
                    humkyung 2018.06.21 add vertices to parameter
36
    '''
37

    
38
    def __init__(self, vertices=[], thickness=None, parent=None, uid=None):
39
        import uuid
40
        from EngineeringConnectorItem import QEngineeringConnectorItem
41
        from SymbolAttr import SymbolProp
42
        from AppDocData import AppDocData
43

    
44
        try:
45
            QGraphicsLineItem.__init__(self, parent)
46
            QEngineeringAbstractItem.__init__(self)
47

    
48
            self.uid = uuid.uuid4() if uid is None else uuid.UUID(uid)
49
            self.thickness = thickness
50

    
51
            self.setPen(QPen(Qt.blue, 4, Qt.SolidLine))  # set default pen
52
            self.isCreated = True
53

    
54
            self._owner = None
55
            self._flowMark = None
56
            configs = AppDocData.instance().getConfigs('Line', 'Default Type')
57
            self._lineType = None
58
            self.lineType = configs[0].value if 1 == len(configs) else 'Secondary'  # defulat line type is 'Secondary'
59

    
60
            self._properties = \
61
                { \
62
                    SymbolProp(None, 'Size', 'Size Text Item', Expression='self.EvaluatedSize'): None
63
                }
64

    
65
            self.setFlags(QGraphicsItem.ItemIsSelectable | QGraphicsItem.ItemIsFocusable)
66

    
67
            self.setAcceptHoverEvents(True)
68
            self.setAcceptTouchEvents(True)
69

    
70
            self.transfer = Transfer()
71
            self.setZValue(QEngineeringLineItem.ZVALUE)
72

    
73
            if vertices:
74
                self.setLine(vertices[0][0], vertices[0][1], vertices[1][0], vertices[1][1])
75

    
76
                index = 0
77
                for vertex in vertices:
78
                    connector = QEngineeringConnectorItem(parent=self, index=index + 1)
79
                    connector.setPos(vertex)
80
                    connector.setParentItem(self)
81
                    # connector의 connectPoint, sceneConnectPoint를 vertex로 함 추후 좀 알아봐서 수정 필요
82
                    connector.connectPoint = vertex
83
                    connector.sceneConnectPoint = vertex
84

    
85
                    # add connector move able
86
                    connector.setFlags(QGraphicsItem.ItemIsSelectable | QGraphicsItem.ItemIsFocusable)
87
                    connector.setAcceptTouchEvents(True)
88
                    connector.transfer.onPosChanged.connect(self.onConnectorPosChaned)
89

    
90
                    connector.setZValue(self.zValue() + 1)
91
                    self.connectors.append(connector)
92
                    index = index + 1
93

    
94
                self.update_arrow()
95

    
96
                tooltip = '<b>{}</b><br>({},{})-({},{})'.format(str(self.uid), vertices[0][0], vertices[0][1],
97
                                                                vertices[1][0], vertices[1][1])
98
                self.setToolTip(tooltip)
99
        except Exception as ex:
100
            from App import App
101
            from AppDocData import MessageType
102

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

    
107
    def __str__(self):
108
        """ return string represent uuid """
109
        return str(self.uid)
110

    
111
    @property
112
    def properties(self):
113
        """ getter of flow mark """
114
        import uuid
115

    
116
        for prop, value in self._properties.items():
117
            if prop.is_selectable and type(value) is uuid.UUID and self.scene():
118
                matches = [x for x in self.scene().items() if hasattr(x, 'uid') and str(x.uid) == str(value)]
119
                if matches: self._properties[prop] = matches[0]
120

    
121
            if prop.Expression: self._properties[prop] = eval(prop.Expression)
122

    
123
        return self._properties
124

    
125
    def set_property(self, property, value):
126
        """ set property with given value """
127
        if issubclass(type(value), QEngineeringAbstractItem): self.add_assoc_item(value, 0)
128
        matches = [prop for prop, _ in self._properties.items() if prop.Attribute == property]
129
        if matches: self._properties[matches[0]] = value
130

    
131
    def prop(self, name):
132
        """ return the value of given property with name """
133
        matches = [(prop, value) for prop, value in self.properties.items() if prop.Attribute == name]
134
        if matches: return matches[0][1]
135

    
136
        return None
137

    
138
    @property
139
    def Size(self):
140
        """ always return None """
141
        return None
142

    
143
    @property
144
    def EvaluatedSize(self):
145
        from EngineeringReducerItem import QEngineeringReducerItem
146

    
147
        if self.Size: return self.Size
148
        if self.owner:
149
            matches = [run for run in self.owner.runs if self in run.items]
150
            if matches:
151
                at = matches[0].items.index(self)
152
                upstream = matches[0].items[:at]
153
                upstream.reverse()
154
                prev = self
155
                for item in upstream:
156
                    if type(item) is QEngineeringReducerItem:
157
                        if item.connectors[0].connectedItem is prev:  ### Main Size
158
                            if item.MainSubSize: return item.MainSubSize[0]
159
                        elif item.connectors[1].connectedItem is prev:  ### Sub Size
160
                            if item.MainSubSize: return item.MainSubSize[1]
161
                    else:
162
                        if item.Size: return item.Size
163
                    prev = item
164

    
165
                downstream = matches[0].items[at:]
166
                prev = self
167
                for item in downstream:
168
                    if type(item) is QEngineeringReducerItem:
169
                        if item.connectors[0].connectedItem is prev:  ### Main Size
170
                            if item.MainSubSize: return item.MainSubSize[0]
171
                        elif item.connectors[1].connectedItem is prev:  ### Sub Size
172
                            if item.MainSubSize: return item.MainSubSize[1]
173
                    else:
174
                        if item.Size: return item.Size
175
                    prev = item
176

    
177
                if 'Drain' == matches[0].Type:
178
                    from AppDocData import AppDocData
179
                    return AppDocData.instance().drain_size
180

    
181
            return self.owner.Size
182

    
183
        return None
184

    
185
    '''
186
        @breif  getter owner
187
        @author humkyung
188
        @date   2018.05.10
189
    '''
190

    
191
    @property
192
    def owner(self):
193
        import uuid
194

    
195
        if self._owner and type(self._owner) is uuid.UUID:
196
            matches = [x for x in self.scene().items() if hasattr(x, 'uid') and str(x.uid) == str(self._owner)]
197
            if matches: self._owner = matches[0]
198

    
199
        if type(self._owner) is not uuid.UUID and type(self._owner) is not str:
200
            return self._owner
201
        else:
202
            self._owner = None
203
            return None
204

    
205
    '''
206
        @brief  setter owner
207
        @author humkyung
208
        @date   2018.05.10
209
        @history    2018.05.17  Jeongwoo    Add Calling setColor
210
    '''
211

    
212
    @owner.setter
213
    def owner(self, value):
214
        self._owner = value
215

    
216
        if self._owner is None:
217
            self._color = self.DEFAULT_COLOR
218
        self.setColor(self._color)
219

    
220
    '''
221
        @brief  getter flow mark
222
        @author humkyung
223
        @date   2018.06.21
224
    '''
225

    
226
    @property
227
    def flowMark(self):
228
        return self._flowMark
229

    
230
    '''
231
        @brief  setter flow mark
232
        @author humkyung
233
        @date   2018.06.21
234
    '''
235

    
236
    @flowMark.setter
237
    def flowMark(self, value):
238
        self._flowMark = value
239

    
240
    '''
241
        @brief  getter of lineType
242
        @author humkyung
243
        @date   2018.06.27
244
    '''
245

    
246
    @property
247
    def lineType(self):
248
        return self._lineType
249

    
250
    '''
251
        @brief  setter of lineType
252
        @author humkyung
253
        @date   2018.06.27
254
    '''
255

    
256
    @lineType.setter
257
    def lineType(self, value):
258
        from AppDocData import AppDocData
259

    
260
        self._lineType = value
261

    
262
        docData = AppDocData.instance()
263
        configs = docData.getLineTypeConfig(self._lineType)
264
        if configs:
265
            _pen = self.pen()
266
            _pen.setWidth(configs[2])
267
            _pen.setStyle(configs[3])
268
            self.setPen(_pen)
269
            self.setOpacity(float(configs[4]) / 100)
270
            self.update()
271

    
272
        if self.scene():
273
            self.update_arrow()
274

    
275
    '''
276
        @brief  clone an object
277
    '''
278

    
279
    def clone(self):
280
        clone = QEngineeringLineItem()
281
        # clone._vertices = copy.deepcopy(self._vertices)
282
        # for vertex in clone._vertices:
283
        #    clone._pol.append(QPointF(vertex[0], vertex[1]))
284
        clone.buildItem()
285
        clone.isCreated = self.isCreated
286

    
287
        return clone
288

    
289
    def set_line(self, line):
290
        """ set line """
291
        self.setLine(line[0][0], line[0][1], line[1][0], line[1][1])
292
        self.connectors[0].setPos(line[0])
293
        self.connectors[1].setPos(line[1])
294
        self.update_arrow()
295

    
296
    '''
297
        @brief  return start point
298
        @author humkyung
299
        @date   2018.04.16
300
    '''
301

    
302
    def startPoint(self):
303
        at = self.line().p1()
304
        return (at.x(), at.y())
305

    
306
    '''
307
        @brief  return last point
308
        @author humkyung
309
        @date   2018.04.16
310
    '''
311

    
312
    def endPoint(self):
313
        at = self.line().p2()
314
        return (at.x(), at.y())
315

    
316
    '''
317
        @brief  dot product of given two vectors
318
        @author humkyung
319
        @date   2018.04.14
320
    '''
321

    
322
    def dotProduct(self, lhs, rhs):
323
        return sum([lhs[i] * rhs[i] for i in range(len(lhs))])
324

    
325
    '''
326
        @brief  distance between line and point
327
        @author humkyung
328
        @date   2018.04.16
329
    '''
330

    
331
    def distanceTo(self, pt):
332
        from shapely.geometry import Point, LineString
333

    
334
        startPt = self.startPoint()
335
        endPt = self.endPoint()
336
        line = LineString([(startPt[0], startPt[1]), (endPt[0], endPt[1])])
337
        dist = line.distance(Point(pt[0], pt[1]))
338

    
339
        return dist
340

    
341
    '''
342
        @brief  return perpendicular vector
343
        @author humkyung
344
        @date   2018.04.21
345
    '''
346

    
347
    def perpendicular(self):
348
        import math
349

    
350
        dx = self.endPoint()[0] - self.startPoint()[0]
351
        dy = self.endPoint()[1] - self.startPoint()[1]
352
        dx, dy = -dy, dx
353
        length = math.sqrt(dx * dx + dy * dy)
354
        dx /= length
355
        dy /= length
356

    
357
        return (dx, dy)
358

    
359
    '''
360
        @brief  return angle of line in radian
361
        @author humkyung
362
        @date   2018.04.22
363
    '''
364

    
365
    def angle(self):
366
        import math
367

    
368
        startPt = self.startPoint()
369
        endPt = self.endPoint()
370
        dx = endPt[0] - startPt[0]
371
        dy = endPt[1] - startPt[1]
372
        dot = self.dotProduct((1, 0), (dx, dy))
373
        length = math.sqrt(dx * dx + dy * dy)
374
        return math.acos(dot / length)
375

    
376
    '''
377
        @brief  return length of line
378
        @author humkyung
379
        @date   2018.05.08
380
    '''
381

    
382
    def length(self):
383
        import math
384

    
385
        startPt = self.startPoint()
386
        endPt = self.endPoint()
387
        dx = endPt[0] - startPt[0]
388
        dy = endPt[1] - startPt[1]
389
        return math.sqrt(dx * dx + dy * dy)
390

    
391
    '''
392
        @brief  check if line is horizontal
393
        @author humkyung
394
        @date   2018.04.27
395
    '''
396

    
397
    def isHorizontal(self):
398
        import math
399

    
400
        startPt = self.startPoint()
401
        endPt = self.endPoint()
402
        dx = endPt[0] - startPt[0]
403
        dy = endPt[1] - startPt[1]
404

    
405
        return math.fabs(dx) > math.fabs(dy)
406

    
407
    '''
408
        @brief  check if line is vertical 
409
        @author humkyung
410
        @date   2018.04.27
411
    '''
412

    
413
    def isVertical(self):
414
        import math
415

    
416
        startPt = self.startPoint()
417
        endPt = self.endPoint()
418
        dx = endPt[0] - startPt[0]
419
        dy = endPt[1] - startPt[1]
420

    
421
        return math.fabs(dy) > math.fabs(dx)
422

    
423
    '''
424
        @brief  get intersection point between this and given line
425
        @author humkyung
426
        @date   2018.04.21
427
        @history    Jeongwoo 2018.05.15 Add normalize
428
                    Jeongwoo 2018.05.16 Add length == 0 check
429
    '''
430

    
431
    def intersection(self, line):
432
        import math
433
        from shapely.geometry import Point, LineString
434

    
435
        startPt = self.startPoint()
436
        endPt = self.endPoint()
437
        dx = endPt[0] - startPt[0]
438
        dy = endPt[1] - startPt[1]
439
        length = math.sqrt(dx * dx + dy * dy)
440
        if length == 0:
441
            return None
442
        dx /= length
443
        dy /= length
444
        lhs = LineString([(startPt[0] - dx * 20, startPt[1] - dy * 20), (endPt[0] + dx * 20, endPt[1] + dy * 20)])
445
        rhs = LineString(line)
446
        return lhs.intersection(rhs)
447

    
448
    def getAngle(self, rhs):
449
        """ get angle between self and given line """
450
        import math
451

    
452
        try:
453
            return math.acos(self.dotProduct(self, rhs) / (self.length() * rhs.length()))
454
        except Exception as ex:
455
            return sys.float_info.max
456

    
457
    def isParallel(self, rhs):
458
        """ check if two lines are parallel """
459
        import math
460

    
461
        try:
462
            vectors = [(self.endPoint()[0] - self.startPoint()[0], self.endPoint()[1] - self.startPoint()[1]),
463
                       (rhs.endPoint()[0] - rhs.startPoint()[0], rhs.endPoint()[1] - rhs.startPoint()[1])]
464
            angle = self.getAngle(rhs)
465
            if (angle == 0) or (angle == math.pi): return True
466
        except ZeroDivisionError:
467
            return True
468

    
469
        return False
470

    
471
    '''
472
        @brief      check if two lines are connectable
473
        @author     humkyung
474
        @date       2018.05.12
475
        @history    Jeongwoo 18.05.15 Add check pt's type
476
                    Jeongwoo 18.05.16 Add length == 0 check
477
    '''
478

    
479
    def is_connectable(self, line, toler=20):
480
        import math
481

    
482
        startPt = line.startPoint()
483
        endPt = line.endPoint()
484

    
485
        dx = endPt[0] - startPt[0]
486
        dy = endPt[1] - startPt[1]
487
        length = math.sqrt(dx * dx + dy * dy)
488
        if length == 0:
489
            return False
490
        dx /= length
491
        dy /= length
492
        extendedLine = [(startPt[0] - dx * toler, startPt[1] - dy * toler),
493
                        (endPt[0] + dx * toler, endPt[1] + dy * toler)]
494
        pt = self.intersection(extendedLine)
495

    
496
        return (pt is not None) and (type(pt) == shapely.geometry.point.Point)
497

    
498
    '''
499
        @author     humkyung
500
        @date       2018.06.26
501
        @history    humkyung 2018.07.03 allow item to be line or symbol
502
    '''
503

    
504
    def is_connected(self, item, at=QEngineeringAbstractItem.CONNECTED_AT_PT):
505
        """ check if given item is connected to self """
506

    
507
        _connectors = [connector for connector in self.connectors if
508
                       (connector.connectedItem == item and connector._connected_at == at)]
509
        return len(_connectors) > 0
510

    
511
    '''
512
        @brief      join line to symbol
513
        @author     kyouho
514
        @date       2018.07.25
515
    '''
516

    
517
    def joinTo(self, item=None):
518
        import math
519
        from SymbolSvgItem import SymbolSvgItem
520

    
521
        # line의 Point 정의
522
        startPoint = self.startPoint()
523
        endPoint = self.endPoint()
524

    
525
        if item is not None and type(item) is QEngineeringLineItem:
526
            pts = [item.startPoint(), item.endPoint()]
527
            selected = startPoint if self._selectedIndex == 0 else endPoint if self._selectedIndex else []
528

    
529
            if selected:
530
                for i in range(len(pts)):
531
                    dx = pts[i][0] - selected[0]
532
                    dy = pts[i][1] - selected[1]
533
                    if math.sqrt(dx * dx + dy * dy) < 10:
534
                        line = QLineF(QPointF(pts[i][0], pts[i][1]),
535
                                      QPointF(endPoint[0], endPoint[1])) if self._selectedIndex == 0 else QLineF(
536
                            QPointF(startPoint[0], startPoint[1]), QPointF(pts[i][0], pts[i][1]))
537
                        self.setLine(line)
538
                        self.update()
539
                        break
540
        else:
541
            if len(item.connectors) == 2:
542
                connector1Point = item.connectors[0].sceneConnectPoint
543
                connector2Point = item.connectors[1].sceneConnectPoint
544

    
545
                # startPoint와 같은 connPts 찾음
546
                if startPoint[0] == connector1Point[0] and startPoint[1] == connector1Point[1]:
547
                    self.connectors[0].connectedItem = item
548
                elif startPoint[0] == connector2Point[0] and startPoint[1] == connector2Point[1]:
549
                    self.connectors[0].connectedItem = item
550

    
551
                # endPoint와 같은 connPts 찾음
552
                if endPoint[0] == connector1Point[0] and endPoint[1] == connector1Point[1]:
553
                    self.connectors[1].connectedItem = item
554
                elif endPoint[0] == connector2Point[0] and endPoint[1] == connector2Point[1]:
555
                    self.connectors[1].connectedItem = item
556

    
557
    '''
558
        @brief      arrange vertex order
559
        @author     humkyung
560
        @date       2018.07.04
561
    '''
562

    
563
    def arrangeVertexOrder(self, arranged):
564
        import math
565

    
566
        lhs = [arranged.startPoint(), arranged.endPoint()]
567
        rhs = [self.startPoint(), self.endPoint()]
568

    
569
        index = 0
570
        indexed = 0
571
        minDist = None
572
        for pt in lhs:
573
            for _pt in rhs:
574
                index += 1
575
                dx = _pt[0] - pt[0]
576
                dy = _pt[1] - pt[1]
577
                dist = math.sqrt(dx * dx + dy * dy)
578
                if minDist is None or dist < minDist:
579
                    minDist = dist
580
                    indexed = index
581

    
582
        if indexed == 1 or indexed == 4:
583
            self.reverse()
584
    
585
    def is_piping(self, strong=False):
586
        """ return true if piping line """
587
        if strong:
588
            return (self._lineType == 'Primary' or self._lineType == 'Secondary')    
589
        else:
590
            return (self._lineType == 'Primary' or self._lineType == 'Secondary' or self._lineType == 'Connect To Process')
591

    
592
    '''
593
        @brief      check if two lines are extendable
594
        @author     humkyung
595
        @date       2018.06.25
596
        @history    humkyung 2018.06.27 check line type
597
    '''
598
    def isExtendable(self, line, toler=5):
599
        import math
600
        from SymbolSvgItem import SymbolSvgItem
601

    
602
        if self.lineType == line.lineType:
603
            if self.isHorizontal() and line.isHorizontal():
604
                flag = (line.connectors[0].connectedItem is not None and issubclass(
605
                    type(line.connectors[0].connectedItem), SymbolSvgItem)) or (
606
                                   line.connectors[1].connectedItem is not None and issubclass(
607
                               type(line.connectors[1].connectedItem), SymbolSvgItem))
608
                return (flag and (math.fabs(self.startPoint()[1] - line.startPoint()[1]) < toler))
609
            elif self.isVertical() and line.isVertical():
610
                flag = (line.connectors[0].connectedItem is not None and issubclass(
611
                    type(line.connectors[0].connectedItem), SymbolSvgItem)) or (
612
                                   line.connectors[1].connectedItem is not None and issubclass(
613
                               type(line.connectors[1].connectedItem), SymbolSvgItem))
614
                return (flag and (math.fabs(self.startPoint()[0] - line.startPoint()[0]) < toler))
615

    
616
        return False
617

    
618
    def is_external_point(self, pt):
619
        """ check given pt is located outside of line """
620
        import math
621

    
622
        try:
623
            dx = self.endPoint()[0] - self.startPoint()[0]
624
            dy = self.endPoint()[1] - self.startPoint()[1]
625
            lineLength = math.sqrt(dx * dx + dy * dy)
626

    
627
            dx = pt.x - self.startPoint()[0]
628
            dy = pt.y - self.startPoint()[1]
629
            length = math.sqrt(dx * dx + dy * dy)
630
            if length > lineLength:
631
                return True
632

    
633
            dx = pt.x - self.endPoint()[0]
634
            dy = pt.y - self.endPoint()[1]
635
            length = math.sqrt(dx * dx + dy * dy)
636
            if length > lineLength:
637
                return True
638

    
639
            return False
640
        except Exception as ex:
641
            from App import App
642
            from AppDocData import MessageType
643

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

    
648
    '''
649
        @author     humkyung
650
        @date       2018.04.16
651
        @history    humkyung 2018.05.08 check if line is possible to be connected
652
                    Jeongwoo 2018.05.15 Split if-statement and Connect each symbol and line
653
    '''
654

    
655
    def connect_if_possible(self, obj, toler=20):
656
        """ connect line or symbol is able to be connected and return symbol or line connected to connectors """
657
        """ this method not update item's position """
658

    
659
        from shapely.geometry import Point
660
        from SymbolSvgItem import SymbolSvgItem
661
        from EngineeringConnectorItem import QEngineeringConnectorItem
662

    
663
        res = []
664

    
665
        startPt = self.startPoint()
666
        endPt = self.endPoint()
667

    
668
        try:
669
            if issubclass(type(obj), SymbolSvgItem):
670
                for i in range(len(obj.connectors)):
671
                    pt = obj.connectors[i].sceneConnectPoint
672
                    if (Point(startPt[0], startPt[1]).distance(Point(pt[0], pt[1])) < toler):
673
                        if self.connectors[0].connectedItem is None and obj.connectors[i].connectedItem is None:
674
                            self.connectors[0].connect(obj)
675
                            obj.connectors[i].connect(self)
676
                            # line, start, end
677
                            res.append(obj)
678
                            res.append(obj.connectors[i].sceneConnectPoint)
679
                            res.append(endPt)
680
                    elif (Point(endPt[0], endPt[1]).distance(Point(pt[0], pt[1])) < toler):
681
                        if self.connectors[1].connectedItem is None and obj.connectors[i].connectedItem is None:
682
                            self.connectors[1].connect(obj)
683
                            obj.connectors[i].connect(self)
684
                            # line, start, end
685
                            res.append(obj)
686
                            res.append(startPt)
687
                            res.append(obj.connectors[i].sceneConnectPoint)
688
            elif type(obj) is QEngineeringLineItem:
689
                _startPt = obj.startPoint()
690
                _endPt = obj.endPoint()
691
                if obj.connectors[0].connectedItem is None and self.distanceTo(_startPt) < toler:
692
                    if self.connectors[0].connectedItem is None and ((Point(startPt[0], startPt[1]).distance(Point(_startPt[0], _startPt[1])) < toler)):
693
                        self.connectors[0].connect(obj)
694
                        obj.connectors[0].connect(self)
695
                        res.append(obj)
696
                    elif self.connectors[1].connectedItem is None and ((Point(endPt[0], endPt[1]).distance(Point(_startPt[0], _startPt[1])) < toler)):
697
                        self.connectors[1].connect(obj)
698
                        obj.connectors[0].connect(self)
699
                        res.append(obj)
700
                    elif obj.connectors[0].connectedItem is None:
701
                        obj.connectors[0].connect(self, at=QEngineeringAbstractItem.CONNECTED_AT_BODY)
702
                        res.append(obj)
703

    
704
                elif obj.connectors[1].connectedItem is None and self.distanceTo(_endPt) < toler:
705
                    if self.connectors[0].connectedItem is None and ((Point(startPt[0], startPt[1]).distance(Point(_endPt[0], _endPt[1])) < toler)):
706
                        self.connectors[0].connect(obj)
707
                        obj.connectors[1].connect(self)
708
                        res.append(obj)
709
                    elif self.connectors[1].connectedItem is None and ((Point(endPt[0], endPt[1]).distance(Point(_endPt[0], _endPt[1])) < toler)):
710
                        self.connectors[1].connect(obj)
711
                        obj.connectors[1].connect(self)
712
                        res.append(obj)
713
                    elif obj.connectors[1].connectedItem is None:
714
                        obj.connectors[1].connect(self, at=QEngineeringAbstractItem.CONNECTED_AT_BODY)
715
                        res.append(obj)
716
        except Exception as ex:
717
            from App import App
718
            from AppDocData import MessageType
719

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

    
724
        return res
725

    
726
    '''
727
        @brief      disconnect connector item
728
        @author     kyouho
729
        @date       2018.08.30
730
    '''
731

    
732
    def disconnectedItemAtConnector(self, connector):
733
        for conn in self.connectors:
734
            if conn.isOverlapConnector(connector):
735
                conn.connectedItem = None
736

    
737
    def arrange_flow_direction(self, _from):
738
        """ reverse if from is connected to second connector """
739
        if not _from: raise ValueError
740

    
741
        if self.connectors[1].connectedItem == _from: self.reverse()
742

    
743
    '''
744
        @brief      reverse line
745
        @author     humkyung
746
        @date       2018.07.03
747
    '''
748

    
749
    def reverse(self):
750
        line = self.line()
751
        self.setLine(QLineF(line.p2(), line.p1()))
752
        self.connectors[0], self.connectors[1] = self.connectors[1], self.connectors[0]
753
        self.update_arrow()
754
        self.update()
755

    
756
    '''
757
        @brief      add flow arrow
758
        @author     humkyung
759
        @date       2018.05.08
760
        @history    2018.05.24  Jeongwoo    Modifying Draw Flow Arrow
761
    '''
762

    
763
    def addFlowArrow(self):
764
        import numpy as np
765
        import cv2
766
        import math
767
        import sys
768
        global src
769
        from shapely.geometry import Point
770
        from QEngineeringFlowArrowItem import QEngineeringFlowArrowItem
771
        from AppDocData import AppDocData
772

    
773
        try:
774
            docData = AppDocData.instance()
775
            area = docData.getArea('Drawing')
776

    
777
            startPt = self.startPoint()
778
            endPt = self.endPoint()
779
            length = self.length()
780
            direction = [(endPt[0] - startPt[0]) / length, (endPt[1] - startPt[1]) / length]
781

    
782
            left = min(startPt[0], endPt[0])
783
            top = min(startPt[1], endPt[1])
784
            right = max(startPt[0], endPt[0])
785
            bottom = max(startPt[1], endPt[1])
786

    
787
            rect = None
788
            if self.isVertical():
789
                rect = QRectF(left - 10, top, (right - left) + 20, (bottom - top))
790
            else:
791
                rect = QRectF(left, top - 10, (right - left), (bottom - top) + 20)
792

    
793
            docData = AppDocData.instance()
794
            area = docData.getArea('Drawing')
795
            img = np.array(AppDocData.instance().getCurrentPidSource().getPyImageOnRect(rect))
796

    
797
            imgLine = cv2.threshold(cv2.cvtColor(img, cv2.COLOR_BGR2GRAY), 127, 255, cv2.THRESH_BINARY)[1]
798
            # remove noise
799
            imgLine = cv2.bitwise_not(imgLine)
800
            imgLine = cv2.erode(imgLine, np.ones((10, 10), np.uint8))
801

    
802
            contours, hierarchy = cv2.findContours(imgLine, cv2.RETR_TREE, cv2.CHAIN_APPROX_SIMPLE)
803
            if contours:
804
                contours = sorted(contours, key=cv2.contourArea, reverse=True)
805
                [x, y, w, h] = cv2.boundingRect(contours[0])
806
                if w > 10 and w < 100 and h > 10 and h < 100:  # check arrow mark size
807
                    imgArrowMark = imgLine[y:y + h, x:x + w]
808

    
809
                    # DEBUG - display flow arrow area
810
                    '''
811
                    item = QGraphicsBoundingBoxItem(rect.left() + x - 10, rect.top() + y - 10, w + 20, h + 20)
812
                    item.isSymbol = True
813
                    item.angle = 0
814
                    item.setPen(QPen(Qt.red, 1, Qt.SolidLine))
815
                    item.setBrush(QBrush(QColor(255,255,0,100)))
816
                    self.scene().addItem(item)
817
                    '''
818
                    # up to here
819

    
820
                    edges = cv2.Canny(imgArrowMark, 50, 150, apertureSize=3)
821
                    lines = cv2.HoughLinesP(edges, 1, np.pi / 180, 10, minLineLength=10, maxLineGap=5)
822

    
823
                    ####### HoughLinesP
824
                    if lines is not None:
825
                        maxLength = None
826
                        selected = None
827
                        for line in lines:
828
                            for x1, y1, x2, y2 in line:
829
                                dx = x2 - x1
830
                                dy = y2 - y1
831
                                selected = line
832
                                length = math.sqrt(dx * dx + dy * dy)
833
                                if maxLength is None or length > maxLength:
834
                                    maxLength = length
835
                                    selected = line
836

    
837
                        for x1, y1, x2, y2 in selected:
838
                            dx = math.fabs(x2 - x1)
839
                            dy = math.fabs(y2 - y1)
840
                            length = math.sqrt(dx * dx + dy * dy)
841
                            dx /= length
842
                            dy /= length
843
                            if (self.isVertical() and (dx < 0.001 or math.fabs(dx - 1) < 0.001)) or (
844
                                    self.isHorizontal() and (dx < 0.001 or math.fabs(dx - 1) < 0.001)): continue
845
                            dist1 = self.distanceTo((rect.left() + x + x1, rect.top() + y + y1))
846
                            dist2 = self.distanceTo((rect.left() + x + x2, rect.top() + y + y2))
847
                            if dist1 > dist2:  # point which's distance is longer would be start point
848
                                _start = (rect.left() + x + x1, rect.top() + y + y1)
849
                                _end = (rect.left() + x + x2, rect.top() + y + y2)
850
                            else:
851
                                _start = (rect.left() + x + x2, rect.top() + y + y2)
852
                                _end = (rect.left() + x + x1, rect.top() + y + y1)
853

    
854
                            # DEBUG display detected line
855
                            '''
856
                            poly = QEngineeringPolylineItem()
857
                            poly._pol.append(QPointF(_start[0], _start[1]))
858
                            poly._pol.append(QPointF(_end[0], _end[1]))
859
                            poly.setPen(QPen(Qt.red, 2, Qt.SolidLine))
860
                            poly.buildItem()
861
                            self.scene().addItem(poly)
862
                            '''
863
                            # up to here
864

    
865
                            dist1 = Point(startPt[0], startPt[1]).distance(Point(_start[0], _start[1]))
866
                            dist2 = Point(startPt[0], startPt[1]).distance(Point(_end[0], _end[1]))
867
                            if dist1 > dist2:
868
                                startPt, endPt = endPt, startPt
869
                                direction[0], direction[1] = -direction[0], -direction[1]
870
                                self.reverse()
871

    
872
                        '''
873
                        center = [(startPt[0]+endPt[0])*0.5, (startPt[1]+endPt[1])*0.5]
874
                        arrow = QEngineeringFlowArrowItem(center, direction)
875
                        arrow.buildItem()
876
                        self.scene().addItem(arrow)
877
                        '''
878

    
879
                        x = round(rect.left() + x - 5)
880
                        y = round(rect.top() + y - 5)
881
                        self.flowMark = ([x, y, w + 10, h + 10], None)
882
                else:
883
                    pass
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,
889
                                                           sys.exc_info()[-1].tb_lineno)
890
            App.mainWnd().addMessage.emit(MessageType.Error, message)
891

    
892
    '''
893
        @breif  insert symbol
894
        @author humkyung
895
        @date   2018.04.22
896
        @history    kyouho  2018.07.24  add symbol angle, transform rotateRadians(-angle -> angle)
897
    '''
898

    
899
    def insertSymbol(self, symbol, pos):
900
        import math
901
        from shapely.geometry import Point
902
        from shapely import affinity
903

    
904
        vec = self.perpendicular()
905
        line = [(pos.x() - vec[0] * 20, pos.y() - vec[1] * 20), (pos.x() + vec[0] * 20, pos.y() + vec[1] * 20)]
906
        origin = self.intersection(line)
907
        transform = QTransform()
908
        transform.translate(origin.x, origin.y)
909
        angle = self.angle()
910
        transform.rotateRadians(-angle)
911
        transform.translate(-symbol.symbolOrigin[0], -symbol.symbolOrigin[1])
912
        symbol.setTransform(transform)
913
        # save angle
914
        symbol.angle = round(angle, 2)
915
        if 2 == len(symbol.connectors):  # 2 way component
916
            for i in range(len(symbol.connectors)):
917
                rotatedPt = affinity.rotate(Point(symbol.connectors[i].connectPoint[0] - symbol.symbolOrigin[0],
918
                                                  symbol.connectors[i].connectPoint[1] - symbol.symbolOrigin[1]),
919
                                            -angle, Point(0, 0), use_radians=True)
920
                symbol.connectors[i].sceneConnectPoint = (origin.x + rotatedPt.x, origin.y + rotatedPt.y)
921

    
922
            dx1 = symbol.connectors[0].sceneConnectPoint[0] - self.startPoint()[0]
923
            dy1 = symbol.connectors[0].sceneConnectPoint[1] - self.startPoint()[1]
924
            length1 = math.sqrt(dx1 * dx1 + dy1 * dy1)
925
            dx2 = symbol.connectors[1].sceneConnectPoint[0] - self.startPoint()[0]
926
            dy2 = symbol.connectors[1].sceneConnectPoint[1] - self.startPoint()[1]
927
            length2 = math.sqrt(dx2 * dx2 + dy2 * dy2)
928

    
929
            if length1 < length2:
930
                processLine = QEngineeringLineItem([symbol.connectors[1].sceneConnectPoint, self.endPoint()])
931
                processLine.connectors[0].connectedItem = symbol
932
                processLine.connectors[1].connectedItem = self.connectors[1].connectedItem
933
                self.scene().addItem(processLine)
934

    
935
                line = QLineF(self.line().p1(), QPointF(symbol.connectors[0].sceneConnectPoint[0],
936
                                                        symbol.connectors[0].sceneConnectPoint[1]))
937
                self.setLine(line)
938
                self.connectors[1].connectedItem = symbol
939

    
940
                symbol.connectors[0].connectedItem = self
941
                symbol.connectors[1].connectedItem = processLine
942
            else:
943
                processLine = QEngineeringLineItem([symbol.connectors[0].sceneConnectPoint, self.endPoint()])
944
                processLine.connectors[0].connectedItem = symbol
945
                processLine.connectors[1].connectedItem = self.connectors[1].connectedItem
946
                self.scene().addItem(processLine)
947

    
948
                line = QLineF(self.line().p1(), QPointF(symbol.connectors[1].sceneConnectPoint[0],
949
                                                        symbol.connectors[1].sceneConnectPoint[1]))
950
                self.setLine(line)
951
                self.connectors[1].connectedItem = symbol
952

    
953
                symbol.connectors[0].connectedItem = processLine
954
                symbol.connectors[1].connectedItem = self
955

    
956
            self.joinTo(symbol)
957
            processLine.joinTo(symbol)
958
            self.update()
959

    
960
        symbol.loc = [origin.x - symbol.symbolOrigin[0], origin.y - symbol.symbolOrigin[1]]
961
        symbol.size = [symbol.boundingRect().width(), symbol.boundingRect().height()]
962
        self.scene().addItem(symbol)
963

    
964
    '''
965
        @brief  redraw symbol
966
        @author kyouho
967
        @date   2018.07.25
968
    '''
969

    
970
    def reDrawLine(self, symbol, point):
971
        for index in range(len(self.connectors)):
972
            if self.connectors[index].connectedItem == symbol:
973
                # startPoint
974
                if index == 0:
975
                    line = QLineF(QPointF(point[0], point[1]), self.line().p2())
976
                    self.setLine(line)
977
                    self.connectors[0].setPos([point[0], point[1]])
978
                # endpoint
979
                else:
980
                    line = QLineF(self.line().p1(), QPointF(point[0], point[1]))
981
                    self.setLine(line)
982
                    self.connectors[1].setPos([point[0], point[1]])
983

    
984
        ## startPoint에 symbol
985
        # if self.connectors[0].connectedItem == symbol:
986
        #    if self.startPoint()[0] == symbol.connectors[0].sceneConnectPoint[0] and self.startPoint()[1] == symbol.connectors[0].sceneConnectPoint[1]:
987
        #        line = QLineF(QPointF(changedConnPoint1[0], changedConnPoint1[1]), self.line().p2())
988
        #        self.setLine(line)
989
        #    else:
990
        #        line = QLineF(QPointF(changedConnPoint2[0], changedConnPoint2[1]), self.line().p2())
991
        #        self.setLine(line)
992
        ## endPoint에 symbol
993
        # elif self.connectors[1].connectedItem == symbol:
994
        #    if self.endPoint()[0] == symbol.connectors[0].sceneConnectPoint[0] and self.endPoint()[1] == symbol.connectors[0].sceneConnectPoint[1]:
995
        #        line = QLineF(self.line().p1(), QPointF(changedConnPoint1[0], changedConnPoint1[1]))
996
        #        self.setLine(line)
997
        #    else:
998
        #        line = QLineF(self.line().p1(), QPointF(changedConnPoint2[0], changedConnPoint2[1]))
999
        #        self.setLine(line)
1000

    
1001
        self.update()
1002

    
1003
    '''
1004
        @brief  remove symbol
1005
        @author humkyung
1006
        @date   2018.04.23
1007
    '''
1008

    
1009
    def removeSymbol(self, symbol):
1010
        import math
1011

    
1012
        if 2 == len(symbol.connectors):  # 2-way component
1013
            connected = symbol.connectors[0].connectedItem if symbol.connectors[0].connectedItem is not self else \
1014
            symbol.connectors[1].connectedItem
1015

    
1016
            pts = []
1017
            pts.append(self.startPoint())
1018
            pts.append(self.endPoint())
1019
            pts.append(connected.startPoint())
1020
            pts.append(connected.endPoint())
1021

    
1022
            self.scene().removeItem(connected)
1023

    
1024
            start = None
1025
            end = None
1026
            maxDist = None
1027
            for i in range(len(pts)):
1028
                for j in range(i + 1, len(pts)):
1029
                    dx = pts[i][0] - pts[j][0]
1030
                    dy = pts[i][1] - pts[j][1]
1031
                    dist = math.sqrt(dx * dx + dy * dy)
1032
                    if maxDist is None:
1033
                        maxDist = dist
1034
                        start = pts[i]
1035
                        end = pts[j]
1036
                    elif dist > maxDist:
1037
                        maxDist = dist
1038
                        start = pts[i]
1039
                        end = pts[j]
1040

    
1041
            if (pts[0] == end) or (pts[1] == start): start, end = end, start
1042

    
1043
            line = QLineF(QPointF(start[0], start[1]), QPointF(end[0], end[1]))
1044
            self.setLine(line)
1045
            # self.buildItem()
1046
            self.update()
1047

    
1048
    def validate(self):
1049
        '''
1050
            @brief  validation check : connection
1051
            @author euisung
1052
            @date   2019.04.01
1053
        '''
1054
        from EngineeringAbstractItem import QEngineeringAbstractItem
1055
        from SymbolSvgItem import SymbolSvgItem
1056
        from EngineeringEquipmentItem import QEngineeringEquipmentItem
1057
        from AppDocData import AppDocData
1058
        errors = []
1059

    
1060
        try:
1061
            _translate = QCoreApplication.translate
1062

    
1063
            docdata = AppDocData.instance()
1064
            dataPath = docdata.getErrorItemSvgPath()
1065

    
1066
            connectedUid = []
1067

    
1068
            for connector in self.connectors:
1069
                # for duplicattion check
1070
                if connector.connectedItem and issubclass(type(connector.connectedItem), QEngineeringAbstractItem):
1071
                    connectedUid.append(str(connector.connectedItem.uid))
1072

    
1073
                # check if there is not connected connector
1074
                if connector.connectedItem is None:
1075
                    error = SymbolSvgItem.createItem('Error', None, dataPath)
1076
                    error.setPosition(connector.center())
1077
                    error.parent = self
1078
                    error.msg = _translate('disconnected', 'disconnected')
1079
                    error.setToolTip(error.msg)
1080
                    error.area = 'Drawing'
1081
                    error.name = 'Error'
1082
                    errors.append(error)
1083

    
1084
                # check line to symbol
1085
                elif issubclass(type(connector.connectedItem), SymbolSvgItem) and type(
1086
                        connector.connectedItem) is not QEngineeringEquipmentItem:
1087
                    matches = [conn for conn in connector.connectedItem.connectors if conn.connectedItem is self]
1088
                    # check if two items are connected each other
1089
                    if not matches:
1090
                        error = SymbolSvgItem.createItem('Error', None, dataPath)
1091
                        error.setPosition(connector.center())
1092
                        error.parent = self
1093
                        error.msg = _translate('disconnected from opposite side', 'disconnected from opposite side')
1094
                        error.setToolTip(error.msg)
1095
                        error.area = 'Drawing'
1096
                        error.name = 'Error'
1097
                        errors.append(error)
1098
                    # check connection position
1099
                    elif not self.isOverlap(connector.sceneBoundingRect(), matches[0].sceneBoundingRect()):
1100
                        error = SymbolSvgItem.createItem('Error', None, dataPath)
1101
                        error.setPosition(connector.center())
1102
                        error.parent = self
1103
                        error.msg = _translate('mismatched position', 'mismatched position')
1104
                        error.setToolTip(error.msg)
1105
                        error.area = 'Drawing'
1106
                        error.name = 'Error'
1107
                        errors.append(error)
1108

    
1109
                elif issubclass(type(connector.connectedItem), QEngineeringLineItem):
1110
                    # check if connected two lines has same direction
1111
                    if connector._connected_at == QEngineeringAbstractItem.CONNECTED_AT_PT:
1112
                        center = connector.center()
1113

    
1114
                        indices = [0, 0]
1115
                        indices[0] = 1 if QPointF(center[0], center[1]) == self.line().p1() else 2
1116
                        matches = [conn for conn in connector.connectedItem.connectors if
1117
                                   conn.connectedItem == self and conn._connected_at == QEngineeringAbstractItem.CONNECTED_AT_PT]
1118
                        if matches:
1119
                            indices[1] = 1 if QPointF(matches[0].center()[0], matches[0].center()[
1120
                                1]) == connector.connectedItem.line().p1() else 2
1121
                        else:
1122
                            error = SymbolSvgItem.createItem('Error', None, dataPath)
1123
                            error.setPosition(connector.center())
1124
                            error.parent = self
1125
                            error.msg = _translate('disconnected from opposite side', 'disconnected from opposite side')
1126
                            error.setToolTip(error.msg)
1127
                            error.area = 'Drawing'
1128
                            error.name = 'Error'
1129
                            errors.append(error)
1130

    
1131
                        if indices[0] == indices[1]:
1132
                            error = SymbolSvgItem.createItem('Error', None, dataPath)
1133
                            error.setPosition(connector.center())
1134
                            error.parent = self
1135
                            error.msg = _translate('flow direction error', 'flow direction error')
1136
                            error.setToolTip(error.msg)
1137
                            error.area = 'Drawing'
1138
                            error.name = 'Error'
1139
                            errors.append(error)
1140

    
1141
                        if self.lineType != connector.connectedItem.lineType:
1142
                            error = SymbolSvgItem.createItem('Error', None, dataPath)
1143
                            error.setPosition(connector.center())
1144
                            error.parent = self
1145
                            error.msg = _translate('line type error', 'line type error')
1146
                            error.setToolTip(error.msg)
1147
                            error.area = 'Drawing'
1148
                            error.name = 'Error'
1149
                            errors.append(error)
1150

    
1151
                errors.extend(connector.validate())
1152

    
1153
            # check duplicated connection
1154
            if len(connectedUid) is not len(set(connectedUid)):
1155
                error = SymbolSvgItem.createItem('Error', None, dataPath)
1156
                error.setPosition([self.sceneBoundingRect().center().x(), self.sceneBoundingRect().center().y()])
1157
                error.parent = self
1158
                error.msg = _translate('duplicated connection', 'duplicated connection')
1159
                error.setToolTip(error.msg)
1160
                error.area = 'Drawing'
1161
                error.name = 'Error'
1162
                errors.append(error)
1163

    
1164
        except Exception as ex:
1165
            from App import App
1166
            from AppDocData import MessageType
1167

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

    
1172
        return errors
1173

    
1174
    '''
1175
        @brief  update line type
1176
        @author humkyung
1177
        @date   2018.07.05
1178
    '''
1179

    
1180
    def update_line_type(self):
1181
        import uuid
1182
        from LineTypeConditions import LineTypeConditions
1183

    
1184
        try:
1185
            pool, visited, items = [self], [], []
1186
            while pool:
1187
                obj = pool.pop()
1188
                visited.append(obj)
1189

    
1190
                """ connected items """
1191
                connected = [connector.connectedItem for connector in obj.connectors if
1192
                             connector.connectedItem and not type(connector.connectedItem) is uuid.UUID]
1193
                """ connected lines at point """
1194
                lines = [connector.connectedItem for connector in obj.connectors if connector.connectedItem and type(
1195
                    connector.connectedItem) is QEngineeringLineItem and connector._connected_at == QEngineeringAbstractItem.CONNECTED_AT_PT]
1196
                """ add items not in 'connected' to items list """
1197
                items.extend([item for item in connected if item not in lines])
1198
                """ add items not visited to pool """
1199
                pool.extend([item for item in lines if item not in visited])
1200

    
1201
            for condition in LineTypeConditions.items():
1202
                if condition.eval(items):
1203
                    self.lineType = condition.name
1204
                    break
1205
                if condition.eval(items, reverse=True):
1206
                    self.lineType = condition.name
1207
                    break
1208
        except Exception as ex:
1209
            from App import App
1210
            from AppDocData import MessageType
1211

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

    
1216
    def hoverEnterEvent(self, event):
1217
        """ hilight item and it's children """
1218
        self.highlight(True)
1219

    
1220
    def hoverLeaveEvent(self, event):
1221
        """ restore original color """
1222
        self.highlight(False)
1223

    
1224
    def highlight(self, flag):
1225
        self.hover = flag
1226
        self.setZValue(QEngineeringAbstractItem.HOVER_ZVALUE) if flag else self.setZValue(QEngineeringLineItem.ZVALUE)
1227
        self.update()
1228

    
1229
        for connector in self.connectors:
1230
            connector.highlight(flag)
1231

    
1232
    def hoverMoveEvent(self, event):
1233
        pass
1234

    
1235
    '''
1236
        @brief      remove item when user press delete key
1237
        @author     humkyung
1238
        @date       2018.04.23
1239
        @history    swap start, end point when user press 'c' key
1240
    '''
1241

    
1242
    def keyPressEvent(self, event):
1243
        if self.isSelected() and event.key() == Qt.Key_Delete:
1244
            self.scene().removeItem(self)
1245
        elif event.key() == Qt.Key_C:
1246
            self.reverse()
1247
        elif event.key() == Qt.Key_A:
1248
            self.toggleFlowMark()
1249

    
1250
    def toggleFlowMark(self):
1251
        from AppDocData import AppDocData
1252

    
1253
        if self.flowMark:
1254
            self.flowMark = None
1255
        else:
1256
            configs = AppDocData.instance().getConfigs('Flow Mark', 'Position')
1257
            self.flowMark = int(configs[0].value) if 1 == len(configs) else 100
1258
        self.update_arrow()
1259

    
1260
    '''
1261
        @brief  draw rect when item is selected
1262
        @author humkyung
1263
        @date   2018.07.07
1264
    '''
1265

    
1266
    def drawFocusRect(self, painter):
1267
        self.focuspen = QPen(Qt.DotLine)
1268
        self.focuspen.setColor(Qt.black)
1269
        self.focuspen.setWidthF(1.5)
1270
        hilightColor = QColor(255, 0, 0, 127)
1271
        painter.setBrush(QBrush(hilightColor))
1272
        painter.setPen(self.focuspen)
1273
        painter.drawRect(self.boundingRect())
1274

    
1275
    '''
1276
        @brief      override paint method
1277
        @history    humkyung 2018.08.30 draw flowmark only for Primary or Secondary
1278
    '''
1279

    
1280
    def paint(self, painter, option, widget):
1281
        color = self.getColor()
1282
        self.setColor(color)
1283

    
1284
        QGraphicsLineItem.paint(self, painter, option, widget)
1285

    
1286
        if self.isSelected():
1287
            self.drawFocusRect(painter)
1288

    
1289
    '''
1290
        @brief  draw self to given image
1291
        @author humkyung
1292
        @date   2018.06.21
1293
    '''
1294

    
1295
    def drawToImage(self, img, color, thickness):
1296
        try:
1297
            # write recognized lines to image
1298
            ptStart = self.startPoint()
1299
            ptEnd = self.endPoint()
1300
            cv2.line(img, (round(ptStart[0]), round(ptStart[1])), (round(ptEnd[0]), round(ptEnd[1])), color, thickness)
1301
            # up to here
1302

    
1303
            # if self.flowMark is not None:
1304
            #    x, y, w, h = self.flowMark[0]
1305
            #    img[y:(y+h), x:(x+w)] = color
1306
        except Exception as ex:
1307
            print('error occurred({}) in {}:{}'.format(ex, sys.exc_info()[-1].tb_frame.f_code.co_filename,
1308
                                                       sys.exc_info()[-1].tb_lineno))
1309

    
1310
    @staticmethod
1311
    def from_database(component):
1312
        """ get line from database """
1313
        import uuid
1314
        from AppDocData import AppDocData
1315
        from SymbolAttr import SymbolAttr
1316

    
1317
        item = None
1318
        try:
1319
            uidNode = component['UID']
1320
            uid = uidNode if uidNode is not None else uuid.uuid4()  # generate UUID
1321
            owner = uuid.UUID(component['Owner']) if component['Owner'] and component['Owner'] != 'None' else None
1322

    
1323
            app_doc_data = AppDocData.instance()
1324
            connectors = app_doc_data.get_component_connectors(uid)
1325
            if 2 != len(connectors): return item
1326
            startPoint = [float(connectors[0]['X']), float(connectors[0]['Y'])]
1327
            endPoint = [float(connectors[1]['X']), float(connectors[1]['Y'])]
1328

    
1329
            item = QEngineeringLineItem(vertices=[startPoint, endPoint], uid=uid)
1330
            item.setVisible(False)
1331
            attrs = app_doc_data.get_component_attributes(uid)
1332
            matches = [attr for attr in attrs if attr['Attribute'] == 'LineType']
1333
            item.lineType = matches[0]['Value'] if matches else 'Secondary'
1334
            ## assign area
1335
            if component['Area']:
1336
                for area in app_doc_data.getAreaList():
1337
                    if area.contains(startPoint) and area.contains(endPoint):
1338
                        item.area = area.name
1339
                        break
1340
            else:
1341
                item.area = component['Area']
1342
            ## up to here
1343

    
1344
            matches = [attr for attr in attrs if attr['Attribute'] == 'Thickness']
1345
            item.thickness = int(matches[0]['Value']) if matches and matches[0]['Value'] != 'None' else None
1346

    
1347
            matches = [attr for attr in attrs if attr['Attribute'] == 'FlowMark']
1348
            item.flowMark = int(matches[0]['Value']) if matches and matches[0]['Value'] != 'None' else None
1349

    
1350
            if connectors:
1351
                iterIndex = 0
1352
                for connector in connectors:
1353
                    item.connectors[iterIndex].parse_record(connector)
1354
                    iterIndex += 1
1355

    
1356
            # get associations 
1357
            associations = app_doc_data.get_component_associations(uid)
1358
            if associations:
1359
                for assoc in associations:
1360
                    _type = assoc['Type']
1361
                    if not _type in item._associations:
1362
                        item._associations[_type] = []
1363
                    item._associations[_type].append(uuid.UUID(assoc['Association']))
1364
            # up to here
1365
        except Exception as ex:
1366
            from App import App
1367
            from AppDocData import MessageType
1368

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

    
1373
        return item if item.length() > 1 else None
1374

    
1375
    '''
1376
        @brief      parse xml code
1377
        @author     humkyung
1378
        @date       2018.06.27
1379
    '''
1380

    
1381
    @staticmethod
1382
    def fromXml(node):
1383
        import uuid
1384
        from AppDocData import AppDocData
1385
        from SymbolAttr import SymbolAttr
1386

    
1387
        item = None
1388
        try:
1389
            uidNode = node.find('UID')
1390
            uid = uidNode.text if uidNode is not None else uuid.uuid4()  # generate UUID
1391
            owner = uuid.UUID(node.attrib['OWNER']) if 'OWNER' in node.attrib and node.attrib[
1392
                'OWNER'] != 'None' else None
1393

    
1394
            startPoint = [float(x) for x in node.find('STARTPOINT').text.split(',')]
1395
            endPoint = [float(x) for x in node.find('ENDPOINT').text.split(',')]
1396

    
1397
            item = QEngineeringLineItem(vertices=[startPoint, endPoint], uid=uid)
1398
            item.setVisible(False)
1399
            item.lineType = node.find('TYPE').text if node.find('TYPE') is not None else 'Secondary'
1400
            ## assign area
1401
            if node.find('AREA') is None:
1402
                appDocData = AppDocData.instance()
1403
                for area in appDocData.getAreaList():
1404
                    if area.contains(startPoint) and area.contains(endPoint):
1405
                        item.area = area.name
1406
                        break
1407
            else:
1408
                item.area = node.find('AREA').text
1409
            ## up to here
1410

    
1411
            thicknessNode = node.find('THICKNESS')
1412
            item.thickness = int(
1413
                thicknessNode.text) if thicknessNode is not None and thicknessNode.text != 'None' else None
1414

    
1415
            flowMarkNode = node.find('FLOWMARK')
1416
            item.flowMark = int(flowMarkNode.text) if flowMarkNode is not None and flowMarkNode.text != 'None' else None
1417

    
1418
            connectors = node.find('CONNECTORS')
1419
            if connectors is not None:
1420
                iterIndex = 0
1421
                for connector in connectors.iter('CONNECTOR'):
1422
                    item.connectors[iterIndex].parse_xml(connector)
1423
                    iterIndex += 1
1424

    
1425
            # get associations 
1426
            attributeValue = node.find('ASSOCIATIONS')
1427
            if attributeValue is not None:
1428
                for assoc in attributeValue.iter('ASSOCIATION'):
1429
                    _type = assoc.attrib['TYPE']
1430
                    if not _type in item._associations:
1431
                        item._associations[_type] = []
1432
                    item._associations[_type].append(uuid.UUID(assoc.text))
1433
            # up to here
1434

    
1435
            attributes = node.find('SYMBOLATTRIBUTES')
1436
            if attributes is not None:
1437
                for attr in attributes.iter('ATTRIBUTE'):
1438
                    _attr = SymbolAttr.fromXml(attr)
1439
                    item.attrs[_attr] = attr.text
1440

    
1441
        except Exception as ex:
1442
            from App import App
1443
            from AppDocData import MessageType
1444

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

    
1449
        return item if item.length() > 1 else None
1450

    
1451
    '''
1452
        @brief      generate xml code
1453
        @author     humkyung
1454
        @date       2018.04.23
1455
        @history    humkyung 2018.06.27 write line type to xml
1456
                    humkyung 2018.07.23 write connected item's uid to xml
1457
    '''
1458

    
1459
    def toXml(self):
1460
        from xml.etree.ElementTree import Element, SubElement, dump, ElementTree
1461
        from LineTypeConditions import LineTypeConditions
1462
        from SymbolAttr import SymbolAttr
1463

    
1464
        try:
1465
            node = Element('LINE')
1466
            node.attrib['OWNER'] = str(self._owner) if self._owner else 'None'
1467
            uidNode = Element('UID')
1468
            uidNode.text = str(self.uid)
1469
            node.append(uidNode)
1470

    
1471
            startPt = self.startPoint()
1472
            endPt = self.endPoint()
1473

    
1474
            startNode = Element('STARTPOINT')
1475
            startNode.text = '{},{}'.format(startPt[0], startPt[1])
1476
            node.append(startNode)
1477

    
1478
            endNode = Element('ENDPOINT')
1479
            endNode.text = '{},{}'.format(endPt[0], endPt[1])
1480
            node.append(endNode)
1481

    
1482
            typeNode = Element('TYPE')
1483
            typeNode.text = self.lineType
1484
            for lineType in LineTypeConditions.items():
1485
                if self.lineType == lineType.name:
1486
                    typeNode.attrib['TYPEUID'] = str(lineType)
1487
                    break
1488
            node.append(typeNode)
1489

    
1490
            areaNode = Element('AREA')
1491
            areaNode.text = self.area
1492
            node.append(areaNode)
1493

    
1494
            thicknessNode = Element('THICKNESS')
1495
            thicknessNode.text = str(self.thickness)
1496
            node.append(thicknessNode)
1497

    
1498
            flowMarkNode = Element('FLOWMARK')
1499
            flowMarkNode.text = str(self.flowMark)
1500
            node.append(flowMarkNode)
1501

    
1502
            connectorsNode = Element('CONNECTORS')
1503
            for connector in self.connectors:
1504
                connectorsNode.append(connector.toXml())
1505
            node.append(connectorsNode)
1506

    
1507
            attributeValueNode = Element('ASSOCIATIONS')
1508
            for assoc in self.associations():
1509
                assoc_node = Element('ASSOCIATION')
1510
                assoc_node.attrib['TYPE'] = QEngineeringAbstractItem.assoc_type(assoc)
1511
                assoc_node.text = str(assoc.uid)
1512
                attributeValueNode.append(assoc_node)
1513
            node.append(attributeValueNode)
1514

    
1515
            attributesNode = Element('SYMBOLATTRIBUTES')
1516
            _attrs = self.getAttributes()
1517
            for attr in _attrs:
1518
                if type(attr) is SymbolAttr:
1519
                    _node = attr.toXml()
1520
                    _node.text = str(_attrs[attr])
1521
                    attributesNode.append(_node)
1522

    
1523
            node.append(attributesNode)
1524

    
1525
            # up to here
1526
        except Exception as ex:
1527
            from App import App
1528
            from AppDocData import MessageType
1529

    
1530
            message = 'error occurred({}) in {}:{}'.format(ex, sys.exc_info()[-1].tb_frame.f_code.co_filename,
1531
                                                           sys.exc_info()[-1].tb_lineno)
1532
            App.mainWnd().addMessage.emit(MessageType.Error, message)
1533
            return None
1534

    
1535
        return node
1536

    
1537
    def toSql_return_separately(self):
1538
        """ generate sql phrase to save line to database """
1539
        import uuid
1540
        from AppDocData import AppDocData
1541

    
1542
        res = []
1543
        resLater = []
1544

    
1545
        app_doc_data = AppDocData.instance()
1546
        cols = ['UID', 'Drawings_UID', 'Symbol_UID', 'X', 'Y', 'Width', 'Height', 'Rotation', 'Area', 'Owner',
1547
                'SpecialItemTypes_UID']
1548
        values = ['?', '?', "(select UID from Symbol where Name='Line' and SymbolType_UID=-1)", '?', '?', '?', '?', '?',
1549
                  '?', '?', '?']
1550

    
1551
        rect = self.sceneBoundingRect()
1552
        param = [
1553
            (str(self.uid), str(app_doc_data.activeDrawing.UID), rect.x(), rect.y(), rect.width(), rect.height(), 0,
1554
             self.area, str(self.owner) if self.owner else None,
1555
             str(self.special_item_type) if self.special_item_type else None)]
1556
        sql = 'insert into Components({}) values({})'.format(','.join(cols), ','.join(values))
1557
        res.append((sql, tuple(param)))
1558

    
1559
        # save connectors to database
1560
        cols = ['Components_UID', '[Index]', 'X', 'Y', 'Connected', 'Connected_At']
1561
        values = ['?', '?', '?', '?', '?', '?']
1562
        params = []
1563
        index = 1
1564
        for connector in self.connectors:
1565
            params.append((  # str(connector.uid),
1566
                str(self.uid), index, connector.connectPoint[0], connector.connectPoint[1], \
1567
                str(connector.connectedItem.uid) if connector.connectedItem else None, \
1568
                str(connector._connected_at)))
1569
            index += 1
1570
        sql = 'insert into Points({}) values({})'.format(','.join(cols), ','.join(values))
1571
        resLater.append((sql, tuple(params)))
1572
        # up to here
1573

    
1574
        # save attributes
1575
        cols = ['UID', 'Components_UID', 'SymbolAttribute_UID', 'Value']
1576
        values = ['?', '?', "(select UID from SymbolAttribute where Attribute='LineType' and SymbolType_UID=-1)", '?']
1577
        param = [(str(uuid.uuid4()), str(self.uid), self.lineType)]
1578
        sql = 'insert into Attributes({}) values({})'.format(','.join(cols), ','.join(values))
1579
        res.append((sql, tuple(param)))
1580
        values = ['?', '?', "(select UID from SymbolAttribute where Attribute='Thickness' and SymbolType_UID=-1)", '?']
1581
        param = [(str(uuid.uuid4()), str(self.uid), str(self.thickness))]
1582
        sql = 'insert into Attributes({}) values({})'.format(','.join(cols), ','.join(values))
1583
        res.append((sql, tuple(param)))
1584
        values = ['?', '?', "(select UID from SymbolAttribute where Attribute='FlowMark' and SymbolType_UID=-1)", '?']
1585
        param = [(str(uuid.uuid4()), str(self.uid), str(self.flowMark))]
1586
        sql = 'insert into Attributes({}) values({})'.format(','.join(cols), ','.join(values))
1587
        res.append((sql, tuple(param)))
1588
        # up to here
1589

    
1590
        return res, resLater
1591

    
1592
    '''
1593
        @brief      Delete Line Item from scene
1594
        @author     Jeongwoo
1595
        @date       2018.05.29
1596
        @history    2018.05.29  Add parameter 'self' / Make comments emit()
1597
    '''
1598

    
1599
    def deleteLineItemFromScene(self):
1600
        self.scene().removeItem(self)
1601

    
1602
    def getColor(self):
1603
        """ return line's color """
1604
        from AppDocData import AppDocData
1605
        from DisplayColors import DisplayColors
1606
        from DisplayColors import DisplayOptions
1607
        from EngineeringAbstractItem import QEngineeringAbstractItem
1608

    
1609
        if DisplayOptions.DisplayByLineType == DisplayColors.instance().option:
1610
            if self.hover:
1611
                return QEngineeringAbstractItem.HOVER_COLOR
1612
            else:
1613
                if self.lineType in QEngineeringLineItem.LINE_TYPE_COLORS:
1614
                    return QEngineeringLineItem.LINE_TYPE_COLORS[self.lineType]
1615
                else:
1616
                    app_doc_data = AppDocData.instance()
1617
                    configs = app_doc_data.getConfigs('LineTypes', self.lineType)
1618
                    if configs:
1619
                        tokens = configs[0].value.split(',')
1620
                        QEngineeringLineItem.LINE_TYPE_COLORS[self.lineType] = tokens[0] if len(
1621
                            tokens) == 4 else QEngineeringAbstractItem.DEFAULT_COLOR
1622
                        return QEngineeringLineItem.LINE_TYPE_COLORS[self.lineType]
1623
                    else:
1624
                        return QEngineeringAbstractItem.DEFAULT_COLOR
1625
        else:
1626
            if self.owner is None:
1627
                return QEngineeringAbstractItem.getColor(self)
1628
            else:
1629
                return self.owner.getColor()
1630

    
1631
    '''
1632
        @brief      Set Color. Override QEngineeringAbstractItem's
1633
        @author     Jeongwoo
1634
        @date       2018.05.11
1635
        @history    2018.05.11  Jeongwoo    Add self.setPen() Method
1636
        @history    humkyung 2018.05.13 call setPen method to apply changed color
1637
    '''
1638
    def setColor(self, color):
1639
        c = QColor()
1640
        c.setNamedColor(color)
1641
        _pen = self.pen()
1642
        _pen.setColor(c)
1643
        self.setPen(_pen)
1644
        self.update()
1645

    
1646
    def clear_labels(self):
1647
        """ clrear spec labels """
1648
        attrs = self.getAttributes()
1649
        index = 0
1650
        for key in attrs.keys():
1651
            if index >= 6:
1652
                break
1653
            if key.AssocItem:
1654
                key.AssocItem.owner = None
1655
                self.remove_assoc_item(key.AssocItem)
1656
                key.AssocItem = None
1657
            attrs[key] = ''
1658
            index += 1
1659
    
1660
    def update_flow_mark(self, position, length):
1661
        """ update flow mark for flow arrow """
1662
        import math
1663

    
1664
        to_item = self.connectors[1].connectedItem
1665
        if type(to_item) is QEngineeringLineItem and self.length() > length and not self.isParallel(to_item):
1666
            self.flowMark = position
1667

    
1668
    def update_arrow(self):
1669
        """ update flow arrow """
1670
        import math
1671
        from EngineeringArrowItem import QEngineeringArrowItem
1672

    
1673
        if self.length() < 0.01:
1674
            return
1675

    
1676
        start = self.line().p1()
1677
        end = self.line().p2()
1678

    
1679
        dx = end.x() - start.x()
1680
        dy = end.y() - start.y()
1681
        _dir = [dx / self.length(), dy / self.length()]
1682

    
1683
        arrow_size = QEngineeringLineItem.ARROW_SIZE * 0.25
1684

    
1685
        if not self.is_piping():
1686
            self.flowMark = None
1687

    
1688
        if self.flowMark:
1689
            arrow_size *= 2
1690
            end = QPointF(start.x() + dx * self.flowMark / 100, start.y() + dy * self.flowMark / 100)
1691

    
1692
        perpendicular = (-_dir[1], _dir[0])
1693
        polygon = QPolygonF()
1694
        polygon.append(QPointF(end.x() - _dir[0] * QEngineeringLineItem.ARROW_SIZE + perpendicular[0] * arrow_size,
1695
                               end.y() - _dir[1] * QEngineeringLineItem.ARROW_SIZE + perpendicular[1] * arrow_size))
1696
        polygon.append(QPointF(end.x() - _dir[0] * QEngineeringLineItem.ARROW_SIZE - perpendicular[0] * arrow_size,
1697
                               end.y() - _dir[1] * QEngineeringLineItem.ARROW_SIZE - perpendicular[1] * arrow_size))
1698
        polygon.append(end)
1699
        polygon.append(polygon[0])  # close polygon
1700

    
1701
        if not hasattr(self, '_arrow'):
1702
            self._arrow = QEngineeringArrowItem(polygon, self)
1703
        else:
1704
            self._arrow.setPolygon(polygon)
1705

    
1706
        if self.flowMark:
1707
            self._arrow.setBrush(Qt.red)
1708
        else:
1709
            self._arrow.setBrush(Qt.blue)
1710
        self._arrow.update()
1711

    
1712
        '''
1713
        if self._flowMark:
1714
            flowMark = self._flowMark[0]
1715
            flowMark.angle = math.atan2(_dir[0], _dir[1]) - math.pi / 2
1716
            flowMark.loc = [self.connectors[1].center()[0] - flowMark.symbolOrigin[0] - _dir[0] * 20, self.connectors[1].center()[1] - flowMark.symbolOrigin[1] - _dir[1] * 20]
1717
            flowMark.origin = [self.connectors[1].center()[0] - _dir[0] * 20, self.connectors[1].center()[1] - _dir[1] * 20]
1718
            scene = flowMark.scene()
1719
            scene.removeItem(flowMark)
1720
            flowMark.addSvgItemToScene(scene)
1721
        '''
1722

    
1723
    def onConnectorPosChaned(self, connector):
1724
        """update line shape when connector is moved"""
1725

    
1726
        start = self.connectors[0].center()
1727
        end = self.connectors[1].center()
1728
        self.setLine(start[0], start[1], end[0], end[1])
1729
        self.update()
1730

    
1731
        tooltip = '<b>{}</b><br>({},{})-({},{})'.format(str(self.uid), start[0], start[1], end[0], end[1])
1732
        self.setToolTip(tooltip)
1733

    
1734
        self.update_arrow()
1735

    
1736
        self.scene().contents_changed.emit()
1737

    
1738
    '''
1739
        @brief      
1740
        @author     humkyung
1741
        @date       2018.07.24
1742
    '''
1743

    
1744
    def mousePressEvent(self, event):
1745
        import math
1746

    
1747
        if event.buttons() == Qt.LeftButton:
1748
            pos = event.scenePos()
1749
            ptStart = self.startPoint()
1750
            dx = ptStart[0] - pos.x()
1751
            dy = ptStart[1] - pos.y()
1752
            if math.sqrt(dx * dx + dy * dy) < 10:
1753
                self._selectedIndex = 0
1754
                return
1755

    
1756
            ptEnd = self.endPoint()
1757
            dx = ptEnd[0] - pos.x()
1758
            dy = ptEnd[1] - pos.y()
1759
            if math.sqrt(dx * dx + dy * dy) < 10:
1760
                self._selectedIndex = 1
1761
                return
1762

    
1763
            self._selectedIndex = -1
1764

    
1765
        QGraphicsLineItem.mousePressEvent(self, event)
1766

    
1767
    def mouseReleaseEvent(self, event):
1768
        self._selectedIndex = -1
1769

    
1770
        QGraphicsLineItem.mouseReleaseEvent(self, event)
1771

    
1772

    
1773
'''
1774
    @brief      The class transfer pyqtSignal Event. Cause Subclass of QGraphicsRectItem can't use pyqtSignal
1775
    @author     Jeongwoo
1776
    @date       2018.06.18
1777
'''
1778

    
1779

    
1780
class Transfer(QObject):
1781
    onRemoved = pyqtSignal(QGraphicsItem)
1782

    
1783
    def __init__(self, parent=None):
1784
        QObject.__init__(self, parent)
클립보드 이미지 추가 (최대 크기: 500 MB)