Browse code

core update v2.1.3

Hexatron authored on 13/04/2017 06:00:22
Showing 19 changed files
1 1
new file mode 100644
... ...
@@ -0,0 +1,1809 @@
0
+"""
1
+=============================================== HI ================================================
2
+Hello World !
3
+Welcome to Skiin script, this is an evolutive weights / effector manager for Maya. Allowing you
4
+to export / import (almost) whatever you want from your Maya rig scene !
5
+
6
+    -   EVOLUTIVE
7
+        Because you can write new 'plugins' to expand the abilities of Skiin.
8
+        A  Skiin's  plugin consists  in  few functions  that  will be  used to  export / import the
9
+        weights / deformers informations see [ http://mehdilouala.com/Skiin_plugins ] for more info
10
+        about how to write  a plugin. If  you feel comfortable  with scripting  you can also have a
11
+        look  by  yourself in  the 'skiin_rsc/plugins'  folder, there  is a  '_model.py'  file with
12
+        explanations
13
+
14
+    -   WEIGHTS MANAGER
15
+        It allows you to  store in external files  (.skin files)  all the datas you want to extract
16
+        from your  Maya's nodes. For instance, the most common usage is to extract your skinCluster
17
+        weights, you can save the weights  in an external file,  and paste them on a new mesh super
18
+        easily.
19
+
20
+    -   EFFECTOR MANAGER
21
+        It also allows  you to do the same process with the objects which modify your object,  such
22
+        as ffd, cluster,  whatever you want actually,  even the constraints  if you want to !  This
23
+        can be a good way to export everything you did on a rig / skin process in one external file.
24
+
25
+============================================= INSTALL =============================================
26
+
27
+Copy Skiin.py and the 'skiin_rsc' folder into your Maya's script folder, then
28
+to run Skiin simply type these lines in a new Script Editor Tab :
29
+
30
+import Skiin;reload(Skiin)
31
+Skiin.Skiin_UI()
32
+
33
+=============================================== UI ================================================
34
+Go to [ http://mehdilouala.com/Skiin ] for better looking explanations of this tool
35
+
36
+The UI is divided in several parts listed below ;
37
+    - the active directory :    This is where Skiin will save / load your files by default
38
+
39
+    - conformity percentage :   The conformity between the input and the output
40
+
41
+    - the two views :           An input and an output view ;
42
+                                    - the Output  view displays the readable  modifiers from  your
43
+                                    selection in Maya
44
+                                    - the Input view displays the loaded .skin files modifiers
45
+                                These two views automatically reorder / colorize so you can easily
46
+                                see what changes has been done
47
+
48
+    - the tools & info :        Use advanced tools, and manage your loaded plugins stuff like that
49
+                                ,  also display  an accurate  count of each  deformer / attributes
50
+                                inside the loaded file(s) and inside the current selection, so you
51
+                                can easily see what matches between input and output
52
+
53
+    - the options :             All the Skiin options, hold the mouse above an options to see what
54
+                                it does =).
55
+
56
+    And some addionnals like progression bars / buttons which speak by themselves
57
+"""
58
+__author__ = "Mehdi Louala"
59
+__copyright__ = "Copyright 2017, Mehdi Louala"
60
+__credits__ = ["Mehdi Louala"]
61
+__license__ = "GPL"
62
+__version__ = "2.1.3"
63
+__maintainer__ = "Mehdi Louala"
64
+__email__ = "mlouala@gmail.com"
65
+__status__ = "Stable Release"
66
+
67
+import json
68
+import ntpath
69
+import os
70
+import sqlite3
71
+from math import sin, pi
72
+
73
+try:
74
+    from PySide.QtCore import Qt
75
+    from PySide.QtGui import QIcon, QLineEdit, QGroupBox, QCheckBox, QFileDialog, QMessageBox, QDialog, \
76
+        QVBoxLayout, QPushButton, QLabel
77
+except ImportError:
78
+    from PySide2.QtCore import Qt
79
+    from PySide2.QtWidgets import QLineEdit, QGroupBox, QCheckBox, QFileDialog, QMessageBox, QDialog, \
80
+        QVBoxLayout, QPushButton, QLabel
81
+    from PySide2.QtGui import QIcon
82
+
83
+from maya.OpenMaya import MEventMessage, MMessage, MGlobal
84
+from maya.cmds import listRelatives, getAttr, ls, select, warning, reorderDeformers
85
+
86
+from skiin_rsc.widgets import Skiin_Item, IconLabel, Skiin_View, Input_View, QMessage_FileExists, \
87
+    QMessage_ModifierConflict, QMessage_SkinClusterHazard
88
+
89
+from skiin_rsc.funcs import maya_tsf_selection, make_unique, objectEvaluate, info, warn, debug, maya_selection, \
90
+    walk_object_tree, mod_deformer_valid, creation_context, db_context, insert_sequence, get_object_from_path, \
91
+    clear_modifier, get_rough_plugs
92
+
93
+from skiin_rsc.plugin import *
94
+from skiin_rsc.UI import SkiinUI
95
+
96
+
97
+class Skiin_Class(SkiinUI, QWidget):
98
+    """
99
+    Welcome to Skiin
100
+    """
101
+    app_dir = os.path.dirname(__file__)
102
+
103
+    base_icons = [QIcon(':out_particleSamplerInfo.png'), QIcon(':samplerInfo.svg'),
104
+                  QIcon(':constraint.png'), QIcon(':parent.png')]
105
+    base_parameters = ['parameters', 'connections', 'constraint', 'parent']
106
+
107
+    def __init__(self, parent=None):
108
+        # first we try to remove the previous window, so it'll save its settings
109
+        win = get_maya_window().findChildren(QWidget, self.title) or []
110
+        for c in win:
111
+            c.close()
112
+            c.deleteLater()
113
+
114
+        # here we'll store all our call backs in order to remove them easily
115
+        self.callbacks = []
116
+
117
+        # we call the UI to be generated
118
+        super(Skiin_Class, self).__init__(parent)
119
+        self.setAttribute(Qt.WA_QuitOnClose)
120
+        self.setAttribute(Qt.WA_DeleteOnClose)
121
+
122
+        # our private active directory
123
+        self._active_dir = self.app_dir
124
+        self.WActivePath.setText(self._active_dir)
125
+        self.config_path = os.path.join(self.active_dir, 'skiin_rsc', 'cfg.json')
126
+
127
+        # simple lambda to forward filtering instructions
128
+        self.filter_mod = lambda view, text: view.filter_items(text)
129
+
130
+        # this list will be filled until all plugins are loaded in Skiin, to prevent
131
+        # too early calls
132
+        self.pending_plugins = []
133
+
134
+        # this is the plugins allowed
135
+        self.allowed_plugins = set()
136
+        self.plugins_view.pluginToggled.connect(self.update_plugin)
137
+
138
+        # where the information Widgets will be stored by names
139
+        self.WInformations = {}
140
+
141
+        # this set class handle all the Maya's objects used by Skiin either read & write
142
+        self.objects = Object_Pool(self)
143
+
144
+        # everytime a plugin is loaded in memory we store its Method class here so we can
145
+        # access static methods such as friends or icons
146
+        self.methods = {}
147
+
148
+        # the icons dictionary
149
+        self.modifier_icons = {}
150
+
151
+        # if the config file exists we load its settings to maintain a linear workflow
152
+        if os.path.isfile(self.config_path):
153
+            with open(self.config_path, 'r') as f:
154
+                json_data = json.load(f)
155
+
156
+                # we first extract the custom data
157
+                if not json_data.pop(u'OptionGroup'):
158
+                    self.WOptionGroup.collapse()
159
+
160
+                if not json_data.pop(u'ToolsInfo'):
161
+                    self.WInfoGroup.collapse()
162
+
163
+                self.allowed_plugins = set(json_data.pop(u'include'))
164
+
165
+                self.setGeometry(*json_data.pop(u'geo_box'))
166
+                self.LViewSplitter.setSizes(json_data.pop(u'view_sz'))
167
+
168
+                for key in json_data:
169
+                    if isinstance(json_data[key], bool):
170
+                        self.findChild(QCheckBox, key).setChecked(json_data[key])
171
+
172
+                    else:
173
+                        self.findChild(QLineEdit, key).setText(json_data[key])
174
+
175
+            self.active_dir = self.WActivePath.text()
176
+
177
+        # we see what plugins are allowed AND in the plugins
178
+        allowed = self.allowed_plugins.intersection(set(self.plugins_view.get_plugins_list()))
179
+
180
+        # now we have loaded settings, we ask our kind SanityMan to check the sanity of
181
+        # our plugins
182
+        for plug in allowed:
183
+            SanityMan(self.add_plugin, plug, os.path.join(self.app_dir, 'skiin_rsc'))
184
+            self.pending_plugins.append(plug)
185
+
186
+        # additionnal information we want to display in the Info & Tools section
187
+        for i, cat in enumerate(self.base_parameters):
188
+            label = IconLabel(cat, self, icon=self.base_icons[i])
189
+            label.setText('%i / %i' % (0, 0))
190
+
191
+            self.WInformations[cat] = label
192
+            self.LInformations.addWidget(label)
193
+
194
+            if not self.WInfoGroup.expanded:
195
+                label.setVisible(False)
196
+
197
+        # we snap the Layout Edit Spliiter to the Layout View Splitter size because
198
+        # they always should be identical
199
+        self.LEditSplitter.setSizes(self.LViewSplitter.sizes())
200
+        self.WFlush.clicked.connect(self.update_informations)
201
+
202
+        # forcing focus at start
203
+        self.WInView.setFocus()
204
+
205
+        # pre-treatment
206
+        self.addCallback()
207
+        self.selection_changed()
208
+
209
+    ### QT ###
210
+
211
+    def closeEvent(self, e):
212
+        # when closing, we kill callbacks, closing additionnal dialogs and saving settings
213
+        self.clearCallback()
214
+        self.plugins_view.close()
215
+
216
+        # defined above, should be located inside the 'skiin_rsc' folder
217
+        with open(self.config_path, 'w') as f:
218
+            datas = {}
219
+            g = self.geometry()
220
+            datas['geo_box'] = (g.left(), g.top(), g.width(), g.height())
221
+            datas['view_sz'] = self.LViewSplitter.sizes()
222
+
223
+            for child in (self.findChildren(QCheckBox) + self.findChildren(QLineEdit)):
224
+                t = child.objectName()
225
+                if len(t):
226
+                    datas[t] = child.isChecked() if isinstance(child, QCheckBox) else child.text()
227
+
228
+            for child in self.findChildren(QGroupBox):
229
+                if hasattr(child, 'expanded'):
230
+                    datas[child.objectName()] = child.expanded
231
+
232
+            datas['include'] = list(self.allowed_plugins)
233
+
234
+            # <3 json :)
235
+            json.dump(datas, f, sort_keys=True,
236
+                      indent=4, separators=(',', ': '))
237
+
238
+    def resizeEvent(self, e):
239
+        # we recall our columns resizing if ever the window is rescaled
240
+        super(Skiin_Class, self).resizeEvent(e)
241
+        self.WOutView.resizeColumns()
242
+        self.WInView.resizeColumns()
243
+
244
+    def dragEnterEvent(self, e):
245
+        # in case of a drop of a .skin file we're here =)
246
+        for url in e.mimeData().urls():
247
+            if url.isLocalFile():
248
+                obj = self.childAt(e.pos())
249
+                # checking if url is consistent
250
+                if url.path().endswith('.skin') and obj and isinstance(obj.parent(), Input_View):
251
+                    e.accept()
252
+
253
+    def dropEvent(self, e):
254
+        # pre-filtering the drops (skipping first character because
255
+        # QUrls starts with a /
256
+        urls = [url.path()[1:] for url in e.mimeData().urls() if url.isLocalFile() and url.path().endswith('.skin')]
257
+
258
+        # if they are consistent
259
+        if len(urls):
260
+            self.WInView.clear()
261
+            self.WInView.update()
262
+            self.WGlobalProgress.restart()
263
+
264
+            for url in urls:
265
+                # we load each file
266
+                self.load_data(url)
267
+
268
+                self.filter_mod(self.WInView, self.WInEdit.text())
269
+
270
+                # applying default functions
271
+                self.conform()
272
+                self.WProgress.done('>> %s loaded in memory' % self.WInName.text())
273
+                self.WGlobalProgress.done()
274
+                self.WInView.resizeColumns()
275
+
276
+            if len(urls) == 1:
277
+                self.WInName.setText('%s' % ntpath.basename(url))
278
+
279
+            else:
280
+                self.WInName.setText('%i files' % len(urls))
281
+
282
+            # refresh the information layouts
283
+            self.update_informations()
284
+
285
+    ### ACTIVE DIRECTORY ###
286
+
287
+    @property
288
+    def active_dir(self):
289
+        # Property returns the current active directory
290
+        return self._active_dir
291
+
292
+    @active_dir.setter
293
+    def active_dir(self, directory):
294
+        # Set the active directory if exists
295
+        if os.path.isdir(directory):
296
+            self._active_dir = directory
297
+
298
+        # otherwise set the script's location folder
299
+        else:
300
+            self._active_dir = os.path.dirname(__file__)
301
+
302
+    def set_active_dir(self, *e):
303
+        """
304
+        Browse to define the current active directory
305
+        """
306
+        options = QFileDialog.DontResolveSymlinks | QFileDialog.ShowDirsOnly | QFileDialog.DontUseNativeDialog
307
+        directory = QFileDialog.getExistingDirectory(self,
308
+                "Set skinMaster active directory",
309
+                self.active_dir, options=options)
310
+
311
+        if directory:
312
+            self.active_dir = directory + '/'
313
+            self.WActivePath.setText(directory + '/')
314
+            # updating the WPathPreview
315
+            self.set_name()
316
+
317
+    ### MISCELLANEOUS ###
318
+
319
+    def addCallback(self):
320
+        """
321
+        Add all the callbacks Skiin needs to be reactive
322
+        """
323
+        self.callbacks.append(MEventMessage.addEventCallback('SelectionChanged', self.selection_changed))
324
+        self.callbacks.append(MEventMessage.addEventCallback('Undo', self.renew_selection))
325
+        self.callbacks.append(MEventMessage.addEventCallback('Redo', self.renew_selection))
326
+        self.callbacks.append(MEventMessage.addEventCallback('NewSceneOpened', self.renew_selection))
327
+        self.callbacks.append(MEventMessage.addEventCallback('SetModified', self.renew_selection))
328
+        self.callbacks.append(MEventMessage.addEventCallback('NameChanged', self.renew_selection))
329
+
330
+    def clearCallback(self):
331
+        """
332
+        We remove all the callbacks
333
+        """
334
+        for callback in reversed(self.callbacks):
335
+            MMessage.removeCallback(callback)
336
+            self.callbacks.remove(callback)
337
+
338
+    def add_plugin(self, plugin_name, err_no, ico=None):
339
+        """
340
+        Add a plugin to Skiin (these files in the 'skiin_rsc/plugins' folder
341
+        :param   plugin_name: plugin's name
342
+        :param err_no: error code
343
+        """
344
+
345
+        # if no error code (-1) or simple warning (>= 10)
346
+        if err_no == -1 or err_no >= 10:
347
+            # some debugging
348
+            if err_no == -1:
349
+                info("SkinMaster loaded plugin '%s'" % plugin_name)
350
+            else:
351
+                warn("SkinMaster loaded plugin '%s' with warnings, see Readers' window" % plugin_name)
352
+
353
+            try:
354
+                # we need remove the previous paths from sys.modules
355
+                # to flush the memory
356
+                if plugin_name in modules:
357
+                    modules.pop(plugin_name)
358
+
359
+                # then we ask for a fresh loading from imp
360
+                plug_in = imp.load_source(plugin_name, join(self.app_dir,
361
+                                                            'skiin_rsc',
362
+                                                            'plugins',
363
+                                                            '%s.py' % plugin_name))
364
+                imp.reload(plug_in)
365
+
366
+                # then we ask to read again the plugin and its icon attributes
367
+                method = plug_in.Method
368
+                assert hasattr(method, '_icon')
369
+                ico = method._icon
370
+                # and we assign the method to our dict for later uses
371
+                self.methods[plugin_name] = method
372
+
373
+            except AssertionError:
374
+                ico = 'default.svg'
375
+
376
+            except:
377
+                # if ever the attribute doesn't exists we set a default one
378
+                if plugin_name in self.pending_plugins:
379
+                    self.pending_plugins.remove(plugin_name)
380
+                return
381
+
382
+            # formatting the icon to be readable by Qt
383
+            icon = QIcon(':%s' % ico)
384
+            self.modifier_icons[plugin_name] = icon
385
+
386
+            # we create the plugins widget label in Info & Tools
387
+            label = IconLabel(plugin_name, self, icon=icon)
388
+            label.setText('%i / %i' % (0, 0))
389
+
390
+            if not self.WInfoGroup.expanded:
391
+                label.setVisible(False)
392
+
393
+            self.WInformations[plugin_name] = label        # updating info Widgets
394
+            self.LInformations.addWidget(label)     # updating info Layout
395
+
396
+            # because there was no error, we set the plugin as allowed by default
397
+            self.allowed_plugins.add(plugin_name)
398
+
399
+            self.update()
400
+
401
+        # we finally update our pending_plugins
402
+        if plugin_name in self.pending_plugins:
403
+            self.pending_plugins.remove(plugin_name)
404
+
405
+            # if the current plugin was the last one pending
406
+            # we ask for a complete redraw
407
+            if not len(self.pending_plugins):
408
+                self.update_informations()
409
+                self.renew_selection()
410
+
411
+                self.LInformations.reorder()
412
+                self.update()
413
+
414
+    def update_plugin(self, plugin_name, status):
415
+        """
416
+        Update a plugin, turning it on or off, equivalent to a redraw
417
+        :param plugin_name: plugin source name
418
+        :type  plugin_name: str
419
+        :param      status: on or off ?
420
+        :type       status: bool
421
+        """
422
+        debug('update_plugin %s' % plugin_name)
423
+        if status:
424
+            # we run an asynchronous loading of the plugin
425
+            SanityMan(self.add_plugin, plugin_name, os.path.join(self.app_dir, 'skiin_rsc'))
426
+            self.pending_plugins.append(plugin_name)
427
+
428
+        else:
429
+            # we kick it
430
+            self.remove_plugin(plugin_name)
431
+
432
+            objects = set()
433
+
434
+            # and we clean our views to remove any elements corresponding to this
435
+            # modifier's type
436
+            for view in (self.WOutView, self.WInView):
437
+                for i in reversed(range(view.topLevelItemCount())):
438
+                    item = view.topLevelItem(i)
439
+                    if item and item.tp() == plugin_name:
440
+                        item.del_checkbox()
441
+                        view.takeTopLevelItem(i)
442
+
443
+                        objects.add(item.obj())
444
+
445
+        self.objects.clear()
446
+        self.selection_changed()
447
+
448
+    def remove_plugin(self, plugin_name):
449
+        """
450
+        Removing a plugin from Skiin's memory
451
+        :param plugin_name: plugin's name
452
+        """
453
+        # we ask everybody to get rid of the plugin
454
+        self.LInformations.removeWidget(plugin_name)
455
+        if plugin_name in self.methods:
456
+            self.methods.pop(plugin_name)
457
+        if plugin_name in self.allowed_plugins:
458
+            self.allowed_plugins.remove(plugin_name)
459
+
460
+    def clean_plugins(self):
461
+        """
462
+        Safe removing all the loaded plugins and their widgets
463
+        """
464
+        pending = []
465
+
466
+        # we wait until the information layout length match
467
+        # the default parameters, this means all plugins unloaded
468
+        while len(self.LInformations.widgets) != len(self.base_parameters):
469
+            for plugin_name, widget in self.LInformations:
470
+                if plugin_name not in self.base_parameters and plugin_name not in pending:
471
+                    self.remove_plugin(plugin_name)
472
+
473
+                    # we make sure the widget will be deleted
474
+                    pending.append(plugin_name)
475
+
476
+            # while widget isn't deleted we don't remove from pending
477
+            for widget in pending:
478
+                if widget not in self.LInformations:
479
+                    pending.remove(widget)
480
+
481
+        self.update()
482
+
483
+    ### FORMATTING ###
484
+
485
+    def format_name(self, name, filename=True):
486
+        """
487
+        Format the object's name considering the options parameters
488
+        which are active
489
+        :param     name: the object's name
490
+        :param filename: if set will remove non-allowed file's name characters
491
+        :return:         correctly formatted name
492
+        :rtype:          str
493
+        """
494
+        # No namespace
495
+        if self.WSaveNons.isChecked():
496
+            name = name.split(':')[-1]
497
+
498
+        # Only leaf for duplicate names
499
+        if self.WSaveNoabs.isChecked():
500
+            try:
501
+                getAttr(name)
502
+
503
+            except (ValueError, RuntimeError, TypeError):
504
+                # if name is multiple in scene
505
+                name = name.split('|')[-1]
506
+
507
+        # format for file's name
508
+        if filename:
509
+            name = name.replace(':', '__').replace('|', '__')
510
+
511
+        return name
512
+
513
+    def set_name(self, obj=None):
514
+        """
515
+        Set the selection's file's name, apply the settings for a nice filename
516
+        formatting and displays the preview path for the current selection
517
+        :param obj: object(s)'s name(s)
518
+        :type  obj: str or list
519
+        :return:    the correct named file
520
+        """
521
+        if obj and isinstance(obj, basestring):
522
+            src = obj
523
+
524
+        elif self.WSaveSmart.isChecked():
525
+            src = self.smart_name()
526
+
527
+        # if nothing above is matching we consider the last object of the selection
528
+        elif len(maya_tsf_selection()):
529
+            src = maya_tsf_selection()[-1]
530
+
531
+        else:
532
+            src = ''
533
+
534
+        # post formatting
535
+        src = self.format_name(src)
536
+        src += '.skin'
537
+
538
+        # diplaying preview depending on case
539
+        if src == '.skin' or not self.WOutView.topLevelItemCount():
540
+            self.WPathPreview.setText('Empty export !')
541
+
542
+        # multiple files case
543
+        elif self.WSaveEach.isChecked() and len(self.WOutView.active_objects()) > 1:
544
+            self.WPathPreview.setText("Preview : <b><font color='white'>%i</font></b> skin files in "
545
+                                      "<font color='white'>%s</font>" % (len(self.WOutView.active_objects()), self.active_dir))
546
+
547
+        # one file case
548
+        else:
549
+            self.WPathPreview.setText("Preview : <font color='white'>%s</font>" % os.path.join(self.active_dir, src))
550
+
551
+        # enabling buttons depending on what's going on
552
+        self.WSave.turn(self.WOutView.topLevelItemCount())
553
+        self.WSaveAs.turn(self.WOutView.topLevelItemCount())
554
+
555
+        return src
556
+
557
+    def smart_name(self, objects=None):
558
+        """
559
+        Hehe... 'Smart' naming... We try to extract a relevant naming from a list
560
+        of object, it will try to find the common letters between all the names,
561
+        or the common modifiers between all the names
562
+        :param objects: object(s)'s name(s)
563
+        :type  objects: list
564
+        :return:        'smart' name
565
+        :rtype:         str
566
+        """
567
+
568
+        # our comparison tools
569
+        mods = [m for m, o in self.WOutView.active_modifiers()]
570
+        umod = make_unique(mods)
571
+        objs = objects or self.WOutView.active_objects() or self.format_selection()[0]
572
+
573
+        # trying to find a common name
574
+        common = ''
575
+        for work in objs:
576
+            subs = [w for w in objs if w != work]
577
+
578
+            for sub_work in subs:
579
+                i = 0
580
+                l = 2
581
+                while (i + l) < len(sub_work):
582
+                    part = work[i:i + l]
583
+
584
+                    if part in sub_work:
585
+                        if len(common) < len(part):
586
+                            if len(subs) == sum([1 for s in subs if part in s]):
587
+                                common = part
588
+
589
+                        l += 1
590
+
591
+                    else:
592
+                        i += 1
593
+                        l = 2
594
+
595
+            # we break after the first loop because results must be the same
596
+            # we did a for loop just to handle the 0 length case
597
+            break
598
+
599
+        # if there is only 1 modifier's type and 1 object but more than one modifier for this type
600
+        if len(umod) == 1 and len(objs) == 1 and len(mods) != 1:
601
+            name = objs[0] + '_' + umod[0]
602
+
603
+        # if there is only 1 modifier's type, and multiple object and a common name for them
604
+        # and more than one modifier
605
+        elif len(umod) == 1 and len(objs) and len(common) > 3 and len(mods) != 1:
606
+            name = common + '_' + umod[0]
607
+
608
+        # if there is 1 object and many modifier's types
609
+        elif len(objs) == 1 and len(umod) > 1:
610
+            name = objs[0]
611
+
612
+        # if no object and no modifier we have a look to the selection
613
+        elif not len(objs) and not len(umod):
614
+            if len(maya_tsf_selection()):
615
+                name = maya_tsf_selection()[0]
616
+
617
+            else:
618
+                return ''
619
+
620
+        # else we take evaluate the component's length of all object, and take the bigger one
621
+        # as main object
622
+        else:
623
+            try:
624
+                name = (sorted(objs, key=lambda x: objectEvaluate(x)), [common])[len(common) > 3]
625
+                name = name[-1]
626
+
627
+            except ValueError:
628
+                name = (objs[0], common)[len(common) > 3]
629
+
630
+            if not len(name):
631
+                return ''
632
+
633
+        # post formatting the name
634
+        return self.format_name(name)
635
+
636
+    def get_context(self, obj=None):
637
+        """
638
+        Setting the context for the given object(s)
639
+        :param obj: object(s)'s name(s)
640
+        :type  obj: list or str
641
+        :return:    object(s)'s path
642
+        """
643
+        return os.path.join(self.active_dir, self.set_name(obj))
644
+
645
+    def format_selection(self):
646
+        """
647
+        We read the selection, objects and components and returns a resume
648
+        :return: tuple in shape (selection's object(s), selections's component(s))
649
+        :rtype:  tuple
650
+        """
651
+        s = maya_selection(et='transform')
652
+        v = maya_selection(et='float3')
653
+        v += maya_selection(et='double3')
654
+
655
+        # pre treatment for component case selection
656
+        if len(v):
657
+            if len(s):
658
+                v = []
659
+            s.extend(ls(hl=True))
660
+            s.extend(listRelatives(ls(sl=True, o=True), p=True))
661
+
662
+        return make_unique(s), v
663
+
664
+    ### SELECTION ###
665
+
666
+    def selection_changed(self, e=None, force=None):
667
+        """
668
+        Core function executed every time the Maya's selection is changed
669
+        :param     e: event parameter, doesn't matter
670
+        :param force: force reload of an object
671
+        :type  force: list
672
+        """
673
+        # if there is still plugins loading or if we're undoing or redoing
674
+        if len(self.pending_plugins) or MGlobal.isUndoing() or MGlobal.isRedoing():
675
+            self.WProgress.set_mode()
676
+            return
677
+
678
+        # initialization
679
+        self.WProgress.start()
680
+        s, v = self.format_selection()
681
+        self.WLocal.setChecked(len(v))
682
+
683
+        # if force is set we try to delete every objects already in memory
684
+        for f in (force or []):
685
+            self.objects.delete(f)
686
+            s.append(f)
687
+
688
+        self.WForceSelection.setEnabled(True)
689
+
690
+        # if there is only one object loading we purge
691
+        if len(s) == 1:
692
+            self.objects.clear()
693
+
694
+        else:
695
+            # otherwise we remove object which are not in the new selection
696
+            for obj in self.objects():
697
+                if obj.n not in s:
698
+                    self.objects.delete(obj)
699
+
700
+            self.WForceSelection.setEnabled(False)
701
+
702
+        # If the view is not holded we go to refresh it
703
+        if not self.WOutView.hold:
704
+            # first we check if there is any invalid item our output view
705
+            for item in self.WOutView.objects():
706
+                if item != '' and item not in self.objects or item not in s:
707
+                    # getting a list of all matchin indexes for this object
708
+                    idx = map(self.WOutView.indexOfTopLevelItem, self.WOutView.findItems(item, Qt.MatchExactly, 2))
709
+
710
+                    for id in reversed(idx):
711
+                        self.WOutView.takeTopLevelItem(id)
712
+
713
+            # for every object in the selection
714
+            for obj in s:
715
+                try:
716
+                    # if not already loaded we build a new one and connect its signals
717
+                    if obj not in self.objects:
718
+                        new_obj = Skiin_Object(obj, self)
719
+                        new_obj.init.connect(self.WOutView.progress.restart)
720
+                        new_obj.step.connect(self.WOutView.progress.add)
721
+                        new_obj.err.connect(self.WProgress.error)
722
+                        new_obj.done.connect(self.WOutView.progress.done)
723
+
724
+                        self.objects.add(new_obj)
725
+
726
+                    # if the object is not in the list we need to make a raw evaluation
727
+                    # of its modifiers and add them as new items
728
+                    if obj not in self.WOutView.objects():
729
+                        new_obj = self.objects[obj]
730
+
731
+                        new_obj.ping()
732
+
733
+                        new_obj.raw_all()
734
+
735
+                        for modifier in new_obj.all_modifiers():
736
+                            tp, hndl, mod = modifier
737
+
738
+                            # we skip modifiers which are not loaded
739
+                            if tp not in self.methods:
740
+                                continue
741
+
742
+                            item = self.insert_modifier_item(new_obj, hndl, mod)
743
+
744
+                            self.WOutView.addTopLevelItem(item)
745
+                            self.WOutView.repaint()
746
+
747
+                        self.WOutView.resizeColumns()
748
+
749
+                    self.objects[obj].components = None
750
+
751
+                except IOError:
752
+                    self.WProgress.done('Error with a plugin, no longer exists or deleted, refresh the Readers Plugins')
753
+                    self.WProgress.set_mode('error')
754
+                    return
755
+
756
+                except:
757
+                    # here we're trying to catch all error while using the Write plugin's
758
+                    # method with a broad except
759
+                    self.WProgress.error(exc_info())
760
+                    continue
761
+
762
+            # if there is some components selected we update the corresponding object
763
+            if len(v):
764
+                self.WCleanOld.setChecked(False)
765
+                obj_from_component = make_unique(listRelatives(ls(v, o=True), p=True))
766
+
767
+                for obj in obj_from_component:
768
+                    self.objects[obj].components = v
769
+
770
+            # after we updated all the objects' components we refresh the filters
771
+            for obj in s:
772
+                if self.objects[obj].components:
773
+                    self.objects[obj].filter(self.WLocal.isChecked(), self.WLocalSet.isChecked())
774
+
775
+        # some formatting
776
+        lv = len(v)
777
+        if lv:
778
+            cmp_title = '%i components' % lv
779
+
780
+            for obj in obj_from_component:
781
+                s.remove(obj)
782
+
783
+        lo = len(s)
784
+        if lo > 1:
785
+            obj_title = '%i objects' % lo
786
+
787
+        elif lo:
788
+            obj_title = '%s' % s[0]
789
+
790
+        else:
791
+            obj_title = ''
792
+
793
+        if lv and lo:
794
+            self.WOutName.setText("%s and %s" % (cmp_title, obj_title))
795
+
796
+        elif lv:
797
+            self.WOutName.setText(cmp_title)
798
+
799
+        else:
800
+            self.WOutName.setText(obj_title)
801
+
802
+        # we ask Skiin to load the objects if ever the Autoload ability is enabled
803
+        if self.WAutoLoad.isChecked() and not self.WInView.hold and not force:
804
+            self.load_selection(self.objects.flat())
805
+
806
+        # re-run views' filtering
807
+        self.filter_mod(self.WOutView, self.WOutEdit.text())
808
+        # applying conformity
809
+        self.conform()
810
+
811
+        # finalizing
812
+        self.WProgress.done('>> Selection deformers acquired')
813
+
814
+        self.set_name()
815
+
816
+        self.WSave.turn(len(self.WOutView.active_objects()))
817
+        self.WSaveAs.turn(len(self.WOutView.active_objects()))
818
+
819
+        self.WOutView.resizeColumns()
820
+        self.update_informations()
821
+
822
+    def renew_selection(self, *args, **kwargs):
823
+        """
824
+        We force a complete redraw of the selection
825
+        :param   args: args we want to forward to self.selection_changed
826
+        :param kwargs: keyword args we want to forward to self.selection_changed
827
+        """
828
+        self.WOutView.progress.restart()
829
+        self.WGlobalProgress.restart()
830
+
831
+        for obj in self.objects:
832
+            self.objects[obj].ping()
833
+
834
+        self.selection_changed(*args, **kwargs)
835
+        self.WProgress.done('>> Full Updated')
836
+
837
+    def select_all_dag(self):
838
+        """
839
+        This (tries to) select all the DAG of an object
840
+        """
841
+        s = maya_selection()
842
+        self.WProgress.restart('Scanning DAG...')
843
+
844
+        if len(s):
845
+            try:
846
+                all_mod = set()
847
+                step = 100.0 / len(listRelatives(s, ad=True) or [1])
848
+
849
+                for obj in walk_object_tree(s):
850
+                    self.WProgress.add(step, 'Scanning %s...' % obj)
851
+                    all_mod.add(obj)
852
+
853
+                if len(all_mod):
854
+                    select(listRelatives(ls(listHistory(list(all_mod), f=True), type='shape'), p=True, f=True))
855
+
856
+            except TypeError:
857
+                select(list(all_mod))
858
+                pass
859
+
860
+            except:
861
+                self.WProgress.error(exc_info())
862
+                return
863
+
864
+        if len(all_mod):
865
+            select(list(all_mod), add=True)
866
+        else:
867
+            warning('Nothing found in this DAG')
868
+            self.WProgress.restart()
869
+
870
+    ### VIEW ITEMS ###
871
+
872
+    def insert_modifier_item(self, obj, handle, mod):
873
+        """
874
+        Insert a new item in the Output view
875
+
876
+        :param    obj: object's name
877
+        :param handle: handle object
878
+        :type  handle: handles.Handler
879
+        :param    mod: modifier's name
880
+        """
881
+        # we get the modifier's type
882
+        tp = handle.kind
883
+
884
+        # creating a new item in the Output view
885
+        item = Skiin_Item(self.WOutView, [mod, tp, obj, ''])
886
+        item.add_checkbox()
887
+        item.setIcon(0, self.modifier_icons[tp])
888
+
889
+        self.WOutView.repaint()
890
+
891
+        # we add subitems deformers
892
+        self.insert_deformers(item, handle)
893
+
894
+        # and we calculate the number of vertices affected
895
+        raw = handle.reader.raw_data[mod]
896
+        total = sum([raw[cnt] for cnt in raw])
897
+
898
+        item.setText(3, str(min(total, len(handle))))
899
+
900
+        return item
901
+
902
+    def insert_deformers(self, parent, handle):
903
+        """
904
+        Insert all subitems of a modifiers, meaning the deformers of
905
+        a modifier, such as joints, clusterHandle, and so on
906
+
907
+        :param parent: Subitem's parent
908
+        :type  parent: Skiin_Item
909
+        :param handle: handle object
910
+        :type  handle: handles.Handler
911
+        :return:
912
+        """
913
+        raw = handle.reader.raw_data
914
+        mod = parent.mod()
915
+        tp = handle.kind
916
+
917
+        # we set the parent as dead if raw data is empty
918
+        if not len(raw[mod]):
919
+            parent.kill()
920
+
921
+        # and we add a sub Skiin_Item for each modifier name in the raw data
922
+        for deformer in raw[mod]:
923
+            tsf = mod_deformer_valid(deformer)
924
+            cnt = len(handle) if self.methods[tp]._is_transform else raw[mod][deformer]
925
+
926
+            sub = Skiin_Item(parent, [deformer, tsf, parent.object, str(cnt)])
927
+
928
+            parent.addChild(sub)
929
+
930
+    def delete_modifier_item(self, item):
931
+        """
932
+        We kick a modifier item
933
+
934
+        :param item: the item object we want to remove
935
+        :type  item: Skiin_Item
936
+        """
937
+        obj = item.obj()
938
+
939
+        if obj in self.objects:
940
+            self.WOutView.takeTopLevelItem(self.WOutView.indexOfTopLevelItem(item))
941
+
942
+    ### SAVING ###
943
+
944
+    def save_data_as(self):
945
+        """
946
+        Save modifiers' data to a specific file, asking the user to choose
947
+        :return:
948
+        """
949
+        options = QFileDialog.Options() | QFileDialog.DontUseNativeDialog
950
+        filename, _ = QFileDialog.getSaveFileName(self,
951
+                "Save skinMaster file as..",
952
+                self.active_dir,
953
+                "skinMaster Files (*.skin)", options=options)
954
+
955
+        if filename:
956
+            # completing the filename if ever doesn't finish as a skin file
957
+            if not filename.endswith('.skin'):
958
+                filename += '.skin'
959
+
960
+            self.save_data(filename, True)
961
+
962
+    @creation_context(None)
963
+    def save_data(self, path=None, override=False):
964
+        """
965
+        This core function save all the active modifiers' data to a given file
966
+        :param     path: file path where to save the data
967
+        :param override: if we want to override existing file or no
968
+        """
969
+        def create_file(obj):
970
+            """
971
+            Internal function to create a database file
972
+            :param obj: the file's path
973
+            :return: sqllite3 database, override all next existing files (bool)
974
+            :rtype: tuple
975
+            """
976
+            yta = override
977
+
978
+            try:
979
+                # if the checkbox is onwe doesn't ask for overriding and directly try
980
+                # to get rid of the file
981
+                if self.WOverride.isChecked() or override:
982
+                    os.remove(obj)
983
+
984
+                # otherwise we double check from the user what to do if file exists
985
+                elif os.path.isfile(obj):
986
+                    reply = QMessage_FileExists(self, obj)
987
+                    if reply == QMessageBox.Yes:
988
+                        os.remove(obj)
989
+
990
+                    elif reply == QMessageBox.YesToAll:
991
+                        os.remove(obj)
992
+                        yta = True
993
+
994
+                    # open a file save dialog
995
+                    elif reply == QMessageBox.Save:
996
+                        self.save_data_as()
997
+                        return None, False
998
+
999
+                    else:
1000
+                        return None, False
1001
+
1002
+            # if deletion fails
1003
+            except WindowsError:
1004
+                # but file still exists this means we dont have the rights
1005
+                if os.path.isfile(obj):
1006
+                    self.WProgress.error(exc_info())
1007
+                    return None, False
1008
+
1009
+            # fastly open / close to create the file
1010
+            finally:
1011
+                open(obj, 'a').close()
1012
+
1013
+            #
1014
+            DB = db_context(obj)
1015
+            return DB, yta
1016
+
1017
+        # getting all active objects
1018
+        s = self.WOutView.active_objects()
1019
+        self.WGlobalProgress.restart()
1020
+        self.WProgress.restart('Empty Export !')
1021
+
1022
+        # iif selection is empty we cancel
1023
+        if not len(s):
1024
+            self.WProgress.message = 'Empty Export'
1025
+            return
1026
+
1027
+        one_file = not self.WSaveEach.isChecked() or path
1028
+
1029
+        # also if database creation fails
1030
+        if one_file:
1031
+            database, override = create_file(os.path.join(self.active_dir, path or self.set_name()))
1032
+            if not database:
1033
+                return
1034
+
1035
+        try:
1036
+            self.WOutView.progress.added.connect(self.WProgress.add)
1037
+
1038
+            global_step = 100.0 / (len([0 for i in self.WOutView.active_items()]) + 1)
1039
+
1040
+            for obj in s:
1041
+                # if the Save Each checkbox is on, we're saving a new database's file
1042
+                # for each object, skipping the saving if ever db creation fails
1043
+                if not one_file:
1044
+                    database, override = create_file(os.path.join(self.active_dir, self.set_name(obj)))
1045
+                    if not database:
1046
+                        continue
1047
+
1048
+                # pre treatment
1049
+                self.WOutView.progress.restart()
1050
+                self.WProgress.restart(start=False)
1051
+
1052
+                step = 100.0 / (len(self.WOutView.active_types(obj)) + 1) / 4
1053
+
1054
+                debug('saving %s - %f' % (obj, step))
1055
+                obj_object = self.objects[obj]
1056
+
1057
+                # we iterate through the list the active items for this object
1058
+                for tp, mod in self.WOutView.active_modifiers(obj):
1059
+                    self.WGlobalProgress.add(global_step)
1060
+                    self.WOutView.progress.add(step, '%s - Saving %s on %s' % (tp, mod, obj))
1061
+
1062
+                    warn('Saving %s' % mod)
1063
+                    handle = obj_object.handles[tp]
1064
+
1065
+                    # we write the modifier's data for this active item
1066
+                    modifier, generator = handle.write(mod)
1067
+
1068
+                    # reading the deformer order to get which modifier is before and
1069
+                    # which one is after
1070
+                    obj_order = obj_object.deformer_order
1071
+                    try:
1072
+                        this = obj_order.index(mod)
1073
+                        before = obj_order[min((this + 1), len(obj_order) - 1)]
1074
+                        after = obj_order[max((this - 1), 0)]
1075
+                    except (ValueError, AttributeError):
1076
+                        # this means 'mod' is not contained in obj_order the most probable
1077
+                        # reason is that 'mod' affects the transform not the shape
1078
+                        before = after = ''
1079
+
1080
+                    # then we start to insert the tables and generic values
1081
+                    database.execute(handle.format_DB_table % (obj, mod))
1082
+                    database.execute('''INSERT INTO 'summary' SELECT
1083
+                                  '%s' AS 'modifer',
1084
+                                  '%s' AS 'type',
1085
+                                  '%s' AS 'object',
1086
+                                  '%i' AS 'count',
1087
+                                  '%s' AS 'back',
1088
+                                  '%s' AS 'front' ''' % (mod, tp, obj, 0,
1089
+                                                              before, after))
1090
+
1091
+                    # saving the weights for this specific modifier
1092
+                    self.WOutView.progress.add(step, '%s - Cooking %s weights on %s' % (tp, mod, obj))
1093
+                    insert_sequence(database, '%s|%s' % (obj, mod),
1094
+                                    generator)
1095
+
1096
+                    # saving all the plugs of the modifier & its relatives
1097
+                    self.WOutView.progress.add(step, '%s - Baking connections for %s' % (tp, mod))
1098
+                    insert_sequence(database, 'connections',
1099
+                                    handle.get_plugs(mod),
1100
+                                    add=(mod,))
1101
+
1102
+                    # saving the raw data (deformers count etc) for this modifier
1103
+                    self.WOutView.progress.add(step, '%s - Counting data for %s' % (tp, mod))
1104
+                    insert_sequence(database, 'raw',
1105
+                                    handle.raw_format(mod),
1106
+                                    add=(mod, obj))
1107
+
1108
+                    # we finally update the total amount of vertices affected
1109
+                    raw = handle.reader.raw_data[mod]
1110
+                    total = sum([raw[cnt] for cnt in raw])
1111
+
1112
+                    database.execute("UPDATE 'summary' SET 'count'='%i' WHERE object=='%s' AND modifier=='%s'"
1113
+                                     % (min(total, len(handle)), obj, mod))
1114
+
1115
+                    database.commit()
1116
+
1117
+                self.WOutView.progress.done()
1118
+
1119
+        except:
1120
+            # broad except for debug purpose
1121
+            self.WProgress.error(exc_info())
1122
+            if database:
1123
+                database.close()
1124
+                database = None
1125
+
1126
+        finally:
1127
+            # finalizing, we close the database if ever it has been opened
1128
+            debug('saving done')
1129
+            if not path:
1130
+                path = self.get_context()
1131
+
1132
+            if database:
1133
+                self.WProgress.done('>> %s' % path)
1134
+                self.WGlobalProgress.done()
1135
+                self.WOutView.progress.added.disconnect(self.WProgress.add)
1136
+
1137
+                database.close()
1138
+
1139
+                if self.WAutoLoad.isChecked():
1140
+                    self.load_selection()
1141
+
1142
+    ### LOADING ###
1143
+
1144
+    def autoload(self, state):
1145
+        """
1146
+        Handle everytime we want to automatically load the selection
1147
+        update or clean
1148
+        :param state: update (True) or clean (False) ?
1149
+        :type  state: bool
1150
+        """
1151
+        if not self.WInView.hold and not len(self.pending_plugins):
1152
+            # we want to force the loading of the selected objects
1153
+            if state:
1154
+                self.load_selection(self.format_selection()[0])
1155
+
1156
+            # otherwise we just want to clear the view
1157
+            else:
1158
+                self.WInView.clear()
1159
+                self.update_informations()
1160
+
1161
+    def load_data_from(self):
1162
+        """
1163
+        We ask the user to give us a .skin file to load
1164
+        """
1165
+        self.WProgress.restart('Loading file...')
1166
+        options = QFileDialog.Options() | QFileDialog.DontUseNativeDialog
1167
+        filename, _ = QFileDialog.getOpenFileName(self,
1168
+                "Load skinMaster file", self.active_dir,
1169
+                "skinMaster Files (*.skin)", options=options)
1170
+
1171
+        if filename:
1172
+            # if file is given we initialize a fully new loading
1173
+            self.WInView.clear()
1174
+            self.WInView.update()
1175
+            self.WGlobalProgress.restart()
1176
+
1177
+            self.load_data(filename)
1178
+
1179
+            self.filter_mod(self.WInView, self.WInEdit.text())
1180
+
1181
+            # standard post loading events
1182
+            self.conform()
1183
+            self.WProgress.done('>> %s loaded in memory' % self.WInName.text())
1184
+            self.WGlobalProgress.done()
1185
+            self.WInView.resizeColumns()
1186
+            self.WFlush.turn(bool(self.WInView.topLevelItemCount()))
1187
+
1188
+            self.update_informations()
1189
+
1190
+    def load_selection(self, selection=None):
1191
+        """
1192
+        Load a given selection, if not provided will try to load all the Output
1193
+        objects, it'll first try to find a common file for all objects, if doesn't
1194
+        exists it'll look for a file for each needed object
1195
+        :param selection: the objects list
1196
+        :type  selection: iterable
1197
+        """
1198
+        # standard init, cleaning lists etc
1199
+        self.WInView.clear()
1200
+        self.WInView.clean()
1201
+        self.WOutView.clean()
1202
+        self.WGlobalProgress.restart()
1203
+
1204
+        items = selection or self.WOutView.active_objects()
1205
+        needs = list(items)
1206
+        # we try to get a generic context for the current selection
1207
+        ctx = self.get_context()
1208
+
1209
+        # if a common file exists for the whole selection
1210
+        if os.path.isfile(ctx):
1211
+            # we try load the data it contains, asking only for the needed objects
1212
+            for item in self.load_data(ctx, needs):
1213
+                try:
1214
+                    needs.remove(item.n)
1215
+
1216
+                except ValueError:
1217
+                    # this means we've loaded an object which was not needed
1218
+                    pass
1219
+
1220
+            # updating the list's title with the file's name
1221
+            self.WInName.setText('%s' % ntpath.basename(ctx))
1222
+
1223
+        # if no common file, we try to load each object individually
1224
+        if len(items) > 1 and len(needs):
1225
+            self.WGlobalProgress.restart()
1226
+            # getting appliers for each object
1227
+            ctxs = [self.get_context(self.format_name(obj)) for obj in needs]
1228
+            step = 100.0 / len(ctxs)
1229
+
1230
+            for i, ctx in enumerate(ctxs):
1231
+                self.WGlobalProgress.add(step)
1232
+
1233
+                for item in self.load_data(ctx, needs):
1234
+                    if item.n in needs:
1235
+                        needs.remove(item.n)
1236
+
1237
+            loaded = len(self.WOutView.active_objects()) - len(needs)
1238
+
1239
+            if loaded > 0:
1240
+                self.WInName.setText('%i files' % loaded)
1241
+
1242
+        # standard post loading events
1243
+        self.filter_mod(self.WInView, self.WInEdit.text())
1244
+        self.conform()
1245
+        self.WProgress.done('>> %s loaded in memory' % self.WInName.text())
1246
+        self.WGlobalProgress.done()
1247
+        self.WInView.resizeColumns()
1248
+
1249
+        self.update_informations()
1250
+
1251
+    @creation_context(None)
1252
+    def load_data(self, obj, needs=None):
1253
+        """
1254
+        Core function to load a .skin file into Skiin, if a need list is given
1255
+        it'll only try to load the needed object, it'll store the raw data o
1256
+        :param   obj: the object's file path or the object's name
1257
+        :type    obj: str
1258
+        :param needs: the wanted objects from this file path
1259
+        :type  needs: tuple
1260
+        :return:      loaded objects
1261
+        :rtype:       list
1262
+        """
1263
+        self.WInView.clean()
1264
+        self.WOutView.clean()
1265
+
1266
+        if os.path.isfile(obj):
1267
+            path = obj
1268
+            obj = get_object_from_path(path)
1269
+
1270
+        # if obj variable is not a file path we try to get a context
1271
+        else:
1272
+            path = self.get_context(obj)
1273
+
1274
+        # if fails => bye bye
1275
+        if not os.path.isfile(path):
1276
+            return []
1277
+
1278
+        # we hook the database
1279
+        database = sqlite3.connect(path)
1280
+        sum_cursor = database.cursor()
1281
+
1282
+        # we add some SQL selector if we want specific objects
1283
+        if needs:
1284
+            selector = "WHERE "
1285
+            selector += " OR ".join(["object=='%s'" % need for need in needs])
1286
+
1287
+        else:
1288
+            selector = ""
1289
+
1290
+        sum_cursor.execute("SELECT * FROM summary %s" % selector)
1291
+        len_summary = database.execute("SELECT COUNT(*) FROM summary %s" % selector).fetchone()[0]
1292
+
1293
+        # if our query is irrelevant
1294
+        if not len_summary:
1295
+            return []
1296
+
1297
+        # standard pre loading events
1298
+        self.WInView.progress.added.connect(self.WProgress.add)
1299
+        self.WProgress.set_mode('loading')
1300
+        self.WInView.progress.restart()
1301
+        self.WProgress.restart(start=False)
1302
+
1303
+        global_step = 100.0 / len_summary
1304
+        raw_cursor = database.cursor()
1305
+        objs = set()
1306
+
1307
+        custom_cat = ((i, cat) for i, cat in enumerate(self.base_parameters))
1308
+
1309
+        # we loop through our cursor to get each object's summary
1310
+        for n in sum_cursor:
1311
+            n = list(map(str, n))
1312
+
1313
+            # we directly skip elements which are not loaded
1314
+            if n[1] not in self.methods:
1315
+                continue
1316
+
1317
+            n[2] = self.objects.get(n[2])
1318
+            item = Skiin_Item(self.WInView, n)
1319
+            item.add_checkbox()
1320
+            item.setIcon(0, self.modifier_icons[n[1]])
1321
+            item.path = path
1322
+
1323
+            # getting information for each type of connection the object may have
1324
+            item.nfo[item.tp()] = 1
1325
+            for i, cat in custom_cat:
1326
+                item.nfo[cat] = database.execute("SELECT COUNT(*) FROM connections "
1327
+                                                 "WHERE main=='%s' AND connected=='%i' " % (item.mod(), i)).fetchone()[0]
1328
+
1329
+            # getting the raw datas for this modifier
1330
+            raw_cursor.execute("SELECT * FROM raw WHERE modifier=='%s' " % item.mod())
1331
+            table_len = database.execute("SELECT COUNT(*) FROM raw WHERE modifier=='%s' " % item.mod()).fetchone()[0]
1332
+
1333
+            step = global_step / table_len
1334
+
1335
+            # adding sub items for every deformer for this modifier
1336
+            for sn in raw_cursor:
1337
+                tsf, tpe, cnt, mod, obj = sn
1338
+                obj = self.objects.get(obj)
1339
+                sub = Skiin_Item(item, [self.format_name(tsf, False), tpe, obj, str(cnt)])
1340
+                item.addChild(sub)
1341
+
1342
+                self.WInView.progress.add(step, 'Reading infos for %s - %s' % (mod, obj))
1343
+
1344
+            objs.add(n[2])
1345
+
1346
+            self.WInView.addTopLevelItem(item)
1347
+
1348
+        # final closing
1349
+        database.close()
1350
+
1351
+        # standard post processing
1352
+        self.WInView.progress.done('')
1353
+        self.WInView.progress.added.disconnect(self.WProgress.add)
1354
+        self.WInName.setText(ntpath.basename(path))
1355
+
1356
+        self.WPaste.turn(len(list(objs)) and self.format_selection()[0])
1357
+        self.WFlush.turn(len(list(objs)))
1358
+
1359
+        return list(objs)
1360
+
1361
+    ### PASTING ###
1362
+
1363
+    @creation_context(None)
1364
+    def paste_data(self):
1365
+        """
1366
+        Core function which paste the loaded and selected information on Maya's objects
1367
+        """
1368
+        # getting the active modifiers and the objects we want to paste on
1369
+        items = [item for item in self.WInView.active_items()]
1370
+        objects = self.WInView.active_objects()
1371
+        selection = self.format_selection()[0]
1372
+        deformers = set()
1373
+        reorder = []
1374
+
1375
+        # abort if no modifier selected
1376
+        if not len(items):
1377
+            return
1378
+
1379
+        main_step = 25.0 / len(items)
1380
+        self.WInView.progress.restart()
1381
+        self.WGlobalProgress.restart()
1382
+
1383
+        # for every modifier we ;
1384
+        for index, item in enumerate(items):
1385
+            # check if the force selection is on, if so, we force the paste target
1386
+            # to be the selection
1387
+            if self.WForceSelection.isChecked() and