├── LICENSE.txt
├── README.md
├── __init__.py
├── config.py
├── ops
├── __init__.py
├── direct_use_ops.py
├── inspect_ops.py
├── main_ops.py
├── missing_file_ops.py
├── support_me_ops.py
└── utils
│ ├── clean.py
│ ├── delete.py
│ ├── duplicate.py
│ └── nuke.py
├── stats
├── count.py
├── misc.py
├── missing.py
├── unnamed.py
├── unused.py
└── users.py
├── ui
├── __init__.py
├── inspect_ui.py
├── main_panel_ui.py
├── missing_file_ui.py
├── pie_menu_ui.py
├── preferences_ui.py
├── stats_panel_ui.py
├── support_me_ui.py
└── utils
│ └── ui_layouts.py
└── updater
├── addon_updater.py
└── addon_updater_ops.py
/README.md:
--------------------------------------------------------------------------------
1 | [](https://remington.pro/software/blender/atomic)
2 |
3 | [**Learn More About Atomic Data Manager on its Official Product Page**](https://remington.pro/software/blender/atomic)
4 |
5 |
6 | ## ENJOY A CLEANER PROJECT
7 | Atomic Data Manager offers Blender artists an intelligent data management solution. This feature-packed add-on provides artists with every tool they need to keep unwanted and unused data out of their Blender files. Even better, Atomic's real-time data analysis and automated data removal tools allow its users to clean their projects in a flash.
8 |
9 | 
10 |
11 |
12 | ## UNMATCHED FEATURE SET
13 |
14 | | Rapid Cleaning | Missing File Detection |
15 | |--|--|
16 | | With Atomic, you can clean your project files in a snap. Simply select a category and click the clean button. | Stop those pink textures! Atomic helps you restore missing project files before you even realize they're gone. |
17 |
18 | | Inspection Tools | Data Security |
19 | |--|--|
20 | | Find out where and how data-blocks are being used, so you can make manual adjustments on the fly. | Know what you're removing before its gone. Atomic offers reliable data security features to keep your projects safe. |
21 |
22 | | Rich Statistics | Compact Design |
23 | |--|--|
24 | | Get a detailed breakdown of the data being used in your Blender projects. Surprisingly interesting and useful! | Atomic's sleek user interface packs numerous powerful features into a convenient and easily accessible space.|
25 |
26 | #### Additional Features:
27 | Pie Menu Controls, Advanced Fake User Options, Mass Delete Categories, Undo Accidental Deletions, Save Data-Blocks, Delete Data-Blocks, Replace Data-Blocks, Rename Data-Blocks, Reload Missing Files, Remove Missing Files, Replace Missing Files, and Search for Missing Files.
28 |
29 |
30 | ## TAKE A VIDEO TOUR
31 | [](https://remington.pro/software/blender/atomic/#tour)
32 |
33 | | Keep Your Projects Clean | Reduce File Sizes | Optimize for Render Farms |
34 | |--|--|--|
35 | | Everyone appreciates a clean project. Use Atomic's intelligent toolset to keep your projects looking spiffy. | Atomic reduces file sizes by removing unused data from your projects. That way, there's more space for other stuff! | Render farms prioritize smaller projects. Atomic can optimize your files so they render sooner! |
36 |
37 |
38 | ## GET ATOMIC
39 |
40 | **Download:** [https://remington.pro/software/blender/atomic/](https://remington.pro/software/blender/atomic)
41 |
42 | **Older Versions:** [https://github.com/grantwilk/atomic-data-manager/releases](https://github.com/grantwilk/atomic-data-manager/releases)
43 |
44 |
45 |
46 | **Like the Add-on?** [Consider Supporting Remington Creative](https://remington.pro/support/)!
47 |
--------------------------------------------------------------------------------
/__init__.py:
--------------------------------------------------------------------------------
1 | """
2 | Copyright (C) 2019 Remington Creative
3 |
4 | This file is part of Atomic Data Manager.
5 |
6 | Atomic Data Manager is free software: you can redistribute
7 | it and/or modify it under the terms of the GNU General Public License
8 | as published by the Free Software Foundation, either version 3 of the
9 | License, or (at your option) any later version.
10 |
11 | Atomic Data Manager is distributed in the hope that it will
12 | be useful, but WITHOUT ANY WARRANTY; without even the implied warranty
13 | of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
14 | General Public License for more details.
15 |
16 | You should have received a copy of the GNU General Public License along
17 | with Atomic Data Manager. If not, see .
18 |
19 | ---
20 |
21 | This file contains Atomic's global properties and handles the
22 | registration for all packages within the add-on.
23 |
24 | """
25 |
26 |
27 | import bpy
28 | from bpy.utils import register_class
29 | from bpy.utils import unregister_class
30 | from atomic_data_manager import ops
31 | from atomic_data_manager import ui
32 | from atomic_data_manager.ui import inspect_ui
33 | from atomic_data_manager.updater import addon_updater_ops
34 |
35 | bl_info = {
36 | "name": "Atomic Data Manager",
37 | "author": "Remington Creative",
38 | "blender": (2, 80, 0),
39 | "version": (1, 0, 3),
40 | "location": "Properties > Scene",
41 | "category": "Remington Creative",
42 | "description": "An Intelligent Data Manager for Blender.",
43 | "wiki_url":
44 | "https://remington.pro/software/blender/atomic",
45 | "tracker_url":
46 | "https://github.com/grantwilk/atomic-data-manager/issues"
47 | }
48 |
49 |
50 | # Atomic Data Manager Properties
51 | class ATOMIC_PG_main(bpy.types.PropertyGroup):
52 | # main panel toggle buttons
53 | collections: bpy.props.BoolProperty(default=False)
54 | images: bpy.props.BoolProperty(default=False)
55 | lights: bpy.props.BoolProperty(default=False)
56 | materials: bpy.props.BoolProperty(default=False)
57 | node_groups: bpy.props.BoolProperty(default=False)
58 | particles: bpy.props.BoolProperty(default=False)
59 | textures: bpy.props.BoolProperty(default=False)
60 | worlds: bpy.props.BoolProperty(default=False)
61 |
62 | # inspect data-block search fields
63 | collections_field: bpy.props.StringProperty(
64 | update=inspect_ui.update_inspection)
65 |
66 | images_field: bpy.props.StringProperty(
67 | update=inspect_ui.update_inspection)
68 |
69 | lights_field: bpy.props.StringProperty(
70 | update=inspect_ui.update_inspection)
71 |
72 | materials_field: bpy.props.StringProperty(
73 | update=inspect_ui.update_inspection)
74 |
75 | node_groups_field: bpy.props.StringProperty(
76 | update=inspect_ui.update_inspection)
77 |
78 | particles_field: bpy.props.StringProperty(
79 | update=inspect_ui.update_inspection)
80 |
81 | textures_field: bpy.props.StringProperty(
82 | update=inspect_ui.update_inspection)
83 |
84 | worlds_field: bpy.props.StringProperty(
85 | update=inspect_ui.update_inspection)
86 |
87 | # enum for the inspection mode that is currently active
88 | active_inspection: bpy.props.EnumProperty(
89 | items=[
90 | (
91 | 'COLLECTIONS',
92 | 'Collections',
93 | 'Collections'
94 | ),
95 | (
96 | 'IMAGES',
97 | 'Images',
98 | 'Images'
99 | ),
100 | (
101 | 'LIGHTS',
102 | 'Lights',
103 | 'Lights'
104 | ),
105 | (
106 | 'MATERIALS',
107 | 'Materials',
108 | 'Materials'
109 | ),
110 | (
111 | 'NODE_GROUPS',
112 | 'Node Groups',
113 | 'Node Groups'
114 | ),
115 | (
116 | 'PARTICLES',
117 | 'Particles',
118 | 'Particles'
119 | ),
120 | (
121 | 'TEXTURES',
122 | 'Textures',
123 | 'Textures'
124 | ),
125 | (
126 | 'WORLDS',
127 | 'Worlds',
128 | 'Worlds'
129 | )
130 | ],
131 | default='COLLECTIONS'
132 | )
133 |
134 | # enum for the type of data being shown in the stats panel
135 | stats_mode: bpy.props.EnumProperty(
136 | items=[
137 | (
138 | 'OVERVIEW', # identifier
139 | 'Overview', # title
140 | 'Overview', # description
141 | 'FILE', # icon
142 | 0 # number / id
143 | ),
144 | (
145 | 'COLLECTIONS',
146 | 'Collections',
147 | 'Collections',
148 | 'GROUP',
149 | 1
150 | ),
151 | (
152 | 'IMAGES',
153 | 'Images',
154 | 'Images',
155 | 'IMAGE_DATA',
156 | 2
157 | ),
158 | (
159 | 'LIGHTS',
160 | 'Lights',
161 | 'Lights',
162 | 'LIGHT',
163 | 3
164 | ),
165 | (
166 | 'MATERIALS',
167 | 'Materials',
168 | 'Materials',
169 | 'MATERIAL',
170 | 4
171 | ),
172 | (
173 | 'OBJECTS',
174 | 'Objects',
175 | 'Objects',
176 | 'OBJECT_DATA',
177 | 5
178 | ),
179 | (
180 | 'NODE_GROUPS',
181 | 'Node Groups',
182 | 'Node Groups',
183 | 'NODETREE',
184 | 6
185 | ),
186 | (
187 | 'PARTICLES',
188 | 'Particle Systems',
189 | 'Particle Systems',
190 | 'PARTICLES',
191 | 7
192 | ),
193 | (
194 | 'TEXTURES',
195 | 'Textures',
196 | 'Textures',
197 | 'TEXTURE',
198 | 8
199 | ),
200 | (
201 | 'WORLDS',
202 | 'Worlds',
203 | 'Worlds',
204 | 'WORLD',
205 | 9
206 | )
207 | ],
208 | default='OVERVIEW'
209 | )
210 |
211 | # text field for the inspect rename operator
212 | rename_field: bpy.props.StringProperty()
213 |
214 | # search field for the inspect replace operator
215 | replace_field: bpy.props.StringProperty()
216 |
217 |
218 | def register():
219 | # add-on updater registration
220 | addon_updater_ops.register(bl_info)
221 |
222 | register_class(ATOMIC_PG_main)
223 | bpy.types.Scene.atomic = bpy.props.PointerProperty(type=ATOMIC_PG_main)
224 |
225 | # atomic package registration
226 | ui.register()
227 | ops.register()
228 |
229 |
230 | def unregister():
231 |
232 | # add-on updated unregistration
233 | addon_updater_ops.unregister()
234 |
235 | # atomic package unregistration
236 | ui.unregister()
237 | ops.unregister()
238 |
239 | unregister_class(ATOMIC_PG_main)
240 | del bpy.types.Scene.atomic
241 |
--------------------------------------------------------------------------------
/config.py:
--------------------------------------------------------------------------------
1 | """
2 | Copyright (C) 2019 Remington Creative
3 |
4 | This file is part of Atomic Data Manager.
5 |
6 | Atomic Data Manager is free software: you can redistribute
7 | it and/or modify it under the terms of the GNU General Public License
8 | as published by the Free Software Foundation, either version 3 of the
9 | License, or (at your option) any later version.
10 |
11 | Atomic Data Manager is distributed in the hope that it will
12 | be useful, but WITHOUT ANY WARRANTY; without even the implied warranty
13 | of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
14 | General Public License for more details.
15 |
16 | You should have received a copy of the GNU General Public License along
17 | with Atomic Data Manager. If not, see .
18 |
19 | ---
20 |
21 | This file contains global copies of Atomic's preferences so that they
22 | can be easily access throughout the add-on.
23 |
24 | NOTE:
25 | Changing the values of these variables will NOT change the values in the
26 | Atomic's preferences. If you want to change a setting, change it in
27 | Blender, not in here.
28 |
29 | """
30 |
31 | # visible atomic preferences
32 | enable_missing_file_warning = True
33 | enable_support_me_popup = True
34 | include_fake_users = False
35 | enable_pie_menu_ui = True
36 |
37 | # hidden atomic preferences
38 | pie_menu_type = "D"
39 | pie_menu_alt = False
40 | pie_menu_any = False
41 | pie_menu_ctrl = False
42 | pie_menu_oskey = False
43 | pie_menu_shift = False
44 | last_popup_day = 0
--------------------------------------------------------------------------------
/ops/__init__.py:
--------------------------------------------------------------------------------
1 | """
2 | Copyright (C) 2019 Remington Creative
3 |
4 | This file is part of Atomic Data Manager.
5 |
6 | Atomic Data Manager is free software: you can redistribute
7 | it and/or modify it under the terms of the GNU General Public License
8 | as published by the Free Software Foundation, either version 3 of the
9 | License, or (at your option) any later version.
10 |
11 | Atomic Data Manager is distributed in the hope that it will
12 | be useful, but WITHOUT ANY WARRANTY; without even the implied warranty
13 | of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
14 | General Public License for more details.
15 |
16 | You should have received a copy of the GNU General Public License along
17 | with Atomic Data Manager. If not, see .
18 |
19 | ---
20 |
21 | This file handles the registration of the atomic_data_manager.ops package
22 |
23 | """
24 |
25 | from atomic_data_manager.ops import main_ops
26 | from atomic_data_manager.ops import inspect_ops
27 | from atomic_data_manager.ops import direct_use_ops
28 | from atomic_data_manager.ops import missing_file_ops
29 | from atomic_data_manager.ops import support_me_ops
30 |
31 |
32 | def register():
33 | main_ops.register()
34 | inspect_ops.register()
35 | direct_use_ops.register()
36 | missing_file_ops.register()
37 | support_me_ops.register()
38 |
39 |
40 | def unregister():
41 | main_ops.unregister()
42 | inspect_ops.unregister()
43 | direct_use_ops.unregister()
44 | missing_file_ops.unregister()
45 | support_me_ops.unregister()
--------------------------------------------------------------------------------
/ops/direct_use_ops.py:
--------------------------------------------------------------------------------
1 | """
2 | Copyright (C) 2019 Remington Creative
3 |
4 | This file is part of Atomic Data Manager.
5 |
6 | Atomic Data Manager is free software: you can redistribute
7 | it and/or modify it under the terms of the GNU General Public License
8 | as published by the Free Software Foundation, either version 3 of the
9 | License, or (at your option) any later version.
10 |
11 | Atomic Data Manager is distributed in the hope that it will
12 | be useful, but WITHOUT ANY WARRANTY; without even the implied warranty
13 | of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
14 | General Public License for more details.
15 |
16 | You should have received a copy of the GNU General Public License along
17 | with Atomic Data Manager. If not, see .
18 |
19 | ---
20 |
21 | This file contains the direct use operators, intended to be used with
22 | Atomic's pie menu interface. However, they can be implemented anywhere
23 | if they need to be.
24 |
25 | These operators basically wrap the functions from ops.utils.nuke.py and
26 | ops.utils.clean.py into operators so they can be easily called by other
27 | intefaces in Blender.
28 |
29 | """
30 |
31 | import bpy
32 | from bpy.utils import register_class
33 | from bpy.utils import unregister_class
34 | from atomic_data_manager import config
35 | from atomic_data_manager.stats import unused
36 | from atomic_data_manager.ops.utils import nuke
37 | from atomic_data_manager.ops.utils import clean
38 | from atomic_data_manager.ui.utils import ui_layouts
39 |
40 |
41 | class ATOMIC_OT_invoke_pie_menu_ui(bpy.types.Operator):
42 | """Invokes Atomic's pie menu UI if the \"Enable Pie Menu UI\"
43 | preference is enabled in Atomic's preferences panel"""
44 | bl_idname = "atomic.invoke_pie_menu_ui"
45 | bl_label = "Invoke Pie Menu UI"
46 |
47 | def execute(self, context):
48 | if config.enable_pie_menu_ui:
49 | bpy.ops.wm.call_menu_pie(name="ATOMIC_MT_main_pie")
50 | return {'FINISHED'}
51 |
52 |
53 | # Atomic Data Manager Nuke All Operator
54 | class ATOMIC_OT_nuke_all(bpy.types.Operator):
55 | """Remove all data-blocks from the selected categories"""
56 | bl_idname = "atomic.nuke_all"
57 | bl_label = "CAUTION!"
58 |
59 | def draw(self, context):
60 | layout = self.layout
61 |
62 | col = layout.column()
63 | col.label(text="Remove the following data-blocks?")
64 |
65 | collections = sorted(bpy.data.collections.keys())
66 | ui_layouts.box_list(
67 | layout=layout,
68 | title="Collections",
69 | items=collections,
70 | icon="OUTLINER_OB_GROUP_INSTANCE"
71 | )
72 |
73 | images = sorted(bpy.data.images.keys())
74 | ui_layouts.box_list(
75 | layout=layout,
76 | title="Images",
77 | items=images,
78 | icon="IMAGE_DATA"
79 | )
80 |
81 | lights = sorted(bpy.data.lights.keys())
82 | ui_layouts.box_list(
83 | layout=layout,
84 | title="Lights",
85 | items=lights,
86 | icon="OUTLINER_OB_LIGHT"
87 | )
88 |
89 | materials = sorted(bpy.data.materials.keys())
90 | ui_layouts.box_list(
91 | layout=layout,
92 | title="Materials",
93 | items=materials,
94 | icon="MATERIAL"
95 | )
96 |
97 | node_groups = sorted(bpy.data.node_groups.keys())
98 | ui_layouts.box_list(
99 | layout=layout,
100 | title="Node Groups",
101 | items=node_groups,
102 | icon="NODETREE"
103 | )
104 |
105 | particles = sorted(bpy.data.particles.keys())
106 | ui_layouts.box_list(
107 | layout=layout,
108 | title="Particle Systems",
109 | items=particles,
110 | icon="PARTICLES"
111 | )
112 |
113 | textures = sorted(bpy.data.textures.keys())
114 | ui_layouts.box_list(
115 | layout=layout,
116 | title="Textures",
117 | items=textures,
118 | icon="TEXTURE"
119 | )
120 |
121 | worlds = sorted(bpy.data.worlds.keys())
122 | ui_layouts.box_list(
123 | layout=layout,
124 | title="Worlds",
125 | items=worlds,
126 | icon="WORLD"
127 | )
128 |
129 | row = layout.row() # extra spacing
130 |
131 | def execute(self, context):
132 |
133 | nuke.collections()
134 | nuke.images()
135 | nuke.lights()
136 | nuke.materials()
137 | nuke.node_groups()
138 | nuke.particles()
139 | nuke.textures()
140 | nuke.worlds()
141 |
142 | return {'FINISHED'}
143 |
144 | def invoke(self, context, event):
145 | wm = context.window_manager
146 | return wm.invoke_props_dialog(self)
147 |
148 |
149 | # Atomic Data Manager Clean All Operator
150 | class ATOMIC_OT_clean_all(bpy.types.Operator):
151 | """Remove all unused data-blocks from the selected categories"""
152 | bl_idname = "atomic.clean_all"
153 | bl_label = "Clean All"
154 |
155 | unused_collections = []
156 | unused_images = []
157 | unused_lights = []
158 | unused_materials = []
159 | unused_node_groups = []
160 | unused_particles = []
161 | unused_textures = []
162 | unused_worlds = []
163 |
164 | def draw(self, context):
165 | layout = self.layout
166 |
167 | col = layout.column()
168 | col.label(text="Remove the following data-blocks?")
169 |
170 | collections = sorted(unused.collections_deep())
171 | ui_layouts.box_list(
172 | layout=layout,
173 | title="Collections",
174 | items=collections,
175 | icon="OUTLINER_OB_GROUP_INSTANCE"
176 | )
177 |
178 | images = sorted(unused.images_deep())
179 | ui_layouts.box_list(
180 | layout=layout,
181 | title="Images",
182 | items=images,
183 | icon="IMAGE_DATA"
184 | )
185 |
186 | lights = sorted(unused.lights_deep())
187 | ui_layouts.box_list(
188 | layout=layout,
189 | title="Lights",
190 | items=lights,
191 | icon="OUTLINER_OB_LIGHT"
192 | )
193 |
194 | materials = sorted(unused.materials_deep())
195 | ui_layouts.box_list(
196 | layout=layout,
197 | title="Materials",
198 | items=materials,
199 | icon="MATERIAL"
200 | )
201 |
202 | node_groups = sorted(unused.node_groups_deep())
203 | ui_layouts.box_list(
204 | layout=layout,
205 | title="Node Groups",
206 | items=node_groups,
207 | icon="NODETREE"
208 | )
209 |
210 | particles = sorted(unused.particles_deep())
211 | ui_layouts.box_list(
212 | layout=layout,
213 | title="Particle Systems",
214 | items=particles,
215 | icon="PARTICLES"
216 | )
217 |
218 | textures = sorted(unused.textures_deep())
219 | ui_layouts.box_list(
220 | layout=layout,
221 | title="Textures",
222 | items=textures,
223 | icon="TEXTURE"
224 | )
225 |
226 | worlds = sorted(unused.worlds())
227 | ui_layouts.box_list(
228 | layout=layout,
229 | title="Worlds",
230 | items=worlds,
231 | icon="WORLD"
232 | )
233 |
234 | row = layout.row() # extra spacing
235 |
236 | def execute(self, context):
237 |
238 | clean.collections()
239 | clean.images()
240 | clean.lights()
241 | clean.materials()
242 | clean.node_groups()
243 | clean.particles()
244 | clean.textures()
245 | clean.worlds()
246 |
247 | return {'FINISHED'}
248 |
249 | def invoke(self, context, event):
250 | wm = context.window_manager
251 |
252 | self.unused_collections = unused.collections_deep()
253 | self.unused_images = unused.images_deep()
254 | self.unused_lights = unused.lights_deep()
255 | self.unused_materials = unused.materials_deep()
256 | self.unused_node_groups = unused.node_groups_deep()
257 | self.unused_particles = unused.particles_deep()
258 | self.unused_textures = unused.textures_deep()
259 | self.unused_worlds = unused.worlds()
260 |
261 | return wm.invoke_props_dialog(self)
262 |
263 |
264 | # Atomic Data Manager Nuke Collections Operator
265 | class ATOMIC_OT_nuke_collections(bpy.types.Operator):
266 | """Remove all collections from this project"""
267 | bl_idname = "atomic.nuke_collections"
268 | bl_label = "Nuke Collections"
269 |
270 | def draw(self, context):
271 | layout = self.layout
272 |
273 | row = layout.row()
274 | row.label(text="Remove the following data-blocks?")
275 |
276 | collections = bpy.data.collections.keys()
277 | ui_layouts.box_list(
278 | layout=layout,
279 | items=collections,
280 | icon="OUTLINER_OB_GROUP_INSTANCE"
281 | )
282 |
283 | row = layout.row() # extra space
284 |
285 | def execute(self, context):
286 | nuke.collections()
287 | return {'FINISHED'}
288 |
289 | def invoke(self, context, event):
290 | wm = context.window_manager
291 | return wm.invoke_props_dialog(self)
292 |
293 |
294 | # Atomic Data Manager Nuke Images Operator
295 | class ATOMIC_OT_nuke_images(bpy.types.Operator):
296 | """Remove all images from this project"""
297 | bl_idname = "atomic.nuke_images"
298 | bl_label = "Nuke Images"
299 |
300 | def draw(self, context):
301 | layout = self.layout
302 |
303 | row = layout.row()
304 | row.label(text="Remove the following data-blocks?")
305 |
306 | images = bpy.data.images.keys()
307 | ui_layouts.box_list(
308 | layout=layout,
309 | items=images,
310 | icon="IMAGE_DATA"
311 | )
312 |
313 | row = layout.row() # extra space
314 |
315 | def execute(self, context):
316 | nuke.images()
317 | return {'FINISHED'}
318 |
319 | def invoke(self, context, event):
320 | wm = context.window_manager
321 | return wm.invoke_props_dialog(self)
322 |
323 |
324 | # Atomic Data Manager Nuke Lights Operator
325 | class ATOMIC_OT_nuke_lights(bpy.types.Operator):
326 | """Remove all lights from this project"""
327 | bl_idname = "atomic.nuke_lights"
328 | bl_label = "Nuke Lights"
329 |
330 | def draw(self, context):
331 | layout = self.layout
332 |
333 | row = layout.row()
334 | row.label(text="Remove the following data-blocks?")
335 |
336 | lights = bpy.data.lights.keys()
337 | ui_layouts.box_list(
338 | layout=layout,
339 | items=lights,
340 | icon="OUTLINER_OB_LIGHT"
341 | )
342 |
343 | row = layout.row() # extra space
344 |
345 | def execute(self, context):
346 | nuke.lights()
347 | return {'FINISHED'}
348 |
349 | def invoke(self, context, event):
350 | wm = context.window_manager
351 | return wm.invoke_props_dialog(self)
352 |
353 |
354 | # Atomic Data Manager Nuke Materials Operator
355 | class ATOMIC_OT_nuke_materials(bpy.types.Operator):
356 | """Remove all materials from this project"""
357 | bl_idname = "atomic.nuke_materials"
358 | bl_label = "Nuke Materials"
359 |
360 | def draw(self, context):
361 | layout = self.layout
362 |
363 | row = layout.row()
364 | row.label(text="Remove the following data-blocks?")
365 |
366 | materials = bpy.data.materials.keys()
367 | ui_layouts.box_list(
368 | layout=layout,
369 | items=materials,
370 | icon="MATERIAL"
371 | )
372 |
373 | row = layout.row() # extra space
374 |
375 | def execute(self, context):
376 | nuke.materials()
377 | return {'FINISHED'}
378 |
379 | def invoke(self, context, event):
380 | wm = context.window_manager
381 | return wm.invoke_props_dialog(self)
382 |
383 |
384 | # Atomic Data Manager Nuke Node Groups Operator
385 | class ATOMIC_OT_nuke_node_groups(bpy.types.Operator):
386 | """Remove all node groups from this project"""
387 | bl_idname = "atomic.nuke_node_groups"
388 | bl_label = "Nuke Node Groups"
389 |
390 | def draw(self, context):
391 | layout = self.layout
392 |
393 | row = layout.row()
394 | row.label(text="Remove the following data-blocks?")
395 |
396 | node_groups = bpy.data.node_groups.keys()
397 | ui_layouts.box_list(
398 | layout=layout,
399 | items=node_groups,
400 | icon="NODETREE"
401 | )
402 |
403 | row = layout.row() # extra space
404 |
405 | def execute(self, context):
406 | nuke.node_groups()
407 | return {'FINISHED'}
408 |
409 | def invoke(self, context, event):
410 | wm = context.window_manager
411 | return wm.invoke_props_dialog(self)
412 |
413 |
414 | # Atomic Data Manager Nuke Particles Operator
415 | class ATOMIC_OT_nuke_particles(bpy.types.Operator):
416 | """Remove all particle systems from this project"""
417 | bl_idname = "atomic.nuke_particles"
418 | bl_label = "Nuke Particles"
419 |
420 | def draw(self, context):
421 | layout = self.layout
422 |
423 | row = layout.row()
424 | row.label(text="Remove the following data-blocks?")
425 |
426 | particles = bpy.data.particles.keys()
427 | ui_layouts.box_list(
428 | layout=layout,
429 | items=particles,
430 | icon="PARTICLES"
431 | )
432 |
433 | row = layout.row() # extra space
434 |
435 | def execute(self, context):
436 | nuke.particles()
437 | return {'FINISHED'}
438 |
439 | def invoke(self, context, event):
440 | wm = context.window_manager
441 | return wm.invoke_props_dialog(self)
442 |
443 |
444 | # Atomic Data Manager Nuke Textures Operator
445 | class ATOMIC_OT_nuke_textures(bpy.types.Operator):
446 | """Remove all textures from this project"""
447 | bl_idname = "atomic.nuke_textures"
448 | bl_label = "Nuke Textures"
449 |
450 | def draw(self, context):
451 | layout = self.layout
452 |
453 | row = layout.row()
454 | row.label(text="Remove the following data-blocks?")
455 |
456 | textures = bpy.data.textures.keys()
457 | ui_layouts.box_list(
458 | layout=layout,
459 | items=textures,
460 | icon="TEXTURE"
461 | )
462 |
463 | row = layout.row() # extra space
464 |
465 | def execute(self, context):
466 | nuke.textures()
467 | return {'FINISHED'}
468 |
469 | def invoke(self, context, event):
470 | wm = context.window_manager
471 | return wm.invoke_props_dialog(self)
472 |
473 |
474 | # Atomic Data Manager Nuke Worlds Operator
475 | class ATOMIC_OT_nuke_worlds(bpy.types.Operator):
476 | """Remove all worlds from this project"""
477 | bl_idname = "atomic.nuke_worlds"
478 | bl_label = "Nuke Worlds"
479 |
480 | def draw(self, context):
481 | layout = self.layout
482 |
483 | row = layout.row()
484 | row.label(text="Remove the following data-blocks?")
485 |
486 | worlds = bpy.data.worlds.keys()
487 | ui_layouts.box_list(
488 | layout=layout,
489 | items=worlds,
490 | icon="WORLD"
491 | )
492 |
493 | row = layout.row() # extra space
494 |
495 | def execute(self, context):
496 | nuke.worlds()
497 | return {'FINISHED'}
498 |
499 | def invoke(self, context, event):
500 | wm = context.window_manager
501 | return wm.invoke_props_dialog(self)
502 |
503 |
504 | # Atomic Data Manager Clean Collections Operator
505 | class ATOMIC_OT_clean_collections(bpy.types.Operator):
506 | """Remove all unused collections from this project"""
507 | bl_idname = "atomic.clean_collections"
508 | bl_label = "Clean Collections"
509 |
510 | unused_collections = []
511 |
512 | def draw(self, context):
513 | layout = self.layout
514 |
515 | row = layout.row()
516 | row.label(text="Remove the following data-blocks?")
517 |
518 | ui_layouts.box_list(
519 | layout=layout,
520 | items=self.unused_collections,
521 | icon="OUTLINER_OB_GROUP_INSTANCE"
522 | )
523 |
524 | row = layout.row() # extra space
525 |
526 | def execute(self, context):
527 | clean.collections()
528 | return {'FINISHED'}
529 |
530 | def invoke(self, context, event):
531 | wm = context.window_manager
532 | self.unused_collections = unused.collections_deep()
533 | return wm.invoke_props_dialog(self)
534 |
535 |
536 | # Atomic Data Manager Clean Images Operator
537 | class ATOMIC_OT_clean_images(bpy.types.Operator):
538 | """Remove all unused images from this project"""
539 | bl_idname = "atomic.clean_images"
540 | bl_label = "Clean Images"
541 |
542 | unused_images = []
543 |
544 | def draw(self, context):
545 | layout = self.layout
546 |
547 | row = layout.row()
548 | row.label(text="Remove the following data-blocks?")
549 |
550 | ui_layouts.box_list(
551 | layout=layout,
552 | items=self.unused_images,
553 | icon="IMAGE_DATA"
554 | )
555 |
556 | row = layout.row() # extra space
557 |
558 | def execute(self, context):
559 | clean.images()
560 | return {'FINISHED'}
561 |
562 | def invoke(self, context, event):
563 | wm = context.window_manager
564 | self.unused_images =unused.images_deep()
565 | return wm.invoke_props_dialog(self)
566 |
567 |
568 | # Atomic Data Manager Clean Lights Operator
569 | class ATOMIC_OT_clean_lights(bpy.types.Operator):
570 | """Remove all unused lights from this project"""
571 | bl_idname = "atomic.clean_lights"
572 | bl_label = "Clean Lights"
573 |
574 | unused_lights = []
575 |
576 | def draw(self, context):
577 | layout = self.layout
578 |
579 | row = layout.row()
580 | row.label(text="Remove the following data-blocks?")
581 |
582 | ui_layouts.box_list(
583 | layout=layout,
584 | items=self.unused_lights,
585 | icon="OUTLINER_OB_LIGHT"
586 | )
587 |
588 | row = layout.row() # extra space
589 |
590 | def execute(self, context):
591 | clean.lights()
592 | return {'FINISHED'}
593 |
594 | def invoke(self, context, event):
595 | wm = context.window_manager
596 | self.unused_lights = unused.lights_deep()
597 | return wm.invoke_props_dialog(self)
598 |
599 |
600 | # Atomic Data Manager Clean Materials Operator
601 | class ATOMIC_OT_clean_materials(bpy.types.Operator):
602 | """Remove all unused materials from this project"""
603 | bl_idname = "atomic.clean_materials"
604 | bl_label = "Clean Materials"
605 |
606 | unused_materials = []
607 |
608 | def draw(self, context):
609 | layout = self.layout
610 |
611 | row = layout.row()
612 | row.label(text="Remove the following data-blocks?")
613 |
614 | ui_layouts.box_list(
615 | layout=layout,
616 | items=self.unused_materials,
617 | icon="MATERIAL"
618 | )
619 |
620 | row = layout.row() # extra space
621 |
622 | def execute(self, context):
623 | clean.materials()
624 | return {'FINISHED'}
625 |
626 | def invoke(self, context, event):
627 | wm = context.window_manager
628 | self.unused_materials = unused.materials_deep()
629 | return wm.invoke_props_dialog(self)
630 |
631 |
632 | # Atomic Data Manager Clean Node Groups Operator
633 | class ATOMIC_OT_clean_node_groups(bpy.types.Operator):
634 | """Remove all unused node groups from this project"""
635 | bl_idname = "atomic.clean_node_groups"
636 | bl_label = "Clean Node Groups"
637 |
638 | unused_node_groups = []
639 |
640 | def draw(self, context):
641 | layout = self.layout
642 |
643 | row = layout.row()
644 | row.label(text="Remove the following data-blocks?")
645 |
646 | ui_layouts.box_list(
647 | layout=layout,
648 | items=self.unused_node_groups,
649 | icon="NODETREE"
650 | )
651 |
652 | row = layout.row() # extra space
653 |
654 | def execute(self, context):
655 | clean.node_groups()
656 | return {'FINISHED'}
657 |
658 | def invoke(self, context, event):
659 | wm = context.window_manager
660 | self.unused_node_groups = unused.node_groups_deep()
661 | return wm.invoke_props_dialog(self)
662 |
663 |
664 | # Atomic Data Manager Clean Particles Operator
665 | class ATOMIC_OT_clean_particles(bpy.types.Operator):
666 | """Remove all unused particle systems from this project"""
667 | bl_idname = "atomic.clean_particles"
668 | bl_label = "Clean Particles"
669 |
670 | unused_particles = []
671 |
672 | def draw(self, context):
673 | layout = self.layout
674 |
675 | row = layout.row()
676 | row.label(text="Remove the following data-blocks?")
677 |
678 | ui_layouts.box_list(
679 | layout=layout,
680 | items=self.unused_particles,
681 | icon="PARTICLES"
682 | )
683 |
684 | row = layout.row() # extra space
685 |
686 | def execute(self, context):
687 | clean.particles()
688 | return {'FINISHED'}
689 |
690 | def invoke(self, context, event):
691 | wm = context.window_manager
692 | self.unused_particles = unused.particles_deep()
693 | return wm.invoke_props_dialog(self)
694 |
695 |
696 | # Atomic Data Manager Clean Textures Operator
697 | class ATOMIC_OT_clean_textures(bpy.types.Operator):
698 | """Remove all unused textures from this project"""
699 | bl_idname = "atomic.clean_textures"
700 | bl_label = "Clean Textures"
701 |
702 | unused_textures = []
703 |
704 | def draw(self, context):
705 | layout = self.layout
706 |
707 | row = layout.row()
708 | row.label(text="Remove the following data-blocks?")
709 |
710 | ui_layouts.box_list(
711 | layout=layout,
712 | items=self.unused_textures,
713 | icon="TEXTURE"
714 | )
715 |
716 | row = layout.row() # extra space
717 |
718 | def execute(self, context):
719 | clean.textures()
720 | return {'FINISHED'}
721 |
722 | def invoke(self, context, event):
723 | wm = context.window_manager
724 | self.unused_textures = unused.textures_deep()
725 | return wm.invoke_props_dialog(self)
726 |
727 |
728 | # Atomic Data Manager Clean Worlds Operator
729 | class ATOMIC_OT_clean_worlds(bpy.types.Operator):
730 | """Remove all unused worlds from this project"""
731 | bl_idname = "atomic.clean_worlds"
732 | bl_label = "Clean Worlds"
733 |
734 | unused_worlds = []
735 |
736 | def draw(self, context):
737 | layout = self.layout
738 |
739 | row = layout.row()
740 | row.label(text="Remove the following data-blocks?")
741 |
742 | ui_layouts.box_list(
743 | layout=layout,
744 | items=self.unused_worlds,
745 | icon="WORLD"
746 | )
747 |
748 | row = layout.row() # extra space
749 |
750 | def execute(self, context):
751 | clean.worlds()
752 | return {'FINISHED'}
753 |
754 | def invoke(self, context, event):
755 | wm = context.window_manager
756 | self.unused_worlds = unused.worlds()
757 | return wm.invoke_props_dialog(self)
758 |
759 |
760 | reg_list = [
761 | ATOMIC_OT_invoke_pie_menu_ui,
762 |
763 | ATOMIC_OT_nuke_all,
764 | ATOMIC_OT_clean_all,
765 |
766 | ATOMIC_OT_nuke_collections,
767 | ATOMIC_OT_nuke_images,
768 | ATOMIC_OT_nuke_lights,
769 | ATOMIC_OT_nuke_materials,
770 | ATOMIC_OT_nuke_node_groups,
771 | ATOMIC_OT_nuke_particles,
772 | ATOMIC_OT_nuke_textures,
773 | ATOMIC_OT_nuke_worlds,
774 |
775 | ATOMIC_OT_clean_collections,
776 | ATOMIC_OT_clean_images,
777 | ATOMIC_OT_clean_lights,
778 | ATOMIC_OT_clean_materials,
779 | ATOMIC_OT_clean_node_groups,
780 | ATOMIC_OT_clean_particles,
781 | ATOMIC_OT_clean_textures,
782 | ATOMIC_OT_clean_worlds
783 | ]
784 |
785 |
786 | def register():
787 | for item in reg_list:
788 | register_class(item)
789 |
790 |
791 | def unregister():
792 | for item in reg_list:
793 | unregister_class(item)
794 |
--------------------------------------------------------------------------------
/ops/inspect_ops.py:
--------------------------------------------------------------------------------
1 | """
2 | Copyright (C) 2019 Remington Creative
3 |
4 | This file is part of Atomic Data Manager.
5 |
6 | Atomic Data Manager is free software: you can redistribute
7 | it and/or modify it under the terms of the GNU General Public License
8 | as published by the Free Software Foundation, either version 3 of the
9 | License, or (at your option) any later version.
10 |
11 | Atomic Data Manager is distributed in the hope that it will
12 | be useful, but WITHOUT ANY WARRANTY; without even the implied warranty
13 | of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
14 | General Public License for more details.
15 |
16 | You should have received a copy of the GNU General Public License along
17 | with Atomic Data Manager. If not, see .
18 |
19 | ---
20 |
21 | This file contains the operators used in the inspection UI's header.
22 | This includes the rename, replace, toggle fake user, delete, and duplicate
23 | operators.
24 |
25 | """
26 |
27 | import bpy
28 | from bpy.utils import register_class
29 | from bpy.utils import unregister_class
30 | from atomic_data_manager.ops.utils import delete
31 | from atomic_data_manager.ops.utils import duplicate
32 |
33 |
34 | # Atomic Data Manager Inspection Rename Operator
35 | class ATOMIC_OT_inspection_rename(bpy.types.Operator):
36 | """Give this data-block a new name"""
37 | bl_idname = "atomic.rename"
38 | bl_label = "Rename Data-Block"
39 |
40 | def draw(self, context):
41 | atom = bpy.context.scene.atomic
42 |
43 | layout = self.layout
44 | row = layout.row()
45 | row.prop(atom, "rename_field", text="", icon="GREASEPENCIL")
46 |
47 | def execute(self, context):
48 | atom = bpy.context.scene.atomic
49 | inspection = atom.active_inspection
50 |
51 | name = atom.rename_field
52 |
53 | if inspection == 'COLLECTIONS':
54 | bpy.data.collections[atom.collections_field].name = name
55 | atom.collections_field = name
56 |
57 | if inspection == 'IMAGES':
58 | bpy.data.images[atom.images_field].name = name
59 | atom.images_field = name
60 |
61 | if inspection == 'LIGHTS':
62 | bpy.data.lights[atom.lights_field].name = name
63 | atom.lights_field = name
64 |
65 | if inspection == 'MATERIALS':
66 | bpy.data.materials[atom.materials_field].name = name
67 | atom.materials_field = name
68 |
69 | if inspection == 'NODE_GROUPS':
70 | bpy.data.node_groups[atom.node_groups_field].name = name
71 | atom.node_groups_field = name
72 |
73 | if inspection == 'PARTICLES':
74 | bpy.data.particles[atom.particles_field].name = name
75 | atom.particles_field = name
76 |
77 | if inspection == 'TEXTURES':
78 | bpy.data.textures[atom.textures_field].name = name
79 | atom.textures_field = name
80 |
81 | if inspection == 'WORLDS':
82 | bpy.data.worlds[atom.worlds_field].name = name
83 | atom.worlds_field = name
84 |
85 | atom.rename_field = ""
86 | return {'FINISHED'}
87 |
88 | def invoke(self, context, event):
89 | wm = context.window_manager
90 | return wm.invoke_props_dialog(self, width=200)
91 |
92 |
93 | # Atomic Data Manager Inspection Replaces Operator
94 | class ATOMIC_OT_inspection_replace(bpy.types.Operator):
95 | """Replace all instances of this data-block with another data-block"""
96 | bl_idname = "atomic.replace"
97 | bl_label = "Replace Data-Block"
98 |
99 | def draw(self, context):
100 | atom = bpy.context.scene.atomic
101 | inspection = atom.active_inspection
102 |
103 | layout = self.layout
104 | row = layout.row()
105 |
106 | if inspection == 'IMAGES':
107 | row.prop_search(
108 | atom,
109 | "replace_field",
110 | bpy.data,
111 | "images",
112 | text=""
113 | )
114 |
115 | if inspection == 'LIGHTS':
116 | row.prop_search(
117 | atom,
118 | "replace_field",
119 | bpy.data,
120 | "lights",
121 | text=""
122 | )
123 |
124 | if inspection == 'MATERIALS':
125 | row.prop_search(
126 | atom,
127 | "replace_field",
128 | bpy.data,
129 | "materials",
130 | text=""
131 | )
132 |
133 | if inspection == 'NODE_GROUPS':
134 | row.prop_search(
135 | atom,
136 | "replace_field",
137 | bpy.data,
138 | "node_groups",
139 | text=""
140 | )
141 |
142 | if inspection == 'PARTICLES':
143 | row.prop_search(
144 | atom,
145 | "replace_field",
146 | bpy.data,
147 | "particles",
148 | text=""
149 | )
150 |
151 | if inspection == 'TEXTURES':
152 | row.prop_search(
153 | atom,
154 | "replace_field",
155 | bpy.data,
156 | "textures",
157 | text=""
158 | )
159 |
160 | if inspection == 'WORLDS':
161 | row.prop_search(
162 | atom,
163 | "replace_field",
164 | bpy.data,
165 | "worlds",
166 | text=""
167 | )
168 |
169 | def execute(self, context):
170 | atom = bpy.context.scene.atomic
171 | inspection = atom.active_inspection
172 |
173 | if inspection == 'IMAGES' and \
174 | atom.replace_field in bpy.data.images.keys():
175 | bpy.data.images[atom.images_field].user_remap(
176 | bpy.data.images[atom.replace_field])
177 | atom.images_field = atom.replace_field
178 |
179 | if inspection == 'LIGHTS' and \
180 | atom.replace_field in bpy.data.lights.keys():
181 | bpy.data.lights[atom.lights_field].user_remap(
182 | bpy.data.lights[atom.replace_field])
183 | atom.lights_field = atom.replace_field
184 |
185 | if inspection == 'MATERIALS' and \
186 | atom.replace_field in bpy.data.materials.keys():
187 | bpy.data.materials[atom.materials_field].user_remap(
188 | bpy.data.materials[atom.replace_field])
189 | atom.materials_field = atom.replace_field
190 |
191 | if inspection == 'NODE_GROUPS' and \
192 | atom.replace_field in bpy.data.node_groups.keys():
193 | bpy.data.node_groups[atom.node_groups_field].user_remap(
194 | bpy.data.node_groups[atom.replace_field])
195 | atom.node_groups_field = atom.replace_field
196 |
197 | if inspection == 'PARTICLES' and \
198 | atom.replace_field in bpy.data.particles.keys():
199 | bpy.data.particles[atom.particles_field].user_remap(
200 | bpy.data.particles[atom.replace_field])
201 | atom.particles_field = atom.replace_field
202 |
203 | if inspection == 'TEXTURES' and \
204 | atom.replace_field in bpy.data.textures.keys():
205 | bpy.data.textures[atom.textures_field].user_remap(
206 | bpy.data.textures[atom.replace_field])
207 | atom.textures_field = atom.replace_field
208 |
209 | if inspection == 'WORLDS' and \
210 | atom.replace_field in bpy.data.worlds.keys():
211 | bpy.data.worlds[atom.worlds_field].user_remap(
212 | bpy.data.worlds[atom.replace_field])
213 | atom.worlds_field = atom.replace_field
214 |
215 | atom.replace_field = ""
216 | return {'FINISHED'}
217 |
218 | def invoke(self, context, event):
219 | wm = context.window_manager
220 | return wm.invoke_props_dialog(self, width=200)
221 |
222 |
223 | # Atomic Data Manager Inspection Toggle Fake User Operator
224 | class ATOMIC_OT_inspection_toggle_fake_user(bpy.types.Operator):
225 | """Save this data-block even if it has no users"""
226 | bl_idname = "atomic.toggle_fake_user"
227 | bl_label = "Toggle Fake User"
228 |
229 | def execute(self, context):
230 | atom = bpy.context.scene.atomic
231 | inspection = atom.active_inspection
232 |
233 | if inspection == 'IMAGES':
234 | image = bpy.data.images[atom.images_field]
235 | bpy.data.images[atom.images_field].use_fake_user = \
236 | not image.use_fake_user
237 |
238 | if inspection == 'LIGHTS':
239 | light = bpy.data.lights[atom.lights_field]
240 | bpy.data.lights[atom.lights_field].use_fake_user = \
241 | not light.use_fake_user
242 |
243 | if inspection == 'MATERIALS':
244 | material = bpy.data.materials[atom.materials_field]
245 | bpy.data.materials[atom.materials_field].use_fake_user = \
246 | not material.use_fake_user
247 |
248 | if inspection == 'NODE_GROUPS':
249 | node_group = bpy.data.node_groups[atom.node_groups_field]
250 | bpy.data.node_groups[atom.node_groups_field].use_fake_user = \
251 | not node_group.use_fake_user
252 |
253 | if inspection == 'PARTICLES':
254 | particle = bpy.data.particles[atom.particles_field]
255 | bpy.data.particles[atom.particles_field].use_fake_user = \
256 | not particle.use_fake_user
257 |
258 | if inspection == 'TEXTURES':
259 | texture = bpy.data.textures[atom.textures_field]
260 | bpy.data.textures[atom.textures_field].use_fake_user = \
261 | not texture.use_fake_user
262 |
263 | if inspection == 'WORLDS':
264 | world = bpy.data.worlds[atom.worlds_field]
265 | bpy.data.worlds[atom.worlds_field].use_fake_user = \
266 | not world.use_fake_user
267 |
268 | return {'FINISHED'}
269 |
270 |
271 | # Atomic Data Manager Inspection Duplicate Operator
272 | class ATOMIC_OT_inspection_duplicate(bpy.types.Operator):
273 | """Make an exact copy of this data-block"""
274 | bl_idname = "atomic.inspection_duplicate"
275 | bl_label = "Duplicate Data-Block"
276 |
277 | def execute(self, context):
278 | atom = bpy.context.scene.atomic
279 | inspection = atom.active_inspection
280 |
281 | if inspection == 'COLLECTIONS':
282 | key = atom.collections_field
283 | collections = bpy.data.collections
284 |
285 | if key in collections.keys():
286 | copy_key = duplicate.collection(key)
287 | atom.collections_field = copy_key
288 |
289 | elif inspection == 'IMAGES':
290 | key = atom.images_field
291 | images = bpy.data.images
292 |
293 | if key in images.keys():
294 | copy_key = duplicate.image(key)
295 | atom.images_field = copy_key
296 |
297 | elif inspection == 'LIGHTS':
298 | key = atom.lights_field
299 | lights = bpy.data.lights
300 |
301 | if key in lights.keys():
302 | copy_key = duplicate.light(key)
303 | atom.lights_field = copy_key
304 |
305 | elif inspection == 'MATERIALS':
306 | key = atom.materials_field
307 | materials = bpy.data.materials
308 |
309 | if key in materials.keys():
310 | copy_key = duplicate.material(key)
311 | atom.materials_field = copy_key
312 |
313 | elif inspection == 'NODE_GROUPS':
314 | key = atom.node_groups_field
315 | node_groups = bpy.data.node_groups
316 |
317 | if key in node_groups.keys():
318 | copy_key = duplicate.node_group(key)
319 | atom.node_groups_field = copy_key
320 |
321 | elif inspection == 'PARTICLES':
322 | key = atom.particles_field
323 | particles = bpy.data.particles
324 |
325 | if key in particles.keys():
326 | copy_key = duplicate.particle(key)
327 | atom.particles_field = copy_key
328 |
329 | elif inspection == 'TEXTURES':
330 | key = atom.textures_field
331 | textures = bpy.data.textures
332 |
333 | if key in textures.keys():
334 | copy_key = duplicate.texture(key)
335 | atom.textures_field = copy_key
336 |
337 | elif inspection == 'WORLDS':
338 | key = atom.worlds_field
339 | worlds = bpy.data.worlds
340 |
341 | if key in worlds.keys():
342 | copy_key = duplicate.world(key)
343 | atom.worlds_field = copy_key
344 |
345 | return {'FINISHED'}
346 |
347 |
348 | # Atomic Data Manager Inspection Delete Operator
349 | class ATOMIC_OT_inspection_delete(bpy.types.Operator):
350 | """Forcibly remove this data-block from the project"""
351 | bl_idname = "atomic.inspection_delete"
352 | bl_label = "Delete Data-Block"
353 |
354 | def execute(self, context):
355 | atom = bpy.context.scene.atomic
356 | inspection = atom.active_inspection
357 |
358 | if inspection == 'COLLECTIONS':
359 | key = atom.collections_field
360 | collections = bpy.data.collections
361 |
362 | if key in collections.keys():
363 | delete.collection(key)
364 | atom.collections_field = ""
365 |
366 | elif inspection == 'IMAGES':
367 | key = atom.images_field
368 | images = bpy.data.images
369 |
370 | if key in images.keys():
371 | delete.image(key)
372 | atom.images_field = ""
373 |
374 | elif inspection == 'LIGHTS':
375 | key = atom.lights_field
376 | lights = bpy.data.lights
377 |
378 | if key in lights.keys():
379 | delete.light(key)
380 | atom.lights_field = ""
381 |
382 | elif inspection == 'MATERIALS':
383 | key = atom.materials_field
384 | materials = bpy.data.materials
385 |
386 | if key in materials.keys():
387 | delete.material(key)
388 | atom.materials_field = ""
389 |
390 | elif inspection == 'NODE_GROUPS':
391 | key = atom.node_groups_field
392 | node_groups = bpy.data.node_groups
393 |
394 | if key in node_groups.keys():
395 | delete.node_group(key)
396 | atom.node_groups_field = ""
397 |
398 | elif inspection == 'PARTICLES':
399 | key = atom.particles_field
400 | particles = bpy.data.particles
401 | if key in particles.keys():
402 | delete.particle(key)
403 | atom.particles_field = ""
404 |
405 | elif inspection == 'TEXTURES':
406 | key = atom.textures_field
407 | textures = bpy.data.textures
408 |
409 | if key in textures.keys():
410 | delete.texture(key)
411 | atom.textures_field = ""
412 |
413 | elif inspection == 'WORLDS':
414 | key = atom.worlds_field
415 | worlds = bpy.data.worlds
416 |
417 | if key in worlds.keys():
418 | delete.world(key)
419 | atom.worlds_field = ""
420 |
421 | return {'FINISHED'}
422 |
423 |
424 | reg_list = [
425 | ATOMIC_OT_inspection_rename,
426 | ATOMIC_OT_inspection_replace,
427 | ATOMIC_OT_inspection_toggle_fake_user,
428 | ATOMIC_OT_inspection_duplicate,
429 | ATOMIC_OT_inspection_delete
430 | ]
431 |
432 |
433 | def register():
434 | for item in reg_list:
435 | register_class(item)
436 |
437 |
438 | def unregister():
439 | for item in reg_list:
440 | unregister_class(item)
441 |
--------------------------------------------------------------------------------
/ops/main_ops.py:
--------------------------------------------------------------------------------
1 | """
2 | Copyright (C) 2019 Remington Creative
3 |
4 | This file is part of Atomic Data Manager.
5 |
6 | Atomic Data Manager is free software: you can redistribute
7 | it and/or modify it under the terms of the GNU General Public License
8 | as published by the Free Software Foundation, either version 3 of the
9 | License, or (at your option) any later version.
10 |
11 | Atomic Data Manager is distributed in the hope that it will
12 | be useful, but WITHOUT ANY WARRANTY; without even the implied warranty
13 | of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
14 | General Public License for more details.
15 |
16 | You should have received a copy of the GNU General Public License along
17 | with Atomic Data Manager. If not, see .
18 |
19 | ---
20 |
21 | This file contains the main operators found in the main panel of the
22 | Atomic Data Manager interface. This includes nuke, clean, undo, and the
23 | various selection operations.
24 |
25 | """
26 |
27 | import bpy
28 | from bpy.utils import register_class
29 | from bpy.utils import unregister_class
30 | from atomic_data_manager.stats import unused
31 | from atomic_data_manager.ops.utils import clean
32 | from atomic_data_manager.ops.utils import nuke
33 | from atomic_data_manager.ui.utils import ui_layouts
34 |
35 |
36 | # Atomic Data Manager Nuke Operator
37 | class ATOMIC_OT_nuke(bpy.types.Operator):
38 | """Remove all data-blocks from the selected categories"""
39 | bl_idname = "atomic.nuke"
40 | bl_label = "CAUTION!"
41 |
42 | def draw(self, context):
43 | atom = bpy.context.scene.atomic
44 | layout = self.layout
45 |
46 | col = layout.column()
47 | col.label(text="Remove the following data-blocks?")
48 |
49 | # No Data Section
50 | if not (atom.collections or atom.images or atom.lights or
51 | atom.materials or atom.node_groups or atom.particles or
52 | atom.textures or atom.worlds):
53 |
54 | ui_layouts.box_list(
55 | layout=layout,
56 | )
57 |
58 | # display when the main panel collections property is toggled
59 | if atom.collections:
60 | collections = sorted(bpy.data.collections.keys())
61 | ui_layouts.box_list(
62 | layout=layout,
63 | title="Collections",
64 | items=collections,
65 | icon="OUTLINER_OB_GROUP_INSTANCE"
66 | )
67 |
68 | # display when the main panel images property is toggled
69 | if atom.images:
70 | images = sorted(bpy.data.images.keys())
71 | ui_layouts.box_list(
72 | layout=layout,
73 | title="Images",
74 | items=images,
75 | icon="IMAGE_DATA"
76 | )
77 |
78 | # display when the main panel lights property is toggled
79 | if atom.lights:
80 | lights = sorted(bpy.data.lights.keys())
81 | ui_layouts.box_list(
82 | layout=layout,
83 | title="Lights",
84 | items=lights,
85 | icon="OUTLINER_OB_LIGHT"
86 | )
87 |
88 | # display when the main panel materials property is toggled
89 | if atom.materials:
90 | materials = sorted(bpy.data.materials.keys())
91 | ui_layouts.box_list(
92 | layout=layout,
93 | title="Materials",
94 | items=materials,
95 | icon="MATERIAL"
96 | )
97 |
98 | # display when the main panel node groups property is toggled
99 | if atom.node_groups:
100 | node_groups = sorted(bpy.data.node_groups.keys())
101 | ui_layouts.box_list(
102 | layout=layout,
103 | title="Node Groups",
104 | items=node_groups,
105 | icon="NODETREE"
106 | )
107 |
108 | # display when the main panel particle systems property is toggled
109 | if atom.particles:
110 | particles = sorted(bpy.data.particles.keys())
111 | ui_layouts.box_list(
112 | layout=layout,
113 | title="Particle Systems",
114 | items=particles,
115 | icon="PARTICLES"
116 | )
117 |
118 | # display when the main panel textures property is toggled
119 | if atom.textures:
120 | textures = sorted(bpy.data.textures.keys())
121 | ui_layouts.box_list(
122 | layout=layout,
123 | title="Textures",
124 | items=textures,
125 | icon="TEXTURE"
126 | )
127 |
128 | # display when the main panel worlds property is toggled
129 | if atom.worlds:
130 | worlds = sorted(bpy.data.worlds.keys())
131 | ui_layouts.box_list(
132 | layout=layout,
133 | title="Worlds",
134 | items=worlds,
135 | icon="WORLD"
136 | )
137 |
138 | row = layout.row() # extra spacing
139 |
140 | def execute(self, context):
141 | atom = bpy.context.scene.atomic
142 |
143 | if atom.collections:
144 | nuke.collections()
145 |
146 | if atom.images:
147 | nuke.images()
148 |
149 | if atom.lights:
150 | nuke.lights()
151 |
152 | if atom.materials:
153 | nuke.materials()
154 |
155 | if atom.node_groups:
156 | nuke.node_groups()
157 |
158 | if atom.particles:
159 | nuke.particles()
160 |
161 | if atom.textures:
162 | nuke.textures()
163 |
164 | if atom.worlds:
165 | nuke.worlds()
166 |
167 | bpy.ops.atomic.deselect_all()
168 |
169 | return {'FINISHED'}
170 |
171 | def invoke(self, context, event):
172 | wm = context.window_manager
173 | return wm.invoke_props_dialog(self)
174 |
175 |
176 | # Atomic Data Manager Clean Operator
177 | class ATOMIC_OT_clean(bpy.types.Operator):
178 | """Remove all unused data-blocks from the selected categories"""
179 | bl_idname = "atomic.clean"
180 | bl_label = "Clean"
181 |
182 | unused_collections = []
183 | unused_images = []
184 | unused_lights = []
185 | unused_materials = []
186 | unused_node_groups = []
187 | unused_particles = []
188 | unused_textures = []
189 | unused_worlds = []
190 |
191 | def draw(self, context):
192 | atom = bpy.context.scene.atomic
193 | layout = self.layout
194 |
195 | col = layout.column()
196 | col.label(text="Remove the following data-blocks?")
197 |
198 | # display if no main panel properties are toggled
199 | if not (atom.collections or atom.images or atom.lights or
200 | atom.materials or atom.node_groups or atom.particles
201 | or atom.textures or atom.worlds):
202 |
203 | ui_layouts.box_list(
204 | layout=layout,
205 | )
206 |
207 | # display when the main panel collections property is toggled
208 | if atom.collections:
209 | ui_layouts.box_list(
210 | layout=layout,
211 | title="Collections",
212 | items=self.unused_collections,
213 | icon="OUTLINER_OB_GROUP_INSTANCE"
214 | )
215 |
216 | # display when the main panel images property is toggled
217 | if atom.images:
218 | ui_layouts.box_list(
219 | layout=layout,
220 | title="Images",
221 | items=self.unused_images,
222 | icon="IMAGE_DATA"
223 | )
224 |
225 | # display when the main panel lights property is toggled
226 | if atom.lights:
227 | ui_layouts.box_list(
228 | layout=layout,
229 | title="Lights",
230 | items=self.unused_lights,
231 | icon="OUTLINER_OB_LIGHT"
232 | )
233 |
234 | # display when the main panel materials property is toggled
235 | if atom.materials:
236 | ui_layouts.box_list(
237 | layout=layout,
238 | title="Materials",
239 | items=self.unused_materials,
240 | icon="MATERIAL"
241 | )
242 |
243 | # display when the main panel node groups property is toggled
244 | if atom.node_groups:
245 | ui_layouts.box_list(
246 | layout=layout,
247 | title="Node Groups",
248 | items=self.unused_node_groups,
249 | icon="NODETREE"
250 | )
251 |
252 | # display when the main panel particle systems property is toggled
253 | if atom.particles:
254 | ui_layouts.box_list(
255 | layout=layout,
256 | title="Particle Systems",
257 | items=self.unused_particles,
258 | icon="PARTICLES"
259 | )
260 |
261 | # display when the main panel textures property is toggled
262 | if atom.textures:
263 | textures = sorted(unused.textures_deep())
264 | ui_layouts.box_list(
265 | layout=layout,
266 | title="Textures",
267 | items=textures,
268 | icon="TEXTURE"
269 | )
270 |
271 | # display when the main panel worlds property is toggled
272 | if atom.worlds:
273 | ui_layouts.box_list(
274 | layout=layout,
275 | title="Worlds",
276 | items=self.unused_worlds,
277 | icon="WORLD"
278 | )
279 |
280 | row = layout.row() # extra spacing
281 |
282 | def execute(self, context):
283 | atom = bpy.context.scene.atomic
284 |
285 | if atom.collections:
286 | clean.collections()
287 |
288 | if atom.images:
289 | clean.images()
290 |
291 | if atom.lights:
292 | clean.lights()
293 |
294 | if atom.materials:
295 | clean.materials()
296 |
297 | if atom.node_groups:
298 | clean.node_groups()
299 |
300 | if atom.particles:
301 | clean.particles()
302 |
303 | if atom.textures:
304 | clean.textures()
305 |
306 | if atom.worlds:
307 | clean.worlds()
308 |
309 | bpy.ops.atomic.deselect_all()
310 |
311 | return {'FINISHED'}
312 |
313 | def invoke(self, context, event):
314 | wm = context.window_manager
315 | atom = bpy.context.scene.atomic
316 |
317 | if atom.collections:
318 | self.unused_collections = unused.collections_deep()
319 |
320 | if atom.images:
321 | self.unused_images = unused.images_deep()
322 |
323 | if atom.lights:
324 | self.unused_lights = unused.lights_deep()
325 |
326 | if atom.materials:
327 | self.unused_materials = unused.materials_deep()
328 |
329 | if atom.node_groups:
330 | self.unused_node_groups = unused.node_groups_deep()
331 |
332 | if atom.particles:
333 | self.unused_particles = unused.particles_deep()
334 |
335 | if atom.textures:
336 | self.unused_textures = unused.textures_deep()
337 |
338 | if atom.worlds:
339 | self.unused_worlds = unused.worlds()
340 |
341 | return wm.invoke_props_dialog(self)
342 |
343 |
344 | # Atomic Data Manager Undo Operator
345 | class ATOMIC_OT_undo(bpy.types.Operator):
346 | """Undo the previous action"""
347 | bl_idname = "atomic.undo"
348 | bl_label = "Undo"
349 |
350 | def execute(self, context):
351 | bpy.ops.ed.undo()
352 | return {'FINISHED'}
353 |
354 |
355 | # Atomic Data Manager Smart Select Operator
356 | class ATOMIC_OT_smart_select(bpy.types.Operator):
357 | """Auto-select categories with unused data"""
358 | bl_idname = "atomic.smart_select"
359 | bl_label = "Smart Select"
360 |
361 | def execute(self, context):
362 |
363 | bpy.context.scene.atomic.collections = \
364 | any(unused.collections_deep())
365 |
366 | bpy.context.scene.atomic.images = \
367 | any(unused.images_deep())
368 |
369 | bpy.context.scene.atomic.lights = \
370 | any(unused.lights_deep())
371 |
372 | bpy.context.scene.atomic.materials = \
373 | any(unused.materials_deep())
374 |
375 | bpy.context.scene.atomic.node_groups = \
376 | any(unused.node_groups_deep())
377 |
378 | bpy.context.scene.atomic.particles = \
379 | any(unused.particles_deep())
380 |
381 | bpy.context.scene.atomic.textures = \
382 | any(unused.textures_deep())
383 |
384 | bpy.context.scene.atomic.worlds = \
385 | any(unused.worlds())
386 |
387 | return {'FINISHED'}
388 |
389 |
390 | # Atomic Data Manager Select All Operator
391 | class ATOMIC_OT_select_all(bpy.types.Operator):
392 | """Select all categories"""
393 | bl_idname = "atomic.select_all"
394 | bl_label = "Select All"
395 |
396 | def execute(self, context):
397 | bpy.context.scene.atomic.collections = True
398 | bpy.context.scene.atomic.images = True
399 | bpy.context.scene.atomic.lights = True
400 | bpy.context.scene.atomic.materials = True
401 | bpy.context.scene.atomic.node_groups = True
402 | bpy.context.scene.atomic.particles = True
403 | bpy.context.scene.atomic.textures = True
404 | bpy.context.scene.atomic.worlds = True
405 | return {'FINISHED'}
406 |
407 |
408 | # Atomic Data Manager Deselect All Operator
409 | class ATOMIC_OT_deselect_all(bpy.types.Operator):
410 | """Deselect all categories"""
411 | bl_idname = "atomic.deselect_all"
412 | bl_label = "Deselect All"
413 |
414 | def execute(self, context):
415 | bpy.context.scene.atomic.collections = False
416 | bpy.context.scene.atomic.images = False
417 | bpy.context.scene.atomic.lights = False
418 | bpy.context.scene.atomic.materials = False
419 | bpy.context.scene.atomic.node_groups = False
420 | bpy.context.scene.atomic.particles = False
421 | bpy.context.scene.atomic.textures = False
422 | bpy.context.scene.atomic.worlds = False
423 |
424 | return {'FINISHED'}
425 |
426 |
427 | reg_list = [
428 | ATOMIC_OT_nuke,
429 | ATOMIC_OT_clean,
430 | ATOMIC_OT_undo,
431 | ATOMIC_OT_smart_select,
432 | ATOMIC_OT_select_all,
433 | ATOMIC_OT_deselect_all
434 | ]
435 |
436 |
437 | def register():
438 | for item in reg_list:
439 | register_class(item)
440 |
441 |
442 | def unregister():
443 | for item in reg_list:
444 | unregister_class(item)
445 |
--------------------------------------------------------------------------------
/ops/missing_file_ops.py:
--------------------------------------------------------------------------------
1 | """
2 | Copyright (C) 2019 Remington Creative
3 |
4 | This file is part of Atomic Data Manager.
5 |
6 | Atomic Data Manager is free software: you can redistribute
7 | it and/or modify it under the terms of the GNU General Public License
8 | as published by the Free Software Foundation, either version 3 of the
9 | License, or (at your option) any later version.
10 |
11 | Atomic Data Manager is distributed in the hope that it will
12 | be useful, but WITHOUT ANY WARRANTY; without even the implied warranty
13 | of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
14 | General Public License for more details.
15 |
16 | You should have received a copy of the GNU General Public License along
17 | with Atomic Data Manager. If not, see .
18 |
19 | ---
20 |
21 | This file contains operations for missing file handling. This includes
22 | the option to reload, remove, replace, and search for these missing files.
23 |
24 | It also contains the post-reload report dialog that appears after
25 | attempting to reload missing project files.
26 |
27 | # TODO: implement missing file replace and search once Blender fixes the
28 | # TODO: bugs with the file chooser not opening from a dialog
29 |
30 | """
31 |
32 | import bpy
33 | from bpy.utils import register_class
34 | from bpy.utils import unregister_class
35 | from atomic_data_manager.stats import missing
36 | from atomic_data_manager.ui.utils import ui_layouts
37 |
38 |
39 | # Atomic Data Manager Reload Missing Files Operator
40 | class ATOMIC_OT_reload_missing(bpy.types.Operator):
41 | """Reload missing files"""
42 | bl_idname = "atomic.reload_missing"
43 | bl_label = "Reload Missing Files"
44 |
45 | def execute(self, context):
46 | # reload images
47 | for image in bpy.data.images:
48 | image.reload()
49 |
50 | # reload libraries
51 | for library in bpy.data.libraries:
52 | library.reload()
53 |
54 | # call reload report
55 | bpy.ops.atomic.reload_report('INVOKE_DEFAULT')
56 | return {'FINISHED'}
57 |
58 |
59 | # Atomic Data Manager Reload Missing Files Report Operator
60 | class ATOMIC_OT_reload_report(bpy.types.Operator):
61 | """Reload report for missing files"""
62 | bl_idname = "atomic.reload_report"
63 | bl_label = "Missing File Reload Report"
64 |
65 | def draw(self, context):
66 | layout = self.layout
67 | missing_images = missing.images()
68 | missing_libraries = missing.libraries()
69 |
70 | if missing_images or missing_libraries:
71 | row = layout.row()
72 | row.label(
73 | text="Atomic was unable to reload the following files:"
74 | )
75 |
76 | if missing_images:
77 | ui_layouts.box_list(
78 | layout=self.layout,
79 | items=missing_images,
80 | icon='IMAGE_DATA',
81 | columns=2
82 | )
83 |
84 | if missing_libraries:
85 | ui_layouts.box_list(
86 | layout=self.layout,
87 | items=missing_images,
88 | icon='LIBRARY_DATA_DIRECT',
89 | columns=2
90 | )
91 |
92 | else:
93 | row = layout.row()
94 | row.label(text="All files successfully reloaded!")
95 |
96 | row = layout.row() # extra space
97 |
98 | def execute(self, context):
99 | return {'FINISHED'}
100 |
101 | def invoke(self, context, event):
102 | wm = context.window_manager
103 | return wm.invoke_props_dialog(self)
104 |
105 |
106 | # Atomic Data Manager Remove Missing Files Operator
107 | class ATOMIC_OT_remove_missing(bpy.types.Operator):
108 | """Remove all missing files from this project"""
109 | bl_idname = "atomic.remove_missing"
110 | bl_label = "Remove Missing Files"
111 |
112 | def draw(self, context):
113 | layout = self.layout
114 | row = layout.row()
115 | row.label(text="Remove the following data-blocks?")
116 |
117 | ui_layouts.box_list(
118 | layout=layout,
119 | items=missing.images(),
120 | icon="IMAGE_DATA",
121 | columns=2
122 | )
123 |
124 | row = layout.row() # extra space
125 |
126 | def execute(self, context):
127 | for image_key in missing.images():
128 | bpy.data.images.remove(bpy.data.images[image_key])
129 |
130 | return {'FINISHED'}
131 |
132 | def invoke(self, context, event):
133 | wm = context.window_manager
134 | return wm.invoke_props_dialog(self)
135 |
136 |
137 | # TODO: Implement search for missing once file browser bugs are fixed
138 | # Atomic Data Manager Search for Missing Files Operator
139 | class ATOMIC_OT_search_missing(bpy.types.Operator):
140 | """Search a specified directory for the missing files"""
141 | bl_idname = "atomic.search_missing"
142 | bl_label = "Search for Missing Files"
143 |
144 | def draw(self, context):
145 | layout = self.layout
146 |
147 | row = layout.row()
148 | row.label(text="Unsupported Operation!")
149 |
150 | def execute(self, context):
151 | return {'FINISHED'}
152 |
153 | def invoke(self, context, event):
154 | wm = context.window_manager
155 | return wm.invoke_props_dialog(self)
156 |
157 |
158 | # TODO: Implement replace missing once file browser bugs are fixed
159 | # Atomic Data Manager Replace Missing Files Operator
160 | class ATOMIC_OT_replace_missing(bpy.types.Operator):
161 | """Replace each missing file with a new file"""
162 | bl_idname = "atomic.replace_missing"
163 | bl_label = "Replace Missing Files"
164 |
165 | def draw(self, context):
166 | layout = self.layout
167 |
168 | row = layout.row()
169 | row.label(text="Unsupported Operation!")
170 |
171 | def execute(self, context):
172 | return {'FINISHED'}
173 |
174 | def invoke(self, context, event):
175 | wm = context.window_manager
176 | return wm.invoke_props_dialog(self)
177 |
178 |
179 | reg_list = [
180 | ATOMIC_OT_reload_missing,
181 | ATOMIC_OT_reload_report,
182 | ATOMIC_OT_search_missing,
183 | ATOMIC_OT_replace_missing,
184 | ATOMIC_OT_remove_missing
185 | ]
186 |
187 |
188 | def register():
189 | for item in reg_list:
190 | register_class(item)
191 |
192 |
193 | def unregister():
194 | for item in reg_list:
195 | unregister_class(item)
196 |
--------------------------------------------------------------------------------
/ops/support_me_ops.py:
--------------------------------------------------------------------------------
1 | """
2 | Copyright (C) 2019 Remington Creative
3 |
4 | This file is part of Atomic Data Manager.
5 |
6 | Atomic Data Manager is free software: you can redistribute
7 | it and/or modify it under the terms of the GNU General Public License
8 | as published by the Free Software Foundation, either version 3 of the
9 | License, or (at your option) any later version.
10 |
11 | Atomic Data Manager is distributed in the hope that it will
12 | be useful, but WITHOUT ANY WARRANTY; without even the implied warranty
13 | of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
14 | General Public License for more details.
15 |
16 | You should have received a copy of the GNU General Public License along
17 | with Atomic Data Manager. If not, see .
18 |
19 |
20 | ---
21 |
22 | This file contains the operator for opening the Remington Creative
23 | support page in the web browser.
24 |
25 |
26 | """
27 |
28 | import bpy
29 | import webbrowser
30 | from bpy.utils import register_class
31 | from bpy.utils import unregister_class
32 |
33 |
34 | # Atomic Data Manager Open Support Me Operator
35 | class ATOMIC_OT_open_support_me(bpy.types.Operator):
36 | """Opens the Remington Creative \"Support Me\" webpage"""
37 | bl_idname = "atomic.open_support_me"
38 | bl_label = "Support Me"
39 |
40 | def execute(self, context):
41 | webbrowser.open("https://remingtoncreative.com/support/")
42 | return {'FINISHED'}
43 |
44 |
45 | reg_list = [ATOMIC_OT_open_support_me]
46 |
47 |
48 | def register():
49 | for cls in reg_list:
50 | register_class(cls)
51 |
52 |
53 | def unregister():
54 | for cls in reg_list:
55 | unregister_class(cls)
56 |
--------------------------------------------------------------------------------
/ops/utils/clean.py:
--------------------------------------------------------------------------------
1 | """
2 | Copyright (C) 2019 Remington Creative
3 |
4 | This file is part of Atomic Data Manager.
5 |
6 | Atomic Data Manager is free software: you can redistribute
7 | it and/or modify it under the terms of the GNU General Public License
8 | as published by the Free Software Foundation, either version 3 of the
9 | License, or (at your option) any later version.
10 |
11 | Atomic Data Manager is distributed in the hope that it will
12 | be useful, but WITHOUT ANY WARRANTY; without even the implied warranty
13 | of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
14 | General Public License for more details.
15 |
16 | You should have received a copy of the GNU General Public License along
17 | with Atomic Data Manager. If not, see .
18 |
19 | ---
20 |
21 | This file contains functions for cleaning out specific data categories.
22 |
23 | """
24 |
25 | import bpy
26 | from atomic_data_manager.stats import unused
27 |
28 |
29 | def collections():
30 | # removes all unused collections from the project
31 | for collection_key in unused.collections_deep():
32 | bpy.data.collections.remove(bpy.data.collections[collection_key])
33 |
34 |
35 | def images():
36 | # removes all unused images from the project
37 | for image_key in unused.images_deep():
38 | bpy.data.images.remove(bpy.data.images[image_key])
39 |
40 |
41 | def lights():
42 | # removes all unused lights from the project
43 | for light_key in unused.lights_deep():
44 | bpy.data.lights.remove(bpy.data.lights[light_key])
45 |
46 |
47 | def materials():
48 | # removes all unused materials from the project
49 | for light_key in unused.materials_deep():
50 | bpy.data.materials.remove(bpy.data.materials[light_key])
51 |
52 |
53 | def node_groups():
54 | # removes all unused node groups from the project
55 | for node_group_key in unused.node_groups_deep():
56 | bpy.data.node_groups.remove(bpy.data.node_groups[node_group_key])
57 |
58 |
59 | def particles():
60 | # removes all unused particle systems from the project
61 | for particle_key in unused.particles_deep():
62 | bpy.data.particles.remove(bpy.data.particles[particle_key])
63 |
64 |
65 | def textures():
66 | # removes all unused textures from the project
67 | for texture_key in unused.textures_deep():
68 | bpy.data.textures.remove(bpy.data.textures[texture_key])
69 |
70 |
71 | def worlds():
72 | # removes all unused worlds from the project
73 | for world_key in unused.worlds():
74 | bpy.data.worlds.remove(bpy.data.worlds[world_key])
75 |
--------------------------------------------------------------------------------
/ops/utils/delete.py:
--------------------------------------------------------------------------------
1 | """
2 | Copyright (C) 2019 Remington Creative
3 |
4 | This file is part of Atomic Data Manager.
5 |
6 | Atomic Data Manager is free software: you can redistribute
7 | it and/or modify it under the terms of the GNU General Public License
8 | as published by the Free Software Foundation, either version 3 of the
9 | License, or (at your option) any later version.
10 |
11 | Atomic Data Manager is distributed in the hope that it will
12 | be useful, but WITHOUT ANY WARRANTY; without even the implied warranty
13 | of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
14 | General Public License for more details.
15 |
16 | You should have received a copy of the GNU General Public License along
17 | with Atomic Data Manager. If not, see .
18 |
19 | ---
20 |
21 | This file contains functions for deleting individual data-blocks from
22 | Atomic's inspection inteface.
23 |
24 | """
25 |
26 | import bpy
27 |
28 |
29 | def delete_datablock(data, key):
30 | # deletes a specific data-block from a set of data
31 | data.remove(data[key])
32 |
33 |
34 | def collection(key):
35 | # removes a specific collection
36 | delete_datablock(bpy.data.collections, key)
37 |
38 |
39 | def image(key):
40 | # removes a specific image
41 | delete_datablock(bpy.data.images, key)
42 |
43 |
44 | def light(key):
45 | # removes a specific light
46 | delete_datablock(bpy.data.lights, key)
47 |
48 |
49 | def material(key):
50 | # removes a specific material
51 | delete_datablock(bpy.data.materials, key)
52 |
53 |
54 | def node_group(key):
55 | # removes a specific node group
56 | delete_datablock(bpy.data.node_groups, key)
57 |
58 |
59 | def particle(key):
60 | # removes a specific particle system
61 | delete_datablock(bpy.data.particles, key)
62 |
63 |
64 | def texture(key):
65 | # removes a specific texture
66 | delete_datablock(bpy.data.textures, key)
67 |
68 |
69 | def world(key):
70 | # removes a specific world
71 | delete_datablock(bpy.data.worlds, key)
72 |
--------------------------------------------------------------------------------
/ops/utils/duplicate.py:
--------------------------------------------------------------------------------
1 | """
2 | Copyright (C) 2019 Remington Creative
3 |
4 | This file is part of Atomic Data Manager.
5 |
6 | Atomic Data Manager is free software: you can redistribute
7 | it and/or modify it under the terms of the GNU General Public License
8 | as published by the Free Software Foundation, either version 3 of the
9 | License, or (at your option) any later version.
10 |
11 | Atomic Data Manager is distributed in the hope that it will
12 | be useful, but WITHOUT ANY WARRANTY; without even the implied warranty
13 | of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
14 | General Public License for more details.
15 |
16 | You should have received a copy of the GNU General Public License along
17 | with Atomic Data Manager. If not, see .
18 |
19 | ---
20 |
21 | This file contains functions for duplicating data-blocks from Atomic's
22 | inspection interface.
23 |
24 | """
25 |
26 | import bpy
27 |
28 |
29 | def duplicate_data(data, key):
30 | # creates a copy of the specified data-block and returns its key
31 | return data[key].copy().name
32 |
33 |
34 | def collection(key):
35 | # creates of copy of the specified collection and places it under the
36 | # scene collection
37 | collections = bpy.data.collections
38 | scene_collection = bpy.context.scene.collection
39 |
40 | copy_key = duplicate_data(collections, key)
41 | scene_collection.children.link(collections[copy_key])
42 | return copy_key
43 |
44 |
45 | def image(key):
46 | # creates of copy of the specified image
47 | return duplicate_data(bpy.data.images, key)
48 |
49 |
50 | def light(key):
51 | # creates of copy of the specified light
52 | return duplicate_data(bpy.data.lights, key)
53 |
54 |
55 | def material(key):
56 | # creates of copy of the specified material
57 | return duplicate_data(bpy.data.materials, key)
58 |
59 |
60 | def node_group(key):
61 | # creates of copy of the specified node group
62 | return duplicate_data(bpy.data.node_groups, key)
63 |
64 |
65 | def particle(key):
66 | # creates of copy of the specified particle
67 | return duplicate_data(bpy.data.particles, key)
68 |
69 |
70 | def texture(key):
71 | # creates of copy of the specified texture
72 | return duplicate_data(bpy.data.textures, key)
73 |
74 |
75 | def world(key):
76 | # creates of copy of the specified world
77 | return duplicate_data(bpy.data.worlds, key)
78 |
--------------------------------------------------------------------------------
/ops/utils/nuke.py:
--------------------------------------------------------------------------------
1 | """
2 | Copyright (C) 2019 Remington Creative
3 |
4 | This file is part of Atomic Data Manager.
5 |
6 | Atomic Data Manager is free software: you can redistribute
7 | it and/or modify it under the terms of the GNU General Public License
8 | as published by the Free Software Foundation, either version 3 of the
9 | License, or (at your option) any later version.
10 |
11 | Atomic Data Manager is distributed in the hope that it will
12 | be useful, but WITHOUT ANY WARRANTY; without even the implied warranty
13 | of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
14 | General Public License for more details.
15 |
16 | You should have received a copy of the GNU General Public License along
17 | with Atomic Data Manager. If not, see .
18 |
19 | ---
20 |
21 | This file contains functions for removing all data-blocks from specified
22 | data categories.
23 |
24 | """
25 |
26 | import bpy
27 |
28 |
29 | def nuke_data(data):
30 | # removes all data-blocks from the indicated set of data
31 | for key in data.keys():
32 | data.remove(data[key])
33 |
34 |
35 | def collections():
36 | # removes all collections from the project
37 | nuke_data(bpy.data.collections)
38 |
39 |
40 | def images():
41 | # removes all images from the project
42 | nuke_data(bpy.data.images)
43 |
44 |
45 | def lights():
46 | # removes all lights from the project
47 | nuke_data(bpy.data.lights)
48 |
49 |
50 | def materials():
51 | # removes all materials from the project
52 | nuke_data(bpy.data.materials)
53 |
54 |
55 | def node_groups():
56 | # removes all node groups from the project
57 | nuke_data(bpy.data.node_groups)
58 |
59 |
60 | def particles():
61 | # removes all particle systems from the project
62 | nuke_data(bpy.data.particles)
63 |
64 |
65 | def textures():
66 | # removes all textures from the project
67 | nuke_data(bpy.data.textures)
68 |
69 |
70 | def worlds():
71 | # removes all worlds from the project
72 | nuke_data(bpy.data.worlds)
73 |
--------------------------------------------------------------------------------
/stats/count.py:
--------------------------------------------------------------------------------
1 | """
2 | Copyright (C) 2019 Remington Creative
3 |
4 | This file is part of Atomic Data Manager.
5 |
6 | Atomic Data Manager is free software: you can redistribute
7 | it and/or modify it under the terms of the GNU General Public License
8 | as published by the Free Software Foundation, either version 3 of the
9 | License, or (at your option) any later version.
10 |
11 | Atomic Data Manager is distributed in the hope that it will
12 | be useful, but WITHOUT ANY WARRANTY; without even the implied warranty
13 | of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
14 | General Public License for more details.
15 |
16 | You should have received a copy of the GNU General Public License along
17 | with Atomic Data Manager. If not, see .
18 |
19 | ---
20 |
21 | This file contains functions that count quantities of various sets of data.
22 |
23 | """
24 |
25 | import bpy
26 | from atomic_data_manager.stats import unused
27 | from atomic_data_manager.stats import unnamed
28 | from atomic_data_manager.stats import missing
29 |
30 |
31 | def collections():
32 | # returns the number of collections in the project
33 |
34 | return len(bpy.data.collections)
35 |
36 |
37 | def collections_unused():
38 | # returns the number of unused collections in the project
39 |
40 | return len(unused.collections_shallow())
41 |
42 |
43 | def collections_unnamed():
44 | # returns the number of unnamed collections in the project
45 |
46 | return len(unnamed.collections())
47 |
48 |
49 | def images():
50 | # returns the number of images in the project
51 |
52 | return len(bpy.data.images)
53 |
54 |
55 | def images_unused():
56 | # returns the number of unused images in the project
57 |
58 | return len(unused.images_shallow())
59 |
60 |
61 | def images_unnamed():
62 | # returns the number of unnamed images in the project
63 |
64 | return len(unnamed.images())
65 |
66 |
67 | def images_missing():
68 | # returns the number of missing images in the project
69 |
70 | return len(missing.images())
71 |
72 |
73 | def lights():
74 | # returns the number of lights in the project
75 |
76 | return len(bpy.data.lights)
77 |
78 |
79 | def lights_unused():
80 | # returns the number of unused lights in the project
81 |
82 | return len(unused.lights_shallow())
83 |
84 |
85 | def lights_unnamed():
86 | # returns the number of unnamed lights in the project
87 |
88 | return len(unnamed.lights())
89 |
90 |
91 | def materials():
92 | # returns the number of materials in the project
93 |
94 | return len(bpy.data.materials)
95 |
96 |
97 | def materials_unused():
98 | # returns the number of unused materials in the project
99 |
100 | return len(unused.materials_shallow())
101 |
102 |
103 | def materials_unnamed():
104 | # returns the number of unnamed materials in the project
105 |
106 | return len(unnamed.materials())
107 |
108 |
109 | def node_groups():
110 | # returns the number of node groups in the project
111 |
112 | return len(bpy.data.node_groups)
113 |
114 |
115 | def node_groups_unused():
116 | # returns the number of unused node groups in the project
117 |
118 | return len(unused.node_groups_shallow())
119 |
120 |
121 | def node_groups_unnamed():
122 | # returns the number of unnamed node groups in the project
123 |
124 | return len(unnamed.node_groups())
125 |
126 |
127 | def objects():
128 | # returns the number of objects in the project
129 |
130 | return len(bpy.data.objects)
131 |
132 |
133 | def objects_unnamed():
134 | # returns the number of unnamed objects in the project
135 |
136 | return len(unnamed.objects())
137 |
138 |
139 | def particles():
140 | # returns the number of particles in the project
141 |
142 | return len(bpy.data.particles)
143 |
144 |
145 | def particles_unused():
146 | # returns the number of unused particles in the project
147 |
148 | return len(unused.particles_shallow())
149 |
150 |
151 | def particles_unnamed():
152 | # returns the number of unnamed particle systems in the project
153 |
154 | return len(unnamed.particles())
155 |
156 |
157 | def textures():
158 | # returns the number of textures in the project
159 |
160 | return len(bpy.data.textures)
161 |
162 |
163 | def textures_unused():
164 | # returns the number of unused textures in the project
165 |
166 | return len(unused.textures_shallow())
167 |
168 |
169 | def textures_unnamed():
170 | # returns the number of unnamed textures in the project
171 |
172 | return len(unnamed.textures())
173 |
174 |
175 | def worlds():
176 | # returns the number of worlds in the project
177 |
178 | return len(bpy.data.worlds)
179 |
180 |
181 | def worlds_unused():
182 | # returns the number of unused worlds in the project
183 |
184 | return len(unused.worlds())
185 |
186 |
187 | def worlds_unnamed():
188 | # returns the number of unnamed worlds in the project
189 |
190 | return len(unnamed.worlds())
191 |
--------------------------------------------------------------------------------
/stats/misc.py:
--------------------------------------------------------------------------------
1 | """
2 | Copyright (C) 2019 Remington Creative
3 |
4 | This file is part of Atomic Data Manager.
5 |
6 | Atomic Data Manager is free software: you can redistribute
7 | it and/or modify it under the terms of the GNU General Public License
8 | as published by the Free Software Foundation, either version 3 of the
9 | License, or (at your option) any later version.
10 |
11 | Atomic Data Manager is distributed in the hope that it will
12 | be useful, but WITHOUT ANY WARRANTY; without even the implied warranty
13 | of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
14 | General Public License for more details.
15 |
16 | You should have received a copy of the GNU General Public License along
17 | with Atomic Data Manager. If not, see .
18 |
19 | ---
20 |
21 | This file contains miscellaneous statistics functions.
22 |
23 | """
24 |
25 | import bpy
26 | import os
27 |
28 |
29 | def blend_size():
30 | # returns the size of the current Blender file as a string
31 |
32 | filepath = bpy.data.filepath
33 | size_bytes = os.stat(filepath).st_size if filepath != '' else -1
34 |
35 | kilobyte = 1024 # bytes
36 | megabyte = 1048576 # bytes
37 | gigabyte = 1073741824 # bytes
38 |
39 | if 0 <= size_bytes < kilobyte:
40 | size_scaled = "{:.1f} B".format(size_bytes)
41 | elif kilobyte <= size_bytes < megabyte:
42 | size_scaled = "{:.1f} KB".format(size_bytes / kilobyte)
43 | elif megabyte <= size_bytes < gigabyte:
44 | size_scaled = "{:.1f} MB".format(size_bytes / megabyte)
45 | elif size_bytes >= gigabyte:
46 | size_scaled = "{:.1f} GB".format(size_bytes / gigabyte)
47 | else:
48 | size_scaled = "No Data!"
49 |
50 | return size_scaled
51 |
--------------------------------------------------------------------------------
/stats/missing.py:
--------------------------------------------------------------------------------
1 | """
2 | Copyright (C) 2019 Remington Creative
3 |
4 | This file is part of Atomic Data Manager.
5 |
6 | Atomic Data Manager is free software: you can redistribute
7 | it and/or modify it under the terms of the GNU General Public License
8 | as published by the Free Software Foundation, either version 3 of the
9 | License, or (at your option) any later version.
10 |
11 | Atomic Data Manager is distributed in the hope that it will
12 | be useful, but WITHOUT ANY WARRANTY; without even the implied warranty
13 | of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
14 | General Public License for more details.
15 |
16 | You should have received a copy of the GNU General Public License along
17 | with Atomic Data Manager. If not, see .
18 |
19 | ---
20 |
21 | This file contains functions that detect missing files in the Blender
22 | project.
23 |
24 | """
25 |
26 | import bpy
27 | import os
28 |
29 |
30 | def get_missing(data):
31 | # returns a list of keys of unpacked data-blocks with non-existent
32 | # filepaths
33 |
34 | missing = []
35 |
36 | # list of keys that should not be flagged
37 | do_not_flag = ["Render Result", "Viewer Node", "D-NOISE Export"]
38 |
39 | for datablock in data:
40 |
41 | # the absolute path to our data-block
42 | abspath = bpy.path.abspath(datablock.filepath)
43 |
44 | # if data-block is not packed and has an invalid filepath
45 | if not datablock.packed_files and not os.path.isfile(abspath):
46 |
47 | # if data-block is not in our do not flag list
48 | # append it to the missing data list
49 | if datablock.name not in do_not_flag:
50 | missing.append(datablock.name)
51 |
52 | # if data-block is packed but it does not have a filepath
53 | elif datablock.packed_files and not abspath:
54 |
55 | # if data-block is not in our do not flag list
56 | # append it to the missing data list
57 | if datablock.name not in do_not_flag:
58 | missing.append(datablock.name)
59 |
60 | return missing
61 |
62 |
63 | def images():
64 | # returns a list of keys of images with a non-existent filepath
65 | return get_missing(bpy.data.images)
66 |
67 |
68 | def libraries():
69 | # returns a list of keys of libraries with a non-existent filepath
70 | return get_missing(bpy.data.libraries)
71 |
--------------------------------------------------------------------------------
/stats/unnamed.py:
--------------------------------------------------------------------------------
1 | """
2 | Copyright (C) 2019 Remington Creative
3 |
4 | This file is part of Atomic Data Manager.
5 |
6 | Atomic Data Manager is free software: you can redistribute
7 | it and/or modify it under the terms of the GNU General Public License
8 | as published by the Free Software Foundation, either version 3 of the
9 | License, or (at your option) any later version.
10 |
11 | Atomic Data Manager is distributed in the hope that it will
12 | be useful, but WITHOUT ANY WARRANTY; without even the implied warranty
13 | of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
14 | General Public License for more details.
15 |
16 | You should have received a copy of the GNU General Public License along
17 | with Atomic Data Manager. If not, see .
18 |
19 | ---
20 |
21 | This file contains functions that detect unnamed data-blocks in the
22 | Blender project.
23 |
24 | """
25 |
26 | import bpy
27 | import re
28 |
29 |
30 | def collections():
31 | # returns the keys of all unnamed collections in the project
32 | unnamed = []
33 |
34 | for collection in bpy.data.collections:
35 | if re.match(r'.*\.\d\d\d$', collection.name) or \
36 | collection.name.startswith("Collection"):
37 | unnamed.append(collection.name)
38 |
39 | return unnamed
40 |
41 |
42 | def images():
43 | # returns the keys of all unnamed images in the project
44 | unnamed = []
45 |
46 | for image in bpy.data.images:
47 | if re.match(r'.*\.\d\d\d$', image.name) or \
48 | image.name.startswith("Untitled"):
49 | unnamed.append(image.name)
50 |
51 | return unnamed
52 |
53 |
54 | def lights():
55 | # returns the keys of all unnamed lights in the project
56 | unnamed = []
57 |
58 | for light in bpy.data.lights:
59 | if re.match(r'.*\.\d\d\d$', light.name) or \
60 | light.name.startswith("Light"):
61 | unnamed.append(light.name)
62 |
63 | return unnamed
64 |
65 |
66 | def materials():
67 | # returns the keys of all unnamed materials in the project
68 | unnamed = []
69 |
70 | for material in bpy.data.lights:
71 | if re.match(r'.*\.\d\d\d$', material.name) or \
72 | material.name.startswith("Material"):
73 | unnamed.append(material.name)
74 |
75 | return unnamed
76 |
77 |
78 | def objects():
79 | # returns the keys of all unnamed materials in the project
80 | # NOTE: lists of default names must be tuples!
81 |
82 | # the default names all curve objects
83 | curve_names = (
84 | "BezierCircle",
85 | "BezierCurve",
86 | "NurbsCircle",
87 | "NurbsCurve",
88 | "NurbsPath"
89 | )
90 |
91 | # the default names of all grease pencil objects
92 | gpencil_names = (
93 | "GPencil",
94 | "Stroke"
95 | )
96 |
97 | # the default names of all light objects
98 | light_names = (
99 | "Area",
100 | "Light",
101 | "Point",
102 | "Spot",
103 | "Sun"
104 | )
105 |
106 | # the default names of all light probe objects
107 | lprobe_names = (
108 | "IrradianceVolume",
109 | "ReflectionCubemap",
110 | "ReflectionPlane"
111 | )
112 |
113 | # the default names of all mesh objects
114 | mesh_names = (
115 | "Circle",
116 | "Cone",
117 | "Cube",
118 | "Cylinder",
119 | "Grid",
120 | "Icosphere",
121 | "Plane",
122 | "Sphere",
123 | "Torus"
124 | )
125 |
126 | # the default names of all miscellaneous objects
127 | misc_names = (
128 | "Mball",
129 | "Text",
130 | "Armature",
131 | "Lattice",
132 | "Empty",
133 | "Camera",
134 | "Speaker",
135 | "Field"
136 | )
137 |
138 | # the default names of all nurbs objects
139 | nurbs_names = (
140 | "SurfCircle",
141 | "SurfCurve",
142 | "SurfPatch",
143 | "SurfTorus",
144 | "Surface"
145 | )
146 |
147 | # the default names of all objects compiled into one tuple
148 | default_obj_names = \
149 | curve_names + gpencil_names + light_names + lprobe_names + \
150 | mesh_names + misc_names + nurbs_names
151 |
152 | unnamed = []
153 |
154 | for obj in bpy.data.objects:
155 | if re.match(r'.*\.\d\d\d$', obj.name) or \
156 | obj.name.startswith(default_obj_names):
157 | unnamed.append(obj.name)
158 |
159 | return unnamed
160 |
161 |
162 | def node_groups():
163 | # returns the keys of all unnamed node groups in the project
164 | unnamed = []
165 |
166 | for node_group in bpy.data.node_groups:
167 | if re.match(r'.*\.\d\d\d$', node_group.name) or \
168 | node_group.name.startswith("NodeGroup"):
169 | unnamed.append(node_group.name)
170 |
171 | return unnamed
172 |
173 |
174 | def particles():
175 | # returns the keys of all unnamed particle systems in the project
176 | unnamed = []
177 |
178 | for particle in bpy.data.particles:
179 | if re.match(r'.*\.\d\d\d$', particle.name) or \
180 | particle.name.startswith("ParticleSettings"):
181 | unnamed.append(particle.name)
182 |
183 | return unnamed
184 |
185 |
186 | def textures():
187 | # returns the keys of all unnamed textures in the project
188 | unnamed = []
189 |
190 | for texture in bpy.data.textures:
191 | if re.match(r'.*\.\d\d\d$', texture.name) or \
192 | texture.name.startswith("Texture"):
193 | unnamed.append(texture.name)
194 |
195 | return unnamed
196 |
197 |
198 | def worlds():
199 | # returns the keys of all unnamed worlds in the project
200 | unnamed = []
201 |
202 | for world in bpy.data.worlds:
203 | if re.match(r'.*\.\d\d\d$', world.name) or \
204 | world.name.startswith("World"):
205 | unnamed.append(world.name)
206 |
207 | return unnamed
--------------------------------------------------------------------------------
/stats/unused.py:
--------------------------------------------------------------------------------
1 | """
2 | Copyright (C) 2019 Remington Creative
3 |
4 | This file is part of Atomic Data Manager.
5 |
6 | Atomic Data Manager is free software: you can redistribute
7 | it and/or modify it under the terms of the GNU General Public License
8 | as published by the Free Software Foundation, either version 3 of the
9 | License, or (at your option) any later version.
10 |
11 | Atomic Data Manager is distributed in the hope that it will
12 | be useful, but WITHOUT ANY WARRANTY; without even the implied warranty
13 | of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
14 | General Public License for more details.
15 |
16 | You should have received a copy of the GNU General Public License along
17 | with Atomic Data Manager. If not, see .
18 |
19 | ---
20 |
21 | This file contains functions that detect data-blocks that have no users,
22 | as determined by stats.users.py
23 |
24 | """
25 |
26 | import bpy
27 | from atomic_data_manager import config
28 | from atomic_data_manager.stats import users
29 |
30 |
31 | def shallow(data):
32 | # returns a list of keys of unused data-blocks in the data that may be
33 | # incomplete, but is significantly faster than doing a deep search
34 |
35 | unused = []
36 |
37 | for datablock in data:
38 |
39 | # if data-block has no users or if it has a fake user and
40 | # ignore fake users is enabled
41 | if datablock.users == 0 or (datablock.users == 1 and
42 | datablock.use_fake_user and
43 | config.include_fake_users):
44 | unused.append(datablock.name)
45 |
46 | return unused
47 |
48 |
49 | def collections_deep():
50 | # returns a full list of keys of unused collections
51 |
52 | unused = []
53 |
54 | for collection in bpy.data.collections:
55 | if not users.collection_all(collection.name):
56 | unused.append(collection.name)
57 |
58 | return unused
59 |
60 |
61 | def collections_shallow():
62 | # returns a list of keys of unused collections that may be
63 | # incomplete, but is significantly faster.
64 |
65 | unused = []
66 |
67 | for collection in bpy.data.collections:
68 | if not (collection.objects or collection.children):
69 | unused.append(collection.name)
70 |
71 | return unused
72 |
73 |
74 | def images_deep():
75 | # returns a full list of keys of unused images
76 |
77 | unused = []
78 |
79 | # a list of image keys that should not be flagged as unused
80 | # this list also exists in images_shallow()
81 | do_not_flag = ["Render Result", "Viewer Node", "D-NOISE Export"]
82 |
83 | for image in bpy.data.images:
84 | if not users.image_all(image.name):
85 |
86 | # check if image has a fake user or if ignore fake users
87 | # is enabled
88 | if not image.use_fake_user or config.include_fake_users:
89 |
90 | # if image is not in our do not flag list
91 | if image.name not in do_not_flag:
92 | unused.append(image.name)
93 |
94 | return unused
95 |
96 |
97 | def images_shallow():
98 | # returns a list of keys of unused images that may be
99 | # incomplete, but is significantly faster than doing a deep search
100 |
101 | unused_images = shallow(bpy.data.images)
102 |
103 | # a list of image keys that should not be flagged as unused
104 | # this list also exists in images_deep()
105 | do_not_flag = ["Render Result", "Viewer Node", "D-NOISE Export"]
106 |
107 | # remove do not flag keys from unused images
108 | for key in do_not_flag:
109 | if key in unused_images:
110 | unused_images.remove(key)
111 |
112 | return unused_images
113 |
114 |
115 | def lights_deep():
116 | # returns a list of keys of unused lights
117 |
118 | unused = []
119 |
120 | for light in bpy.data.lights:
121 | if not users.light_all(light.name):
122 |
123 | # check if light has a fake user or if ignore fake users
124 | # is enabled
125 | if not light.use_fake_user or config.include_fake_users:
126 | unused.append(light.name)
127 |
128 | return unused
129 |
130 |
131 | def lights_shallow():
132 | # returns a list of keys of unused lights that may be
133 | # incomplete, but is significantly faster than doing a deep search
134 |
135 | return shallow(bpy.data.lights)
136 |
137 |
138 | def materials_deep():
139 | # returns a list of keys of unused materials
140 |
141 | unused = []
142 |
143 | for material in bpy.data.materials:
144 | if not users.material_all(material.name):
145 |
146 | # check if material has a fake user or if ignore fake users
147 | # is enabled
148 | if not material.use_fake_user or config.include_fake_users:
149 | unused.append(material.name)
150 |
151 | return unused
152 |
153 |
154 | def materials_shallow():
155 | # returns a list of keys of unused material that may be
156 | # incomplete, but is significantly faster than doing a deep search
157 |
158 | return shallow(bpy.data.materials)
159 |
160 |
161 | def node_groups_deep():
162 | # returns a list of keys of unused node_groups
163 |
164 | unused = []
165 |
166 | for node_group in bpy.data.node_groups:
167 | if not users.node_group_all(node_group.name):
168 |
169 | # check if node group has a fake user or if ignore fake users
170 | # is enabled
171 | if not node_group.use_fake_user or config.include_fake_users:
172 | unused.append(node_group.name)
173 |
174 | return unused
175 |
176 |
177 | def node_groups_shallow():
178 | # returns a list of keys of unused node groups that may be
179 | # incomplete, but is significantly faster than doing a deep search
180 |
181 | return shallow(bpy.data.node_groups)
182 |
183 |
184 | def particles_deep():
185 | # returns a list of keys of unused particle systems
186 |
187 | unused = []
188 |
189 | for particle in bpy.data.particles:
190 | if not users.particle_all(particle.name):
191 |
192 | # check if particle system has a fake user or if ignore fake
193 | # users is enabled
194 | if not particle.use_fake_user or config.include_fake_users:
195 | unused.append(particle.name)
196 |
197 | return unused
198 |
199 |
200 | def particles_shallow():
201 | # returns a list of keys of unused particle systems that may be
202 | # incomplete, but is significantly faster than doing a deep search
203 |
204 | return shallow(bpy.data.particles)
205 |
206 |
207 | def textures_deep():
208 | # returns a list of keys of unused textures
209 |
210 | unused = []
211 |
212 | for texture in bpy.data.textures:
213 | if not users.texture_all(texture.name):
214 |
215 | # check if texture has a fake user or if ignore fake users
216 | # is enabled
217 | if not texture.use_fake_user or config.include_fake_users:
218 | unused.append(texture.name)
219 |
220 | return unused
221 |
222 |
223 | def textures_shallow():
224 | # returns a list of keys of unused textures that may be
225 | # incomplete, but is significantly faster than doing a deep search
226 |
227 | return shallow(bpy.data.textures)
228 |
229 |
230 | def worlds():
231 | # returns a full list of keys of unused worlds
232 |
233 | unused = []
234 |
235 | for world in bpy.data.worlds:
236 |
237 | # if data-block has no users or if it has a fake user and
238 | # ignore fake users is enabled
239 | if world.users == 0 or (world.users == 1 and
240 | world.use_fake_user and
241 | config.include_fake_users):
242 | unused.append(world.name)
243 |
244 | return unused
245 |
--------------------------------------------------------------------------------
/stats/users.py:
--------------------------------------------------------------------------------
1 | """
2 | Copyright (C) 2019 Remington Creative
3 |
4 | This file is part of Atomic Data Manager.
5 |
6 | Atomic Data Manager is free software: you can redistribute
7 | it and/or modify it under the terms of the GNU General Public License
8 | as published by the Free Software Foundation, either version 3 of the
9 | License, or (at your option) any later version.
10 |
11 | Atomic Data Manager is distributed in the hope that it will
12 | be useful, but WITHOUT ANY WARRANTY; without even the implied warranty
13 | of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
14 | General Public License for more details.
15 |
16 | You should have received a copy of the GNU General Public License along
17 | with Atomic Data Manager. If not, see .
18 |
19 | ---
20 |
21 | This file contains functions that return the keys of data-blocks that
22 | use other data-blocks.
23 |
24 | They are titled as such that the first part of the function name is the
25 | type of the data being passed in and the second part of the function name
26 | is the users of that type.
27 |
28 | e.g. If you were searching for all of the places where an image is used in
29 | a material would be searching for the image_materials() function.
30 |
31 | """
32 |
33 | import bpy
34 |
35 |
36 | def collection_all(collection_key):
37 | # returns a list of keys of every data-block that uses this collection
38 |
39 | return collection_cameras(collection_key) + \
40 | collection_children(collection_key) + \
41 | collection_lights(collection_key) + \
42 | collection_meshes(collection_key) + \
43 | collection_others(collection_key)
44 |
45 |
46 | def collection_cameras(collection_key):
47 | # recursively returns a list of camera object keys that are in the
48 | # collection and its child collections
49 |
50 | users = []
51 | collection = bpy.data.collections[collection_key]
52 |
53 | # append all camera objects in our collection
54 | for obj in collection.objects:
55 | if obj.type == 'CAMERA':
56 | users.append(obj.name)
57 |
58 | # list of all child collections in our collection
59 | children = collection_children(collection_key)
60 |
61 | # append all camera objects from the child collections
62 | for child in children:
63 | for obj in bpy.data.collections[child].objects:
64 | if obj.type == 'CAMERA':
65 | users.append(obj.name)
66 |
67 | return distinct(users)
68 |
69 |
70 | def collection_children(collection_key):
71 | # returns a list of all child collections under the specified
72 | # collection using recursive functions
73 |
74 | collection = bpy.data.collections[collection_key]
75 |
76 | children = collection_children_recursive(collection_key)
77 | children.remove(collection.name)
78 |
79 | return children
80 |
81 |
82 | def collection_children_recursive(collection_key):
83 | # recursively returns a list of all child collections under the
84 | # specified collection including the collection itself
85 |
86 | collection = bpy.data.collections[collection_key]
87 |
88 | # base case
89 | if not collection.children:
90 | return [collection.name]
91 |
92 | # recursion case
93 | else:
94 | children = []
95 | for child in collection.children:
96 | children += collection_children(child.name)
97 | children.append(collection.name)
98 | return children
99 |
100 |
101 | def collection_lights(collection_key):
102 | # returns a list of light object keys that are in the collection
103 |
104 | users = []
105 | collection = bpy.data.collections[collection_key]
106 |
107 | # append all light objects in our collection
108 | for obj in collection.objects:
109 | if obj.type == 'LIGHT':
110 | users.append(obj.name)
111 |
112 | # list of all child collections in our collection
113 | children = collection_children(collection_key)
114 |
115 | # append all light objects from the child collections
116 | for child in children:
117 | for obj in bpy.data.collections[child].objects:
118 | if obj.type == 'LIGHT':
119 | users.append(obj.name)
120 |
121 | return distinct(users)
122 |
123 |
124 | def collection_meshes(collection_key):
125 | # returns a list of mesh object keys that are in the collection
126 |
127 | users = []
128 | collection = bpy.data.collections[collection_key]
129 |
130 | # append all mesh objects in our collection and from child
131 | # collections
132 | for obj in collection.all_objects:
133 | if obj.type == 'MESH':
134 | users.append(obj.name)
135 |
136 | return distinct(users)
137 |
138 |
139 | def collection_others(collection_key):
140 | # returns a list of other object keys that are in the collection
141 | # NOTE: excludes cameras, lights, and meshes
142 |
143 | users = []
144 | collection = bpy.data.collections[collection_key]
145 |
146 | # object types to exclude from this search
147 | excluded_types = ['CAMERA', 'LIGHT', 'MESH']
148 |
149 | # append all other objects in our collection and from child
150 | # collections
151 | for obj in collection.all_objects:
152 | if obj.type not in excluded_types:
153 | users.append(obj.name)
154 |
155 | return distinct(users)
156 |
157 |
158 | def image_all(image_key):
159 | # returns a list of keys of every data-block that uses this image
160 |
161 | return image_compositors(image_key) + \
162 | image_materials(image_key) + \
163 | image_node_groups(image_key) + \
164 | image_textures(image_key) + \
165 | image_worlds(image_key)
166 |
167 |
168 | def image_compositors(image_key):
169 | # returns a list containing "Compositor" if the image is used in
170 | # the scene's compositor
171 |
172 | users = []
173 | image = bpy.data.images[image_key]
174 |
175 | # a list of node groups that use our image
176 | node_group_users = image_node_groups(image_key)
177 |
178 | # if our compositor uses nodes and has a valid node tree
179 | if bpy.context.scene.use_nodes and bpy.context.scene.node_tree:
180 |
181 | # check each node in the compositor
182 | for node in bpy.context.scene.node_tree.nodes:
183 |
184 | # if the node is an image node with a valid image
185 | if hasattr(node, 'image') and node.image:
186 |
187 | # if the node's image is our image
188 | if node.image.name == image.name:
189 | users.append("Compositor")
190 |
191 | # if the node is a group node with a valid node tree
192 | elif hasattr(node, 'node_tree') and node.node_tree:
193 |
194 | # if the node tree's name is in our list of node group
195 | # users
196 | if node.node_tree.name in node_group_users:
197 | users.append("Compositor")
198 |
199 | return distinct(users)
200 |
201 |
202 | def image_materials(image_key):
203 | # returns a list of material keys that use the image
204 |
205 | users = []
206 | image = bpy.data.images[image_key]
207 |
208 | # list of node groups that use this image
209 | node_group_users = image_node_groups(image_key)
210 |
211 | for mat in bpy.data.materials:
212 |
213 | # if material uses a valid node tree, check each node
214 | if mat.use_nodes and mat.node_tree:
215 | for node in mat.node_tree.nodes:
216 |
217 | # if node is has a not none image attribute
218 | if hasattr(node, 'image') and node.image:
219 |
220 | # if the nodes image is our image
221 | if node.image.name == image.name:
222 | users.append(mat.name)
223 |
224 | # if image in node in node group in node tree
225 | elif node.type == 'GROUP':
226 |
227 | # if node group has a valid node tree and is in our
228 | # list of node groups that use this image
229 | if node.node_tree and \
230 | node.node_tree.name in node_group_users:
231 | users.append(mat.name)
232 |
233 | return distinct(users)
234 |
235 |
236 | def image_node_groups(image_key):
237 | # returns a list of keys of node groups that use this image
238 |
239 | users = []
240 | image = bpy.data.images[image_key]
241 |
242 | # for each node group
243 | for node_group in bpy.data.node_groups:
244 |
245 | # if node group contains our image
246 | if node_group_has_image(node_group.name, image.name):
247 | users.append(node_group.name)
248 |
249 | return distinct(users)
250 |
251 |
252 | def image_textures(image_key):
253 | # returns a list of texture keys that use the image
254 |
255 | users = []
256 | image = bpy.data.images[image_key]
257 |
258 | # list of node groups that use this image
259 | node_group_users = image_node_groups(image_key)
260 |
261 | for texture in bpy.data.textures:
262 |
263 | # if texture uses a valid node tree, check each node
264 | if texture.use_nodes and texture.node_tree:
265 | for node in texture.node_tree.nodes:
266 |
267 | # check image nodes that use this image
268 | if hasattr(node, 'image') and node.image:
269 | if node.image.name == image.name:
270 | users.append(texture.name)
271 |
272 | # check for node groups that use this image
273 | elif hasattr(node, 'node_tree') and node.node_tree:
274 |
275 | # if node group is in our list of node groups that
276 | # use this image
277 | if node.node_tree.name in node_group_users:
278 | users.append(texture.name)
279 |
280 | # otherwise check the texture's image attribute
281 | else:
282 |
283 | # if texture uses an image
284 | if hasattr(texture, 'image') and texture.image:
285 |
286 | # if texture image is our image
287 | if texture.image.name == image.name:
288 | users.append(texture.name)
289 |
290 | return distinct(users)
291 |
292 |
293 | def image_worlds(image_key):
294 | # returns a list of world keys that use the image
295 |
296 | users = []
297 | image = bpy.data.images[image_key]
298 |
299 | # list of node groups that use this image
300 | node_group_users = image_node_groups(image_key)
301 |
302 | for world in bpy.data.worlds:
303 |
304 | # if world uses a valid node tree, check each node
305 | if world.use_nodes and world.node_tree:
306 | for node in world.node_tree.nodes:
307 |
308 | # check image nodes
309 | if hasattr(node, 'image') and node.image:
310 | if node.image.name == image.name:
311 | users.append(world.name)
312 |
313 | # check for node groups that use this image
314 | elif hasattr(node, 'node_tree') and node.node_tree:
315 | if node.node_tree.name in node_group_users:
316 | users.append(world.name)
317 |
318 | return distinct(users)
319 |
320 |
321 | def light_all(light_key):
322 | # returns a list of keys of every data-block that uses this light
323 |
324 | return light_objects(light_key)
325 |
326 |
327 | def light_objects(light_key):
328 | # returns a list of light object keys that use the light data
329 |
330 | users = []
331 | light = bpy.data.lights[light_key]
332 |
333 | for obj in bpy.data.objects:
334 | if obj.type == 'LIGHT' and obj.data:
335 | if obj.data.name == light.name:
336 | users.append(obj.name)
337 |
338 | return distinct(users)
339 |
340 |
341 | def material_all(material_key):
342 | # returns a list of keys of every data-block that uses this material
343 |
344 | return material_objects(material_key)
345 |
346 |
347 | def material_objects(material_key):
348 | # returns a list of object keys that use this material
349 |
350 | users = []
351 | material = bpy.data.materials[material_key]
352 |
353 | for obj in bpy.data.objects:
354 |
355 | # if the object has the option to add materials
356 | if hasattr(obj, 'material_slots'):
357 |
358 | # for each material slot
359 | for slot in obj.material_slots:
360 |
361 | # if material slot has a valid material and it is our
362 | # material
363 | if slot.material and slot.material.name == material.name:
364 | users.append(obj.name)
365 |
366 | return distinct(users)
367 |
368 |
369 | def node_group_all(node_group_key):
370 | # returns a list of keys of every data-block that uses this node group
371 |
372 | return node_group_compositors(node_group_key) + \
373 | node_group_materials(node_group_key) + \
374 | node_group_node_groups(node_group_key) + \
375 | node_group_textures(node_group_key) + \
376 | node_group_worlds(node_group_key)
377 |
378 |
379 | def node_group_compositors(node_group_key):
380 | # returns a list containing "Compositor" if the node group is used in
381 | # the scene's compositor
382 |
383 | users = []
384 | node_group = bpy.data.node_groups[node_group_key]
385 |
386 | # a list of node groups that use our node group
387 | node_group_users = node_group_node_groups(node_group_key)
388 |
389 | # if our compositor uses nodes and has a valid node tree
390 | if bpy.context.scene.use_nodes and bpy.context.scene.node_tree:
391 |
392 | # check each node in the compositor
393 | for node in bpy.context.scene.node_tree.nodes:
394 |
395 | # if the node is a group and has a valid node tree
396 | if hasattr(node, 'node_tree') and node.node_tree:
397 |
398 | # if the node group is our node group
399 | if node.node_tree.name == node_group.name:
400 | users.append("Compositor")
401 |
402 | # if the node group is in our list of node group users
403 | if node.node_tree.name in node_group_users:
404 | users.append("Compositor")
405 |
406 | return distinct(users)
407 |
408 |
409 | def node_group_materials(node_group_key):
410 | # returns a list of material keys that use the node group in their
411 | # node trees
412 |
413 | users = []
414 | node_group = bpy.data.node_groups[node_group_key]
415 |
416 | # node groups that use this node group
417 | node_group_users = node_group_node_groups(node_group_key)
418 |
419 | for material in bpy.data.materials:
420 |
421 | # if material uses nodes and has a valid node tree, check each node
422 | if material.use_nodes and material.node_tree:
423 | for node in material.node_tree.nodes:
424 |
425 | # if node is a group node
426 | if hasattr(node, 'node_tree') and node.node_tree:
427 |
428 | # if node is the node group
429 | if node.node_tree.name == node_group.name:
430 | users.append(material.name)
431 |
432 | # if node is using a node group contains our node group
433 | if node.node_tree.name in node_group_users:
434 | users.append(material.name)
435 |
436 | return distinct(users)
437 |
438 |
439 | def node_group_node_groups(node_group_key):
440 | # returns a list of all node groups that use this node group in
441 | # their node tree
442 |
443 | users = []
444 | node_group = bpy.data.node_groups[node_group_key]
445 |
446 | # for each search group
447 | for search_group in bpy.data.node_groups:
448 |
449 | # if the search group contains our node group
450 | if node_group_has_node_group(
451 | search_group.name, node_group.name):
452 | users.append(search_group.name)
453 |
454 | return distinct(users)
455 |
456 |
457 | def node_group_textures(node_group_key):
458 | # returns a list of texture keys that use this node group in their
459 | # node trees
460 |
461 | users = []
462 | node_group = bpy.data.node_groups[node_group_key]
463 |
464 | # list of node groups that use this node group
465 | node_group_users = node_group_node_groups(node_group_key)
466 |
467 | for texture in bpy.data.textures:
468 |
469 | # if texture uses a valid node tree, check each node
470 | if texture.use_nodes and texture.node_tree:
471 | for node in texture.node_tree.nodes:
472 |
473 | # check if node is a node group and has a valid node tree
474 | if hasattr(node, 'node_tree') and node.node_tree:
475 |
476 | # if node is our node group
477 | if node.node_tree.name == node_group.name:
478 | users.append(texture.name)
479 |
480 | # if node is a node group that contains our node group
481 | if node.node_tree.name in node_group_users:
482 | users.append(texture.name)
483 |
484 | return distinct(users)
485 |
486 |
487 | def node_group_worlds(node_group_key):
488 | # returns a list of world keys that use the node group in their node
489 | # trees
490 |
491 | users = []
492 | node_group = bpy.data.node_groups[node_group_key]
493 |
494 | # node groups that use this node group
495 | node_group_users = node_group_node_groups(node_group_key)
496 |
497 | for world in bpy.data.worlds:
498 |
499 | # if world uses nodes and has a valid node tree
500 | if world.use_nodes and world.node_tree:
501 | for node in world.node_tree.nodes:
502 |
503 | # if node is a node group and has a valid node tree
504 | if hasattr(node, 'node_tree') and node.node_tree:
505 |
506 | # if this node is our node group
507 | if node.node_tree.name == node_group.name:
508 | users.append(world.name)
509 |
510 | # if this node is one of the node groups that use
511 | # our node group
512 | elif node.node_tree.name in node_group_users:
513 | users.append(world.name)
514 |
515 | return distinct(users)
516 |
517 |
518 | def node_group_has_image(node_group_key, image_key):
519 | # recursively returns true if the node group contains this image
520 | # directly or if it contains a node group a node group that contains
521 | # the image indirectly
522 |
523 | has_image = False
524 | node_group = bpy.data.node_groups[node_group_key]
525 | image = bpy.data.images[image_key]
526 |
527 | # for each node in our search group
528 | for node in node_group.nodes:
529 |
530 | # base case
531 | # if node has a not none image attribute
532 | if hasattr(node, 'image') and node.image:
533 |
534 | # if the node group is our node group
535 | if node.image.name == image.name:
536 | has_image = True
537 |
538 | # recurse case
539 | # if node is a node group and has a valid node tree
540 | elif hasattr(node, 'node_tree') and node.node_tree:
541 | has_image = node_group_has_image(
542 | node.node_tree.name, image.name)
543 |
544 | # break the loop if the image is found
545 | if has_image:
546 | break
547 |
548 | return has_image
549 |
550 |
551 | def node_group_has_node_group(search_group_key, node_group_key):
552 | # returns true if a node group contains this node group
553 |
554 | has_node_group = False
555 | search_group = bpy.data.node_groups[search_group_key]
556 | node_group = bpy.data.node_groups[node_group_key]
557 |
558 | # for each node in our search group
559 | for node in search_group.nodes:
560 |
561 | # if node is a node group and has a valid node tree
562 | if hasattr(node, 'node_tree') and node.node_tree:
563 |
564 | if node.node_tree.name == "RG_MetallicMap":
565 | print(node.node_tree.name)
566 | print(node_group.name)
567 |
568 | # base case
569 | # if node group is our node group
570 | if node.node_tree.name == node_group.name:
571 | has_node_group = True
572 |
573 | # recurse case
574 | # if node group is any other node group
575 | else:
576 | has_node_group = node_group_has_node_group(
577 | node.node_tree.name, node_group.name)
578 |
579 | # break the loop if the node group is found
580 | if has_node_group:
581 | break
582 |
583 | return has_node_group
584 |
585 |
586 | def node_group_has_texture(node_group_key, texture_key):
587 | # returns true if a node group contains this image
588 |
589 | has_texture = False
590 | node_group = bpy.data.node_groups[node_group_key]
591 | texture = bpy.data.textures[texture_key]
592 |
593 | # for each node in our search group
594 | for node in node_group.nodes:
595 |
596 | # base case
597 | # if node has a not none image attribute
598 | if hasattr(node, 'texture') and node.texture:
599 |
600 | # if the node group is our node group
601 | if node.texture.name == texture.name:
602 | has_texture = True
603 |
604 | # recurse case
605 | # if node is a node group and has a valid node tree
606 | elif hasattr(node, 'node_tree') and node.node_tree:
607 | has_texture = node_group_has_texture(
608 | node.node_tree.name, texture.name)
609 |
610 | # break the loop if the texture is found
611 | if has_texture:
612 | break
613 |
614 | return has_texture
615 |
616 |
617 | def particle_all(particle_key):
618 | # returns a list of keys of every data-block that uses this particle
619 | # system
620 |
621 | return particle_objects(particle_key)
622 |
623 |
624 | def particle_objects(particle_key):
625 | # returns a list of object keys that use the particle system
626 |
627 | users = []
628 | particle_system = bpy.data.particles[particle_key]
629 |
630 | for obj in bpy.data.objects:
631 |
632 | # if object can have a particle system
633 | if hasattr(obj, 'particle_systems'):
634 | for particle in obj.particle_systems:
635 |
636 | # if particle settings is our particle system
637 | if particle.settings.name == particle_system.name:
638 | users.append(obj.name)
639 |
640 | return distinct(users)
641 |
642 |
643 | def texture_all(texture_key):
644 | # returns a list of keys of every data-block that uses this texture
645 |
646 | return texture_brushes(texture_key) + \
647 | texture_compositor(texture_key) + \
648 | texture_objects(texture_key) + \
649 | texture_node_groups(texture_key) + \
650 | texture_particles(texture_key)
651 |
652 |
653 | def texture_brushes(texture_key):
654 | # returns a list of brush keys that use the texture
655 |
656 | users = []
657 | texture = bpy.data.textures[texture_key]
658 |
659 | for brush in bpy.data.brushes:
660 |
661 | # if brush has a texture
662 | if brush.texture:
663 |
664 | # if brush texture is our texture
665 | if brush.texture.name == texture.name:
666 | users.append(brush.name)
667 |
668 | return distinct(users)
669 |
670 |
671 | def texture_compositor(texture_key):
672 | # returns a list containing "Compositor" if the texture is used in
673 | # the scene's compositor
674 |
675 | users = []
676 | texture = bpy.data.textures[texture_key]
677 |
678 | # a list of node groups that use our image
679 | node_group_users = texture_node_groups(texture_key)
680 |
681 | # if our compositor uses nodes and has a valid node tree
682 | if bpy.context.scene.use_nodes and bpy.context.scene.node_tree:
683 |
684 | # check each node in the compositor
685 | for node in bpy.context.scene.node_tree.nodes:
686 |
687 | # if the node is an texture node with a valid texture
688 | if hasattr(node, 'texture') and node.texture:
689 |
690 | # if the node's texture is our texture
691 | if node.texture.name == texture.name:
692 | users.append("Compositor")
693 |
694 | # if the node is a group node with a valid node tree
695 | elif hasattr(node, 'node_tree') and node.node_tree:
696 |
697 | # if the node tree's name is in our list of node group
698 | # users
699 | if node.node_tree.name in node_group_users:
700 | users.append("Compositor")
701 |
702 | return distinct(users)
703 |
704 |
705 | def texture_objects(texture_key):
706 | # returns a list of object keys that use the texture in one of their
707 | # modifiers
708 |
709 | users = []
710 | texture = bpy.data.textures[texture_key]
711 |
712 | # list of particle systems that use our texture
713 | particle_users = texture_particles(texture_key)
714 |
715 | # append objects that use the texture in a modifier
716 | for obj in bpy.data.objects:
717 |
718 | # if object can have modifiers applied to it
719 | if hasattr(obj, 'modifiers'):
720 | for modifier in obj.modifiers:
721 |
722 | # if the modifier has a texture attribute that is not None
723 | if hasattr(modifier, 'texture') \
724 | and modifier.texture:
725 | if modifier.texture.name == texture.name:
726 | users.append(obj.name)
727 |
728 | # if the modifier has a mask_texture attribute that is
729 | # not None
730 | elif hasattr(modifier, 'mask_texture') \
731 | and modifier.mask_texture:
732 | if modifier.mask_texture.name == texture.name:
733 | users.append(obj.name)
734 |
735 | # append objects that use the texture in a particle system
736 | for particle in particle_users:
737 |
738 | # append all objects that use the particle system
739 | users += particle_objects(particle)
740 |
741 | return distinct(users)
742 |
743 |
744 | def texture_node_groups(texture_key):
745 | # returns a list of keys of all node groups that use this texture
746 |
747 | users = []
748 | texture = bpy.data.textures[texture_key]
749 |
750 | # for each node group
751 | for node_group in bpy.data.node_groups:
752 |
753 | # if node group contains our texture
754 | if node_group_has_texture(
755 | node_group.name, texture.name):
756 | users.append(node_group.name)
757 |
758 | return distinct(users)
759 |
760 |
761 | def texture_particles(texture_key):
762 | # returns a list of particle system keys that use the texture in
763 | # their texture slots
764 |
765 | users = []
766 | texture = bpy.data.textures[texture_key]
767 |
768 | for particle in bpy.data.particles:
769 |
770 | # for each texture slot in the particle system
771 | for texture_slot in particle.texture_slots:
772 |
773 | # if texture slot has a texture that is not None
774 | if hasattr(texture_slot, 'texture') and texture_slot.texture:
775 |
776 | # if texture in texture slot is our texture
777 | if texture_slot.texture.name == texture.name:
778 | users.append(particle.name)
779 |
780 | return distinct(users)
781 |
782 |
783 | def distinct(seq):
784 | # returns a list of distinct elements
785 |
786 | return list(set(seq))
787 |
--------------------------------------------------------------------------------
/ui/__init__.py:
--------------------------------------------------------------------------------
1 | """
2 | Copyright (C) 2019 Remington Creative
3 |
4 | This file is part of Atomic Data Manager.
5 |
6 | Atomic Data Manager is free software: you can redistribute
7 | it and/or modify it under the terms of the GNU General Public License
8 | as published by the Free Software Foundation, either version 3 of the
9 | License, or (at your option) any later version.
10 |
11 | Atomic Data Manager is distributed in the hope that it will
12 | be useful, but WITHOUT ANY WARRANTY; without even the implied warranty
13 | of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
14 | General Public License for more details.
15 |
16 | You should have received a copy of the GNU General Public License along
17 | with Atomic Data Manager. If not, see .
18 |
19 | ---
20 |
21 | This file handles the registration of the atomic_data_manager.ui package
22 |
23 | """
24 |
25 | from atomic_data_manager.ui import main_panel_ui
26 | from atomic_data_manager.ui import stats_panel_ui
27 | from atomic_data_manager.ui import inspect_ui
28 | from atomic_data_manager.ui import missing_file_ui
29 | from atomic_data_manager.ui import missing_file_ui
30 | from atomic_data_manager.ui import pie_menu_ui
31 | from atomic_data_manager.ui import preferences_ui
32 | from atomic_data_manager.ui import support_me_ui
33 |
34 |
35 | def register():
36 | # register preferences first so we can access variables in config.py
37 | preferences_ui.register()
38 |
39 | # register everything else
40 | main_panel_ui.register()
41 | stats_panel_ui.register()
42 | inspect_ui.register()
43 | missing_file_ui.register()
44 | pie_menu_ui.register()
45 | support_me_ui.register()
46 |
47 |
48 | def unregister():
49 | main_panel_ui.unregister()
50 | stats_panel_ui.unregister()
51 | inspect_ui.unregister()
52 | missing_file_ui.unregister()
53 | pie_menu_ui.unregister()
54 | preferences_ui.unregister()
55 | support_me_ui.unregister()
56 |
--------------------------------------------------------------------------------
/ui/inspect_ui.py:
--------------------------------------------------------------------------------
1 | """
2 | Copyright (C) 2019 Remington Creative
3 |
4 | This file is part of Atomic Data Manager.
5 |
6 | Atomic Data Manager is free software: you can redistribute
7 | it and/or modify it under the terms of the GNU General Public License
8 | as published by the Free Software Foundation, either version 3 of the
9 | License, or (at your option) any later version.
10 |
11 | Atomic Data Manager is distributed in the hope that it will
12 | be useful, but WITHOUT ANY WARRANTY; without even the implied warranty
13 | of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
14 | General Public License for more details.
15 |
16 | You should have received a copy of the GNU General Public License along
17 | with Atomic Data Manager. If not, see .
18 |
19 | ---
20 |
21 | This file contains the inspection user interface.
22 |
23 | """
24 |
25 | import bpy
26 | from bpy.utils import register_class
27 | from bpy.utils import unregister_class
28 | from atomic_data_manager.stats import users
29 | from atomic_data_manager.ui.utils import ui_layouts
30 |
31 |
32 | # bool that triggers an inspection update if it is True when the
33 | # inspection's draw() method is called
34 | inspection_update_trigger = False
35 |
36 |
37 | def update_inspection(self, context):
38 | global inspection_update_trigger
39 | inspection_update_trigger = True
40 |
41 |
42 | # Atomic Data Manager Inspect Collections UI Operator
43 | class ATOMIC_OT_inspect_collections(bpy.types.Operator):
44 | """Inspect Collections"""
45 | bl_idname = "atomic.inspect_collections"
46 | bl_label = "Inspect Collections"
47 |
48 | # user lists
49 | users_meshes = []
50 | users_lights = []
51 | users_cameras = []
52 | users_others = []
53 | users_children = []
54 |
55 | def draw(self, context):
56 | global inspection_update_trigger
57 | atom = bpy.context.scene.atomic
58 |
59 | layout = self.layout
60 |
61 | # inspect collections box list
62 | ui_layouts.inspect_header(
63 | layout=layout,
64 | atom_prop="collections_field",
65 | data="collections"
66 | )
67 |
68 | # inspection update code
69 | if inspection_update_trigger:
70 |
71 | # if key is valid, update the user lists
72 | if atom.collections_field in bpy.data.collections.keys():
73 | self.users_meshes = \
74 | users.collection_meshes(atom.collections_field)
75 | self.users_lights = \
76 | users.collection_lights(atom.collections_field)
77 | self.users_cameras = \
78 | users.collection_cameras(atom.collections_field)
79 | self.users_others = \
80 | users.collection_others(atom.collections_field)
81 | self.users_children = \
82 | users.collection_children(atom.collections_field)
83 |
84 | # if key is invalid, empty the user lists
85 | else:
86 | self.users_meshes = []
87 | self.users_lights = []
88 | self.users_cameras = []
89 | self.users_others = []
90 | self.users_children = []
91 |
92 | inspection_update_trigger = False
93 |
94 | # mesh box list
95 | ui_layouts.box_list(
96 | layout=layout,
97 | title="Meshes",
98 | items=self.users_meshes,
99 | icon="OUTLINER_OB_MESH"
100 | )
101 |
102 | # light box list
103 | ui_layouts.box_list(
104 | layout=layout,
105 | title="Lights",
106 | items=self.users_lights,
107 | icon="OUTLINER_OB_LIGHT"
108 | )
109 |
110 | # camera box list
111 | ui_layouts.box_list(
112 | layout=layout,
113 | title="Cameras",
114 | items=self.users_cameras,
115 | icon="OUTLINER_OB_CAMERA"
116 | )
117 |
118 | # other objects box list
119 | ui_layouts.box_list_diverse(
120 | layout=layout,
121 | title="Other",
122 | items=self.users_others
123 | )
124 |
125 | # child collections box list
126 | ui_layouts.box_list(
127 | layout=layout,
128 | title="Child Collections",
129 | items=self.users_children,
130 | icon="OUTLINER_OB_GROUP_INSTANCE"
131 | )
132 |
133 | row = layout.row() # extra row for spacing
134 |
135 | def execute(self, context):
136 | return {'FINISHED'}
137 |
138 | def invoke(self, context, event):
139 | # update inspection context
140 | atom = bpy.context.scene.atomic
141 | atom.active_inspection = "COLLECTIONS"
142 |
143 | # trigger update on invoke
144 | global inspection_update_trigger
145 | inspection_update_trigger = True
146 |
147 | # invoke inspect dialog
148 | wm = context.window_manager
149 | return wm.invoke_props_dialog(self)
150 |
151 |
152 | # Atomic Data Manager Inspect Images UI Operator
153 | class ATOMIC_OT_inspect_images(bpy.types.Operator):
154 | """Inspect Images"""
155 | bl_idname = "atomic.inspect_images"
156 | bl_label = "Inspect Images"
157 |
158 | # user lists
159 | users_compositors = []
160 | users_materials = []
161 | users_node_groups = []
162 | users_textures = []
163 | users_worlds = []
164 |
165 | def draw(self, context):
166 | global inspection_update_trigger
167 | atom = bpy.context.scene.atomic
168 |
169 | layout = self.layout
170 |
171 | # inspect images header
172 | ui_layouts.inspect_header(
173 | layout=layout,
174 | atom_prop="images_field",
175 | data="images"
176 | )
177 |
178 | # inspection update code
179 | if inspection_update_trigger:
180 |
181 | # if key is valid, update the user lists
182 | if atom.images_field in bpy.data.images.keys():
183 | self.users_compositors = \
184 | users.image_compositors(atom.images_field)
185 | self.users_materials = \
186 | users.image_materials(atom.images_field)
187 | self.users_node_groups = \
188 | users.image_node_groups(atom.images_field)
189 | self.users_textures = \
190 | users.image_textures(atom.images_field)
191 | self.users_worlds = \
192 | users.image_worlds(atom.images_field)
193 |
194 | # if key is invalid, empty the user lists
195 | else:
196 | self.users_compositors = []
197 | self.users_materials = []
198 | self.users_node_groups = []
199 | self.users_textures = []
200 | self.users_worlds = []
201 |
202 | inspection_update_trigger = False
203 |
204 | # compositors box list
205 | ui_layouts.box_list(
206 | layout=layout,
207 | title="Compositors",
208 | items=self.users_compositors,
209 | icon="NODE_COMPOSITING"
210 | )
211 |
212 | # materials box list
213 | ui_layouts.box_list(
214 | layout=layout,
215 | title="Materials",
216 | items=self.users_materials,
217 | icon="MATERIAL"
218 | )
219 |
220 | # node groups box list
221 | ui_layouts.box_list(
222 | layout=layout,
223 | title="Node Groups",
224 | items=self.users_node_groups,
225 | icon="NODETREE"
226 | )
227 |
228 | # textures box list
229 | ui_layouts.box_list(
230 | layout=layout,
231 | title="Textures",
232 | items=self.users_textures,
233 | icon="TEXTURE"
234 | )
235 |
236 | # worlds box list
237 | ui_layouts.box_list(
238 | layout=layout,
239 | title="Worlds",
240 | items=self.users_worlds,
241 | icon="WORLD"
242 | )
243 |
244 | row = layout.row() # extra row for spacing
245 |
246 | def execute(self, context):
247 | return {'FINISHED'}
248 |
249 | def invoke(self, context, event):
250 | # update inspection context
251 | atom = bpy.context.scene.atomic
252 | atom.active_inspection = "IMAGES"
253 |
254 | # trigger update on invoke
255 | global inspection_update_trigger
256 | inspection_update_trigger = True
257 |
258 | # invoke inspect dialog
259 | wm = context.window_manager
260 | return wm.invoke_props_dialog(self)
261 |
262 | # Atomic Data Manager Inspect Lights UI Operator
263 | class ATOMIC_OT_inspect_lights(bpy.types.Operator):
264 | """Inspect Lights"""
265 | bl_idname = "atomic.inspect_lights"
266 | bl_label = "Inspect Lights"
267 |
268 | # user lists
269 | users_objects = []
270 |
271 | def draw(self, context):
272 | global inspection_update_trigger
273 | atom = bpy.context.scene.atomic
274 |
275 | layout = self.layout
276 |
277 | # inspect lights header
278 | ui_layouts.inspect_header(
279 | layout=layout,
280 | atom_prop="lights_field",
281 | data="lights"
282 | )
283 |
284 | # inspection update code
285 | if inspection_update_trigger:
286 | # if key is valid, update the user lists
287 | if atom.lights_field in bpy.data.lights.keys():
288 | self.users_objects = users.light_objects(atom.lights_field)
289 |
290 | # if key is invalid, empty the user lists
291 | else:
292 | self.users_objects = []
293 |
294 | inspection_update_trigger = False
295 |
296 | # light objects box list
297 | ui_layouts.box_list(
298 | layout=layout,
299 | title="Light Objects",
300 | items=self.users_objects,
301 | icon="OUTLINER_OB_LIGHT"
302 | )
303 |
304 | row = layout.row() # extra row for spacing
305 |
306 | def execute(self, context):
307 | return {'FINISHED'}
308 |
309 | def invoke(self, context, event):
310 | # update inspection context
311 | atom = bpy.context.scene.atomic
312 | atom.active_inspection = "LIGHTS"
313 |
314 | # trigger update on invoke
315 | global inspection_update_trigger
316 | inspection_update_trigger = True
317 |
318 | # invoke inspect dialog
319 | wm = context.window_manager
320 | return wm.invoke_props_dialog(self)
321 |
322 |
323 | # Atomic Data Manager Inspect Materials UI Operator
324 | class ATOMIC_OT_inspect_materials(bpy.types.Operator):
325 | """Inspect Materials"""
326 | bl_idname = "atomic.inspect_materials"
327 | bl_label = "Inspect Materials"
328 |
329 | # user lists
330 | users_objects = []
331 |
332 | def draw(self, context):
333 | global inspection_update_trigger
334 | atom = bpy.context.scene.atomic
335 |
336 | layout = self.layout
337 |
338 | # inspect materials header
339 | ui_layouts.inspect_header(
340 | layout=layout,
341 | atom_prop="materials_field",
342 | data="materials"
343 | )
344 |
345 | # inspection update code
346 | if inspection_update_trigger:
347 |
348 | # if key is valid, update the user lists
349 | if atom.materials_field in bpy.data.materials.keys():
350 | self.users_objects = \
351 | users.material_objects(atom.materials_field)
352 |
353 | # if key is invalid, empty the user lists
354 | else:
355 | self.users_objects = []
356 |
357 | inspection_update_trigger = False
358 |
359 | # objects box list
360 | ui_layouts.box_list_diverse(
361 | layout=layout,
362 | title="Objects",
363 | items=self.users_objects
364 | )
365 |
366 | row = layout.row() # extra row for spacing
367 |
368 | def execute(self, context):
369 | return {'FINISHED'}
370 |
371 | def invoke(self, context, event):
372 | # update inspection context
373 | atom = bpy.context.scene.atomic
374 | atom.active_inspection = "MATERIALS"
375 |
376 | # trigger update on invoke
377 | global inspection_update_trigger
378 | inspection_update_trigger = True
379 |
380 | # invoke inspect dialog
381 | wm = context.window_manager
382 | return wm.invoke_props_dialog(self)
383 |
384 |
385 | # Atomic Data Manager Inspect Node Groups UI Operator
386 | class ATOMIC_OT_inspect_node_groups(bpy.types.Operator):
387 | """Inspect Node Groups"""
388 | bl_idname = "atomic.inspect_node_groups"
389 | bl_label = "Inspect Node Groups"
390 |
391 | # user lists
392 | users_compositors = []
393 | users_materials = []
394 | users_node_groups = []
395 | users_textures = []
396 | users_worlds = []
397 |
398 | def draw(self, context):
399 | global inspection_update_trigger
400 | atom = bpy.context.scene.atomic
401 |
402 | layout = self.layout
403 |
404 | # inspect node groups header
405 | ui_layouts.inspect_header(
406 | layout=layout,
407 | atom_prop="node_groups_field",
408 | data="node_groups"
409 | )
410 |
411 | # inspection update code
412 | if inspection_update_trigger:
413 |
414 | # if key is valid, update the user lists
415 | if atom.node_groups_field in bpy.data.node_groups.keys():
416 |
417 | self.users_compositors = \
418 | users.node_group_compositors(atom.node_groups_field)
419 | self.users_materials = \
420 | users.node_group_materials(atom.node_groups_field)
421 | self.users_node_groups = \
422 | users.node_group_node_groups(atom.node_groups_field)
423 | self.users_textures = \
424 | users.node_group_textures(atom.node_groups_field)
425 | self.users_worlds = \
426 | users.node_group_worlds(atom.node_groups_field)
427 |
428 | # if key is invalid, empty the user lists
429 | else:
430 | self.users_compositors = []
431 | self.users_materials = []
432 | self.users_node_groups = []
433 | self.users_textures = []
434 | self.users_worlds = []
435 |
436 | inspection_update_trigger = False
437 |
438 | # compositors box list
439 | ui_layouts.box_list(
440 | layout=layout,
441 | title="Compositors",
442 | items=self.users_compositors,
443 | icon="NODE_COMPOSITING"
444 | )
445 |
446 | # materials box list
447 | ui_layouts.box_list(
448 | layout=layout,
449 | title="Materials",
450 | items=self.users_materials,
451 | icon="MATERIAL"
452 | )
453 |
454 | # node groups box list
455 | ui_layouts.box_list(
456 | layout=layout,
457 | title="Node Groups",
458 | items=self.users_node_groups,
459 | icon="NODETREE"
460 | )
461 |
462 | # textures box list
463 | ui_layouts.box_list(
464 | layout=layout,
465 | title="Textures",
466 | items=self.users_textures,
467 | icon="TEXTURE"
468 | )
469 |
470 | # world box list
471 | ui_layouts.box_list(
472 | layout=layout,
473 | title="Worlds",
474 | items=self.users_worlds,
475 | icon="WORLD"
476 | )
477 |
478 | row = layout.row() # extra row for spacing
479 |
480 | def execute(self, context):
481 | return {'FINISHED'}
482 |
483 | def invoke(self, context, event):
484 | # update inspection context
485 | atom = bpy.context.scene.atomic
486 | atom.active_inspection = "NODE_GROUPS"
487 |
488 | # trigger update on invoke
489 | global inspection_update_trigger
490 | inspection_update_trigger = True
491 |
492 | # invoke inspect dialog
493 | wm = context.window_manager
494 | return wm.invoke_props_dialog(self)
495 |
496 |
497 | # Atomic Data Manager Inspect Particles UI Operator
498 | class ATOMIC_OT_inspect_particles(bpy.types.Operator):
499 | """Inspect Particle Systems"""
500 | bl_idname = "atomic.inspect_particles"
501 | bl_label = "Inspect Particles"
502 |
503 | # user lists
504 | users_objects = []
505 |
506 | def draw(self, context):
507 | global inspection_update_trigger
508 | atom = bpy.context.scene.atomic
509 |
510 | layout = self.layout
511 |
512 | # inspect particles header
513 | ui_layouts.inspect_header(
514 | layout=layout,
515 | atom_prop="particles_field",
516 | data="particles"
517 | )
518 |
519 | # inspection update code
520 | if inspection_update_trigger:
521 |
522 | # if key is valid, update the user lists
523 | if atom.particles_field in bpy.data.particles.keys():
524 |
525 | self.users_objects = \
526 | users.particle_objects(atom.particles_field)
527 |
528 | # if key is invalid, empty the user lists
529 | else:
530 | self.users_objects = []
531 |
532 | inspection_update_trigger = False
533 |
534 | # objects box list
535 | ui_layouts.box_list(
536 | layout=layout,
537 | title="Objects",
538 | items=self.users_objects,
539 | icon="OUTLINER_OB_MESH"
540 | )
541 |
542 | row = layout.row() # extra row for spacing
543 |
544 | def execute(self, context):
545 | return {'FINISHED'}
546 |
547 | def invoke(self, context, event):
548 | # update inspection context
549 | atom = bpy.context.scene.atomic
550 | atom.active_inspection = "PARTICLES"
551 |
552 | # trigger update on invoke
553 | global inspection_update_trigger
554 | inspection_update_trigger = True
555 |
556 | # invoke inspect dialog
557 | wm = context.window_manager
558 | return wm.invoke_props_dialog(self)
559 |
560 |
561 | # Atomic Data Manager Inspect Textures UI Operator
562 | class ATOMIC_OT_inspect_textures(bpy.types.Operator):
563 | """Inspect Textures"""
564 | bl_idname = "atomic.inspect_textures"
565 | bl_label = "Inspect Textures"
566 |
567 | # user lists
568 | users_compositors = []
569 | users_brushes = []
570 | users_particles = []
571 | users_objects = []
572 |
573 | def draw(self, context):
574 | global inspection_update_trigger
575 | atom = bpy.context.scene.atomic
576 |
577 | layout = self.layout
578 |
579 | # inspect textures header
580 | ui_layouts.inspect_header(
581 | layout=layout,
582 | atom_prop="textures_field",
583 | data="textures"
584 | )
585 |
586 | # inspection update code
587 | if inspection_update_trigger:
588 |
589 | # if the key is valid, update the user lists
590 | if atom.textures_field in bpy.data.textures.keys():
591 |
592 | self.users_compositors = \
593 | users.texture_compositor(atom.textures_field)
594 | self.users_brushes = \
595 | users.texture_brushes(atom.textures_field)
596 | self.users_objects = \
597 | users.texture_objects(atom.textures_field)
598 | self.users_particles = \
599 | users.texture_particles(atom.textures_field)
600 |
601 | # if the key is invalid, set empty the user lists
602 | else:
603 | self.users_compositors = []
604 | self.users_brushes = []
605 | self.users_particles = []
606 | self.users_objects = []
607 |
608 | inspection_update_trigger = False
609 |
610 | # brushes box list
611 | ui_layouts.box_list(
612 | layout=layout,
613 | title="Brushes",
614 | items=self.users_brushes,
615 | icon="BRUSH_DATA"
616 | )
617 |
618 | # compositors box list
619 | ui_layouts.box_list(
620 | layout=layout,
621 | title="Compositors",
622 | items=self.users_compositors,
623 | icon="NODE_COMPOSITING"
624 | )
625 |
626 | # particles box list
627 | ui_layouts.box_list(
628 | layout=layout,
629 | title="Particles",
630 | items=self.users_particles,
631 | icon="PARTICLES"
632 | )
633 |
634 | # objects box list
635 | ui_layouts.box_list_diverse(
636 | layout=layout,
637 | title="Objects",
638 | items=self.users_objects,
639 | )
640 |
641 | row = layout.row() # extra row for spacing
642 |
643 | def execute(self, context):
644 | return {'FINISHED'}
645 |
646 | def invoke(self, context, event):
647 | # update inspection context
648 | atom = bpy.context.scene.atomic
649 | atom.active_inspection = "TEXTURES"
650 |
651 | # trigger update on invoke
652 | global inspection_update_trigger
653 | inspection_update_trigger = True
654 |
655 | # invoke inspect dialog
656 | wm = context.window_manager
657 | return wm.invoke_props_dialog(self)
658 |
659 |
660 | # Atomic Data Manager Inspect Worlds UI Operator
661 | class ATOMIC_OT_inspect_worlds(bpy.types.Operator):
662 | """Inspect Worlds"""
663 | bl_idname = "atomic.inspect_worlds"
664 | bl_label = "Inspect Worlds"
665 |
666 | def draw(self, context):
667 | layout = self.layout
668 |
669 | # inspect worlds header
670 | ui_layouts.inspect_header(
671 | layout=layout,
672 | atom_prop="worlds_field",
673 | data="worlds"
674 | )
675 |
676 | # worlds box list
677 | ui_layouts.box_list(
678 | layout=layout,
679 | title="Worlds in Scene",
680 | items=bpy.data.worlds.keys(),
681 | icon="WORLD"
682 | )
683 |
684 | row = layout.row() # extra row for spacing
685 |
686 | def execute(self, context):
687 | return {'FINISHED'}
688 |
689 | def invoke(self, context, event):
690 | # update inspection context
691 | atom = bpy.context.scene.atomic
692 | atom.active_inspection = "WORLDS"
693 |
694 | # trigger update on invoke
695 | global inspection_update_trigger
696 | inspection_update_trigger = True
697 |
698 | # invoke inspect dialog
699 | wm = context.window_manager
700 | return wm.invoke_props_dialog(self)
701 |
702 |
703 | reg_list = [
704 | ATOMIC_OT_inspect_collections,
705 | ATOMIC_OT_inspect_images,
706 | ATOMIC_OT_inspect_lights,
707 | ATOMIC_OT_inspect_materials,
708 | ATOMIC_OT_inspect_node_groups,
709 | ATOMIC_OT_inspect_particles,
710 | ATOMIC_OT_inspect_textures,
711 | ATOMIC_OT_inspect_worlds
712 | ]
713 |
714 |
715 | def register():
716 | for cls in reg_list:
717 | register_class(cls)
718 |
719 |
720 | def unregister():
721 | for cls in reg_list:
722 | unregister_class(cls)
723 |
--------------------------------------------------------------------------------
/ui/main_panel_ui.py:
--------------------------------------------------------------------------------
1 | """
2 | Copyright (C) 2019 Remington Creative
3 |
4 | This file is part of Atomic Data Manager.
5 |
6 | Atomic Data Manager is free software: you can redistribute
7 | it and/or modify it under the terms of the GNU General Public License
8 | as published by the Free Software Foundation, either version 3 of the
9 | License, or (at your option) any later version.
10 |
11 | Atomic Data Manager is distributed in the hope that it will
12 | be useful, but WITHOUT ANY WARRANTY; without even the implied warranty
13 | of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
14 | General Public License for more details.
15 |
16 | You should have received a copy of the GNU General Public License along
17 | with Atomic Data Manager. If not, see .
18 |
19 | ---
20 |
21 | This file contains the primary Atomic Data Manager panel that will
22 | appear in the Scene tab of the Properties panel.
23 |
24 | This panel contains the Nuke/Clean/Undo buttons as well as the data
25 | category toggles and the category selection tools.
26 |
27 | """
28 |
29 | import bpy
30 | from bpy.utils import register_class
31 | from bpy.utils import unregister_class
32 | from atomic_data_manager.stats import count
33 | from atomic_data_manager.ui.utils import ui_layouts
34 |
35 |
36 | # Atomic Data Manager Main Panel
37 | class ATOMIC_PT_main_panel(bpy.types.Panel):
38 | """The main Atomic Data Manager panel"""
39 | bl_label = "Atomic Data Manager"
40 | bl_space_type = "PROPERTIES"
41 | bl_region_type = "WINDOW"
42 | bl_context = "scene"
43 |
44 | def draw(self, context):
45 | layout = self.layout
46 | atom = bpy.context.scene.atomic
47 | category_props = [
48 | atom.collections,
49 | atom.images,
50 | atom.lights,
51 | atom.materials,
52 | atom.node_groups,
53 | atom.particles,
54 | atom.textures,
55 | atom.worlds
56 | ]
57 |
58 | # nuke and clean buttons
59 | row = layout.row(align=True)
60 | row.scale_y = 2.0
61 | row.operator("atomic.nuke", text="Nuke", icon="GHOST_ENABLED")
62 | row.operator("atomic.clean", text="Clean", icon="PARTICLEMODE")
63 | row.operator("atomic.undo", text="Undo", icon="LOOP_BACK")
64 |
65 | row = layout.row()
66 |
67 | # category toggles
68 | split = layout.split(align=False)
69 |
70 | # left column
71 | col = split.column(align=True)
72 |
73 | # collections buttons
74 | splitcol = col.split(factor=0.8, align=True)
75 |
76 | splitcol.prop(
77 | atom,
78 | "collections",
79 | text="Collections",
80 | icon='GROUP',
81 | toggle=True
82 | )
83 |
84 | splitcol.operator(
85 | "atomic.inspect_collections",
86 | icon='VIEWZOOM',
87 | text=""
88 | )
89 |
90 | # lights buttons
91 | splitcol = col.split(factor=0.8, align=True)
92 |
93 | splitcol.prop(
94 | atom,
95 | "lights",
96 | text="Lights",
97 | icon='LIGHT',
98 | toggle=True
99 | )
100 |
101 | splitcol.operator(
102 | "atomic.inspect_lights",
103 | icon='VIEWZOOM',
104 | text=""
105 | )
106 |
107 | # node groups buttons
108 | splitcol = col.split(factor=0.8, align=True)
109 |
110 | splitcol.prop(
111 | atom,
112 | "node_groups",
113 | text="Node Groups",
114 | icon='NODETREE',
115 | toggle=True
116 | )
117 |
118 | splitcol.operator(
119 | "atomic.inspect_node_groups",
120 | icon='VIEWZOOM',
121 | text=""
122 | )
123 |
124 | # textures button
125 | splitcol = col.split(factor=0.8, align=True)
126 |
127 | splitcol.prop(
128 | atom,
129 | "textures",
130 | text="Textures",
131 | icon='TEXTURE',
132 | toggle=True
133 | )
134 |
135 | splitcol.operator(
136 | "atomic.inspect_textures",
137 | icon='VIEWZOOM',
138 | text=""
139 | )
140 |
141 | # right column
142 | col = split.column(align=True)
143 |
144 | # images buttons
145 | splitcol = col.split(factor=0.8, align=True)
146 |
147 | splitcol.prop(
148 | atom,
149 | "images",
150 | text="Images",
151 | toggle=True,
152 | icon='IMAGE_DATA'
153 | )
154 |
155 | splitcol.operator(
156 | "atomic.inspect_images",
157 | icon='VIEWZOOM',
158 | text=""
159 | )
160 |
161 | # materials buttons
162 | splitcol = col.split(factor=0.8, align=True)
163 |
164 | splitcol.prop(
165 | atom,
166 | "materials",
167 | text="Materials",
168 | icon='MATERIAL',
169 | toggle=True
170 | )
171 |
172 | splitcol.operator(
173 | "atomic.inspect_materials",
174 | icon='VIEWZOOM',
175 | text=""
176 | )
177 |
178 | # particles buttons
179 | splitcol = col.split(factor=0.8, align=True)
180 |
181 | splitcol.prop(
182 | atom,
183 | "particles",
184 | text="Particles",
185 | icon='PARTICLES',
186 | toggle=True
187 | )
188 |
189 | splitcol.operator(
190 | "atomic.inspect_particles",
191 | icon='VIEWZOOM',
192 | text=""
193 | )
194 |
195 | # worlds buttons
196 | splitcol = col.split(factor=0.8, align=True)
197 | splitcol.prop(
198 | atom,
199 | "worlds",
200 | text="Worlds",
201 | icon='WORLD',
202 | toggle=True
203 | )
204 |
205 | splitcol.operator(
206 | "atomic.inspect_worlds",
207 | icon='VIEWZOOM',
208 | text=""
209 | )
210 |
211 | # selection operators
212 | row = layout.row(align=True)
213 |
214 | row.operator(
215 | "atomic.smart_select",
216 | text='Smart Select',
217 | icon='ZOOM_SELECTED'
218 | )
219 |
220 | if all(prop is True for prop in category_props):
221 | row.operator(
222 | "atomic.deselect_all",
223 | text="Deselect All",
224 | icon='RESTRICT_SELECT_ON'
225 | )
226 |
227 | else:
228 | row.operator(
229 | "atomic.select_all",
230 | text="Select All",
231 | icon='RESTRICT_SELECT_OFF'
232 | )
233 |
234 |
235 | reg_list = [ATOMIC_PT_main_panel]
236 |
237 |
238 | def register():
239 | for cls in reg_list:
240 | register_class(cls)
241 |
242 |
243 | def unregister():
244 | for cls in reg_list:
245 | unregister_class(cls)
246 |
--------------------------------------------------------------------------------
/ui/missing_file_ui.py:
--------------------------------------------------------------------------------
1 | """
2 | Copyright (C) 2019 Remington Creative
3 |
4 | This file is part of Atomic Data Manager.
5 |
6 | Atomic Data Manager is free software: you can redistribute
7 | it and/or modify it under the terms of the GNU General Public License
8 | as published by the Free Software Foundation, either version 3 of the
9 | License, or (at your option) any later version.
10 |
11 | Atomic Data Manager is distributed in the hope that it will
12 | be useful, but WITHOUT ANY WARRANTY; without even the implied warranty
13 | of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
14 | General Public License for more details.
15 |
16 | You should have received a copy of the GNU General Public License along
17 | with Atomic Data Manager. If not, see .
18 |
19 | ---
20 |
21 | This file contains the user interface for the missing file dialog that
22 | pops up when missing files are detected on file load.
23 |
24 | """
25 |
26 | import bpy
27 | from bpy.utils import register_class
28 | from bpy.utils import unregister_class
29 | from bpy.app.handlers import persistent
30 | from atomic_data_manager import config
31 | from atomic_data_manager.stats import missing
32 | from atomic_data_manager.ui.utils import ui_layouts
33 |
34 |
35 | # Atomic Data Manager Detect Missing Files Popup
36 | class ATOMIC_OT_detect_missing(bpy.types.Operator):
37 | """Detect missing files in this project"""
38 | bl_idname = "atomic.detect_missing"
39 | bl_label = "Missing File Detection"
40 |
41 | # missing file lists
42 | missing_images = []
43 | missing_libraries = []
44 |
45 | # missing file recovery option enum property
46 | recovery_option: bpy.props.EnumProperty(
47 | items=[
48 | (
49 | 'IGNORE',
50 | 'Ignore Missing Files',
51 | 'Ignore the missing files and leave them offline'
52 | ),
53 | (
54 | 'RELOAD',
55 | 'Reload Missing Files',
56 | 'Reload the missing files from their existing file paths'
57 | ),
58 | (
59 | 'REMOVE',
60 | 'Remove Missing Files',
61 | 'Remove the missing files from the project'
62 | ),
63 | (
64 | 'SEARCH',
65 | 'Search for Missing Files (under development)',
66 | 'Search for the missing files in a directory'
67 | ),
68 | (
69 | 'REPLACE',
70 | 'Specify Replacement Files (under development)',
71 | 'Replace missing files with new files'
72 | ),
73 | ],
74 | default='IGNORE'
75 | )
76 |
77 | def draw(self, context):
78 | layout = self.layout
79 |
80 | # missing files interface if missing files are found
81 | if self.missing_images or self.missing_libraries:
82 |
83 | # header warning
84 | row = layout.row()
85 | row.label(
86 | text="Atomic has detected one or more missing files in "
87 | "your project!"
88 | )
89 |
90 | # missing images box list
91 | if self.missing_images:
92 | ui_layouts.box_list(
93 | layout=layout,
94 | title="Images",
95 | items=self.missing_images,
96 | icon="IMAGE_DATA",
97 | columns=3
98 | )
99 |
100 | # missing libraries box list
101 | if self.missing_libraries:
102 | ui_layouts.box_list(
103 | layout=layout,
104 | title="Libraries",
105 | items=self.missing_libraries,
106 | icon="LIBRARY_DATA_DIRECT",
107 | columns=3
108 | )
109 |
110 | row = layout.separator() # extra space
111 |
112 | # recovery option selection
113 | row = layout.row()
114 | row.label(text="What would you like to do?")
115 |
116 | row = layout.row()
117 | row.prop(self, 'recovery_option', text="")
118 |
119 | # missing files interface if no missing files are found
120 | else:
121 | row = layout.row()
122 | row.label(text="No missing files were found!")
123 |
124 | # empty box list
125 | ui_layouts.box_list(
126 | layout=layout
127 | )
128 |
129 | row = layout.separator() # extra space
130 |
131 | def execute(self, context):
132 |
133 | # ignore missing files will take no action
134 |
135 | # reload missing files
136 | if self.recovery_option == 'RELOAD':
137 | bpy.ops.atomic.reload_missing('INVOKE_DEFAULT')
138 |
139 | # remove missing files
140 | elif self.recovery_option == 'REMOVE':
141 | bpy.ops.atomic.remove_missing('INVOKE_DEFAULT')
142 |
143 | # search for missing files
144 | elif self.recovery_option == 'SEARCH':
145 | bpy.ops.atomic.search_missing('INVOKE_DEFAULT')
146 |
147 | # replace missing files
148 | elif self.recovery_option == 'REPLACE':
149 | bpy.ops.atomic.replace_missing('INVOKE_DEFAULT')
150 |
151 | return {'FINISHED'}
152 |
153 | def invoke(self, context, event):
154 |
155 | # update missing file lists
156 | self.missing_images = missing.images()
157 | self.missing_libraries = missing.libraries()
158 |
159 | wm = context.window_manager
160 |
161 | # invoke large dialog if there are missing files
162 | if self.missing_images or self.missing_libraries:
163 | return wm.invoke_props_dialog(self, width=500)
164 |
165 | # invoke small dialog if there are no missing files
166 | else:
167 | return wm.invoke_popup(self, width=300)
168 |
169 |
170 | @persistent
171 | def autodetect_missing_files(dummy=None):
172 | # invokes the detect missing popup when missing files are detected upon
173 | # loading a new Blender project
174 | if config.enable_missing_file_warning and \
175 | (missing.images() or missing.libraries()):
176 | bpy.ops.atomic.detect_missing('INVOKE_DEFAULT')
177 |
178 |
179 | reg_list = [ATOMIC_OT_detect_missing]
180 |
181 |
182 | def register():
183 | for item in reg_list:
184 | register_class(item)
185 |
186 | # run missing file auto-detection after loading a Blender file
187 | bpy.app.handlers.load_post.append(autodetect_missing_files)
188 |
189 |
190 | def unregister():
191 | for item in reg_list:
192 | unregister_class(item)
193 |
194 | # stop running missing file auto-detection after loading a Blender file
195 | bpy.app.handlers.load_post.remove(autodetect_missing_files)
196 |
--------------------------------------------------------------------------------
/ui/pie_menu_ui.py:
--------------------------------------------------------------------------------
1 | """
2 | Copyright (C) 2019 Remington Creative
3 |
4 | This file is part of Atomic Data Manager.
5 |
6 | Atomic Data Manager is free software: you can redistribute
7 | it and/or modify it under the terms of the GNU General Public License
8 | as published by the Free Software Foundation, either version 3 of the
9 | License, or (at your option) any later version.
10 |
11 | Atomic Data Manager is distributed in the hope that it will
12 | be useful, but WITHOUT ANY WARRANTY; without even the implied warranty
13 | of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
14 | General Public License for more details.
15 |
16 | You should have received a copy of the GNU General Public License along
17 | with Atomic Data Manager. If not, see .
18 |
19 | ---
20 |
21 | This file contains Atomic's pie menu UI and its pie menu keymap
22 | registration.
23 |
24 | """
25 |
26 | import bpy
27 | from bpy.utils import register_class
28 | from bpy.utils import unregister_class
29 |
30 |
31 | # Atomic Data Manager Main Pie Menu
32 | class ATOMIC_MT_main_pie(bpy.types.Menu):
33 | bl_idname = "ATOMIC_MT_main_pie"
34 | bl_label = "Atomic Data Manager"
35 |
36 | def draw(self, context):
37 | layout = self.layout
38 | pie = layout.menu_pie()
39 |
40 | # nuke all operator
41 | pie.operator(
42 | "atomic.nuke_all",
43 | text="Nuke All",
44 | icon="GHOST_ENABLED"
45 | )
46 |
47 | # clean all operator
48 | pie.operator(
49 | "atomic.clean_all",
50 | text="Clean All",
51 | icon="PARTICLEMODE"
52 | )
53 |
54 | # undo operator
55 | pie.operator(
56 | "atomic.detect_missing",
57 | text="Detect Missing Files",
58 | icon="SHADERFX"
59 | )
60 |
61 | # inspect category operator
62 | pie.operator(
63 | "wm.call_menu_pie",
64 | text="Inspect",
65 | icon="VIEWZOOM"
66 | ).name = "ATOMIC_MT_inspect_pie"
67 |
68 | # nuke category operator
69 | pie.operator(
70 | "wm.call_menu_pie",
71 | text="Nuke",
72 | icon="GHOST_ENABLED"
73 | ).name = "ATOMIC_MT_nuke_pie"
74 |
75 | # clean category operator
76 | pie.operator(
77 | "wm.call_menu_pie",
78 | text="Clean",
79 | icon="PARTICLEMODE"
80 | ).name = "ATOMIC_MT_clean_pie"
81 |
82 |
83 | # Atomic Data Manager Nuke Pie Menu
84 | class ATOMIC_MT_nuke_pie(bpy.types.Menu):
85 | bl_idname = "ATOMIC_MT_nuke_pie"
86 | bl_label = "Atomic Nuke"
87 |
88 | def draw(self, context):
89 | layout = self.layout
90 | pie = layout.menu_pie()
91 |
92 | # nuke node groups operator
93 | pie.operator("atomic.nuke_node_groups", icon="NODETREE")
94 |
95 | # nuke materials operator
96 | pie.operator("atomic.nuke_materials", icon="MATERIAL")
97 |
98 | # nuke worlds operator
99 | pie.operator("atomic.nuke_worlds", icon="WORLD")
100 |
101 | # nuke collections operator
102 | pie.operator("atomic.nuke_collections", icon="GROUP")
103 |
104 | # nuke lights operator
105 | pie.operator("atomic.nuke_lights", icon="LIGHT")
106 |
107 | # nuke images operator
108 | pie.operator("atomic.nuke_images", icon="IMAGE_DATA")
109 |
110 | # nuke textures operator
111 | pie.operator("atomic.nuke_textures", icon="TEXTURE")
112 |
113 | # nuke particles operator
114 | pie.operator("atomic.nuke_particles", icon="PARTICLES")
115 |
116 |
117 | # Atomic Data Manager Clean Pie Menu
118 | class ATOMIC_MT_clean_pie(bpy.types.Menu):
119 | bl_idname = "ATOMIC_MT_clean_pie"
120 | bl_label = "Atomic Clean"
121 |
122 | def draw(self, context):
123 | layout = self.layout
124 | pie = layout.menu_pie()
125 |
126 | # clean node groups operator
127 | pie.operator("atomic.clean_node_groups", icon="NODETREE")
128 |
129 | # clean materials operator
130 | pie.operator("atomic.clean_materials", icon="MATERIAL")
131 |
132 | # clean worlds operator
133 | pie.operator("atomic.clean_worlds", icon="WORLD")
134 |
135 | # clean collections operator
136 | pie.operator("atomic.clean_collections", icon="GROUP")
137 |
138 | # clean lights operator
139 | pie.operator("atomic.clean_lights", icon="LIGHT")
140 |
141 | # clean images operator
142 | pie.operator("atomic.clean_images", icon="IMAGE_DATA")
143 |
144 | # clean textures operator
145 | pie.operator("atomic.clean_textures", icon="TEXTURE")
146 |
147 | # clean materials operator
148 | pie.operator("atomic.clean_particles", icon="PARTICLES")
149 |
150 |
151 | # Atomic Data Manager Inspect Pie Menu
152 | class ATOMIC_MT_inspect_pie(bpy.types.Menu):
153 | bl_idname = "ATOMIC_MT_inspect_pie"
154 | bl_label = "Atomic Inspect"
155 |
156 | def draw(self, context):
157 | layout = self.layout
158 | pie = layout.menu_pie()
159 |
160 | # inspect node groups operator
161 | pie.operator("atomic.inspect_node_groups", icon="NODETREE")
162 |
163 | # inspect materials operator
164 | pie.operator("atomic.inspect_materials", icon="MATERIAL")
165 |
166 | # inspect worlds operator
167 | pie.operator("atomic.inspect_worlds", icon="WORLD")
168 |
169 | # inspect groups operator
170 | pie.operator("atomic.inspect_collections", icon="GROUP")
171 |
172 | # inspect lights operator
173 | pie.operator("atomic.inspect_lights", icon="LIGHT")
174 |
175 | # inspect images operator
176 | pie.operator("atomic.inspect_images", icon="IMAGE_DATA")
177 |
178 | # inspect textures operator
179 | pie.operator("atomic.inspect_textures", icon="TEXTURE")
180 |
181 | # inspect particles operator
182 | pie.operator("atomic.inspect_particles", icon="PARTICLES")
183 |
184 |
185 | reg_list = [
186 | ATOMIC_MT_main_pie,
187 | ATOMIC_MT_nuke_pie,
188 | ATOMIC_MT_clean_pie,
189 | ATOMIC_MT_inspect_pie
190 | ]
191 |
192 |
193 | def register():
194 | for cls in reg_list:
195 | register_class(cls)
196 |
197 |
198 | def unregister():
199 | for cls in reg_list:
200 | unregister_class(cls)
201 |
--------------------------------------------------------------------------------
/ui/preferences_ui.py:
--------------------------------------------------------------------------------
1 | """
2 | Copyright (C) 2019 Remington Creative
3 |
4 | This file is part of Atomic Data Manager.
5 |
6 | Atomic Data Manager is free software: you can redistribute
7 | it and/or modify it under the terms of the GNU General Public License
8 | as published by the Free Software Foundation, either version 3 of the
9 | License, or (at your option) any later version.
10 |
11 | Atomic Data Manager is distributed in the hope that it will
12 | be useful, but WITHOUT ANY WARRANTY; without even the implied warranty
13 | of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
14 | General Public License for more details.
15 |
16 | You should have received a copy of the GNU General Public License along
17 | with Atomic Data Manager. If not, see .
18 |
19 | ---
20 |
21 | This file contains the Atomic preferences UI, preferences properties, and
22 | some functions for syncing the preference properties with external factors.
23 |
24 | """
25 |
26 | import bpy
27 | from bpy.utils import register_class
28 | from bpy.utils import unregister_class
29 | from atomic_data_manager import config
30 | from atomic_data_manager.updater import addon_updater_ops
31 |
32 |
33 | def set_enable_support_me_popup(value):
34 | # sets the value of the enable_support_me_popup boolean property
35 |
36 | bpy.context.preferences.addons["atomic_data_manager"]\
37 | .preferences.enable_support_me_popup = value
38 | copy_prefs_to_config(None, None)
39 | bpy.ops.wm.save_userpref()
40 |
41 |
42 | def set_last_popup_day(day):
43 | # sets the value of the last_popup_day float property
44 |
45 | bpy.context.preferences.addons["atomic_data_manager"]\
46 | .preferences.last_popup_day = day
47 | copy_prefs_to_config(None, None)
48 |
49 |
50 | def copy_prefs_to_config(self, context):
51 | # copies the values of Atomic's preferences to the variables in
52 | # config.py for global use
53 |
54 | preferences = bpy.context.preferences
55 |
56 | atomic_preferences = preferences.addons['atomic_data_manager']\
57 | .preferences
58 |
59 | # visible atomic preferences
60 | config.enable_missing_file_warning = \
61 | atomic_preferences.enable_missing_file_warning
62 |
63 | config.enable_pie_menu_ui = \
64 | atomic_preferences.enable_pie_menu_ui
65 |
66 | config.enable_support_me_popup = \
67 | atomic_preferences.enable_support_me_popup
68 |
69 | config.include_fake_users = \
70 | atomic_preferences.include_fake_users
71 |
72 | # hidden atomic preferences
73 | config.pie_menu_type = \
74 | atomic_preferences.pie_menu_type
75 |
76 | config.pie_menu_alt = \
77 | atomic_preferences.pie_menu_alt
78 |
79 | config.pie_menu_any = \
80 | atomic_preferences.pie_menu_any
81 |
82 | config.pie_menu_ctrl = \
83 | atomic_preferences.pie_menu_ctrl
84 |
85 | config.pie_menu_oskey = \
86 | atomic_preferences.pie_menu_oskey
87 |
88 | config.pie_menu_shift = \
89 | atomic_preferences.pie_menu_shift
90 |
91 | config.last_popup_day = \
92 | atomic_preferences.last_popup_day
93 |
94 |
95 | def update_pie_menu_hotkeys(self, context):
96 | preferences = bpy.context.preferences
97 | atomic_preferences = preferences.addons['atomic_data_manager'] \
98 | .preferences
99 |
100 | # add the hotkeys if the preference is enabled
101 | if atomic_preferences.enable_pie_menu_ui:
102 | add_pie_menu_hotkeys()
103 |
104 | # remove the hotkeys otherwise
105 | else:
106 | remove_pie_menu_hotkeys()
107 |
108 |
109 | def add_pie_menu_hotkeys():
110 | # adds the pie menu hotkeys to blender's addon keymaps
111 |
112 | global keymaps
113 | keyconfigs = bpy.context.window_manager.keyconfigs.addon
114 |
115 | # check to see if a window keymap already exists
116 | if "Window" in keyconfigs.keymaps.keys():
117 | km = keyconfigs.keymaps['Window']
118 |
119 | # if not, crate a new one
120 | else:
121 | km = keyconfigs.keymaps.new(
122 | name="Window",
123 | space_type='EMPTY',
124 | region_type='WINDOW'
125 | )
126 |
127 | # add a new keymap item to that keymap
128 | kmi = km.keymap_items.new(
129 | idname="atomic.invoke_pie_menu_ui",
130 | type=config.pie_menu_type,
131 | value="PRESS",
132 | alt=config.pie_menu_alt,
133 | any=config.pie_menu_any,
134 | ctrl=config.pie_menu_ctrl,
135 | oskey=config.pie_menu_oskey,
136 | shift=config.pie_menu_shift,
137 | )
138 |
139 | # # point the keymap item to our pie menu
140 | # kmi.properties.name = "ATOMIC_MT_main_pie"
141 | keymaps.append((km, kmi))
142 |
143 |
144 | def remove_pie_menu_hotkeys():
145 | # removes the pie menu hotkeys from blender's addon keymaps if they
146 | # exist there
147 |
148 | global keymaps
149 |
150 | # remove each hotkey in our keymaps list if it exists in blenders
151 | # addon keymaps
152 | for km, kmi in keymaps:
153 | km.keymap_items.remove(kmi)
154 |
155 | # clear our keymaps list
156 | keymaps.clear()
157 |
158 |
159 | # Atomic Data Manager Preference Panel UI
160 | class ATOMIC_PT_preferences_panel(bpy.types.AddonPreferences):
161 | bl_idname = "atomic_data_manager"
162 |
163 | # visible atomic preferences
164 | enable_missing_file_warning: bpy.props.BoolProperty(
165 | description="Display a warning on startup if Atomic detects "
166 | "missing files in your project",
167 | default=True
168 | )
169 |
170 | enable_support_me_popup: bpy.props.BoolProperty(
171 | description="Occasionally display a popup asking if you would "
172 | "like to support Remington Creative",
173 | default=True
174 | )
175 |
176 | include_fake_users: bpy.props.BoolProperty(
177 | description="Include data-blocks with only fake users in unused "
178 | "data detection",
179 | default=False
180 | )
181 |
182 | enable_pie_menu_ui: bpy.props.BoolProperty(
183 | description="Enable the Atomic pie menu UI, so you can clean "
184 | "your project from anywhere.",
185 | default=True,
186 | update=update_pie_menu_hotkeys
187 | )
188 |
189 | # hidden atomic preferences
190 | pie_menu_type: bpy.props.StringProperty(
191 | default="D"
192 | )
193 |
194 | pie_menu_alt: bpy.props.BoolProperty(
195 | default=False
196 | )
197 |
198 | pie_menu_any: bpy.props.BoolProperty(
199 | default=False
200 | )
201 |
202 | pie_menu_ctrl: bpy.props.BoolProperty(
203 | default=False
204 | )
205 |
206 | pie_menu_oskey: bpy.props.BoolProperty(
207 | default=False
208 | )
209 |
210 | pie_menu_shift: bpy.props.BoolProperty(
211 | default=False
212 | )
213 |
214 | last_popup_day: bpy.props.FloatProperty(
215 | default=0
216 | )
217 |
218 | # add-on updater properties
219 | auto_check_update: bpy.props.BoolProperty(
220 | name="Auto-check for Update",
221 | description="If enabled, auto-check for updates using an interval",
222 | default=True,
223 | )
224 |
225 | updater_intrval_months: bpy.props.IntProperty(
226 | name='Months',
227 | description="Number of months between checking for updates",
228 | default=0,
229 | min=0,
230 | max=6
231 | )
232 | updater_intrval_days: bpy.props.IntProperty(
233 | name='Days',
234 | description="Number of days between checking for updates",
235 | default=7,
236 | min=0,
237 | )
238 | updater_intrval_hours: bpy.props.IntProperty(
239 | name='Hours',
240 | description="Number of hours between checking for updates",
241 | default=0,
242 | min=0,
243 | max=23
244 | )
245 | updater_intrval_minutes: bpy.props.IntProperty(
246 | name='Minutes',
247 | description="Number of minutes between checking for updates",
248 | default=0,
249 | min=0,
250 | max=59
251 | )
252 |
253 | def draw(self, context):
254 | layout = self.layout
255 |
256 | split = layout.split()
257 |
258 | # left column
259 | col = split.column()
260 |
261 | # enable missing file warning toggle
262 | col.prop(
263 | self,
264 | "enable_missing_file_warning",
265 | text="Show Missing File Warning"
266 | )
267 |
268 | # enable support me popup toggle
269 | col.prop(
270 | self,
271 | "enable_support_me_popup",
272 | text="Show \"Support Me\" Popup"
273 | )
274 |
275 | # right column
276 | col = split.column()
277 |
278 | # ignore fake users toggle
279 | col.prop(
280 | self,
281 | "include_fake_users",
282 | text="Include Fake Users"
283 | )
284 |
285 | # pie menu settings
286 | pie_split = col.split(factor=0.55) # nice
287 |
288 | # enable pie menu ui toggle
289 | pie_split.prop(
290 | self,
291 | "enable_pie_menu_ui",
292 | text="Enable Pie Menu"
293 | )
294 |
295 | # put the property in a row so it can be disabled
296 | pie_row = pie_split.row()
297 | pie_row.enabled = self.enable_pie_menu_ui
298 |
299 | if pie_row.enabled:
300 | # keymap item that contains our pie menu hotkey
301 | # note: keymap item index hardcoded with an index -- may be
302 | # dangerous if more keymap items are added
303 | kmi = bpy.context.window_manager.keyconfigs.addon.keymaps[
304 | 'Window'].keymap_items[0]
305 |
306 | # hotkey property
307 | pie_row.prop(
308 | kmi,
309 | "type",
310 | text="",
311 | full_event=True
312 | )
313 |
314 | # update hotkey preferences
315 | self.pie_menu_type = kmi.type
316 | self.pie_menu_any = kmi.any
317 | self.pie_menu_alt = kmi.alt
318 | self.pie_menu_ctrl = kmi.ctrl
319 | self.pie_menu_oskey = kmi.oskey
320 | self.pie_menu_shift = kmi.shift
321 |
322 | separator = layout.row() # extra space
323 |
324 | # add-on updater box
325 | addon_updater_ops.update_settings_ui(self, context)
326 |
327 | # update config with any new preferences
328 | copy_prefs_to_config(None, None)
329 |
330 |
331 | reg_list = [ATOMIC_PT_preferences_panel]
332 | keymaps = []
333 |
334 |
335 | def register():
336 | for cls in reg_list:
337 | register_class(cls)
338 |
339 | # make sure global preferences are updated on registration
340 | copy_prefs_to_config(None, None)
341 |
342 | # update keymaps
343 | add_pie_menu_hotkeys()
344 |
345 |
346 | def unregister():
347 | for cls in reg_list:
348 | unregister_class(cls)
349 |
350 | remove_pie_menu_hotkeys()
351 |
--------------------------------------------------------------------------------
/ui/stats_panel_ui.py:
--------------------------------------------------------------------------------
1 | """
2 | Copyright (C) 2019 Remington Creative
3 |
4 | This file is part of Atomic Data Manager.
5 |
6 | Atomic Data Manager is free software: you can redistribute
7 | it and/or modify it under the terms of the GNU General Public License
8 | as published by the Free Software Foundation, either version 3 of the
9 | License, or (at your option) any later version.
10 |
11 | Atomic Data Manager is distributed in the hope that it will
12 | be useful, but WITHOUT ANY WARRANTY; without even the implied warranty
13 | of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
14 | General Public License for more details.
15 |
16 | You should have received a copy of the GNU General Public License along
17 | with Atomic Data Manager. If not, see .
18 |
19 | ---
20 |
21 | This file contains the user interface for Atomic's statistics subpanel.
22 |
23 | The statistics panel is nested in the main Atomic Data Manager panel. This
24 | panel contains statistics about the Blender file and each data category in
25 | it.
26 |
27 | """
28 |
29 | import bpy
30 | from bpy.utils import register_class
31 | from bpy.utils import unregister_class
32 | from atomic_data_manager.stats import count
33 | from atomic_data_manager.stats import misc
34 | from atomic_data_manager.ui.utils import ui_layouts
35 |
36 |
37 | # Atomic Data Manager Statistics SubPanel
38 | class ATOMIC_PT_stats_panel(bpy.types.Panel):
39 | """The Atomic Data Manager \"Stats for Nerds\" panel"""
40 | bl_idname = "ATOMIC_PT_stats_panel"
41 | bl_label = "Stats for Nerds"
42 | bl_space_type = "PROPERTIES"
43 | bl_region_type = "WINDOW"
44 | bl_parent_id = "ATOMIC_PT_main_panel"
45 |
46 | def draw(self, context):
47 | layout = self.layout
48 | atom = bpy.context.scene.atomic
49 |
50 | # categories selector / header
51 | row = layout.row()
52 | row.label(text="Categories:")
53 | row.prop(atom, "stats_mode", expand=True, icon_only=True)
54 |
55 | # statistics box
56 | box = layout.box()
57 |
58 | # overview statistics
59 | if atom.stats_mode == 'OVERVIEW':
60 |
61 | # category header label
62 | row = box.row()
63 | row.label(text="Overview", icon='FILE')
64 |
65 | # blender project file size statistic
66 | row = box.row()
67 | row.label(text="Blend File Size: " + misc.blend_size())
68 |
69 | # cateogry statistics
70 | split = box.split()
71 |
72 | # left column
73 | col = split.column()
74 |
75 | # left column category labels
76 | col.label(text="Collections")
77 | col.label(text="Lights")
78 | col.label(text="Node Groups")
79 | col.label(text="Textures")
80 |
81 | col = split.column()
82 |
83 | # collection count
84 | col.label(text=str(count.collections()))
85 |
86 | # light count
87 | col.label(text=str(count.lights()))
88 |
89 | # node group count
90 | col.label(text=str(count.node_groups()))
91 |
92 | # texture count
93 | col.label(text=str(count.textures()))
94 |
95 | # right column
96 | col = split.column()
97 |
98 | # right column category labels
99 | col.label(text="Images")
100 | col.label(text="Materials")
101 | col.label(text="Particles")
102 | col.label(text="Worlds")
103 |
104 | col = split.column()
105 |
106 | # image count
107 | col.label(text=str(count.images()))
108 |
109 | # material count
110 | col.label(text=str(count.materials()))
111 |
112 | # particle system count
113 | col.label(text=str(count.particles()))
114 |
115 | # world count
116 | col.label(text=str(count.worlds()))
117 |
118 | # collection statistics
119 | elif atom.stats_mode == 'COLLECTIONS':
120 |
121 | # category header label
122 | row = box.row()
123 | row.label(text="Collections", icon='GROUP')
124 |
125 | split = box.split()
126 |
127 | # total and placeholder count
128 | col = split.column()
129 |
130 | col.label(
131 | text="Total: {0}".format(count.collections())
132 | )
133 |
134 | # col.label(text="Placeholder") # TODO: remove placeholder
135 |
136 | # unused and unnamed count
137 | col = split.column()
138 |
139 | col.label(
140 | text="Unused: {0}".format(count.collections_unused())
141 | )
142 |
143 | col.label(
144 | text="Unnamed: {0}".format(count.collections_unnamed())
145 | )
146 |
147 | # image statistics
148 | elif atom.stats_mode == 'IMAGES':
149 |
150 | # category header label
151 | row = box.row()
152 | row.label(text="Images", icon='IMAGE_DATA')
153 |
154 | split = box.split()
155 |
156 | # total and missing count
157 | col = split.column()
158 |
159 | col.label(
160 | text="Total: {0}".format(count.images())
161 | )
162 |
163 | col.label(
164 | text="Missing: {0}".format(count.images_missing())
165 | )
166 |
167 | # unused and unnamed count
168 | col = split.column()
169 |
170 | col.label(
171 | text="Unused: {0}".format(count.images_unused())
172 | )
173 | col.label(
174 | text="Unnamed: {0}".format(count.images_unnamed())
175 | )
176 |
177 | # light statistics
178 | elif atom.stats_mode == 'LIGHTS':
179 | row = box.row()
180 | row.label(text="Lights", icon='LIGHT')
181 |
182 | split = box.split()
183 |
184 | # total and placeholder count
185 | col = split.column()
186 |
187 | col.label(
188 | text="Total: {0}".format(count.lights())
189 | )
190 |
191 | # col.label(text="Placeholder") # TODO: remove placeholder
192 |
193 | # unused and unnamed count
194 | col = split.column()
195 |
196 | col.label(
197 | text="Unused: {0}".format(count.lights_unused())
198 | )
199 |
200 | col.label(
201 | text="Unnamed: {0}".format(count.lights_unnamed())
202 | )
203 |
204 | # material statistics
205 | elif atom.stats_mode == 'MATERIALS':
206 |
207 | # category header label
208 | row = box.row()
209 | row.label(text="Materials", icon='MATERIAL')
210 |
211 | split = box.split()
212 |
213 | # total and placeholder count
214 | col = split.column()
215 |
216 | col.label(
217 | text="Total: {0}".format(count.materials())
218 | )
219 |
220 | # col.label(text="Placeholder") # TODO: remove placeholder
221 |
222 | # unused and unnamed count
223 | col = split.column()
224 |
225 | col.label(
226 | text="Unused: {0}".format(count.materials_unused())
227 | )
228 |
229 | col.label(
230 | text="Unnamed: {0}".format(count.materials_unnamed())
231 | )
232 |
233 | # object statistics
234 | elif atom.stats_mode == 'OBJECTS':
235 |
236 | # category header label
237 | row = box.row()
238 | row.label(text="Objects", icon='OBJECT_DATA')
239 |
240 | # total count
241 | split = box.split()
242 | col = split.column()
243 |
244 | col.label(
245 | text="Total: {0}".format(count.objects())
246 | )
247 |
248 | # unnamed count
249 | col = split.column()
250 |
251 | col.label(
252 | text="Unnamed: {0}".format(count.objects_unnamed())
253 | )
254 |
255 | # node group statistics
256 | elif atom.stats_mode == 'NODE_GROUPS':
257 |
258 | # category header label
259 | row = box.row()
260 | row.label(text="Node Groups", icon='NODETREE')
261 |
262 | split = box.split()
263 |
264 | # total and placeholder count
265 | col = split.column()
266 |
267 | col.label(
268 | text="Total: {0}".format(count.node_groups())
269 | )
270 |
271 | # col.label(text="Placeholder") # TODO: remove placeholder
272 |
273 | # unused and unnamed count
274 | col = split.column()
275 | col.label(
276 | text="Unused: {0}".format(count.node_groups_unused())
277 | )
278 | col.label(
279 | text="Unnamed: {0}".format(count.node_groups_unnamed())
280 | )
281 |
282 | # particle statistics
283 | elif atom.stats_mode == 'PARTICLES':
284 |
285 | # category header label
286 | row = box.row()
287 | row.label(text="Particle Systems", icon='PARTICLES')
288 |
289 | split = box.split()
290 |
291 | # total and placeholder count
292 | col = split.column()
293 |
294 | col.label(
295 | text="Total: {0}".format(count.particles())
296 | )
297 |
298 | # col.label(text="Placeholder") # TODO: remove placeholder
299 |
300 | # unused and unnamed count
301 | col = split.column()
302 |
303 | col.label(
304 | text="Unused: {0}".format(count.particles_unused())
305 | )
306 |
307 | col.label(
308 | text="Unnamed: {0}".format(count.particles_unnamed())
309 | )
310 |
311 | # texture statistics
312 | elif atom.stats_mode == 'TEXTURES':
313 | row = box.row()
314 | row.label(text="Textures", icon='TEXTURE')
315 |
316 | split = box.split()
317 |
318 | # total and placeholder count
319 | col = split.column()
320 |
321 | col.label(
322 | text="Total: {0}".format(count.textures())
323 | )
324 |
325 | # col.label(text="Placeholder") # TODO: remove placeholder
326 |
327 | # unused and unnamed count
328 | col = split.column()
329 |
330 | col.label(
331 | text="Unused: {0}".format(count.textures_unused())
332 | )
333 |
334 | col.label(
335 | text="Unnamed: {0}".format(count.textures_unnamed())
336 | )
337 |
338 | # world statistics
339 | elif atom.stats_mode == 'WORLDS':
340 | row = box.row()
341 | row.label(text="Worlds", icon='WORLD')
342 |
343 | split = box.split()
344 |
345 | # total and placeholder count
346 | col = split.column()
347 |
348 | col.label(
349 | text="Total: {0}".format(count.worlds())
350 | )
351 |
352 | # # col.label(text="Placeholder") # TODO: remove placeholder
353 |
354 | # unused and unnamed count
355 | col = split.column()
356 |
357 | col.label(
358 | text="Unused: {0}".format(count.worlds_unused())
359 | )
360 |
361 | col.label(
362 | text="Unnamed: {0}".format(count.worlds_unnamed())
363 | )
364 |
365 |
366 | reg_list = [ATOMIC_PT_stats_panel]
367 |
368 |
369 | def register():
370 | for cls in reg_list:
371 | register_class(cls)
372 |
373 |
374 | def unregister():
375 | for cls in reg_list:
376 | unregister_class(cls)
377 |
--------------------------------------------------------------------------------
/ui/support_me_ui.py:
--------------------------------------------------------------------------------
1 | """
2 | Copyright (C) 2019 Remington Creative
3 |
4 | This file is part of Atomic Data Manager.
5 |
6 | Atomic Data Manager is free software: you can redistribute
7 | it and/or modify it under the terms of the GNU General Public License
8 | as published by the Free Software Foundation, either version 3 of the
9 | License, or (at your option) any later version.
10 |
11 | Atomic Data Manager is distributed in the hope that it will
12 | be useful, but WITHOUT ANY WARRANTY; without even the implied warranty
13 | of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
14 | General Public License for more details.
15 |
16 | You should have received a copy of the GNU General Public License along
17 | with Atomic Data Manager. If not, see .
18 |
19 | ---
20 |
21 | This file contains the user interface and some helper functions for the
22 | support Remington Creative popup.
23 |
24 | """
25 |
26 | import bpy
27 | import time
28 | from bpy.utils import register_class
29 | from bpy.utils import unregister_class
30 | from bpy.app.handlers import persistent
31 | from atomic_data_manager import config
32 | from atomic_data_manager.ui import preferences_ui
33 |
34 |
35 | def get_current_day():
36 | # returns the current day since the start of the computer clock
37 | seconds_per_day = 86400
38 | return int(time.time() / seconds_per_day)
39 |
40 |
41 | def update_enable_show_support_me_popup(self, context):
42 | # copy the inverse of the stop show support popup property to Atomic's
43 | # enable support me popup preference
44 | preferences_ui.set_enable_support_me_popup(
45 | not self.stop_showing_support_popup)
46 |
47 |
48 | @persistent
49 | def show_support_me_popup(dummy=None):
50 | # shows the support me popup if the 5 day interval has expired and the
51 | # enable support me popup preference is enabled
52 |
53 | popup_interval = 5 # days
54 |
55 | current_day = get_current_day()
56 | next_day = config.last_popup_day + popup_interval
57 |
58 | if config.enable_support_me_popup and current_day >= next_day:
59 | preferences_ui.set_last_popup_day(current_day)
60 | bpy.ops.atomic.show_support_me('INVOKE_DEFAULT')
61 |
62 |
63 | # Atomic Data Manager Support Me Popup Operator
64 | class ATOMIC_OT_support_me_popup(bpy.types.Operator):
65 | """Displays the Atomic \"Support Me\" popup"""
66 | bl_idname = "atomic.show_support_me"
67 | bl_label = "Like Atomic Data Manager?"
68 | bl_options = {'REGISTER', 'UNDO'}
69 |
70 | stop_showing_support_popup: bpy.props.BoolProperty(
71 | default=False,
72 | update=update_enable_show_support_me_popup
73 | )
74 |
75 | def draw(self, context):
76 | layout = self.layout
77 |
78 | # call to action label
79 | col = layout.column(align=True)
80 | col.label(
81 | text="Consider supporting our free software development!"
82 | )
83 |
84 | separator = layout.separator() # extra space
85 |
86 | # never show again toggle
87 | row = layout.row()
88 | row.prop(
89 | self, "stop_showing_support_popup", text="Never Show Again"
90 | )
91 |
92 | # support remington creative button
93 | row = layout.row()
94 | row.scale_y = 2
95 | row.operator(
96 | "atomic.open_support_me",
97 | text="Support Remington Creative",
98 | icon="FUND"
99 | )
100 |
101 | def execute(self, context):
102 | return {'FINISHED'}
103 |
104 | def invoke(self, context, event):
105 | wm = context.window_manager
106 | return wm.invoke_props_dialog(self)
107 |
108 |
109 | reg_list = [ATOMIC_OT_support_me_popup]
110 |
111 |
112 | def register():
113 | for cls in reg_list:
114 | register_class(cls)
115 |
116 | bpy.app.handlers.load_post.append(show_support_me_popup)
117 |
118 | # reset day counter if it equals zero of if it is in the future
119 | if config.last_popup_day == 0 \
120 | or config.last_popup_day > get_current_day():
121 | preferences_ui.set_last_popup_day(get_current_day())
122 |
123 |
124 | def unregister():
125 | for cls in reg_list:
126 | unregister_class(cls)
127 |
128 | bpy.app.handlers.load_post.remove(show_support_me_popup)
129 |
--------------------------------------------------------------------------------
/ui/utils/ui_layouts.py:
--------------------------------------------------------------------------------
1 | """
2 | Copyright (C) 2019 Remington Creative
3 |
4 | This file is part of Atomic Data Manager.
5 |
6 | Atomic Data Manager is free software: you can redistribute
7 | it and/or modify it under the terms of the GNU General Public License
8 | as published by the Free Software Foundation, either version 3 of the
9 | License, or (at your option) any later version.
10 |
11 | Atomic Data Manager is distributed in the hope that it will
12 | be useful, but WITHOUT ANY WARRANTY; without even the implied warranty
13 | of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
14 | General Public License for more details.
15 |
16 | You should have received a copy of the GNU General Public License along
17 | with Atomic Data Manager. If not, see .
18 |
19 | ---
20 |
21 | This file contains basic UI layouts for the Atomic add-on that can be
22 | used throughout the interface.
23 |
24 | """
25 |
26 | import bpy
27 |
28 |
29 | def box_list(layout, title=None, items=None, columns=2, icon=None):
30 | # a title label followed by a box that contains a two column list of
31 | # items, each of which is preceded by a uniform icon that does not
32 | # change depending on the objects type
33 |
34 | # box list title
35 | row = layout.row() # extra row for additional spacing
36 |
37 | if title is not None:
38 | row = layout.row()
39 | row.label(text=title)
40 |
41 | box = layout.box()
42 |
43 | # if the list has elements
44 | if items is not None and len(items) != 0:
45 |
46 | # display the list
47 | flow = box.column_flow(columns=columns)
48 | for item in items:
49 | if icon is not None:
50 | flow.label(text=item, icon=icon)
51 | else:
52 | flow.label(text=item)
53 |
54 | # if the list has no elements
55 | else:
56 |
57 | # display the none label
58 | row = box.row()
59 | row.enabled = False
60 | row.label(text="none")
61 |
62 |
63 | def box_list_diverse(layout, title, items, columns=2):
64 | # a title label followed by a box that contains a two column list of
65 | # items, each of which is preceded by an icon that changes depending
66 | # on the type of object that is being listed
67 |
68 | # box list title
69 | row = layout.row() # extra row for additional spacing
70 | row = layout.row()
71 | row.label(text=title)
72 | box = layout.box()
73 |
74 | # if the list has elements
75 | if len(items) != 0:
76 |
77 | # display the list
78 | flow = box.column_flow(columns=columns)
79 | objects = bpy.data.objects
80 | for item in items:
81 | if objects[item].type == 'ARMATURE':
82 | flow.label(text=item, icon="OUTLINER_OB_ARMATURE")
83 |
84 | elif objects[item].type == 'CAMERA':
85 | flow.label(text=item, icon="OUTLINER_OB_CAMERA")
86 |
87 | elif objects[item].type == 'CURVE':
88 | flow.label(text=item, icon="OUTLINER_OB_CURVE")
89 |
90 | elif objects[item].type == 'EMPTY':
91 | flow.label(text=item, icon="OUTLINER_OB_EMPTY")
92 |
93 | elif objects[item].type == 'FONT':
94 | flow.label(text=item, icon="OUTLINER_OB_FONT")
95 |
96 | elif objects[item].type == 'GPENCIL':
97 | flow.label(text=item, icon="OUTLINER_OB_GREASEPENCIL")
98 |
99 | elif objects[item].type == 'LATTICE':
100 | flow.label(text=item, icon="OUTLINER_OB_LATTICE")
101 |
102 | elif objects[item].type == 'LIGHT':
103 | flow.label(text=item, icon="OUTLINER_OB_LIGHT")
104 |
105 | elif objects[item].type == 'LIGHT_PROBE':
106 | flow.label(text=item, icon="OUTLINER_OB_LIGHTPROBE")
107 |
108 | elif objects[item].type == 'MESH':
109 | flow.label(text=item, icon="OUTLINER_OB_MESH")
110 |
111 | elif objects[item].type == 'META':
112 | flow.label(text=item, icon="OUTLINER_OB_META")
113 |
114 | elif objects[item].type == 'SPEAKER':
115 | flow.label(text=item, icon="OUTLINER_OB_SPEAKER")
116 |
117 | elif objects[item].type == 'SURFACE':
118 | flow.label(text=item, icon="OUTLINER_OB_SURFACE")
119 |
120 | # if the object doesn't fit any of the previous types
121 | else:
122 | flow.label(text=item, icon="QUESTION")
123 |
124 | # if the list has no elements
125 | else:
126 |
127 | # display the none label
128 | row = box.row()
129 | row.enabled = False
130 | row.label(text="none")
131 |
132 |
133 | def inspect_header(layout, atom_prop, data):
134 | # a single column containing a search property and basic data
135 | # manipulation functions that appears at the top of all inspect data
136 | # set dialogs
137 |
138 | atom = bpy.context.scene.atomic
139 |
140 | # exterior box and prop search for data-blocks
141 | col = layout.column(align=True)
142 | box = col.box()
143 | row = box.row()
144 | split = row.split()
145 | split.prop_search(atom, atom_prop, bpy.data, data, text="")
146 |
147 | # convert the data set string into an actual data set reference
148 | data = getattr(bpy.data, data)
149 |
150 | # get the string value of the string property
151 | text_field = getattr(atom, atom_prop)
152 |
153 | # determine whether or not the text entered in the string property
154 | # is a valid key
155 | is_valid_key = text_field in data.keys()
156 |
157 | # determine whether or not the piece of data is using a fake user
158 | has_fake_user = is_valid_key and data[text_field].use_fake_user
159 |
160 | # buttons that follow the prop search
161 | split = row.split()
162 | row = split.row(align=True)
163 |
164 | # disable the buttons if the key in the search property is invalid
165 | row.enabled = is_valid_key
166 |
167 | # toggle fake user button (do not show for collections)
168 | # icon and depression changes depending on whether or not the object
169 | # is using a fake user
170 | if data != bpy.data.collections:
171 |
172 | # has fake user
173 | if has_fake_user:
174 | row.operator(
175 | "atomic.toggle_fake_user",
176 | text="",
177 | icon="FAKE_USER_ON",
178 | depress=True
179 | )
180 |
181 | # does not have fake user
182 | else:
183 | row.operator(
184 | "atomic.toggle_fake_user",
185 | text="",
186 | icon="FAKE_USER_OFF",
187 | depress=False
188 | )
189 |
190 | # duplicate button
191 | row.operator(
192 | "atomic.inspection_duplicate",
193 | text="",
194 | icon="DUPLICATE"
195 | )
196 |
197 | # replace button (do not show for collections)
198 | if data != bpy.data.collections:
199 | row.operator(
200 | "atomic.replace",
201 | text="",
202 | icon="UV_SYNC_SELECT"
203 | )
204 |
205 | # rename button
206 | row.operator(
207 | "atomic.rename",
208 | text="",
209 | icon="GREASEPENCIL"
210 | )
211 |
212 | # delete button
213 | row.operator(
214 | "atomic.inspection_delete",
215 | text="",
216 | icon="TRASH"
217 | )
218 |
219 |
220 | def number_suffix(text, number):
221 | # returns the text properly formatted as a suffix
222 | # e.g. passing in "hello" and "100" will result in "hello (100)"
223 |
224 | return text + " ({0})".format(number) if int(number) != 0 else text
225 |
--------------------------------------------------------------------------------