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
+218 -17
View File
@@ -32,6 +32,7 @@ from bpy.props import (
FloatProperty,
IntProperty,
FloatVectorProperty,
StringProperty,
)
from . import bg_blender, global_vars, paths, tasks_queue, utils, upload, search
@@ -81,16 +82,31 @@ def get_texture_ui(tpath, iname):
return tex
def check_thumbnail(props, imgpath):
def check_thumbnail(
props,
imgpath,
*,
texture_name="upload_preview",
flag_attr="has_thumbnail",
state_attr="thumbnail_generating_state",
):
"""Reload a thumbnail preview and update status attributes."""
def _set_prop(attr_name, value):
if attr_name and hasattr(props, attr_name):
setattr(props, attr_name, value)
# TODO implement check if the file exists, if size is correct etc. needs some care
if imgpath == "":
props.has_thumbnail = False
_set_prop(flag_attr, False)
return None
img = utils.get_hidden_image(imgpath, "upload_preview", force_reload=True)
img = utils.get_hidden_image(imgpath, texture_name, force_reload=True)
if img is not None: # and img.size[0] == img.size[1] and img.size[0] >= 512 and (
# img.file_format == 'JPEG' or img.file_format == 'PNG'):
props.has_thumbnail = True
props.thumbnail_generating_state = ""
_set_prop(flag_attr, True)
if hasattr(props, "THUMBNAIL_GENERATING_STATE"):
props.THUMBNAIL_GENERATING_STATE = ""
_set_prop(state_attr, "")
utils.get_hidden_texture(img.name)
# pcoll = icons.icon_collections["previews"]
@@ -98,7 +114,7 @@ def check_thumbnail(props, imgpath):
return img
else:
props.has_thumbnail = False
_set_prop(flag_attr, False)
output = ""
if (
img is None
@@ -115,7 +131,7 @@ def check_thumbnail(props, imgpath):
# output += 'image too small, should be at least 512x512\n'
# if img.file_format != 'JPEG' or img.file_format != 'PNG':
# output += 'image has to be a jpeg or png'
props.thumbnail_generating_state = output
_set_prop(state_attr, output)
def update_upload_model_preview(self, context):
@@ -126,6 +142,20 @@ def update_upload_model_preview(self, context):
check_thumbnail(props, imgpath)
def update_wire_thumbnail_preview(self, context):
ob = utils.get_active_model()
if ob is not None:
props = ob.blenderkit
imgpath = props.wire_thumbnail
check_thumbnail(
props,
imgpath,
texture_name=".upload_preview_wire",
flag_attr=None,
state_attr="wire_thumbnail_generating_state",
)
def update_upload_scene_preview(self, context):
s = bpy.context.scene
props = s.blenderkit
@@ -149,7 +179,7 @@ def update_upload_brush_preview(self, context):
brush = utils.get_active_brush()
if brush is not None:
props = brush.blenderkit
imgpath = bpy.path.abspath(brush.icon_filepath)
imgpath = props.thumbnail
check_thumbnail(props, imgpath)
@@ -182,9 +212,26 @@ def start_model_thumbnailer(
):
"""Start Blender in background and render the thumbnail."""
SCRIPT_NAME = "autothumb_model_bg.py"
thumbnail_upload_type = (
json_args.get("thumbnail_upload_type") if json_args else None
)
is_wire_upload = thumbnail_upload_type == "wire_thumbnail"
computing_attr = (
"is_generating_wire_thumbnail" if is_wire_upload else "is_generating_thumbnail"
)
state_attr = (
"wire_thumbnail_generating_state"
if is_wire_upload
else "thumbnail_generating_state"
)
def _set_prop(attr_name, value):
if props and hasattr(props, attr_name):
setattr(props, attr_name, value)
if props:
props.is_generating_thumbnail = True
props.thumbnail_generating_state = "Saving .blend file"
_set_prop(computing_attr, True)
_set_prop(state_attr, "Saving .blend file")
datafile = os.path.join(json_args["tempdir"], BLENDERKIT_EXPORT_DATA_FILE)
user_preferences = bpy.context.preferences.addons[__package__].preferences
@@ -194,6 +241,10 @@ def start_model_thumbnailer(
"cycles"
].preferences.compute_device_type
json_args["thumbnail_disable_subdivision"] = (
user_preferences.thumbnail_disable_subdivision
)
try:
with open(datafile, "w", encoding="utf-8") as s:
json.dump(json_args, s, ensure_ascii=False, indent=4)
@@ -228,9 +279,10 @@ def start_model_thumbnailer(
env=env,
)
bk_logger.info("Started Blender executing %s on file %s", SCRIPT_NAME, datafile)
eval_path_computing = f"bpy.data.objects['{json_args['asset_name']}'].blenderkit.is_generating_thumbnail"
eval_path_state = f"bpy.data.objects['{json_args['asset_name']}'].blenderkit.thumbnail_generating_state"
eval_path = f"bpy.data.objects['{json_args['asset_name']}']"
eval_path_base = f"bpy.data.objects['{json_args['asset_name']}']"
eval_path = eval_path_base
eval_path_computing = f"{eval_path_base}.blenderkit.{computing_attr}"
eval_path_state = f"{eval_path_base}.blenderkit.{state_attr}"
name = f"{json_args['asset_name']} thumbnailer"
bg_blender.add_bg_process(
name=name,
@@ -241,7 +293,7 @@ def start_model_thumbnailer(
process=proc,
)
if props:
props.thumbnail_generating_state = "Started Blender instance"
_set_prop(state_attr, "Started Blender instance")
if wait:
while proc.poll() is None:
@@ -449,6 +501,132 @@ class GenerateThumbnailOperator(bpy.types.Operator):
return wm.invoke_props_dialog(self, width=400)
class GenerateWireframeThumbnailOperator(bpy.types.Operator):
"""Generate Cycles wireframe thumbnail for model assets"""
bl_idname = "object.blenderkit_generate_wireframe_thumbnail"
bl_label = "BlenderKit Wireframe Thumbnail Generator"
bl_options = {"REGISTER", "INTERNAL"}
@classmethod
def poll(cls, context):
return bpy.context.view_layer.objects.active is not None
def draw(self, context):
ui_props = bpy.context.window_manager.blenderkitUI
asset_type = ui_props.asset_type
ob = utils.get_active_model()
props = ob.blenderkit
layout = self.layout
layout.label(text="thumbnailer settings")
layout.prop(props, "thumbnail_background_lightness")
# for printable models
if asset_type == "PRINTABLE":
layout.prop(props, "thumbnail_material_color")
layout.prop(props, "thumbnail_angle")
layout.prop(props, "thumbnail_snap_to")
layout.prop(props, "thumbnail_samples")
layout.prop(props, "thumbnail_resolution")
layout.prop(props, "thumbnail_denoising")
preferences = bpy.context.preferences.addons[__package__].preferences
layout.prop(preferences, "thumbnail_use_gpu")
# TODO: wireframe
# layout.prop(preferences, "thumbnail_disable_subdivision")
def execute(self, context):
if not upload.wire_thumbnail_upload_enabled():
self.report({"ERROR"}, "Wireframe thumbnail uploads are disabled.")
return {"CANCELLED"}
asset = utils.get_active_model()
asset.blenderkit.is_generating_wire_thumbnail = True
asset.blenderkit.wire_thumbnail_generating_state = "starting blender instance"
tempdir = tempfile.mkdtemp()
ext = ".blend"
filepath = os.path.join(tempdir, "thumbnailer_wf_blenderkit" + ext)
path_can_be_relative = True
thumb_dir = os.path.dirname(bpy.data.filepath)
if thumb_dir == "":
thumb_dir = tempdir
path_can_be_relative = False
an_slug = paths.slugify(asset.name)
# add suffix to distinguish from regular thumbnail
an_slug += "_wf"
thumb_path = os.path.join(thumb_dir, an_slug)
if path_can_be_relative:
rel_thumb_path = f"//{an_slug}"
else:
rel_thumb_path = thumb_path
i = 0
while os.path.isfile(thumb_path + ".jpg"):
thumb_name = f"{an_slug}_{str(i).zfill(4)}"
thumb_path = os.path.join(thumb_dir, thumb_name)
if path_can_be_relative:
rel_thumb_path = f"//{thumb_name}"
i += 1
bkit = asset.blenderkit
bkit.wire_thumbnail = rel_thumb_path + ".jpg"
bkit.wire_thumbnail_generating_state = "Saving .blend file"
# if this isn't here, blender crashes.
if bpy.app.version >= (3, 0, 0):
bpy.context.preferences.filepaths.file_preview_type = "NONE"
# save a copy of actual scene but don't interfere with the users models
bpy.ops.wm.save_as_mainfile(filepath=filepath, compress=False, copy=True)
# get all included objects
obs = utils.get_hierarchy(asset)
obnames = []
for ob in obs:
obnames.append(ob.name)
# asset type can be model or printable
ui_props = bpy.context.window_manager.blenderkitUI
asset_type = ui_props.asset_type
args_dict = {
"type": asset_type,
"asset_name": asset.name,
"filepath": filepath,
"thumbnail_path": thumb_path,
"tempdir": tempdir,
"thumbnail_render_type": "WIREFRAME",
"thumbnail_upload_type": "wire_thumbnail",
}
thumbnail_args = {
"type": asset_type,
"models": str(obnames),
"thumbnail_angle": bkit.thumbnail_angle,
"thumbnail_snap_to": bkit.thumbnail_snap_to,
"thumbnail_background_lightness": 0.2,
"thumbnail_material_color": (
bkit.thumbnail_material_color[0],
bkit.thumbnail_material_color[1],
bkit.thumbnail_material_color[2],
),
"thumbnail_resolution": bkit.thumbnail_resolution,
"thumbnail_samples": bkit.thumbnail_samples,
"thumbnail_denoising": bkit.thumbnail_denoising,
}
args_dict.update(thumbnail_args)
start_model_thumbnailer(
self, json_args=args_dict, props=asset.blenderkit, wait=False
)
return {"FINISHED"}
def invoke(self, context, event):
wm = context.window_manager
return wm.invoke_props_dialog(self, width=400)
class ReGenerateThumbnailOperator(bpy.types.Operator):
"""
Generate default thumbnail with Cycles renderer and upload it.
@@ -464,6 +642,12 @@ class ReGenerateThumbnailOperator(bpy.types.Operator):
name="Asset Index", description="asset index in search results", default=-1
)
asset_type: StringProperty( # type: ignore[valid-type]
name="Asset Type",
description="Asset type used for thumbnail generation",
default="",
)
render_locally: BoolProperty( # type: ignore[valid-type]
name="Render Locally",
description="Render thumbnail locally instead of using server-side rendering",
@@ -529,7 +713,12 @@ class ReGenerateThumbnailOperator(bpy.types.Operator):
layout.label(text="thumbnailer settings")
layout.prop(props, "thumbnail_background_lightness")
# for printable models
if self.asset_type == "PRINTABLE":
asset_type = (
getattr(self, "asset_type", "")
or getattr(self, "asset_data", {}).get("assetType", "")
or bpy.context.window_manager.blenderkitUI.asset_type
).upper()
if asset_type == "PRINTABLE":
layout.prop(props, "thumbnail_material_color")
layout.prop(props, "thumbnail_angle")
layout.prop(props, "thumbnail_snap_to")
@@ -545,6 +734,11 @@ class ReGenerateThumbnailOperator(bpy.types.Operator):
preferences = bpy.context.preferences.addons[__package__].preferences
# Ensure asset_type is set when execution is triggered directly.
ui_props = bpy.context.window_manager.blenderkitUI
if not getattr(self, "asset_type", ""):
self.asset_type = ui_props.asset_type
if not self.render_locally:
# Use server-side thumbnail regeneration
success = upload.mark_for_thumbnail(
@@ -575,8 +769,7 @@ class ReGenerateThumbnailOperator(bpy.types.Operator):
thumb_path = os.path.join(tempdir, an_slug)
# asset type can be model or printable
ui_props = bpy.context.window_manager.blenderkitUI
self.asset_type = ui_props.asset_type
self.asset_type = self.asset_type or ui_props.asset_type
args_dict = {
"type": self.asset_type,
"asset_name": self.asset_data["name"],
@@ -607,6 +800,12 @@ class ReGenerateThumbnailOperator(bpy.types.Operator):
history_step = search.get_active_history_step()
sr = history_step.get("search_results", [])
self.asset_data = sr[self.asset_index]
# Prepopulate asset_type so draw() can safely access it.
self.asset_type = (
self.asset_data.get("assetType", "")
if isinstance(self.asset_data, dict)
else ""
).upper() or bpy.context.window_manager.blenderkitUI.asset_type
return wm.invoke_props_dialog(self, width=400)
@@ -891,6 +1090,7 @@ class ReGenerateMaterialThumbnailOperator(bpy.types.Operator):
def register_thumbnailer():
bpy.utils.register_class(GenerateThumbnailOperator)
bpy.utils.register_class(ReGenerateThumbnailOperator)
bpy.utils.register_class(GenerateWireframeThumbnailOperator)
bpy.utils.register_class(GenerateMaterialThumbnailOperator)
bpy.utils.register_class(ReGenerateMaterialThumbnailOperator)
@@ -898,5 +1098,6 @@ def register_thumbnailer():
def unregister_thumbnailer():
bpy.utils.unregister_class(GenerateThumbnailOperator)
bpy.utils.unregister_class(ReGenerateThumbnailOperator)
bpy.utils.unregister_class(GenerateWireframeThumbnailOperator)
bpy.utils.unregister_class(GenerateMaterialThumbnailOperator)
bpy.utils.unregister_class(ReGenerateMaterialThumbnailOperator)