-
Notifications
You must be signed in to change notification settings - Fork 28
Expand file tree
/
Copy pathwindow.py
More file actions
1537 lines (1328 loc) · 81.4 KB
/
window.py
File metadata and controls
1537 lines (1328 loc) · 81.4 KB
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
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
r"""
Name : skinner.window.py
Author : Eric Pavey - warpcat@gmail.com - www.akeric.com
Creation Date : 2019-09-28
Description : Window / UI code for the skinner tool. For the back-end API code,
see skinner.core.py
Dependencies:
* Python 3.x+
* numpy & scipy on the path for import. To install them for
your version of Maya, see the skinner.core.py -> confirmDependencies function.
Updates:
2021-09-26 : v1.0.0 : Ready for release.
2021-10-01 : v1.0.1 : Bug fixing the 'Verbose Logging' checkbox not having
its prefs stored. Updating Window to have additional field for working
with version control.
2021-10-08 : v1.0.2 : Updating to expose 'Post Smooth Steps' field. Other minor
UI improvments.
2021-10-19 : v1.0.3 : Adding notes about P4 usage in the Export tab.
2021-10-21 : v1.0.4 : Setting 'Unbind First?' to True by default, to get around
certain crashes when setting weights on 'old' skinClusters.
2021-10-22 : v1.0.5 : Updating to add new import option that when a SkinChunk
can't be found by mesh name, can if find one by matching vert count/order?
2021-10-25 : v1.0.6 : Adding new section to print 'import overview' results.
Adding new 'Export -> Set to bindpose?' checkbox. Updating with link to
the official docs!
2021-11-01 : v1.0.7 : Updating with temp icon!
2021-11-06 : v1.0.11 : Adding link to github page.
2021-11-03 : v1.0.15 : Upating App.exportSkin to handle updates to core.exportSkin,
core.exportTempSkin, and core.importTempSkin.
2021-12-07 : v1.0.16 : Adding version info to SkinChunks, updating print code.
2021-12-15 : v1.1.0 : Updating import tab to include new 'Import Using Pre-Deformed
Shape Positions?'
2021-12-19 : v1.1.1 : Updating enable/disable logic or 'Import Using Pre-Deformed
Shape Positions?', 'Set To Bindpose?', and 'Unbind First?'.
2021-12-30 : v1.1.2 : Updating all source to use Python3 type hint notation.
2021-12-30 : v1.1.3 : Updating Window extras tab with separators and install path.
2022-03-09 : v1.1.7 : Updating to add additional post-smoothing options.
2024-06-10 : v1.2.0 : Rearranging some of the App import UI elements. Bugfixing
App.importSkin : It wasn't closing the undoChunk. Adding the 'Auto-Fix Broken
skinCluster' to the 'Extras' tab. Updating tooltips, making multi-line.
2024-10-26 : v1.3.0 : Updating to support PySide6, adding 'Reset Preferences'
button to the 'Extras' tab.
Examples:
# Launch the window:
import skinner.window
skinner.window.App()
"""
import os
from functools import partial as callback
import maya.cmds as mc
import maya.api.OpenMaya as om2
from maya.app.general.mayaMixin import MayaQWidgetBaseMixin
try:
from PySide6 import QtWidgets, QtCore, QtGui
except:
from PySide2 import QtWidgets, QtCore, QtGui
from . import utils
from . import core
from . import __version__, __documentation__, __source__
#-----------------------
# PySide QSetting settings:
SETTING_LAST_SAVE_PATH = "setting_skinner_lastSavePath"
SETTING_LAST_TAB = "setting_skinner_lastTab"
SETTING_FALLBACK_SKIN_METHOD = "setting_skinner_fallbackSkinMethod"
SETTING_NUM_NEAREST_NEIGHBORS = "setting_skinner_numNearestNeighbors"
SETTING_NEARSET_NEIGHBOR_MULT = "setting_skinner_nearestNeighborMult"
SETTING_BUILD_MISSING_INFS = "setting_skinner_buildMissingInfs"
SETTING_FORCE_UBERCHUNK = "setting_skinner_forceUberChunk"
SETTING_UNBIND_FIRST = "setting_skinner_unbindFirst"
SETTING_SELECT_INSTEAD = "setting_skinner_selectInstead"
SETTING_VERBOSE_LOG = "setting_skinner_verboseLogging"
SETTING_MIN_PRINT_INDEX = "setting_skinner_minPrintIndex"
SETTING_MAX_PRINT_INDEX = "setting_skinner_maxPrintIndex"
SETTING_VERT_NORMAL_FILTER = "setting_skinner_vertNormalFilter"
SETTING_VERT_NORMAL_TOLLERANCE = "setting_skinner_vertNormalTollerance"
SETTING_VC_CALL = "setting_skinner_vcCall"
SETTING_DEPOT_ROOT = "setting_skinner_depotRoot"
SETTING_AUTO_FILL_DIR = "setting_skinner_autoFilldir"
SETTING_POST_SMOOTH_STEPS = "settings_skinner_postSmoothSteps"
SETTING_POST_DIFF_SMOOTH = "settings_skinner_postSmoothDiff"
SETTING_LOAD_BY_VERT_COUNT_NORMAL = "settings_skinner_loadByVertCountNormal"
SETTING_IMPORT_OVERVIEW = "settings_skinner_import_overview"
SETTINGS_IMPORT_USE_PRE_DEFORMED_SHAPE = "settings_skinner_importUsePreDeformedShape"
SETTING_IMPORT_SET_TO_BINDPOSE = "settings_skinner_importSetToBindpose"
SETTING_EXPORT_SET_TO_BINDPOSE = "settings_skinner_exportSetToBindpose"
PRINT_DATA_CHECKBOXES = [
"version", "sknrNdarrayMarker", "meshShape", "creationDate",
"importPath", "user", "skinMethod", "meshVertCount",
"numVerts", "vertIds", "infNum", "influences",
"hasPreDeformedData", "atBindPose",
"blendWeightsPerVert", "infWeightsPerVert", "normalsPerVert", "neighbors"
]
REQUESTED_DEFAULTS = {
SETTING_FALLBACK_SKIN_METHOD: 1,
SETTING_NUM_NEAREST_NEIGHBORS: 3,
SETTING_NEARSET_NEIGHBOR_MULT: 2.0,
SETTING_VERT_NORMAL_FILTER: 0,
SETTING_VERT_NORMAL_TOLLERANCE: "0.75",
SETTING_IMPORT_SET_TO_BINDPOSE: False,
SETTINGS_IMPORT_USE_PRE_DEFORMED_SHAPE: True,
SETTING_BUILD_MISSING_INFS: True,
SETTING_UNBIND_FIRST: False,
SETTING_POST_SMOOTH_STEPS: 2,
SETTING_POST_DIFF_SMOOTH: 0.25,
SETTING_LOAD_BY_VERT_COUNT_NORMAL: True,
SETTING_FORCE_UBERCHUNK: False,
SETTING_SELECT_INSTEAD: False,
SETTING_IMPORT_OVERVIEW: 2,
SETTING_EXPORT_SET_TO_BINDPOSE: True,
SETTING_VERBOSE_LOG: True,
SETTING_MIN_PRINT_INDEX: "0",
SETTING_MAX_PRINT_INDEX: "0",
}
#-----------------------
# UI Tools
def makeSeparator(mode="horizontal") -> QtWidgets.QFrame:
"""
Make a QFrame designed to be a horizontal/vertical separator.
Parameters:
mode : string : Default "horizontal". Also supports "vertical".
Return : QtWidgets.QFrame
"""
wiget_separator = QtWidgets.QFrame()
if mode == "horizontal":
wiget_separator.setFrameStyle(QtWidgets.QFrame.HLine)
elif mode == "vertical":
wiget_separator.setFrameStyle(QtWidgets.QFrame.VLine)
wiget_separator.setSizePolicy(QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Minimum)
return wiget_separator
#-----------------------
# Window Code
class App(MayaQWidgetBaseMixin, QtWidgets.QWidget):
"""
Interactive UI for the humans.
"""
name = "skinnerWindow"
title = 'Skinner'
def __init__(self, vcExecCmd=None, vcDepotRoot=None, autoFillSubdir=None, docsOverride=__documentation__,
*args, **kwargs):
"""
Init our main window. There are certain defaults that you may want exposed
to your team consistently: That's what the below parameters are for.
Parameters:
vcExecCmd : string/None : Default None : Project level config: If provided
here, this value will be auto-filled into the Export tab's Version
Control 'Exec Command' section and set i tto be read-only. For
details on what this string is, please see the docstring of
skinner.core.py -> exportSkin.
vcDepotRoot : string/None : Default None : Project level config: If provided
here, this value will be auto-filled into the Export tab's Version
Control 'Depot Root' section, and set it to be read-only. For
details on what this string is, please see the docstring of
skinner.core.py -> exportSkin.
autoFillSubdir : string/None : Default None : Project level config: If
provided here, this value will be auto-filled into the Extra tab's
"'Auto-Fill' Subdir" field, and set it to be read-only. This path is
auto appended to the end of the dirname of the currently open file
when the 'Auto-Fill' buttons are pressed, so that you can easily
export weights to a standadized subdir structure.
docsOverride : string/None : Default skinner.core.DOCS : Project level config:
If overridden here, this is a web address to your own custom documentation
for the tool that is launched by the 'Documentation...' button in
the Extra's tab. If this isn't provided, then the link in the
skinner.core.DOCS global is used.
"""
super(App, self).__init__(*args, **kwargs)
if mc.window(App.name, exists=True):
mc.deleteUI(App.name)
self.vcExecCmd = vcExecCmd
self.vcDepotRoot = vcDepotRoot
self.autoFillSubdir = autoFillSubdir
self.docsOverride = docsOverride
self.setObjectName(App.name)
self.setWindowTitle("%s : %s"%(App.title, __version__))
self.setWindowFlags(QtCore.Qt.Window)
self.setProperty("saveWindowPref", True) # Save prefs on exit.
self.setAttribute(QtCore.Qt.WA_DeleteOnClose)
self.settings = QtCore.QSettings("AK_Eric", App.name)
iconPath = utils.getIconPath()
if iconPath:
self.setWindowIcon(QtGui.QIcon(iconPath))
utils.confirmDependencies()
self.nnOptions = []
self.weightPaths = []
self.widgets_printerCheckBoxes = []
self.populate()
# Reload the last place/side the window was in:
try:
self.restoreGeometry(self.settings.value("geometry", ""))
except TypeError:
pass
self.show()
# Filled out below
def closeEvent(self, event:QtCore.QEvent):
"""
Overridden supeclass method: Save any settings as we close the window.
"""
# Save the last place/size the window was in:
self.settings.setValue("geometry", self.saveGeometry())
event.accept()
def hideEvent(self, event:QtCore.QEvent):
"""
Delete the window instead of hiding it when presssing the 'X' button.
"""
if not self.isMinimized():
self.close()
self.deleteLater()
def _getDefault(self, settingKey:str):
"""
Return the canonical default for a tracked UI setting.
"""
return REQUESTED_DEFAULTS[settingKey]
def _settingsValue(self, settingKey:str):
"""
Read a QSetting using the canonical default for this UI.
"""
return self.settings.value(settingKey, self._getDefault(settingKey))
def _registerResetContext(self, widget:QtWidgets.QWidget, resetCallback):
"""
Attach a right-click menu to reset a widget to default.
"""
widget.setContextMenuPolicy(QtCore.Qt.CustomContextMenu)
widget.customContextMenuRequested.connect(
callback(self._showResetContextMenu, widget, resetCallback)
)
def _showResetContextMenu(self, widget:QtWidgets.QWidget, resetCallback, point):
"""
Show context menu with 'Reset To Default' action.
"""
menu = QtWidgets.QMenu(widget)
action = menu.addAction("Reset To Default")
if hasattr(menu, "exec"):
selected = menu.exec(widget.mapToGlobal(point))
else:
selected = menu.exec_(widget.mapToGlobal(point))
if selected == action:
resetCallback()
def _applyRequestedDefaults(self):
"""
Apply defaults for requested UI controls and save to settings.
"""
self.widget_fallbackRadioGroup.button(self._getDefault(SETTING_FALLBACK_SKIN_METHOD)).setChecked(True)
self.cbFallbackMethod(self.widget_fallbackRadioGroup.checkedButton())
self.widget_nearestNeighborNum.setText(str(self._getDefault(SETTING_NUM_NEAREST_NEIGHBORS)))
self.cbNearestNeighborOptions()
self.widget_nearestNeighborDistMult.setText(str(self._getDefault(SETTING_NEARSET_NEIGHBOR_MULT)))
self.cbNearestNeighborOptions()
self.widget_useVertNormal.setChecked(bool(self._getDefault(SETTING_VERT_NORMAL_FILTER)))
self.widget_vertNormalTollerance.setText(str(self._getDefault(SETTING_VERT_NORMAL_TOLLERANCE)))
self.cbVertNormal()
self.widget_importSetBindpose.setChecked(bool(self._getDefault(SETTING_IMPORT_SET_TO_BINDPOSE)))
self.widget_usePreDeformedShape.setChecked(bool(self._getDefault(SETTINGS_IMPORT_USE_PRE_DEFORMED_SHAPE)))
self.cbImpoprtUsingPreDeformedShapePos()
self.widget_buildMissingInfs.setChecked(bool(self._getDefault(SETTING_BUILD_MISSING_INFS)))
self.cbMissingInfs()
self.widget_unbindFirst.setChecked(bool(self._getDefault(SETTING_UNBIND_FIRST)))
self.cbUnbindFirst()
self.widget_postSmooth.setValue(int(self._getDefault(SETTING_POST_SMOOTH_STEPS)))
self.cbPostSmoothSteps()
self.widget_postSmoothWeightDiff.setText(str(self._getDefault(SETTING_POST_DIFF_SMOOTH)))
self.cbPostSmoothDiff()
self.widget_loadByVeryCountOrderCheck.setChecked(bool(self._getDefault(SETTING_LOAD_BY_VERT_COUNT_NORMAL)))
self.cbLoadVyVertcountOrder()
self.widget_forceUberChunk.setChecked(bool(self._getDefault(SETTING_FORCE_UBERCHUNK)))
self.cbForceUberChunk()
self.widget_selectInstead.setChecked(bool(self._getDefault(SETTING_SELECT_INSTEAD)))
self.cbSelInstead()
self.widget_importOvererviewGroup.button(self._getDefault(SETTING_IMPORT_OVERVIEW)).setChecked(True)
self.cbImportOverview(self.widget_importOvererviewGroup.checkedButton())
self.widget_exportSetBindpose.setChecked(bool(self._getDefault(SETTING_EXPORT_SET_TO_BINDPOSE)))
self.cbExportSetToBindpose()
self.widget_verboseLogging.setChecked(bool(self._getDefault(SETTING_VERBOSE_LOG)))
self.cbVerboseLog()
self.widget_minPrintIndex.setText(str(self._getDefault(SETTING_MIN_PRINT_INDEX)))
self.widget_maxPrintIndex.setText(str(self._getDefault(SETTING_MAX_PRINT_INDEX)))
self.cbMinMaxPrintIndinces()
for widget in self.widgets_printerCheckBoxes:
widget.setChecked(True)
def populate(self):
"""
Create the primary contents of our Window/Widget.
"""
self.layout_main = QtWidgets.QVBoxLayout(self)
if self.layout_main:
self.widget_tab = QtWidgets.QTabWidget()
self.layout_main.addWidget(self.widget_tab)
if self.widget_tab:
widget_importTab = QtWidgets.QWidget()
widget_exportTab = QtWidgets.QWidget()
widget_extrasTab = QtWidgets.QWidget()
self.widget_tab.addTab(widget_importTab, "Import")
self.widget_tab.addTab(widget_exportTab, "Export")
self.widget_tab.addTab(widget_extrasTab, "Extras")
tabIndex = self.settings.value(SETTING_LAST_TAB, 1)
self.widget_tab.setCurrentIndex(tabIndex)
self.widget_tab.currentChanged.connect(self.cbTabChanged)
#---------------------------------------------------------------
# IMPORT TAB
layout_import = QtWidgets.QVBoxLayout()
widget_importTab.setLayout(layout_import)
if widget_importTab:
layout_importPath = QtWidgets.QHBoxLayout()
layout_import.addLayout(layout_importPath)
if layout_importPath:
layout_importPath.addWidget(QtWidgets.QLabel("1: Path:"))
self.widget_importPath = QtWidgets.QLineEdit()
self.widget_importPath.setReadOnly(True)
layout_importPath.addWidget(self.widget_importPath)
widget_autoFillImport = QtWidgets.QPushButton("<- Auto-Fill")
layout_importPath.addWidget(widget_autoFillImport)
widget_autoFillImport.setToolTip("Auto-fill the path based on the current scene name, and (optional) 'Auto-fill subdir' in the Extras tab.")
widget_autoFillImport.clicked.connect(self.cbAutoFillPath)
widget_importBrowser = QtWidgets.QPushButton("...")
widget_importBrowser.setToolTip("Browse to the skinner file to import.")
layout_importPath.addWidget(widget_importBrowser)
widget_importBrowser.clicked.connect(callback(self.cbFileBrowser, "import"))
layout_import.addWidget(makeSeparator())
layout_fbSkinMethod = QtWidgets.QHBoxLayout()
layout_import.addLayout(layout_fbSkinMethod)
if layout_fbSkinMethod:
widget_fallBackLabel = QtWidgets.QLabel("2: Fallback Skinning Method (FSM):")
layout_fbSkinMethod.addWidget(widget_fallBackLabel)
widget_fallBackLabel.setToolTip("For each mesh, if the vert count/order isn't 1:1, the algorithm to use to generate the new weights.")
self.widget_fallbackRadioGroup = QtWidgets.QButtonGroup()
self.widget_closestNeighbors = QtWidgets.QRadioButton("Closest Neighbors")
self.widget_closestNeighbors.setToolTip("Interpolate the weights of the closest imported vert neighbors, based on the below options.")
self.widget_closestPoint = QtWidgets.QRadioButton("Closest Point")
self.widget_closestPoint.setToolTip("Use the weights of the single closest imported vert position.")
self.widget_fallbackRadioGroup.addButton(self.widget_closestNeighbors, 1)
self.widget_fallbackRadioGroup.addButton(self.widget_closestPoint, 2)
layout_fbSkinMethod.addWidget(self.widget_closestNeighbors)
layout_fbSkinMethod.addWidget(self.widget_closestPoint)
fallbackMethod = int(self._settingsValue(SETTING_FALLBACK_SKIN_METHOD))
if fallbackMethod == 1:
self.widget_closestNeighbors.setChecked(True)
else:
self.widget_closestPoint.setChecked(True)
self.widget_fallbackRadioGroup.buttonClicked.connect(self.cbFallbackMethod)
layout_nnOptions = QtWidgets.QHBoxLayout()
layout_import.addLayout(layout_nnOptions)
if layout_nnOptions:
widget_nnOptionsLabel = QtWidgets.QLabel("Closest Neighbor Options:")
layout_nnOptions.addWidget(widget_nnOptionsLabel)
self.nnOptions.append(widget_nnOptionsLabel)
widget_nnOptionsLabel.setToolTip("Options used when the 'Fallback Skinning Method' is set to 'Closest Neighbors'")
layout_nnOptions.addStretch()
widget_numNearNeighborsLabel = QtWidgets.QLabel("Number Closest Neighbors:")
layout_nnOptions.addWidget(widget_numNearNeighborsLabel)
self.nnOptions.append(widget_numNearNeighborsLabel)
widget_numNearNeighborsLabel.setToolTip("Interpolate weights based on these number of neighbor verts, as long as they're within 'closest vert distance * nearest neighbor distance mult'.\nZero or less means use all the neighbors found within that distance.")
self.widget_nearestNeighborNum = QtWidgets.QLineEdit()
nearNeighborValidator = QtGui.QIntValidator()
nearNeighborValidator.setBottom(0)
self.widget_nearestNeighborNum.setValidator(nearNeighborValidator)
nnVal = self._settingsValue(SETTING_NUM_NEAREST_NEIGHBORS)
self.widget_nearestNeighborNum.setText(str(nnVal))
layout_nnOptions.addWidget(self.widget_nearestNeighborNum)
self.widget_nearestNeighborNum.textChanged.connect(self.cbNearestNeighborOptions)
self.nnOptions.append(self.widget_nearestNeighborNum)
layout_nnOptions.addStretch()
widget_nearNeighborDistMultLabel = QtWidgets.QLabel("Nearest Neighbor Distance Mult:")
layout_nnOptions.addWidget(widget_nearNeighborDistMultLabel )
self.nnOptions.append(widget_nearNeighborDistMultLabel)
widget_nearNeighborDistMultLabel.setToolTip("Based on the distance to the closest target vert, what size radius (based on multiply by this value)\naround that should be searched for 'neigbor verts'?")
self.widget_nearestNeighborDistMult = QtWidgets.QLineEdit()
nearNeighborValidator = QtGui.QDoubleValidator()
nearNeighborValidator.setBottom(1.0)
self.widget_nearestNeighborDistMult.setValidator(nearNeighborValidator)
distMult = self._settingsValue(SETTING_NEARSET_NEIGHBOR_MULT)
self.widget_nearestNeighborDistMult.setText(str(float(distMult)))
layout_nnOptions.addWidget(self.widget_nearestNeighborDistMult)
self.widget_nearestNeighborDistMult.textChanged.connect(self.cbNearestNeighborOptions)
self.nnOptions.append(self.widget_nearestNeighborDistMult)
layout_import.addWidget(makeSeparator())
layout_vertNormal = QtWidgets.QHBoxLayout()
layout_import.addLayout(layout_vertNormal)
if layout_vertNormal:
layout_vertNormal.addWidget(QtWidgets.QLabel("3: Vert Normal Options:"))
layout_vertNormal.addStretch()
tt_vertNormal = "Used to reduce 'stretching' source verts during import when there was overlapping mesh during export:\nIf checked, target verts that don't have their normals within the tolerance to a source vert will be rejecetd."
self.widget_useVertNormal = QtWidgets.QCheckBox("Use Vert Normal Filter")
self.widget_useVertNormal.setToolTip(tt_vertNormal)
layout_vertNormal.addWidget(self.widget_useVertNormal)
if bool(int(self._settingsValue(SETTING_VERT_NORMAL_FILTER))):
self.widget_useVertNormal.setChecked(True)
self.widget_useVertNormal.clicked.connect(self.cbVertNormal)
layout_vertNormal.addStretch()
self.widget_vertNormalTolleranceLabel = QtWidgets.QLabel("Vert Normal Tolerance:")
self.widget_vertNormalTolleranceLabel.setToolTip(tt_vertNormal)
layout_vertNormal.addWidget(self.widget_vertNormalTolleranceLabel)
tolVal = self._settingsValue(SETTING_VERT_NORMAL_TOLLERANCE)
self.widget_vertNormalTollerance = QtWidgets.QLineEdit(str(tolVal))
self.widget_vertNormalTollerance.setToolTip(tt_vertNormal)
normalTolValid = QtGui.QDoubleValidator()
normalTolValid.setBottom(-1.0)
normalTolValid.setTop(1.0)
self.widget_vertNormalTollerance.setValidator(normalTolValid)
self.widget_vertNormalTollerance.editingFinished.connect(self.cbVertNormal)
layout_vertNormal.addWidget(self.widget_vertNormalTollerance)
if not bool(int(self._settingsValue(SETTING_VERT_NORMAL_FILTER))):
self.widget_vertNormalTollerance.setDisabled(True)
self.widget_vertNormalTolleranceLabel.setDisabled(True)
layout_vertNormal.addStretch()
layout_import.addWidget(makeSeparator())
layout_poseOptions = QtWidgets.QHBoxLayout()
layout_import.addLayout(layout_poseOptions)
if layout_poseOptions:
layout_poseOptions.addWidget(QtWidgets.QLabel("4: Posing Options:"))
#layout_poseOptions.addStretch()
self.widget_importSetBindpose = QtWidgets.QCheckBox("Set To Bindpose?")
layout_poseOptions.addWidget(self.widget_importSetBindpose)
self.widget_importSetBindpose.setToolTip("Set all influences to their bindpose before importing the new data?\nUnnecessary if 'Import Using Pre-Deformed Shape' is checked. This happens before 'Unbind First'")
if bool(int(self._settingsValue(SETTING_IMPORT_SET_TO_BINDPOSE))):
self.widget_importSetBindpose.setChecked(True)
self.widget_importSetBindpose.clicked.connect(self.cbImportSetToBindpose)
#layout_poseOptions.addStretch()
self.widget_usePreDeformedShape = QtWidgets.QCheckBox("Import Using Pre-Deformed Shape Positions?")
layout_poseOptions.addWidget(self.widget_usePreDeformedShape)
self.widget_usePreDeformedShape.setToolTip("If checked and a 'Fallback Skinning Method' is used during import, use the positions of the pre-deformed shape node (intermediateObject) for import,\nrather than the current (possibly deformed) worldspace locations.\nThis also uses the 'pre-deformed' worldspace positions in the SkinChunk.")
if bool(int(self._settingsValue(SETTINGS_IMPORT_USE_PRE_DEFORMED_SHAPE))):
self.widget_usePreDeformedShape.setChecked(True)
self.widget_usePreDeformedShape.clicked.connect(self.cbImpoprtUsingPreDeformedShapePos)
layout_import.addWidget(makeSeparator())
layout_moreOptions = QtWidgets.QHBoxLayout()
layout_import.addLayout(layout_moreOptions)
if layout_moreOptions:
layout_moreOptions.addWidget(QtWidgets.QLabel("5: Influence Options:"))
self.widget_buildMissingInfs = QtWidgets.QCheckBox("Build Missing Influences?")
layout_moreOptions.addWidget(self.widget_buildMissingInfs)
self.widget_buildMissingInfs.setToolTip("Build any missing joint influences (+ try to reparent them) this skinning counts on?\nIf any are missing, the skin import will fail.")
if bool(int(self._settingsValue(SETTING_BUILD_MISSING_INFS))):
self.widget_buildMissingInfs.setChecked(True)
self.widget_buildMissingInfs.clicked.connect(self.cbMissingInfs)
#layout_moreOptions.addStretch()
self.widget_unbindFirst = QtWidgets.QCheckBox("Unbind First?")
layout_moreOptions.addWidget(self.widget_unbindFirst)
self.widget_unbindFirst.setToolTip("If any mesh is currently skinned, unbind it before import?\nThis will set the mesh back to the bindpose before the import.\nOtherwise the old/new skinning is merged together.")
if bool(int(self._settingsValue(SETTING_UNBIND_FIRST))):
self.widget_unbindFirst.setChecked(True)
self.widget_unbindFirst.clicked.connect(self.cbUnbindFirst)
layout_import.addWidget(makeSeparator())
layout_postSmooth = QtWidgets.QHBoxLayout()
layout_import.addLayout(layout_postSmooth)
if layout_postSmooth:
tt_smooth = "If skin weights are loaded by FSM (meaning, not 1:1 vert order), should the tool apply any post-smoothing to make it look better?\nIt wil only smooth if the source mesh has more verts than the target SkinChunk."
widget_smoothLabel = QtWidgets.QLabel("6: Post-Skinning Smooth Options:")
layout_postSmooth.addWidget(widget_smoothLabel)
widget_smoothLabel.setToolTip(tt_smooth)
layout_postSmooth.addStretch()
tt_postSmoothSteps = "This is the number of iterations, set to 0 to disable. 2 is default."
widget_smoothLabel = QtWidgets.QLabel("Smooth Steps:")
layout_postSmooth.addWidget(widget_smoothLabel)
widget_smoothLabel.setToolTip(tt_postSmoothSteps)
self.widget_postSmooth = QtWidgets.QSpinBox()
layout_postSmooth.addWidget(self.widget_postSmooth)
self.widget_postSmooth.setMinimum(0)
postSmoothVal = int(self._settingsValue(SETTING_POST_SMOOTH_STEPS))
self.widget_postSmooth.setValue(postSmoothVal)
self.widget_postSmooth.setToolTip(tt_postSmoothSteps)
self.widget_postSmooth.valueChanged.connect(self.cbPostSmoothSteps)
layout_postSmooth.addStretch()
tt_postSmoothWeightDiff = "Weights will only be smoothed if their difference is GREATER than this value.\nRange from 0.01 -> 1.0, default 0.25 : *Smaller* values here will smooth *more* weights."
widget_postSmoothWeightDiffLabel = QtWidgets.QLabel("Weight Difference Threhold:")
layout_postSmooth.addWidget(widget_postSmoothWeightDiffLabel)
widget_postSmoothWeightDiffLabel.setToolTip(tt_postSmoothWeightDiff)
self.widget_postSmoothWeightDiff = QtWidgets.QLineEdit()
weightDiffValidator = QtGui.QDoubleValidator()
weightDiffValidator.setBottom(0.01)
weightDiffValidator.setTop(1.0)
self.widget_postSmoothWeightDiff.setToolTip(tt_postSmoothWeightDiff)
self.widget_postSmoothWeightDiff.setValidator(weightDiffValidator)
weightDiff = self._settingsValue(SETTING_POST_DIFF_SMOOTH)
self.widget_postSmoothWeightDiff.setText(str(float(weightDiff)))
layout_postSmooth.addWidget(self.widget_postSmoothWeightDiff)
self.widget_postSmoothWeightDiff.textChanged.connect(self.cbPostSmoothDiff)
#self.nnOptions.append(self.widget_postSmoothWeightDiff)
layout_postSmooth.addStretch()
layout_import.addWidget(makeSeparator())
layout_debugOptions = QtWidgets.QHBoxLayout()
layout_import.addLayout(layout_debugOptions)
if layout_debugOptions:
layout_debugOptions.addWidget(QtWidgets.QLabel("7: Debug Options:"))
layout_debugOptions.addStretch()
tt_loadByVertCountOrder = "Default On: If a mesh can't find a SkinChunk name match, should it try to find one by vert count / order match?\nUsually this is only disabled for debugging purposes."
self.widget_loadByVeryCountOrderCheck = QtWidgets.QCheckBox("Load By Vert Count / Order?")
self.widget_loadByVeryCountOrderCheck.setToolTip(tt_loadByVertCountOrder)
if bool(int(self._settingsValue(SETTING_LOAD_BY_VERT_COUNT_NORMAL))):
self.widget_loadByVeryCountOrderCheck.setChecked(True)
layout_debugOptions.addWidget(self.widget_loadByVeryCountOrderCheck)
self.widget_loadByVeryCountOrderCheck.clicked.connect(self.cbLoadVyVertcountOrder)
layout_debugOptions.addStretch()
self.widget_forceUberChunk = QtWidgets.QCheckBox("Force Import From UberChunk?")
layout_debugOptions.addWidget(self.widget_forceUberChunk)
self.widget_forceUberChunk.setToolTip("Default Off: Force the weight import to use the point-cloud data in the UberChunk,\ninstead of trying to find SkinChunk mesh name matches.")
if bool(int(self._settingsValue(SETTING_FORCE_UBERCHUNK))):
self.widget_forceUberChunk.setChecked(True)
self.widget_forceUberChunk.clicked.connect(self.cbForceUberChunk)
layout_debugOptions.addStretch()
self.widget_selectInstead = QtWidgets.QCheckBox("Select Instead Of Skin")
layout_debugOptions.addWidget(self.widget_selectInstead)
self.widget_selectInstead.setToolTip("Default Off: Instead of importing/applying the skinning, select the verts that will get skinning applied based on the loaded data.\nNote, this will fail if any of the import options will cause the existing skinCluster dasta to be changed (like adding missing influences).")
if bool(int(self._settingsValue(SETTING_SELECT_INSTEAD))):
self.widget_selectInstead.setChecked(True)
self.widget_selectInstead.clicked.connect(self.cbSelInstead)
layout_import.addWidget(makeSeparator())
layout_printOptions = QtWidgets.QHBoxLayout()
layout_import.addLayout(layout_printOptions)
if layout_printOptions:
widget_printReportLabel = QtWidgets.QLabel("8: Print Import Overview:")
layout_printOptions.addWidget(widget_printReportLabel)
widget_printReportLabel.setToolTip("Should an 'import overview' report be printed to the Script Editor?")
self.widget_importOvererviewGroup = QtWidgets.QButtonGroup()
self.widget_noOverview = QtWidgets.QRadioButton("None")
self.widget_noOverview.setToolTip("Print no report")
self.widget_overviewByType = QtWidgets.QRadioButton("By Import Type")
self.widget_overviewByType.setToolTip("Organize the report based on import type.")
self.widget_overviewByMesh = QtWidgets.QRadioButton("By Mesh Name")
self.widget_overviewByMesh.setToolTip("Organize the report based on mesh name.")
self.widget_importOvererviewGroup.addButton(self.widget_noOverview, 1)
self.widget_importOvererviewGroup.addButton(self.widget_overviewByType, 2)
self.widget_importOvererviewGroup.addButton(self.widget_overviewByMesh, 3)
for widget in (self.widget_noOverview, self.widget_overviewByType, self.widget_overviewByMesh):
layout_printOptions.addWidget(widget)
overviewType = int(self._settingsValue(SETTING_IMPORT_OVERVIEW))
if overviewType == 1:
self.widget_noOverview.setChecked(True)
elif overviewType == 2:
self.widget_overviewByType.setChecked(True)
elif overviewType == 3:
self.widget_overviewByMesh.setChecked(True)
self.widget_importOvererviewGroup.buttonClicked.connect(self.cbImportOverview)
layout_import.addWidget(makeSeparator())
layout_importButs = QtWidgets.QGridLayout()
layout_import.addLayout(layout_importButs)
if layout_importButs:
widget_import = QtWidgets.QPushButton("Import From Path")
layout_importButs.addWidget(widget_import,0,0)
widget_import.setToolTip("Import skinner weights on the selected mesh hierarchy.")
widget_import.clicked.connect(callback(self.importSkin, "browser"))
widget_importTemp = QtWidgets.QPushButton("Import Temp")
layout_importButs.addWidget(widget_importTemp,0,1)
widget_importTemp.setToolTip("Import skinner weights on the selected mesh hierarchy, from the last exported temp file.")
widget_importTemp.clicked.connect(callback(self.importSkin, mode='temp'))
layout_import.addStretch()
#---------------------------------------------------------------
# EXPORT TAB
layout_export = QtWidgets.QVBoxLayout()
widget_exportTab.setLayout(layout_export)
if layout_export:
layout_exportPath = QtWidgets.QHBoxLayout()
layout_export.addLayout(layout_exportPath)
if layout_exportPath:
layout_exportPath.addWidget(QtWidgets.QLabel("Path:"))
self.widget_exportPath = QtWidgets.QLineEdit()
self.widget_exportPath.setReadOnly(True)
layout_exportPath.addWidget(self.widget_exportPath)
widget_autoFillExport = QtWidgets.QPushButton("<- Auto-Fill")
layout_exportPath.addWidget(widget_autoFillExport)
widget_autoFillExport.setToolTip("Auto-fill the path based on the current scene name, and (optional) 'Auto-fill subdir' in the Extras tab.")
widget_autoFillExport.clicked.connect(self.cbAutoFillPath)
widget_exportBrowser = QtWidgets.QPushButton("...")
widget_exportBrowser.setToolTip("Browse to the skinner file to export.")
layout_exportPath.addWidget(widget_exportBrowser)
widget_exportBrowser.clicked.connect(callback(self.cbFileBrowser, "export"))
layout_exportButs = QtWidgets.QGridLayout()
layout_export.addLayout(layout_exportButs)
if layout_exportButs:
self.widget_exportSetBindpose = QtWidgets.QCheckBox("Set To Bindpose?")
self.widget_exportSetBindpose.setToolTip("If checked, set all influence to their bindpose before exporting the Skinner weights.\nGood to have checked so as to store out the bindpose transforms for the joints, so they can be rebuilt correctly during import.\nBut not needed if that isn't a concern.")
layout_exportButs.addWidget(self.widget_exportSetBindpose,0,0)
if bool(int(self._settingsValue(SETTING_EXPORT_SET_TO_BINDPOSE))):
self.widget_exportSetBindpose.setChecked(True)
self.widget_exportSetBindpose.clicked.connect(self.cbExportSetToBindpose)
widget_export = QtWidgets.QPushButton("Export To Path")
layout_exportButs.addWidget(widget_export,1,0)
widget_export.setToolTip("Export skinner weights on the selected mesh hierarchy, based on the above path.")
widget_export.clicked.connect(callback(self.exportSkin, mode='browser'))
widget_exportTemp = QtWidgets.QPushButton("Export Temp")
layout_exportButs.addWidget(widget_exportTemp,1,1)
widget_exportTemp.setToolTip("Export skinner weights on the selected mesh hierarchy, to a temp file.")
widget_exportTemp.clicked.connect(callback(self.exportSkin, mode='temp'))
layout_export.addWidget(makeSeparator())
layout_export.addWidget(QtWidgets.QLabel("Version Control:"))
layout_vcExecCmd = QtWidgets.QHBoxLayout()
layout_export.addLayout(layout_vcExecCmd)
if layout_vcExecCmd:
layout_vcExecCmd.addWidget(QtWidgets.QLabel("Exec Command:"))
vcCmd = None
if self.vcExecCmd:
vcCmd = self.vcExecCmd
else:
vcCmd = self.settings.value(SETTING_VC_CALL, "")
self.widget_vcCmd = QtWidgets.QLineEdit(vcCmd)
self.widget_vcCmd.editingFinished.connect(self.cbVcCmd)
layout_vcExecCmd.addWidget(self.widget_vcCmd)
self.widget_vcCmd.setToolTip("Enter the command that should be executed to add/edit the exported file to your version control:\nIt must include a string formatted \"'%s'\" in it, to string-format in the filepath.\nYou can separte multiple calls by ending them with a semicolon ;")
if self.vcExecCmd:
self.widget_vcCmd.setEnabled(False)
layout_vcDepotRoot = QtWidgets.QHBoxLayout()
layout_export.addLayout(layout_vcDepotRoot)
if layout_vcDepotRoot:
layout_vcDepotRoot.addWidget(QtWidgets.QLabel("Depot Root:"))
depotRoot = None
if self.vcDepotRoot:
depotRoot = self.vcDepotRoot
else:
depotRoot = self.settings.value(SETTING_DEPOT_ROOT, "")
self.widget_depotRoot = QtWidgets.QLineEdit(depotRoot)
self.widget_depotRoot.setReadOnly(True)
layout_vcDepotRoot.addWidget(self.widget_depotRoot)
self.widget_depotRoot.setToolTip("Only files living under this directory will be added to your version control.\nLeaving this empty will disable version control. To clear it, open the browser, and then cancel.")
if self.vcDepotRoot:
self.widget_depotRoot.setEnabled(False)
widget_depotBrowser = QtWidgets.QPushButton("...")
widget_depotBrowser.clicked.connect(self.cbDepotRoot)
layout_vcDepotRoot.addWidget(widget_depotBrowser)
layout_export.addWidget(QtWidgets.QLabel(f"IMPORTANT: If you're using P4 for version control, be sure to set its '.{core.EXT}' file type to 'binary', or it will mangle them on the server."))
layout_export.addWidget(QtWidgets.QLabel("See its 'p4 typemap' command."))
layout_export.addStretch()
#---------------------------------------------------------------
# EXTRAS TAB
layout_extras = QtWidgets.QVBoxLayout()
widget_extrasTab.setLayout(layout_extras)
if layout_extras:
layout_extrasGrid = QtWidgets.QGridLayout()
layout_extras.addLayout(layout_extrasGrid)
if layout_extrasGrid:
widget_runTest = QtWidgets.QPushButton("Run the 'skinner test suite'?")
layout_extrasGrid.addWidget(widget_runTest, 0,0)
widget_runTest.setToolTip("This will prompt the user to create a new empty scene, and run a series of tests:\nThe results will be printed to the Script Editor")
widget_runTest.clicked.connect(core.test)
layout_docs = QtWidgets.QHBoxLayout()
layout_extrasGrid.addLayout(layout_docs, 0, 1)
if layout_docs:
widget_homepage = QtWidgets.QPushButton("Homepage...")
layout_docs.addWidget(widget_homepage)
widget_homepage.clicked.connect(self.cbOpenHomepage)
widget_docs = QtWidgets.QPushButton("Documentation...")
layout_docs.addWidget(widget_docs)
widget_docs.clicked.connect(self.cbShowDocs)
layout_loggingResetPrefs = QtWidgets.QHBoxLayout()
layout_extrasGrid.addLayout(layout_loggingResetPrefs, 1,0)
if layout_loggingResetPrefs:
self.widget_verboseLogging = QtWidgets.QCheckBox("Verbose Logging?")
layout_loggingResetPrefs.addWidget(self.widget_verboseLogging)
self.widget_verboseLogging.setToolTip("Print verbose results of the import/export operations to the Maya Script Editor?\nIf this is unchecked, nothing (unless errors) will be printed to the Script Editor.")
if bool(int(self._settingsValue(SETTING_VERBOSE_LOG))):
self.widget_verboseLogging.setChecked(True)
self.widget_verboseLogging.clicked.connect(self.cbVerboseLog)
widget_resetBut = QtWidgets.QPushButton("Reset Preferences")
widget_resetBut.setToolTip("Reset all user changed values back to defaults.")
layout_loggingResetPrefs.addWidget(widget_resetBut)
widget_resetBut.clicked.connect(self.cbResetSettings)
layout_autoFill = QtWidgets.QHBoxLayout()
layout_extrasGrid.addLayout(layout_autoFill, 1,1)
if layout_autoFill:
tt_autoFill = "If provided (optional), this is some subdir of the currently open scene where the Import & Export tab's '<- Auto-Fill' tools will update the paths to."
widget_autoFillLabel = QtWidgets.QLabel("'Auto-Fill' Subdir:")
layout_autoFill.addWidget(widget_autoFillLabel)
widget_autoFillLabel.setToolTip(tt_autoFill)
autoFillDir = None
if self.autoFillSubdir:
autoFillDir = self.autoFillSubdir
else:
autoFillDir = self.settings.value(SETTING_AUTO_FILL_DIR, "")
self.widget_autoFillDir = QtWidgets.QLineEdit(autoFillDir)
self.widget_autoFillDir.setToolTip(tt_autoFill)
self.widget_autoFillDir.editingFinished.connect(self.cbAutoFillSubdir)
layout_autoFill.addWidget(self.widget_autoFillDir)
if self.autoFillSubdir:
self.widget_autoFillDir.setEnabled(False)
packageDir = os.path.dirname(__file__)
tt_packagePath = "The location where the Skinner Python package is saved."
widget_packagePathLabel = QtWidgets.QLabel(f"Package Path:")
widget_packagePathLabel.setToolTip(tt_packagePath)
layout_extrasGrid.addWidget(widget_packagePathLabel, 2,0, QtCore.Qt.AlignRight)
widget_packagePath = QtWidgets.QLineEdit(packageDir)
widget_packagePath.setReadOnly(True)
layout_extrasGrid.addWidget(widget_packagePath, 2,1)
widget_packagePath.setToolTip(tt_packagePath)
# ----------
# sknr / SkinChunk Printing:
# Visually separate the below section from above
layout_extrasGrid.addWidget(makeSeparator(), 3, 0)
layout_extrasGrid.addWidget(makeSeparator(), 3, 1)
layout_extrasGrid.addWidget(QtWidgets.QLabel(f"Print .{core.EXT} File Info"), 4,0)
widget_printButs = QtWidgets.QWidget()
layout_extrasGrid.addWidget(widget_printButs, 5,0)
layout_printButs = QtWidgets.QGridLayout()
widget_printButs.setLayout(layout_printButs)
if layout_printButs:
#layout_printButs.addWidget(QtWidgets.QLabel("Print %s file info..."%core.EXT))
widget_printSknr = QtWidgets.QPushButton("Browse and print...")
layout_printButs.addWidget(widget_printSknr,0,0)
widget_printSknr.setToolTip("Browse to a .%s file on disk, and print info on it to the Script Editor, based on what's checked."%core.EXT)
widget_printSknr.clicked.connect(self.printSkinInfo)
widget_minMaxIndices = QtWidgets.QWidget()
layout_printButs.addWidget(widget_minMaxIndices, 0, 1)
if widget_minMaxIndices:
tt_minMaxIndices = "There could be a lot of stuff to print:\nHere, you can control the range of vert index info that is printed.\nMin 0 starts at the beginning.\nMax 0 prints until the end. Max supports negative indices "
layout_minMaxIndices = QtWidgets.QHBoxLayout()
widget_minMaxIndices.setLayout(layout_minMaxIndices)
label_minMaxIndices = QtWidgets.QLabel("Min/Max Print Indices:")
layout_minMaxIndices.addWidget(label_minMaxIndices)
minVal = self._settingsValue(SETTING_MIN_PRINT_INDEX)
maxVal = self._settingsValue(SETTING_MAX_PRINT_INDEX)
self.widget_minPrintIndex = QtWidgets.QLineEdit(minVal)
self.widget_maxPrintIndex = QtWidgets.QLineEdit(maxVal)
layout_minMaxIndices.addWidget(self.widget_minPrintIndex)
layout_minMaxIndices.addWidget(self.widget_maxPrintIndex)
intValidator = QtGui.QIntValidator()
self.widget_minPrintIndex.setValidator(intValidator)
self.widget_maxPrintIndex.setValidator(intValidator)
for widget in (widget_minMaxIndices, self.widget_minPrintIndex, self.widget_maxPrintIndex):
widget.setToolTip(tt_minMaxIndices)
self.widget_minPrintIndex.editingFinished.connect(self.cbMinMaxPrintIndinces)
self.widget_maxPrintIndex.editingFinished.connect(self.cbMinMaxPrintIndinces)
widget_checkAll = QtWidgets.QPushButton("Check All")
layout_printButs.addWidget(widget_checkAll, 1,0)
widget_checkAll.clicked.connect(callback(self.cbCheckPrintOptions, True))
widget_checkNone = QtWidgets.QPushButton("Check None")
layout_printButs.addWidget(widget_checkNone, 1,1)
widget_checkNone.clicked.connect(callback(self.cbCheckPrintOptions, False))
layout_printButs.setRowStretch(2,1)
wiget_printOptions = QtWidgets.QWidget()
layout_extrasGrid.addWidget(wiget_printOptions, 5,1)
layout_printOptions = QtWidgets.QGridLayout()
wiget_printOptions.setLayout(layout_printOptions)
if layout_printOptions:
row = 0
column = 0
maxCol = 3
# These are the same as the arg names to SkinChunk.printData:
# they should be kept in sync.
for i,but in enumerate(PRINT_DATA_CHECKBOXES):
if i%maxCol == 0 and i != 0:
row = 0
column += 1
widget_printBut = QtWidgets.QCheckBox(but)
layout_printOptions.addWidget(widget_printBut, column, row )
widget_printBut.setChecked(True)
self.widgets_printerCheckBoxes.append(widget_printBut)
row += 1
layout_extrasGrid.setRowStretch(5,1)
layout_utils = QtWidgets.QHBoxLayout()
layout_extras.addLayout(layout_utils)
if layout_utils:
widget_autoFixSkinCluster = QtWidgets.QPushButton("Auto-Fix Broken skinCluster")
layout_utils.addWidget(widget_autoFixSkinCluster)
widget_autoFixSkinCluster.setToolTip("For the selected (skinned) mesh, auto-export / reimport sknr skinning on it (unbinding it in the process):\nThis will regenrate the skinCluster, and can fix the import error:\n'(kInvalidParameter): Object is incompatible with this method'")
widget_autoFixSkinCluster.clicked.connect(callback(core.regenrateSkinCluster))
widget_resetAllDefaults = QtWidgets.QPushButton("Reset All To Defaults")
layout_utils.addWidget(widget_resetAllDefaults)
widget_resetAllDefaults.setToolTip("Reset the requested Import/Export/Extras options back to their startup defaults.")
widget_resetAllDefaults.clicked.connect(self.cbResetAllRequestedDefaults)
layout_utils.addStretch()
layout_extras.addStretch()
# Update our options enabled state:
checkedButton = self.widget_fallbackRadioGroup.checkedButton()
if checkedButton.text() == "Closest Neighbors":
self.settings.setValue(SETTING_FALLBACK_SKIN_METHOD, 1)
for wid in self.nnOptions:
wid.setDisabled(False)
else:
self.settings.setValue(SETTING_FALLBACK_SKIN_METHOD, 2)
for wid in self.nnOptions:
wid.setDisabled(True)
# Right-click reset support for requested controls.
self._registerResetContext(self.widget_closestNeighbors, self._resetFallbackMethodDefault)
self._registerResetContext(self.widget_closestPoint, self._resetFallbackMethodDefault)
self._registerResetContext(self.widget_nearestNeighborNum, self._resetNearestNeighborNumDefault)
self._registerResetContext(self.widget_nearestNeighborDistMult, self._resetNearestNeighborDistMultDefault)
self._registerResetContext(self.widget_useVertNormal, self._resetVertNormalDefault)
self._registerResetContext(self.widget_vertNormalTollerance, self._resetVertNormalToleranceDefault)
self._registerResetContext(self.widget_importSetBindpose, self._resetImportSetToBindposeDefault)
self._registerResetContext(self.widget_usePreDeformedShape, self._resetImportUsePreDeformedDefault)
self._registerResetContext(self.widget_buildMissingInfs, self._resetBuildMissingInfluencesDefault)
self._registerResetContext(self.widget_unbindFirst, self._resetUnbindFirstDefault)
self._registerResetContext(self.widget_postSmooth, self._resetPostSmoothStepsDefault)
self._registerResetContext(self.widget_postSmoothWeightDiff, self._resetPostSmoothDiffDefault)
self._registerResetContext(self.widget_loadByVeryCountOrderCheck, self._resetLoadByVertCountDefault)
self._registerResetContext(self.widget_forceUberChunk, self._resetForceUberChunkDefault)
self._registerResetContext(self.widget_selectInstead, self._resetSelectInsteadDefault)
self._registerResetContext(self.widget_noOverview, self._resetImportOverviewDefault)
self._registerResetContext(self.widget_overviewByType, self._resetImportOverviewDefault)
self._registerResetContext(self.widget_overviewByMesh, self._resetImportOverviewDefault)
self._registerResetContext(self.widget_exportSetBindpose, self._resetExportSetToBindposeDefault)
self._registerResetContext(self.widget_verboseLogging, self._resetVerboseLoggingDefault)
self._registerResetContext(self.widget_minPrintIndex, self._resetMinPrintIndexDefault)
self._registerResetContext(self.widget_maxPrintIndex, self._resetMaxPrintIndexDefault)
for widget in self.widgets_printerCheckBoxes:
self._registerResetContext(widget, callback(self._resetPrinterCheckboxDefault, widget))
#------------------
# Callbacks
def _resetFallbackMethodDefault(self):
self.widget_fallbackRadioGroup.button(self._getDefault(SETTING_FALLBACK_SKIN_METHOD)).setChecked(True)
self.cbFallbackMethod(self.widget_fallbackRadioGroup.checkedButton())
def _resetNearestNeighborNumDefault(self):
self.widget_nearestNeighborNum.setText(str(self._getDefault(SETTING_NUM_NEAREST_NEIGHBORS)))
self.cbNearestNeighborOptions()
def _resetNearestNeighborDistMultDefault(self):
self.widget_nearestNeighborDistMult.setText(str(self._getDefault(SETTING_NEARSET_NEIGHBOR_MULT)))
self.cbNearestNeighborOptions()
def _resetVertNormalDefault(self):
self.widget_useVertNormal.setChecked(bool(self._getDefault(SETTING_VERT_NORMAL_FILTER)))
self.cbVertNormal()
def _resetVertNormalToleranceDefault(self):
self.widget_vertNormalTollerance.setText(str(self._getDefault(SETTING_VERT_NORMAL_TOLLERANCE)))
self.cbVertNormal()
def _resetImportSetToBindposeDefault(self):
self.widget_importSetBindpose.setChecked(bool(self._getDefault(SETTING_IMPORT_SET_TO_BINDPOSE)))
self.cbImportSetToBindpose()
def _resetImportUsePreDeformedDefault(self):
self.widget_usePreDeformedShape.setChecked(bool(self._getDefault(SETTINGS_IMPORT_USE_PRE_DEFORMED_SHAPE)))
self.cbImpoprtUsingPreDeformedShapePos()
def _resetBuildMissingInfluencesDefault(self):
self.widget_buildMissingInfs.setChecked(bool(self._getDefault(SETTING_BUILD_MISSING_INFS)))
self.cbMissingInfs()
def _resetUnbindFirstDefault(self):
self.widget_unbindFirst.setChecked(bool(self._getDefault(SETTING_UNBIND_FIRST)))
self.cbUnbindFirst()
def _resetPostSmoothStepsDefault(self):
self.widget_postSmooth.setValue(int(self._getDefault(SETTING_POST_SMOOTH_STEPS)))
self.cbPostSmoothSteps()
def _resetPostSmoothDiffDefault(self):
self.widget_postSmoothWeightDiff.setText(str(self._getDefault(SETTING_POST_DIFF_SMOOTH)))
self.cbPostSmoothDiff()
def _resetLoadByVertCountDefault(self):
self.widget_loadByVeryCountOrderCheck.setChecked(bool(self._getDefault(SETTING_LOAD_BY_VERT_COUNT_NORMAL)))
self.cbLoadVyVertcountOrder()
def _resetForceUberChunkDefault(self):
self.widget_forceUberChunk.setChecked(bool(self._getDefault(SETTING_FORCE_UBERCHUNK)))
self.cbForceUberChunk()
def _resetSelectInsteadDefault(self):
self.widget_selectInstead.setChecked(bool(self._getDefault(SETTING_SELECT_INSTEAD)))
self.cbSelInstead()
def _resetImportOverviewDefault(self):
self.widget_importOvererviewGroup.button(self._getDefault(SETTING_IMPORT_OVERVIEW)).setChecked(True)
self.cbImportOverview(self.widget_importOvererviewGroup.checkedButton())
def _resetExportSetToBindposeDefault(self):
self.widget_exportSetBindpose.setChecked(bool(self._getDefault(SETTING_EXPORT_SET_TO_BINDPOSE)))
self.cbExportSetToBindpose()
def _resetVerboseLoggingDefault(self):
self.widget_verboseLogging.setChecked(bool(self._getDefault(SETTING_VERBOSE_LOG)))
self.cbVerboseLog()
def _resetMinPrintIndexDefault(self):
self.widget_minPrintIndex.setText(str(self._getDefault(SETTING_MIN_PRINT_INDEX)))
self.cbMinMaxPrintIndinces()
def _resetMaxPrintIndexDefault(self):
self.widget_maxPrintIndex.setText(str(self._getDefault(SETTING_MAX_PRINT_INDEX)))
self.cbMinMaxPrintIndinces()
def _resetPrinterCheckboxDefault(self, widget:QtWidgets.QCheckBox):
widget.setChecked(True)
def cbTabChanged(self, *args):