Skip to content

Instantly share code, notes, and snippets.

@rivetchip
Last active March 30, 2023 21:34
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save rivetchip/bdbc5db09465a85c34935a6097e35c11 to your computer and use it in GitHub Desktop.
Save rivetchip/bdbc5db09465a85c34935a6097e35c11 to your computer and use it in GitHub Desktop.
import bpy
import glob
import os
import os.path as path
import time
import uuid
from datetime import date
from math import radians
"""
Sources :
https://gist.github.com/Psyda/7b795a392d0f7ec8ff1a4345b08e17a3
https://projects.blender.org/blender/blender/issues/93893
https://github.com/Gorgious56/asset_browser_utilities/blob/master/core/library/tool.py#L12
Launch :
/blender/app/blender --background --python /blender/assets/Kitbash3D/kitbash3d-create-assets.py
"""
# --------------------------------------------------------------------------------------------
library_dirname = path.dirname(path.abspath(__file__))
kit_catalog = {}
def main():
today = str(date.today())
blendcats_filepath = path.join(library_dirname, "blender_assets.cats.txt")
# clean previous assets catalogs
if path.exists(blendcats_filepath):
os.remove(blendcats_filepath)
if path.exists(blendcats_filepath + "~"):
os.remove(blendcats_filepath + "~")
# loop on subfolder only, not root files
for entry in os.scandir(library_dirname):
if not entry.name.startswith(".") and entry.is_dir():
for file in glob.glob(path.join(library_dirname, entry.name, "**/*.blend"), recursive=True):
process_blender_file(file)
with open(blendcats_filepath, "a") as file:
file.write(f"# Generation date: {today}\n")
file.write("VERSION 1\n")
for catalog_name, catalog_uid in kit_catalog.items():
catalog_hyphen = catalog_name.replace("/", "-")
file.write(f"{catalog_uid}:{catalog_name}:{catalog_hyphen}\n")
print("finish! quitting...")
bpy.ops.wm.quit_blender()
def process_blender_file(blend_filepath):
blend_dirname = path.dirname(blend_filepath)
# simulate open file from directory
os.chdir(blend_dirname)
bpy.ops.wm.open_mainfile(filepath=blend_filepath)
# create catalogs mappings
catalog_name = path.basename(blend_dirname)
if catalog_name not in kit_catalog:
kit_catalog[catalog_name] = str(uuid.uuid4())
catalog_uid = kit_catalog[catalog_name]
# remove all others scenes
for scene in bpy.data.scenes:
if not scene.name.startswith("KB3D_"):
bpy.data.scenes.remove(scene, do_unlink=True)
# checks for missings files
missing_images = list_missing_blend_images()
if len(missing_images) > 0:
print("########## MISSING IMAGES: ##########")
for missing_img in missing_images:
print(f"# {missing_img}")
print("####################################")
# get current scene
scene = bpy.context.scene
# remove all cameras
for obj in scene.objects:
if obj.type == "CAMERA":
bpy.data.objects.remove(obj)
# remove all empties empty
for obj in scene.objects:
if obj.type == "EMPTY" and len(obj.children) == 0:
bpy.data.objects.remove(obj)
# current collections assets already done
collections_assets_names = [col.name for col in scene.collection.children]
# convert all empty to collection
for obj in scene.objects:
if obj.type == "EMPTY" and obj.name.startswith("KB3D_"):
obj.location = (0, 0, 0)
# wrap object to collection if not already
if obj.name not in collections_assets_names:
create_collection_from_object(scene, obj)
# add default camera/world for rendering
scene.camera = create_scene_camera(scene)
scene.world = create_scene_world(scene)
scene_apply_rendering_settings(scene)
# create assets from the new collections
for col in scene.collection.children:
if col.asset_data is None:
col.asset_mark()
col.asset_data.catalog_id = catalog_uid
if col.preview is None:
generate_asset_preview_from_collection(scene, col)
# cleanup preview datas and render
bpy.data.objects.remove(scene.camera)
bpy.data.worlds.remove(scene.world)
bpy.data.orphans_purge(do_recursive=True)
# if bpy.data.is_dirty:
time.sleep(1)
bpy.context.preferences.filepaths.save_version = 0 # No backup blends needed
bpy.ops.wm.save_as_mainfile(filepath=blend_filepath)
def scene_apply_rendering_settings(scene):
scene.render.engine = "CYCLES"
scene.cycles.device = "GPU"
scene.cycles.feature_set = "SUPPORTED"
scene.cycles.samples = 10
scene.cycles.use_denoising = False
scene.cycles.use_auto_tile = False
scene.render.use_persistent_data = True
scene.render.resolution_x = 200
scene.render.resolution_y = 200
scene.render.image_settings.file_format = "PNG"
scene.render.image_settings.color_mode = "RGBA"
scene.render.image_settings.compression = 15
scene.render.image_settings.color_management = "FOLLOW_SCENE"
scene.render.film_transparent = True
scene.display_settings.display_device = "sRGB"
scene.view_settings.view_transform = "Standard"
scene.view_settings.look = "Medium Contrast"
scene.view_settings.exposure = 0.00
scene.view_settings.gamma = 1
def create_scene_camera(scene):
camera = bpy.data.cameras.new("Camera")
camera_obj = bpy.data.objects.new("Camera Render", camera)
camera_obj.location = (0, 0, 0)
camera_obj.rotation_euler = (radians(75), radians(0), radians(45))
scene.collection.objects.link(camera_obj)
return camera_obj
def create_scene_world(scene):
world = bpy.data.worlds.new("World Render")
world.use_nodes = True
node_tree = world.node_tree
node_tree.nodes.clear()
node_background = node_tree.nodes.new(type="ShaderNodeBackground")
node_background.inputs.get("Color").default_value = (1, 1, 1, 1) # white
node_background.inputs.get("Strength").default_value = 1
node_output = node_tree.nodes.new(type="ShaderNodeOutputWorld")
node_tree.links.new(input=node_background.outputs.get("Background"), output=node_output.inputs.get("Surface"))
return world
def create_collection_from_object(scene, obj):
print("create_collection:", obj.name)
# create a collection and add it to scene main collection
new_collection = bpy.data.collections.new(obj.name)
scene.collection.children.link(new_collection)
# move all objects to collection
new_collection.objects.link(obj)
for child in obj.children:
new_collection.objects.link(child)
# unlink all children from previous parent
scene.collection.objects.unlink(obj)
for child in obj.children:
scene.collection.objects.unlink(child)
def generate_asset_preview_from_collection(scene, col):
print("generate_preview:", col.name)
# mark other collections as non-visible
for col2 in scene.collection.children:
col2.hide_render = (col2.name != col.name)
# center the camera acording to objects in collection
visible_objects = [obj.name for obj in col.all_objects.values()]
for obj in scene.objects:
obj.select_set(obj.name in visible_objects)
bpy.ops.view3d.camera_to_view_selected()
# finally, render
preview_filename = str(uuid.uuid4()) + ".png"
scene.render.filepath = path.join(bpy.app.tempdir, preview_filename)
bpy.ops.render.render(write_still=True)
while bpy.app.is_job_running("RENDER"):
print("Waiting for render...")
time.sleep(1)
with bpy.context.temp_override(id=col):
bpy.ops.ed.lib_id_load_custom_preview(filepath=str(scene.render.filepath))
# re-set default visility values
for col2 in scene.collection.children:
col2.hide_render = False
def list_missing_blend_libs():
return [bpy.path.abspath(lib.filepath) for lib in bpy.data.libraries
if not path.exists(bpy.path.abspath(lib.filepath))]
def list_missing_blend_images():
return [bpy.path.abspath(img.filepath) for img in bpy.data.images
if img.source == "FILE" and img.packed_file is None and not path.exists(bpy.path.abspath(img.filepath))]
if __name__ == "__main__":
main()
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment