1 # SPDX-FileCopyrightText: 2011 Ryan Inch
3 # SPDX-License-Identifier: GPL-2.0-or-later
8 from . import internals
11 from .internals
import (
12 update_property_group
,
20 'EDIT_SURFACE': 'EDIT',
22 'EDIT_ARMATURE': 'EDIT',
23 'EDIT_METABALL': 'EDIT',
24 'EDIT_LATTICE': 'EDIT',
27 'PAINT_WEIGHT': 'WEIGHT_PAINT',
28 'PAINT_VERTEX': 'VERTEX_PAINT',
29 'PAINT_TEXTURE': 'TEXTURE_PAINT',
30 'PARTICLE': 'PARTICLE_EDIT',
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',
42 "select": "collection.hide_select",
43 "hide": "hide_viewport",
44 "disable": "collection.hide_viewport",
45 "render": "collection.hide_render",
47 "indirect": "indirect_only",
104 def get_rto(layer_collection
, rto
):
105 if rto
in ["exclude", "hide", "holdout", "indirect"]:
106 return getattr(layer_collection
, rto_path
[rto
])
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
)
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
]
129 for child_list
in child_lists
:
130 for child
in child_list
:
131 apply_function(child
, *args
, **kwargs
)
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
]
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
]
177 internals
.rto_history
[rto
][view_layer
]["target"] = self
.name
183 for item
in internals
.layer_collections
.values():
184 history
.append(get_rto(item
["ptr"], rto
))
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
)
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"]
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
)
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
)
233 def isolate_sel_objs_collections(view_layer
, rto
, caller
, *, use_active
=False):
234 selected_objects
= get_move_selection()
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"]
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:
256 # save history and isolate RTOs
257 for item
in internals
.layer_collections
.values():
258 history
.append(get_rto(item
["ptr"], rto
))
261 # check if any of the selected objects are in the collection
262 if not set(selected_objects
).isdisjoint(item
["ptr"].collection
.objects
):
265 if history
[-1] != rto_state
:
269 set_exclude_state(item
["ptr"], rto_state
)
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"]
285 return "Collection already isolated"
289 for x
, item
in enumerate(internals
.layer_collections
.values()):
290 set_rto(item
["ptr"], rto
, history
[x
])
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()
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
:
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
):
331 set_exclude_state(item
["ptr"], off
)
334 set_rto(item
["ptr"], rto
, off
)
338 for x
, item
in enumerate(internals
.layer_collections
.values()):
339 set_rto(item
["ptr"], rto
, history
[x
])
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"]
352 del internals
.rto_history
[rto
][view_layer
]
353 internals
.rto_history
[rto
+"_all"].pop(view_layer
, None)
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:
375 for item
in reversed(list(internals
.layer_collections
.values())):
376 if get_rto(item
["ptr"], rto
) == off
:
379 history
.append(get_rto(item
["ptr"], rto
))
381 set_rto(item
["ptr"], rto
, on
)
389 for x
, item
in enumerate(internals
.layer_collections
.values()):
390 set_rto(item
["ptr"], rto
, history
[x
])
393 del internals
.rto_history
[rto
+"_all"][view_layer
]
396 def invert_rtos(view_layer
, rto
):
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
])
407 for item
in internals
.layer_collections
.values():
408 set_rto(item
["ptr"], rto
, not get_rto(item
["ptr"], rto
))
411 internals
.rto_history
[rto
].pop(view_layer
, None)
414 def copy_rtos(view_layer
, rto
):
415 if not internals
.copy_buffer
["RTO"]:
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
)
428 for x
, laycol
in enumerate(internals
.layer_collections
.values()):
429 set_rto(laycol
["ptr"],
432 internals
.copy_buffer
["values"][x
]
437 internals
.rto_history
[rto
].pop(view_layer
, None)
438 del internals
.rto_history
[rto
+"_all"][view_layer
]
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"]:
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
)
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
)
469 for x
, laycol
in enumerate(internals
.layer_collections
.values()):
470 set_rto(laycol
["ptr"], internals
.swap_buffer
["A"]["RTO"],
472 internals
.swap_buffer
["A"]["RTO"]
474 internals
.swap_buffer
["B"]["values"][x
]
478 set_rto(laycol
["ptr"], internals
.swap_buffer
["B"]["RTO"],
480 internals
.swap_buffer
["B"]["RTO"]
482 internals
.swap_buffer
["A"]["values"][x
]
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)
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()
504 if internals
.copy_buffer
["RTO"] == rto
:
505 internals
.copy_buffer
["RTO"] = ""
506 internals
.copy_buffer
["values"].clear()
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
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
535 def restore_child_states(layer_collection
):
536 state
= child_states
.get(layer_collection
.name
)
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
):
549 cm
= context
.scene
.collection_manager
550 selected_row_name
= cm
.cm_list_collection
[cm
.cm_list_index
].name
553 bpy
.data
.collections
.remove(collection
)
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"])
571 for rto
in internals
.rto_history
.values():
575 update_property_group(context
)
577 # update selected row
578 laycol
= internals
.layer_collections
.get(selected_row_name
, None)
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':
598 if is_master_collection
:
599 target_collection
= bpy
.context
.view_layer
.layer_collection
.collection
602 laycol
= internals
.layer_collections
[collection_name
]
603 target_collection
= laycol
["ptr"].collection
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
:
614 obj
.select_set(selection_state
)
618 select_objects(target_collection
, selection_state
)
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
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
)
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]