diff --git a/korman/operators/op_modifier.py b/korman/operators/op_modifier.py index 4c8650f..13879d5 100644 --- a/korman/operators/op_modifier.py +++ b/korman/operators/op_modifier.py @@ -17,6 +17,7 @@ import bpy from bpy.props import * import time +from ..ordered_set import OrderedSet from ..properties import modifiers def _fetch_modifiers(): @@ -71,17 +72,64 @@ class ModifierRemoveOperator(ModifierOperator, bpy.types.Operator): default=-1, options={"HIDDEN"}) + mods2delete = CollectionProperty(type=modifiers.PlasmaModifierSpec, options=set()) + + def draw(self, context): + layout = self.layout + mods = context.object.plasma_modifiers + + layout.label("This action will remove the following modifiers:") + layout = layout.column_flow(align=True) + for i in self.mods2delete: + mod = getattr(mods, i.name) + layout.label(" {}".format(mod.bl_label), icon=getattr(mod, "bl_icon", "NONE")) + def execute(self, context): - assert self.active_modifier >= 0 + want2delete = set((i.name for i in self.mods2delete)) + mods = sorted(context.object.plasma_modifiers.modifiers, key=lambda x: x.display_order) + subtract = 0 - for mod in context.object.plasma_modifiers.modifiers: - if mod.display_order == self.active_modifier: + for mod in mods: + if mod.pl_id in want2delete: mod.display_order = -1 mod.destroyed() - elif mod.display_order > self.active_modifier: - mod.display_order -= 1 + subtract += 1 + else: + mod.display_order -= subtract return {"FINISHED"} + def invoke(self, context, event): + assert self.active_modifier >= -1 + mods = context.object.plasma_modifiers + self.mods2delete.clear() + + want2delete = OrderedSet() + for i in mods.modifiers: + if i.display_order == self.active_modifier: + want2delete.add(i.pl_id) + break + else: + raise IndexError() + + # Here's the rub + # When we start, we should have just one modifier in want2delete + # HOWEVER, the mod may have dependencies, which in turn may have more deps + # So we collect them into the list... you dig? + for i in want2delete: + for mod in modifiers.PlasmaModifierProperties.__subclasses__(): + if not getattr(mods, mod.pl_id).enabled: + continue + if i in getattr(mod, "pl_depends", set()): + want2delete.add(mod.pl_id) + for i in want2delete: + mod = self.mods2delete.add() + mod.name = i + + if len(want2delete) == 1: + return self.execute(context) + else: + return context.window_manager.invoke_props_dialog(self) + class ModifierMoveOperator(ModifierOperator): def swap_modifier_ids(self, mods, s1, s2): diff --git a/korman/ordered_set.py b/korman/ordered_set.py new file mode 100644 index 0000000..eb333f6 --- /dev/null +++ b/korman/ordered_set.py @@ -0,0 +1,200 @@ +""" +An OrderedSet is a custom MutableSet that remembers its order, so that every +entry has an index that can be looked up. + +Based on a recipe originally posted to ActiveState Recipes by Raymond Hettiger, +and released under the MIT license. + +Rob Speer's changes are as follows: + + - changed the content from a doubly-linked list to a regular Python list. + Seriously, who wants O(1) deletes but O(N) lookups by index? + - add() returns the index of the added item + - index() just returns the index of an item + - added a __getstate__ and __setstate__ so it can be pickled + - added __getitem__ + +Updated for Python 3.5 by Adam Johnson +""" +import collections.abc + +SLICE_ALL = slice(None) +__version__ = '2.0.1' + + +def is_iterable(obj): + """ + Are we being asked to look up a list of things, instead of a single thing? + We check for the `__iter__` attribute so that this can cover types that + don't have to be known by this module, such as NumPy arrays. + + Strings, however, should be considered as atomic values to look up, not + iterables. The same goes for tuples, since they are immutable and therefore + valid entries. + + We don't need to check for the Python 2 `unicode` type, because it doesn't + have an `__iter__` attribute anyway. + """ + return hasattr(obj, '__iter__') and not isinstance(obj, str) and not isinstance(obj, tuple) + + +class OrderedSet(collections.abc.MutableSet): + """ + An OrderedSet is a custom MutableSet that remembers its order, so that + every entry has an index that can be looked up. + """ + def __init__(self, iterable=None): + self.items = [] + self.map = {} + if iterable is not None: + self |= iterable + + def __len__(self): + return len(self.items) + + def __getitem__(self, index): + """ + Get the item at a given index. + + If `index` is a slice, you will get back that slice of items. If it's + the slice [:], exactly the same object is returned. (If you want an + independent copy of an OrderedSet, use `OrderedSet.copy()`.) + + If `index` is an iterable, you'll get the OrderedSet of items + corresponding to those indices. This is similar to NumPy's + "fancy indexing". + """ + if index == SLICE_ALL: + return self + elif hasattr(index, '__index__') or isinstance(index, slice): + result = self.items[index] + if isinstance(result, list): + return OrderedSet(result) + else: + return result + elif is_iterable(index): + return OrderedSet([self.items[i] for i in index]) + else: + raise TypeError("Don't know how to index an OrderedSet by %r" % + index) + + def copy(self): + return OrderedSet(self) + + def __getstate__(self): + if len(self) == 0: + # The state can't be an empty list. + # We need to return a truthy value, or else __setstate__ won't be run. + # + # This could have been done more gracefully by always putting the state + # in a tuple, but this way is backwards- and forwards- compatible with + # previous versions of OrderedSet. + return (None,) + else: + return list(self) + + def __setstate__(self, state): + if state == (None,): + self.__init__([]) + else: + self.__init__(state) + + def __contains__(self, key): + return key in self.map + + def add(self, key): + """ + Add `key` as an item to this OrderedSet, then return its index. + + If `key` is already in the OrderedSet, return the index it already + had. + """ + if key not in self.map: + self.map[key] = len(self.items) + self.items.append(key) + return self.map[key] + append = add + + def update(self, sequence): + """ + Update the set with the given iterable sequence, then return the index + of the last element inserted. + """ + item_index = None + try: + for item in sequence: + item_index = self.add(item) + except TypeError: + raise ValueError('Argument needs to be an iterable, got %s' % type(sequence)) + return item_index + + def index(self, key): + """ + Get the index of a given entry, raising an IndexError if it's not + present. + + `key` can be an iterable of entries that is not a string, in which case + this returns a list of indices. + """ + if is_iterable(key): + return [self.index(subkey) for subkey in key] + return self.map[key] + + def pop(self): + """ + Remove and return the last element from the set. + + Raises KeyError if the set is empty. + """ + if not self.items: + raise KeyError('Set is empty') + + elem = self.items[-1] + del self.items[-1] + del self.map[elem] + return elem + + def discard(self, key): + """ + Remove an element. Do not raise an exception if absent. + + The MutableSet mixin uses this to implement the .remove() method, which + *does* raise an error when asked to remove a non-existent item. + """ + if key in self: + i = self.items.index(key) + del self.items[i] + del self.map[key] + for k, v in self.map.items(): + if v >= i: + self.map[k] = v - 1 + + def clear(self): + """ + Remove all items from this OrderedSet. + """ + del self.items[:] + self.map.clear() + + def __iter__(self): + return iter(self.items) + + def __reversed__(self): + return reversed(self.items) + + def __repr__(self): + if not self: + return '%s()' % (self.__class__.__name__,) + return '%s(%r)' % (self.__class__.__name__, list(self)) + + def __eq__(self, other): + if isinstance(other, OrderedSet): + return len(self) == len(other) and self.items == other.items + try: + other_as_set = set(other) + except TypeError: + # If `other` can't be converted into a set, it's not equal. + return False + else: + return set(self) == other_as_set + diff --git a/korman/properties/modifiers/__init__.py b/korman/properties/modifiers/__init__.py index 938e333..ab55d08 100644 --- a/korman/properties/modifiers/__init__.py +++ b/korman/properties/modifiers/__init__.py @@ -66,6 +66,10 @@ class PlasmaModifiers(bpy.types.PropertyGroup): bpy.types.Object.plasma_modifiers = bpy.props.PointerProperty(type=cls) +class PlasmaModifierSpec(bpy.types.PropertyGroup): + pass + + def modifier_mapping(): """This returns a dict mapping Plasma Modifier categories to names"""