Commit 684c04bb authored by Dawid Huczynski's avatar Dawid Huczynski
Browse files

update to 1.2

parent 331fecad
# Simple Asset Manager
Simple Asset Manager for Blender 2.8. Works with **objects**, **materials**, **hdr** and **particle systems**.
Simple Asset Manager for Blender 2.8 and up. Works with **objects**, **materials**, **hdr** and **particle systems**.
Did this one due to dealys for internal asset manager for Blender.
**Made for simplicity of usage.**
- Opens `blend`, `obj`, `fbx`, `hdr`, `exr` from the same place;
- Dynamic, and custom categories: Except of `material`, and `particle` this addon will follow your library layout, folders and subfolders;
- absolute or relative libraries folder;
- Dynamic search bar !!!;
- Master and Local (per project/working file) Libraries !!!
- Dynamic, and custom directories/categories: Except of `material`, `particle` and `hdr` this addon will follow your library layout, folders and subfolders;
- creates structured collections: `Assets`, `Assets/Particles`;
- With `Ctrl+scroll` over the categories it is even faster;
- Small and simple UI. Simple, and small to modify code as you like;
- option to batch render all previews;
- option to batch render all previews or missing ones;
- works as operator too (One can search `Simple Asset Manager` in command search, and assign a SHORTCUT!! :) But there is no default shortcut assigned for it);
- search bar;
- Master and Local (per project/working file) Libraries !!!
None of the mangers I've tried was enough for me. Asset flinger was nice, but the idea with previews was much better. Chocofur asset manager is almost perfect, but still, it had its cons: separate spaces for categories, and (i only assume) fixed categories.
None of the mangers I've tried was enough for me. Asset flinger was nice, but the idea with previews was much better. Chocofur asset manager is almost perfect, but still, it had its cons: separate spaces for categories, mostly layout and functionality.
Focused on simplicity. Less buttons more functionality.
It is my search for something new. Focused on simplicity. Less buttons more functionality.
## Install
[Download this link SimpleAssetManager.zip file](https://gitlab.com/tibicen/simple-asset-manager/raw/master/SimpleAssetManager.zip)
[Download this SimpleAssetManager.zip file](https://gitlab.com/tibicen/simple-asset-manager/raw/master/SimpleAssetManager.zip)
Install .zip file without unpacking. Choose Library folder with your blends.
## Usage
## Usage
After install you can also mark to render missing previews. It will render material view with eevee of your models, materials, and particle systems in one specific manner. Of course you can always render your own previews as 200x200 .png with the same name as blend file. But remember it can take awhile. **Remember to save preferences before rendering previews with external instance!!**
- After Install: In preferences render missing previews. It will render material view with eevee of your models, materials, and particle systems in one specific manner. Of course you can always render your own previews as 500x500 .png with the same name as blend file. But remember it can take awhile. **Remember to save preferences before rendering previews with external instance!!**
Manager tab is located in `Viewport tool panel` (shortcut key: `N`).
- Manager tab is located in `Viewport tool panel` (shortcut key: `N`).
You will see all your folders as categories, and if any folder has additional folders you will see them as subcategory. For simplicity i didn't add any additinal ones.
**For simple usage i made some hacks.** So in folder named `materials` the addon will only import materials, and if the folder is called `particles`, then it will import all the particle systems. All the rest of the folders will import you blend, obj, fbx models, and hdr images.
**For simple usage i made some hacks:**
Master Library path is for you general library. If your local library path is empty it will use master. Local library path is per blend file, and stored in scene.
- So in folder named `materials` the addon will only import materials,
- and if the folder is called `particles`, then it will import all the particle systems.
- `hdr` will import hdr or exr image into the world light.
All the rest of the folders will import you blend, obj, fbx models, and hdr images.
`Master Library` path is for you general library, saved in Blender preferences. `Local library` path is per blend file, and stored in scene.
![Small and handy.](preview.gif "Small and handy")
## Changelog
- 0.9.6 - FIXED on cursor rotation placement!, master-library, and local library(per project), operators and panels renaming with blender separators,
- 0.9.5 - search bar, relative library path, fix non-english characters, fix at origin import, added rotation, API updates.
- 0.9.4 - fixed append at origin issue, particles when imported again use previous import group.
- 0.9.3 - UI popup operator (shortcut)
- 0.9.0 - fixed silent enum error, improts cllections from blend files, imports particles as groups, can choose exr for render prev, fix render prevs.
- 0.8.0 - materials can be `append`, `added` and `replaced`.
- 0.7.0 - `open` opens file in separate instance, as well as `render previews`.
- 0.6.0 - added exr format for env images, asset collection!
- 0.5.0 - fix material shpere UV.
- 0.4.0 - fix exr, fir particle render prev, updated api, fix hdr duplicate import.
- 0.3.0 - adds obj, fbx, hdr handling, hides unnecesarry buttons.
- 0.2.0 - adds at cursor placement, minor typos, fixes importing objects with materials.
## TODO:
- most important: 0.9.6 will import obj into Assets collection, and create instance of it.
- create master library, and per project library override.
- (!) search option!;
- (?) add licence info (would be nice to add separate licence parameter for each object in file, then with one click can generate credits for everyone).
- 1.2.0 - totally changed "dynamic categories" on the backend, both local and global libraries works, fixed lagging, many minor fixes, previews are now 500x500px;
- 1.0.0 - fix "object already in a collection" issue with objects without a collection;
- 0.9.9 - fix macOS resource path issue (render previews with matcaps, or hdrs);
- 0.9.7 - dynamic search bar!!!, link works now as "Empty instance", append creates new object every time (except when the name is the same then shares the mesh), matcaps render avileable, preview images size option, layout cleanup;
- 0.9.6 - FIXED on cursor rotation placement!, master-library, and local library(per project), operators and panels renaming with blender separators;
- 0.9.5 - search bar, relative library path, fix non-english characters, fix at origin import, added rotation, API updates;
- 0.9.4 - fixed append at origin issue, particles when imported again use previous import group;
- 0.9.3 - UI popup operator (shortcut);
- 0.9.0 - fixed silent enum error, improts cllections from blend files, imports particles as groups, can choose exr for render prev, fix render prevs;
- 0.8.0 - materials can be `append`, `added` and `replaced`;
- 0.7.0 - `open` opens file in separate instance, as well as `render previews`;
- 0.6.0 - added exr format for env images, asset collection!;
- 0.5.0 - fix material shpere UV;
- 0.4.0 - fix exr, fir particle render prev, updated api, fix hdr duplicate import;
- 0.3.0 - adds obj, fbx, hdr handling, hides unnecesarry buttons;
- 0.2.0 - adds at cursor placement, minor typos, fixes importing objects with materials;
## TODO
- Textures/Images
No preview for this file type
This diff is collapsed.
import bpy
import os
from . import FORMATS, __name__
def get_library_path():
lib_global = bpy.context.preferences.addons[__name__].preferences.lib_global
lib_local = bpy.context.scene.simple_asset_manager.lib_local
lg = bpy.context.scene.simple_asset_manager.local_global
lib_path = lib_global if lg == "global" else lib_local
return bpy.path.abspath(lib_path)
def scan_for_elements(path):
if path.lower().endswith(FORMATS):
png = os.path.splitext(path)[0] + ".png"
if os.path.exists(png):
return (path, True)
else:
return (path, False)
else:
return None
def get_assets_coll():
if "Assets" not in bpy.context.scene.collection.children.keys():
asset_coll = bpy.data.collections.new("Assets")
bpy.context.scene.collection.children.link(asset_coll)
bpy.context.view_layer.layer_collection.children["Assets"].exclude = True
else:
asset_coll = bpy.data.collections["Assets"]
return asset_coll
def find_layer(coll, lay_coll=None):
if lay_coll is None:
lay_coll = bpy.context.view_layer.layer_collection
if lay_coll.collection == coll:
return lay_coll
else:
for child in lay_coll.children:
a = find_layer(coll, child)
if a:
return a
return None
def rotate_hdr(self, context):
nodes = context.scene.world.node_tree.nodes
if "Mapping" in nodes.keys():
if bpy.app.version < (2, 81):
nodes["Mapping"].rotation[2] = self.hdr_rot
else:
nodes["Mapping"].inputs[2].default_value[2] = self.hdr_rot
def append_blend_objects(blendFile, link, active_layer, coll_name):
if link:
asset_coll = get_assets_coll()
if coll_name in bpy.data.collections.keys():
# IF EXISTS THEN INSTANCE IT END FINISH
# FIXED: on second link it links twice !!! Was duplicated on append_element last line
# bpy.ops.object.collection_instance_add(collection=coll_name)
return True
else:
obj_coll = bpy.data.collections.new(coll_name)
asset_coll.children.link(obj_coll)
obj_lay_coll = find_layer(obj_coll)
bpy.context.view_layer.active_layer_collection = obj_lay_coll
else:
obj_coll = active_layer.collection
objects = []
scenes = []
with bpy.data.libraries.load(blendFile) as (data_from, _):
for name in data_from.scenes:
scenes.append({"name": name})
action = bpy.ops.wm.link if link else bpy.ops.wm.append
if bpy.app.version >= (2, 90, 0):
action(
directory=blendFile + "/Scene/",
files=scenes,
instance_collections=False,
instance_object_data=False,
link=False,
)
for lib in bpy.data.libraries:
if lib.filepath == blendFile:
bpy.data.libraries.remove(lib)
else:
action(
directory=blendFile + "/Scene/",
files=scenes,
instance_collections=False,
link=False,
)
scenes = bpy.data.scenes[-len(scenes):]
target_coll = obj_coll if link else active_layer.collection
for scene in scenes:
scene.name = "[SAM]: " + coll_name
objs = 0
for ob in scene.collection.objects:
try:
target_coll.objects.link(ob) # MASTER COLLECTION
except RuntimeError as e:
if e.args[0].endswith("already in collection 'Collection'\n"):
pass
else:
raise e
objs += 1
objects.append(ob)
for coll in scene.collection.children:
if coll.name.startswith("Collection"):
for ob in coll.objects:
target_coll.objects.link(ob)
objects.append(ob)
for sub_coll in coll.children:
target_coll.children.link(sub_coll)
else:
target_coll.children.link(coll)
bpy.data.scenes.remove(scene, do_unlink=True)
sel = False if link else True
for obj in objects:
obj.select_set(sel)
return objects
def append_blend_collections(blendFile, link):
collections = []
with bpy.data.libraries.load(blendFile) as (data_from, _):
for name in data_from.collections:
collections.append({"name": name})
action = bpy.ops.wm.link if link else bpy.ops.wm.append
action(directory=blendFile + "/Collection/", files=collections)
# SEARCH FOR NESTED COLLECTIONS
for coll in collections:
keys = bpy.data.collections[coll["name"]].children.keys()
for k in keys:
pass
def append_element(blendFile, active_layer, link=False):
# CREATE ASSETS COLLECTIONS
coll_name = os.path.splitext(os.path.basename(blendFile))[0].title()
# IMPORT OBJECTS
existing_objs = bpy.data.objects.values()
objects = []
# OBJECTS
if blendFile.endswith(".obj"):
bpy.ops.import_scene.obj(filepath=blendFile)
for ob in bpy.context.selected_objects:
objects.append(ob)
elif blendFile.endswith(".fbx"):
bpy.ops.import_scene.fbx(filepath=blendFile)
for ob in bpy.context.selected_objects:
objects.append(ob)
elif blendFile.endswith(".blend"):
objects = append_blend_objects(blendFile, link, active_layer, coll_name)
# append_blend_collections(blendFile, link)
# # COPY DATA FROM PREVIOUS IMPORT IF EXISTS
# for ob in objects:
# if "." in ob.name and ob.name.rsplit(".")[-1].isdigit():
# for eob in existing_objs:
# if (
# ob.name.rsplit(".", 1)[0] == eob.name.rsplit(".", 1)[0]
# and ob.data.name.rsplit(".", 1)[0]
# == eob.data.name.rsplit(".", 1)[0]
# and "." in ob.data.name
# and ob.data.name.rsplit(".")[-1].isdigit()
# ):
# ob.data = eob.data
bpy.context.view_layer.active_layer_collection = active_layer
if link:
bpy.ops.object.collection_instance_add(collection=coll_name)
def append_hdr(path):
file = os.path.basename(path)
# CHECK IF ALREADY LOADED
if file in bpy.data.worlds.keys():
world = bpy.data.worlds[file]
else:
bpy.ops.image.open(filepath=path)
im = bpy.data.images[file]
world = bpy.data.worlds.new(file)
world.use_nodes = True
nodes = world.node_tree.nodes
tex = nodes.new("ShaderNodeTexEnvironment")
tex.image = im
mapping = nodes.new("ShaderNodeMapping")
coord = nodes.new("ShaderNodeTexCoord")
background = nodes["Background"]
tex.location = (-300, 300)
mapping.location = (-700, 300)
coord.location = (-900, 300)
world.node_tree.links.new(background.inputs[0], tex.outputs[0])
world.node_tree.links.new(tex.inputs[0], mapping.outputs[0])
world.node_tree.links.new(mapping.inputs[0], coord.outputs[0])
bpy.context.scene.world = world
def append_material(blendFile, link=False):
files = []
with bpy.data.libraries.load(blendFile) as (data_from, _):
for name in data_from.materials:
files.append({"name": name})
action = bpy.ops.wm.link if link else bpy.ops.wm.append
for file in files:
if file["name"] not in bpy.data.materials.keys():
action(directory=blendFile + "/Material/", files=[file,])
return files
def append_particles(blendFile, link=False):
particles = []
asset_coll = get_assets_coll()
if "Particles" not in bpy.data.collections.keys():
particles_coll = bpy.data.collections.new("Particles")
asset_coll.children.link(particles_coll)
else:
particles_coll = bpy.data.collections["Particles"]
with bpy.data.libraries.load(blendFile) as (data_from, _):
for name in data_from.particles:
particles.append({"name": name})
exists = True
for name in [x["name"] for x in particles]:
if name not in bpy.data.particles.keys():
exists = False
if exists:
return particles
coll_name = os.path.splitext(os.path.basename(blendFile))[0].title()
obj_coll = bpy.data.collections.new(coll_name)
particles_coll.children.link(obj_coll)
obj_lay_coll = find_layer(obj_coll)
bpy.context.view_layer.active_layer_collection = obj_lay_coll
action = bpy.ops.wm.link if link else bpy.ops.wm.append
# colls = bpy.data.collections[:]
action(directory=blendFile + "/ParticleSettings/", files=particles)
return particles
def execute_insert(context, link):
active_layer = context.view_layer.active_layer_collection
for ob in bpy.context.scene.objects:
ob.select_set(False)
bpy.ops.object.select_all(action="DESELECT")
selected_preview = bpy.data.window_managers["WinMan"].simple_asset_manager_prevs
folder = os.path.split(os.path.split(selected_preview)[0])[1]
# APPEND OBJECTS
if "material" in folder.lower():
return append_material(selected_preview, link)
elif "particle" in folder.lower():
files = append_particles(selected_preview, link)
return files
elif selected_preview.endswith((".hdr", ".exr")):
append_hdr(selected_preview)
else:
curr_pivot = context.scene.tool_settings.transform_pivot_point
context.scene.tool_settings.transform_pivot_point = "CURSOR"
cur = context.scene.cursor
cur_loc = cur.location.copy()
cur_rot = cur.rotation_euler.copy()
cur.location = (0, 0, 0)
cur.rotation_euler = (0, 0, 0)
append_element(selected_preview, active_layer, link)
if context.scene.simple_asset_manager.origin:
if context.scene.simple_asset_manager.incl_cursor_rot:
for x, y in list(zip(cur_rot, "XYZ")):
bpy.ops.transform.rotate(value=-x, orient_axis=y)
bpy.ops.transform.translate(value=cur_loc)
# bpy.ops.wm.tool_set_by_id(name="builtin.rotate")
else:
cur.location = cur_loc
cur.rotation_euler = cur_rot
cur_loc = context.scene.cursor.location
bpy.ops.transform.translate(value=cur_loc)
cur.location = cur_loc
cur.rotation_euler = cur_rot
context.scene.tool_settings.transform_pivot_point = curr_pivot
context.view_layer.active_layer_collection = active_layer
# UI
def gen_thumbnails(image_paths, enum_items, pcoll, empty_path):
# FOR EACH IMAGE IN THE DIRECTORY, LOAD THE THUMB
# UNLESS IT HAS ALREADY BEEN LOADED
for i, im in enumerate(sorted(image_paths)):
filepath, prev = im
name = os.path.splitext(os.path.basename(filepath))[0]
name = name.replace(".", " ").replace("_", " ").lower().capitalize()
if filepath in pcoll:
enum_items.append((filepath, name, "", pcoll[filepath].icon_id, i))
else:
if prev:
imgpath = filepath.rsplit(".", 1)[0] + ".png"
# if filepath.endswith(('.hdr', '.exr')):
# imgpath = filepath
thumb = pcoll.load(filepath, imgpath, "IMAGE")
else:
thumb = pcoll.load(filepath, empty_path, "IMAGE")
enum_items.append((filepath, name, "", thumb.icon_id, i))
return enum_items
import bpy
from bpy.props import BoolProperty, PointerProperty, \
StringProperty, EnumProperty
import bmesh
import os
from platform import platform
import subprocess
from math import pi, cos, sin, radians
import bpy
from bpy.props import BoolProperty, StringProperty
import bmesh
from mathutils import Euler
from . import DEBUG, EXRS, FORMATS, __name__
from . import FORMATS, __name__
from . import find_layer, append_element, append_material, append_hdr, append_particles
from . import preview_collections
PREVIEW_RESOLUTION = 500
def purge(data):
# RENDER PREVIEW SCENE PREPARATION METHODS
for el in data:
......@@ -19,13 +21,15 @@ def purge(data):
data.remove(el)
def prepare_scene(blendFile):
def prepare_scene():
pref = bpy.context.preferences.addons[__name__].preferences
# RENDER PREVIEW SET SCENE
for ob in bpy.data.objects:
ob.hide_select = False
ob.hide_render = False
ob.hide_viewport = False
ob.hide_set(False)
bpy.data.objects.remove(ob)
for coll in bpy.data.collections:
coll.hide_select = False
coll.hide_render = False
......@@ -43,17 +47,10 @@ def prepare_scene(blendFile):
purge(bpy.data.images)
purge(bpy.data.collections)
# set output
eevee = bpy.context.scene.eevee
render = bpy.context.scene.render
eevee.use_ssr_refraction = True
eevee.use_ssr = True
eevee.use_gtao = True
eevee.gtao_distance = 1
render.filepath = os.path.splitext(blendFile)[0]
render.stamp_note_text = os.path.splitext(blendFile)[1][1:].upper()
render.film_transparent = True
render.resolution_x = 200
render.resolution_y = 200
render.resolution_x = PREVIEW_RESOLUTION
render.resolution_y = PREVIEW_RESOLUTION
render.use_stamp_date = False
render.use_stamp_render_time = False
render.use_stamp_camera = False
......@@ -63,9 +60,39 @@ def prepare_scene(blendFile):
render.use_stamp_time = False
render.use_stamp = True
render.use_stamp_note = True
render.stamp_font_size = 20
render.stamp_font_size = 50
render.image_settings.file_format = 'PNG'
render.image_settings.color_mode = 'RGBA'
# # add hdr for render
# b_path = os.path.dirname(bpy.app.binary_path)
# ver = '{}.{}'.format(*bpy.app.version[:2])
resource_path = bpy.utils.resource_path('LOCAL')
hdr = pref.render_env
hdr_path = os.path.join(resource_path, 'datafiles', 'studiolights', 'world', hdr)
append_hdr(hdr_path)
def setup_cycles():
render = bpy.context.scene.render
cycles = bpy.context.scene.cycles
render.engine = 'CYCLES'
render.tile_x = PREVIEW_RESOLUTION
render.tile_y = PREVIEW_RESOLUTION
cycles.device = 'CPU'
cycles.samples = 32
bpy.context.view_layer.cycles.use_denoising = True
def setup_eevee():
render = bpy.context.scene.render
eevee = bpy.context.scene.eevee
render.engine = 'BLENDER_EEVEE'
eevee.use_ssr_refraction = True
eevee.use_ssr = True
eevee.use_gtao = True
eevee.gtao_distance = 1
eevee.taa_render_samples = 16
def add_camera():
......@@ -95,9 +122,11 @@ def rotate_uv(ob, angle):
def set_scene_material(blendFile, cam):
bpy.ops.mesh.primitive_uv_sphere_add(segments=64,
ring_count=32,
location=(0, 0, 0))
bpy.ops.mesh.primitive_uv_sphere_add(
segments=64,
ring_count=32,
location=(0, 0, 0),
)
ob = bpy.context.active_object
bpy.ops.object.editmode_toggle()
bpy.ops.uv.sphere_project(direction='ALIGN_TO_OBJECT')
......@@ -126,32 +155,76 @@ def set_scene_material(blendFile, cam):
cam.data.lens = 41
def set_scene_hdr(blendFile, cam):
bpy.ops.mesh.primitive_uv_sphere_add(segments=64,
ring_count=32,
location=(0, 0, 0))
bpy.ops.object.shade_smooth()
bpy.ops.object.material_slot_add()
bpy.ops.material.new()
ob = bpy.context.active_object
mat = bpy.data.materials[-1]
ob.material_slots[0].material = mat
shader = mat.node_tree.nodes['Principled BSDF']
shader.inputs['Metallic'].default_value = 1
shader.inputs['Roughness'].default_value = 0
append_hdr(blendFile)
def set_scene_hdr(path, cam):
append_hdr(path)
# Add spheres and plane
positions = ((0, 0, 1), (-2.2, 0, 1), (2.2, 0, 1))
mats = ['Diffuse', 'Mirror', 'Glass']
for pos in positions:
bpy.ops.mesh.primitive_uv_sphere_add(
segments=64,
ring_count=32,
location=pos,
)
bpy.ops.object.shade_smooth()
bpy.ops.object.material_slot_add()
ob = bpy.context.active_object
mat = bpy.data.materials.new(mats.pop(0))
mat.use_nodes = True
ob.material_slots[0].material = mat
bpy.ops.mesh.primitive_plane_add()
bpy.context.active_object.scale = (22, 6, 6)
# Add materials
diffuse = bpy.data.materials['Diffuse'].node_tree.nodes['Principled BSDF']
diffuse.inputs['Metallic'].default_value = 0
diffuse.inputs['Roughness'].default_value = 1
mirror = bpy.data.materials['Mirror'].node_tree.nodes['Principled BSDF']
mirror.inputs['Metallic'].default_value = 1
mirror.inputs['Roughness'].default_value = 0
glass_ntree = bpy.data.materials['Glass'].node_tree
glass = glass_ntree.nodes.new('ShaderNodeBsdfGlass')
output = glass_ntree.nodes['Material Output']
glass_ntree.links.new(output.inputs[0], glass.outputs[0])
# Camera and scene
cam.rotation_euler = Euler((pi / 2, 0, 0), 'XYZ')
cam.data.shift_y = 0
cam.data.lens = 41
cam.location = (0, -9., 3.)
# Add render settings cycles
setup_cycles()
## Compositor
bpy.context.scene.use_nodes = True
tree = bpy.context.scene.node_tree
# hdr image
image_node = tree.nodes.new('CompositorNodeImage')
file = os.path.basename(path)
im = bpy.data.images[file]
image_node.image = im
# alpha node