프로젝트

일반

사용자정보

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

hytos / DTI_PID / DTI_PID / Shapes / EngineeringLineItem.py @ 5a049576

이력 | 보기 | 이력해설 | 다운로드 (81.8 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
            angle = self.getAngle(rhs)
459
            if (angle == 0) or (angle == math.pi): return True
460
        except ZeroDivisionError:
461
            return True
462

    
463
        return False
464

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

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

    
477
        if type(item) is QEngineeringLineItem:
478
            startPt = item.start_point()
479
            endPt = item.end_point()
480

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

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

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

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

    
512
            return False
513

    
514
        return False
515

    
516
    '''
517
        @author     humkyung
518
        @date       2018.06.26
519
        @history    humkyung 2018.07.03 allow item to be line or symbol
520
    '''
521

    
522
    def is_connected(self, item, at=QEngineeringAbstractItem.CONNECTED_AT_PT):
523
        """ check if given item is connected to self """
524

    
525
        _connectors = [connector for connector in self.connectors if
526
                       (connector.connectedItem == item and connector._connected_at == at)]
527
        return len(_connectors) > 0
528

    
529
    '''
530
        @brief      join line to symbol
531
        @author     kyouho
532
        @date       2018.07.25
533
    '''
534

    
535
    def joinTo(self, item=None):
536
        import math
537
        from SymbolSvgItem import SymbolSvgItem
538

    
539
        # line의 Point 정의
540
        startPoint = self.start_point()
541
        endPoint = self.end_point()
542

    
543
        if item is not None and type(item) is QEngineeringLineItem:
544
            pts = [item.start_point(), item.end_point()]
545
            selected = startPoint if self._selectedIndex == 0 else endPoint if self._selectedIndex else []
546

    
547
            if selected:
548
                for i in range(len(pts)):
549
                    dx = pts[i][0] - selected[0]
550
                    dy = pts[i][1] - selected[1]
551
                    if math.sqrt(dx * dx + dy * dy) < 10:
552
                        line = QLineF(QPointF(pts[i][0], pts[i][1]),
553
                                      QPointF(endPoint[0], endPoint[1])) if self._selectedIndex == 0 else QLineF(
554
                            QPointF(startPoint[0], startPoint[1]), QPointF(pts[i][0], pts[i][1]))
555
                        self.setLine(line)
556
                        self.update()
557
                        break
558
        else:
559
            if len(item.connectors) == 2:
560
                connector1Point = item.connectors[0].center()
561
                connector2Point = item.connectors[1].center()
562

    
563
                # startPoint와 같은 connPts 찾음
564
                if startPoint[0] == connector1Point[0] and startPoint[1] == connector1Point[1]:
565
                    self.connectors[0].connectedItem = item
566
                elif startPoint[0] == connector2Point[0] and startPoint[1] == connector2Point[1]:
567
                    self.connectors[0].connectedItem = item
568

    
569
                # endPoint와 같은 connPts 찾음
570
                if endPoint[0] == connector1Point[0] and endPoint[1] == connector1Point[1]:
571
                    self.connectors[1].connectedItem = item
572
                elif endPoint[0] == connector2Point[0] and endPoint[1] == connector2Point[1]:
573
                    self.connectors[1].connectedItem = item
574

    
575
    '''
576
        @brief      arrange vertex order
577
        @author     humkyung
578
        @date       2018.07.04
579
    '''
580

    
581
    def arrangeVertexOrder(self, arranged):
582
        import math
583

    
584
        lhs = [arranged.start_point(), arranged.end_point()]
585
        rhs = [self.start_point(), self.end_point()]
586

    
587
        index = 0
588
        indexed = 0
589
        minDist = None
590
        for pt in lhs:
591
            for _pt in rhs:
592
                index += 1
593
                dx = _pt[0] - pt[0]
594
                dy = _pt[1] - pt[1]
595
                dist = math.sqrt(dx * dx + dy * dy)
596
                if minDist is None or dist < minDist:
597
                    minDist = dist
598
                    indexed = index
599

    
600
        if indexed == 1 or indexed == 4:
601
            self.reverse()
602
    
603
    def is_piping(self, strong=False):
604
        """ return true if piping line """
605
        if strong:
606
            return self._lineType == 'Primary' or self._lineType == 'Secondary'
607
        else:
608
            return self._lineType == 'Primary' or self._lineType == 'Secondary' or \
609
                   self._lineType == 'Connect To Process'
610

    
611
    def next_connected(self, lhs, rhs):
612
        """ check given two item's are next connected(ex: 0-1) """
613

    
614
        lhs_matches = [at for at in range(len(self.connectors)) if self.connectors[at].connectedItem == lhs]
615
        rhs_matches = [at for at in range(len(self.connectors)) if self.connectors[at].connectedItem == rhs]
616
        if lhs_matches and rhs_matches and lhs is not rhs:
617
            return (lhs_matches[0] in [0, 1] and rhs_matches[0] in [0, 1])
618

    
619
        return False
620

    
621
    @staticmethod
622
    def check_piping(lineType, strong=False):
623
        """ return true if piping line """
624
        if strong:
625
            return lineType == 'Primary' or lineType == 'Secondary'
626
        else:
627
            return lineType == 'Primary' or lineType == 'Secondary' or \
628
                   lineType == 'Connect To Process'
629

    
630

    
631
    '''
632
        @brief      check if two lines are extendable
633
        @author     humkyung
634
        @date       2018.06.25
635
        @history    humkyung 2018.06.27 check line type
636
    '''
637
    def isExtendable(self, line, toler=5):
638
        import math
639
        from SymbolSvgItem import SymbolSvgItem
640

    
641
        if self.lineType == line.lineType:
642
            if self.isHorizontal() and line.isHorizontal():
643
                flag = (line.connectors[0].connectedItem is not None and issubclass(
644
                    type(line.connectors[0].connectedItem), SymbolSvgItem)) or (
645
                                   line.connectors[1].connectedItem is not None and issubclass(
646
                               type(line.connectors[1].connectedItem), SymbolSvgItem))
647
                return flag and (math.fabs(self.start_point()[1] - line.start_point()[1]) < toler)
648
            elif self.isVertical() and line.isVertical():
649
                flag = (line.connectors[0].connectedItem is not None and issubclass(
650
                    type(line.connectors[0].connectedItem), SymbolSvgItem)) or (
651
                                   line.connectors[1].connectedItem is not None and issubclass(
652
                               type(line.connectors[1].connectedItem), SymbolSvgItem))
653
                return flag and (math.fabs(self.start_point()[0] - line.start_point()[0]) < toler)
654

    
655
        return False
656

    
657
    def is_external_point(self, pt):
658
        """ check given pt is located outside of line """
659
        import math
660

    
661
        try:
662
            dx = self.end_point()[0] - self.start_point()[0]
663
            dy = self.end_point()[1] - self.start_point()[1]
664
            lineLength = math.sqrt(dx * dx + dy * dy)
665

    
666
            dx = pt.x - self.start_point()[0]
667
            dy = pt.y - self.start_point()[1]
668
            length = math.sqrt(dx * dx + dy * dy)
669
            if length > lineLength:
670
                return True
671

    
672
            dx = pt.x - self.end_point()[0]
673
            dy = pt.y - self.end_point()[1]
674
            length = math.sqrt(dx * dx + dy * dy)
675
            if length > lineLength:
676
                return True
677

    
678
            return False
679
        except Exception as ex:
680
            from App import App
681
            from AppDocData import MessageType
682

    
683
            message = 'error occurred({}) in {}:{}'.format(ex, sys.exc_info()[-1].tb_frame.f_code.co_filename,
684
                                                           sys.exc_info()[-1].tb_lineno)
685
            App.mainWnd().addMessage.emit(MessageType.Error, message)
686

    
687
    '''
688
        @author     humkyung
689
        @date       2018.04.16
690
        @history    humkyung 2018.05.08 check if line is possible to be connected
691
                    Jeongwoo 2018.05.15 Split if-statement and Connect each symbol and line
692
    '''
693

    
694
    def connect_if_possible(self, obj, toler=20):
695
        """connect line or symbol is able to be connected and return symbol or line connected to connectors
696
        this method not update item's position' \
697
        """
698

    
699
        from shapely.geometry import Point
700
        from SymbolSvgItem import SymbolSvgItem
701
        from EngineeringConnectorItem import QEngineeringConnectorItem
702

    
703
        res = []
704

    
705
        start_pt = self.start_point()
706
        end_pt = self.end_point()
707

    
708
        try:
709
            if issubclass(type(obj), SymbolSvgItem):
710
                for i in range(len(obj.connectors)):
711
                    if not self.is_connectable(obj.connectors[i]):
712
                        continue
713

    
714
                    pt = obj.connectors[i].center()
715
                    """
716
                    dist = [(self.connectors[0], Point(start_pt[0], start_pt[1]).distance(Point(pt[0], pt[1])), start_pt),
717
                            (self.connectors[1], Point(end_pt[0], end_pt[1]).distance(Point(pt[0], pt[1])), end_pt)]
718

719
                    dist.sort(key=lambda x: x[1])
720

721
                    if dist[0][0].connectedItem is None and obj.connectors[i].connectedItem is None:
722
                        dist[0][0].connect(obj)
723
                        obj.connectors[i].connect(self)
724
                        # line, start, end
725
                        res.append(obj)
726
                        res.append(obj.connectors[i].center())
727
                        res.append(dist[0][2])
728
                    """
729
                    if Point(start_pt[0], start_pt[1]).distance(Point(pt[0], pt[1])) < toler:
730
                        if self.connectors[0].connectedItem is None and obj.connectors[i].connectedItem is None:
731
                            self.connectors[0].connect(obj)
732
                            obj.connectors[i].connect(self)
733
                            # line, start, end
734
                            res.append(obj)
735
                            res.append(obj.connectors[i].center())
736
                            res.append(end_pt)
737
                    elif Point(end_pt[0], end_pt[1]).distance(Point(pt[0], pt[1])) < toler:
738
                        if self.connectors[1].connectedItem is None and obj.connectors[i].connectedItem is None:
739
                            self.connectors[1].connect(obj)
740
                            obj.connectors[i].connect(self)
741
                            # line, start, end
742
                            res.append(obj)
743
                            res.append(start_pt)
744
                            res.append(obj.connectors[i].center())
745
            elif type(obj) is QEngineeringLineItem:
746
                _startPt = obj.start_point()
747
                _endPt = obj.end_point()
748

    
749
                # avoid zero length line, can be losted short line
750
                #if Point(_startPt[0], _startPt[1]).distance(Point(_endPt[0], _endPt[1])) < toler or \
751
                #        Point(start_pt[0], start_pt[1]).distance(Point(end_pt[0], end_pt[1])) < toler:
752
                #    return res
753

    
754
                if obj.connectors[0].connectedItem is None and self.distanceTo(_startPt) < toler:
755
                    if self.connectors[0].connectedItem is None and \
756
                            (Point(start_pt[0], start_pt[1]).distance(Point(_startPt[0], _startPt[1])) < toler):
757
                        self.connectors[0].connect(obj)
758
                        obj.connectors[0].connect(self)
759
                        res.append(obj)
760
                    elif self.connectors[1].connectedItem is None and \
761
                            (Point(end_pt[0], end_pt[1]).distance(Point(_startPt[0], _startPt[1])) < toler):
762
                        self.connectors[1].connect(obj)
763
                        obj.connectors[0].connect(self)
764
                        res.append(obj)
765
                    elif self.connectors[1].connectedItem is not obj and self.connectors[0].connectedItem is not obj and obj.connectors[1].connectedItem is not self:
766
                        obj.connectors[0].connect(self, at=QEngineeringAbstractItem.CONNECTED_AT_BODY)
767
                        res.append(obj)
768

    
769
                elif obj.connectors[1].connectedItem is None and self.distanceTo(_endPt) < toler:
770
                    if self.connectors[0].connectedItem is None and \
771
                            (Point(start_pt[0], start_pt[1]).distance(Point(_endPt[0], _endPt[1])) < toler):
772
                        self.connectors[0].connect(obj)
773
                        obj.connectors[1].connect(self)
774
                        res.append(obj)
775
                    elif self.connectors[1].connectedItem is None and \
776
                            (Point(end_pt[0], end_pt[1]).distance(Point(_endPt[0], _endPt[1])) < toler):
777
                        self.connectors[1].connect(obj)
778
                        obj.connectors[1].connect(self)
779
                        res.append(obj)
780
                    elif self.connectors[1].connectedItem is not obj and self.connectors[0].connectedItem is not obj and obj.connectors[0].connectedItem is not self:
781
                        obj.connectors[1].connect(self, at=QEngineeringAbstractItem.CONNECTED_AT_BODY)
782
                        res.append(obj)
783
        except Exception as ex:
784
            from App import App
785
            from AppDocData import MessageType
786

    
787
            message = 'error occurred({}) in {}:{}'.format(ex, sys.exc_info()[-1].tb_frame.f_code.co_filename,
788
                                                           sys.exc_info()[-1].tb_lineno)
789
            App.mainWnd().addMessage.emit(MessageType.Error, message)
790

    
791
        return res
792

    
793
    '''
794
        @brief      disconnect connector item
795
        @author     kyouho
796
        @date       2018.08.30
797
    '''
798

    
799
    def disconnectedItemAtConnector(self, connector):
800
        for conn in self.connectors:
801
            if conn.isOverlapConnector(connector):
802
                conn.connectedItem = None
803

    
804
    def arrange_flow_direction(self, _from, reverse=False):
805
        """ reverse if from is connected to second connector """
806
        if not _from:
807
            raise ValueError
808

    
809
        if not reverse and self.connectors[0].connectedItem != _from:
810
            self.reverse()
811
        elif reverse and self.connectors[1].connectedItem != _from:
812
            self.reverse()
813

    
814
    def find_connected_objects(self):
815
        """find all connected items except equipment and instrument"""
816
        from EngineeringLineItem import QEngineeringLineItem
817
        from EngineeringEquipmentItem import QEngineeringEquipmentItem
818
        from SymbolSvgItem import SymbolSvgItem
819

    
820
        left_visited, right_visited = [self], [self]
821

    
822
        try:
823
            left_pool, right_pool = [self.connectors[0].connectedItem] if self.connectors[0].connectedItem else [], \
824
                                    [self.connectors[1].connectedItem] if self.connectors[1].connectedItem else []
825

    
826
            while left_pool:
827
                obj = left_pool.pop()
828

    
829
                if issubclass(type(obj), QEngineeringEquipmentItem) or (len(obj.connectors) > 2) or \
830
                        not obj.is_connected(left_visited[-1]):
831
                    continue
832

    
833
                left_visited.append(obj)
834

    
835
                if (type(obj) is QEngineeringLineItem and self.is_piping(True)) or issubclass(type(obj), SymbolSvgItem):
836
                    founds = [conn.connectedItem for conn in obj.connectors if
837
                              conn.connectedItem and conn.connectedItem not in left_visited and
838
                              2 == len(conn.connectedItem.connectors)]
839
                    for found in founds:
840
                        matches = [conn.connectedItem for conn in found.connectors if conn.connectedItem is obj]
841
                        if matches:
842
                            left_pool.append(found)
843

    
844
            while right_pool:
845
                obj = right_pool.pop()
846

    
847
                if issubclass(type(obj), QEngineeringEquipmentItem) or (len(obj.connectors) > 2) or \
848
                        not obj.is_connected(right_visited[-1]):
849
                    continue
850

    
851
                right_visited.append(obj)
852

    
853
                if (type(obj) is QEngineeringLineItem and self.is_piping(True)) or issubclass(type(obj), SymbolSvgItem):
854
                    founds = [conn.connectedItem for conn in obj.connectors if
855
                              conn.connectedItem and conn.connectedItem not in right_visited and
856
                              2 == len(conn.connectedItem.connectors)]
857
                    for found in founds:
858
                        matches = [conn.connectedItem for conn in found.connectors if conn.connectedItem is obj]
859
                        if matches:
860
                            right_pool.append(found)
861
        except Exception as ex:
862
            from App import App
863
            from AppDocData import MessageType
864

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

    
869
        return left_visited, right_visited
870

    
871
    def reverse(self, auto=False):
872
        """revere the line"""
873
        from EngineeringLineItem import QEngineeringLineItem
874
        from SymbolSvgItem import SymbolSvgItem
875

    
876
        line = self.line()
877
        self.setLine(QLineF(line.p2(), line.p1()))
878
        self.connectors[0], self.connectors[1] = self.connectors[1], self.connectors[0]
879
        self.update_arrow()
880
        self.update()
881

    
882
        if auto:
883
            left, right = self.find_connected_objects()
884

    
885
            if left:
886
                connected_item = self
887
                for obj in left[1:]:
888
                    if type(obj) is QEngineeringLineItem:
889
                        obj.arrange_flow_direction(connected_item, True)
890

    
891
                    connected_item = obj
892

    
893
            if right:
894
                connected_item = self
895
                for obj in right[1:]:
896
                    if type(obj) is QEngineeringLineItem:
897
                        obj.arrange_flow_direction(connected_item)
898

    
899
                    connected_item = obj
900

    
901
    '''
902
        @brief      add flow arrow
903
        @author     humkyung
904
        @date       2018.05.08
905
        @history    2018.05.24  Jeongwoo    Modifying Draw Flow Arrow
906
    '''
907

    
908
    def addFlowArrow(self):
909
        import numpy as np
910
        import cv2
911
        import math
912
        import sys
913
        global src
914
        from shapely.geometry import Point
915
        from QEngineeringFlowArrowItem import QEngineeringFlowArrowItem
916
        from AppDocData import AppDocData
917

    
918
        try:
919
            docData = AppDocData.instance()
920
            area = docData.getArea('Drawing')
921

    
922
            startPt = self.start_point()
923
            endPt = self.end_point()
924
            length = self.length()
925
            direction = [(endPt[0] - startPt[0]) / length, (endPt[1] - startPt[1]) / length]
926

    
927
            left = min(startPt[0], endPt[0])
928
            top = min(startPt[1], endPt[1])
929
            right = max(startPt[0], endPt[0])
930
            bottom = max(startPt[1], endPt[1])
931

    
932
            rect = None
933
            if self.isVertical():
934
                rect = QRectF(left - 10, top, (right - left) + 20, (bottom - top))
935
            else:
936
                rect = QRectF(left, top - 10, (right - left), (bottom - top) + 20)
937

    
938
            docData = AppDocData.instance()
939
            area = docData.getArea('Drawing')
940
            img = np.array(AppDocData.instance().getCurrentPidSource().getPyImageOnRect(rect))
941

    
942
            imgLine = cv2.threshold(cv2.cvtColor(img, cv2.COLOR_BGR2GRAY), 127, 255, cv2.THRESH_BINARY)[1]
943
            # remove noise
944
            imgLine = cv2.bitwise_not(imgLine)
945
            imgLine = cv2.erode(imgLine, np.ones((10, 10), np.uint8))
946

    
947
            contours, hierarchy = cv2.findContours(imgLine, cv2.RETR_TREE, cv2.CHAIN_APPROX_SIMPLE)
948
            if contours:
949
                contours = sorted(contours, key=cv2.contourArea, reverse=True)
950
                [x, y, w, h] = cv2.boundingRect(contours[0])
951
                if w > 10 and w < 100 and h > 10 and h < 100:  # check arrow mark size
952
                    imgArrowMark = imgLine[y:y + h, x:x + w]
953

    
954
                    # DEBUG - display flow arrow area
955
                    '''
956
                    item = QGraphicsBoundingBoxItem(rect.left() + x - 10, rect.top() + y - 10, w + 20, h + 20)
957
                    item.isSymbol = True
958
                    item.angle = 0
959
                    item.setPen(QPen(Qt.red, 1, Qt.SolidLine))
960
                    item.setBrush(QBrush(QColor(255,255,0,100)))
961
                    self.scene().addItem(item)
962
                    '''
963
                    # up to here
964

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

    
968
                    ####### HoughLinesP
969
                    if lines is not None:
970
                        maxLength = None
971
                        selected = None
972
                        for line in lines:
973
                            for x1, y1, x2, y2 in line:
974
                                dx = x2 - x1
975
                                dy = y2 - y1
976
                                selected = line
977
                                length = math.sqrt(dx * dx + dy * dy)
978
                                if maxLength is None or length > maxLength:
979
                                    maxLength = length
980
                                    selected = line
981

    
982
                        for x1, y1, x2, y2 in selected:
983
                            dx = math.fabs(x2 - x1)
984
                            dy = math.fabs(y2 - y1)
985
                            length = math.sqrt(dx * dx + dy * dy)
986
                            dx /= length
987
                            dy /= length
988
                            if (self.isVertical() and (dx < 0.001 or math.fabs(dx - 1) < 0.001)) or (
989
                                    self.isHorizontal() and (dx < 0.001 or math.fabs(dx - 1) < 0.001)): continue
990
                            dist1 = self.distanceTo((rect.left() + x + x1, rect.top() + y + y1))
991
                            dist2 = self.distanceTo((rect.left() + x + x2, rect.top() + y + y2))
992
                            if dist1 > dist2:  # point which's distance is longer would be start point
993
                                _start = (rect.left() + x + x1, rect.top() + y + y1)
994
                                _end = (rect.left() + x + x2, rect.top() + y + y2)
995
                            else:
996
                                _start = (rect.left() + x + x2, rect.top() + y + y2)
997
                                _end = (rect.left() + x + x1, rect.top() + y + y1)
998

    
999
                            # DEBUG display detected line
1000
                            '''
1001
                            poly = QEngineeringPolylineItem()
1002
                            poly._pol.append(QPointF(_start[0], _start[1]))
1003
                            poly._pol.append(QPointF(_end[0], _end[1]))
1004
                            poly.setPen(QPen(Qt.red, 2, Qt.SolidLine))
1005
                            poly.buildItem()
1006
                            self.scene().addItem(poly)
1007
                            '''
1008
                            # up to here
1009

    
1010
                            dist1 = Point(startPt[0], startPt[1]).distance(Point(_start[0], _start[1]))
1011
                            dist2 = Point(startPt[0], startPt[1]).distance(Point(_end[0], _end[1]))
1012
                            if dist1 > dist2:
1013
                                startPt, endPt = endPt, startPt
1014
                                direction[0], direction[1] = -direction[0], -direction[1]
1015
                                self.reverse()
1016

    
1017
                        '''
1018
                        center = [(startPt[0]+endPt[0])*0.5, (startPt[1]+endPt[1])*0.5]
1019
                        arrow = QEngineeringFlowArrowItem(center, direction)
1020
                        arrow.buildItem()
1021
                        self.scene().addItem(arrow)
1022
                        '''
1023

    
1024
                        x = round(rect.left() + x - 5)
1025
                        y = round(rect.top() + y - 5)
1026
                        self.flowMark = ([x, y, w + 10, h + 10], None)
1027
                else:
1028
                    pass
1029
        except Exception as ex:
1030
            from App import App
1031
            from AppDocData import MessageType
1032

    
1033
            message = 'error occurred({}) in {}:{}'.format(ex, sys.exc_info()[-1].tb_frame.f_code.co_filename,
1034
                                                           sys.exc_info()[-1].tb_lineno)
1035
            App.mainWnd().addMessage.emit(MessageType.Error, message)
1036

    
1037
    '''
1038
        @breif  insert symbol
1039
        @author humkyung
1040
        @date   2018.04.22
1041
        @history    kyouho  2018.07.24  add symbol angle, transform rotateRadians(-angle -> angle)
1042
    '''
1043

    
1044
    def insertSymbol(self, symbol, pos):
1045
        import math
1046
        from shapely.geometry import Point
1047
        from shapely import affinity
1048

    
1049
        vec = self.perpendicular()
1050
        line = [(pos.x() - vec[0] * 20, pos.y() - vec[1] * 20), (pos.x() + vec[0] * 20, pos.y() + vec[1] * 20)]
1051
        origin = self.intersection(line)
1052
        transform = QTransform()
1053
        transform.translate(origin.x, origin.y)
1054
        angle = self.angle()
1055
        transform.rotateRadians(-angle)
1056
        transform.translate(-symbol.symbolOrigin[0], -symbol.symbolOrigin[1])
1057
        symbol.setTransform(transform)
1058
        # save angle
1059
        symbol.angle = round(angle, 2)
1060
        if 2 == len(symbol.connectors):  # 2 way component
1061
            for i in range(len(symbol.connectors)):
1062
                rotatedPt = affinity.rotate(Point(symbol.connectors[i].connectPoint[0] - symbol.symbolOrigin[0],
1063
                                                  symbol.connectors[i].connectPoint[1] - symbol.symbolOrigin[1]),
1064
                                            -angle, Point(0, 0), use_radians=True)
1065
                #symbol.connectors[i].sceneConnectPoint = (origin.x + rotatedPt.x, origin.y + rotatedPt.y)
1066

    
1067
            dx1 = symbol.connectors[0].center()[0] - self.start_point()[0]
1068
            dy1 = symbol.connectors[0].center()[1] - self.start_point()[1]
1069
            length1 = math.sqrt(dx1 * dx1 + dy1 * dy1)
1070
            dx2 = symbol.connectors[1].center()[0] - self.start_point()[0]
1071
            dy2 = symbol.connectors[1].center()[1] - self.start_point()[1]
1072
            length2 = math.sqrt(dx2 * dx2 + dy2 * dy2)
1073

    
1074
            if length1 < length2:
1075
                processLine = QEngineeringLineItem([symbol.connectors[1].center(), self.end_point()])
1076
                processLine.connectors[0].connectedItem = symbol
1077
                processLine.connectors[1].connectedItem = self.connectors[1].connectedItem
1078
                self.scene().addItem(processLine)
1079

    
1080
                line = QLineF(self.line().p1(), QPointF(symbol.connectors[0].center()[0],
1081
                                                        symbol.connectors[0].center()[1]))
1082
                self.setLine(line)
1083
                self.connectors[1].connectedItem = symbol
1084

    
1085
                symbol.connectors[0].connectedItem = self
1086
                symbol.connectors[1].connectedItem = processLine
1087
            else:
1088
                processLine = QEngineeringLineItem([symbol.connectors[0].center(), self.end_point()])
1089
                processLine.connectors[0].connectedItem = symbol
1090
                processLine.connectors[1].connectedItem = self.connectors[1].connectedItem
1091
                self.scene().addItem(processLine)
1092

    
1093
                line = QLineF(self.line().p1(), QPointF(symbol.connectors[1].center()[0],
1094
                                                        symbol.connectors[1].center()[1]))
1095
                self.setLine(line)
1096
                self.connectors[1].connectedItem = symbol
1097

    
1098
                symbol.connectors[0].connectedItem = processLine
1099
                symbol.connectors[1].connectedItem = self
1100

    
1101
            self.joinTo(symbol)
1102
            processLine.joinTo(symbol)
1103
            self.update()
1104

    
1105
        symbol.loc = [origin.x - symbol.symbolOrigin[0], origin.y - symbol.symbolOrigin[1]]
1106
        symbol.size = [symbol.boundingRect().width(), symbol.boundingRect().height()]
1107
        self.scene().addItem(symbol)
1108

    
1109
    def update_shape(self, symbol, point):
1110
        """update line shape"""
1111
        for index in range(len(self.connectors)):
1112
            if self.connectors[index].connectedItem == symbol:
1113
                # startPoint
1114
                if index == 0:
1115
                    line = QLineF(QPointF(point[0], point[1]), self.line().p2())
1116
                    self.setLine(line)
1117
                    self.connectors[0].setPos([point[0], point[1]])
1118
                # endpoint
1119
                else:
1120
                    line = QLineF(self.line().p1(), QPointF(point[0], point[1]))
1121
                    self.setLine(line)
1122
                    self.connectors[1].setPos([point[0], point[1]])
1123

    
1124
        ## startPoint에 symbol
1125
        # if self.connectors[0].connectedItem == symbol:
1126
        #    if self.startPoint()[0] == symbol.connectors[0].sceneConnectPoint[0] and self.startPoint()[1] == symbol.connectors[0].sceneConnectPoint[1]:
1127
        #        line = QLineF(QPointF(changedConnPoint1[0], changedConnPoint1[1]), self.line().p2())
1128
        #        self.setLine(line)
1129
        #    else:
1130
        #        line = QLineF(QPointF(changedConnPoint2[0], changedConnPoint2[1]), self.line().p2())
1131
        #        self.setLine(line)
1132
        ## endPoint에 symbol
1133
        # elif self.connectors[1].connectedItem == symbol:
1134
        #    if self.endPoint()[0] == symbol.connectors[0].sceneConnectPoint[0] and self.endPoint()[1] == symbol.connectors[0].sceneConnectPoint[1]:
1135
        #        line = QLineF(self.line().p1(), QPointF(changedConnPoint1[0], changedConnPoint1[1]))
1136
        #        self.setLine(line)
1137
        #    else:
1138
        #        line = QLineF(self.line().p1(), QPointF(changedConnPoint2[0], changedConnPoint2[1]))
1139
        #        self.setLine(line)
1140

    
1141
        self.update()
1142

    
1143
    '''
1144
        @brief  remove symbol
1145
        @author humkyung
1146
        @date   2018.04.23
1147
    '''
1148

    
1149
    def removeSymbol(self, symbol):
1150
        import math
1151

    
1152
        if 2 == len(symbol.connectors):  # 2-way component
1153
            connected = symbol.connectors[0].connectedItem if symbol.connectors[0].connectedItem is not self else \
1154
            symbol.connectors[1].connectedItem
1155

    
1156
            pts = []
1157
            pts.append(self.start_point())
1158
            pts.append(self.end_point())
1159
            pts.append(connected.start_point())
1160
            pts.append(connected.end_point())
1161

    
1162
            self.scene().removeItem(connected)
1163

    
1164
            start = None
1165
            end = None
1166
            maxDist = None
1167
            for i in range(len(pts)):
1168
                for j in range(i + 1, len(pts)):
1169
                    dx = pts[i][0] - pts[j][0]
1170
                    dy = pts[i][1] - pts[j][1]
1171
                    dist = math.sqrt(dx * dx + dy * dy)
1172
                    if maxDist is None:
1173
                        maxDist = dist
1174
                        start = pts[i]
1175
                        end = pts[j]
1176
                    elif dist > maxDist:
1177
                        maxDist = dist
1178
                        start = pts[i]
1179
                        end = pts[j]
1180

    
1181
            if (pts[0] == end) or (pts[1] == start): start, end = end, start
1182

    
1183
            line = QLineF(QPointF(start[0], start[1]), QPointF(end[0], end[1]))
1184
            self.setLine(line)
1185
            # self.buildItem()
1186
            self.update()
1187

    
1188
    def validate(self):
1189
        '''
1190
            @brief  validation check : connection
1191
            @author euisung
1192
            @date   2019.04.01
1193
        '''
1194
        from EngineeringAbstractItem import QEngineeringAbstractItem
1195
        from SymbolSvgItem import SymbolSvgItem
1196
        from EngineeringEquipmentItem import QEngineeringEquipmentItem
1197
        from AppDocData import AppDocData
1198
        errors = []
1199

    
1200
        try:
1201
            _translate = QCoreApplication.translate
1202

    
1203
            docdata = AppDocData.instance()
1204
            dataPath = docdata.getErrorItemSvgPath()
1205

    
1206
            connectedUid = []
1207

    
1208
            for connector in self.connectors:
1209
                # for duplicattion check
1210
                if connector.connectedItem and issubclass(type(connector.connectedItem), QEngineeringAbstractItem):
1211
                    connectedUid.append(str(connector.connectedItem.uid))
1212

    
1213
                # check if there is not connected connector
1214
                if connector.connectedItem is None:
1215
                    error = SymbolSvgItem.createItem('Error', None, dataPath)
1216
                    error.setPosition(list(connector.center()))
1217
                    error.parent = self
1218
                    error.msg = _translate('Disconnection error', 'Disconnection error')
1219
                    error.setToolTip(error.msg)
1220
                    error.area = 'Drawing'
1221
                    error.name = 'Error'
1222
                    errors.append(error)
1223

    
1224
                # check line to symbol
1225
                elif issubclass(type(connector.connectedItem), SymbolSvgItem) and type(
1226
                        connector.connectedItem) is not QEngineeringEquipmentItem:
1227
                    matches = [conn for conn in connector.connectedItem.connectors if conn.connectedItem is self]
1228
                    # check if two items are connected each other
1229
                    if not matches:
1230
                        error = SymbolSvgItem.createItem('Error', None, dataPath)
1231
                        error.setPosition(list(connector.center()))
1232
                        error.parent = self
1233
                        error.msg = _translate('Disconnected from opposite side', 'Disconnected from opposite side')
1234
                        error.setToolTip(error.msg)
1235
                        error.area = 'Drawing'
1236
                        error.name = 'Error'
1237
                        errors.append(error)
1238
                    # check connection position
1239
                    elif not self.isOverlap(connector.sceneBoundingRect(), matches[0].sceneBoundingRect()):
1240
                        error = SymbolSvgItem.createItem('Error', None, dataPath)
1241
                        error.setPosition(list(connector.center()))
1242
                        error.parent = self
1243
                        error.msg = _translate('Mismatched position', 'Mismatched position')
1244
                        error.setToolTip(error.msg)
1245
                        error.area = 'Drawing'
1246
                        error.name = 'Error'
1247
                        errors.append(error)
1248

    
1249
                elif issubclass(type(connector.connectedItem), QEngineeringLineItem):
1250
                    # check if connected two lines has same direction
1251
                    if connector._connected_at == QEngineeringAbstractItem.CONNECTED_AT_PT:
1252
                        center = connector.center()
1253

    
1254
                        indices = [0, 0]
1255
                        indices[0] = 1 if QPointF(center[0], center[1]) == self.line().p1() else 2
1256
                        matches = [conn for conn in connector.connectedItem.connectors if
1257
                                   conn.connectedItem == self and conn._connected_at == QEngineeringAbstractItem.CONNECTED_AT_PT]
1258
                        if matches:
1259
                            indices[1] = 1 if QPointF(matches[0].center()[0], matches[0].center()[
1260
                                1]) == connector.connectedItem.line().p1() else 2
1261
                        else:
1262
                            error = SymbolSvgItem.createItem('Error', None, dataPath)
1263
                            error.setPosition(list(connector.center()))
1264
                            error.parent = self
1265
                            error.msg = _translate('Disconnected from opposite side', 'Disconnected from opposite side')
1266
                            error.setToolTip(error.msg)
1267
                            error.area = 'Drawing'
1268
                            error.name = 'Error'
1269
                            errors.append(error)
1270

    
1271
                        if indices[0] == indices[1]:
1272
                            error = SymbolSvgItem.createItem('Error', None, dataPath)
1273
                            error.setPosition(list(connector.center()))
1274
                            error.parent = self
1275
                            error.msg = _translate('Flow direction error', 'Flow direction error')
1276
                            error.setToolTip(error.msg)
1277
                            error.area = 'Drawing'
1278
                            error.name = 'Error'
1279
                            errors.append(error)
1280

    
1281
                        if self.lineType != connector.connectedItem.lineType:
1282
                            error = SymbolSvgItem.createItem('Error', None, dataPath)
1283
                            error.setPosition(list(connector.center()))
1284
                            error.parent = self
1285
                            error.msg = _translate('Line type error', 'Line type error')
1286
                            error.setToolTip(error.msg)
1287
                            error.area = 'Drawing'
1288
                            error.name = 'Error'
1289
                            errors.append(error)
1290
                    
1291
                    else:
1292
                        center = connector.center()
1293
                        line = connector.connectedItem
1294

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

    
1298
                        if line.distanceTo(center) > toler * 2:
1299
                            error = SymbolSvgItem.createItem('Error', None, dataPath)
1300
                            error.setPosition(list(connector.center()))
1301
                            error.parent = self
1302
                            error.msg = _translate('Line position error', 'Line position error')
1303
                            error.setToolTip(error.msg)
1304
                            error.area = 'Drawing'
1305
                            error.name = 'Error'
1306
                            errors.append(error)
1307

    
1308
                errors.extend(connector.validate())
1309

    
1310
            # check duplicated connection
1311
            if len(connectedUid) is not len(set(connectedUid)):
1312
                error = SymbolSvgItem.createItem('Error', None, dataPath)
1313
                error.setPosition([self.sceneBoundingRect().center().x(), self.sceneBoundingRect().center().y()])
1314
                error.parent = self
1315
                error.msg = _translate('Duplicated connection error', 'Duplicated connection error')
1316
                error.setToolTip(error.msg)
1317
                error.area = 'Drawing'
1318
                error.name = 'Error'
1319
                errors.append(error)
1320

    
1321
        except Exception as ex:
1322
            from App import App
1323
            from AppDocData import MessageType
1324

    
1325
            message = 'error occurred({}) in {}:{}'.format(ex, sys.exc_info()[-1].tb_frame.f_code.co_filename,
1326
                                                           sys.exc_info()[-1].tb_lineno)
1327
            App.mainWnd().addMessage.emit(MessageType.Error, message)
1328

    
1329
        return errors
1330

    
1331
    '''
1332
        @brief  update line type
1333
        @author humkyung
1334
        @date   2018.07.05
1335
    '''
1336

    
1337
    def update_line_type(self):
1338
        import uuid
1339
        from LineTypeConditions import LineTypeConditions
1340

    
1341
        try:
1342
            pool, visited, items = [self], [], []
1343
            while pool:
1344
                obj = pool.pop()
1345
                visited.append(obj)
1346

    
1347
                """ connected items """
1348
                connected = [connector.connectedItem for connector in obj.connectors if
1349
                             connector.connectedItem and not type(connector.connectedItem) is uuid.UUID]
1350
                """ connected lines at point """
1351
                lines = [connector.connectedItem for connector in obj.connectors if connector.connectedItem and type(
1352
                    connector.connectedItem) is QEngineeringLineItem and connector._connected_at == QEngineeringAbstractItem.CONNECTED_AT_PT]
1353
                """ add items not in 'connected' to items list """
1354
                items.extend([item for item in connected if item not in lines])
1355
                """ add items not visited to pool """
1356
                pool.extend([item for item in lines if item not in visited])
1357

    
1358
            for condition in LineTypeConditions.items():
1359
                if condition.eval(items):
1360
                    self.lineType = condition.name
1361
                    break
1362
                if condition.eval(items, reverse=True):
1363
                    self.lineType = condition.name
1364
                    break
1365
        except Exception as ex:
1366
            from App import App
1367
            from AppDocData import MessageType
1368

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

    
1373
    def hoverEnterEvent(self, event):
1374
        """ hilight item and it's children """
1375
        self.highlight(True)
1376

    
1377
    def hoverLeaveEvent(self, event):
1378
        """ restore original color """
1379
        self.highlight(False)
1380

    
1381
    def highlight(self, flag):
1382
        self.hover = flag
1383
        self.setZValue(QEngineeringAbstractItem.HOVER_ZVALUE) if flag else self.setZValue(QEngineeringLineItem.ZVALUE)
1384
        self.update()
1385

    
1386
        for assoc in self.associations():
1387
            assoc.highlight(flag)
1388

    
1389
        for connector in self.connectors:
1390
            connector.highlight(flag)
1391

    
1392
    def hoverMoveEvent(self, event):
1393
        pass
1394

    
1395
    '''
1396
        @brief      remove item when user press delete key
1397
        @author     humkyung
1398
        @date       2018.04.23
1399
        @history    swap start, end point when user press 'c' key
1400
    '''
1401

    
1402
    def keyPressEvent(self, event):
1403
        from EngineeringLineNoTextItem import QEngineeringLineNoTextItem
1404

    
1405
        if self.isSelected() and event.key() == Qt.Key_Delete:
1406
            self.scene().removeItem(self)
1407
        elif event.key() == Qt.Key_C:# and self.is_piping(True):
1408
            if self.owner and issubclass(type(self.owner), QEngineeringLineNoTextItem):
1409
                index = 1
1410
                for run in self.owner.runs:
1411
                    if self in run.items:
1412
                        if index == 1:
1413
                            self.owner.reverse()
1414
                        else:
1415
                            run.reverse()
1416
                        break
1417
                    else:
1418
                        index = index + 1
1419
            else:
1420
                self.reverse(True)
1421
        elif event.key() == Qt.Key_A:
1422
            self.toggleFlowMark()
1423
        elif event.key() == Qt.Key_Up:  # translate up/down/left/right symbol
1424
            modifiers = QApplication.keyboardModifiers()
1425
            delta = 10 if modifiers == Qt.ControlModifier else 1
1426

    
1427
            dx, dy = 0*delta, -1*delta
1428
            self.set_line([[self.start_point()[0] + dx, self.start_point()[1] + dy],
1429
                           [self.end_point()[0] + dx, self.end_point()[1] + dy]])
1430
        elif event.key() == Qt.Key_Down:
1431
            modifiers = QApplication.keyboardModifiers()
1432
            delta = 10 if modifiers == Qt.ControlModifier else 1
1433

    
1434
            dx, dy = 0*delta, 1*delta
1435
            self.set_line([[self.start_point()[0] + dx, self.start_point()[1] + dy],
1436
                           [self.end_point()[0] + dx, self.end_point()[1] + dy]])
1437
        elif event.key() == Qt.Key_Left:
1438
            modifiers = QApplication.keyboardModifiers()
1439
            delta = 10 if modifiers == Qt.ControlModifier else 1
1440

    
1441
            dx, dy = -1*delta, 0*delta
1442
            self.set_line([[self.start_point()[0] + dx, self.start_point()[1] + dy],
1443
                           [self.end_point()[0] + dx, self.end_point()[1] + dy]])
1444
        elif event.key() == Qt.Key_Right:
1445
            modifiers = QApplication.keyboardModifiers()
1446
            delta = 10 if modifiers == Qt.ControlModifier else 1
1447

    
1448
            dx, dy = 1*delta, 0*delta
1449
            self.set_line([[self.start_point()[0] + dx, self.start_point()[1] + dy],
1450
                           [self.end_point()[0] + dx, self.end_point()[1] + dy]])
1451

    
1452
    def toggleFlowMark(self):
1453
        from AppDocData import AppDocData
1454

    
1455
        if self.flowMark:
1456
            self.flowMark = None
1457
        else:
1458
            configs = AppDocData.instance().getConfigs('Flow Mark', 'Position')
1459
            self.flowMark = int(configs[0].value) if 1 == len(configs) else 100
1460
        self.update_arrow()
1461

    
1462
    def shape(self):
1463
        """return shape for non-rectangle shape"""
1464
        x1, y1, x2, y2 = self.line().x1(), self.line().y1(), self.line().x2(), self.line().y2()
1465
        path = QPainterPath()
1466
        path.moveTo(x1, y1)
1467
        path.lineTo(x2, y2)
1468

    
1469
        stroke = QPainterPathStroker()
1470
        stroke.setWidth(15)
1471
        return stroke.createStroke(path)
1472

    
1473
    def paint(self, painter, option, widget):
1474
        # override paint method
1475
        color = self.getColor()
1476
        self.setColor(color)
1477

    
1478
        painter.setPen(self.pen())
1479
        painter.drawLine(self.line())
1480
        #QGraphicsLineItem.paint(self, painter, options, widget)
1481

    
1482
        if self.isSelected():
1483
            painter.setOpacity(0.7)
1484
            painter.setPen(QPen(Qt.black, 1.5, Qt.DashLine))
1485
            painter.setBrush(QEngineeringLineItem.HIGHLIGHT_BRUSH)
1486
            painter.drawPolygon(self.minimum_bounding_box)
1487

    
1488
    def drawToImage(self, img, color, thickness):
1489
        """write recognized lines to image"""
1490
        try:
1491
            ptStart = self.start_point()
1492
            ptEnd = self.end_point()
1493
            cv2.line(img, (round(ptStart[0]), round(ptStart[1])), (round(ptEnd[0]), round(ptEnd[1])), color, thickness)
1494
            # up to here
1495
        except Exception as ex:
1496
            message = f"error occurred({repr(ex)}) in {sys.exc_info()[-1].tb_frame.f_code.co_filename}:" \
1497
                      f"{sys.exc_info()[-1].tb_lineno}"
1498

    
1499
    @staticmethod
1500
    def from_database(component):
1501
        """ get line from database """
1502
        import uuid
1503
        from AppDocData import AppDocData
1504
        from SymbolAttr import SymbolAttr
1505

    
1506
        item = None
1507
        try:
1508
            uidNode = component['UID']
1509
            uid = uidNode if uidNode is not None else uuid.uuid4()  # generate UUID
1510
            owner = uuid.UUID(component['Owner']) if component['Owner'] and component['Owner'] != 'None' else None
1511

    
1512
            app_doc_data = AppDocData.instance()
1513
            connectors = app_doc_data.get_component_connectors(uid)
1514
            if 2 != len(connectors): return item
1515
            startPoint = [float(connectors[0]['X']), float(connectors[0]['Y'])]
1516
            endPoint = [float(connectors[1]['X']), float(connectors[1]['Y'])]
1517

    
1518
            item = QEngineeringLineItem(vertices=[startPoint, endPoint], uid=uid)
1519
            item.setVisible(False)
1520
            attrs = app_doc_data.get_component_attributes(uid)
1521
            matches = [attr for attr in attrs if attr['Attribute'] == 'LineType']
1522
            item.lineType = matches[0]['Value'] if matches else 'Secondary'
1523
            if matches:
1524
                attrs.remove(matches[0])
1525

    
1526
            for key in item._properties.keys():
1527
                for compo in component.keys():
1528
                    if key.Attribute == compo:
1529
                        item._properties[key] = key.parse_value(component[key.Attribute]) if component[
1530
                            key.Attribute] else ''
1531

    
1532
            ## assign area
1533
            if component['Area']:
1534
                for area in app_doc_data.getAreaList():
1535
                    if area.contains(startPoint) and area.contains(endPoint):
1536
                        item.area = area.name
1537
                        break
1538
            else:
1539
                item.area = component['Area']
1540
            ## up to here
1541

    
1542
            matches = [attr for attr in attrs if attr['Attribute'] == 'Thickness']
1543
            item.thickness = int(matches[0]['Value']) if matches and matches[0]['Value'] != 'None' else None
1544
            if matches:
1545
                attrs.remove(matches[0])
1546

    
1547
            matches = [attr for attr in attrs if attr['Attribute'] == 'FlowMark']
1548
            item.flowMark = int(matches[0]['Value']) if matches and matches[0]['Value'] != 'None' else None
1549
            if matches:
1550
                attrs.remove(matches[0])
1551

    
1552
            if connectors:
1553
                iterIndex = 0
1554
                for connector in connectors:
1555
                    item.connectors[iterIndex].parse_record(connector)
1556
                    iterIndex += 1
1557

    
1558
            # get associations
1559
            associations = app_doc_data.get_component_associations(uid)
1560
            if associations:
1561
                for assoc in associations:
1562
                    _type = assoc['Type']
1563
                    if not _type in item._associations:
1564
                        item._associations[_type] = []
1565
                    item._associations[_type].append(uuid.UUID(assoc['Association']))
1566
            # up to here
1567

    
1568
            if attrs:
1569
                for attr in attrs:
1570
                    _attr = SymbolAttr.from_record(attr)
1571
                    item.attrs[_attr] = attr['Value']
1572

    
1573
        except Exception as ex:
1574
            from App import App
1575
            from AppDocData import MessageType
1576

    
1577
            message = 'error occurred({}) in {}:{}'.format(ex, sys.exc_info()[-1].tb_frame.f_code.co_filename,
1578
                                                           sys.exc_info()[-1].tb_lineno)
1579
            App.mainWnd().addMessage.emit(MessageType.Error, message)
1580

    
1581
        return item if item.length() > 1 else None
1582

    
1583
    '''
1584
        @brief      parse xml code
1585
        @author     humkyung
1586
        @date       2018.06.27
1587
    '''
1588

    
1589
    @staticmethod
1590
    def fromXml(node):
1591
        import uuid
1592
        from AppDocData import AppDocData
1593
        from SymbolAttr import SymbolAttr
1594

    
1595
        item = None
1596
        try:
1597
            uidNode = node.find('UID')
1598
            uid = uidNode.text if uidNode is not None else uuid.uuid4()  # generate UUID
1599
            owner = uuid.UUID(node.attrib['OWNER']) if 'OWNER' in node.attrib and node.attrib[
1600
                'OWNER'] != 'None' else None
1601

    
1602
            startPoint = [float(x) for x in node.find('STARTPOINT').text.split(',')]
1603
            endPoint = [float(x) for x in node.find('ENDPOINT').text.split(',')]
1604

    
1605
            item = QEngineeringLineItem(vertices=[startPoint, endPoint], uid=uid)
1606
            if owner:
1607
                item._owner = owner
1608
            item.setVisible(False)
1609
            item.lineType = node.find('TYPE').text if node.find('TYPE') is not None else 'Secondary'
1610
            # assign area
1611
            if node.find('AREA') is None:
1612
                appDocData = AppDocData.instance()
1613
                for area in appDocData.getAreaList():
1614
                    if area.contains(startPoint) and area.contains(endPoint):
1615
                        item.area = area.name
1616
                        break
1617
            else:
1618
                item.area = node.find('AREA').text
1619
            # up to here
1620

    
1621
            thicknessNode = node.find('THICKNESS')
1622
            item.thickness = int(
1623
                thicknessNode.text) if thicknessNode is not None and thicknessNode.text != 'None' else None
1624

    
1625
            flowMarkNode = node.find('FLOWMARK')
1626
            item.flowMark = int(flowMarkNode.text) if flowMarkNode is not None and flowMarkNode.text != 'None' else None
1627

    
1628
            connectors = node.find('CONNECTORS')
1629
            if connectors is not None:
1630
                iterIndex = 0
1631
                for connector in connectors.iter('CONNECTOR'):
1632
                    item.connectors[iterIndex].parse_xml(connector)
1633
                    iterIndex += 1
1634

    
1635
            # get associations
1636
            attributeValue = node.find('ASSOCIATIONS')
1637
            if attributeValue is not None:
1638
                for assoc in attributeValue.iter('ASSOCIATION'):
1639
                    _type = assoc.attrib['TYPE']
1640
                    if not _type in item._associations:
1641
                        item._associations[_type] = []
1642
                    item._associations[_type].append(uuid.UUID(assoc.text))
1643
            # up to here
1644

    
1645
            attributes = node.find('SYMBOLATTRIBUTES')
1646
            if attributes is not None:
1647
                for attr in attributes.iter('ATTRIBUTE'):
1648
                    _attr = SymbolAttr.fromXml(attr)
1649
                    item.attrs[_attr] = attr.text
1650

    
1651
            for prop_node in node.iter('PROPERTY'):
1652
                matches = [prop for prop in item._properties.keys() if
1653
                            prop.Attribute == prop_node.attrib['Attribute']]
1654
                if matches:
1655
                    item._properties[matches[0]] = matches[0].parse_value(prop_node.text) if prop_node.text else ''
1656

    
1657
        except Exception as ex:
1658
            from App import App
1659
            from AppDocData import MessageType
1660

    
1661
            message = 'error occurred({}) in {}:{}'.format(ex, sys.exc_info()[-1].tb_frame.f_code.co_filename,
1662
                                                           sys.exc_info()[-1].tb_lineno)
1663
            App.mainWnd().addMessage.emit(MessageType.Error, message)
1664

    
1665
        return item if item.length() > 1 else None
1666

    
1667
    '''
1668
        @brief      generate xml code
1669
        @author     humkyung
1670
        @date       2018.04.23
1671
        @history    humkyung 2018.06.27 write line type to xml
1672
                    humkyung 2018.07.23 write connected item's uid to xml
1673
    '''
1674

    
1675
    def toXml(self):
1676
        from xml.etree.ElementTree import Element, SubElement, dump, ElementTree
1677
        from LineTypeConditions import LineTypeConditions
1678
        from SymbolAttr import SymbolAttr
1679

    
1680
        try:
1681
            node = Element('LINE')
1682
            node.attrib['OWNER'] = str(self._owner) if self._owner else 'None'
1683
            uidNode = Element('UID')
1684
            uidNode.text = str(self.uid)
1685
            node.append(uidNode)
1686

    
1687
            startPt = self.start_point()
1688
            endPt = self.end_point()
1689

    
1690
            startNode = Element('STARTPOINT')
1691
            startNode.text = '{},{}'.format(startPt[0], startPt[1])
1692
            node.append(startNode)
1693

    
1694
            endNode = Element('ENDPOINT')
1695
            endNode.text = '{},{}'.format(endPt[0], endPt[1])
1696
            node.append(endNode)
1697

    
1698
            typeNode = Element('TYPE')
1699
            typeNode.text = self.lineType
1700
            for lineType in LineTypeConditions.items():
1701
                if self.lineType == lineType.name:
1702
                    typeNode.attrib['TYPEUID'] = str(lineType)
1703
                    break
1704
            node.append(typeNode)
1705

    
1706
            areaNode = Element('AREA')
1707
            areaNode.text = self.area
1708
            node.append(areaNode)
1709

    
1710
            thicknessNode = Element('THICKNESS')
1711
            thicknessNode.text = str(self.thickness)
1712
            node.append(thicknessNode)
1713

    
1714
            flowMarkNode = Element('FLOWMARK')
1715
            flowMarkNode.text = str(self.flowMark)
1716
            node.append(flowMarkNode)
1717

    
1718
            connectorsNode = Element('CONNECTORS')
1719
            for connector in self.connectors:
1720
                connectorsNode.append(connector.toXml())
1721
            node.append(connectorsNode)
1722

    
1723
            attributeValueNode = Element('ASSOCIATIONS')
1724
            for assoc in self.associations():
1725
                assoc_node = Element('ASSOCIATION')
1726
                assoc_node.attrib['TYPE'] = QEngineeringAbstractItem.assoc_type(assoc)
1727
                assoc_node.text = str(assoc.uid)
1728
                attributeValueNode.append(assoc_node)
1729
            node.append(attributeValueNode)
1730

    
1731
            properties_node = Element('PROPERTIES')
1732
            for prop, value in self.properties.items():
1733
                prop_node = prop.toXml()
1734
                prop_node.text = str(value) if value else ''
1735
                properties_node.append(prop_node)
1736
            node.append(properties_node)
1737

    
1738
            attributesNode = Element('SYMBOLATTRIBUTES')
1739
            _attrs = self.getAttributes()
1740
            for attr in _attrs:
1741
                if type(attr) is SymbolAttr:
1742
                    _node = attr.toXml()
1743
                    _node.text = str(_attrs[attr])
1744
                    attributesNode.append(_node)
1745

    
1746
            node.append(attributesNode)
1747

    
1748
            # up to here
1749
        except Exception as ex:
1750
            from App import App
1751
            from AppDocData import MessageType
1752

    
1753
            message = 'error occurred({}) in {}:{}'.format(ex, sys.exc_info()[-1].tb_frame.f_code.co_filename,
1754
                                                           sys.exc_info()[-1].tb_lineno)
1755
            App.mainWnd().addMessage.emit(MessageType.Error, message)
1756
            return None
1757

    
1758
        return node
1759

    
1760
    def to_svg(self, parent) -> list:
1761
        """convert line item to svg"""
1762
        from xml.etree.ElementTree import Element, SubElement, dump, ElementTree
1763
        from App import App
1764
        from AppDocData import AppDocData
1765

    
1766
        res = []
1767
        try:
1768
            app_doc_data = AppDocData.instance()
1769
            prj = app_doc_data.getCurrentProject()
1770

    
1771
            node = Element('g')
1772
            node.attrib['id'] = str(self.uid)
1773
            node.attrib['stroke'] = self.pen().color().name()
1774
            node.attrib['stroke-width'] = str(int(self.pen().width()*0.5))
1775

    
1776
            line_style_map = {Qt.DotLine: '2', Qt.DashLine: '4 2',
1777
                              Qt.DashDotLine: '4 2 2', Qt.DashDotDotLine: '4 2 2 2 2 2'}
1778
            line_type_style = self.line_type_style
1779
            if line_type_style[3] in line_style_map:
1780
                node.attrib['stroke-dasharray'] = line_style_map[line_type_style[3]]
1781

    
1782
            if not parent:
1783
                trans = QTransform()
1784
            else:
1785
                trans, _ = parent.sceneTransform().inverted()
1786

    
1787
            node.attrib['transform'] = f"matrix(" \
1788
                                       f"{trans.m11()},{trans.m12()}," \
1789
                                       f"{trans.m21()},{trans.m22()}," \
1790
                                       f"{trans.m31()},{trans.m32()}" \
1791
                                       f")"
1792

    
1793
            polyline = Element('polyline')
1794
            polyline.attrib['fill'] = 'none'
1795
            start_pt = self.start_point()
1796
            end_pt = self.end_point()
1797
            polyline.attrib['points'] = f"{start_pt[0]},{start_pt[1]} " \
1798
                                        f"{end_pt[0]},{end_pt[1]}"
1799
            node.append(polyline)
1800

    
1801
            res.append(node)
1802
        except Exception as ex:
1803
            from App import App
1804
            from AppDocData import MessageType
1805
            message = 'error occurred({}) in {}:{}'.format(ex, sys.exc_info()[-1].tb_frame.f_code.co_filename,
1806
                                                          sys.exc_info()[-1].tb_lineno)
1807
            App.mainWnd().addMessage.emit(MessageType.Error, message)
1808

    
1809
        return res
1810

    
1811
    def toSql_return_separately(self):
1812
        """ generate sql phrase to save line to database """
1813
        import uuid
1814
        from AppDocData import AppDocData
1815

    
1816
        res = []
1817
        resLater = []
1818

    
1819
        app_doc_data = AppDocData.instance()
1820
        cols = ['UID', 'Drawings_UID', 'Symbol_UID', 'X', 'Y', 'Width', 'Height', 'Rotation', 'Area', 'Owner',
1821
                'SpecialItemTypes_UID', 'Freeze']
1822
        values = ['?', '?', "(select UID from Symbol where Name='Line' and SymbolType_UID=-1)", '?', '?', '?', '?', '?',
1823
                  '?', '?', '?', '?']
1824

    
1825
        rect = self.sceneBoundingRect()
1826
        param = [
1827
            (str(self.uid), str(app_doc_data.activeDrawing.UID), rect.x(), rect.y(), rect.width(), rect.height(), 0,
1828
             self.area, str(self.owner) if self.owner else None,
1829
             str(self.special_item_type) if self.special_item_type else None, self.prop('Freeze') if self.prop('Freeze') else 0)]
1830
        sql = 'insert into Components({}) values({})'.format(','.join(cols), ','.join(values))
1831
        res.append((sql, tuple(param)))
1832

    
1833
        _attrs = self.getAttributes()
1834
        if _attrs:
1835
            cols = ['UID', 'Components_UID', 'SymbolAttribute_UID', 'Value', 'Association_UID', 'Freeze']
1836
            values = ['?', '?', '?', '?', '?', '?']
1837
            params = []
1838
            for key in _attrs.keys():
1839
                if key.AttributeType != 'Spec':
1840
                    params.append((str(uuid.uuid4()), str(self.uid), str(key.UID), str(_attrs[key]), str(key.AssocItem),
1841
                                   str(key.Freeze)))
1842
                elif key.AttributeType == 'Spec':
1843
                    if type(_attrs[key]) is not list: continue
1844
                    params.append((str(uuid.uuid4()), str(self.uid), str(key.UID), (str(_attrs[key][0]) + ',' + str(_attrs[key][1])), str(key.AssocItem),
1845
                                   str(key.Freeze)))
1846
            sql = 'insert into Attributes({}) values({})'.format(','.join(cols), ','.join(values))
1847
            res.append((sql, tuple(params)))
1848

    
1849
        if self.associations():
1850
            cols = ['UID', '[Type]', 'Components_UID', 'Association']
1851
            values = ['?', '?', '?', '?']
1852
            params = []
1853
            for assoc in self.associations():
1854
                params.append(
1855
                    (str(uuid.uuid4()), QEngineeringAbstractItem.assoc_type(assoc), str(self.uid), str(assoc.uid)))
1856
            sql = 'insert into Associations({}) values({})'.format(','.join(cols), ','.join(values))
1857
            resLater.append((sql, tuple(params)))
1858

    
1859
        # save connectors to database
1860
        cols = ['Components_UID', '[Index]', 'X', 'Y', 'Connected', 'Connected_At']
1861
        values = ['?', '?', '?', '?', '?', '?']
1862
        params = []
1863
        index = 1
1864
        for connector in self.connectors:
1865
            params.append((  # str(connector.uid),
1866
                str(self.uid), index, connector.center()[0], connector.center()[1], \
1867
                str(connector.connectedItem.uid) if connector.connectedItem else None, \
1868
                str(connector._connected_at)))
1869
            index += 1
1870
        sql = 'insert into Points({}) values({})'.format(','.join(cols), ','.join(values))
1871
        resLater.append((sql, tuple(params)))
1872
        # up to here
1873

    
1874
        # save attributes
1875
        cols = ['UID', 'Components_UID', 'SymbolAttribute_UID', 'Value']
1876
        values = ['?', '?', "(select UID from SymbolAttribute where Attribute='LineType' and SymbolType_UID=-1)", '?']
1877
        param = [(str(uuid.uuid4()), str(self.uid), self.lineType)]
1878
        sql = 'insert into Attributes({}) values({})'.format(','.join(cols), ','.join(values))
1879
        res.append((sql, tuple(param)))
1880
        values = ['?', '?', "(select UID from SymbolAttribute where Attribute='Thickness' and SymbolType_UID=-1)", '?']
1881
        param = [(str(uuid.uuid4()), str(self.uid), str(self.thickness))]
1882
        sql = 'insert into Attributes({}) values({})'.format(','.join(cols), ','.join(values))
1883
        res.append((sql, tuple(param)))
1884
        values = ['?', '?', "(select UID from SymbolAttribute where Attribute='FlowMark' and SymbolType_UID=-1)", '?']
1885
        param = [(str(uuid.uuid4()), str(self.uid), str(self.flowMark))]
1886
        sql = 'insert into Attributes({}) values({})'.format(','.join(cols), ','.join(values))
1887
        res.append((sql, tuple(param)))
1888
        # up to here
1889

    
1890
        return res, resLater
1891

    
1892
    '''
1893
        @brief      Delete Line Item from scene
1894
        @author     Jeongwoo
1895
        @date       2018.05.29
1896
        @history    2018.05.29  Add parameter 'self' / Make comments emit()
1897
    '''
1898

    
1899
    def deleteLineItemFromScene(self):
1900
        self.scene().removeItem(self)
1901

    
1902
    def setColor(self, color):
1903
        """Set Color. Override QEngineeringAbstractItem's"""
1904
        if color.upper() != self.pen().color().name().upper():
1905
            c = QColor()
1906
            c.setNamedColor(color)
1907
            _pen = self.pen()
1908
            _pen.setColor(c)
1909
            self.setPen(_pen)
1910
            #self.update()
1911

    
1912
    def clear_labels(self):
1913
        """ clear spec labels """
1914
        attrs = self.getAttributes()
1915
        index = 0
1916
        for key in attrs.keys():
1917
            #if index >= 6:
1918
            #    break
1919
            if key.AssocItem and not key.Freeze:
1920
                key.AssocItem.owner = None
1921
                self.remove_assoc_item(key.AssocItem)
1922
                key.AssocItem = None
1923
            if not key.Freeze:
1924
                attrs[key] = ''
1925
            index += 1
1926
    
1927
    def update_flow_mark(self, position, length):
1928
        """ update flow mark for flow arrow """
1929
        import math
1930

    
1931
        to_item = self.connectors[1].connectedItem
1932
        if type(to_item) is QEngineeringLineItem and self.length() > length and not self.isParallel(to_item):
1933
            self.flowMark = position
1934

    
1935
    @staticmethod
1936
    def update_arrows(lines):
1937
        for line in lines:
1938
            line.update_arrow()
1939

    
1940
        return lines
1941

    
1942
    def update_arrow(self):
1943
        """ update flow arrow """
1944
        import math
1945
        from EngineeringArrowItem import QEngineeringArrowItem
1946

    
1947
        if self.length() < 0.01:
1948
            return
1949

    
1950
        start = self.line().p1()
1951
        end = self.line().p2()
1952

    
1953
        dx = end.x() - start.x()
1954
        dy = end.y() - start.y()
1955
        _dir = [dx / self.length(), dy / self.length()]
1956

    
1957
        arrow_size = QEngineeringLineItem.ARROW_SIZE * 0.25
1958

    
1959
        # allow flow mark at signal line
1960
        #if not self.is_piping():
1961
        #    self.flowMark = None
1962

    
1963
        if self.flowMark:
1964
            arrow_size *= 2
1965
            end = QPointF(start.x() + dx * self.flowMark / 100, start.y() + dy * self.flowMark / 100)
1966

    
1967
        perpendicular = (-_dir[1], _dir[0])
1968
        polygon = QPolygonF()
1969
        polygon.append(QPointF(end.x() - _dir[0] * QEngineeringLineItem.ARROW_SIZE + perpendicular[0] * arrow_size,
1970
                               end.y() - _dir[1] * QEngineeringLineItem.ARROW_SIZE + perpendicular[1] * arrow_size))
1971
        polygon.append(QPointF(end.x() - _dir[0] * QEngineeringLineItem.ARROW_SIZE - perpendicular[0] * arrow_size,
1972
                               end.y() - _dir[1] * QEngineeringLineItem.ARROW_SIZE - perpendicular[1] * arrow_size))
1973
        polygon.append(end)
1974
        polygon.append(polygon[0])  # close polygon
1975

    
1976
        if not hasattr(self, '_arrow'):
1977
            self._arrow = QEngineeringArrowItem(polygon, self)
1978
        else:
1979
            self._arrow.setPolygon(polygon)
1980

    
1981
        if self.flowMark:
1982
            self._arrow.setBrush(Qt.red)
1983
        else:
1984
            self._arrow.setBrush(Qt.blue)
1985
        self._arrow.update()
1986

    
1987
    def onConnectorPosChaned(self, connector):
1988
        """update line shape when connector is moved"""
1989

    
1990
        start = self.connectors[0].center()
1991
        end = self.connectors[1].center()
1992
        self.set_line([[start[0], start[1]], [end[0], end[1]]])
1993

    
1994
        if hasattr(self.scene(), 'contents_changed'):
1995
            self.scene().contents_changed.emit()
1996

    
1997
        # register resize command
1998
        pt_start = self.connectors[0].pressed_position if self.connectors[0].pressed_position else None
1999
        pt_end = self.connectors[-1].pressed_position if self.connectors[-1].pressed_position else None
2000
        if pt_start or pt_end:
2001
            from ResizeCommand import ResizeCommand
2002
            cmd = ResizeCommand(self.scene(), self,
2003
                                [pt_start if pt_start else
2004
                                 QPointF(self.connectors[0].center()[0], self.connectors[0].center()[1]),
2005
                                 pt_end if pt_end else
2006
                                 QPointF(self.connectors[-1].center()[0], self.connectors[-1].center()[-1])])
2007
            self.scene().undo_stack.push(cmd)
2008

    
2009
    '''
2010
        @brief      
2011
        @author     humkyung
2012
        @date       2018.07.24
2013
    '''
2014

    
2015
    def mousePressEvent(self, event):
2016
        import math
2017

    
2018
        if event.buttons() == Qt.LeftButton:
2019
            pos = event.scenePos()
2020
            ptStart = self.start_point()
2021
            dx = ptStart[0] - pos.x()
2022
            dy = ptStart[1] - pos.y()
2023
            if math.sqrt(dx * dx + dy * dy) < 10:
2024
                self._selectedIndex = 0
2025
                return
2026

    
2027
            ptEnd = self.end_point()
2028
            dx = ptEnd[0] - pos.x()
2029
            dy = ptEnd[1] - pos.y()
2030
            if math.sqrt(dx * dx + dy * dy) < 10:
2031
                self._selectedIndex = 1
2032
                return
2033

    
2034
            self._selectedIndex = -1
2035

    
2036
        QGraphicsLineItem.mousePressEvent(self, event)
2037

    
2038
    def mouseReleaseEvent(self, event):
2039
        self._selectedIndex = -1
2040

    
2041
        QGraphicsLineItem.mouseReleaseEvent(self, event)
2042

    
2043
    def contextMenuEvent(self, event):
2044
        menu = QMenu()
2045
        testAction = QAction('Test', None)
2046
        testAction.triggered.connect(self.print_out)
2047
        menu.addAction(testAction)
2048
        menu.exec_(event.screenPos())
2049

    
2050
    def print_out(self):
2051
        print('Triggered')
2052

    
2053
'''
2054
    @brief      The class transfer pyqtSignal Event. Cause Subclass of QGraphicsRectItem can't use pyqtSignal
2055
    @author     Jeongwoo
2056
    @date       2018.06.18
2057
'''
2058

    
2059

    
2060
class Transfer(QObject):
2061
    onRemoved = pyqtSignal(QGraphicsItem)
2062

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