프로젝트

일반

사용자정보

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

hytos / DTI_PID / DTI_PID / Shapes / SymbolSvgItem.py @ b78d1df0

이력 | 보기 | 이력해설 | 다운로드 (88.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
    '''
1073
        @brief      connect attribute
1074
        @author     humkyung
1075
        @date       2018.05.02
1076
        @history    humkyung 2018.05.09 append only nearest size attribute
1077
    '''
1078
    def connectAttribute(self, attributes, clear=True):
1079
        import math
1080
        from EngineeringTextItem import QEngineeringTextItem
1081
        from QEngineeringSizeTextItem import QEngineeringSizeTextItem
1082
        from EngineeringInstrumentItem import QEngineeringInstrumentItem
1083
        from EngineeringValveOperCodeTextItem import QEngineeringValveOperCodeTextItem
1084
        from EngineeringLineItem import QEngineeringLineItem
1085

    
1086
        try:
1087
            if clear:
1088
                if not self.clear_attr_and_assoc_item():
1089
                    return
1090

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

    
1094
            rect = self.sceneBoundingRect()
1095
            dist = max(self.sceneBoundingRect().height(), self.sceneBoundingRect().width()) * ratio
1096
            center = self.sceneBoundingRect().center()
1097

    
1098
            minDist = None
1099
            selected = None
1100

    
1101
            contain_texts = []
1102
            for attr in attributes:
1103
                # size text and operation code text will find owner themselves in findowner method
1104
                if False:  # type(attr) is QEngineeringSizeTextItem or type(attr) is QEngineeringValveOperCodeTextItem:
1105
                    dx = attr.center().x() - center.x()
1106
                    dy = attr.center().y() - center.y()
1107
                    length = math.sqrt(dx * dx + dy * dy)
1108
                    if (length < dist) and (minDist is None or length < minDist):
1109
                        minDist = length
1110
                        selected = attr
1111
                elif False:#type(attr) is QEngineeringInstrumentItem:
1112
                    if not attr.is_connected():
1113
                        dx = attr.center().x() - center.x()
1114
                        dy = attr.center().y() - center.y()
1115
                        if math.sqrt(dx * dx + dy * dy) < dist:
1116
                            if self.add_assoc_item(attr):
1117
                                attr.owner = self
1118
                elif issubclass(type(attr), QEngineeringTextItem):
1119
                    if rect.contains(attr.center()):
1120
                        dx = attr.center().x() - center.x()
1121
                        dy = attr.center().y() - center.y()
1122
                        length = math.sqrt(dx * dx + dy * dy)
1123
                        contain_texts.append([length, attr])
1124
                elif type(attr) is QEngineeringLineItem:
1125
                    length = attr.distanceTo([center.x(), center.y()])
1126
                    if (length < dist) and (minDist is None or length < minDist):
1127
                        minDist = length
1128
                        selected = attr
1129

    
1130
            if contain_texts:
1131
                num = len([_attr for _attr in self.getAttributes(findOwner=True) if _attr.AttributeType == 'Text Item'])
1132
                contain_texts = sorted(contain_texts, key=lambda param: param[0])
1133
                index = 0
1134
                for _, attr in contain_texts:
1135
                    if index >= num:
1136
                        break
1137
                    if self.add_assoc_item(attr):
1138
                        attr.owner = self  # set owner of text
1139
                    index += 1
1140

    
1141
            if selected is not None:
1142
                self.add_assoc_item(selected)
1143

    
1144
        except Exception as ex:
1145
            from App import App
1146
            message = 'error occurred({}) in {}:{}'.format(ex, sys.exc_info()[-1].tb_frame.f_code.co_filename,
1147
                                                          sys.exc_info()[-1].tb_lineno)
1148
            App.mainWnd().addMessage.emit(MessageType.Error, message)
1149

    
1150
    '''
1151
        @brief      start rotating
1152
        @author     euisung
1153
        @date       2019.04.16
1154
    '''
1155

    
1156
    def mouseDoubleClickEvent(self, event):
1157
        if not hasattr(self, '_rotating'):
1158
            self._rotating = self.rotation()
1159
            self.grabMouse()
1160

    
1161
    def toXml(self):
1162
        """
1163
        generate xml code
1164
        :return:
1165
        """
1166
        import uuid
1167
        from xml.etree.ElementTree import Element, SubElement, dump, ElementTree
1168
        from EngineeringAbstractItem import QEngineeringAbstractItem
1169
        from EngineeringTextItem import QEngineeringTextItem
1170
        from EngineeringSpecBreakItem import QEngineeringSpecBreakItem
1171
        from SymbolAttr import SymbolAttr
1172

    
1173
        try:
1174
            node = Element('SYMBOL')
1175
            node.attrib['Converted'] = str(self.converted)
1176
            uidNode = Element('UID')
1177
            uidNode.text = str(self.uid)
1178
            node.append(uidNode)
1179

    
1180
            dbUidNode = Element('DBUID')
1181
            dbUidNode.text = str(self.dbUid)
1182
            node.append(dbUidNode)
1183

    
1184
            nameNode = Element('NAME')
1185
            nameNode.text = self.name
1186
            node.append(nameNode)
1187

    
1188
            attributeValueNode = Element('ASSOCIATIONS')
1189
            for key, value in self._associations.items():
1190
                for assoc in value:
1191
                    assoc_node = Element('ASSOCIATION')
1192
                    assoc_node.attrib['TYPE'] = str(key)
1193
                    assoc_node.text = str(assoc)
1194
                    attributeValueNode.append(assoc_node)
1195
            node.append(attributeValueNode)
1196

    
1197
            typeNode = Element('TYPE')
1198
            typeNode.text = self.type
1199
            node.append(typeNode)
1200

    
1201
            # write owner's uid to xml
1202
            ownerNode = Element('OWNER')
1203
            if self.owner is not None:
1204
                ownerNode.text = str(self.owner)
1205
            else:
1206
                ownerNode.text = 'None'
1207
            node.append(ownerNode)
1208
            # up to here
1209

    
1210
            originNode = Element('ORIGINALPOINT')
1211
            if not self.transformOriginPoint().isNull():
1212
                origin = self.mapToScene(self.transformOriginPoint())
1213
                originNode.text = f"{origin.x()},{origin.y()}"
1214
            else:
1215
                origin = self.origin
1216
                originNode.text = f"{origin[0]},{origin[1]}"
1217
            node.append(originNode)
1218

    
1219
            connectorsNode = Element('CONNECTORS')
1220
            for connector in self.connectors:
1221
                connectorsNode.append(connector.toXml())
1222
            node.append(connectorsNode)
1223

    
1224
            connectionNode = Element('CONNECTIONPOINT')
1225
            connection_point = []
1226
            if self.connectors is not None:
1227
                for connector in self.connectors:
1228
                    connection_point.append(repr(connector))
1229
            connectionNode.text = '/'.join(connection_point)
1230
            node.append(connectionNode)
1231

    
1232
            locNode = Element('LOCATION')
1233
            locNode.text = f'{self.loc[0]},{self.loc[1]}'
1234
            '''
1235
            # calculate symbol's left-top corner
1236
            transform = QTransform()
1237
            transform.translate(self.scenePos().x(), self.scenePos().y())
1238
            transform.rotateRadians(-self.angle)
1239
            loc = transform.map(QPointF(self.symbolOrigin[0], self.symbolOrigin[1]))
1240
            # up to here
1241
            locNode.text = '{},{}'.format(loc.x() - self.symbolOrigin[0], loc.y() - self.symbolOrigin[1])
1242
            '''
1243
            node.append(locNode)
1244

    
1245
            sizeNode = Element('SIZE')
1246
            sizeNode.text = f'{self.size[0]},{self.size[1]}'
1247
            node.append(sizeNode)
1248

    
1249
            angleNode = Element('ANGLE')
1250
            angleNode.text = str(math.radians(self.rotation())) if self.scene() else str(self.angle)
1251
            node.append(angleNode)
1252

    
1253
            parentSymbolNode = Element('PARENT')
1254
            parentSymbolNode.text = str(self.parentSymbol)
1255
            node.append(parentSymbolNode)
1256

    
1257
            childSymbolNode = Element('CHILD')
1258
            childSymbolNode.text = str(self.childSymbol)
1259
            node.append(childSymbolNode)
1260

    
1261
            hasInstrumentLabelNode = Element('HASINSTRUMENTLABEL')
1262
            hasInstrumentLabelNode.text = str(self.hasInstrumentLabel)
1263
            node.append(hasInstrumentLabelNode)
1264

    
1265
            areaNode = Element('AREA')
1266
            areaNode.text = self.area
1267
            node.append(areaNode)
1268

    
1269
            flipNode = Element('FLIP')
1270
            flipNode.text = str(self.flip)
1271
            node.append(flipNode)
1272

    
1273
            if self.hit_ratio:
1274
                ratioNode = Element('RATIO')
1275
                ratioNode.text = str(self.hit_ratio)
1276
                node.append(ratioNode)
1277

    
1278
            properties_node = Element('PROPERTIES')
1279
            for prop, value in self.properties.items():
1280
                prop_node = prop.toXml()
1281
                prop_node.text = str(value) if value else ''
1282
                properties_node.append(prop_node)
1283
            node.append(properties_node)
1284

    
1285
            attributesNode = Element('SYMBOLATTRIBUTES')
1286
            _attrs = self.getAttributes()
1287
            for attr in _attrs:
1288
                if type(attr) is SymbolAttr:
1289
                    _node = attr.toXml()
1290
                    if attr.AttributeType != 'Spec':
1291
                        _node.text = str(_attrs[attr])
1292
                    elif attr.AttributeType == 'Spec':
1293
                        _node.text = str(self.attrs[attr][0]) + ',' + str(self.attrs[attr][1])
1294
                    attributesNode.append(_node)
1295

    
1296
            node.append(attributesNode)
1297

    
1298
            if hasattr(self, 'currentPointModeIndex'):
1299
                currentPointModeIndexNode = Element('CURRENTPOINTMODEINDEX')
1300
                currentPointModeIndexNode.text = str(self.currentPointModeIndex) if self.currentPointModeIndex else ''
1301
                node.append(currentPointModeIndexNode)
1302
        except Exception as ex:
1303
            from App import App
1304
            message = f'error occurred({ex}) in {sys.exc_info()[-1].tb_frame.f_code.co_filename}:' \
1305
                      f'{sys.exc_info()[-1].tb_lineno}'
1306
            App.mainWnd().addMessage.emit(MessageType.Error, message)
1307

    
1308
            return None
1309

    
1310
        return node
1311

    
1312
    @staticmethod
1313
    def from_database(component):
1314
        """ create a item related to given component from database """
1315
        import uuid
1316
        from EngineeringSpecBreakItem import QEngineeringSpecBreakItem
1317
        from EngineeringEndBreakItem import QEngineeringEndBreakItem
1318
        from SymbolAttr import SymbolAttr
1319
        item = None
1320

    
1321
        try:
1322
            app_doc_data = AppDocData.instance()
1323

    
1324
            uid = component['UID']
1325
            pt = [float(component['X']), float(component['Y'])]
1326
            size = [float(component['Width']), float(component['Height'])]
1327

    
1328
            dbUid = int(component['Symbol_UID'])
1329
            dbData = app_doc_data.getSymbolByQuery('UID', dbUid)
1330
            name = dbData.sName
1331
            _type = dbData.sType
1332
            angle = float(component['Rotation'])
1333
            origin = [float(x) for x in component['SceneOriginPoint'].split(',')] if component['SceneOriginPoint'] is not None else pt
1334
            connPts = []
1335
            if component['ConnectionPoint']:
1336
                if dbData:
1337
                    db_conn = dbData.connectionPoint.split('/')
1338
                    db_symbol_num = [conn.split(',')[3] for conn in db_conn]
1339
                index = 0
1340
                for conn_pt in component['ConnectionPoint'].split('/'):
1341
                    tokens = conn_pt.split(',')
1342
                    connPts.append(('AUTO', float(tokens[0]), float(tokens[1]), '0') if len(tokens) == 2 else
1343
                                   (tokens[0], float(tokens[1]), float(tokens[2]), '0') if len(tokens) == 3 else
1344
                                   (tokens[0], float(tokens[1]), float(tokens[2]), tokens[3] if dbData is None else db_symbol_num[index]))
1345
                    index += 1
1346

    
1347
            baseSymbol = dbData.baseSymbol
1348

    
1349
            childSymbolNode = component['AdditionalSymbol']
1350
            childSymbol = childSymbolNode if childSymbolNode is not None else ''
1351

    
1352
            owner = component['Owner'] if component['Owner'] is not None and component['Owner'] != 'None' else None
1353

    
1354
            hasInstrumentLabel = dbData.hasInstrumentLabel
1355

    
1356
            flipLabelNode = component['Flip']
1357
            flipLabel = int(flipLabelNode) if flipLabelNode is not None else 0
1358

    
1359
            project = app_doc_data.getCurrentProject()
1360
            svgFilePath = os.path.join(project.getSvgFilePath(), _type, name + '.svg')
1361
            if os.path.isfile(svgFilePath):
1362
                item = SymbolSvgItem.createItem(_type, name, svgFilePath, uid, owner=owner, flip=flipLabel)
1363
                item.setVisible(False)
1364

    
1365
                # if additional symbol was changed, change symbol info
1366
                symbolInfo = None
1367
                if dbUid is None:
1368
                    symbolInfo = app_doc_data.getSymbolByQuery('name', name)
1369
                else:
1370
                    symbolInfo = app_doc_data.getSymbolByQuery('UID', dbUid)
1371
                if symbolInfo:
1372
                    childSymbol = symbolInfo.additionalSymbol
1373

    
1374
                if item.buildItem(name, _type, angle, pt, size, origin, connPts, baseSymbol, childSymbol,
1375
                                  hasInstrumentLabel, dbUid=dbUid):
1376
                    pass
1377
                else:
1378
                    return None
1379

    
1380
                for key in item._properties.keys():
1381
                    for compo in component.keys():
1382
                        if key.Attribute == compo:
1383
                            item._properties[key] = key.parse_value(component[key.Attribute]) if component[
1384
                                key.Attribute] else ''
1385

    
1386
                ## assign area
1387
                areaNode = component['Area']
1388
                if areaNode is None:
1389
                    for area in app_doc_data.getAreaList():
1390
                        if area.contains(pt):
1391
                            item.area = area.name
1392
                            break
1393
                else:
1394
                    item.area = areaNode
1395
                ## up to here
1396

    
1397
                connectors = app_doc_data.get_component_connectors(uid)
1398
                if connectors:
1399
                    iterIndex = 0
1400
                    for connector in connectors:
1401
                        item.connectors[iterIndex].parse_record(connector)
1402
                        iterIndex += 1
1403

    
1404
                # get associations 
1405
                associations = app_doc_data.get_component_associations(uid)
1406
                if associations:
1407
                    for assoc in associations:
1408
                        _attrType = assoc['Type']
1409
                        if not _attrType in item._associations:
1410
                            item._associations[_attrType] = []
1411
                        item._associations[_attrType].append(
1412
                            uuid.UUID(assoc['Association']) if assoc['Association'] != 'None' else None)
1413
                # up to here
1414

    
1415
                attributes = app_doc_data.get_component_attributes(uid)
1416
                if attributes:
1417
                    for attr in attributes:
1418
                        _attr = SymbolAttr.from_record(attr)
1419
                        if type(item) is not QEngineeringSpecBreakItem and type(item) is not QEngineeringEndBreakItem:
1420
                            item.attrs[_attr] = attr['Value']
1421
                        else:
1422
                            if _attr.AttributeType == 'Spec':
1423
                                item.attrs[_attr] = [attr['Value'].split(',')[0], attr['Value'].split(',')[1]]
1424
                            else:
1425
                                item.attrs[_attr] = attr['Value']
1426
                            '''
1427
                            elif _attr.Attribute == 'UpStream':
1428
                                for initKey in item.attrs.keys():
1429
                                    if initKey.Attribute == 'UpStream':
1430
                                        item.attrs[initKey] = attr['Value']
1431
                            elif _attr.Attribute == 'DownStream':
1432
                                for initKey in item.attrs.keys():
1433
                                    if initKey.Attribute == 'DownStream':
1434
                                        item.attrs[initKey] = attr['Value']
1435
                            '''
1436

    
1437
                currentPointModeIndex = component['OriginIndex']
1438
                if currentPointModeIndex is not None:
1439
                    item.currentPointModeIndex = int(currentPointModeIndex)
1440
        except Exception as ex:
1441
            from App import App
1442
            message = 'error occurred({}) in {}:{}'.format(ex, sys.exc_info()[-1].tb_frame.f_code.co_filename,
1443
                                                          sys.exc_info()[-1].tb_lineno)
1444
            App.mainWnd().addMessage.emit(MessageType.Error, message)
1445

    
1446
            return None
1447

    
1448
        return item
1449

    
1450
    '''
1451
        @brief      parse xml code
1452
        @author     humkyung
1453
        @date       2018.07.20
1454
        @history    humkyung 2018.07.20 parse uid from xml node
1455
                    humkyung 2018.07.23 parse connected item's uid from xml node
1456
                    kyouho  2018.07.31 
1457
                    humkyung 2018.09.06 assign area to item
1458
    '''
1459

    
1460
    @staticmethod
1461
    def fromXml(node):
1462
        import uuid
1463
        from EngineeringSpecBreakItem import QEngineeringSpecBreakItem
1464
        from EngineeringEndBreakItem import QEngineeringEndBreakItem
1465
        from SymbolAttr import SymbolAttr
1466
        item = None
1467

    
1468
        try:
1469
            appDocData = AppDocData.instance()
1470

    
1471
            uidNode = node.find('UID')
1472
            uid = uidNode.text if uidNode is not None else uuid.uuid4()  # generate UUID
1473

    
1474
            pt = [float(x) for x in node.find('LOCATION').text.split(',')]
1475
            size = [float(x) for x in node.find('SIZE').text.split(',')]
1476

    
1477
            dbUidNode = node.find('DBUID')
1478
            dbUid = int(dbUidNode.text) if dbUidNode is not None else None
1479
            dbData = None
1480
            if dbUid:
1481
                dbData = appDocData.getSymbolByQuery('UID', dbUid)
1482
            name = node.find('NAME').text if dbData is None else dbData.sName
1483

    
1484
            angle = float(node.find('ANGLE').text)
1485
            _type = node.find('TYPE').text if dbData is None else dbData.sType
1486
            origin = [float(x) for x in node.find('ORIGINALPOINT').text.split(',')]
1487
            connPts = []
1488
            if node.find('CONNECTIONPOINT').text is not None:
1489
                if dbData:
1490
                    db_conn = dbData.connectionPoint.split('/')
1491
                    db_symbol_num = [conn.split(',')[3] for conn in db_conn]
1492
                index = 0
1493
                for conn_pt in node.find('CONNECTIONPOINT').text.split('/'):
1494
                    tokens = conn_pt.split(',')
1495
                    connPts.append(('AUTO', float(tokens[0]), float(tokens[1]), '0') if len(tokens) == 2 else
1496
                                   (tokens[0], float(tokens[1]), float(tokens[2]), '0') if len(tokens) == 3 else
1497
                                   (tokens[0], float(tokens[1]), float(tokens[2]), tokens[3] if dbData is None else db_symbol_num[index]))
1498
                    index += 1
1499
            baseSymbol = node.find('PARENT').text if dbData is None else dbData.baseSymbol
1500
            childSymbolNode = node.find('CHILD')
1501
            childSymbol = ''
1502
            if childSymbolNode is not None:
1503
                childSymbol = childSymbolNode.text
1504

    
1505
            ownerNode = node.find('OWNER')
1506
            owner = ownerNode.text if ownerNode is not None and ownerNode.text != 'None' else None
1507

    
1508
            hasInstrumentLabelNode = node.find('HASINSTRUMENTLABEL')
1509
            hasInstrumentLabel = hasInstrumentLabelNode.text if dbData is None else dbData.hasInstrumentLabel
1510

    
1511
            flipLabelNode = node.find('FLIP')
1512
            flipLabel = int(flipLabelNode.text) if flipLabelNode is not None else 0
1513

    
1514
            ratioNode = node.find('RATIO')
1515
            hit_ratio = float(ratioNode.text) if ratioNode is not None else None
1516

    
1517
            project = appDocData.getCurrentProject()
1518
            svgFilePath = os.path.join(project.getSvgFilePath(), _type, name + '.svg')
1519
            if os.path.isfile(svgFilePath):
1520
                item = SymbolSvgItem.createItem(_type, name, svgFilePath, uid, owner=owner, flip=flipLabel)
1521
                item.setVisible(False)
1522

    
1523
                # if additional symbol was changed, change symbol info
1524
                symbolInfo = None
1525
                if dbUid is None:
1526
                    symbolInfo = appDocData.getSymbolByQuery('name', name)
1527
                else:
1528
                    symbolInfo = appDocData.getSymbolByQuery('UID', dbUid)
1529
                if symbolInfo:
1530
                    childSymbol = symbolInfo.additionalSymbol
1531

    
1532
                if hit_ratio:
1533
                    item.hit_ratio = hit_ratio
1534

    
1535
                if item.buildItem(name, _type, angle, pt, size, origin, connPts, baseSymbol, childSymbol,
1536
                                  hasInstrumentLabel, dbUid=dbUid):
1537
                    pass
1538
                else:
1539
                    return None
1540

    
1541
                for prop_node in node.iter('PROPERTY'):
1542
                    matches = [prop for prop in item._properties.keys() if
1543
                               prop.Attribute == prop_node.attrib['Attribute']]
1544
                    if matches:
1545
                        item._properties[matches[0]] = matches[0].parse_value(prop_node.text) if prop_node.text else ''
1546

    
1547
                # assign area
1548
                areaNode = node.find('AREA')
1549
                if areaNode is None:
1550
                    for area in appDocData.getAreaList():
1551
                        if area.contains(pt):
1552
                            item.area = area.name
1553
                            break
1554
                else:
1555
                    item.area = areaNode.text
1556
                # up to here
1557

    
1558
                connectors = node.find('CONNECTORS')
1559
                if connectors is not None:
1560
                    iterIndex = 0
1561
                    for connector in connectors.iter('CONNECTOR'):
1562
                        item.connectors[iterIndex].parse_xml(connector)
1563
                        iterIndex += 1
1564

    
1565
                # get associations 
1566
                attributeValue = node.find('ASSOCIATIONS')
1567
                if attributeValue is not None:
1568
                    for assoc in attributeValue.iter('ASSOCIATION'):
1569
                        _attrType = assoc.attrib['TYPE']
1570
                        if not _attrType in item._associations:
1571
                            item._associations[_attrType] = []
1572
                        item._associations[_attrType].append(uuid.UUID(assoc.text) if assoc.text != 'None' else None)
1573
                # up to here
1574

    
1575
                attributes = node.find('SYMBOLATTRIBUTES')
1576
                if attributes is not None:
1577
                    '''
1578
                    ## for old spec break item that has not uid currectly, may not necessary new data
1579
                    if _type == 'Segment Breaks':
1580
                        specBreakAttrs = appDocData.getSymbolAttribute('Segment Breaks')
1581
                    ## up to here 1
1582
                    '''
1583
                    for attr in attributes.iter('ATTRIBUTE'):
1584
                        _attr = SymbolAttr.fromXml(attr)
1585
                        if type(item) is not QEngineeringSpecBreakItem and type(item) is not QEngineeringEndBreakItem:
1586
                            item.attrs[_attr] = attr.text
1587
                        else:
1588
                            '''
1589
                            ## for old spec break item that has not uid currectly, may not necessary new data
1590
                            matchAttr = [cAttr for cAttr in specBreakAttrs if _attr.Attribute == cAttr.Attribute]
1591
                            matchAttr = matchAttr[0] if matchAttr else None
1592
                            if matchAttr:
1593
                                _attr.UID = matchAttr.UID
1594
                                _attr.DisplayAttribute = matchAttr.DisplayAttribute
1595
                                _attr.AttributeType = matchAttr.AttributeType
1596
                                _attr.AttrAt = matchAttr.AttrAt
1597
                                _attr.Expression = matchAttr.Expression
1598
                                _attr.Length = matchAttr.Length
1599
                            # up to here 2
1600
                            '''
1601
                            if _attr.AttributeType == 'Spec':
1602
                                item.attrs[_attr] = [attr.text.split(',')[0], attr.text.split(',')[1]]
1603
                            else:
1604
                                '''
1605
                                # for old spec break item that has not uid currectly, may not necessary new data
1606
                                _attr.AssocItem = uuid.UUID(attr.text) if attr.text and attr.text != 'None' else None
1607
                                # up to here 3
1608
                                '''
1609
                                item.attrs[_attr] = attr.text
1610
                            '''
1611
                            elif _attr.Attribute == 'UpStream':
1612
                                for initKey in item.attrs.keys():
1613
                                    if initKey.Attribute == 'UpStream':
1614
                                        item.attrs[initKey] = attr.text
1615
                            elif _attr.Attribute == 'DownStream':
1616
                                for initKey in item.attrs.keys():
1617
                                    if initKey.Attribute == 'DownStream':
1618
                                        item.attrs[initKey] = attr.text
1619
                            '''
1620

    
1621
                currentPointModeIndex = node.find('CURRENTPOINTMODEINDEX')
1622
                if currentPointModeIndex is not None:
1623
                    item.currentPointModeIndex = int(currentPointModeIndex.text) if currentPointModeIndex.text else 0
1624
        except Exception as ex:
1625
            from App import App
1626
            message = 'error occurred({}) in {}:{}'.format(ex, sys.exc_info()[-1].tb_frame.f_code.co_filename,
1627
                                                          sys.exc_info()[-1].tb_lineno)
1628
            App.mainWnd().addMessage.emit(MessageType.Error, message)
1629

    
1630
            return None
1631

    
1632
        return item
1633

    
1634
    def to_svg(self, parent) -> list:
1635
        """convert symbol svg item to svg"""
1636
        import re
1637
        from xml.etree.ElementTree import Element, SubElement, dump, ElementTree
1638
        from App import App
1639

    
1640
        res = []
1641
        try:
1642
            app_doc_data = AppDocData.instance()
1643
            prj = app_doc_data.getCurrentProject()
1644

    
1645
            node = Element('g')
1646
            node.attrib['id'] = str(self.uid)
1647
            node.attrib['class'] = self._class
1648

    
1649
            except_pattern = re.compile('[^a-zA-Z0-9-_]')
1650
            for attr, value in self.getAttributes().items():
1651
                node.attrib[re.sub(except_pattern, '_', attr.Attribute)] = str(value) if value else ''
1652
            trans = self.sceneTransform()
1653
            node.attrib['transform'] = f"matrix(" \
1654
                                       f"{trans.m11()},{trans.m12()}," \
1655
                                       f"{trans.m21()},{trans.m22()}," \
1656
                                       f"{trans.m31()},{trans.m32()}" \
1657
                                       f")"
1658

    
1659
            node_list = self._document.elementsByTagName('path')
1660
            for at in range(node_list.count()):
1661
                path = Element('path')
1662
                path.attrib['d'] = node_list.item(at).attributes().namedItem('d').nodeValue()
1663
                path.attrib['transform'] = self._document.elementsByTagName('g').item(0).attributes().namedItem('transform').nodeValue()
1664
                node.append(path)
1665

    
1666
            for assoc in self.associations():
1667
                assoc_node = assoc.to_svg(parent=self)
1668
                node.extend(assoc_node)
1669

    
1670
            res.append(node)
1671
        except Exception as ex:
1672
            from App import App
1673
            message = 'error occurred({}) in {}:{}'.format(ex, sys.exc_info()[-1].tb_frame.f_code.co_filename,
1674
                                                          sys.exc_info()[-1].tb_lineno)
1675
            App.mainWnd().addMessage.emit(MessageType.Error, message)
1676

    
1677
        return res
1678

    
1679
    '''
1680
        @brief      create item corresponding to given type
1681
        @author     humkyung
1682
        @date       2018.05.02
1683
        @history    2018.05.08  Jeongwoo    Change type name (Piping OPC''S → Piping OPC's)
1684
                    humkyung 2018.05.10 change symbol's color to blue
1685
                    humkyung 2018.07.19 create nozzle instance if type is 'Nozzles'
1686
    '''
1687

    
1688
    @staticmethod
1689
    def createItem(type: str, name: str, path: str, uid=None, owner=None, flip=0):
1690
        from QEngineeringOPCItem import QEngineeringOPCItem
1691
        from EngineeringEquipmentItem import QEngineeringEquipmentItem
1692
        from EngineeringInstrumentItem import QEngineeringInstrumentItem
1693
        from EngineeringNozzleItem import QEngineeringNozzleItem
1694
        from EngineeringSpecBreakItem import QEngineeringSpecBreakItem
1695
        from EngineeringReducerItem import QEngineeringReducerItem
1696
        from EngineeringErrorItem import QEngineeringErrorItem
1697
        from EngineeringEndBreakItem import QEngineeringEndBreakItem
1698
        from EngineeringFlowMarkItem import QEngineeringFlowMarkItem
1699
        from AppDocData import AppDocData
1700
        import uuid
1701

    
1702
        app_doc_data = AppDocData.instance()
1703

    
1704
        item = None
1705
        try:
1706
            cateogry = app_doc_data.getSymbolCategoryByType(type)
1707
            if type == "Piping OPC's" or type == "Instrument OPC's":
1708
                item = QEngineeringOPCItem(path, uid, flip=flip)
1709
            elif type == 'Nozzles':
1710
                item = QEngineeringNozzleItem(path, uid, flip=flip)
1711
            elif cateogry == 'Equipment' or cateogry == 'Equipment Components' or cateogry == 'Package':
1712
                item = QEngineeringEquipmentItem(path, uid, flip=flip)
1713
            elif cateogry == 'Instrumentation':
1714
                item = QEngineeringInstrumentItem(path, uid, flip=flip)
1715
            elif type == 'Segment Breaks':
1716
                item = QEngineeringSpecBreakItem(path, uid, flip=flip)
1717
            elif type == 'Reducers':
1718
                item = QEngineeringReducerItem(path, uid, flip=flip)
1719
            elif type == 'Error':
1720
                item = QEngineeringErrorItem(path, uid, flip=flip)
1721
            elif type == 'End Break':
1722
                item = QEngineeringEndBreakItem(path, uid, flip=flip)
1723
            # elif type == 'Flow Mark':
1724
            #    item = QEngineeringFlowMarkItem(path, uid, flip=flip)
1725
            else:
1726
                item = SymbolSvgItem(name, path, uid, flip=flip)
1727

    
1728
            if owner is not None:
1729
                item.owner = uuid.UUID(owner)
1730

    
1731
        except Exception as ex:
1732
            from App import App
1733
            from AppDocData import MessageType
1734

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

    
1739
        return item
1740

    
1741
    def setColor(self, color):
1742
        """change svg item's color"""
1743
        if self._document:# and color != self.__color:
1744
            self.changeAttributes('fill', color)
1745
            self.changeAttributes('stroke', color)
1746
            self.renderer().load(self._document.toByteArray())
1747
            self.__color = color
1748
            #self.update()
1749

    
1750
    '''
1751
        @brief  get attributes from svg file
1752
        @author humkyung
1753
        @date   2019.03.08
1754
    '''
1755

    
1756
    def get_attribute(self, attName):
1757
        root = self._document.documentElement()
1758
        node = root.firstChild()
1759
        while not node.isNull():
1760
            if node.isElement():
1761
                element = node.toElement()
1762
                if element.hasAttribute(attName):
1763
                    return element.attribute(attName)
1764

    
1765
                if element.hasChildNodes():
1766
                    att_val = self.recursive_get_attribute(element.firstChild(), attName)
1767
                    if att_val is not None: return att_val
1768

    
1769
            node = node.nextSibling()
1770

    
1771
        return None
1772

    
1773
    '''
1774
        @brief  get recursively attribute
1775
        @author humkyung
1776
        @date   2019.03.08
1777
    '''
1778

    
1779
    def recursive_get_attribute(self, node, attName):
1780
        while not node.isNull():
1781
            if node.isElement():
1782
                element = node.toElement()
1783
                if element.hasAttribute(attName):
1784
                    return element.attribute(attName)
1785

    
1786
                if node.hasChildNodes():
1787
                    att_val = self.recursive_get_attribute(node.firstChild(), attName)
1788
                    if att_val is not None: return att_val
1789

    
1790
            node = node.nextSibling()
1791

    
1792
        return None
1793

    
1794
    '''
1795
        @brief  change attributes
1796
        @author humkyung
1797
        @date   2018.05.10
1798
    '''
1799

    
1800
    def changeAttributes(self, attName, attValue):
1801
        root = self._document.documentElement()
1802
        node = root.firstChild()
1803
        while not node.isNull():
1804
            if node.isElement():
1805
                element = node.toElement()
1806
                if element.hasAttribute(attName):
1807
                    element.setAttribute(attName, attValue)
1808

    
1809
                if element.hasChildNodes():
1810
                    recursiveChangeAttributes(element.firstChild(), attName, attValue)
1811

    
1812
            node = node.nextSibling()
1813

    
1814
    '''
1815
        @brief  change attribute
1816
        @author humkyung
1817
        @date   2018.05.10
1818
    '''
1819

    
1820
    def recursiveChangeAttributes(self, node, attName, attValue):
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 node.hasChildNodes():
1828
                    recursiveChangeAttributes(node.firstChild(), attName, attValue)
1829

    
1830
            node = node.nextSibling()
1831

    
1832
    '''
1833
        @brief  draw rect when item is selected
1834
        @author humkyung
1835
        @date   2018.07.07
1836
    '''
1837

    
1838
    def drawFocusRect(self, painter):
1839
        self.focuspen = QPen(Qt.DotLine)
1840
        self.focuspen.setColor(Qt.black)
1841
        self.focuspen.setWidthF(1.5)
1842
        hilightColor = QColor(255, 0, 0, 127)
1843
        painter.setBrush(QBrush(hilightColor))
1844
        painter.setPen(self.focuspen)
1845
        painter.drawRect(self.boundingRect())
1846

    
1847
    '''
1848
        @brief  override paint(draw connection points)
1849
        @author humkyung
1850
        @date   2018.04.21
1851
    '''
1852

    
1853
    def paint(self, painter, options=None, widget=None):
1854
        from EngineeringAbstractItem import QEngineeringAbstractItem
1855
        from EngineeringTextItem import QEngineeringTextItem
1856

    
1857
        self.setColor(self.getColor())
1858

    
1859
        painter.setClipRect(options.exposedRect)
1860
        QGraphicsSvgItem.paint(self, painter, options, widget)
1861
        '''
1862
        # not used
1863
        for attr in self.attrs:
1864
            if issubclass(type(attr), QEngineeringTextItem):
1865
                color = QEngineeringAbstractItem.HOVER_COLOR if self.hover else (
1866
                    self._owner._color if self._owner else QEngineeringAbstractItem.DEFAULT_COLOR)
1867
                attr.setColor(color)
1868
            elif issubclass(type(attr), SymbolSvgItem):
1869
                attr.setColor(self.getColor())
1870
                attr.update()
1871
        '''
1872

    
1873
        if self.isSelected():
1874
            self.drawFocusRect(painter)
1875

    
1876
    def addSvgItemToScene(self, scene, undoable: bool = False) -> None:
1877
        """Add Svg Item into ImageViewer's Scene"""
1878
        if self.flip:
1879
            self.flip_symbol()
1880

    
1881
        self.setTransformOriginPoint(QPointF(self.symbolOrigin[0], self.symbolOrigin[1]))
1882

    
1883
        self.moveBy(-self.symbolOrigin[0], -self.symbolOrigin[1])
1884
        self.setRotation(math.degrees(self.angle))
1885
        self.moveBy(self.origin[0], self.origin[1])
1886

    
1887
        scene.addItem(self)
1888
        self.size[0], self.size[1] = round(self.sceneBoundingRect().width()), round(self.sceneBoundingRect().height())
1889

    
1890
        if undoable:
1891
            from CreateCommand import CreateCommand
1892
            self.scene().undo_stack.push(CreateCommand(self.scene(), [self,]))
1893

    
1894
    '''
1895
        @brief      
1896
        @author     humkyung
1897
        @date       2018.07.27
1898
    '''
1899
    def onConnectorPosChaned(self, connector):
1900
        pass
1901

    
1902
    '''
1903
        @brief      set connector
1904
        @author     kyouho
1905
        @date       2018.07.26
1906
    '''
1907
    def setConnector(self, index):
1908
        connector = QEngineeringConnectorItem(parent=self, index=index)
1909
        connector.setParentItem(self)
1910
        self.connectors.append(connector)
1911

    
1912
    def refreshConnector(self):
1913
        for connector in self.connectors:
1914
            connector.buildItem()
1915

    
1916
    '''
1917
        @brief      Delete svg item
1918
        @author     Jeongwoo
1919
        @date       2018.05.25
1920
    '''
1921
    def deleteSvgItemFromScene(self):
1922
        ''' not used '''
1923
        for connector in self.connectors:
1924
            if connector.connectedItem is not None:
1925
                try:
1926
                    connector.connectedItem.removeSymbol(self)
1927
                except Exception as ex:
1928
                    from App import App
1929
                    from AppDocData import MessageType
1930

    
1931
                    message = 'error occurred({}) in {}:{}'.format(ex, sys.exc_info()[-1].tb_frame.f_code.co_filename,
1932
                                                                  sys.exc_info()[-1].tb_lineno)
1933
                    App.mainWnd().addMessage.emit(MessageType.Error, message)
1934
                break
1935

    
1936
        self.transfer.onRemoved.emit(self)
1937

    
1938
    def flip_symbol(self) -> None:
1939
        """flip symbol"""
1940

    
1941
        transform = QTransform()
1942
        if self.flip is 1:
1943
            rect = self.boundingRect()
1944
            transform.scale(-1.0, 1.0)
1945
            transform.translate(-2 * self.symbolOrigin[0], 0)
1946

    
1947
        self.setTransform(transform)
1948

    
1949
    def rotate(self, angle: float) -> None:
1950
        """rotate symbol by given angle in radian"""
1951
        import math
1952
        self.setRotation(math.degrees(angle))
1953

    
1954
    '''
1955
        @brief      change Conn point 
1956
        @author     kyouho
1957
        @date       2018.07.25
1958
    '''
1959
    def changeConnPoint(self):
1960
        if len(self.connectors) == 2:
1961
            conn1Item = self.connectors[0].connectedItem
1962
            conn2Item = self.connectors[1].connectedItem
1963
            self.connectors[0].connectedItem = conn2Item
1964
            self.connectors[1].connectedItem = conn1Item
1965

    
1966
            currentPoint = self.getCurrentPoint()
1967
            #self.reSettingSymbol(currentPoint, self.angle)
1968

    
1969
    '''
1970
        @brief      change standard point
1971
        @author     kyouho
1972
        @date       2018.07.24
1973
    '''
1974
    def changeStandardPoint(self):
1975
        connPtsCount = len(self.connectors)
1976

    
1977
        if self.currentPointModeIndex < connPtsCount:
1978
            self.currentPointModeIndex += 1
1979
        else:
1980
            self.currentPointModeIndex = 0
1981

    
1982
        currentPoint = self.getCurrentPoint()
1983
        #self.reSettingSymbol(currentPoint, self.angle)
1984

    
1985
    def getCurrentPoint(self):
1986
        """return transform origin point"""
1987
        pointList = []
1988
        pointList.append(self.symbolOrigin)
1989
        for connector in self.connectors:
1990
            pointList.append(connector.connectPoint)
1991

    
1992
        return pointList[self.currentPointModeIndex]
1993

    
1994
    def EvaluatedCode(self, old_code, code_name):
1995
        """ return new attribute code """
1996
        from AppDocData import AppDocData
1997
        from CodeTables import CodeTable
1998
        from LineTypeConditions import LineTypeConditions
1999
        from EngineeringLineItem import QEngineeringLineItem
2000
        import re
2001

    
2002
        try:
2003
            code = old_code
2004
            start_item = None
2005
            if self.iType == 19:    # Labels - Symbol
2006
                matches = [assoc for assoc in self.associations() if issubclass(type(assoc), SymbolSvgItem)]
2007
                start_item = matches[0] if matches else self
2008
            else:
2009
                start_item = self
2010

    
2011
            #if not start_item:
2012
            #    return ''
2013

    
2014
            app_doc_data = AppDocData.instance()
2015
            connected_items_lists = app_doc_data._connected_items_lists
2016
            items = [connected_items_list.items for connected_items_list in connected_items_lists.runs if start_item in connected_items_list.items]
2017
            if len(items) != 1:
2018
                return ''
2019
            else:
2020
                items = [item for item in items[0] if issubclass(type(item), SymbolSvgItem)]
2021

    
2022
            table = CodeTable.instance(code_name, inst=True)
2023
            line_types = [condition.name for condition in LineTypeConditions.items()]
2024

    
2025
            new_codes = []
2026
            for value in table.values: # uid, old code, symbol, attribute, new code, expression, priority
2027
                # symbol, line type
2028
                if len(value[3]) == 1 and not value[3][0]:
2029
                    # symbol
2030
                    if not [line_type for line_type in line_types if line_type in value[2]]:
2031
                        for item in items:
2032
                            match = re.search(value[1][0], code, re.DOTALL)
2033
                            if (code in value[1] or (match and match.start() is 0 and match.end() is len(code))) \
2034
                                and item.name in value[2]:
2035
                                dx = item.origin[0] - self.origin[0]
2036
                                dy = item.origin[1] - self.origin[1]
2037
                                length = math.sqrt(dx*dx + dy*dy)
2038
                                if not value[5]:
2039
                                    new_codes.append([length if not value[6] else value[6], '"' + value[4] + '"', item, length])
2040
                                else:
2041
                                    new_codes.append([length if not value[6] else value[6], value[5], item, length])
2042
                    # line
2043
                    else:
2044
                        match = re.search(value[1][0], code, re.DOTALL)
2045
                        types = [conn.connectedItem.lineType for conn in self.connectors if conn.connectedItem and type(conn.connectedItem) is QEngineeringLineItem]
2046
                        if (code in value[1] or (match and match.start() is 0 and match.end() is len(code))) and \
2047
                            [line_type for line_type in value[2] if line_type in types]:
2048
                            if not value[5]:
2049
                                new_codes.append([0 if not value[6] else value[6], '"' + value[4] + '"', None, sys.maxsize])
2050
                            else:
2051
                                new_codes.append([0 if not value[6] else value[6], value[5], None, sys.maxsize])
2052

    
2053
                # self attribute
2054
                elif len(value[2]) == 1 and not value[2][0] and value[3][0]:
2055
                    for key, _value in self.attrs.items():
2056
                        if _value in value[3]:
2057
                            if not value[5]:
2058
                                new_codes.append([0 if not value[6] else value[6], '"' + value[4] + '"', self, sys.maxsize])
2059
                            else:
2060
                                new_codes.append([0 if not value[6] else value[6], value[5], self, sys.maxsize])
2061

    
2062
                # symbol + attribute
2063
                elif value[2][0] and value[3][0]:
2064
                    for item in items:
2065
                        match = re.search(value[1][0], code, re.DOTALL)
2066
                        if (code in value[1] or (match and match.start() is 0 and match.end() is len(code))) \
2067
                            and item.name in value[2]:
2068
                            dx = item.origin[0] - self.origin[0]
2069
                            dy = item.origin[1] - self.origin[1]
2070
                            length = math.sqrt(dx*dx + dy*dy)
2071

    
2072
                            for key, _value in item.attrs.items():
2073
                                if _value in value[3]:
2074
                                    if not value[5]:
2075
                                        new_codes.append([length if not value[6] else value[6], '"' + value[4] + '"', item, length])
2076
                                    else:
2077
                                        new_codes.append([length if not value[6] else value[6], value[5], item, length])
2078
                        
2079
            # default
2080
            for value in table.values:
2081
                match = re.search(value[1][0], code, re.DOTALL)
2082
                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] == '':
2083
                    if not value[5]:
2084
                        new_codes.append([sys.maxsize if not value[6] else value[6], '"' + value[4] + '"', None, sys.maxsize])
2085
                    else:
2086
                        new_codes.append([sys.maxsize if not value[6] else value[6], value[5], None, sys.maxsize])
2087

    
2088
            if new_codes:
2089
                new_codes = sorted(new_codes, key=lambda param: param[0])
2090
                new_codes = [_code for _code in new_codes if _code[0] == new_codes[0][0]]
2091
                new_codes = sorted(new_codes, key=lambda param: param[-1])
2092
                item = new_codes[0][-2]
2093
                new_code = eval(new_codes[0][1])
2094
                return new_code
2095
            else:
2096
                return code
2097
        except Exception as ex:
2098
            from App import App
2099
            from AppDocData import MessageType
2100

    
2101
            message = 'error occurred({}) in {}:{}'.format(ex, sys.exc_info()[-1].tb_frame.f_code.co_filename,
2102
                                                          sys.exc_info()[-1].tb_lineno)
2103
            #App.mainWnd().addMessage.emit(MessageType.Error, str(self.uid) + self.name + message)
2104
            App.mainWnd().addMessage.emit(MessageType.Error, message)
2105

    
2106
def recursiveChangeAttributes(node, attName, attValue):
2107
    while not node.isNull():
2108
        if node.isElement():
2109
            element = node.toElement()
2110
            if element.hasAttribute(attName):
2111
                element.setAttribute(attName, attValue)
2112

    
2113
            if node.hasChildNodes():
2114
                recursiveChangeAttributes(node.firstChild(), attName, attValue)
2115

    
2116
        node = node.nextSibling()
2117

    
2118

    
2119
'''
2120
    @brief      The class transfer pyqtSignal Event. Cause Subclass of QGraphicsRectItem can't use pyqtSignal
2121
    @author     Jeongwoo
2122
    @date       2018.06.18
2123
'''
2124
class Transfer(QObject):
2125
    on_pos_changed = pyqtSignal(QGraphicsItem)
2126
    onRemoved = pyqtSignal(QGraphicsItem)
2127

    
2128
    def __init__(self, parent=None):
2129
        QObject.__init__(self, parent)
2130

    
2131

    
2132
if __name__ == '__main__':
2133
    f = QFile('d:/Projects/DTIPID/DTI_PID/DTI_PID/SG_TEST/svg/ANGLE VALVE.svg')
2134
    f.open(QIODevice.ReadOnly)
2135
    array = f.readAll()
2136
    document = QDomDocument()
2137
    document.setContent(array)
2138

    
2139
    root = document.documentElement()
2140
    node = root.firstChild()
2141
    while not node.isNull():
2142
        if node.isElement():
2143
            element = node.toElement()
2144
            if element.hasAttribute('fill'):
2145
                element.setAttribute('fill', '#FFFFF')
2146

    
2147
            if element.hasChildNodes():
2148
                recursiveChangeAttributes(element.firstChild(), 'fill', '#FFFFF')
2149

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