Skip to content

Instantly share code, notes, and snippets.

@Psyda
Last active January 15, 2024 14:33
Show Gist options
  • Star 6 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save Psyda/7b795a392d0f7ec8ff1a4345b08e17a3 to your computer and use it in GitHub Desktop.
Save Psyda/7b795a392d0f7ec8ff1a4345b08e17a3 to your computer and use it in GitHub Desktop.
Moves all empty objects into collections named after them for easy importing into Blender's Asset Library
# Open your KB3D Pack. Make sure the textures are loaded and everything works as intended. This script assumes the Scene is named KB3D_"TitleCasePackName"-Native. It should be that by default.
# Create a new folder in your Asset Library Folder for Blender. (Edit>Preferences>FilePaths>AssetLibraries). I recommend a setup like this
# (D:/BlenderAssetLibraries/KB3D/CyberDistricts/Cyberdistricts.blend)
# Where "CyberDistricts" would be the AssetLibrary.
# Open the script in Blender by going to the Text Editor and clicking "Open" to locate the file, or paste it from the Gist.
# Run the script by clicking the "Run Script" button in the Text Editor or pressing Alt+P.
# The script will create new collections for each type of asset in your scene and move the assets into these collections. It will also generate a unique ID for the asset pack and each asset in the pack.
# The script will then update the asset catalog file to include the new asset pack and each asset in the pack.
# Once the script is finished, you can save your Blender file and close it.
# You should now see it in the asset libraries section on Blender.
# Troubleshooting: If something goes wrong or a mistake is maid I have minimal errorchecking. Just undo before the script was run and delete the Catalogs or the "blender_assets.cats.txt" file,
# they may trip up the script if it failed partway through.
# I put a lot of effort into making this, so if you'd like to show your appreciation-
# you can donate to me through PayPal at the link below: https://paypal.me/mintyfresh
# Update: Remade better.
# TODO: Add simple GUI, test all packs, ensure consistent sizing and proper integration for easy placing of props.
import bpy
import os
from pathlib import Path
import uuid
#filename
file_name = str(bpy.data.scenes.keys()[0].split('_')[1].split('-')[0])
folder = Path(bpy.data.filepath).parent
new_uuid = str(uuid.uuid4())
# Mapping for collections to asset types
collection_map = {
"Props": "Prop",
"Buildings": "Bldg",
"Structures": "Strc",
"Vehicles": "Vehi",
"Creatures": "Crea"
}
kit_catalog = {}
flipped_cmap = {v: k for k, v in collection_map.items()}
# Create the primary collections
primary_collections = ["Props", "Buildings", "Structures", "Vehicles", "Creatures", "Other"]
for collection_name in primary_collections:
new_collection = bpy.data.collections.new(collection_name)
bpy.context.scene.collection.children.link(new_collection)
# Loop through all the empties and sort them into the collections for the library
for obj in bpy.data.objects:
if obj.type == "EMPTY" and obj.name.startswith("KB3D_"):
# Sort the new collection into the correct group
asset_type = obj.name.split("_")[2][:4]
# Check if the asset_type exists in the flipped_cmap dictionary
if asset_type in flipped_cmap:
cm_col_name = flipped_cmap[asset_type]
else:
cm_col_name = "Other"
# Create a new collection with Empty's name
new_collection = bpy.data.collections.new(obj.name)
bpy.data.collections[obj.name].objects.link(obj)
# Linking Object groups
cm_col = bpy.data.collections[cm_col_name]
# Move the object's children to the new collection
bpy.data.collections[cm_col.name].children.link(bpy.data.collections[obj.name])
kit_catalog[obj.name] = cm_col.name
print(kit_catalog.values())
# Loop over Object and relink to parents.
for obj in bpy.data.objects:
if obj.type == "MESH" and obj.parent != None:
bpy.data.collections[obj.parent.name].objects.link(obj)
for empty in bpy.data.objects:
if empty.type == 'EMPTY' and empty.name.startswith('KB3D_'):
bpy.context.scene.collection.objects.unlink(empty)
for child in empty.children:
bpy.context.scene.collection.objects.unlink(child)
for col in bpy.data.collections:
if col.name.startswith('KB3D_'):
col.asset_mark()
# Create a list of all empty objects
empty_objects = [obj for obj in bpy.data.objects if obj.type == "EMPTY" and obj.name.startswith("KB3D_")]
# Loop through the list of empty objects and clear their locations
for obj in empty_objects:
obj.location = (0, 0, 0)
asset_catalog_path = folder / "blender_assets.cats.txt"
# Initialize the list of lines and the asset_uuids dictionary
lines = []
asset_uuids = {}
# Read the existing asset catalog file into a list of lines
with asset_catalog_path.open('a+') as f:
f.seek(0) # Move the file pointer to the beginning of the file
lines = f.readlines()
# Check if the blender_assets.cats.txt file is empty and initialize it if necessary
if os.path.getsize(asset_catalog_path) == 0:
header_lines = [
"# This is an Asset Catalog Definition file for Blender.\n",
"#\n",
"# Empty lines and lines starting with `#` will be ignored.\n",
"# The first non-ignored line should be the version indicator.\n",
"# Other lines are of the format \"UUID:catalog/path/for/assets:simple catalog name\"\n",
"\n",
"VERSION 1\n\n"
]
lines.extend(header_lines)
# Add new lines to the catalog if necessary
file_contents = ''.join(lines)
# Initialize the asset_uuids dictionary outside the condition
collection_uuids = {collection_name: [] for collection_name in collection_map.keys()}
collection_uuids["Other"] = []
if file_name not in file_contents:
if "KB3D" not in file_contents:
lines.append(f"{str(uuid.uuid4())}:KB3D:KB3D\n")
lines.append(f"{str(uuid.uuid4())}:KB3D/{file_name}:{file_name}\n")
for collection_name in collection_map.keys():
asset_uuid = str(uuid.uuid4())
asset_uuids[collection_name] = asset_uuid
lines.append(f"{asset_uuid}:KB3D/{file_name}/{collection_name}:{collection_name}\n")
asset_uuid = str(uuid.uuid4())
asset_uuids["Other"] = asset_uuid
lines.append(f"{asset_uuid}:KB3D/{file_name}/Other:Other\n")
# Write the updated list of lines back to the file
with asset_catalog_path.open("w") as f:
f.writelines(lines)
kit_catalog = {}
# Asset Library Adder
for col in bpy.data.collections:
if col.name.startswith("KB3D_"):
# Gets the asset type of an object from the Name
# Ex: Bldg Prop Strc
asset_type = col.name.split("_")[2][:4] # e.g. "Bldg"
# Check if the asset_type exists in the flipped_cmap dictionary
if asset_type in flipped_cmap:
asset_name = flipped_cmap[asset_type] # e.g. "Buildings"
col.asset_data.catalog_id = asset_uuids[asset_name]
else:
asset_name = "Other"
col.asset_data.catalog_id = asset_uuids[asset_name] # Set the catalog_id for the "Other" category
print(col.name, asset_name)
# Linking Object groups
cm_col = bpy.data.collections[col.name] # Buildings [Collection]
print('adding', col.name, 'asset_data')
@imloic
Copy link

imloic commented Jan 26, 2023

Works well but there is one problem. When you put your asset on the scene, the object is not attached to his origin point. I need to click on the object and "SET ORIGIN" - > ORIGIN TO GEOMETRY.

Blender 3.4.1

@Psyda
Copy link
Author

Psyda commented Jan 26, 2023

Works well but there is one problem. When you put your asset on the scene, the object is not attached to his origin point. I need to click on the object and "SET ORIGIN" - > ORIGIN TO GEOMETRY.

Blender 3.4.1

Good catch, I'll take a look and update the script this week.

@imloic
Copy link

imloic commented Jan 30, 2023

Hi I come to the news. I tried to modify the script but I still have origin point problems. I have several KITs to integrate and manually it's hell! Thanks

@Psyda
Copy link
Author

Psyda commented Jan 30, 2023

Hi I come to the news. I tried to modify the script but I still have origin point problems. I have several KITs to integrate and manually it's hell! Thanks

I believe it should be fixed now!

@imloic
Copy link

imloic commented Jan 30, 2023

Hi ! I got an error line 27 :(

@Psyda
Copy link
Author

Psyda commented Jan 30, 2023

Hi ! I got an error line 27 :(

Sorry about that, Wasn't paying attention and pasted the changed file into the old script. Fixed it now.

@imloic
Copy link

imloic commented Jan 30, 2023

I just tried again the origin point is not on the object when you drag the asset on the scene. This is very strange. If I do the manipulation manually I don't have this problem.

#Sets the origin point bpy.ops.object.origin_set(type='ORIGIN_GEOMETRY', center='MEDIAN')

I had already tried on my side. Nothing to do.

@Psyda
Copy link
Author

Psyda commented Feb 19, 2023

I just tried again the origin point is not on the object when you drag the asset on the scene. This is very strange. If I do the manipulation manually I don't have this problem.

#Sets the origin point bpy.ops.object.origin_set(type='ORIGIN_GEOMETRY', center='MEDIAN')

I had already tried on my side. Nothing to do.

Sorry for the delay, I've rewritten it. It's far faster and now works as expected. :)

I'm working on auto-generating folders, but it's taking quite a long time to wrap my head around handling the blender_asset.cats.txt file properly.

@imloic
Copy link

imloic commented Feb 19, 2023

Thank you so much!

@WestRodri
Copy link

Hey, Thanks so much for making this and taking the time to answer. I get this error "line 62, in
KeyError: 'ACPD' Would you know what could be causing that ?

@Psyda
Copy link
Author

Psyda commented Feb 21, 2023

Hey, Thanks so much for making this and taking the time to answer. I get this error "line 62, in KeyError: 'ACPD' Would you know what could be causing that?

I'm betting its an uncaught asset type. What is the name? It would be added to the list of objects like

Which pack are you using? Does the name structure go "KB3D_PropTableEtc_A_grp"? If so It might be an object I didn't add to the group.

Can you take a picture of your outlines asset names?

@WestRodri
Copy link

Thanks for the reply! I'm using Cyberpunk
2023-02-21 00_57_33-

Thanks makes sense. I fiddle with it a bit and say that ACPD was an asset in the outliner.

@WestRodri
Copy link

It looks like CBP is on all the assets which could be what's tripping it up.

@Psyda
Copy link
Author

Psyda commented Feb 21, 2023

Thanks for the reply! I'm using Cyberpunk 2023-02-21 00_57_33-

Thanks makes sense. I fiddle with it a bit and say that ACPD was an asset in the outliner.

Oh, I didn't realize some packs weren't sorted into categories. I'll try to push out an update tonight that will add a new category catch-all for all objects that aren't pre-sorted.

@WestRodri
Copy link

Oh dang nice! I really appreciate you!

@Psyda
Copy link
Author

Psyda commented Feb 21, 2023

Oh dang nice! I really appreciate you!

:)

I've just updated the script. I didn't properly add in full support, but it should add unknowns to a collection Catalog called "Other" now.

If you have issues. Make sure to delete all the Catalogs existing for the current blend file or asset library it's to be stored in.

@WestRodri
Copy link

Thanks for helping me out with this! Works on my end.

@Psyda
Copy link
Author

Psyda commented Feb 21, 2023

Thanks for helping me out with this! Works on my end.

Absolutely! ❤️ Cheers!

@rivetchip
Copy link

Hello, I've made a little script myself which generate preview if anyone interested 😄
(basically it create a default world and center the camera on each assets)

It needs to be run on the command line :
blender --background --python kitbash3d-create-assets.py

Preview :
2023-02-28 03-52-20

@Psyda
Copy link
Author

Psyda commented Mar 1, 2023

Hello, I've made a little script myself which generate preview if anyone interested 😄 (basically it create a default world and center the camera on each assets)

It needs to be run on the command line : blender --background --python kitbash3d-create-assets.py

Preview : 2023-02-28 03-52-20

I love you for this. It has been on my todo list for awhile.

@rivetchip
Copy link

rivetchip commented Mar 1, 2023

I love you for this. It has been on my todo list for awhile.

Thanks! Basically the script loop on every blend files, move each empty to a new collection, mark them as assets, align the camera, then render each collection separately (hide others) and finally set the rendered picture as preview.

Blender have a "asset_generate_preview" function, but it apparently don't work on empty or collection... (and sometimes it gives me a black screen).

The camera is set to a fixed rotation (as I don't think it's possible to "guess" the best angle) :

camera_obj.rotation_euler = (radians(75), radians(0), radians(45))

This is nothing fancy, but it dos the job 😃

@Psyda
Copy link
Author

Psyda commented Mar 1, 2023

I love you for this. It has been on my todo list for awhile.

Thanks! Basically the script loop on every blend files, move each empty to a new collection, mark them as assets, align the camera, then render each collection separately (hide others) and finally set the rendered picture as preview.

Blender have a "asset_generate_preview" function, but it apparently don't work on empty or collection... (and sometimes it gives me a black screen).

The camera is set to a fixed rotation (as I don't think it's possible to "guess" the best angle) :

camera_obj.rotation_euler = (radians(75), radians(0), radians(45))

This is nothing fancy, but it dos the job 😃

Ah! Not bad, also should note that it should be possible to guess the best angle for KB3D as all assets face forward where possible towards the Y+ direction.

Also, the function for generating previews does technically work on collections with:
bpy.ops.ed.lib_id_generate_preview() I'm still trying to figure out an ideal way to grab asset data blocks for this, but this way should be faster.

@imloic
Copy link

imloic commented Mar 1, 2023

Hi @rivetchip. Do you mean that your script allows you to add the objects to the assets library? I haven't tried it yet

@rivetchip
Copy link

Also, the function for generating previews does technically work on collections with:
bpy.ops.ed.lib_id_generate_preview() I'm still trying to figure out an ideal way to grab asset data blocks for this, but this way should be faster.

I stumbled upon some issues with either "lib_id_generate_preview" or "asset_generate_preview" like this one : https://projects.blender.org/blender/blender/issues/93893 ; that is why I did rendered each collection.

These functions seems to run in background and you can't tell when they finish, a call to bpy.app.is_job_running('RENDER_PREVIEW') always return "True", so you need to wait an arbitrary number of seconds before saving the file...

But I agree with you, using these functions should be more efficient!


@imloic I have set-up my assets library like this :

  • /assets
    • /Kitbash3D
      • /Lunar Base
      • /Other
      • /Other
    • create-assets-script.py

When I launch the script, it move every empty to a collection and mark them as asset, to be available in the library. But maybe I didn't correctly understand the question ?

@imloic
Copy link

imloic commented Mar 1, 2023

@rivetchip Thanks, I'll try with the next assets. I already have a nice collection. @Psyda script has done me a lot of good already!

@DomiTorfs
Copy link

I'm having issues with using this script. When I run the script I get the following:

Python: Traceback (most recent call last):
File "D:\BlenderAssetLibraries\KB3D\MissionToMinerva\kb3d_missiontominerva-native.blend\Blender KB3D Collection Maker.py", line 112, in
File "C:\Program Files\Blender Foundation\Blender 3.4\3.4\python\lib\pathlib.py", line 1119, in open
return self._accessor.open(self, mode, buffering, encoding, errors,
FileNotFoundError: [Errno 2] No such file or directory: 'D:\BlenderAssetLibraries\KB3D\MissionToMinerva\blender_assets.cats.txt'

I'm no coder, and I have no idea how to fix this. I do see the assets in my asset browser, but when I drag and drop one of them in the scene I just get the empty without the model.

Also, after running the script, all the models move to the 3D cursor or the world origin I guess.

Can anyone tell me what I'm doing wrong and how to fix it?

@Psyda
Copy link
Author

Psyda commented Apr 15, 2023

Could you try again @DomiTorfs?
I've gone ahead and remade the entire script to clean up the code, comment better, and finish concept ideas.

Just gotta find the kb3d asset file, delete any blender asset cat file if its in the same folder, then run the script.

If it cannot find the file you mentioned, it will now create one, but if one exists, it tends to have some trouble adjusting it.

Once its run with no errors, and is sorted in the asset library its ready to be tested. If it works, save the file and make sure the folder the blend file is in, is added to the Preferences>filepaths>asset libraries area so you can use it everywhere.

@MeltedSynapse
Copy link

Getting :
Error: Python: Traceback (most recent call last):
File "D:\BlenderAssetLibraries\KB3D\Roads\KB3D_Roads-Native.blend\Blender KB3D Collection Maker.py", line 25, in
IndexError: list index out of range
I followed the instructions in the title , not sure what i'm doing wrong.
Any help would be appreciated, thanks.

@Psyda
Copy link
Author

Psyda commented Apr 23, 2023

@MeltedSynapse I don't have access to that Prop pack currently. Could you give me a screenshot of your outliner? I'm going to assume the file naming conventions are different in some packs.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment