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": [],
115 self
._slots
= persistent_data
.slots
116 self
.overrides
= persistent_data
.overrides
119 return self
._slots
.items().__iter
__()
122 return self
._slots
.__repr
__()
124 def contains(self
, *, idx
=None, name
=None):
126 return idx
in self
._slots
.keys()
129 return name
in self
._slots
.values()
133 def object_in_slots(self
, obj
):
134 for collection
in obj
.users_collection
:
135 if self
.contains(name
=collection
.name
):
140 def get_data_for_blend(self
):
141 return f
"{self._slots.__repr__()}\n{self.overrides.__repr__()}"
143 def load_blend_data(self
, data
):
144 decoupled_data
= data
.split("\n")
145 blend_slots
= eval(decoupled_data
[0])
146 blend_overrides
= eval(decoupled_data
[1])
149 self
.overrides
.clear()
151 for key
, value
in blend_slots
.items():
152 self
._slots
[key
] = value
154 for key
in blend_overrides
:
155 self
.overrides
.add(key
)
158 return len(self
._slots
)
160 def get_idx(self
, name
, r_value
=None):
161 for idx
, slot_name
in self
._slots
.items():
162 if slot_name
== name
:
167 def get_name(self
, idx
, r_value
=None):
168 if idx
in self
._slots
:
169 return self
._slots
[idx
]
173 def add_slot(self
, idx
, name
):
174 self
._slots
[idx
] = name
176 if name
in self
.overrides
:
177 self
.overrides
.remove(name
)
179 def update_slot(self
, idx
, name
):
180 self
.add_slot(idx
, name
)
182 def del_slot(self
, *, idx
=None, name
=None):
188 slot_idx
= self
.get_idx(name
)
189 del self
._slots
[slot_idx
]
194 def add_override(self
, name
):
195 qcd_slots
.del_slot(name
=name
)
196 qcd_slots
.overrides
.add(name
)
198 def clear_slots(self
):
201 def update_qcd(self
):
202 for idx
, name
in list(self
._slots
.items()):
203 if not layer_collections
.get(name
, None):
204 qcd_slots
.del_slot(name
=name
)
206 def auto_numerate(self
):
207 if self
.length() < 20:
208 laycol
= bpy
.context
.view_layer
.layer_collection
210 laycol_iter_list
= list(laycol
.children
)
211 while laycol_iter_list
:
212 layer_collection
= laycol_iter_list
.pop(0)
213 laycol_iter_list
.extend(list(layer_collection
.children
))
215 if layer_collection
.name
in qcd_slots
.overrides
:
219 if (not self
.contains(idx
=str(x
+1)) and
220 not self
.contains(name
=layer_collection
.name
)):
221 self
.add_slot(str(x
+1), layer_collection
.name
)
224 if self
.length() > 20:
227 def renumerate(self
, *, beginning
=False, depth_first
=False, constrain
=False):
230 self
.overrides
.clear()
232 starting_laycol_name
= self
.get_name("1")
234 if not starting_laycol_name
:
235 laycol
= bpy
.context
.view_layer
.layer_collection
236 starting_laycol_name
= laycol
.children
[0].name
239 self
.overrides
.clear()
242 parent
= layer_collections
[starting_laycol_name
]["parent"]
245 for laycol
in layer_collections
.values():
246 if self
.length() == 0 and starting_laycol_name
!= laycol
["name"]:
251 if laycol
["parent"]["name"] == parent
["name"]:
254 self
.add_slot(f
"{x}", laycol
["name"])
258 if self
.length() > 20:
262 laycol
= layer_collections
[starting_laycol_name
]["parent"]["ptr"]
264 laycol_iter_list
= []
265 for laycol
in laycol
.children
:
266 if laycol
.name
== starting_laycol_name
:
267 laycol_iter_list
.append(laycol
)
269 elif not constrain
and laycol_iter_list
:
270 laycol_iter_list
.append(laycol
)
273 while laycol_iter_list
:
274 layer_collection
= laycol_iter_list
.pop(0)
276 self
.add_slot(f
"{x}", layer_collection
.name
)
278 laycol_iter_list
.extend(list(layer_collection
.children
))
282 if self
.length() > 20:
286 for laycol
in layer_collections
.values():
287 if not self
.contains(name
=laycol
["name"]):
288 self
.overrides
.add(laycol
["name"])
290 qcd_slots
= QCDSlots()
293 def update_col_name(self
, context
):
294 global layer_collections
297 global expand_history
299 if self
.name
!= self
.last_name
:
301 self
.name
= self
.last_name
304 # if statement prevents update on list creation
305 if self
.last_name
!= '':
306 view_layer_name
= context
.view_layer
.name
308 # update collection name
309 layer_collections
[self
.last_name
]["ptr"].collection
.name
= self
.name
312 orig_expanded
= {x
for x
in expanded
}
314 if self
.last_name
in orig_expanded
:
315 expanded
.remove(self
.last_name
)
316 expanded
.add(self
.name
)
319 idx
= qcd_slots
.get_idx(self
.last_name
)
321 qcd_slots
.update_slot(idx
, self
.name
)
323 # update qcd_overrides
324 if self
.last_name
in qcd_slots
.overrides
:
325 qcd_slots
.overrides
.remove(self
.last_name
)
326 qcd_slots
.overrides
.add(self
.name
)
340 rto
: rto_history
[rto
][view_layer_name
]["target"]
342 if rto_history
[rto
].get(view_layer_name
)
346 history
= rto_history
[rto
].get(view_layer_name
)
348 if history
and orig_targets
[rto
] == self
.last_name
:
349 history
["target"] = self
.name
351 # update expand history
352 orig_expand_target
= expand_history
["target"]
353 orig_expand_history
= [x
for x
in expand_history
["history"]]
355 if orig_expand_target
== self
.last_name
:
356 expand_history
["target"] = self
.name
358 for x
, name
in enumerate(orig_expand_history
):
359 if name
== self
.last_name
:
360 expand_history
["history"][x
] = self
.name
362 # update names in expanded, qcd slots, and rto_history for any other
363 # collection names that changed as a result of this name change
364 cm_list_collection
= context
.scene
.collection_manager
.cm_list_collection
366 laycol_iter_list
= list(context
.view_layer
.layer_collection
.children
)
368 while laycol_iter_list
:
369 layer_collection
= laycol_iter_list
[0]
370 cm_list_item
= cm_list_collection
[count
]
372 if cm_list_item
.name
!= layer_collection
.name
:
374 if cm_list_item
.last_name
in orig_expanded
:
375 if not cm_list_item
.last_name
in layer_collections
:
376 expanded
.remove(cm_list_item
.name
)
378 expanded
.add(layer_collection
.name
)
381 idx
= cm_list_item
.qcd_slot_idx
383 qcd_slots
.update_slot(idx
, layer_collection
.name
)
385 # update qcd_overrides
386 if cm_list_item
.name
in qcd_slots
.overrides
:
387 if not cm_list_item
.name
in layer_collections
:
388 qcd_slots
.overrides
.remove(cm_list_item
.name
)
390 qcd_slots
.overrides
.add(layer_collection
.name
)
394 history
= rto_history
[rto
].get(view_layer_name
)
396 if history
and orig_targets
[rto
] == cm_list_item
.last_name
:
397 history
["target"] = layer_collection
.name
399 # update expand history
400 if orig_expand_target
== cm_list_item
.last_name
:
401 expand_history
["target"] = layer_collection
.name
403 for x
, name
in enumerate(orig_expand_history
):
404 if name
== cm_list_item
.last_name
:
405 expand_history
["history"][x
] = layer_collection
.name
407 if layer_collection
.children
:
408 laycol_iter_list
[0:0] = list(layer_collection
.children
)
411 laycol_iter_list
.remove(layer_collection
)
415 update_property_group(context
)
418 self
.last_name
= self
.name
421 def update_qcd_slot(self
, context
):
424 if not qcd_slots
.allow_update
:
427 update_needed
= False
430 int(self
.qcd_slot_idx
)
433 if self
.qcd_slot_idx
== "":
434 qcd_slots
.add_override(self
.name
)
436 if qcd_slots
.contains(name
=self
.name
):
437 qcd_slots
.allow_update
= False
438 self
.qcd_slot_idx
= qcd_slots
.get_idx(self
.name
)
439 qcd_slots
.allow_update
= True
441 if self
.name
in qcd_slots
.overrides
:
442 qcd_slots
.allow_update
= False
443 self
.qcd_slot_idx
= ""
444 qcd_slots
.allow_update
= True
448 if qcd_slots
.contains(name
=self
.name
):
449 qcd_slots
.del_slot(name
=self
.name
)
452 if qcd_slots
.contains(idx
=self
.qcd_slot_idx
):
453 qcd_slots
.add_override(qcd_slots
.get_name(self
.qcd_slot_idx
))
456 if int(self
.qcd_slot_idx
) > 20:
457 self
.qcd_slot_idx
= "20"
459 if int(self
.qcd_slot_idx
) < 1:
460 self
.qcd_slot_idx
= "1"
462 qcd_slots
.add_slot(self
.qcd_slot_idx
, self
.name
)
465 update_property_group(context
)
468 class CMListCollection(PropertyGroup
):
469 name
: StringProperty(update
=update_col_name
)
470 last_name
: StringProperty()
471 qcd_slot_idx
: StringProperty(name
="QCD Slot", update
=update_qcd_slot
)
474 def update_collection_tree(context
):
477 global collection_tree
478 global layer_collections
481 collection_tree
.clear()
482 layer_collections
.clear()
486 layer_collection
= context
.view_layer
.layer_collection
487 init_laycol_list
= layer_collection
.children
489 master_laycol
= {"id": 0,
490 "name": layer_collection
.name
,
494 "has_children": True,
498 "ptr": layer_collection
501 get_all_collections(context
, init_laycol_list
, master_laycol
, master_laycol
["children"], visible
=True)
503 for laycol
in master_laycol
["children"]:
504 collection_tree
.append(laycol
)
506 qcd_slots
.update_qcd()
508 qcd_slots
.auto_numerate()
511 def get_all_collections(context
, collections
, parent
, tree
, level
=0, visible
=False):
518 for item
in collections
:
519 laycol
= {"id": len(layer_collections
) +1,
522 "row_index": row_index
,
524 "has_children": False,
533 layer_collections
[item
.name
] = laycol
536 if len(item
.children
) > 0:
537 laycol
["has_children"] = True
539 if item
.name
in expanded
and laycol
["visible"]:
540 laycol
["expanded"] = True
541 get_all_collections(context
, item
.children
, laycol
, laycol
["children"], level
+1, visible
=True)
544 get_all_collections(context
, item
.children
, laycol
, laycol
["children"], level
+1)
547 def update_property_group(context
):
548 global collection_tree
551 qcd_slots
.allow_update
= False
553 update_collection_tree(context
)
554 context
.scene
.collection_manager
.cm_list_collection
.clear()
555 create_property_group(context
, collection_tree
)
557 qcd_slots
.allow_update
= True
560 def create_property_group(context
, tree
):
564 cm
= context
.scene
.collection_manager
567 new_cm_listitem
= cm
.cm_list_collection
.add()
568 new_cm_listitem
.name
= laycol
["name"]
569 new_cm_listitem
.qcd_slot_idx
= qcd_slots
.get_idx(laycol
["name"], "")
571 if laycol
["has_children"]:
572 create_property_group(context
, laycol
["children"])
575 def get_modifiers(event
):
579 modifiers
.append("alt")
582 modifiers
.append("ctrl")
585 modifiers
.append("oskey")
588 modifiers
.append("shift")
590 return set(modifiers
)
593 def generate_state(*, qcd
=False):
594 global layer_collections
608 for name
, laycol
in layer_collections
.items():
609 state
["name"].append(name
)
610 state
["exclude"].append(laycol
["ptr"].exclude
)
611 state
["select"].append(laycol
["ptr"].collection
.hide_select
)
612 state
["hide"].append(laycol
["ptr"].hide_viewport
)
613 state
["disable"].append(laycol
["ptr"].collection
.hide_viewport
)
614 state
["render"].append(laycol
["ptr"].collection
.hide_render
)
615 state
["holdout"].append(laycol
["ptr"].holdout
)
616 state
["indirect"].append(laycol
["ptr"].indirect_only
)
619 state
["qcd"] = dict(qcd_slots
)
624 def get_move_selection(*, names_only
=False):
625 global move_selection
627 if not move_selection
:
628 move_selection
= {obj
.name
for obj
in bpy
.context
.selected_objects
}
631 return move_selection
634 if len(move_selection
) <= 5:
635 return {bpy
.data
.objects
[name
] for name
in move_selection
}
638 return {obj
for obj
in bpy
.data
.objects
if obj
.name
in move_selection
}
641 def get_move_active(*, always
=False):
643 global move_selection
646 move_active
= getattr(bpy
.context
.view_layer
.objects
.active
, "name", None)
648 if not always
and move_active
not in get_move_selection(names_only
=True):
651 return bpy
.data
.objects
[move_active
] if move_active
else None
654 def update_qcd_header():
655 cm
= bpy
.context
.scene
.collection_manager
656 cm
.update_header
.clear()
657 new_update_header
= cm
.update_header
.add()
658 new_update_header
.name
= "updated"
661 class CMSendReport(Operator
):
662 bl_label
= "Send Report"
663 bl_idname
= "view3d.cm_send_report"
665 message
: StringProperty()
667 def draw(self
, context
):
669 col
= layout
.column(align
=True)
674 for num
, char
in enumerate(self
.message
):
677 col
.row(align
=True).label(text
=string
, icon
='ERROR')
680 col
.row(align
=True).label(text
=string
, icon
='BLANK1')
685 string
= string
+ char
688 col
.row(align
=True).label(text
=string
, icon
='ERROR')
690 col
.row(align
=True).label(text
=string
, icon
='BLANK1')
692 def invoke(self
, context
, event
):
693 wm
= context
.window_manager
698 for char
in self
.message
:
709 return wm
.invoke_popup(self
, width
=(30 + (max_len
*5.5)))
711 def execute(self
, context
):
712 self
.report({'INFO'}, self
.message
)
716 def send_report(message
):
718 window
= bpy
.context
.window_manager
.windows
[0]
719 ctx
= {'window': window
, 'screen': window
.screen
, }
720 bpy
.ops
.view3d
.cm_send_report(ctx
, 'INVOKE_DEFAULT', message
=message
)
722 bpy
.app
.timers
.register(report
)