Multi Change

Download multiChange.py

python, nuke

This scripts allows you to change an attributes on multiple nodes at the same time

 ⚭ multiChange demo

Here is full code ;

select
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
from PySide.QtGui import QLineEdit, QDialog, QApplication, QLabel,\
QVBoxLayout, QSpinBox, QDoubleSpinBox, QCheckBox, QGroupBox, QGridLayout,\
QComboBox, QPushButton, QHBoxLayout
import nuke


class MChanges(QDialog):
    makingContext = False
    # some knobs we don't want to evaluate
    exclude = ('onCreate', 'onDestroy', 'xpos', 'ypos', 'selected',
               'updateUI', 'knobChanged', 'icon', 'help', 'panel',
               'indicators', 'name')
    last_knob = None

    class Color(object):
        # empty class for colour handling
        pass

    def __init__(self, parent=QApplication.activeWindow()):
        ancestor = parent.findChild(QDialog, "MultiChanges")
        if ancestor:
            ancestor.done(True)
            
        super(MChanges, self).__init__(parent)
        self.setWindowTitle("MultiChanges")
        self.setObjectName("MultiChanges")
        self.setMinimumWidth(200)

        self.knob_name = QComboBox(self)
        self.knob_name.currentIndexChanged.connect(self.set_editor)

        # we create an editor for each type of variable and we update
        # their internal def in needed
        self.text_editor = QLineEdit(self)
        self.text_editor.textChanged.connect(self.apply_ctx)
        self.text_editor.value = self.text_editor.text
        self.text_editor.setValue = self.text_editor.setText
        self.int_editor = QSpinBox(self)
        self.int_editor.valueChanged.connect(self.apply_ctx)
        self.float_editor = QDoubleSpinBox(self)
        self.float_editor.valueChanged.connect(self.apply_ctx)
        self.float_editor.setSingleStep(0.1)
        self.bool_editor = QCheckBox('on', self)
        self.bool_editor.stateChanged.connect(self.apply_ctx)
        self.bool_editor.value = self.bool_editor.checkState
        self.bool_editor.setValue = self.bool_editor.setChecked
        self.color_editor = QPushButton('Color...', self)
        self.color_editor.clicked.connect(self.pick)
        def color_setValue(v):
            self.color_editor.v = v
            self.apply_ctx()
        self.color_editor.value = lambda :self.color_editor.v
        self.color_editor.setValue = color_setValue
        self.matrix_editor = QGroupBox(self)
        self.matrix_editor.setContentsMargins(0, 0, 0, 0)
        matrix_layout = QHBoxLayout()
        self.matrix_editor.setLayout(matrix_layout)
        # generator to get all DoubleSpinBox children
        def matrix_children():
            for c in self.matrix_editor.children():
                if isinstance(c, QDoubleSpinBox):
                    yield c
        # tuple's setValue
        def matrix_setValue(v):
            self.matrix_editor.v = v
            for c in matrix_children():
                c.deleteLater()
            lay = self.matrix_editor.layout()
            for t in v:
                n = QDoubleSpinBox(self)
                n.setSingleStep(0.1)
                n.setValue(t)
                n.setFixedHeight(20)
                n.valueChanged.connect(self.apply_ctx)
                lay.addWidget(n)
        self.matrix_editor.setValue = matrix_setValue
        self.matrix_editor.value = lambda :[c.value() for c in matrix_children()]

        # we create a dictionnary with a key for each type
        self.editor_grid = {bool: self.bool_editor,
                            int: self.int_editor,
                            float: self.float_editor,
                            str: self.text_editor,
                            tuple: self.matrix_editor,
                            MChanges.Color: self.color_editor}

        group = QGroupBox(self)
        grid = QGridLayout()
        group.setLayout(grid)
        for t in self.editor_grid:
            w = self.editor_grid[t]
            grid.addWidget(w)
            w.setFixedHeight(20)
        self.matrix_editor.setFixedHeight(40)
        group.setFixedHeight(55)
        group.setContentsMargins(0, 0, 0, 0)

        # setting layout
        contentBox = QVBoxLayout(self)
        contentBox.addWidget(QLabel("Knob name", self))
        contentBox.addWidget(self.knob_name)
        contentBox.addWidget(QLabel("New Value", self))
        contentBox.addWidget(group)
        self.setLayout(contentBox)

        # final updates
        self.refresh_list()
        self.set_editor()

    def pick(self):
        color = nuke.getColor(self.color_editor.value())
        if color:
            self.color_editor.setValue(color)
    
    def refresh_list(self):
        # update the QComboBox with knobs in common
        nodes = nuke.selectedNodes()
        name = self.knob_name.currentText()
        if name != '':
            self.last_knob = name

        if len(nodes) == 0:
            knobs = []
        else:
            knobs = [k for k in nodes[0].knobs() if k not in self.exclude]
            if len(nodes) > 1:
                for k in reversed(knobs):
                    for n in nodes:
                        if k not in n.knobs():
                            knobs.remove(k)
                            break
        knobs.sort()
        self.knob_name.clear()
        idx = 0 if self.last_knob not in knobs else knobs.index(self.last_knob)
        self.knob_name.addItems(knobs)
        self.knob_name.setCurrentIndex(idx)

    def apply_ctx(self):
        # we set the editor's changes on the knobs
        if not self.makingContext:
            nuke.Undo.begin()
            w = self.editor_grid[self.get_ctx()[1]]
            for o in nuke.selectedNodes():
                o[self.knob_name.currentText()].setValue(w.value())
            nuke.Undo.end()

    def get_ctx(self):
        # we get the current knob's type and value
        if len(nuke.selectedNodes()) != 0:
            n = nuke.selectedNodes()[0]
            text = self.knob_name.currentText()
            v = n[text].value()
            kt = type(v)
            # special case for color
            if (kt is int or kt is long) and 'color' in text:
                kt = MChanges.Color
        else:
            v, kt = None, None
        return v, kt

    def set_editor(self):
        # we define the correct editor widget depending on type
        self.makingContext = True
        v, kt = self.get_ctx()
        for t in self.editor_grid:
            self.editor_grid[t].setHidden(t is not kt)
            if t is kt:
                self.editor_grid[t].setValue(v)
        self.makingContext = False

    def done(self, state):
        # unregistering callbacks
        nuke.removeKnobChanged(MCKnobChanged)
        nuke.removeOnCreate(MCKnobChanged)
        self.deleteLater()


mc = MChanges()
mc.show()


def MCKnobChanged():
    # callback for MChanges()
    if nuke.thisKnob() and nuke.thisKnob().name() == "selected":
        mc.refresh_list()

# some callbacks
nuke.addKnobChanged(MCKnobChanged)
nuke.addOnCreate(MCKnobChanged)

If you have any questions... =)