2025-07-01

This commit is contained in:
2026-03-17 14:30:01 -06:00
parent f9a22056dd
commit 62b5978595
4579 changed files with 1257472 additions and 0 deletions
@@ -0,0 +1,55 @@
"""
Copyright (C) 2019 Remington Creative
This file is part of Atomic Data Manager.
Atomic Data Manager is free software: you can redistribute
it and/or modify it under the terms of the GNU General Public License
as published by the Free Software Foundation, either version 3 of the
License, or (at your option) any later version.
Atomic Data Manager is distributed in the hope that it will
be useful, but WITHOUT ANY WARRANTY; without even the implied warranty
of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
General Public License for more details.
You should have received a copy of the GNU General Public License along
with Atomic Data Manager. If not, see <https://www.gnu.org/licenses/>.
---
This file handles the registration of the atomic_data_manager.ui package
"""
from atomic_data_manager.ui import main_panel_ui
from atomic_data_manager.ui import stats_panel_ui
from atomic_data_manager.ui import inspect_ui
from atomic_data_manager.ui import missing_file_ui
from atomic_data_manager.ui import missing_file_ui
from atomic_data_manager.ui import pie_menu_ui
from atomic_data_manager.ui import preferences_ui
from atomic_data_manager.ui import support_me_ui
def register():
# register preferences first so we can access variables in config.py
preferences_ui.register()
# register everything else
main_panel_ui.register()
stats_panel_ui.register()
inspect_ui.register()
missing_file_ui.register()
pie_menu_ui.register()
support_me_ui.register()
def unregister():
main_panel_ui.unregister()
stats_panel_ui.unregister()
inspect_ui.unregister()
missing_file_ui.unregister()
pie_menu_ui.unregister()
preferences_ui.unregister()
support_me_ui.unregister()
@@ -0,0 +1,722 @@
"""
Copyright (C) 2019 Remington Creative
This file is part of Atomic Data Manager.
Atomic Data Manager is free software: you can redistribute
it and/or modify it under the terms of the GNU General Public License
as published by the Free Software Foundation, either version 3 of the
License, or (at your option) any later version.
Atomic Data Manager is distributed in the hope that it will
be useful, but WITHOUT ANY WARRANTY; without even the implied warranty
of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
General Public License for more details.
You should have received a copy of the GNU General Public License along
with Atomic Data Manager. If not, see <https://www.gnu.org/licenses/>.
---
This file contains the inspection user interface.
"""
import bpy
from bpy.utils import register_class
from bpy.utils import unregister_class
from atomic_data_manager.stats import users
from atomic_data_manager.ui.utils import ui_layouts
# bool that triggers an inspection update if it is True when the
# inspection's draw() method is called
inspection_update_trigger = False
def update_inspection(self, context):
global inspection_update_trigger
inspection_update_trigger = True
# Atomic Data Manager Inspect Collections UI Operator
class ATOMIC_OT_inspect_collections(bpy.types.Operator):
"""Inspect Collections"""
bl_idname = "atomic.inspect_collections"
bl_label = "Inspect Collections"
# user lists
users_meshes = []
users_lights = []
users_cameras = []
users_others = []
users_children = []
def draw(self, context):
global inspection_update_trigger
atom = bpy.context.scene.atomic
layout = self.layout
# inspect collections box list
ui_layouts.inspect_header(
layout=layout,
atom_prop="collections_field",
data="collections"
)
# inspection update code
if inspection_update_trigger:
# if key is valid, update the user lists
if atom.collections_field in bpy.data.collections.keys():
self.users_meshes = \
users.collection_meshes(atom.collections_field)
self.users_lights = \
users.collection_lights(atom.collections_field)
self.users_cameras = \
users.collection_cameras(atom.collections_field)
self.users_others = \
users.collection_others(atom.collections_field)
self.users_children = \
users.collection_children(atom.collections_field)
# if key is invalid, empty the user lists
else:
self.users_meshes = []
self.users_lights = []
self.users_cameras = []
self.users_others = []
self.users_children = []
inspection_update_trigger = False
# mesh box list
ui_layouts.box_list(
layout=layout,
title="Meshes",
items=self.users_meshes,
icon="OUTLINER_OB_MESH"
)
# light box list
ui_layouts.box_list(
layout=layout,
title="Lights",
items=self.users_lights,
icon="OUTLINER_OB_LIGHT"
)
# camera box list
ui_layouts.box_list(
layout=layout,
title="Cameras",
items=self.users_cameras,
icon="OUTLINER_OB_CAMERA"
)
# other objects box list
ui_layouts.box_list_diverse(
layout=layout,
title="Other",
items=self.users_others
)
# child collections box list
ui_layouts.box_list(
layout=layout,
title="Child Collections",
items=self.users_children,
icon="OUTLINER_OB_GROUP_INSTANCE"
)
row = layout.row() # extra row for spacing
def execute(self, context):
return {'FINISHED'}
def invoke(self, context, event):
# update inspection context
atom = bpy.context.scene.atomic
atom.active_inspection = "COLLECTIONS"
# trigger update on invoke
global inspection_update_trigger
inspection_update_trigger = True
# invoke inspect dialog
wm = context.window_manager
return wm.invoke_props_dialog(self)
# Atomic Data Manager Inspect Images UI Operator
class ATOMIC_OT_inspect_images(bpy.types.Operator):
"""Inspect Images"""
bl_idname = "atomic.inspect_images"
bl_label = "Inspect Images"
# user lists
users_compositors = []
users_materials = []
users_node_groups = []
users_textures = []
users_worlds = []
def draw(self, context):
global inspection_update_trigger
atom = bpy.context.scene.atomic
layout = self.layout
# inspect images header
ui_layouts.inspect_header(
layout=layout,
atom_prop="images_field",
data="images"
)
# inspection update code
if inspection_update_trigger:
# if key is valid, update the user lists
if atom.images_field in bpy.data.images.keys():
self.users_compositors = \
users.image_compositors(atom.images_field)
self.users_materials = \
users.image_materials(atom.images_field)
self.users_node_groups = \
users.image_node_groups(atom.images_field)
self.users_textures = \
users.image_textures(atom.images_field)
self.users_worlds = \
users.image_worlds(atom.images_field)
# if key is invalid, empty the user lists
else:
self.users_compositors = []
self.users_materials = []
self.users_node_groups = []
self.users_textures = []
self.users_worlds = []
inspection_update_trigger = False
# compositors box list
ui_layouts.box_list(
layout=layout,
title="Compositors",
items=self.users_compositors,
icon="NODE_COMPOSITING"
)
# materials box list
ui_layouts.box_list(
layout=layout,
title="Materials",
items=self.users_materials,
icon="MATERIAL"
)
# node groups box list
ui_layouts.box_list(
layout=layout,
title="Node Groups",
items=self.users_node_groups,
icon="NODETREE"
)
# textures box list
ui_layouts.box_list(
layout=layout,
title="Textures",
items=self.users_textures,
icon="TEXTURE"
)
# worlds box list
ui_layouts.box_list(
layout=layout,
title="Worlds",
items=self.users_worlds,
icon="WORLD"
)
row = layout.row() # extra row for spacing
def execute(self, context):
return {'FINISHED'}
def invoke(self, context, event):
# update inspection context
atom = bpy.context.scene.atomic
atom.active_inspection = "IMAGES"
# trigger update on invoke
global inspection_update_trigger
inspection_update_trigger = True
# invoke inspect dialog
wm = context.window_manager
return wm.invoke_props_dialog(self)
# Atomic Data Manager Inspect Lights UI Operator
class ATOMIC_OT_inspect_lights(bpy.types.Operator):
"""Inspect Lights"""
bl_idname = "atomic.inspect_lights"
bl_label = "Inspect Lights"
# user lists
users_objects = []
def draw(self, context):
global inspection_update_trigger
atom = bpy.context.scene.atomic
layout = self.layout
# inspect lights header
ui_layouts.inspect_header(
layout=layout,
atom_prop="lights_field",
data="lights"
)
# inspection update code
if inspection_update_trigger:
# if key is valid, update the user lists
if atom.lights_field in bpy.data.lights.keys():
self.users_objects = users.light_objects(atom.lights_field)
# if key is invalid, empty the user lists
else:
self.users_objects = []
inspection_update_trigger = False
# light objects box list
ui_layouts.box_list(
layout=layout,
title="Light Objects",
items=self.users_objects,
icon="OUTLINER_OB_LIGHT"
)
row = layout.row() # extra row for spacing
def execute(self, context):
return {'FINISHED'}
def invoke(self, context, event):
# update inspection context
atom = bpy.context.scene.atomic
atom.active_inspection = "LIGHTS"
# trigger update on invoke
global inspection_update_trigger
inspection_update_trigger = True
# invoke inspect dialog
wm = context.window_manager
return wm.invoke_props_dialog(self)
# Atomic Data Manager Inspect Materials UI Operator
class ATOMIC_OT_inspect_materials(bpy.types.Operator):
"""Inspect Materials"""
bl_idname = "atomic.inspect_materials"
bl_label = "Inspect Materials"
# user lists
users_objects = []
def draw(self, context):
global inspection_update_trigger
atom = bpy.context.scene.atomic
layout = self.layout
# inspect materials header
ui_layouts.inspect_header(
layout=layout,
atom_prop="materials_field",
data="materials"
)
# inspection update code
if inspection_update_trigger:
# if key is valid, update the user lists
if atom.materials_field in bpy.data.materials.keys():
self.users_objects = \
users.material_objects(atom.materials_field)
# if key is invalid, empty the user lists
else:
self.users_objects = []
inspection_update_trigger = False
# objects box list
ui_layouts.box_list_diverse(
layout=layout,
title="Objects",
items=self.users_objects
)
row = layout.row() # extra row for spacing
def execute(self, context):
return {'FINISHED'}
def invoke(self, context, event):
# update inspection context
atom = bpy.context.scene.atomic
atom.active_inspection = "MATERIALS"
# trigger update on invoke
global inspection_update_trigger
inspection_update_trigger = True
# invoke inspect dialog
wm = context.window_manager
return wm.invoke_props_dialog(self)
# Atomic Data Manager Inspect Node Groups UI Operator
class ATOMIC_OT_inspect_node_groups(bpy.types.Operator):
"""Inspect Node Groups"""
bl_idname = "atomic.inspect_node_groups"
bl_label = "Inspect Node Groups"
# user lists
users_compositors = []
users_materials = []
users_node_groups = []
users_textures = []
users_worlds = []
def draw(self, context):
global inspection_update_trigger
atom = bpy.context.scene.atomic
layout = self.layout
# inspect node groups header
ui_layouts.inspect_header(
layout=layout,
atom_prop="node_groups_field",
data="node_groups"
)
# inspection update code
if inspection_update_trigger:
# if key is valid, update the user lists
if atom.node_groups_field in bpy.data.node_groups.keys():
self.users_compositors = \
users.node_group_compositors(atom.node_groups_field)
self.users_materials = \
users.node_group_materials(atom.node_groups_field)
self.users_node_groups = \
users.node_group_node_groups(atom.node_groups_field)
self.users_textures = \
users.node_group_textures(atom.node_groups_field)
self.users_worlds = \
users.node_group_worlds(atom.node_groups_field)
# if key is invalid, empty the user lists
else:
self.users_compositors = []
self.users_materials = []
self.users_node_groups = []
self.users_textures = []
self.users_worlds = []
inspection_update_trigger = False
# compositors box list
ui_layouts.box_list(
layout=layout,
title="Compositors",
items=self.users_compositors,
icon="NODE_COMPOSITING"
)
# materials box list
ui_layouts.box_list(
layout=layout,
title="Materials",
items=self.users_materials,
icon="MATERIAL"
)
# node groups box list
ui_layouts.box_list(
layout=layout,
title="Node Groups",
items=self.users_node_groups,
icon="NODETREE"
)
# textures box list
ui_layouts.box_list(
layout=layout,
title="Textures",
items=self.users_textures,
icon="TEXTURE"
)
# world box list
ui_layouts.box_list(
layout=layout,
title="Worlds",
items=self.users_worlds,
icon="WORLD"
)
row = layout.row() # extra row for spacing
def execute(self, context):
return {'FINISHED'}
def invoke(self, context, event):
# update inspection context
atom = bpy.context.scene.atomic
atom.active_inspection = "NODE_GROUPS"
# trigger update on invoke
global inspection_update_trigger
inspection_update_trigger = True
# invoke inspect dialog
wm = context.window_manager
return wm.invoke_props_dialog(self)
# Atomic Data Manager Inspect Particles UI Operator
class ATOMIC_OT_inspect_particles(bpy.types.Operator):
"""Inspect Particle Systems"""
bl_idname = "atomic.inspect_particles"
bl_label = "Inspect Particles"
# user lists
users_objects = []
def draw(self, context):
global inspection_update_trigger
atom = bpy.context.scene.atomic
layout = self.layout
# inspect particles header
ui_layouts.inspect_header(
layout=layout,
atom_prop="particles_field",
data="particles"
)
# inspection update code
if inspection_update_trigger:
# if key is valid, update the user lists
if atom.particles_field in bpy.data.particles.keys():
self.users_objects = \
users.particle_objects(atom.particles_field)
# if key is invalid, empty the user lists
else:
self.users_objects = []
inspection_update_trigger = False
# objects box list
ui_layouts.box_list(
layout=layout,
title="Objects",
items=self.users_objects,
icon="OUTLINER_OB_MESH"
)
row = layout.row() # extra row for spacing
def execute(self, context):
return {'FINISHED'}
def invoke(self, context, event):
# update inspection context
atom = bpy.context.scene.atomic
atom.active_inspection = "PARTICLES"
# trigger update on invoke
global inspection_update_trigger
inspection_update_trigger = True
# invoke inspect dialog
wm = context.window_manager
return wm.invoke_props_dialog(self)
# Atomic Data Manager Inspect Textures UI Operator
class ATOMIC_OT_inspect_textures(bpy.types.Operator):
"""Inspect Textures"""
bl_idname = "atomic.inspect_textures"
bl_label = "Inspect Textures"
# user lists
users_compositors = []
users_brushes = []
users_particles = []
users_objects = []
def draw(self, context):
global inspection_update_trigger
atom = bpy.context.scene.atomic
layout = self.layout
# inspect textures header
ui_layouts.inspect_header(
layout=layout,
atom_prop="textures_field",
data="textures"
)
# inspection update code
if inspection_update_trigger:
# if the key is valid, update the user lists
if atom.textures_field in bpy.data.textures.keys():
self.users_compositors = \
users.texture_compositor(atom.textures_field)
self.users_brushes = \
users.texture_brushes(atom.textures_field)
self.users_objects = \
users.texture_objects(atom.textures_field)
self.users_particles = \
users.texture_particles(atom.textures_field)
# if the key is invalid, set empty the user lists
else:
self.users_compositors = []
self.users_brushes = []
self.users_particles = []
self.users_objects = []
inspection_update_trigger = False
# brushes box list
ui_layouts.box_list(
layout=layout,
title="Brushes",
items=self.users_brushes,
icon="BRUSH_DATA"
)
# compositors box list
ui_layouts.box_list(
layout=layout,
title="Compositors",
items=self.users_compositors,
icon="NODE_COMPOSITING"
)
# particles box list
ui_layouts.box_list(
layout=layout,
title="Particles",
items=self.users_particles,
icon="PARTICLES"
)
# objects box list
ui_layouts.box_list_diverse(
layout=layout,
title="Objects",
items=self.users_objects,
)
row = layout.row() # extra row for spacing
def execute(self, context):
return {'FINISHED'}
def invoke(self, context, event):
# update inspection context
atom = bpy.context.scene.atomic
atom.active_inspection = "TEXTURES"
# trigger update on invoke
global inspection_update_trigger
inspection_update_trigger = True
# invoke inspect dialog
wm = context.window_manager
return wm.invoke_props_dialog(self)
# Atomic Data Manager Inspect Worlds UI Operator
class ATOMIC_OT_inspect_worlds(bpy.types.Operator):
"""Inspect Worlds"""
bl_idname = "atomic.inspect_worlds"
bl_label = "Inspect Worlds"
def draw(self, context):
layout = self.layout
# inspect worlds header
ui_layouts.inspect_header(
layout=layout,
atom_prop="worlds_field",
data="worlds"
)
# worlds box list
ui_layouts.box_list(
layout=layout,
title="Worlds in Scene",
items=bpy.data.worlds.keys(),
icon="WORLD"
)
row = layout.row() # extra row for spacing
def execute(self, context):
return {'FINISHED'}
def invoke(self, context, event):
# update inspection context
atom = bpy.context.scene.atomic
atom.active_inspection = "WORLDS"
# trigger update on invoke
global inspection_update_trigger
inspection_update_trigger = True
# invoke inspect dialog
wm = context.window_manager
return wm.invoke_props_dialog(self)
reg_list = [
ATOMIC_OT_inspect_collections,
ATOMIC_OT_inspect_images,
ATOMIC_OT_inspect_lights,
ATOMIC_OT_inspect_materials,
ATOMIC_OT_inspect_node_groups,
ATOMIC_OT_inspect_particles,
ATOMIC_OT_inspect_textures,
ATOMIC_OT_inspect_worlds
]
def register():
for cls in reg_list:
register_class(cls)
def unregister():
for cls in reg_list:
unregister_class(cls)
@@ -0,0 +1,245 @@
"""
Copyright (C) 2019 Remington Creative
This file is part of Atomic Data Manager.
Atomic Data Manager is free software: you can redistribute
it and/or modify it under the terms of the GNU General Public License
as published by the Free Software Foundation, either version 3 of the
License, or (at your option) any later version.
Atomic Data Manager is distributed in the hope that it will
be useful, but WITHOUT ANY WARRANTY; without even the implied warranty
of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
General Public License for more details.
You should have received a copy of the GNU General Public License along
with Atomic Data Manager. If not, see <https://www.gnu.org/licenses/>.
---
This file contains the primary Atomic Data Manager panel that will
appear in the Scene tab of the Properties panel.
This panel contains the Nuke/Clean/Undo buttons as well as the data
category toggles and the category selection tools.
"""
import bpy
from bpy.utils import register_class
from bpy.utils import unregister_class
from atomic_data_manager.stats import count
from atomic_data_manager.ui.utils import ui_layouts
# Atomic Data Manager Main Panel
class ATOMIC_PT_main_panel(bpy.types.Panel):
"""The main Atomic Data Manager panel"""
bl_label = "Atomic Data Manager"
bl_space_type = "PROPERTIES"
bl_region_type = "WINDOW"
bl_context = "scene"
def draw(self, context):
layout = self.layout
atom = bpy.context.scene.atomic
category_props = [
atom.collections,
atom.images,
atom.lights,
atom.materials,
atom.node_groups,
atom.particles,
atom.textures,
atom.worlds
]
# nuke and clean buttons
row = layout.row(align=True)
row.scale_y = 2.0
row.operator("atomic.nuke", text="Nuke", icon="GHOST_ENABLED")
row.operator("atomic.clean", text="Clean", icon="PARTICLEMODE")
row.operator("atomic.undo", text="Undo", icon="LOOP_BACK")
row = layout.row()
# category toggles
split = layout.split(align=False)
# left column
col = split.column(align=True)
# collections buttons
splitcol = col.split(factor=0.8, align=True)
splitcol.prop(
atom,
"collections",
text="Collections",
icon='GROUP',
toggle=True
)
splitcol.operator(
"atomic.inspect_collections",
icon='VIEWZOOM',
text=""
)
# lights buttons
splitcol = col.split(factor=0.8, align=True)
splitcol.prop(
atom,
"lights",
text="Lights",
icon='LIGHT',
toggle=True
)
splitcol.operator(
"atomic.inspect_lights",
icon='VIEWZOOM',
text=""
)
# node groups buttons
splitcol = col.split(factor=0.8, align=True)
splitcol.prop(
atom,
"node_groups",
text="Node Groups",
icon='NODETREE',
toggle=True
)
splitcol.operator(
"atomic.inspect_node_groups",
icon='VIEWZOOM',
text=""
)
# textures button
splitcol = col.split(factor=0.8, align=True)
splitcol.prop(
atom,
"textures",
text="Textures",
icon='TEXTURE',
toggle=True
)
splitcol.operator(
"atomic.inspect_textures",
icon='VIEWZOOM',
text=""
)
# right column
col = split.column(align=True)
# images buttons
splitcol = col.split(factor=0.8, align=True)
splitcol.prop(
atom,
"images",
text="Images",
toggle=True,
icon='IMAGE_DATA'
)
splitcol.operator(
"atomic.inspect_images",
icon='VIEWZOOM',
text=""
)
# materials buttons
splitcol = col.split(factor=0.8, align=True)
splitcol.prop(
atom,
"materials",
text="Materials",
icon='MATERIAL',
toggle=True
)
splitcol.operator(
"atomic.inspect_materials",
icon='VIEWZOOM',
text=""
)
# particles buttons
splitcol = col.split(factor=0.8, align=True)
splitcol.prop(
atom,
"particles",
text="Particles",
icon='PARTICLES',
toggle=True
)
splitcol.operator(
"atomic.inspect_particles",
icon='VIEWZOOM',
text=""
)
# worlds buttons
splitcol = col.split(factor=0.8, align=True)
splitcol.prop(
atom,
"worlds",
text="Worlds",
icon='WORLD',
toggle=True
)
splitcol.operator(
"atomic.inspect_worlds",
icon='VIEWZOOM',
text=""
)
# selection operators
row = layout.row(align=True)
row.operator(
"atomic.smart_select",
text='Smart Select',
icon='ZOOM_SELECTED'
)
if all(prop is True for prop in category_props):
row.operator(
"atomic.deselect_all",
text="Deselect All",
icon='RESTRICT_SELECT_ON'
)
else:
row.operator(
"atomic.select_all",
text="Select All",
icon='RESTRICT_SELECT_OFF'
)
reg_list = [ATOMIC_PT_main_panel]
def register():
for cls in reg_list:
register_class(cls)
def unregister():
for cls in reg_list:
unregister_class(cls)
@@ -0,0 +1,195 @@
"""
Copyright (C) 2019 Remington Creative
This file is part of Atomic Data Manager.
Atomic Data Manager is free software: you can redistribute
it and/or modify it under the terms of the GNU General Public License
as published by the Free Software Foundation, either version 3 of the
License, or (at your option) any later version.
Atomic Data Manager is distributed in the hope that it will
be useful, but WITHOUT ANY WARRANTY; without even the implied warranty
of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
General Public License for more details.
You should have received a copy of the GNU General Public License along
with Atomic Data Manager. If not, see <https://www.gnu.org/licenses/>.
---
This file contains the user interface for the missing file dialog that
pops up when missing files are detected on file load.
"""
import bpy
from bpy.utils import register_class
from bpy.utils import unregister_class
from bpy.app.handlers import persistent
from atomic_data_manager import config
from atomic_data_manager.stats import missing
from atomic_data_manager.ui.utils import ui_layouts
# Atomic Data Manager Detect Missing Files Popup
class ATOMIC_OT_detect_missing(bpy.types.Operator):
"""Detect missing files in this project"""
bl_idname = "atomic.detect_missing"
bl_label = "Missing File Detection"
# missing file lists
missing_images = []
missing_libraries = []
# missing file recovery option enum property
recovery_option: bpy.props.EnumProperty(
items=[
(
'IGNORE',
'Ignore Missing Files',
'Ignore the missing files and leave them offline'
),
(
'RELOAD',
'Reload Missing Files',
'Reload the missing files from their existing file paths'
),
(
'REMOVE',
'Remove Missing Files',
'Remove the missing files from the project'
),
(
'SEARCH',
'Search for Missing Files (under development)',
'Search for the missing files in a directory'
),
(
'REPLACE',
'Specify Replacement Files (under development)',
'Replace missing files with new files'
),
],
default='IGNORE'
)
def draw(self, context):
layout = self.layout
# missing files interface if missing files are found
if self.missing_images or self.missing_libraries:
# header warning
row = layout.row()
row.label(
text="Atomic has detected one or more missing files in "
"your project!"
)
# missing images box list
if self.missing_images:
ui_layouts.box_list(
layout=layout,
title="Images",
items=self.missing_images,
icon="IMAGE_DATA",
columns=3
)
# missing libraries box list
if self.missing_libraries:
ui_layouts.box_list(
layout=layout,
title="Libraries",
items=self.missing_libraries,
icon="LIBRARY_DATA_DIRECT",
columns=3
)
row = layout.separator() # extra space
# recovery option selection
row = layout.row()
row.label(text="What would you like to do?")
row = layout.row()
row.prop(self, 'recovery_option', text="")
# missing files interface if no missing files are found
else:
row = layout.row()
row.label(text="No missing files were found!")
# empty box list
ui_layouts.box_list(
layout=layout
)
row = layout.separator() # extra space
def execute(self, context):
# ignore missing files will take no action
# reload missing files
if self.recovery_option == 'RELOAD':
bpy.ops.atomic.reload_missing('INVOKE_DEFAULT')
# remove missing files
elif self.recovery_option == 'REMOVE':
bpy.ops.atomic.remove_missing('INVOKE_DEFAULT')
# search for missing files
elif self.recovery_option == 'SEARCH':
bpy.ops.atomic.search_missing('INVOKE_DEFAULT')
# replace missing files
elif self.recovery_option == 'REPLACE':
bpy.ops.atomic.replace_missing('INVOKE_DEFAULT')
return {'FINISHED'}
def invoke(self, context, event):
# update missing file lists
self.missing_images = missing.images()
self.missing_libraries = missing.libraries()
wm = context.window_manager
# invoke large dialog if there are missing files
if self.missing_images or self.missing_libraries:
return wm.invoke_props_dialog(self, width=500)
# invoke small dialog if there are no missing files
else:
return wm.invoke_popup(self, width=300)
@persistent
def autodetect_missing_files(dummy=None):
# invokes the detect missing popup when missing files are detected upon
# loading a new Blender project
if config.enable_missing_file_warning and \
(missing.images() or missing.libraries()):
bpy.ops.atomic.detect_missing('INVOKE_DEFAULT')
reg_list = [ATOMIC_OT_detect_missing]
def register():
for item in reg_list:
register_class(item)
# run missing file auto-detection after loading a Blender file
bpy.app.handlers.load_post.append(autodetect_missing_files)
def unregister():
for item in reg_list:
unregister_class(item)
# stop running missing file auto-detection after loading a Blender file
bpy.app.handlers.load_post.remove(autodetect_missing_files)
@@ -0,0 +1,200 @@
"""
Copyright (C) 2019 Remington Creative
This file is part of Atomic Data Manager.
Atomic Data Manager is free software: you can redistribute
it and/or modify it under the terms of the GNU General Public License
as published by the Free Software Foundation, either version 3 of the
License, or (at your option) any later version.
Atomic Data Manager is distributed in the hope that it will
be useful, but WITHOUT ANY WARRANTY; without even the implied warranty
of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
General Public License for more details.
You should have received a copy of the GNU General Public License along
with Atomic Data Manager. If not, see <https://www.gnu.org/licenses/>.
---
This file contains Atomic's pie menu UI and its pie menu keymap
registration.
"""
import bpy
from bpy.utils import register_class
from bpy.utils import unregister_class
# Atomic Data Manager Main Pie Menu
class ATOMIC_MT_main_pie(bpy.types.Menu):
bl_idname = "ATOMIC_MT_main_pie"
bl_label = "Atomic Data Manager"
def draw(self, context):
layout = self.layout
pie = layout.menu_pie()
# nuke all operator
pie.operator(
"atomic.nuke_all",
text="Nuke All",
icon="GHOST_ENABLED"
)
# clean all operator
pie.operator(
"atomic.clean_all",
text="Clean All",
icon="PARTICLEMODE"
)
# undo operator
pie.operator(
"atomic.detect_missing",
text="Detect Missing Files",
icon="SHADERFX"
)
# inspect category operator
pie.operator(
"wm.call_menu_pie",
text="Inspect",
icon="VIEWZOOM"
).name = "ATOMIC_MT_inspect_pie"
# nuke category operator
pie.operator(
"wm.call_menu_pie",
text="Nuke",
icon="GHOST_ENABLED"
).name = "ATOMIC_MT_nuke_pie"
# clean category operator
pie.operator(
"wm.call_menu_pie",
text="Clean",
icon="PARTICLEMODE"
).name = "ATOMIC_MT_clean_pie"
# Atomic Data Manager Nuke Pie Menu
class ATOMIC_MT_nuke_pie(bpy.types.Menu):
bl_idname = "ATOMIC_MT_nuke_pie"
bl_label = "Atomic Nuke"
def draw(self, context):
layout = self.layout
pie = layout.menu_pie()
# nuke node groups operator
pie.operator("atomic.nuke_node_groups", icon="NODETREE")
# nuke materials operator
pie.operator("atomic.nuke_materials", icon="MATERIAL")
# nuke worlds operator
pie.operator("atomic.nuke_worlds", icon="WORLD")
# nuke collections operator
pie.operator("atomic.nuke_collections", icon="GROUP")
# nuke lights operator
pie.operator("atomic.nuke_lights", icon="LIGHT")
# nuke images operator
pie.operator("atomic.nuke_images", icon="IMAGE_DATA")
# nuke textures operator
pie.operator("atomic.nuke_textures", icon="TEXTURE")
# nuke particles operator
pie.operator("atomic.nuke_particles", icon="PARTICLES")
# Atomic Data Manager Clean Pie Menu
class ATOMIC_MT_clean_pie(bpy.types.Menu):
bl_idname = "ATOMIC_MT_clean_pie"
bl_label = "Atomic Clean"
def draw(self, context):
layout = self.layout
pie = layout.menu_pie()
# clean node groups operator
pie.operator("atomic.clean_node_groups", icon="NODETREE")
# clean materials operator
pie.operator("atomic.clean_materials", icon="MATERIAL")
# clean worlds operator
pie.operator("atomic.clean_worlds", icon="WORLD")
# clean collections operator
pie.operator("atomic.clean_collections", icon="GROUP")
# clean lights operator
pie.operator("atomic.clean_lights", icon="LIGHT")
# clean images operator
pie.operator("atomic.clean_images", icon="IMAGE_DATA")
# clean textures operator
pie.operator("atomic.clean_textures", icon="TEXTURE")
# clean materials operator
pie.operator("atomic.clean_particles", icon="PARTICLES")
# Atomic Data Manager Inspect Pie Menu
class ATOMIC_MT_inspect_pie(bpy.types.Menu):
bl_idname = "ATOMIC_MT_inspect_pie"
bl_label = "Atomic Inspect"
def draw(self, context):
layout = self.layout
pie = layout.menu_pie()
# inspect node groups operator
pie.operator("atomic.inspect_node_groups", icon="NODETREE")
# inspect materials operator
pie.operator("atomic.inspect_materials", icon="MATERIAL")
# inspect worlds operator
pie.operator("atomic.inspect_worlds", icon="WORLD")
# inspect groups operator
pie.operator("atomic.inspect_collections", icon="GROUP")
# inspect lights operator
pie.operator("atomic.inspect_lights", icon="LIGHT")
# inspect images operator
pie.operator("atomic.inspect_images", icon="IMAGE_DATA")
# inspect textures operator
pie.operator("atomic.inspect_textures", icon="TEXTURE")
# inspect particles operator
pie.operator("atomic.inspect_particles", icon="PARTICLES")
reg_list = [
ATOMIC_MT_main_pie,
ATOMIC_MT_nuke_pie,
ATOMIC_MT_clean_pie,
ATOMIC_MT_inspect_pie
]
def register():
for cls in reg_list:
register_class(cls)
def unregister():
for cls in reg_list:
unregister_class(cls)
@@ -0,0 +1,350 @@
"""
Copyright (C) 2019 Remington Creative
This file is part of Atomic Data Manager.
Atomic Data Manager is free software: you can redistribute
it and/or modify it under the terms of the GNU General Public License
as published by the Free Software Foundation, either version 3 of the
License, or (at your option) any later version.
Atomic Data Manager is distributed in the hope that it will
be useful, but WITHOUT ANY WARRANTY; without even the implied warranty
of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
General Public License for more details.
You should have received a copy of the GNU General Public License along
with Atomic Data Manager. If not, see <https://www.gnu.org/licenses/>.
---
This file contains the Atomic preferences UI, preferences properties, and
some functions for syncing the preference properties with external factors.
"""
import bpy
from bpy.utils import register_class
from bpy.utils import unregister_class
from atomic_data_manager import config
from atomic_data_manager.updater import addon_updater_ops
def set_enable_support_me_popup(value):
# sets the value of the enable_support_me_popup boolean property
bpy.context.preferences.addons["atomic_data_manager"]\
.preferences.enable_support_me_popup = value
copy_prefs_to_config(None, None)
bpy.ops.wm.save_userpref()
def set_last_popup_day(day):
# sets the value of the last_popup_day float property
bpy.context.preferences.addons["atomic_data_manager"]\
.preferences.last_popup_day = day
copy_prefs_to_config(None, None)
def copy_prefs_to_config(self, context):
# copies the values of Atomic's preferences to the variables in
# config.py for global use
preferences = bpy.context.preferences
atomic_preferences = preferences.addons['atomic_data_manager']\
.preferences
# visible atomic preferences
config.enable_missing_file_warning = \
atomic_preferences.enable_missing_file_warning
config.enable_pie_menu_ui = \
atomic_preferences.enable_pie_menu_ui
config.enable_support_me_popup = \
atomic_preferences.enable_support_me_popup
config.include_fake_users = \
atomic_preferences.include_fake_users
# hidden atomic preferences
config.pie_menu_type = \
atomic_preferences.pie_menu_type
config.pie_menu_alt = \
atomic_preferences.pie_menu_alt
config.pie_menu_any = \
atomic_preferences.pie_menu_any
config.pie_menu_ctrl = \
atomic_preferences.pie_menu_ctrl
config.pie_menu_oskey = \
atomic_preferences.pie_menu_oskey
config.pie_menu_shift = \
atomic_preferences.pie_menu_shift
config.last_popup_day = \
atomic_preferences.last_popup_day
def update_pie_menu_hotkeys(self, context):
preferences = bpy.context.preferences
atomic_preferences = preferences.addons['atomic_data_manager'] \
.preferences
# add the hotkeys if the preference is enabled
if atomic_preferences.enable_pie_menu_ui:
add_pie_menu_hotkeys()
# remove the hotkeys otherwise
else:
remove_pie_menu_hotkeys()
def add_pie_menu_hotkeys():
# adds the pie menu hotkeys to blender's addon keymaps
global keymaps
keyconfigs = bpy.context.window_manager.keyconfigs.addon
# check to see if a window keymap already exists
if "Window" in keyconfigs.keymaps.keys():
km = keyconfigs.keymaps['Window']
# if not, crate a new one
else:
km = keyconfigs.keymaps.new(
name="Window",
space_type='EMPTY',
region_type='WINDOW'
)
# add a new keymap item to that keymap
kmi = km.keymap_items.new(
idname="atomic.invoke_pie_menu_ui",
type=config.pie_menu_type,
value="PRESS",
alt=config.pie_menu_alt,
any=config.pie_menu_any,
ctrl=config.pie_menu_ctrl,
oskey=config.pie_menu_oskey,
shift=config.pie_menu_shift,
)
# # point the keymap item to our pie menu
# kmi.properties.name = "ATOMIC_MT_main_pie"
keymaps.append((km, kmi))
def remove_pie_menu_hotkeys():
# removes the pie menu hotkeys from blender's addon keymaps if they
# exist there
global keymaps
# remove each hotkey in our keymaps list if it exists in blenders
# addon keymaps
for km, kmi in keymaps:
km.keymap_items.remove(kmi)
# clear our keymaps list
keymaps.clear()
# Atomic Data Manager Preference Panel UI
class ATOMIC_PT_preferences_panel(bpy.types.AddonPreferences):
bl_idname = "atomic_data_manager"
# visible atomic preferences
enable_missing_file_warning: bpy.props.BoolProperty(
description="Display a warning on startup if Atomic detects "
"missing files in your project",
default=True
)
enable_support_me_popup: bpy.props.BoolProperty(
description="Occasionally display a popup asking if you would "
"like to support Remington Creative",
default=True
)
include_fake_users: bpy.props.BoolProperty(
description="Include data-blocks with only fake users in unused "
"data detection",
default=False
)
enable_pie_menu_ui: bpy.props.BoolProperty(
description="Enable the Atomic pie menu UI, so you can clean "
"your project from anywhere.",
default=True,
update=update_pie_menu_hotkeys
)
# hidden atomic preferences
pie_menu_type: bpy.props.StringProperty(
default="D"
)
pie_menu_alt: bpy.props.BoolProperty(
default=False
)
pie_menu_any: bpy.props.BoolProperty(
default=False
)
pie_menu_ctrl: bpy.props.BoolProperty(
default=False
)
pie_menu_oskey: bpy.props.BoolProperty(
default=False
)
pie_menu_shift: bpy.props.BoolProperty(
default=False
)
last_popup_day: bpy.props.FloatProperty(
default=0
)
# add-on updater properties
auto_check_update: bpy.props.BoolProperty(
name="Auto-check for Update",
description="If enabled, auto-check for updates using an interval",
default=True,
)
updater_intrval_months: bpy.props.IntProperty(
name='Months',
description="Number of months between checking for updates",
default=0,
min=0,
max=6
)
updater_intrval_days: bpy.props.IntProperty(
name='Days',
description="Number of days between checking for updates",
default=7,
min=0,
)
updater_intrval_hours: bpy.props.IntProperty(
name='Hours',
description="Number of hours between checking for updates",
default=0,
min=0,
max=23
)
updater_intrval_minutes: bpy.props.IntProperty(
name='Minutes',
description="Number of minutes between checking for updates",
default=0,
min=0,
max=59
)
def draw(self, context):
layout = self.layout
split = layout.split()
# left column
col = split.column()
# enable missing file warning toggle
col.prop(
self,
"enable_missing_file_warning",
text="Show Missing File Warning"
)
# enable support me popup toggle
col.prop(
self,
"enable_support_me_popup",
text="Show \"Support Me\" Popup"
)
# right column
col = split.column()
# ignore fake users toggle
col.prop(
self,
"include_fake_users",
text="Include Fake Users"
)
# pie menu settings
pie_split = col.split(factor=0.55) # nice
# enable pie menu ui toggle
pie_split.prop(
self,
"enable_pie_menu_ui",
text="Enable Pie Menu"
)
# put the property in a row so it can be disabled
pie_row = pie_split.row()
pie_row.enabled = self.enable_pie_menu_ui
if pie_row.enabled:
# keymap item that contains our pie menu hotkey
# note: keymap item index hardcoded with an index -- may be
# dangerous if more keymap items are added
kmi = bpy.context.window_manager.keyconfigs.addon.keymaps[
'Window'].keymap_items[0]
# hotkey property
pie_row.prop(
kmi,
"type",
text="",
full_event=True
)
# update hotkey preferences
self.pie_menu_type = kmi.type
self.pie_menu_any = kmi.any
self.pie_menu_alt = kmi.alt
self.pie_menu_ctrl = kmi.ctrl
self.pie_menu_oskey = kmi.oskey
self.pie_menu_shift = kmi.shift
separator = layout.row() # extra space
# add-on updater box
addon_updater_ops.update_settings_ui(self, context)
# update config with any new preferences
copy_prefs_to_config(None, None)
reg_list = [ATOMIC_PT_preferences_panel]
keymaps = []
def register():
for cls in reg_list:
register_class(cls)
# make sure global preferences are updated on registration
copy_prefs_to_config(None, None)
# update keymaps
add_pie_menu_hotkeys()
def unregister():
for cls in reg_list:
unregister_class(cls)
remove_pie_menu_hotkeys()
@@ -0,0 +1,376 @@
"""
Copyright (C) 2019 Remington Creative
This file is part of Atomic Data Manager.
Atomic Data Manager is free software: you can redistribute
it and/or modify it under the terms of the GNU General Public License
as published by the Free Software Foundation, either version 3 of the
License, or (at your option) any later version.
Atomic Data Manager is distributed in the hope that it will
be useful, but WITHOUT ANY WARRANTY; without even the implied warranty
of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
General Public License for more details.
You should have received a copy of the GNU General Public License along
with Atomic Data Manager. If not, see <https://www.gnu.org/licenses/>.
---
This file contains the user interface for Atomic's statistics subpanel.
The statistics panel is nested in the main Atomic Data Manager panel. This
panel contains statistics about the Blender file and each data category in
it.
"""
import bpy
from bpy.utils import register_class
from bpy.utils import unregister_class
from atomic_data_manager.stats import count
from atomic_data_manager.stats import misc
from atomic_data_manager.ui.utils import ui_layouts
# Atomic Data Manager Statistics SubPanel
class ATOMIC_PT_stats_panel(bpy.types.Panel):
"""The Atomic Data Manager \"Stats for Nerds\" panel"""
bl_idname = "ATOMIC_PT_stats_panel"
bl_label = "Stats for Nerds"
bl_space_type = "PROPERTIES"
bl_region_type = "WINDOW"
bl_parent_id = "ATOMIC_PT_main_panel"
def draw(self, context):
layout = self.layout
atom = bpy.context.scene.atomic
# categories selector / header
row = layout.row()
row.label(text="Categories:")
row.prop(atom, "stats_mode", expand=True, icon_only=True)
# statistics box
box = layout.box()
# overview statistics
if atom.stats_mode == 'OVERVIEW':
# category header label
row = box.row()
row.label(text="Overview", icon='FILE')
# blender project file size statistic
row = box.row()
row.label(text="Blend File Size: " + misc.blend_size())
# cateogry statistics
split = box.split()
# left column
col = split.column()
# left column category labels
col.label(text="Collections")
col.label(text="Lights")
col.label(text="Node Groups")
col.label(text="Textures")
col = split.column()
# collection count
col.label(text=str(count.collections()))
# light count
col.label(text=str(count.lights()))
# node group count
col.label(text=str(count.node_groups()))
# texture count
col.label(text=str(count.textures()))
# right column
col = split.column()
# right column category labels
col.label(text="Images")
col.label(text="Materials")
col.label(text="Particles")
col.label(text="Worlds")
col = split.column()
# image count
col.label(text=str(count.images()))
# material count
col.label(text=str(count.materials()))
# particle system count
col.label(text=str(count.particles()))
# world count
col.label(text=str(count.worlds()))
# collection statistics
elif atom.stats_mode == 'COLLECTIONS':
# category header label
row = box.row()
row.label(text="Collections", icon='GROUP')
split = box.split()
# total and placeholder count
col = split.column()
col.label(
text="Total: {0}".format(count.collections())
)
# col.label(text="Placeholder") # TODO: remove placeholder
# unused and unnamed count
col = split.column()
col.label(
text="Unused: {0}".format(count.collections_unused())
)
col.label(
text="Unnamed: {0}".format(count.collections_unnamed())
)
# image statistics
elif atom.stats_mode == 'IMAGES':
# category header label
row = box.row()
row.label(text="Images", icon='IMAGE_DATA')
split = box.split()
# total and missing count
col = split.column()
col.label(
text="Total: {0}".format(count.images())
)
col.label(
text="Missing: {0}".format(count.images_missing())
)
# unused and unnamed count
col = split.column()
col.label(
text="Unused: {0}".format(count.images_unused())
)
col.label(
text="Unnamed: {0}".format(count.images_unnamed())
)
# light statistics
elif atom.stats_mode == 'LIGHTS':
row = box.row()
row.label(text="Lights", icon='LIGHT')
split = box.split()
# total and placeholder count
col = split.column()
col.label(
text="Total: {0}".format(count.lights())
)
# col.label(text="Placeholder") # TODO: remove placeholder
# unused and unnamed count
col = split.column()
col.label(
text="Unused: {0}".format(count.lights_unused())
)
col.label(
text="Unnamed: {0}".format(count.lights_unnamed())
)
# material statistics
elif atom.stats_mode == 'MATERIALS':
# category header label
row = box.row()
row.label(text="Materials", icon='MATERIAL')
split = box.split()
# total and placeholder count
col = split.column()
col.label(
text="Total: {0}".format(count.materials())
)
# col.label(text="Placeholder") # TODO: remove placeholder
# unused and unnamed count
col = split.column()
col.label(
text="Unused: {0}".format(count.materials_unused())
)
col.label(
text="Unnamed: {0}".format(count.materials_unnamed())
)
# object statistics
elif atom.stats_mode == 'OBJECTS':
# category header label
row = box.row()
row.label(text="Objects", icon='OBJECT_DATA')
# total count
split = box.split()
col = split.column()
col.label(
text="Total: {0}".format(count.objects())
)
# unnamed count
col = split.column()
col.label(
text="Unnamed: {0}".format(count.objects_unnamed())
)
# node group statistics
elif atom.stats_mode == 'NODE_GROUPS':
# category header label
row = box.row()
row.label(text="Node Groups", icon='NODETREE')
split = box.split()
# total and placeholder count
col = split.column()
col.label(
text="Total: {0}".format(count.node_groups())
)
# col.label(text="Placeholder") # TODO: remove placeholder
# unused and unnamed count
col = split.column()
col.label(
text="Unused: {0}".format(count.node_groups_unused())
)
col.label(
text="Unnamed: {0}".format(count.node_groups_unnamed())
)
# particle statistics
elif atom.stats_mode == 'PARTICLES':
# category header label
row = box.row()
row.label(text="Particle Systems", icon='PARTICLES')
split = box.split()
# total and placeholder count
col = split.column()
col.label(
text="Total: {0}".format(count.particles())
)
# col.label(text="Placeholder") # TODO: remove placeholder
# unused and unnamed count
col = split.column()
col.label(
text="Unused: {0}".format(count.particles_unused())
)
col.label(
text="Unnamed: {0}".format(count.particles_unnamed())
)
# texture statistics
elif atom.stats_mode == 'TEXTURES':
row = box.row()
row.label(text="Textures", icon='TEXTURE')
split = box.split()
# total and placeholder count
col = split.column()
col.label(
text="Total: {0}".format(count.textures())
)
# col.label(text="Placeholder") # TODO: remove placeholder
# unused and unnamed count
col = split.column()
col.label(
text="Unused: {0}".format(count.textures_unused())
)
col.label(
text="Unnamed: {0}".format(count.textures_unnamed())
)
# world statistics
elif atom.stats_mode == 'WORLDS':
row = box.row()
row.label(text="Worlds", icon='WORLD')
split = box.split()
# total and placeholder count
col = split.column()
col.label(
text="Total: {0}".format(count.worlds())
)
# # col.label(text="Placeholder") # TODO: remove placeholder
# unused and unnamed count
col = split.column()
col.label(
text="Unused: {0}".format(count.worlds_unused())
)
col.label(
text="Unnamed: {0}".format(count.worlds_unnamed())
)
reg_list = [ATOMIC_PT_stats_panel]
def register():
for cls in reg_list:
register_class(cls)
def unregister():
for cls in reg_list:
unregister_class(cls)
@@ -0,0 +1,128 @@
"""
Copyright (C) 2019 Remington Creative
This file is part of Atomic Data Manager.
Atomic Data Manager is free software: you can redistribute
it and/or modify it under the terms of the GNU General Public License
as published by the Free Software Foundation, either version 3 of the
License, or (at your option) any later version.
Atomic Data Manager is distributed in the hope that it will
be useful, but WITHOUT ANY WARRANTY; without even the implied warranty
of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
General Public License for more details.
You should have received a copy of the GNU General Public License along
with Atomic Data Manager. If not, see <https://www.gnu.org/licenses/>.
---
This file contains the user interface and some helper functions for the
support Remington Creative popup.
"""
import bpy
import time
from bpy.utils import register_class
from bpy.utils import unregister_class
from bpy.app.handlers import persistent
from atomic_data_manager import config
from atomic_data_manager.ui import preferences_ui
def get_current_day():
# returns the current day since the start of the computer clock
seconds_per_day = 86400
return int(time.time() / seconds_per_day)
def update_enable_show_support_me_popup(self, context):
# copy the inverse of the stop show support popup property to Atomic's
# enable support me popup preference
preferences_ui.set_enable_support_me_popup(
not self.stop_showing_support_popup)
@persistent
def show_support_me_popup(dummy=None):
# shows the support me popup if the 5 day interval has expired and the
# enable support me popup preference is enabled
popup_interval = 5 # days
current_day = get_current_day()
next_day = config.last_popup_day + popup_interval
if config.enable_support_me_popup and current_day >= next_day:
preferences_ui.set_last_popup_day(current_day)
bpy.ops.atomic.show_support_me('INVOKE_DEFAULT')
# Atomic Data Manager Support Me Popup Operator
class ATOMIC_OT_support_me_popup(bpy.types.Operator):
"""Displays the Atomic \"Support Me\" popup"""
bl_idname = "atomic.show_support_me"
bl_label = "Like Atomic Data Manager?"
bl_options = {'REGISTER', 'UNDO'}
stop_showing_support_popup: bpy.props.BoolProperty(
default=False,
update=update_enable_show_support_me_popup
)
def draw(self, context):
layout = self.layout
# call to action label
col = layout.column(align=True)
col.label(
text="Consider supporting our free software development!"
)
separator = layout.separator() # extra space
# never show again toggle
row = layout.row()
row.prop(
self, "stop_showing_support_popup", text="Never Show Again"
)
# support remington creative button
row = layout.row()
row.scale_y = 2
row.operator(
"atomic.open_support_me",
text="Support Remington Creative",
icon="FUND"
)
def execute(self, context):
return {'FINISHED'}
def invoke(self, context, event):
wm = context.window_manager
return wm.invoke_props_dialog(self)
reg_list = [ATOMIC_OT_support_me_popup]
def register():
for cls in reg_list:
register_class(cls)
bpy.app.handlers.load_post.append(show_support_me_popup)
# reset day counter if it equals zero of if it is in the future
if config.last_popup_day == 0 \
or config.last_popup_day > get_current_day():
preferences_ui.set_last_popup_day(get_current_day())
def unregister():
for cls in reg_list:
unregister_class(cls)
bpy.app.handlers.load_post.remove(show_support_me_popup)
@@ -0,0 +1,224 @@
"""
Copyright (C) 2019 Remington Creative
This file is part of Atomic Data Manager.
Atomic Data Manager is free software: you can redistribute
it and/or modify it under the terms of the GNU General Public License
as published by the Free Software Foundation, either version 3 of the
License, or (at your option) any later version.
Atomic Data Manager is distributed in the hope that it will
be useful, but WITHOUT ANY WARRANTY; without even the implied warranty
of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
General Public License for more details.
You should have received a copy of the GNU General Public License along
with Atomic Data Manager. If not, see <https://www.gnu.org/licenses/>.
---
This file contains basic UI layouts for the Atomic add-on that can be
used throughout the interface.
"""
import bpy
def box_list(layout, title=None, items=None, columns=2, icon=None):
# a title label followed by a box that contains a two column list of
# items, each of which is preceded by a uniform icon that does not
# change depending on the objects type
# box list title
row = layout.row() # extra row for additional spacing
if title is not None:
row = layout.row()
row.label(text=title)
box = layout.box()
# if the list has elements
if items is not None and len(items) != 0:
# display the list
flow = box.column_flow(columns=columns)
for item in items:
if icon is not None:
flow.label(text=item, icon=icon)
else:
flow.label(text=item)
# if the list has no elements
else:
# display the none label
row = box.row()
row.enabled = False
row.label(text="none")
def box_list_diverse(layout, title, items, columns=2):
# a title label followed by a box that contains a two column list of
# items, each of which is preceded by an icon that changes depending
# on the type of object that is being listed
# box list title
row = layout.row() # extra row for additional spacing
row = layout.row()
row.label(text=title)
box = layout.box()
# if the list has elements
if len(items) != 0:
# display the list
flow = box.column_flow(columns=columns)
objects = bpy.data.objects
for item in items:
if objects[item].type == 'ARMATURE':
flow.label(text=item, icon="OUTLINER_OB_ARMATURE")
elif objects[item].type == 'CAMERA':
flow.label(text=item, icon="OUTLINER_OB_CAMERA")
elif objects[item].type == 'CURVE':
flow.label(text=item, icon="OUTLINER_OB_CURVE")
elif objects[item].type == 'EMPTY':
flow.label(text=item, icon="OUTLINER_OB_EMPTY")
elif objects[item].type == 'FONT':
flow.label(text=item, icon="OUTLINER_OB_FONT")
elif objects[item].type == 'GPENCIL':
flow.label(text=item, icon="OUTLINER_OB_GREASEPENCIL")
elif objects[item].type == 'LATTICE':
flow.label(text=item, icon="OUTLINER_OB_LATTICE")
elif objects[item].type == 'LIGHT':
flow.label(text=item, icon="OUTLINER_OB_LIGHT")
elif objects[item].type == 'LIGHT_PROBE':
flow.label(text=item, icon="OUTLINER_OB_LIGHTPROBE")
elif objects[item].type == 'MESH':
flow.label(text=item, icon="OUTLINER_OB_MESH")
elif objects[item].type == 'META':
flow.label(text=item, icon="OUTLINER_OB_META")
elif objects[item].type == 'SPEAKER':
flow.label(text=item, icon="OUTLINER_OB_SPEAKER")
elif objects[item].type == 'SURFACE':
flow.label(text=item, icon="OUTLINER_OB_SURFACE")
# if the object doesn't fit any of the previous types
else:
flow.label(text=item, icon="QUESTION")
# if the list has no elements
else:
# display the none label
row = box.row()
row.enabled = False
row.label(text="none")
def inspect_header(layout, atom_prop, data):
# a single column containing a search property and basic data
# manipulation functions that appears at the top of all inspect data
# set dialogs
atom = bpy.context.scene.atomic
# exterior box and prop search for data-blocks
col = layout.column(align=True)
box = col.box()
row = box.row()
split = row.split()
split.prop_search(atom, atom_prop, bpy.data, data, text="")
# convert the data set string into an actual data set reference
data = getattr(bpy.data, data)
# get the string value of the string property
text_field = getattr(atom, atom_prop)
# determine whether or not the text entered in the string property
# is a valid key
is_valid_key = text_field in data.keys()
# determine whether or not the piece of data is using a fake user
has_fake_user = is_valid_key and data[text_field].use_fake_user
# buttons that follow the prop search
split = row.split()
row = split.row(align=True)
# disable the buttons if the key in the search property is invalid
row.enabled = is_valid_key
# toggle fake user button (do not show for collections)
# icon and depression changes depending on whether or not the object
# is using a fake user
if data != bpy.data.collections:
# has fake user
if has_fake_user:
row.operator(
"atomic.toggle_fake_user",
text="",
icon="FAKE_USER_ON",
depress=True
)
# does not have fake user
else:
row.operator(
"atomic.toggle_fake_user",
text="",
icon="FAKE_USER_OFF",
depress=False
)
# duplicate button
row.operator(
"atomic.inspection_duplicate",
text="",
icon="DUPLICATE"
)
# replace button (do not show for collections)
if data != bpy.data.collections:
row.operator(
"atomic.replace",
text="",
icon="UV_SYNC_SELECT"
)
# rename button
row.operator(
"atomic.rename",
text="",
icon="GREASEPENCIL"
)
# delete button
row.operator(
"atomic.inspection_delete",
text="",
icon="TRASH"
)
def number_suffix(text, number):
# returns the text properly formatted as a suffix
# e.g. passing in "hello" and "100" will result in "hello (100)"
return text + " ({0})".format(number) if int(number) != 0 else text