프로젝트

일반

사용자정보

통계
| 개정판:

hytos / DTI_PID / DTI_PID / Shapes / EngineeringLineItem.py @ 11045d18

이력 | 보기 | 이력해설 | 다운로드 (84.6 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 }
63

    
64
            self.setFlags(QGraphicsItem.ItemIsSelectable | QGraphicsItem.ItemIsFocusable)
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._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
            if type(self._owner) is not uuid.UUID and type(self._owner) is not str:
152
                return self._owner
153
            else:
154
                self._owner = None
155
                return None
156
        except Exception as ex:
157
            from App import App
158
            from AppDocData import MessageType
159

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

    
164
            return None
165

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

    
173
    @owner.setter
174
    def owner(self, value):
175
        self._owner = value
176

    
177
        if self._owner is None:
178
            self._color = self.DEFAULT_COLOR
179
        self.setColor(self._color)
180

    
181
    '''
182
        @brief  getter flow mark
183
        @author humkyung
184
        @date   2018.06.21
185
    '''
186

    
187
    @property
188
    def flowMark(self):
189
        return self._flowMark
190

    
191
    '''
192
        @brief  setter flow mark
193
        @author humkyung
194
        @date   2018.06.21
195
    '''
196

    
197
    @flowMark.setter
198
    def flowMark(self, value):
199
        self._flowMark = value
200

    
201
    '''
202
        @brief  getter of lineType
203
        @author humkyung
204
        @date   2018.06.27
205
    '''
206

    
207
    @property
208
    def lineType(self):
209
        return self._lineType
210

    
211
    '''
212
        @brief  setter of lineType
213
        @author humkyung
214
        @date   2018.06.27
215
    '''
216

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

    
221
        self._lineType = value
222

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

    
235
        if self.scene():
236
            self.update_arrow()
237

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

    
243
        app_doc_data = AppDocData.instance()
244
        configs = app_doc_data.getLineTypeConfig(self._lineType)
245
        return configs
246

    
247
    '''
248
        @brief  clone an object
249
    '''
250

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

    
259
        return clone
260

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

    
268
        self._minimum_bounding_box = None
269

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

    
275
        if not self._minimum_bounding_box:
276
            offset = 10
277

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

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

    
288
            self._minimum_bounding_box = polygon
289

    
290
        return self._minimum_bounding_box
291

    
292
    '''
293
        @brief  return start point
294
        @author humkyung
295
        @date   2018.04.16
296
    '''
297

    
298
    def start_point(self):
299
        at = self.line().p1()
300
        return (at.x(), at.y())
301

    
302
    '''
303
        @brief  return last point
304
        @author humkyung
305
        @date   2018.04.16
306
    '''
307

    
308
    def end_point(self):
309
        at = self.line().p2()
310
        return (at.x(), at.y())
311

    
312
    '''
313
        @brief  dot product of given two vectors
314
        @author humkyung
315
        @date   2018.04.14
316
    '''
317
    def dotProduct(self, lhs, rhs):
318
        return sum([lhs[i] * rhs[i] for i in range(len(lhs))])
319

    
320
    '''
321
        @brief  distance between line and point
322
        @author humkyung
323
        @date   2018.04.16
324
    '''
325

    
326
    def distanceTo(self, pt):
327
        from shapely.geometry import Point, LineString
328

    
329
        startPt = self.start_point()
330
        endPt = self.end_point()
331
        line = LineString([(startPt[0], startPt[1]), (endPt[0], endPt[1])])
332
        dist = line.distance(Point(pt[0], pt[1]))
333

    
334
        return dist
335

    
336
    '''
337
        @brief  return perpendicular vector
338
        @author humkyung
339
        @date   2018.04.21
340
    '''
341

    
342
    def perpendicular(self):
343
        import math
344

    
345
        dx = self.end_point()[0] - self.start_point()[0]
346
        dy = self.end_point()[1] - self.start_point()[1]
347
        dx, dy = -dy, dx
348
        length = math.sqrt(dx * dx + dy * dy)
349
        dx /= length
350
        dy /= length
351

    
352
        return (dx, dy)
353

    
354
    def angle(self):
355
        """
356
        @brief  return angle of line in radian
357
        @author humkyung
358
        """
359

    
360
        import math
361

    
362
        startPt = self.start_point()
363
        endPt = self.end_point()
364
        dx = endPt[0] - startPt[0]
365
        dy = endPt[1] - startPt[1]
366
        dot = self.dotProduct((1, 0), (dx, dy))
367
        length = math.sqrt(dx * dx + dy * dy)
368
        return math.acos(dot / length)
369

    
370
    '''
371
        @brief  return length of line
372
        @author humkyung
373
        @date   2018.05.08
374
    '''
375

    
376
    def length(self):
377
        import math
378

    
379
        startPt = self.start_point()
380
        endPt = self.end_point()
381
        dx = endPt[0] - startPt[0]
382
        dy = endPt[1] - startPt[1]
383
        return math.sqrt(dx * dx + dy * dy)
384

    
385
    '''
386
        @brief  check if line is horizontal
387
        @author humkyung
388
        @date   2018.04.27
389
    '''
390

    
391
    def isHorizontal(self):
392
        import math
393

    
394
        startPt = self.start_point()
395
        endPt = self.end_point()
396
        dx = endPt[0] - startPt[0]
397
        dy = endPt[1] - startPt[1]
398

    
399
        return math.fabs(dx) > math.fabs(dy)
400

    
401
    '''
402
        @brief  check if line is vertical 
403
        @author humkyung
404
        @date   2018.04.27
405
    '''
406

    
407
    def isVertical(self):
408
        import math
409

    
410
        startPt = self.start_point()
411
        endPt = self.end_point()
412
        dx = endPt[0] - startPt[0]
413
        dy = endPt[1] - startPt[1]
414

    
415
        return math.fabs(dy) > math.fabs(dx)
416

    
417
    '''
418
        @brief  get intersection point between this and given line
419
        @author humkyung
420
        @date   2018.04.21
421
        @history    Jeongwoo 2018.05.15 Add normalize
422
                    Jeongwoo 2018.05.16 Add length == 0 check
423
    '''
424

    
425
    def intersection(self, line):
426
        import math
427
        from shapely.geometry import Point, LineString
428

    
429
        startPt = self.start_point()
430
        endPt = self.end_point()
431
        dx = endPt[0] - startPt[0]
432
        dy = endPt[1] - startPt[1]
433
        length = math.sqrt(dx * dx + dy * dy)
434
        if length == 0:
435
            return None
436
        dx /= length
437
        dy /= length
438
        lhs = LineString([(startPt[0] - dx * 20, startPt[1] - dy * 20), (endPt[0] + dx * 20, endPt[1] + dy * 20)])
439
        rhs = LineString(line)
440
        return lhs.intersection(rhs)
441

    
442
    def getAngle(self, rhs):
443
        """ get angle between self and given line """
444
        import math
445

    
446
        try:
447
            return math.acos(self.dotProduct(self, rhs) / (self.length() * rhs.length()))
448
        except Exception as ex:
449
            return sys.float_info.max
450

    
451
    def isParallel(self, rhs):
452
        """ check if two lines are parallel """
453
        import math
454

    
455
        try:
456
            #vectors = [(self.end_point()[0] - self.start_point()[0], self.end_point()[1] - self.start_point()[1]),
457
            #           (rhs.end_point()[0] - rhs.start_point()[0], rhs.end_point()[1] - rhs.start_point()[1])]
458
            allowed_error = 0.01
459
            angle = self.getAngle(rhs)
460
            if (abs(angle - 0) < allowed_error) or (abs(angle - math.pi) < allowed_error):
461
                return True
462
        except ZeroDivisionError:
463
            return True
464

    
465
        return False
466

    
467
    '''
468
        @brief      check if two lines are connectable
469
        @author     humkyung
470
        @date       2018.05.12
471
        @history    Jeongwoo 18.05.15 Add check pt's type
472
                    Jeongwoo 18.05.16 Add length == 0 check
473
    '''
474

    
475
    def is_connectable(self, item, toler=20):
476
        import math
477
        from EngineeringConnectorItem import QEngineeringConnectorItem
478

    
479
        try:
480
            if type(item) is QEngineeringLineItem:
481
                startPt = item.start_point()
482
                endPt = item.end_point()
483

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

    
495
                return (pt is not None) and (type(pt) == shapely.geometry.point.Point)
496
            elif type(item) is QEngineeringConnectorItem:
497
                start_pt = self.start_point()
498
                end_pt = self.end_point()
499

    
500
                lhs = [end_pt[0] - start_pt[0], end_pt[1] - start_pt[1]]
501
                length = math.sqrt(lhs[0] * lhs[0] + lhs[1] * lhs[1])
502
                if length == 0:
503
                    return False
504

    
505
                rhs = [item.dir().x(), item.dir().y()]
506
                dot = sum([lhs[i] * rhs[i] for i in range(len(lhs))])
507
                angle = math.degrees(math.acos(dot / length))
508
                if (abs(angle) < 10) or (abs(angle - 180) < 10) or len(item.parent.connectors) == 1:
509
                    _center = item.center()
510
                    dx = [start_pt[0] - _center[0], end_pt[0] - _center[0]]
511
                    dy = [start_pt[1] - _center[1], end_pt[1] - _center[1]]
512
                    length = [math.sqrt(dx[0] * dx[0] + dy[0] * dy[0]), math.sqrt(dx[1] * dx[1] + dy[1] * dy[1])]
513
                    return length[0] < toler or length[1] < toler
514

    
515
                return False
516

    
517
        except Exception as ex:
518
            from App import App
519
            from AppDocData import MessageType
520

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

    
525
        return False
526

    
527
    '''
528
        @author     humkyung
529
        @date       2018.06.26
530
        @history    humkyung 2018.07.03 allow item to be line or symbol
531
    '''
532

    
533
    def is_connected(self, item, at=QEngineeringAbstractItem.CONNECTED_AT_PT):
534
        """ check if given item is connected to self """
535

    
536
        _connectors = [connector for connector in self.connectors if
537
                       (connector.connectedItem == item and connector._connected_at == at)]
538
        return len(_connectors) > 0
539

    
540
    '''
541
        @brief      join line to symbol
542
        @author     kyouho
543
        @date       2018.07.25
544
    '''
545

    
546
    def joinTo(self, item=None):
547
        import math
548
        from SymbolSvgItem import SymbolSvgItem
549

    
550
        # line의 Point 정의
551
        startPoint = self.start_point()
552
        endPoint = self.end_point()
553

    
554
        if item is not None and type(item) is QEngineeringLineItem:
555
            pts = [item.start_point(), item.end_point()]
556
            selected = startPoint if self._selectedIndex == 0 else endPoint if self._selectedIndex else []
557

    
558
            if selected:
559
                for i in range(len(pts)):
560
                    dx = pts[i][0] - selected[0]
561
                    dy = pts[i][1] - selected[1]
562
                    if math.sqrt(dx * dx + dy * dy) < 10:
563
                        line = QLineF(QPointF(pts[i][0], pts[i][1]),
564
                                      QPointF(endPoint[0], endPoint[1])) if self._selectedIndex == 0 else QLineF(
565
                            QPointF(startPoint[0], startPoint[1]), QPointF(pts[i][0], pts[i][1]))
566
                        self.setLine(line)
567
                        self.update()
568
                        break
569
        else:
570
            if len(item.connectors) == 2:
571
                connector1Point = item.connectors[0].center()
572
                connector2Point = item.connectors[1].center()
573

    
574
                # startPoint와 같은 connPts 찾음
575
                if startPoint[0] == connector1Point[0] and startPoint[1] == connector1Point[1]:
576
                    self.connectors[0].connectedItem = item
577
                elif startPoint[0] == connector2Point[0] and startPoint[1] == connector2Point[1]:
578
                    self.connectors[0].connectedItem = item
579

    
580
                # endPoint와 같은 connPts 찾음
581
                if endPoint[0] == connector1Point[0] and endPoint[1] == connector1Point[1]:
582
                    self.connectors[1].connectedItem = item
583
                elif endPoint[0] == connector2Point[0] and endPoint[1] == connector2Point[1]:
584
                    self.connectors[1].connectedItem = item
585

    
586
    '''
587
        @brief      arrange vertex order
588
        @author     humkyung
589
        @date       2018.07.04
590
    '''
591

    
592
    def arrangeVertexOrder(self, arranged):
593
        import math
594

    
595
        lhs = [arranged.start_point(), arranged.end_point()]
596
        rhs = [self.start_point(), self.end_point()]
597

    
598
        index = 0
599
        indexed = 0
600
        minDist = None
601
        for pt in lhs:
602
            for _pt in rhs:
603
                index += 1
604
                dx = _pt[0] - pt[0]
605
                dy = _pt[1] - pt[1]
606
                dist = math.sqrt(dx * dx + dy * dy)
607
                if minDist is None or dist < minDist:
608
                    minDist = dist
609
                    indexed = index
610

    
611
        if indexed == 1 or indexed == 4:
612
            self.reverse()
613
    
614
    def is_piping(self, strong=False):
615
        """ return true if piping line """
616
        from AppDocData import AppDocData
617

    
618
        configs = AppDocData.instance().getConfigs('Line', 'Piping')
619
        pipings = configs[0].value if 1 == len(configs) else 'Secondary,Primary'
620
        pipings = [piping.strip() for piping in pipings.split(',')]
621
    
622
        if strong:
623
            return self._lineType in pipings
624
        else:
625
            return self._lineType in pipings or self._lineType == 'Connect To Process'
626

    
627
    def next_connected(self, lhs, rhs):
628
        """ check given two item's are next connected(ex: 0-1) """
629

    
630
        lhs_matches = [at for at in range(len(self.connectors)) if self.connectors[at].connectedItem == lhs]
631
        rhs_matches = [at for at in range(len(self.connectors)) if self.connectors[at].connectedItem == rhs]
632
        if lhs_matches and rhs_matches and lhs is not rhs:
633
            return (lhs_matches[0] in [0, 1] and rhs_matches[0] in [0, 1])
634

    
635
        return False
636

    
637
    @staticmethod
638
    def check_piping(lineType, strong=False):
639
        """ return true if piping line """
640
        from AppDocData import AppDocData
641

    
642
        configs = AppDocData.instance().getConfigs('Line', 'Piping')
643
        pipings = configs[0].value if 1 == len(configs) else 'Secondary,Primary'
644
        pipings = [piping.strip() for piping in pipings.split(',')]
645

    
646
        if strong:
647
            return lineType in pipings
648
        else:
649
            return lineType in pipings or lineType == 'Connect To Process'
650

    
651
    '''
652
        @brief      check if two lines are extendable
653
        @author     humkyung
654
        @date       2018.06.25
655
        @history    humkyung 2018.06.27 check line type
656
    '''
657
    def isExtendable(self, line, toler=5):
658
        import math
659
        from SymbolSvgItem import SymbolSvgItem
660

    
661
        if self.lineType == line.lineType:
662
            if self.isHorizontal() and line.isHorizontal():
663
                flag = (line.connectors[0].connectedItem is not None and issubclass(
664
                    type(line.connectors[0].connectedItem), SymbolSvgItem)) or (
665
                                   line.connectors[1].connectedItem is not None and issubclass(
666
                               type(line.connectors[1].connectedItem), SymbolSvgItem))
667
                return flag and (math.fabs(self.start_point()[1] - line.start_point()[1]) < toler)
668
            elif self.isVertical() and line.isVertical():
669
                flag = (line.connectors[0].connectedItem is not None and issubclass(
670
                    type(line.connectors[0].connectedItem), SymbolSvgItem)) or (
671
                                   line.connectors[1].connectedItem is not None and issubclass(
672
                               type(line.connectors[1].connectedItem), SymbolSvgItem))
673
                return flag and (math.fabs(self.start_point()[0] - line.start_point()[0]) < toler)
674

    
675
        return False
676

    
677
    def is_external_point(self, pt):
678
        """ check given pt is located outside of line """
679
        import math
680

    
681
        try:
682
            dx = self.end_point()[0] - self.start_point()[0]
683
            dy = self.end_point()[1] - self.start_point()[1]
684
            lineLength = math.sqrt(dx * dx + dy * dy)
685

    
686
            dx = pt.x - self.start_point()[0]
687
            dy = pt.y - self.start_point()[1]
688
            length = math.sqrt(dx * dx + dy * dy)
689
            if length > lineLength:
690
                return True
691

    
692
            dx = pt.x - self.end_point()[0]
693
            dy = pt.y - self.end_point()[1]
694
            length = math.sqrt(dx * dx + dy * dy)
695
            if length > lineLength:
696
                return True
697

    
698
            return False
699
        except Exception as ex:
700
            from App import App
701
            from AppDocData import MessageType
702

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

    
707
    '''
708
        @author     humkyung
709
        @date       2018.04.16
710
        @history    humkyung 2018.05.08 check if line is possible to be connected
711
                    Jeongwoo 2018.05.15 Split if-statement and Connect each symbol and line
712
    '''
713

    
714
    def connect_if_possible(self, obj, toler=20):
715
        """connect line or symbol is able to be connected and return symbol or line connected to connectors
716
        this method not update item's position' \
717
        """
718

    
719
        from shapely.geometry import Point
720
        from SymbolSvgItem import SymbolSvgItem
721
        from EngineeringConnectorItem import QEngineeringConnectorItem
722

    
723
        res = []
724

    
725
        start_pt = self.start_point()
726
        end_pt = self.end_point()
727

    
728
        try:
729
            if issubclass(type(obj), SymbolSvgItem):
730
                for i in range(len(obj.connectors)):
731
                    if not self.is_connectable(obj.connectors[i]):
732
                        continue
733

    
734
                    pt = obj.connectors[i].center()
735
                    """
736
                    dist = [(self.connectors[0], Point(start_pt[0], start_pt[1]).distance(Point(pt[0], pt[1])), start_pt),
737
                            (self.connectors[1], Point(end_pt[0], end_pt[1]).distance(Point(pt[0], pt[1])), end_pt)]
738

739
                    dist.sort(key=lambda x: x[1])
740

741
                    if dist[0][0].connectedItem is None and obj.connectors[i].connectedItem is None:
742
                        dist[0][0].connect(obj)
743
                        obj.connectors[i].connect(self)
744
                        # line, start, end
745
                        res.append(obj)
746
                        res.append(obj.connectors[i].center())
747
                        res.append(dist[0][2])
748
                    """
749
                    if Point(start_pt[0], start_pt[1]).distance(Point(pt[0], pt[1])) < toler and \
750
                        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])):
751
                        if self.connectors[0].connectedItem is None and obj.connectors[i].connectedItem is None:
752
                            self.connectors[0].connect(obj)
753
                            obj.connectors[i].connect(self)
754
                            # line, start, end
755
                            res.append(obj)
756
                            res.append(obj.connectors[i].center())
757
                            res.append(end_pt)
758
                    elif Point(end_pt[0], end_pt[1]).distance(Point(pt[0], pt[1])) < toler:
759
                        if self.connectors[1].connectedItem is None and obj.connectors[i].connectedItem is None:
760
                            self.connectors[1].connect(obj)
761
                            obj.connectors[i].connect(self)
762
                            # line, start, end
763
                            res.append(obj)
764
                            res.append(start_pt)
765
                            res.append(obj.connectors[i].center())
766
            elif type(obj) is QEngineeringLineItem:
767
                _startPt = obj.start_point()
768
                _endPt = obj.end_point()
769

    
770
                # avoid zero length line, can be losted short line
771
                #if Point(_startPt[0], _startPt[1]).distance(Point(_endPt[0], _endPt[1])) < toler or \
772
                #        Point(start_pt[0], start_pt[1]).distance(Point(end_pt[0], end_pt[1])) < toler:
773
                #    return res
774

    
775
                if obj.connectors[0].connectedItem is None and self.distanceTo(_startPt) < toler:
776
                    if self.connectors[0].connectedItem is None and \
777
                            (Point(start_pt[0], start_pt[1]).distance(Point(_startPt[0], _startPt[1])) < toler):
778
                        self.connectors[0].connect(obj)
779
                        obj.connectors[0].connect(self)
780
                        res.append(obj)
781
                    elif self.connectors[1].connectedItem is None and \
782
                            (Point(end_pt[0], end_pt[1]).distance(Point(_startPt[0], _startPt[1])) < toler):
783
                        self.connectors[1].connect(obj)
784
                        obj.connectors[0].connect(self)
785
                        res.append(obj)
786
                    elif self.connectors[1].connectedItem is not obj and self.connectors[0].connectedItem is not obj and obj.connectors[1].connectedItem is not self:
787
                        obj.connectors[0].connect(self, at=QEngineeringAbstractItem.CONNECTED_AT_BODY)
788
                        res.append(obj)
789

    
790
                elif obj.connectors[1].connectedItem is None and self.distanceTo(_endPt) < toler:
791
                    if self.connectors[0].connectedItem is None and \
792
                            (Point(start_pt[0], start_pt[1]).distance(Point(_endPt[0], _endPt[1])) < toler):
793
                        self.connectors[0].connect(obj)
794
                        obj.connectors[1].connect(self)
795
                        res.append(obj)
796
                    elif self.connectors[1].connectedItem is None and \
797
                            (Point(end_pt[0], end_pt[1]).distance(Point(_endPt[0], _endPt[1])) < toler):
798
                        self.connectors[1].connect(obj)
799
                        obj.connectors[1].connect(self)
800
                        res.append(obj)
801
                    elif self.connectors[1].connectedItem is not obj and self.connectors[0].connectedItem is not obj and obj.connectors[0].connectedItem is not self:
802
                        obj.connectors[1].connect(self, at=QEngineeringAbstractItem.CONNECTED_AT_BODY)
803
                        res.append(obj)
804
        except Exception as ex:
805
            from App import App
806
            from AppDocData import MessageType
807

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

    
812
        return res
813

    
814
    '''
815
        @brief      disconnect connector item
816
        @author     kyouho
817
        @date       2018.08.30
818
    '''
819

    
820
    def disconnectedItemAtConnector(self, connector):
821
        for conn in self.connectors:
822
            if conn.isOverlapConnector(connector):
823
                conn.connectedItem = None
824

    
825
    def arrange_flow_direction(self, _from, reverse=False):
826
        """ reverse if from is connected to second connector """
827
        if not _from:
828
            raise ValueError
829

    
830
        if not reverse and self.connectors[0].connectedItem != _from:
831
            self.reverse()
832
        elif reverse and self.connectors[1].connectedItem != _from:
833
            self.reverse()
834

    
835
    def find_connected_objects(self):
836
        """find all connected items except equipment and instrument"""
837
        from EngineeringLineItem import QEngineeringLineItem
838
        from EngineeringEquipmentItem import QEngineeringEquipmentItem
839
        from SymbolSvgItem import SymbolSvgItem
840

    
841
        left_visited, right_visited = [self], [self]
842

    
843
        try:
844
            left_pool, right_pool = [self.connectors[0].connectedItem] if self.connectors[0].connectedItem else [], \
845
                                    [self.connectors[1].connectedItem] if self.connectors[1].connectedItem else []
846

    
847
            while left_pool:
848
                obj = left_pool.pop()
849

    
850
                if issubclass(type(obj), QEngineeringEquipmentItem) or (len(obj.connectors) > 2) or \
851
                        not obj.is_connected(left_visited[-1]):
852
                    continue
853

    
854
                left_visited.append(obj)
855

    
856
                if (type(obj) is QEngineeringLineItem and self.is_piping(True)) or issubclass(type(obj), SymbolSvgItem):
857
                    founds = [conn.connectedItem for conn in obj.connectors if
858
                              conn.connectedItem and conn.connectedItem not in left_visited and
859
                              2 == len(conn.connectedItem.connectors)]
860
                    for found in founds:
861
                        matches = [conn.connectedItem for conn in found.connectors if conn.connectedItem is obj]
862
                        if matches:
863
                            left_pool.append(found)
864

    
865
            while right_pool:
866
                obj = right_pool.pop()
867

    
868
                if issubclass(type(obj), QEngineeringEquipmentItem) or (len(obj.connectors) > 2) or \
869
                        not obj.is_connected(right_visited[-1]):
870
                    continue
871

    
872
                right_visited.append(obj)
873

    
874
                if (type(obj) is QEngineeringLineItem and self.is_piping(True)) or issubclass(type(obj), SymbolSvgItem):
875
                    founds = [conn.connectedItem for conn in obj.connectors if
876
                              conn.connectedItem and conn.connectedItem not in right_visited and
877
                              2 == len(conn.connectedItem.connectors)]
878
                    for found in founds:
879
                        matches = [conn.connectedItem for conn in found.connectors if conn.connectedItem is obj]
880
                        if matches:
881
                            right_pool.append(found)
882
        except Exception as ex:
883
            from App import App
884
            from AppDocData import MessageType
885

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

    
890
        return left_visited, right_visited
891

    
892
    def reverse(self, auto=False):
893
        """revere the line"""
894
        from EngineeringLineItem import QEngineeringLineItem
895
        from SymbolSvgItem import SymbolSvgItem
896

    
897
        line = self.line()
898
        self.setLine(QLineF(line.p2(), line.p1()))
899
        self.connectors[0], self.connectors[1] = self.connectors[1], self.connectors[0]
900
        self.update_arrow()
901
        self.update()
902

    
903
        if auto:
904
            left, right = self.find_connected_objects()
905

    
906
            if left:
907
                connected_item = self
908
                for obj in left[1:]:
909
                    if type(obj) is QEngineeringLineItem:
910
                        obj.arrange_flow_direction(connected_item, True)
911

    
912
                    connected_item = obj
913

    
914
            if right:
915
                connected_item = self
916
                for obj in right[1:]:
917
                    if type(obj) is QEngineeringLineItem:
918
                        obj.arrange_flow_direction(connected_item)
919

    
920
                    connected_item = obj
921

    
922
    '''
923
        @brief      add flow arrow
924
        @author     humkyung
925
        @date       2018.05.08
926
        @history    2018.05.24  Jeongwoo    Modifying Draw Flow Arrow
927
    '''
928

    
929
    def addFlowArrow(self):
930
        import numpy as np
931
        import cv2
932
        import math
933
        import sys
934
        global src
935
        from shapely.geometry import Point
936
        from QEngineeringFlowArrowItem import QEngineeringFlowArrowItem
937
        from AppDocData import AppDocData
938

    
939
        try:
940
            docData = AppDocData.instance()
941
            area = docData.getArea('Drawing')
942

    
943
            startPt = self.start_point()
944
            endPt = self.end_point()
945
            length = self.length()
946
            direction = [(endPt[0] - startPt[0]) / length, (endPt[1] - startPt[1]) / length]
947

    
948
            left = min(startPt[0], endPt[0])
949
            top = min(startPt[1], endPt[1])
950
            right = max(startPt[0], endPt[0])
951
            bottom = max(startPt[1], endPt[1])
952

    
953
            rect = None
954
            if self.isVertical():
955
                rect = QRectF(left - 10, top, (right - left) + 20, (bottom - top))
956
            else:
957
                rect = QRectF(left, top - 10, (right - left), (bottom - top) + 20)
958

    
959
            docData = AppDocData.instance()
960
            area = docData.getArea('Drawing')
961
            img = np.array(AppDocData.instance().getCurrentPidSource().getPyImageOnRect(rect))
962

    
963
            imgLine = cv2.threshold(cv2.cvtColor(img, cv2.COLOR_BGR2GRAY), 127, 255, cv2.THRESH_BINARY)[1]
964
            # remove noise
965
            imgLine = cv2.bitwise_not(imgLine)
966
            imgLine = cv2.erode(imgLine, np.ones((10, 10), np.uint8))
967

    
968
            contours, hierarchy = cv2.findContours(imgLine, cv2.RETR_TREE, cv2.CHAIN_APPROX_SIMPLE)
969
            if contours:
970
                contours = sorted(contours, key=cv2.contourArea, reverse=True)
971
                [x, y, w, h] = cv2.boundingRect(contours[0])
972
                if w > 10 and w < 100 and h > 10 and h < 100:  # check arrow mark size
973
                    imgArrowMark = imgLine[y:y + h, x:x + w]
974

    
975
                    # DEBUG - display flow arrow area
976
                    '''
977
                    item = QGraphicsBoundingBoxItem(rect.left() + x - 10, rect.top() + y - 10, w + 20, h + 20)
978
                    item.isSymbol = True
979
                    item.angle = 0
980
                    item.setPen(QPen(Qt.red, 1, Qt.SolidLine))
981
                    item.setBrush(QBrush(QColor(255,255,0,100)))
982
                    self.scene().addItem(item)
983
                    '''
984
                    # up to here
985

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

    
989
                    ####### HoughLinesP
990
                    if lines is not None:
991
                        maxLength = None
992
                        selected = None
993
                        for line in lines:
994
                            for x1, y1, x2, y2 in line:
995
                                dx = x2 - x1
996
                                dy = y2 - y1
997
                                selected = line
998
                                length = math.sqrt(dx * dx + dy * dy)
999
                                if maxLength is None or length > maxLength:
1000
                                    maxLength = length
1001
                                    selected = line
1002

    
1003
                        for x1, y1, x2, y2 in selected:
1004
                            dx = math.fabs(x2 - x1)
1005
                            dy = math.fabs(y2 - y1)
1006
                            length = math.sqrt(dx * dx + dy * dy)
1007
                            dx /= length
1008
                            dy /= length
1009
                            if (self.isVertical() and (dx < 0.001 or math.fabs(dx - 1) < 0.001)) or (
1010
                                    self.isHorizontal() and (dx < 0.001 or math.fabs(dx - 1) < 0.001)): continue
1011
                            dist1 = self.distanceTo((rect.left() + x + x1, rect.top() + y + y1))
1012
                            dist2 = self.distanceTo((rect.left() + x + x2, rect.top() + y + y2))
1013
                            if dist1 > dist2:  # point which's distance is longer would be start point
1014
                                _start = (rect.left() + x + x1, rect.top() + y + y1)
1015
                                _end = (rect.left() + x + x2, rect.top() + y + y2)
1016
                            else:
1017
                                _start = (rect.left() + x + x2, rect.top() + y + y2)
1018
                                _end = (rect.left() + x + x1, rect.top() + y + y1)
1019

    
1020
                            # DEBUG display detected line
1021
                            '''
1022
                            poly = QEngineeringPolylineItem()
1023
                            poly._pol.append(QPointF(_start[0], _start[1]))
1024
                            poly._pol.append(QPointF(_end[0], _end[1]))
1025
                            poly.setPen(QPen(Qt.red, 2, Qt.SolidLine))
1026
                            poly.buildItem()
1027
                            self.scene().addItem(poly)
1028
                            '''
1029
                            # up to here
1030

    
1031
                            dist1 = Point(startPt[0], startPt[1]).distance(Point(_start[0], _start[1]))
1032
                            dist2 = Point(startPt[0], startPt[1]).distance(Point(_end[0], _end[1]))
1033
                            if dist1 > dist2:
1034
                                startPt, endPt = endPt, startPt
1035
                                direction[0], direction[1] = -direction[0], -direction[1]
1036
                                self.reverse()
1037

    
1038
                        '''
1039
                        center = [(startPt[0]+endPt[0])*0.5, (startPt[1]+endPt[1])*0.5]
1040
                        arrow = QEngineeringFlowArrowItem(center, direction)
1041
                        arrow.buildItem()
1042
                        self.scene().addItem(arrow)
1043
                        '''
1044

    
1045
                        x = round(rect.left() + x - 5)
1046
                        y = round(rect.top() + y - 5)
1047
                        self.flowMark = ([x, y, w + 10, h + 10], None)
1048
                else:
1049
                    pass
1050
        except Exception as ex:
1051
            from App import App
1052
            from AppDocData import MessageType
1053

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

    
1058
    '''
1059
        @breif  insert symbol
1060
        @author humkyung
1061
        @date   2018.04.22
1062
        @history    kyouho  2018.07.24  add symbol angle, transform rotateRadians(-angle -> angle)
1063
    '''
1064

    
1065
    def insertSymbol(self, symbol, pos):
1066
        import math
1067
        from shapely.geometry import Point
1068
        from shapely import affinity
1069

    
1070
        vec = self.perpendicular()
1071
        line = [(pos.x() - vec[0] * 20, pos.y() - vec[1] * 20), (pos.x() + vec[0] * 20, pos.y() + vec[1] * 20)]
1072
        origin = self.intersection(line)
1073
        transform = QTransform()
1074
        transform.translate(origin.x, origin.y)
1075
        angle = self.angle()
1076
        transform.rotateRadians(-angle)
1077
        transform.translate(-symbol.symbolOrigin[0], -symbol.symbolOrigin[1])
1078
        symbol.setTransform(transform)
1079
        # save angle
1080
        symbol.angle = round(angle, 2)
1081
        if 2 == len(symbol.connectors):  # 2 way component
1082
            for i in range(len(symbol.connectors)):
1083
                rotatedPt = affinity.rotate(Point(symbol.connectors[i].connectPoint[0] - symbol.symbolOrigin[0],
1084
                                                  symbol.connectors[i].connectPoint[1] - symbol.symbolOrigin[1]),
1085
                                            -angle, Point(0, 0), use_radians=True)
1086
                #symbol.connectors[i].sceneConnectPoint = (origin.x + rotatedPt.x, origin.y + rotatedPt.y)
1087

    
1088
            dx1 = symbol.connectors[0].center()[0] - self.start_point()[0]
1089
            dy1 = symbol.connectors[0].center()[1] - self.start_point()[1]
1090
            length1 = math.sqrt(dx1 * dx1 + dy1 * dy1)
1091
            dx2 = symbol.connectors[1].center()[0] - self.start_point()[0]
1092
            dy2 = symbol.connectors[1].center()[1] - self.start_point()[1]
1093
            length2 = math.sqrt(dx2 * dx2 + dy2 * dy2)
1094

    
1095
            if length1 < length2:
1096
                processLine = QEngineeringLineItem([symbol.connectors[1].center(), self.end_point()])
1097
                processLine.connectors[0].connectedItem = symbol
1098
                processLine.connectors[1].connectedItem = self.connectors[1].connectedItem
1099
                self.scene().addItem(processLine)
1100

    
1101
                line = QLineF(self.line().p1(), QPointF(symbol.connectors[0].center()[0],
1102
                                                        symbol.connectors[0].center()[1]))
1103
                self.setLine(line)
1104
                self.connectors[1].connectedItem = symbol
1105

    
1106
                symbol.connectors[0].connectedItem = self
1107
                symbol.connectors[1].connectedItem = processLine
1108
            else:
1109
                processLine = QEngineeringLineItem([symbol.connectors[0].center(), self.end_point()])
1110
                processLine.connectors[0].connectedItem = symbol
1111
                processLine.connectors[1].connectedItem = self.connectors[1].connectedItem
1112
                self.scene().addItem(processLine)
1113

    
1114
                line = QLineF(self.line().p1(), QPointF(symbol.connectors[1].center()[0],
1115
                                                        symbol.connectors[1].center()[1]))
1116
                self.setLine(line)
1117
                self.connectors[1].connectedItem = symbol
1118

    
1119
                symbol.connectors[0].connectedItem = processLine
1120
                symbol.connectors[1].connectedItem = self
1121

    
1122
            self.joinTo(symbol)
1123
            processLine.joinTo(symbol)
1124
            self.update()
1125

    
1126
        symbol.loc = [origin.x - symbol.symbolOrigin[0], origin.y - symbol.symbolOrigin[1]]
1127
        symbol.size = [symbol.boundingRect().width(), symbol.boundingRect().height()]
1128
        self.scene().addItem(symbol)
1129

    
1130
    def update_shape(self, symbol, point):
1131
        """update line shape"""
1132
        for index in range(len(self.connectors)):
1133
            if self.connectors[index].connectedItem == symbol:
1134
                # startPoint
1135
                if index == 0:
1136
                    line = QLineF(QPointF(point[0], point[1]), self.line().p2())
1137
                    self.setLine(line)
1138
                    self.connectors[0].setPos([point[0], point[1]])
1139
                # endpoint
1140
                else:
1141
                    line = QLineF(self.line().p1(), QPointF(point[0], point[1]))
1142
                    self.setLine(line)
1143
                    self.connectors[1].setPos([point[0], point[1]])
1144

    
1145
        ## startPoint에 symbol
1146
        # if self.connectors[0].connectedItem == symbol:
1147
        #    if self.startPoint()[0] == symbol.connectors[0].sceneConnectPoint[0] and self.startPoint()[1] == symbol.connectors[0].sceneConnectPoint[1]:
1148
        #        line = QLineF(QPointF(changedConnPoint1[0], changedConnPoint1[1]), self.line().p2())
1149
        #        self.setLine(line)
1150
        #    else:
1151
        #        line = QLineF(QPointF(changedConnPoint2[0], changedConnPoint2[1]), self.line().p2())
1152
        #        self.setLine(line)
1153
        ## endPoint에 symbol
1154
        # elif self.connectors[1].connectedItem == symbol:
1155
        #    if self.endPoint()[0] == symbol.connectors[0].sceneConnectPoint[0] and self.endPoint()[1] == symbol.connectors[0].sceneConnectPoint[1]:
1156
        #        line = QLineF(self.line().p1(), QPointF(changedConnPoint1[0], changedConnPoint1[1]))
1157
        #        self.setLine(line)
1158
        #    else:
1159
        #        line = QLineF(self.line().p1(), QPointF(changedConnPoint2[0], changedConnPoint2[1]))
1160
        #        self.setLine(line)
1161

    
1162
        self.update()
1163

    
1164
    '''
1165
        @brief  remove symbol
1166
        @author humkyung
1167
        @date   2018.04.23
1168
    '''
1169

    
1170
    def removeSymbol(self, symbol):
1171
        import math
1172

    
1173
        if 2 == len(symbol.connectors):  # 2-way component
1174
            connected = symbol.connectors[0].connectedItem if symbol.connectors[0].connectedItem is not self else \
1175
            symbol.connectors[1].connectedItem
1176

    
1177
            pts = []
1178
            pts.append(self.start_point())
1179
            pts.append(self.end_point())
1180
            pts.append(connected.start_point())
1181
            pts.append(connected.end_point())
1182

    
1183
            self.scene().removeItem(connected)
1184

    
1185
            start = None
1186
            end = None
1187
            maxDist = None
1188
            for i in range(len(pts)):
1189
                for j in range(i + 1, len(pts)):
1190
                    dx = pts[i][0] - pts[j][0]
1191
                    dy = pts[i][1] - pts[j][1]
1192
                    dist = math.sqrt(dx * dx + dy * dy)
1193
                    if maxDist is None:
1194
                        maxDist = dist
1195
                        start = pts[i]
1196
                        end = pts[j]
1197
                    elif dist > maxDist:
1198
                        maxDist = dist
1199
                        start = pts[i]
1200
                        end = pts[j]
1201

    
1202
            if (pts[0] == end) or (pts[1] == start): start, end = end, start
1203

    
1204
            line = QLineF(QPointF(start[0], start[1]), QPointF(end[0], end[1]))
1205
            self.setLine(line)
1206
            # self.buildItem()
1207
            self.update()
1208

    
1209
    def validate(self):
1210
        '''
1211
            @brief  validation check : connection
1212
            @author euisung
1213
            @date   2019.04.01
1214
        '''
1215
        from EngineeringAbstractItem import QEngineeringAbstractItem
1216
        from SymbolSvgItem import SymbolSvgItem
1217
        from EngineeringEquipmentItem import QEngineeringEquipmentItem
1218
        from AppDocData import AppDocData
1219
        errors = []
1220

    
1221
        try:
1222
            _translate = QCoreApplication.translate
1223

    
1224
            docdata = AppDocData.instance()
1225
            dataPath = docdata.getErrorItemSvgPath()
1226

    
1227
            connectedUid = []
1228

    
1229
            for connector in self.connectors:
1230
                # for duplicattion check
1231
                if connector.connectedItem and issubclass(type(connector.connectedItem), QEngineeringAbstractItem):
1232
                    connectedUid.append(str(connector.connectedItem.uid))
1233

    
1234
                # check if there is not connected connector
1235
                if connector.connectedItem is None:
1236
                    error = SymbolSvgItem.createItem('Error', None, dataPath)
1237
                    error.setPosition(list(connector.center()))
1238
                    error.parent = self
1239
                    error.msg = _translate('Disconnection error', 'Disconnection error')
1240
                    error.setToolTip(error.msg)
1241
                    error.area = 'Drawing'
1242
                    error.name = 'Error'
1243
                    errors.append(error)
1244

    
1245
                # check line to symbol
1246
                elif issubclass(type(connector.connectedItem), SymbolSvgItem) and type(
1247
                        connector.connectedItem) is not QEngineeringEquipmentItem:
1248
                    matches = [conn for conn in connector.connectedItem.connectors if conn.connectedItem is self]
1249
                    # check if two items are connected each other
1250
                    if not matches:
1251
                        error = SymbolSvgItem.createItem('Error', None, dataPath)
1252
                        error.setPosition(list(connector.center()))
1253
                        error.parent = self
1254
                        error.msg = _translate('Disconnected from opposite side', 'Disconnected from opposite side')
1255
                        error.setToolTip(error.msg)
1256
                        error.area = 'Drawing'
1257
                        error.name = 'Error'
1258
                        errors.append(error)
1259
                    # check connection position
1260
                    elif not self.isOverlap(connector.sceneBoundingRect(), matches[0].sceneBoundingRect()):
1261
                        error = SymbolSvgItem.createItem('Error', None, dataPath)
1262
                        error.setPosition(list(connector.center()))
1263
                        error.parent = self
1264
                        error.msg = _translate('Mismatched position', 'Mismatched position')
1265
                        error.setToolTip(error.msg)
1266
                        error.area = 'Drawing'
1267
                        error.name = 'Error'
1268
                        errors.append(error)
1269

    
1270
                elif issubclass(type(connector.connectedItem), QEngineeringLineItem):
1271
                    # check if connected two lines has same direction
1272
                    if connector._connected_at == QEngineeringAbstractItem.CONNECTED_AT_PT:
1273
                        center = connector.center()
1274

    
1275
                        indices = [0, 0]
1276
                        indices[0] = 1 if QPointF(center[0], center[1]) == self.line().p1() else 2
1277
                        matches = [conn for conn in connector.connectedItem.connectors if
1278
                                   conn.connectedItem == self and conn._connected_at == QEngineeringAbstractItem.CONNECTED_AT_PT]
1279
                        if matches:
1280
                            indices[1] = 1 if QPointF(matches[0].center()[0], matches[0].center()[
1281
                                1]) == connector.connectedItem.line().p1() else 2
1282
                        else:
1283
                            error = SymbolSvgItem.createItem('Error', None, dataPath)
1284
                            error.setPosition(list(connector.center()))
1285
                            error.parent = self
1286
                            error.msg = _translate('Disconnected from opposite side', 'Disconnected from opposite side')
1287
                            error.setToolTip(error.msg)
1288
                            error.area = 'Drawing'
1289
                            error.name = 'Error'
1290
                            errors.append(error)
1291

    
1292
                        if indices[0] == indices[1]:
1293
                            error = SymbolSvgItem.createItem('Error', None, dataPath)
1294
                            error.setPosition(list(connector.center()))
1295
                            error.parent = self
1296
                            error.msg = _translate('Flow direction error', 'Flow direction error')
1297
                            error.setToolTip(error.msg)
1298
                            error.area = 'Drawing'
1299
                            error.name = 'Error'
1300
                            errors.append(error)
1301

    
1302
                        if self.lineType != connector.connectedItem.lineType:
1303
                            error = SymbolSvgItem.createItem('Error', None, dataPath)
1304
                            error.setPosition(list(connector.center()))
1305
                            error.parent = self
1306
                            error.msg = _translate('Line type error', 'Line type error')
1307
                            error.setToolTip(error.msg)
1308
                            error.area = 'Drawing'
1309
                            error.name = 'Error'
1310
                            errors.append(error)
1311
                    
1312
                    else:
1313
                        center = connector.center()
1314
                        line = connector.connectedItem
1315

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

    
1319
                        if line.distanceTo(center) > toler * 2:
1320
                            error = SymbolSvgItem.createItem('Error', None, dataPath)
1321
                            error.setPosition(list(connector.center()))
1322
                            error.parent = self
1323
                            error.msg = _translate('Line position error', 'Line position error')
1324
                            error.setToolTip(error.msg)
1325
                            error.area = 'Drawing'
1326
                            error.name = 'Error'
1327
                            errors.append(error)
1328

    
1329
                errors.extend(connector.validate())
1330

    
1331
            # check duplicated connection
1332
            if len(connectedUid) is not len(set(connectedUid)):
1333
                error = SymbolSvgItem.createItem('Error', None, dataPath)
1334
                error.setPosition([self.sceneBoundingRect().center().x(), self.sceneBoundingRect().center().y()])
1335
                error.parent = self
1336
                error.msg = _translate('Duplicated connection error', 'Duplicated connection error')
1337
                error.setToolTip(error.msg)
1338
                error.area = 'Drawing'
1339
                error.name = 'Error'
1340
                errors.append(error)
1341

    
1342
        except Exception as ex:
1343
            from App import App
1344
            from AppDocData import MessageType
1345

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

    
1350
        return errors
1351

    
1352
    '''
1353
        @brief  update line type
1354
        @author humkyung
1355
        @date   2018.07.05
1356
    '''
1357

    
1358
    def update_line_type(self):
1359
        import uuid
1360
        from LineTypeConditions import LineTypeConditions
1361

    
1362
        try:
1363
            pool, visited, items = [self], [], []
1364
            while pool:
1365
                obj = pool.pop()
1366
                visited.append(obj)
1367

    
1368
                """ connected items """
1369
                connected = [connector.connectedItem for connector in obj.connectors if
1370
                             connector.connectedItem and not type(connector.connectedItem) is uuid.UUID]
1371
                """ connected lines at point """
1372
                lines = [connector.connectedItem for connector in obj.connectors if connector.connectedItem and type(
1373
                    connector.connectedItem) is QEngineeringLineItem and connector._connected_at == QEngineeringAbstractItem.CONNECTED_AT_PT]
1374
                """ add items not in 'connected' to items list """
1375
                items.extend([item for item in connected if item not in lines])
1376
                """ add items not visited to pool """
1377
                pool.extend([item for item in lines if item not in visited])
1378

    
1379
            for condition in LineTypeConditions.items():
1380
                if condition.eval(items):
1381
                    self.lineType = condition.name
1382
                    break
1383
                if condition.eval(items, reverse=True):
1384
                    self.lineType = condition.name
1385
                    break
1386
        except Exception as ex:
1387
            from App import App
1388
            from AppDocData import MessageType
1389

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

    
1394
    def hoverEnterEvent(self, event, minimum=False):
1395
        """ hilight item and it's children """
1396
        self.highlight(True, minimum)
1397

    
1398
    def hoverLeaveEvent(self, event, minimum=False):
1399
        """ restore original color """
1400
        self.highlight(False, minimum)
1401

    
1402
    def highlight(self, flag, minimum=False):
1403
        self.hover = flag
1404
        self.setZValue(QEngineeringAbstractItem.HOVER_ZVALUE) if flag else self.setZValue(QEngineeringLineItem.ZVALUE)
1405
        self.update()
1406

    
1407
        if not minimum:
1408
            for assoc in self.associations():
1409
                assoc.highlight(flag)
1410

    
1411
            for connector in self.connectors:
1412
                connector.highlight(flag)
1413

    
1414
    def hoverMoveEvent(self, event):
1415
        pass
1416

    
1417
    '''
1418
        @brief      remove item when user press delete key
1419
        @author     humkyung
1420
        @date       2018.04.23
1421
        @history    swap start, end point when user press 'c' key
1422
    '''
1423

    
1424
    def keyPressEvent(self, event):
1425
        from EngineeringLineNoTextItem import QEngineeringLineNoTextItem
1426

    
1427
        #if self.isSelected() and event.key() == Qt.Key_Delete:
1428
        #    self.scene().removeItem(self)
1429
        if event.key() == Qt.Key_C:# and self.is_piping(True):
1430
            if self.owner and issubclass(type(self.owner), QEngineeringLineNoTextItem):
1431
                index = 1
1432
                for run in self.owner.runs:
1433
                    if self in run.items:
1434
                        if index == 1:
1435
                            self.owner.reverse()
1436
                        else:
1437
                            run.reverse()
1438
                        break
1439
                    else:
1440
                        index = index + 1
1441
            else:
1442
                self.reverse(True)
1443
        elif event.key() == Qt.Key_A:
1444
            self.toggleFlowMark()
1445
        elif event.key() == Qt.Key_Up:  # translate up/down/left/right symbol
1446
            modifiers = QApplication.keyboardModifiers()
1447
            delta = 10 if modifiers == Qt.ControlModifier else 1
1448

    
1449
            dx, dy = 0*delta, -1*delta
1450
            self.set_line([[self.start_point()[0] + dx, self.start_point()[1] + dy],
1451
                           [self.end_point()[0] + dx, self.end_point()[1] + dy]])
1452
        elif event.key() == Qt.Key_Down:
1453
            modifiers = QApplication.keyboardModifiers()
1454
            delta = 10 if modifiers == Qt.ControlModifier else 1
1455

    
1456
            dx, dy = 0*delta, 1*delta
1457
            self.set_line([[self.start_point()[0] + dx, self.start_point()[1] + dy],
1458
                           [self.end_point()[0] + dx, self.end_point()[1] + dy]])
1459
        elif event.key() == Qt.Key_Left:
1460
            modifiers = QApplication.keyboardModifiers()
1461
            delta = 10 if modifiers == Qt.ControlModifier else 1
1462

    
1463
            dx, dy = -1*delta, 0*delta
1464
            self.set_line([[self.start_point()[0] + dx, self.start_point()[1] + dy],
1465
                           [self.end_point()[0] + dx, self.end_point()[1] + dy]])
1466
        elif event.key() == Qt.Key_Right:
1467
            modifiers = QApplication.keyboardModifiers()
1468
            delta = 10 if modifiers == Qt.ControlModifier else 1
1469

    
1470
            dx, dy = 1*delta, 0*delta
1471
            self.set_line([[self.start_point()[0] + dx, self.start_point()[1] + dy],
1472
                           [self.end_point()[0] + dx, self.end_point()[1] + dy]])
1473
        elif event.key() == Qt.Key_M:
1474
            from App import App
1475
            App.mainWnd().keyPressEvent(event)
1476

    
1477
    def toggleFlowMark(self):
1478
        from AppDocData import AppDocData
1479

    
1480
        if self.flowMark:
1481
            self.flowMark = None
1482
        else:
1483
            configs = AppDocData.instance().getConfigs('Flow Mark', 'Position')
1484
            self.flowMark = int(configs[0].value) if 1 == len(configs) else 100
1485
        self.update_arrow()
1486

    
1487
    def shape(self):
1488
        """return shape for non-rectangle shape"""
1489
        x1, y1, x2, y2 = self.line().x1(), self.line().y1(), self.line().x2(), self.line().y2()
1490
        path = QPainterPath()
1491
        path.moveTo(x1, y1)
1492
        path.lineTo(x2, y2)
1493

    
1494
        stroke = QPainterPathStroker()
1495
        stroke.setWidth(15)
1496
        return stroke.createStroke(path)
1497

    
1498
    def paint(self, painter, option, widget):
1499
        # override paint method
1500
        color = self.getColor()
1501
        self.setColor(color)
1502

    
1503
        painter.setPen(self.pen())
1504
        painter.drawLine(self.line())
1505
        #QGraphicsLineItem.paint(self, painter, options, widget)
1506

    
1507
        if self.isSelected():
1508
            painter.setOpacity(0.7)
1509
            painter.setPen(QPen(Qt.black, 1.5, Qt.DashLine))
1510
            painter.setBrush(QEngineeringLineItem.HIGHLIGHT_BRUSH)
1511
            painter.drawPolygon(self.minimum_bounding_box)
1512

    
1513
    def drawToImage(self, img, color, thickness):
1514
        """write recognized lines to image"""
1515
        try:
1516
            ptStart = self.start_point()
1517
            ptEnd = self.end_point()
1518
            cv2.line(img, (round(ptStart[0]), round(ptStart[1])), (round(ptEnd[0]), round(ptEnd[1])), color, thickness)
1519
            # up to here
1520
        except Exception as ex:
1521
            message = f"error occurred({repr(ex)}) in {sys.exc_info()[-1].tb_frame.f_code.co_filename}:" \
1522
                      f"{sys.exc_info()[-1].tb_lineno}"
1523

    
1524
    @staticmethod
1525
    def from_database(component):
1526
        """ get line from database """
1527
        import uuid
1528
        from AppDocData import AppDocData
1529
        from SymbolAttr import SymbolAttr
1530

    
1531
        item = None
1532
        try:
1533
            uidNode = component['UID']
1534
            uid = uidNode if uidNode is not None else uuid.uuid4()  # generate UUID
1535
            owner = uuid.UUID(component['Owner']) if component['Owner'] and component['Owner'] != 'None' else None
1536

    
1537
            app_doc_data = AppDocData.instance()
1538
            connectors = app_doc_data.get_component_connectors(uid)
1539
            if 2 != len(connectors): return item
1540
            startPoint = [float(connectors[0]['X']), float(connectors[0]['Y'])]
1541
            endPoint = [float(connectors[1]['X']), float(connectors[1]['Y'])]
1542

    
1543
            item = QEngineeringLineItem(vertices=[startPoint, endPoint], uid=uid)
1544
            item.setVisible(False)
1545
            attrs = app_doc_data.get_component_attributes(uid)
1546
            matches = [attr for attr in attrs if attr['Attribute'] == 'LineType']
1547
            item.lineType = matches[0]['Value'] if matches else 'Secondary'
1548
            if matches:
1549
                attrs.remove(matches[0])
1550

    
1551
            for key in item._properties.keys():
1552
                for compo in component.keys():
1553
                    if key.Attribute == compo:
1554
                        item._properties[key] = key.parse_value(component[key.Attribute]) if component[
1555
                            key.Attribute] else ''
1556

    
1557
            ## assign area
1558
            if component['Area']:
1559
                for area in app_doc_data.getAreaList():
1560
                    if area.contains(startPoint) and area.contains(endPoint):
1561
                        item.area = area.name
1562
                        break
1563
            else:
1564
                item.area = component['Area']
1565
            ## up to here
1566

    
1567
            matches = [attr for attr in attrs if attr['Attribute'] == 'Thickness']
1568
            item.thickness = int(matches[0]['Value']) if matches and matches[0]['Value'] != 'None' else None
1569
            if matches:
1570
                attrs.remove(matches[0])
1571

    
1572
            matches = [attr for attr in attrs if attr['Attribute'] == 'FlowMark']
1573
            item.flowMark = int(matches[0]['Value']) if matches and matches[0]['Value'] != 'None' else None
1574
            if matches:
1575
                attrs.remove(matches[0])
1576

    
1577
            if connectors:
1578
                iterIndex = 0
1579
                for connector in connectors:
1580
                    item.connectors[iterIndex].parse_record(connector)
1581
                    iterIndex += 1
1582

    
1583
            # get associations
1584
            associations = app_doc_data.get_component_associations(uid)
1585
            if associations:
1586
                for assoc in associations:
1587
                    _type = assoc['Type']
1588
                    if not _type in item._associations:
1589
                        item._associations[_type] = []
1590
                    item._associations[_type].append(uuid.UUID(assoc['Association']))
1591
            # up to here
1592

    
1593
            if attrs:
1594
                for attr in attrs:
1595
                    _attr = SymbolAttr.from_record(attr)
1596
                    item.attrs[_attr] = attr['Value']
1597

    
1598
        except Exception as ex:
1599
            from App import App
1600
            from AppDocData import MessageType
1601

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

    
1606
        return item if item.length() > 1 else None
1607

    
1608
    '''
1609
        @brief      parse xml code
1610
        @author     humkyung
1611
        @date       2018.06.27
1612
    '''
1613

    
1614
    @staticmethod
1615
    def fromXml(node):
1616
        import uuid
1617
        from AppDocData import AppDocData
1618
        from SymbolAttr import SymbolAttr
1619

    
1620
        item = None
1621
        try:
1622
            uidNode = node.find('UID')
1623
            uid = uidNode.text if uidNode is not None else uuid.uuid4()  # generate UUID
1624
            owner = uuid.UUID(node.attrib['OWNER']) if 'OWNER' in node.attrib and node.attrib[
1625
                'OWNER'] != 'None' else None
1626

    
1627
            startPoint = [float(x) for x in node.find('STARTPOINT').text.split(',')]
1628
            endPoint = [float(x) for x in node.find('ENDPOINT').text.split(',')]
1629

    
1630
            item = QEngineeringLineItem(vertices=[startPoint, endPoint], uid=uid)
1631
            if owner:
1632
                item._owner = owner
1633
            item.setVisible(False)
1634
            item.lineType = node.find('TYPE').text if node.find('TYPE') is not None else 'Secondary'
1635
            # assign area
1636
            if node.find('AREA') is None:
1637
                appDocData = AppDocData.instance()
1638
                for area in appDocData.getAreaList():
1639
                    if area.contains(startPoint) and area.contains(endPoint):
1640
                        item.area = area.name
1641
                        break
1642
            else:
1643
                item.area = node.find('AREA').text
1644
            # up to here
1645

    
1646
            thicknessNode = node.find('THICKNESS')
1647
            item.thickness = int(
1648
                thicknessNode.text) if thicknessNode is not None and thicknessNode.text != 'None' else None
1649

    
1650
            flowMarkNode = node.find('FLOWMARK')
1651
            item.flowMark = int(flowMarkNode.text) if flowMarkNode is not None and flowMarkNode.text != 'None' else None
1652

    
1653
            connectors = node.find('CONNECTORS')
1654
            if connectors is not None:
1655
                iterIndex = 0
1656
                for connector in connectors.iter('CONNECTOR'):
1657
                    item.connectors[iterIndex].parse_xml(connector)
1658
                    iterIndex += 1
1659

    
1660
            # get associations
1661
            attributeValue = node.find('ASSOCIATIONS')
1662
            if attributeValue is not None:
1663
                for assoc in attributeValue.iter('ASSOCIATION'):
1664
                    _type = assoc.attrib['TYPE']
1665
                    if not _type in item._associations:
1666
                        item._associations[_type] = []
1667
                    item._associations[_type].append(uuid.UUID(assoc.text))
1668
            # up to here
1669

    
1670
            attributes = node.find('SYMBOLATTRIBUTES')
1671
            if attributes is not None:
1672
                for attr in attributes.iter('ATTRIBUTE'):
1673
                    _attr = SymbolAttr.fromXml(attr)
1674
                    item.attrs[_attr] = attr.text
1675

    
1676
            for prop_node in node.iter('PROPERTY'):
1677
                matches = [prop for prop in item._properties.keys() if
1678
                            prop.Attribute == prop_node.attrib['Attribute']]
1679
                if matches:
1680
                    item._properties[matches[0]] = matches[0].parse_value(prop_node.text) if prop_node.text else ''
1681

    
1682
        except Exception as ex:
1683
            from App import App
1684
            from AppDocData import MessageType
1685

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

    
1690
        #print(str(item.length()))
1691
        return item if item.length() > 1 else None
1692

    
1693
    '''
1694
        @brief      generate xml code
1695
        @author     humkyung
1696
        @date       2018.04.23
1697
        @history    humkyung 2018.06.27 write line type to xml
1698
                    humkyung 2018.07.23 write connected item's uid to xml
1699
    '''
1700

    
1701
    def toXml(self):
1702
        from xml.etree.ElementTree import Element, SubElement, dump, ElementTree
1703
        from LineTypeConditions import LineTypeConditions
1704
        from SymbolAttr import SymbolAttr
1705

    
1706
        try:
1707
            node = Element('LINE')
1708
            node.attrib['OWNER'] = str(self._owner) if self._owner else 'None'
1709
            uidNode = Element('UID')
1710
            uidNode.text = str(self.uid)
1711
            node.append(uidNode)
1712

    
1713
            startPt = self.start_point()
1714
            endPt = self.end_point()
1715

    
1716
            startNode = Element('STARTPOINT')
1717
            startNode.text = '{},{}'.format(startPt[0], startPt[1])
1718
            node.append(startNode)
1719

    
1720
            endNode = Element('ENDPOINT')
1721
            endNode.text = '{},{}'.format(endPt[0], endPt[1])
1722
            node.append(endNode)
1723

    
1724
            typeNode = Element('TYPE')
1725
            typeNode.text = self.lineType
1726
            for lineType in LineTypeConditions.items():
1727
                if self.lineType == lineType.name:
1728
                    typeNode.attrib['TYPEUID'] = str(lineType)
1729
                    break
1730
            node.append(typeNode)
1731

    
1732
            areaNode = Element('AREA')
1733
            areaNode.text = self.area
1734
            node.append(areaNode)
1735

    
1736
            thicknessNode = Element('THICKNESS')
1737
            thicknessNode.text = str(self.thickness)
1738
            node.append(thicknessNode)
1739

    
1740
            flowMarkNode = Element('FLOWMARK')
1741
            flowMarkNode.text = str(self.flowMark)
1742
            node.append(flowMarkNode)
1743

    
1744
            connectorsNode = Element('CONNECTORS')
1745
            for connector in self.connectors:
1746
                connectorsNode.append(connector.toXml())
1747
            node.append(connectorsNode)
1748

    
1749
            attributeValueNode = Element('ASSOCIATIONS')
1750
            for assoc in self.associations():
1751
                assoc_node = Element('ASSOCIATION')
1752
                assoc_node.attrib['TYPE'] = QEngineeringAbstractItem.assoc_type(assoc)
1753
                assoc_node.text = str(assoc.uid)
1754
                attributeValueNode.append(assoc_node)
1755
            node.append(attributeValueNode)
1756

    
1757
            properties_node = Element('PROPERTIES')
1758
            for prop, value in self.properties.items():
1759
                prop_node = prop.toXml()
1760
                prop_node.text = str(value) if value else ''
1761
                properties_node.append(prop_node)
1762
            node.append(properties_node)
1763

    
1764
            attributesNode = Element('SYMBOLATTRIBUTES')
1765
            _attrs = self.getAttributes()
1766
            for attr in _attrs:
1767
                if type(attr) is SymbolAttr:
1768
                    _node = attr.toXml()
1769
                    _node.text = str(_attrs[attr])
1770
                    attributesNode.append(_node)
1771

    
1772
            node.append(attributesNode)
1773

    
1774
            # up to here
1775
        except Exception as ex:
1776
            from App import App
1777
            from AppDocData import MessageType
1778

    
1779
            message = 'error occurred({}) in {}:{}'.format(ex, sys.exc_info()[-1].tb_frame.f_code.co_filename,
1780
                                                           sys.exc_info()[-1].tb_lineno)
1781
            App.mainWnd().addMessage.emit(MessageType.Error, message)
1782
            return None
1783

    
1784
        return node
1785

    
1786
    def to_svg(self, parent) -> list:
1787
        """convert line item to svg"""
1788
        from xml.etree.ElementTree import Element, SubElement, dump, ElementTree
1789
        from App import App
1790
        from AppDocData import AppDocData
1791

    
1792
        res = []
1793
        try:
1794
            app_doc_data = AppDocData.instance()
1795
            prj = app_doc_data.getCurrentProject()
1796

    
1797
            node = Element('g')
1798
            node.attrib['id'] = str(self.uid)
1799
            node.attrib['stroke'] = self.pen().color().name()
1800
            node.attrib['stroke-width'] = str(int(self.pen().width()*0.5))
1801

    
1802
            line_style_map = {Qt.DotLine: '2', Qt.DashLine: '4 2',
1803
                              Qt.DashDotLine: '4 2 2', Qt.DashDotDotLine: '4 2 2 2 2 2'}
1804
            line_type_style = self.line_type_style
1805
            if line_type_style[3] in line_style_map:
1806
                node.attrib['stroke-dasharray'] = line_style_map[line_type_style[3]]
1807

    
1808
            if not parent:
1809
                trans = QTransform()
1810
            else:
1811
                trans, _ = parent.sceneTransform().inverted()
1812

    
1813
            node.attrib['transform'] = f"matrix(" \
1814
                                       f"{trans.m11()},{trans.m12()}," \
1815
                                       f"{trans.m21()},{trans.m22()}," \
1816
                                       f"{trans.m31()},{trans.m32()}" \
1817
                                       f")"
1818

    
1819
            polyline = Element('polyline')
1820
            polyline.attrib['fill'] = 'none'
1821
            start_pt = self.start_point()
1822
            end_pt = self.end_point()
1823
            polyline.attrib['points'] = f"{start_pt[0]},{start_pt[1]} " \
1824
                                        f"{end_pt[0]},{end_pt[1]}"
1825
            node.append(polyline)
1826

    
1827
            res.append(node)
1828
        except Exception as ex:
1829
            from App import App
1830
            from AppDocData import MessageType
1831
            message = 'error occurred({}) in {}:{}'.format(ex, sys.exc_info()[-1].tb_frame.f_code.co_filename,
1832
                                                          sys.exc_info()[-1].tb_lineno)
1833
            App.mainWnd().addMessage.emit(MessageType.Error, message)
1834

    
1835
        return res
1836

    
1837
    def toSql_return_separately(self):
1838
        """ generate sql phrase to save line to database """
1839
        import uuid
1840
        from AppDocData import AppDocData
1841

    
1842
        res = []
1843
        resLater = []
1844

    
1845
        app_doc_data = AppDocData.instance()
1846
        cols = ['UID', 'Drawings_UID', 'Symbol_UID', 'X', 'Y', 'Width', 'Height', 'Rotation', 'Area', 'Owner',
1847
                'SpecialItemTypes_UID', 'Freeze']
1848
        values = ['?', '?', "(select UID from Symbol where Name='Line' and SymbolType_UID=-1)", '?', '?', '?', '?', '?',
1849
                  '?', '?', '?', '?']
1850

    
1851
        rect = self.sceneBoundingRect()
1852
        param = [
1853
            (str(self.uid), str(app_doc_data.activeDrawing.UID), rect.x(), rect.y(), rect.width(), rect.height(), 0,
1854
             self.area, str(self.owner) if self.owner else None,
1855
             str(self.special_item_type) if self.special_item_type else None, self.prop('Freeze') if self.prop('Freeze') else 0)]
1856
        sql = 'insert into Components({}) values({})'.format(','.join(cols), ','.join(values))
1857
        res.append((sql, tuple(param)))
1858

    
1859
        _attrs = self.getAttributes()
1860
        if _attrs:
1861
            cols = ['UID', 'Components_UID', 'SymbolAttribute_UID', 'Value', 'Association_UID', 'Freeze']
1862
            values = ['?', '?', '?', '?', '?', '?']
1863
            params = []
1864
            for key in _attrs.keys():
1865
                if key.AttributeType != 'Spec':
1866
                    params.append((str(uuid.uuid4()), str(self.uid), str(key.UID), str(_attrs[key]), str(key.AssocItem),
1867
                                   str(key.Freeze)))
1868
                elif key.AttributeType == 'Spec':
1869
                    if type(_attrs[key]) is not list: continue
1870
                    params.append((str(uuid.uuid4()), str(self.uid), str(key.UID), (str(_attrs[key][0]) + ',' + str(_attrs[key][1])), str(key.AssocItem),
1871
                                   str(key.Freeze)))
1872
            sql = 'insert into Attributes({}) values({})'.format(','.join(cols), ','.join(values))
1873
            res.append((sql, tuple(params)))
1874

    
1875
        if self.associations():
1876
            cols = ['UID', '[Type]', 'Components_UID', 'Association']
1877
            values = ['?', '?', '?', '?']
1878
            params = []
1879
            for assoc in self.associations():
1880
                params.append(
1881
                    (str(uuid.uuid4()), QEngineeringAbstractItem.assoc_type(assoc), str(self.uid), str(assoc.uid)))
1882
            sql = 'insert into Associations({}) values({})'.format(','.join(cols), ','.join(values))
1883
            resLater.append((sql, tuple(params)))
1884

    
1885
        # save connectors to database
1886
        cols = ['Components_UID', '[Index]', 'X', 'Y', 'Connected', 'Connected_At']
1887
        values = ['?', '?', '?', '?', '?', '?']
1888
        params = []
1889
        index = 1
1890
        for connector in self.connectors:
1891
            params.append((  # str(connector.uid),
1892
                str(self.uid), index, connector.center()[0], connector.center()[1], \
1893
                str(connector.connectedItem.uid) if connector.connectedItem else None, \
1894
                str(connector._connected_at)))
1895
            index += 1
1896
        sql = 'insert into Points({}) values({})'.format(','.join(cols), ','.join(values))
1897
        resLater.append((sql, tuple(params)))
1898
        # up to here
1899

    
1900
        # save attributes
1901
        cols = ['UID', 'Components_UID', 'SymbolAttribute_UID', 'Value']
1902
        values = ['?', '?', "(select UID from SymbolAttribute where Attribute='LineType' and SymbolType_UID=-1)", '?']
1903
        param = [(str(uuid.uuid4()), str(self.uid), self.lineType)]
1904
        sql = 'insert into Attributes({}) values({})'.format(','.join(cols), ','.join(values))
1905
        res.append((sql, tuple(param)))
1906
        values = ['?', '?', "(select UID from SymbolAttribute where Attribute='Thickness' and SymbolType_UID=-1)", '?']
1907
        param = [(str(uuid.uuid4()), str(self.uid), str(self.thickness))]
1908
        sql = 'insert into Attributes({}) values({})'.format(','.join(cols), ','.join(values))
1909
        res.append((sql, tuple(param)))
1910
        values = ['?', '?', "(select UID from SymbolAttribute where Attribute='FlowMark' and SymbolType_UID=-1)", '?']
1911
        param = [(str(uuid.uuid4()), str(self.uid), str(self.flowMark))]
1912
        sql = 'insert into Attributes({}) values({})'.format(','.join(cols), ','.join(values))
1913
        res.append((sql, tuple(param)))
1914
        # up to here
1915

    
1916
        return res, resLater
1917

    
1918
    '''
1919
        @brief      Delete Line Item from scene
1920
        @author     Jeongwoo
1921
        @date       2018.05.29
1922
        @history    2018.05.29  Add parameter 'self' / Make comments emit()
1923
    '''
1924

    
1925
    def deleteLineItemFromScene(self):
1926
        self.scene().removeItem(self)
1927

    
1928
    def setColor(self, color):
1929
        """Set Color. Override QEngineeringAbstractItem's"""
1930
        if color.upper() != self.pen().color().name().upper():
1931
            c = QColor()
1932
            c.setNamedColor(color)
1933
            _pen = self.pen()
1934
            _pen.setColor(c)
1935
            self.setPen(_pen)
1936
            #self.update()
1937

    
1938
    def clear_labels(self):
1939
        """ clear spec labels """
1940
        attrs = self.getAttributes()
1941
        index = 0
1942
        for key in attrs.keys():
1943
            #if index >= 6:
1944
            #    break
1945
            if key.AssocItem and not key.Freeze:
1946
                key.AssocItem.owner = None
1947
                self.remove_assoc_item(key.AssocItem)
1948
                key.AssocItem = None
1949
            if not key.Freeze:
1950
                attrs[key] = ''
1951
            index += 1
1952
    
1953
    def update_flow_mark(self, position, length):
1954
        """ update flow mark for flow arrow """
1955
        import math
1956

    
1957
        to_item = self.connectors[1].connectedItem
1958
        if type(to_item) is QEngineeringLineItem and self.length() > length and not self.isParallel(to_item):
1959
            self.flowMark = position
1960

    
1961
    @staticmethod
1962
    def update_arrows(lines):
1963
        for line in lines:
1964
            line.update_arrow()
1965

    
1966
        return lines
1967

    
1968
    def update_arrow(self):
1969
        """ update flow arrow """
1970
        import math
1971
        from EngineeringArrowItem import QEngineeringArrowItem
1972

    
1973
        if self.length() < 0.01:
1974
            return
1975

    
1976
        start = self.line().p1()
1977
        end = self.line().p2()
1978

    
1979
        dx = end.x() - start.x()
1980
        dy = end.y() - start.y()
1981
        _dir = [dx / self.length(), dy / self.length()]
1982

    
1983
        arrow_size = QEngineeringLineItem.ARROW_SIZE * 0.25
1984

    
1985
        # allow flow mark at signal line
1986
        #if not self.is_piping():
1987
        #    self.flowMark = None
1988

    
1989
        if self.flowMark:
1990
            arrow_size *= 2
1991
            end = QPointF(start.x() + dx * self.flowMark / 100, start.y() + dy * self.flowMark / 100)
1992

    
1993
        perpendicular = (-_dir[1], _dir[0])
1994
        polygon = QPolygonF()
1995
        polygon.append(QPointF(end.x() - _dir[0] * QEngineeringLineItem.ARROW_SIZE + perpendicular[0] * arrow_size,
1996
                               end.y() - _dir[1] * QEngineeringLineItem.ARROW_SIZE + perpendicular[1] * arrow_size))
1997
        polygon.append(QPointF(end.x() - _dir[0] * QEngineeringLineItem.ARROW_SIZE - perpendicular[0] * arrow_size,
1998
                               end.y() - _dir[1] * QEngineeringLineItem.ARROW_SIZE - perpendicular[1] * arrow_size))
1999
        polygon.append(end)
2000
        polygon.append(polygon[0])  # close polygon
2001

    
2002
        if not hasattr(self, '_arrow'):
2003
            self._arrow = QEngineeringArrowItem(polygon, self)
2004
        else:
2005
            self._arrow.setPolygon(polygon)
2006

    
2007
        if self.flowMark:
2008
            self._arrow.setBrush(Qt.red)
2009
        else:
2010
            self._arrow.setBrush(Qt.blue)
2011
        self._arrow.update()
2012

    
2013
    def onConnectorPosChaned(self, connector):
2014
        """update line shape when connector is moved"""
2015

    
2016
        start = self.connectors[0].center()
2017
        end = self.connectors[1].center()
2018
        self.set_line([[start[0], start[1]], [end[0], end[1]]])
2019

    
2020
        if hasattr(self.scene(), 'contents_changed'):
2021
            self.scene().contents_changed.emit()
2022

    
2023
        # register resize command
2024
        pt_start = self.connectors[0].pressed_position if self.connectors[0].pressed_position else None
2025
        pt_end = self.connectors[-1].pressed_position if self.connectors[-1].pressed_position else None
2026
        if pt_start or pt_end:
2027
            from ResizeCommand import ResizeCommand
2028
            cmd = ResizeCommand(self.scene(), self,
2029
                                [pt_start if pt_start else
2030
                                 QPointF(self.connectors[0].center()[0], self.connectors[0].center()[1]),
2031
                                 pt_end if pt_end else
2032
                                 QPointF(self.connectors[-1].center()[0], self.connectors[-1].center()[-1])])
2033
            self.scene().undo_stack.push(cmd)
2034

    
2035
    '''
2036
        @brief      
2037
        @author     humkyung
2038
        @date       2018.07.24
2039
    '''
2040

    
2041
    def mousePressEvent(self, event):
2042
        import math
2043

    
2044
        if event.buttons() == Qt.LeftButton:
2045
            pos = event.scenePos()
2046
            ptStart = self.start_point()
2047
            dx = ptStart[0] - pos.x()
2048
            dy = ptStart[1] - pos.y()
2049
            if math.sqrt(dx * dx + dy * dy) < 10:
2050
                self._selectedIndex = 0
2051
                return
2052

    
2053
            ptEnd = self.end_point()
2054
            dx = ptEnd[0] - pos.x()
2055
            dy = ptEnd[1] - pos.y()
2056
            if math.sqrt(dx * dx + dy * dy) < 10:
2057
                self._selectedIndex = 1
2058
                return
2059

    
2060
            self._selectedIndex = -1
2061

    
2062
        QGraphicsLineItem.mousePressEvent(self, event)
2063

    
2064
    def mouseReleaseEvent(self, event):
2065
        self._selectedIndex = -1
2066

    
2067
        QGraphicsLineItem.mouseReleaseEvent(self, event)
2068

    
2069
    def contextMenuEvent(self, event):
2070
        items = self.scene().selectedItems()
2071
        if len(items) > 0 and self in items:
2072
            menu = QMenu()
2073

    
2074
            showAction = QAction('Show Onwer', None)
2075
            showAction.triggered.connect(self.contextShow)
2076
            menu.addAction(showAction)
2077

    
2078
            mergeAction = QAction('Merge(M)', None)
2079
            mergeAction.triggered.connect(self.contextMerge)
2080
            menu.addAction(mergeAction)
2081

    
2082
            reverseAction = QAction('Reverse(C)', None)
2083
            reverseAction.triggered.connect(self.contextReverse)
2084
            menu.addAction(reverseAction)
2085

    
2086
            arrowAction = QAction('Arrow(A)', None)
2087
            arrowAction.triggered.connect(self.contextArrow)
2088
            menu.addAction(arrowAction)
2089

    
2090
            deleteAction = QAction('Delete(E)', None)
2091
            deleteAction.triggered.connect(self.contextDelete)
2092
            menu.addAction(deleteAction)
2093

    
2094
            menu.exec_(event.screenPos())
2095

    
2096
    def contextShow(self):
2097
        from EngineeringLineNoTextItem import QEngineeringLineNoTextItem
2098

    
2099
        if type(self.owner) is QEngineeringLineNoTextItem:
2100
            self.owner.contextHighlight(self.owner)
2101

    
2102
    def contextDelete(self):
2103
        event = QKeyEvent(QEvent.KeyPress, Qt.Key_Delete, Qt.NoModifier)
2104
        self.scene().keyPressEvent(event)
2105

    
2106
    def contextArrow(self):
2107
        event = QKeyEvent(QEvent.KeyPress, Qt.Key_A, Qt.NoModifier)
2108
        self.keyPressEvent(event)
2109

    
2110
    def contextReverse(self):
2111
        event = QKeyEvent(QEvent.KeyPress, Qt.Key_C, Qt.NoModifier)
2112
        self.keyPressEvent(event)
2113

    
2114
    def contextMerge(self):
2115
        event = QKeyEvent(QEvent.KeyPress, Qt.Key_M, Qt.NoModifier)
2116
        self.keyPressEvent(event)
2117

    
2118
'''
2119
    @brief      The class transfer pyqtSignal Event. Cause Subclass of QGraphicsRectItem can't use pyqtSignal
2120
    @author     Jeongwoo
2121
    @date       2018.06.18
2122
'''
2123

    
2124

    
2125
class Transfer(QObject):
2126
    onRemoved = pyqtSignal(QGraphicsItem)
2127

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