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
21 from . import persistent_data
25 from bpy
.types
import (
30 from bpy
.props
import (
35 move_triggered
= False
39 layer_collections
= {}
42 qcd_collection_state
= {}
75 "exclude_history": {},
78 "disable_history": {},
80 "holdout_history": {},
81 "indirect_history": {},
83 "exclude_all_history": [],
84 "select_all_history": [],
85 "hide_all_history": [],
86 "disable_all_history": [],
87 "render_all_history": [],
88 "holdout_all_history": [],
89 "indirect_all_history": [],
119 self
._slots
= persistent_data
.slots
120 self
.overrides
= persistent_data
.overrides
123 return self
._slots
.items().__iter
__()
126 return self
._slots
.__repr
__()
128 def contains(self
, *, idx
=None, name
=None):
130 return idx
in self
._slots
.keys()
133 return name
in self
._slots
.values()
137 def object_in_slots(self
, obj
):
138 for collection
in obj
.users_collection
:
139 if self
.contains(name
=collection
.name
):
144 def get_data_for_blend(self
):
145 return f
"{self._slots.__repr__()}\n{self.overrides.__repr__()}"
147 def load_blend_data(self
, data
):
148 decoupled_data
= data
.split("\n")
149 blend_slots
= eval(decoupled_data
[0])
150 blend_overrides
= eval(decoupled_data
[1])
153 self
.overrides
.clear()
155 for key
, value
in blend_slots
.items():
156 self
._slots
[key
] = value
158 for key
in blend_overrides
:
159 self
.overrides
.add(key
)
162 return len(self
._slots
)
164 def get_idx(self
, name
, r_value
=None):
165 for idx
, slot_name
in self
._slots
.items():
166 if slot_name
== name
:
171 def get_name(self
, idx
, r_value
=None):
172 if idx
in self
._slots
:
173 return self
._slots
[idx
]
177 def add_slot(self
, idx
, name
):
178 self
._slots
[idx
] = name
180 if name
in self
.overrides
:
181 self
.overrides
.remove(name
)
183 def update_slot(self
, idx
, name
):
184 self
.add_slot(idx
, name
)
186 def del_slot(self
, *, idx
=None, name
=None):
192 slot_idx
= self
.get_idx(name
)
193 del self
._slots
[slot_idx
]
198 def add_override(self
, name
):
199 qcd_slots
.del_slot(name
=name
)
200 qcd_slots
.overrides
.add(name
)
202 def clear_slots(self
):
205 def update_qcd(self
):
206 for idx
, name
in list(self
._slots
.items()):
207 if not layer_collections
.get(name
, None):
208 qcd_slots
.del_slot(name
=name
)
210 def auto_numerate(self
):
211 if self
.length() < 20:
212 laycol
= bpy
.context
.view_layer
.layer_collection
214 laycol_iter_list
= list(laycol
.children
)
215 while laycol_iter_list
:
216 layer_collection
= laycol_iter_list
.pop(0)
217 laycol_iter_list
.extend(list(layer_collection
.children
))
219 if layer_collection
.name
in qcd_slots
.overrides
:
223 if (not self
.contains(idx
=str(x
+1)) and
224 not self
.contains(name
=layer_collection
.name
)):
225 self
.add_slot(str(x
+1), layer_collection
.name
)
228 if self
.length() > 20:
231 def renumerate(self
, *, beginning
=False, depth_first
=False, constrain
=False):
234 self
.overrides
.clear()
236 starting_laycol_name
= self
.get_name("1")
238 if not starting_laycol_name
:
239 laycol
= bpy
.context
.view_layer
.layer_collection
240 starting_laycol_name
= laycol
.children
[0].name
243 self
.overrides
.clear()
246 parent
= layer_collections
[starting_laycol_name
]["parent"]
249 for laycol
in layer_collections
.values():
250 if self
.length() == 0 and starting_laycol_name
!= laycol
["name"]:
255 if laycol
["parent"]["name"] == parent
["name"]:
258 self
.add_slot(f
"{x}", laycol
["name"])
262 if self
.length() > 20:
266 laycol
= layer_collections
[starting_laycol_name
]["parent"]["ptr"]
268 laycol_iter_list
= []
269 for laycol
in laycol
.children
:
270 if laycol
.name
== starting_laycol_name
:
271 laycol_iter_list
.append(laycol
)
273 elif not constrain
and laycol_iter_list
:
274 laycol_iter_list
.append(laycol
)
277 while laycol_iter_list
:
278 layer_collection
= laycol_iter_list
.pop(0)
280 self
.add_slot(f
"{x}", layer_collection
.name
)
282 laycol_iter_list
.extend(list(layer_collection
.children
))
286 if self
.length() > 20:
290 for laycol
in layer_collections
.values():
291 if not self
.contains(name
=laycol
["name"]):
292 self
.overrides
.add(laycol
["name"])
294 qcd_slots
= QCDSlots()
297 def update_col_name(self
, context
):
298 global layer_collections
301 global expand_history
303 if self
.name
!= self
.last_name
:
305 self
.name
= self
.last_name
308 # if statement prevents update on list creation
309 if self
.last_name
!= '':
310 view_layer_name
= context
.view_layer
.name
312 # update collection name
313 layer_collections
[self
.last_name
]["ptr"].collection
.name
= self
.name
316 orig_expanded
= {x
for x
in expanded
}
318 if self
.last_name
in orig_expanded
:
319 expanded
.remove(self
.last_name
)
320 expanded
.add(self
.name
)
323 idx
= qcd_slots
.get_idx(self
.last_name
)
325 qcd_slots
.update_slot(idx
, self
.name
)
327 # update qcd_overrides
328 if self
.last_name
in qcd_slots
.overrides
:
329 qcd_slots
.overrides
.remove(self
.last_name
)
330 qcd_slots
.overrides
.add(self
.name
)
344 rto
: rto_history
[rto
][view_layer_name
]["target"]
346 if rto_history
[rto
].get(view_layer_name
)
350 history
= rto_history
[rto
].get(view_layer_name
)
352 if history
and orig_targets
[rto
] == self
.last_name
:
353 history
["target"] = self
.name
355 # update expand history
356 orig_expand_target
= expand_history
["target"]
357 orig_expand_history
= [x
for x
in expand_history
["history"]]
359 if orig_expand_target
== self
.last_name
:
360 expand_history
["target"] = self
.name
362 for x
, name
in enumerate(orig_expand_history
):
363 if name
== self
.last_name
:
364 expand_history
["history"][x
] = self
.name
366 # update names in expanded, qcd slots, and rto_history for any other
367 # collection names that changed as a result of this name change
368 cm_list_collection
= context
.scene
.collection_manager
.cm_list_collection
370 laycol_iter_list
= list(context
.view_layer
.layer_collection
.children
)
372 while laycol_iter_list
:
373 layer_collection
= laycol_iter_list
[0]
374 cm_list_item
= cm_list_collection
[count
]
376 if cm_list_item
.name
!= layer_collection
.name
:
378 if cm_list_item
.last_name
in orig_expanded
:
379 if not cm_list_item
.last_name
in layer_collections
:
380 expanded
.remove(cm_list_item
.name
)
382 expanded
.add(layer_collection
.name
)
385 idx
= cm_list_item
.qcd_slot_idx
387 qcd_slots
.update_slot(idx
, layer_collection
.name
)
389 # update qcd_overrides
390 if cm_list_item
.name
in qcd_slots
.overrides
:
391 if not cm_list_item
.name
in layer_collections
:
392 qcd_slots
.overrides
.remove(cm_list_item
.name
)
394 qcd_slots
.overrides
.add(layer_collection
.name
)
398 history
= rto_history
[rto
].get(view_layer_name
)
400 if history
and orig_targets
[rto
] == cm_list_item
.last_name
:
401 history
["target"] = layer_collection
.name
403 # update expand history
404 if orig_expand_target
== cm_list_item
.last_name
:
405 expand_history
["target"] = layer_collection
.name
407 for x
, name
in enumerate(orig_expand_history
):
408 if name
== cm_list_item
.last_name
:
409 expand_history
["history"][x
] = layer_collection
.name
411 if layer_collection
.children
:
412 laycol_iter_list
[0:0] = list(layer_collection
.children
)
415 laycol_iter_list
.remove(layer_collection
)
419 update_property_group(context
)
422 self
.last_name
= self
.name
425 def update_qcd_slot(self
, context
):
428 if not qcd_slots
.allow_update
:
431 update_needed
= False
434 int(self
.qcd_slot_idx
)
437 if self
.qcd_slot_idx
== "":
438 qcd_slots
.add_override(self
.name
)
440 if qcd_slots
.contains(name
=self
.name
):
441 qcd_slots
.allow_update
= False
442 self
.qcd_slot_idx
= qcd_slots
.get_idx(self
.name
)
443 qcd_slots
.allow_update
= True
445 if self
.name
in qcd_slots
.overrides
:
446 qcd_slots
.allow_update
= False
447 self
.qcd_slot_idx
= ""
448 qcd_slots
.allow_update
= True
452 if qcd_slots
.contains(name
=self
.name
):
453 qcd_slots
.del_slot(name
=self
.name
)
456 if qcd_slots
.contains(idx
=self
.qcd_slot_idx
):
457 qcd_slots
.add_override(qcd_slots
.get_name(self
.qcd_slot_idx
))
460 if int(self
.qcd_slot_idx
) > 20:
461 self
.qcd_slot_idx
= "20"
463 if int(self
.qcd_slot_idx
) < 1:
464 self
.qcd_slot_idx
= "1"
466 qcd_slots
.add_slot(self
.qcd_slot_idx
, self
.name
)
469 update_property_group(context
)
472 class CMListCollection(PropertyGroup
):
473 name
: StringProperty(update
=update_col_name
)
474 last_name
: StringProperty()
475 qcd_slot_idx
: StringProperty(name
="QCD Slot", update
=update_qcd_slot
)
478 def update_collection_tree(context
):
481 global collection_tree
482 global layer_collections
485 collection_tree
.clear()
486 layer_collections
.clear()
490 layer_collection
= context
.view_layer
.layer_collection
491 init_laycol_list
= layer_collection
.children
493 master_laycol
= {"id": 0,
494 "name": layer_collection
.name
,
498 "has_children": True,
502 "ptr": layer_collection
505 get_all_collections(context
, init_laycol_list
, master_laycol
, master_laycol
["children"], visible
=True)
507 for laycol
in master_laycol
["children"]:
508 collection_tree
.append(laycol
)
510 qcd_slots
.update_qcd()
512 qcd_slots
.auto_numerate()
515 def get_all_collections(context
, collections
, parent
, tree
, level
=0, visible
=False):
522 for item
in collections
:
523 laycol
= {"id": len(layer_collections
) +1,
526 "row_index": row_index
,
528 "has_children": False,
537 layer_collections
[item
.name
] = laycol
540 if len(item
.children
) > 0:
541 laycol
["has_children"] = True
543 if item
.name
in expanded
and laycol
["visible"]:
544 laycol
["expanded"] = True
545 get_all_collections(context
, item
.children
, laycol
, laycol
["children"], level
+1, visible
=True)
548 get_all_collections(context
, item
.children
, laycol
, laycol
["children"], level
+1)
551 def update_property_group(context
):
552 global collection_tree
555 qcd_slots
.allow_update
= False
557 update_collection_tree(context
)
558 context
.scene
.collection_manager
.cm_list_collection
.clear()
559 create_property_group(context
, collection_tree
)
561 qcd_slots
.allow_update
= True
564 def create_property_group(context
, tree
):
568 cm
= context
.scene
.collection_manager
571 new_cm_listitem
= cm
.cm_list_collection
.add()
572 new_cm_listitem
.name
= laycol
["name"]
573 new_cm_listitem
.qcd_slot_idx
= qcd_slots
.get_idx(laycol
["name"], "")
575 if laycol
["has_children"]:
576 create_property_group(context
, laycol
["children"])
579 def get_modifiers(event
):
583 modifiers
.append("alt")
586 modifiers
.append("ctrl")
589 modifiers
.append("oskey")
592 modifiers
.append("shift")
594 return set(modifiers
)
597 def generate_state(*, qcd
=False):
598 global layer_collections
612 for name
, laycol
in layer_collections
.items():
613 state
["name"].append(name
)
614 state
["exclude"].append(laycol
["ptr"].exclude
)
615 state
["select"].append(laycol
["ptr"].collection
.hide_select
)
616 state
["hide"].append(laycol
["ptr"].hide_viewport
)
617 state
["disable"].append(laycol
["ptr"].collection
.hide_viewport
)
618 state
["render"].append(laycol
["ptr"].collection
.hide_render
)
619 state
["holdout"].append(laycol
["ptr"].holdout
)
620 state
["indirect"].append(laycol
["ptr"].indirect_only
)
623 state
["qcd"] = dict(qcd_slots
)
628 def get_move_selection(*, names_only
=False):
629 global move_selection
631 if not move_selection
:
632 move_selection
= {obj
.name
for obj
in bpy
.context
.selected_objects
}
635 return move_selection
638 if len(move_selection
) <= 5:
639 return {bpy
.data
.objects
[name
] for name
in move_selection
}
642 return {obj
for obj
in bpy
.data
.objects
if obj
.name
in move_selection
}
645 def get_move_active():
647 global move_selection
650 move_active
= getattr(bpy
.context
.view_layer
.objects
.active
, "name", None)
652 if move_active
not in get_move_selection(names_only
=True):
655 return bpy
.data
.objects
[move_active
] if move_active
else None
658 def update_qcd_header():
659 cm
= bpy
.context
.scene
.collection_manager
660 cm
.update_header
.clear()
661 new_update_header
= cm
.update_header
.add()
662 new_update_header
.name
= "updated"
665 class CMSendReport(Operator
):
666 bl_label
= "Send Report"
667 bl_idname
= "view3d.cm_send_report"
669 message
: StringProperty()
671 def draw(self
, context
):
673 col
= layout
.column(align
=True)
678 for num
, char
in enumerate(self
.message
):
681 col
.row(align
=True).label(text
=string
, icon
='ERROR')
684 col
.row(align
=True).label(text
=string
, icon
='BLANK1')
689 string
= string
+ char
692 col
.row(align
=True).label(text
=string
, icon
='ERROR')
694 col
.row(align
=True).label(text
=string
, icon
='BLANK1')
696 def invoke(self
, context
, event
):
697 wm
= context
.window_manager
702 for char
in self
.message
:
713 return wm
.invoke_popup(self
, width
=(30 + (max_len
*5.5)))
715 def execute(self
, context
):
716 self
.report({'INFO'}, self
.message
)
720 def send_report(message
):
722 window
= bpy
.context
.window_manager
.windows
[0]
723 ctx
= {'window': window
, 'screen': window
.screen
, }
724 bpy
.ops
.view3d
.cm_send_report(ctx
, 'INVOKE_DEFAULT', message
=message
)
726 bpy
.app
.timers
.register(report
)