Added localEnvUV world node.

Added support for blender's asset browser.
Added some simple utility scripts managing assets.
main
Frank Firsching 2022-11-05 23:25:15 +01:00
rodzic 433ecf0ad6
commit 0acc53d72b
20 zmienionych plików z 261 dodań i 2 usunięć

3
.gitignore vendored
Wyświetl plik

@ -1 +1,2 @@
*.blend1
*.blend1
*~

Wyświetl plik

@ -2,6 +2,9 @@
A collection of my commonly used blender node groups (shading nodes, geometry nodes, ...)
All the content is prepared to be used with blender's asset browser. Just add the root directory of this repository to
the asset libraries in the preferences and enjoy all the content.
## Getting started
To get the nodes either download the respecive blend file directly or download the full repository as a zip file using
@ -48,6 +51,17 @@ support the asset workflow.
> A volumetric water material.
## World shader nodes
### [LocalEnvUV](worldNodes/halfDomeEnv.blend)
<img src="worldNodes/halfDomeEnv.png" height=200 />
> A node, that calculates environment map UVs for realizing a flat ground on an HDRI based world environment. Just
> connect this node to your environment image and adjust the size, camera height and the orientation angle to setup the
> HDRI.
## Geometry nodes
### [Tissue](geoNodes/tissue.blend) (Blender 3.1)
@ -126,4 +140,16 @@ The repository also will contain a list of small assets, that can be reused in m
[<img src="assets/ufo.png" height=200/>](assets/ufo.blend)
> A procedural UFO, that utilizes geometry nodes to build the spaceship from a cross section curve. This model additionally is rigged and has a laser mounted at the bottom.
> A procedural UFO, that utilizes geometry nodes to build the spaceship from a cross section curve. This model
> additionally is rigged and has a laser mounted at the bottom.
# Scripts
The [scripts](scripts/) directory contains some little utility scripts, that are useful for asset handling.
* [createAssetCatalogsFromFileTree.py](scripts/createAssetCatalogsFromFileTree.py) takes a directory structure and
creates/updates a catalog file for blender's asset browser.
* [mark_scene_entities_as_asset.sh](scripts/mark_scene_entities_as_asset.sh) is a little script, that takes a scene file
and marks the content as assets. It looks into the scene file and if there's only a single object, this objects gets
marked as an asset, otherwise all top level collections will be assets. Additionally is finds the right catalog to
sort the assets into, ensures there's a preview image and if needed even renders one.

Plik binarny nie jest wyświetlany.

Wyświetl plik

@ -0,0 +1,12 @@
# This is an Asset Catalog Definition file for Blender.
#
# Empty lines and lines starting with `#` will be ignored.
# The first non-ignored line should be the version indicator.
# Other lines are of the format "UUID:catalog/path/for/assets:simple catalog name"
VERSION 1
e35464fe-861b-4038-bf24-25968b09e5e9:assets:
92f9a51c-b9e6-4acc-a7d7-8e3b070b24a8:geoNodes:
897ff8e7-4d16-46e8-9ef2-c1a8fa3221a3:shadingNodes:
db071ab1-33be-4ac0-943f-9012546c3f0d:worldNodes:

Plik binarny nie jest wyświetlany.

Plik binarny nie jest wyświetlany.

Plik binarny nie jest wyświetlany.

Plik binarny nie jest wyświetlany.

Plik binarny nie jest wyświetlany.

Plik binarny nie jest wyświetlany.

Wyświetl plik

Wyświetl plik

Wyświetl plik

@ -0,0 +1,59 @@
#!/usr/bin/python3
import os
import sys
import uuid
import argparse
import shutil
parser = argparse.ArgumentParser(description='Generate an updated blender_assets.cats.txt')
parser.add_argument('--write', help="If given, update blender's catalog file of the "+
"current dir. If not, print content to stdout.",
action='store_true')
args = parser.parse_args()
pathsUUIDs = {}
ignored_dirs = [".git", ".vscode"]
catalog_file = "./blender_assets.cats.txt"
if os.path.exists(catalog_file):
with open(catalog_file) as f:
for line in f.readlines():
if not line.startswith("#") and ":" in line:
line_uuid = line.split(":")[0].strip()
path = line.split(":")[1].strip()
pathsUUIDs[path] = line_uuid
f = None
if args.write:
if os.path.exists(catalog_file):
shutil.copyfile(catalog_file, catalog_file+".bak")
f = open(catalog_file, "w")
else:
f = sys.stdout
print("# Autogenerated folder based asset catalogs", file=f)
print("", file=f)
print("VERSION 1", file=f)
catalog_paths = []
for root,dirs,files in os.walk("."):
traverse_dirs = []
for dir in dirs:
if dir in ignored_dirs:
continue
path = os.path.join(root, dir)
if not os.path.exists(os.path.join(path, ".catalogignore")):
catalog_path = path.replace("./", "")
catalog_paths.append(catalog_path)
if not os.path.exists(os.path.join(path, ".nosubcatalogs")):
traverse_dirs.append(dir)
dirs[:] = traverse_dirs
catalog_paths.sort()
for catalog_path in catalog_paths:
line_uuid = str(uuid.uuid4())
if catalog_path in pathsUUIDs:
line_uuid = pathsUUIDs[catalog_path]
print(":".join([line_uuid, catalog_path])+":", file=f)

Wyświetl plik

@ -0,0 +1,153 @@
#!/usr/bin/python3
import os
import pathlib
import random
import math
from posixpath import relpath
import tempfile
import bpy
# Config
tags=[""]
force_preview_update = True
# Nothing to change below
def find_catalog_id():
pathsUUIDs = {}
blender_catalogs_file = "blender_assets.cats.txt"
current_dir = pathlib.Path().absolute()
blender_catalogs_dir = None
catalog_id = None
for x in [current_dir, *pathlib.Path(current_dir).parents]:
if os.path.exists(os.path.join(x, blender_catalogs_file)):
blender_catalogs_dir = x
break
if blender_catalogs_dir is not None:
with open(os.path.join(blender_catalogs_dir, blender_catalogs_file)) as f:
for line in f.readlines():
if not line.startswith("#") and ":" in line:
line_uuid = line.split(":")[0].strip()
path = line.split(":")[1].strip()
pathsUUIDs[path] = line_uuid
rel_path = os.path.relpath(current_dir, blender_catalogs_dir)
while rel_path != ".":
if rel_path in pathsUUIDs:
catalog_id = pathsUUIDs[rel_path]
break
rel_path = str(pathlib.Path(rel_path).parent)
return catalog_id
def setup_world(scene):
# Make sure we have a camera
camera_data = bpy.data.cameras.new(name='Camera')
camera = bpy.data.objects.new('Camera', camera_data)
scene.camera = camera
bpy.context.scene.collection.objects.link(camera)
# Change Settings
camera.rotation_euler = (70/180*math.pi, 0, -20/180*math.pi)
# This was needed for very small assets. We could base the clip planes on the scene's bounding box.
# camera_data.clip_start = 0.001
# camera_data.clip_end = 10
scene.render.engine = 'CYCLES'
scene.render.resolution_y = 256
scene.render.resolution_x = 256
scene.render.film_transparent = True
scene.render.image_settings.file_format = 'PNG'
# Setup the environment map
scene.world.use_nodes = True
world_tree = scene.world.node_tree
env_node = world_tree.nodes.new('ShaderNodeTexEnvironment')
bg_node = world_tree.nodes['Background']
world_tree.links.new(bg_node.inputs['Color'], env_node.outputs['Color'])
env_node.image = bpy.data.images.load("/windows/d/3DLibrary/hdri/HDRIHaven/old_depot_4k.hdr")
def snapshot(scene, entity, tmpdirname):
bpy.ops.object.select_all(action='DESELECT')
if entity.rna_type.name == 'Collection':
for o in entity.objects:
o.select_set(True)
else:
entity.select_set(True)
bpy.ops.view3d.camera_to_view_selected()
filename = str(random.randint(0,100000000000))+".png"
filepath = str(os.path.abspath(os.path.join(tmpdirname, filename)))
scene.render.filepath = filepath
#Render File, Mark Asset and Set Image
bpy.ops.render.render(write_still = True)
return filepath
def ensure_preview_image(obj, tmpdirname):
preview_filepath = None
if obj.preview is None or force_preview_update:
exts=['.jpg', '.jpeg', '.png']
for ext in exts:
check_filepath = bpy.data.filepath.replace(".blend", ext)
if os.path.exists(check_filepath):
preview_filepath = check_filepath
if preview_filepath is None:
print("Generating preview image")
preview_filepath = snapshot(bpy.context.scene, obj, tmpdirname)
return preview_filepath
def mark_entity(obj, preview_filepath, catalog_id):
# Mark asset
if obj.asset_data is None:
print("Marking ", obj)
obj.asset_mark()
# Set tags
for tag in tags:
if not tag in obj.asset_data.tags:
print("Creating tag ", tag)
obj.asset_data.tags.new(tag)
# Set preview
if obj.preview is None or force_preview_update:
override = bpy.context.copy()
override['id'] = obj
print("Loading preview image: ", preview_filepath)
with bpy.context.temp_override(**override):
bpy.ops.ed.lib_id_load_custom_preview(filepath=preview_filepath)
# Put into right catalog
if catalog_id is not None:
print("Setting catalog id:", catalog_id)
obj.asset_data.catalog_id = catalog_id
def collect_asset_entities():
asset_entities = []
if len(bpy.data.objects)==1:
for obj in bpy.data.objects:
if obj.parent is None:
asset_entities.append(obj)
else:
# We are only interested in root collections, which we can find as
# children of the scene collection
for col in bpy.context.scene.collection.children:
asset_entities.append(col)
return asset_entities
with tempfile.TemporaryDirectory() as tmpdirname:
setup_world(bpy.context.scene)
preview_file_paths = []
for entity in collect_asset_entities():
preview_file_paths.append(ensure_preview_image(entity, tmpdirname))
#Cleanup
bpy.ops.wm.revert_mainfile()
# We need to collect the entities twice, because we might revert the blend file inbetween
for entity,preview_file_path in zip(collect_asset_entities(), preview_file_paths):
mark_entity(entity, preview_file_path, find_catalog_id())
bpy.ops.wm.save_mainfile()

Wyświetl plik

@ -0,0 +1,8 @@
#!/usr/bin/bash
if [[ $# -ne 1 ]]; then
echo "Usage: $0 blend-file"
exit 1
fi
/opt/blender-3.3/blender -b "$1" -P ~/blender/mark_scene_entities_as_asset.py

Plik binarny nie jest wyświetlany.

Plik binarny nie jest wyświetlany.

Plik binarny nie jest wyświetlany.

Plik binarny nie jest wyświetlany.

Plik binarny nie jest wyświetlany.

Po

Szerokość:  |  Wysokość:  |  Rozmiar: 69 KiB