Merge branch 'blender-v4.0-release'
[blender-addons.git] / object_collection_manager / operator_utils.py
blobc1c70132a200a472350f8075f6dec31cccab53f9
1 # SPDX-FileCopyrightText: 2011 Ryan Inch
3 # SPDX-License-Identifier: GPL-2.0-or-later
5 import bpy
7 # For VARS
8 from . import internals
10 # For FUNCTIONS
11 from .internals import (
12 update_property_group,
13 get_move_selection,
14 get_move_active,
17 mode_converter = {
18 'EDIT_MESH': 'EDIT',
19 'EDIT_CURVE': 'EDIT',
20 'EDIT_SURFACE': 'EDIT',
21 'EDIT_TEXT': 'EDIT',
22 'EDIT_ARMATURE': 'EDIT',
23 'EDIT_METABALL': 'EDIT',
24 'EDIT_LATTICE': 'EDIT',
25 'POSE': 'POSE',
26 'SCULPT': 'SCULPT',
27 'PAINT_WEIGHT': 'WEIGHT_PAINT',
28 'PAINT_VERTEX': 'VERTEX_PAINT',
29 'PAINT_TEXTURE': 'TEXTURE_PAINT',
30 'PARTICLE': 'PARTICLE_EDIT',
31 'OBJECT': 'OBJECT',
32 'PAINT_GPENCIL': 'PAINT_GPENCIL',
33 'EDIT_GPENCIL': 'EDIT_GPENCIL',
34 'SCULPT_GPENCIL': 'SCULPT_GPENCIL',
35 'WEIGHT_GPENCIL': 'WEIGHT_GPENCIL',
36 'VERTEX_GPENCIL': 'VERTEX_GPENCIL',
40 rto_path = {
41 "exclude": "exclude",
42 "select": "collection.hide_select",
43 "hide": "hide_viewport",
44 "disable": "collection.hide_viewport",
45 "render": "collection.hide_render",
46 "holdout": "holdout",
47 "indirect": "indirect_only",
50 set_off_on = {
51 "exclude": {
52 "off": True,
53 "on": False
55 "select": {
56 "off": True,
57 "on": False
59 "hide": {
60 "off": True,
61 "on": False
63 "disable": {
64 "off": True,
65 "on": False
67 "render": {
68 "off": True,
69 "on": False
71 "holdout": {
72 "off": False,
73 "on": True
75 "indirect": {
76 "off": False,
77 "on": True
81 get_off_on = {
82 False: {
83 "exclude": "on",
84 "select": "on",
85 "hide": "on",
86 "disable": "on",
87 "render": "on",
88 "holdout": "off",
89 "indirect": "off",
92 True: {
93 "exclude": "off",
94 "select": "off",
95 "hide": "off",
96 "disable": "off",
97 "render": "off",
98 "holdout": "on",
99 "indirect": "on",
104 def get_rto(layer_collection, rto):
105 if rto in ["exclude", "hide", "holdout", "indirect"]:
106 return getattr(layer_collection, rto_path[rto])
108 else:
109 collection = getattr(layer_collection, "collection")
110 return getattr(collection, rto_path[rto].split(".")[1])
113 def set_rto(layer_collection, rto, value):
114 if rto in ["exclude", "hide", "holdout", "indirect"]:
115 setattr(layer_collection, rto_path[rto], value)
117 else:
118 collection = getattr(layer_collection, "collection")
119 setattr(collection, rto_path[rto].split(".")[1], value)
122 def apply_to_children(parent, apply_function, *args, **kwargs):
123 # works for both Collections & LayerCollections
124 child_lists = [parent.children]
126 while child_lists:
127 new_child_lists = []
129 for child_list in child_lists:
130 for child in child_list:
131 apply_function(child, *args, **kwargs)
133 if child.children:
134 new_child_lists.append(child.children)
136 child_lists = new_child_lists
139 def isolate_rto(cls, self, view_layer, rto, *, children=False):
140 off = set_off_on[rto]["off"]
141 on = set_off_on[rto]["on"]
143 laycol_ptr = internals.layer_collections[self.name]["ptr"]
144 target = internals.rto_history[rto][view_layer]["target"]
145 history = internals.rto_history[rto][view_layer]["history"]
147 # get active collections
148 active_layer_collections = [x["ptr"] for x in internals.layer_collections.values()
149 if get_rto(x["ptr"], rto) == on]
151 # check if previous state should be restored
152 if cls.isolated and self.name == target:
153 # restore previous state
154 for x, item in enumerate(internals.layer_collections.values()):
155 set_rto(item["ptr"], rto, history[x])
157 # reset target and history
158 del internals.rto_history[rto][view_layer]
160 cls.isolated = False
162 # check if all RTOs should be activated
163 elif (len(active_layer_collections) == 1 and
164 active_layer_collections[0].name == self.name):
165 # activate all collections
166 for item in internals.layer_collections.values():
167 set_rto(item["ptr"], rto, on)
169 # reset target and history
170 del internals.rto_history[rto][view_layer]
172 cls.isolated = False
174 else:
175 # isolate collection
177 internals.rto_history[rto][view_layer]["target"] = self.name
179 # reset history
180 history.clear()
182 # save state
183 for item in internals.layer_collections.values():
184 history.append(get_rto(item["ptr"], rto))
186 child_states = {}
187 if children:
188 # get child states
189 def get_child_states(layer_collection):
190 child_states[layer_collection.name] = get_rto(layer_collection, rto)
192 apply_to_children(laycol_ptr, get_child_states)
194 # isolate collection
195 for item in internals.layer_collections.values():
196 if item["name"] != laycol_ptr.name:
197 set_rto(item["ptr"], rto, off)
199 set_rto(laycol_ptr, rto, on)
201 if rto not in ["exclude", "holdout", "indirect"]:
202 # activate all parents
203 laycol = internals.layer_collections[self.name]
204 while laycol["id"] != 0:
205 set_rto(laycol["ptr"], rto, on)
206 laycol = laycol["parent"]
208 if children:
209 # restore child states
210 def restore_child_states(layer_collection):
211 set_rto(layer_collection, rto, child_states[layer_collection.name])
213 apply_to_children(laycol_ptr, restore_child_states)
215 else:
216 if children:
217 # restore child states
218 def restore_child_states(layer_collection):
219 set_rto(layer_collection, rto, child_states[layer_collection.name])
221 apply_to_children(laycol_ptr, restore_child_states)
223 elif rto == "exclude":
224 # deactivate all children
225 def deactivate_all_children(layer_collection):
226 set_rto(layer_collection, rto, True)
228 apply_to_children(laycol_ptr, deactivate_all_children)
230 cls.isolated = True
233 def isolate_sel_objs_collections(view_layer, rto, caller, *, use_active=False):
234 selected_objects = get_move_selection()
236 if use_active:
237 selected_objects.add(get_move_active(always=True))
239 if not selected_objects:
240 return "No selected objects"
242 off = set_off_on[rto]["off"]
243 on = set_off_on[rto]["on"]
245 if caller == "CM":
246 history = internals.rto_history[rto+"_all"][view_layer]
248 elif caller == "QCD":
249 history = internals.qcd_history[view_layer]
252 # if not isolated, isolate collections of selected objects
253 if len(history) == 0:
254 keep_history = False
256 # save history and isolate RTOs
257 for item in internals.layer_collections.values():
258 history.append(get_rto(item["ptr"], rto))
259 rto_state = off
261 # check if any of the selected objects are in the collection
262 if not set(selected_objects).isdisjoint(item["ptr"].collection.objects):
263 rto_state = on
265 if history[-1] != rto_state:
266 keep_history = True
268 if rto == "exclude":
269 set_exclude_state(item["ptr"], rto_state)
271 else:
272 set_rto(item["ptr"], rto, rto_state)
274 # activate all parents if needed
275 if rto_state == on and rto not in ["holdout", "indirect"]:
276 laycol = item["parent"]
277 while laycol["id"] != 0:
278 set_rto(laycol["ptr"], rto, on)
279 laycol = laycol["parent"]
282 if not keep_history:
283 history.clear()
285 return "Collection already isolated"
288 else:
289 for x, item in enumerate(internals.layer_collections.values()):
290 set_rto(item["ptr"], rto, history[x])
292 # clear history
293 if caller == "CM":
294 del internals.rto_history[rto+"_all"][view_layer]
296 elif caller == "QCD":
297 del internals.qcd_history[view_layer]
300 def disable_sel_objs_collections(view_layer, rto, caller):
301 off = set_off_on[rto]["off"]
302 on = set_off_on[rto]["on"]
303 selected_objects = get_move_selection()
305 if caller == "CM":
306 history = internals.rto_history[rto+"_all"][view_layer]
308 elif caller == "QCD":
309 history = internals.qcd_history[view_layer]
312 if not selected_objects and not history:
313 # clear history
314 if caller == "CM":
315 del internals.rto_history[rto+"_all"][view_layer]
317 elif caller == "QCD":
318 del internals.qcd_history[view_layer]
320 return "No selected objects"
322 # if not disabled, disable collections of selected objects
323 if len(history) == 0:
324 # save history and disable RTOs
325 for item in internals.layer_collections.values():
326 history.append(get_rto(item["ptr"], rto))
328 # check if any of the selected objects are in the collection
329 if not set(selected_objects).isdisjoint(item["ptr"].collection.objects):
330 if rto == "exclude":
331 set_exclude_state(item["ptr"], off)
333 else:
334 set_rto(item["ptr"], rto, off)
337 else:
338 for x, item in enumerate(internals.layer_collections.values()):
339 set_rto(item["ptr"], rto, history[x])
341 # clear history
342 if caller == "CM":
343 del internals.rto_history[rto+"_all"][view_layer]
345 elif caller == "QCD":
346 del internals.qcd_history[view_layer]
349 def toggle_children(self, view_layer, rto):
350 laycol_ptr = internals.layer_collections[self.name]["ptr"]
351 # clear rto history
352 del internals.rto_history[rto][view_layer]
353 internals.rto_history[rto+"_all"].pop(view_layer, None)
355 # toggle rto state
356 state = not get_rto(laycol_ptr, rto)
357 set_rto(laycol_ptr, rto, state)
359 def set_state(layer_collection):
360 set_rto(layer_collection, rto, state)
362 apply_to_children(laycol_ptr, set_state)
365 def activate_all_rtos(view_layer, rto):
366 off = set_off_on[rto]["off"]
367 on = set_off_on[rto]["on"]
369 history = internals.rto_history[rto+"_all"][view_layer]
371 # if not activated, activate all
372 if len(history) == 0:
373 keep_history = False
375 for item in reversed(list(internals.layer_collections.values())):
376 if get_rto(item["ptr"], rto) == off:
377 keep_history = True
379 history.append(get_rto(item["ptr"], rto))
381 set_rto(item["ptr"], rto, on)
383 if not keep_history:
384 history.clear()
386 history.reverse()
388 else:
389 for x, item in enumerate(internals.layer_collections.values()):
390 set_rto(item["ptr"], rto, history[x])
392 # clear rto history
393 del internals.rto_history[rto+"_all"][view_layer]
396 def invert_rtos(view_layer, rto):
397 if rto == "exclude":
398 orig_values = []
400 for item in internals.layer_collections.values():
401 orig_values.append(get_rto(item["ptr"], rto))
403 for x, item in enumerate(internals.layer_collections.values()):
404 set_rto(item["ptr"], rto, not orig_values[x])
406 else:
407 for item in internals.layer_collections.values():
408 set_rto(item["ptr"], rto, not get_rto(item["ptr"], rto))
410 # clear rto history
411 internals.rto_history[rto].pop(view_layer, None)
414 def copy_rtos(view_layer, rto):
415 if not internals.copy_buffer["RTO"]:
416 # copy
417 internals.copy_buffer["RTO"] = rto
418 for laycol in internals.layer_collections.values():
419 internals.copy_buffer["values"].append(get_off_on[
420 get_rto(laycol["ptr"], rto)
426 else:
427 # paste
428 for x, laycol in enumerate(internals.layer_collections.values()):
429 set_rto(laycol["ptr"],
430 rto,
431 set_off_on[rto][
432 internals.copy_buffer["values"][x]
436 # clear rto history
437 internals.rto_history[rto].pop(view_layer, None)
438 del internals.rto_history[rto+"_all"][view_layer]
440 # clear copy buffer
441 internals.copy_buffer["RTO"] = ""
442 internals.copy_buffer["values"].clear()
445 def swap_rtos(view_layer, rto):
446 if not internals.swap_buffer["A"]["values"]:
447 # get A
448 internals.swap_buffer["A"]["RTO"] = rto
449 for laycol in internals.layer_collections.values():
450 internals.swap_buffer["A"]["values"].append(get_off_on[
451 get_rto(laycol["ptr"], rto)
457 else:
458 # get B
459 internals.swap_buffer["B"]["RTO"] = rto
460 for laycol in internals.layer_collections.values():
461 internals.swap_buffer["B"]["values"].append(get_off_on[
462 get_rto(laycol["ptr"], rto)
468 # swap A with B
469 for x, laycol in enumerate(internals.layer_collections.values()):
470 set_rto(laycol["ptr"], internals.swap_buffer["A"]["RTO"],
471 set_off_on[
472 internals.swap_buffer["A"]["RTO"]
474 internals.swap_buffer["B"]["values"][x]
478 set_rto(laycol["ptr"], internals.swap_buffer["B"]["RTO"],
479 set_off_on[
480 internals.swap_buffer["B"]["RTO"]
482 internals.swap_buffer["A"]["values"][x]
487 # clear rto history
488 swap_a = internals.swap_buffer["A"]["RTO"]
489 swap_b = internals.swap_buffer["B"]["RTO"]
491 internals.rto_history[swap_a].pop(view_layer, None)
492 internals.rto_history[swap_a+"_all"].pop(view_layer, None)
493 internals.rto_history[swap_b].pop(view_layer, None)
494 internals.rto_history[swap_b+"_all"].pop(view_layer, None)
496 # clear swap buffer
497 internals.swap_buffer["A"]["RTO"] = ""
498 internals.swap_buffer["A"]["values"].clear()
499 internals.swap_buffer["B"]["RTO"] = ""
500 internals.swap_buffer["B"]["values"].clear()
503 def clear_copy(rto):
504 if internals.copy_buffer["RTO"] == rto:
505 internals.copy_buffer["RTO"] = ""
506 internals.copy_buffer["values"].clear()
509 def clear_swap(rto):
510 if internals.swap_buffer["A"]["RTO"] == rto:
511 internals.swap_buffer["A"]["RTO"] = ""
512 internals.swap_buffer["A"]["values"].clear()
513 internals.swap_buffer["B"]["RTO"] = ""
514 internals.swap_buffer["B"]["values"].clear()
517 def link_child_collections_to_parent(laycol, collection, parent_collection):
518 # store view layer RTOs for all children of the to be deleted collection
519 child_states = {}
520 def get_child_states(layer_collection):
521 child_states[layer_collection.name] = (layer_collection.exclude,
522 layer_collection.hide_viewport,
523 layer_collection.holdout,
524 layer_collection.indirect_only)
526 apply_to_children(laycol["ptr"], get_child_states)
528 # link any subcollections of the to be deleted collection to it's parent
529 for subcollection in collection.children:
530 if not subcollection.name in parent_collection.children:
531 parent_collection.children.link(subcollection)
533 # apply the stored view layer RTOs to the newly linked collections and their
534 # children
535 def restore_child_states(layer_collection):
536 state = child_states.get(layer_collection.name)
538 if state:
539 layer_collection.exclude = state[0]
540 layer_collection.hide_viewport = state[1]
541 layer_collection.holdout = state[2]
542 layer_collection.indirect_only = state[3]
544 apply_to_children(laycol["parent"]["ptr"], restore_child_states)
547 def remove_collection(laycol, collection, context):
548 # get selected row
549 cm = context.scene.collection_manager
550 selected_row_name = cm.cm_list_collection[cm.cm_list_index].name
552 # delete collection
553 bpy.data.collections.remove(collection)
555 # update references
556 internals.expanded.discard(laycol["name"])
558 if internals.expand_history["target"] == laycol["name"]:
559 internals.expand_history["target"] = ""
561 if laycol["name"] in internals.expand_history["history"]:
562 internals.expand_history["history"].remove(laycol["name"])
564 if internals.qcd_slots.contains(name=laycol["name"]):
565 internals.qcd_slots.del_slot(name=laycol["name"])
567 if laycol["name"] in internals.qcd_slots.overrides:
568 internals.qcd_slots.overrides.remove(laycol["name"])
570 # reset history
571 for rto in internals.rto_history.values():
572 rto.clear()
574 # update tree view
575 update_property_group(context)
577 # update selected row
578 laycol = internals.layer_collections.get(selected_row_name, None)
579 if laycol:
580 cm.cm_list_index = laycol["row_index"]
582 elif len(cm.cm_list_collection) <= cm.cm_list_index:
583 cm.cm_list_index = len(cm.cm_list_collection) - 1
585 if cm.cm_list_index > -1:
586 name = cm.cm_list_collection[cm.cm_list_index].name
587 laycol = internals.layer_collections[name]
588 while not laycol["visible"]:
589 laycol = laycol["parent"]
591 cm.cm_list_index = laycol["row_index"]
594 def select_collection_objects(is_master_collection, collection_name, replace, nested, selection_state=None):
595 if bpy.context.mode != 'OBJECT':
596 return
598 if is_master_collection:
599 target_collection = bpy.context.view_layer.layer_collection.collection
601 else:
602 laycol = internals.layer_collections[collection_name]
603 target_collection = laycol["ptr"].collection
605 if replace:
606 bpy.ops.object.select_all(action='DESELECT')
608 if selection_state == None:
609 selection_state = get_move_selection().isdisjoint(target_collection.objects)
611 def select_objects(collection, selection_state):
612 for obj in collection.objects:
613 try:
614 obj.select_set(selection_state)
615 except RuntimeError:
616 pass
618 select_objects(target_collection, selection_state)
620 if nested:
621 apply_to_children(target_collection, select_objects, selection_state)
623 def set_exclude_state(target_layer_collection, state):
624 # get current child exclusion state
625 child_exclusion = []
627 def get_child_exclusion(layer_collection):
628 child_exclusion.append([layer_collection, layer_collection.exclude])
630 apply_to_children(target_layer_collection, get_child_exclusion)
633 # set exclusion
634 target_layer_collection.exclude = state
637 # set correct state for all children
638 for laycol in child_exclusion:
639 laycol[0].exclude = laycol[1]