def __init__(self, name, parent, dataType, direction, **kwargs): QGraphicsWidget.__init__(self) PinBase.__init__(self, name, parent, dataType, direction, **kwargs) self.setParentItem(parent) self.setCursor(QtCore.Qt.CrossCursor) ## context menu for pin self.menu = QMenu() ## Disconnect all connections self.actionDisconnect = self.menu.addAction('disconnect all') self.actionDisconnect.triggered.connect(self.disconnectAll) ## Copy UUID to buffer self.actionCopyUid = self.menu.addAction('copy uid') self.actionCopyUid.triggered.connect(self.saveUidToClipboard) ## Call exec pin self.actionCall = self.menu.addAction('execute') self.actionCall.triggered.connect(self.call) self.newPos = QtCore.QPointF() self.setFlag(QGraphicsWidget.ItemSendsGeometryChanges) self.setCacheMode(self.DeviceCoordinateCache) self.setAcceptHoverEvents(True) self.setZValue(2) self.width = 8 + 1 self.height = 8 + 1 self.hovered = False self.startPos = None self.endPos = None self._container = None self._execPen = QtGui.QPen(Colors.Exec, 0.5, QtCore.Qt.SolidLine) self.setGeometry(0, 0, self.width, self.height) self._dirty_pen = QtGui.QPen(Colors.DirtyPen, 0.5, QtCore.Qt.DashLine, QtCore.Qt.RoundCap, QtCore.Qt.RoundJoin) self.pinImage = QtGui.QImage(':/icons/resources/array.png')
def __init__(self, name, graph, w=8000, color=Colors.NodeBackgrounds, headColor=Colors.NodeNameRect, bUseTextureBg=False): QGraphicsItem.__init__(self) NodeBase.__init__(self, name, graph) self.setCacheMode(QGraphicsItem.DeviceCoordinateCache) self.opt_node_base_color = Colors.NodeBackgrounds self.opt_selected_pen_color = Colors.NodeSelectedPenColor self.opt_pen_selected_type = QtCore.Qt.SolidLine self._left_stretch = 0 self.color = color self.height_offset = 3 self.nodeMainGWidget = QGraphicsWidget(self) self.nodeMainGWidget.setObjectName('{0}MainLayout'.format(name)) self._w = 0 self.h = 40 self.sizes = [0, 0, self.w, self.h, 10, 10] self.w = w self.setFlag(QGraphicsItem.ItemIsMovable) self.setFlag(QGraphicsItem.ItemIsFocusable) self.setFlag(QGraphicsItem.ItemIsSelectable) self.setFlag(QGraphicsItem.ItemSendsGeometryChanges) self.custom_widget_data = {} # node name self.label = weakref.ref(NodeName(self, bUseTextureBg, headColor)) # set node layouts self.nodeMainGWidget.setParentItem(self) # main self.portsMainLayout = QGraphicsLinearLayout(QtCore.Qt.Horizontal) self.portsMainLayout.setSizePolicy(QSizePolicy.Maximum, QSizePolicy.Maximum) self.portsMainLayout.setContentsMargins(1, 1, 1, 1) self.nodeMainGWidget.setLayout(self.portsMainLayout) self.nodeMainGWidget.setX(self.nodeMainGWidget.x()) # inputs layout self.inputsLayout = QGraphicsLinearLayout(QtCore.Qt.Vertical) self.inputsLayout.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Maximum) self.inputsLayout.setContentsMargins(1, 1, 1, 1) self.portsMainLayout.addItem(self.inputsLayout) # outputs layout self.outputsLayout = QGraphicsLinearLayout(QtCore.Qt.Vertical) self.outputsLayout.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Maximum) self.outputsLayout.setContentsMargins(1, 1, 1, 1) self.portsMainLayout.addItem(self.outputsLayout) self.setZValue(1) self.tweakPosition() self.icon = None self._Constraints = {} self.asGraphSides = False self.isTemp = False
def mousePressEvent(self, event): QGraphicsWidget.mousePressEvent(self, event) if self.expanded: self.onCollapsed.emit(self._container) else: self.onExpanded.emit(self._container) self.expanded = not self.expanded self.update()
def addContainer(self): container = QGraphicsWidget() container.setObjectName('{0}PinContainerWidget'.format(self.name)) container.setSizePolicy(QSizePolicy.Minimum, QSizePolicy.Maximum) container.sizeHint(QtCore.Qt.MinimumSize, QtCore.QSizeF(50.0, 10.0)) lyt = QGraphicsLinearLayout() lyt.setSizePolicy(QSizePolicy.Maximum, QSizePolicy.Maximum) lyt.setContentsMargins(1, 1, 1, 1) container.setLayout(lyt) return container
def addGroupContainer(self, portType, groupName="group"): container = QGraphicsWidget() container.setObjectName('{0}PinGroupContainerWidget'.format(self.name)) lyt = QGraphicsLinearLayout() lyt.setOrientation(QtCore.Qt.Vertical) lyt.setSizePolicy(QSizePolicy.Maximum, QSizePolicy.Maximum) lyt.setContentsMargins(1, 1, 1, 1) container.group_name = EditableLabel(name=groupName, node=self, canvas=self.canvasRef()) font = QtGui.QFont('Consolas') font.setBold(True) font.setPointSize(500) container.group_name._font = font container.group_name.nameLabel.setFont(font) container.group_name.nameLabel.update() container.group_name.setObjectName('{0}_GroupConnector'.format( container.group_name)) container.group_name.setContentsMargins(0, 0, 0, 0) container.group_name.setColor(Colors.AbsoluteBlack) grpCon = self.addContainer() container.groupIcon = UIGroupPinBase(container) lyt.addItem(grpCon) container.setLayout(lyt) if portType == PinDirection.Input: container.group_name.nameLabel.setAlignment(QtCore.Qt.AlignLeft | QtCore.Qt.AlignTop) grpCon.layout().addItem(container.groupIcon) grpCon.layout().addItem(container.group_name) else: container.group_name.nameLabel.setAlignment(QtCore.Qt.AlignRight | QtCore.Qt.AlignTop) grpCon.layout().addItem(container.group_name) grpCon.layout().addItem(container.groupIcon) return container
def addContainer(self, portType, head=False): container = QGraphicsWidget() container.setObjectName('{0}PinContainerWidget'.format(self.name)) container.setSizePolicy(QSizePolicy.Minimum, QSizePolicy.Maximum) container.sizeHint(QtCore.Qt.MinimumSize, QtCore.QSizeF(50.0, 10.0)) lyt = QGraphicsLinearLayout() lyt.setSizePolicy(QSizePolicy.Maximum, QSizePolicy.Maximum) lyt.setContentsMargins(1, 1, 1, 1) container.setLayout(lyt) if portType == PinDirection.Input: self.inputsLayout.addItem(container) else: self.outputsLayout.addItem(container) return container
def __init__(self, raw_node): super(UIStickyNote, self).__init__(raw_node) self.color = QtGui.QColor(255, 255, 136) self.color.setAlpha(255) self.labelTextColor = QtGui.QColor(0, 0, 0, 255) self.resizable = True self.roundness = 1 self.textInput = InputTextField( "Text Goes Here", self, singleLine=False) self.textInput.setPos(QtCore.QPointF( 5, self.nodeNameWidget.boundingRect().height())) self.textInput.document().contentsChanged.connect(self.updateSize) self.textInput.editingFinished.connect(self.editingFinished) self.textInput.startEditing.connect(self.startEditing) self.textWidget = QGraphicsWidget() self.textWidget.setGraphicsItem(self.textInput) self.nodeLayout.addItem(self.textWidget) self.NonFormatedText = self.textInput.toPlainText() self.updateSize()
class Node(QGraphicsItem, NodeBase): """ Default node description """ def __init__(self, name, graph, w=80, color=Colors.NodeBackgrounds, headColor=Colors.NodeNameRect, bUseTextureBg=True): QGraphicsItem.__init__(self) NodeBase.__init__(self, name, graph) self.setCacheMode(QGraphicsItem.DeviceCoordinateCache) self.opt_node_base_color = Colors.NodeBackgrounds self.opt_selected_pen_color = Colors.NodeSelectedPenColor self.opt_pen_selected_type = QtCore.Qt.SolidLine self._left_stretch = 0 self.color = color self.height_offset = 3 self.nodeMainGWidget = QGraphicsWidget() self.nodeMainGWidget.setObjectName('{0}MainLayout'.format(name)) self._w = 0 self.h = 40 self.sizes = [0, 0, self.w, self.h, 1, 1] self.w = w self.setFlag(QGraphicsItem.ItemIsMovable) self.setFlag(QGraphicsItem.ItemIsFocusable) self.setFlag(QGraphicsItem.ItemIsSelectable) self.setFlag(QGraphicsItem.ItemSendsGeometryChanges) self.custom_widget_data = {} # node name self.label = weakref.ref(NodeName(self, bUseTextureBg, headColor)) # set node layouts self.nodeMainGWidget.setParentItem(self) # main self.portsMainLayout = QGraphicsLinearLayout(QtCore.Qt.Horizontal) self.portsMainLayout.setSizePolicy(QSizePolicy.Maximum, QSizePolicy.Maximum) self.portsMainLayout.setContentsMargins(1, 1, 1, 1) self.nodeMainGWidget.setLayout(self.portsMainLayout) self.nodeMainGWidget.setX(self.nodeMainGWidget.x()) # inputs layout self.inputsLayout = QGraphicsLinearLayout(QtCore.Qt.Vertical) self.inputsLayout.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Maximum) self.inputsLayout.setContentsMargins(1, 1, 1, 1) self.portsMainLayout.addItem(self.inputsLayout) # outputs layout self.outputsLayout = QGraphicsLinearLayout(QtCore.Qt.Vertical) self.outputsLayout.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Maximum) self.outputsLayout.setContentsMargins(1, 1, 1, 1) self.portsMainLayout.addItem(self.outputsLayout) self.setZValue(1) self.setCursor(QtCore.Qt.OpenHandCursor) self.tweakPosition() self.icon = None @staticmethod def recreate(node): templ = node.serialize() uid = node.uid node.kill() newNode = node.graph().createNode(templ) newNode.uid = uid return newNode @property def w(self): return self._w @w.setter def w(self, value): self._w = value self.sizes[2] = value def call(self, name): if pinName in [ p.name for p in self.outputs.values() if p.dataType is DataTypes.Exec ]: p = self.getPinByName(pinName) return p.call() def getData(self, pinName): if pinName in [p.name for p in self.inputs.values()]: p = self.getPinByName(pinName, PinSelectionGroup.Inputs) return p.getData() def setData(self, pinName, data): if pinName in [p.name for p in self.outputs.values()]: p = self.getPinByName(pinName, PinSelectionGroup.Outputs) p.setData(data) @staticmethod ## Constructs a node from given annotated function and adds it to the canvas def initializeFromFunction(foo, graph): meta = foo.__annotations__['meta'] returnType = returnDefaultValue = None if foo.__annotations__['return'] is not None: returnType, returnDefaultValue = foo.__annotations__['return'] nodeType = foo.__annotations__['nodeType'] fooArgNames = getargspec(foo).args @staticmethod def description(): return foo.__doc__ @staticmethod def category(): return meta['Category'] @staticmethod def keywords(): return meta['Keywords'] def constructor(self, name, graph, **kwargs): Node.__init__(self, name, graph, **kwargs) nodeClass = type( foo.__name__, (Node, ), { '__init__': constructor, 'category': category, 'keywords': keywords, 'description': description }) inst = nodeClass(graph.getUniqNodeName(foo.__name__), graph) if returnType is not None: structClass = type( returnDefaultValue) if returnType == DataTypes.Enum else ENone p = inst.addOutputPin('out', returnType, userStructClass=structClass) p.setData(returnDefaultValue) p.setDefaultValue(returnDefaultValue) # this is array of 'references' outputs will be created for refs = [] outExec = None # iterate over function arguments and create pins according to data types for index in range(len(fooArgNames)): argName = fooArgNames[index] argDefaultValue = foo.__defaults__[index] dataType = foo.__annotations__[argName] structClass = type( argDefaultValue) if dataType == DataTypes.Enum else ENone # tuple means this is reference pin with default value eg - (dataType, defaultValue) if isinstance(dataType, tuple): outRef = inst.addOutputPin(argName, dataType[0], userStructClass=structClass) outRef.setDefaultValue(argDefaultValue) outRef.setData(dataType[1]) refs.append(outRef) else: inp = inst.addInputPin(argName, dataType, userStructClass=structClass) inp.setData(argDefaultValue) inp.setDefaultValue(argDefaultValue) # all inputs affects on all outputs for i in inst.inputs.values(): for o in inst.outputs.values(): pinAffects(i, o) # generate compute method from function def compute(self): # arguments will be taken from inputs kwargs = {} for i in self.inputs.values(): if i.dataType is not DataTypes.Exec: kwargs[i.name] = i.getData() for ref in refs: if ref.dataType is not DataTypes.Exec: kwargs[ref.name] = ref.setData result = foo(**kwargs) if returnType is not None: self.setData('out', result) if nodeType == NodeTypes.Callable: outExec.call() inst.compute = MethodType(compute, inst, Node) # create execs if callable if nodeType == NodeTypes.Callable: inst.addInputPin('inExec', DataTypes.Exec, inst.compute, True, index=0) outExec = inst.addOutputPin('outExec', DataTypes.Exec, inst.compute, True, index=0) return inst @staticmethod def deserialize(data, graph): node = graph.createNode(data) node.uid = uuid.UUID(data['uuid']) node.currentComputeCode = data['computeCode'] # set pins data for inpJson in data['inputs']: pin = node.getPinByName(inpJson['name'], PinSelectionGroup.Inputs) pin.uid = uuid.UUID(inpJson['uuid']) pin.setData(inpJson['value']) if inpJson['bDirty']: pin.setDirty() else: pin.setClean() for outJson in data['outputs']: pin = node.getPinByName(outJson['name'], PinSelectionGroup.Outputs) pin.uid = uuid.UUID(outJson['uuid']) pin.setData(outJson['value']) if outJson['bDirty']: pin.setDirty() else: pin.setClean() return node def tweakPosition(self): value = self.scenePos() self.setX( roundup(value.x() - self.graph().grid_size, self.graph().grid_size)) self.setY( roundup(value.y() - self.graph().grid_size, self.graph().grid_size)) def boundingRect(self): return self.childrenBoundingRect() def itemChange(self, change, value): if change == self.ItemPositionChange: # grid snapping value.setX( roundup( value.x() - self.graph().grid_size + self.graph().grid_size / 3.0, self.graph().grid_size)) value.setY( roundup( value.y() - self.graph().grid_size + self.graph().grid_size / 3.0, self.graph().grid_size)) value.setY(value.y() - 2) return value return QGraphicsItem.itemChange(self, change, value) @staticmethod def description(): return "Default node description" @staticmethod def pinTypeHints(): return {'inputs': [], 'outputs': []} def updateNodeShape(self, label=None): for i in range(0, self.inputsLayout.count()): container = self.inputsLayout.itemAt(i) lyt = container.layout() if lyt: for j in range(0, lyt.count()): lyt.setAlignment(lyt.itemAt(j), QtCore.Qt.AlignLeft | QtCore.Qt.AlignTop) for i in range(0, self.outputsLayout.count()): container = self.outputsLayout.itemAt(i) lyt = container.layout() if lyt: for j in range(0, lyt.count()): lyt.setAlignment(lyt.itemAt(j), QtCore.Qt.AlignRight | QtCore.Qt.AlignTop) if label is None: self.label().setPlainText(self.__class__.__name__) else: self.label().setPlainText(label) self.w = self.getWidth() + Spacings.kPinOffset self.nodeMainGWidget.setMaximumWidth(self.w) self.nodeMainGWidget.setGeometry( QtCore.QRectF(0, 0, self.w, self.childrenBoundingRect().height())) if self.isCallable(): if 'flow' not in self.category().lower(): if self.label().bUseTextureBg: self.label().bg = QtGui.QImage( ':/icons/resources/blue.png') else: if self.label().bUseTextureBg: self.label().bg = QtGui.QImage(':/icons/resources/green.png') self.setToolTip(self.description()) self.update() def postCreate(self, jsonTemplate=None): self.updateNodeShape(label=jsonTemplate['meta']['label']) NodeBase.postCreate(self, jsonTemplate) def getWidth(self): fontWidth = QtGui.QFontMetricsF(self.label().font()).width( self.label().toPlainText()) + Spacings.kPinSpacing return fontWidth if fontWidth > 25 else 25 @staticmethod def jsonTemplate(): doc = '''# access pins like this\n\t# self.pinName.getData()\n\t# self.pinName.setData()''' doc += '''\n\t# self.getData(name) to get data from input pin by name''' doc += '''\n\t# self.setData(name, data) to set data to output pin by name\n''' template = { 'type': None, 'x': None, 'y': None, 'name': None, 'uuid': None, 'computeCode': doc + "\nprint('Hello world')\n", 'inputs': [], 'outputs': [], 'meta': { 'label': 'Node', 'var': {} } } return template def serialize(self): template = Node.jsonTemplate() template['type'] = self.__class__.__name__ template['name'] = self.name template['x'] = self.scenePos().x() template['y'] = self.scenePos().y() template['uuid'] = str(self.uid) template['computeCode'] = self.computeCode() template['inputs'] = [i.serialize() for i in self.inputs.values()] template['outputs'] = [o.serialize() for o in self.outputs.values()] template['meta']['label'] = self.label().toPlainText() return template def propertyView(self): return self.graph().parent.dockWidgetNodeView def Tick(self, delta): pass def setName(self, name): NodeBase.setName(self, name) def clone(self): templ = self.serialize() templ['name'] = self.graph().getUniqNodeName(self.name) templ['uuid'] = str(uuid.uuid4()) for inp in templ['inputs']: inp['uuid'] = str(uuid.uuid4()) for out in templ['outputs']: out['uuid'] = str(uuid.uuid4()) new_node = self.graph().createNode(templ) return new_node def paint(self, painter, option, widget): NodePainter.default(self, painter, option, widget) def mousePressEvent(self, event): self.update() # self.setCursor(QtCore.Qt.ClosedHandCursor) QGraphicsItem.mousePressEvent(self, event) def mouseReleaseEvent(self, event): self.update() QGraphicsItem.mouseReleaseEvent(self, event) def addInputPin(self, pinName, dataType, foo=None, hideLabel=False, bCreateInputWidget=True, index=-1, userStructClass=ENone, defaultValue=None): p = self._addPin(PinDirection.Input, dataType, foo, hideLabel, bCreateInputWidget, pinName, index=index, userStructClass=userStructClass, defaultValue=defaultValue) return p def addOutputPin(self, pinName, dataType, foo=None, hideLabel=False, bCreateInputWidget=True, index=-1, userStructClass=ENone, defaultValue=None): p = self._addPin(PinDirection.Output, dataType, foo, hideLabel, bCreateInputWidget, pinName, index=index, userStructClass=userStructClass, defaultValue=defaultValue) return p @staticmethod def category(): return "Default" @staticmethod def keywords(): return [] def propertyEditingFinished(self): le = QApplication.instance().focusWidget() if isinstance(le, QLineEdit): nodeName, attr = le.objectName().split('.') Pin = self.getPinByName(attr) Pin.setData(le.text()) def onUpdatePropertyView(self, formLayout): # name le_name = QLineEdit(self.getName()) le_name.setReadOnly(True) if self.label().IsRenamable(): le_name.setReadOnly(False) le_name.returnPressed.connect(lambda: self.setName(le_name.text())) formLayout.addRow("Name", le_name) # uid leUid = QLineEdit(str(self.uid)) leUid.setReadOnly(True) formLayout.addRow("Uuid", leUid) # type leType = QLineEdit(self.__class__.__name__) leType.setReadOnly(True) formLayout.addRow("Type", leType) # pos le_pos = QLineEdit("{0} x {1}".format(self.pos().x(), self.pos().y())) formLayout.addRow("Pos", le_pos) # inputs if len([i for i in self.inputs.values()]) != 0: sep_inputs = QLabel() sep_inputs.setStyleSheet("background-color: black;") sep_inputs.setText("INPUTS") formLayout.addRow("", sep_inputs) for inp in self.inputs.values(): dataSetter = inp.call if inp.dataType == DataTypes.Exec else inp.setData w = getInputWidget(inp.dataType, dataSetter, inp.defaultValue(), inp.getUserStruct()) if w: w.setWidgetValue(inp.currentData()) w.setObjectName(inp.getName()) formLayout.addRow(inp.name, w) if inp.hasConnections(): w.setEnabled(False) # outputs if len([i for i in self.outputs.values()]) != 0: sep_outputs = QLabel() sep_outputs.setStyleSheet("background-color: black;") sep_outputs.setText("OUTPUTS") formLayout.addRow("", sep_outputs) for out in self.outputs.values(): if out.dataType == DataTypes.Exec: continue w = getInputWidget(out.dataType, out.setData, out.defaultValue(), out.getUserStruct()) if w: w.setWidgetValue(out.currentData()) w.setObjectName(out.getName()) formLayout.addRow(out.name, w) if out.hasConnections(): w.setEnabled(False) doc_lb = QLabel() doc_lb.setStyleSheet("background-color: black;") doc_lb.setText("Description") formLayout.addRow("", doc_lb) doc = QTextBrowser() doc.setOpenExternalLinks(True) doc.setHtml(self.description()) formLayout.addRow("", doc) def addContainer(self, portType, head=False): container = QGraphicsWidget() container.setObjectName('{0}PinContainerWidget'.format(self.name)) container.setSizePolicy(QSizePolicy.Minimum, QSizePolicy.Maximum) container.sizeHint(QtCore.Qt.MinimumSize, QtCore.QSizeF(50.0, 10.0)) lyt = QGraphicsLinearLayout() lyt.setSizePolicy(QSizePolicy.Maximum, QSizePolicy.Maximum) lyt.setContentsMargins(1, 1, 1, 1) container.setLayout(lyt) if portType == PinDirection.Input: self.inputsLayout.addItem(container) else: self.outputsLayout.addItem(container) return container def kill(self): # disconnect edges for i in self.inputs.values() + self.outputs.values(): i.kill() if self.uid in self.graph().nodes: self.graph().nodes.pop(self.uid) self.graph().nodesPendingKill.append(self) self.scene().removeItem(self) del (self) def setPosition(self, x, y): NodeBase.setPosition(self, x, y) self.setPos(QtCore.QPointF(x, y)) @staticmethod def removePinByName(node, name): pin = node.getPinByName(name) if pin: pin.kill() def _addPin(self, pinDirection, dataType, foo, hideLabel=False, bCreateInputWidget=True, name='', index=-1, userStructClass=ENone, defaultValue=None): # check if pins with this name already exists and get uniq name name = self.getUniqPinName(name) p = CreatePin(name, self, dataType, pinDirection, userStructClass=userStructClass) if defaultValue is not None: p.setDefaultValue(defaultValue) self.graph().pins[p.uid] = p if pinDirection == PinDirection.Input and foo is not None: p.call = foo connector_name = QGraphicsProxyWidget() connector_name.setObjectName('{0}PinConnector'.format(name)) connector_name.setContentsMargins(0, 0, 0, 0) lblName = name if hideLabel: lblName = '' p.bLabelHidden = True lbl = QLabel(lblName) p.nameChanged.connect(lbl.setText) lbl.setContentsMargins(0, 0, 0, 0) lbl.setAttribute(QtCore.Qt.WA_TranslucentBackground) font = QtGui.QFont('Consolas') color = Colors.PinNameColor font.setPointSize(6) lbl.setFont(font) style = 'color: rgb({0}, {1}, {2}, {3});'.format( color.red(), color.green(), color.blue(), color.alpha()) lbl.setStyleSheet(style) connector_name.setWidget(lbl) if pinDirection == PinDirection.Input: container = self.addContainer(pinDirection) if hideLabel: container.setMinimumWidth(15) lbl.setAlignment(QtCore.Qt.AlignLeft | QtCore.Qt.AlignTop) container.layout().addItem(p) p._container = container container.layout().addItem(connector_name) self.inputs[p.uid] = p self.inputsLayout.insertItem(index, container) container.adjustSize() elif pinDirection == PinDirection.Output: container = self.addContainer(pinDirection) if hideLabel: container.setMinimumWidth(15) lbl.setAlignment(QtCore.Qt.AlignRight | QtCore.Qt.AlignTop) container.layout().addItem(connector_name) container.layout().addItem(p) p._container = container self.outputs[p.uid] = p self.outputsLayout.insertItem(index, container) container.adjustSize() p.setSizePolicy(QSizePolicy.Maximum, QSizePolicy.Maximum) # create member if created in runtime if not hasattr(self, name): setattr(self, name, p) return p
class UIstickyNote(UINodeBase): def __init__(self, raw_node): super(UIstickyNote, self).__init__(raw_node) self.color = QtGui.QColor(255, 255, 136) self.color.setAlpha(255) self.labelTextColor = QtGui.QColor(0, 0, 0, 255) self.resizable = True self.roundness = 1 self.textInput = InputTextField("Text Goes Here", self, singleLine=False) self.textInput.setPos( QtCore.QPointF(5, self.nodeNameWidget.boundingRect().height())) self.textInput.document().contentsChanged.connect(self.updateSize) self.textInput.editingFinished.connect(self.editingFinished) self.textInput.startEditing.connect(self.startEditing) self.textWidget = QGraphicsWidget() self.textWidget.setGraphicsItem(self.textInput) self.nodeLayout.addItem(self.textWidget) self.NonFormatedText = self.textInput.toPlainText() self.updateSize() def serializationHook(self): original = super(UIstickyNote, self).serializationHook() original["color"] = self.color.rgba() original["textColor"] = self.labelTextColor.rgba() original["currentText"] = self.NonFormatedText return original def postCreate(self, jsonTemplate=None): super(UIstickyNote, self).postCreate(jsonTemplate=jsonTemplate) if "color" in jsonTemplate["wrapper"]: self.color = QtGui.QColor.fromRgba( jsonTemplate["wrapper"]["color"]) if "textColor" in jsonTemplate["wrapper"]: self.labelTextColor = QtGui.QColor.fromRgba( jsonTemplate["wrapper"]["textColor"]) if "currentText" in jsonTemplate["wrapper"]: self.NonFormatedText = jsonTemplate["wrapper"]["currentText"] self.textInput.setHtml(self.NonFormatedText.replace( "\\n", "<br/>")) def mouseDoubleClickEvent(self, event): self.textInput.setFlag(QGraphicsWidget.ItemIsFocusable, True) self.textInput.setFocus() self.startEditing() super(UIstickyNote, self).mouseDoubleClickEvent(event) def itemChange(self, change, value): if change == QGraphicsItem.ItemSelectedChange: if value == False: self.textInput.clearFocus() self.textInput.setFlag(QGraphicsWidget.ItemIsFocusable, False) return super(UIstickyNote, self).itemChange(change, value) def aboutToCollapse(self, futureCollapseState): if not futureCollapseState: self.textInput.show() else: self.textInput.hide() def paint(self, painter, option, widget): NodePainter.asCommentNode(self, painter, option, widget) self.updateSize() def mouseMoveEvent(self, event): super(UIstickyNote, self).mouseMoveEvent(event) self.updateSize() def startEditing(self): if IS_PYTHON2: self.textInput.setPlainText( self.NonFormatedText.decode('unicode-escape')) else: self.textInput.setPlainText( bytes(self.NonFormatedText, "utf-8").decode('unicode-escape')) def editingFinished(self, succes): if succes: if IS_PYTHON2: self.NonFormatedText = self.textInput.toPlainText().encode( 'unicode-escape') self.textInput.setHtml( self.NonFormatedText.replace("\\n", "<br/>")) else: self.NonFormatedText = self.textInput.toPlainText().encode( 'unicode-escape') self.NonFormatedText = self.NonFormatedText.replace( b"\\n", b"<br/>").decode('unicode-escape') self.textInput.setHtml(self.NonFormatedText) def updateSize(self): self.textInput.setTextWidth(self.boundingRect().width() - 10) newHeight = self.textInput.boundingRect().height( ) + self.nodeNameWidget.boundingRect().height() + 5 if self._rect.height() < newHeight: self._rect.setHeight(newHeight) try: self.updateNodeShape() except: pass self.minHeight = newHeight def updateColor(self, color): res = QtGui.QColor(color[0], color[1], color[2], color[3]) if res.isValid(): self.color = res self.update() def updateTextColor(self, color): res = QtGui.QColor(color[0], color[1], color[2], color[3]) if res.isValid(): self.labelTextColor = res self.textInput.setDefaultTextColor(res) self.update() def createPropertiesWidget(self, propertiesWidget): super(UIstickyNote, self).createPropertiesWidget(propertiesWidget) appearanceCategory = CollapsibleFormWidget(headName="Appearance") pb = pyf_ColorSlider(type="int", alpha=True, startColor=list(self.color.getRgbF())) pb.valueChanged.connect(self.updateColor) appearanceCategory.addWidget("Color", pb) pb = pyf_ColorSlider(type="int", alpha=True, startColor=list(self.labelTextColor.getRgbF())) pb.valueChanged.connect(self.updateTextColor) appearanceCategory.addWidget("TextColor", pb) propertiesWidget.insertWidget(appearanceCategory, 1)
def __init__(self, raw_node, w=80, color=Colors.NodeBackgrounds, headColorOverride=None): super(UINodeBase, self).__init__() self.setFlag(QGraphicsWidget.ItemIsMovable) self.setFlag(QGraphicsWidget.ItemIsFocusable) self.setFlag(QGraphicsWidget.ItemIsSelectable) self.setFlag(QGraphicsWidget.ItemSendsGeometryChanges) self.setCacheMode(QGraphicsItem.DeviceCoordinateCache) self.setFocusPolicy(QtCore.Qt.StrongFocus) self.setAcceptHoverEvents(True) self.setZValue(NodeDefaults().Z_LAYER) self._rawNode = raw_node self._rawNode.setWrapper(self) self._rawNode.killed.connect(self.kill) self._rawNode.tick.connect(self.Tick) self.custom_widget_data = {} # node name self._displayName = self.name # GUI Layout self.opt_node_base_color = Colors.NodeBackgrounds self.opt_selected_pen_color = Colors.NodeSelectedPenColor self.opt_pen_selected_type = QtCore.Qt.SolidLine self._collapsed = False self._left_stretch = 0 self.color = color self.drawlabel = True self.headColorOverride = headColorOverride self.headColor = headColorOverride self._w = 0 self.h = 30 self.minWidth = 25 self.minHeight = self.h self._labelTextColor = QtCore.Qt.white self.drawLayoutsDebug = False self.nodeLayout = QGraphicsLinearLayout(QtCore.Qt.Vertical) self.nodeLayout.setContentsMargins(NodeDefaults().CONTENT_MARGINS, NodeDefaults().CONTENT_MARGINS, NodeDefaults().CONTENT_MARGINS, NodeDefaults().CONTENT_MARGINS) self.nodeLayout.setSpacing(NodeDefaults().LAYOUTS_SPACING) self.nodeNameFont = QtGui.QFont("Consolas") self.nodeNameFont.setPointSize(6) self.nodeTypeFont = QtGui.QFont("Consolas") self.nodeTypeFont.setPointSize(4) self.nodeTypeFont.setItalic(True) self.headerLayout = QGraphicsLinearLayout(QtCore.Qt.Horizontal) self.nodeLayout.addItem(self.headerLayout) self.nodeNameWidget = NodeName(self) self.headerLayout.addItem(self.nodeNameWidget) self.headerLayout.setContentsMargins(0, 0, 0, 0) self.headerLayout.setSpacing(3) self.nameActionsSpacer = QGraphicsWidget() self.nameActionsSpacer.setObjectName("nameActionsSpacer") self.nameActionsSpacer.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Maximum) self.headerLayout.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Maximum) self.headerLayout.addItem(self.nameActionsSpacer) self.headerLayout.setMaximumHeight(self.labelHeight) self.pinsLayout = QGraphicsLinearLayout(QtCore.Qt.Horizontal) self.pinsLayout.setContentsMargins(0, 0, 0, 0) self.pinsLayout.setSpacing(NodeDefaults().LAYOUTS_SPACING) self.nodeLayout.addItem(self.pinsLayout) self.nodeLayout.setStretchFactor(self.pinsLayout, 2) self.inputsLayout = QGraphicsLinearLayout(QtCore.Qt.Vertical) self.inputsLayout.setContentsMargins(0, 0, 0, 0) self.inputsLayout.setSpacing(NodeDefaults().LAYOUTS_SPACING) self.outputsLayout = QGraphicsLinearLayout(QtCore.Qt.Vertical) self.outputsLayout.setContentsMargins(0, 0, 0, 0) self.outputsLayout.setSpacing(NodeDefaults().LAYOUTS_SPACING) self.pinsLayout.addItem(self.inputsLayout) self.pinLayoutSpacer = QGraphicsWidget() self.pinLayoutSpacer.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding) self.pinLayoutSpacer.setObjectName("pinLayoutSpacer") self.pinsLayout.addItem(self.pinLayoutSpacer) self.pinsLayout.addItem(self.outputsLayout) self.setLayout(self.nodeLayout) self.svgIcon = QtSvg.QGraphicsSvgItem(self) self.svgIcon.setPos(-6, -6) self._image = None self.canvasRef = None self._menu = QMenu() # Resizing Options self.initialRectWidth = self.minWidth self.initialRectHeight = self.minHeight self.expanded = True self.resizable = False self.bResize = False self.resizeDirection = (0, 0) self.resizeStripsSize = 2 self.resizeStrips = [0, 0, 0, 0, 0] # Left, Top, Right, Bottom, BottomRight self.lastMousePos = QtCore.QPointF() self.mousePressPos = QtCore.QPointF() # Hiding/Moving By Group/collapse/By Pin self.pressedCommentNode = None self.owningCommentNode = None self.edgesToHide = [] self.nodesNamesToMove = [] self.pinsToMove = {} self._rect = QtCore.QRectF(0, 0, self.minWidth, self.minHeight) # Group pins self.inputGroupPins = {} self.outputGroupPins = {} # Action buttons self._actionButtons = set() # Core nodes support self.isTemp = False self.isCommentNode = False self.propertyEditor = None # collapse action self.actionToggleCollapse = self._menu.addAction("ToggleCollapse") self.actionToggleCollapse.setToolTip( "Toggles node's body collapsed or not") self.actionToggleCollapse.triggered.connect(self.toggleCollapsed) self.actionToggleCollapse.setData( NodeActionButtonInfo(RESOURCES_DIR + "/nodeCollapse.svg", CollapseNodeActionButton))
class UINodeBase(QGraphicsWidget, IPropertiesViewSupport): """ Default node description """ # Event called when node name changes displayNameChanged = QtCore.Signal(str) def __init__(self, raw_node, w=80, color=Colors.NodeBackgrounds, headColorOverride=None): super(UINodeBase, self).__init__() self.setFlag(QGraphicsWidget.ItemIsMovable) self.setFlag(QGraphicsWidget.ItemIsFocusable) self.setFlag(QGraphicsWidget.ItemIsSelectable) self.setFlag(QGraphicsWidget.ItemSendsGeometryChanges) self.setCacheMode(QGraphicsItem.DeviceCoordinateCache) self.setFocusPolicy(QtCore.Qt.StrongFocus) self.setAcceptHoverEvents(True) self.setZValue(NodeDefaults().Z_LAYER) self._rawNode = raw_node self._rawNode.setWrapper(self) self._rawNode.killed.connect(self.kill) self._rawNode.tick.connect(self.Tick) self.custom_widget_data = {} # node name self._displayName = self.name # GUI Layout self.opt_node_base_color = Colors.NodeBackgrounds self.opt_selected_pen_color = Colors.NodeSelectedPenColor self.opt_pen_selected_type = QtCore.Qt.SolidLine self._collapsed = False self._left_stretch = 0 self.color = color self.drawlabel = True self.headColorOverride = headColorOverride self.headColor = headColorOverride self._w = 0 self.h = 30 self.minWidth = 25 self.minHeight = self.h self._labelTextColor = QtCore.Qt.white self.drawLayoutsDebug = False self.nodeLayout = QGraphicsLinearLayout(QtCore.Qt.Vertical) self.nodeLayout.setContentsMargins(NodeDefaults().CONTENT_MARGINS, NodeDefaults().CONTENT_MARGINS, NodeDefaults().CONTENT_MARGINS, NodeDefaults().CONTENT_MARGINS) self.nodeLayout.setSpacing(NodeDefaults().LAYOUTS_SPACING) self.nodeNameFont = QtGui.QFont("Consolas") self.nodeNameFont.setPointSize(6) self.nodeTypeFont = QtGui.QFont("Consolas") self.nodeTypeFont.setPointSize(4) self.nodeTypeFont.setItalic(True) self.headerLayout = QGraphicsLinearLayout(QtCore.Qt.Horizontal) self.nodeLayout.addItem(self.headerLayout) self.nodeNameWidget = NodeName(self) self.headerLayout.addItem(self.nodeNameWidget) self.headerLayout.setContentsMargins(0, 0, 0, 0) self.headerLayout.setSpacing(3) self.nameActionsSpacer = QGraphicsWidget() self.nameActionsSpacer.setObjectName("nameActionsSpacer") self.nameActionsSpacer.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Maximum) self.headerLayout.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Maximum) self.headerLayout.addItem(self.nameActionsSpacer) self.headerLayout.setMaximumHeight(self.labelHeight) self.pinsLayout = QGraphicsLinearLayout(QtCore.Qt.Horizontal) self.pinsLayout.setContentsMargins(0, 0, 0, 0) self.pinsLayout.setSpacing(NodeDefaults().LAYOUTS_SPACING) self.nodeLayout.addItem(self.pinsLayout) self.nodeLayout.setStretchFactor(self.pinsLayout, 2) self.inputsLayout = QGraphicsLinearLayout(QtCore.Qt.Vertical) self.inputsLayout.setContentsMargins(0, 0, 0, 0) self.inputsLayout.setSpacing(NodeDefaults().LAYOUTS_SPACING) self.outputsLayout = QGraphicsLinearLayout(QtCore.Qt.Vertical) self.outputsLayout.setContentsMargins(0, 0, 0, 0) self.outputsLayout.setSpacing(NodeDefaults().LAYOUTS_SPACING) self.pinsLayout.addItem(self.inputsLayout) self.pinLayoutSpacer = QGraphicsWidget() self.pinLayoutSpacer.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding) self.pinLayoutSpacer.setObjectName("pinLayoutSpacer") self.pinsLayout.addItem(self.pinLayoutSpacer) self.pinsLayout.addItem(self.outputsLayout) self.setLayout(self.nodeLayout) self.svgIcon = QtSvg.QGraphicsSvgItem(self) self.svgIcon.setPos(-6, -6) self._image = None self.canvasRef = None self._menu = QMenu() # Resizing Options self.initialRectWidth = self.minWidth self.initialRectHeight = self.minHeight self.expanded = True self.resizable = False self.bResize = False self.resizeDirection = (0, 0) self.resizeStripsSize = 2 self.resizeStrips = [0, 0, 0, 0, 0] # Left, Top, Right, Bottom, BottomRight self.lastMousePos = QtCore.QPointF() self.mousePressPos = QtCore.QPointF() # Hiding/Moving By Group/collapse/By Pin self.pressedCommentNode = None self.owningCommentNode = None self.edgesToHide = [] self.nodesNamesToMove = [] self.pinsToMove = {} self._rect = QtCore.QRectF(0, 0, self.minWidth, self.minHeight) # Group pins self.inputGroupPins = {} self.outputGroupPins = {} # Action buttons self._actionButtons = set() # Core nodes support self.isTemp = False self.isCommentNode = False self.propertyEditor = None # collapse action self.actionToggleCollapse = self._menu.addAction("ToggleCollapse") self.actionToggleCollapse.setToolTip( "Toggles node's body collapsed or not") self.actionToggleCollapse.triggered.connect(self.toggleCollapsed) self.actionToggleCollapse.setData( NodeActionButtonInfo(RESOURCES_DIR + "/nodeCollapse.svg", CollapseNodeActionButton)) def toggleCollapsed(self): self.collapsed = not self.collapsed def aboutToCollapse(self, futureCollapseState): """Called before collapsing or expanding.""" pass @property def collapsed(self): return self._collapsed @collapsed.setter def collapsed(self, bCollapsed): if bCollapsed != self._collapsed: self._collapsed = bCollapsed self.aboutToCollapse(self._collapsed) for i in range(0, self.inputsLayout.count()): inp = self.inputsLayout.itemAt(i) inp.setVisible(not bCollapsed) for o in range(0, self.outputsLayout.count()): out = self.outputsLayout.itemAt(o) out.setVisible(not bCollapsed) self.pinLayoutSpacer.setVisible(not bCollapsed) self.updateNodeShape() @property def image(self): return self._image @image.setter def image(self, value): self._image = value self.svgIcon.renderer().load(value) elementName = QtCore.QFileInfo(value).baseName() self.svgIcon.setElementId(elementName) # self.svgIcon.setPos(self.geometry().topRight()) def getImageDrawRect(self): topRight = self.boundingRect().topRight() topRight.setY(-12) topRight.setX(self.boundingRect().width() - 12) r = self.boundingRect() r.setWidth(24) r.setHeight(24) r.translate(topRight) return r @property def labelTextColor(self): return self._labelTextColor @labelTextColor.setter def labelTextColor(self, value): self._labelTextColor = value self.nodeNameWidget.setTextColor(self._labelTextColor) def __repr__(self): graphName = self._rawNode.graph( ).name if self._rawNode.graph is not None else str(None) return "<class[{0}]; name[{1}]; graph[{2}]>".format( self.__class__.__name__, self.getName(), graphName) def sizeHint(self, which, constraint): return QtCore.QSizeF(self.getNodeWidth(), self.getNodeHeight()) def setGeometry(self, rect): self.prepareGeometryChange() super(QGraphicsWidget, self).setGeometry(rect) self.setPos(rect.topLeft()) @property def uid(self): return self._rawNode._uid @uid.setter def uid(self, value): self._rawNode._uid = value @property def name(self): return self._rawNode.name @name.setter def name(self, value): self._rawNode.setName(value) @property def displayName(self): return self._displayName @displayName.setter def displayName(self, value): self._displayName = value self.displayNameChanged.emit(self._displayName) self.updateNodeShape() @property def pins(self): return self._rawNode.pins @property def UIPins(self): result = OrderedDict() for rawPin in self._rawNode.pins: uiPinRef = rawPin.getWrapper() if uiPinRef is not None: result[rawPin.uid] = uiPinRef() return result @property def UIinputs(self): result = OrderedDict() for rawPin in self._rawNode.pins: if rawPin.direction == PinDirection.Input: result[rawPin.uid] = rawPin.getWrapper()() return result @property def UIoutputs(self): result = OrderedDict() for rawPin in self._rawNode.pins: if rawPin.direction == PinDirection.Output: result[rawPin.uid] = rawPin.getWrapper()() return result @property def namePinOutputsMap(self): result = OrderedDict() for rawPin in self._rawNode.pins: if rawPin.direction == PinDirection.Output: result[rawPin.name] = rawPin.getWrapper()() return result @property def namePinInputsMap(self): result = OrderedDict() for rawPin in self._rawNode.pins: if rawPin.direction == PinDirection.Input: result[rawPin.name] = rawPin.getWrapper()() return result @property def w(self): return self._w @w.setter def w(self, value): self._w = value def getName(self): return self._rawNode.getName() def isRenamable(self): return False def setName(self, name): self._rawNode.setName(name) def getPin(self, name, pinsGroup=PinSelectionGroup.BothSides): pin = self._rawNode.getPin(str(name), pinsGroup) if pin is not None: if pin.getWrapper() is not None: return pin.getWrapper()() return None @staticmethod def removePinByName(node, name): pin = node.getPin(name) if pin: pin.kill() @staticmethod def recreate(node): templ = node.serialize() uid = node.uid node.kill() newNode = node.canvas.createNode(templ) newNode.uid = uid return newNode @property def isCompoundNode(self): return self._rawNode.isCompoundNode # TODO: add this to ui node interface def serializationHook(self): # this will be called by raw node # to gather ui specific info template = {} if self.resizable: template['resize'] = { 'w': self._rect.right(), 'h': self._rect.bottom() } template['displayName'] = self.displayName template['collapsed'] = self.collapsed template['headerHtml'] = self.nodeNameWidget.getHtml() return template def setHeaderHtml(self, html): self.nodeNameWidget.setHtml(html) def serialize(self): return self._rawNode.serialize() def onVisibilityChanged(self, bVisible): pass def itemChange(self, change, value): if change == QGraphicsItem.ItemPositionChange: self._rawNode.setPosition(value.x(), value.y()) if change == QGraphicsItem.ItemVisibleChange: if self.owningCommentNode is not None: if self.owningCommentNode.collapsed: self.onVisibilityChanged(False) else: self.onVisibilityChanged(bool(value)) return super(UINodeBase, self).itemChange(change, value) def isUnderActiveGraph(self): return self._rawNode.isUnderActiveGraph() def autoAffectPins(self): self._rawNode.autoAffectPins() def postCreate(self, jsonTemplate=None): # create ui pin wrappers for i in self._rawNode.getOrderedPins(): self._createUIPinWrapper(i) self.updateNodeShape() self.setPos(self._rawNode.x, self._rawNode.y) if self.canvasRef().graphManager.activeGraph() != self._rawNode.graph( ): self.hide() if not self.drawlabel: self.nodeNameWidget.hide() if self.headColorOverride is None: if self.isCallable(): self.headColor = NodeDefaults().CALLABLE_NODE_HEAD_COLOR else: self.headColor = NodeDefaults().PURE_NODE_HEAD_COLOR else: self.headColor = self.headColorOverride self.createActionButtons() headerHtml = self.name if jsonTemplate is not None: if "collapsed" in jsonTemplate["wrapper"]: self.collapsed = jsonTemplate["wrapper"]["collapsed"] if "headerHtml" in jsonTemplate["wrapper"]: headerHtml = jsonTemplate["wrapper"]["headerHtml"] self.setToolTip(self.description()) if self.resizable: w = self.getNodeWidth() h = self.getNodeHeight() if jsonTemplate is not None: if "resize" in jsonTemplate["wrapper"]: w = jsonTemplate["wrapper"]["resize"]["w"] h = jsonTemplate["wrapper"]["resize"]["h"] self._rect.setWidth(w) self._rect.setHeight(h) self.updateNodeShape() self.setHeaderHtml(headerHtml) def createActionButtons(self): # NOTE: actions with action button class specified will be added next to node name for action in self._menu.actions(): actionData = action.data() if isinstance(actionData, NodeActionButtonInfo): actionButtonClass = actionData.actionButtonClass() svgFilePath = actionData.filePath() if actionButtonClass is None: actionButtonClass = NodeActionButtonBase self.headerLayout.addItem( actionButtonClass(svgFilePath, action, self)) action.setVisible(False) def isCallable(self): return self._rawNode.isCallable() def category(self): return self._rawNode.category() def description(self): return self._rawNode.description() @property def packageName(self): return self._rawNode.packageName def getData(self, pinName): if pinName in [p.name for p in self.inputs.values()]: p = self.getPin(pinName, PinSelectionGroup.Inputs) return p.getData() def setData(self, pinName, data): if pinName in [p.name for p in self.outputs.values()]: p = self.getPin(pinName, PinSelectionGroup.Outputs) p.setData(data) @property def labelHeight(self): return self.nodeNameWidget.sizeHint(None, None).height() @property def labelWidth(self): headerWidth = self.nodeNameWidget.sizeHint(None, None).width() # actions width. 10 is svg icon size, probably need to move this value to some preferences numActions = len(self._actionButtons) headerWidth += numActions * 10 headerWidth += numActions * self.headerLayout.spacing() if self.collapsed and not self.resizable: headerWidth += self.nameActionsSpacer.boundingRect().width() headerWidth += self.headerLayout.spacing( ) + NodeDefaults().CONTENT_MARGINS * 2 return headerWidth def getNodeWidth(self): width = self.getPinsWidth() + self.pinsLayout.spacing() * 2 if self.resizable: width = max(self._rect.width(), width) width = max(width, self.labelWidth) return width def getNodeHeight(self): h = self.nodeNameWidget.sizeHint(None, None).height() h += self.nodeLayout.spacing() try: numInputs = len(self.UIinputs) numOutputs = len(self.UIoutputs) pins = self.UIinputs.values( ) if numInputs > numOutputs else self.UIoutputs.values() h += NodeDefaults().CONTENT_MARGINS * 2 for pin in pins: if pin.isVisible(): h += pin.sizeHint( None, None).height() + NodeDefaults().LAYOUTS_SPACING except: pass if h < self.minHeight: h = self.minHeight if self.resizable: h = max(self._rect.height(), h) if self.collapsed: h = max(self.minHeight, self.labelHeight) return h def getPinsWidth(self): iwidth = 0 owidth = 0 pinwidth = 0 pinwidth2 = 0 for i in self.UIPins.values(): if i.direction == PinDirection.Input: iwidth = max(iwidth, i.sizeHint(None, None).width()) # pinwidth = max(pinwidth, i.width) else: owidth = max(owidth, i.sizeHint(None, None).width()) # pinwidth2 = max(pinwidth2, i.width) return iwidth + owidth + pinwidth + pinwidth2 + Spacings.kPinOffset def invalidateNodeLayouts(self): self.inputsLayout.invalidate() self.outputsLayout.invalidate() self.pinsLayout.invalidate() self.headerLayout.invalidate() self.nodeLayout.invalidate() def updateNodeShape(self): self.prepareGeometryChange() self.invalidateNodeLayouts() self.updateGeometry() self.update() self.canvasRef().update() def onChangeColor(self, label=False): res = QColorDialog.getColor(self.color, None, 'Node color setup') if res.isValid(): res.setAlpha(80) self.color = res if label: self.update() def isUnderCollapsedComment(self): if self.owningCommentNode is None: return False else: if self.owningCommentNode.collapsed: return True parent = self.owningCommentNode.owningCommentNode while parent is not None: upperComment = parent if upperComment.collapsed: return True parent = upperComment.owningCommentNode return False def getTopMostOwningCollapsedComment(self): """Returns top most owning comment. If bCollapsed=True, it will stop when first collapsed comment is found. """ if self.owningCommentNode is None: return None # build chain of comments collapse states topMostComment = self.owningCommentNode parent = topMostComment.owningCommentNode chain = OrderedDict() chain[topMostComment] = topMostComment.collapsed while parent is not None: topMostComment = parent chain[topMostComment] = topMostComment.collapsed parent = topMostComment.owningCommentNode last = None for comment, collapsed in chain.items(): if not comment.isVisible(): continue if last is not None: if collapsed + last.collapsed == 1: topMostComment = last break last = comment else: last = comment return topMostComment def updateOwningCommentNode(self): if self.owningCommentNode is not None and self.owningCommentNode.collapsed: return collidingItems = self.collidingItems(QtCore.Qt.ContainsItemShape) collidingNodes = set() for item in collidingItems: if item.sceneBoundingRect().contains( self.sceneBoundingRect()) and isinstance(item, UINodeBase): if item.isCommentNode: collidingNodes.add(item) owningCommentNode = None if len(collidingNodes) == 1: owningCommentNode = list(collidingNodes)[0] elif len(collidingNodes) > 1: # find smallest rect smallest = list(collidingNodes)[0] for commentNode in collidingNodes: s1 = smallest.boundingRect().size() s2 = commentNode.boundingRect().size() if s1.width() > s2.width() and s1.height() > s2.height(): smallest = commentNode if self in commentNode.owningNodes: commentNode.owningNodes.remove(self) owningCommentNode = smallest self.owningCommentNode = owningCommentNode if self.owningCommentNode is not None: if owningCommentNode._rawNode.graph() == self.canvasRef( ).graphManager.activeGraph(): self.owningCommentNode.owningNodes.add(self) def getCollidedNodes(self, bFullyCollided=True, classNameFilters=set()): collidingItems = self.collidingItems() collidingNodes = set() for item in collidingItems: node = item.topLevelItem() if bFullyCollided: if self.sceneBoundingRect().contains(node.sceneBoundingRect()): if node is not self and isinstance(node, UINodeBase): if classNameFilters: if node.__class__.__name__ not in classNameFilters: continue if node._rawNode.graph() != self.canvasRef( ).graphManager.activeGraph(): continue collidingNodes.add(node) else: if node is not self and isinstance(node, UINodeBase): if classNameFilters: if node.__class__.__name__ not in classNameFilters: continue if node._rawNode.graph() != self.canvasRef( ).graphManager.activeGraph(): continue collidingNodes.add(node) return collidingNodes def translate(self, x, y): super(UINodeBase, self).moveBy(x, y) def paint(self, painter, option, widget): NodePainter.default(self, painter, option, widget) if self.drawLayoutsDebug: painter.drawRect(self.headerLayout.geometry()) painter.drawRect(self.nodeLayout.geometry()) painter.drawRect(self.inputsLayout.geometry()) painter.drawRect(self.outputsLayout.geometry()) def shouldResize(self, cursorPos): result = {"resize": False, "direction": self.resizeDirection} if self.resizeStrips[0] == 1: result["resize"] = True result["direction"] = (-1, 0) if self.resizeStrips[1] == 1: result["resize"] = True result["direction"] = (0, -1) if self.resizeStrips[2] == 1: result["resize"] = True result["direction"] = (1, 0) elif self.resizeStrips[3] == 1: result["resize"] = True result["direction"] = (0, 1) elif self.resizeStrips[4] == 1: result["resize"] = True result["direction"] = (1, 1) return result def contextMenuEvent(self, event): self._menu.exec_(event.screenPos()) def mousePressEvent(self, event): self.update() self.mousePressPos = event.pos() self.pressedCommentNode = self.owningCommentNode super(UINodeBase, self).mousePressEvent(event) self.mousePressPos = event.scenePos() self.origPos = self.pos() self.initPos = self.pos() self.initialRect = self.boundingRect() if self.expanded and self.resizable: resizeOpts = self.shouldResize(self.mapToScene(event.pos())) if resizeOpts["resize"]: self.resizeDirection = resizeOpts["direction"] self.initialRectWidth = self.initialRect.width() self.initialRectHeight = self.initialRect.height() self.setFlag(QGraphicsItem.ItemIsMovable, False) self.bResize = True def mouseMoveEvent(self, event): super(UINodeBase, self).mouseMoveEvent(event) # resize if self.bResize: delta = event.scenePos() - self.mousePressPos if self.resizeDirection == (1, 0): # right connection resize newWidth = delta.x() + self.initialRectWidth if newWidth > self.minWidth: self._rect.setWidth(newWidth) self.w = newWidth self.updateNodeShape() elif self.resizeDirection == (0, 1): newHeight = delta.y() + self.initialRectHeight if newHeight > self.minHeight: self._rect.setHeight(newHeight) self.updateNodeShape() elif self.resizeDirection == (-1, 0): # left connection resize posdelta = self.mapToScene(event.pos()) - self.origPos posdelta2 = self.mapToScene(event.pos()) - self.initPos newWidth = -posdelta2.x() + self.initialRectWidth if newWidth > self.minWidth: self.translate(posdelta.x(), 0) self.origPos = self.pos() self._rect.setWidth(newWidth) self.updateNodeShape() elif self.resizeDirection == (1, 1): newWidth = delta.x() + self.initialRectWidth newHeight = delta.y() + self.initialRectHeight if newWidth > self.minWidth: self._rect.setWidth(newWidth) self.w = newWidth self.updateNodeShape() if newHeight > self.minHeight: self._rect.setHeight(newHeight) self.updateNodeShape() self.update() self.lastMousePos = event.pos() def mouseReleaseEvent(self, event): self.bResize = False self.update() self.updateOwningCommentNode() if self.owningCommentNode != self.pressedCommentNode: if self.pressedCommentNode is not None: if self in self.pressedCommentNode.owningNodes: self.pressedCommentNode.owningNodes.remove(self) super(UINodeBase, self).mouseReleaseEvent(event) def clone(self): templ = self.serialize() templ['name'] = self.name templ['uuid'] = str(uuid.uuid4()) for inp in templ['inputs']: inp['uuid'] = str(uuid.uuid4()) for out in templ['outputs']: out['uuid'] = str(uuid.uuid4()) new_node = self.canvasRef().createNode(templ) return new_node def call(self, name): self._rawNode.call(name) def createPropertiesWidget(self, propertiesWidget): self.propertyEditor = weakref.ref(propertiesWidget) baseCategory = CollapsibleFormWidget(headName="Base") le_name = QLineEdit(self.getName()) le_name.setReadOnly(True) baseCategory.addWidget("Name", le_name) leUid = QLineEdit(str(self._rawNode.graph().name)) leUid.setReadOnly(True) baseCategory.addWidget("Owning graph", leUid) text = "{0}".format(self.packageName) if self._rawNode.lib: text += " | {0}".format(self._rawNode.lib) text += " | {0}".format(self._rawNode.__class__.__name__) leType = QLineEdit(text) leType.setReadOnly(True) baseCategory.addWidget("Type", leType) self.propertyEditor().addWidget(baseCategory) self.createInputWidgets(self.propertyEditor()) Info = CollapsibleFormWidget(headName="Info", collapsed=True) doc = QTextBrowser() doc.setOpenExternalLinks(True) doc.setHtml(self.description()) Info.addWidget(widget=doc) self.propertyEditor().addWidget(Info) def createInputWidgets(self, propertiesWidget): # inputs if len([i for i in self.UIinputs.values()]) != 0: inputsCategory = CollapsibleFormWidget(headName="Inputs") sortedInputs = sorted(self.UIinputs.values(), key=lambda x: x.name) for inp in sortedInputs: if inp.isArray(): # TODO: create list input widget continue dataSetter = inp.call if inp.isExec() else inp.setData w = createInputWidget(inp.dataType, dataSetter, inp.defaultValue()) if w: inp.dataBeenSet.connect(w.setWidgetValueNoSignals) w.blockWidgetSignals(True) w.setWidgetValue(inp.currentData()) w.blockWidgetSignals(False) w.setObjectName(inp.getName()) inputsCategory.addWidget(inp.name, w) if inp.hasConnections(): w.setEnabled(False) propertiesWidget.addWidget(inputsCategory) return inputsCategory def getChainedNodes(self): nodes = [] for pin in self.UIinputs.values(): for connection in pin.connections: node = connection.source().topLevelItem() # topLevelItem nodes.append(node) nodes += node.getChainedNodes() return nodes def kill(self, *args, **kwargs): scene = self.scene() if scene is not None: self.scene().removeItem(self) del (self) def collidesWithCommentNode(self): nodes = self.getCollidedNodes() result = None for n in nodes: if n.isCommentNode: result = n break return result def handleVisibility(self): if self._rawNode.graph() != self.canvasRef().graphManager.activeGraph( ): # if current graph != node's graph - hide node and connections self.hide() for uiPin in self.UIPins.values(): for connection in uiPin.uiConnectionList: connection.hide() else: # if current graph == node's graph - show it only if its not under collapsed comment node collidedCommentNode = self.collidesWithCommentNode() if collidedCommentNode is None: self.show() else: if collidedCommentNode.collapsed: self.hide() else: self.show() def hoverLeaveEvent(self, event): self.resizeStrips = [0, 0, 0, 0, 0] self.update() def hoverMoveEvent(self, event): if self.resizable and not self.collapsed: height = self.geometry().height() width = self.geometry().width() rf = NodeDefaults().CORNERS_ROUND_FACTOR leftStrip = QtCore.QRectF(0, rf, self.resizeStripsSize, height - rf * 2) topStrip = QtCore.QRectF(rf, 0, width - rf * 2, self.resizeStripsSize) rightStrip = QtCore.QRectF(width - self.resizeStripsSize, rf, self.resizeStripsSize, height - rf * 2) bottomStrip = QtCore.QRectF(rf, height - self.resizeStripsSize, width - rf * 2, self.resizeStripsSize) bottomRightStrip = QtCore.QRectF(width - rf, height - rf, rf, rf) # detect where on the node self.resizeStrips[0] = 1 if leftStrip.contains(event.pos()) else 0 self.resizeStrips[1] = 1 if topStrip.contains(event.pos()) else 0 self.resizeStrips[2] = 1 if rightStrip.contains(event.pos()) else 0 self.resizeStrips[3] = 1 if bottomStrip.contains( event.pos()) else 0 self.resizeStrips[4] = 1 if bottomRightStrip.contains( event.pos()) else 0 self.update() def Tick(self, delta, *args, **kwargs): # NOTE: Do not call wrapped raw node Tick method here! # this ui node tick called from underlined raw node's emitted signal # do here only UI stuff # self.handleVisibility() pass def addGroupContainer(self, portType, groupName="group"): container = QGraphicsWidget() container.setObjectName('{0}PinGroupContainerWidget'.format(self.name)) lyt = QGraphicsLinearLayout() lyt.setOrientation(QtCore.Qt.Vertical) lyt.setSizePolicy(QSizePolicy.Maximum, QSizePolicy.Maximum) lyt.setContentsMargins(1, 1, 1, 1) container.group_name = EditableLabel(name=groupName, node=self, canvas=self.canvasRef()) font = QtGui.QFont('Consolas') font.setBold(True) font.setPointSize(500) container.group_name._font = font container.group_name.nameLabel.setFont(font) container.group_name.nameLabel.update() container.group_name.setObjectName('{0}_GroupConnector'.format( container.group_name)) container.group_name.setContentsMargins(0, 0, 0, 0) container.group_name.setColor(Colors.AbsoluteBlack) grpCon = self.addContainer() container.groupIcon = UIGroupPinBase(container) lyt.addItem(grpCon) container.setLayout(lyt) if portType == PinDirection.Input: container.group_name.nameLabel.setAlignment(QtCore.Qt.AlignLeft | QtCore.Qt.AlignTop) grpCon.layout().addItem(container.groupIcon) grpCon.layout().addItem(container.group_name) else: container.group_name.nameLabel.setAlignment(QtCore.Qt.AlignRight | QtCore.Qt.AlignTop) grpCon.layout().addItem(container.group_name) grpCon.layout().addItem(container.groupIcon) return container def addContainer(self): container = QGraphicsWidget() container.setObjectName('{0}PinContainerWidget'.format(self.name)) container.setSizePolicy(QSizePolicy.Minimum, QSizePolicy.Maximum) container.sizeHint(QtCore.Qt.MinimumSize, QtCore.QSizeF(50.0, 10.0)) lyt = QGraphicsLinearLayout() lyt.setSizePolicy(QSizePolicy.Maximum, QSizePolicy.Maximum) lyt.setContentsMargins(1, 1, 1, 1) container.setLayout(lyt) return container def _createUIPinWrapper(self, rawPin, index=-1, group=None, linkedPin=None): wrapper = rawPin.getWrapper() if wrapper is not None: return wrapper() p = getUIPinInstance(self, rawPin) p.call = rawPin.call name = rawPin.name lblName = name if rawPin.direction == PinDirection.Input: self.inputsLayout.addItem(p) self.inputsLayout.setAlignment(p, QtCore.Qt.AlignLeft) elif rawPin.direction == PinDirection.Output: self.outputsLayout.addItem(p) self.outputsLayout.setAlignment(p, QtCore.Qt.AlignRight) p.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding) self.update() # self.nodeMainGWidget.update() self.updateNodeShape() p.syncDynamic() p.syncRenamable() if self.collapsed: p.hide() return p def collapsePinGroup(self, container): for i in range(1, container.layout().count()): item = container.layout().itemAt(i) pin = item.layout().itemAt(0) if isinstance( item.layout().itemAt(0), UIPinBase) else item.layout().itemAt(1) if pin.hasConnections: if pin.direction == PinDirection.Input: for ege in pin.connections: ege.drawDestination = container.layout().itemAt( 0).layout().itemAt(0) if pin.direction == PinDirection.Output: for ege in pin.connections: ege.drawSource = container.layout().itemAt( 0).layout().itemAt(1) item.hide() def expandPinGroup(self, container): for i in range(1, container.layout().count()): item = container.layout().itemAt(i) pin = item.layout().itemAt(0) if isinstance( item.layout().itemAt(0), UIPinBase) else item.layout().itemAt(1) if pin.hasConnections: if pin.direction == PinDirection.Input: for ege in pin.connections: ege.drawDestination = pin if pin.direction == PinDirection.Output: for ege in pin.connections: ege.drawSource = pin item.show()
def sizeHint(self, which, constraint): try: return QtCore.QSizeF(self.pinSize, self.height) except: return QGraphicsWidget.sizeHint(self, which, constraint)