프로젝트

일반

사용자정보

통계
| 개정판:

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

이력 | 보기 | 이력해설 | 다운로드 (102 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
    HIGHLIGHT_BRUSH = QBrush(QColor(QEngineeringAbstractItem.SELECTED_COLOR))
31
    LINE_TYPE_COLORS = {}
32

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

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

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

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

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

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

    
62
            self._properties = { SymbolProp(None, 'Freeze', 'Boolean'):False, SymbolProp(None, 'Bidirectional', 'Boolean'):False }
63

    
64
            self.setFlags(QGraphicsItem.ItemIsSelectable | QGraphicsItem.ItemIsFocusable)# | QGraphicsItem.ItemIsMovable)
65

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

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

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

    
75
                index = 0
76
                for vertex in vertices:
77
                    connector = QEngineeringConnectorItem(parent=self, index=index + 1)
78
                    connector.setPos(vertex)
79
                    connector.setParentItem(self)
80
                    connector.connectPoint = vertex
81
                    connector.recognized_pt = vertex    # 좌표 위치 저장
82

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

    
88
                    connector.setZValue(self.zValue() + 1)
89
                    self.connectors.append(connector)
90
                    index = index + 1
91

    
92
                self.update_arrow()
93
                self.setToolTip(self.tooltip)
94
        except Exception as ex:
95
            from App import App
96
            from AppDocData import MessageType
97

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

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

    
106
    @property
107
    def tooltip(self):
108
        """return tooltip string"""
109

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

    
114
        return tooltip
115

    
116
    @property
117
    def symbolOrigin(self):
118
        # return center point like symbol
119
        return [self.sceneBoundingRect().center().x(), self.sceneBoundingRect().center().y()]
120

    
121
    @property
122
    def origin(self):
123
        # return center point like symbol
124
        return [self.sceneBoundingRect().center().x(), self.sceneBoundingRect().center().y()]
125
    
126
    @property
127
    def flip(self):
128
        return 0
129

    
130
    '''
131
        @breif  getter owner
132
        @author humkyung
133
        @date   2018.05.10
134
    '''
135

    
136
    @property
137
    def owner(self):
138
        import uuid
139

    
140
        try:
141
            if self.scene() and self._owner and type(self._owner) is uuid.UUID:
142
                #matches = [x for x in self.scene().items() if hasattr(x, 'uid') and str(x.uid) == str(self._owner)]
143
                matches = []
144
                for x in self.scene().items():
145
                    if hasattr(x, 'uid') and str(x.uid) == str(self._owner):
146
                        matches = [x]
147
                        break
148
                if matches:
149
                    self._owner = matches[0]
150

    
151
                return matches[0] if matches else None
152

    
153
            if type(self._owner) is not uuid.UUID and type(self._owner) is not str:
154
                return self._owner
155
            else:
156
                self._owner = None
157
                return None
158
        except Exception as ex:
159
            from App import App
160
            from AppDocData import MessageType
161

    
162
            message = 'error occurred({}) in {}:{}'.format(ex, sys.exc_info()[-1].tb_frame.f_code.co_filename,
163
                                                           sys.exc_info()[-1].tb_lineno) + ' Item UID : ' + str(self.uid)
164
            App.mainWnd().addMessage.emit(MessageType.Error, message)
165

    
166
            return None
167

    
168
    '''
169
        @brief  setter owner
170
        @author humkyung
171
        @date   2018.05.10
172
        @history    2018.05.17  Jeongwoo    Add Calling setColor
173
    '''
174

    
175
    @owner.setter
176
    def owner(self, value):
177
        self._owner = value
178

    
179
        if self._owner is None:
180
            self._color = self.DEFAULT_COLOR
181
        self.setColor(self._color)
182

    
183
    '''
184
        @brief  getter flow mark
185
        @author humkyung
186
        @date   2018.06.21
187
    '''
188

    
189
    @property
190
    def flowMark(self):
191
        return self._flowMark
192

    
193
    '''
194
        @brief  setter flow mark
195
        @author humkyung
196
        @date   2018.06.21
197
    '''
198

    
199
    @flowMark.setter
200
    def flowMark(self, value):
201
        self._flowMark = value
202

    
203
    '''
204
        @brief  getter of lineType
205
        @author humkyung
206
        @date   2018.06.27
207
    '''
208

    
209
    @property
210
    def lineType(self):
211
        return self._lineType
212

    
213
    '''
214
        @brief  setter of lineType
215
        @author humkyung
216
        @date   2018.06.27
217
    '''
218

    
219
    @lineType.setter
220
    def lineType(self, value):
221
        from AppDocData import AppDocData
222

    
223
        self._lineType = value
224

    
225
        line_type_style = self.line_type_style
226
        if line_type_style:
227
            _pen = self.pen()
228
            c = QColor()
229
            c.setNamedColor(line_type_style[1])
230
            _pen.setColor(c)
231
            _pen.setWidth(line_type_style[2])
232
            _pen.setStyle(line_type_style[3])
233
            self.setPen(_pen)
234
            self.setOpacity(float(line_type_style[4]) / 100)
235
            self.update()
236

    
237
        if self.scene():
238
            self.update_arrow()
239

    
240
    @property
241
    def line_type_style(self):
242
        """return line type style"""
243
        from AppDocData import AppDocData
244

    
245
        app_doc_data = AppDocData.instance()
246
        configs = app_doc_data.getLineTypeConfig(self._lineType)
247
        return configs
248

    
249
    '''
250
        @brief  clone an object
251
    '''
252

    
253
    def clone(self):
254
        clone = QEngineeringLineItem()
255
        # clone._vertices = copy.deepcopy(self._vertices)
256
        # for vertex in clone._vertices:
257
        #    clone._pol.append(QPointF(vertex[0], vertex[1]))
258
        clone.buildItem()
259
        clone.isCreated = self.isCreated
260

    
261
        return clone
262

    
263
    def set_line(self, line):
264
        """ set line """
265
        self.setLine(line[0][0], line[0][1], line[1][0], line[1][1])
266
        self.connectors[0].setPos(line[0])
267
        self.connectors[1].setPos(line[1])
268
        self.update_arrow()
269

    
270
        self._minimum_bounding_box = None
271

    
272
    @property
273
    def minimum_bounding_box(self):
274
        """calculate minimum bounding box of line"""
275
        import math
276

    
277
        if not self._minimum_bounding_box:
278
            offset = self.line_type_style[2] - 5 if self.line_type_style[2] - 5 > 0 else 3
279

    
280
            angle = self.line().angle() * math.pi / 180
281
            dx = offset * math.sin(angle)
282
            dy = offset * math.cos(angle)
283
            offset1 = QPointF(dx, dy)
284
            offset2 = QPointF(-dx, -dy)
285

    
286
            polygon = QPolygonF()
287
            polygon << self.line().p1() + offset1 << self.line().p1() + offset2 << \
288
            self.line().p2() + offset2 << self.line().p2() + offset1
289

    
290
            self._minimum_bounding_box = polygon
291

    
292
        return self._minimum_bounding_box
293

    
294
    '''
295
        @brief  return start point
296
        @author humkyung
297
        @date   2018.04.16
298
    '''
299
    def start_point(self):
300
        from AppDocData import AppDocData
301

    
302
        configs = AppDocData.instance().getConfigs('Data', 'Grid')
303
        grid = int(configs[0].value) if 1 == len(configs) else -1
304

    
305
        at = self.line().p1()
306

    
307
        if grid == 1:
308
            return (round(at.x()), round(at.y()))
309
        else:
310
            return (at.x(), at.y())
311

    
312
    '''
313
        @brief  return last point
314
        @author humkyung
315
        @date   2018.04.16
316
    '''
317
    def end_point(self):
318
        from AppDocData import AppDocData
319
        
320
        configs = AppDocData.instance().getConfigs('Data', 'Grid')
321
        grid = int(configs[0].value) if 1 == len(configs) else -1
322

    
323
        at = self.line().p2()
324
        
325
        if grid == 1:
326
            return (round(at.x()), round(at.y()))
327
        else:
328
            return (at.x(), at.y())
329

    
330
    '''
331
        @brief  dot product of given two vectors
332
        @author humkyung
333
        @date   2018.04.14
334
    '''
335
    def dotProduct(self, lhs, rhs):
336
        return sum([lhs[i] * rhs[i] for i in range(len(lhs))])
337

    
338
    '''
339
        @brief  distance between line and point
340
        @author humkyung
341
        @date   2018.04.16
342
    '''
343
    def distanceTo(self, pt):
344
        from shapely.geometry import Point, LineString
345

    
346
        startPt = self.start_point()
347
        endPt = self.end_point()
348
        line = LineString([(startPt[0], startPt[1]), (endPt[0], endPt[1])])
349
        dist = line.distance(Point(pt[0], pt[1]))
350

    
351
        return dist
352

    
353
    '''
354
        @brief  return perpendicular vector
355
        @author humkyung
356
        @date   2018.04.21
357
    '''
358
    def perpendicular(self):
359
        import math
360

    
361
        dx = self.end_point()[0] - self.start_point()[0]
362
        dy = self.end_point()[1] - self.start_point()[1]
363
        dx, dy = -dy, dx
364
        length = math.sqrt(dx * dx + dy * dy)
365
        dx /= length
366
        dy /= length
367

    
368
        return (dx, dy)
369

    
370
    def angle(self):
371
        """
372
        @brief  return angle of line in radian
373
        @author humkyung
374
        """
375
        import math
376

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

    
385
    '''
386
        @brief  return length of line
387
        @author humkyung
388
        @date   2018.05.08
389
    '''
390
    def length(self):
391
        import math
392

    
393
        startPt = self.start_point()
394
        endPt = self.end_point()
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.start_point()
409
        endPt = self.end_point()
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.start_point()
425
        endPt = self.end_point()
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.start_point()
444
        endPt = self.end_point()
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.end_point()[0] - self.start_point()[0], self.end_point()[1] - self.start_point()[1]),
471
            #           (rhs.end_point()[0] - rhs.start_point()[0], rhs.end_point()[1] - rhs.start_point()[1])]
472
            allowed_error = 0.01
473
            angle = self.getAngle(rhs)
474
            if (abs(angle - 0) < allowed_error) or (abs(angle - math.pi) < allowed_error):
475
                return True
476
        except ZeroDivisionError:
477
            return True
478

    
479
        return False
480

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

    
489
    def is_connectable(self, item, toler=20):
490
        import math
491
        from EngineeringConnectorItem import QEngineeringConnectorItem
492

    
493
        try:
494
            if type(item) is QEngineeringLineItem:
495
                startPt = item.start_point()
496
                endPt = item.end_point()
497

    
498
                dx = endPt[0] - startPt[0]
499
                dy = endPt[1] - startPt[1]
500
                length = math.sqrt(dx * dx + dy * dy)
501
                if length == 0:
502
                    return False
503
                dx /= length
504
                dy /= length
505
                extendedLine = [(startPt[0] - dx * toler, startPt[1] - dy * toler),
506
                                (endPt[0] + dx * toler, endPt[1] + dy * toler)]
507
                pt = self.intersection(extendedLine)
508

    
509
                return (pt is not None) and (type(pt) == shapely.geometry.point.Point)
510
            elif type(item) is QEngineeringConnectorItem:
511
                start_pt = self.start_point()
512
                end_pt = self.end_point()
513

    
514
                lhs = [end_pt[0] - start_pt[0], end_pt[1] - start_pt[1]]
515
                length = math.sqrt(lhs[0] * lhs[0] + lhs[1] * lhs[1])
516
                if length == 0:
517
                    return False
518

    
519
                rhs = [item.dir().x(), item.dir().y()]
520
                dot = sum([lhs[i] * rhs[i] for i in range(len(lhs))])
521
                angle = math.degrees(math.acos(dot / length))
522
                if (abs(angle) < 10) or (abs(angle - 180) < 10) or len(item.parent.connectors) == 1:
523
                    _center = item.center()
524
                    dx = [start_pt[0] - _center[0], end_pt[0] - _center[0]]
525
                    dy = [start_pt[1] - _center[1], end_pt[1] - _center[1]]
526
                    length = [math.sqrt(dx[0] * dx[0] + dy[0] * dy[0]), math.sqrt(dx[1] * dx[1] + dy[1] * dy[1])]
527
                    return length[0] < toler or length[1] < toler
528

    
529
                return False
530

    
531
        except Exception as ex:
532
            from App import App
533
            from AppDocData import MessageType
534

    
535
            message = 'error occurred({}) in {}:{}'.format(ex, sys.exc_info()[-1].tb_frame.f_code.co_filename,
536
                                                           sys.exc_info()[-1].tb_lineno) + ' Item UID : ' + str(self.uid)
537
            App.mainWnd().addMessage.emit(MessageType.Error, message)
538

    
539
        return False
540

    
541
    '''
542
        @author     humkyung
543
        @date       2018.06.26
544
        @history    humkyung 2018.07.03 allow item to be line or symbol
545
    '''
546

    
547
    def is_connected(self, item, at=QEngineeringAbstractItem.CONNECTED_AT_PT):
548
        """ check if given item is connected to self """
549

    
550
        _connectors = [connector for connector in self.connectors if
551
                       (connector.connectedItem == item and connector._connected_at == at)]
552
        return len(_connectors) > 0
553

    
554
    '''
555
        @brief      join line to symbol
556
        @author     kyouho
557
        @date       2018.07.25
558
    '''
559

    
560
    def joinTo(self, item=None):
561
        import math
562
        from SymbolSvgItem import SymbolSvgItem
563

    
564
        # line의 Point 정의
565
        startPoint = self.start_point()
566
        endPoint = self.end_point()
567

    
568
        if item is not None and type(item) is QEngineeringLineItem:
569
            pts = [item.start_point(), item.end_point()]
570
            selected = startPoint if self._selectedIndex == 0 else endPoint if self._selectedIndex else []
571

    
572
            if selected:
573
                for i in range(len(pts)):
574
                    dx = pts[i][0] - selected[0]
575
                    dy = pts[i][1] - selected[1]
576
                    if math.sqrt(dx * dx + dy * dy) < 10:
577
                        line = QLineF(QPointF(pts[i][0], pts[i][1]),
578
                                      QPointF(endPoint[0], endPoint[1])) if self._selectedIndex == 0 else QLineF(
579
                            QPointF(startPoint[0], startPoint[1]), QPointF(pts[i][0], pts[i][1]))
580
                        self.setLine(line)
581
                        self.update()
582
                        break
583
        else:
584
            if len(item.connectors) == 2:
585
                connector1Point = item.connectors[0].center()
586
                connector2Point = item.connectors[1].center()
587

    
588
                # startPoint와 같은 connPts 찾음
589
                if startPoint[0] == connector1Point[0] and startPoint[1] == connector1Point[1]:
590
                    self.connectors[0].connectedItem = item
591
                elif startPoint[0] == connector2Point[0] and startPoint[1] == connector2Point[1]:
592
                    self.connectors[0].connectedItem = item
593

    
594
                # endPoint와 같은 connPts 찾음
595
                if endPoint[0] == connector1Point[0] and endPoint[1] == connector1Point[1]:
596
                    self.connectors[1].connectedItem = item
597
                elif endPoint[0] == connector2Point[0] and endPoint[1] == connector2Point[1]:
598
                    self.connectors[1].connectedItem = item
599

    
600
    '''
601
        @brief      arrange vertex order
602
        @author     humkyung
603
        @date       2018.07.04
604
    '''
605

    
606
    def arrangeVertexOrder(self, arranged):
607
        import math
608

    
609
        lhs = [arranged.start_point(), arranged.end_point()]
610
        rhs = [self.start_point(), self.end_point()]
611

    
612
        index = 0
613
        indexed = 0
614
        minDist = None
615
        for pt in lhs:
616
            for _pt in rhs:
617
                index += 1
618
                dx = _pt[0] - pt[0]
619
                dy = _pt[1] - pt[1]
620
                dist = math.sqrt(dx * dx + dy * dy)
621
                if minDist is None or dist < minDist:
622
                    minDist = dist
623
                    indexed = index
624

    
625
        if indexed == 1 or indexed == 4:
626
            self.reverse()
627
    
628
    def is_piping(self, strong=False):
629
        """ return true if piping line """
630
        from AppDocData import AppDocData
631

    
632
        configs = AppDocData.instance().getConfigs('Line', 'Piping')
633
        pipings = configs[0].value if 1 == len(configs) else 'Secondary,Primary'
634
        pipings = [piping.strip() for piping in pipings.split(',')]
635
    
636
        if strong:
637
            return self._lineType in pipings
638
        else:
639
            return self._lineType in pipings or self._lineType == 'Connect To Process'
640

    
641
    def next_connected(self, lhs, rhs):
642
        """ check given two item's are next connected(ex: 0-1) """
643

    
644
        lhs_matches = [at for at in range(len(self.connectors)) if self.connectors[at].connectedItem == lhs]
645
        rhs_matches = [at for at in range(len(self.connectors)) if self.connectors[at].connectedItem == rhs]
646
        if lhs_matches and rhs_matches and lhs is not rhs:
647
            return (lhs_matches[0] in [0, 1] and rhs_matches[0] in [0, 1])
648

    
649
        return False
650

    
651
    @staticmethod
652
    def check_piping(lineType, strong=False):
653
        """ return true if piping line """
654
        from AppDocData import AppDocData
655

    
656
        configs = AppDocData.instance().getConfigs('Line', 'Piping')
657
        pipings = configs[0].value if 1 == len(configs) else 'Secondary,Primary'
658
        pipings = [piping.strip() for piping in pipings.split(',')]
659

    
660
        if strong:
661
            return lineType in pipings
662
        else:
663
            return lineType in pipings or lineType == 'Connect To Process'
664

    
665
    '''
666
        @brief      check if two lines are extendable
667
        @author     humkyung
668
        @date       2018.06.25
669
        @history    humkyung 2018.06.27 check line type
670
    '''
671
    def isExtendable(self, line, toler=5):
672
        import math
673
        from SymbolSvgItem import SymbolSvgItem
674

    
675
        if self.lineType == line.lineType:
676
            if self.isHorizontal() and line.isHorizontal():
677
                flag = (line.connectors[0].connectedItem is not None and issubclass(
678
                    type(line.connectors[0].connectedItem), SymbolSvgItem)) or (
679
                                   line.connectors[1].connectedItem is not None and issubclass(
680
                               type(line.connectors[1].connectedItem), SymbolSvgItem))
681
                return flag and (math.fabs(self.start_point()[1] - line.start_point()[1]) < toler)
682
            elif self.isVertical() and line.isVertical():
683
                flag = (line.connectors[0].connectedItem is not None and issubclass(
684
                    type(line.connectors[0].connectedItem), SymbolSvgItem)) or (
685
                                   line.connectors[1].connectedItem is not None and issubclass(
686
                               type(line.connectors[1].connectedItem), SymbolSvgItem))
687
                return flag and (math.fabs(self.start_point()[0] - line.start_point()[0]) < toler)
688

    
689
        return False
690

    
691
    def is_external_point(self, pt):
692
        """ check given pt is located outside of line """
693
        import math
694

    
695
        try:
696
            dx = self.end_point()[0] - self.start_point()[0]
697
            dy = self.end_point()[1] - self.start_point()[1]
698
            lineLength = math.sqrt(dx * dx + dy * dy)
699

    
700
            dx = pt.x - self.start_point()[0]
701
            dy = pt.y - self.start_point()[1]
702
            length = math.sqrt(dx * dx + dy * dy)
703
            if length > lineLength:
704
                return True
705

    
706
            dx = pt.x - self.end_point()[0]
707
            dy = pt.y - self.end_point()[1]
708
            length = math.sqrt(dx * dx + dy * dy)
709
            if length > lineLength:
710
                return True
711

    
712
            return False
713
        except Exception as ex:
714
            from App import App
715
            from AppDocData import MessageType
716

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

    
721
    '''
722
        @author     humkyung
723
        @date       2018.04.16
724
        @history    humkyung 2018.05.08 check if line is possible to be connected
725
                    Jeongwoo 2018.05.15 Split if-statement and Connect each symbol and line
726
    '''
727

    
728
    def connect_if_possible(self, obj, toler=20):
729
        """connect line or symbol is able to be connected and return symbol or line connected to connectors
730
        this method not update item's position' \
731
        """
732

    
733
        from shapely.geometry import Point
734
        from SymbolSvgItem import SymbolSvgItem
735
        from EngineeringConnectorItem import QEngineeringConnectorItem
736
        from AppDocData import AppDocData
737

    
738
        res = []
739

    
740
        start_pt = self.start_point()
741
        end_pt = self.end_point()
742

    
743
        try:
744
            if issubclass(type(obj), SymbolSvgItem):
745
                configs = AppDocData.instance().getConfigs('Data', 'Grid')
746
                grid = int(configs[0].value) if 1 == len(configs) else -1
747

    
748
                for i in range(len(obj.connectors)):
749
                    if not self.is_connectable(obj.connectors[i]):
750
                        continue
751

    
752
                    pt = obj.connectors[i].center()
753
                    """
754
                    dist = [(self.connectors[0], Point(start_pt[0], start_pt[1]).distance(Point(pt[0], pt[1])), start_pt),
755
                            (self.connectors[1], Point(end_pt[0], end_pt[1]).distance(Point(pt[0], pt[1])), end_pt)]
756

757
                    dist.sort(key=lambda x: x[1])
758

759
                    if dist[0][0].connectedItem is None and obj.connectors[i].connectedItem is None:
760
                        dist[0][0].connect(obj)
761
                        obj.connectors[i].connect(self)
762
                        # line, start, end
763
                        res.append(obj)
764
                        res.append(obj.connectors[i].center())
765
                        res.append(dist[0][2])
766
                    """
767
                    if Point(start_pt[0], start_pt[1]).distance(Point(pt[0], pt[1])) < toler and \
768
                        Point(start_pt[0], start_pt[1]).distance(Point(pt[0], pt[1])) < Point(end_pt[0], end_pt[1]).distance(Point(pt[0], pt[1])):
769
                        if self.connectors[0].connectedItem is None and obj.connectors[i].connectedItem is None:
770
                            self.connectors[0].connect(obj)
771
                            obj.connectors[i].connect(self)
772
                            # line, start, end
773
                            res.append(obj)
774
                            if grid == 1:
775
                                res.append((round(obj.connectors[i].center()[0]), round(obj.connectors[i].center()[1])))
776
                                res.append((round(end_pt[0]), round(end_pt[1])))
777
                            else:
778
                                res.append(obj.connectors[i].center())
779
                                res.append(end_pt)
780
                    elif Point(end_pt[0], end_pt[1]).distance(Point(pt[0], pt[1])) < toler:
781
                        if self.connectors[1].connectedItem is None and obj.connectors[i].connectedItem is None:
782
                            self.connectors[1].connect(obj)
783
                            obj.connectors[i].connect(self)
784
                            # line, start, end
785
                            res.append(obj)
786
                            if grid == 1:
787
                                res.append((round(start_pt[0]), round(start_pt[1])))
788
                                res.append((round(obj.connectors[i].center()[0]), round(obj.connectors[i].center()[1])))
789
                            else:
790
                                res.append(start_pt)
791
                                res.append(obj.connectors[i].center())
792
            elif type(obj) is QEngineeringLineItem:
793
                _startPt = obj.start_point()
794
                _endPt = obj.end_point()
795

    
796
                # avoid zero length line, can be losted short line
797
                #if Point(_startPt[0], _startPt[1]).distance(Point(_endPt[0], _endPt[1])) < toler or \
798
                #        Point(start_pt[0], start_pt[1]).distance(Point(end_pt[0], end_pt[1])) < toler:
799
                #    return res
800

    
801
                if obj.connectors[0].connectedItem is None and self.distanceTo(_startPt) < toler and self.distanceTo(_startPt) < self.distanceTo(_endPt):
802
                    if self.connectors[0].connectedItem is None and \
803
                            (Point(start_pt[0], start_pt[1]).distance(Point(_startPt[0], _startPt[1])) < toler) and \
804
                                Point(start_pt[0], start_pt[1]).distance(Point(_startPt[0], _startPt[1])) < Point(end_pt[0], end_pt[1]).distance(Point(_startPt[0], _startPt[1])):
805
                        self.connectors[0].connect(obj)
806
                        obj.connectors[0].connect(self)
807
                        res.append(obj)
808
                    elif self.connectors[1].connectedItem is None and \
809
                            (Point(end_pt[0], end_pt[1]).distance(Point(_startPt[0], _startPt[1])) < toler):
810
                        self.connectors[1].connect(obj)
811
                        obj.connectors[0].connect(self)
812
                        res.append(obj)
813
                    elif self.connectors[1].connectedItem is not obj and self.connectors[0].connectedItem is not obj and obj.connectors[1].connectedItem is not self:
814
                        obj.connectors[0].connect(self, at=QEngineeringAbstractItem.CONNECTED_AT_BODY)
815
                        res.append(obj)
816

    
817
                elif obj.connectors[1].connectedItem is None and self.distanceTo(_endPt) < toler:
818
                    if self.connectors[0].connectedItem is None and \
819
                            (Point(start_pt[0], start_pt[1]).distance(Point(_endPt[0], _endPt[1])) < toler) and \
820
                                Point(start_pt[0], start_pt[1]).distance(Point(_endPt[0], _endPt[1])) < Point(end_pt[0], end_pt[1]).distance(Point(_endPt[0], _endPt[1])):
821
                        self.connectors[0].connect(obj)
822
                        obj.connectors[1].connect(self)
823
                        res.append(obj)
824
                    elif self.connectors[1].connectedItem is None and \
825
                            (Point(end_pt[0], end_pt[1]).distance(Point(_endPt[0], _endPt[1])) < toler):
826
                        self.connectors[1].connect(obj)
827
                        obj.connectors[1].connect(self)
828
                        res.append(obj)
829
                    elif self.connectors[1].connectedItem is not obj and self.connectors[0].connectedItem is not obj and obj.connectors[0].connectedItem is not self:
830
                        obj.connectors[1].connect(self, at=QEngineeringAbstractItem.CONNECTED_AT_BODY)
831
                        res.append(obj)
832
        except Exception as ex:
833
            from App import App
834
            from AppDocData import MessageType
835

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

    
840
        return res
841

    
842
    '''
843
        @brief      disconnect connector item
844
        @author     kyouho
845
        @date       2018.08.30
846
    '''
847

    
848
    def disconnectedItemAtConnector(self, connector):
849
        for conn in self.connectors:
850
            if conn.isOverlapConnector(connector):
851
                conn.connectedItem = None
852

    
853
    def arrange_flow_direction(self, _from, reverse=False):
854
        """ reverse if from is connected to second connector """
855
        if not _from:
856
            raise ValueError
857

    
858
        if not reverse and self.connectors[0].connectedItem != _from:
859
            self.reverse()
860
        elif reverse and self.connectors[1].connectedItem != _from:
861
            self.reverse()
862

    
863
    def find_connected_objects(self):
864
        """find all connected items except equipment and instrument"""
865
        from EngineeringLineItem import QEngineeringLineItem
866
        from EngineeringEquipmentItem import QEngineeringEquipmentItem
867
        from SymbolSvgItem import SymbolSvgItem
868

    
869
        left_visited, right_visited = [self], [self]
870

    
871
        try:
872
            left_pool, right_pool = [self.connectors[0].connectedItem] if self.connectors[0].connectedItem else [], \
873
                                    [self.connectors[1].connectedItem] if self.connectors[1].connectedItem else []
874

    
875
            while left_pool:
876
                obj = left_pool.pop()
877

    
878
                if issubclass(type(obj), QEngineeringEquipmentItem) or (len(obj.connectors) > 2) or \
879
                        not obj.is_connected(left_visited[-1]):
880
                    continue
881

    
882
                left_visited.append(obj)
883

    
884
                if (type(obj) is QEngineeringLineItem and self.is_piping(True)) or issubclass(type(obj), SymbolSvgItem):
885
                    founds = [conn.connectedItem for conn in obj.connectors if
886
                              conn.connectedItem and conn.connectedItem not in left_visited and
887
                              2 == len(conn.connectedItem.connectors)]
888
                    for found in founds:
889
                        matches = [conn.connectedItem for conn in found.connectors if conn.connectedItem is obj]
890
                        if matches:
891
                            left_pool.append(found)
892

    
893
            while right_pool:
894
                obj = right_pool.pop()
895

    
896
                if issubclass(type(obj), QEngineeringEquipmentItem) or (len(obj.connectors) > 2) or \
897
                        not obj.is_connected(right_visited[-1]):
898
                    continue
899

    
900
                right_visited.append(obj)
901

    
902
                if (type(obj) is QEngineeringLineItem and self.is_piping(True)) or issubclass(type(obj), SymbolSvgItem):
903
                    founds = [conn.connectedItem for conn in obj.connectors if
904
                              conn.connectedItem and conn.connectedItem not in right_visited and
905
                              2 == len(conn.connectedItem.connectors)]
906
                    for found in founds:
907
                        matches = [conn.connectedItem for conn in found.connectors if conn.connectedItem is obj]
908
                        if matches:
909
                            right_pool.append(found)
910
        except Exception as ex:
911
            from App import App
912
            from AppDocData import MessageType
913

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

    
918
        return left_visited, right_visited
919

    
920
    def reverse(self, auto=False):
921
        """revere the line"""
922
        from EngineeringLineItem import QEngineeringLineItem
923
        from SymbolSvgItem import SymbolSvgItem
924

    
925
        line = self.line()
926
        self.setLine(QLineF(line.p2(), line.p1()))
927
        self.connectors[0], self.connectors[1] = self.connectors[1], self.connectors[0]
928
        self.update_arrow()
929
        self.update()
930

    
931
        if auto:
932
            left, right = self.find_connected_objects()
933

    
934
            if left:
935
                connected_item = self
936
                for obj in left[1:]:
937
                    if type(obj) is QEngineeringLineItem:
938
                        obj.arrange_flow_direction(connected_item, True)
939

    
940
                    connected_item = obj
941

    
942
            if right:
943
                connected_item = self
944
                for obj in right[1:]:
945
                    if type(obj) is QEngineeringLineItem:
946
                        obj.arrange_flow_direction(connected_item)
947

    
948
                    connected_item = obj
949

    
950
    '''
951
        @brief      add flow arrow
952
        @author     humkyung
953
        @date       2018.05.08
954
        @history    2018.05.24  Jeongwoo    Modifying Draw Flow Arrow
955
    '''
956

    
957
    def addFlowArrow(self):
958
        import numpy as np
959
        import cv2
960
        import math
961
        import sys
962
        global src
963
        from shapely.geometry import Point
964
        from QEngineeringFlowArrowItem import QEngineeringFlowArrowItem
965
        from AppDocData import AppDocData
966

    
967
        try:
968
            docData = AppDocData.instance()
969
            area = docData.getArea('Drawing')
970

    
971
            startPt = self.start_point()
972
            endPt = self.end_point()
973
            length = self.length()
974
            direction = [(endPt[0] - startPt[0]) / length, (endPt[1] - startPt[1]) / length]
975

    
976
            left = min(startPt[0], endPt[0])
977
            top = min(startPt[1], endPt[1])
978
            right = max(startPt[0], endPt[0])
979
            bottom = max(startPt[1], endPt[1])
980

    
981
            rect = None
982
            if self.isVertical():
983
                rect = QRectF(left - 10, top, (right - left) + 20, (bottom - top))
984
            else:
985
                rect = QRectF(left, top - 10, (right - left), (bottom - top) + 20)
986

    
987
            docData = AppDocData.instance()
988
            area = docData.getArea('Drawing')
989
            img = np.array(AppDocData.instance().getCurrentPidSource().getPyImageOnRect(rect))
990

    
991
            imgLine = cv2.threshold(cv2.cvtColor(img, cv2.COLOR_BGR2GRAY), 127, 255, cv2.THRESH_BINARY)[1]
992
            # remove noise
993
            imgLine = cv2.bitwise_not(imgLine)
994
            imgLine = cv2.erode(imgLine, np.ones((10, 10), np.uint8))
995

    
996
            contours, hierarchy = cv2.findContours(imgLine, cv2.RETR_TREE, cv2.CHAIN_APPROX_SIMPLE)
997
            if contours:
998
                contours = sorted(contours, key=cv2.contourArea, reverse=True)
999
                [x, y, w, h] = cv2.boundingRect(contours[0])
1000
                if w > 10 and w < 100 and h > 10 and h < 100:  # check arrow mark size
1001
                    imgArrowMark = imgLine[y:y + h, x:x + w]
1002

    
1003
                    # DEBUG - display flow arrow area
1004
                    '''
1005
                    item = QGraphicsBoundingBoxItem(rect.left() + x - 10, rect.top() + y - 10, w + 20, h + 20)
1006
                    item.isSymbol = True
1007
                    item.angle = 0
1008
                    item.setPen(QPen(Qt.red, 1, Qt.SolidLine))
1009
                    item.setBrush(QBrush(QColor(255,255,0,100)))
1010
                    self.scene().addItem(item)
1011
                    '''
1012
                    # up to here
1013

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

    
1017
                    ####### HoughLinesP
1018
                    if lines is not None:
1019
                        maxLength = None
1020
                        selected = None
1021
                        for line in lines:
1022
                            for x1, y1, x2, y2 in line:
1023
                                dx = x2 - x1
1024
                                dy = y2 - y1
1025
                                selected = line
1026
                                length = math.sqrt(dx * dx + dy * dy)
1027
                                if maxLength is None or length > maxLength:
1028
                                    maxLength = length
1029
                                    selected = line
1030

    
1031
                        for x1, y1, x2, y2 in selected:
1032
                            dx = math.fabs(x2 - x1)
1033
                            dy = math.fabs(y2 - y1)
1034
                            length = math.sqrt(dx * dx + dy * dy)
1035
                            dx /= length
1036
                            dy /= length
1037
                            if (self.isVertical() and (dx < 0.001 or math.fabs(dx - 1) < 0.001)) or (
1038
                                    self.isHorizontal() and (dx < 0.001 or math.fabs(dx - 1) < 0.001)): continue
1039
                            dist1 = self.distanceTo((rect.left() + x + x1, rect.top() + y + y1))
1040
                            dist2 = self.distanceTo((rect.left() + x + x2, rect.top() + y + y2))
1041
                            if dist1 > dist2:  # point which's distance is longer would be start point
1042
                                _start = (rect.left() + x + x1, rect.top() + y + y1)
1043
                                _end = (rect.left() + x + x2, rect.top() + y + y2)
1044
                            else:
1045
                                _start = (rect.left() + x + x2, rect.top() + y + y2)
1046
                                _end = (rect.left() + x + x1, rect.top() + y + y1)
1047

    
1048
                            # DEBUG display detected line
1049
                            '''
1050
                            poly = QEngineeringPolylineItem()
1051
                            poly._pol.append(QPointF(_start[0], _start[1]))
1052
                            poly._pol.append(QPointF(_end[0], _end[1]))
1053
                            poly.setPen(QPen(Qt.red, 2, Qt.SolidLine))
1054
                            poly.buildItem()
1055
                            self.scene().addItem(poly)
1056
                            '''
1057
                            # up to here
1058

    
1059
                            dist1 = Point(startPt[0], startPt[1]).distance(Point(_start[0], _start[1]))
1060
                            dist2 = Point(startPt[0], startPt[1]).distance(Point(_end[0], _end[1]))
1061
                            if dist1 > dist2:
1062
                                startPt, endPt = endPt, startPt
1063
                                direction[0], direction[1] = -direction[0], -direction[1]
1064
                                self.reverse()
1065

    
1066
                        '''
1067
                        center = [(startPt[0]+endPt[0])*0.5, (startPt[1]+endPt[1])*0.5]
1068
                        arrow = QEngineeringFlowArrowItem(center, direction)
1069
                        arrow.buildItem()
1070
                        self.scene().addItem(arrow)
1071
                        '''
1072

    
1073
                        x = round(rect.left() + x - 5)
1074
                        y = round(rect.top() + y - 5)
1075
                        self.flowMark = ([x, y, w + 10, h + 10], None)
1076
                else:
1077
                    pass
1078
        except Exception as ex:
1079
            from App import App
1080
            from AppDocData import MessageType
1081

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

    
1086
    def update_shape(self, symbol, point):
1087
        """update line shape"""
1088
        for index in range(len(self.connectors)):
1089
            if self.connectors[index].connectedItem == symbol:
1090
                # startPoint
1091
                if index == 0:
1092
                    line = QLineF(QPointF(point[0], point[1]), self.line().p2())
1093
                    self.setLine(line)
1094
                    self.connectors[0].setPos([point[0], point[1]])
1095
                # endpoint
1096
                else:
1097
                    line = QLineF(self.line().p1(), QPointF(point[0], point[1]))
1098
                    self.setLine(line)
1099
                    self.connectors[1].setPos([point[0], point[1]])
1100

    
1101
        ## startPoint에 symbol
1102
        # if self.connectors[0].connectedItem == symbol:
1103
        #    if self.startPoint()[0] == symbol.connectors[0].sceneConnectPoint[0] and self.startPoint()[1] == symbol.connectors[0].sceneConnectPoint[1]:
1104
        #        line = QLineF(QPointF(changedConnPoint1[0], changedConnPoint1[1]), self.line().p2())
1105
        #        self.setLine(line)
1106
        #    else:
1107
        #        line = QLineF(QPointF(changedConnPoint2[0], changedConnPoint2[1]), self.line().p2())
1108
        #        self.setLine(line)
1109
        ## endPoint에 symbol
1110
        # elif self.connectors[1].connectedItem == symbol:
1111
        #    if self.endPoint()[0] == symbol.connectors[0].sceneConnectPoint[0] and self.endPoint()[1] == symbol.connectors[0].sceneConnectPoint[1]:
1112
        #        line = QLineF(self.line().p1(), QPointF(changedConnPoint1[0], changedConnPoint1[1]))
1113
        #        self.setLine(line)
1114
        #    else:
1115
        #        line = QLineF(self.line().p1(), QPointF(changedConnPoint2[0], changedConnPoint2[1]))
1116
        #        self.setLine(line)
1117

    
1118
        self.update()
1119

    
1120
    '''
1121
        @brief  remove symbol
1122
        @author humkyung
1123
        @date   2018.04.23
1124
    '''
1125

    
1126
    def removeSymbol(self, symbol):
1127
        import math
1128

    
1129
        if 2 == len(symbol.connectors):  # 2-way component
1130
            connected = symbol.connectors[0].connectedItem if symbol.connectors[0].connectedItem is not self else \
1131
            symbol.connectors[1].connectedItem
1132

    
1133
            pts = []
1134
            pts.append(self.start_point())
1135
            pts.append(self.end_point())
1136
            pts.append(connected.start_point())
1137
            pts.append(connected.end_point())
1138

    
1139
            self.scene().removeItem(connected)
1140

    
1141
            start = None
1142
            end = None
1143
            maxDist = None
1144
            for i in range(len(pts)):
1145
                for j in range(i + 1, len(pts)):
1146
                    dx = pts[i][0] - pts[j][0]
1147
                    dy = pts[i][1] - pts[j][1]
1148
                    dist = math.sqrt(dx * dx + dy * dy)
1149
                    if maxDist is None:
1150
                        maxDist = dist
1151
                        start = pts[i]
1152
                        end = pts[j]
1153
                    elif dist > maxDist:
1154
                        maxDist = dist
1155
                        start = pts[i]
1156
                        end = pts[j]
1157

    
1158
            if (pts[0] == end) or (pts[1] == start): start, end = end, start
1159

    
1160
            line = QLineF(QPointF(start[0], start[1]), QPointF(end[0], end[1]))
1161
            self.setLine(line)
1162
            # self.buildItem()
1163
            self.update()
1164

    
1165
    def validate(self, rules):
1166
        '''
1167
            @brief  validation check : connection
1168
            @author euisung
1169
            @date   2019.04.01
1170
        '''
1171
        from EngineeringAbstractItem import QEngineeringAbstractItem
1172
        from SymbolSvgItem import SymbolSvgItem
1173
        from EngineeringEquipmentItem import QEngineeringEquipmentItem
1174
        from AppDocData import AppDocData
1175
        import math
1176
        from ValidateCommand import LineValidation
1177

    
1178
        errors = []
1179

    
1180
        try:
1181
            _translate = QCoreApplication.translate
1182

    
1183
            docdata = AppDocData.instance()
1184
            dataPath = docdata.getErrorItemSvgPath()
1185

    
1186
            connectedUid = []
1187

    
1188
            for connector in self.connectors:
1189
                # for duplicattion check
1190
                if connector.connectedItem and issubclass(type(connector.connectedItem), QEngineeringAbstractItem):
1191
                    connectedUid.append(str(connector.connectedItem.uid))
1192

    
1193
                # check if there is not connected connector
1194
                if LineValidation.DisconnectionWarning in rules:
1195
                    if connector.connectedItem is None:
1196
                        error = SymbolSvgItem.createItem('Error', None, dataPath)
1197
                        error.setPosition(list(connector.center()))
1198
                        error.parent = self
1199
                        error.msg = _translate('Disconnection warning', 'Disconnection warning')
1200
                        error.setToolTip(error.msg)
1201
                        error.area = 'Drawing'
1202
                        error.name = 'Warning'
1203
                        errors.append(error)
1204

    
1205
                # check line to symbol
1206
                elif issubclass(type(connector.connectedItem), SymbolSvgItem) and type(
1207
                        connector.connectedItem) is not QEngineeringEquipmentItem:
1208
                    matches = [conn for conn in connector.connectedItem.connectors if conn.connectedItem is self]
1209
                    # check if two items are connected each other
1210
                    if not matches:
1211
                        if LineValidation.DisconnectedFromOppositeSide in rules:
1212
                            error = SymbolSvgItem.createItem('Error', None, dataPath)
1213
                            error.setPosition(list(connector.center()))
1214
                            error.parent = self
1215
                            error.msg = _translate('Disconnected from opposite side', 'Disconnected from opposite side')
1216
                            error.setToolTip(error.msg)
1217
                            error.area = 'Drawing'
1218
                            error.name = 'Error'
1219
                            error.items = [ connector.connectedItem ]
1220
                            errors.append(error)
1221
                    # check connection position
1222
                    elif not self.isOverlap(connector.sceneBoundingRect(), matches[0].sceneBoundingRect()):
1223
                        if LineValidation.MismatchedPositionError in rules:
1224
                            error = SymbolSvgItem.createItem('Error', None, dataPath)
1225
                            error.setPosition(list(connector.center()))
1226
                            error.parent = self
1227
                            error.msg = _translate('Mismatched position error', 'Mismatched position error')
1228
                            error.setToolTip(error.msg)
1229
                            error.area = 'Drawing'
1230
                            error.name = 'Error'
1231
                            error.items = [ connector.connectedItem ]
1232
                            errors.append(error)
1233

    
1234
                elif issubclass(type(connector.connectedItem), QEngineeringLineItem):
1235
                    # check if connected two lines has same direction
1236
                    if connector._connected_at == QEngineeringAbstractItem.CONNECTED_AT_PT:
1237
                        center = connector.center()
1238

    
1239
                        indices = [0, 0]
1240
                        indices[0] = 1 if QPointF(center[0], center[1]) == self.line().p1() else 2
1241
                        matches = [conn for conn in connector.connectedItem.connectors if
1242
                                   conn.connectedItem == self and conn._connected_at == QEngineeringAbstractItem.CONNECTED_AT_PT]
1243
                        if matches:
1244
                            indices[1] = 1 if QPointF(matches[0].center()[0], matches[0].center()[
1245
                                1]) == connector.connectedItem.line().p1() else 2
1246
                        else:
1247
                            if LineValidation.DisconnectedFromOppositeSide in rules:
1248
                                error = SymbolSvgItem.createItem('Error', None, dataPath)
1249
                                error.setPosition(list(connector.center()))
1250
                                error.parent = self
1251
                                error.msg = _translate('Disconnected from opposite side', 'Disconnected from opposite side')
1252
                                error.setToolTip(error.msg)
1253
                                error.area = 'Drawing'
1254
                                error.name = 'Error'
1255
                                error.items = [ connector.connectedItem ]
1256
                                errors.append(error)
1257

    
1258
                        if LineValidation.FlowDirectionError in rules:
1259
                            if indices[0] == indices[1]:
1260
                                error = SymbolSvgItem.createItem('Error', None, dataPath)
1261
                                error.setPosition(list(connector.center()))
1262
                                error.parent = self
1263
                                error.msg = _translate('Flow direction error', 'Flow direction error')
1264
                                error.setToolTip(error.msg)
1265
                                error.area = 'Drawing'
1266
                                error.name = 'Error'
1267
                                error.items = [ connector.connectedItem ]
1268
                                errors.append(error)
1269

    
1270
                        if LineValidation.LineTypeError in rules:
1271
                            if self.lineType != connector.connectedItem.lineType:
1272
                                error = SymbolSvgItem.createItem('Error', None, dataPath)
1273
                                error.setPosition(list(connector.center()))
1274
                                error.parent = self
1275
                                error.msg = _translate('Line type error', 'Line type error')
1276
                                error.setToolTip(error.msg)
1277
                                error.area = 'Drawing'
1278
                                error.name = 'Error'
1279
                                error.items = [ connector.connectedItem ]
1280
                                errors.append(error)
1281
                    
1282
                    else:
1283
                        if LineValidation.LinePositionError in rules:
1284
                            center = connector.center()
1285
                            line = connector.connectedItem
1286

    
1287
                            configs = docdata.getConfigs('Line Detector', 'Length to connect line')
1288
                            toler = int(configs[0].value) if configs else 20
1289

    
1290
                            if line.distanceTo(center) > toler * 2:
1291
                                error = SymbolSvgItem.createItem('Error', None, dataPath)
1292
                                error.setPosition(list(connector.center()))
1293
                                error.parent = self
1294
                                error.msg = _translate('Line position error', 'Line position error')
1295
                                error.setToolTip(error.msg)
1296
                                error.area = 'Drawing'
1297
                                error.name = 'Error'
1298
                                error.items = [ line ]
1299
                                errors.append(error)
1300

    
1301
                #errors.extend(connector.validate())
1302

    
1303
            # check duplicated connection
1304
            if LineValidation.DuplicatedConnectionError in rules:
1305
                if len(connectedUid) is not len(set(connectedUid)):
1306
                    error = SymbolSvgItem.createItem('Error', None, dataPath)
1307
                    error.setPosition([self.sceneBoundingRect().center().x(), self.sceneBoundingRect().center().y()])
1308
                    error.parent = self
1309
                    error.msg = _translate('Duplicated connection error', 'Duplicated connection error')
1310
                    error.setToolTip(error.msg)
1311
                    error.area = 'Drawing'
1312
                    error.name = 'Error'
1313
                    errors.append(error)
1314

    
1315
            # check overlapping
1316
            if LineValidation.LineOverlappingWarning in rules:
1317
                lines = [item for item in self.scene().items() if item is not self and type(item) is QEngineeringLineItem]
1318
                for line in lines:
1319
                    lineRect = line.sceneBoundingRect()
1320
                    rect1 = QRectF(lineRect.left() - 3, lineRect.top() - 3, lineRect.width() + 6, lineRect.height() + 6)
1321
                    if rect1.contains(self.sceneBoundingRect()) or (not (self.isVertical() ^ line.isVertical()) and lineRect.contains(self.sceneBoundingRect().center())):
1322
                        error = SymbolSvgItem.createItem('Error', None, dataPath)
1323
                        error.setPosition([self.sceneBoundingRect().center().x(), self.sceneBoundingRect().center().y()])
1324
                        error.parent = self
1325
                        error.msg = _translate('Line overlapping warning', 'Line overlapping warning')
1326
                        error.setToolTip(error.msg)
1327
                        error.area = 'Drawing'
1328
                        error.name = 'Warning'
1329
                        error.items = [ line ]
1330
                        errors.append(error)
1331

    
1332
            configs = docdata.getConfigs('Project', 'Operation Code')
1333
            code = configs[0].value if 1 == len(configs) else ''
1334
            if code == 'nK6uurpuiw==': # Samsung
1335
                # check line angle
1336
                if LineValidation.LineAngleWarning in rules:
1337
                    angle = self.angle()
1338
                    allowed_error = 0.0008726646#0.0175
1339
                    isAngleError = True
1340
                    #degree 0
1341
                    if abs(angle - 0) <= allowed_error:
1342
                        isAngleError = False
1343
                    #degree 90
1344
                    elif abs(angle - math.pi / 2) <= allowed_error:
1345
                        isAngleError = False
1346
                    #degree 180
1347
                    elif abs(angle - math.pi) <= allowed_error:
1348
                        isAngleError = False
1349
                    #degree 270
1350
                    elif abs(angle - math.pi * 3 / 2) <= allowed_error:
1351
                        isAngleError = False
1352

    
1353
                    if isAngleError:
1354
                        error = SymbolSvgItem.createItem('Error', None, dataPath)
1355
                        error.setPosition([self.sceneBoundingRect().center().x(), self.sceneBoundingRect().center().y()])
1356
                        error.parent = self
1357
                        error.msg = _translate('Line angle warning', 'Line angle warning')
1358
                        error.setToolTip(error.msg)
1359
                        error.area = 'Drawing'
1360
                        error.name = 'Warning'
1361
                        errors.append(error)
1362

    
1363
            # loop check
1364
            if LineValidation.LoopPathError in rules:
1365
                visited = self.find_connected_objects_size(self)
1366
                if visited.index(self) == 1 and type(visited[0]) is QEngineeringLineItem and visited[0].connectors[1].connectedItem is self and \
1367
                        visited[0].connectors[0].connectedItem and visited[0].connectors[0].connectedItem in visited:
1368
                    visited.pop(1)
1369
                    error = SymbolSvgItem.createItem('Error', None, dataPath)
1370
                    error.setPosition([self.sceneBoundingRect().center().x(), self.sceneBoundingRect().center().y()])
1371
                    error.parent = self
1372
                    error.msg = _translate('Loop path error', 'Loop path error')
1373
                    error.setToolTip(error.msg)
1374
                    error.area = 'Drawing'
1375
                    error.name = 'Error'
1376
                    error.items = visited
1377
                    errors.append(error)
1378

    
1379
        except Exception as ex:
1380
            from App import App
1381
            from AppDocData import MessageType
1382

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

    
1387
        return errors
1388

    
1389
    '''
1390
        @brief  update line type
1391
        @author humkyung
1392
        @date   2018.07.05
1393
    '''
1394

    
1395
    def update_line_type(self):
1396
        import uuid
1397
        from LineTypeConditions import LineTypeConditions
1398

    
1399
        try:
1400
            pool, visited, items = [self], [], []
1401
            while pool:
1402
                obj = pool.pop()
1403
                visited.append(obj)
1404

    
1405
                """ connected items """
1406
                connected = [connector.connectedItem for connector in obj.connectors if
1407
                             connector.connectedItem and not type(connector.connectedItem) is uuid.UUID]
1408
                """ connected lines at point """
1409
                lines = [connector.connectedItem for connector in obj.connectors if connector.connectedItem and type(
1410
                    connector.connectedItem) is QEngineeringLineItem and connector._connected_at == QEngineeringAbstractItem.CONNECTED_AT_PT]
1411
                """ add items not in 'connected' to items list """
1412
                items.extend([item for item in connected if item not in lines])
1413
                """ add items not visited to pool """
1414
                pool.extend([item for item in lines if item not in visited])
1415

    
1416
            for condition in LineTypeConditions.items():
1417
                if condition.eval(items):
1418
                    self.lineType = condition.name
1419
                    break
1420
                if condition.eval(items, reverse=True):
1421
                    self.lineType = condition.name
1422
                    break
1423
        except Exception as ex:
1424
            from App import App
1425
            from AppDocData import MessageType
1426

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

    
1431
    def hoverEnterEvent(self, event, minimum=False):
1432
        """ hilight item and it's children """
1433
        self.highlight(True, minimum)
1434

    
1435
    def hoverLeaveEvent(self, event, minimum=False):
1436
        """ restore original color """
1437
        self.highlight(False, minimum)
1438

    
1439
    def highlight(self, flag, minimum=False):
1440
        self.hover = flag
1441
        self.setZValue(QEngineeringAbstractItem.HOVER_ZVALUE) if flag else self.setZValue(QEngineeringLineItem.ZVALUE)
1442
        self.update()
1443

    
1444
        if not minimum:
1445
            for assoc in self.associations():
1446
                assoc.highlight(flag)
1447

    
1448
            for connector in self.connectors:
1449
                connector.highlight(flag)
1450

    
1451
    def hoverMoveEvent(self, event):
1452
        pass
1453

    
1454
    '''
1455
        @brief      remove item when user press delete key
1456
        @author     humkyung
1457
        @date       2018.04.23
1458
        @history    swap start, end point when user press 'c' key
1459
    '''
1460

    
1461
    def keyPressEvent(self, event):
1462
        from EngineeringLineNoTextItem import QEngineeringLineNoTextItem
1463

    
1464
        #if self.isSelected() and event.key() == Qt.Key_Delete:
1465
        #    self.scene().removeItem(self)
1466
        if event.key() == Qt.Key_C:# and self.is_piping(True):
1467
            if self.owner and issubclass(type(self.owner), QEngineeringLineNoTextItem):
1468
                index = 1
1469
                for run in self.owner.runs:
1470
                    if self in run.items:
1471
                        if index == 1:
1472
                            self.owner.reverse()
1473
                        else:
1474
                            run.reverse()
1475
                        break
1476
                    else:
1477
                        index = index + 1
1478
            else:
1479
                self.reverse(True)
1480
        elif event.key() == Qt.Key_A:
1481
            self.toggleFlowMark()
1482
        elif event.key() == Qt.Key_G:
1483
            self.freeze_attriute()
1484
        elif event.key() == Qt.Key_Up:  # translate up/down/left/right symbol
1485
            modifiers = QApplication.keyboardModifiers()
1486
            delta = 10 if modifiers == Qt.ControlModifier else 1
1487

    
1488
            dx, dy = 0*delta, -1*delta
1489
            self.set_line([[self.start_point()[0] + dx, self.start_point()[1] + dy],
1490
                           [self.end_point()[0] + dx, self.end_point()[1] + dy]])
1491
        elif event.key() == Qt.Key_Down:
1492
            modifiers = QApplication.keyboardModifiers()
1493
            delta = 10 if modifiers == Qt.ControlModifier else 1
1494

    
1495
            dx, dy = 0*delta, 1*delta
1496
            self.set_line([[self.start_point()[0] + dx, self.start_point()[1] + dy],
1497
                           [self.end_point()[0] + dx, self.end_point()[1] + dy]])
1498
        elif event.key() == Qt.Key_Left:
1499
            modifiers = QApplication.keyboardModifiers()
1500
            delta = 10 if modifiers == Qt.ControlModifier else 1
1501

    
1502
            dx, dy = -1*delta, 0*delta
1503
            self.set_line([[self.start_point()[0] + dx, self.start_point()[1] + dy],
1504
                           [self.end_point()[0] + dx, self.end_point()[1] + dy]])
1505
        elif event.key() == Qt.Key_Right:
1506
            modifiers = QApplication.keyboardModifiers()
1507
            delta = 10 if modifiers == Qt.ControlModifier else 1
1508

    
1509
            dx, dy = 1*delta, 0*delta
1510
            self.set_line([[self.start_point()[0] + dx, self.start_point()[1] + dy],
1511
                           [self.end_point()[0] + dx, self.end_point()[1] + dy]])
1512
        elif event.key() == Qt.Key_M:
1513
            from App import App
1514

    
1515
            App.mainWnd().keyPressEvent(event)
1516

    
1517
    def freeze_attriute(self, flag=None):
1518
        from App import App
1519

    
1520
        if flag is None:
1521
            freeze = True
1522
            for _attr, _value in self.attrs.items():
1523
                if _attr.Freeze:
1524
                    freeze = False
1525
                    break
1526
            
1527
            for _attr, _value in self.attrs.items():
1528
                _attr.Freeze = freeze
1529

    
1530
            App.mainWnd().resultPropertyTableWidget.onSuccessSelectAttribute(self)
1531
        else:
1532
            for _attr, _value in self.attrs.items():
1533
                _attr.Freeze = flag
1534

    
1535
    def toggleFlowMark(self):
1536
        from AppDocData import AppDocData
1537

    
1538
        if self.flowMark:
1539
            self.flowMark = None
1540
        else:
1541
            configs = AppDocData.instance().getConfigs('Flow Mark', 'Position')
1542
            self.flowMark = int(configs[0].value) if 1 == len(configs) else 100
1543
        self.update_arrow()
1544

    
1545
    def shape(self):
1546
        """return shape for non-rectangle shape"""
1547
        x1, y1, x2, y2 = self.line().x1(), self.line().y1(), self.line().x2(), self.line().y2()
1548
        path = QPainterPath()
1549
        path.moveTo(x1, y1)
1550
        path.lineTo(x2, y2)
1551

    
1552
        stroke = QPainterPathStroker()
1553
        stroke.setWidth(15)
1554
        return stroke.createStroke(path)
1555

    
1556
    def paint(self, painter, option, widget):
1557
        # override paint method
1558
        color = self.getColor()
1559
        self.setColor(color)
1560

    
1561
        painter.setPen(self.pen())
1562
        painter.drawLine(self.line())
1563
        #QGraphicsLineItem.paint(self, painter, options, widget)
1564

    
1565
        if self.isSelected():
1566
            painter.setOpacity(0.7)
1567
            painter.setPen(QPen(Qt.black, 1.5, Qt.DashLine))
1568
            painter.setBrush(QEngineeringLineItem.HIGHLIGHT_BRUSH)
1569
            painter.drawPolygon(self.minimum_bounding_box)
1570

    
1571
    def drawToImage(self, img, color, thickness):
1572
        """write recognized lines to image"""
1573
        try:
1574
            ptStart = self.start_point()
1575
            ptEnd = self.end_point()
1576
            cv2.line(img, (round(ptStart[0]), round(ptStart[1])), (round(ptEnd[0]), round(ptEnd[1])), color, thickness)
1577
            # up to here
1578
        except Exception as ex:
1579
            message = f"error occurred({repr(ex)}) in {sys.exc_info()[-1].tb_frame.f_code.co_filename}:" \
1580
                      f"{sys.exc_info()[-1].tb_lineno}"
1581

    
1582
    @staticmethod
1583
    def from_database(component):
1584
        """ get line from database """
1585
        import uuid
1586
        from AppDocData import AppDocData
1587
        from SymbolAttr import SymbolAttr
1588

    
1589
        item = None
1590
        try:
1591
            uidNode = component['UID']
1592
            uid = uidNode if uidNode is not None else uuid.uuid4()  # generate UUID
1593
            owner = uuid.UUID(component['Owner']) if component['Owner'] and component['Owner'] != 'None' else None
1594

    
1595
            app_doc_data = AppDocData.instance()
1596
            connectors = app_doc_data.get_component_connectors(uid)
1597
            if 2 != len(connectors): return item
1598
            startPoint = [float(connectors[0]['X']), float(connectors[0]['Y'])]
1599
            endPoint = [float(connectors[1]['X']), float(connectors[1]['Y'])]
1600

    
1601
            configs = AppDocData.instance().getConfigs('Data', 'Grid')
1602
            grid = int(configs[0].value) if 1 == len(configs) else -1
1603
            if grid == 1:
1604
                startPoint = [round(startPoint[0]), round(startPoint[1])]
1605
                endPoint = [round(endPoint[0]), round(endPoint[1])]
1606

    
1607
            item = QEngineeringLineItem(vertices=[startPoint, endPoint], uid=uid)
1608
            item.setVisible(False)
1609
            attrs = app_doc_data.get_component_attributes(uid)
1610
            matches = [attr for attr in attrs if attr['Attribute'] == 'LineType']
1611
            item.lineType = matches[0]['Value'] if matches else 'Secondary'
1612
            if matches:
1613
                attrs.remove(matches[0])
1614

    
1615
            # freeze => freeze and bidirectional
1616
            freeze_bidirectional = component['Freeze']
1617
            if int(freeze_bidirectional) == 3:
1618
                item.set_property('Freeze', True)
1619
                item.set_property('Bidirectional', True)
1620
            elif int(freeze_bidirectional) == 2:
1621
                item.set_property('Freeze', False)
1622
                item.set_property('Bidirectional', True)
1623
            elif int(freeze_bidirectional) == 1:
1624
                item.set_property('Freeze', True)
1625
                item.set_property('Bidirectional', False)
1626

    
1627
            # line has only freeze
1628
            '''
1629
            for key in item._properties.keys():
1630
                for compo in component.keys():
1631
                    if key.Attribute == compo:
1632
                        item._properties[key] = key.parse_value(component[key.Attribute])# if component[key.Attribute] else ''
1633
            '''
1634

    
1635
            ## assign area
1636
            if component['Area']:
1637
                for area in app_doc_data.getAreaList():
1638
                    if area.contains(startPoint) and area.contains(endPoint):
1639
                        item.area = area.name
1640
                        break
1641
            else:
1642
                item.area = component['Area']
1643
            ## up to here
1644

    
1645
            matches = [attr for attr in attrs if attr['Attribute'] == 'Thickness']
1646
            item.thickness = int(matches[0]['Value']) if matches and matches[0]['Value'] != 'None' else None
1647
            if matches:
1648
                attrs.remove(matches[0])
1649

    
1650
            matches = [attr for attr in attrs if attr['Attribute'] == 'FlowMark']
1651
            item.flowMark = int(matches[0]['Value']) if matches and matches[0]['Value'] != 'None' else None
1652
            if matches:
1653
                attrs.remove(matches[0])
1654

    
1655
            if connectors:
1656
                iterIndex = 0
1657
                for connector in connectors:
1658
                    item.connectors[iterIndex].parse_record(connector)
1659
                    iterIndex += 1
1660

    
1661
            # get associations
1662
            associations = app_doc_data.get_component_associations(uid)
1663
            if associations:
1664
                for assoc in associations:
1665
                    _type = assoc['Type']
1666
                    if not _type in item._associations:
1667
                        item._associations[_type] = []
1668
                    item._associations[_type].append(uuid.UUID(assoc['Association']))
1669
            # up to here
1670

    
1671
            if attrs:
1672
                for attr in attrs:
1673
                    _attr = SymbolAttr.from_record(attr)
1674
                    item.attrs[_attr] = attr['Value']
1675

    
1676
        except Exception as ex:
1677
            from App import App
1678
            from AppDocData import MessageType
1679

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

    
1684
        return item if item.length() > 1 else None
1685

    
1686
    '''
1687
        @brief      parse xml code
1688
        @author     humkyung
1689
        @date       2018.06.27
1690
    '''
1691

    
1692
    @staticmethod
1693
    def fromXml(node):
1694
        import uuid
1695
        from AppDocData import AppDocData
1696
        from SymbolAttr import SymbolAttr
1697

    
1698
        item = None
1699
        try:
1700
            uidNode = node.find('UID')
1701
            uid = uidNode.text if uidNode is not None else uuid.uuid4()  # generate UUID
1702
            owner = uuid.UUID(node.attrib['OWNER']) if 'OWNER' in node.attrib and node.attrib[
1703
                'OWNER'] != 'None' else None
1704

    
1705
            startPoint = [float(x) for x in node.find('STARTPOINT').text.split(',')]
1706
            endPoint = [float(x) for x in node.find('ENDPOINT').text.split(',')]
1707

    
1708
            configs = AppDocData.instance().getConfigs('Data', 'Grid')
1709
            grid = int(configs[0].value) if 1 == len(configs) else -1
1710
            if grid == 1:
1711
                startPoint = [round(startPoint[0]), round(startPoint[1])]
1712
                endPoint = [round(endPoint[0]), round(endPoint[1])]
1713

    
1714
            item = QEngineeringLineItem(vertices=[startPoint, endPoint], uid=uid)
1715
            if owner:
1716
                item._owner = owner
1717
            item.setVisible(False)
1718
            item.lineType = node.find('TYPE').text if node.find('TYPE') is not None else 'Secondary'
1719
            # assign area
1720
            if node.find('AREA') is None:
1721
                appDocData = AppDocData.instance()
1722
                for area in appDocData.getAreaList():
1723
                    if area.contains(startPoint) and area.contains(endPoint):
1724
                        item.area = area.name
1725
                        break
1726
            else:
1727
                item.area = node.find('AREA').text
1728
            # up to here
1729

    
1730
            thicknessNode = node.find('THICKNESS')
1731
            item.thickness = int(thicknessNode.text) if thicknessNode is not None and thicknessNode.text and thicknessNode.text != 'None' else None
1732

    
1733
            flowMarkNode = node.find('FLOWMARK')
1734
            item.flowMark = int(flowMarkNode.text) if flowMarkNode is not None and flowMarkNode.text and flowMarkNode.text != 'None' else None
1735

    
1736
            connectors = node.find('CONNECTORS')
1737
            if connectors is not None:
1738
                iterIndex = 0
1739
                for connector in connectors.iter('CONNECTOR'):
1740
                    item.connectors[iterIndex].parse_xml(connector)
1741
                    iterIndex += 1
1742

    
1743
            # get associations
1744
            attributeValue = node.find('ASSOCIATIONS')
1745
            if attributeValue is not None:
1746
                for assoc in attributeValue.iter('ASSOCIATION'):
1747
                    _type = assoc.attrib['TYPE']
1748
                    if not _type in item._associations:
1749
                        item._associations[_type] = []
1750
                    item._associations[_type].append(uuid.UUID(assoc.text))
1751
            # up to here
1752

    
1753
            attributes = node.find('SYMBOLATTRIBUTES')
1754
            if attributes is not None:
1755
                for attr in attributes.iter('ATTRIBUTE'):
1756
                    _attr = SymbolAttr.fromXml(attr)
1757
                    item.attrs[_attr] = attr.text
1758

    
1759
            for prop_node in node.iter('PROPERTY'):
1760
                matches = [prop for prop in item._properties.keys() if
1761
                            prop.Attribute == prop_node.attrib['Attribute']]
1762
                if matches:
1763
                    item._properties[matches[0]] = matches[0].parse_value(prop_node.text)# if prop_node.text else ''
1764

    
1765
        except Exception as ex:
1766
            from App import App
1767
            from AppDocData import MessageType
1768

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

    
1773
        #print(str(item.length()))
1774
        return item if item.length() > 1 else None
1775

    
1776
    '''
1777
        @brief      generate xml code
1778
        @author     humkyung
1779
        @date       2018.04.23
1780
        @history    humkyung 2018.06.27 write line type to xml
1781
                    humkyung 2018.07.23 write connected item's uid to xml
1782
    '''
1783

    
1784
    def toXml(self):
1785
        from xml.etree.ElementTree import Element, SubElement, dump, ElementTree
1786
        from LineTypeConditions import LineTypeConditions
1787
        from SymbolAttr import SymbolAttr
1788

    
1789
        try:
1790
            node = Element('LINE')
1791
            node.attrib['OWNER'] = str(self._owner) if self._owner else 'None'
1792
            uidNode = Element('UID')
1793
            uidNode.text = str(self.uid)
1794
            node.append(uidNode)
1795

    
1796
            startPt = self.start_point()
1797
            endPt = self.end_point()
1798

    
1799
            startNode = Element('STARTPOINT')
1800
            startNode.text = '{},{}'.format(startPt[0], startPt[1])
1801
            node.append(startNode)
1802

    
1803
            endNode = Element('ENDPOINT')
1804
            endNode.text = '{},{}'.format(endPt[0], endPt[1])
1805
            node.append(endNode)
1806

    
1807
            typeNode = Element('TYPE')
1808
            typeNode.text = self.lineType
1809
            for lineType in LineTypeConditions.items():
1810
                if self.lineType == lineType.name:
1811
                    typeNode.attrib['TYPEUID'] = str(lineType)
1812
                    break
1813
            node.append(typeNode)
1814

    
1815
            areaNode = Element('AREA')
1816
            areaNode.text = self.area
1817
            node.append(areaNode)
1818

    
1819
            thicknessNode = Element('THICKNESS')
1820
            thicknessNode.text = str(self.thickness)
1821
            node.append(thicknessNode)
1822

    
1823
            flowMarkNode = Element('FLOWMARK')
1824
            flowMarkNode.text = str(self.flowMark)
1825
            node.append(flowMarkNode)
1826

    
1827
            connectorsNode = Element('CONNECTORS')
1828
            for connector in self.connectors:
1829
                connectorsNode.append(connector.toXml())
1830
            node.append(connectorsNode)
1831

    
1832
            attributeValueNode = Element('ASSOCIATIONS')
1833
            for assoc in self.associations():
1834
                assoc_node = Element('ASSOCIATION')
1835
                assoc_node.attrib['TYPE'] = QEngineeringAbstractItem.assoc_type(assoc)
1836
                assoc_node.text = str(assoc.uid)
1837
                attributeValueNode.append(assoc_node)
1838
            node.append(attributeValueNode)
1839

    
1840
            properties_node = Element('PROPERTIES')
1841
            for prop, value in self.properties.items():
1842
                prop_node = prop.toXml()
1843
                prop_node.text = str(value) if value else ''
1844
                properties_node.append(prop_node)
1845
            node.append(properties_node)
1846

    
1847
            attributesNode = Element('SYMBOLATTRIBUTES')
1848
            _attrs = self.getAttributes()
1849
            for attr in _attrs:
1850
                if type(attr) is SymbolAttr:
1851
                    _node = attr.toXml()
1852
                    _node.text = str(_attrs[attr])
1853
                    attributesNode.append(_node)
1854

    
1855
            node.append(attributesNode)
1856

    
1857
            # up to here
1858
        except Exception as ex:
1859
            from App import App
1860
            from AppDocData import MessageType
1861

    
1862
            message = 'error occurred({}) in {}:{}'.format(ex, sys.exc_info()[-1].tb_frame.f_code.co_filename,
1863
                                                           sys.exc_info()[-1].tb_lineno)
1864
            App.mainWnd().addMessage.emit(MessageType.Error, message)
1865
            return None
1866

    
1867
        return node
1868

    
1869
    def to_svg(self, parent) -> list:
1870
        """convert line item to svg"""
1871
        from xml.etree.ElementTree import Element, SubElement, dump, ElementTree
1872
        from App import App
1873
        from AppDocData import AppDocData
1874

    
1875
        res = []
1876
        try:
1877
            app_doc_data = AppDocData.instance()
1878
            prj = app_doc_data.getCurrentProject()
1879

    
1880
            node = Element('g')
1881
            node.attrib['id'] = str(self.uid)
1882
            node.attrib['stroke'] = self.pen().color().name()
1883
            node.attrib['stroke-width'] = str(int(self.pen().width()*0.5))
1884

    
1885
            line_style_map = {Qt.DotLine: '2', Qt.DashLine: '4 2',
1886
                              Qt.DashDotLine: '4 2 2', Qt.DashDotDotLine: '4 2 2 2 2 2'}
1887
            line_type_style = self.line_type_style
1888
            if line_type_style[3] in line_style_map:
1889
                node.attrib['stroke-dasharray'] = line_style_map[line_type_style[3]]
1890

    
1891
            if not parent:
1892
                trans = QTransform()
1893
            else:
1894
                trans, _ = parent.sceneTransform().inverted()
1895

    
1896
            node.attrib['transform'] = f"matrix(" \
1897
                                       f"{trans.m11()},{trans.m12()}," \
1898
                                       f"{trans.m21()},{trans.m22()}," \
1899
                                       f"{trans.m31()},{trans.m32()}" \
1900
                                       f")"
1901

    
1902
            polyline = Element('polyline')
1903
            polyline.attrib['fill'] = 'none'
1904
            start_pt = self.start_point()
1905
            end_pt = self.end_point()
1906
            polyline.attrib['points'] = f"{start_pt[0]},{start_pt[1]} " \
1907
                                        f"{end_pt[0]},{end_pt[1]}"
1908
            node.append(polyline)
1909

    
1910
            res.append(node)
1911
        except Exception as ex:
1912
            from App import App
1913
            from AppDocData import MessageType
1914
            message = 'error occurred({}) in {}:{}'.format(ex, sys.exc_info()[-1].tb_frame.f_code.co_filename,
1915
                                                          sys.exc_info()[-1].tb_lineno)
1916
            App.mainWnd().addMessage.emit(MessageType.Error, message)
1917

    
1918
        return res
1919

    
1920
    def toSql_return_separately(self):
1921
        """ generate sql phrase to save line to database """
1922
        import uuid
1923
        from AppDocData import AppDocData
1924

    
1925
        res = []
1926
        resLater = []
1927

    
1928
        app_doc_data = AppDocData.instance()
1929
        cols = ['UID', 'Drawings_UID', 'Symbol_UID', 'X', 'Y', 'Width', 'Height', 'Rotation', 'Area', 'Owner',
1930
                'SpecialItemTypes_UID', 'Freeze']
1931
        values = ['?', '?', "(select UID from Symbol where Name='Line' and SymbolType_UID=-1)", '?', '?', '?', '?', '?',
1932
                  '?', '?', '?', '?']
1933

    
1934
        rect = self.sceneBoundingRect()
1935
        
1936
        # freeze and bidirectional is stored at freeze column
1937
        freeze_bidirectional = 0
1938
        if self.prop('Freeze') and self.prop('Bidirectional'):
1939
            freeze_bidirectional = 3
1940
        elif not self.prop('Freeze') and self.prop('Bidirectional'):
1941
            freeze_bidirectional = 2
1942
        elif self.prop('Freeze') and not self.prop('Bidirectional'):
1943
            freeze_bidirectional = 1
1944

    
1945
        param = [
1946
            (str(self.uid), str(app_doc_data.activeDrawing.UID), rect.x(), rect.y(), rect.width(), rect.height(), 0,
1947
             self.area, str(self.owner) if self.owner else None,
1948
             str(self.special_item_type) if self.special_item_type else None, freeze_bidirectional)]
1949
        sql = 'insert into Components({}) values({})'.format(','.join(cols), ','.join(values))
1950
        res.append((sql, tuple(param)))
1951

    
1952
        _attrs = self.getAttributes()
1953
        if _attrs:
1954
            cols = ['UID', 'Components_UID', 'SymbolAttribute_UID', 'Value', 'Association_UID', 'Freeze']
1955
            values = ['?', '?', '?', '?', '?', '?']
1956
            params = []
1957
            for key in _attrs.keys():
1958
                if key.AttributeType != 'Spec':
1959
                    params.append((str(uuid.uuid4()), str(self.uid), str(key.UID), str(_attrs[key]), str(key.AssocItem),
1960
                                   str(key.Freeze)))
1961
                elif key.AttributeType == 'Spec':
1962
                    if type(_attrs[key]) is not list: continue
1963
                    params.append((str(uuid.uuid4()), str(self.uid), str(key.UID), (str(_attrs[key][0]) + ',' + str(_attrs[key][1])), str(key.AssocItem),
1964
                                   str(key.Freeze)))
1965
            sql = 'insert into Attributes({}) values({})'.format(','.join(cols), ','.join(values))
1966
            res.append((sql, tuple(params)))
1967

    
1968
        if self.associations():
1969
            cols = ['UID', '[Type]', 'Components_UID', 'Association']
1970
            values = ['?', '?', '?', '?']
1971
            params = []
1972
            for assoc in self.associations():
1973
                params.append(
1974
                    (str(uuid.uuid4()), QEngineeringAbstractItem.assoc_type(assoc), str(self.uid), str(assoc.uid)))
1975
            sql = 'insert into Associations({}) values({})'.format(','.join(cols), ','.join(values))
1976
            resLater.append((sql, tuple(params)))
1977

    
1978
        # save connectors to database
1979
        cols = ['Components_UID', '[Index]', 'X', 'Y', 'Connected', 'Connected_At']
1980
        values = ['?', '?', '?', '?', '?', '?']
1981
        params = []
1982
        index = 1
1983
        for connector in self.connectors:
1984
            params.append((  # str(connector.uid),
1985
                str(self.uid), index, connector.center()[0], connector.center()[1], \
1986
                str(connector.connectedItem.uid) if connector.connectedItem else None, \
1987
                str(connector._connected_at)))
1988
            index += 1
1989
        sql = 'insert into Points({}) values({})'.format(','.join(cols), ','.join(values))
1990
        resLater.append((sql, tuple(params)))
1991
        # up to here
1992

    
1993
        # save attributes
1994
        cols = ['UID', 'Components_UID', 'SymbolAttribute_UID', 'Value']
1995
        values = ['?', '?', "(select UID from SymbolAttribute where Attribute='LineType' and SymbolType_UID=-1)", '?']
1996
        param = [(str(uuid.uuid4()), str(self.uid), self.lineType)]
1997
        sql = 'insert into Attributes({}) values({})'.format(','.join(cols), ','.join(values))
1998
        res.append((sql, tuple(param)))
1999
        values = ['?', '?', "(select UID from SymbolAttribute where Attribute='Thickness' and SymbolType_UID=-1)", '?']
2000
        param = [(str(uuid.uuid4()), str(self.uid), str(self.thickness))]
2001
        sql = 'insert into Attributes({}) values({})'.format(','.join(cols), ','.join(values))
2002
        res.append((sql, tuple(param)))
2003
        values = ['?', '?', "(select UID from SymbolAttribute where Attribute='FlowMark' and SymbolType_UID=-1)", '?']
2004
        param = [(str(uuid.uuid4()), str(self.uid), str(self.flowMark))]
2005
        sql = 'insert into Attributes({}) values({})'.format(','.join(cols), ','.join(values))
2006
        res.append((sql, tuple(param)))
2007
        # up to here
2008

    
2009
        return res, resLater
2010

    
2011
    '''
2012
        @brief      Delete Line Item from scene
2013
        @author     Jeongwoo
2014
        @date       2018.05.29
2015
        @history    2018.05.29  Add parameter 'self' / Make comments emit()
2016
    '''
2017

    
2018
    def deleteLineItemFromScene(self):
2019
        self.scene().removeItem(self)
2020

    
2021
    def setColor(self, color):
2022
        """Set Color. Override QEngineeringAbstractItem's"""
2023
        if color.upper() != self.pen().color().name().upper():
2024
            c = QColor()
2025
            c.setNamedColor(color)
2026
            _pen = self.pen()
2027
            _pen.setColor(c)
2028
            self.setPen(_pen)
2029
            #self.update()
2030

    
2031
    def clear_labels(self):
2032
        """ clear spec labels """
2033
        attrs = self.getAttributes()
2034
        index = 0
2035
        for key in attrs.keys():
2036
            #if index >= 6:
2037
            #    break
2038
            if key.AssocItem and not key.Freeze:
2039
                key.AssocItem.owner = None
2040
                self.remove_assoc_item(key.AssocItem)
2041
                key.AssocItem = None
2042
            if not key.Freeze:
2043
                attrs[key] = ''
2044
            index += 1
2045
    
2046
    def update_flow_mark(self, position, length):
2047
        """ update flow mark for flow arrow """
2048

    
2049
        to_item = self.connectors[1].connectedItem
2050
        if type(to_item) is QEngineeringLineItem and self.length() > length and not self.isParallel(to_item):
2051
            self.flowMark = position
2052

    
2053
    @staticmethod
2054
    def update_arrows(lines):
2055
        for line in lines:
2056
            line.update_arrow()
2057

    
2058
        return lines
2059

    
2060
    def update_arrow(self):
2061
        """ update flow arrow """
2062

    
2063
        from EngineeringArrowItem import QEngineeringArrowItem
2064

    
2065
        if self.length() < 0.01:
2066
            return
2067

    
2068
        start = self.line().p1()
2069
        end = self.line().p2()
2070

    
2071
        dx = end.x() - start.x()
2072
        dy = end.y() - start.y()
2073
        _dir = [dx / self.length(), dy / self.length()]
2074

    
2075
        arrow_size = QEngineeringLineItem.ARROW_SIZE * 0.25
2076

    
2077
        # allow flow mark at signal line
2078
        #if not self.is_piping():
2079
        #    self.flowMark = None
2080

    
2081
        if self.flowMark:
2082
            arrow_size *= 2
2083
            end = QPointF(start.x() + dx * self.flowMark / 100, start.y() + dy * self.flowMark / 100)
2084

    
2085
        perpendicular = (-_dir[1], _dir[0])
2086
        polygon = QPolygonF()
2087
        if not self.prop('Bidirectional'):
2088
            polygon.append(QPointF(end.x() - _dir[0] * QEngineeringLineItem.ARROW_SIZE + perpendicular[0] * arrow_size,
2089
                                end.y() - _dir[1] * QEngineeringLineItem.ARROW_SIZE + perpendicular[1] * arrow_size))
2090
            polygon.append(QPointF(end.x() - _dir[0] * QEngineeringLineItem.ARROW_SIZE - perpendicular[0] * arrow_size,
2091
                                end.y() - _dir[1] * QEngineeringLineItem.ARROW_SIZE - perpendicular[1] * arrow_size))
2092
            polygon.append(end)
2093
            polygon.append(polygon[0])  # close polygon
2094
        else:
2095
            polygon.append(QPointF(end.x() - _dir[0] * QEngineeringLineItem.ARROW_SIZE + perpendicular[0] * arrow_size,
2096
                                end.y() - _dir[1] * QEngineeringLineItem.ARROW_SIZE + perpendicular[1] * arrow_size))
2097
            polygon.append(end)
2098
            polygon.append(QPointF(end.x() - _dir[0] * QEngineeringLineItem.ARROW_SIZE - perpendicular[0] * arrow_size,
2099
                                end.y() - _dir[1] * QEngineeringLineItem.ARROW_SIZE - perpendicular[1] * arrow_size))
2100
            polygon.append(QPointF(end.x() - _dir[0] * 2 * QEngineeringLineItem.ARROW_SIZE, end.y() - _dir[1] * 2 * QEngineeringLineItem.ARROW_SIZE))
2101
            polygon.append(polygon[0])  # close polygon
2102

    
2103
        if not hasattr(self, '_arrow'):
2104
            self._arrow = QEngineeringArrowItem(polygon, self)
2105
        else:
2106
            self._arrow.setPolygon(polygon)
2107

    
2108
        isFreezed = False
2109
        for prop, value in self.properties.items():
2110
            if prop.Attribute == 'Freeze':
2111
                if value and str(value) == 'True':
2112
                    isFreezed = True
2113
                break
2114
        
2115
        if isFreezed:
2116
            self._arrow.setFlag(QGraphicsItem.ItemStacksBehindParent, False)
2117
            self._arrow.setBrush(Qt.green)
2118
        elif self.prop('Bidirectional'):
2119
            self._arrow.setFlag(QGraphicsItem.ItemStacksBehindParent, False)
2120
            self._arrow.setBrush(Qt.yellow)
2121
        elif self.flowMark:
2122
            self._arrow.setFlag(QGraphicsItem.ItemStacksBehindParent, True)
2123
            self._arrow.setBrush(Qt.red)
2124
        else:
2125
            self._arrow.setFlag(QGraphicsItem.ItemStacksBehindParent, True)
2126
            self._arrow.setBrush(Qt.blue)
2127
        self._arrow.update()
2128

    
2129
    def onConnectorPosChaned(self, connector):
2130
        """update line shape when connector is moved"""
2131

    
2132
        start = self.connectors[0].center()
2133
        end = self.connectors[1].center()
2134
        self.set_line([[start[0], start[1]], [end[0], end[1]]])
2135

    
2136
        if hasattr(self.scene(), 'contents_changed'):
2137
            self.scene().contents_changed.emit()
2138

    
2139
        # register resize command
2140
        pt_start = self.connectors[0].pressed_position if self.connectors[0].pressed_position else None
2141
        pt_end = self.connectors[-1].pressed_position if self.connectors[-1].pressed_position else None
2142
        if pt_start or pt_end:
2143
            from ResizeCommand import ResizeCommand
2144
            cmd = ResizeCommand(self.scene(), self,
2145
                                [pt_start if pt_start else
2146
                                 QPointF(self.connectors[0].center()[0], self.connectors[0].center()[1]),
2147
                                 pt_end if pt_end else
2148
                                 QPointF(self.connectors[-1].center()[0], self.connectors[-1].center()[-1])])
2149
            self.scene().undo_stack.push(cmd)
2150

    
2151
    '''
2152
        @brief      
2153
        @author     humkyung
2154
        @date       2018.07.24
2155
    '''
2156

    
2157
    def mousePressEvent(self, event):
2158
        import math
2159

    
2160
        if event.buttons() == Qt.LeftButton:
2161
            pos = event.scenePos()
2162
            ptStart = self.start_point()
2163
            dx = ptStart[0] - pos.x()
2164
            dy = ptStart[1] - pos.y()
2165
            if math.sqrt(dx * dx + dy * dy) < 10:
2166
                self._selectedIndex = 0
2167
                return
2168

    
2169
            ptEnd = self.end_point()
2170
            dx = ptEnd[0] - pos.x()
2171
            dy = ptEnd[1] - pos.y()
2172
            if math.sqrt(dx * dx + dy * dy) < 10:
2173
                self._selectedIndex = 1
2174
                return
2175

    
2176
            self._selectedIndex = -1
2177

    
2178
        QGraphicsLineItem.mousePressEvent(self, event)
2179

    
2180
    def mouseReleaseEvent(self, event):
2181
        self._selectedIndex = -1
2182

    
2183
        QGraphicsLineItem.mouseReleaseEvent(self, event)
2184

    
2185
    def evaluatedBidirectional(self):
2186
        isBidirectional = self.prop('Bidirectional')
2187
        if isBidirectional:
2188
            return True
2189
        
2190
        if self.owner:
2191
            matches = [run for run in self.owner.runs if self in run.items]
2192
            if matches:
2193
                for line in [item for item in matches[0].items if type(item) is QEngineeringLineItem and item is not self]:
2194
                    if line.prop('Bidirectional'):
2195
                        return True
2196
                    
2197
        return False
2198
    
2199
    def contextMenuEvent(self, event):
2200
        from LineTypeConditions import LineTypeConditions
2201

    
2202
        items = [item for item in self.scene().selectedItems() if type(item) is QEngineeringLineItem]
2203
        if len(items) > 0 and self in items:
2204
            menu = QMenu()
2205

    
2206
            if len(items) == 1:
2207
                showAction = QAction('Show Onwer', None)
2208
                showAction.triggered.connect(self.contextShow)
2209
                menu.addAction(showAction)
2210

    
2211
                splitAction = QAction('Split', None)
2212
                splitAction.triggered.connect(lambda: self.contextSplit(event.pos()))
2213
                menu.addAction(splitAction)
2214

    
2215
                freezeAttrAction = QAction('Freeze Attributes(G)', None)
2216
                freezeAttrAction.triggered.connect(self.freeze_attriute)
2217
                menu.addAction(freezeAttrAction)
2218

    
2219
            freezeAction = QAction('Set Freeze', None) if not self.prop('Freeze') else QAction('Unset Freeze', None)
2220
            freezeAction.triggered.connect(self.contextFreeze)
2221
            menu.addAction(freezeAction)
2222

    
2223
            bidirectionalAction = QAction('Set Bidirectional', None) if not self.prop('Bidirectional') else QAction('Unset Bidirectional', None)
2224
            bidirectionalAction.triggered.connect(self.contextBidirectional)
2225
            menu.addAction(bidirectionalAction)
2226

    
2227
            if len(items) > 1:
2228
                if len(items) == 2:
2229
                    make3PointMenu = QMenu(menu)
2230
                    make3PointMenu.setTitle('3 Point')
2231
                    inAction = QAction('In', None)
2232
                    inAction.triggered.connect(self.contextIn)
2233
                    make3PointMenu.addAction(inAction)
2234
                    outAction = QAction('Out', None)
2235
                    outAction.triggered.connect(self.contextOut)
2236
                    make3PointMenu.addAction(outAction)
2237
                    menu.addMenu(make3PointMenu)
2238

    
2239
                    intersectionAction = QAction('Intersect', None)
2240
                    intersectionAction.triggered.connect(self.contextIntersection)
2241
                    menu.addAction(intersectionAction)
2242

    
2243
                mergeAction = QAction('Merge(M)', None)
2244
                mergeAction.triggered.connect(self.contextMerge)
2245
                menu.addAction(mergeAction)
2246

    
2247
            reverseAction = QAction('Reverse(C)', None)
2248
            reverseAction.triggered.connect(self.contextReverse)
2249
            menu.addAction(reverseAction)
2250

    
2251
            arrowAction = QAction('Arrow(A)', None)
2252
            arrowAction.triggered.connect(self.contextArrow)
2253
            menu.addAction(arrowAction)
2254

    
2255
            lineTypeChangeMenu = QMenu(menu)
2256
            lineTypeChangeMenu.setTitle('Change Line Type')
2257

    
2258
            index = 0
2259
            lineTypeActions = []
2260
            typeNames = []
2261
            for condition in reversed(LineTypeConditions.items()):
2262
                lineTypeActions.append(QAction(condition.name, None))
2263
                typeNames.append(condition.name)
2264
                lineTypeChangeMenu.addAction(lineTypeActions[index])
2265
                index = index + 1
2266

    
2267
            binding = [lambda index=index: lineTypeActions[index].triggered.connect(lambda: self.contextLineType(typeNames[index])) for index in range(len(lineTypeActions))]
2268
            
2269
            for index in range(len(lineTypeActions)):
2270
                binding[index]()
2271

    
2272
            menu.addMenu(lineTypeChangeMenu)
2273

    
2274
            deleteAction = QAction('Delete(E)', None)
2275
            deleteAction.triggered.connect(self.contextDelete)
2276
            menu.addAction(deleteAction)
2277

    
2278
            ShowOnOtherAppAction = QAction('Show on other App', None)
2279
            ShowOnOtherAppAction.triggered.connect(self.contextShowOnOtherApp)
2280
            menu.addAction(ShowOnOtherAppAction)
2281

    
2282
            menu.exec_(event.screenPos())
2283

    
2284
    def contextIntersection(self):
2285
        from LineDetector import LineDetector
2286
        from AppDocData import AppDocData
2287

    
2288
        items = [item for item in self.scene().selectedItems() if type(item) is QEngineeringLineItem]
2289
        if len(items) != 2 or self not in items:
2290
            return
2291
        
2292
        app_doc_data = AppDocData.instance()
2293
        detector = LineDetector(app_doc_data.imgSrc)
2294
        line = [line for line in items if line is not self][0]
2295

    
2296
        detector.connectLineToLine(self, line, 150)
2297

    
2298
    def contextIn(self):
2299
        self.make3Point(True)
2300

    
2301
    def contextOut(self):
2302
        self.make3Point(False)
2303

    
2304
    def contextLineType(self, line_type):
2305
        from App import App
2306

    
2307
        App.mainWnd().change_selected_line_type(line_type)
2308

    
2309
    def contextShow(self):
2310
        from EngineeringLineNoTextItem import QEngineeringLineNoTextItem
2311

    
2312
        if type(self.owner) is QEngineeringLineNoTextItem:
2313
            self.owner.contextHighlight(self.owner)
2314

    
2315
    def contextSplit(self, pos):
2316
        from App import App
2317
        
2318
        _pos = [round(pos.x()), round(pos.y())]
2319
        if abs(_pos[0] - self.start_point()[0]) > abs(_pos[1] - self.start_point()[1]):
2320
            _pos[1] = self.start_point()[1]
2321
        else:
2322
            _pos[0] = self.start_point()[0]
2323

    
2324
        savedConnectors = []
2325
        for _connector in self.connectors:
2326
            if _connector.connectedItem and _connector._connected_at == QEngineeringAbstractItem.CONNECTED_AT_PT:
2327
                _connectors2 = [_connector2 for _connector2 in _connector.connectedItem.connectors if _connector2.connectedItem is self and _connector2._connected_at == QEngineeringAbstractItem.CONNECTED_AT_PT]
2328
                if _connectors2:
2329
                    savedConnectors.append(_connectors2[0])
2330
                    continue
2331
            savedConnectors.append(None)
2332

    
2333
        inLine = QEngineeringLineItem(vertices=[self.start_point(), _pos])
2334
        inLine.transfer.onRemoved.connect(App.mainWnd().itemRemoved)
2335
        inLine.lineType = self.lineType
2336

    
2337
        outLine = QEngineeringLineItem(vertices=[_pos, self.end_point()])
2338
        outLine.transfer.onRemoved.connect(App.mainWnd().itemRemoved)
2339
        outLine.lineType = self.lineType
2340

    
2341
        inLine.connectors[0].connect(self.connectors[0].connectedItem, self.connectors[0]._connected_at)
2342
        inLine.connectors[1].connect(outLine)
2343
        outLine.connectors[0].connect(inLine)
2344
        outLine.connectors[1].connect(self.connectors[1].connectedItem, self.connectors[1]._connected_at)
2345

    
2346
        if savedConnectors[0]:
2347
            savedConnectors[0].connect(inLine)
2348
        if savedConnectors[1]:
2349
            savedConnectors[1].connect(outLine)
2350

    
2351
        self.scene().addItem(inLine)
2352
        self.scene().addItem(outLine)
2353
        self.transfer.onRemoved.emit(self)
2354

    
2355
    def contextFreeze(self):
2356
        from App import App
2357

    
2358
        scene = self.scene()
2359
        if scene:
2360
            isFreeze = self.prop('Freeze')
2361
            lineItems = [symbol for symbol in scene.selectedItems() if type(symbol) is QEngineeringLineItem]
2362

    
2363
            for line in lineItems:
2364
                for prop, value in line.properties.items():
2365
                    if prop.Attribute == 'Freeze':
2366
                        line.set_property(prop.Attribute, not isFreeze)
2367
                line.update_arrow()
2368

    
2369
            App.mainWnd().resultPropertyTableWidget.onSuccessSelectAttribute(self)
2370

    
2371
    def contextBidirectional(self):
2372
        from App import App
2373

    
2374
        scene = self.scene()
2375
        if scene:
2376
            isBidirectional = self.prop('Bidirectional')
2377
            lineItems = [symbol for symbol in scene.selectedItems() if type(symbol) is QEngineeringLineItem]
2378

    
2379
            for line in lineItems:
2380
                for prop, value in line.properties.items():
2381
                    if prop.Attribute == 'Bidirectional':
2382
                        line.set_property(prop.Attribute, not isBidirectional)
2383
                line.update_arrow()
2384

    
2385
            App.mainWnd().resultPropertyTableWidget.onSuccessSelectAttribute(self)
2386

    
2387
    def contextDelete(self):
2388
        event = QKeyEvent(QEvent.KeyPress, Qt.Key_Delete, Qt.NoModifier)
2389
        self.scene().keyPressEvent(event)
2390

    
2391
    def contextShowOnOtherApp(self):
2392
        from TcpServer import TcpSocket
2393
        from AppDocData import AppDocData
2394

    
2395
        app_doc_data = AppDocData.instance()
2396
        configs = app_doc_data.getConfigs('app', 'conn port')
2397
        port = 3030
2398
        if configs and 1 == len(configs):
2399
            port = int(configs[0].value)
2400
        tcpserver = TcpSocket(port)
2401

    
2402
        origin_x = (self.start_point()[0] + self.end_point()[0]) * 0.5
2403
        origin_y = (self.start_point()[1] + self.end_point()[1]) * 0.5
2404
        min_x = min(self.start_point()[0], self.end_point()[0])
2405
        min_y = min(self.start_point()[1], self.end_point()[1])
2406
        max_x = max(self.start_point()[0], self.end_point()[0])
2407
        max_y = max(self.start_point()[1], self.end_point()[1])
2408
        size_x = max_x - min_x
2409
        if not size_x: size_x = 1
2410
        size_y = max_y - min_y
2411
        if not size_y: size_y = 1
2412
        tcpserver.sendMessage(f"{str(self.uid)},{origin_x},{origin_y},{size_x},{size_y},"
2413
                              f"{app_doc_data.imgName}")
2414

    
2415
    def contextArrow(self):
2416
        event = QKeyEvent(QEvent.KeyPress, Qt.Key_A, Qt.NoModifier)
2417
        self.scene().keyPressEvent(event)
2418

    
2419
    def contextReverse(self):
2420
        event = QKeyEvent(QEvent.KeyPress, Qt.Key_C, Qt.NoModifier)
2421
        self.scene().keyPressEvent(event)
2422

    
2423
    def contextMerge(self):
2424
        event = QKeyEvent(QEvent.KeyPress, Qt.Key_M, Qt.NoModifier)
2425
        self.keyPressEvent(event)
2426

    
2427
    def make3Point(self, start):
2428
        from App import App
2429
        
2430
        items = [item for item in self.scene().selectedItems() if type(item) is QEngineeringLineItem]
2431
        if len(items) != 2 or self not in items:
2432
            return
2433
        
2434
        line = [line for line in items if line is not self][0]
2435
        _connectors = [_connector for _connector in self.connectors if _connector.connectedItem == line and _connector._connected_at == QEngineeringAbstractItem.CONNECTED_AT_BODY]
2436
        if not _connectors:
2437
            return
2438
        
2439
        savedConnectors = []
2440
        for _connector in line.connectors:
2441
            if _connector.connectedItem and _connector._connected_at == QEngineeringAbstractItem.CONNECTED_AT_PT:
2442
                _connectors2 = [_connector2 for _connector2 in _connector.connectedItem.connectors if _connector2.connectedItem is line and _connector2._connected_at == QEngineeringAbstractItem.CONNECTED_AT_PT]
2443
                if _connectors2:
2444
                    savedConnectors.append(_connectors2[0])
2445
                    continue
2446
            savedConnectors.append(None)
2447
        
2448
        connector = _connectors[0]
2449

    
2450
        inLine = QEngineeringLineItem(vertices=[line.start_point(), connector.center()])
2451
        inLine.transfer.onRemoved.connect(App.mainWnd().itemRemoved)
2452
        inLine.lineType = line.lineType
2453

    
2454
        outLine = QEngineeringLineItem(vertices=[connector.center(), line.end_point()])
2455
        outLine.transfer.onRemoved.connect(App.mainWnd().itemRemoved)
2456
        outLine.lineType = line.lineType
2457

    
2458
        if start:
2459
            inLine.connectors[0].connect(line.connectors[0].connectedItem, line.connectors[0]._connected_at)
2460
            inLine.connectors[1].connect(self)
2461
            outLine.connectors[0].connect(self, QEngineeringAbstractItem.CONNECTED_AT_BODY)
2462
            outLine.connectors[1].connect(line.connectors[1].connectedItem, line.connectors[1]._connected_at)
2463
            connector.connect(inLine)
2464
        else:
2465
            inLine.connectors[0].connect(line.connectors[0].connectedItem, line.connectors[0]._connected_at)
2466
            inLine.connectors[1].connect(self, QEngineeringAbstractItem.CONNECTED_AT_BODY)
2467
            outLine.connectors[0].connect(self)
2468
            outLine.connectors[1].connect(line.connectors[1].connectedItem, line.connectors[1]._connected_at)
2469
            connector.connect(outLine)
2470

    
2471
        if savedConnectors[0]:
2472
            savedConnectors[0].connect(inLine)
2473
        if savedConnectors[1]:
2474
            savedConnectors[1].connect(outLine)
2475

    
2476
        self.scene().addItem(inLine)
2477
        self.scene().addItem(outLine)
2478
        line.transfer.onRemoved.emit(line)
2479

    
2480
'''
2481
    @brief      The class transfer pyqtSignal Event. Cause Subclass of QGraphicsRectItem can't use pyqtSignal
2482
    @author     Jeongwoo
2483
    @date       2018.06.18
2484
'''
2485
class Transfer(QObject):
2486
    onRemoved = pyqtSignal(QGraphicsItem)
2487

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