1
2 import nuke, os, re, sys, math, time
3 from nukescripts import execute_panel
4 from nukescripts import panels
5 from PySide.QtCore import *
6 from PySide.QtGui import *
7
9
10 """Parsing a filename string in search of the udim number.
11 The function returns the udim number, and None if it is not able to decode the udim value.
12
13 The udim value is a unique number that defines the tile coordinate.
14 If u,v are the real tile coordinates the equivalent udim number is calculated with the following formula:
15 udim = 1001 + u + 10 * v (Note: u >=0 && u < 10 && udim > 1000 && udim < 2000)
16
17 Redefine this function if the parsing function is not appropriate with your filename syntax."""
18
19 sequences = re.split("[._]+", f)
20
21 udim = None
22
23
24
25 for s in sequences:
26 try:
27 udim_value = int(s)
28 except ValueError:
29
30 udim_value = 0
31
32 if udim_value > 1000 and udim_value < 2000:
33 udim = udim_value
34
35 if udim == None:
36 return None
37
38 return udim
39
41 u,v = uv
42 return 1001 + u + 10 * v
43
45 if udim == None:
46 return True
47
48 if type(udim) == int:
49 return True
50
51 if type(udim) != tuple:
52 return False
53
54 u,v = udim
55
56 if type(u) == int and type(v) == int:
57 return True
58
59 return False
60
63
65 - def __init__(self, parent=None, error_msg="", udim_label="UDIM"):
66 super(UDIMErrorDialog, self).__init__(parent)
67
68
69 self.Text = QTextEdit()
70 self.OkButton = QPushButton("Ok")
71
72 hlayout = QHBoxLayout()
73 hlayout.insertSpacerItem(0, QSpacerItem(1, 1, QSizePolicy.Expanding, QSizePolicy.Minimum))
74 hlayout.addWidget(self.OkButton)
75
76
77 layout = QVBoxLayout()
78 layout.addWidget(self.Text)
79 layout.addLayout(hlayout)
80
81
82 self.setMinimumSize( 600, 400 )
83 self.setLayout(layout)
84 self.setModal(True)
85 self.setWindowTitle( udimStr("{0} files import error", udim_label))
86
87
88 self.Text.setReadOnly(True)
89 self.Text.setText(error_msg)
90
91
92 self.OkButton.setSizePolicy(QSizePolicy.Fixed, QSizePolicy.Fixed)
93 self.OkButton.clicked.connect(self.accept)
94
95
103
106
108
110 QStyledItemDelegate.initStyleOption(self, option, index)
111 if index.column() == 1:
112 option.textElideMode = Qt.ElideLeft
113
115
117 super(UDIMOptionsDialog, self).__init__(parent)
118
119 self.setWindowTitle(udim_label + " import")
120
121 self.UdimMap = []
122 self.UdimConflict = False
123 self.UdimParsingFunc = parsing_func
124 self.cellChangedConnected = False
125 self.ForceToExit = False
126 self.ErrorMsg = None
127 self.UdimLabel = udim_label
128
129
130 self.UdimList = QTableWidget(0, 3, self)
131 self.AddFilesButton = QPushButton("Add Files")
132 self.ConflictLabel = QLabel("")
133 self.Separator = QFrame()
134 self.ReadModeComboBox = QComboBox()
135 self.PostageStampCheckBox = QCheckBox("postage stamp")
136 self.GroupNodesCheckBox = QCheckBox("group nodes")
137
138 self.OkButton = QPushButton("Ok")
139 self.CancelButton = QPushButton("Cancel")
140
141
142 self.PostageStampCheckBox.setToolTip( udimStr("Enable the node postage stamp generation for all {0} files.", self.UdimLabel) )
143 self.GroupNodesCheckBox.setToolTip( udimStr("Place all {0} files in a group.", self.UdimLabel) )
144
145
146 layout = QVBoxLayout()
147 layout.addWidget(self.UdimList)
148 layout.addWidget(self.AddFilesButton)
149 layout.addWidget(self.ConflictLabel)
150 layout.addWidget(self.Separator)
151 layout.addWidget(self.ReadModeComboBox)
152 layout.addWidget(self.PostageStampCheckBox)
153 layout.addWidget(self.GroupNodesCheckBox)
154
155 hlayout = QHBoxLayout()
156 hlayout.insertSpacerItem(0, QSpacerItem(1, 1, QSizePolicy.Expanding, QSizePolicy.Minimum))
157 hlayout.addWidget(self.OkButton)
158 hlayout.addWidget(self.CancelButton)
159
160 layout.addLayout(hlayout)
161
162
163 self.setMinimumSize(800, 400)
164 self.setLayout(layout)
165 self.setModal(True)
166
167 self.Separator.setFrameShape(QFrame.HLine)
168 self.Separator.setFrameShadow(QFrame.Sunken)
169
170 self.AddFilesButton.setSizePolicy(QSizePolicy.Fixed, QSizePolicy.Fixed)
171 self.OkButton.setSizePolicy(QSizePolicy.Fixed, QSizePolicy.Fixed)
172 self.CancelButton.setSizePolicy(QSizePolicy.Fixed, QSizePolicy.Fixed)
173 self.ReadModeComboBox.setSizePolicy(QSizePolicy.Fixed, QSizePolicy.Fixed)
174
175 headerView = self.UdimList.horizontalHeader()
176 headerView.setResizeMode(0, QHeaderView.Fixed)
177 headerView.setResizeMode(1, QHeaderView.Stretch)
178 headerView.setResizeMode(2, QHeaderView.ResizeToContents)
179
180 self.UdimList.setItemDelegate(TableDelegate())
181 self.UdimList.setHorizontalHeaderLabels( [self.UdimLabel, "filename", ""] )
182 self.UdimList.setEditTriggers(QAbstractItemView.NoEditTriggers)
183
184 self.ReadModeComboBox.addItems( ["single read node", "multiple read nodes"] )
185
186
187 self.AddFilesButton.clicked.connect(self.importUdimFiles)
188 self.OkButton.clicked.connect(self.accept)
189 self.CancelButton.clicked.connect(self.reject)
190
266
268
269 if column == 2:
270 checked = self.UdimList.item(row, column)
271
272 if checked.checkState() == Qt.Checked:
273 self.UdimMap[row].enabled = True
274 else:
275 self.UdimMap[row].enabled = False
276
277
278 self.updateTableWidget()
279
281
282
283 for u in self.UdimMap:
284 if u.filename == udim_file.filename:
285 return
286
287 self.UdimMap.append(udim_file)
288
290 default_dir = None
291
292 files = nuke.getClipname( "Read File(s)", default=default_dir, multiple=True )
293
294 if files == None:
295 if len(self.UdimMap) == 0:
296 self.ForceToExit = True
297 return
298
299 warning_msg = ""
300
301
302 for f in files:
303
304
305 sequences = splitInSequence(f)
306
307 for s in sequences:
308
309 if os.path.isfile(s) == False:
310 continue
311
312
313 udim = self.UdimParsingFunc(s)
314
315
316 if checkUdimValue(udim) == False:
317 self.reject()
318 self.ErrorMsg = udimStr("Error. Wrong type returned by {0} parsing function.",self.UdimLabel )
319 raise ValueError(self.ErrorMsg)
320
321 if udim == None:
322 warning_msg = warning_msg + s + "\n"
323 warning = True
324 else:
325 uv = None
326 udim_value = 0
327
328 try:
329 udim_value = int(udim)
330 except TypeError:
331 udim_value = uv2udim(udim)
332 uv = udim
333
334 self.addUdimFile(UDIMFile(udim_value, uv, s))
335
336
337 if len(warning_msg) > 0:
338 errorMsg = udimStr( "The following files do not contain a valid {0} number: \n\n" + warning_msg, self.UdimLabel )
339 udimLabel = self.UdimLabel
340 e = UDIMErrorDialog(error_msg = errorMsg, udim_label = udimLabel)
341 e.exec_()
342
343
344 self.updateTableWidget()
345
347
348
349
350
351
352
353 idx = f.find('#')
354 if idx == -1:
355 return [f]
356
357
358 subst = ''
359 for x in range(idx, len(f)):
360 if f[x] != '#':
361 break
362 subst = subst + '#'
363
364
365 sfile = f.split(' ')
366
367
368 try :
369 frame_range = nuke.FrameRange( sfile[1] )
370 except ValueError:
371 return [f]
372
373 args = "%(#)0" + str(len(subst)) + "d"
374
375 sequences = []
376 for r in frame_range:
377
378 filename = sfile[0].replace( subst, args % {"#":r} )
379 sequences.append( filename )
380
381 return sequences
382
384 i = 1
385 while nuke.toNode ( name + str(i) ) != None:
386 i += 1
387
388 return name + str(i)
389
422
431
433
434 """ Imports a sequence of UDIM files and creates the node material tree needed.
435 This function simplifies the process of importing textures. It generates a tree of nodes which
436 adjusts the texture coordinates at rendering time for a model containing multiple texture tiles.
437 In general a tile texture coordinate can be expressed with a single value(UDIM) or with a tuple(ST or UV).
438 The udim_import function can decode a UDIM number from a filename.
439 To determine the tile coordinate encoding for a generic filename convention, the udim_import script can use an
440 external parsing function.
441
442 The redefined parsing function needs to decode a filename string and return the udim or the u,v tile coordinate
443 as an integer or tuple of integers. It should return None if the tile coordinate id can not be determined.
444
445 @param udim_parsing_func: The parsing function. This parses a filename string and returns a tile id.
446 @param udim_column_label: The name of the column in the dialog box used to show the tile id.
447 @return: None
448 """
449
450
451
452 p = UDIMOptionsDialog(parsing_func = udim_parsing_func, udim_label = udim_column_label)
453
454 try:
455 p.importUdimFiles()
456 except ValueError as e:
457 nuke.message(str(e))
458 return
459
460 if p.ForceToExit:
461 return
462
463 result = p.exec_()
464
465 if result == False:
466 if p.ErrorMsg != None:
467 nuke.message(p.ErrorMsg)
468 return
469
470 UdimMap = p.UdimMap
471 postagestamp = p.PostageStampCheckBox.isChecked()
472 makegroup = p.GroupNodesCheckBox.isChecked()
473 makesingleread = (p.ReadModeComboBox.currentIndex() == 0)
474
475 uvtile = []
476 nodes = []
477
478 read_node_width = 80
479 dot_node_width = 12
480 read_node_height = 78
481 dot_node_height = 12
482 frame_hold_width = 80
483 frame_hold_height = 28
484 other_node_height = 18
485
486 if postagestamp == False:
487 read_node_height = 28
488
489 h_spacing = 30
490 v_spacing = 20
491
492 udim_file_count = 0
493
494
495 for u in UdimMap:
496
497 if u.enabled == False:
498 continue
499
500 if os.path.isfile(u.filename) == False:
501 u.enabled = False
502 continue
503
504 udim_file_count += 1
505
506
507 if udim_file_count == 0:
508 return
509 if udim_file_count == 1:
510 makesingleread = False
511
512 selected_nodes = nuke.selectedNodes()
513
514
515 for n in selected_nodes:
516 n["selected"].setValue ( False )
517
518 groupBaseName = None
519 single_read_node = None
520 udim_file_sequence = ""
521 sequence_index = 1
522 parent_dot_frame_hold = None
523
524
525 if makesingleread:
526 single_read_node = nuke.nodes.Read()
527 parent_dot_frame_hold = single_read_node
528
529 for u in UdimMap:
530
531
532 if u.enabled == False:
533 continue
534
535
536 udim = u.udim
537 uv = u.uv
538 img = u.filename
539
540 if groupBaseName == None:
541 groupBaseName = os.path.basename(img)
542
543 xpos = None
544 ypos = None
545 udim_node = None
546
547
548 if single_read_node != None:
549 udim_file_sequence += img + "\n"
550
551
552 frame_hold_xpos = single_read_node.xpos() + (frame_hold_width + h_spacing) * (sequence_index-1)
553
554 dot_node = nuke.nodes.Dot( xpos = frame_hold_xpos + (frame_hold_width - dot_node_width) / 2,
555 ypos = single_read_node.ypos() + read_node_height + v_spacing )
556 dot_node.setInput(0, parent_dot_frame_hold)
557 parent_dot_frame_hold = dot_node
558
559
560 udim_node = nuke.nodes.FrameHold( xpos = frame_hold_xpos,
561 ypos = dot_node.ypos() + dot_node_height + v_spacing )
562
563 udim_node['first_frame'].setValue(sequence_index)
564 udim_node.setInput(0, dot_node)
565
566 xpos = udim_node.xpos()
567 ypos = udim_node.ypos() + frame_hold_height + v_spacing
568
569 nodes.append(dot_node)
570
571
572 sequence_index += 1
573 else:
574
575 udim_node = nuke.nodes.Read()
576 udim_node['file'].setValue( img )
577 udim_node['postage_stamp'].setValue( postagestamp )
578 udim_node.autoplace()
579
580 xpos = udim_node.xpos()
581 ypos = udim_node.ypos() + read_node_height + v_spacing
582
583
584 uvtile_node = nuke.nodes.UVTile2(xpos=xpos, ypos=ypos)
585 uvtile_node.setInput(0, udim_node)
586
587 if uv == None:
588 uvtile_node['udim_enable'].setValue(True)
589 uvtile_node['udim'].setValue(udim)
590 else:
591 u,v = uv
592 uvtile_node['tile_u'].setValue( u )
593 uvtile_node['tile_v'].setValue( v )
594
595 uvtile.append(uvtile_node)
596
597 nodes.append(udim_node)
598 nodes.append(uvtile_node)
599
600 if (len(uvtile) == 0):
601 return
602
603 if single_read_node != None:
604 single_read_node['file'].setValue("[lindex [knob sequence] [expr [frame]-1]]")
605 single_read_node['postage_stamp'].setValue( postagestamp )
606 single_read_node['sequence'].setValue(udim_file_sequence)
607 single_read_node['last'].setValue(sequence_index-1)
608 single_read_node['origlast'].setValue(sequence_index-1)
609
610 latest_merge = uvtile[0]
611
612 if len(uvtile) > 1:
613
614 xpos = latest_merge.xpos()
615 ypos = latest_merge.ypos() + other_node_height + v_spacing
616
617 dot_node = nuke.nodes.Dot( xpos=xpos + (read_node_width - dot_node_width) / 2,
618 ypos=ypos + (other_node_height - dot_node_height) / 2)
619
620 dot_node.setInput(0, latest_merge)
621 nodes.append(dot_node)
622 latest_merge = dot_node
623
624 for x in range(1, len(uvtile)):
625
626 xpos = uvtile[x].xpos()
627 ypos = uvtile[x].ypos() + other_node_height + v_spacing
628
629 mergemat_node = nuke.nodes.MergeMat(xpos=xpos, ypos=ypos)
630 mergemat_node.setInput(0, latest_merge)
631 mergemat_node.setInput(1, uvtile[x])
632
633 latest_merge = mergemat_node
634 nodes.append(mergemat_node)
635
636
637
638 if len(uvtile) > 1:
639 xpos = latest_merge.xpos()
640 ypos = latest_merge.ypos() + other_node_height + v_spacing
641
642 multitexture = nuke.nodes.MultiTexture(xpos=xpos, ypos=ypos)
643 multitexture.setInput(0, latest_merge)
644 latest_merge = multitexture
645 nodes.append(latest_merge)
646
647 for n in nodes:
648 n["selected"].setValue ( True )
649
650 if makegroup == True:
651 latest_merge = udim_group( nodes )
652
653
654 split = re.split("[._]+", groupBaseName)
655 name = split[0]
656
657 latest_merge.setName( findNextName(name) )
658
659 nodes = []
660 nodes.append( latest_merge )
661
662 if single_read_node != None:
663 single_read_node["selected"].setValue ( True )
664
665 if len(selected_nodes) == 1:
666 if single_read_node != None:
667 nodes.append(single_read_node)
668
669 allign_nodes( nodes, selected_nodes[0] )
670
671 for n in selected_nodes:
672 n["selected"].setValue ( True )
673 if n.Class() == 'ReadGeo2':
674 n.setInput(0, latest_merge)
675