프로젝트

일반

사용자정보

통계
| 개정판:

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

이력 | 보기 | 이력해설 | 다운로드 (64.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, QGraphicsLineItem
15
    except ImportError:
16
        raise ImportError("ImageViewerQt: Requires PyQt5 or PyQt4.")
17

    
18
from EngineeringAbstractItem import QEngineeringAbstractItem
19
import shapely
20
from LineTypeConditions import LineTypeConditions
21

    
22
class QEngineeringLineItem(QGraphicsLineItem, QEngineeringAbstractItem):
23
    """ This is engineering line item """
24

    
25
    ARROW_SIZE = 30
26
    ZVALUE = 100
27
    HIGHLIGHT = '#BC4438'
28
    LINE_TYPE_COLORS = {}
29

    
30
    '''
31
        @history    2018.05.11  Jeongwoo    Make Comments self.setPen()
32
                    2018.05.15  Jeongwoo    Change method to call parent's __init__
33
                    humkyung 2018.06.21 add vertices to parameter
34
    '''
35
    def __init__(self, vertices=[], thickness=None, parent=None, uid=None):
36
        import uuid
37
        from EngineeringConnectorItem import QEngineeringConnectorItem
38
        from SymbolAttr import SymbolProp
39
        from AppDocData import AppDocData
40

    
41
        try:
42
            QGraphicsLineItem.__init__(self, parent)
43
            QEngineeringAbstractItem.__init__(self)
44

    
45
            self.uid = uuid.uuid4() if uid is None else uuid.UUID(uid, version=4)
46
            self.thickness = thickness
47

    
48
            self.setPen(QPen(Qt.blue, 4, Qt.SolidLine)) # set default pen
49
            self.isCreated = True 
50

    
51
            self._owner = None
52
            self._flowMark = None
53
            configs = AppDocData.instance().getConfigs('Line', 'Default Type')
54
            self._lineType = None
55
            self.lineType = configs[0].value if 1 == len(configs) else 'Secondary' # defulat line type is 'Secondary'
56

    
57
            self._properties = \
58
                {\
59
                    SymbolProp(None, 'Size', 'Size Text Item', Expression='self.EvaluatedSize'):None,\
60
                    SymbolProp(None, 'Flow Mark', 'Size Text Item', Expression='self.EvaluatedSize'):None
61
                }
62

    
63
            self.setFlags(QGraphicsItem.ItemIsSelectable|QGraphicsItem.ItemIsFocusable)
64

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

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

    
71
            if vertices:
72
                self.setLine(vertices[0][0], vertices[0][1], vertices[1][0], vertices[1][1])
73
                
74
                index = 0
75
                for vertex in vertices:
76
                    connector = QEngineeringConnectorItem(parent=self, index=index+1)
77
                    connector.setPos(vertex)
78
                    connector.setParentItem(self)
79
                    # connector의 connectPoint, sceneConnectPoint를 vertex로 함 추후 좀 알아봐서 수정 필요
80
                    connector.connectPoint = vertex
81
                    connector.sceneConnectPoint = 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
                
94
                tooltip = '<b>{}</b><br>({},{})-({},{})'.format(str(self.uid), vertices[0][0], vertices[0][1], vertices[1][0], vertices[1][1])
95
                self.setToolTip(tooltip)
96
        except Exception as ex:
97
            from App import App
98
            from AppDocData import MessageType
99

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

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

    
107
    @property
108
    def properties(self):
109
        """ getter of flow mark """
110
        import uuid
111

    
112
        for prop,value in self._properties.items():
113
            if prop.is_selectable and type(value) is uuid.UUID and self.scene():
114
                matches = [x for x in self.scene().items() if hasattr(x, 'uid') and str(x.uid) == str(value)]
115
                if matches: self._properties[prop] = matches[0]
116
            
117
            if prop.Expression: self._properties[prop] = eval(prop.Expression)
118
            
119
        return self._properties
120

    
121
    def set_property(self, property, value):
122
        """ set property with given value """
123
        if issubclass(type(value), QEngineeringAbstractItem): self.add_assoc_item(value, 0)
124
        matches = [prop for prop,_ in self._properties.items() if prop.Attribute == property]
125
        if matches: self._properties[matches[0]] = value
126

    
127
    def prop(self, name):
128
        """ return the value of given property with name """
129
        matches = [(prop,value) for prop,value in self.properties.items() if prop.Attribute == name]
130
        if matches: return matches[0][1]
131

    
132
        return None
133

    
134
    @property
135
    def Size(self):
136
        """ always return None """
137
        return None
138
    
139
    @property
140
    def EvaluatedSize(self):
141
        from EngineeringReducerItem import QEngineeringReducerItem
142

    
143
        if self.Size: return self.Size
144
        if self.owner:
145
            matches = [run for run in self.owner.runs if self in run.items]
146
            if matches:
147
                at = matches[0].items.index(self)
148
                upstream = matches[0].items[:at]
149
                upstream.reverse()
150
                prev = self 
151
                for item in upstream:
152
                    if type(item) is QEngineeringReducerItem:
153
                        if item.connectors[0].connectedItem is prev:    ### Main Size
154
                            if item.MainSize: return item.MainSize
155
                        elif item.connectors[1].connectedItem is prev:  ### Sub Size
156
                            if item.SubSize: return item.SubSize
157
                    else:
158
                        if item.Size: return item.Size
159
                    prev = item
160

    
161
                downstream = matches[0].items[at:]
162
                prev = self
163
                for item in downstream:
164
                    if type(item) is QEngineeringReducerItem:
165
                        if item.connectors[0].connectedItem is prev:    ### Main Size
166
                            if item.MainSize: return item.MainSize
167
                        elif item.connectors[1].connectedItem is prev:  ### Sub Size
168
                            if item.SubSize: return item.SubSize
169
                    else:
170
                        if item.Size: return item.Size
171
                    prev = item
172

    
173
                if 'Drain' == matches[0].Type:
174
                    from AppDocData import AppDocData
175
                    return AppDocData.instance().drain_size
176
                    
177
            return self.owner.Size
178

    
179
        return None
180

    
181
    '''
182
        @breif  getter owner
183
        @author humkyung
184
        @date   2018.05.10
185
    '''
186
    @property
187
    def owner(self):
188
        import uuid
189

    
190
        if self._owner and type(self._owner) is uuid.UUID:
191
            matches = [x for x in self.scene().items() if hasattr(x, 'uid') and str(x.uid) == str(self._owner)]
192
            if matches: self._owner = matches[0]
193
        
194
        if type(self._owner) is not uuid.UUID and type(self._owner) is not str:
195
            return self._owner
196
        else:
197
            self._owner = None
198
            return None
199

    
200
    '''
201
        @brief  setter owner
202
        @author humkyung
203
        @date   2018.05.10
204
        @history    2018.05.17  Jeongwoo    Add Calling setColor
205
    '''
206
    @owner.setter
207
    def owner(self, value):
208
        self._owner = value
209

    
210
        if self._owner is None:
211
            self._color = self.DEFAULT_COLOR
212
        self.setColor(self._color)
213

    
214
    '''
215
        @brief  getter flow mark
216
        @author humkyung
217
        @date   2018.06.21
218
    '''
219
    @property
220
    def flowMark(self):
221
        return self._flowMark
222

    
223
    '''
224
        @brief  setter flow mark
225
        @author humkyung
226
        @date   2018.06.21
227
    '''
228
    @flowMark.setter
229
    def flowMark(self, value):
230
        self._flowMark = value
231

    
232
    '''
233
        @brief  getter of lineType
234
        @author humkyung
235
        @date   2018.06.27
236
    '''
237
    @property
238
    def lineType(self):
239
        return self._lineType
240
    
241
    '''
242
        @brief  setter of lineType
243
        @author humkyung
244
        @date   2018.06.27
245
    '''
246
    @lineType.setter
247
    def lineType(self, value):
248
        from AppDocData import AppDocData
249

    
250
        self._lineType = value
251

    
252
        docData = AppDocData.instance()
253
        configs = docData.getLineTypeConfig(self._lineType)
254
        if configs:
255
            _pen = self.pen() 
256
            _pen.setWidth(configs[2])
257
            _pen.setStyle(configs[3])
258
            self.setPen(_pen)
259
            self.setOpacity(float(configs[4]) / 100)
260
            self.update()
261

    
262
        if self.scene():
263
            self.update_arrow()
264

    
265
    '''
266
        @brief  clone an object
267
    '''
268
    def clone(self):
269
        clone = QEngineeringLineItem()
270
        #clone._vertices = copy.deepcopy(self._vertices)
271
        #for vertex in clone._vertices:
272
        #    clone._pol.append(QPointF(vertex[0], vertex[1]))
273
        clone.buildItem()
274
        clone.isCreated = self.isCreated
275

    
276
        return clone
277

    
278
    def set_line(self, line):
279
        """ set line """
280
        self.setLine(line[0][0], line[0][1], line[1][0], line[1][1])
281
        self.connectors[0].setPos(line[0])
282
        self.connectors[1].setPos(line[1])
283

    
284
    '''
285
        @brief  return start point
286
        @author humkyung
287
        @date   2018.04.16
288
    '''
289
    def startPoint(self):
290
        at = self.line().p1()
291
        return (at.x(), at.y())
292

    
293
    '''
294
        @brief  return last point
295
        @author humkyung
296
        @date   2018.04.16
297
    '''
298
    def endPoint(self):
299
        at = self.line().p2()
300
        return (at.x(), at.y())
301

    
302
    '''
303
        @brief  dot product of given two vectors
304
        @author humkyung
305
        @date   2018.04.14
306
    '''
307
    def dotProduct(self, lhs, rhs):
308
        return sum([lhs[i]*rhs[i] for i in range(len(lhs))])
309

    
310
    '''
311
        @brief  distance between line and point
312
        @author humkyung
313
        @date   2018.04.16
314
    '''
315
    def distanceTo(self, pt):
316
        from shapely.geometry import Point, LineString
317

    
318
        startPt = self.startPoint()
319
        endPt = self.endPoint()
320
        line = LineString([(startPt[0], startPt[1]), (endPt[0], endPt[1])])
321
        dist = line.distance(Point(pt[0], pt[1]))
322

    
323
        return dist
324

    
325
    '''
326
        @brief  return perpendicular vector
327
        @author humkyung
328
        @date   2018.04.21
329
    '''
330
    def perpendicular(self):
331
        import math
332

    
333
        dx = self.endPoint()[0] - self.startPoint()[0]
334
        dy = self.endPoint()[1] - self.startPoint()[1]
335
        dx,dy = -dy,dx
336
        length = math.sqrt(dx*dx + dy*dy)
337
        dx /= length
338
        dy /= length
339

    
340
        return (dx,dy)
341

    
342
    '''
343
        @brief  return angle of line in radian
344
        @author humkyung
345
        @date   2018.04.22
346
    '''
347
    def angle(self):
348
        import math
349

    
350
        startPt = self.startPoint()
351
        endPt = self.endPoint()
352
        dx = endPt[0] - startPt[0]
353
        dy = endPt[1] - startPt[1]
354
        dot = self.dotProduct((1,0), (dx, dy))
355
        length = math.sqrt(dx*dx + dy*dy)
356
        return math.acos(dot/length)
357

    
358
    '''
359
        @brief  return length of line
360
        @author humkyung
361
        @date   2018.05.08
362
    '''
363
    def length(self):
364
        import math
365

    
366
        startPt = self.startPoint()
367
        endPt = self.endPoint()
368
        dx = endPt[0] - startPt[0]
369
        dy = endPt[1] - startPt[1]
370
        return math.sqrt(dx*dx + dy*dy)
371

    
372
    '''
373
        @brief  check if line is horizontal
374
        @author humkyung
375
        @date   2018.04.27
376
    '''
377
    def isHorizontal(self):
378
        import math
379

    
380
        startPt = self.startPoint()
381
        endPt = self.endPoint()
382
        dx = endPt[0] - startPt[0]
383
        dy = endPt[1] - startPt[1]
384

    
385
        return math.fabs(dx) > math.fabs(dy)
386

    
387
    '''
388
        @brief  check if line is vertical 
389
        @author humkyung
390
        @date   2018.04.27
391
    '''
392
    def isVertical(self):
393
        import math
394

    
395
        startPt = self.startPoint()
396
        endPt = self.endPoint()
397
        dx = endPt[0] - startPt[0]
398
        dy = endPt[1] - startPt[1]
399

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

    
402
    '''
403
        @brief  get intersection point between this and given line
404
        @author humkyung
405
        @date   2018.04.21
406
        @history    Jeongwoo 2018.05.15 Add normalize
407
                    Jeongwoo 2018.05.16 Add length == 0 check
408
    '''
409
    def intersection(self, line):
410
        import math
411
        from shapely.geometry import Point, LineString
412

    
413
        startPt = self.startPoint()
414
        endPt = self.endPoint()
415
        dx = endPt[0] - startPt[0]
416
        dy = endPt[1] - startPt[1]
417
        length = math.sqrt(dx*dx + dy*dy)
418
        if length == 0:
419
            return None
420
        dx /= length
421
        dy /= length
422
        lhs = LineString([(startPt[0] - dx*20, startPt[1] - dy*20), (endPt[0] + dx*20, endPt[1] + dy*20)])
423
        rhs = LineString(line)
424
        return lhs.intersection(rhs)
425

    
426
    def getAngle(self, rhs):
427
        """ get angle between self and given line """
428
        import math
429

    
430
        try:
431
            return math.acos(self.dotProduct(self, rhs) / (self.length() * rhs.length()))
432
        except Exception as ex:
433
            return sys.float_info.max
434

    
435
    def isParallel(self, rhs):
436
        """ check if two lines are parallel """
437
        import math
438

    
439
        try:
440
            vectors = [(self.endPoint()[0]-self.startPoint()[0], self.endPoint()[1]-self.startPoint()[1]), (rhs.endPoint()[0]-rhs.startPoint()[0], rhs.endPoint()[1]-rhs.startPoint()[1])]
441
            angle = self.getAngle(rhs)
442
            if (angle == 0) or (angle == math.pi): return True
443
        except ZeroDivisionError:
444
            return True
445

    
446
        return False
447

    
448
    '''
449
        @brief      check if two lines are connectable
450
        @author     humkyung
451
        @date       2018.05.12
452
        @history    Jeongwoo 18.05.15 Add check pt's type
453
                    Jeongwoo 18.05.16 Add length == 0 check
454
    '''
455
    def is_connectable(self, line, toler=20):
456
        import math
457

    
458
        startPt = line.startPoint()
459
        endPt = line.endPoint()
460

    
461
        dx = endPt[0] - startPt[0]
462
        dy = endPt[1] - startPt[1]
463
        length = math.sqrt(dx*dx + dy*dy)
464
        if length == 0:
465
            return False
466
        dx /= length
467
        dy /= length
468
        extendedLine = [(startPt[0] - dx*toler, startPt[1] - dy*toler), (endPt[0] + dx*toler, endPt[1] + dy*toler)]
469
        pt = self.intersection(extendedLine)
470

    
471
        return (pt is not None) and (type(pt) == shapely.geometry.point.Point)
472

    
473
    '''
474
        @author     humkyung
475
        @date       2018.06.26
476
        @history    humkyung 2018.07.03 allow item to be line or symbol
477
    '''
478
    def is_connected(self, item, at=QEngineeringAbstractItem.CONNECTED_AT_PT):
479
        """ check if given item is connected to self """
480

    
481
        _connectors = [connector for connector in self.connectors if (connector.connectedItem == item and connector._connected_at == at)]
482
        return len(_connectors) > 0
483

    
484
    '''
485
        @brief      join line to symbol
486
        @author     kyouho
487
        @date       2018.07.25
488
    '''
489
    def joinTo(self, item = None):
490
        import math
491
        from SymbolSvgItem import SymbolSvgItem
492

    
493
        # line의 Point 정의
494
        startPoint = self.startPoint()
495
        endPoint = self.endPoint()
496

    
497
        if item is not None and type(item) is QEngineeringLineItem:
498
            pts = [item.startPoint(), item.endPoint()]
499
            selected = startPoint if self._selectedIndex == 0 else endPoint if self._selectedIndex else []
500

    
501
            if selected:
502
                for i in range(len(pts)):
503
                    dx = pts[i][0] - selected[0]
504
                    dy = pts[i][1] - selected[1]
505
                    if math.sqrt(dx*dx + dy*dy) < 10:
506
                        line = QLineF(QPointF(pts[i][0], pts[i][1]), QPointF(endPoint[0], endPoint[1])) if self._selectedIndex == 0 else QLineF(QPointF(startPoint[0], startPoint[1]), QPointF(pts[i][0], pts[i][1]))
507
                        self.setLine(line)
508
                        self.update()
509
                        break
510
        else:
511
            if len(item.connectors) == 2:
512
                connector1Point = item.connectors[0].sceneConnectPoint
513
                connector2Point = item.connectors[1].sceneConnectPoint
514

    
515
                # startPoint와 같은 connPts 찾음
516
                if startPoint[0] == connector1Point[0] and startPoint[1] == connector1Point[1]:
517
                    self.connectors[0].connectedItem = item
518
                elif startPoint[0] == connector2Point[0] and startPoint[1] == connector2Point[1]:
519
                    self.connectors[0].connectedItem = item
520

    
521
                # endPoint와 같은 connPts 찾음
522
                if endPoint[0] == connector1Point[0] and endPoint[1] == connector1Point[1]:
523
                    self.connectors[1].connectedItem = item
524
                elif endPoint[0] == connector2Point[0] and endPoint[1] == connector2Point[1]:
525
                    self.connectors[1].connectedItem = item
526

    
527

    
528
    '''
529
        @brief      arrange vertex order
530
        @author     humkyung
531
        @date       2018.07.04
532
    '''
533
    def arrangeVertexOrder(self, arranged):
534
        import math
535

    
536
        lhs = [arranged.startPoint(), arranged.endPoint()]
537
        rhs = [self.startPoint(), self.endPoint()]
538

    
539
        index = 0
540
        indexed = 0
541
        minDist = None
542
        for pt in lhs:
543
            for _pt in rhs:
544
                index += 1
545
                dx = _pt[0] - pt[0]
546
                dy = _pt[1] - pt[1]
547
                dist = math.sqrt(dx*dx + dy*dy)
548
                if minDist is None or dist < minDist:
549
                    minDist = dist
550
                    indexed = index
551
                    
552
        if indexed == 1 or indexed == 4:
553
            self.reverse()
554

    
555
    '''
556
        @brief      check if two lines are extendable
557
        @author     humkyung
558
        @date       2018.06.25
559
        @history    humkyung 2018.06.27 check line type
560
    '''
561
    def isExtendable(self, line, toler=5):
562
        import math
563
        from SymbolSvgItem import SymbolSvgItem
564

    
565
        if self.lineType == line.lineType:
566
            if self.isHorizontal() and line.isHorizontal():
567
                flag = (line.connectors[0].connectedItem is not None and issubclass(type(line.connectors[0].connectedItem), SymbolSvgItem)) or (line.connectors[1].connectedItem is not None and issubclass(type(line.connectors[1].connectedItem), SymbolSvgItem))
568
                return (flag and (math.fabs(self.startPoint()[1] - line.startPoint()[1]) < toler))
569
            elif self.isVertical() and line.isVertical():
570
                flag = (line.connectors[0].connectedItem is not None and issubclass(type(line.connectors[0].connectedItem), SymbolSvgItem)) or (line.connectors[1].connectedItem is not None and issubclass(type(line.connectors[1].connectedItem), SymbolSvgItem))
571
                return (flag and (math.fabs(self.startPoint()[0] - line.startPoint()[0]) < toler))
572
            
573
        return False
574
        
575
    def is_external_point(self, pt):
576
        """ check given pt is located outside of line """
577
        import math
578

    
579
        try:
580
            dx = self.endPoint()[0] - self.startPoint()[0]
581
            dy = self.endPoint()[1] - self.startPoint()[1]
582
            lineLength = math.sqrt(dx * dx + dy * dy)
583
            
584
            dx = pt.x - self.startPoint()[0]
585
            dy = pt.y - self.startPoint()[1]
586
            length = math.sqrt(dx * dx + dy * dy)
587
            if length > lineLength:
588
                return True
589

    
590
            dx = pt.x - self.endPoint()[0]
591
            dy = pt.y - self.endPoint()[1]
592
            length = math.sqrt(dx * dx + dy * dy)
593
            if length > lineLength:
594
                return True
595

    
596
            return False
597
        except Exception as ex:
598
            from App import App 
599
            from AppDocData import MessageType
600

    
601
            message = 'error occured({}) in {}:{}'.format(ex, sys.exc_info()[-1].tb_frame.f_code.co_filename, sys.exc_info()[-1].tb_lineno)
602
            App.mainWnd().addMessage.emit(MessageType.Error, message)
603

    
604
    '''
605
        @author     humkyung
606
        @date       2018.04.16
607
        @history    humkyung 2018.05.08 check if line is possible to be connected
608
                    Jeongwoo 2018.05.15 Split if-statement and Connect each symbol and line
609
    '''
610
    def connect_if_possible(self, obj, toler):
611
        """ connect line or symbol is able to be connected and return symbol or line connected to connectors """
612

    
613
        from shapely.geometry import Point
614
        from SymbolSvgItem import SymbolSvgItem
615
        from EngineeringConnectorItem import QEngineeringConnectorItem
616

    
617
        res = []
618

    
619
        startPt = self.startPoint() 
620
        endPt = self.endPoint()
621

    
622
        try:
623
            if issubclass(type(obj), SymbolSvgItem):
624
                for i in range(len(obj.connectors)):
625
                    pt = obj.connectors[i].sceneConnectPoint
626

    
627
                    if (Point(startPt[0], startPt[1]).distance(Point(pt[0], pt[1])) < toler):
628
                        if self.connectors[0].connectedItem is None:
629
                            self.connectors[0].connect(obj) 
630
                        if obj.connectors[i].connectedItem is None:
631
                            obj.connectors[i].connect(self)
632

    
633
                        res.append(obj)
634
                    if (Point(endPt[0], endPt[1]).distance(Point(pt[0], pt[1])) < toler):
635
                        if self.connectors[1].connectedItem is None:
636
                            self.connectors[1].connect(obj)
637
                        if obj.connectors[i].connectedItem is None:
638
                            obj.connectors[i].connect(self)
639

    
640
                        res.append(obj)
641
            elif type(obj) is QEngineeringLineItem:
642
                _startPt = obj.startPoint()
643
                _endPt = obj.endPoint()
644
                if self.distanceTo(_startPt) < toler:
645
                    if((Point(startPt[0], startPt[1]).distance(Point(_startPt[0], _startPt[1])) < toler)):
646
                        self.connectors[0].connect(obj)
647
                        obj.connectors[0].connect(self)
648
                        res.append(obj)
649
                    elif((Point(endPt[0], endPt[1]).distance(Point(_startPt[0], _startPt[1])) < toler)):
650
                        self.connectors[1].connect(obj)
651
                        obj.connectors[0].connect(self)
652
                        res.append(obj)
653
                    else:
654
                        obj.connectors[0].connect(self, at=QEngineeringAbstractItem.CONNECTED_AT_BODY)
655

    
656
                if self.distanceTo(_endPt) < toler:
657
                    if ((Point(startPt[0], startPt[1]).distance(Point(_endPt[0], _endPt[1])) < toler)):
658
                        self.connectors[0].connect(obj)
659
                        obj.connectors[1].connect(self)
660
                        res.append(obj)
661
                    elif ((Point(endPt[0], endPt[1]).distance(Point(_endPt[0], _endPt[1])) < toler)):
662
                        self.connectors[1].connect(obj)
663
                        obj.connectors[1].connect(self)
664
                        res.append(obj)
665
                    else:
666
                        obj.connectors[1].connect(self, at=QEngineeringAbstractItem.CONNECTED_AT_BODY)
667
        except Exception as ex:
668
            from App import App 
669
            from AppDocData import MessageType
670

    
671
            message = 'error occured({}) in {}:{}'.format(ex, sys.exc_info()[-1].tb_frame.f_code.co_filename, sys.exc_info()[-1].tb_lineno)
672
            App.mainWnd().addMessage.emit(MessageType.Error, message)
673

    
674
        return res
675

    
676
    '''
677
        @brief      disconnect connector item
678
        @author     kyouho
679
        @date       2018.08.30
680
    '''
681
    def disconnectedItemAtConnector(self, connector):
682
        for conn in self.connectors:
683
            if conn.isOverlapConnector(connector):
684
                conn.connectedItem = None
685

    
686
    def arrange_flow_direction(self, _from):
687
        """ reverse if from is connected to second connector """
688
        if not _from: raise ValueError
689

    
690
        if self.connectors[1].connectedItem == _from: self.reverse() 
691

    
692
    '''
693
        @brief      reverse line
694
        @author     humkyung
695
        @date       2018.07.03
696
    '''
697
    def reverse(self):
698
        line = self.line()
699
        self.setLine(QLineF(line.p2(), line.p1()))
700
        self.connectors[0], self.connectors[1] = self.connectors[1], self.connectors[0]
701
        self.update_arrow()
702
        self.update()
703

    
704
    '''
705
        @brief      add flow arrow
706
        @author     humkyung
707
        @date       2018.05.08
708
        @history    2018.05.24  Jeongwoo    Modifying Draw Flow Arrow
709
    '''
710
    def addFlowArrow(self):
711
        import numpy as np
712
        import cv2
713
        import math
714
        import sys
715
        global src
716
        from shapely.geometry import Point
717
        from QEngineeringFlowArrowItem import QEngineeringFlowArrowItem
718
        from AppDocData import AppDocData
719
        
720
        try:
721
            docData = AppDocData.instance()
722
            area = docData.getArea('Drawing')
723

    
724
            startPt = self.startPoint()
725
            endPt = self.endPoint()
726
            length = self.length()
727
            direction = [(endPt[0] - startPt[0]) / length, (endPt[1] - startPt[1]) / length]
728

    
729
            left = min(startPt[0], endPt[0])
730
            top = min(startPt[1], endPt[1])
731
            right = max(startPt[0], endPt[0])
732
            bottom = max(startPt[1], endPt[1])
733

    
734
            rect = None
735
            if self.isVertical():
736
                rect = QRectF(left - 10, top, (right - left) + 20, (bottom - top))
737
            else:
738
                rect = QRectF(left, top - 10, (right - left), (bottom - top) + 20)
739

    
740
            docData = AppDocData.instance()
741
            area = docData.getArea('Drawing')
742
            img = np.array(AppDocData.instance().getCurrentPidSource().getPyImageOnRect(rect))
743
            
744
            imgLine = cv2.threshold(cv2.cvtColor(img, cv2.COLOR_BGR2GRAY), 127, 255, cv2.THRESH_BINARY)[1]
745
            # remove noise
746
            imgLine = cv2.bitwise_not(imgLine)
747
            imgLine = cv2.erode(imgLine, np.ones((10, 10), np.uint8))
748

    
749
            contours, hierarchy = cv2.findContours(imgLine, cv2.RETR_TREE, cv2.CHAIN_APPROX_SIMPLE)
750
            if contours:
751
                contours = sorted(contours, key=cv2.contourArea, reverse=True)
752
                [x, y, w, h] = cv2.boundingRect(contours[0])
753
                if w > 10 and w < 100 and h > 10 and h < 100:   # check arrow mark size
754
                    imgArrowMark = imgLine[y:y+h, x:x+w]
755

    
756
                    # DEBUG - display flow arrow area
757
                    '''
758
                    item = QGraphicsBoundingBoxItem(rect.left() + x - 10, rect.top() + y - 10, w + 20, h + 20)
759
                    item.isSymbol = True
760
                    item.angle = 0
761
                    item.setPen(QPen(Qt.red, 1, Qt.SolidLine))
762
                    item.setBrush(QBrush(QColor(255,255,0,100)))
763
                    self.scene().addItem(item)
764
                    '''
765
                    # up to here
766

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

    
770
                    ####### HoughLinesP
771
                    if lines is not None:
772
                        maxLength = None
773
                        selected = None
774
                        for line in lines:
775
                            for x1, y1, x2, y2 in line:
776
                                dx = x2 - x1
777
                                dy = y2 - y1
778
                                selected = line
779
                                length = math.sqrt(dx*dx + dy*dy)
780
                                if maxLength is None or length > maxLength:
781
                                    maxLength = length
782
                                    selected = line
783

    
784
                        for x1, y1, x2, y2 in selected:
785
                            dx = math.fabs(x2 - x1)
786
                            dy = math.fabs(y2 - y1)
787
                            length = math.sqrt(dx*dx + dy*dy)
788
                            dx /= length
789
                            dy /= length
790
                            if (self.isVertical() and (dx < 0.001 or math.fabs(dx - 1) < 0.001)) or (self.isHorizontal() and (dx < 0.001 or math.fabs(dx - 1) < 0.001)): continue 
791
                            dist1 = self.distanceTo((rect.left() + x + x1, rect.top() + y + y1))
792
                            dist2 = self.distanceTo((rect.left() + x + x2, rect.top() + y + y2))
793
                            if dist1 > dist2:   # point which's distance is longer would be start point
794
                                _start = (rect.left() + x + x1, rect.top() + y + y1)
795
                                _end = (rect.left() + x + x2, rect.top() + y + y2)
796
                            else:
797
                                _start = (rect.left() + x + x2, rect.top() + y + y2)
798
                                _end = (rect.left() + x + x1, rect.top() + y + y1)
799
                            
800
                            # DEBUG display detected line
801
                            '''
802
                            poly = QEngineeringPolylineItem()
803
                            poly._pol.append(QPointF(_start[0], _start[1]))
804
                            poly._pol.append(QPointF(_end[0], _end[1]))
805
                            poly.setPen(QPen(Qt.red, 2, Qt.SolidLine))
806
                            poly.buildItem()
807
                            self.scene().addItem(poly)
808
                            '''
809
                            # up to here
810

    
811
                            dist1 = Point(startPt[0], startPt[1]).distance(Point(_start[0], _start[1]))
812
                            dist2 = Point(startPt[0], startPt[1]).distance(Point(_end[0], _end[1]))
813
                            if dist1 > dist2:
814
                                startPt,endPt = endPt,startPt
815
                                direction[0],direction[1] = -direction[0],-direction[1]
816
                                self.reverse()
817
                                    
818
                        '''
819
                        center = [(startPt[0]+endPt[0])*0.5, (startPt[1]+endPt[1])*0.5]
820
                        arrow = QEngineeringFlowArrowItem(center, direction)
821
                        arrow.buildItem()
822
                        self.scene().addItem(arrow)
823
                        '''
824

    
825
                        x = round(rect.left() + x - 5)
826
                        y = round(rect.top() + y - 5)
827
                        self.flowMark = ([x, y, w + 10, h + 10], None)
828
                else:
829
                    pass
830
        except Exception as ex:
831
            from App import App 
832
            from AppDocData import MessageType
833

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

    
837
    '''
838
        @breif  insert symbol
839
        @author humkyung
840
        @date   2018.04.22
841
        @history    kyouho  2018.07.24  add symbol angle, transform rotateRadians(-angle -> angle)
842
    '''
843
    def insertSymbol(self, symbol, pos):
844
        import math
845
        from shapely.geometry import Point
846
        from shapely import affinity
847

    
848
        vec = self.perpendicular()
849
        line = [(pos.x() - vec[0]*20, pos.y() - vec[1]*20),(pos.x() + vec[0]*20, pos.y() + vec[1]*20)]
850
        origin = self.intersection(line)
851
        transform = QTransform()
852
        transform.translate(origin.x, origin.y)
853
        angle = self.angle()
854
        transform.rotateRadians(-angle)
855
        transform.translate(-symbol.symbolOrigin[0], -symbol.symbolOrigin[1])
856
        symbol.setTransform(transform)
857
        #save angle
858
        symbol.angle = round(angle, 2)
859
        if 2 == len(symbol.connectors):    # 2 way component
860
            for i in range(len(symbol.connectors)):
861
                rotatedPt = affinity.rotate(Point(symbol.connectors[i].connectPoint[0] - symbol.symbolOrigin[0], symbol.connectors[i].connectPoint[1] - symbol.symbolOrigin[1]), -angle, Point(0, 0), use_radians=True)
862
                symbol.connectors[i].sceneConnectPoint = (origin.x+rotatedPt.x, origin.y+rotatedPt.y)
863

    
864
            dx1 = symbol.connectors[0].sceneConnectPoint[0] - self.startPoint()[0]
865
            dy1 = symbol.connectors[0].sceneConnectPoint[1] - self.startPoint()[1]
866
            length1 = math.sqrt(dx1*dx1 + dy1*dy1)
867
            dx2 = symbol.connectors[1].sceneConnectPoint[0] - self.startPoint()[0]
868
            dy2 = symbol.connectors[1].sceneConnectPoint[1] - self.startPoint()[1]
869
            length2 = math.sqrt(dx2*dx2 + dy2*dy2)
870

    
871
            if length1 < length2:
872
                processLine = QEngineeringLineItem([symbol.connectors[1].sceneConnectPoint, self.endPoint()])
873
                processLine.connectors[0].connectedItem = symbol
874
                processLine.connectors[1].connectedItem = self.connectors[1].connectedItem
875
                self.scene().addItem(processLine)
876

    
877
                line = QLineF(self.line().p1(), QPointF(symbol.connectors[0].sceneConnectPoint[0], symbol.connectors[0].sceneConnectPoint[1]))
878
                self.setLine(line)
879
                self.connectors[1].connectedItem = symbol
880

    
881
                symbol.connectors[0].connectedItem = self
882
                symbol.connectors[1].connectedItem = processLine
883
            else:
884
                processLine = QEngineeringLineItem([symbol.connectors[0].sceneConnectPoint, self.endPoint()])
885
                processLine.connectors[0].connectedItem = symbol
886
                processLine.connectors[1].connectedItem = self.connectors[1].connectedItem
887
                self.scene().addItem(processLine)
888

    
889
                line = QLineF(self.line().p1(), QPointF(symbol.connectors[1].sceneConnectPoint[0], symbol.connectors[1].sceneConnectPoint[1]))
890
                self.setLine(line)
891
                self.connectors[1].connectedItem = symbol
892

    
893
                symbol.connectors[0].connectedItem = processLine
894
                symbol.connectors[1].connectedItem = self
895
                
896

    
897
            self.joinTo(symbol)
898
            processLine.joinTo(symbol)
899
            self.update()
900
            
901
        symbol.loc = [origin.x - symbol.symbolOrigin[0], origin.y - symbol.symbolOrigin[1]]
902
        symbol.size = [symbol.boundingRect().width(), symbol.boundingRect().height()]
903
        self.scene().addItem(symbol)
904
    
905
    '''
906
        @brief  redraw symbol
907
        @author kyouho
908
        @date   2018.07.25
909
    '''
910
    def reDrawLine(self, symbol, point):
911
        for index in range(len(self.connectors)):
912
            if self.connectors[index].connectedItem == symbol:
913
                #startPoint
914
                if index == 0:
915
                    line = QLineF(QPointF(point[0], point[1]), self.line().p2())
916
                    self.setLine(line)
917
                    self.connectors[0].setPos([point[0], point[1]])
918
                #endpoint
919
                else:
920
                    line = QLineF(self.line().p1(), QPointF(point[0], point[1]))
921
                    self.setLine(line)
922
                    self.connectors[1].setPos([point[0], point[1]])
923
        
924

    
925
        
926
        ## startPoint에 symbol
927
        #if self.connectors[0].connectedItem == symbol:
928
        #    if self.startPoint()[0] == symbol.connectors[0].sceneConnectPoint[0] and self.startPoint()[1] == symbol.connectors[0].sceneConnectPoint[1]:
929
        #        line = QLineF(QPointF(changedConnPoint1[0], changedConnPoint1[1]), self.line().p2())
930
        #        self.setLine(line)
931
        #    else:
932
        #        line = QLineF(QPointF(changedConnPoint2[0], changedConnPoint2[1]), self.line().p2())
933
        #        self.setLine(line)
934
        ## endPoint에 symbol
935
        #elif self.connectors[1].connectedItem == symbol:
936
        #    if self.endPoint()[0] == symbol.connectors[0].sceneConnectPoint[0] and self.endPoint()[1] == symbol.connectors[0].sceneConnectPoint[1]:
937
        #        line = QLineF(self.line().p1(), QPointF(changedConnPoint1[0], changedConnPoint1[1]))
938
        #        self.setLine(line)
939
        #    else:
940
        #        line = QLineF(self.line().p1(), QPointF(changedConnPoint2[0], changedConnPoint2[1]))
941
        #        self.setLine(line)
942

    
943
        self.update()
944

    
945
    '''
946
        @brief  remove symbol
947
        @author humkyung
948
        @date   2018.04.23
949
    '''
950
    def removeSymbol(self, symbol):
951
        import math
952

    
953
        if 2 == len(symbol.connectors):  # 2-way component
954
            connected = symbol.connectors[0].connectedItem if symbol.connectors[0].connectedItem is not self else symbol.connectors[1].connectedItem
955
            
956
            pts = []
957
            pts.append(self.startPoint())
958
            pts.append(self.endPoint())
959
            pts.append(connected.startPoint())
960
            pts.append(connected.endPoint())
961

    
962
            self.scene().removeItem(connected)
963

    
964
            start = None
965
            end = None
966
            maxDist = None
967
            for i in range(len(pts)):
968
                for j in range(i+1,len(pts)):
969
                    dx = pts[i][0] - pts[j][0]
970
                    dy = pts[i][1] - pts[j][1]
971
                    dist = math.sqrt(dx*dx + dy*dy)
972
                    if maxDist is None:
973
                        maxDist = dist
974
                        start = pts[i]
975
                        end = pts[j]
976
                    elif dist > maxDist:
977
                        maxDist = dist
978
                        start = pts[i]
979
                        end = pts[j]
980

    
981
            if (pts[0] == end) or (pts[1] == start): start,end = end,start
982

    
983
            line = QLineF(QPointF(start[0], start[1]), QPointF(end[0], end[1]))
984
            self.setLine(line)
985
            #self.buildItem()
986
            self.update()
987

    
988
    def validate(self):
989
        '''
990
            @brief  validation check : connection
991
            @author euisung
992
            @date   2019.04.01
993
        '''
994
        from EngineeringAbstractItem import QEngineeringAbstractItem
995
        from SymbolSvgItem import SymbolSvgItem
996
        from EngineeringEquipmentItem import QEngineeringEquipmentItem
997
        from AppDocData import AppDocData
998
        errors = []
999

    
1000
        try:
1001
            _translate = QCoreApplication.translate
1002

    
1003
            docdata = AppDocData.instance()
1004
            dataPath = docdata.getErrorItemSvgPath()
1005

    
1006
            connectedUid = []
1007

    
1008
            for connector in self.connectors:
1009
                # for duplicattion check
1010
                if connector.connectedItem and issubclass(type(connector.connectedItem), QEngineeringAbstractItem):
1011
                    connectedUid.append(str(connector.connectedItem.uid))
1012

    
1013
                # check if there is not connected connector
1014
                if connector.connectedItem is None:
1015
                    error = SymbolSvgItem.createItem('Error', dataPath)
1016
                    error.setPosition(connector.center())
1017
                    error.parent = self
1018
                    error.msg = _translate('disconnected', 'disconnected')
1019
                    error.setToolTip(error.msg)
1020
                    error.area = 'Drawing'
1021
                    error.name = 'Error'
1022
                    errors.append(error)
1023

    
1024
                # check line to symbol
1025
                elif issubclass(type(connector.connectedItem), SymbolSvgItem) and type(connector.connectedItem) is not QEngineeringEquipmentItem:
1026
                    matches = [conn for conn in connector.connectedItem.connectors if conn.connectedItem is self]
1027
                    # check if two items are connected each other
1028
                    if not matches:
1029
                        error = SymbolSvgItem.createItem('Error', dataPath)
1030
                        error.setPosition(connector.center())
1031
                        error.parent = self
1032
                        error.msg = _translate('disconnected from opposite side', 'disconnected from opposite side')
1033
                        error.setToolTip(error.msg)
1034
                        error.area = 'Drawing'
1035
                        error.name = 'Error'
1036
                        errors.append(error)
1037
                    # check connection position
1038
                    elif not self.isOverlap(connector.sceneBoundingRect(), matches[0].sceneBoundingRect()):
1039
                        error = SymbolSvgItem.createItem('Error', dataPath)
1040
                        error.setPosition(connector.center())
1041
                        error.parent = self
1042
                        error.msg = _translate('mismatched position', 'mismatched position')
1043
                        error.setToolTip(error.msg)
1044
                        error.area = 'Drawing'
1045
                        error.name = 'Error'
1046
                        errors.append(error)
1047

    
1048
                elif issubclass(type(connector.connectedItem), QEngineeringLineItem):
1049
                    # check if connected two lines has same direction
1050
                    if connector._connected_at == QEngineeringAbstractItem.CONNECTED_AT_PT:
1051
                        center = connector.center()
1052

    
1053
                        indices = [0, 0]
1054
                        indices[0] = 1 if QPointF(center[0], center[1]) == self.line().p1() else 2
1055
                        matches = [conn for conn in connector.connectedItem.connectors if conn.connectedItem == self and conn._connected_at == QEngineeringAbstractItem.CONNECTED_AT_PT]
1056
                        if matches:
1057
                            indices[1] = 1 if QPointF(matches[0].center()[0], matches[0].center()[1]) == connector.connectedItem.line().p1() else 2
1058

    
1059
                        if indices[0] == indices[1]: 
1060
                            error = SymbolSvgItem.createItem('Error', dataPath)
1061
                            error.setPosition(connector.center())
1062
                            error.parent = self
1063
                            error.msg = _translate('flow direction error', 'flow direction error')
1064
                            error.setToolTip(error.msg)
1065
                            error.area = 'Drawing'
1066
                            error.name = 'Error'
1067
                            errors.append(error)
1068

    
1069
                        if self.lineType != connector.connectedItem.lineType:
1070
                            error = SymbolSvgItem.createItem('Error', dataPath)
1071
                            error.setPosition(connector.center())
1072
                            error.parent = self
1073
                            error.msg = _translate('line type error', 'line type error')
1074
                            error.setToolTip(error.msg)
1075
                            error.area = 'Drawing'
1076
                            error.name = 'Error'
1077
                            errors.append(error)
1078

    
1079
                errors.extend(connector.validate())
1080

    
1081
            # check duplicated connection
1082
            if len(connectedUid) is not len(set(connectedUid)):
1083
                error = SymbolSvgItem.createItem('Error', dataPath)
1084
                error.setPosition([self.sceneBoundingRect().center().x(), self.sceneBoundingRect().center().y()])
1085
                error.parent = self
1086
                error.msg = _translate('duplicated connection', 'duplicated connection')
1087
                error.setToolTip(error.msg)
1088
                error.area = 'Drawing'
1089
                error.name = 'Error'
1090
                errors.append(error)
1091

    
1092
        except Exception as ex:
1093
            from App import App 
1094
            from AppDocData import MessageType
1095

    
1096
            message = 'error occured({}) in {}:{}'.format(ex, sys.exc_info()[-1].tb_frame.f_code.co_filename, sys.exc_info()[-1].tb_lineno)
1097
            App.mainWnd().addMessage.emit(MessageType.Error, message)
1098
        
1099
        return errors
1100

    
1101
    '''
1102
        @brief  update line type
1103
        @author humkyung
1104
        @date   2018.07.05
1105
    '''
1106
    def update_line_type(self):
1107
        import uuid
1108
        from LineTypeConditions import LineTypeConditions
1109

    
1110
        try:
1111
            pool,visited,items = [self],[],[]
1112
            while pool:
1113
                obj = pool.pop()
1114
                visited.append(obj)
1115

    
1116
                """ connected items """
1117
                connected = [connector.connectedItem for connector in obj.connectors if connector.connectedItem and not type(connector.connectedItem) is uuid.UUID]
1118
                """ connected lines at point """
1119
                lines = [connector.connectedItem for connector in obj.connectors if connector.connectedItem and type(connector.connectedItem) is QEngineeringLineItem and connector._connected_at == QEngineeringAbstractItem.CONNECTED_AT_PT]
1120
                """ add items not in 'connected' to items list """
1121
                items.extend([item for item in connected if item not in lines])
1122
                """ add items not visited to pool """
1123
                pool.extend([item for item in lines if item not in visited])
1124

    
1125
            for condition in LineTypeConditions.items():
1126
                if condition.eval(items):
1127
                    self.lineType = condition.name
1128
                    break
1129
        except Exception as ex:
1130
            from App import App 
1131
            from AppDocData import MessageType
1132

    
1133
            message = 'error occured({}) in {}:{}'.format(ex, sys.exc_info()[-1].tb_frame.f_code.co_filename, sys.exc_info()[-1].tb_lineno)
1134
            App.mainWnd().addMessage.emit(MessageType.Error, message)
1135

    
1136
    def hoverEnterEvent(self, event):
1137
        """ hilight item and it's children """
1138
        self.highlight(True)
1139

    
1140
    def hoverLeaveEvent(self, event):
1141
        """ restore original color """
1142
        self.highlight(False)
1143

    
1144
    def highlight(self, flag):
1145
        self.hover = flag 
1146
        self.setZValue(QEngineeringAbstractItem.HOVER_ZVALUE) if flag else self.setZValue(QEngineeringLineItem.ZVALUE)
1147
        self.update()
1148

    
1149
        for connector in self.connectors:
1150
            connector.highlight(flag)
1151

    
1152
    def hoverMoveEvent(self, event):
1153
        pass
1154
    
1155
    '''
1156
        @brief      remove item when user press delete key
1157
        @author     humkyung
1158
        @date       2018.04.23
1159
        @history    swap start, end point when user press 'c' key
1160
    '''
1161
    def keyPressEvent(self, event): 
1162
        if self.isSelected() and event.key() == Qt.Key_Delete:
1163
            self.scene().removeItem(self)
1164
        elif event.key() == Qt.Key_C:
1165
            self.reverse()
1166
    
1167
    '''
1168
        @brief  draw rect when item is selected
1169
        @author humkyung
1170
        @date   2018.07.07
1171
    '''
1172
    def drawFocusRect(self, painter):
1173
        self.focuspen = QPen(Qt.DotLine)
1174
        self.focuspen.setColor(Qt.black)
1175
        self.focuspen.setWidthF(1.5)
1176
        hilightColor = QColor(255, 0, 0, 127)
1177
        painter.setBrush(QBrush(hilightColor))
1178
        painter.setPen(self.focuspen)
1179
        painter.drawRect(self.boundingRect())
1180

    
1181
    '''
1182
        @brief      override paint method
1183
        @history    humkyung 2018.08.30 draw flowmark only for Primary or Secondary
1184
    '''
1185
    def paint(self, painter, option, widget):
1186
        color = self.getColor()
1187
        self.setColor(color)
1188

    
1189
        QGraphicsLineItem.paint(self, painter, option, widget)
1190

    
1191
        if self.isSelected():
1192
            self.drawFocusRect(painter)
1193

    
1194
    '''
1195
        @brief  draw self to given image
1196
        @author humkyung
1197
        @date   2018.06.21
1198
    '''
1199
    def drawToImage(self, img, color, thickness):
1200
        try:
1201
            # write recognized lines to image
1202
            ptStart = self.startPoint()
1203
            ptEnd = self.endPoint()
1204
            cv2.line(img, (round(ptStart[0]), round(ptStart[1])), (round(ptEnd[0]), round(ptEnd[1])), color, thickness)
1205
            # up to here
1206

    
1207
            #if self.flowMark is not None:
1208
            #    x, y, w, h = self.flowMark[0]
1209
            #    img[y:(y+h), x:(x+w)] = color
1210
        except Exception as ex:
1211
            print('error occured({}) in {}:{}'.format(ex, sys.exc_info()[-1].tb_frame.f_code.co_filename, sys.exc_info()[-1].tb_lineno))
1212

    
1213
    @staticmethod 
1214
    def from_database(component):
1215
        """ get line from database """
1216
        import uuid
1217
        from AppDocData import AppDocData
1218
        from SymbolAttr import SymbolAttr
1219
        
1220
        item = None
1221
        try:
1222
            uidNode = component['UID']
1223
            uid = uidNode if uidNode is not None else uuid.uuid4() # generate UUID
1224
            owner = uuid.UUID(component['Owner'], version=4) if component['Owner'] and component['Owner'] != 'None' else None
1225

    
1226
            app_doc_data = AppDocData.instance()
1227
            connectors = app_doc_data.get_component_connectors(uid)
1228
            if 2 != len(connectors): return item
1229
            startPoint = [float(connectors[0]['X']), float(connectors[0]['Y'])]
1230
            endPoint = [float(connectors[1]['X']), float(connectors[1]['Y'])]
1231

    
1232
            item = QEngineeringLineItem(vertices=[startPoint, endPoint], uid=uid)
1233
            item.setVisible(False)
1234
            attrs = app_doc_data.get_component_attributes(uid)
1235
            matches = [attr for attr in attrs if attr['Attribute'] == 'LineType']
1236
            item.lineType = matches[0]['Value'] if matches else 'Secondary'
1237
            ## assign area
1238
            if component['Area']:
1239
                for area in app_doc_data.getAreaList():
1240
                    if area.contains(startPoint) and area.contains(endPoint):
1241
                        item.area = area.name
1242
                        break
1243
            else:
1244
                item.area = component['Area']
1245
            ## up to here
1246

    
1247
            matches = [attr for attr in attrs if attr['Attribute'] == 'Thickness']
1248
            item.thickness = int(matches[0]['Value']) if matches and matches[0]['Value'] != 'None' else None
1249

    
1250
            matches = [attr for attr in attrs if attr['Attribute'] == 'FlowMark']
1251
            item.flowMark = int(matches[0]['Value']) if matches and matches[0]['Value'] != 'None' else None
1252

    
1253
            if connectors:
1254
                iterIndex = 0
1255
                for connector in connectors:
1256
                    item.connectors[iterIndex].parse_record(connector)
1257
                    iterIndex += 1
1258

    
1259
            # get associations 
1260
            associations = app_doc_data.get_component_associations(uid)
1261
            if associations:
1262
                for assoc in associations:
1263
                    _type = assoc['Type']
1264
                    if not _type in item._associations:
1265
                        item._associations[_type] = []
1266
                    item._associations[_type].append(uuid.UUID(assoc['Association']))
1267
            # up to here
1268
        except Exception as ex:
1269
            from App import App 
1270
            from AppDocData import MessageType
1271

    
1272
            message = 'error occured({}) in {}:{}'.format(ex, sys.exc_info()[-1].tb_frame.f_code.co_filename, sys.exc_info()[-1].tb_lineno)
1273
            App.mainWnd().addMessage.emit(MessageType.Error, message)
1274

    
1275
        return item if item.length() > 1 else None
1276

    
1277
    '''
1278
        @brief      parse xml code
1279
        @author     humkyung
1280
        @date       2018.06.27
1281
    '''
1282
    @staticmethod 
1283
    def fromXml(node):
1284
        import uuid
1285
        from AppDocData import AppDocData
1286
        from SymbolAttr import SymbolAttr
1287
        
1288
        item = None
1289
        try:
1290
            uidNode = node.find('UID')
1291
            uid = uidNode.text if uidNode is not None else uuid.uuid4() # generate UUID
1292
            owner = uuid.UUID(node.attrib['OWNER'], version=4) if 'OWNER' in node.attrib and node.attrib['OWNER'] != 'None' else None
1293

    
1294
            startPoint = [float(x) for x in node.find('STARTPOINT').text.split(',')]
1295
            endPoint = [float(x) for x in node.find('ENDPOINT').text.split(',')]
1296

    
1297
            item = QEngineeringLineItem(vertices=[startPoint, endPoint], uid=uid)
1298
            item.setVisible(False)
1299
            item.lineType = node.find('TYPE').text if node.find('TYPE') is not None else 'Secondary'
1300
            ## assign area
1301
            if node.find('AREA') is None:
1302
                appDocData = AppDocData.instance()
1303
                for area in appDocData.getAreaList():
1304
                    if area.contains(startPoint) and area.contains(endPoint):
1305
                        item.area = area.name
1306
                        break
1307
            else:
1308
                item.area = node.find('AREA').text
1309
            ## up to here
1310

    
1311
            thicknessNode = node.find('THICKNESS')
1312
            item.thickness = int(thicknessNode.text) if thicknessNode is not None and thicknessNode.text != 'None' else None
1313

    
1314
            flowMarkNode = node.find('FLOWMARK')
1315
            item.flowMark = int(flowMarkNode.text) if flowMarkNode is not None and flowMarkNode.text != 'None' else None
1316

    
1317
            connectors = node.find('CONNECTORS')
1318
            if connectors is not None:
1319
                iterIndex = 0
1320
                for connector in connectors.iter('CONNECTOR'):
1321
                    item.connectors[iterIndex].parse_xml(connector)
1322
                    iterIndex += 1
1323

    
1324
            # get associations 
1325
            attributeValue = node.find('ASSOCIATIONS')
1326
            if attributeValue is not None:
1327
                for assoc in attributeValue.iter('ASSOCIATION'):
1328
                    _type = assoc.attrib['TYPE']
1329
                    if not _type in item._associations:
1330
                        item._associations[_type] = []
1331
                    item._associations[_type].append(uuid.UUID(assoc.text))
1332
            # up to here
1333

    
1334
            attributes = node.find('SYMBOLATTRIBUTES')
1335
            if attributes is not None:
1336
                for attr in attributes.iter('ATTRIBUTE'):
1337
                    _attr = SymbolAttr.fromXml(attr)
1338
                    item.attrs[_attr] = attr.text
1339
                    
1340
        except Exception as ex:
1341
            from App import App 
1342
            from AppDocData import MessageType
1343

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

    
1347
        return item if item.length() > 1 else None
1348

    
1349
    '''
1350
        @brief      generate xml code
1351
        @author     humkyung
1352
        @date       2018.04.23
1353
        @history    humkyung 2018.06.27 write line type to xml
1354
                    humkyung 2018.07.23 write connected item's uid to xml
1355
    '''
1356
    def toXml(self):
1357
        from xml.etree.ElementTree import Element, SubElement, dump, ElementTree
1358
        from LineTypeConditions import LineTypeConditions
1359
        from SymbolAttr import SymbolAttr
1360

    
1361
        try:
1362
            node = Element('LINE')
1363
            node.attrib['OWNER'] = str(self._owner) if self._owner else 'None'
1364
            uidNode = Element('UID')
1365
            uidNode.text = str(self.uid)
1366
            node.append(uidNode)
1367

    
1368
            startPt = self.startPoint()
1369
            endPt = self.endPoint()
1370

    
1371
            startNode = Element('STARTPOINT')
1372
            startNode.text = '{},{}'.format(startPt[0], startPt[1])
1373
            node.append(startNode)
1374

    
1375
            endNode = Element('ENDPOINT')
1376
            endNode.text = '{},{}'.format(endPt[0], endPt[1])
1377
            node.append(endNode)
1378

    
1379
            typeNode = Element('TYPE')
1380
            typeNode.text = self.lineType
1381
            for lineType in LineTypeConditions.items():
1382
                if self.lineType == lineType.name:
1383
                    typeNode.attrib['TYPEUID'] = str(lineType)
1384
                    break
1385
            node.append(typeNode)
1386

    
1387
            areaNode = Element('AREA')
1388
            areaNode.text = self.area
1389
            node.append(areaNode)
1390

    
1391
            thicknessNode = Element('THICKNESS')
1392
            thicknessNode.text = str(self.thickness)
1393
            node.append(thicknessNode)
1394

    
1395
            flowMarkNode = Element('FLOWMARK')
1396
            flowMarkNode.text = str(self.flowMark)
1397
            node.append(flowMarkNode)
1398

    
1399
            connectorsNode = Element('CONNECTORS')
1400
            for connector in self.connectors:
1401
                connectorsNode.append(connector.toXml())
1402
            node.append(connectorsNode)
1403

    
1404
            attributeValueNode = Element('ASSOCIATIONS')
1405
            for assoc in self.associations():
1406
                assoc_node = Element('ASSOCIATION')
1407
                assoc_node.attrib['TYPE'] = QEngineeringAbstractItem.assoc_type(assoc)
1408
                assoc_node.text = str(assoc.uid)
1409
                attributeValueNode.append(assoc_node)
1410
            node.append(attributeValueNode)
1411

    
1412
            attributesNode = Element('SYMBOLATTRIBUTES')
1413
            _attrs = self.getAttributes()
1414
            for attr in _attrs:
1415
                if type(attr) is SymbolAttr:
1416
                    _node = attr.toXml()
1417
                    _node.text = str(_attrs[attr])
1418
                    attributesNode.append(_node)
1419

    
1420
            node.append(attributesNode)
1421

    
1422
            # up to here
1423
        except Exception as ex:
1424
            from App import App 
1425
            from AppDocData import MessageType
1426

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

    
1431
        return node
1432

    
1433
    def toSql(self):
1434
        """ generate sql phrase to save line to database """
1435
        import uuid
1436
        from AppDocData import AppDocData
1437

    
1438
        res = []
1439

    
1440
        app_doc_data = AppDocData.instance()
1441
        cols = ['UID', 'Drawings_UID', 'Symbol_UID', 'X', 'Y', 'Width', 'Height', 'Rotation', 'Area', 'Owner', 'SpecialItemTypes_UID']
1442
        values = ['?', '?', "(select UID from Symbol where Name='Line' and SymbolType_UID=-1)", '?', '?', '?', '?', '?', '?', '?', '?']
1443

    
1444
        rect = self.sceneBoundingRect()
1445
        param = [(str(self.uid), str(app_doc_data.activeDrawing.UID), rect.x(), rect.y(), rect.width(), rect.height(), 0, 
1446
        self.area, str(self.owner) if self.owner else None, str(self.special_item_type) if self.special_item_type else None)]
1447
        sql = 'insert into Components({}) values({})'.format(','.join(cols), ','.join(values))
1448
        res.append((sql, tuple(param)))
1449

    
1450
        # save connectors to database
1451
        cols = ['Components_UID', '[Index]', 'X', 'Y', 'Connected', 'Connected_At']
1452
        values = ['?', '?', '?', '?', '?', '?']
1453
        params = []
1454
        index = 1 
1455
        for connector in self.connectors:
1456
            params.append((#str(connector.uid),
1457
                str(self.uid), index, connector.connectPoint[0], connector.connectPoint[1],\
1458
                str(connector.connectedItem.uid) if connector.connectedItem else None,\
1459
                str(connector._connected_at)))
1460
            index += 1
1461
        sql = 'insert into Points({}) values({})'.format(','.join(cols), ','.join(values))
1462
        res.append((sql, tuple(params)))
1463
        # up to here
1464

    
1465
        # save attributes
1466
        cols = ['UID', 'Components_UID', 'SymbolAttribute_UID', 'Value']
1467
        values = ['?', '?', "(select UID from SymbolAttribute where Attribute='LineType' and SymbolType_UID=-1)", '?']
1468
        param = [(str(uuid.uuid4()), str(self.uid), self.lineType)]
1469
        sql = 'insert into Attributes({}) values({})'.format(','.join(cols), ','.join(values))
1470
        res.append((sql, tuple(param)))
1471
        values = ['?', '?', "(select UID from SymbolAttribute where Attribute='Thickness' and SymbolType_UID=-1)", '?']
1472
        param = [(str(uuid.uuid4()), str(self.uid), str(self.thickness))]
1473
        sql = 'insert into Attributes({}) values({})'.format(','.join(cols), ','.join(values))
1474
        res.append((sql, tuple(param)))
1475
        values = ['?', '?', "(select UID from SymbolAttribute where Attribute='FlowMark' and SymbolType_UID=-1)", '?']
1476
        param = [(str(uuid.uuid4()), str(self.uid), str(self.flowMark))]
1477
        sql = 'insert into Attributes({}) values({})'.format(','.join(cols), ','.join(values))
1478
        res.append((sql, tuple(param)))
1479
        # up to here
1480
        
1481
        return res
1482

    
1483
    '''
1484
        @brief      Delete Line Item from scene
1485
        @author     Jeongwoo
1486
        @date       2018.05.29
1487
        @history    2018.05.29  Add parameter 'self' / Make comments emit()
1488
    '''
1489
    def deleteLineItemFromScene(self):
1490
        self.scene().removeItem(self)
1491
        
1492
    def getColor(self):
1493
        """ return line's color """
1494
        from AppDocData import AppDocData
1495
        from DisplayColors import DisplayColors
1496
        from DisplayColors import DisplayOptions
1497
        from EngineeringAbstractItem import QEngineeringAbstractItem
1498

    
1499
        if DisplayOptions.DisplayByLineType == DisplayColors.instance().option:
1500
            if self.hover:
1501
                return QEngineeringAbstractItem.HOVER_COLOR
1502
            else:
1503
                if self.lineType in QEngineeringLineItem.LINE_TYPE_COLORS:
1504
                    return QEngineeringLineItem.LINE_TYPE_COLORS[self.lineType]
1505
                else:
1506
                    app_doc_data = AppDocData.instance()
1507
                    configs = app_doc_data.getConfigs('LineTypes', self.lineType)
1508
                    if configs:
1509
                        tokens = configs[0].value.split(',')
1510
                        QEngineeringLineItem.LINE_TYPE_COLORS[self.lineType] = tokens[0] if len(tokens) == 4 else QEngineeringAbstractItem.DEFAULT_COLOR
1511
                        return QEngineeringLineItem.LINE_TYPE_COLORS[self.lineType]
1512
                    else:
1513
                        return QEngineeringAbstractItem.DEFAULT_COLOR
1514
        else:
1515
            if self.owner is None:
1516
                return QEngineeringAbstractItem.getColor(self)
1517
            else:
1518
                return self.owner.getColor()
1519

    
1520

    
1521
    '''
1522
        @brief      Set Color. Override QEngineeringAbstractItem's
1523
        @author     Jeongwoo
1524
        @date       2018.05.11
1525
        @history    2018.05.11  Jeongwoo    Add self.setPen() Method
1526
        @history    humkyung 2018.05.13 call setPen method to apply changed color
1527
    '''
1528
    def setColor(self, color):
1529
        c = QColor()
1530
        c.setNamedColor(color)
1531
        _pen = self.pen()
1532
        _pen.setColor(c)
1533
        self.setPen(_pen)
1534
        self.update()
1535

    
1536
    def update_arrow(self):
1537
        """ update flow arrow """
1538
        import math
1539
        from EngineeringArrowItem import QEngineeringArrowItem
1540
        if self.length() < 0.01:
1541
            return
1542

    
1543
        start = self.line().p1()
1544
        end = self.line().p2()
1545

    
1546
        dx = end.x() - start.x()
1547
        dy = end.y() - start.y()
1548
        _dir = [dx/self.length(), dy/self.length()]
1549

    
1550
        arrow_size = QEngineeringLineItem.ARROW_SIZE * 0.25
1551
        
1552
        if not (self._lineType == 'Primary' or self._lineType == 'Secondary'):
1553
            self.flowMark = None
1554

    
1555
        if self.flowMark:
1556
            arrow_size *= 2
1557
            end = QPointF(start.x() + dx * self.flowMark / 100, start.y() + dy * self.flowMark / 100)
1558

    
1559
        perpendicular = (-_dir[1], _dir[0])
1560
        polygon = QPolygonF()
1561
        polygon.append(QPointF(end.x() - _dir[0]*QEngineeringLineItem.ARROW_SIZE + perpendicular[0]*arrow_size, 
1562
                            end.y() - _dir[1]*QEngineeringLineItem.ARROW_SIZE + perpendicular[1]*arrow_size))
1563
        polygon.append(QPointF(end.x() - _dir[0]*QEngineeringLineItem.ARROW_SIZE - perpendicular[0]*arrow_size, 
1564
                            end.y() - _dir[1]*QEngineeringLineItem.ARROW_SIZE - perpendicular[1]*arrow_size))
1565
        polygon.append(end)
1566
        polygon.append(polygon[0])  # close polygon
1567

    
1568
        if not hasattr(self, '_arrow'):
1569
            self._arrow = QEngineeringArrowItem(polygon, self)
1570
        else:
1571
            self._arrow.setPolygon(polygon)
1572

    
1573
        if self.flowMark:
1574
            self._arrow.setBrush(Qt.red)
1575
        else:
1576
            self._arrow.setBrush(Qt.blue)
1577
        self._arrow.update()
1578

    
1579
        '''
1580
        if self._flowMark:
1581
            flowMark = self._flowMark[0]
1582
            flowMark.angle = math.atan2(_dir[0], _dir[1]) - math.pi / 2
1583
            flowMark.loc = [self.connectors[1].center()[0] - flowMark.symbolOrigin[0] - _dir[0] * 20, self.connectors[1].center()[1] - flowMark.symbolOrigin[1] - _dir[1] * 20]
1584
            flowMark.origin = [self.connectors[1].center()[0] - _dir[0] * 20, self.connectors[1].center()[1] - _dir[1] * 20]
1585
            scene = flowMark.scene()
1586
            scene.removeItem(flowMark)
1587
            flowMark.addSvgItemToScene(scene)
1588
        '''
1589

    
1590
    '''
1591
        @brief      reshape line
1592
        @author     humkyung
1593
        @date       2018.07.27
1594
    '''
1595
    def onConnectorPosChaned(self, connector):
1596
        from EngineeringArrowItem import QEngineeringArrowItem
1597

    
1598
        start = self.connectors[0].center()
1599
        end = self.connectors[1].center()
1600
        self.setLine(start[0], start[1], end[0], end[1])
1601
        self.update()
1602

    
1603
        tooltip = '<b>{}</b><br>({},{})-({},{})'.format(str(self.uid), start[0], start[1], end[0], end[1])
1604
        self.setToolTip(tooltip)
1605

    
1606
        self.update_arrow()
1607

    
1608
    '''
1609
        @brief      
1610
        @author     humkyung
1611
        @date       2018.07.24
1612
    '''
1613
    def mousePressEvent(self, event):
1614
        import math
1615

    
1616
        if event.buttons() == Qt.LeftButton:
1617
            pos = event.scenePos()
1618
            ptStart = self.startPoint()
1619
            dx = ptStart[0] - pos.x()
1620
            dy = ptStart[1] - pos.y()
1621
            if math.sqrt(dx*dx + dy*dy) < 10:
1622
                self._selectedIndex = 0
1623
                return
1624

    
1625
            ptEnd = self.endPoint()
1626
            dx = ptEnd[0] - pos.x()
1627
            dy = ptEnd[1] - pos.y()
1628
            if math.sqrt(dx*dx + dy*dy) < 10:
1629
                self._selectedIndex = 1
1630
                return
1631

    
1632
            self._selectedIndex = -1
1633
            
1634
        QGraphicsLineItem.mousePressEvent(self, event)
1635

    
1636
    def mouseReleaseEvent(self, event):
1637
        self._selectedIndex = -1
1638

    
1639
        QGraphicsLineItem.mouseReleaseEvent(self, event)
1640

    
1641
'''
1642
    @brief      The class transfer pyqtSignal Event. Cause Subclass of QGraphicsRectItem can't use pyqtSignal
1643
    @author     Jeongwoo
1644
    @date       2018.06.18
1645
'''
1646
class Transfer(QObject):
1647
    onRemoved = pyqtSignal(QGraphicsItem)
1648

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