프로젝트

일반

사용자정보

통계
| 개정판:

hytos / DTI_PID / DTI_PID / Shapes / SymbolSvgItem.py @ 8487ecb2

이력 | 보기 | 이력해설 | 다운로드 (89.4 KB)

1
#!/usr/bin/env/python3
2
# coding: utf-8
3

    
4
import sys
5
import os
6
import math
7
import numpy as np
8
from PyQt5.QtGui import *
9
from PyQt5.QtCore import *
10
from PyQt5.QtSvg import *
11
from PyQt5.QtWidgets import (QApplication, QGraphicsItem, QDialog)
12
from PyQt5.QtXml import *
13

    
14
from AppDocData import *
15
from EngineeringConnectorItem import QEngineeringConnectorItem
16
from EngineeringAbstractItem import QEngineeringAbstractItem
17
from EngineeringConnectorItem import QEngineeringConnectorItem
18
from UserInputAttribute import UserInputAttribute
19
import SelectAttributeCommand
20

    
21

    
22
class SymbolSvgItem(QGraphicsSvgItem, QEngineeringAbstractItem):
23
    """ This is symbolsvgitem class """
24

    
25
    clicked = pyqtSignal(QGraphicsSvgItem)
26
    ZVALUE = 50
27
    HOVER_COLOR = 'url(#hover)'
28

    
29
    DOCUMENTS = {}  # store documents and renders for symbols
30

    
31
    '''
32
        @history    18.04.11    Jeongwoo    Add Variable (Name, Type)
33
                    18.05.11    Jeongwoo    Declare variable self.color
34
                    18.05.25    Jeongwoo    Call setColor() method
35
                    18.05.30    Jeongwoo    Add self variables (parentSymbol, childSymbol)
36
    '''
37

    
38
    def __init__(self, name, path, uid=None, flip=0):
39
        import uuid
40
        from SymbolAttr import SymbolProp
41

    
42
        QGraphicsSvgItem.__init__(self)
43
        QEngineeringAbstractItem.__init__(self)
44

    
45
        self.setFlags(
46
            QGraphicsItem.ItemIsSelectable | QGraphicsItem.ItemIsFocusable | QGraphicsItem.ItemIsMovable |
47
            QGraphicsItem.ItemSendsGeometryChanges | QGraphicsItem.ItemClipsToShape)
48

    
49
        self.dbUid = None  # symbol UID
50
        self.uid = uuid.uuid4() if uid is None else uuid.UUID(uid)
51
        self.name = name
52
        self.type = ''
53
        self.iType = -2
54
        self.text_area = None
55
        self.angle = 0
56
        self.origin = None
57
        self.loc = None
58
        self.size = None
59
        self._owner = None
60
        self.parentSymbol = ''
61
        self.childSymbol = ''
62
        self.hasInstrumentLabel = 0
63
        self.flip = flip
64
        self.hit_ratio = None
65
        self._converted = False  # flag indicate whether symbol is converted or not
66
        # attributeType uid
67
        self.attribute = ''
68
        self._properties = {SymbolProp(None, 'Supplied By', 'String'): None}
69

    
70
        self.setAcceptDrops(True)
71
        self.setAcceptHoverEvents(True)
72
        self.setAcceptedMouseButtons(Qt.LeftButton)
73
        self.setAcceptTouchEvents(True)
74

    
75
        self.currentCursor = 0
76
        self.transfer = Transfer()
77

    
78
        self._angle = 0
79

    
80
        self.in_out_connector = [[], []] # 0 : in, 1 : out
81
        self.break_connector = []
82
        self.conn_type = []
83

    
84
        try:
85
            app_doc_data = AppDocData.instance()
86
            svg = None
87

    
88
            if path not in SymbolSvgItem.DOCUMENTS:
89
                if self.name:
90
                    _, svg = app_doc_data.read_symbol_shape(self.name)
91

    
92
                if not svg and path and os.path.isfile(path):
93
                    f = QFile(path)
94
                    f.open(QIODevice.ReadOnly)
95
                    svg = f.readAll()
96
                    f.close()
97

    
98
                self._document = QDomDocument()
99
                self._document.setContent(svg)
100
                self._renderer = QSvgRenderer(self._document.toByteArray())
101
                SymbolSvgItem.DOCUMENTS[path] = [self._document, self._renderer]
102
            else:
103
                self._document, self._renderer = SymbolSvgItem.DOCUMENTS[path]
104

    
105
            self.setSharedRenderer(self._renderer)
106
            self.setCacheMode(QGraphicsItem.ItemCoordinateCache)
107

    
108
            #self._color = self.get_attribute('fill')
109
        except Exception as ex:
110
            from App import App
111

    
112
            message = f'error occurred({repr(ex)}) in {sys.exc_info()[-1].tb_frame.f_code.co_filename}:' \
113
                      f'{sys.exc_info()[-1].tb_lineno}'
114
            App.mainWnd().addMessage.emit(MessageType.Error, message)
115

    
116
        self.setZValue(SymbolSvgItem.ZVALUE)
117

    
118
        app_doc_data = AppDocData.instance()
119
        configs = app_doc_data.getConfigs('Symbol Style', 'Opacity')
120
        self.setOpacity(float(configs[0].value) / 100 if configs else 0.5)
121

    
122
        self.__color = None  # svg item's color
123

    
124
    @property
125
    def converted(self):
126
        """return whethere symbol is converted or not"""
127
        return self._converted
128

    
129
    @converted.setter
130
    def converted(self, value):
131
        """set converted flag with given value"""
132
        self._converted = value
133

    
134
    def has_in_out_connector(self):
135
        """ return True if item has in or out connector """
136
        if len(self.in_out_connector[0]) > 0 and len(self.in_out_connector[1]) > 0:
137
            return True
138
        else:
139
            return False
140

    
141
    def has_break_connector(self):
142
        """ return True if item has break connector """
143
        if len(self.break_connector) > 0:
144
            return True
145
        else:
146
            return False
147

    
148
    def __str__(self):
149
        """ return string represent uuid """
150
        return str(self.uid)
151

    
152
    @property
153
    def owner(self):
154
        """get owner"""
155
        import uuid
156

    
157
        if self._owner and type(self._owner) is uuid.UUID:
158
            matches = [x for x in self.scene().items() if hasattr(x, 'uid') and str(x.uid) == str(self._owner)]
159
            if matches:
160
                self._owner = matches[0]
161

    
162
        if type(self._owner) is not uuid.UUID and type(self._owner) is not str:
163
            return self._owner
164
        else:
165
            self._owner = None
166
            return None
167

    
168
    '''
169
        @brief  setter owner
170
        @author humkyung
171
        @date   2018.05.10
172
        @history    2018.05.17  Jeongwoo    Add Calling setColor if self._owner is None or not
173
    '''
174
    @owner.setter
175
    def owner(self, value):
176
        self._owner = value
177

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

    
182
    def validate(self):
183
        """validation check"""
184

    
185
        from EngineeringAbstractItem import QEngineeringAbstractItem
186
        from EngineeringLineItem import QEngineeringLineItem
187
        from EngineeringSpecBreakItem import QEngineeringSpecBreakItem
188
        from EngineeringTextItem import QEngineeringTextItem
189
        from EngineeringEquipmentItem import QEngineeringEquipmentItem
190
        errors = []
191

    
192
        try:
193
            app_doc_data = AppDocData.instance()
194
            dataPath = app_doc_data.getErrorItemSvgPath()
195

    
196
            # validate connectors
197
            for connector in self.connectors:
198
                errors.extend(connector.validate())
199

    
200
            # check if connected two lines has same direction
201
            if len(self.connectors) is 2:
202
                if (type(self.connectors[0].connectedItem) is QEngineeringLineItem and
203
                    self.connectors[0]._connected_at == QEngineeringAbstractItem.CONNECTED_AT_PT) and \
204
                        (type(self.connectors[1].connectedItem) is QEngineeringLineItem and
205
                         self.connectors[1]._connected_at == QEngineeringAbstractItem.CONNECTED_AT_PT):
206
                    indices = [0, 0]
207

    
208
                    center = self.connectors[0].center()
209
                    dx, dy = [0, 0], [0, 0]
210
                    dx[0] = center[0] - self.connectors[0].connectedItem.line().p1().x()
211
                    dy[0] = center[1] - self.connectors[0].connectedItem.line().p1().y()
212
                    dx[1] = center[0] - self.connectors[0].connectedItem.line().p2().x()
213
                    dy[1] = center[1] - self.connectors[0].connectedItem.line().p2().y()
214
                    indices[0] = 1 if (dx[0] * dx[0] + dy[0] * dy[0]) < (dx[1] * dx[1]) + (dy[1] * dy[1]) else 2
215

    
216
                    center = self.connectors[1].center()
217
                    dx[0] = center[0] - self.connectors[1].connectedItem.line().p1().x()
218
                    dy[0] = center[1] - self.connectors[1].connectedItem.line().p1().y()
219
                    dx[1] = center[0] - self.connectors[1].connectedItem.line().p2().x()
220
                    dy[1] = center[1] - self.connectors[1].connectedItem.line().p2().y()
221
                    indices[1] = 1 if (dx[0] * dx[0] + dy[0] * dy[0]) < (dx[1] * dx[1]) + (dy[1] * dy[1]) else 2
222

    
223
                    if indices[0] == indices[1]:
224
                        error = SymbolSvgItem.createItem('Error', None, dataPath)
225
                        error.parent = self
226
                        error.msg = self.tr('flow direction error')
227
                        error.setToolTip(error.msg)
228
                        error.area = 'Drawing'
229
                        error.name = 'Error'
230
                        errors.append(error)
231

    
232
            # check disconnected point
233
            attrs = self.getAttributes()
234
            matches = [(attr, value) for attr, value in attrs.items() if attr.Attribute == 'OWNERSYMBOL']
235
            if matches:
236
                if not matches[0][1]:
237
                    error = SymbolSvgItem.createItem('Error', None, dataPath)
238
                    error.parent = self
239
                    error.msg = 'not combined'
240
                    error.setToolTip(error.msg)
241
                    error.area = 'Drawing'
242
                    error.name = 'Error'
243
                    errors.append(error)
244
            else:
245
                disconnect = False
246
                if len(self.connectors) is not 0:
247
                    disconnect = True
248
                    for connector in self.connectors:
249
                        if connector.connectedItem is not None:
250
                            disconnect = False
251
                            break
252

    
253
                if disconnect:
254
                    error = SymbolSvgItem.createItem('Error', None, dataPath)
255
                    error.parent = self
256
                    error.msg = 'disconnected'
257
                    error.setToolTip(error.msg)
258
                    error.area = 'Drawing'
259
                    error.name = 'Error'
260
                    errors.append(error)
261

    
262
            # check if symbol size if 0
263
            if self.size[0] == 0 or self.size[1] == 0:
264
                error = SymbolSvgItem.createItem('Error', None, dataPath)
265
                error.parent = self
266
                error.msg = self.tr('size error')
267
                error.setToolTip(error.msg)
268
                error.area = 'Drawing'
269
                error.name = 'Error'
270
                errors.append(error)
271

    
272
            # check if association item's owner exists
273
            for assoc in self.associations():
274
                if issubclass(type(assoc), QEngineeringTextItem) and not assoc.owner:
275
                    error = SymbolSvgItem.createItem('Error', None, dataPath)
276
                    error.parent = self
277
                    error.msg = self.tr('association error')
278
                    error.setToolTip(error.msg)
279
                    error.area = self.area
280
                    error.name = 'Error'
281
                    errors.append(error)
282

    
283
            # set error position
284
            for error in errors:
285
                error.setPosition([self.sceneBoundingRect().center().x(), self.sceneBoundingRect().center().y()])
286

    
287
            connectedUid = []
288
            for connector in self.connectors:
289
                # for duplicattion check
290
                if connector.connectedItem and issubclass(type(connector.connectedItem), QEngineeringAbstractItem):
291
                    connectedUid.append(str(connector.connectedItem.uid))
292

    
293
                if (issubclass(type(connector.connectedItem), SymbolSvgItem) and \
294
                        type(connector.connectedItem) is not QEngineeringEquipmentItem) or \
295
                                type(connector.connectedItem) is QEngineeringLineItem:
296
                    matches = [conn for conn in connector.connectedItem.connectors if conn.connectedItem is self]
297
                    # check if two items are connected each other
298
                    if not matches:
299
                        error = SymbolSvgItem.createItem('Error', None, dataPath)
300
                        error.setPosition(list(connector.center()))
301
                        error.parent = self
302
                        error.msg = self.tr('disconnected from opposite side')
303
                        error.setToolTip(error.msg)
304
                        error.area = self.area
305
                        error.name = 'Error'
306
                        errors.append(error)
307

    
308
            # check duplicated connection
309
            if len(connectedUid) is not len(set(connectedUid)):
310
                error = SymbolSvgItem.createItem('Error', None, dataPath)
311
                error.setPosition([self.sceneBoundingRect().center().x(), self.sceneBoundingRect().center().y()])
312
                error.parent = self
313
                error.msg = self.tr('duplicated connection')
314
                error.setToolTip(error.msg)
315
                error.area = self.area
316
                error.name = 'Error'
317
                errors.append(error)
318

    
319
            # check line type
320
            if self.conn_type:
321
                for index in range(len(self.conn_type)):
322
                    item = self.connectors[index].connectedItem
323
                    if item and type(item) is QEngineeringLineItem:
324
                        if ((self.conn_type[index] == 'Primary' or self.conn_type[index] == 'Secondary') and not item.is_piping()) or \
325
                                (not (self.conn_type[index] == 'Primary' or self.conn_type[index] == 'Secondary') and item.is_piping(True)):
326
                            error = SymbolSvgItem.createItem('Error', None, dataPath)
327
                            error.setPosition(list(self.connectors[index].center()))
328
                            error.parent = self
329
                            error.msg = self.tr('line type error')
330
                            error.setToolTip(error.msg)
331
                            error.area = self.area
332
                            error.name = 'Error'
333
                            errors.append(error)
334
                            
335
        except Exception as ex:
336
            from App import App
337
            message = 'error occurred({}) in {}:{}'.format(ex, sys.exc_info()[-1].tb_frame.f_code.co_filename,
338
                                                          sys.exc_info()[-1].tb_lineno)
339
            App.mainWnd().addMessage.emit(MessageType.Error, message)
340

    
341
        return errors
342

    
343
    def includes(self, item, margin=0):
344
        """return True if symbol contains given point else return False"""
345
        if hasattr(item, 'sceneBoundingRect'):
346
            rect = item.sceneBoundingRect()
347
            topLeft = QPoint(rect.x(), rect.y())
348
            bottomRight = QPoint(rect.x() + rect.width(), rect.y() + rect.height())
349
            margin_rect = QRect(self.sceneBoundingRect().x() - margin, self.sceneBoundingRect().y() - margin, \
350
                            self.sceneBoundingRect().width() + 2 * margin, self.sceneBoundingRect().height() + 2 * margin)
351
            if margin_rect.contains(topLeft) and margin_rect.contains(bottomRight):
352
                return True
353
            else:
354
                return False
355

    
356
        else:
357
            pt = item
358
            rect = self.sceneBoundingRect()
359
            allowed_error = 0.1
360

    
361
            if abs(rect.x() - 0) <= allowed_error and abs(rect.y() - 0) <= allowed_error:
362
                # when first recognition step, symbols are not in scene(not yet added) therefore cannot use scenebounding rect
363
                minX = self.loc[0] - margin
364
                minY = self.loc[1] - margin
365
                maxX = minX + self.size[0] + margin
366
                maxY = minY + self.size[1] + margin
367
            else:
368
                minX = rect.x() - margin
369
                minY = rect.y() - margin
370
                maxX = minX + rect.width() + margin
371
                maxY = minY + rect.height() + margin
372

    
373
            return True if (pt[0] >= minX and pt[0] <= maxX and pt[1] >= minY and pt[1] <= maxY) else False
374

    
375
    '''
376
    def associations(self):
377
        """ return associated instance """
378
        # move to abstractitem
379
        import uuid
380

381
        res = []
382
        for key in self._associations.keys():
383
            index = 0
384
            for assoc in self._associations[key]:
385
                # find owner with uid
386
                if assoc and type(assoc) is uuid.UUID:
387
                    matches = [x for x in self.scene().items() if hasattr(x, 'uid') and str(x.uid) == str(assoc)]
388
                    if matches:
389
                        res.append(matches[0]) # TODO: need to update association with instance
390
                        self._associations[key][index] = matches[0]
391
                    index += 1
392
                # up to here
393
                elif assoc:
394
                    res.append(assoc)
395

396
        for key in self.attrs.keys():
397
            if type(key.AssocItem) is uuid.UUID:
398
                for assoc in res:
399
                    if str(key.AssocItem) == str(assoc.uid):
400
                        key.AssocItem = assoc
401

402
        return res
403

404
    def texts(self):
405
        """ return text type of associations """
406
        from EngineeringTextItem import QEngineeringTextItem
407

408
        res = []
409
        for text in [x for x in self.associations() if issubclass(type(x), QEngineeringTextItem)]:
410
            consumed = False
411
            for key in list(self.attrs.keys()):
412
                if key.AssocItem and key.AssocItem is text:
413
                    consumed = True
414
            if not consumed:
415
                res.append(text)
416

417
        return res
418

419
    def symbols(self):
420
        """ return symbol type of associations """
421
        res = []
422
        for symbol in [x for x in self.associations() if issubclass(type(x), SymbolSvgItem)]:
423
            consumed = False
424
            for key in list(self.attrs.keys()):
425
                if key.AssocItem and key.AssocItem is symbol:
426
                    consumed = True
427
            if not consumed:
428
                res.append(symbol)
429

430
        return res
431
    '''
432

    
433
    def itemChange(self, change, value):
434
        """ call signals when item's position or rotation is changed """
435
        if not self.scene(): return super().itemChange(change, value)
436

    
437
        if change == QGraphicsItem.ItemPositionHasChanged or change == QGraphicsItem.ItemRotationHasChanged:
438
            from EngineeringLineItem import QEngineeringLineItem
439
            for connector in self.connectors:
440
                if connector.connectedItem is not None and type(connector.connectedItem) is QEngineeringLineItem:
441
                    line = connector.connectedItem
442
                    line.update_shape(self, connector.center())
443
                    line.update_arrow()
444

    
445
            self.size[0], self.size[1] = round(self.sceneBoundingRect().width()), \
446
                                         round(self.sceneBoundingRect().height())
447

    
448
            scene_origin = self.mapToScene(self.transformOriginPoint())
449
            self.origin = [round(scene_origin.x(), 1), round(scene_origin.y(), 1)]
450

    
451
            if hasattr(self.scene(), 'contents_changed'):
452
                self.scene().contents_changed.emit()
453

    
454
            return value
455

    
456
        return super().itemChange(change, value)
457

    
458
    def toSql_Components(self):
459
        """ convert Components data to sql query """
460
        from AppDocData import AppDocData
461

    
462
        cols = ['UID', 'Drawings_UID', 'Symbol_UID', 'X', 'Y', 'Width', 'Height', 'Rotation', 'Area', 'Owner',
463
                'Connected', '[Supplied By]', \
464
                'SpecialItemTypes_UID', 'OriginIndex', '[From]', '[To]', '[Freeze]', '[Connected Item]', '[Flip]',
465
                'SceneOriginPoint']
466
        values = ['?', '?', '?', '?', '?', '?', '?', '?', '?', '?', '?', '?', '?', '?', '?', '?', '?', '?', '?', '?']
467
        origin = self.mapToScene(self.transformOriginPoint())
468
        param = [(str(self.uid), str(AppDocData.instance().activeDrawing.UID), self.dbUid, self.loc[0], self.loc[1],
469
                  self.size[0], self.size[1], math.radians(self.rotation()),
470
                  self.area, str(self.owner) if self.owner else None, \
471
                  str(self.conns[0]) if self.conns else None, \
472
                  self.prop('Supplied By'), \
473
                  str(self.special_item_type) if self.special_item_type else None, \
474
                  self.currentPointModeIndex, \
475
                  None, None, self.prop('Freeze') if self.prop('Freeze') else 0,
476
                  str(self.prop('Connected Item')) if self.prop('Connected Item') else None,
477
                  self.flip, '{},{}'.format(origin.x(), origin.y()))]
478
        sql = 'insert into Components({}) values({})'.format(','.join(cols), ','.join(values))
479

    
480
        return (sql, tuple(param))
481

    
482
    def toSql_return_separately(self):
483
        """ convert valve data to sql query """
484
        import uuid
485

    
486
        res = []
487
        resLater = []
488

    
489
        res.append(self.toSql_Components())
490

    
491
        _attrs = self.getAttributes()
492
        if _attrs:
493
            cols = ['UID', 'Components_UID', 'SymbolAttribute_UID', 'Value', 'Association_UID', 'Freeze']
494
            values = ['?', '?', '?', '?', '?', '?']
495
            params = []
496
            for key in _attrs.keys():
497
                if key.AttributeType != 'Spec':
498
                    params.append((str(uuid.uuid4()), str(self.uid), str(key.UID), str(_attrs[key]), str(key.AssocItem),
499
                                   str(key.Freeze)))
500
                elif key.AttributeType == 'Spec':
501
                    if type(_attrs[key]) is not list: continue
502
                    params.append((str(uuid.uuid4()), str(self.uid), str(key.UID), (str(_attrs[key][0]) + ',' + str(_attrs[key][1])), str(key.AssocItem),
503
                                   str(key.Freeze)))
504
            sql = 'insert into Attributes({}) values({})'.format(','.join(cols), ','.join(values))
505
            res.append((sql, tuple(params)))
506

    
507
        if self.associations():
508
            cols = ['UID', '[Type]', 'Components_UID', 'Association']
509
            values = ['?', '?', '?', '?']
510
            params = []
511
            for assoc in self.associations():
512
                params.append(
513
                    (str(uuid.uuid4()), QEngineeringAbstractItem.assoc_type(assoc), str(self.uid), str(assoc.uid)))
514
            sql = 'insert into Associations({}) values({})'.format(','.join(cols), ','.join(values))
515
            resLater.append((sql, tuple(params)))
516

    
517
        # save connectors to database
518
        if self.connectors:
519
            cols = ['Components_UID', '[Index]', 'X', 'Y', 'Connected', 'Connected_At']
520
            values = ['?', '?', '?', '?', '?', '?']
521
            params = []
522
            index = 1
523
            for connector in self.connectors:
524
                params.append( \
525
                    (  # str(connector.uid),
526
                        str(self.uid), index, connector.center()[0], connector.center()[1], \
527
                        str(connector.connectedItem.uid) if connector.connectedItem else None, \
528
                        str(connector._connected_at)) \
529
                    )
530
                index += 1
531
            sql = 'insert into Points({}) values({})'.format(','.join(cols), ','.join(values))
532
            resLater.append((sql, tuple(params)))
533
        # up to here
534

    
535
        return res, resLater
536

    
537
    def buildItem(self, name, _type, angle: float, loc, size, origin, connPts, parentSymbol, childSymbol, hasInstrumentLabel,
538
                  dbUid=None):
539
        """
540
        build symbol item
541
        :param name:
542
        :param _type:
543
        :param angle:
544
        :param loc:
545
        :param size:
546
        :param origin:
547
        :param connPts:
548
        :param parentSymbol:
549
        :param childSymbol:
550
        :param hasInstrumentLabel:
551
        :param dbUid:
552
        :return:
553
        """
554
        from SpecialItemTypesDialog import SpecialItemTypes
555

    
556
        try:
557
            app_doc_data = AppDocData.instance()
558
            self.name = name
559
            self.type = _type
560
            self.angle = angle
561
            self.loc = loc
562
            self.size = size if size else [0, 0]
563
            self.origin = origin
564
            if dbUid is None:
565
                symbolInfo = app_doc_data.getSymbolByQuery('name', name)
566
            else:
567
                symbolInfo = app_doc_data.getSymbolByQuery('UID', dbUid)
568
            self.dbUid = symbolInfo.uid
569
            self.iType = symbolInfo.iType
570
            self.text_area = symbolInfo.text_area
571
            self._class = symbolInfo.baseSymbol
572
            originalPoint = symbolInfo.getOriginalPoint().split(',')
573
            self.symbolOrigin = [float(originalPoint[0]), float(originalPoint[1])]
574

    
575
            # setting connectors
576
            connectionPoints = symbolInfo.getConnectionPoint().split('/')
577
            for index in range(len(connectionPoints)):
578
                if connectionPoints[index] == '':
579
                    break
580
                tokens = connectionPoints[index].split(',')
581

    
582
                direction = 'AUTO'
583
                symbol_idx = '0'
584
                if len(tokens) == 2:
585
                    x = float(tokens[0])
586
                    y = float(tokens[1])
587
                elif len(tokens) >= 3:
588
                    direction = connPts[index][0] if index < len(connPts) else tokens[0]
589
                    x = float(tokens[1])
590
                    y = float(tokens[2])
591
                if len(tokens) >= 4:
592
                    symbol_idx = tokens[3]
593
                if len(tokens) >= 6:
594
                    if tokens[4] == 'In':
595
                        self.in_out_connector[0].append(index)
596
                    elif tokens[4] == 'Out':
597
                        self.in_out_connector[1].append(index)
598
                    if tokens[5] == 'O':
599
                        self.break_connector.append(index)
600
                if len(tokens) >= 7:
601
                    self.conn_type.append(tokens[6])
602

    
603
                self.setConnector(index + 1)
604
                self.connectors[index].direction = direction
605
                self.connectors[index].symbol_idx = symbol_idx
606
                self.connectors[index].setPos((x, y))
607
                self.connectors[index].connectPoint = (x, y)
608
                # recognized_pt is only valid right after symbol is recognized
609
                if index < len(connPts):
610
                    self.connectors[index].recognized_pt = (connPts[index][0], connPts[index][1]) if \
611
                        len(connPts[index]) == 2 else (connPts[index][1], connPts[index][2]) if \
612
                        len(connPts[index]) == 3 else (connPts[index][1], connPts[index][2]) if \
613
                        len(connPts[index]) == 4 else None
614
                # up to here
615
            self.parentSymbol = parentSymbol
616
            self.childSymbol = childSymbol
617
            self.hasInstrumentLabel = hasInstrumentLabel
618
            self.currentPointModeIndex = 0
619
            self.special_item_type = SpecialItemTypes.instance().find_match_exactly(self)
620

    
621
            tooltip = f"<b>{str(self.uid)}</b><br>{self.type}={self.name}"
622
            if self.hit_ratio:
623
                tooltip += f"<br><li>recognition ratio={self.hit_ratio}"
624
            self.setToolTip(tooltip)
625

    
626
            return True
627
        except Exception as ex:
628
            from App import App
629

    
630
            message = 'error occurred({}) in {}:{}'.format(ex, sys.exc_info()[-1].tb_frame.f_code.co_filename,
631
                                                          sys.exc_info()[-1].tb_lineno)
632
            App.mainWnd().addMessage.emit(MessageType.Error, message)
633

    
634
            return False
635

    
636
    '''
637
        @brief  return bounding box of symbol
638
        @author humkyung
639
        @date   2018.04.08
640
    '''
641

    
642
    def rect(self):
643
        return self.sceneBoundingRect()
644

    
645
    def is_connectable(self, item, toler=10):
646
        """return true if line is able to connect symbol"""
647

    
648
        for connector in self.connectors:
649
            for iConnector in item.connectors:
650
                dx = connector.center()[0] - iConnector.center()[0]
651
                dy = connector.center()[1] - iConnector.center()[1]
652
                if math.sqrt(dx * dx + dy * dy) < toler:
653
                    return True
654

    
655
        return False
656

    
657
    '''
658
        @author     humkyung
659
        @date       2018.07.03
660
    '''
661

    
662
    def is_connected(self, item, at=QEngineeringAbstractItem.CONNECTED_AT_PT):
663
        """ check if given item is connected to self """
664

    
665
        _connectors = [connector for connector in self.connectors if
666
                       (connector.connectedItem == item and (connector._connected_at == at if at else True))]
667
        return len(_connectors) > 0
668

    
669
    def next_connected(self, lhs, rhs):
670
        """ check given two item's are next connected(ex: 0-1, 2-3) """
671

    
672
        lhs_matches = [at for at in range(len(self.connectors)) if self.connectors[at].connectedItem == lhs]
673
        rhs_matches = [at for at in range(len(self.connectors)) if self.connectors[at].connectedItem == rhs]
674
        if lhs_matches and rhs_matches and lhs is not rhs:
675
            return (lhs_matches[0] in [0, 1] and rhs_matches[0] in [0, 1]) or (
676
                    lhs_matches[0] in [2, 3] and rhs_matches[0] in [2, 3])
677

    
678
        return False
679

    
680
    def canBeSecondary(self, line):
681
        """ check given line is not connected(ex: 0-1, 2-3) """
682
        preItem = None
683

    
684
        item = [item.connectedItem for item in self.connectors if
685
                item.connectedItem is not None and item.connectedItem.owner is not None]
686
        if item:
687
            preItem = item[0]
688

    
689
        if preItem is not None and not self.next_connected(line, preItem):
690
            return True
691
        else:
692
            return False
693

    
694
    '''
695
        @brief      connect line and symbol is able to be connected and return line
696
        @author     humkyung
697
        @date       2018.04.16
698
        @history    humkyung 2018.05.08 check if symbol is possible to be connected
699
                    Jeongwoo 2018.05.15 Connect each symbol and line
700
    '''
701

    
702
    def connect_if_possible(self, obj, toler=10):
703
        """ this method not update item's position """
704

    
705
        from shapely.geometry import Point
706
        from EngineeringLineItem import QEngineeringLineItem
707

    
708
        res = []
709
        try:
710
            if type(obj) is QEngineeringLineItem:
711
                startPt = obj.start_point()
712
                endPt = obj.end_point()
713
                for i in range(len(self.connectors)):
714
                    if (Point(startPt[0], startPt[1]).distance(Point(self.connectors[i].center()[0],
715
                                                                     self.connectors[i].center()[1])) < toler):
716
                        if self.connectors[i].connectedItem is None and obj.connectors[0].connectedItem is None:
717
                            self.connectors[i].connect(obj)
718
                            obj.connectors[0].connect(self)
719
                            # line, start, end
720
                            res.append(obj)
721
                            res.append(self.connectors[i].center())
722
                            res.append(endPt)
723
                            continue
724
                    if (Point(endPt[0], endPt[1]).distance(Point(self.connectors[i].center()[0],
725
                                                                 self.connectors[i].center()[1])) < toler):
726
                        if self.connectors[i].connectedItem is None and obj.connectors[1].connectedItem is None:
727
                            self.connectors[i].connect(obj)
728
                            obj.connectors[1].connect(self)
729
                            # line, start, end
730
                            res.append(obj)
731
                            res.append(startPt)
732
                            res.append(self.connectors[i].center())
733
            elif issubclass(type(obj), SymbolSvgItem):
734
                selected = None
735
                for i in range(len(self.connectors)):
736
                    if i > 3: break
737
                    _pt1 = Point(self.connectors[i].center()[0], self.connectors[i].center()[1])
738
                    for j in range(len(obj.connectors)):
739
                        if j > 3: break
740
                        _pt2 = Point(obj.connectors[j].center()[0], obj.connectors[j].center()[1])
741
                        length = _pt1.distance(_pt2)
742
                        if (length < toler) and (selected is None or length < selected[0]):
743
                            selected = [length, self.connectors[i], obj.connectors[j]]
744

    
745
                if selected and selected[1].connectedItem is None and selected[2].connectedItem is None:
746
                    selected[1].connect(selected[2].parent)
747
                    selected[2].connect(selected[1].parent)
748
                    res.append(obj)
749
        except Exception as ex:
750
            from App import App
751
            message = 'error occurred({}) in {}:{}'.format(ex, sys.exc_info()[-1].tb_frame.f_code.co_filename,
752
                                                          sys.exc_info()[-1].tb_lineno)
753
            App.mainWnd().addMessage.emit(MessageType.Error, message)
754

    
755
        return res
756

    
757
    '''
758
        @brief      disconnect connector item
759
        @author     kyouho
760
        @date       2018.08.30
761
    '''
762

    
763
    def disconnectedItemAtConnector(self, connector):
764
        for conn in self.connectors:
765
            if conn.isOverlapConnector(connector):
766
                conn.connectedItem = None
767

    
768
    '''
769
        @brief  get connection point close to given point in tolerance
770
        @author humkyung
771
        @dat
772
    '''
773

    
774
    def getConnectionPointCloseTo(self, pt, toler=10):
775
        import math
776

    
777
        for connector in self.connectors:
778
            dx = connector.center()[0] - pt[0]
779
            dy = connector.center()[1] - pt[1]
780

    
781
            if math.sqrt(dx * dx + dy * dy) < toler:
782
                return connPt
783

    
784
        return None
785

    
786
    '''
787
        @brief  return center of symbol
788
        @author humkyung
789
        @date   2018.04.08
790
    '''
791

    
792
    def center(self):
793
        return self.sceneBoundingRect().center()
794

    
795
    '''
796
        @brief      highlight connector and attribute
797
        @authro     humkyung
798
        @date       2018.05.02
799
    '''
800

    
801
    def hoverEnterEvent(self, event):
802
        self.highlight(True)
803

    
804
    '''
805
        @brief      unhighlight connector and attribute
806
        @author     humkyung
807
        @date       2018.05.02
808
        @history    kyouho 2018.07.18 edit ArrowCursor
809
    '''
810

    
811
    def hoverLeaveEvent(self, event):
812
        self.highlight(False)
813

    
814
    def highlight(self, flag):
815
        """ highlight/unhighlight the symbol """
816

    
817
        try:
818
            self.hover = flag
819
            self.setZValue(QEngineeringAbstractItem.HOVER_ZVALUE) if flag else self.setZValue(SymbolSvgItem.ZVALUE)
820
            self.update()
821

    
822
            for assoc in self.associations():
823
                assoc.highlight(flag)
824

    
825
            for connector in self.connectors:
826
                connector.highlight(flag)
827
        except Exception as ex:
828
            from App import App
829
            message = 'error occurred({}) in {}:{}'.format(ex, sys.exc_info()[-1].tb_frame.f_code.co_filename,
830
                                                          sys.exc_info()[-1].tb_lineno)
831
            App.mainWnd().addMessage.emit(MessageType.Error, message)
832

    
833
    '''
834
        @brief      set highlight
835
        @author     kyouho
836
        @date       2018.08.27
837
    '''
838

    
839
    def setHightlight(self):
840
        self.setColor('url(#hover)')
841
        self.update()
842

    
843
    '''
844
        @brief      unset highlight
845
        @author     kyouho
846
        @date       2018.08.27
847
    '''
848

    
849
    def unsetHightlight(self):
850
        self.setColor('url(#normal)')
851
        self.update()
852

    
853
    '''
854
        @brief  change cursor to CrossCursor if mouse point is close to connection point
855
        @author humkyung
856
        @date   2018.04.28
857
    '''
858

    
859
    def hoverMoveEvent(self, event):
860
        pass
861

    
862
    '''
863
        @brief      Mouse Press Event
864
        @author     Jeongwoo
865
        @date       18.04.11
866
        @history    kyouho 2018.07.18 add isClick logic
867
    '''
868

    
869
    def mousePressEvent(self, event):
870
        if event.buttons() == Qt.LeftButton:
871
            self.clicked.emit(self)
872

    
873
    def mouseReleaseEvent(self, event):
874
        """Mouse Release Event"""
875
        if hasattr(self, '_rotating') and event.button() == Qt.RightButton:
876
            from RotateCommand import RotateCommand
877

    
878
            self.angle = -self._angle if -math.pi < self._angle < 0 else 2 * math.pi - self._angle
879

    
880
            self.scene().undo_stack.push(RotateCommand(self.scene(), [self,], self._rotating))
881

    
882
            self.ungrabMouse()
883
            del self._rotating
884

    
885
        super().mouseReleaseEvent(event)
886

    
887
    def mouseMoveEvent(self, event):
888
        """ rotate symbol around current mouse point """
889
        if hasattr(self, '_rotating'):
890
            # get origin point of symbol
891
            origin = self.mapToScene(self.transformOriginPoint())
892
            # up to here
893

    
894
            dx, dy = event.scenePos().x() - origin.x(), event.scenePos().y() - origin.y()
895
            length = math.sqrt(dx * dx + dy * dy)
896

    
897
            self._angle = 0
898
            if length > 0:
899
                self._angle = math.acos(dx / length)
900
                cross = int(np.cross([1, 0], [dx, dy]))
901
                self._angle = -self._angle if cross < 0 else self._angle
902
                self._angle = math.pi*2 + self._angle if self._angle < 0 else self._angle
903

    
904
                modifiers = QApplication.keyboardModifiers()
905
                if modifiers == Qt.ShiftModifier:
906
                    step = math.radians(30)
907
                    quotient = int(self._angle / step)
908
                    angle = quotient*step
909
                    self._angle = angle if (self._angle - angle) < step*0.5 else (quotient + 1)*step
910

    
911
                self.rotate(self._angle)
912

    
913
        modifiers = QApplication.keyboardModifiers()
914
        if modifiers == Qt.ShiftModifier:
915
            super(SymbolSvgItem, self).mouseMoveEvent(event)
916
    
917
    def removeSelfAttr(self, attributeName):
918
        target = None
919
        for attr in self.attrs:
920
            if attr.Attribute == attributeName:
921
                target = attr
922
                break
923

    
924
        if target:
925
            del self.attrs[attr]
926

    
927
    '''
928
        @brief      Find TextItem contain Point
929
        @author     kyouho
930
        @date       18.07.17
931
    '''
932

    
933
    def findTextItemInPoint(self, point):
934
        from EngineeringTextItem import QEngineeringTextItem
935

    
936
        scene = self.scene()
937

    
938
        for item in scene.items():
939
            if type(item) is QEngineeringTextItem:
940
                if self.isOverlapItemAndPoint(item, point):
941
                    return (True, item)
942

    
943
        return (False,)
944

    
945
    '''
946
        @brief      Check Overlap
947
        @author     kyouho
948
        @date       18.07.17
949
    '''
950

    
951
    def isOverlapItemAndPoint(self, item, point):
952
        x = point.x()
953
        y = point.y()
954
        loc = item.loc
955
        size = item.size
956

    
957
        if loc[0] <= x and loc[0] + size[0] >= x and loc[1] <= y and loc[1] + size[1] >= y:
958
            return True
959
        else:
960
            return False
961

    
962
    '''
963
        @brief  remove item when user press delete key
964
        @author humkyung
965
        @date   2018.04.23
966
        @history    2018.05.17  Jeongwoo    Add if-statement and move 'break'
967
                    2018.05.25  Jeongwoo    Seperate delete item method
968
    '''
969
    def keyPressEvent(self, event):
970
        from EngineeringErrorItem import QEngineeringErrorItem
971
        from RotateSymbolDialog import QRotateSymbolDialog
972
        from RotateCommand import RotateCommand
973

    
974
        if not self.isSelected():
975
            return
976
        elif event.key() == Qt.Key_B:
977
            self.bind_close_items()
978
        elif event.key() == Qt.Key_O and type(self) is not QEngineeringErrorItem:
979
            pass
980
        elif event.key() == Qt.Key_C and type(self) is not QEngineeringErrorItem:
981
            self.changeConnPoint()
982
        elif event.key() == Qt.Key_Return:
983
            dlg = QRotateSymbolDialog(None, self.rotation(), self.origin, self.zValue())
984
            if QDialog.Accepted == dlg.showDialog():
985
                _angle = self.rotation()
986
                self.rotate(math.radians(dlg.angle))
987
                self.scene().undo_stack.push(RotateCommand(self.scene(), [self, ], _angle))
988
                self.angle = dlg.angle
989
        elif event.key() == Qt.Key_Escape:
990
            if hasattr(self, '_rotating'):
991
                self.ungrabMouse()
992

    
993
                self.rotate(math.radians(self._rotating))
994
                del self._rotating
995
        elif event.key() == Qt.Key_Up:  # translate up/down/left/right symbol
996
            modifiers = QApplication.keyboardModifiers()
997
            delta = 10 if modifiers == Qt.ControlModifier else 1
998

    
999
            self.loc[1] = self.loc[1] - delta
1000
            #self.origin[1] = self.origin[1] - delta
1001
            self.moveBy(0, -delta)
1002
        elif event.key() == Qt.Key_Down:
1003
            modifiers = QApplication.keyboardModifiers()
1004
            delta = 10 if modifiers == Qt.ControlModifier else 1
1005

    
1006
            self.loc[1] = self.loc[1] + delta
1007
            #self.origin[1] = self.origin[1] + delta
1008
            self.moveBy(0, delta)
1009
        elif event.key() == Qt.Key_Left:
1010
            modifiers = QApplication.keyboardModifiers()
1011
            delta = 10 if modifiers == Qt.ControlModifier else 1
1012

    
1013
            self.loc[0] = self.loc[0] - delta
1014
            #self.origin[0] = self.origin[0] - delta
1015
            self.moveBy(-delta, 0)
1016
        elif event.key() == Qt.Key_Right:
1017
            modifiers = QApplication.keyboardModifiers()
1018
            delta = 10 if modifiers == Qt.ControlModifier else 1
1019

    
1020
            self.loc[0] = self.loc[0] + delta
1021
            #self.origin[0] = self.origin[0] + delta
1022
            self.moveBy(delta, 0)
1023
        elif event.key() == Qt.Key_I or event.key() == Qt.Key_X or event.key() == Qt.Key_J:
1024
            from App import App 
1025
            App.mainWnd().keyPressEvent(event)
1026

    
1027
    def bind_close_items(self):
1028
        """ connect close item by pressing B """
1029
        from EngineeringLineItem import QEngineeringLineItem
1030
        from EngineeringNozzleItem import QEngineeringNozzleItem
1031
        from EngineeringEquipmentItem import QEngineeringEquipmentItem
1032
        from shapely.geometry import Point
1033

    
1034
        scene = self.scene()
1035
        if scene:
1036
            configs = AppDocData.instance().getConfigs('Line Detector', 'Length to connect line')
1037
            toler = int(configs[0].value) if configs else 20
1038

    
1039
            items = [item for item in scene.items() if hasattr(item, 'connectors') and item is not self]
1040

    
1041
            for s_connector in self.connectors:
1042
                if s_connector.connectedItem:
1043
                    continue
1044

    
1045
                _pt1 = Point(s_connector.center()[0], s_connector.center()[1])
1046
                dist = sys.maxsize
1047
                selected = None
1048

    
1049
                for item in items:
1050
                    for i_connector in item.connectors:
1051
                        if i_connector.connectedItem:
1052
                            continue
1053

    
1054
                        _pt2 = Point(i_connector.center()[0], i_connector.center()[1])
1055
                        length = _pt1.distance(_pt2)
1056
                        if length < toler and length < dist:
1057
                            selected = item
1058

    
1059
                if selected:
1060
                    res = self.connect_if_possible(selected, toler)
1061
                    if res and type(selected) is QEngineeringLineItem:
1062
                        selected.set_line([res[1], res[2]])
1063

    
1064
            if type(self) is QEngineeringNozzleItem and self.connectors and not self.connectors[0].connectedItem:
1065
                items = [item for item in scene.items() if issubclass(type(item), QEngineeringEquipmentItem)]
1066

    
1067
                for item in items:
1068
                    if item.includes(self.connectors[0], margin=100):
1069
                        self.connectors[0].connect(item)
1070
                        break
1071

    
1072
            if type(self) is QEngineeringEquipmentItem:
1073
                for item in items:
1074
                    if type(item) is QEngineeringLineItem and item.is_piping(True):
1075
                        for i_connector in item.connectors:
1076
                            if i_connector.connectedItem:
1077
                                continue
1078
                            
1079
                            if self.includes(i_connector, margin=100):
1080
                                i_connector.connect(self)
1081

    
1082
                    elif issubclass(type(item), SymbolSvgItem) and item.has_connection:
1083
                        for index in range(len(item.connectors)):
1084
                            if item.connectors[index].connectedItem or (item.conn_type[index] != 'Secondary' and item.conn_type[index] != 'Primary'):
1085
                                continue
1086

    
1087
                            if self.includes(item.connectors[index], margin=100):
1088
                                item.connectors[index].connect(self)
1089

    
1090
    '''
1091
        @brief      connect attribute
1092
        @author     humkyung
1093
        @date       2018.05.02
1094
        @history    humkyung 2018.05.09 append only nearest size attribute
1095
    '''
1096
    def connectAttribute(self, attributes, clear=True):
1097
        import math
1098
        from EngineeringTextItem import QEngineeringTextItem
1099
        from QEngineeringSizeTextItem import QEngineeringSizeTextItem
1100
        from EngineeringInstrumentItem import QEngineeringInstrumentItem
1101
        from EngineeringValveOperCodeTextItem import QEngineeringValveOperCodeTextItem
1102
        from EngineeringLineItem import QEngineeringLineItem
1103

    
1104
        try:
1105
            if clear:
1106
                if not self.clear_attr_and_assoc_item():
1107
                    return
1108

    
1109
            configs = AppDocData.instance().getConfigs('Range', 'Detection Ratio')
1110
            ratio = float(configs[0].value) if 1 == len(configs) else 1.5
1111

    
1112
            rect = self.sceneBoundingRect()
1113
            dist = max(self.sceneBoundingRect().height(), self.sceneBoundingRect().width()) * ratio
1114
            center = self.sceneBoundingRect().center()
1115

    
1116
            minDist = None
1117
            selected = None
1118

    
1119
            contain_texts = []
1120
            for attr in attributes:
1121
                # size text and operation code text will find owner themselves in findowner method
1122
                if False:  # type(attr) is QEngineeringSizeTextItem or type(attr) is QEngineeringValveOperCodeTextItem:
1123
                    dx = attr.center().x() - center.x()
1124
                    dy = attr.center().y() - center.y()
1125
                    length = math.sqrt(dx * dx + dy * dy)
1126
                    if (length < dist) and (minDist is None or length < minDist):
1127
                        minDist = length
1128
                        selected = attr
1129
                elif False:#type(attr) is QEngineeringInstrumentItem:
1130
                    if not attr.is_connected():
1131
                        dx = attr.center().x() - center.x()
1132
                        dy = attr.center().y() - center.y()
1133
                        if math.sqrt(dx * dx + dy * dy) < dist:
1134
                            if self.add_assoc_item(attr):
1135
                                attr.owner = self
1136
                elif issubclass(type(attr), QEngineeringTextItem):
1137
                    if rect.contains(attr.center()):
1138
                        dx = attr.center().x() - center.x()
1139
                        dy = attr.center().y() - center.y()
1140
                        length = math.sqrt(dx * dx + dy * dy)
1141
                        contain_texts.append([length, attr])
1142
                elif type(attr) is QEngineeringLineItem:
1143
                    length = attr.distanceTo([center.x(), center.y()])
1144
                    if (length < dist) and (minDist is None or length < minDist):
1145
                        minDist = length
1146
                        selected = attr
1147

    
1148
            if contain_texts:
1149
                num = len([_attr for _attr in self.getAttributes(findOwner=True) if _attr.AttributeType == 'Text Item'])
1150
                contain_texts = sorted(contain_texts, key=lambda param: param[0])
1151
                index = 0
1152
                for _, attr in contain_texts:
1153
                    if index >= num:
1154
                        break
1155
                    if self.add_assoc_item(attr):
1156
                        attr.owner = self  # set owner of text
1157
                    index += 1
1158

    
1159
            if selected is not None:
1160
                self.add_assoc_item(selected)
1161

    
1162
        except Exception as ex:
1163
            from App import App
1164
            message = 'error occurred({}) in {}:{}'.format(ex, sys.exc_info()[-1].tb_frame.f_code.co_filename,
1165
                                                          sys.exc_info()[-1].tb_lineno)
1166
            App.mainWnd().addMessage.emit(MessageType.Error, message)
1167

    
1168
    '''
1169
        @brief      start rotating
1170
        @author     euisung
1171
        @date       2019.04.16
1172
    '''
1173

    
1174
    def mouseDoubleClickEvent(self, event):
1175
        if not hasattr(self, '_rotating'):
1176
            self._rotating = self.rotation()
1177
            self.grabMouse()
1178

    
1179
    def toXml(self):
1180
        """
1181
        generate xml code
1182
        :return:
1183
        """
1184
        import uuid
1185
        from xml.etree.ElementTree import Element, SubElement, dump, ElementTree
1186
        from EngineeringAbstractItem import QEngineeringAbstractItem
1187
        from EngineeringTextItem import QEngineeringTextItem
1188
        from EngineeringSpecBreakItem import QEngineeringSpecBreakItem
1189
        from SymbolAttr import SymbolAttr
1190

    
1191
        try:
1192
            node = Element('SYMBOL')
1193
            node.attrib['Converted'] = str(self.converted)
1194
            uidNode = Element('UID')
1195
            uidNode.text = str(self.uid)
1196
            node.append(uidNode)
1197

    
1198
            dbUidNode = Element('DBUID')
1199
            dbUidNode.text = str(self.dbUid)
1200
            node.append(dbUidNode)
1201

    
1202
            nameNode = Element('NAME')
1203
            nameNode.text = self.name
1204
            node.append(nameNode)
1205

    
1206
            attributeValueNode = Element('ASSOCIATIONS')
1207
            for key, value in self._associations.items():
1208
                for assoc in value:
1209
                    assoc_node = Element('ASSOCIATION')
1210
                    assoc_node.attrib['TYPE'] = str(key)
1211
                    assoc_node.text = str(assoc)
1212
                    attributeValueNode.append(assoc_node)
1213
            node.append(attributeValueNode)
1214

    
1215
            typeNode = Element('TYPE')
1216
            typeNode.text = self.type
1217
            node.append(typeNode)
1218

    
1219
            # write owner's uid to xml
1220
            ownerNode = Element('OWNER')
1221
            if self.owner is not None:
1222
                ownerNode.text = str(self.owner)
1223
            else:
1224
                ownerNode.text = 'None'
1225
            node.append(ownerNode)
1226
            # up to here
1227

    
1228
            originNode = Element('ORIGINALPOINT')
1229
            if not self.transformOriginPoint().isNull():
1230
                origin = self.mapToScene(self.transformOriginPoint())
1231
                originNode.text = f"{origin.x()},{origin.y()}"
1232
            else:
1233
                origin = self.origin
1234
                originNode.text = f"{origin[0]},{origin[1]}"
1235
            node.append(originNode)
1236

    
1237
            connectorsNode = Element('CONNECTORS')
1238
            for connector in self.connectors:
1239
                connectorsNode.append(connector.toXml())
1240
            node.append(connectorsNode)
1241

    
1242
            connectionNode = Element('CONNECTIONPOINT')
1243
            connection_point = []
1244
            if self.connectors is not None:
1245
                for connector in self.connectors:
1246
                    connection_point.append(repr(connector))
1247
            connectionNode.text = '/'.join(connection_point)
1248
            node.append(connectionNode)
1249

    
1250
            locNode = Element('LOCATION')
1251
            locNode.text = f'{self.loc[0]},{self.loc[1]}'
1252
            '''
1253
            # calculate symbol's left-top corner
1254
            transform = QTransform()
1255
            transform.translate(self.scenePos().x(), self.scenePos().y())
1256
            transform.rotateRadians(-self.angle)
1257
            loc = transform.map(QPointF(self.symbolOrigin[0], self.symbolOrigin[1]))
1258
            # up to here
1259
            locNode.text = '{},{}'.format(loc.x() - self.symbolOrigin[0], loc.y() - self.symbolOrigin[1])
1260
            '''
1261
            node.append(locNode)
1262

    
1263
            sizeNode = Element('SIZE')
1264
            sizeNode.text = f'{self.size[0]},{self.size[1]}'
1265
            node.append(sizeNode)
1266

    
1267
            angleNode = Element('ANGLE')
1268
            angleNode.text = str(math.radians(self.rotation())) if self.scene() else str(self.angle)
1269
            node.append(angleNode)
1270

    
1271
            parentSymbolNode = Element('PARENT')
1272
            parentSymbolNode.text = str(self.parentSymbol)
1273
            node.append(parentSymbolNode)
1274

    
1275
            childSymbolNode = Element('CHILD')
1276
            childSymbolNode.text = str(self.childSymbol)
1277
            node.append(childSymbolNode)
1278

    
1279
            hasInstrumentLabelNode = Element('HASINSTRUMENTLABEL')
1280
            hasInstrumentLabelNode.text = str(self.hasInstrumentLabel)
1281
            node.append(hasInstrumentLabelNode)
1282

    
1283
            areaNode = Element('AREA')
1284
            areaNode.text = self.area
1285
            node.append(areaNode)
1286

    
1287
            flipNode = Element('FLIP')
1288
            flipNode.text = str(self.flip)
1289
            node.append(flipNode)
1290

    
1291
            if self.hit_ratio:
1292
                ratioNode = Element('RATIO')
1293
                ratioNode.text = str(self.hit_ratio)
1294
                node.append(ratioNode)
1295

    
1296
            properties_node = Element('PROPERTIES')
1297
            for prop, value in self.properties.items():
1298
                prop_node = prop.toXml()
1299
                prop_node.text = str(value) if value else ''
1300
                properties_node.append(prop_node)
1301
            node.append(properties_node)
1302

    
1303
            attributesNode = Element('SYMBOLATTRIBUTES')
1304
            _attrs = self.getAttributes()
1305
            for attr in _attrs:
1306
                if type(attr) is SymbolAttr:
1307
                    _node = attr.toXml()
1308
                    if attr.AttributeType != 'Spec':
1309
                        _node.text = str(_attrs[attr])
1310
                    elif attr.AttributeType == 'Spec':
1311
                        _node.text = str(self.attrs[attr][0]) + ',' + str(self.attrs[attr][1])
1312
                    attributesNode.append(_node)
1313

    
1314
            node.append(attributesNode)
1315

    
1316
            if hasattr(self, 'currentPointModeIndex'):
1317
                currentPointModeIndexNode = Element('CURRENTPOINTMODEINDEX')
1318
                currentPointModeIndexNode.text = str(self.currentPointModeIndex) if self.currentPointModeIndex else ''
1319
                node.append(currentPointModeIndexNode)
1320
        except Exception as ex:
1321
            from App import App
1322
            message = f'error occurred({ex}) in {sys.exc_info()[-1].tb_frame.f_code.co_filename}:' \
1323
                      f'{sys.exc_info()[-1].tb_lineno}'
1324
            App.mainWnd().addMessage.emit(MessageType.Error, message)
1325

    
1326
            return None
1327

    
1328
        return node
1329

    
1330
    @staticmethod
1331
    def from_database(component):
1332
        """ create a item related to given component from database """
1333
        import uuid
1334
        from EngineeringSpecBreakItem import QEngineeringSpecBreakItem
1335
        from EngineeringEndBreakItem import QEngineeringEndBreakItem
1336
        from SymbolAttr import SymbolAttr
1337
        item = None
1338

    
1339
        try:
1340
            app_doc_data = AppDocData.instance()
1341

    
1342
            uid = component['UID']
1343
            pt = [float(component['X']), float(component['Y'])]
1344
            size = [float(component['Width']), float(component['Height'])]
1345

    
1346
            dbUid = int(component['Symbol_UID'])
1347
            dbData = app_doc_data.getSymbolByQuery('UID', dbUid)
1348
            name = dbData.sName
1349
            _type = dbData.sType
1350
            angle = float(component['Rotation'])
1351
            origin = [float(x) for x in component['SceneOriginPoint'].split(',')] if component['SceneOriginPoint'] is not None else pt
1352
            connPts = []
1353
            if component['ConnectionPoint']:
1354
                if dbData:
1355
                    db_conn = dbData.connectionPoint.split('/')
1356
                    db_symbol_num = [conn.split(',')[3] for conn in db_conn]
1357
                index = 0
1358
                for conn_pt in component['ConnectionPoint'].split('/'):
1359
                    tokens = conn_pt.split(',')
1360
                    connPts.append(('AUTO', float(tokens[0]), float(tokens[1]), '0') if len(tokens) == 2 else
1361
                                   (tokens[0], float(tokens[1]), float(tokens[2]), '0') if len(tokens) == 3 else
1362
                                   (tokens[0], float(tokens[1]), float(tokens[2]), tokens[3] if dbData is None else db_symbol_num[index]))
1363
                    index += 1
1364

    
1365
            baseSymbol = dbData.baseSymbol
1366

    
1367
            childSymbolNode = component['AdditionalSymbol']
1368
            childSymbol = childSymbolNode if childSymbolNode is not None else ''
1369

    
1370
            owner = component['Owner'] if component['Owner'] is not None and component['Owner'] != 'None' else None
1371

    
1372
            hasInstrumentLabel = dbData.hasInstrumentLabel
1373

    
1374
            flipLabelNode = component['Flip']
1375
            flipLabel = int(flipLabelNode) if flipLabelNode is not None else 0
1376

    
1377
            project = app_doc_data.getCurrentProject()
1378
            svgFilePath = os.path.join(project.getSvgFilePath(), _type, name + '.svg')
1379
            if os.path.isfile(svgFilePath):
1380
                item = SymbolSvgItem.createItem(_type, name, svgFilePath, uid, owner=owner, flip=flipLabel)
1381
                item.setVisible(False)
1382

    
1383
                # if additional symbol was changed, change symbol info
1384
                symbolInfo = None
1385
                if dbUid is None:
1386
                    symbolInfo = app_doc_data.getSymbolByQuery('name', name)
1387
                else:
1388
                    symbolInfo = app_doc_data.getSymbolByQuery('UID', dbUid)
1389
                if symbolInfo:
1390
                    childSymbol = symbolInfo.additionalSymbol
1391

    
1392
                if item.buildItem(name, _type, angle, pt, size, origin, connPts, baseSymbol, childSymbol,
1393
                                  hasInstrumentLabel, dbUid=dbUid):
1394
                    pass
1395
                else:
1396
                    return None
1397

    
1398
                for key in item._properties.keys():
1399
                    for compo in component.keys():
1400
                        if key.Attribute == compo:
1401
                            item._properties[key] = key.parse_value(component[key.Attribute]) if component[
1402
                                key.Attribute] else ''
1403

    
1404
                ## assign area
1405
                areaNode = component['Area']
1406
                if areaNode is None:
1407
                    for area in app_doc_data.getAreaList():
1408
                        if area.contains(pt):
1409
                            item.area = area.name
1410
                            break
1411
                else:
1412
                    item.area = areaNode
1413
                ## up to here
1414

    
1415
                connectors = app_doc_data.get_component_connectors(uid)
1416
                if connectors:
1417
                    iterIndex = 0
1418
                    for connector in connectors:
1419
                        item.connectors[iterIndex].parse_record(connector)
1420
                        iterIndex += 1
1421

    
1422
                # get associations 
1423
                associations = app_doc_data.get_component_associations(uid)
1424
                if associations:
1425
                    for assoc in associations:
1426
                        _attrType = assoc['Type']
1427
                        if not _attrType in item._associations:
1428
                            item._associations[_attrType] = []
1429
                        item._associations[_attrType].append(
1430
                            uuid.UUID(assoc['Association']) if assoc['Association'] != 'None' else None)
1431
                # up to here
1432

    
1433
                attributes = app_doc_data.get_component_attributes(uid)
1434
                if attributes:
1435
                    for attr in attributes:
1436
                        _attr = SymbolAttr.from_record(attr)
1437
                        if type(item) is not QEngineeringSpecBreakItem and type(item) is not QEngineeringEndBreakItem:
1438
                            item.attrs[_attr] = attr['Value']
1439
                        else:
1440
                            if _attr.AttributeType == 'Spec':
1441
                                item.attrs[_attr] = [attr['Value'].split(',')[0], attr['Value'].split(',')[1]]
1442
                            else:
1443
                                item.attrs[_attr] = attr['Value']
1444
                            '''
1445
                            elif _attr.Attribute == 'UpStream':
1446
                                for initKey in item.attrs.keys():
1447
                                    if initKey.Attribute == 'UpStream':
1448
                                        item.attrs[initKey] = attr['Value']
1449
                            elif _attr.Attribute == 'DownStream':
1450
                                for initKey in item.attrs.keys():
1451
                                    if initKey.Attribute == 'DownStream':
1452
                                        item.attrs[initKey] = attr['Value']
1453
                            '''
1454

    
1455
                currentPointModeIndex = component['OriginIndex']
1456
                if currentPointModeIndex is not None:
1457
                    item.currentPointModeIndex = int(currentPointModeIndex)
1458
        except Exception as ex:
1459
            from App import App
1460
            message = 'error occurred({}) in {}:{}'.format(ex, sys.exc_info()[-1].tb_frame.f_code.co_filename,
1461
                                                          sys.exc_info()[-1].tb_lineno)
1462
            App.mainWnd().addMessage.emit(MessageType.Error, message)
1463

    
1464
            return None
1465

    
1466
        return item
1467

    
1468
    '''
1469
        @brief      parse xml code
1470
        @author     humkyung
1471
        @date       2018.07.20
1472
        @history    humkyung 2018.07.20 parse uid from xml node
1473
                    humkyung 2018.07.23 parse connected item's uid from xml node
1474
                    kyouho  2018.07.31 
1475
                    humkyung 2018.09.06 assign area to item
1476
    '''
1477

    
1478
    @staticmethod
1479
    def fromXml(node):
1480
        import uuid
1481
        from EngineeringSpecBreakItem import QEngineeringSpecBreakItem
1482
        from EngineeringEndBreakItem import QEngineeringEndBreakItem
1483
        from SymbolAttr import SymbolAttr
1484
        item = None
1485

    
1486
        try:
1487
            appDocData = AppDocData.instance()
1488

    
1489
            uidNode = node.find('UID')
1490
            uid = uidNode.text if uidNode is not None else uuid.uuid4()  # generate UUID
1491

    
1492
            pt = [float(x) for x in node.find('LOCATION').text.split(',')]
1493
            size = [float(x) for x in node.find('SIZE').text.split(',')]
1494

    
1495
            dbUidNode = node.find('DBUID')
1496
            dbUid = int(dbUidNode.text) if dbUidNode is not None else None
1497
            dbData = None
1498
            if dbUid:
1499
                dbData = appDocData.getSymbolByQuery('UID', dbUid)
1500
            name = node.find('NAME').text if dbData is None else dbData.sName
1501

    
1502
            angle = float(node.find('ANGLE').text)
1503
            _type = node.find('TYPE').text if dbData is None else dbData.sType
1504
            origin = [float(x) for x in node.find('ORIGINALPOINT').text.split(',')]
1505
            connPts = []
1506
            if node.find('CONNECTIONPOINT').text is not None:
1507
                if dbData:
1508
                    db_conn = dbData.connectionPoint.split('/')
1509
                    db_symbol_num = [conn.split(',')[3] for conn in db_conn]
1510
                index = 0
1511
                for conn_pt in node.find('CONNECTIONPOINT').text.split('/'):
1512
                    tokens = conn_pt.split(',')
1513
                    connPts.append(('AUTO', float(tokens[0]), float(tokens[1]), '0') if len(tokens) == 2 else
1514
                                   (tokens[0], float(tokens[1]), float(tokens[2]), '0') if len(tokens) == 3 else
1515
                                   (tokens[0], float(tokens[1]), float(tokens[2]), tokens[3] if dbData is None else db_symbol_num[index]))
1516
                    index += 1
1517
            baseSymbol = node.find('PARENT').text if dbData is None else dbData.baseSymbol
1518
            childSymbolNode = node.find('CHILD')
1519
            childSymbol = ''
1520
            if childSymbolNode is not None:
1521
                childSymbol = childSymbolNode.text
1522

    
1523
            ownerNode = node.find('OWNER')
1524
            owner = ownerNode.text if ownerNode is not None and ownerNode.text != 'None' else None
1525

    
1526
            hasInstrumentLabelNode = node.find('HASINSTRUMENTLABEL')
1527
            hasInstrumentLabel = hasInstrumentLabelNode.text if dbData is None else dbData.hasInstrumentLabel
1528

    
1529
            flipLabelNode = node.find('FLIP')
1530
            flipLabel = int(flipLabelNode.text) if flipLabelNode is not None else 0
1531

    
1532
            ratioNode = node.find('RATIO')
1533
            hit_ratio = float(ratioNode.text) if ratioNode is not None else None
1534

    
1535
            project = appDocData.getCurrentProject()
1536
            svgFilePath = os.path.join(project.getSvgFilePath(), _type, name + '.svg')
1537
            if os.path.isfile(svgFilePath):
1538
                item = SymbolSvgItem.createItem(_type, name, svgFilePath, uid, owner=owner, flip=flipLabel)
1539
                item.setVisible(False)
1540

    
1541
                # if additional symbol was changed, change symbol info
1542
                symbolInfo = None
1543
                if dbUid is None:
1544
                    symbolInfo = appDocData.getSymbolByQuery('name', name)
1545
                else:
1546
                    symbolInfo = appDocData.getSymbolByQuery('UID', dbUid)
1547
                if symbolInfo:
1548
                    childSymbol = symbolInfo.additionalSymbol
1549

    
1550
                if hit_ratio:
1551
                    item.hit_ratio = hit_ratio
1552

    
1553
                if item.buildItem(name, _type, angle, pt, size, origin, connPts, baseSymbol, childSymbol,
1554
                                  hasInstrumentLabel, dbUid=dbUid):
1555
                    pass
1556
                else:
1557
                    return None
1558

    
1559
                for prop_node in node.iter('PROPERTY'):
1560
                    matches = [prop for prop in item._properties.keys() if
1561
                               prop.Attribute == prop_node.attrib['Attribute']]
1562
                    if matches:
1563
                        item._properties[matches[0]] = matches[0].parse_value(prop_node.text) if prop_node.text else ''
1564

    
1565
                # assign area
1566
                areaNode = node.find('AREA')
1567
                if areaNode is None:
1568
                    for area in appDocData.getAreaList():
1569
                        if area.contains(pt):
1570
                            item.area = area.name
1571
                            break
1572
                else:
1573
                    item.area = areaNode.text
1574
                # up to here
1575

    
1576
                connectors = node.find('CONNECTORS')
1577
                if connectors is not None:
1578
                    iterIndex = 0
1579
                    for connector in connectors.iter('CONNECTOR'):
1580
                        item.connectors[iterIndex].parse_xml(connector)
1581
                        iterIndex += 1
1582

    
1583
                # get associations 
1584
                attributeValue = node.find('ASSOCIATIONS')
1585
                if attributeValue is not None:
1586
                    for assoc in attributeValue.iter('ASSOCIATION'):
1587
                        _attrType = assoc.attrib['TYPE']
1588
                        if not _attrType in item._associations:
1589
                            item._associations[_attrType] = []
1590
                        item._associations[_attrType].append(uuid.UUID(assoc.text) if assoc.text != 'None' else None)
1591
                # up to here
1592

    
1593
                attributes = node.find('SYMBOLATTRIBUTES')
1594
                if attributes is not None:
1595
                    '''
1596
                    ## for old spec break item that has not uid currectly, may not necessary new data
1597
                    if _type == 'Segment Breaks':
1598
                        specBreakAttrs = appDocData.getSymbolAttribute('Segment Breaks')
1599
                    ## up to here 1
1600
                    '''
1601
                    for attr in attributes.iter('ATTRIBUTE'):
1602
                        _attr = SymbolAttr.fromXml(attr)
1603
                        if type(item) is not QEngineeringSpecBreakItem and type(item) is not QEngineeringEndBreakItem:
1604
                            item.attrs[_attr] = attr.text
1605
                        else:
1606
                            '''
1607
                            ## for old spec break item that has not uid currectly, may not necessary new data
1608
                            matchAttr = [cAttr for cAttr in specBreakAttrs if _attr.Attribute == cAttr.Attribute]
1609
                            matchAttr = matchAttr[0] if matchAttr else None
1610
                            if matchAttr:
1611
                                _attr.UID = matchAttr.UID
1612
                                _attr.DisplayAttribute = matchAttr.DisplayAttribute
1613
                                _attr.AttributeType = matchAttr.AttributeType
1614
                                _attr.AttrAt = matchAttr.AttrAt
1615
                                _attr.Expression = matchAttr.Expression
1616
                                _attr.Length = matchAttr.Length
1617
                            # up to here 2
1618
                            '''
1619
                            if _attr.AttributeType == 'Spec':
1620
                                item.attrs[_attr] = [attr.text.split(',')[0], attr.text.split(',')[1]]
1621
                            else:
1622
                                '''
1623
                                # for old spec break item that has not uid currectly, may not necessary new data
1624
                                _attr.AssocItem = uuid.UUID(attr.text) if attr.text and attr.text != 'None' else None
1625
                                # up to here 3
1626
                                '''
1627
                                item.attrs[_attr] = attr.text
1628
                            '''
1629
                            elif _attr.Attribute == 'UpStream':
1630
                                for initKey in item.attrs.keys():
1631
                                    if initKey.Attribute == 'UpStream':
1632
                                        item.attrs[initKey] = attr.text
1633
                            elif _attr.Attribute == 'DownStream':
1634
                                for initKey in item.attrs.keys():
1635
                                    if initKey.Attribute == 'DownStream':
1636
                                        item.attrs[initKey] = attr.text
1637
                            '''
1638

    
1639
                currentPointModeIndex = node.find('CURRENTPOINTMODEINDEX')
1640
                if currentPointModeIndex is not None:
1641
                    item.currentPointModeIndex = int(currentPointModeIndex.text) if currentPointModeIndex.text else 0
1642
        except Exception as ex:
1643
            from App import App
1644
            message = 'error occurred({}) in {}:{}'.format(ex, sys.exc_info()[-1].tb_frame.f_code.co_filename,
1645
                                                          sys.exc_info()[-1].tb_lineno)
1646
            App.mainWnd().addMessage.emit(MessageType.Error, message)
1647

    
1648
            return None
1649

    
1650
        return item
1651

    
1652
    def to_svg(self, parent) -> list:
1653
        """convert symbol svg item to svg"""
1654
        import re
1655
        from xml.etree.ElementTree import Element, SubElement, dump, ElementTree
1656
        from App import App
1657

    
1658
        res = []
1659
        try:
1660
            app_doc_data = AppDocData.instance()
1661
            prj = app_doc_data.getCurrentProject()
1662

    
1663
            node = Element('g')
1664
            node.attrib['id'] = str(self.uid)
1665
            node.attrib['class'] = self._class
1666

    
1667
            except_pattern = re.compile('[^a-zA-Z0-9-_]')
1668
            for attr, value in self.getAttributes().items():
1669
                node.attrib[re.sub(except_pattern, '_', attr.Attribute)] = str(value) if value else ''
1670
            trans = self.sceneTransform()
1671
            node.attrib['transform'] = f"matrix(" \
1672
                                       f"{trans.m11()},{trans.m12()}," \
1673
                                       f"{trans.m21()},{trans.m22()}," \
1674
                                       f"{trans.m31()},{trans.m32()}" \
1675
                                       f")"
1676

    
1677
            node_list = self._document.elementsByTagName('path')
1678
            for at in range(node_list.count()):
1679
                path = Element('path')
1680
                path.attrib['d'] = node_list.item(at).attributes().namedItem('d').nodeValue()
1681
                path.attrib['transform'] = self._document.elementsByTagName('g').item(0).attributes().namedItem('transform').nodeValue()
1682
                node.append(path)
1683

    
1684
            for assoc in self.associations():
1685
                assoc_node = assoc.to_svg(parent=self)
1686
                node.extend(assoc_node)
1687

    
1688
            res.append(node)
1689
        except Exception as ex:
1690
            from App import App
1691
            message = 'error occurred({}) in {}:{}'.format(ex, sys.exc_info()[-1].tb_frame.f_code.co_filename,
1692
                                                          sys.exc_info()[-1].tb_lineno)
1693
            App.mainWnd().addMessage.emit(MessageType.Error, message)
1694

    
1695
        return res
1696

    
1697
    '''
1698
        @brief      create item corresponding to given type
1699
        @author     humkyung
1700
        @date       2018.05.02
1701
        @history    2018.05.08  Jeongwoo    Change type name (Piping OPC''S → Piping OPC's)
1702
                    humkyung 2018.05.10 change symbol's color to blue
1703
                    humkyung 2018.07.19 create nozzle instance if type is 'Nozzles'
1704
    '''
1705

    
1706
    @staticmethod
1707
    def createItem(type: str, name: str, path: str, uid=None, owner=None, flip=0):
1708
        from QEngineeringOPCItem import QEngineeringOPCItem
1709
        from EngineeringEquipmentItem import QEngineeringEquipmentItem
1710
        from EngineeringInstrumentItem import QEngineeringInstrumentItem
1711
        from EngineeringNozzleItem import QEngineeringNozzleItem
1712
        from EngineeringSpecBreakItem import QEngineeringSpecBreakItem
1713
        from EngineeringReducerItem import QEngineeringReducerItem
1714
        from EngineeringErrorItem import QEngineeringErrorItem
1715
        from EngineeringEndBreakItem import QEngineeringEndBreakItem
1716
        from EngineeringFlowMarkItem import QEngineeringFlowMarkItem
1717
        from AppDocData import AppDocData
1718
        import uuid
1719

    
1720
        app_doc_data = AppDocData.instance()
1721

    
1722
        item = None
1723
        try:
1724
            cateogry = app_doc_data.getSymbolCategoryByType(type)
1725
            if type == "Piping OPC's" or type == "Instrument OPC's":
1726
                item = QEngineeringOPCItem(path, uid, flip=flip)
1727
            elif type == 'Nozzles':
1728
                item = QEngineeringNozzleItem(path, uid, flip=flip)
1729
            elif cateogry == 'Equipment' or cateogry == 'Equipment Components' or cateogry == 'Package':
1730
                item = QEngineeringEquipmentItem(path, uid, flip=flip)
1731
            elif cateogry == 'Instrumentation':
1732
                item = QEngineeringInstrumentItem(path, uid, flip=flip)
1733
            elif type == 'Segment Breaks':
1734
                item = QEngineeringSpecBreakItem(path, uid, flip=flip)
1735
            elif type == 'Reducers':
1736
                item = QEngineeringReducerItem(path, uid, flip=flip)
1737
            elif type == 'Error':
1738
                item = QEngineeringErrorItem(path, uid, flip=flip)
1739
            elif type == 'End Break':
1740
                item = QEngineeringEndBreakItem(path, uid, flip=flip)
1741
            # elif type == 'Flow Mark':
1742
            #    item = QEngineeringFlowMarkItem(path, uid, flip=flip)
1743
            else:
1744
                item = SymbolSvgItem(name, path, uid, flip=flip)
1745

    
1746
            if owner is not None:
1747
                item.owner = uuid.UUID(owner)
1748

    
1749
        except Exception as ex:
1750
            from App import App
1751
            from AppDocData import MessageType
1752

    
1753
            message = 'error occurred({}) in {}:{}'.format(repr(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

    
1757
        return item
1758

    
1759
    def setColor(self, color):
1760
        """change svg item's color"""
1761
        if self._document:# and color != self.__color:
1762
            self.changeAttributes('fill', color)
1763
            self.changeAttributes('stroke', color)
1764
            self.renderer().load(self._document.toByteArray())
1765
            self.__color = color
1766
            #self.update()
1767

    
1768
    '''
1769
        @brief  get attributes from svg file
1770
        @author humkyung
1771
        @date   2019.03.08
1772
    '''
1773

    
1774
    def get_attribute(self, attName):
1775
        root = self._document.documentElement()
1776
        node = root.firstChild()
1777
        while not node.isNull():
1778
            if node.isElement():
1779
                element = node.toElement()
1780
                if element.hasAttribute(attName):
1781
                    return element.attribute(attName)
1782

    
1783
                if element.hasChildNodes():
1784
                    att_val = self.recursive_get_attribute(element.firstChild(), attName)
1785
                    if att_val is not None: return att_val
1786

    
1787
            node = node.nextSibling()
1788

    
1789
        return None
1790

    
1791
    '''
1792
        @brief  get recursively attribute
1793
        @author humkyung
1794
        @date   2019.03.08
1795
    '''
1796

    
1797
    def recursive_get_attribute(self, node, attName):
1798
        while not node.isNull():
1799
            if node.isElement():
1800
                element = node.toElement()
1801
                if element.hasAttribute(attName):
1802
                    return element.attribute(attName)
1803

    
1804
                if node.hasChildNodes():
1805
                    att_val = self.recursive_get_attribute(node.firstChild(), attName)
1806
                    if att_val is not None: return att_val
1807

    
1808
            node = node.nextSibling()
1809

    
1810
        return None
1811

    
1812
    '''
1813
        @brief  change attributes
1814
        @author humkyung
1815
        @date   2018.05.10
1816
    '''
1817

    
1818
    def changeAttributes(self, attName, attValue):
1819
        root = self._document.documentElement()
1820
        node = root.firstChild()
1821
        while not node.isNull():
1822
            if node.isElement():
1823
                element = node.toElement()
1824
                if element.hasAttribute(attName):
1825
                    element.setAttribute(attName, attValue)
1826

    
1827
                if element.hasChildNodes():
1828
                    recursiveChangeAttributes(element.firstChild(), attName, attValue)
1829

    
1830
            node = node.nextSibling()
1831

    
1832
    '''
1833
        @brief  change attribute
1834
        @author humkyung
1835
        @date   2018.05.10
1836
    '''
1837

    
1838
    def recursiveChangeAttributes(self, node, attName, attValue):
1839
        while not node.isNull():
1840
            if node.isElement():
1841
                element = node.toElement()
1842
                if element.hasAttribute(attName):
1843
                    element.setAttribute(attName, attValue)
1844

    
1845
                if node.hasChildNodes():
1846
                    recursiveChangeAttributes(node.firstChild(), attName, attValue)
1847

    
1848
            node = node.nextSibling()
1849

    
1850
    '''
1851
        @brief  draw rect when item is selected
1852
        @author humkyung
1853
        @date   2018.07.07
1854
    '''
1855

    
1856
    def drawFocusRect(self, painter):
1857
        self.focuspen = QPen(Qt.DotLine)
1858
        self.focuspen.setColor(Qt.black)
1859
        self.focuspen.setWidthF(1.5)
1860
        hilightColor = QColor(255, 0, 0, 127)
1861
        painter.setBrush(QBrush(hilightColor))
1862
        painter.setPen(self.focuspen)
1863
        painter.drawRect(self.boundingRect())
1864

    
1865
    '''
1866
        @brief  override paint(draw connection points)
1867
        @author humkyung
1868
        @date   2018.04.21
1869
    '''
1870

    
1871
    def paint(self, painter, options=None, widget=None):
1872
        from EngineeringAbstractItem import QEngineeringAbstractItem
1873
        from EngineeringTextItem import QEngineeringTextItem
1874

    
1875
        self.setColor(self.getColor())
1876

    
1877
        painter.setClipRect(options.exposedRect)
1878
        QGraphicsSvgItem.paint(self, painter, options, widget)
1879
        '''
1880
        # not used
1881
        for attr in self.attrs:
1882
            if issubclass(type(attr), QEngineeringTextItem):
1883
                color = QEngineeringAbstractItem.HOVER_COLOR if self.hover else (
1884
                    self._owner._color if self._owner else QEngineeringAbstractItem.DEFAULT_COLOR)
1885
                attr.setColor(color)
1886
            elif issubclass(type(attr), SymbolSvgItem):
1887
                attr.setColor(self.getColor())
1888
                attr.update()
1889
        '''
1890

    
1891
        if self.isSelected():
1892
            self.drawFocusRect(painter)
1893

    
1894
    def addSvgItemToScene(self, scene, undoable: bool = False) -> None:
1895
        """Add Svg Item into ImageViewer's Scene"""
1896
        if self.flip:
1897
            self.flip_symbol()
1898

    
1899
        self.setTransformOriginPoint(QPointF(self.symbolOrigin[0], self.symbolOrigin[1]))
1900

    
1901
        self.moveBy(-self.symbolOrigin[0], -self.symbolOrigin[1])
1902
        self.setRotation(math.degrees(self.angle))
1903
        self.moveBy(self.origin[0], self.origin[1])
1904

    
1905
        scene.addItem(self)
1906
        self.size[0], self.size[1] = round(self.sceneBoundingRect().width()), round(self.sceneBoundingRect().height())
1907

    
1908
        if undoable:
1909
            from CreateCommand import CreateCommand
1910
            self.scene().undo_stack.push(CreateCommand(self.scene(), [self,]))
1911

    
1912
    '''
1913
        @brief      
1914
        @author     humkyung
1915
        @date       2018.07.27
1916
    '''
1917
    def onConnectorPosChaned(self, connector):
1918
        pass
1919

    
1920
    '''
1921
        @brief      set connector
1922
        @author     kyouho
1923
        @date       2018.07.26
1924
    '''
1925
    def setConnector(self, index):
1926
        connector = QEngineeringConnectorItem(parent=self, index=index)
1927
        connector.setParentItem(self)
1928
        self.connectors.append(connector)
1929

    
1930
    def refreshConnector(self):
1931
        for connector in self.connectors:
1932
            connector.buildItem()
1933

    
1934
    '''
1935
        @brief      Delete svg item
1936
        @author     Jeongwoo
1937
        @date       2018.05.25
1938
    '''
1939
    def deleteSvgItemFromScene(self):
1940
        ''' not used '''
1941
        for connector in self.connectors:
1942
            if connector.connectedItem is not None:
1943
                try:
1944
                    connector.connectedItem.removeSymbol(self)
1945
                except Exception as ex:
1946
                    from App import App
1947
                    from AppDocData import MessageType
1948

    
1949
                    message = 'error occurred({}) in {}:{}'.format(ex, sys.exc_info()[-1].tb_frame.f_code.co_filename,
1950
                                                                  sys.exc_info()[-1].tb_lineno)
1951
                    App.mainWnd().addMessage.emit(MessageType.Error, message)
1952
                break
1953

    
1954
        self.transfer.onRemoved.emit(self)
1955

    
1956
    def flip_symbol(self) -> None:
1957
        """flip symbol"""
1958

    
1959
        transform = QTransform()
1960
        if self.flip is 1:
1961
            rect = self.boundingRect()
1962
            transform.scale(-1.0, 1.0)
1963
            transform.translate(-2 * self.symbolOrigin[0], 0)
1964

    
1965
        self.setTransform(transform)
1966

    
1967
    def rotate(self, angle: float) -> None:
1968
        """rotate symbol by given angle in radian"""
1969
        import math
1970
        self.setRotation(math.degrees(angle))
1971

    
1972
    '''
1973
        @brief      change Conn point 
1974
        @author     kyouho
1975
        @date       2018.07.25
1976
    '''
1977
    def changeConnPoint(self):
1978
        if len(self.connectors) == 2:
1979
            conn1Item = self.connectors[0].connectedItem
1980
            conn2Item = self.connectors[1].connectedItem
1981
            self.connectors[0].connectedItem = conn2Item
1982
            self.connectors[1].connectedItem = conn1Item
1983

    
1984
            currentPoint = self.getCurrentPoint()
1985
            #self.reSettingSymbol(currentPoint, self.angle)
1986

    
1987
    '''
1988
        @brief      change standard point
1989
        @author     kyouho
1990
        @date       2018.07.24
1991
    '''
1992
    def changeStandardPoint(self):
1993
        connPtsCount = len(self.connectors)
1994

    
1995
        if self.currentPointModeIndex < connPtsCount:
1996
            self.currentPointModeIndex += 1
1997
        else:
1998
            self.currentPointModeIndex = 0
1999

    
2000
        currentPoint = self.getCurrentPoint()
2001
        #self.reSettingSymbol(currentPoint, self.angle)
2002

    
2003
    def getCurrentPoint(self):
2004
        """return transform origin point"""
2005
        pointList = []
2006
        pointList.append(self.symbolOrigin)
2007
        for connector in self.connectors:
2008
            pointList.append(connector.connectPoint)
2009

    
2010
        return pointList[self.currentPointModeIndex]
2011

    
2012
    def EvaluatedCode(self, old_code, code_name):
2013
        """ return new attribute code """
2014
        from AppDocData import AppDocData
2015
        from CodeTables import CodeTable
2016
        from LineTypeConditions import LineTypeConditions
2017
        from EngineeringLineItem import QEngineeringLineItem
2018
        import re
2019

    
2020
        try:
2021
            code = old_code
2022
            start_item = None
2023
            if self.iType == 19:    # Labels - Symbol
2024
                matches = [assoc for assoc in self.associations() if issubclass(type(assoc), SymbolSvgItem)]
2025
                start_item = matches[0] if matches else self
2026
            else:
2027
                start_item = self
2028

    
2029
            #if not start_item:
2030
            #    return ''
2031

    
2032
            app_doc_data = AppDocData.instance()
2033
            connected_items_lists = app_doc_data._connected_items_lists
2034
            items = [connected_items_list.items for connected_items_list in connected_items_lists.runs if start_item in connected_items_list.items]
2035
            if len(items) != 1:
2036
                return ''
2037
            else:
2038
                items = [item for item in items[0] if issubclass(type(item), SymbolSvgItem)]
2039

    
2040
            table = CodeTable.instance(code_name, inst=True)
2041
            line_types = [condition.name for condition in LineTypeConditions.items()]
2042

    
2043
            new_codes = []
2044
            for value in table.values: # uid, old code, symbol, attribute, new code, expression, priority
2045
                # symbol, line type
2046
                if len(value[3]) == 1 and not value[3][0]:
2047
                    # symbol
2048
                    if not [line_type for line_type in line_types if line_type in value[2]]:
2049
                        for item in items:
2050
                            match = re.search(value[1][0], code, re.DOTALL)
2051
                            if (code in value[1] or (match and match.start() is 0 and match.end() is len(code))) \
2052
                                and item.name in value[2]:
2053
                                dx = item.origin[0] - self.origin[0]
2054
                                dy = item.origin[1] - self.origin[1]
2055
                                length = math.sqrt(dx*dx + dy*dy)
2056
                                if not value[5]:
2057
                                    new_codes.append([length if not value[6] else value[6], '"' + value[4] + '"', item, length])
2058
                                else:
2059
                                    new_codes.append([length if not value[6] else value[6], value[5], item, length])
2060
                    # line
2061
                    else:
2062
                        match = re.search(value[1][0], code, re.DOTALL)
2063
                        types = [conn.connectedItem.lineType for conn in self.connectors if conn.connectedItem and type(conn.connectedItem) is QEngineeringLineItem]
2064
                        if (code in value[1] or (match and match.start() is 0 and match.end() is len(code))) and \
2065
                            [line_type for line_type in value[2] if line_type in types]:
2066
                            if not value[5]:
2067
                                new_codes.append([0 if not value[6] else value[6], '"' + value[4] + '"', None, sys.maxsize])
2068
                            else:
2069
                                new_codes.append([0 if not value[6] else value[6], value[5], None, sys.maxsize])
2070

    
2071
                # self attribute
2072
                elif len(value[2]) == 1 and not value[2][0] and value[3][0]:
2073
                    for key, _value in self.attrs.items():
2074
                        if _value in value[3]:
2075
                            if not value[5]:
2076
                                new_codes.append([0 if not value[6] else value[6], '"' + value[4] + '"', self, sys.maxsize])
2077
                            else:
2078
                                new_codes.append([0 if not value[6] else value[6], value[5], self, sys.maxsize])
2079

    
2080
                # symbol + attribute
2081
                elif value[2][0] and value[3][0]:
2082
                    for item in items:
2083
                        match = re.search(value[1][0], code, re.DOTALL)
2084
                        if (code in value[1] or (match and match.start() is 0 and match.end() is len(code))) \
2085
                            and item.name in value[2]:
2086
                            dx = item.origin[0] - self.origin[0]
2087
                            dy = item.origin[1] - self.origin[1]
2088
                            length = math.sqrt(dx*dx + dy*dy)
2089

    
2090
                            for key, _value in item.attrs.items():
2091
                                if _value in value[3]:
2092
                                    if not value[5]:
2093
                                        new_codes.append([length if not value[6] else value[6], '"' + value[4] + '"', item, length])
2094
                                    else:
2095
                                        new_codes.append([length if not value[6] else value[6], value[5], item, length])
2096
                        
2097
            # default
2098
            for value in table.values:
2099
                match = re.search(value[1][0], code, re.DOTALL)
2100
                if (code in value[1] or (match and match.start() is 0 and match.end() is len(code))) and len(value[2]) == 1 and len(value[3]) == 1 and value[2][0] == '' and value[3][0] == '':
2101
                    if not value[5]:
2102
                        new_codes.append([sys.maxsize if not value[6] else value[6], '"' + value[4] + '"', None, sys.maxsize])
2103
                    else:
2104
                        new_codes.append([sys.maxsize if not value[6] else value[6], value[5], None, sys.maxsize])
2105

    
2106
            if new_codes:
2107
                new_codes = sorted(new_codes, key=lambda param: param[0])
2108
                new_codes = [_code for _code in new_codes if _code[0] == new_codes[0][0]]
2109
                new_codes = sorted(new_codes, key=lambda param: param[-1])
2110
                item = new_codes[0][-2]
2111
                new_code = eval(new_codes[0][1])
2112
                return new_code
2113
            else:
2114
                return code
2115
        except Exception as ex:
2116
            from App import App
2117
            from AppDocData import MessageType
2118

    
2119
            message = 'error occurred({}) in {}:{}'.format(ex, sys.exc_info()[-1].tb_frame.f_code.co_filename,
2120
                                                          sys.exc_info()[-1].tb_lineno)
2121
            #App.mainWnd().addMessage.emit(MessageType.Error, str(self.uid) + self.name + message)
2122
            App.mainWnd().addMessage.emit(MessageType.Error, message)
2123

    
2124
def recursiveChangeAttributes(node, attName, attValue):
2125
    while not node.isNull():
2126
        if node.isElement():
2127
            element = node.toElement()
2128
            if element.hasAttribute(attName):
2129
                element.setAttribute(attName, attValue)
2130

    
2131
            if node.hasChildNodes():
2132
                recursiveChangeAttributes(node.firstChild(), attName, attValue)
2133

    
2134
        node = node.nextSibling()
2135

    
2136

    
2137
'''
2138
    @brief      The class transfer pyqtSignal Event. Cause Subclass of QGraphicsRectItem can't use pyqtSignal
2139
    @author     Jeongwoo
2140
    @date       2018.06.18
2141
'''
2142
class Transfer(QObject):
2143
    on_pos_changed = pyqtSignal(QGraphicsItem)
2144
    onRemoved = pyqtSignal(QGraphicsItem)
2145

    
2146
    def __init__(self, parent=None):
2147
        QObject.__init__(self, parent)
2148

    
2149

    
2150
if __name__ == '__main__':
2151
    f = QFile('d:/Projects/DTIPID/DTI_PID/DTI_PID/SG_TEST/svg/ANGLE VALVE.svg')
2152
    f.open(QIODevice.ReadOnly)
2153
    array = f.readAll()
2154
    document = QDomDocument()
2155
    document.setContent(array)
2156

    
2157
    root = document.documentElement()
2158
    node = root.firstChild()
2159
    while not node.isNull():
2160
        if node.isElement():
2161
            element = node.toElement()
2162
            if element.hasAttribute('fill'):
2163
                element.setAttribute('fill', '#FFFFF')
2164

    
2165
            if element.hasChildNodes():
2166
                recursiveChangeAttributes(element.firstChild(), 'fill', '#FFFFF')
2167

    
2168
        node = node.nextSibling()
클립보드 이미지 추가 (최대 크기: 500 MB)