blender-addons/blenderkit/autothumb.py
Vilem Duha ae7be84e2d BlenderKit: fix an error when trying to assign material to unsupported object type
-do same warning for drag-drop although that can be now further improved since it seems ray cast to other object types works already)
-remove some old prints
2021-07-26 08:11:52 +02:00

662 lines
25 KiB
Python

# ##### BEGIN GPL LICENSE BLOCK #####
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software Foundation,
# Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
#
# ##### END GPL LICENSE BLOCK #####
from blenderkit import paths, utils, bg_blender, ui_panels, icons, tasks_queue, download
import tempfile, os, subprocess, json, sys
import bpy
from bpy.props import (
FloatProperty,
IntProperty,
EnumProperty,
BoolProperty,
StringProperty,
)
BLENDERKIT_EXPORT_DATA_FILE = "data.json"
thumbnail_resolutions = (
('256', '256', ''),
('512', '512', ''),
('1024', '1024 - minimum for public', ''),
('2048', '2048', ''),
)
thumbnail_angles = (
('DEFAULT', 'default', ''),
('FRONT', 'front', ''),
('SIDE', 'side', ''),
('TOP', 'top', ''),
)
thumbnail_snap = (
('GROUND', 'ground', ''),
('WALL', 'wall', ''),
('CEILING', 'ceiling', ''),
('FLOAT', 'floating', ''),
)
def get_texture_ui(tpath, iname):
tex = bpy.data.textures.get(iname)
if tpath.startswith('//'):
tpath = bpy.path.abspath(tpath)
if not tex or not tex.image or not tex.image.filepath == tpath:
tasks_queue.add_task((utils.get_hidden_image, (tpath, iname)), only_last=True)
tasks_queue.add_task((utils.get_hidden_texture, (iname,)), only_last=True)
return None
return tex
def check_thumbnail(props, imgpath):
img = utils.get_hidden_image(imgpath, 'upload_preview', force_reload=True)
# print(' check thumbnail ', img)
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 = ''
tex = utils.get_hidden_texture(img.name)
# pcoll = icons.icon_collections["previews"]
# pcoll.load(img.name, img.filepath, 'IMAGE')
return img
else:
props.has_thumbnail = False
output = ''
if img is None or img.size[0] == 0 or img.filepath.find('thumbnail_notready.jpg') > -1:
output += 'No thumbnail or wrong file path\n'
else:
pass;
# this is causing problems on some platforms, don't know why..
# if img.size[0] != img.size[1]:
# output += 'image not a square\n'
# if img.size[0] < 512:
# 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
def update_upload_model_preview(self, context):
ob = utils.get_active_model()
if ob is not None:
props = ob.blenderkit
imgpath = props.thumbnail
img = check_thumbnail(props, imgpath)
def update_upload_scene_preview(self, context):
s = bpy.context.scene
props = s.blenderkit
imgpath = props.thumbnail
check_thumbnail(props, imgpath)
def update_upload_material_preview(self, context):
if hasattr(bpy.context, 'active_object') \
and bpy.context.view_layer.objects.active is not None \
and bpy.context.active_object.active_material is not None:
mat = bpy.context.active_object.active_material
props = mat.blenderkit
imgpath = props.thumbnail
check_thumbnail(props, imgpath)
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)
check_thumbnail(props, imgpath)
def start_thumbnailer(self=None, json_args=None, props=None, wait=False, add_bg_process=True):
# Prepare to save the file
binary_path = bpy.app.binary_path
script_path = os.path.dirname(os.path.realpath(__file__))
ext = '.blend'
tfpath = paths.get_thumbnailer_filepath()
datafile = os.path.join(json_args['tempdir'], BLENDERKIT_EXPORT_DATA_FILE)
try:
with open(datafile, 'w', encoding='utf-8') as s:
json.dump(json_args, s, ensure_ascii=False, indent=4)
proc = subprocess.Popen([
binary_path,
"--background",
"-noaudio",
tfpath,
"--python", os.path.join(script_path, "autothumb_model_bg.py"),
"--", datafile,
], bufsize=1, stdout=subprocess.PIPE, stdin=subprocess.PIPE, creationflags=utils.get_process_flags())
eval_path_computing = "bpy.data.objects['%s'].blenderkit.is_generating_thumbnail" % json_args['asset_name']
eval_path_state = "bpy.data.objects['%s'].blenderkit.thumbnail_generating_state" % json_args['asset_name']
eval_path = "bpy.data.objects['%s']" % json_args['asset_name']
bg_blender.add_bg_process(name = f"{json_args['asset_name']} thumbnailer" ,eval_path_computing=eval_path_computing, eval_path_state=eval_path_state,
eval_path=eval_path, process_type='THUMBNAILER', process=proc)
except Exception as e:
self.report({'WARNING'}, "Error while exporting file: %s" % str(e))
return {'FINISHED'}
def start_material_thumbnailer(self=None, json_args=None, props=None, wait=False, add_bg_process=True):
'''
Parameters
----------
self
json_args - all arguments:
props - blenderkit upload props with thumbnail settings, to communicate back, if not present, not used.
wait - wait for the rendering to finish
Returns
-------
'''
if props:
props.is_generating_thumbnail = True
props.thumbnail_generating_state = 'starting blender instance'
binary_path = bpy.app.binary_path
script_path = os.path.dirname(os.path.realpath(__file__))
tfpath = paths.get_material_thumbnailer_filepath()
datafile = os.path.join(json_args['tempdir'], BLENDERKIT_EXPORT_DATA_FILE)
try:
with open(datafile, 'w', encoding='utf-8') as s:
json.dump(json_args, s, ensure_ascii=False, indent=4)
proc = subprocess.Popen([
binary_path,
"--background",
"-noaudio",
tfpath,
"--python", os.path.join(script_path, "autothumb_material_bg.py"),
"--", datafile,
], bufsize=1, stdout=subprocess.PIPE, stdin=subprocess.PIPE, creationflags=utils.get_process_flags())
eval_path_computing = "bpy.data.materials['%s'].blenderkit.is_generating_thumbnail" % json_args['asset_name']
eval_path_state = "bpy.data.materials['%s'].blenderkit.thumbnail_generating_state" % json_args['asset_name']
eval_path = "bpy.data.materials['%s']" % json_args['asset_name']
bg_blender.add_bg_process(name=f"{json_args['asset_name']} thumbnailer", eval_path_computing=eval_path_computing,
eval_path_state=eval_path_state,
eval_path=eval_path, process_type='THUMBNAILER', process=proc)
if props:
props.thumbnail_generating_state = 'Saving .blend file'
if wait:
while proc.poll() is None:
stdout_data, stderr_data = proc.communicate()
print(stdout_data)
except Exception as e:
if self:
self.report({'WARNING'}, "Error while packing file: %s" % str(e))
else:
print(e)
return {'FINISHED'}
class GenerateThumbnailOperator(bpy.types.Operator):
"""Generate Cycles thumbnail for model assets"""
bl_idname = "object.blenderkit_generate_thumbnail"
bl_label = "BlenderKit 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):
ob = bpy.context.active_object
while ob.parent is not None:
ob = ob.parent
props = ob.blenderkit
layout = self.layout
layout.label(text='thumbnailer settings')
layout.prop(props, 'thumbnail_background_lightness')
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['blenderkit'].preferences
layout.prop(preferences, "thumbnail_use_gpu")
def execute(self, context):
asset = utils.get_active_model()
asset.blenderkit.is_generating_thumbnail = True
asset.blenderkit.thumbnail_generating_state = 'starting blender instance'
tempdir = tempfile.mkdtemp()
ext = '.blend'
filepath = os.path.join(tempdir, "thumbnailer_blenderkit" + ext)
path_can_be_relative = True
file_dir = os.path.dirname(bpy.data.filepath)
if file_dir == '':
file_dir = tempdir
path_can_be_relative = False
an_slug = paths.slugify(asset.name)
thumb_path = os.path.join(file_dir, an_slug)
if path_can_be_relative:
rel_thumb_path = os.path.join('//', an_slug)
else:
rel_thumb_path = thumb_path
i = 0
while os.path.isfile(thumb_path + '.jpg'):
thumb_path = os.path.join(file_dir, an_slug + '_' + str(i).zfill(4))
rel_thumb_path = os.path.join('//', an_slug + '_' + str(i).zfill(4))
i += 1
bkit = asset.blenderkit
bkit.thumbnail = rel_thumb_path + '.jpg'
bkit.thumbnail_generating_state = 'Saving .blend file'
# 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)
args_dict = {
"type": "material",
"asset_name": asset.name,
"filepath": filepath,
"thumbnail_path": thumb_path,
"tempdir": tempdir,
}
thumbnail_args = {
"type": "model",
"models": str(obnames),
"thumbnail_angle": bkit.thumbnail_angle,
"thumbnail_snap_to": bkit.thumbnail_snap_to,
"thumbnail_background_lightness": bkit.thumbnail_background_lightness,
"thumbnail_resolution": bkit.thumbnail_resolution,
"thumbnail_samples": bkit.thumbnail_samples,
"thumbnail_denoising": bkit.thumbnail_denoising,
}
args_dict.update(thumbnail_args)
start_thumbnailer(self,
json_args=args_dict,
props=asset.blenderkit, wait=False)
return {'FINISHED'}
def invoke(self, context, event):
wm = context.window_manager
# if bpy.data.filepath == '':
# ui_panels.ui_message(
# title="Can't render thumbnail",
# message="please save your file first")
#
# return {'FINISHED'}
return wm.invoke_props_dialog(self)
class ReGenerateThumbnailOperator(bpy.types.Operator):
"""
Generate default thumbnail with Cycles renderer and upload it.
Works also for assets from search results, without being downloaded before.
"""
bl_idname = "object.blenderkit_regenerate_thumbnail"
bl_label = "BlenderKit Thumbnail Re-generate"
bl_options = {'REGISTER', 'INTERNAL'}
asset_index: IntProperty(name="Asset Index", description='asset index in search results', default=-1)
thumbnail_background_lightness: FloatProperty(name="Thumbnail Background Lightness",
description="set to make your material stand out", default=1.0,
min=0.01, max=10)
thumbnail_angle: EnumProperty(
name='Thumbnail Angle',
items=thumbnail_angles,
default='DEFAULT',
description='thumbnailer angle',
)
thumbnail_snap_to: EnumProperty(
name='Model Snaps To:',
items=thumbnail_snap,
default='GROUND',
description='typical placing of the interior. Leave on ground for most objects that respect gravity :)',
)
thumbnail_resolution: EnumProperty(
name="Resolution",
items=thumbnail_resolutions,
description="Thumbnail resolution",
default="1024",
)
thumbnail_samples: IntProperty(name="Cycles Samples",
description="cycles samples setting", default=100,
min=5, max=5000)
thumbnail_denoising: BoolProperty(name="Use Denoising",
description="Use denoising", default=True)
@classmethod
def poll(cls, context):
return True # bpy.context.view_layer.objects.active is not None
def draw(self, context):
props = self
layout = self.layout
# layout.label('This will re-generate thumbnail and directly upload it to server. You should see your updated thumbnail online depending ')
layout.label(text='thumbnailer settings')
layout.prop(props, 'thumbnail_background_lightness')
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['blenderkit'].preferences
layout.prop(preferences, "thumbnail_use_gpu")
def execute(self, context):
if not self.asset_index > -1:
return {'CANCELLED'}
# either get the data from search results
sr = bpy.context.window_manager['search results']
asset_data = sr[self.asset_index].to_dict()
tempdir = tempfile.mkdtemp()
an_slug = paths.slugify(asset_data['name'])
thumb_path = os.path.join(tempdir, an_slug)
args_dict = {
"type": "material",
"asset_name": asset_data['name'],
"asset_data": asset_data,
# "filepath": filepath,
"thumbnail_path": thumb_path,
"tempdir": tempdir,
"do_download": True,
"upload_after_render": True,
}
thumbnail_args = {
"type": "model",
"thumbnail_angle": self.thumbnail_angle,
"thumbnail_snap_to": self.thumbnail_snap_to,
"thumbnail_background_lightness": self.thumbnail_background_lightness,
"thumbnail_resolution": self.thumbnail_resolution,
"thumbnail_samples": self.thumbnail_samples,
"thumbnail_denoising": self.thumbnail_denoising,
}
args_dict.update(thumbnail_args)
start_thumbnailer(self,
json_args=args_dict,
wait=False)
return {'FINISHED'}
def invoke(self, context, event):
wm = context.window_manager
# if bpy.data.filepath == '':
# ui_panels.ui_message(
# title="Can't render thumbnail",
# message="please save your file first")
#
# return {'FINISHED'}
return wm.invoke_props_dialog(self)
class GenerateMaterialThumbnailOperator(bpy.types.Operator):
"""Generate default thumbnail with Cycles renderer."""
bl_idname = "object.blenderkit_generate_material_thumbnail"
bl_label = "BlenderKit Material Thumbnail Generator"
bl_options = {'REGISTER', 'INTERNAL'}
@classmethod
def poll(cls, context):
return bpy.context.view_layer.objects.active is not None
def check(self, context):
return True
def draw(self, context):
layout = self.layout
props = bpy.context.active_object.active_material.blenderkit
layout.prop(props, 'thumbnail_generator_type')
layout.prop(props, 'thumbnail_scale')
layout.prop(props, 'thumbnail_background')
if props.thumbnail_background:
layout.prop(props, 'thumbnail_background_lightness')
layout.prop(props, 'thumbnail_resolution')
layout.prop(props, 'thumbnail_samples')
layout.prop(props, 'thumbnail_denoising')
layout.prop(props, 'adaptive_subdivision')
preferences = bpy.context.preferences.addons['blenderkit'].preferences
layout.prop(preferences, "thumbnail_use_gpu")
def execute(self, context):
asset = bpy.context.active_object.active_material
tempdir = tempfile.mkdtemp()
filepath = os.path.join(tempdir, "material_thumbnailer_cycles.blend")
# 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)
thumb_dir = os.path.dirname(bpy.data.filepath)
thumb_path = os.path.join(thumb_dir, asset.name)
rel_thumb_path = os.path.join('//', asset.name)
# auto increase number of the generated thumbnail.
i = 0
while os.path.isfile(thumb_path + '.png'):
thumb_path = os.path.join(thumb_dir, asset.name + '_' + str(i).zfill(4))
rel_thumb_path = os.path.join('//', asset.name + '_' + str(i).zfill(4))
i += 1
asset.blenderkit.thumbnail = rel_thumb_path + '.png'
bkit = asset.blenderkit
args_dict = {
"type": "material",
"asset_name": asset.name,
"filepath": filepath,
"thumbnail_path": thumb_path,
"tempdir": tempdir,
}
thumbnail_args = {
"thumbnail_type": bkit.thumbnail_generator_type,
"thumbnail_scale": bkit.thumbnail_scale,
"thumbnail_background": bkit.thumbnail_background,
"thumbnail_background_lightness": bkit.thumbnail_background_lightness,
"thumbnail_resolution": bkit.thumbnail_resolution,
"thumbnail_samples": bkit.thumbnail_samples,
"thumbnail_denoising": bkit.thumbnail_denoising,
"adaptive_subdivision": bkit.adaptive_subdivision,
"texture_size_meters": bkit.texture_size_meters,
}
args_dict.update(thumbnail_args)
start_material_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)
class ReGenerateMaterialThumbnailOperator(bpy.types.Operator):
"""
Generate default thumbnail with Cycles renderer and upload it.
Works also for assets from search results, without being downloaded before
"""
bl_idname = "object.blenderkit_regenerate_material_thumbnail"
bl_label = "BlenderKit Material Thumbnail Re-Generator"
bl_options = {'REGISTER', 'INTERNAL'}
asset_index: IntProperty(name="Asset Index", description='asset index in search results', default=-1)
thumbnail_scale: FloatProperty(name="Thumbnail Object Size",
description="Size of material preview object in meters."
"Change for materials that look better at sizes different than 1m",
default=1, min=0.00001, max=10)
thumbnail_background: BoolProperty(name="Thumbnail Background (for Glass only)",
description="For refractive materials, you might need a background.\n"
"Don't use for other types of materials.\n"
"Transparent background is preferred",
default=False)
thumbnail_background_lightness: FloatProperty(name="Thumbnail Background Lightness",
description="Set to make your material stand out with enough contrast",
default=.9,
min=0.00001, max=1)
thumbnail_samples: IntProperty(name="Cycles Samples",
description="Cycles samples", default=100,
min=5, max=5000)
thumbnail_denoising: BoolProperty(name="Use Denoising",
description="Use denoising", default=True)
adaptive_subdivision: BoolProperty(name="Adaptive Subdivide",
description="Use adaptive displacement subdivision", default=False)
thumbnail_resolution: EnumProperty(
name="Resolution",
items=thumbnail_resolutions,
description="Thumbnail resolution",
default="1024",
)
thumbnail_generator_type: EnumProperty(
name="Thumbnail Style",
items=(
('BALL', 'Ball', ""),
('BALL_COMPLEX', 'Ball complex', 'Complex ball to highlight edgewear or material thickness'),
('FLUID', 'Fluid', 'Fluid'),
('CLOTH', 'Cloth', 'Cloth'),
('HAIR', 'Hair', 'Hair ')
),
description="Style of asset",
default="BALL",
)
@classmethod
def poll(cls, context):
return True # bpy.context.view_layer.objects.active is not None
def check(self, context):
return True
def draw(self, context):
layout = self.layout
props = self
layout.prop(props, 'thumbnail_generator_type')
layout.prop(props, 'thumbnail_scale')
layout.prop(props, 'thumbnail_background')
if props.thumbnail_background:
layout.prop(props, 'thumbnail_background_lightness')
layout.prop(props, 'thumbnail_resolution')
layout.prop(props, 'thumbnail_samples')
layout.prop(props, 'thumbnail_denoising')
layout.prop(props, 'adaptive_subdivision')
preferences = bpy.context.preferences.addons['blenderkit'].preferences
layout.prop(preferences, "thumbnail_use_gpu")
def execute(self, context):
if not self.asset_index > -1:
return {'CANCELLED'}
# either get the data from search results
sr = bpy.context.window_manager['search results']
asset_data = sr[self.asset_index].to_dict()
tempdir = tempfile.mkdtemp()
thumb_path = os.path.join(tempdir, asset_data['name'])
args_dict = {
"type": "material",
"asset_name": asset_data['name'],
"asset_data": asset_data,
"thumbnail_path": thumb_path,
"tempdir": tempdir,
"do_download": True,
"upload_after_render": True,
}
thumbnail_args = {
"thumbnail_type": self.thumbnail_generator_type,
"thumbnail_scale": self.thumbnail_scale,
"thumbnail_background": self.thumbnail_background,
"thumbnail_background_lightness": self.thumbnail_background_lightness,
"thumbnail_resolution": self.thumbnail_resolution,
"thumbnail_samples": self.thumbnail_samples,
"thumbnail_denoising": self.thumbnail_denoising,
"adaptive_subdivision": self.adaptive_subdivision,
"texture_size_meters": utils.get_param(asset_data, 'textureSizeMeters', 1.0),
}
args_dict.update(thumbnail_args)
start_material_thumbnailer(self,
json_args=args_dict,
wait=False)
return {'FINISHED'}
def invoke(self, context, event):
# scene = bpy.context.scene
# ui_props = scene.blenderkitUI
# if ui_props.active_index > -1:
# sr = bpy.context.window_manager['search results']
# self.asset_data = dict(sr[ui_props.active_index])
# else:
#
# active_asset = utils.get_active_asset_by_type(asset_type = self.asset_type)
# self.asset_data = active_asset.get('asset_data')
wm = context.window_manager
return wm.invoke_props_dialog(self)
def register_thumbnailer():
bpy.utils.register_class(GenerateThumbnailOperator)
bpy.utils.register_class(ReGenerateThumbnailOperator)
bpy.utils.register_class(GenerateMaterialThumbnailOperator)
bpy.utils.register_class(ReGenerateMaterialThumbnailOperator)
def unregister_thumbnailer():
bpy.utils.unregister_class(GenerateThumbnailOperator)
bpy.utils.unregister_class(ReGenerateThumbnailOperator)
bpy.utils.unregister_class(GenerateMaterialThumbnailOperator)
bpy.utils.unregister_class(ReGenerateMaterialThumbnailOperator)