diff --git a/NodeGraphQt/constants.py b/NodeGraphQt/constants.py index 59ef50e4..7e5c7ae2 100644 --- a/NodeGraphQt/constants.py +++ b/NodeGraphQt/constants.py @@ -81,6 +81,8 @@ NODE_PROP_FLOAT = 14 #: Property type represented with int widget in the properties bin. NODE_PROP_INT = 15 +#: Property type represented with button widget in the properties bin. +NODE_PROP_BUTTON = 16 # === NODE VIEWER === diff --git a/NodeGraphQt/widgets/node_widgets.py b/NodeGraphQt/widgets/node_widgets.py index 33c8ec8e..47ab743a 100644 --- a/NodeGraphQt/widgets/node_widgets.py +++ b/NodeGraphQt/widgets/node_widgets.py @@ -417,7 +417,7 @@ def __init__(self, parent=None, name='', label='', text='', ext="*"): self._ext = ext def _on_select_file(self): - file_path = file_dialog.getOpenFileName() + file_path = file_dialog.getOpenFileName(ext_filter=self._ext) file = file_path[0] or None if file: self.value = file diff --git a/NodeGraphQt/widgets/properties.py b/NodeGraphQt/widgets/properties.py index 819914f2..588c10e1 100644 --- a/NodeGraphQt/widgets/properties.py +++ b/NodeGraphQt/widgets/properties.py @@ -15,7 +15,8 @@ NODE_PROP_VECTOR3, NODE_PROP_VECTOR4, NODE_PROP_FLOAT, - NODE_PROP_INT) + NODE_PROP_INT, + NODE_PROP_BUTTON) from NodeGraphQt.widgets.file_dialog import file_dialog @@ -333,6 +334,7 @@ class _valueMenu(QtWidgets.QMenu): mouseMove = QtCore.Signal(object) mouseRelease = QtCore.Signal(object) + stepChange = QtCore.Signal() def __init__(self, parent=None): super(_valueMenu, self).__init__(parent) @@ -357,6 +359,8 @@ def mouseMoveEvent(self, event): action = self.actionAt(event.pos()) if action: + if action is not self.last_action: + self.stepChange.emit() self.last_action = action self.step = action.step elif self.last_action: @@ -389,9 +393,9 @@ def __init__(self, parent=None): self._data_type = float self.setText("0") - self.pre_x = 0 + self.pre_x = None + self.pre_val = None self._step = 1 - self._tmp_value = 0 self._speed = 0.1 self.textChanged.connect(self._on_text_changed) @@ -399,6 +403,7 @@ def __init__(self, parent=None): self.menu = _valueMenu() self.menu.mouseMove.connect(self.mouseMoveEvent) self.menu.mouseRelease.connect(self.mouseReleaseEvent) + self.menu.stepChange.connect(self._reset) steps = [0.001, 0.01, 0.1, 1, 10, 100, 1000] self.menu.set_steps(steps) @@ -407,25 +412,26 @@ def __init__(self, parent=None): def _on_text_changed(self, value): self.valueChanged.emit(self.value()) + def _reset(self): + self.pre_x = None + def mouseMoveEvent(self, event): if self.mid_state: if self.pre_x is None: self.pre_x = event.x() - self.set_step(self.menu.step) - delta = (event.x() - self.pre_x) - self._tmp_value += delta * self._speed * self._step - if abs(self._tmp_value) > self._step: - value = self.value() + delta * self._step + self.pre_val = self.value() + else: + self.set_step(self.menu.step) + delta = event.x() - self.pre_x + value = self.pre_val + int(delta*self._speed) * self._step self.setValue(value) - self._tmp_value = 0 - self.pre_x = event.x() + super(_valueEdit,self).mouseMoveEvent(event) def mousePressEvent(self, event): if event.button() == QtCore.Qt.MiddleButton: self.mid_state = True - self.pre_x = None - self._tmp_value = 0 + self._reset() self.menu.exec_(QtGui.QCursor.pos()) super(_valueEdit,self).mousePressEvent(event) @@ -448,8 +454,10 @@ def set_data_type(self, dt): def _convert_text(self,text): # int("1.0") will return error # so we use int(float("1.0")) - - value = float(text) + try: + value = float(text) + except: + value = 0.0 if self._data_type is int: value = int(value) return value @@ -506,14 +514,13 @@ def __init__(self, parent=None): self._lock = False def _on_edit_changed(self,value): - if self._lock: - return - self._lock = True self._set_slider_value(value) self.valueChanged.emit(self._edit.value()) - self._lock = False def _on_slider_changed(self,value): + if self._lock: + self._lock = False + return value = value / float(self._mul) self._edit.setValue(value) @@ -522,7 +529,7 @@ def _set_slider_value(self,value): if value == self._slider.value(): return - + self._lock = True _min = self._slider.minimum() _max = self._slider.maximum() if _min<=value<=_max: @@ -532,6 +539,7 @@ def _set_slider_value(self,value): elif value > _max and self._slider.value() != _max: self._slider.setValue(_max) + def set_min(self, value=0): self._slider.setMinimum(int(value*self._mul)) @@ -656,6 +664,21 @@ def __init__(self, parent=None): self.set_data_type(int) +class PropButton(QtWidgets.QPushButton): + value_changed = QtCore.Signal(str, object) + + def __init__(self, parent=None): + super(PropButton, self).__init__(parent) + + def set_value(self, value): + # value: list of functions + for func in value: + self.clicked.connect(func) + + def get_value(self): + return None + + WIDGET_MAP = { NODE_PROP_QLABEL: PropLabel, NODE_PROP_QLINEEDIT: PropLineEdit, @@ -671,6 +694,7 @@ def __init__(self, parent=None): NODE_PROP_VECTOR4: PropVector4, NODE_PROP_FLOAT: PropFloat, NODE_PROP_INT: PropInt, + NODE_PROP_BUTTON: PropButton } @@ -710,11 +734,14 @@ def add_widget(self, name, widget, value, label=None): if row > 0: row += 1 + label = QtWidgets.QLabel(label) label_flags = QtCore.Qt.AlignCenter | QtCore.Qt.AlignRight if widget.__class__.__name__ == 'PropTextEdit': label_flags = label_flags | QtCore.Qt.AlignTop - - self.__layout.addWidget(QtWidgets.QLabel(label), row, 0, label_flags) + elif widget.__class__.__name__ == 'PropButton': + label.setVisible(False) + widget.setText(name) + self.__layout.addWidget(label, row, 0, label_flags) self.__layout.addWidget(widget, row, 1) def get_widget(self, name): diff --git a/NodeGraphQt/widgets/viewer.py b/NodeGraphQt/widgets/viewer.py index 2482bae1..0725f222 100644 --- a/NodeGraphQt/widgets/viewer.py +++ b/NodeGraphQt/widgets/viewer.py @@ -246,6 +246,9 @@ def mousePressEvent(self, event): nodes = [i for i in items if isinstance(i, AbstractNodeItem)] pipes = [i for i in items if isinstance(i, Pipe)] + if nodes: + self.MMB_state = False + # toggle extend node selection. if self.LMB_state: if self.SHIFT_state: diff --git a/example_auto_nodes/node_base/auto_node.py b/example_auto_nodes/node_base/auto_node.py index fd0aeffc..fc6b5159 100644 --- a/example_auto_nodes/node_base/auto_node.py +++ b/example_auto_nodes/node_base/auto_node.py @@ -4,7 +4,7 @@ from NodeGraphQt import QtCore import random import copy - +import time def rand_color(seed_type): seed = id(seed_type) @@ -35,6 +35,9 @@ def __init__(self, defaultInputType=None, defaultOutputType=None): self.defaultInputType = defaultInputType self.defaultOutputType = defaultOutputType + self._cookTime = 0.0 + self._toolTip = self._setup_tool_tip() + @property def autoCook(self): return self._autoCook @@ -51,11 +54,24 @@ def autoCook(self, mode): self.defaultColor = self.get_property("color") self.set_property('color', self.stopCookColor) + @property + def cookTime(self): + return self._cookTime + + @autoCook.setter + def cookTime(self, time): + self._cookTime = time + self._update_tool_tip() + def cookNextNode(self): for nodeList in self.connected_output_nodes().values(): for n in nodeList: n.cook() + def getData(self, port): + # for custom output data + return self.get_property(port.name()) + def getInputData(self, port): # get input data by input Port,the type of "port" can be : # int : Port index @@ -77,38 +93,41 @@ def getInputData(self, port): return copy.deepcopy(self.defaultValue) for from_port in from_ports: - data = from_port.node().get_property(from_port.name()) + data = from_port.node().getData(from_port) return copy.deepcopy(data) + def when_disabled(self): + num = len(self.input_ports()) + for index, out_port in enumerate(self.output_ports()): + self.set_property(out_port.name(), self.getInputData(index % num)) + def cook(self, forceCook=False): if not self._autoCook and forceCook is not True: return - _tmp = self._autoCook - self._autoCook = False - - if self.disabled(): - num = len(self.input_ports()) - for index, out_port in enumerate(self.output_ports()): - self.set_property(out_port.name(), self.getInputData(index % num)) - self.cookNextNode() - return - if not self.needCook: return + _tmp = self._autoCook + self._autoCook = False + if self.error(): self._close_error() + _start_time = time.time() + try: self.run() except Exception as error: self.error(error) + self._autoCook = _tmp if self.error(): return + self.cookTime = time.time() - _start_time + self.cooked.emit() self.cookNextNode() @@ -128,11 +147,6 @@ def on_input_disconnected(self, to_port, from_port): return self.cook() - def set_disabled(self, mode=False): - super(AutoNode, self).set_disabled(mode) - if self.input_ports(): - self.cook() - def checkPortType(self, to_port, from_port): # None type port can connect with any other type port # types in self.matchTypes can connect with each other @@ -203,10 +217,20 @@ def add_output(self, name='output', data_type=None, multi_output=True, display_n self.set_port_type(new_port, self.defaultOutputType) return new_port + def set_disabled(self, mode=False): + super(AutoNode, self).set_disabled(mode) + self._autoCook = not mode + if mode is True: + self.when_disabled() + self.cookNextNode() + else: + self.cook() + + def _close_error(self): self._error = False self.set_property('color', self.defaultColor) - self._view._tooltip_disable(False) + self._update_tool_tip() def _show_error(self, message): if not self._error: @@ -214,10 +238,22 @@ def _show_error(self, message): self._error = True self.set_property('color', self.errorColor) - tooltip = '{}'.format(self.name()) - tooltip += '
({})
'.format(message) - tooltip += '
{}
'.format(self._view.type_) - self._view.setToolTip(tooltip) + tooltip = '
({})
'.format(message) + self._update_tool_tip(tooltip) + + def _update_tool_tip(self, message = None): + if message is None: + tooltip = self._toolTip.format(self._cookTime) + else: + tooltip = '{}'.format(self.name()) + tooltip += message + tooltip += '
{}
'.format(self._view.type_) + self.view.setToolTip(tooltip) + return tooltip + + def _setup_tool_tip(self): + tooltip = '
last cook used: {}s
' + return self._update_tool_tip(tooltip) def error(self, message=None): if message is None: