프로젝트

일반

사용자정보

통계
| 브랜치(Branch): | 개정판:

hytos / DTI_PID / DTI_PID / Shapes / EngineeringLineItem.py @ 47af3d63

이력 | 보기 | 이력해설 | 다운로드 (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
                self.setToolTip(self.tooltip)
96
        except Exception as ex:
97
            from App import App
98
            from AppDocData import MessageType
99

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

    
104
    def __str__(self):
105
        """ return string represent uuid """
106
        return str(self.uid)
107

    
108
    @property
109
    def tooltip(self):
110
        """return tooltip string"""
111

    
112
        start = self.connectors[0].center()
113
        end = self.connectors[1].center()
114
        tooltip = f"<b>TYPE=LINE</b><br>UID={str(self.uid)}<br>POS=({start[0]},{start[1]})-({end[0]},{end[1]})"
115

    
116
        return tooltip
117

    
118
    @property
119
    def properties(self):
120
        """ getter of flow mark """
121
        import uuid
122

    
123
        for prop, value in self._properties.items():
124
            if prop.is_selectable and type(value) is uuid.UUID and self.scene():
125
                matches = [x for x in self.scene().items() if hasattr(x, 'uid') and str(x.uid) == str(value)]
126
                if matches: self._properties[prop] = matches[0]
127

    
128
            if prop.Expression: self._properties[prop] = eval(prop.Expression)
129

    
130
        return self._properties
131

    
132
    def set_property(self, property, value):
133
        """ set property with given value """
134
        if issubclass(type(value), QEngineeringAbstractItem): self.add_assoc_item(value, 0)
135
        matches = [prop for prop, _ in self._properties.items() if prop.Attribute == property]
136
        if matches: self._properties[matches[0]] = value
137

    
138
    def prop(self, name):
139
        """ return the value of given property with name """
140
        matches = [(prop, value) for prop, value in self.properties.items() if prop.Attribute == name]
141
        if matches: return matches[0][1]
142

    
143
        return None
144

    
145
    @property
146
    def Size(self):
147
        """ always return None """
148
        return None
149

    
150
    @property
151
    def EvaluatedSize(self):
152
        from EngineeringReducerItem import QEngineeringReducerItem
153
        from EngineeringLineNoTextItem import QEngineeringLineNoTextItem
154

    
155
        if self.Size: return self.Size
156
        if self.owner and issubclass(type(self.owner), QEngineeringLineNoTextItem):
157
            matches = [run for run in self.owner.runs if self in run.items]
158
            if matches:
159
                at = matches[0].items.index(self)
160
                upstream = matches[0].items[:at]
161
                upstream.reverse()
162
                prev = self
163
                for item in upstream:
164
                    if type(item) is QEngineeringReducerItem:
165
                        if item.connectors[0].connectedItem is prev:  ### Main Size
166
                            if item.MainSubSize: return item.MainSubSize[0]
167
                        elif item.connectors[1].connectedItem is prev:  ### Sub Size
168
                            if item.MainSubSize: return item.MainSubSize[1]
169
                    else:
170
                        if item.Size: return item.Size
171
                    prev = item
172

    
173
                downstream = matches[0].items[at:]
174
                prev = self
175
                for item in downstream:
176
                    if type(item) is QEngineeringReducerItem:
177
                        if item.connectors[0].connectedItem is prev:  ### Main Size
178
                            if item.MainSubSize: return item.MainSubSize[0]
179
                        elif item.connectors[1].connectedItem is prev:  ### Sub Size
180
                            if item.MainSubSize: return item.MainSubSize[1]
181
                    else:
182
                        if item.Size: return item.Size
183
                    prev = item
184

    
185
                if 'Drain' == matches[0].Type:
186
                    from AppDocData import AppDocData
187
                    return AppDocData.instance().drain_size
188

    
189
            return self.owner.Size
190

    
191
        return None
192

    
193
    '''
194
        @breif  getter owner
195
        @author humkyung
196
        @date   2018.05.10
197
    '''
198

    
199
    @property
200
    def owner(self):
201
        import uuid
202

    
203
        if self._owner and type(self._owner) is uuid.UUID:
204
            matches = [x for x in self.scene().items() if hasattr(x, 'uid') and str(x.uid) == str(self._owner)]
205
            if matches: self._owner = matches[0]
206

    
207
        if type(self._owner) is not uuid.UUID and type(self._owner) is not str:
208
            return self._owner
209
        else:
210
            self._owner = None
211
            return None
212

    
213
    '''
214
        @brief  setter owner
215
        @author humkyung
216
        @date   2018.05.10
217
        @history    2018.05.17  Jeongwoo    Add Calling setColor
218
    '''
219

    
220
    @owner.setter
221
    def owner(self, value):
222
        self._owner = value
223

    
224
        if self._owner is None:
225
            self._color = self.DEFAULT_COLOR
226
        self.setColor(self._color)
227

    
228
    '''
229
        @brief  getter flow mark
230
        @author humkyung
231
        @date   2018.06.21
232
    '''
233

    
234
    @property
235
    def flowMark(self):
236
        return self._flowMark
237

    
238
    '''
239
        @brief  setter flow mark
240
        @author humkyung
241
        @date   2018.06.21
242
    '''
243

    
244
    @flowMark.setter
245
    def flowMark(self, value):
246
        self._flowMark = value
247

    
248
    '''
249
        @brief  getter of lineType
250
        @author humkyung
251
        @date   2018.06.27
252
    '''
253

    
254
    @property
255
    def lineType(self):
256
        return self._lineType
257

    
258
    '''
259
        @brief  setter of lineType
260
        @author humkyung
261
        @date   2018.06.27
262
    '''
263

    
264
    @lineType.setter
265
    def lineType(self, value):
266
        from AppDocData import AppDocData
267

    
268
        self._lineType = value
269

    
270
        docData = AppDocData.instance()
271
        configs = docData.getLineTypeConfig(self._lineType)
272
        if configs:
273
            _pen = self.pen()
274
            _pen.setWidth(configs[2])
275
            _pen.setStyle(configs[3])
276
            self.setPen(_pen)
277
            self.setOpacity(float(configs[4]) / 100)
278
            self.update()
279

    
280
        if self.scene():
281
            self.update_arrow()
282

    
283
    '''
284
        @brief  clone an object
285
    '''
286

    
287
    def clone(self):
288
        clone = QEngineeringLineItem()
289
        # clone._vertices = copy.deepcopy(self._vertices)
290
        # for vertex in clone._vertices:
291
        #    clone._pol.append(QPointF(vertex[0], vertex[1]))
292
        clone.buildItem()
293
        clone.isCreated = self.isCreated
294

    
295
        return clone
296

    
297
    def set_line(self, line):
298
        """ set line """
299
        self.setLine(line[0][0], line[0][1], line[1][0], line[1][1])
300
        self.connectors[0].setPos(line[0])
301
        self.connectors[1].setPos(line[1])
302
        self.update_arrow()
303

    
304
    '''
305
        @brief  return start point
306
        @author humkyung
307
        @date   2018.04.16
308
    '''
309

    
310
    def startPoint(self):
311
        at = self.line().p1()
312
        return (at.x(), at.y())
313

    
314
    '''
315
        @brief  return last point
316
        @author humkyung
317
        @date   2018.04.16
318
    '''
319

    
320
    def endPoint(self):
321
        at = self.line().p2()
322
        return (at.x(), at.y())
323

    
324
    '''
325
        @brief  dot product of given two vectors
326
        @author humkyung
327
        @date   2018.04.14
328
    '''
329

    
330
    def dotProduct(self, lhs, rhs):
331
        return sum([lhs[i] * rhs[i] for i in range(len(lhs))])
332

    
333
    '''
334
        @brief  distance between line and point
335
        @author humkyung
336
        @date   2018.04.16
337
    '''
338

    
339
    def distanceTo(self, pt):
340
        from shapely.geometry import Point, LineString
341

    
342
        startPt = self.startPoint()
343
        endPt = self.endPoint()
344
        line = LineString([(startPt[0], startPt[1]), (endPt[0], endPt[1])])
345
        dist = line.distance(Point(pt[0], pt[1]))
346

    
347
        return dist
348

    
349
    '''
350
        @brief  return perpendicular vector
351
        @author humkyung
352
        @date   2018.04.21
353
    '''
354

    
355
    def perpendicular(self):
356
        import math
357

    
358
        dx = self.endPoint()[0] - self.startPoint()[0]
359
        dy = self.endPoint()[1] - self.startPoint()[1]
360
        dx, dy = -dy, dx
361
        length = math.sqrt(dx * dx + dy * dy)
362
        dx /= length
363
        dy /= length
364

    
365
        return (dx, dy)
366

    
367
    '''
368
        @brief  return angle of line in radian
369
        @author humkyung
370
        @date   2018.04.22
371
    '''
372

    
373
    def angle(self):
374
        import math
375

    
376
        startPt = self.startPoint()
377
        endPt = self.endPoint()
378
        dx = endPt[0] - startPt[0]
379
        dy = endPt[1] - startPt[1]
380
        dot = self.dotProduct((1, 0), (dx, dy))
381
        length = math.sqrt(dx * dx + dy * dy)
382
        return math.acos(dot / length)
383

    
384
    '''
385
        @brief  return length of line
386
        @author humkyung
387
        @date   2018.05.08
388
    '''
389

    
390
    def length(self):
391
        import math
392

    
393
        startPt = self.startPoint()
394
        endPt = self.endPoint()
395
        dx = endPt[0] - startPt[0]
396
        dy = endPt[1] - startPt[1]
397
        return math.sqrt(dx * dx + dy * dy)
398

    
399
    '''
400
        @brief  check if line is horizontal
401
        @author humkyung
402
        @date   2018.04.27
403
    '''
404

    
405
    def isHorizontal(self):
406
        import math
407

    
408
        startPt = self.startPoint()
409
        endPt = self.endPoint()
410
        dx = endPt[0] - startPt[0]
411
        dy = endPt[1] - startPt[1]
412

    
413
        return math.fabs(dx) > math.fabs(dy)
414

    
415
    '''
416
        @brief  check if line is vertical 
417
        @author humkyung
418
        @date   2018.04.27
419
    '''
420

    
421
    def isVertical(self):
422
        import math
423

    
424
        startPt = self.startPoint()
425
        endPt = self.endPoint()
426
        dx = endPt[0] - startPt[0]
427
        dy = endPt[1] - startPt[1]
428

    
429
        return math.fabs(dy) > math.fabs(dx)
430

    
431
    '''
432
        @brief  get intersection point between this and given line
433
        @author humkyung
434
        @date   2018.04.21
435
        @history    Jeongwoo 2018.05.15 Add normalize
436
                    Jeongwoo 2018.05.16 Add length == 0 check
437
    '''
438

    
439
    def intersection(self, line):
440
        import math
441
        from shapely.geometry import Point, LineString
442

    
443
        startPt = self.startPoint()
444
        endPt = self.endPoint()
445
        dx = endPt[0] - startPt[0]
446
        dy = endPt[1] - startPt[1]
447
        length = math.sqrt(dx * dx + dy * dy)
448
        if length == 0:
449
            return None
450
        dx /= length
451
        dy /= length
452
        lhs = LineString([(startPt[0] - dx * 20, startPt[1] - dy * 20), (endPt[0] + dx * 20, endPt[1] + dy * 20)])
453
        rhs = LineString(line)
454
        return lhs.intersection(rhs)
455

    
456
    def getAngle(self, rhs):
457
        """ get angle between self and given line """
458
        import math
459

    
460
        try:
461
            return math.acos(self.dotProduct(self, rhs) / (self.length() * rhs.length()))
462
        except Exception as ex:
463
            return sys.float_info.max
464

    
465
    def isParallel(self, rhs):
466
        """ check if two lines are parallel """
467
        import math
468

    
469
        try:
470
            vectors = [(self.endPoint()[0] - self.startPoint()[0], self.endPoint()[1] - self.startPoint()[1]),
471
                       (rhs.endPoint()[0] - rhs.startPoint()[0], rhs.endPoint()[1] - rhs.startPoint()[1])]
472
            angle = self.getAngle(rhs)
473
            if (angle == 0) or (angle == math.pi): return True
474
        except ZeroDivisionError:
475
            return True
476

    
477
        return False
478

    
479
    '''
480
        @brief      check if two lines are connectable
481
        @author     humkyung
482
        @date       2018.05.12
483
        @history    Jeongwoo 18.05.15 Add check pt's type
484
                    Jeongwoo 18.05.16 Add length == 0 check
485
    '''
486

    
487
    def is_connectable(self, line, toler=20):
488
        import math
489

    
490
        startPt = line.startPoint()
491
        endPt = line.endPoint()
492

    
493
        dx = endPt[0] - startPt[0]
494
        dy = endPt[1] - startPt[1]
495
        length = math.sqrt(dx * dx + dy * dy)
496
        if length == 0:
497
            return False
498
        dx /= length
499
        dy /= length
500
        extendedLine = [(startPt[0] - dx * toler, startPt[1] - dy * toler),
501
                        (endPt[0] + dx * toler, endPt[1] + dy * toler)]
502
        pt = self.intersection(extendedLine)
503

    
504
        return (pt is not None) and (type(pt) == shapely.geometry.point.Point)
505

    
506
    '''
507
        @author     humkyung
508
        @date       2018.06.26
509
        @history    humkyung 2018.07.03 allow item to be line or symbol
510
    '''
511

    
512
    def is_connected(self, item, at=QEngineeringAbstractItem.CONNECTED_AT_PT):
513
        """ check if given item is connected to self """
514

    
515
        _connectors = [connector for connector in self.connectors if
516
                       (connector.connectedItem == item and connector._connected_at == at)]
517
        return len(_connectors) > 0
518

    
519
    '''
520
        @brief      join line to symbol
521
        @author     kyouho
522
        @date       2018.07.25
523
    '''
524

    
525
    def joinTo(self, item=None):
526
        import math
527
        from SymbolSvgItem import SymbolSvgItem
528

    
529
        # line의 Point 정의
530
        startPoint = self.startPoint()
531
        endPoint = self.endPoint()
532

    
533
        if item is not None and type(item) is QEngineeringLineItem:
534
            pts = [item.startPoint(), item.endPoint()]
535
            selected = startPoint if self._selectedIndex == 0 else endPoint if self._selectedIndex else []
536

    
537
            if selected:
538
                for i in range(len(pts)):
539
                    dx = pts[i][0] - selected[0]
540
                    dy = pts[i][1] - selected[1]
541
                    if math.sqrt(dx * dx + dy * dy) < 10:
542
                        line = QLineF(QPointF(pts[i][0], pts[i][1]),
543
                                      QPointF(endPoint[0], endPoint[1])) if self._selectedIndex == 0 else QLineF(
544
                            QPointF(startPoint[0], startPoint[1]), QPointF(pts[i][0], pts[i][1]))
545
                        self.setLine(line)
546
                        self.update()
547
                        break
548
        else:
549
            if len(item.connectors) == 2:
550
                connector1Point = item.connectors[0].sceneConnectPoint
551
                connector2Point = item.connectors[1].sceneConnectPoint
552

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

    
559
                # endPoint와 같은 connPts 찾음
560
                if endPoint[0] == connector1Point[0] and endPoint[1] == connector1Point[1]:
561
                    self.connectors[1].connectedItem = item
562
                elif endPoint[0] == connector2Point[0] and endPoint[1] == connector2Point[1]:
563
                    self.connectors[1].connectedItem = item
564

    
565
    '''
566
        @brief      arrange vertex order
567
        @author     humkyung
568
        @date       2018.07.04
569
    '''
570

    
571
    def arrangeVertexOrder(self, arranged):
572
        import math
573

    
574
        lhs = [arranged.startPoint(), arranged.endPoint()]
575
        rhs = [self.startPoint(), self.endPoint()]
576

    
577
        index = 0
578
        indexed = 0
579
        minDist = None
580
        for pt in lhs:
581
            for _pt in rhs:
582
                index += 1
583
                dx = _pt[0] - pt[0]
584
                dy = _pt[1] - pt[1]
585
                dist = math.sqrt(dx * dx + dy * dy)
586
                if minDist is None or dist < minDist:
587
                    minDist = dist
588
                    indexed = index
589

    
590
        if indexed == 1 or indexed == 4:
591
            self.reverse()
592
    
593
    def is_piping(self, strong=False):
594
        """ return true if piping line """
595
        if strong:
596
            return (self._lineType == 'Primary' or self._lineType == 'Secondary')    
597
        else:
598
            return (self._lineType == 'Primary' or self._lineType == 'Secondary' or self._lineType == 'Connect To Process')
599

    
600
    '''
601
        @brief      check if two lines are extendable
602
        @author     humkyung
603
        @date       2018.06.25
604
        @history    humkyung 2018.06.27 check line type
605
    '''
606
    def isExtendable(self, line, toler=5):
607
        import math
608
        from SymbolSvgItem import SymbolSvgItem
609

    
610
        if self.lineType == line.lineType:
611
            if self.isHorizontal() and line.isHorizontal():
612
                flag = (line.connectors[0].connectedItem is not None and issubclass(
613
                    type(line.connectors[0].connectedItem), SymbolSvgItem)) or (
614
                                   line.connectors[1].connectedItem is not None and issubclass(
615
                               type(line.connectors[1].connectedItem), SymbolSvgItem))
616
                return (flag and (math.fabs(self.startPoint()[1] - line.startPoint()[1]) < toler))
617
            elif self.isVertical() and line.isVertical():
618
                flag = (line.connectors[0].connectedItem is not None and issubclass(
619
                    type(line.connectors[0].connectedItem), SymbolSvgItem)) or (
620
                                   line.connectors[1].connectedItem is not None and issubclass(
621
                               type(line.connectors[1].connectedItem), SymbolSvgItem))
622
                return (flag and (math.fabs(self.startPoint()[0] - line.startPoint()[0]) < toler))
623

    
624
        return False
625

    
626
    def is_external_point(self, pt):
627
        """ check given pt is located outside of line """
628
        import math
629

    
630
        try:
631
            dx = self.endPoint()[0] - self.startPoint()[0]
632
            dy = self.endPoint()[1] - self.startPoint()[1]
633
            lineLength = math.sqrt(dx * dx + dy * dy)
634

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

    
641
            dx = pt.x - self.endPoint()[0]
642
            dy = pt.y - self.endPoint()[1]
643
            length = math.sqrt(dx * dx + dy * dy)
644
            if length > lineLength:
645
                return True
646

    
647
            return False
648
        except Exception as ex:
649
            from App import App
650
            from AppDocData import MessageType
651

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

    
656
    '''
657
        @author     humkyung
658
        @date       2018.04.16
659
        @history    humkyung 2018.05.08 check if line is possible to be connected
660
                    Jeongwoo 2018.05.15 Split if-statement and Connect each symbol and line
661
    '''
662

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

    
667
        from shapely.geometry import Point
668
        from SymbolSvgItem import SymbolSvgItem
669
        from EngineeringConnectorItem import QEngineeringConnectorItem
670

    
671
        res = []
672

    
673
        startPt = self.startPoint()
674
        endPt = self.endPoint()
675

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

    
712
                elif obj.connectors[1].connectedItem is None and self.distanceTo(_endPt) < toler:
713
                    if self.connectors[0].connectedItem is None and \
714
                            ((Point(startPt[0], startPt[1]).distance(Point(_endPt[0], _endPt[1])) < toler)):
715
                        self.connectors[0].connect(obj)
716
                        obj.connectors[1].connect(self)
717
                        res.append(obj)
718
                    elif self.connectors[1].connectedItem is None and \
719
                            ((Point(endPt[0], endPt[1]).distance(Point(_endPt[0], _endPt[1])) < toler)):
720
                        self.connectors[1].connect(obj)
721
                        obj.connectors[1].connect(self)
722
                        res.append(obj)
723
                    elif obj.connectors[1].connectedItem is None:
724
                        obj.connectors[1].connect(self, at=QEngineeringAbstractItem.CONNECTED_AT_BODY)
725
                        res.append(obj)
726
        except Exception as ex:
727
            from App import App
728
            from AppDocData import MessageType
729

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

    
734
        return res
735

    
736
    '''
737
        @brief      disconnect connector item
738
        @author     kyouho
739
        @date       2018.08.30
740
    '''
741

    
742
    def disconnectedItemAtConnector(self, connector):
743
        for conn in self.connectors:
744
            if conn.isOverlapConnector(connector):
745
                conn.connectedItem = None
746

    
747
    def arrange_flow_direction(self, _from):
748
        """ reverse if from is connected to second connector """
749
        if not _from: raise ValueError
750

    
751
        if self.connectors[1].connectedItem == _from: self.reverse()
752

    
753
    '''
754
        @brief      reverse line
755
        @author     humkyung
756
        @date       2018.07.03
757
    '''
758

    
759
    def reverse(self):
760
        line = self.line()
761
        self.setLine(QLineF(line.p2(), line.p1()))
762
        self.connectors[0], self.connectors[1] = self.connectors[1], self.connectors[0]
763
        self.update_arrow()
764
        self.update()
765

    
766
    '''
767
        @brief      add flow arrow
768
        @author     humkyung
769
        @date       2018.05.08
770
        @history    2018.05.24  Jeongwoo    Modifying Draw Flow Arrow
771
    '''
772

    
773
    def addFlowArrow(self):
774
        import numpy as np
775
        import cv2
776
        import math
777
        import sys
778
        global src
779
        from shapely.geometry import Point
780
        from QEngineeringFlowArrowItem import QEngineeringFlowArrowItem
781
        from AppDocData import AppDocData
782

    
783
        try:
784
            docData = AppDocData.instance()
785
            area = docData.getArea('Drawing')
786

    
787
            startPt = self.startPoint()
788
            endPt = self.endPoint()
789
            length = self.length()
790
            direction = [(endPt[0] - startPt[0]) / length, (endPt[1] - startPt[1]) / length]
791

    
792
            left = min(startPt[0], endPt[0])
793
            top = min(startPt[1], endPt[1])
794
            right = max(startPt[0], endPt[0])
795
            bottom = max(startPt[1], endPt[1])
796

    
797
            rect = None
798
            if self.isVertical():
799
                rect = QRectF(left - 10, top, (right - left) + 20, (bottom - top))
800
            else:
801
                rect = QRectF(left, top - 10, (right - left), (bottom - top) + 20)
802

    
803
            docData = AppDocData.instance()
804
            area = docData.getArea('Drawing')
805
            img = np.array(AppDocData.instance().getCurrentPidSource().getPyImageOnRect(rect))
806

    
807
            imgLine = cv2.threshold(cv2.cvtColor(img, cv2.COLOR_BGR2GRAY), 127, 255, cv2.THRESH_BINARY)[1]
808
            # remove noise
809
            imgLine = cv2.bitwise_not(imgLine)
810
            imgLine = cv2.erode(imgLine, np.ones((10, 10), np.uint8))
811

    
812
            contours, hierarchy = cv2.findContours(imgLine, cv2.RETR_TREE, cv2.CHAIN_APPROX_SIMPLE)
813
            if contours:
814
                contours = sorted(contours, key=cv2.contourArea, reverse=True)
815
                [x, y, w, h] = cv2.boundingRect(contours[0])
816
                if w > 10 and w < 100 and h > 10 and h < 100:  # check arrow mark size
817
                    imgArrowMark = imgLine[y:y + h, x:x + w]
818

    
819
                    # DEBUG - display flow arrow area
820
                    '''
821
                    item = QGraphicsBoundingBoxItem(rect.left() + x - 10, rect.top() + y - 10, w + 20, h + 20)
822
                    item.isSymbol = True
823
                    item.angle = 0
824
                    item.setPen(QPen(Qt.red, 1, Qt.SolidLine))
825
                    item.setBrush(QBrush(QColor(255,255,0,100)))
826
                    self.scene().addItem(item)
827
                    '''
828
                    # up to here
829

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

    
833
                    ####### HoughLinesP
834
                    if lines is not None:
835
                        maxLength = None
836
                        selected = None
837
                        for line in lines:
838
                            for x1, y1, x2, y2 in line:
839
                                dx = x2 - x1
840
                                dy = y2 - y1
841
                                selected = line
842
                                length = math.sqrt(dx * dx + dy * dy)
843
                                if maxLength is None or length > maxLength:
844
                                    maxLength = length
845
                                    selected = line
846

    
847
                        for x1, y1, x2, y2 in selected:
848
                            dx = math.fabs(x2 - x1)
849
                            dy = math.fabs(y2 - y1)
850
                            length = math.sqrt(dx * dx + dy * dy)
851
                            dx /= length
852
                            dy /= length
853
                            if (self.isVertical() and (dx < 0.001 or math.fabs(dx - 1) < 0.001)) or (
854
                                    self.isHorizontal() and (dx < 0.001 or math.fabs(dx - 1) < 0.001)): continue
855
                            dist1 = self.distanceTo((rect.left() + x + x1, rect.top() + y + y1))
856
                            dist2 = self.distanceTo((rect.left() + x + x2, rect.top() + y + y2))
857
                            if dist1 > dist2:  # point which's distance is longer would be start point
858
                                _start = (rect.left() + x + x1, rect.top() + y + y1)
859
                                _end = (rect.left() + x + x2, rect.top() + y + y2)
860
                            else:
861
                                _start = (rect.left() + x + x2, rect.top() + y + y2)
862
                                _end = (rect.left() + x + x1, rect.top() + y + y1)
863

    
864
                            # DEBUG display detected line
865
                            '''
866
                            poly = QEngineeringPolylineItem()
867
                            poly._pol.append(QPointF(_start[0], _start[1]))
868
                            poly._pol.append(QPointF(_end[0], _end[1]))
869
                            poly.setPen(QPen(Qt.red, 2, Qt.SolidLine))
870
                            poly.buildItem()
871
                            self.scene().addItem(poly)
872
                            '''
873
                            # up to here
874

    
875
                            dist1 = Point(startPt[0], startPt[1]).distance(Point(_start[0], _start[1]))
876
                            dist2 = Point(startPt[0], startPt[1]).distance(Point(_end[0], _end[1]))
877
                            if dist1 > dist2:
878
                                startPt, endPt = endPt, startPt
879
                                direction[0], direction[1] = -direction[0], -direction[1]
880
                                self.reverse()
881

    
882
                        '''
883
                        center = [(startPt[0]+endPt[0])*0.5, (startPt[1]+endPt[1])*0.5]
884
                        arrow = QEngineeringFlowArrowItem(center, direction)
885
                        arrow.buildItem()
886
                        self.scene().addItem(arrow)
887
                        '''
888

    
889
                        x = round(rect.left() + x - 5)
890
                        y = round(rect.top() + y - 5)
891
                        self.flowMark = ([x, y, w + 10, h + 10], None)
892
                else:
893
                    pass
894
        except Exception as ex:
895
            from App import App
896
            from AppDocData import MessageType
897

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

    
902
    '''
903
        @breif  insert symbol
904
        @author humkyung
905
        @date   2018.04.22
906
        @history    kyouho  2018.07.24  add symbol angle, transform rotateRadians(-angle -> angle)
907
    '''
908

    
909
    def insertSymbol(self, symbol, pos):
910
        import math
911
        from shapely.geometry import Point
912
        from shapely import affinity
913

    
914
        vec = self.perpendicular()
915
        line = [(pos.x() - vec[0] * 20, pos.y() - vec[1] * 20), (pos.x() + vec[0] * 20, pos.y() + vec[1] * 20)]
916
        origin = self.intersection(line)
917
        transform = QTransform()
918
        transform.translate(origin.x, origin.y)
919
        angle = self.angle()
920
        transform.rotateRadians(-angle)
921
        transform.translate(-symbol.symbolOrigin[0], -symbol.symbolOrigin[1])
922
        symbol.setTransform(transform)
923
        # save angle
924
        symbol.angle = round(angle, 2)
925
        if 2 == len(symbol.connectors):  # 2 way component
926
            for i in range(len(symbol.connectors)):
927
                rotatedPt = affinity.rotate(Point(symbol.connectors[i].connectPoint[0] - symbol.symbolOrigin[0],
928
                                                  symbol.connectors[i].connectPoint[1] - symbol.symbolOrigin[1]),
929
                                            -angle, Point(0, 0), use_radians=True)
930
                symbol.connectors[i].sceneConnectPoint = (origin.x + rotatedPt.x, origin.y + rotatedPt.y)
931

    
932
            dx1 = symbol.connectors[0].sceneConnectPoint[0] - self.startPoint()[0]
933
            dy1 = symbol.connectors[0].sceneConnectPoint[1] - self.startPoint()[1]
934
            length1 = math.sqrt(dx1 * dx1 + dy1 * dy1)
935
            dx2 = symbol.connectors[1].sceneConnectPoint[0] - self.startPoint()[0]
936
            dy2 = symbol.connectors[1].sceneConnectPoint[1] - self.startPoint()[1]
937
            length2 = math.sqrt(dx2 * dx2 + dy2 * dy2)
938

    
939
            if length1 < length2:
940
                processLine = QEngineeringLineItem([symbol.connectors[1].sceneConnectPoint, self.endPoint()])
941
                processLine.connectors[0].connectedItem = symbol
942
                processLine.connectors[1].connectedItem = self.connectors[1].connectedItem
943
                self.scene().addItem(processLine)
944

    
945
                line = QLineF(self.line().p1(), QPointF(symbol.connectors[0].sceneConnectPoint[0],
946
                                                        symbol.connectors[0].sceneConnectPoint[1]))
947
                self.setLine(line)
948
                self.connectors[1].connectedItem = symbol
949

    
950
                symbol.connectors[0].connectedItem = self
951
                symbol.connectors[1].connectedItem = processLine
952
            else:
953
                processLine = QEngineeringLineItem([symbol.connectors[0].sceneConnectPoint, self.endPoint()])
954
                processLine.connectors[0].connectedItem = symbol
955
                processLine.connectors[1].connectedItem = self.connectors[1].connectedItem
956
                self.scene().addItem(processLine)
957

    
958
                line = QLineF(self.line().p1(), QPointF(symbol.connectors[1].sceneConnectPoint[0],
959
                                                        symbol.connectors[1].sceneConnectPoint[1]))
960
                self.setLine(line)
961
                self.connectors[1].connectedItem = symbol
962

    
963
                symbol.connectors[0].connectedItem = processLine
964
                symbol.connectors[1].connectedItem = self
965

    
966
            self.joinTo(symbol)
967
            processLine.joinTo(symbol)
968
            self.update()
969

    
970
        symbol.loc = [origin.x - symbol.symbolOrigin[0], origin.y - symbol.symbolOrigin[1]]
971
        symbol.size = [symbol.boundingRect().width(), symbol.boundingRect().height()]
972
        self.scene().addItem(symbol)
973

    
974
    '''
975
        @brief  redraw symbol
976
        @author kyouho
977
        @date   2018.07.25
978
    '''
979

    
980
    def reDrawLine(self, symbol, point):
981
        for index in range(len(self.connectors)):
982
            if self.connectors[index].connectedItem == symbol:
983
                # startPoint
984
                if index == 0:
985
                    line = QLineF(QPointF(point[0], point[1]), self.line().p2())
986
                    self.setLine(line)
987
                    self.connectors[0].setPos([point[0], point[1]])
988
                # endpoint
989
                else:
990
                    line = QLineF(self.line().p1(), QPointF(point[0], point[1]))
991
                    self.setLine(line)
992
                    self.connectors[1].setPos([point[0], point[1]])
993

    
994
        ## startPoint에 symbol
995
        # if self.connectors[0].connectedItem == symbol:
996
        #    if self.startPoint()[0] == symbol.connectors[0].sceneConnectPoint[0] and self.startPoint()[1] == symbol.connectors[0].sceneConnectPoint[1]:
997
        #        line = QLineF(QPointF(changedConnPoint1[0], changedConnPoint1[1]), self.line().p2())
998
        #        self.setLine(line)
999
        #    else:
1000
        #        line = QLineF(QPointF(changedConnPoint2[0], changedConnPoint2[1]), self.line().p2())
1001
        #        self.setLine(line)
1002
        ## endPoint에 symbol
1003
        # elif self.connectors[1].connectedItem == symbol:
1004
        #    if self.endPoint()[0] == symbol.connectors[0].sceneConnectPoint[0] and self.endPoint()[1] == symbol.connectors[0].sceneConnectPoint[1]:
1005
        #        line = QLineF(self.line().p1(), QPointF(changedConnPoint1[0], changedConnPoint1[1]))
1006
        #        self.setLine(line)
1007
        #    else:
1008
        #        line = QLineF(self.line().p1(), QPointF(changedConnPoint2[0], changedConnPoint2[1]))
1009
        #        self.setLine(line)
1010

    
1011
        self.update()
1012

    
1013
    '''
1014
        @brief  remove symbol
1015
        @author humkyung
1016
        @date   2018.04.23
1017
    '''
1018

    
1019
    def removeSymbol(self, symbol):
1020
        import math
1021

    
1022
        if 2 == len(symbol.connectors):  # 2-way component
1023
            connected = symbol.connectors[0].connectedItem if symbol.connectors[0].connectedItem is not self else \
1024
            symbol.connectors[1].connectedItem
1025

    
1026
            pts = []
1027
            pts.append(self.startPoint())
1028
            pts.append(self.endPoint())
1029
            pts.append(connected.startPoint())
1030
            pts.append(connected.endPoint())
1031

    
1032
            self.scene().removeItem(connected)
1033

    
1034
            start = None
1035
            end = None
1036
            maxDist = None
1037
            for i in range(len(pts)):
1038
                for j in range(i + 1, len(pts)):
1039
                    dx = pts[i][0] - pts[j][0]
1040
                    dy = pts[i][1] - pts[j][1]
1041
                    dist = math.sqrt(dx * dx + dy * dy)
1042
                    if maxDist is None:
1043
                        maxDist = dist
1044
                        start = pts[i]
1045
                        end = pts[j]
1046
                    elif dist > maxDist:
1047
                        maxDist = dist
1048
                        start = pts[i]
1049
                        end = pts[j]
1050

    
1051
            if (pts[0] == end) or (pts[1] == start): start, end = end, start
1052

    
1053
            line = QLineF(QPointF(start[0], start[1]), QPointF(end[0], end[1]))
1054
            self.setLine(line)
1055
            # self.buildItem()
1056
            self.update()
1057

    
1058
    def validate(self):
1059
        '''
1060
            @brief  validation check : connection
1061
            @author euisung
1062
            @date   2019.04.01
1063
        '''
1064
        from EngineeringAbstractItem import QEngineeringAbstractItem
1065
        from SymbolSvgItem import SymbolSvgItem
1066
        from EngineeringEquipmentItem import QEngineeringEquipmentItem
1067
        from AppDocData import AppDocData
1068
        errors = []
1069

    
1070
        try:
1071
            _translate = QCoreApplication.translate
1072

    
1073
            docdata = AppDocData.instance()
1074
            dataPath = docdata.getErrorItemSvgPath()
1075

    
1076
            connectedUid = []
1077

    
1078
            for connector in self.connectors:
1079
                # for duplicattion check
1080
                if connector.connectedItem and issubclass(type(connector.connectedItem), QEngineeringAbstractItem):
1081
                    connectedUid.append(str(connector.connectedItem.uid))
1082

    
1083
                # check if there is not connected connector
1084
                if connector.connectedItem is None:
1085
                    error = SymbolSvgItem.createItem('Error', None, dataPath)
1086
                    error.setPosition(connector.center())
1087
                    error.parent = self
1088
                    error.msg = _translate('disconnected', 'disconnected')
1089
                    error.setToolTip(error.msg)
1090
                    error.area = 'Drawing'
1091
                    error.name = 'Error'
1092
                    errors.append(error)
1093

    
1094
                # check line to symbol
1095
                elif issubclass(type(connector.connectedItem), SymbolSvgItem) and type(
1096
                        connector.connectedItem) is not QEngineeringEquipmentItem:
1097
                    matches = [conn for conn in connector.connectedItem.connectors if conn.connectedItem is self]
1098
                    # check if two items are connected each other
1099
                    if not matches:
1100
                        error = SymbolSvgItem.createItem('Error', None, dataPath)
1101
                        error.setPosition(connector.center())
1102
                        error.parent = self
1103
                        error.msg = _translate('disconnected from opposite side', 'disconnected from opposite side')
1104
                        error.setToolTip(error.msg)
1105
                        error.area = 'Drawing'
1106
                        error.name = 'Error'
1107
                        errors.append(error)
1108
                    # check connection position
1109
                    elif not self.isOverlap(connector.sceneBoundingRect(), matches[0].sceneBoundingRect()):
1110
                        error = SymbolSvgItem.createItem('Error', None, dataPath)
1111
                        error.setPosition(connector.center())
1112
                        error.parent = self
1113
                        error.msg = _translate('mismatched position', 'mismatched position')
1114
                        error.setToolTip(error.msg)
1115
                        error.area = 'Drawing'
1116
                        error.name = 'Error'
1117
                        errors.append(error)
1118

    
1119
                elif issubclass(type(connector.connectedItem), QEngineeringLineItem):
1120
                    # check if connected two lines has same direction
1121
                    if connector._connected_at == QEngineeringAbstractItem.CONNECTED_AT_PT:
1122
                        center = connector.center()
1123

    
1124
                        indices = [0, 0]
1125
                        indices[0] = 1 if QPointF(center[0], center[1]) == self.line().p1() else 2
1126
                        matches = [conn for conn in connector.connectedItem.connectors if
1127
                                   conn.connectedItem == self and conn._connected_at == QEngineeringAbstractItem.CONNECTED_AT_PT]
1128
                        if matches:
1129
                            indices[1] = 1 if QPointF(matches[0].center()[0], matches[0].center()[
1130
                                1]) == connector.connectedItem.line().p1() else 2
1131
                        else:
1132
                            error = SymbolSvgItem.createItem('Error', None, dataPath)
1133
                            error.setPosition(connector.center())
1134
                            error.parent = self
1135
                            error.msg = _translate('disconnected from opposite side', 'disconnected from opposite side')
1136
                            error.setToolTip(error.msg)
1137
                            error.area = 'Drawing'
1138
                            error.name = 'Error'
1139
                            errors.append(error)
1140

    
1141
                        if indices[0] == indices[1]:
1142
                            error = SymbolSvgItem.createItem('Error', None, dataPath)
1143
                            error.setPosition(connector.center())
1144
                            error.parent = self
1145
                            error.msg = _translate('flow direction error', 'flow direction error')
1146
                            error.setToolTip(error.msg)
1147
                            error.area = 'Drawing'
1148
                            error.name = 'Error'
1149
                            errors.append(error)
1150

    
1151
                        if self.lineType != connector.connectedItem.lineType:
1152
                            error = SymbolSvgItem.createItem('Error', None, dataPath)
1153
                            error.setPosition(connector.center())
1154
                            error.parent = self
1155
                            error.msg = _translate('line type error', 'line type error')
1156
                            error.setToolTip(error.msg)
1157
                            error.area = 'Drawing'
1158
                            error.name = 'Error'
1159
                            errors.append(error)
1160

    
1161
                errors.extend(connector.validate())
1162

    
1163
            # check duplicated connection
1164
            if len(connectedUid) is not len(set(connectedUid)):
1165
                error = SymbolSvgItem.createItem('Error', None, dataPath)
1166
                error.setPosition([self.sceneBoundingRect().center().x(), self.sceneBoundingRect().center().y()])
1167
                error.parent = self
1168
                error.msg = _translate('duplicated connection', 'duplicated connection')
1169
                error.setToolTip(error.msg)
1170
                error.area = 'Drawing'
1171
                error.name = 'Error'
1172
                errors.append(error)
1173

    
1174
        except Exception as ex:
1175
            from App import App
1176
            from AppDocData import MessageType
1177

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

    
1182
        return errors
1183

    
1184
    '''
1185
        @brief  update line type
1186
        @author humkyung
1187
        @date   2018.07.05
1188
    '''
1189

    
1190
    def update_line_type(self):
1191
        import uuid
1192
        from LineTypeConditions import LineTypeConditions
1193

    
1194
        try:
1195
            pool, visited, items = [self], [], []
1196
            while pool:
1197
                obj = pool.pop()
1198
                visited.append(obj)
1199

    
1200
                """ connected items """
1201
                connected = [connector.connectedItem for connector in obj.connectors if
1202
                             connector.connectedItem and not type(connector.connectedItem) is uuid.UUID]
1203
                """ connected lines at point """
1204
                lines = [connector.connectedItem for connector in obj.connectors if connector.connectedItem and type(
1205
                    connector.connectedItem) is QEngineeringLineItem and connector._connected_at == QEngineeringAbstractItem.CONNECTED_AT_PT]
1206
                """ add items not in 'connected' to items list """
1207
                items.extend([item for item in connected if item not in lines])
1208
                """ add items not visited to pool """
1209
                pool.extend([item for item in lines if item not in visited])
1210

    
1211
            for condition in LineTypeConditions.items():
1212
                if condition.eval(items):
1213
                    self.lineType = condition.name
1214
                    break
1215
                if condition.eval(items, reverse=True):
1216
                    self.lineType = condition.name
1217
                    break
1218
        except Exception as ex:
1219
            from App import App
1220
            from AppDocData import MessageType
1221

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

    
1226
    def hoverEnterEvent(self, event):
1227
        """ hilight item and it's children """
1228
        self.highlight(True)
1229

    
1230
    def hoverLeaveEvent(self, event):
1231
        """ restore original color """
1232
        self.highlight(False)
1233

    
1234
    def highlight(self, flag):
1235
        self.hover = flag
1236
        self.setZValue(QEngineeringAbstractItem.HOVER_ZVALUE) if flag else self.setZValue(QEngineeringLineItem.ZVALUE)
1237
        self.update()
1238

    
1239
        for connector in self.connectors:
1240
            connector.highlight(flag)
1241

    
1242
    def hoverMoveEvent(self, event):
1243
        pass
1244

    
1245
    '''
1246
        @brief      remove item when user press delete key
1247
        @author     humkyung
1248
        @date       2018.04.23
1249
        @history    swap start, end point when user press 'c' key
1250
    '''
1251

    
1252
    def keyPressEvent(self, event):
1253
        if self.isSelected() and event.key() == Qt.Key_Delete:
1254
            self.scene().removeItem(self)
1255
        elif event.key() == Qt.Key_C:
1256
            self.reverse()
1257
        elif event.key() == Qt.Key_A:
1258
            self.toggleFlowMark()
1259

    
1260
    def toggleFlowMark(self):
1261
        from AppDocData import AppDocData
1262

    
1263
        if self.flowMark:
1264
            self.flowMark = None
1265
        else:
1266
            configs = AppDocData.instance().getConfigs('Flow Mark', 'Position')
1267
            self.flowMark = int(configs[0].value) if 1 == len(configs) else 100
1268
        self.update_arrow()
1269

    
1270
    '''
1271
        @brief  draw rect when item is selected
1272
        @author humkyung
1273
        @date   2018.07.07
1274
    '''
1275

    
1276
    def drawFocusRect(self, painter):
1277
        self.focuspen = QPen(Qt.DotLine)
1278
        self.focuspen.setColor(Qt.black)
1279
        self.focuspen.setWidthF(1.5)
1280
        hilightColor = QColor(255, 0, 0, 127)
1281
        painter.setBrush(QBrush(hilightColor))
1282
        painter.setPen(self.focuspen)
1283
        painter.drawRect(self.boundingRect())
1284

    
1285
    '''
1286
        @brief      override paint method
1287
        @history    humkyung 2018.08.30 draw flowmark only for Primary or Secondary
1288
    '''
1289

    
1290
    def paint(self, painter, option, widget):
1291
        color = self.getColor()
1292
        self.setColor(color)
1293

    
1294
        QGraphicsLineItem.paint(self, painter, option, widget)
1295

    
1296
        if self.isSelected():
1297
            self.drawFocusRect(painter)
1298

    
1299
    def drawToImage(self, img, color, thickness):
1300
        """write recognized lines to image"""
1301
        try:
1302
            ptStart = self.startPoint()
1303
            ptEnd = self.endPoint()
1304
            cv2.line(img, (round(ptStart[0]), round(ptStart[1])), (round(ptEnd[0]), round(ptEnd[1])), color, thickness)
1305
            # up to here
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.sceneConnectPoint[0], connector.sceneConnectPoint[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 and not key.Freeze:
1654
                key.AssocItem.owner = None
1655
                self.remove_assoc_item(key.AssocItem)
1656
                key.AssocItem = None
1657
            if not key.Freeze:
1658
                attrs[key] = ''
1659
            index += 1
1660
    
1661
    def update_flow_mark(self, position, length):
1662
        """ update flow mark for flow arrow """
1663
        import math
1664

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

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

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

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

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

    
1684
        arrow_size = QEngineeringLineItem.ARROW_SIZE * 0.25
1685

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

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

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

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

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

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

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

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

    
1732
        self.setToolTip(self.tooltip)
1733
        self.update_arrow()
1734

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

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

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

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

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

    
1762
            self._selectedIndex = -1
1763

    
1764
        QGraphicsLineItem.mousePressEvent(self, event)
1765

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

    
1769
        QGraphicsLineItem.mouseReleaseEvent(self, event)
1770

    
1771

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

    
1778

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

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