Merge branch 'blender-v2.92-release'
[blender-addons.git] / object_collection_manager / operator_utils.py
blob820ab1bb59b861be3771ab8b3a103382e93a7872
1 # ##### BEGIN GPL LICENSE BLOCK #####
3 # This program is free software; you can redistribute it and/or
4 # modify it under the terms of the GNU General Public License
5 # as published by the Free Software Foundation; either version 2
6 # of the License, or (at your option) any later version.
8 # This program is distributed in the hope that it will be useful,
9 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # GNU General Public License for more details.
13 # You should have received a copy of the GNU General Public License
14 # along with this program; if not, write to the Free Software Foundation,
15 # Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
17 # ##### END GPL LICENSE BLOCK #####
19 # Copyright 2011, Ryan Inch
20 import bpy
22 # For VARS
23 from . import internals
25 # For FUNCTIONS
26 from .internals import (
27 update_property_group,
28 get_move_selection,
29 get_move_active,
32 mode_converter = {
33 'EDIT_MESH': 'EDIT',
34 'EDIT_CURVE': 'EDIT',
35 'EDIT_SURFACE': 'EDIT',
36 'EDIT_TEXT': 'EDIT',
37 'EDIT_ARMATURE': 'EDIT',
38 'EDIT_METABALL': 'EDIT',
39 'EDIT_LATTICE': 'EDIT',
40 'POSE': 'POSE',
41 'SCULPT': 'SCULPT',
42 'PAINT_WEIGHT': 'WEIGHT_PAINT',
43 'PAINT_VERTEX': 'VERTEX_PAINT',
44 'PAINT_TEXTURE': 'TEXTURE_PAINT',
45 'PARTICLE': 'PARTICLE_EDIT',
46 'OBJECT': 'OBJECT',
47 'PAINT_GPENCIL': 'PAINT_GPENCIL',
48 'EDIT_GPENCIL': 'EDIT_GPENCIL',
49 'SCULPT_GPENCIL': 'SCULPT_GPENCIL',
50 'WEIGHT_GPENCIL': 'WEIGHT_GPENCIL',
51 'VERTEX_GPENCIL': 'VERTEX_GPENCIL',
55 rto_path = {
56 "exclude": "exclude",
57 "select": "collection.hide_select",
58 "hide": "hide_viewport",
59 "disable": "collection.hide_viewport",
60 "render": "collection.hide_render",
61 "holdout": "holdout",
62 "indirect": "indirect_only",
65 set_off_on = {
66 "exclude": {
67 "off": True,
68 "on": False
70 "select": {
71 "off": True,
72 "on": False
74 "hide": {
75 "off": True,
76 "on": False
78 "disable": {
79 "off": True,
80 "on": False
82 "render": {
83 "off": True,
84 "on": False
86 "holdout": {
87 "off": False,
88 "on": True
90 "indirect": {
91 "off": False,
92 "on": True
96 get_off_on = {
97 False: {
98 "exclude": "on",
99 "select": "on",
100 "hide": "on",
101 "disable": "on",
102 "render": "on",
103 "holdout": "off",
104 "indirect": "off",
107 True: {
108 "exclude": "off",
109 "select": "off",
110 "hide": "off",
111 "disable": "off",
112 "render": "off",
113 "holdout": "on",
114 "indirect": "on",
119 def get_rto(layer_collection, rto):
120 if rto in ["exclude", "hide", "holdout", "indirect"]:
121 return getattr(layer_collection, rto_path[rto])
123 else:
124 collection = getattr(layer_collection, "collection")
125 return getattr(collection, rto_path[rto].split(".")[1])
128 def set_rto(layer_collection, rto, value):
129 if rto in ["exclude", "hide", "holdout", "indirect"]:
130 setattr(layer_collection, rto_path[rto], value)
132 else:
133 collection = getattr(layer_collection, "collection")
134 setattr(collection, rto_path[rto].split(".")[1], value)
137 def apply_to_children(parent, apply_function):
138 # works for both Collections & LayerCollections
139 child_lists = [parent.children]
141 while child_lists:
142 new_child_lists = []
144 for child_list in child_lists:
145 for child in child_list:
146 apply_function(child)
148 if child.children:
149 new_child_lists.append(child.children)
151 child_lists = new_child_lists
154 def isolate_rto(cls, self, view_layer, rto, *, children=False):
155 off = set_off_on[rto]["off"]
156 on = set_off_on[rto]["on"]
158 laycol_ptr = internals.layer_collections[self.name]["ptr"]
159 target = internals.rto_history[rto][view_layer]["target"]
160 history = internals.rto_history[rto][view_layer]["history"]
162 # get active collections
163 active_layer_collections = [x["ptr"] for x in internals.layer_collections.values()
164 if get_rto(x["ptr"], rto) == on]
166 # check if previous state should be restored
167 if cls.isolated and self.name == target:
168 # restore previous state
169 for x, item in enumerate(internals.layer_collections.values()):
170 set_rto(item["ptr"], rto, history[x])
172 # reset target and history
173 del internals.rto_history[rto][view_layer]
175 cls.isolated = False
177 # check if all RTOs should be activated
178 elif (len(active_layer_collections) == 1 and
179 active_layer_collections[0].name == self.name):
180 # activate all collections
181 for item in internals.layer_collections.values():
182 set_rto(item["ptr"], rto, on)
184 # reset target and history
185 del internals.rto_history[rto][view_layer]
187 cls.isolated = False
189 else:
190 # isolate collection
192 internals.rto_history[rto][view_layer]["target"] = self.name
194 # reset history
195 history.clear()
197 # save state
198 for item in internals.layer_collections.values():
199 history.append(get_rto(item["ptr"], rto))
201 child_states = {}
202 if children:
203 # get child states
204 def get_child_states(layer_collection):
205 child_states[layer_collection.name] = get_rto(layer_collection, rto)
207 apply_to_children(laycol_ptr, get_child_states)
209 # isolate collection
210 for item in internals.layer_collections.values():
211 if item["name"] != laycol_ptr.name:
212 set_rto(item["ptr"], rto, off)
214 set_rto(laycol_ptr, rto, on)
216 if rto not in ["exclude", "holdout", "indirect"]:
217 # activate all parents
218 laycol = internals.layer_collections[self.name]
219 while laycol["id"] != 0:
220 set_rto(laycol["ptr"], rto, on)
221 laycol = laycol["parent"]
223 if children:
224 # restore child states
225 def restore_child_states(layer_collection):
226 set_rto(layer_collection, rto, child_states[layer_collection.name])
228 apply_to_children(laycol_ptr, restore_child_states)
230 else:
231 if children:
232 # restore child states
233 def restore_child_states(layer_collection):
234 set_rto(layer_collection, rto, child_states[layer_collection.name])
236 apply_to_children(laycol_ptr, restore_child_states)
238 elif rto == "exclude":
239 # deactivate all children
240 def deactivate_all_children(layer_collection):
241 set_rto(layer_collection, rto, True)
243 apply_to_children(laycol_ptr, deactivate_all_children)
245 cls.isolated = True
248 def isolate_sel_objs_collections(view_layer, rto, caller, *, use_active=False):
249 selected_objects = get_move_selection()
251 if use_active:
252 selected_objects.add(get_move_active(always=True))
254 if not selected_objects:
255 return "No selected objects"
257 off = set_off_on[rto]["off"]
258 on = set_off_on[rto]["on"]
260 if caller == "CM":
261 history = internals.rto_history[rto+"_all"][view_layer]
263 elif caller == "QCD":
264 history = internals.qcd_history[view_layer]
267 # if not isolated, isolate collections of selected objects
268 if len(history) == 0:
269 keep_history = False
271 # save history and isolate RTOs
272 for item in internals.layer_collections.values():
273 history.append(get_rto(item["ptr"], rto))
274 rto_state = off
276 # check if any of the selected objects are in the collection
277 if not set(selected_objects).isdisjoint(item["ptr"].collection.objects):
278 rto_state = on
280 if history[-1] != rto_state:
281 keep_history = True
283 if rto == "exclude":
284 set_exclude_state(item["ptr"], rto_state)
286 else:
287 set_rto(item["ptr"], rto, rto_state)
289 # activate all parents if needed
290 if rto_state == on and rto not in ["holdout", "indirect"]:
291 laycol = item["parent"]
292 while laycol["id"] != 0:
293 set_rto(laycol["ptr"], rto, on)
294 laycol = laycol["parent"]
297 if not keep_history:
298 history.clear()
300 return "Collection already isolated"
303 else:
304 for x, item in enumerate(internals.layer_collections.values()):
305 set_rto(item["ptr"], rto, history[x])
307 # clear history
308 if caller == "CM":
309 del internals.rto_history[rto+"_all"][view_layer]
311 elif caller == "QCD":
312 del internals.qcd_history[view_layer]
315 def disable_sel_objs_collections(view_layer, rto, caller):
316 off = set_off_on[rto]["off"]
317 on = set_off_on[rto]["on"]
318 selected_objects = get_move_selection()
320 if caller == "CM":
321 history = internals.rto_history[rto+"_all"][view_layer]
323 elif caller == "QCD":
324 history = internals.qcd_history[view_layer]
327 if not selected_objects and not history:
328 # clear history
329 if caller == "CM":
330 del internals.rto_history[rto+"_all"][view_layer]
332 elif caller == "QCD":
333 del internals.qcd_history[view_layer]
335 return "No selected objects"
337 # if not disabled, disable collections of selected objects
338 if len(history) == 0:
339 # save history and disable RTOs
340 for item in internals.layer_collections.values():
341 history.append(get_rto(item["ptr"], rto))
343 # check if any of the selected objects are in the collection
344 if not set(selected_objects).isdisjoint(item["ptr"].collection.objects):
345 if rto == "exclude":
346 set_exclude_state(item["ptr"], off)
348 else:
349 set_rto(item["ptr"], rto, off)
352 else:
353 for x, item in enumerate(internals.layer_collections.values()):
354 set_rto(item["ptr"], rto, history[x])
356 # clear history
357 if caller == "CM":
358 del internals.rto_history[rto+"_all"][view_layer]
360 elif caller == "QCD":
361 del internals.qcd_history[view_layer]
364 def toggle_children(self, view_layer, rto):
365 laycol_ptr = internals.layer_collections[self.name]["ptr"]
366 # clear rto history
367 del internals.rto_history[rto][view_layer]
368 internals.rto_history[rto+"_all"].pop(view_layer, None)
370 # toggle rto state
371 state = not get_rto(laycol_ptr, rto)
372 set_rto(laycol_ptr, rto, state)
374 def set_state(layer_collection):
375 set_rto(layer_collection, rto, state)
377 apply_to_children(laycol_ptr, set_state)
380 def activate_all_rtos(view_layer, rto):
381 off = set_off_on[rto]["off"]
382 on = set_off_on[rto]["on"]
384 history = internals.rto_history[rto+"_all"][view_layer]
386 # if not activated, activate all
387 if len(history) == 0:
388 keep_history = False
390 for item in reversed(list(internals.layer_collections.values())):
391 if get_rto(item["ptr"], rto) == off:
392 keep_history = True
394 history.append(get_rto(item["ptr"], rto))
396 set_rto(item["ptr"], rto, on)
398 if not keep_history:
399 history.clear()
401 history.reverse()
403 else:
404 for x, item in enumerate(internals.layer_collections.values()):
405 set_rto(item["ptr"], rto, history[x])
407 # clear rto history
408 del internals.rto_history[rto+"_all"][view_layer]
411 def invert_rtos(view_layer, rto):
412 if rto == "exclude":
413 orig_values = []
415 for item in internals.layer_collections.values():
416 orig_values.append(get_rto(item["ptr"], rto))
418 for x, item in enumerate(internals.layer_collections.values()):
419 set_rto(item["ptr"], rto, not orig_values[x])
421 else:
422 for item in internals.layer_collections.values():
423 set_rto(item["ptr"], rto, not get_rto(item["ptr"], rto))
425 # clear rto history
426 internals.rto_history[rto].pop(view_layer, None)
429 def copy_rtos(view_layer, rto):
430 if not internals.copy_buffer["RTO"]:
431 # copy
432 internals.copy_buffer["RTO"] = rto
433 for laycol in internals.layer_collections.values():
434 internals.copy_buffer["values"].append(get_off_on[
435 get_rto(laycol["ptr"], rto)
441 else:
442 # paste
443 for x, laycol in enumerate(internals.layer_collections.values()):
444 set_rto(laycol["ptr"],
445 rto,
446 set_off_on[rto][
447 internals.copy_buffer["values"][x]
451 # clear rto history
452 internals.rto_history[rto].pop(view_layer, None)
453 del internals.rto_history[rto+"_all"][view_layer]
455 # clear copy buffer
456 internals.copy_buffer["RTO"] = ""
457 internals.copy_buffer["values"].clear()
460 def swap_rtos(view_layer, rto):
461 if not internals.swap_buffer["A"]["values"]:
462 # get A
463 internals.swap_buffer["A"]["RTO"] = rto
464 for laycol in internals.layer_collections.values():
465 internals.swap_buffer["A"]["values"].append(get_off_on[
466 get_rto(laycol["ptr"], rto)
472 else:
473 # get B
474 internals.swap_buffer["B"]["RTO"] = rto
475 for laycol in internals.layer_collections.values():
476 internals.swap_buffer["B"]["values"].append(get_off_on[
477 get_rto(laycol["ptr"], rto)
483 # swap A with B
484 for x, laycol in enumerate(internals.layer_collections.values()):
485 set_rto(laycol["ptr"], internals.swap_buffer["A"]["RTO"],
486 set_off_on[
487 internals.swap_buffer["A"]["RTO"]
489 internals.swap_buffer["B"]["values"][x]
493 set_rto(laycol["ptr"], internals.swap_buffer["B"]["RTO"],
494 set_off_on[
495 internals.swap_buffer["B"]["RTO"]
497 internals.swap_buffer["A"]["values"][x]
502 # clear rto history
503 swap_a = internals.swap_buffer["A"]["RTO"]
504 swap_b = internals.swap_buffer["B"]["RTO"]
506 internals.rto_history[swap_a].pop(view_layer, None)
507 internals.rto_history[swap_a+"_all"].pop(view_layer, None)
508 internals.rto_history[swap_b].pop(view_layer, None)
509 internals.rto_history[swap_b+"_all"].pop(view_layer, None)
511 # clear swap buffer
512 internals.swap_buffer["A"]["RTO"] = ""
513 internals.swap_buffer["A"]["values"].clear()
514 internals.swap_buffer["B"]["RTO"] = ""
515 internals.swap_buffer["B"]["values"].clear()
518 def clear_copy(rto):
519 if internals.copy_buffer["RTO"] == rto:
520 internals.copy_buffer["RTO"] = ""
521 internals.copy_buffer["values"].clear()
524 def clear_swap(rto):
525 if internals.swap_buffer["A"]["RTO"] == rto:
526 internals.swap_buffer["A"]["RTO"] = ""
527 internals.swap_buffer["A"]["values"].clear()
528 internals.swap_buffer["B"]["RTO"] = ""
529 internals.swap_buffer["B"]["values"].clear()
532 def link_child_collections_to_parent(laycol, collection, parent_collection):
533 # store view layer RTOs for all children of the to be deleted collection
534 child_states = {}
535 def get_child_states(layer_collection):
536 child_states[layer_collection.name] = (layer_collection.exclude,
537 layer_collection.hide_viewport,
538 layer_collection.holdout,
539 layer_collection.indirect_only)
541 apply_to_children(laycol["ptr"], get_child_states)
543 # link any subcollections of the to be deleted collection to it's parent
544 for subcollection in collection.children:
545 if not subcollection.name in parent_collection.children:
546 parent_collection.children.link(subcollection)
548 # apply the stored view layer RTOs to the newly linked collections and their
549 # children
550 def restore_child_states(layer_collection):
551 state = child_states.get(layer_collection.name)
553 if state:
554 layer_collection.exclude = state[0]
555 layer_collection.hide_viewport = state[1]
556 layer_collection.holdout = state[2]
557 layer_collection.indirect_only = state[3]
559 apply_to_children(laycol["parent"]["ptr"], restore_child_states)
562 def remove_collection(laycol, collection, context):
563 # get selected row
564 cm = context.scene.collection_manager
565 selected_row_name = cm.cm_list_collection[cm.cm_list_index].name
567 # delete collection
568 bpy.data.collections.remove(collection)
570 # update references
571 internals.expanded.discard(laycol["name"])
573 if internals.expand_history["target"] == laycol["name"]:
574 internals.expand_history["target"] = ""
576 if laycol["name"] in internals.expand_history["history"]:
577 internals.expand_history["history"].remove(laycol["name"])
579 if internals.qcd_slots.contains(name=laycol["name"]):
580 internals.qcd_slots.del_slot(name=laycol["name"])
582 if laycol["name"] in internals.qcd_slots.overrides:
583 internals.qcd_slots.overrides.remove(laycol["name"])
585 # reset history
586 for rto in internals.rto_history.values():
587 rto.clear()
589 # update tree view
590 update_property_group(context)
592 # update selected row
593 laycol = internals.layer_collections.get(selected_row_name, None)
594 if laycol:
595 cm.cm_list_index = laycol["row_index"]
597 elif len(cm.cm_list_collection) <= cm.cm_list_index:
598 cm.cm_list_index = len(cm.cm_list_collection) - 1
600 if cm.cm_list_index > -1:
601 name = cm.cm_list_collection[cm.cm_list_index].name
602 laycol = internals.layer_collections[name]
603 while not laycol["visible"]:
604 laycol = laycol["parent"]
606 cm.cm_list_index = laycol["row_index"]
609 def select_collection_objects(is_master_collection, collection_name, replace, nested, selection_state=None):
610 if bpy.context.mode != 'OBJECT':
611 return
613 if is_master_collection:
614 target_collection = bpy.context.view_layer.layer_collection.collection
616 else:
617 laycol = internals.layer_collections[collection_name]
618 target_collection = laycol["ptr"].collection
620 if replace:
621 bpy.ops.object.select_all(action='DESELECT')
623 if selection_state == None:
624 selection_state = get_move_selection().isdisjoint(target_collection.objects)
626 def select_objects(collection):
627 for obj in collection.objects:
628 try:
629 obj.select_set(selection_state)
630 except RuntimeError:
631 pass
633 select_objects(target_collection)
635 if nested:
636 apply_to_children(target_collection, select_objects)
638 def set_exclude_state(target_layer_collection, state):
639 # get current child exclusion state
640 child_exclusion = []
642 def get_child_exclusion(layer_collection):
643 child_exclusion.append([layer_collection, layer_collection.exclude])
645 apply_to_children(target_layer_collection, get_child_exclusion)
648 # set exclusion
649 target_layer_collection.exclude = state
652 # set correct state for all children
653 for laycol in child_exclusion:
654 laycol[0].exclude = laycol[1]