kopia lustrzana https://gitlab.com/subdivided_xyz/nodes_xyz
Added localEnvUV world node.
Added support for blender's asset browser. Added some simple utility scripts managing assets.main
rodzic
433ecf0ad6
commit
0acc53d72b
|
@ -1 +1,2 @@
|
|||
*.blend1
|
||||
*.blend1
|
||||
*~
|
||||
|
|
28
README.md
28
README.md
|
@ -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.
|
BIN
assets/eye.blend
BIN
assets/eye.blend
Plik binarny nie jest wyświetlany.
|
@ -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.
|
@ -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)
|
|
@ -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()
|
|
@ -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 |
Ładowanie…
Reference in New Issue