From a32b859fecb1a1d269408d0b9730eb5c7fc52c0f Mon Sep 17 00:00:00 2001
From: Ryan Inch <mythologylover75@gmail.com>
Date: Mon, 30 Mar 2020 02:34:28 -0400
Subject: [PATCH] Collection Manager: QCD polishing. Task: T69577

Made QCD internal structure more stable.
Prevented reload scripts from resetting QCD slots.
Fixed QCD being left in an incorrect state on undo/redo.
Fixed a small error on unregister.
---
 object_collection_manager/__init__.py        |   7 +-
 object_collection_manager/internals.py       | 168 +++++++++++--------
 object_collection_manager/operators.py       |   4 +-
 object_collection_manager/persistent_data.py |  23 +++
 object_collection_manager/qcd_init.py        |  12 +-
 object_collection_manager/qcd_move_widget.py |  14 +-
 object_collection_manager/qcd_operators.py   |  39 ++---
 object_collection_manager/ui.py              |  10 +-
 8 files changed, 159 insertions(+), 118 deletions(-)
 create mode 100644 object_collection_manager/persistent_data.py

diff --git a/object_collection_manager/__init__.py b/object_collection_manager/__init__.py
index 564500648..de40b9e1f 100644
--- a/object_collection_manager/__init__.py
+++ b/object_collection_manager/__init__.py
@@ -22,7 +22,7 @@ bl_info = {
     "name": "Collection Manager",
     "description": "Manage collections and their objects",
     "author": "Ryan Inch",
-    "version": (2,4,5),
+    "version": (2,4,9),
     "blender": (2, 80, 0),
     "location": "View3D - Object Mode (Shortcut - M)",
     "warning": '',  # used for warning icon and text in addons panel
@@ -126,6 +126,9 @@ def register():
         qcd_init.register_qcd()
 
 def unregister():
+    if bpy.context.preferences.addons[__package__].preferences.enable_qcd:
+        qcd_init.unregister_qcd()
+
     for cls in classes:
         bpy.utils.unregister_class(cls)
 
@@ -136,8 +139,6 @@ def unregister():
         km.keymap_items.remove(kmi)
     addon_keymaps.clear()
 
-    qcd_init.unregister_qcd()
-
 
 if __name__ == "__main__":
     register()
diff --git a/object_collection_manager/internals.py b/object_collection_manager/internals.py
index 5ebc60252..116eb38ca 100644
--- a/object_collection_manager/internals.py
+++ b/object_collection_manager/internals.py
@@ -18,6 +18,8 @@
 
 # Copyright 2011, Ryan Inch
 
+from . import persistent_data
+
 import bpy
 
 from bpy.types import (
@@ -45,21 +47,23 @@ class QCDSlots():
     overrides = {}
     allow_update = True
 
+    def __init__(self):
+        self._slots = persistent_data.slots
+        self.overrides = persistent_data.overrides
+
     def __iter__(self):
         return self._slots.items().__iter__()
 
     def __repr__(self):
         return self._slots.__repr__()
 
-    def __contains__(self, key):
-        try:
-            int(key)
-            return key in self._slots
+    def contains(self, *, idx=None, name=None):
+        if idx:
+            return idx in self._slots.keys()
+        if name:
+            return name in self._slots.values()
 
-        except ValueError:
-            return key in self._slots.values()
-
-        return False
+        raise
 
     def get_data_for_blend(self):
         return f"{self._slots.__repr__()}\n{self.overrides.__repr__()}"
@@ -69,8 +73,14 @@ class QCDSlots():
         blend_slots = eval(decoupled_data[0])
         blend_overrides = eval(decoupled_data[1])
 
-        self._slots = blend_slots
-        self.overrides = blend_overrides
+        self._slots.clear()
+        self.overrides.clear()
+
+        for k, v in blend_slots.items():
+            self._slots[k] = v
+
+        for k, v in blend_overrides.items():
+            self.overrides[k] = v
 
     def length(self):
         return len(self._slots)
@@ -91,21 +101,64 @@ class QCDSlots():
     def add_slot(self, idx, name):
         self._slots[idx] = name
 
-    def update_slot(self, idx, name):
-        self._slots[idx] = name
+        if name in self.overrides:
+            del self.overrides[name]
 
-    def del_slot(self, slot):
-        try:
-            int(slot)
-            del self._slots[slot]
+    def update_slot(self, idx, name):
+        self.add_slot(idx, name)
 
-        except ValueError:
-            idx = self.get_idx(slot)
+    def del_slot(self, *, idx=None, name=None):
+        if idx and not name:
             del self._slots[idx]
+            return
+
+        if name and not idx:
+            slot_idx = self.get_idx(name)
+            del self._slots[slot_idx]
+            return
 
-    def clear(self):
+        raise
+
+    def add_override(self, name):
+        qcd_slots.del_slot(name=name)
+        qcd_slots.overrides[name] = True
+
+    def clear_slots(self):
         self._slots.clear()
 
+    def update_qcd(self):
+        for idx, name in list(self._slots.items()):
+            if not layer_collections.get(name, None):
+                qcd_slots.del_slot(name=name)
+
+    def auto_numerate(self, *, renumerate=False):
+        global max_lvl
+
+        if self.length() < 20:
+            lvl = 0
+            num = 1
+            while lvl <= max_lvl:
+                if num > 20:
+                    break
+
+                for laycol in layer_collections.values():
+                    if num > 20:
+                        break
+
+                    if int(laycol["lvl"]) == lvl:
+                        if laycol["name"] in qcd_slots.overrides:
+                            if not renumerate:
+                                num += 1
+                            continue
+
+                        if (not self.contains(idx=str(num)) and
+                            not self.contains(name=laycol["name"])):
+                                self.add_slot(str(num), laycol["name"])
+
+                        num += 1
+
+                lvl += 1
+
 qcd_slots = QCDSlots()
 
 
@@ -140,45 +193,39 @@ def update_qcd_slot(self, context):
     update_needed = False
 
     try:
-        int(self.qcd_slot)
+        int(self.qcd_slot_idx)
     except:
 
-        if self.qcd_slot == "":
-            qcd_slots.del_slot(self.name)
-            qcd_slots.overrides[self.name] = True
+        if self.qcd_slot_idx == "":
+            qcd_slots.add_override(self.name)
 
-        if self.name in qcd_slots:
+        if qcd_slots.contains(name=self.name):
             qcd_slots.allow_update = False
-            self.qcd_slot = qcd_slots.get_idx(self.name)
+            self.qcd_slot_idx = qcd_slots.get_idx(self.name)
             qcd_slots.allow_update = True
 
         if self.name in qcd_slots.overrides:
             qcd_slots.allow_update = False
-            self.qcd_slot = ""
+            self.qcd_slot_idx = ""
             qcd_slots.allow_update = True
 
         return
 
-    if self.name in qcd_slots:
-        qcd_slots.del_slot(self.name)
+    if qcd_slots.contains(name=self.name):
+        qcd_slots.del_slot(name=self.name)
         update_needed = True
 
-    if self.qcd_slot in qcd_slots:
-        qcd_slots.overrides[qcd_slots.get_name(self.qcd_slot)] = True
-        qcd_slots.del_slot(self.qcd_slot)
+    if qcd_slots.contains(idx=self.qcd_slot_idx):
+        qcd_slots.add_override(qcd_slots.get_name(self.qcd_slot_idx))
         update_needed = True
 
-    if int(self.qcd_slot) > 20:
-        self.qcd_slot = "20"
+    if int(self.qcd_slot_idx) > 20:
+        self.qcd_slot_idx = "20"
 
-    if int(self.qcd_slot) < 1:
-        self.qcd_slot = "1"
-
-    qcd_slots.add_slot(self.qcd_slot, self.name)
-
-    if self.name in qcd_slots.overrides:
-        del qcd_slots.overrides[self.name]
+    if int(self.qcd_slot_idx) < 1:
+        self.qcd_slot_idx = "1"
 
+    qcd_slots.add_slot(self.qcd_slot_idx, self.name)
 
     if update_needed:
         update_property_group(context)
@@ -187,10 +234,10 @@ def update_qcd_slot(self, context):
 class CMListCollection(PropertyGroup):
     name: StringProperty(update=update_col_name)
     last_name: StringProperty()
-    qcd_slot: StringProperty(name="QCD Slot", update=update_qcd_slot)
+    qcd_slot_idx: StringProperty(name="QCD Slot", update=update_qcd_slot)
 
 
-def update_collection_tree(context, renumerate=False):
+def update_collection_tree(context, *, renumerate_qcd=False):
     global max_lvl
     global row_index
     global collection_tree
@@ -222,36 +269,9 @@ def update_collection_tree(context, renumerate=False):
     for laycol in master_laycol["children"]:
         collection_tree.append(laycol)
 
-    # update qcd
-    for x in range(20):
-        qcd_slot = qcd_slots.get_name(str(x+1))
-        if qcd_slot and not layer_collections.get(qcd_slot, None):
-            qcd_slots.del_slot(qcd_slot)
-
-    # update autonumeration
-    if qcd_slots.length() < 20:
-        lvl = 0
-        num = 1
-        while lvl <= max_lvl:
-            if num > 20:
-                break
-
-            for laycol in layer_collections.values():
-                if num > 20:
-                    break
-
-                if int(laycol["lvl"]) == lvl:
-                    if laycol["name"] in qcd_slots.overrides:
-                        if not renumerate:
-                            num += 1
-                        continue
-
-                    if str(num) not in qcd_slots and laycol["name"] not in qcd_slots:
-                        qcd_slots.add_slot(str(num), laycol["name"])
-
-                    num += 1
+    qcd_slots.update_qcd()
 
-            lvl += 1
+    qcd_slots.auto_numerate(renumerate=renumerate_qcd)
 
 
 def get_all_collections(context, collections, parent, tree, level=0, visible=False):
@@ -290,13 +310,13 @@ def get_all_collections(context, collections, parent, tree, level=0, visible=Fal
                 get_all_collections(context, item.children, laycol, laycol["children"], level+1)
 
 
-def update_property_group(context, renumerate=False):
+def update_property_group(context, *, renumerate_qcd=False):
     global collection_tree
     global qcd_slots
 
     qcd_slots.allow_update = False
 
-    update_collection_tree(context, renumerate)
+    update_collection_tree(context, renumerate_qcd=renumerate_qcd)
     context.scene.collection_manager.cm_list_collection.clear()
     create_property_group(context, collection_tree)
 
@@ -312,7 +332,7 @@ def create_property_group(context, tree):
     for laycol in tree:
         new_cm_listitem = cm.cm_list_collection.add()
         new_cm_listitem.name = laycol["name"]
-        new_cm_listitem.qcd_slot = qcd_slots.get_idx(laycol["name"], "")
+        new_cm_listitem.qcd_slot_idx = qcd_slots.get_idx(laycol["name"], "")
 
         if laycol["has_children"]:
             create_property_group(context, laycol["children"])
diff --git a/object_collection_manager/operators.py b/object_collection_manager/operators.py
index bf7a77f87..c8c10336f 100644
--- a/object_collection_manager/operators.py
+++ b/object_collection_manager/operators.py
@@ -1905,8 +1905,8 @@ class CMRemoveCollectionOperator(Operator):
 
 
         # update qcd
-        if self.collection_name in qcd_slots:
-            qcd_slots.del_slot(self.collection_name)
+        if qcd_slots.contains(name=self.collection_name):
+            qcd_slots.del_slot(name=self.collection_name)
 
         if self.collection_name in qcd_slots.overrides:
             del qcd_slots.overrides[self.collection_name]
diff --git a/object_collection_manager/persistent_data.py b/object_collection_manager/persistent_data.py
new file mode 100644
index 000000000..a3432b7df
--- /dev/null
+++ b/object_collection_manager/persistent_data.py
@@ -0,0 +1,23 @@
+# ##### BEGIN GPL LICENSE BLOCK #####
+#
+#  This program is free software; you can redistribute it and/or
+#  modify it under the terms of the GNU General Public License
+#  as published by the Free Software Foundation; either version 2
+#  of the License, or (at your option) any later version.
+#
+#  This program is distributed in the hope that it will be useful,
+#  but WITHOUT ANY WARRANTY; without even the implied warranty of
+#  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+#  GNU General Public License for more details.
+#
+#  You should have received a copy of the GNU General Public License
+#  along with this program; if not, write to the Free Software Foundation,
+#  Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+#
+# ##### END GPL LICENSE BLOCK #####
+
+# Copyright 2011, Ryan Inch
+
+# QCD
+slots = {}
+overrides = {}
diff --git a/object_collection_manager/qcd_init.py b/object_collection_manager/qcd_init.py
index 4857719ac..87cbcd161 100644
--- a/object_collection_manager/qcd_init.py
+++ b/object_collection_manager/qcd_init.py
@@ -3,6 +3,7 @@ import bpy
 import bpy.utils.previews
 from bpy.app.handlers import persistent
 
+from . import internals
 from . import preferences
 from . import qcd_move_widget
 from . import qcd_operators
@@ -27,8 +28,11 @@ def depsgraph_update_post_handler(dummy):
 
     qcd_operators.move_selection.clear()
     qcd_operators.move_active = None
-    qcd_operators.get_move_selection()
-    qcd_operators.get_move_active()
+
+@persistent
+def undo_redo_post_handler(dummy):
+    qcd_operators.move_selection.clear()
+    qcd_operators.move_active = None
 
 @persistent
 def save_internal_data(dummy):
@@ -63,6 +67,8 @@ def register_qcd():
     addon_qcd_keymaps.append((km, kmi))
 
     bpy.app.handlers.depsgraph_update_post.append(depsgraph_update_post_handler)
+    bpy.app.handlers.undo_post.append(undo_redo_post_handler)
+    bpy.app.handlers.redo_post.append(undo_redo_post_handler)
     bpy.app.handlers.save_pre.append(save_internal_data)
     bpy.app.handlers.load_post.append(load_internal_data)
 
@@ -117,6 +123,8 @@ def unregister_qcd():
         bpy.utils.unregister_class(cls)
 
     bpy.app.handlers.depsgraph_update_post.remove(depsgraph_update_post_handler)
+    bpy.app.handlers.undo_post.remove(undo_redo_post_handler)
+    bpy.app.handlers.redo_post.remove(undo_redo_post_handler)
     bpy.app.handlers.save_pre.remove(save_internal_data)
     bpy.app.handlers.load_post.remove(load_internal_data)
 
diff --git a/object_collection_manager/qcd_move_widget.py b/object_collection_manager/qcd_move_widget.py
index 441c39dee..132121466 100644
--- a/object_collection_manager/qcd_move_widget.py
+++ b/object_collection_manager/qcd_move_widget.py
@@ -653,10 +653,10 @@ def allocate_main_ui(self, context):
         for num in range(button_group):
             slot_num = row_num + num
 
-            qcd_slot = qcd_slots.get_name(f"{slot_num}")
+            qcd_slot_name = qcd_slots.get_name(f"{slot_num}")
 
-            if qcd_slot:
-                qcd_laycol = layer_collections[qcd_slot]["ptr"]
+            if qcd_slot_name:
+                qcd_laycol = layer_collections[qcd_slot_name]["ptr"]
                 collection_objects = qcd_laycol.collection.objects
                 selected_objects = qcd_operators.get_move_selection()
                 active_object = qcd_operators.get_move_active()
@@ -815,16 +815,16 @@ def draw_callback_px(self, context):
             rounding = 5
 
             if num < 10:
-                if not f"{num+2}" in qcd_slots:
+                if not qcd_slots.contains(idx=f"{num+2}"):
                     tr = rounding
 
-                if not f"{num}" in qcd_slots:
+                if not qcd_slots.contains(idx=f"{num}"):
                     tl = rounding
             else:
-                if not f"{num+2}" in qcd_slots:
+                if not qcd_slots.contains(idx=f"{num+2}"):
                     br = rounding
 
-                if not f"{num}" in qcd_slots:
+                if not qcd_slots.contains(idx=f"{num}"):
                     bl = rounding
 
             if num in [0,5]:
diff --git a/object_collection_manager/qcd_operators.py b/object_collection_manager/qcd_operators.py
index 2b45c1de9..9bfa21109 100644
--- a/object_collection_manager/qcd_operators.py
+++ b/object_collection_manager/qcd_operators.py
@@ -47,35 +47,22 @@ def get_move_selection():
     global move_selection
 
     if not move_selection:
-        move_selection = bpy.context.selected_objects
+        move_selection = [obj.name for obj in bpy.context.selected_objects]
 
-    return move_selection
+    return [bpy.data.objects[name] for name in move_selection]
 
 def get_move_active():
     global move_active
     global move_selection
 
     if not move_active:
-        move_active = bpy.context.view_layer.objects.active
+        move_active = getattr(bpy.context.view_layer.objects.active, "name", None)
 
-    if move_active not in get_move_selection():
+    if move_active not in [obj.name for obj in get_move_selection()]:
         move_active = None
 
-    if move_active:
-        try:
-            move_active.name
+    return bpy.data.objects[move_active] if move_active else None
 
-        except:
-            move_active = None
-            move_selection = []
-
-            # update header widget
-            cm = bpy.context.scene.collection_manager
-            cm.update_header.clear()
-            new_update_header = cm.update_header.add()
-            new_update_header.name = "updated"
-
-    return move_active
 
 class MoveToQCDSlot(Operator):
     '''Move object(s) to QCD slot'''
@@ -94,10 +81,11 @@ class MoveToQCDSlot(Operator):
         selected_objects = get_move_selection()
         active_object = get_move_active()
         move_triggered = True
-        qcd_laycol = qcd_slots.get_name(self.slot)
+        qcd_laycol = None
+        slot_name = qcd_slots.get_name(self.slot)
 
-        if qcd_laycol:
-            qcd_laycol = layer_collections[qcd_laycol]["ptr"]
+        if slot_name:
+            qcd_laycol = layer_collections[slot_name]["ptr"]
 
         else:
             return {'CANCELLED'}
@@ -213,10 +201,11 @@ class ViewQCDSlot(Operator):
         global layer_collections
         global rto_history
 
-        qcd_laycol = qcd_slots.get_name(self.slot)
+        qcd_laycol = None
+        slot_name = qcd_slots.get_name(self.slot)
 
-        if qcd_laycol:
-            qcd_laycol = layer_collections[qcd_laycol]["ptr"]
+        if slot_name:
+            qcd_laycol = layer_collections[slot_name]["ptr"]
 
         else:
             return {'CANCELLED'}
@@ -297,6 +286,6 @@ class RenumerateQCDSlots(Operator):
         if event.ctrl:
             qcd_slots.overrides.clear()
 
-        update_property_group(context, renumerate=True)
+        update_property_group(context, renumerate_qcd=True)
 
         return {'FINISHED'}
diff --git a/object_collection_manager/ui.py b/object_collection_manager/ui.py
index e2f075dbf..4640c9c23 100644
--- a/object_collection_manager/ui.py
+++ b/object_collection_manager/ui.py
@@ -343,7 +343,7 @@ class CM_UL_items(UIList):
         if context.preferences.addons[__package__].preferences.enable_qcd:
             QCD = row.row()
             QCD.scale_x = 0.4
-            QCD.prop(item, "qcd_slot", text="")
+            QCD.prop(item, "qcd_slot_idx", text="")
 
         name_row = row.row()
 
@@ -483,7 +483,7 @@ class CM_UL_items(UIList):
             flt_flags = [0] * len(list_items)
 
             for idx, item in enumerate(list_items):
-                if item.qcd_slot:
+                if item.qcd_slot_idx:
                     flt_flags[idx] |= self.bitflag_filter_item
 
         else: # display as treeview
@@ -533,10 +533,10 @@ def view3d_header_qcd_slots(self, context):
     update_collection_tree(context)
 
     for x in range(20):
-        qcd_slot = qcd_slots.get_name(str(x+1))
+        qcd_slot_name = qcd_slots.get_name(str(x+1))
 
-        if qcd_slot:
-            qcd_laycol = layer_collections[qcd_slot]["ptr"]
+        if qcd_slot_name:
+            qcd_laycol = layer_collections[qcd_slot_name]["ptr"]
             collection_objects = qcd_laycol.collection.objects
             selected_objects = qcd_operators.get_move_selection()
             active_object = qcd_operators.get_move_active()
-- 
GitLab