1 # SPDX-License-Identifier: GPL-2.0-or-later
3 # Copyright 2011, Ryan Inch
7 from . import internals
10 from .internals
import (
11 update_property_group
,
19 'EDIT_SURFACE': 'EDIT',
21 'EDIT_ARMATURE': 'EDIT',
22 'EDIT_METABALL': 'EDIT',
23 'EDIT_LATTICE': 'EDIT',
26 'PAINT_WEIGHT': 'WEIGHT_PAINT',
27 'PAINT_VERTEX': 'VERTEX_PAINT',
28 'PAINT_TEXTURE': 'TEXTURE_PAINT',
29 'PARTICLE': 'PARTICLE_EDIT',
31 'PAINT_GPENCIL': 'PAINT_GPENCIL',
32 'EDIT_GPENCIL': 'EDIT_GPENCIL',
33 'SCULPT_GPENCIL': 'SCULPT_GPENCIL',
34 'WEIGHT_GPENCIL': 'WEIGHT_GPENCIL',
35 'VERTEX_GPENCIL': 'VERTEX_GPENCIL',
41 "select": "collection.hide_select",
42 "hide": "hide_viewport",
43 "disable": "collection.hide_viewport",
44 "render": "collection.hide_render",
46 "indirect": "indirect_only",
103 def get_rto(layer_collection
, rto
):
104 if rto
in ["exclude", "hide", "holdout", "indirect"]:
105 return getattr(layer_collection
, rto_path
[rto
])
108 collection
= getattr(layer_collection
, "collection")
109 return getattr(collection
, rto_path
[rto
].split(".")[1])
112 def set_rto(layer_collection
, rto
, value
):
113 if rto
in ["exclude", "hide", "holdout", "indirect"]:
114 setattr(layer_collection
, rto_path
[rto
], value
)
117 collection
= getattr(layer_collection
, "collection")
118 setattr(collection
, rto_path
[rto
].split(".")[1], value
)
121 def apply_to_children(parent
, apply_function
, *args
, **kwargs
):
122 # works for both Collections & LayerCollections
123 child_lists
= [parent
.children
]
128 for child_list
in child_lists
:
129 for child
in child_list
:
130 apply_function(child
, *args
, **kwargs
)
133 new_child_lists
.append(child
.children
)
135 child_lists
= new_child_lists
138 def isolate_rto(cls
, self
, view_layer
, rto
, *, children
=False):
139 off
= set_off_on
[rto
]["off"]
140 on
= set_off_on
[rto
]["on"]
142 laycol_ptr
= internals
.layer_collections
[self
.name
]["ptr"]
143 target
= internals
.rto_history
[rto
][view_layer
]["target"]
144 history
= internals
.rto_history
[rto
][view_layer
]["history"]
146 # get active collections
147 active_layer_collections
= [x
["ptr"] for x
in internals
.layer_collections
.values()
148 if get_rto(x
["ptr"], rto
) == on
]
150 # check if previous state should be restored
151 if cls
.isolated
and self
.name
== target
:
152 # restore previous state
153 for x
, item
in enumerate(internals
.layer_collections
.values()):
154 set_rto(item
["ptr"], rto
, history
[x
])
156 # reset target and history
157 del internals
.rto_history
[rto
][view_layer
]
161 # check if all RTOs should be activated
162 elif (len(active_layer_collections
) == 1 and
163 active_layer_collections
[0].name
== self
.name
):
164 # activate all collections
165 for item
in internals
.layer_collections
.values():
166 set_rto(item
["ptr"], rto
, on
)
168 # reset target and history
169 del internals
.rto_history
[rto
][view_layer
]
176 internals
.rto_history
[rto
][view_layer
]["target"] = self
.name
182 for item
in internals
.layer_collections
.values():
183 history
.append(get_rto(item
["ptr"], rto
))
188 def get_child_states(layer_collection
):
189 child_states
[layer_collection
.name
] = get_rto(layer_collection
, rto
)
191 apply_to_children(laycol_ptr
, get_child_states
)
194 for item
in internals
.layer_collections
.values():
195 if item
["name"] != laycol_ptr
.name
:
196 set_rto(item
["ptr"], rto
, off
)
198 set_rto(laycol_ptr
, rto
, on
)
200 if rto
not in ["exclude", "holdout", "indirect"]:
201 # activate all parents
202 laycol
= internals
.layer_collections
[self
.name
]
203 while laycol
["id"] != 0:
204 set_rto(laycol
["ptr"], rto
, on
)
205 laycol
= laycol
["parent"]
208 # restore child states
209 def restore_child_states(layer_collection
):
210 set_rto(layer_collection
, rto
, child_states
[layer_collection
.name
])
212 apply_to_children(laycol_ptr
, restore_child_states
)
216 # restore child states
217 def restore_child_states(layer_collection
):
218 set_rto(layer_collection
, rto
, child_states
[layer_collection
.name
])
220 apply_to_children(laycol_ptr
, restore_child_states
)
222 elif rto
== "exclude":
223 # deactivate all children
224 def deactivate_all_children(layer_collection
):
225 set_rto(layer_collection
, rto
, True)
227 apply_to_children(laycol_ptr
, deactivate_all_children
)
232 def isolate_sel_objs_collections(view_layer
, rto
, caller
, *, use_active
=False):
233 selected_objects
= get_move_selection()
236 selected_objects
.add(get_move_active(always
=True))
238 if not selected_objects
:
239 return "No selected objects"
241 off
= set_off_on
[rto
]["off"]
242 on
= set_off_on
[rto
]["on"]
245 history
= internals
.rto_history
[rto
+"_all"][view_layer
]
247 elif caller
== "QCD":
248 history
= internals
.qcd_history
[view_layer
]
251 # if not isolated, isolate collections of selected objects
252 if len(history
) == 0:
255 # save history and isolate RTOs
256 for item
in internals
.layer_collections
.values():
257 history
.append(get_rto(item
["ptr"], rto
))
260 # check if any of the selected objects are in the collection
261 if not set(selected_objects
).isdisjoint(item
["ptr"].collection
.objects
):
264 if history
[-1] != rto_state
:
268 set_exclude_state(item
["ptr"], rto_state
)
271 set_rto(item
["ptr"], rto
, rto_state
)
273 # activate all parents if needed
274 if rto_state
== on
and rto
not in ["holdout", "indirect"]:
275 laycol
= item
["parent"]
276 while laycol
["id"] != 0:
277 set_rto(laycol
["ptr"], rto
, on
)
278 laycol
= laycol
["parent"]
284 return "Collection already isolated"
288 for x
, item
in enumerate(internals
.layer_collections
.values()):
289 set_rto(item
["ptr"], rto
, history
[x
])
293 del internals
.rto_history
[rto
+"_all"][view_layer
]
295 elif caller
== "QCD":
296 del internals
.qcd_history
[view_layer
]
299 def disable_sel_objs_collections(view_layer
, rto
, caller
):
300 off
= set_off_on
[rto
]["off"]
301 on
= set_off_on
[rto
]["on"]
302 selected_objects
= get_move_selection()
305 history
= internals
.rto_history
[rto
+"_all"][view_layer
]
307 elif caller
== "QCD":
308 history
= internals
.qcd_history
[view_layer
]
311 if not selected_objects
and not history
:
314 del internals
.rto_history
[rto
+"_all"][view_layer
]
316 elif caller
== "QCD":
317 del internals
.qcd_history
[view_layer
]
319 return "No selected objects"
321 # if not disabled, disable collections of selected objects
322 if len(history
) == 0:
323 # save history and disable RTOs
324 for item
in internals
.layer_collections
.values():
325 history
.append(get_rto(item
["ptr"], rto
))
327 # check if any of the selected objects are in the collection
328 if not set(selected_objects
).isdisjoint(item
["ptr"].collection
.objects
):
330 set_exclude_state(item
["ptr"], off
)
333 set_rto(item
["ptr"], rto
, off
)
337 for x
, item
in enumerate(internals
.layer_collections
.values()):
338 set_rto(item
["ptr"], rto
, history
[x
])
342 del internals
.rto_history
[rto
+"_all"][view_layer
]
344 elif caller
== "QCD":
345 del internals
.qcd_history
[view_layer
]
348 def toggle_children(self
, view_layer
, rto
):
349 laycol_ptr
= internals
.layer_collections
[self
.name
]["ptr"]
351 del internals
.rto_history
[rto
][view_layer
]
352 internals
.rto_history
[rto
+"_all"].pop(view_layer
, None)
355 state
= not get_rto(laycol_ptr
, rto
)
356 set_rto(laycol_ptr
, rto
, state
)
358 def set_state(layer_collection
):
359 set_rto(layer_collection
, rto
, state
)
361 apply_to_children(laycol_ptr
, set_state
)
364 def activate_all_rtos(view_layer
, rto
):
365 off
= set_off_on
[rto
]["off"]
366 on
= set_off_on
[rto
]["on"]
368 history
= internals
.rto_history
[rto
+"_all"][view_layer
]
370 # if not activated, activate all
371 if len(history
) == 0:
374 for item
in reversed(list(internals
.layer_collections
.values())):
375 if get_rto(item
["ptr"], rto
) == off
:
378 history
.append(get_rto(item
["ptr"], rto
))
380 set_rto(item
["ptr"], rto
, on
)
388 for x
, item
in enumerate(internals
.layer_collections
.values()):
389 set_rto(item
["ptr"], rto
, history
[x
])
392 del internals
.rto_history
[rto
+"_all"][view_layer
]
395 def invert_rtos(view_layer
, rto
):
399 for item
in internals
.layer_collections
.values():
400 orig_values
.append(get_rto(item
["ptr"], rto
))
402 for x
, item
in enumerate(internals
.layer_collections
.values()):
403 set_rto(item
["ptr"], rto
, not orig_values
[x
])
406 for item
in internals
.layer_collections
.values():
407 set_rto(item
["ptr"], rto
, not get_rto(item
["ptr"], rto
))
410 internals
.rto_history
[rto
].pop(view_layer
, None)
413 def copy_rtos(view_layer
, rto
):
414 if not internals
.copy_buffer
["RTO"]:
416 internals
.copy_buffer
["RTO"] = rto
417 for laycol
in internals
.layer_collections
.values():
418 internals
.copy_buffer
["values"].append(get_off_on
[
419 get_rto(laycol
["ptr"], rto
)
427 for x
, laycol
in enumerate(internals
.layer_collections
.values()):
428 set_rto(laycol
["ptr"],
431 internals
.copy_buffer
["values"][x
]
436 internals
.rto_history
[rto
].pop(view_layer
, None)
437 del internals
.rto_history
[rto
+"_all"][view_layer
]
440 internals
.copy_buffer
["RTO"] = ""
441 internals
.copy_buffer
["values"].clear()
444 def swap_rtos(view_layer
, rto
):
445 if not internals
.swap_buffer
["A"]["values"]:
447 internals
.swap_buffer
["A"]["RTO"] = rto
448 for laycol
in internals
.layer_collections
.values():
449 internals
.swap_buffer
["A"]["values"].append(get_off_on
[
450 get_rto(laycol
["ptr"], rto
)
458 internals
.swap_buffer
["B"]["RTO"] = rto
459 for laycol
in internals
.layer_collections
.values():
460 internals
.swap_buffer
["B"]["values"].append(get_off_on
[
461 get_rto(laycol
["ptr"], rto
)
468 for x
, laycol
in enumerate(internals
.layer_collections
.values()):
469 set_rto(laycol
["ptr"], internals
.swap_buffer
["A"]["RTO"],
471 internals
.swap_buffer
["A"]["RTO"]
473 internals
.swap_buffer
["B"]["values"][x
]
477 set_rto(laycol
["ptr"], internals
.swap_buffer
["B"]["RTO"],
479 internals
.swap_buffer
["B"]["RTO"]
481 internals
.swap_buffer
["A"]["values"][x
]
487 swap_a
= internals
.swap_buffer
["A"]["RTO"]
488 swap_b
= internals
.swap_buffer
["B"]["RTO"]
490 internals
.rto_history
[swap_a
].pop(view_layer
, None)
491 internals
.rto_history
[swap_a
+"_all"].pop(view_layer
, None)
492 internals
.rto_history
[swap_b
].pop(view_layer
, None)
493 internals
.rto_history
[swap_b
+"_all"].pop(view_layer
, None)
496 internals
.swap_buffer
["A"]["RTO"] = ""
497 internals
.swap_buffer
["A"]["values"].clear()
498 internals
.swap_buffer
["B"]["RTO"] = ""
499 internals
.swap_buffer
["B"]["values"].clear()
503 if internals
.copy_buffer
["RTO"] == rto
:
504 internals
.copy_buffer
["RTO"] = ""
505 internals
.copy_buffer
["values"].clear()
509 if internals
.swap_buffer
["A"]["RTO"] == rto
:
510 internals
.swap_buffer
["A"]["RTO"] = ""
511 internals
.swap_buffer
["A"]["values"].clear()
512 internals
.swap_buffer
["B"]["RTO"] = ""
513 internals
.swap_buffer
["B"]["values"].clear()
516 def link_child_collections_to_parent(laycol
, collection
, parent_collection
):
517 # store view layer RTOs for all children of the to be deleted collection
519 def get_child_states(layer_collection
):
520 child_states
[layer_collection
.name
] = (layer_collection
.exclude
,
521 layer_collection
.hide_viewport
,
522 layer_collection
.holdout
,
523 layer_collection
.indirect_only
)
525 apply_to_children(laycol
["ptr"], get_child_states
)
527 # link any subcollections of the to be deleted collection to it's parent
528 for subcollection
in collection
.children
:
529 if not subcollection
.name
in parent_collection
.children
:
530 parent_collection
.children
.link(subcollection
)
532 # apply the stored view layer RTOs to the newly linked collections and their
534 def restore_child_states(layer_collection
):
535 state
= child_states
.get(layer_collection
.name
)
538 layer_collection
.exclude
= state
[0]
539 layer_collection
.hide_viewport
= state
[1]
540 layer_collection
.holdout
= state
[2]
541 layer_collection
.indirect_only
= state
[3]
543 apply_to_children(laycol
["parent"]["ptr"], restore_child_states
)
546 def remove_collection(laycol
, collection
, context
):
548 cm
= context
.scene
.collection_manager
549 selected_row_name
= cm
.cm_list_collection
[cm
.cm_list_index
].name
552 bpy
.data
.collections
.remove(collection
)
555 internals
.expanded
.discard(laycol
["name"])
557 if internals
.expand_history
["target"] == laycol
["name"]:
558 internals
.expand_history
["target"] = ""
560 if laycol
["name"] in internals
.expand_history
["history"]:
561 internals
.expand_history
["history"].remove(laycol
["name"])
563 if internals
.qcd_slots
.contains(name
=laycol
["name"]):
564 internals
.qcd_slots
.del_slot(name
=laycol
["name"])
566 if laycol
["name"] in internals
.qcd_slots
.overrides
:
567 internals
.qcd_slots
.overrides
.remove(laycol
["name"])
570 for rto
in internals
.rto_history
.values():
574 update_property_group(context
)
576 # update selected row
577 laycol
= internals
.layer_collections
.get(selected_row_name
, None)
579 cm
.cm_list_index
= laycol
["row_index"]
581 elif len(cm
.cm_list_collection
) <= cm
.cm_list_index
:
582 cm
.cm_list_index
= len(cm
.cm_list_collection
) - 1
584 if cm
.cm_list_index
> -1:
585 name
= cm
.cm_list_collection
[cm
.cm_list_index
].name
586 laycol
= internals
.layer_collections
[name
]
587 while not laycol
["visible"]:
588 laycol
= laycol
["parent"]
590 cm
.cm_list_index
= laycol
["row_index"]
593 def select_collection_objects(is_master_collection
, collection_name
, replace
, nested
, selection_state
=None):
594 if bpy
.context
.mode
!= 'OBJECT':
597 if is_master_collection
:
598 target_collection
= bpy
.context
.view_layer
.layer_collection
.collection
601 laycol
= internals
.layer_collections
[collection_name
]
602 target_collection
= laycol
["ptr"].collection
605 bpy
.ops
.object.select_all(action
='DESELECT')
607 def select_objects(collection
, selection_state
):
608 if selection_state
== None:
609 selection_state
= get_move_selection().isdisjoint(collection
.objects
)
611 for obj
in collection
.objects
:
613 obj
.select_set(selection_state
)
617 select_objects(target_collection
, selection_state
)
620 apply_to_children(target_collection
, select_objects
, selection_state
)
622 def set_exclude_state(target_layer_collection
, state
):
623 # get current child exclusion state
626 def get_child_exclusion(layer_collection
):
627 child_exclusion
.append([layer_collection
, layer_collection
.exclude
])
629 apply_to_children(target_layer_collection
, get_child_exclusion
)
633 target_layer_collection
.exclude
= state
636 # set correct state for all children
637 for laycol
in child_exclusion
:
638 laycol
[0].exclude
= laycol
[1]