save startup blend for animation tab & whatnot
This commit is contained in:
2026-04-08 12:10:18 -06:00
parent 57a652524a
commit 692e200ffe
180 changed files with 12336 additions and 3431 deletions
@@ -17,14 +17,14 @@
# ##### END GPL LICENSE BLOCK #####
# type: ignore
from __future__ import annotations
import json
import math
import os
import random
import colorsys
import sys
from traceback import print_exc
from typing import Any, Union
import bpy
@@ -36,47 +36,66 @@ def get_obnames(BLENDERKIT_EXPORT_DATA: str):
return obnames
def center_obs_for_thumbnail(obs):
s = bpy.context.scene
# obs = bpy.context.selected_objects
def center_objs_for_thumbnail(obs: list[Any]) -> None:
"""Center and scale objects for optimal thumbnail framing.
Steps:
1. Center objects in world space (handles parent-child hierarchy)
2. Adjust camera distance based on object bounds
3. Scale helper objects to fit the model in frame
Args:
obs: List of Blender objects to center and frame.
"""
scene = bpy.context.scene
parent = obs[0]
# Handle instanced collections (linked objects)
if parent.type == "EMPTY" and parent.instance_collection is not None:
obs = parent.instance_collection.objects[:]
# Get top-level parent
while parent.parent is not None:
parent = parent.parent
# reset parent rotation, so we see how it really snaps.
# Reset parent rotation for accurate snapping
parent.rotation_euler = (0, 0, 0)
parent.location = (0, 0, 0)
bpy.context.view_layer.update()
# Calculate bounding box in world space
minx, miny, minz, maxx, maxy, maxz = utils.get_bounds_worldspace(obs)
# Center object at world origin
cx = (maxx - minx) / 2 + minx
cy = (maxy - miny) / 2 + miny
for ob in s.collection.objects:
for ob in scene.collection.objects:
ob.select_set(False)
bpy.context.view_layer.objects.active = parent
# parent.location += mathutils.Vector((-cx, -cy, -minz))
parent.location = (-cx, -cy, 0)
camZ = s.camera.parent.parent
# camZ.location.z = (maxz - minz) / 2
camZ.location.z = (maxz) / 2
# Adjust camera position and scale based on object size
cam_z = scene.camera.parent.parent
cam_z.location.z = maxz / 2
# Calculate diagonal size of object for scaling
dx = maxx - minx
dy = maxy - miny
dz = maxz - minz
r = math.sqrt(dx * dx + dy * dy + dz * dz)
# Scale scene elements to fit object
scaler = bpy.context.view_layer.objects["scaler"]
scaler.scale = (r, r, r)
coef = 0.7
coef = 0.7 # Camera distance coefficient
r *= coef
camZ.scale = (r, r, r)
cam_z.scale = (r, r, r)
bpy.context.view_layer.update()
def render_thumbnails():
def render_thumbnails() -> None:
"""Render the current scene to a still image (no animation)."""
bpy.ops.render.render(write_still=True, animation=False)
@@ -112,17 +131,23 @@ def patch_imports(addon_module_name: str):
print(f"- Local repository {parts[1]} added")
def replace_materials(obs, material_name):
"""Replace all materials on objects with the specified material
def replace_materials(
obs: list[Any], material_name: str
) -> Union[bpy.types.Material, None]:
"""Replace all materials on the given objects with a wireframe material.
Args:
obs: List of objects to process
material_name: Name of the material to apply to all objects
obs: List of Blender objects to modify.
material_name: Name of the wireframe material to use.
"""
material = bpy.data.materials.get(material_name)
if not material:
# Create or get the wireframe material
if material_name in bpy.data.materials:
material = bpy.data.materials[material_name]
else:
bg_blender.progress(f"Material {material_name} not found")
return
# Assign the wireframe material to all objects
for ob in obs:
if ob.type == "MESH":
# Clear all material slots and add the specified material
@@ -131,6 +156,57 @@ def replace_materials(obs, material_name):
return material
def disable_modifier(obs: list[Any], modifier_type: str) -> None:
"""Disable a specific type of modifier on all given objects.
Args:
obs: List of Blender objects to modify.
modifier_type: Type of the modifier to disable (e.g., 'SUBSURF').
"""
for ob in obs:
if ob.type == "MESH":
for mod in ob.modifiers:
if mod.type == modifier_type:
mod.show_viewport = False
mod.show_render = False
# disable only first found
break
def _str_to_color(s: str) -> Union[tuple[float, float, float], None]:
"""Convert a color string to an RGB tuple.
Args:
s: Color string in the format "#RRGGBB" or "R,G,B".
Returns:
A tuple of (R, G, B) values as floats in the range [0.0, 1.0], or None.
"""
hex_size = 7 # e.g. "#RRGGBB"
rgb_size = 5 # e.g. "R,G,B"
rgb_count = 3
s = s.strip()
if s.startswith("#") and len(s) == hex_size:
r = int(s[1:3], 16) / 255.0
g = int(s[3:5], 16) / 255.0
b = int(s[5:7], 16) / 255.0
return (r, g, b)
if len(s) == rgb_size:
parts = s.split(",")
if len(parts) == rgb_count:
try:
r = float(parts[0].strip())
g = float(parts[1].strip())
b = float(parts[2].strip())
except ValueError:
pass
else:
return (r, g, b)
# Default to None
return None
if __name__ == "__main__":
try:
# args order must match the order in blenderkit/autothumb.py:get_thumbnailer_args()!
@@ -144,6 +220,7 @@ if __name__ == "__main__":
with open(BLENDERKIT_EXPORT_DATA, "r", encoding="utf-8") as s:
data = json.load(s)
thumbnail_use_gpu = data.get("thumbnail_use_gpu")
thumbnail_disable_subdivision = data.get("thumbnail_disable_subdivision", False)
if data.get("do_download"):
# if this isn't here, blender crashes.
@@ -195,7 +272,7 @@ if __name__ == "__main__":
}
bpy.context.scene.camera = bpy.data.objects[camdict[data["thumbnail_snap_to"]]]
center_obs_for_thumbnail(allobs)
center_objs_for_thumbnail(allobs)
bpy.context.scene.render.filepath = data["thumbnail_path"]
if thumbnail_use_gpu is True:
bpy.context.scene.cycles.device = "GPU"
@@ -214,8 +291,8 @@ if __name__ == "__main__":
"SIDE": 4,
"TOP": 5,
}
s = bpy.context.scene
s.frame_set(fdict[data["thumbnail_angle"]])
scene = bpy.context.scene
scene.frame_set(fdict[data["thumbnail_angle"]])
snapdict = {
"GROUND": "Ground",
@@ -262,12 +339,19 @@ if __name__ == "__main__":
1 - random_color[2],
1,
)
# disable subdivision for thumbnail rendering if needed
if thumbnail_disable_subdivision:
disable_modifier(allobs, "SUBSURF")
# replace material if we need to render wireframe thumbnail
if data.get("thumbnail_render_type") == "WIREFRAME":
replace_materials(allobs, "bkit wireframe")
bpy.data.materials["bkit background"].node_tree.nodes["Value"].outputs[
"Value"
].default_value = data["thumbnail_background_lightness"]
s.cycles.samples = data["thumbnail_samples"]
scene.cycles.samples = data["thumbnail_samples"]
bpy.context.view_layer.cycles.use_denoising = data["thumbnail_denoising"]
bpy.context.view_layer.update()
@@ -297,14 +381,17 @@ if __name__ == "__main__":
)
sys.exit(0)
# get sub type if we are not generating for main beauty thumbnail
filetype = "thumbnail"
if data.get("thumbnail_upload_type"):
filetype = data["thumbnail_upload_type"].lower()
bg_blender.progress("uploading thumbnail")
fpath = data["thumbnail_path"] + ".jpg"
ok = client_lib.complete_upload_file_blocking(
api_key=BLENDERKIT_EXPORT_API_KEY,
asset_id=data["asset_data"]["id"],
filepath=fpath,
filetype=f"thumbnail",
filetype=filetype,
fileindex=0,
)
if not ok: