This repository has been archived on 2023-05-13. You can view files and clone it. You cannot open issues or pull requests or push a commit.
Files
powerlib/__init__.py
Andy Goralczyk 08d73b3f6f Initial conversion to work with 2.8. collections are used instead of the old group system.
As an additional asset component type, Non-instanced collections have been added back (this used to be supported by the pre-blender institute version developed by Bassam Kurdali afaik.
2020-06-12 10:44:52 +02:00

943 lines
32 KiB
Python

# ***** BEGIN GPL LICENSE BLOCK *****
#
# This program 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 2
# of the License, or (at your option) any later version.
#
# This program 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 this program; if not, write to the Free Software Foundation,
# Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
#
# ***** END GPL LICENCE BLOCK *****
# <pep8 compliant>
from bpy.props import (
BoolProperty,
IntProperty,
StringProperty,
EnumProperty,
CollectionProperty,
PointerProperty,
)
from bpy.types import (
Operator,
Menu,
Panel,
UIList,
PropertyGroup,
)
from bpy.app.handlers import persistent
import bpy
import json
import os
bl_info = {
"name": "Powerlib",
"author": "Inês Almeida, Francesco Siddi, Olivier Amrein, Dalai Felinto",
"version": (2, 0, 0),
"blender": (2, 90, 0),
"location": "View3D > Properties (N)",
"description": "Asset management",
"warning": "",
"wiki_url": "http://wiki.blender.org/index.php/Extensions:2.6/Py/"
"Scripts/Workflow/-todo-create-new-documentation-page!",
"category": "Workflow",
}
VERBOSE = False # enable this for debugging
def debug_print(*args):
"""Print debug messages"""
if VERBOSE:
print(*args)
# Data Structure ##############################################################
# A powerlib library is structured as a series of collections of assets.
# An asset has a set of components which are organized by type.
# eg. the library defined in XXX has the Collections "Characters" and "Props"
# the collection "Characters" contains the assets "Boris" and "Agent327"
# an asset has one or more components of type "instance_collections"
# which consist of a path to the blend file and the name of the collection to instance
runtime_vars = {}
class ReadState:
NotLoaded, NoFile, FilePathInvalid, FileContentInvalid, EmptyLib, AllGood = range(
6)
runtime_vars["read_state"] = ReadState.NotLoaded
class SaveState:
HasUnsavedChanges, AllSaved = range(2)
runtime_vars["save_state"] = SaveState.AllSaved
enum_component_type = EnumProperty(
items=(
('INSTANCE_COLLECTIONS', "Instance Collections", "", 'EMPTY_DATA', 0),
('NON_INSTANCE_COLLECTIONS', "Non instance Collections", "", 'GROUP', 1),
('COLLECTION_REFERENCE_OBJECTS',
"Collection Reference Objects", "", 'OBJECT_DATA', 2),
),
default='INSTANCE_COLLECTIONS',
name="Component Type",
description="Type of an asset component",
)
def enum_item_name_icon(enum, value):
"""Return the item name and icon of an enum
"""
prop, settings = enum
for item in settings['items']:
if item[0] == value:
return item[1], item[3]
return "Error", 'ERROR'
class ComponentItem(PropertyGroup):
name: StringProperty()
class Component(PropertyGroup):
def update_filepath_rel(self, context):
"""Updates the filepath property after we picked a new value for
filepath_rel via a file browser.
"""
# ~ self.collection = None
self.collections.clear()
if self.filepath_rel == '':
return
# TODO: ensure path is valid
# Make path relative to the library
from . import linking
import importlib
importlib.reload(linking)
fp_rel_to_lib = linking.relative_path_to_lib(self.filepath_rel)
debug_print(f'Updating library link to {fp_rel_to_lib}')
self.filepath = fp_rel_to_lib
cache_key = self.filepath
if self.filepath_rel == '//' + os.path.basename(bpy.data.filepath):
for col in bpy.data.collections:
if col.library:
continue
self.collections.add().name = col.name
else:
filepath = bpy.path.relpath(self.absolute_filepath)
with bpy.data.libraries.load(filepath) as (data_from, data_to):
for colname in data_from.collections:
self.collections.add().name = colname
id: StringProperty(
name="Name",
description="Name for this component, eg. the name of a collection",
)
collections: CollectionProperty(type=ComponentItem)
filepath: StringProperty(
name="File path",
description="Path to the blend file which holds this data relative to the library",
subtype='FILE_PATH',
)
filepath_rel: StringProperty(
name="Relative file path",
description="Path to the blend file which holds this data relative from the current file",
subtype='FILE_PATH',
update=update_filepath_rel,
)
@property
def absolute_filepath(self):
library_path = os.path.dirname(
bpy.path.abspath(bpy.context.scene['lib_path']))
abspath = os.path.join(library_path, self.filepath)
normpath = os.path.normpath(abspath)
if os.path.isfile(normpath):
return normpath
else:
# raise IOError('File {} not found'.format(normpath))
debug_print(f'IOError: File {normpath} not found')
class ComponentsList(PropertyGroup):
"""A set of components of a certain type that build an asset
example types: (collections, collection_reference_objects, scripts).
"""
component_type: enum_component_type
components: CollectionProperty(
name="Components",
description="List of components of this type",
type=Component,
)
active_component: IntProperty(
name="Selected Component",
description="Currently selected component of this type",
)
@staticmethod
def getComponentType(name):
lookup = {
'instance_collections': 'INSTANCE_COLLECTIONS',
'non_instance_collections': 'NON_INSTANCE_COLLECTIONS',
'collection_reference_objects': 'COLLECTION_REFERENCE_OBJECTS',
}
value = lookup.get(name)
if value is None:
raise Exception(f"Component type not supported: {name}")
return value
class AssetItem(PropertyGroup):
components_by_type: CollectionProperty(
name="Components by Type",
type=ComponentsList,
)
class AssetCollection(PropertyGroup):
active_asset: IntProperty(
name="Selected Asset",
description="Currently selected asset",
)
assets: CollectionProperty(
name="Assets",
description="List of assets in this collection",
type=AssetItem,
)
class PowerProperties(PropertyGroup):
is_edit_mode: BoolProperty(
name="Is in Edit Mode",
description="Toggle for Edit/Selection mode",
default=False,
)
collections: CollectionProperty(
name="PowerLib Collections",
description="List of Asset Collections in the active library",
type=AssetCollection,
)
active_col: StringProperty(
name="Active Category",
description="Currently selected Asset Category",
)
# Operators ###################################################################
class ColRequiredOperator(Operator):
@classmethod
def poll(self, context):
wm = context.window_manager
active_col = wm.powerlib_props.active_col
return (active_col
and wm.powerlib_props.collections[active_col])
@staticmethod
def name_new_item(container, default_name):
if default_name not in container:
return default_name
else:
sorted_container = []
for a in container:
if a.name.startswith(default_name + "."):
index = a.name[len(default_name) + 1:]
if index.isdigit():
sorted_container.append(index)
sorted_container = sorted(sorted_container)
min_index = 1
for num in sorted_container:
num = int(num)
if min_index < num:
break
min_index = num + 1
return"{:s}.{:03d}".format(default_name, min_index)
class ColAndAssetRequiredOperator(ColRequiredOperator):
@classmethod
def poll(self, context):
if super().poll(context):
wm = context.window_manager
col = wm.powerlib_props.collections[wm.powerlib_props.active_col]
return (col.active_asset < len(col.assets)
and col.active_asset >= 0)
return False
class ASSET_OT_powerlib_reload_from_json(Operator):
bl_idname = "wm.powerlib_reload_from_json"
bl_label = "Reload from JSON"
bl_description = "Loads the library from the JSON file. Overrides non saved local edits!"
bl_options = {'UNDO', 'REGISTER'}
@classmethod
def poll(self, context):
return True
def execute(self, context):
from . import linking
import importlib
importlib.reload(linking)
wm = context.window_manager
wm.powerlib_props.collections.clear()
wm.powerlib_props.active_col = ""
runtime_vars["save_state"] = SaveState.AllSaved
# Load single json library file
library_path = bpy.path.abspath(context.scene.lib_path)
debug_print(
f"PowerLib2: Reading JSON library file from {library_path}")
if not library_path:
debug_print("PowerLib2: ... no library path specified!")
runtime_vars["read_state"] = ReadState.NoFile
return {'FINISHED'}
if not os.path.exists(library_path):
debug_print("PowerLib2: ... library filepath invalid!")
runtime_vars["read_state"] = ReadState.FilePathInvalid
return {'FINISHED'}
library = {}
with open(library_path) as data_file:
try:
library = json.load(data_file)
except (json.decoder.JSONDecodeError, KeyError, ValueError):
# malformed json data
debug_print(
"PowerLib2: ... JSON content is empty or malformed!")
runtime_vars["read_state"] = ReadState.FileContentInvalid
return {'FINISHED'}
# Collections, eg. Characters
for collection_name in library:
asset_collection_prop = wm.powerlib_props.collections.add()
asset_collection_prop.name = collection_name
# Assets, eg. Boris
collection = library[collection_name]
for asset_name in sorted(collection.keys()):
asset_json = collection[asset_name]
asset_prop = asset_collection_prop.assets.add()
asset_prop.name = asset_name
# Component Types, eg. instance_collections
for ctype_name, ctype_components in asset_json.items():
ctype_prop = asset_prop.components_by_type.add()
ctype_prop.name = ctype_name
ctype_prop.component_type = ctype_prop.getComponentType(
ctype_name)
# Individual components of this type, each with filepath and name
for filepath, name in ctype_components:
component_prop = ctype_prop.components.add()
component_prop.name = name
component_prop.id = name
component_prop.filepath = filepath
absolute_filepath = component_prop.absolute_filepath
if absolute_filepath:
bf_rel_fp = linking.relative_path_to_file(
component_prop.absolute_filepath)
else:
bf_rel_fp = ''
component_prop.filepath_rel = bf_rel_fp
if library:
# Assign some collection by default (dictionaries are unordered)
wm.powerlib_props.active_col = next(iter(library.keys()))
runtime_vars["read_state"] = ReadState.AllGood
else:
runtime_vars["read_state"] = ReadState.EmptyLib
debug_print("PowerLib2: ... looks good!")
return {'FINISHED'}
class ASSET_OT_powerlib_save_to_json(Operator):
bl_idname = "wm.powerlib_save_to_json"
bl_label = "Save to JSON"
bl_description = "Saves the edited library to the json file. Overrides the previous content!"
bl_options = {'UNDO', 'REGISTER'}
@classmethod
def poll(self, context):
return True
def execute(self, context):
wm = context.window_manager
# Save properties to the JSON library file
library_path = bpy.path.abspath(context.scene.lib_path)
debug_print(f"PowerLib2: Saving JSON library to file {library_path}")
if not library_path or not os.path.exists(library_path):
debug_print("PowerLib2: ... invalid filepath! Could not save!")
self.report({'ERROR'}, "Invalid path! Could not save!")
return {'FINISHED'}
collections_json_dict = {}
# Collections, eg. Characters
for collection in wm.powerlib_props.collections:
assets_json_dict = {}
# Assets, eg. Boris
for asset_name, asset_body in collection.assets.items():
comps_by_type_json_dict = {}
# Component Types, eg. instance_collections
for comp_type_name, comp_type_body in asset_body.components_by_type.items():
comps_by_type_json_dict[comp_type_name] = []
# Individual components of this type, each with filepath and name
for i in comp_type_body.components:
comps_by_type_json_dict[comp_type_name].append([
i.filepath, i.id
])
assets_json_dict[asset_name] = comps_by_type_json_dict
collections_json_dict[collection.name] = assets_json_dict
with open(library_path, 'w') as data_file:
json.dump(collections_json_dict, data_file,
indent=4, sort_keys=True,)
runtime_vars["save_state"] = SaveState.AllSaved
debug_print("PowerLib2: ... no errors!")
return {'FINISHED'}
class ASSET_OT_powerlib_collection_rename(ColRequiredOperator):
bl_idname = "wm.powerlib_collection_rename"
bl_label = "Rename Category"
bl_description = "Rename the asset category"
bl_options = {'UNDO', 'REGISTER'}
name: StringProperty(name="Name", description="Name of the collection")
def invoke(self, context, event):
wm = context.window_manager
# fill in the field with the current value
self.name = wm.powerlib_props.collections[wm.powerlib_props.active_col].name
return wm.invoke_props_dialog(self)
def execute(self, context):
wm = context.window_manager
col = wm.powerlib_props.collections[wm.powerlib_props.active_col]
col.name = self.name
wm.powerlib_props.active_col = self.name
runtime_vars["save_state"] = SaveState.HasUnsavedChanges
return {'FINISHED'}
class ASSET_OT_powerlib_collection_add(Operator):
bl_idname = "wm.powerlib_collection_add"
bl_label = "Add Category"
bl_description = "Add a new asset category"
bl_options = {'UNDO', 'REGISTER'}
name: StringProperty(name="Name", description="Name of the collection")
def invoke(self, context, event):
wm = context.window_manager
return wm.invoke_props_dialog(self)
def execute(self, context):
wm = context.window_manager
col = wm.powerlib_props.collections.add()
col.name = self.name
wm.powerlib_props.active_col = self.name
runtime_vars["save_state"] = SaveState.HasUnsavedChanges
return {'FINISHED'}
class ASSET_OT_powerlib_collection_del(ColRequiredOperator):
bl_idname = "wm.powerlib_collection_del"
bl_label = "Delete Category"
bl_description = "Delete the selected asset category"
bl_options = {'UNDO', 'REGISTER'}
def execute(self, context):
wm = context.window_manager
idx = wm.powerlib_props.collections.find(wm.powerlib_props.active_col)
wm.powerlib_props.collections.remove(idx)
wm.powerlib_props.active_col = ""
runtime_vars["save_state"] = SaveState.HasUnsavedChanges
return {'FINISHED'}
class ASSET_OT_powerlib_assetitem_add(ColRequiredOperator):
bl_idname = "wm.powerlib_assetitem_add"
bl_label = "Add Asset"
bl_description = "Add a new asset to the selected category"
bl_options = {'UNDO', 'REGISTER'}
def execute(self, context):
wm = context.window_manager
col = wm.powerlib_props.collections[wm.powerlib_props.active_col]
asset = col.assets.add()
# naming
asset.name = self.name_new_item(col.assets, "NewAsset")
# select newly created asset
col.active_asset = len(col.assets) - 1
runtime_vars["save_state"] = SaveState.HasUnsavedChanges
return {'FINISHED'}
class ASSET_OT_powerlib_assetitem_del(ColAndAssetRequiredOperator):
bl_idname = "wm.powerlib_assetitem_del"
bl_label = "Delete Asset"
bl_description = "Delete the selected asset"
bl_options = {'UNDO', 'REGISTER'}
def execute(self, context):
wm = context.window_manager
col = wm.powerlib_props.collections[wm.powerlib_props.active_col]
col.assets.remove(col.active_asset)
# change currently active asset
num_assets = len(col.assets)
if (col.active_asset > (num_assets - 1) and num_assets > 0):
col.active_asset = num_assets - 1
runtime_vars["save_state"] = SaveState.HasUnsavedChanges
return {'FINISHED'}
class ASSET_OT_powerlib_component_add(ColAndAssetRequiredOperator):
bl_idname = "wm.powerlib_component_add"
bl_label = "Add Asset Component"
bl_description = "Add a new component to the selected asset"
bl_options = {'UNDO', 'REGISTER'}
component_type: enum_component_type
needs_select: BoolProperty(default=False, options={'HIDDEN'})
def invoke(self, context, event):
wm = context.window_manager
if self.needs_select:
self.needs_select = False
return wm.invoke_props_dialog(self)
else:
return self.execute(context)
def execute(self, context):
wm = context.window_manager
asset_collection = wm.powerlib_props.collections[wm.powerlib_props.active_col]
active_asset = asset_collection.assets[asset_collection.active_asset]
# create container for type if it does not exist yet
components_of_type = active_asset.components_by_type.get(
self.component_type.lower())
if components_of_type is None:
components_of_type = active_asset.components_by_type.add()
components_of_type.name = self.component_type.lower()
components_of_type.component_type = self.component_type
component = components_of_type.components.add()
# naming
component.name = self.name_new_item(
components_of_type.components, "NewComponent")
# select newly created component
components_of_type.active_component = len(
components_of_type.components) - 1
runtime_vars["save_state"] = SaveState.HasUnsavedChanges
return {'FINISHED'}
class ASSET_OT_powerlib_component_del(ColAndAssetRequiredOperator):
bl_idname = "wm.powerlib_component_del"
bl_label = "Delete Asset Component"
bl_description = "Delete the selected asset component"
bl_options = {'UNDO', 'REGISTER'}
component_type: enum_component_type
def execute(self, context):
wm = context.window_manager
asset_collection = wm.powerlib_props.collections[wm.powerlib_props.active_col]
active_asset = asset_collection.assets[asset_collection.active_asset]
# ignore if container for type does not exist
components_of_type = active_asset.components_by_type.get(
self.component_type.lower())
if components_of_type is None:
return {'FINISHED'}
components_of_type.components.remove(
components_of_type.active_component)
num_components = len(components_of_type.components)
# if this component type list is empty, delete
if num_components == 0:
idx = active_asset.components_by_type.find(
self.component_type.lower())
active_asset.components_by_type.remove(idx)
# change currently active component
elif (components_of_type.active_component > (num_components - 1) and num_components > 0):
components_of_type.active_component = num_components - 1
runtime_vars["save_state"] = SaveState.HasUnsavedChanges
return {'FINISHED'}
class AssetFiles():
def __init__(self):
self._components = {}
self._files = {}
@staticmethod
def get_nested_array(_dict, key, array):
if key not in _dict:
_dict[key] = array()
return _dict[key]
def get_component(self, component_type):
return self.get_nested_array(self._components, component_type, dict)
def add(self, component_type, filepath, _id):
"""Populate the dictionary of lists"""
_component = self.get_component(component_type)
_file = self.get_nested_array(_component, filepath, list)
_file.append(_id)
def process(self):
"""handle the importing"""
callbacks = {
'COLLECTION_REFERENCE_OBJECTS': linking.load_collection_reference_objects,
'NON_INSTANCE_COLLECTIONS': linking.load_non_instance_collections,
'INSTANCE_COLLECTIONS': linking.load_instance_collections,
}
for _component, _files in self._components.items():
callback = callbacks.get(_component)
assert callback, f"Component \"{_component}\" not supported"
for _file, ids in _files.items():
callback(_file, ids)
class ASSET_OT_powerlib_link_in_component(ColAndAssetRequiredOperator):
bl_idname = "wm.powerlib_link_in_component"
bl_label = "Add component to scene"
bl_description = "Add component to scene"
bl_options = {'UNDO', 'REGISTER'}
index: IntProperty(
default=-1,
options={'HIDDEN', 'SKIP_SAVE'},
)
def execute(self, context):
from . import linking
if "bpy" in locals():
import importlib
importlib.reload(linking)
wm = context.window_manager
asset_collection = wm.powerlib_props.collections[wm.powerlib_props.active_col]
if self.index == -1:
active_asset = asset_collection.assets[asset_collection.active_asset]
else:
active_asset = asset_collection.assets[self.index]
debug_print(f'Linking in {active_asset.name}')
files = AssetFiles()
for component_list in active_asset.components_by_type:
component_type = component_list.component_type
for component in component_list.components:
files.add(component_type,
component.absolute_filepath, component.id)
files.process()
return {'FINISHED'}
# Panel #######################################################################
class ASSET_UL_asset_components(UIList):
def draw_item(self, context, layout, data, item, icon, active_data, active_propname, index):
is_edit_mode = context.window_manager.powerlib_props.is_edit_mode
col = layout.split()
col.enabled = is_edit_mode
col.prop(item, "filepath_rel", text="", emboss=is_edit_mode)
col.prop_search(item, "id", item, "collections", text="")
class ASSET_UL_collection_assets(UIList):
def draw_item(self, context, layout, data, set, icon, active_data, active_propname, index):
# layout.prop(set, "name", text="", icon='LINK_BLEND', emboss=False)
is_edit_mode = context.window_manager.powerlib_props.is_edit_mode
col = layout.split()
col.prop(set, "name", text="", icon='LINK_BLEND', emboss=False)
if is_edit_mode:
return
col = layout.split()
col.enabled = True
plus = col.operator(
"wm.powerlib_link_in_component", text="", icon='IMPORT')
plus.index = index
class ASSET_PT_powerlib(Panel):
bl_label = 'Powerlib' # panel section name
bl_space_type = 'VIEW_3D'
bl_region_type = 'UI'
bl_category = 'Powerlib' # tab name
@classmethod
def poll(cls, context):
# restrict availability? Yes, only object mode
return context.mode == 'OBJECT'
def draw_header(self, context):
wm = context.window_manager
row = self.layout.row(align=True)
row.prop(wm.powerlib_props, "is_edit_mode",
text="",
icon='TOOL_SETTINGS')
row.operator("wm.powerlib_reload_from_json",
text="", icon='FILE_REFRESH')
def draw(self, context):
wm = context.window_manager
scene = context.scene
is_edit_mode = wm.powerlib_props.is_edit_mode
layout = self.layout
# Setting for the JSON library path
if is_edit_mode:
row = layout.row()
row.prop(scene, "lib_path", text="Library Path")
layout.separator()
# Fail report for library loading
read_state = runtime_vars["read_state"]
if (read_state != ReadState.AllGood
and not (read_state == ReadState.EmptyLib and is_edit_mode)):
row = layout.row()
row.alignment = 'CENTER'
if (read_state == ReadState.NotLoaded or read_state == ReadState.NoFile):
if is_edit_mode:
row.label(text="No library path loaded", icon='ERROR')
else:
row.alignment = 'EXPAND'
row.label(text="Choose a library path:")
row = layout.row()
row.prop(scene, "lib_path", text="")
elif (read_state == ReadState.FilePathInvalid):
if not is_edit_mode:
row.alignment = 'EXPAND'
row.label(text="Choose a library path:")
row = layout.row()
row.prop(scene, "lib_path", text="")
row = layout.row()
row.label(text="Can not find a library in the given path",
icon='ERROR')
elif (read_state == ReadState.FileContentInvalid):
row.label(text="The library is empty or corrupt!", icon='ERROR')
elif (read_state == ReadState.EmptyLib and not is_edit_mode):
row.label(text="The chosen library is empty")
return
# Category selector
row = layout.row(align=True)
row.prop_search(
wm.powerlib_props, "active_col", # Currently active
wm.powerlib_props, "collections", # Collection to search
text="", icon="MENU_PANEL" # UI icon and label
)
if is_edit_mode:
row.operator("wm.powerlib_collection_rename",
text="", icon='OUTLINER_DATA_FONT')
row.operator("wm.powerlib_collection_add", text="", icon='ADD')
row.operator("wm.powerlib_collection_del", text="", icon='REMOVE')
# UI List with the assets of the selected category
row = layout.row()
if (wm.powerlib_props.active_col):
asset_collection = wm.powerlib_props.collections[wm.powerlib_props.active_col]
row.template_list(
"ASSET_UL_collection_assets", "", # type and unique id
asset_collection, "assets", # pointer to the CollectionProperty
asset_collection, "active_asset", # pointer to the active identifier
rows=6,
)
# add/remove/specials UI list Menu
if is_edit_mode:
col = row.column(align=True)
col.operator("wm.powerlib_assetitem_add",
icon='ADD', text="")
col.operator("wm.powerlib_assetitem_del",
icon='REMOVE', text="")
# col.menu("ASSET_MT_powerlib_assetlist_specials", icon='DOWNARROW_HLT', text="")
else:
row.enabled = False
row.label(text="Choose an Asset Collection!")
# Properties and Components of this Asset
if wm.powerlib_props.active_col:
layout.separator()
if asset_collection.assets:
active_asset = asset_collection.assets[asset_collection.active_asset]
for components_of_type in active_asset.components_by_type:
row = layout.row()
name, icon = enum_item_name_icon(
enum_component_type, components_of_type.component_type)
row.label(text=name, icon=icon)
row = layout.row()
row.template_list(
"ASSET_UL_asset_components", # type
"components_of_type.component_type", # unique id
components_of_type, "components", # pointer to the CollectionProperty
components_of_type, "active_component", # pointer to the active identifier
rows=2,
)
# add/remove/specials UI list Menu
if is_edit_mode:
col = row.column(align=True)
col.operator("wm.powerlib_component_add", icon='ADD',
text="").component_type = components_of_type.component_type
col.operator("wm.powerlib_component_del", icon='REMOVE',
text="").component_type = components_of_type.component_type
if is_edit_mode:
layout.separator()
row = layout.row()
row.operator("wm.powerlib_component_add",
icon='ADD').needs_select = True
# Save
if is_edit_mode:
layout.separator()
row = layout.row()
row.operator("wm.powerlib_save_to_json",
icon='ERROR' if runtime_vars["save_state"] == SaveState.HasUnsavedChanges else 'FILE_TICK')
# Registry ####################################################################
classes = (
ComponentItem,
Component,
ComponentsList,
AssetItem,
AssetCollection,
PowerProperties,
ASSET_UL_asset_components,
ASSET_UL_collection_assets,
ASSET_PT_powerlib,
ASSET_OT_powerlib_reload_from_json,
ASSET_OT_powerlib_save_to_json,
ASSET_OT_powerlib_collection_rename,
ASSET_OT_powerlib_collection_add,
ASSET_OT_powerlib_collection_del,
ASSET_OT_powerlib_assetitem_add,
ASSET_OT_powerlib_assetitem_del,
ASSET_OT_powerlib_component_add,
ASSET_OT_powerlib_component_del,
ASSET_OT_powerlib_link_in_component,
)
def powerlib_lib_path_update_cb(self, context):
debug_print("PowerLib2: Loading Add-on and Library")
bpy.ops.wm.powerlib_reload_from_json()
def register():
for cls in classes:
bpy.utils.register_class(cls)
bpy.types.WindowManager.powerlib_props = PointerProperty(
name="Powerlib Add-on Properties",
description="Properties and data used by the Powerlib Add-on",
type=PowerProperties,
)
bpy.types.Scene.lib_path = StringProperty(
name="Powerlib Add-on Library Path",
description="Path to a PowerLib JSON file",
subtype='FILE_PATH',
update=powerlib_lib_path_update_cb,
)
def unregister():
del bpy.types.Scene.lib_path
del bpy.types.WindowManager.powerlib_props
for cls in classes:
bpy.utils.unregister_class(cls)
if __name__ == "__main__":
register()