프로젝트

일반

사용자정보

통계
| 개정판:

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

이력 | 보기 | 이력해설 | 다운로드 (104 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
        elif event.key() == Qt.Key_B:
1517
            self.bind_close_items()
1518

    
1519
    def bind_close_items(self):
1520
        """ connect close item by pressing B """
1521
        from SymbolSvgItem import SymbolSvgItem
1522
        from EngineeringNozzleItem import QEngineeringNozzleItem
1523
        from EngineeringEquipmentItem import QEngineeringEquipmentItem
1524
        from shapely.geometry import Point
1525
        from AppDocData import AppDocData
1526

    
1527
        scene = self.scene()
1528
        app_doc_data = AppDocData.instance()
1529
        if scene:
1530
            configs = app_doc_data.getConfigs('Line Detector', 'Length to connect line')
1531
            toler = int(configs[0].value) * 2 if configs else 20
1532

    
1533
            if self.length() < 2 * toler:
1534
                return
1535

    
1536
            items = [item for item in scene.selectedItems() if issubclass(type(item), SymbolSvgItem)]
1537

    
1538
            for l_connector in self.connectors:
1539
                if l_connector.connectedItem:
1540
                    continue
1541

    
1542
                _pt1 = Point(l_connector.center()[0], l_connector.center()[1])
1543
                dist = sys.maxsize
1544
                selected = None
1545
                dx = 0
1546
                dy = 0
1547

    
1548
                for item in items:
1549
                    for i_connector in item.connectors:
1550
                        if i_connector.connectedItem:
1551
                            continue
1552

    
1553
                        _pt2 = Point(i_connector.center()[0], i_connector.center()[1])
1554
                        length = _pt1.distance(_pt2)
1555
                        if length < toler and length < dist:
1556
                            selected = item
1557
                            dx = _pt1.x - _pt2.x
1558
                            dy = _pt1.y - _pt2.y
1559

    
1560
                if selected:
1561
                    res = self.connect_if_possible(selected, toler)
1562
                    if res:
1563
                        selected.moveBy(dx, dy)
1564

    
1565
    def freeze_attriute(self, flag=None):
1566
        from App import App
1567

    
1568
        if flag is None:
1569
            freeze = True
1570
            for _attr, _value in self.attrs.items():
1571
                if _attr.Freeze:
1572
                    freeze = False
1573
                    break
1574
            
1575
            for _attr, _value in self.attrs.items():
1576
                _attr.Freeze = freeze
1577

    
1578
            App.mainWnd().resultPropertyTableWidget.onSuccessSelectAttribute(self)
1579
        else:
1580
            for _attr, _value in self.attrs.items():
1581
                _attr.Freeze = flag
1582

    
1583
    def toggleFlowMark(self):
1584
        from AppDocData import AppDocData
1585

    
1586
        if self.flowMark:
1587
            self.flowMark = None
1588
        else:
1589
            configs = AppDocData.instance().getConfigs('Flow Mark', 'Position')
1590
            self.flowMark = int(configs[0].value) if 1 == len(configs) else 100
1591
        self.update_arrow()
1592

    
1593
    def shape(self):
1594
        """return shape for non-rectangle shape"""
1595
        x1, y1, x2, y2 = self.line().x1(), self.line().y1(), self.line().x2(), self.line().y2()
1596
        path = QPainterPath()
1597
        path.moveTo(x1, y1)
1598
        path.lineTo(x2, y2)
1599

    
1600
        stroke = QPainterPathStroker()
1601
        stroke.setWidth(15)
1602
        return stroke.createStroke(path)
1603

    
1604
    def paint(self, painter, option, widget):
1605
        # override paint method
1606
        color = self.getColor()
1607
        self.setColor(color)
1608

    
1609
        painter.setPen(self.pen())
1610
        painter.drawLine(self.line())
1611
        #QGraphicsLineItem.paint(self, painter, options, widget)
1612

    
1613
        if self.isSelected():
1614
            painter.setOpacity(0.7)
1615
            painter.setPen(QPen(Qt.black, 1.5, Qt.DashLine))
1616
            painter.setBrush(QEngineeringLineItem.HIGHLIGHT_BRUSH)
1617
            painter.drawPolygon(self.minimum_bounding_box)
1618

    
1619
    def drawToImage(self, img, color, thickness):
1620
        """write recognized lines to image"""
1621
        try:
1622
            ptStart = self.start_point()
1623
            ptEnd = self.end_point()
1624
            cv2.line(img, (round(ptStart[0]), round(ptStart[1])), (round(ptEnd[0]), round(ptEnd[1])), color, thickness)
1625
            # up to here
1626
        except Exception as ex:
1627
            message = f"error occurred({repr(ex)}) in {sys.exc_info()[-1].tb_frame.f_code.co_filename}:" \
1628
                      f"{sys.exc_info()[-1].tb_lineno}"
1629

    
1630
    @staticmethod
1631
    def from_database(component):
1632
        """ get line from database """
1633
        import uuid
1634
        from AppDocData import AppDocData
1635
        from SymbolAttr import SymbolAttr
1636

    
1637
        item = None
1638
        try:
1639
            uidNode = component['UID']
1640
            uid = uidNode if uidNode is not None else uuid.uuid4()  # generate UUID
1641
            owner = uuid.UUID(component['Owner']) if component['Owner'] and component['Owner'] != 'None' else None
1642

    
1643
            app_doc_data = AppDocData.instance()
1644
            connectors = app_doc_data.get_component_connectors(uid)
1645
            if 2 != len(connectors): return item
1646
            startPoint = [float(connectors[0]['X']), float(connectors[0]['Y'])]
1647
            endPoint = [float(connectors[1]['X']), float(connectors[1]['Y'])]
1648

    
1649
            configs = AppDocData.instance().getConfigs('Data', 'Grid')
1650
            grid = int(configs[0].value) if 1 == len(configs) else -1
1651
            if grid == 1:
1652
                startPoint = [round(startPoint[0]), round(startPoint[1])]
1653
                endPoint = [round(endPoint[0]), round(endPoint[1])]
1654

    
1655
            item = QEngineeringLineItem(vertices=[startPoint, endPoint], uid=uid)
1656
            item.setVisible(False)
1657
            attrs = app_doc_data.get_component_attributes(uid)
1658
            matches = [attr for attr in attrs if attr['Attribute'] == 'LineType']
1659
            item.lineType = matches[0]['Value'] if matches else 'Secondary'
1660
            if matches:
1661
                attrs.remove(matches[0])
1662

    
1663
            # freeze => freeze and bidirectional
1664
            freeze_bidirectional = component['Freeze']
1665
            if int(freeze_bidirectional) == 3:
1666
                item.set_property('Freeze', True)
1667
                item.set_property('Bidirectional', True)
1668
            elif int(freeze_bidirectional) == 2:
1669
                item.set_property('Freeze', False)
1670
                item.set_property('Bidirectional', True)
1671
            elif int(freeze_bidirectional) == 1:
1672
                item.set_property('Freeze', True)
1673
                item.set_property('Bidirectional', False)
1674

    
1675
            # line has only freeze
1676
            '''
1677
            for key in item._properties.keys():
1678
                for compo in component.keys():
1679
                    if key.Attribute == compo:
1680
                        item._properties[key] = key.parse_value(component[key.Attribute])# if component[key.Attribute] else ''
1681
            '''
1682

    
1683
            ## assign area
1684
            if component['Area']:
1685
                for area in app_doc_data.getAreaList():
1686
                    if area.contains(startPoint) and area.contains(endPoint):
1687
                        item.area = area.name
1688
                        break
1689
            else:
1690
                item.area = component['Area']
1691
            ## up to here
1692

    
1693
            matches = [attr for attr in attrs if attr['Attribute'] == 'Thickness']
1694
            item.thickness = int(matches[0]['Value']) if matches and matches[0]['Value'] != 'None' else None
1695
            if matches:
1696
                attrs.remove(matches[0])
1697

    
1698
            matches = [attr for attr in attrs if attr['Attribute'] == 'FlowMark']
1699
            item.flowMark = int(matches[0]['Value']) if matches and matches[0]['Value'] != 'None' else None
1700
            if matches:
1701
                attrs.remove(matches[0])
1702

    
1703
            if connectors:
1704
                iterIndex = 0
1705
                for connector in connectors:
1706
                    item.connectors[iterIndex].parse_record(connector)
1707
                    iterIndex += 1
1708

    
1709
            # get associations
1710
            associations = app_doc_data.get_component_associations(uid)
1711
            if associations:
1712
                for assoc in associations:
1713
                    _type = assoc['Type']
1714
                    if not _type in item._associations:
1715
                        item._associations[_type] = []
1716
                    item._associations[_type].append(uuid.UUID(assoc['Association']))
1717
            # up to here
1718

    
1719
            if attrs:
1720
                for attr in attrs:
1721
                    _attr = SymbolAttr.from_record(attr)
1722
                    item.attrs[_attr] = attr['Value']
1723

    
1724
        except Exception as ex:
1725
            from App import App
1726
            from AppDocData import MessageType
1727

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

    
1732
        return item if item.length() > 1 else None
1733

    
1734
    '''
1735
        @brief      parse xml code
1736
        @author     humkyung
1737
        @date       2018.06.27
1738
    '''
1739

    
1740
    @staticmethod
1741
    def fromXml(node):
1742
        import uuid
1743
        from AppDocData import AppDocData
1744
        from SymbolAttr import SymbolAttr
1745

    
1746
        item = None
1747
        try:
1748
            uidNode = node.find('UID')
1749
            uid = uidNode.text if uidNode is not None else uuid.uuid4()  # generate UUID
1750
            owner = uuid.UUID(node.attrib['OWNER']) if 'OWNER' in node.attrib and node.attrib[
1751
                'OWNER'] != 'None' else None
1752

    
1753
            startPoint = [float(x) for x in node.find('STARTPOINT').text.split(',')]
1754
            endPoint = [float(x) for x in node.find('ENDPOINT').text.split(',')]
1755

    
1756
            configs = AppDocData.instance().getConfigs('Data', 'Grid')
1757
            grid = int(configs[0].value) if 1 == len(configs) else -1
1758
            if grid == 1:
1759
                startPoint = [round(startPoint[0]), round(startPoint[1])]
1760
                endPoint = [round(endPoint[0]), round(endPoint[1])]
1761

    
1762
            item = QEngineeringLineItem(vertices=[startPoint, endPoint], uid=uid)
1763
            if owner:
1764
                item._owner = owner
1765
            item.setVisible(False)
1766
            item.lineType = node.find('TYPE').text if node.find('TYPE') is not None else 'Secondary'
1767
            # assign area
1768
            if node.find('AREA') is None:
1769
                appDocData = AppDocData.instance()
1770
                for area in appDocData.getAreaList():
1771
                    if area.contains(startPoint) and area.contains(endPoint):
1772
                        item.area = area.name
1773
                        break
1774
            else:
1775
                item.area = node.find('AREA').text
1776
            # up to here
1777

    
1778
            thicknessNode = node.find('THICKNESS')
1779
            item.thickness = int(thicknessNode.text) if thicknessNode is not None and thicknessNode.text and thicknessNode.text != 'None' else None
1780

    
1781
            flowMarkNode = node.find('FLOWMARK')
1782
            item.flowMark = int(flowMarkNode.text) if flowMarkNode is not None and flowMarkNode.text and flowMarkNode.text != 'None' else None
1783

    
1784
            connectors = node.find('CONNECTORS')
1785
            if connectors is not None:
1786
                iterIndex = 0
1787
                for connector in connectors.iter('CONNECTOR'):
1788
                    item.connectors[iterIndex].parse_xml(connector)
1789
                    iterIndex += 1
1790

    
1791
            # get associations
1792
            attributeValue = node.find('ASSOCIATIONS')
1793
            if attributeValue is not None:
1794
                for assoc in attributeValue.iter('ASSOCIATION'):
1795
                    _type = assoc.attrib['TYPE']
1796
                    if not _type in item._associations:
1797
                        item._associations[_type] = []
1798
                    item._associations[_type].append(uuid.UUID(assoc.text))
1799
            # up to here
1800

    
1801
            attributes = node.find('SYMBOLATTRIBUTES')
1802
            if attributes is not None:
1803
                for attr in attributes.iter('ATTRIBUTE'):
1804
                    _attr = SymbolAttr.fromXml(attr)
1805
                    item.attrs[_attr] = attr.text
1806

    
1807
            for prop_node in node.iter('PROPERTY'):
1808
                matches = [prop for prop in item._properties.keys() if
1809
                            prop.Attribute == prop_node.attrib['Attribute']]
1810
                if matches:
1811
                    item._properties[matches[0]] = matches[0].parse_value(prop_node.text)# if prop_node.text else ''
1812

    
1813
        except Exception as ex:
1814
            from App import App
1815
            from AppDocData import MessageType
1816

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

    
1821
        #print(str(item.length()))
1822
        return item if item.length() > 1 else None
1823

    
1824
    '''
1825
        @brief      generate xml code
1826
        @author     humkyung
1827
        @date       2018.04.23
1828
        @history    humkyung 2018.06.27 write line type to xml
1829
                    humkyung 2018.07.23 write connected item's uid to xml
1830
    '''
1831

    
1832
    def toXml(self):
1833
        from xml.etree.ElementTree import Element, SubElement, dump, ElementTree
1834
        from LineTypeConditions import LineTypeConditions
1835
        from SymbolAttr import SymbolAttr
1836

    
1837
        try:
1838
            node = Element('LINE')
1839
            node.attrib['OWNER'] = str(self._owner) if self._owner else 'None'
1840
            uidNode = Element('UID')
1841
            uidNode.text = str(self.uid)
1842
            node.append(uidNode)
1843

    
1844
            startPt = self.start_point()
1845
            endPt = self.end_point()
1846

    
1847
            startNode = Element('STARTPOINT')
1848
            startNode.text = '{},{}'.format(startPt[0], startPt[1])
1849
            node.append(startNode)
1850

    
1851
            endNode = Element('ENDPOINT')
1852
            endNode.text = '{},{}'.format(endPt[0], endPt[1])
1853
            node.append(endNode)
1854

    
1855
            typeNode = Element('TYPE')
1856
            typeNode.text = self.lineType
1857
            for lineType in LineTypeConditions.items():
1858
                if self.lineType == lineType.name:
1859
                    typeNode.attrib['TYPEUID'] = str(lineType)
1860
                    break
1861
            node.append(typeNode)
1862

    
1863
            areaNode = Element('AREA')
1864
            areaNode.text = self.area
1865
            node.append(areaNode)
1866

    
1867
            thicknessNode = Element('THICKNESS')
1868
            thicknessNode.text = str(self.thickness)
1869
            node.append(thicknessNode)
1870

    
1871
            flowMarkNode = Element('FLOWMARK')
1872
            flowMarkNode.text = str(self.flowMark)
1873
            node.append(flowMarkNode)
1874

    
1875
            connectorsNode = Element('CONNECTORS')
1876
            for connector in self.connectors:
1877
                connectorsNode.append(connector.toXml())
1878
            node.append(connectorsNode)
1879

    
1880
            attributeValueNode = Element('ASSOCIATIONS')
1881
            for assoc in self.associations():
1882
                assoc_node = Element('ASSOCIATION')
1883
                assoc_node.attrib['TYPE'] = QEngineeringAbstractItem.assoc_type(assoc)
1884
                assoc_node.text = str(assoc.uid)
1885
                attributeValueNode.append(assoc_node)
1886
            node.append(attributeValueNode)
1887

    
1888
            properties_node = Element('PROPERTIES')
1889
            for prop, value in self.properties.items():
1890
                prop_node = prop.toXml()
1891
                prop_node.text = str(value) if value else ''
1892
                properties_node.append(prop_node)
1893
            node.append(properties_node)
1894

    
1895
            attributesNode = Element('SYMBOLATTRIBUTES')
1896
            _attrs = self.getAttributes()
1897
            for attr in _attrs:
1898
                if type(attr) is SymbolAttr:
1899
                    _node = attr.toXml()
1900
                    _node.text = str(_attrs[attr])
1901
                    attributesNode.append(_node)
1902

    
1903
            node.append(attributesNode)
1904

    
1905
            # up to here
1906
        except Exception as ex:
1907
            from App import App
1908
            from AppDocData import MessageType
1909

    
1910
            message = 'error occurred({}) in {}:{}'.format(ex, sys.exc_info()[-1].tb_frame.f_code.co_filename,
1911
                                                           sys.exc_info()[-1].tb_lineno)
1912
            App.mainWnd().addMessage.emit(MessageType.Error, message)
1913
            return None
1914

    
1915
        return node
1916

    
1917
    def to_svg(self, parent) -> list:
1918
        """convert line item to svg"""
1919
        from xml.etree.ElementTree import Element, SubElement, dump, ElementTree
1920
        from App import App
1921
        from AppDocData import AppDocData
1922

    
1923
        res = []
1924
        try:
1925
            app_doc_data = AppDocData.instance()
1926
            prj = app_doc_data.getCurrentProject()
1927

    
1928
            node = Element('g')
1929
            node.attrib['id'] = str(self.uid)
1930
            node.attrib['stroke'] = self.pen().color().name()
1931
            node.attrib['stroke-width'] = str(int(self.pen().width()*0.5))
1932

    
1933
            line_style_map = {Qt.DotLine: '2', Qt.DashLine: '4 2',
1934
                              Qt.DashDotLine: '4 2 2', Qt.DashDotDotLine: '4 2 2 2 2 2'}
1935
            line_type_style = self.line_type_style
1936
            if line_type_style[3] in line_style_map:
1937
                node.attrib['stroke-dasharray'] = line_style_map[line_type_style[3]]
1938

    
1939
            if not parent:
1940
                trans = QTransform()
1941
            else:
1942
                trans, _ = parent.sceneTransform().inverted()
1943

    
1944
            node.attrib['transform'] = f"matrix(" \
1945
                                       f"{trans.m11()},{trans.m12()}," \
1946
                                       f"{trans.m21()},{trans.m22()}," \
1947
                                       f"{trans.m31()},{trans.m32()}" \
1948
                                       f")"
1949

    
1950
            polyline = Element('polyline')
1951
            polyline.attrib['fill'] = 'none'
1952
            start_pt = self.start_point()
1953
            end_pt = self.end_point()
1954
            polyline.attrib['points'] = f"{start_pt[0]},{start_pt[1]} " \
1955
                                        f"{end_pt[0]},{end_pt[1]}"
1956
            node.append(polyline)
1957

    
1958
            res.append(node)
1959
        except Exception as ex:
1960
            from App import App
1961
            from AppDocData import MessageType
1962
            message = 'error occurred({}) in {}:{}'.format(ex, sys.exc_info()[-1].tb_frame.f_code.co_filename,
1963
                                                          sys.exc_info()[-1].tb_lineno)
1964
            App.mainWnd().addMessage.emit(MessageType.Error, message)
1965

    
1966
        return res
1967

    
1968
    def toSql_return_separately(self):
1969
        """ generate sql phrase to save line to database """
1970
        import uuid
1971
        from AppDocData import AppDocData
1972

    
1973
        res = []
1974
        resLater = []
1975

    
1976
        app_doc_data = AppDocData.instance()
1977
        cols = ['UID', 'Drawings_UID', 'Symbol_UID', 'X', 'Y', 'Width', 'Height', 'Rotation', 'Area', 'Owner',
1978
                'SpecialItemTypes_UID', 'Freeze']
1979
        values = ['?', '?', "(select UID from Symbol where Name='Line' and SymbolType_UID=-1)", '?', '?', '?', '?', '?',
1980
                  '?', '?', '?', '?']
1981

    
1982
        rect = self.sceneBoundingRect()
1983
        
1984
        # freeze and bidirectional is stored at freeze column
1985
        freeze_bidirectional = 0
1986
        if self.prop('Freeze') and self.prop('Bidirectional'):
1987
            freeze_bidirectional = 3
1988
        elif not self.prop('Freeze') and self.prop('Bidirectional'):
1989
            freeze_bidirectional = 2
1990
        elif self.prop('Freeze') and not self.prop('Bidirectional'):
1991
            freeze_bidirectional = 1
1992

    
1993
        param = [
1994
            (str(self.uid), str(app_doc_data.activeDrawing.UID), rect.x(), rect.y(), rect.width(), rect.height(), 0,
1995
             self.area, str(self.owner) if self.owner else None,
1996
             str(self.special_item_type) if self.special_item_type else None, freeze_bidirectional)]
1997
        sql = 'insert into Components({}) values({})'.format(','.join(cols), ','.join(values))
1998
        res.append((sql, tuple(param)))
1999

    
2000
        _attrs = self.getAttributes()
2001
        if _attrs:
2002
            cols = ['UID', 'Components_UID', 'SymbolAttribute_UID', 'Value', 'Association_UID', 'Freeze']
2003
            values = ['?', '?', '?', '?', '?', '?']
2004
            params = []
2005
            for key in _attrs.keys():
2006
                if key.AttributeType != 'Spec':
2007
                    params.append((str(uuid.uuid4()), str(self.uid), str(key.UID), str(_attrs[key]), str(key.AssocItem),
2008
                                   str(key.Freeze)))
2009
                elif key.AttributeType == 'Spec':
2010
                    if type(_attrs[key]) is not list: continue
2011
                    params.append((str(uuid.uuid4()), str(self.uid), str(key.UID), (str(_attrs[key][0]) + ',' + str(_attrs[key][1])), str(key.AssocItem),
2012
                                   str(key.Freeze)))
2013
            sql = 'insert into Attributes({}) values({})'.format(','.join(cols), ','.join(values))
2014
            res.append((sql, tuple(params)))
2015

    
2016
        if self.associations():
2017
            cols = ['UID', '[Type]', 'Components_UID', 'Association']
2018
            values = ['?', '?', '?', '?']
2019
            params = []
2020
            for assoc in self.associations():
2021
                params.append(
2022
                    (str(uuid.uuid4()), QEngineeringAbstractItem.assoc_type(assoc), str(self.uid), str(assoc.uid)))
2023
            sql = 'insert into Associations({}) values({})'.format(','.join(cols), ','.join(values))
2024
            resLater.append((sql, tuple(params)))
2025

    
2026
        # save connectors to database
2027
        cols = ['Components_UID', '[Index]', 'X', 'Y', 'Connected', 'Connected_At']
2028
        values = ['?', '?', '?', '?', '?', '?']
2029
        params = []
2030
        index = 1
2031
        for connector in self.connectors:
2032
            params.append((  # str(connector.uid),
2033
                str(self.uid), index, connector.center()[0], connector.center()[1], \
2034
                str(connector.connectedItem.uid) if connector.connectedItem else None, \
2035
                str(connector._connected_at)))
2036
            index += 1
2037
        sql = 'insert into Points({}) values({})'.format(','.join(cols), ','.join(values))
2038
        resLater.append((sql, tuple(params)))
2039
        # up to here
2040

    
2041
        # save attributes
2042
        cols = ['UID', 'Components_UID', 'SymbolAttribute_UID', 'Value']
2043
        values = ['?', '?', "(select UID from SymbolAttribute where Attribute='LineType' and SymbolType_UID=-1)", '?']
2044
        param = [(str(uuid.uuid4()), str(self.uid), self.lineType)]
2045
        sql = 'insert into Attributes({}) values({})'.format(','.join(cols), ','.join(values))
2046
        res.append((sql, tuple(param)))
2047
        values = ['?', '?', "(select UID from SymbolAttribute where Attribute='Thickness' and SymbolType_UID=-1)", '?']
2048
        param = [(str(uuid.uuid4()), str(self.uid), str(self.thickness))]
2049
        sql = 'insert into Attributes({}) values({})'.format(','.join(cols), ','.join(values))
2050
        res.append((sql, tuple(param)))
2051
        values = ['?', '?', "(select UID from SymbolAttribute where Attribute='FlowMark' and SymbolType_UID=-1)", '?']
2052
        param = [(str(uuid.uuid4()), str(self.uid), str(self.flowMark))]
2053
        sql = 'insert into Attributes({}) values({})'.format(','.join(cols), ','.join(values))
2054
        res.append((sql, tuple(param)))
2055
        # up to here
2056

    
2057
        return res, resLater
2058

    
2059
    '''
2060
        @brief      Delete Line Item from scene
2061
        @author     Jeongwoo
2062
        @date       2018.05.29
2063
        @history    2018.05.29  Add parameter 'self' / Make comments emit()
2064
    '''
2065

    
2066
    def deleteLineItemFromScene(self):
2067
        self.scene().removeItem(self)
2068

    
2069
    def setColor(self, color):
2070
        """Set Color. Override QEngineeringAbstractItem's"""
2071
        if color.upper() != self.pen().color().name().upper():
2072
            c = QColor()
2073
            c.setNamedColor(color)
2074
            _pen = self.pen()
2075
            _pen.setColor(c)
2076
            self.setPen(_pen)
2077
            #self.update()
2078

    
2079
    def clear_labels(self):
2080
        """ clear spec labels """
2081
        attrs = self.getAttributes()
2082
        index = 0
2083
        for key in attrs.keys():
2084
            #if index >= 6:
2085
            #    break
2086
            if key.AssocItem and not key.Freeze:
2087
                key.AssocItem.owner = None
2088
                self.remove_assoc_item(key.AssocItem)
2089
                key.AssocItem = None
2090
            if not key.Freeze:
2091
                attrs[key] = ''
2092
            index += 1
2093
    
2094
    def update_flow_mark(self, position, length):
2095
        """ update flow mark for flow arrow """
2096

    
2097
        to_item = self.connectors[1].connectedItem
2098
        if type(to_item) is QEngineeringLineItem and self.length() > length and not self.isParallel(to_item):
2099
            self.flowMark = position
2100

    
2101
    @staticmethod
2102
    def update_arrows(lines):
2103
        for line in lines:
2104
            line.update_arrow()
2105

    
2106
        return lines
2107

    
2108
    def update_arrow(self):
2109
        """ update flow arrow """
2110

    
2111
        from EngineeringArrowItem import QEngineeringArrowItem
2112

    
2113
        if self.length() < 0.01:
2114
            return
2115

    
2116
        start = self.line().p1()
2117
        end = self.line().p2()
2118

    
2119
        dx = end.x() - start.x()
2120
        dy = end.y() - start.y()
2121
        _dir = [dx / self.length(), dy / self.length()]
2122

    
2123
        arrow_size = QEngineeringLineItem.ARROW_SIZE * 0.25
2124

    
2125
        # allow flow mark at signal line
2126
        #if not self.is_piping():
2127
        #    self.flowMark = None
2128

    
2129
        if self.flowMark:
2130
            arrow_size *= 2
2131
            end = QPointF(start.x() + dx * self.flowMark / 100, start.y() + dy * self.flowMark / 100)
2132

    
2133
        perpendicular = (-_dir[1], _dir[0])
2134
        polygon = QPolygonF()
2135
        if not self.prop('Bidirectional'):
2136
            polygon.append(QPointF(end.x() - _dir[0] * QEngineeringLineItem.ARROW_SIZE + perpendicular[0] * arrow_size,
2137
                                end.y() - _dir[1] * QEngineeringLineItem.ARROW_SIZE + perpendicular[1] * arrow_size))
2138
            polygon.append(QPointF(end.x() - _dir[0] * QEngineeringLineItem.ARROW_SIZE - perpendicular[0] * arrow_size,
2139
                                end.y() - _dir[1] * QEngineeringLineItem.ARROW_SIZE - perpendicular[1] * arrow_size))
2140
            polygon.append(end)
2141
            polygon.append(polygon[0])  # close polygon
2142
        else:
2143
            polygon.append(QPointF(end.x() - _dir[0] * QEngineeringLineItem.ARROW_SIZE + perpendicular[0] * arrow_size,
2144
                                end.y() - _dir[1] * QEngineeringLineItem.ARROW_SIZE + perpendicular[1] * arrow_size))
2145
            polygon.append(end)
2146
            polygon.append(QPointF(end.x() - _dir[0] * QEngineeringLineItem.ARROW_SIZE - perpendicular[0] * arrow_size,
2147
                                end.y() - _dir[1] * QEngineeringLineItem.ARROW_SIZE - perpendicular[1] * arrow_size))
2148
            polygon.append(QPointF(end.x() - _dir[0] * 2 * QEngineeringLineItem.ARROW_SIZE, end.y() - _dir[1] * 2 * QEngineeringLineItem.ARROW_SIZE))
2149
            polygon.append(polygon[0])  # close polygon
2150

    
2151
        if not hasattr(self, '_arrow'):
2152
            self._arrow = QEngineeringArrowItem(polygon, self)
2153
        else:
2154
            self._arrow.setPolygon(polygon)
2155

    
2156
        isFreezed = False
2157
        for prop, value in self.properties.items():
2158
            if prop.Attribute == 'Freeze':
2159
                if value and str(value) == 'True':
2160
                    isFreezed = True
2161
                break
2162
        
2163
        if isFreezed:
2164
            self._arrow.setFlag(QGraphicsItem.ItemStacksBehindParent, False)
2165
            self._arrow.setBrush(Qt.green)
2166
        elif self.prop('Bidirectional'):
2167
            self._arrow.setFlag(QGraphicsItem.ItemStacksBehindParent, False)
2168
            self._arrow.setBrush(Qt.yellow)
2169
        elif self.flowMark:
2170
            self._arrow.setFlag(QGraphicsItem.ItemStacksBehindParent, True)
2171
            self._arrow.setBrush(Qt.red)
2172
        else:
2173
            self._arrow.setFlag(QGraphicsItem.ItemStacksBehindParent, True)
2174
            self._arrow.setBrush(Qt.blue)
2175
        self._arrow.update()
2176

    
2177
    def onConnectorPosChaned(self, connector):
2178
        """update line shape when connector is moved"""
2179

    
2180
        start = self.connectors[0].center()
2181
        end = self.connectors[1].center()
2182
        self.set_line([[start[0], start[1]], [end[0], end[1]]])
2183

    
2184
        if hasattr(self.scene(), 'contents_changed'):
2185
            self.scene().contents_changed.emit()
2186

    
2187
        # register resize command
2188
        pt_start = self.connectors[0].pressed_position if self.connectors[0].pressed_position else None
2189
        pt_end = self.connectors[-1].pressed_position if self.connectors[-1].pressed_position else None
2190
        if pt_start or pt_end:
2191
            from ResizeCommand import ResizeCommand
2192
            cmd = ResizeCommand(self.scene(), self,
2193
                                [pt_start if pt_start else
2194
                                 QPointF(self.connectors[0].center()[0], self.connectors[0].center()[1]),
2195
                                 pt_end if pt_end else
2196
                                 QPointF(self.connectors[-1].center()[0], self.connectors[-1].center()[-1])])
2197
            self.scene().undo_stack.push(cmd)
2198

    
2199
    '''
2200
        @brief      
2201
        @author     humkyung
2202
        @date       2018.07.24
2203
    '''
2204

    
2205
    def mousePressEvent(self, event):
2206
        import math
2207

    
2208
        if event.buttons() == Qt.LeftButton:
2209
            pos = event.scenePos()
2210
            ptStart = self.start_point()
2211
            dx = ptStart[0] - pos.x()
2212
            dy = ptStart[1] - pos.y()
2213
            if math.sqrt(dx * dx + dy * dy) < 10:
2214
                self._selectedIndex = 0
2215
                return
2216

    
2217
            ptEnd = self.end_point()
2218
            dx = ptEnd[0] - pos.x()
2219
            dy = ptEnd[1] - pos.y()
2220
            if math.sqrt(dx * dx + dy * dy) < 10:
2221
                self._selectedIndex = 1
2222
                return
2223

    
2224
            self._selectedIndex = -1
2225

    
2226
        QGraphicsLineItem.mousePressEvent(self, event)
2227

    
2228
    def mouseReleaseEvent(self, event):
2229
        self._selectedIndex = -1
2230

    
2231
        QGraphicsLineItem.mouseReleaseEvent(self, event)
2232

    
2233
    def evaluatedBidirectional(self):
2234
        isBidirectional = self.prop('Bidirectional')
2235
        if isBidirectional:
2236
            return True
2237
        
2238
        if self.owner:
2239
            matches = [run for run in self.owner.runs if self in run.items]
2240
            if matches:
2241
                for line in [item for item in matches[0].items if type(item) is QEngineeringLineItem and item is not self]:
2242
                    if line.prop('Bidirectional'):
2243
                        return True
2244
                    
2245
        return False
2246
    
2247
    def contextMenuEvent(self, event):
2248
        from LineTypeConditions import LineTypeConditions
2249

    
2250
        items = [item for item in self.scene().selectedItems() if type(item) is QEngineeringLineItem]
2251
        if len(items) > 0 and self in items:
2252
            menu = QMenu()
2253

    
2254
            if len(items) == 1:
2255
                showAction = QAction('Show Onwer', None)
2256
                showAction.triggered.connect(self.contextShow)
2257
                menu.addAction(showAction)
2258

    
2259
                splitAction = QAction('Split', None)
2260
                splitAction.triggered.connect(lambda: self.contextSplit(event.pos()))
2261
                menu.addAction(splitAction)
2262

    
2263
                freezeAttrAction = QAction('Freeze Attributes(G)', None)
2264
                freezeAttrAction.triggered.connect(self.freeze_attriute)
2265
                menu.addAction(freezeAttrAction)
2266

    
2267
            freezeAction = QAction('Set Freeze', None) if not self.prop('Freeze') else QAction('Unset Freeze', None)
2268
            freezeAction.triggered.connect(self.contextFreeze)
2269
            menu.addAction(freezeAction)
2270

    
2271
            bidirectionalAction = QAction('Set Bidirectional', None) if not self.prop('Bidirectional') else QAction('Unset Bidirectional', None)
2272
            bidirectionalAction.triggered.connect(self.contextBidirectional)
2273
            menu.addAction(bidirectionalAction)
2274

    
2275
            if len(items) > 1:
2276
                if len(items) == 2:
2277
                    make3PointMenu = QMenu(menu)
2278
                    make3PointMenu.setTitle('3 Point')
2279
                    inAction = QAction('In', None)
2280
                    inAction.triggered.connect(self.contextIn)
2281
                    make3PointMenu.addAction(inAction)
2282
                    outAction = QAction('Out', None)
2283
                    outAction.triggered.connect(self.contextOut)
2284
                    make3PointMenu.addAction(outAction)
2285
                    menu.addMenu(make3PointMenu)
2286

    
2287
                    intersectionAction = QAction('Intersect', None)
2288
                    intersectionAction.triggered.connect(self.contextIntersection)
2289
                    menu.addAction(intersectionAction)
2290

    
2291
                mergeAction = QAction('Merge(M)', None)
2292
                mergeAction.triggered.connect(self.contextMerge)
2293
                menu.addAction(mergeAction)
2294

    
2295
            reverseAction = QAction('Reverse(C)', None)
2296
            reverseAction.triggered.connect(self.contextReverse)
2297
            menu.addAction(reverseAction)
2298

    
2299
            arrowAction = QAction('Arrow(A)', None)
2300
            arrowAction.triggered.connect(self.contextArrow)
2301
            menu.addAction(arrowAction)
2302

    
2303
            lineTypeChangeMenu = QMenu(menu)
2304
            lineTypeChangeMenu.setTitle('Change Line Type')
2305

    
2306
            index = 0
2307
            lineTypeActions = []
2308
            typeNames = []
2309
            for condition in reversed(LineTypeConditions.items()):
2310
                lineTypeActions.append(QAction(condition.name, None))
2311
                typeNames.append(condition.name)
2312
                lineTypeChangeMenu.addAction(lineTypeActions[index])
2313
                index = index + 1
2314

    
2315
            binding = [lambda index=index: lineTypeActions[index].triggered.connect(lambda: self.contextLineType(typeNames[index])) for index in range(len(lineTypeActions))]
2316
            
2317
            for index in range(len(lineTypeActions)):
2318
                binding[index]()
2319

    
2320
            menu.addMenu(lineTypeChangeMenu)
2321

    
2322
            deleteAction = QAction('Delete(E)', None)
2323
            deleteAction.triggered.connect(self.contextDelete)
2324
            menu.addAction(deleteAction)
2325

    
2326
            ShowOnOtherAppAction = QAction('Show on other App', None)
2327
            ShowOnOtherAppAction.triggered.connect(self.contextShowOnOtherApp)
2328
            menu.addAction(ShowOnOtherAppAction)
2329

    
2330
            menu.exec_(event.screenPos())
2331

    
2332
    def contextIntersection(self):
2333
        from LineDetector import LineDetector
2334
        from AppDocData import AppDocData
2335

    
2336
        items = [item for item in self.scene().selectedItems() if type(item) is QEngineeringLineItem]
2337
        if len(items) != 2 or self not in items:
2338
            return
2339
        
2340
        app_doc_data = AppDocData.instance()
2341
        detector = LineDetector(app_doc_data.imgSrc)
2342
        line = [line for line in items if line is not self][0]
2343

    
2344
        detector.connectLineToLine(self, line, 150)
2345

    
2346
    def contextIn(self):
2347
        self.make3Point(True)
2348

    
2349
    def contextOut(self):
2350
        self.make3Point(False)
2351

    
2352
    def contextLineType(self, line_type):
2353
        from App import App
2354

    
2355
        App.mainWnd().change_selected_line_type(line_type)
2356

    
2357
    def contextShow(self):
2358
        from EngineeringLineNoTextItem import QEngineeringLineNoTextItem
2359

    
2360
        if type(self.owner) is QEngineeringLineNoTextItem:
2361
            self.owner.contextHighlight(self.owner)
2362

    
2363
    def contextSplit(self, pos):
2364
        from App import App
2365
        
2366
        _pos = [round(pos.x()), round(pos.y())]
2367
        if abs(_pos[0] - self.start_point()[0]) > abs(_pos[1] - self.start_point()[1]):
2368
            _pos[1] = self.start_point()[1]
2369
        else:
2370
            _pos[0] = self.start_point()[0]
2371

    
2372
        savedConnectors = []
2373
        for _connector in self.connectors:
2374
            if _connector.connectedItem and _connector._connected_at == QEngineeringAbstractItem.CONNECTED_AT_PT:
2375
                _connectors2 = [_connector2 for _connector2 in _connector.connectedItem.connectors if _connector2.connectedItem is self and _connector2._connected_at == QEngineeringAbstractItem.CONNECTED_AT_PT]
2376
                if _connectors2:
2377
                    savedConnectors.append(_connectors2[0])
2378
                    continue
2379
            savedConnectors.append(None)
2380

    
2381
        inLine = QEngineeringLineItem(vertices=[self.start_point(), _pos])
2382
        inLine.transfer.onRemoved.connect(App.mainWnd().itemRemoved)
2383
        inLine.lineType = self.lineType
2384

    
2385
        outLine = QEngineeringLineItem(vertices=[_pos, self.end_point()])
2386
        outLine.transfer.onRemoved.connect(App.mainWnd().itemRemoved)
2387
        outLine.lineType = self.lineType
2388

    
2389
        inLine.connectors[0].connect(self.connectors[0].connectedItem, self.connectors[0]._connected_at)
2390
        inLine.connectors[1].connect(outLine)
2391
        outLine.connectors[0].connect(inLine)
2392
        outLine.connectors[1].connect(self.connectors[1].connectedItem, self.connectors[1]._connected_at)
2393

    
2394
        if savedConnectors[0]:
2395
            savedConnectors[0].connect(inLine)
2396
        if savedConnectors[1]:
2397
            savedConnectors[1].connect(outLine)
2398

    
2399
        self.scene().addItem(inLine)
2400
        self.scene().addItem(outLine)
2401
        self.transfer.onRemoved.emit(self)
2402

    
2403
    def contextFreeze(self):
2404
        from App import App
2405

    
2406
        scene = self.scene()
2407
        if scene:
2408
            isFreeze = self.prop('Freeze')
2409
            lineItems = [symbol for symbol in scene.selectedItems() if type(symbol) is QEngineeringLineItem]
2410

    
2411
            for line in lineItems:
2412
                for prop, value in line.properties.items():
2413
                    if prop.Attribute == 'Freeze':
2414
                        line.set_property(prop.Attribute, not isFreeze)
2415
                line.update_arrow()
2416

    
2417
            App.mainWnd().resultPropertyTableWidget.onSuccessSelectAttribute(self)
2418

    
2419
    def contextBidirectional(self):
2420
        from App import App
2421

    
2422
        scene = self.scene()
2423
        if scene:
2424
            isBidirectional = self.prop('Bidirectional')
2425
            lineItems = [symbol for symbol in scene.selectedItems() if type(symbol) is QEngineeringLineItem]
2426

    
2427
            for line in lineItems:
2428
                for prop, value in line.properties.items():
2429
                    if prop.Attribute == 'Bidirectional':
2430
                        line.set_property(prop.Attribute, not isBidirectional)
2431
                line.update_arrow()
2432

    
2433
            App.mainWnd().resultPropertyTableWidget.onSuccessSelectAttribute(self)
2434

    
2435
    def contextDelete(self):
2436
        event = QKeyEvent(QEvent.KeyPress, Qt.Key_Delete, Qt.NoModifier)
2437
        self.scene().keyPressEvent(event)
2438

    
2439
    def contextShowOnOtherApp(self):
2440
        from TcpServer import TcpSocket
2441
        from AppDocData import AppDocData
2442

    
2443
        app_doc_data = AppDocData.instance()
2444
        configs = app_doc_data.getConfigs('app', 'conn port')
2445
        port = 3030
2446
        if configs and 1 == len(configs):
2447
            port = int(configs[0].value)
2448
        tcpserver = TcpSocket(port)
2449

    
2450
        origin_x = (self.start_point()[0] + self.end_point()[0]) * 0.5
2451
        origin_y = (self.start_point()[1] + self.end_point()[1]) * 0.5
2452
        min_x = min(self.start_point()[0], self.end_point()[0])
2453
        min_y = min(self.start_point()[1], self.end_point()[1])
2454
        max_x = max(self.start_point()[0], self.end_point()[0])
2455
        max_y = max(self.start_point()[1], self.end_point()[1])
2456
        size_x = max_x - min_x
2457
        if not size_x: size_x = 1
2458
        size_y = max_y - min_y
2459
        if not size_y: size_y = 1
2460
        tcpserver.sendMessage(f"{str(self.uid)},{origin_x},{origin_y},{size_x},{size_y},"
2461
                              f"{app_doc_data.imgName}")
2462

    
2463
    def contextArrow(self):
2464
        event = QKeyEvent(QEvent.KeyPress, Qt.Key_A, Qt.NoModifier)
2465
        self.scene().keyPressEvent(event)
2466

    
2467
    def contextReverse(self):
2468
        event = QKeyEvent(QEvent.KeyPress, Qt.Key_C, Qt.NoModifier)
2469
        self.scene().keyPressEvent(event)
2470

    
2471
    def contextMerge(self):
2472
        event = QKeyEvent(QEvent.KeyPress, Qt.Key_M, Qt.NoModifier)
2473
        self.keyPressEvent(event)
2474

    
2475
    def make3Point(self, start):
2476
        from App import App
2477
        
2478
        items = [item for item in self.scene().selectedItems() if type(item) is QEngineeringLineItem]
2479
        if len(items) != 2 or self not in items:
2480
            return
2481
        
2482
        line = [line for line in items if line is not self][0]
2483
        _connectors = [_connector for _connector in self.connectors if _connector.connectedItem == line and _connector._connected_at == QEngineeringAbstractItem.CONNECTED_AT_BODY]
2484
        if not _connectors:
2485
            return
2486
        
2487
        savedConnectors = []
2488
        for _connector in line.connectors:
2489
            if _connector.connectedItem and _connector._connected_at == QEngineeringAbstractItem.CONNECTED_AT_PT:
2490
                _connectors2 = [_connector2 for _connector2 in _connector.connectedItem.connectors if _connector2.connectedItem is line and _connector2._connected_at == QEngineeringAbstractItem.CONNECTED_AT_PT]
2491
                if _connectors2:
2492
                    savedConnectors.append(_connectors2[0])
2493
                    continue
2494
            savedConnectors.append(None)
2495
        
2496
        connector = _connectors[0]
2497

    
2498
        inLine = QEngineeringLineItem(vertices=[line.start_point(), connector.center()])
2499
        inLine.transfer.onRemoved.connect(App.mainWnd().itemRemoved)
2500
        inLine.lineType = line.lineType
2501

    
2502
        outLine = QEngineeringLineItem(vertices=[connector.center(), line.end_point()])
2503
        outLine.transfer.onRemoved.connect(App.mainWnd().itemRemoved)
2504
        outLine.lineType = line.lineType
2505

    
2506
        if start:
2507
            inLine.connectors[0].connect(line.connectors[0].connectedItem, line.connectors[0]._connected_at)
2508
            inLine.connectors[1].connect(self)
2509
            outLine.connectors[0].connect(self, QEngineeringAbstractItem.CONNECTED_AT_BODY)
2510
            outLine.connectors[1].connect(line.connectors[1].connectedItem, line.connectors[1]._connected_at)
2511
            connector.connect(inLine)
2512
        else:
2513
            inLine.connectors[0].connect(line.connectors[0].connectedItem, line.connectors[0]._connected_at)
2514
            inLine.connectors[1].connect(self, QEngineeringAbstractItem.CONNECTED_AT_BODY)
2515
            outLine.connectors[0].connect(self)
2516
            outLine.connectors[1].connect(line.connectors[1].connectedItem, line.connectors[1]._connected_at)
2517
            connector.connect(outLine)
2518

    
2519
        if savedConnectors[0]:
2520
            savedConnectors[0].connect(inLine)
2521
        if savedConnectors[1]:
2522
            savedConnectors[1].connect(outLine)
2523

    
2524
        self.scene().addItem(inLine)
2525
        self.scene().addItem(outLine)
2526
        line.transfer.onRemoved.emit(line)
2527

    
2528
'''
2529
    @brief      The class transfer pyqtSignal Event. Cause Subclass of QGraphicsRectItem can't use pyqtSignal
2530
    @author     Jeongwoo
2531
    @date       2018.06.18
2532
'''
2533
class Transfer(QObject):
2534
    onRemoved = pyqtSignal(QGraphicsItem)
2535

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