diff options
author | Vilém Duha <vilda.novak@gmail.com> | 2021-04-26 18:48:18 +0300 |
---|---|---|
committer | Vilém Duha <vilda.novak@gmail.com> | 2021-04-26 18:48:18 +0300 |
commit | e7acb93c89fc00a5dfffcff90ab3956a0eaf34f0 (patch) | |
tree | 28853a2e858e7f6ae059fcbc52887e297dedc2db | |
parent | 4cb833e84acfd2be5fa08ce75118ce9cb60643b8 (diff) |
BlenderKit:fixes
- improve material and model thumbnailers
-Now it's possible to re-render assets directly from right-click menu.
-fix appending of assets with wrong name
-several fixes for experimental asset bar
- small fixes in bg_blender for background operations
- material thumbnailer background fixed
- draw upload thumbnail in upload UI
-rw-r--r-- | blenderkit/__init__.py | 50 | ||||
-rw-r--r-- | blenderkit/append_link.py | 19 | ||||
-rw-r--r-- | blenderkit/asset_bar_op.py | 68 | ||||
-rw-r--r-- | blenderkit/autothumb.py | 567 | ||||
-rw-r--r-- | blenderkit/autothumb_material_bg.py | 47 | ||||
-rw-r--r-- | blenderkit/autothumb_model_bg.py | 69 | ||||
-rw-r--r-- | blenderkit/bg_blender.py | 24 | ||||
-rw-r--r-- | blenderkit/blendfiles/material_thumbnailer_cycles.blend | bin | 2975884 -> 2976697 bytes | |||
-rw-r--r-- | blenderkit/categories.py | 6 | ||||
-rw-r--r-- | blenderkit/download.py | 15 | ||||
-rw-r--r-- | blenderkit/icons.py | 1 | ||||
-rw-r--r-- | blenderkit/resolutions.py | 4 | ||||
-rw-r--r-- | blenderkit/search.py | 65 | ||||
-rw-r--r-- | blenderkit/ui.py | 21 | ||||
-rw-r--r-- | blenderkit/ui_bgl.py | 2 | ||||
-rw-r--r-- | blenderkit/ui_panels.py | 56 | ||||
-rw-r--r-- | blenderkit/upload.py | 6 | ||||
-rw-r--r-- | blenderkit/upload_bg.py | 5 | ||||
-rw-r--r-- | blenderkit/utils.py | 20 |
19 files changed, 789 insertions, 256 deletions
diff --git a/blenderkit/__init__.py b/blenderkit/__init__.py index 15f8c23a..9633ae50 100644 --- a/blenderkit/__init__.py +++ b/blenderkit/__init__.py @@ -227,26 +227,10 @@ mesh_poly_types = ( ('OTHER', 'other', ''), ) -thumbnail_angles = ( - ('DEFAULT', 'default', ''), - ('FRONT', 'front', ''), - ('SIDE', 'side', ''), - ('TOP', 'top', ''), -) -thumbnail_snap = ( - ('GROUND', 'ground', ''), - ('WALL', 'wall', ''), - ('CEILING', 'ceiling', ''), - ('FLOAT', 'floating', ''), -) -thumbnail_resolutions = ( - ('256', '256', ''), - ('512', '512', ''), - ('1024', '1024 - minimum for public', ''), - ('2048', '2048', ''), -) + + def udate_down_up(self, context): @@ -588,8 +572,30 @@ def update_free(self, context): " based on our fair share system. " \ "Part of subscription is sent to artists based on usage by paying users.\n") +# common_upload_props = [ +# { +# 'identifier':'id', +# 'name':"Asset Version Id", +# 'type':'StringProperty', +# 'description':'Unique name of the asset version(hidden)', +# 'default':'' +# } +# { +# 'identifier':'id', +# 'name':"Asset Version Id", +# 'type':'StringProperty', +# 'description':'Unique name of the asset version(hidden)', +# 'default':'' +# } +# ] + + + class BlenderKitCommonUploadProps(object): + # for p in common_upload_props: + # exec(f"{p['identifier']}: {p['type']}(name='{p['name']}',description='{p['description']}',default='{p['default']}')") + id: StringProperty( name="Asset Version Id", description="Unique name of the asset version(hidden)", @@ -888,7 +894,7 @@ class BlenderKitMaterialUploadProps(PropertyGroup, BlenderKitCommonUploadProps): thumbnail_resolution: EnumProperty( name="Resolution", - items=thumbnail_resolutions, + items=autothumb.thumbnail_resolutions, description="Thumbnail resolution", default="1024", ) @@ -1064,21 +1070,21 @@ class BlenderKitModelUploadProps(PropertyGroup, BlenderKitCommonUploadProps): thumbnail_angle: EnumProperty( name='Thumbnail Angle', - items=thumbnail_angles, + items=autothumb.thumbnail_angles, default='DEFAULT', description='thumbnailer angle', ) thumbnail_snap_to: EnumProperty( name='Model Snaps To:', - items=thumbnail_snap, + items=autothumb.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, + items=autothumb.thumbnail_resolutions, description="Thumbnail resolution", default="1024", ) diff --git a/blenderkit/append_link.py b/blenderkit/append_link.py index a8b5c718..28b0d24e 100644 --- a/blenderkit/append_link.py +++ b/blenderkit/append_link.py @@ -55,23 +55,28 @@ def append_material(file_name, matname=None, link=False, fake_user=True): break; #not found yet? probably some name inconsistency then. - # if not found and len(data_from.materials)>0: - # data_to.materials = data_from.materials[0] - # matname = data_from.materials[0] - # print('had to assign') + if not found and len(data_from.materials)>0: + data_to.materials = [data_from.materials[0]] + matname = data_from.materials[0] + print(f"the material wasn't found under the exact name, appended another one: {matname}") # print('in the appended file the name is ', matname) except Exception as e: print(e) print('failed to open the asset file') - # we have to find the new material :( + # we have to find the new material , due to possible name changes + mat = None for m in bpy.data.materials: + print(m.name) if m not in mats_before: mat = m - break + break; + #still not found? + if mat is None: + mat = bpy.data.materials.get(matname) + if fake_user: mat.use_fake_user = True - return mat diff --git a/blenderkit/asset_bar_op.py b/blenderkit/asset_bar_op.py index 41ead99c..acb9cf94 100644 --- a/blenderkit/asset_bar_op.py +++ b/blenderkit/asset_bar_op.py @@ -52,6 +52,11 @@ BL_UI_Widget.get_area_height = get_area_height def asset_bar_modal(self, context, event): + ui_props = bpy.context.scene.blenderkitUI + if ui_props.turn_off: + ui_props.turn_off = False + self.finish() + if self._finished: return {'FINISHED'} @@ -76,7 +81,9 @@ def asset_bar_modal(self, context, event): self.scroll_update() return {'RUNNING_MODAL'} - + if self.check_ui_resized(context): + self.update_ui_size(context) + self.update_layout(context) return {"PASS_THROUGH"} def asset_bar_invoke(self, context, event): @@ -235,6 +242,27 @@ class BlenderKitAssetBarOperator(BL_UI_OT_draw_operator): for w in self.tooltip_widgets: w.visible = True + def check_ui_resized(self,context): + region = context.region + area = context.area + ui_props = bpy.context.scene.blenderkitUI + ui_scale = bpy.context.preferences.view.ui_scale + + reg_multiplier = 1 + if not bpy.context.preferences.system.use_region_overlap: + reg_multiplier = 0 + + for r in area.regions: + if r.type == 'TOOLS': + self.bar_x = r.width * reg_multiplier + self.margin + ui_props.bar_x_offset * ui_scale + elif r.type == 'UI': + self.bar_end = r.width * reg_multiplier + 100 * ui_scale + + bar_width = region.width - self.bar_x - self.bar_end + if bar_width != self.bar_width: + return True + return False + def update_ui_size(self, context): if bpy.app.background or not context.area: @@ -264,16 +292,18 @@ class BlenderKitAssetBarOperator(BL_UI_OT_draw_operator): elif r.type == 'UI': self.bar_end = r.width * reg_multiplier + 100 * ui_scale - self.bar_width = region.width - ui_props.bar_x - ui_props.bar_end + self.bar_width = region.width - self.bar_x - self.bar_end self.wcount = math.floor( (self.bar_width) / (self.button_size)) search_results = bpy.context.window_manager.get('search results') - if search_results is not None and self.wcount > 0: - self.hcount = min(user_preferences.max_assetbar_rows, math.ceil(len(search_results) / self.wcount)) - else: - self.hcount = 1 + # we need to init all possible thumb previews in advance/ + self.hcount = user_preferences.max_assetbar_rows + # if search_results is not None and self.wcount > 0: + # self.hcount = min(user_preferences.max_assetbar_rows, math.ceil(len(search_results) / self.wcount)) + # else: + # self.hcount = 1 self.bar_height = (self.button_size) * self.hcount + 2 * self.assetbar_margin # self.bar_y = region.height - ui_props.bar_y_offset * ui_scale @@ -285,6 +315,9 @@ class BlenderKitAssetBarOperator(BL_UI_OT_draw_operator): self.reports_y = self.bar_y - self.bar_height - 100 self.reports_x = self.bar_x + def update_layout(self, context): + pass; + def __init__(self): super().__init__() @@ -490,7 +523,14 @@ class BlenderKitAssetBarOperator(BL_UI_OT_draw_operator): if img: self.tooltip_image.set_image(img.filepath) self.asset_name.text = asset_data['name'] - self.tooltip_panel.update(widget.x_screen + widget.width, widget.y_screen + widget.height) + + properties_width = 0 + for r in bpy.context.area.regions: + if r.type == 'UI': + properties_width = r.width + tooltip_x = min(widget.x_screen + widget.width, bpy.context.region.width - self.tooltip_panel.width -properties_width) + + self.tooltip_panel.update(tooltip_x, widget.y_screen + widget.height) self.tooltip_panel.layout_widgets() def exit_button(self, widget): @@ -518,8 +558,9 @@ class BlenderKitAssetBarOperator(BL_UI_OT_draw_operator): blenderkit.search.search(get_next=True) def update_images(self): - sr = bpy.context.window_manager['search results'] - + sr = bpy.context.window_manager.get('search results') + if not sr: + return for asset_button in self.asset_buttons: asset_button.asset_index = asset_button.button_index + self.scroll_offset if asset_button.asset_index < len(sr): @@ -580,6 +621,15 @@ class BlenderKitAssetBarOperator(BL_UI_OT_draw_operator): self.scroll_offset -= self.wcount * self.hcount self.scroll_update() + def update_sizes(self): + properties_width = 0 + for r in bpy.context.area.regions: + if r.type == 'UI': + properties_width = r.width + tooltip_x = min(widget.x_screen + widget.width, + bpy.context.region.width - self.tooltip_panel.width - properties_width) + print(widget.x_screen + widget.width, bpy.context.region.width - self.tooltip_panel.width) + def register(): bpy.utils.register_class(BlenderKitAssetBarOperator) diff --git a/blenderkit/autothumb.py b/blenderkit/autothumb.py index 9f47d3c3..26697d74 100644 --- a/blenderkit/autothumb.py +++ b/blenderkit/autothumb.py @@ -16,23 +16,69 @@ # # ##### END GPL LICENSE BLOCK ##### - -from blenderkit import paths, utils, bg_blender, ui_panels +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 = '' - return + + 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 = '' @@ -55,7 +101,7 @@ def update_upload_model_preview(self, context): if ob is not None: props = ob.blenderkit imgpath = props.thumbnail - check_thumbnail(props, imgpath) + img = check_thumbnail(props, imgpath) def update_upload_scene_preview(self, context): @@ -83,55 +129,19 @@ def update_upload_brush_preview(self, context): check_thumbnail(props, imgpath) -def start_thumbnailer(self, context): +def start_thumbnailer(self=None, json_args=None, props=None, wait=False, add_bg_process=True): # Prepare to save the file - mainmodel = utils.get_active_model() - mainmodel.blenderkit.is_generating_thumbnail = True - mainmodel.blenderkit.thumbnail_generating_state = 'starting blender instance' binary_path = bpy.app.binary_path script_path = os.path.dirname(os.path.realpath(__file__)) - basename, ext = os.path.splitext(bpy.data.filepath) - if not basename: - basename = os.path.join(basename, "temp") - if not ext: - ext = ".blend" - asset_name = mainmodel.name - tempdir = tempfile.mkdtemp() - - file_dir = os.path.dirname(bpy.data.filepath) - thumb_path = os.path.join(file_dir, asset_name) - rel_thumb_path = os.path.join('//', asset_name) - - i = 0 - while os.path.isfile(thumb_path + '.jpg'): - thumb_path = os.path.join(file_dir, asset_name + '_' + str(i).zfill(4)) - rel_thumb_path = os.path.join('//', asset_name + '_' + str(i).zfill(4)) - i += 1 - - filepath = os.path.join(tempdir, "thumbnailer_blenderkit" + ext) + + ext = '.blend' + tfpath = paths.get_thumbnailer_filepath() - datafile = os.path.join(tempdir, BLENDERKIT_EXPORT_DATA_FILE) + datafile = os.path.join(json_args['tempdir'], BLENDERKIT_EXPORT_DATA_FILE) try: - # 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) - - obs = utils.get_hierarchy(mainmodel) - obnames = [] - for ob in obs: - obnames.append(ob.name) - with open(datafile, 'w', encoding = 'utf-8') as s: - bkit = mainmodel.blenderkit - json.dump({ - "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, - }, s, ensure_ascii=False, indent=4) + with open(datafile, 'w', encoding='utf-8') as s: + json.dump(json_args, s, ensure_ascii=False, indent=4) proc = subprocess.Popen([ binary_path, @@ -139,72 +149,49 @@ def start_thumbnailer(self, context): "-noaudio", tfpath, "--python", os.path.join(script_path, "autothumb_model_bg.py"), - "--", datafile, filepath, thumb_path, tempdir + "--", 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" % mainmodel.name - eval_path_state = "bpy.data.objects['%s'].blenderkit.thumbnail_generating_state" % mainmodel.name - eval_path = "bpy.data.objects['%s']" % mainmodel.name + 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(eval_path_computing=eval_path_computing, eval_path_state=eval_path_state, eval_path=eval_path, process_type='THUMBNAILER', process=proc) - mainmodel.blenderkit.thumbnail = rel_thumb_path + '.jpg' - mainmodel.blenderkit.thumbnail_generating_state = 'Saving .blend file' except Exception as e: self.report({'WARNING'}, "Error while exporting file: %s" % str(e)) return {'FINISHED'} -def start_material_thumbnailer(self, context, wait=False): - # Prepare to save the file - mat = bpy.context.active_object.active_material - mat.blenderkit.is_generating_thumbnail = True - mat.blenderkit.thumbnail_generating_state = 'starting blender instance' +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__)) - basename, ext = os.path.splitext(bpy.data.filepath) - if not basename: - basename = os.path.join(basename, "temp") - if not ext: - ext = ".blend" - asset_name = mat.name - tempdir = tempfile.mkdtemp() - - file_dir = os.path.dirname(bpy.data.filepath) - - thumb_path = os.path.join(file_dir, asset_name) - rel_thumb_path = os.path.join('//', mat.name) - i = 0 - while os.path.isfile(thumb_path + '.png'): - thumb_path = os.path.join(file_dir, mat.name + '_' + str(i).zfill(4)) - rel_thumb_path = os.path.join('//', mat.name + '_' + str(i).zfill(4)) - i += 1 - - filepath = os.path.join(tempdir, "material_thumbnailer_cycles" + ext) + tfpath = paths.get_material_thumbnailer_filepath() - datafile = os.path.join(tempdir, BLENDERKIT_EXPORT_DATA_FILE) - try: - # 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) + datafile = os.path.join(json_args['tempdir'], BLENDERKIT_EXPORT_DATA_FILE) - with open(datafile, 'w', encoding = 'utf-8') as s: - bkit = mat.blenderkit - json.dump({ - "type": "material", - "material": mat.name, - "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, - }, s, ensure_ascii=False, indent=4) + 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, @@ -212,20 +199,28 @@ def start_material_thumbnailer(self, context, wait=False): "-noaudio", tfpath, "--python", os.path.join(script_path, "autothumb_material_bg.py"), - "--", datafile, filepath, thumb_path, tempdir + "--", 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" % mat.name - eval_path_state = "bpy.data.materials['%s'].blenderkit.thumbnail_generating_state" % mat.name - eval_path = "bpy.data.materials['%s']" % mat.name + 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(eval_path_computing=eval_path_computing, eval_path_state=eval_path_state, + bg_blender.add_bg_process(name=json_args['asset_name'], 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' - mat.blenderkit.thumbnail = rel_thumb_path + '.png' - mat.blenderkit.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: - self.report({'WARNING'}, "Error while packing file: %s" % str(e)) + if self: + self.report({'WARNING'}, "Error while packing file: %s" % str(e)) + else: + print(e) return {'FINISHED'} @@ -256,24 +251,198 @@ class GenerateThumbnailOperator(bpy.types.Operator): layout.prop(preferences, "thumbnail_use_gpu") def execute(self, context): - start_thumbnailer(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") + # 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 Cycles thumbnail for model assets""" + 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): + ob = bpy.context.active_object + while ob.parent is not None: + ob = ob.parent + props = self + 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") - return {'FINISHED'} + 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'} + start_thumbnailer(self, context) + 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_material_thumbnail" + bl_idname = "object.blenderkit_generate_material_thumbnail" bl_label = "BlenderKit Material Thumbnail Generator" bl_options = {'REGISTER', 'INTERNAL'} @@ -300,7 +469,48 @@ class GenerateMaterialThumbnailOperator(bpy.types.Operator): layout.prop(preferences, "thumbnail_use_gpu") def execute(self, context): - start_material_thumbnailer(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'} @@ -309,11 +519,144 @@ class GenerateMaterialThumbnailOperator(bpy.types.Operator): return wm.invoke_props_dialog(self) +class ReGenerateMaterialThumbnailOperator(bpy.types.Operator): + """ + Generate default thumbnail with Cycles renderer. + 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) diff --git a/blenderkit/autothumb_material_bg.py b/blenderkit/autothumb_material_bg.py index d27d7b90..0a0ce5db 100644 --- a/blenderkit/autothumb_material_bg.py +++ b/blenderkit/autothumb_material_bg.py @@ -18,16 +18,14 @@ -from blenderkit import utils, append_link, bg_blender +from blenderkit import utils, append_link, bg_blender, upload_bg, download import sys, json, math import bpy from pathlib import Path -BLENDERKIT_EXPORT_TEMP_DIR = sys.argv[-1] -BLENDERKIT_THUMBNAIL_PATH = sys.argv[-2] -BLENDERKIT_EXPORT_FILE_INPUT = sys.argv[-3] -BLENDERKIT_EXPORT_DATA = sys.argv[-4] + +BLENDERKIT_EXPORT_DATA = sys.argv[-1] def render_thumbnails(): @@ -44,13 +42,26 @@ def unhide_collection(cname): if __name__ == "__main__": try: bg_blender.progress('preparing thumbnail scene') + user_preferences = bpy.context.preferences.addons['blenderkit'].preferences + with open(BLENDERKIT_EXPORT_DATA, 'r',encoding='utf-8') as s: data = json.load(s) # append_material(file_name, matname = None, link = False, fake_user = True) - mat = append_link.append_material(file_name=BLENDERKIT_EXPORT_FILE_INPUT, matname=data["material"], link=True, + if data.get('do_download'): + asset_data = data['asset_data'] + has_url = download.get_download_url(asset_data, download.get_scene_id(), user_preferences.api_key, tcom=None, + resolution='blend') + if not has_url: + bg_blender.progress("couldn't download asset for thumnbail re-rendering") + exit() + # download first, or rather make sure if it's already downloaded + bg_blender.progress('downloading asset') + fpath = download.download_asset_file(asset_data) + data['filepath'] = fpath + + mat = append_link.append_material(file_name=data['filepath'], matname=data["asset_name"], link=True, fake_user=False) - user_preferences = bpy.context.preferences.addons['blenderkit'].preferences s = bpy.context.scene @@ -61,7 +72,6 @@ if __name__ == "__main__": 'CLOTH': 'Cloth', 'HAIR': 'Hair' } - unhide_collection(colmapdict[data["thumbnail_type"]]) if data['thumbnail_background']: unhide_collection('Background') @@ -70,6 +80,9 @@ if __name__ == "__main__": tscale = data["thumbnail_scale"] bpy.context.view_layer.objects['scaler'].scale = (tscale, tscale, tscale) bpy.context.view_layer.update() + print('we have this materialB') + print(mat) + for ob in bpy.context.visible_objects: if ob.name[:15] == 'MaterialPreview': ob.material_slots[0].material = mat @@ -86,6 +99,7 @@ if __name__ == "__main__": if data["thumbnail_type"] in ['BALL', 'BALL_COMPLEX', 'CLOTH']: utils.automap(ob.name, tex_size = ts / tscale, just_scale = True, bg_exception=True) bpy.context.view_layer.update() + print('got to C') s.cycles.volume_step_size = tscale * .1 @@ -113,9 +127,24 @@ if __name__ == "__main__": bpy.context.scene.render.resolution_x = int(data['thumbnail_resolution']) bpy.context.scene.render.resolution_y = int(data['thumbnail_resolution']) - bpy.context.scene.render.filepath = BLENDERKIT_THUMBNAIL_PATH + bpy.context.scene.render.filepath = data['thumbnail_path'] bg_blender.progress('rendering thumbnail') render_thumbnails() + if data.get('upload_after_render') and data.get('asset_data'): + bg_blender.progress('uploading thumbnail') + preferences = bpy.context.preferences.addons['blenderkit'].preferences + + file = { + "type": "thumbnail", + "index": 0, + "file_path": data['thumbnail_path'] + '.png' + } + upload_data = { + "name": data['asset_data']['name'], + "token": preferences.api_key, + "id": data['asset_data']['id'] + } + upload_bg.upload_file(upload_data, file) bg_blender.progress('background autothumbnailer finished successfully') diff --git a/blenderkit/autothumb_model_bg.py b/blenderkit/autothumb_model_bg.py index 87acfa19..9be56d9f 100644 --- a/blenderkit/autothumb_model_bg.py +++ b/blenderkit/autothumb_model_bg.py @@ -18,17 +18,14 @@ -from blenderkit import utils, append_link, bg_blender +from blenderkit import utils, append_link, bg_blender, download, upload_bg import sys, json, math from pathlib import Path import bpy import mathutils -BLENDERKIT_EXPORT_TEMP_DIR = sys.argv[-1] -BLENDERKIT_THUMBNAIL_PATH = sys.argv[-2] -BLENDERKIT_EXPORT_FILE_INPUT = sys.argv[-3] -BLENDERKIT_EXPORT_DATA = sys.argv[-4] +BLENDERKIT_EXPORT_DATA = sys.argv[-1] def get_obnames(): @@ -42,6 +39,8 @@ def center_obs_for_thumbnail(obs): s = bpy.context.scene # obs = bpy.context.selected_objects parent = obs[0] + if parent.type == 'EMPTY' and parent.instance_collection is not None: + obs = parent.instance_collection.objects[:] while parent.parent != None: parent = parent.parent @@ -79,18 +78,42 @@ def render_thumbnails(): if __name__ == "__main__": try: + print( 'got to A') with open(BLENDERKIT_EXPORT_DATA, 'r',encoding='utf-8') as s: data = json.load(s) user_preferences = bpy.context.preferences.addons['blenderkit'].preferences - bg_blender.progress('preparing thumbnail scene') - obnames = get_obnames() - main_object, allobs = append_link.append_objects(file_name=BLENDERKIT_EXPORT_FILE_INPUT, + + if data.get('do_download'): + bg_blender.progress('Downloading asset') + + asset_data = data['asset_data'] + has_url = download.get_download_url(asset_data, download.get_scene_id(), user_preferences.api_key, tcom=None, + resolution='blend') + if not has_url == True: + bg_blender.progress("couldn't download asset for thumnbail re-rendering") + # download first, or rather make sure if it's already downloaded + bg_blender.progress('downloading asset') + fpath = download.download_asset_file(asset_data) + data['filepath'] = fpath + main_object, allobs = append_link.link_collection(fpath, + location=(0,0,0), + rotation=(0,0,0), + link=True, + name=asset_data['name'], + parent=None) + allobs = [main_object] + else: + bg_blender.progress('preparing thumbnail scene') + + obnames = get_obnames() + main_object, allobs = append_link.append_objects(file_name=data['filepath'], obnames=obnames, - link=True) + link=True) bpy.context.view_layer.update() + camdict = { 'GROUND': 'camera ground', 'WALL': 'camera wall', @@ -100,7 +123,7 @@ if __name__ == "__main__": bpy.context.scene.camera = bpy.data.objects[camdict[data['thumbnail_snap_to']]] center_obs_for_thumbnail(allobs) - bpy.context.scene.render.filepath = BLENDERKIT_THUMBNAIL_PATH + bpy.context.scene.render.filepath = data['thumbnail_path'] if user_preferences.thumbnail_use_gpu: bpy.context.scene.cycles.device = 'GPU' @@ -112,6 +135,7 @@ if __name__ == "__main__": } s = bpy.context.scene s.frame_set(fdict[data['thumbnail_angle']]) + print( 'got to C') snapdict = { 'GROUND': 'Ground', @@ -131,6 +155,7 @@ if __name__ == "__main__": s.cycles.samples = data['thumbnail_samples'] bpy.context.view_layer.cycles.use_denoising = data['thumbnail_denoising'] bpy.context.view_layer.update() + print( 'got to D') # import blender's HDR here # hdr_path = Path('datafiles/studiolights/world/interior.exr') @@ -152,10 +177,32 @@ if __name__ == "__main__": bg_blender.progress('rendering thumbnail') render_thumbnails() - fpath = BLENDERKIT_THUMBNAIL_PATH + '0001.jpg' + fpath = data['thumbnail_path'] + '.jpg' + if data.get('upload_after_render') and data.get('asset_data'): + bg_blender.progress('uploading thumbnail') + preferences = bpy.context.preferences.addons['blenderkit'].preferences + print('uploading A') + file = { + "type": "thumbnail", + "index": 0, + "file_path": fpath + } + upload_data = { + "name": data['asset_data']['name'], + "token": preferences.api_key, + "id": data['asset_data']['id'] + } + print('uploading B') + + upload_bg.upload_file(upload_data, file) + print('uploading C') + bg_blender.progress('background autothumbnailer finished successfully') + print( 'got to E') + + except: import traceback diff --git a/blenderkit/bg_blender.py b/blenderkit/bg_blender.py index bdce32af..01c62538 100644 --- a/blenderkit/bg_blender.py +++ b/blenderkit/bg_blender.py @@ -56,6 +56,9 @@ def threadread(tcom): fills the data, dies.''' found = False while not found: + if tcom.proc.poll() is not None: + #process terminated + return inline = tcom.proc.stdout.readline() # print('readthread', time.time()) inline = str(inline) @@ -111,7 +114,15 @@ def bg_update(): global bg_processes if len(bg_processes) == 0: return 2 + #cleanup dead processes first + remove_processes = [] + for p in bg_processes: + if p[1].proc.poll() is not None: + remove_processes.append(p) + for p in remove_processes: + bg_processes.remove(p) + #Parse process output for p in bg_processes: # proc=p[1].proc readthread = p[0] @@ -119,26 +130,29 @@ def bg_update(): if not readthread.is_alive(): readthread.join() # readthread. + estring = None if tcom.error: estring = tcom.eval_path_computing + ' = False' - exec(estring) - tcom.lasttext = tcom.outtext if tcom.outtext != '': tcom.outtext = '' text =tcom.lasttext.replace("'","") estring = tcom.eval_path_state + ' = text' - - exec(estring) # print(tcom.lasttext) if 'finished successfully' in tcom.lasttext: bg_processes.remove(p) estring = tcom.eval_path_computing + ' = False' - exec(estring) else: readthread = threading.Thread(target=threadread, args=([tcom]), daemon=True) readthread.start() p[0] = readthread + if estring: + try: + exec(estring) + except Exception as e: + print('Exception while reading from background process') + print(e) + # if len(bg_processes) == 0: # bpy.app.timers.unregister(bg_update) if len(bg_processes) > 0: diff --git a/blenderkit/blendfiles/material_thumbnailer_cycles.blend b/blenderkit/blendfiles/material_thumbnailer_cycles.blend Binary files differindex 23df6817..1faa5807 100644 --- a/blenderkit/blendfiles/material_thumbnailer_cycles.blend +++ b/blenderkit/blendfiles/material_thumbnailer_cycles.blend diff --git a/blenderkit/categories.py b/blenderkit/categories.py index 2da830cb..f91fe512 100644 --- a/blenderkit/categories.py +++ b/blenderkit/categories.py @@ -17,7 +17,7 @@ # ##### END GPL LICENSE BLOCK ##### -from blenderkit import paths, utils, tasks_queue, rerequests +from blenderkit import paths, utils, tasks_queue, rerequests, ui, colors import requests import json @@ -233,7 +233,9 @@ def fetch_categories(API_key, force=False): json.dump(categories, s, ensure_ascii=False, indent=4) tasks_queue.add_task((load_categories, ())) except Exception as e: - bk_logger.debug('category fetching failed') + t = 'BlenderKit failed to download fresh categories from the server' + tasks_queue.add_task((ui.add_report,(t, 15, colors.RED))) + bk_logger.debug(t) bk_logger.exception(e) if not os.path.exists(categories_filepath): source_path = paths.get_addon_file(subpath='data' + os.sep + 'categories.json') diff --git a/blenderkit/download.py b/blenderkit/download.py index 5d14ee41..eca35c41 100644 --- a/blenderkit/download.py +++ b/blenderkit/download.py @@ -492,6 +492,8 @@ def append_asset(asset_data, **kwargs): # downloaders=[], location=None, udpate_asset_data_in_dicts(asset_data) asset_main['asset_data'] = asset_data # TODO remove this??? should write to blenderkit Props? + asset_main.blenderkit.asset_base_id = asset_data['assetBaseId'] + asset_main.blenderkit.id = asset_data['id'] bpy.ops.wm.undo_push_context(message='add %s to scene' % asset_data['name']) # moving reporting to on save. # report_use_success(asset_data['id']) @@ -683,7 +685,7 @@ def delete_unfinished_file(file_name): return -def download_file(asset_data, resolution='blend'): +def download_asset_file(asset_data, resolution='blend', api_key = ''): # this is a simple non-threaded way to download files for background resolution genenration tool file_name = paths.get_download_filepaths(asset_data, resolution)[0] # prefer global dir if possible. @@ -691,14 +693,11 @@ def download_file(asset_data, resolution='blend'): # this sends the thread for processing, where another check should occur, since the file might be corrupted. bk_logger.debug('not downloading, already in db') return file_name - preferences = bpy.context.preferences.addons['blenderkit'].preferences - api_key = preferences.api_key download_canceled = False with open(file_name, "wb") as f: print("Downloading %s" % file_name) - headers = utils.get_headers(api_key) res_file_info, resolution = paths.get_res_file(asset_data, resolution) response = requests.get(res_file_info['url'], stream=True) total_length = response.headers.get('Content-Length') @@ -1110,18 +1109,18 @@ def get_download_url(asset_data, scene_id, api_key, tcom=None, resolution='blend tasks_queue.add_task((ui.add_report, (str(r), 10, colors.RED))) if r.status_code == 403: - r = 'You need Full plan to get this item.' + report = 'You need Full plan to get this item.' # r1 = 'All materials and brushes are available for free. Only users registered to Standard plan can use all models.' # tasks_queue.add_task((ui.add_report, (r1, 5, colors.RED))) if tcom is not None: - tcom.report = r + tcom.report = report tcom.error = True if r.status_code == 404: - r = 'Url not found - 404.' + report = 'Url not found - 404.' # r1 = 'All materials and brushes are available for free. Only users registered to Standard plan can use all models.' if tcom is not None: - tcom.report = r + tcom.report = report tcom.error = True elif r.status_code >= 500: diff --git a/blenderkit/icons.py b/blenderkit/icons.py index 5d877b25..45c729df 100644 --- a/blenderkit/icons.py +++ b/blenderkit/icons.py @@ -51,6 +51,7 @@ def register_icons(): # iprev.image_pixels_float = img.pixels[:] icon_collections["main"] = pcoll + icon_collections["previews"] = bpy.utils.previews.new() def unregister_icons(): diff --git a/blenderkit/resolutions.py b/blenderkit/resolutions.py index b2a5e806..1a4af5fc 100644 --- a/blenderkit/resolutions.py +++ b/blenderkit/resolutions.py @@ -410,7 +410,7 @@ def regenerate_thumbnail_material(data): # preferences = bpy.context.preferences.addons['blenderkit'].preferences # layout.prop(preferences, "thumbnail_use_gpu") # TODO: here it should call start_material_thumbnailer , but with the wait property on, so it can upload afterwards. - bpy.ops.object.blenderkit_material_thumbnail() + bpy.ops.object.blenderkit_generate_material_thumbnail() time.sleep(130) # save # this does the actual job @@ -525,7 +525,7 @@ def download_asset(asset_data, resolution='blend', unpack=False, api_key=''): has_url = download.get_download_url(asset_data, download.get_scene_id(), api_key, tcom=None, resolution='blend') if has_url: - fpath = download.download_file(asset_data) + fpath = download.download_asset_file(asset_data, api_key = api_key) if fpath and unpack and asset_data['assetType'] != 'hdr': send_to_bg(asset_data, fpath, command='unpack', wait=True) return fpath diff --git a/blenderkit/search.py b/blenderkit/search.py index d802973b..1465d103 100644 --- a/blenderkit/search.py +++ b/blenderkit/search.py @@ -48,7 +48,7 @@ import copy import json import math import unicodedata - +import queue import logging bk_logger = logging.getLogger('blenderkit') @@ -75,7 +75,7 @@ def check_errors(rdata): search_threads = [] thumb_sml_download_threads = {} thumb_full_download_threads = {} -reports = '' +reports_queue = queue.Queue() rtips = ['Click or drag model or material in scene to link/append ', "Please rate responsively and plentifully. This helps us distribute rewards to the authors.", @@ -412,9 +412,10 @@ def timer_update(): wm[search_name] = [] - global reports - if reports != '': - props.report = str(reports) + global reports_queue + + while not reports_queue.empty(): + props.report = str(reports_queue.get()) return .2 rdata = thread[0].result @@ -628,7 +629,7 @@ def generate_tooltip(mdata): t += 'Size: %s x %s x %sm\n' % (fmt_length(mparams['dimensionX']), fmt_length(mparams['dimensionY']), fmt_length(mparams['dimensionZ'])) - if has(mparams, 'faceCount'): + if has(mparams, 'faceCount') and mdata['assetType'] == 'model': t += 'Face count: %s\n' % (mparams['faceCount']) # t += 'face count: %s, render: %s\n' % (mparams['faceCount'], mparams['faceCountRender']) @@ -709,7 +710,7 @@ def generate_tooltip(mdata): show_rating_threshold = 5 - if rcount < show_rating_threshold: + if rcount < show_rating_threshold and mdata['assetType'] != 'hdr': t += f"Only assets with enough ratings \nshow the rating value. Please rate.\n" if rc['quality'] >= show_rating_threshold: # t += f"{int(mdata['ratingsAverage']['quality']) * '*'}\n" @@ -765,26 +766,6 @@ def generate_author_textblock(adata): return t -def get_items_models(self, context): - global search_items_models - return search_items_models - - -def get_items_brushes(self, context): - global search_items_brushes - return search_items_brushes - - -def get_items_materials(self, context): - global search_items_materials - return search_items_materials - - -def get_items_textures(self, context): - global search_items_textures - return search_items_textures - - class ThumbDownloader(threading.Thread): query = None @@ -801,7 +782,12 @@ class ThumbDownloader(threading.Thread): return self._stop_event.is_set() def run(self): - r = rerequests.get(self.url, stream=False) + print('thumb downloader', self.url) + try: + r = requests.get(self.url, stream=False) + except Exception as e: + bk_logger.error('Thumbnail download failed') + bk_logger.error(str(e)) if r.status_code == 200: with open(self.path, 'wb') as f: f.write(r.content) @@ -962,6 +948,11 @@ def query_to_url(query = {}, params = {}): urlquery = url + requeststring return urlquery +def parse_html_formated_error(text): + report = text[text.find('<title>') + 7: text.find('</title>')] + + return report + class Searcher(threading.Thread): query = None @@ -982,10 +973,12 @@ class Searcher(threading.Thread): return self._stop_event.is_set() def run(self): + global reports_queue + maxthreads = 50 query = self.query params = self.params - global reports + t = time.time() mt('search thread started') @@ -999,21 +992,21 @@ class Searcher(threading.Thread): try: utils.p(self.urlquery) r = rerequests.get(self.urlquery, headers=self.headers) # , params = rparameters) - # print(r.url) - reports = '' - # utils.p(r.text) except requests.exceptions.RequestException as e: bk_logger.error(e) - reports = e - # props.report = e + reports_queue.put(str(e)) return + mt('search response is back ') try: rdata = r.json() except Exception as e: - reports = r.text - bk_logger.error(e) + error_description = parse_html_formated_error(r.text) + reports_queue.put(error_description) + tasks_queue.add_task((ui.add_report, (error_description, 10, colors.RED))) + bk_logger.error(e) + return mt('data parsed ') if not rdata.get('results'): utils.pprint(rdata) diff --git a/blenderkit/ui.py b/blenderkit/ui.py index 7969bea2..7117055f 100644 --- a/blenderkit/ui.py +++ b/blenderkit/ui.py @@ -655,7 +655,10 @@ def draw_callback_2d_progress(self, context): for process in bg_blender.bg_processes: tcom = process[1] - draw_progress(x, y - index * 30, '%s' % tcom.lasttext, + n='' + if tcom.name is not None: + n = tcom.name +': ' + draw_progress(x, y - index * 30, '%s' % n+tcom.lasttext, tcom.progress) index += 1 global reports @@ -1873,6 +1876,11 @@ class AssetBarOperator(bpy.types.Operator): context.window_manager.modal_handler_add(self) ui_props.assetbar_on = True + + #in an exceptional case these were accessed before drag start. + self.drag_start_x = 0 + self.drag_start_y = 0 + return {'RUNNING_MODAL'} def execute(self, context): @@ -2151,6 +2159,9 @@ class RunAssetBarWithContext(bpy.types.Operator): bl_description = "Run assetbar with fixed context" bl_options = {'REGISTER', 'UNDO', 'INTERNAL'} + keep_running: BoolProperty(name="Keep Running", description='', default=True, options={'SKIP_SAVE'}) + do_search: BoolProperty(name="Run Search", description='', default=False, options={'SKIP_SAVE'}) + # def modal(self, context, event): # return {'RUNNING_MODAL'} @@ -2159,11 +2170,11 @@ class RunAssetBarWithContext(bpy.types.Operator): if C_dict.get('window'): # no 3d view, no asset bar. preferences = bpy.context.preferences.addons['blenderkit'].preferences if preferences.experimental_features: - bpy.ops.view3d.blenderkit_asset_bar_widget(C_dict, 'INVOKE_REGION_WIN', keep_running=True, - do_search=False) + bpy.ops.view3d.blenderkit_asset_bar_widget(C_dict, 'INVOKE_REGION_WIN', keep_running=self.keep_running, + do_search=self.do_search) else: - bpy.ops.view3d.blenderkit_asset_bar(C_dict, 'INVOKE_REGION_WIN', keep_running=True, do_search=False) + bpy.ops.view3d.blenderkit_asset_bar(C_dict, 'INVOKE_REGION_WIN', keep_running=self.keep_running, do_search=self.do_search) return {'FINISHED'} @@ -2208,7 +2219,7 @@ def register_ui(): return km = wm.keyconfigs.addon.keymaps.new(name="Window", space_type='EMPTY') # asset bar shortcut - kmi = km.keymap_items.new(AssetBarOperator.bl_idname, 'SEMI_COLON', 'PRESS', ctrl=False, shift=False) + kmi = km.keymap_items.new("object.run_assetbar_fix_context", 'SEMI_COLON', 'PRESS', ctrl=False, shift=False) kmi.properties.keep_running = False kmi.properties.do_search = False addon_keymapitems.append(kmi) diff --git a/blenderkit/ui_bgl.py b/blenderkit/ui_bgl.py index 2165cdc5..fabda329 100644 --- a/blenderkit/ui_bgl.py +++ b/blenderkit/ui_bgl.py @@ -77,6 +77,8 @@ def draw_rect_3d(coords, color): def draw_image(x, y, width, height, image, transparency, crop=(0, 0, 1, 1)): # draw_rect(x,y, width, height, (.5,0,0,.5)) + if not image: + return; coords = [ (x, y), (x + width, y), diff --git a/blenderkit/ui_panels.py b/blenderkit/ui_panels.py index 0dbeaf89..6bacc895 100644 --- a/blenderkit/ui_panels.py +++ b/blenderkit/ui_panels.py @@ -17,7 +17,8 @@ # ##### END GPL LICENSE BLOCK ##### -from blenderkit import paths, ratings, utils, download, categories, icons, search, resolutions, ui +from blenderkit import paths, ratings, utils, download, categories, icons, search, resolutions, ui, tasks_queue, \ + autothumb from bpy.types import ( Panel @@ -35,8 +36,11 @@ from bpy.props import ( import bpy import os import random +import logging import blenderkit +bk_logger = logging.getLogger('blenderkit') + # this was moved to separate interface: @@ -153,6 +157,7 @@ def draw_upload_common(layout, props, asset_type, context): layout.prop(props, 'description') layout.prop(props, 'tags') + def poll_local_panels(): user_preferences = bpy.context.preferences.addons['blenderkit'].preferences return user_preferences.panel_behaviour == 'BOTH' or user_preferences.panel_behaviour == 'LOCAL' @@ -189,7 +194,6 @@ def draw_panel_hdr_upload(self, context): draw_upload_common(layout, props, 'HDR', context) - def draw_panel_hdr_search(self, context): s = context.scene props = s.blenderkit_HDR @@ -203,6 +207,15 @@ def draw_panel_hdr_search(self, context): utils.label_multiline(layout, text=props.report) + +def draw_thumbnail_upload_panel(layout, props): + update = False + tex = autothumb.get_texture_ui(props.thumbnail, '.upload_preview') + if not tex or not tex.image: + return + box = layout.box() + box.template_icon(icon_value=tex.image.preview.icon_id, scale=6.0) + def draw_panel_model_upload(self, context): ob = bpy.context.active_object while ob.parent is not None: @@ -216,6 +229,9 @@ def draw_panel_model_upload(self, context): col = layout.column() if props.is_generating_thumbnail: col.enabled = False + + draw_thumbnail_upload_panel(col, props) + prop_needed(col, props, 'thumbnail', props.thumbnail) if bpy.context.scene.render.engine in ('CYCLES', 'BLENDER_EEVEE'): col.operator("object.blenderkit_generate_thumbnail", text='Generate thumbnail', icon='IMAGE') @@ -275,6 +291,8 @@ def draw_panel_scene_upload(self, context): col = layout.column() # if props.is_generating_thumbnail: # col.enabled = False + draw_thumbnail_upload_panel(col, props) + prop_needed(col, props, 'thumbnail', props.has_thumbnail, False) # if bpy.context.scene.render.engine == 'CYCLES': # col.operator("object.blenderkit_generate_thumbnail", text='Generate thumbnail', icon='IMAGE_COL') @@ -289,8 +307,6 @@ def draw_panel_scene_upload(self, context): # elif props.thumbnail_generating_state != '': # utils.label_multiline(layout, text = props.thumbnail_generating_state) - - layout.prop(props, 'style') layout.prop(props, 'production_level') layout.prop(props, 'use_design_year') @@ -621,15 +637,16 @@ def draw_panel_material_upload(self, context): draw_upload_common(layout, props, 'MATERIAL', context) # THUMBNAIL - row = layout.row() + row = layout.column() if props.is_generating_thumbnail: row.enabled = False - prop_needed(row, props, 'thumbnail', props.has_thumbnail, False) + draw_thumbnail_upload_panel(row, props) + prop_needed(row, props, 'thumbnail', props.has_thumbnail, False) if bpy.context.scene.render.engine in ('CYCLES', 'BLENDER_EEVEE'): - layout.operator("object.blenderkit_material_thumbnail", text='Render thumbnail with Cycles', icon='EXPORT') + layout.operator("object.blenderkit_generate_material_thumbnail", text='Render thumbnail with Cycles', icon='EXPORT') if props.is_generating_thumbnail: row = layout.row(align=True) row.label(text=props.thumbnail_generating_state, icon='RENDER_STILL') @@ -653,8 +670,6 @@ def draw_panel_material_upload(self, context): layout.prop(props, 'animated') layout.prop(props, 'texture_size_meters') - - # tname = "." + bpy.context.active_object.active_material.name + "_thumbnail" # if props.has_thumbnail and bpy.data.textures.get(tname) is not None: # row = layout.row() @@ -971,15 +986,15 @@ class VIEW3D_PT_blenderkit_unified(Panel): if ui_props.asset_type_fold: expand_icon = 'TRIA_RIGHT' row = layout.row() - split = row.split(factor = 0.15) - split.prop(ui_props, 'asset_type_fold', icon = expand_icon, icon_only = True, emboss = False) + split = row.split(factor=0.15) + split.prop(ui_props, 'asset_type_fold', icon=expand_icon, icon_only=True, emboss=False) if ui_props.asset_type_fold: pass - #expanded interface with names in column + # expanded interface with names in column split = split.row() split.scale_x = 8 - split.scale_y =1.6 + split.scale_y = 1.6 # split = row # split = layout.row() else: @@ -1160,10 +1175,9 @@ def draw_asset_context_menu(layout, context, asset_data, from_panel=False): # build search string from description and tags: op.keywords = asset_data['name'] if asset_data.get('description'): - op.keywords += ' ' + asset_data.get('description')+' ' + op.keywords += ' ' + asset_data.get('description') + ' ' op.keywords += ' '.join(asset_data.get('tags')) - if asset_data.get('canDownload') != 0: if len(bpy.context.selected_objects) > 0 and ui_props.asset_type == 'MODEL': aob = bpy.context.active_object @@ -1228,7 +1242,7 @@ def draw_asset_context_menu(layout, context, asset_data, from_panel=False): 0) # str(utils.get_param(asset_data, 'textureResolutionMax')) elif asset_data['assetBaseId'] in s['assets used'].keys() and asset_data['assetType'] != 'hdr': - #HDRs are excluded from replacement, since they are always replaced. + # HDRs are excluded from replacement, since they are always replaced. # called from asset bar: print('context menu') op = col.operator('scene.blenderkit_download', text='Replace asset resolution') @@ -1287,6 +1301,16 @@ def draw_asset_context_menu(layout, context, asset_data, from_panel=False): op.asset_id = asset_data['id'] op.asset_type = asset_data['assetType'] + if asset_data['assetType'] == 'model': + op = layout.operator('object.blenderkit_regenerate_thumbnail', text='Regenerate thumbnail') + op.asset_index = ui_props.active_index + + if asset_data['assetType'] == 'material': + op = layout.operator('object.blenderkit_regenerate_material_thumbnail', text='Regenerate thumbnail') + op.asset_index = ui_props.active_index + # op.asset_id = asset_data['id'] + # op.asset_type = asset_data['assetType'] + if author_id == str(profile['user']['id']): row = layout.row() row.operator_context = 'INVOKE_DEFAULT' diff --git a/blenderkit/upload.py b/blenderkit/upload.py index b2395f68..cc72b92a 100644 --- a/blenderkit/upload.py +++ b/blenderkit/upload.py @@ -659,6 +659,11 @@ class FastMetadata(bpy.types.Operator): update=update_free_full ) + + #################### + + + @classmethod def poll(cls, context): scene = bpy.context.scene @@ -739,6 +744,7 @@ class FastMetadata(bpy.types.Operator): self.subcategory = cat_path[2] except Exception as e: print(e) + self.message = f"Fast edit metadata of {asset_data['name']}" self.name = asset_data['displayName'] self.description = asset_data['description'] diff --git a/blenderkit/upload_bg.py b/blenderkit/upload_bg.py index 3307a72a..1b91d51b 100644 --- a/blenderkit/upload_bg.py +++ b/blenderkit/upload_bg.py @@ -93,8 +93,9 @@ def upload_file(upload_data, f): uploaded = True upload_done_url = paths.get_api_url() + 'uploads_s3/' + upload['id'] + '/upload-file/' upload_response = rerequests.post(upload_done_url, headers=headers, verify=True) - print(upload_response) - tasks_queue.add_task((ui.add_report, (f"Finished file upload{os.path.basename(f['file_path'])}",))) + # print(upload_response) + # print(upload_response.text) + tasks_queue.add_task((ui.add_report, (f"Finished file upload: {os.path.basename(f['file_path'])}",))) return True else: print(upload_response.text) diff --git a/blenderkit/utils.py b/blenderkit/utils.py index 7abc07d5..953ded5e 100644 --- a/blenderkit/utils.py +++ b/blenderkit/utils.py @@ -326,14 +326,14 @@ def uploadable_asset_poll(): return True -def get_hidden_texture(img, force_reload=False): - # i = get_hidden_image(tpath, bdata_name, force_reload=force_reload) - # bdata_name = f".{bdata_name}" - t = bpy.data.textures.get(img.name) +def get_hidden_texture(name, force_reload=False): + t = bpy.data.textures.get(name) if t is None: - t = bpy.data.textures.new(img.name, 'IMAGE') - if t.image != img: - t.image = img + t = bpy.data.textures.new(name, 'IMAGE') + if not t.image or t.image.name != name: + img = bpy.data.images.get(name) + if img: + t.image = img return t @@ -691,15 +691,15 @@ def name_update(props): asset.name = fname -def get_param(asset_data, parameter_name): +def get_param(asset_data, parameter_name, default = None): if not asset_data.get('parameters'): # this can appear in older version files. - return None + return default for p in asset_data['parameters']: if p.get('parameterType') == parameter_name: return p['value'] - return None + return default def params_to_dict(params): |