Welcome to mirror list, hosted at ThFree Co, Russian Federation.

git.blender.org/blender-addons.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorVilem Duha <vilem.duha@gmail.com>2021-11-22 12:41:16 +0300
committerVilem Duha <vilem.duha@gmail.com>2021-11-22 12:41:16 +0300
commit13ecbb8fee64fe3c262c6f904a2bd21ea2ca2070 (patch)
treec72eab64a027179f1a29f70e194ba2d9a7dd263f
parentafb8eb2feffb17034eb3ba70fa0c0f1d489cf3bc (diff)
Moving BlenderKit to new repository on GitHub
BlenderKit addon will be here since now: https://github.com/BlenderKit/blenderkit And all release notes , docs and information will be on BlenderKit website: www.blenderkit.com The move happens since Blender Foundation ended the commercial addon offering for all commercial addons. This means a bit less comfort for our users, but also many new possibilities!
-rw-r--r--blenderkit/README.md3
-rw-r--r--blenderkit/__init__.py1977
-rw-r--r--blenderkit/append_link.py403
-rw-r--r--blenderkit/asset_bar_op.py1034
-rw-r--r--blenderkit/asset_inspector.py393
-rw-r--r--blenderkit/asset_pack_bg.py8
-rw-r--r--blenderkit/autothumb.py671
-rw-r--r--blenderkit/autothumb_material_bg.py171
-rw-r--r--blenderkit/autothumb_model_bg.py207
-rw-r--r--blenderkit/bg_blender.py278
-rw-r--r--blenderkit/bkit_oauth.py201
-rw-r--r--blenderkit/bl_ui_widgets/__init__.py36
-rw-r--r--blenderkit/bl_ui_widgets/bl_ui_button.py205
-rw-r--r--blenderkit/bl_ui_widgets/bl_ui_drag_panel.py59
-rw-r--r--blenderkit/bl_ui_widgets/bl_ui_draw_op.py93
-rw-r--r--blenderkit/bl_ui_widgets/bl_ui_image.py97
-rw-r--r--blenderkit/bl_ui_widgets/bl_ui_label.py72
-rw-r--r--blenderkit/bl_ui_widgets/bl_ui_widget.py195
-rw-r--r--blenderkit/blendfiles/cleaned.blendbin41012 -> 0 bytes
-rw-r--r--blenderkit/blendfiles/material_thumbnailer_cycles.blendbin2976697 -> 0 bytes
-rw-r--r--blenderkit/blendfiles/thumbnailer.blendbin294674 -> 0 bytes
-rw-r--r--blenderkit/categories.py274
-rw-r--r--blenderkit/colors.py25
-rw-r--r--blenderkit/comments_utils.py232
-rw-r--r--blenderkit/data/categories.json6176
-rw-r--r--blenderkit/download.py1467
-rw-r--r--blenderkit/icons.py78
-rw-r--r--blenderkit/image_utils.py505
-rw-r--r--blenderkit/oauth.py117
-rw-r--r--blenderkit/overrides.py300
-rw-r--r--blenderkit/paths.py407
-rw-r--r--blenderkit/ratings.py297
-rw-r--r--blenderkit/ratings_utils.py367
-rw-r--r--blenderkit/reports.py67
-rw-r--r--blenderkit/rerequests.py115
-rw-r--r--blenderkit/resolutions.py716
-rw-r--r--blenderkit/resolutions_bg.py8
-rw-r--r--blenderkit/search.py1685
-rw-r--r--blenderkit/tasks_queue.py125
-rw-r--r--blenderkit/thumbnails/arrow_left.pngbin2119 -> 0 bytes
-rw-r--r--blenderkit/thumbnails/arrow_right.pngbin2084 -> 0 bytes
-rw-r--r--blenderkit/thumbnails/bar_slider.pngbin861 -> 0 bytes
-rw-r--r--blenderkit/thumbnails/bell.pngbin946 -> 0 bytes
-rw-r--r--blenderkit/thumbnails/cc0.pngbin5419 -> 0 bytes
-rw-r--r--blenderkit/thumbnails/dumbbell.pngbin1759 -> 0 bytes
-rw-r--r--blenderkit/thumbnails/filter.pngbin525 -> 0 bytes
-rw-r--r--blenderkit/thumbnails/filter_active.pngbin525 -> 0 bytes
-rw-r--r--blenderkit/thumbnails/flp.pngbin540 -> 0 bytes
-rw-r--r--blenderkit/thumbnails/fp.pngbin561 -> 0 bytes
-rw-r--r--blenderkit/thumbnails/intro.jpgbin66605 -> 0 bytes
-rw-r--r--blenderkit/thumbnails/locked.pngbin2184 -> 0 bytes
-rw-r--r--blenderkit/thumbnails/private.pngbin540 -> 0 bytes
-rw-r--r--blenderkit/thumbnails/royalty_free.pngbin4755 -> 0 bytes
-rw-r--r--blenderkit/thumbnails/star_grey.pngbin1733 -> 0 bytes
-rw-r--r--blenderkit/thumbnails/star_white.pngbin1552 -> 0 bytes
-rw-r--r--blenderkit/thumbnails/thumbnail_not_available.jpgbin8999 -> 0 bytes
-rw-r--r--blenderkit/thumbnails/thumbnail_notready.jpgbin2999 -> 0 bytes
-rw-r--r--blenderkit/thumbnails/trophy.pngbin3078 -> 0 bytes
-rw-r--r--blenderkit/thumbnails/vs_deleted.pngbin2151 -> 0 bytes
-rw-r--r--blenderkit/thumbnails/vs_on_hold.pngbin1583 -> 0 bytes
-rw-r--r--blenderkit/thumbnails/vs_ready.pngbin2137 -> 0 bytes
-rw-r--r--blenderkit/thumbnails/vs_rejected.pngbin2114 -> 0 bytes
-rw-r--r--blenderkit/thumbnails/vs_uploaded.pngbin1801 -> 0 bytes
-rw-r--r--blenderkit/thumbnails/vs_uploading.pngbin1383 -> 0 bytes
-rw-r--r--blenderkit/thumbnails/vs_validated.pngbin2393 -> 0 bytes
-rw-r--r--blenderkit/ui.py1937
-rw-r--r--blenderkit/ui_bgl.py155
-rw-r--r--blenderkit/ui_panels.py2681
-rw-r--r--blenderkit/upload.py1387
-rw-r--r--blenderkit/upload_bg.py187
-rw-r--r--blenderkit/utils.py1001
-rw-r--r--blenderkit/version_checker.py80
72 files changed, 0 insertions, 26492 deletions
diff --git a/blenderkit/README.md b/blenderkit/README.md
deleted file mode 100644
index e2e77067..00000000
--- a/blenderkit/README.md
+++ /dev/null
@@ -1,3 +0,0 @@
-BlenderKit add-on is the official addon of the BlenderKit service for Blender 3d.
-It enables users to upload, search, download, and rate different assets for blender.
-It works together with BlenderKit server. \ No newline at end of file
diff --git a/blenderkit/__init__.py b/blenderkit/__init__.py
deleted file mode 100644
index 22afd2a1..00000000
--- a/blenderkit/__init__.py
+++ /dev/null
@@ -1,1977 +0,0 @@
-# ##### 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 #####
-
-bl_info = {
- "name": "BlenderKit Online Asset Library",
- "author": "Vilem Duha, Petr Dlouhy",
- "version": (3, 0, 0),
- "blender": (2, 93, 0),
- "location": "View3D > Properties > BlenderKit",
- "description": "Online BlenderKit library (materials, models, brushes and more). Connects to the internet.",
- "warning": "",
- "doc_url": "{BLENDER_MANUAL_URL}/addons/3d_view/blenderkit.html",
- "category": "3D View",
-}
-
-if "bpy" in locals():
- from importlib import reload
-
- # alphabetically sorted all add-on modules since reload only happens from __init__.
- # modules with _bg are used for background computations in separate blender instance and that's why they don't need reload.
-
- append_link = reload(append_link)
- asset_bar_op = reload(asset_bar_op)
- asset_inspector = reload(asset_inspector)
- autothumb = reload(autothumb)
- bg_blender = reload(bg_blender)
- bkit_oauth = reload(bkit_oauth)
- categories = reload(categories)
- colors = reload(colors)
- download = reload(download)
- icons = reload(icons)
- image_utils = reload(image_utils)
- oauth = reload(oauth)
- overrides = reload(overrides)
- paths = reload(paths)
- ratings = reload(ratings)
- ratings_utils = reload(ratings_utils)
- comments_utils = reload(comments_utils)
- resolutions = reload(resolutions)
- search = reload(search)
- tasks_queue = reload(tasks_queue)
- ui = reload(ui)
- ui_bgl = reload(ui_bgl)
- ui_panels = reload(ui_panels)
- upload = reload(upload)
- upload_bg = reload(upload_bg)
- utils = reload(utils)
- reports = reload(reports)
-
- bl_ui_widget = reload(bl_ui_widget)
- bl_ui_label = reload(bl_ui_label)
- bl_ui_button = reload(bl_ui_button)
- bl_ui_image = reload(bl_ui_image)
- # bl_ui_checkbox = reload(bl_ui_checkbox)
- # bl_ui_slider = reload(bl_ui_slider)
- # bl_ui_up_down = reload(bl_ui_up_down)
- bl_ui_drag_panel = reload(bl_ui_drag_panel)
- bl_ui_draw_op = reload(bl_ui_draw_op)
- # bl_ui_textbox = reload(bl_ui_textbox)
-
-else:
- from blenderkit import append_link
- from blenderkit import asset_bar_op
- from blenderkit import asset_inspector
- from blenderkit import autothumb
- from blenderkit import bg_blender
- from blenderkit import bkit_oauth
- from blenderkit import categories
- from blenderkit import colors
- from blenderkit import download
- from blenderkit import icons
- from blenderkit import image_utils
- from blenderkit import oauth
- from blenderkit import overrides
- from blenderkit import paths
- from blenderkit import ratings
- from blenderkit import ratings_utils
- from blenderkit import comments_utils
- from blenderkit import resolutions
- from blenderkit import search
- from blenderkit import tasks_queue
- from blenderkit import ui
- from blenderkit import ui_bgl
- from blenderkit import ui_panels
- from blenderkit import upload
- from blenderkit import upload_bg
- from blenderkit import utils
- from blenderkit import reports
-
- from blenderkit.bl_ui_widgets import bl_ui_widget
- from blenderkit.bl_ui_widgets import bl_ui_label
- from blenderkit.bl_ui_widgets import bl_ui_button
- from blenderkit.bl_ui_widgets import bl_ui_image
- # from blenderkit.bl_ui_widgets import bl_ui_checkbox
- # from blenderkit.bl_ui_widgets import bl_ui_slider
- # from blenderkit.bl_ui_widgets import bl_ui_up_down
- from blenderkit.bl_ui_widgets import bl_ui_draw_op
- from blenderkit.bl_ui_widgets import bl_ui_drag_panel
- # from blenderkit.bl_ui_widgets import bl_ui_textbox
-
-import os
-import math
-import time
-import logging
-import bpy
-import pathlib
-
-log = logging.getLogger(__name__)
-
-from bpy.app.handlers import persistent
-import bpy.utils.previews
-import mathutils
-from mathutils import Vector
-from bpy.props import (
- IntProperty,
- FloatProperty,
- FloatVectorProperty,
- StringProperty,
- EnumProperty,
- BoolProperty,
- PointerProperty,
-)
-from bpy.types import (
- Operator,
- Panel,
- AddonPreferences,
- PropertyGroup,
-)
-
-
-# logging.basicConfig(filename = 'blenderkit.log', level = logging.INFO,
-# format = ' %(asctime)s:%(filename)s:%(funcName)s:%(lineno)d:%(message)s')
-
-
-@persistent
-def scene_load(context):
- ui_props = bpy.context.window_manager.blenderkitUI
- ui_props.assetbar_on = False
- ui_props.turn_off = False
- preferences = bpy.context.preferences.addons['blenderkit'].preferences
- preferences.login_attempt = False
-
-
-@bpy.app.handlers.persistent
-def check_timers_timer():
- ''' checks if all timers are registered regularly. Prevents possible bugs from stopping the addon.'''
- if not bpy.app.background:
- if not bpy.app.timers.is_registered(search.search_timer):
- bpy.app.timers.register(search.search_timer)
- if not bpy.app.timers.is_registered(download.download_timer):
- bpy.app.timers.register(download.download_timer)
- if not (bpy.app.timers.is_registered(tasks_queue.queue_worker)):
- bpy.app.timers.register(tasks_queue.queue_worker)
- if not bpy.app.timers.is_registered(bg_blender.bg_update):
- bpy.app.timers.register(bg_blender.bg_update)
- return 5.0
-
-
-conditions = (
- ('UNSPECIFIED', 'Unspecified', ""),
- ('NEW', 'New', 'Shiny new item'),
- ('USED', 'Used', 'Casually used item'),
- ('OLD', 'Old', 'Old item'),
- ('DESOLATE', 'Desolate', 'Desolate item - dusty & rusty'),
-)
-model_styles = (
- ('REALISTIC', 'Realistic', "Photo realistic model"),
- ('PAINTERLY', 'Painterly', 'Hand painted with visible strokes'),
- ('LOWPOLY', 'Lowpoly', "Lowpoly art -don't mix up with polycount!"),
- ('ANIME', 'Anime', 'Anime style'),
- ('2D_VECTOR', '2D Vector', '2D vector'),
- ('3D_GRAPHICS', '3D Graphics', '3D graphics'),
- ('OTHER', 'Other', 'Other styles'),
-)
-search_model_styles = (
- ('REALISTIC', 'Realistic', "Photo realistic model"),
- ('PAINTERLY', 'Painterly', 'Hand painted with visible strokes'),
- ('LOWPOLY', 'Lowpoly', "Lowpoly art -don't mix up with polycount!"),
- ('ANIME', 'Anime', 'Anime style'),
- ('2D_VECTOR', '2D Vector', '2D vector'),
- ('3D_GRAPHICS', '3D Graphics', '3D graphics'),
- ('OTHER', 'Other', 'Other Style'),
- ('ANY', 'Any', 'Any Style'),
-)
-material_styles = (
- ('REALISTIC', 'Realistic', "Photo realistic model"),
- ('NPR', 'Non photorealistic', 'Hand painted with visible strokes'),
- ('OTHER', 'Other', 'Other style'),
-)
-search_material_styles = (
- ('REALISTIC', 'Realistic', "Photo realistic model"),
- ('NPR', 'Non photorealistic', 'Hand painted with visible strokes'),
- ('ANY', 'Any', 'Any'),
-)
-engines = (
- ('CYCLES', 'Cycles', 'Blender Cycles'),
- ('EEVEE', 'Eevee', 'Blender eevee renderer'),
- ('OCTANE', 'Octane', 'Octane render enginge'),
- ('ARNOLD', 'Arnold', 'Arnold render engine'),
- ('V-RAY', 'V-Ray', 'V-Ray renderer'),
- ('UNREAL', 'Unreal', 'Unreal engine'),
- ('UNITY', 'Unity', 'Unity engine'),
- ('GODOT', 'Godot', 'Godot engine'),
- ('3D-PRINT', '3D printer', 'object can be 3D printed'),
- ('OTHER', 'Other', 'any other engine'),
- ('NONE', 'None', 'no more engine block'),
-)
-pbr_types = (
- ('METALLIC', 'Metallic-Roughness', 'Metallic/Roughness PBR material type'),
- ('SPECULAR', 'Specular Glossy', ''),
-)
-
-mesh_poly_types = (
- ('QUAD', 'quad', ''),
- ('QUAD_DOMINANT', 'quad_dominant', ''),
- ('TRI_DOMINANT', 'tri_dominant', ''),
- ('TRI', 'tri', ''),
- ('NGON', 'ngon_dominant', ''),
- ('OTHER', 'other', ''),
-)
-
-
-
-
-
-
-
-def udate_down_up(self, context):
- """Perform a search if results are empty."""
- s = context.scene
- wm = bpy.context.window_manager
- props = bpy.context.window_manager.blenderkitUI
- if wm.get('search results') == None and props.down_up == 'SEARCH':
- search.search()
-
-
-def switch_search_results(self, context):
- s = bpy.context.scene
- wm = bpy.context.window_manager
- props = bpy.context.window_manager.blenderkitUI
- if props.asset_type == 'MODEL':
- wm['search results'] = wm.get('bkit model search')
- wm['search results orig'] = wm.get('bkit model search orig')
- elif props.asset_type == 'SCENE':
- wm['search results'] = wm.get('bkit scene search')
- wm['search results orig'] = wm.get('bkit scene search orig')
- elif props.asset_type == 'HDR':
- wm['search results'] = wm.get('bkit hdr search')
- wm['search results orig'] = wm.get('bkit hdr search orig')
- elif props.asset_type == 'MATERIAL':
- wm['search results'] = wm.get('bkit material search')
- wm['search results orig'] = wm.get('bkit material search orig')
- elif props.asset_type == 'TEXTURE':
- wm['search results'] = wm.get('bkit texture search')
- wm['search results orig'] = wm.get('bkit texture search orig')
- elif props.asset_type == 'BRUSH':
- wm['search results'] = wm.get('bkit brush search')
- wm['search results orig'] = wm.get('bkit brush search orig')
- if not (context.sculpt_object or context.image_paint_object):
- reports.add_report(
- 'Switch to paint or sculpt mode to search in BlenderKit brushes.')
- # if wm['search results'] == None:
- # wm['search results'] = []
- # if wm['search results orig'] == None:
- # wm['search results orig'] = {'count': 0, 'results': []}
-
- search.load_previews()
- if wm['search results'] == None and props.down_up == 'SEARCH':
- search.search()
-
-
-def asset_type_callback(self, context):
- '''
- Returns
- items for Enum property, depending on the down_up property - BlenderKit is either in search or in upload mode.
-
- '''
- user_preferences = bpy.context.preferences.addons['blenderkit'].preferences
-
- if self.down_up == 'SEARCH':
- items = (
- ('MODEL', 'Models', 'Find models', 'OBJECT_DATAMODE', 0),
- ('MATERIAL', 'Materials', 'Find materials', 'MATERIAL', 2),
- # ('TEXTURE', 'Texture', 'Browse textures', 'TEXTURE', 3),
- ('SCENE', 'Scenes', 'Find scenes', 'SCENE_DATA', 3),
- ('HDR', 'HDRs', 'Find HDRs', 'WORLD', 4),
- ('BRUSH', 'Brushes', 'Find brushes', 'BRUSH_DATA', 5)
- )
- else:
- items = (
- ('MODEL', 'Model', 'Upload a model', 'OBJECT_DATAMODE', 0),
- # ('SCENE', 'SCENE', 'Browse scenes', 'SCENE_DATA', 1),
- ('MATERIAL', 'Material', 'Upload a material', 'MATERIAL', 2),
- # ('TEXTURE', 'Texture', 'Browse textures', 'TEXTURE', 3),
- ('SCENE', 'Scene', 'Upload a scene', 'SCENE_DATA', 3),
- ('HDR', 'HDR', 'Upload a HDR', 'WORLD', 4),
- ('BRUSH', 'Brush', 'Upload a brush', 'BRUSH_DATA', 5)
- )
-
- return items
-
-
-def run_drag_drop_update(self, context):
- if self.drag_init_button:
- ui_props = bpy.context.window_manager.blenderkitUI
- # ctx = utils.get_fake_context(bpy.context)
-
- bpy.ops.view3d.close_popup_button('INVOKE_DEFAULT')
- bpy.ops.view3d.asset_drag_drop('INVOKE_DEFAULT', asset_search_index=ui_props.active_index + ui_props.scroll_offset)
-
- self.drag_init_button = False
-
-
-class BlenderKitUIProps(PropertyGroup):
-
- down_up: EnumProperty(
- name="Download vs Upload",
- items=(
- ('SEARCH', 'Search', 'Activate searching', 'VIEWZOOM', 0),
- ('UPLOAD', 'Upload', 'Activate uploading', 'COPYDOWN', 1),
- # ('RATING', 'Rating', 'Activate rating', 'SOLO_ON', 2)
- ),
- description="BlenderKit",
- default="SEARCH",
- update=udate_down_up
- )
- asset_type: EnumProperty(
- name=" ",
- items=asset_type_callback,
- description="",
- default=None,
- update=switch_search_results
- )
-
- asset_type_fold: BoolProperty(name="Expand asset types", default=False)
- # these aren't actually used ( by now, seems to better use globals in UI module:
- draw_tooltip: BoolProperty(name="Draw Tooltip", default=False)
- addon_update: BoolProperty(name="Should Update Addon", default=False)
- tooltip: StringProperty(
- name="Tooltip",
- description="asset preview info",
- default="")
-
- ui_scale = 1
-
- thumb_size_def = 96
- margin_def = 0
-
- thumb_size: IntProperty(name="Thumbnail Size", default=thumb_size_def, min=-1, max=256)
-
- margin: IntProperty(name="Margin", default=margin_def, min=-1, max=256)
- highlight_margin: IntProperty(name="Highlight Margin", default=int(margin_def / 2), min=-10, max=256)
-
- bar_height: IntProperty(name="Bar Height", default=thumb_size_def + 2 * margin_def, min=-1, max=2048)
- bar_x_offset: IntProperty(name="Bar X Offset", default=40, min=0, max=5000)
- bar_y_offset: IntProperty(name="Bar Y Offset", default=80, min=0, max=5000)
-
- bar_x: IntProperty(name="Bar X", default=100, min=0, max=5000)
- bar_y: IntProperty(name="Bar Y", default=100, min=50, max=5000)
- bar_end: IntProperty(name="Bar End", default=100, min=0, max=5000)
- bar_width: IntProperty(name="Bar Width", default=100, min=0, max=5000)
-
- wcount: IntProperty(name="Width Count", default=10, min=0, max=5000)
- hcount: IntProperty(name="Rows", default=5, min=0, max=5000)
-
- reports_y: IntProperty(name="Reports Y", default=5, min=0, max=5000)
- reports_x: IntProperty(name="Reports X", default=5, min=0, max=5000)
-
- assetbar_on: BoolProperty(name="Assetbar On", default=False)
- turn_off: BoolProperty(name="Turn Off", default=False)
-
- mouse_x: IntProperty(name="Mouse X", default=0)
- mouse_y: IntProperty(name="Mouse Y", default=0)
-
- active_index: IntProperty(name="Active Index", default=-3)
- scroll_offset: IntProperty(name="Scroll Offset", default=0)
- drawoffset: IntProperty(name="Draw Offset", default=0)
-
- dragging: BoolProperty(name="Dragging", default=False)
- drag_init: BoolProperty(name="Drag Initialisation", default=False)
- drag_init_button: BoolProperty(name="Drag Initialisation from button",
- default=False,
- description="Click or drag into scene for download",
- update = run_drag_drop_update)
- drag_length: IntProperty(name="Drag length", default=0)
- draw_drag_image: BoolProperty(name="Draw Drag Image", default=False)
- draw_snapped_bounds: BoolProperty(name="Draw Snapped Bounds", default=False)
-
- snapped_location: FloatVectorProperty(name="Snapped Location", default=(0, 0, 0))
- snapped_bbox_min: FloatVectorProperty(name="Snapped Bbox Min", default=(0, 0, 0))
- snapped_bbox_max: FloatVectorProperty(name="Snapped Bbox Max", default=(0, 0, 0))
- snapped_normal: FloatVectorProperty(name="Snapped Normal", default=(0, 0, 0))
-
- snapped_rotation: FloatVectorProperty(name="Snapped Rotation", default=(0, 0, 0), subtype='QUATERNION')
-
- has_hit: BoolProperty(name="has_hit", default=False)
- thumbnail_image = StringProperty(
- name="Thumbnail Image",
- description="",
- default=paths.get_addon_thumbnail_path('thumbnail_notready.jpg'))
-
- #### rating UI props
- rating_ui_scale = ui_scale
-
- rating_button_on: BoolProperty(name="Rating Button On", default=True)
- rating_menu_on: BoolProperty(name="Rating Menu On", default=False)
- rating_on: BoolProperty(name="Rating on", default=True)
-
- rating_button_width: IntProperty(name="Rating Button Width", default=50 * ui_scale)
- rating_button_height: IntProperty(name="Rating Button Height", default=50 * ui_scale)
-
- rating_x: IntProperty(name="Rating UI X", default=10)
- rating_y: IntProperty(name="Rating UI Y", default=10)
-
- rating_ui_width: IntProperty(name="Rating UI Width", default=rating_ui_scale * 600)
- rating_ui_height: IntProperty(name="Rating UI Heightt", default=rating_ui_scale * 256)
-
- quality_stars_x: IntProperty(name="Rating UI Stars X", default=rating_ui_scale * 90)
- quality_stars_y: IntProperty(name="Rating UI Stars Y", default=rating_ui_scale * 190)
-
- star_size: IntProperty(name="Star Size", default=rating_ui_scale * 50)
-
- workhours_bar_slider_size: IntProperty(name="Workhours Bar Slider Size", default=rating_ui_scale * 30)
-
- workhours_bar_x: IntProperty(name="Workhours Bar X", default=rating_ui_scale * (100 - 15))
- workhours_bar_y: IntProperty(name="Workhours Bar Y", default=rating_ui_scale * (45 - 15))
-
- workhours_bar_x_max: IntProperty(name="Workhours Bar X Max", default=rating_ui_scale * (480 - 15))
-
- dragging_rating: BoolProperty(name="Dragging Rating", default=False)
- dragging_rating_quality: BoolProperty(name="Dragging Rating Quality", default=False)
- dragging_rating_work_hours: BoolProperty(name="Dragging Rating Work Hours", default=False)
- last_rating_time: FloatProperty(name="Last Rating Time", default=0.0)
-
- hdr_upload_image: PointerProperty(name='Upload HDR',
- type=bpy.types.Image,
- description='Pick an image to upload')
-
- # StringProperty(
- # name="Upload HDR",
- # description="Active HDR image to upload",
- # default="")
-
-
-def search_procedural_update(self, context):
- if self.search_procedural in ('PROCEDURAL', 'BOTH'):
- self.search_texture_resolution = False
- search.search_update(self, context)
-
-
-class BlenderKitCommonSearchProps(object):
- # STATES
- is_searching: BoolProperty(name="Searching", description="search is currently running (internal)", default=False)
- is_downloading: BoolProperty(name="Downloading", description="download is currently running (internal)",
- default=False)
- search_done: BoolProperty(name="Search Completed", description="at least one search did run (internal)",
- default=False)
- own_only: BoolProperty(name="My Assets Only", description="Search only for your assets",
- default=False, update=search.search_update)
- use_filters: BoolProperty(name="Filters are on", description="some filters are used",
- default=False)
-
- search_error: BoolProperty(name="Search Error", description="last search had an error", default=False)
- report: StringProperty(
- name="Report",
- description="errors and messages",
- default="")
-
- # TEXTURE RESOLUTION
- search_texture_resolution: BoolProperty(name="Texture Resolution",
- description="Limit texture resolutions",
- default=False,
- update=search.search_update,
- )
- search_texture_resolution_min: IntProperty(name="Min Texture Resolution",
- description="Minimum texture resolution",
- default=256,
- min=0,
- max=32768,
- update=search.search_update,
- )
-
- search_texture_resolution_max: IntProperty(name="Max Texture Resolution",
- description="Maximum texture resolution",
- default=4096,
- min=0,
- max=32768,
- update=search.search_update,
- )
-
- # file_size
- search_file_size: BoolProperty(name="File Size",
- description="Limit file sizes",
- default=False,
- update=search.search_update,
- )
- search_file_size_min: IntProperty(name="Min File Size",
- description="Minimum file size",
- default=0,
- min=0,
- max=2000,
- update=search.search_update,
- )
-
- search_file_size_max: IntProperty(name="Max File Size",
- description="Maximum file size",
- default=500,
- min=0,
- max=2000,
- update=search.search_update,
- )
-
- search_procedural: EnumProperty(
- items=(
- ('BOTH', 'Both', ''),
- ('PROCEDURAL', 'Procedural', ''),
- ('TEXTURE_BASED', 'Texture based', ''),
-
- ),
- default='BOTH',
- description='Search only procedural/texture based assets',
- update=search_procedural_update
- )
-
- search_verification_status: EnumProperty(
- name="Verification status",
- description="Search by verification status",
- items=
- (
- ('ALL', 'All', 'All'),
- ('UPLOADING', 'Uploading', 'Uploading'),
- ('UPLOADED', 'Uploaded', 'Uploaded'),
- ('READY', 'Ready for V.', 'Ready for validation (deprecated since 2.8)'),
- ('VALIDATED', 'Validated', 'Validated'),
- ('ON_HOLD', 'On Hold', 'On Hold'),
- ('REJECTED', 'Rejected', 'Rejected'),
- ('DELETED', 'Deleted', 'Deleted'),
- ),
- default='ALL',
- update=search.search_update,
- )
-
- # resolution download/import settings
- resolution: EnumProperty(
- name="Max resolution",
- description="Cap texture sizes in the file to this resolution",
- items=
- (
- # ('256', '256x256', ''),
- ('512', '512x512', ''),
- ('1024', '1024x1024', ''),
- ('2048', '2048x2048', ''),
- ('4096', '4096x4096', ''),
- ('8192', '8192x8192', ''),
- ('ORIGINAL', 'ORIGINAL FILE', ''),
-
- ),
- default='1024',
- )
- free_only: BoolProperty(name="Free first", description="Show free models first",
- default=False, update=search.search_update)
-
- unpack_files: BoolProperty(name="Unpack Files",
- description="Unpack files after download",
- default=True
- )
-
- unrated_only: BoolProperty(name="Unrated only", description="Show only unrated models",
- default=False, update=search.search_update)
- quality_limit: IntProperty(name="Quality limit",
- description = 'Only show assets with a higher quality',
- default=0, min=0, max=10, update=search.search_update)
-
-
-
-def name_update(self, context):
- ''' checks for name change, because it decides if whole asset has to be re-uploaded. Name is stored in the blend file
- and that's the reason.'''
- utils.name_update(self)
-
-
-def update_free(self, context):
- if self.is_free == 'FULL':
- self.is_free = 'FREE'
- ui_panels.ui_message(title="All BlenderKit materials are free",
- message="Any material uploaded to BlenderKit is free." \
- " However, it can still earn money for the author," \
- " 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)",
- default="")
- asset_base_id: StringProperty(
- name="Asset Base Id",
- description="Unique name of the asset (hidden)",
- default="")
- name: StringProperty(
- name="Name",
- description="Main name of the asset",
- default="",
- update=name_update
- )
- # this is to store name for purpose of checking if name has changed.
- name_old: StringProperty(
- name="Old Name",
- description="Old name of the asset",
- default="",
- )
-
- description: StringProperty(
- name="Description",
- description="Description of the asset",
- default="")
- tags: StringProperty(
- name="Tags",
- description="List of tags, separated by commas (optional)",
- default="",
- update=utils.update_tags
- )
-
- name_changed: BoolProperty(name="Name Changed",
- description="Name has changed, the asset has to be re-uploaded with all data",
- default=False)
-
- pbr: BoolProperty(name="Pure PBR Compatible",
- description="Is compatible with PBR standard. This means only image textures are used with no"
- " procedurals and no color correction, only principled shader is used",
- default=False)
-
- pbr_type: EnumProperty(
- name="PBR Type",
- items=pbr_types,
- description="PBR type",
- default="METALLIC",
- )
- license: EnumProperty(
- items=upload.licenses,
- default='royalty_free',
- description='License. Please read our help for choosing the right licenses',
- )
-
- is_private: EnumProperty(
- name="Thumbnail Style",
- items=(
- ('PRIVATE', 'Private', ""),
- ('PUBLIC', 'Public', "")
- ),
- description="Public assets go into the validation process. \n"
- "Validated assets are visible to all users.\n"
- "Private assets are limited by your plan quota\n"
- "State",
- default="PUBLIC",
- )
-
- is_procedural: BoolProperty(name="Procedural",
- description="Asset is procedural - has no texture",
- default=True
- )
- node_count: IntProperty(name="Node count", description="Total nodes in the asset", default=0)
- texture_count: IntProperty(name="Texture count", description="Total texture count in asset", default=0)
- total_megapixels: IntProperty(name="Megapixels", description="Total megapixels of texture", default=0)
-
- # is_private: BoolProperty(name="Asset is Private",
- # description="If not marked private, your asset will go into the validation process automatically\n"
- # "Private assets are limited by quota",
- # default=False)
-
- is_free: EnumProperty(
- name="Thumbnail Style",
- items=(
- ('FULL', 'Full', "Your asset will be only available for subscribers"),
- ('FREE', 'Free', "You consent you want to release this asset as free for everyone")
- ),
- description="Assets can be in Free or in Full plan. Also free assets generate credits",
- default="FULL",
- )
-
- uploading: BoolProperty(name="Uploading",
- description="True when background process is running",
- default=False,
- update=autothumb.update_upload_material_preview)
- upload_state: StringProperty(
- name="State Of Upload",
- description="bg process reports for upload",
- default='')
-
- has_thumbnail: BoolProperty(name="Has Thumbnail", description="True when thumbnail was checked and loaded",
- default=False)
-
- thumbnail_generating_state: StringProperty(
- name="Thumbnail Generating State",
- description="bg process reports for thumbnail generation",
- default='Please add thumbnail(jpg or png, at least 512x512)')
-
- report: StringProperty(
- name="Missing Upload Properties",
- description="used to write down what's missing",
- default='')
-
- category: EnumProperty(
- name="Category",
- description="main category to put into",
- items=categories.get_category_enums,
- update=categories.update_category_enums
- )
- subcategory: EnumProperty(
- name="Subcategory",
- description="Subcategory to put into",
- items=categories.get_subcategory_enums,
- update=categories.update_subcategory_enums
- )
- subcategory1: EnumProperty(
- name="Subcategory lvl2",
- description="Subcategory to put into",
- items=categories.get_subcategory1_enums
- )
-
-
-class BlenderKitRatingProps(PropertyGroup):
- rating_quality: IntProperty(name="Quality",
- description="quality of the material",
- default=0,
- min=-1, max=10,
- update=ratings_utils.update_ratings_quality)
-
- # the following enum is only to ease interaction - enums support 'drag over' and enable to draw the stars easily.
- rating_quality_ui: EnumProperty(name='rating_quality_ui',
- items=ratings_utils.stars_enum_callback,
- description='Rating stars 0 - 10',
- default=None,
- update=ratings_utils.update_quality_ui,
- )
-
- rating_work_hours: FloatProperty(name="Work Hours",
- description="How many hours did this work take?",
- default=0.00,
- min=0.0, max=150, update=ratings_utils.update_ratings_work_hours
- )
-
- # rating_complexity: IntProperty(name="Complexity",
- # description="Complexity is a number estimating how much work was spent on the asset.aaa",
- # default=0, min=0, max=10)
- # rating_virtual_price: FloatProperty(name="Virtual Price",
- # description="How much would you pay for this object if buing it?",
- # default=0, min=0, max=10000)
- rating_problems: StringProperty(
- name="Problems",
- description="Problems found/ why did you take points down - this will be available for the author"
- " As short as possible",
- default="",
- )
- rating_compliments: StringProperty(
- name="Compliments",
- description="Comliments - let the author know you like his work! "
- " As short as possible",
- default="",
- )
-
-
-class BlenderKitMaterialSearchProps(PropertyGroup, BlenderKitCommonSearchProps):
- search_keywords: StringProperty(
- name="Search",
- description="Search for these keywords",
- default="",
- update=search.search_update
- )
- search_style: EnumProperty(
- name="Style",
- items=search_material_styles,
- description="Style of material",
- default="ANY",
- update=search.search_update,
- )
- search_style_other: StringProperty(
- name="Style Other",
- description="Style not in the list",
- default="",
- update=search.search_update,
- )
- search_engine: EnumProperty(
- name='Engine',
- items=engines,
- default='NONE',
- description='Output engine',
- update=search.search_update,
- )
- search_engine_other: StringProperty(
- name="Engine",
- description="engine not specified by addon",
- default="",
- update=search.search_update,
- )
- append_method: EnumProperty(
- name="Import Method",
- items=(
- ('LINK', 'Link', "Link Material - will be in external file and can't be directly edited"),
- ('APPEND', 'Append', 'Append if you need to edit the material'),
- ),
- description="Appended materials are editable in your scene. Linked assets are saved in original files, "
- "aren't editable directly, but also don't increase your file size",
- default="APPEND"
- )
- automap: BoolProperty(name="Auto-Map",
- description="reset object texture space and also add automatically a cube mapped UV "
- "to the object. \n this allows most materials to apply instantly to any mesh",
- default=True)
-
-
-class BlenderKitMaterialUploadProps(PropertyGroup, BlenderKitCommonUploadProps):
- style: EnumProperty(
- name="Style",
- items=material_styles,
- description="Style of material",
- default="REALISTIC",
- )
- style_other: StringProperty(
- name="Style Other",
- description="Style not in the list",
- default="",
- )
- engine: EnumProperty(
- name='Engine',
- items=engines,
- default='CYCLES',
- description='Output engine',
- )
- engine_other: StringProperty(
- name="Engine Other",
- description="engine not specified by addon",
- default="",
- )
-
- shaders: StringProperty(
- name="Shaders Used",
- description="shaders used in asset, autofilled",
- default="",
- )
-
- is_free: EnumProperty(
- name="Thumbnail Style",
- items=(
- ('FULL', 'Full', "Your asset will be only available for subscribers."),
- ('FREE', 'Free', "You consent you want to release this asset as free for everyone.")
- ),
- description="Assets can be in Free or in Full plan. Also free assets generate credits. \n"
- "All BlenderKit materials are free",
- default="FREE",
- update=update_free
- )
-
-
-
- uv: BoolProperty(name="Needs UV", description="needs an UV set", default=False)
- # printable_3d : BoolProperty( name = "3d printable", description = "can be 3d printed", default = False)
- animated: BoolProperty(name="Animated", description="is animated", default=False)
- texture_resolution_min: IntProperty(name="Texture Resolution Min", description="texture resolution minimum",
- default=0)
- texture_resolution_max: IntProperty(name="Texture Resolution Max", description="texture resolution maximum",
- default=0)
-
- texture_size_meters: FloatProperty(name="Texture Size in Meters", description="Size of texture in real world units",
- default=1.0, min=0)
-
- 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=autothumb.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",
- )
-
- thumbnail: StringProperty(
- name="Thumbnail",
- description="Thumbnail path - 512x512 .jpg image, rendered with cycles.\n"
- "Only standard BlenderKit previews will be accepted.\n"
- "Only exception are special effects like fire or similar",
- subtype='FILE_PATH',
- default="",
- update=autothumb.update_upload_material_preview)
-
- is_generating_thumbnail: BoolProperty(name="Generating Thumbnail",
- description="True when background process is running", default=False,
- update=autothumb.update_upload_material_preview)
-
-
-class BlenderKitTextureUploadProps(PropertyGroup, BlenderKitCommonUploadProps):
- style: EnumProperty(
- name="Style",
- items=material_styles,
- description="Style of texture",
- default="REALISTIC",
- )
- style_other: StringProperty(
- name="Style Other",
- description="Style not in the list",
- default="",
- )
-
- pbr: BoolProperty(name="PBR Compatible", description="Is compatible with PBR standard", default=False)
-
- # printable_3d : BoolProperty( name = "3d printable", description = "can be 3d printed", default = False)
- animated: BoolProperty(name="Animated", description="is animated", default=False)
- resolution: IntProperty(name="Texture Resolution", description="texture resolution", default=0)
-
-
-class BlenderKitBrushSearchProps(PropertyGroup, BlenderKitCommonSearchProps):
- search_keywords: StringProperty(
- name="Search",
- description="Search for these keywords",
- default="",
- update=search.search_update
- )
-
-
-class BlenderKitHDRUploadProps(PropertyGroup, BlenderKitCommonUploadProps):
- texture_resolution_max: IntProperty(name="Texture Resolution Max", description="texture resolution maximum",
- default=0)
- evs_cap: IntProperty(name="EV cap", description="EVs dynamic range",
- default=0)
- true_hdr: BoolProperty(name="Real HDR", description="Image has High dynamic range.",default=False)
-
-
-class BlenderKitBrushUploadProps(PropertyGroup, BlenderKitCommonUploadProps):
- mode: EnumProperty(
- name="Mode",
- items=(
- ("IMAGE", "Texture paint", "Texture brush"),
- ("SCULPT", "Sculpt", "Sculpt brush"),
- ("VERTEX", "Vertex paint", "Vertex paint brush"),
- ("WEIGHT", "Weight paint", "Weight paint brush"),
- ),
- description="Mode where the brush works",
- default="SCULPT",
- )
-
-
-# upload properties
-class BlenderKitModelUploadProps(PropertyGroup, BlenderKitCommonUploadProps):
- style: EnumProperty(
- name="Style",
- items=model_styles,
- description="Style of asset",
- default="REALISTIC",
- )
- style_other: StringProperty(
- name="Style Other",
- description="Style not in the list",
- default="",
- )
- engine: EnumProperty(
- name='Engine',
- items=engines,
- default='CYCLES',
- description='Output engine',
- )
-
- production_level: EnumProperty(
- name='Production Level',
- items=(
- ('FINISHED', 'Finished', 'Render or animation ready asset'),
- ('TEMPLATE', 'Template', 'Asset intended to help in creation of something else'),
- ),
- default='FINISHED',
- description='Production state of the asset. \n'
- 'Templates should be tools to finish certain tasks, like a thumbnailer scene, \n '
- 'finished mesh topology as start for modelling or others',
- )
-
- engine_other: StringProperty(
- name="Engine",
- description="engine not specified by addon",
- default="",
- )
-
- engine1: EnumProperty(
- name='2nd Engine',
- items=engines,
- default='NONE',
- description='Output engine',
- )
- engine2: EnumProperty(
- name='3rd Engine',
- items=engines,
- default='NONE',
- description='Output engine',
- )
- engine3: EnumProperty(
- name='4th Engine',
- items=engines,
- default='NONE',
- description='Output engine',
- )
-
- manufacturer: StringProperty(
- name="Manufacturer",
- description="Manufacturer, company making a design piece or product. Not you",
- default="",
- )
-
- designer: StringProperty(
- name="Designer",
- description="Author of the original design piece depicted. Usually not you",
- default="",
- )
-
- design_collection: StringProperty(
- name="Design Collection",
- description="Fill if this piece is part of a real world design collection",
- default="",
- )
-
- design_variant: StringProperty(
- name="Variant",
- description="Colour or material variant of the product",
- default="",
- )
-
- thumbnail: StringProperty(
- name="Thumbnail",
- description="Thumbnail path - 512x512 .jpg\n"
- "Rendered with cycles",
-
- subtype='FILE_PATH',
- default="",
- update=autothumb.update_upload_model_preview)
-
- 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=autothumb.thumbnail_angles,
- default='DEFAULT',
- description='thumbnailer angle',
- )
-
- thumbnail_snap_to: EnumProperty(
- name='Model Snaps To:',
- 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=autothumb.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)
-
- use_design_year: BoolProperty(name="Use Design Year",
- description="When this thing came into world for the first time\n"
- " e.g. for dinosaur, you set -240 million years ;) ",
- default=False)
- design_year: IntProperty(name="Design Year", description="when was this item designed", default=1960)
- # use_age : BoolProperty( name = "use item age", description = "use item age", default = False)
- condition: EnumProperty(
- items=conditions,
- default='UNSPECIFIED',
- description='age of the object',
- )
-
- adult: BoolProperty(name="Adult Content", description="adult content", default=False)
-
- work_hours: FloatProperty(name="Work Hours", description="How long did it take you to finish the asset?",
- default=0.0, min=0.0, max=8760)
-
- modifiers: StringProperty(
- name="Modifiers Used",
- description="if you need specific modifiers, autofilled",
- default="",
- )
-
- materials: StringProperty(
- name="Material Names",
- description="names of materials in the file, autofilled",
- default="",
- )
- shaders: StringProperty(
- name="Shaders Used",
- description="shaders used in asset, autofilled",
- default="",
- )
-
- dimensions: FloatVectorProperty(
- name="Dimensions",
- description="dimensions of the whole asset hierarchy",
- default=(0, 0, 0),
- )
- bbox_min: FloatVectorProperty(
- name="Bbox Min",
- description="dimensions of the whole asset hierarchy",
- default=(-.25, -.25, 0),
- )
- bbox_max: FloatVectorProperty(
- name="Bbox Max",
- description="dimensions of the whole asset hierarchy",
- default=(.25, .25, .5),
- )
-
- texture_resolution_min: IntProperty(name="Texture Resolution Min",
- description="texture resolution min, autofilled", default=0)
- texture_resolution_max: IntProperty(name="Texture Resolution Max",
- description="texture resolution max, autofilled", default=0)
-
- pbr: BoolProperty(name="PBR Compatible", description="Is compatible with PBR standard", default=False)
-
- uv: BoolProperty(name="Has UV", description="has an UV set", default=False)
- # printable_3d : BoolProperty( name = "3d printable", description = "can be 3d printed", default = False)
- animated: BoolProperty(name="Animated", description="is animated", default=False)
- face_count: IntProperty(name="Face count", description="face count, autofilled", default=0)
- face_count_render: IntProperty(name="Render Face Count", description="render face count, autofilled", default=0)
-
- object_count: IntProperty(name="Number of Objects", description="how many objects are in the asset, autofilled",
- default=0)
- mesh_poly_type: EnumProperty(
- name='Dominant Poly Type',
- items=mesh_poly_types,
- default='OTHER',
- description='',
- )
-
- manifold: BoolProperty(name="Manifold", description="asset is manifold, autofilled", default=False)
-
- rig: BoolProperty(name="Rig", description="asset is rigged, autofilled", default=False)
- simulation: BoolProperty(name="Simulation", description="asset uses simulation, autofilled", default=False)
- '''
- filepath : StringProperty(
- name="Filepath",
- description="file path",
- default="",
- )
- '''
-
- # THUMBNAIL STATES
- is_generating_thumbnail: BoolProperty(name="Generating Thumbnail",
- description="True when background process is running", default=False,
- update=autothumb.update_upload_model_preview)
-
- has_autotags: BoolProperty(name="Has Autotagging Done", description="True when autotagging done", default=False)
-
-
-class BlenderKitSceneUploadProps(PropertyGroup, BlenderKitCommonUploadProps):
- style: EnumProperty(
- name="Style",
- items=model_styles,
- description="Style of asset",
- default="REALISTIC",
- )
- style_other: StringProperty(
- name="Style Other",
- description="Style not in the list",
- default="",
- )
- engine: EnumProperty(
- name='Engine',
- items=engines,
- default='CYCLES',
- description='Output engine',
- )
-
- production_level: EnumProperty(
- name='Production Level',
- items=(
- ('FINISHED', 'Finished', 'Render or animation ready asset'),
- ('TEMPLATE', 'Template', 'Asset intended to help in creation of something else'),
- ),
- default='FINISHED',
- description='Production state of the asset, \n also template should be actually finished, \n'
- 'just the nature of it can be a template, like a thumbnailer scene, \n '
- 'finished mesh topology as start for modelling or similar',
- )
-
- engine_other: StringProperty(
- name="Engine",
- description="engine not specified by addon",
- default="",
- )
-
- engine1: EnumProperty(
- name='2nd Engine',
- items=engines,
- default='NONE',
- description='Output engine',
- )
- engine2: EnumProperty(
- name='3rd Engine',
- items=engines,
- default='NONE',
- description='Output engine',
- )
- engine3: EnumProperty(
- name='4th Engine',
- items=engines,
- default='NONE',
- description='Output engine',
- )
-
- thumbnail: StringProperty(
- name="Thumbnail",
- description="Thumbnail path - 512x512 .jpg\n"
- "Rendered with cycles",
- subtype='FILE_PATH',
- default="",
- update=autothumb.update_upload_scene_preview)
-
- use_design_year: BoolProperty(name="Use Design Year",
- description="When this thing came into world for the first time\n"
- " e.g. for dinosaur, you set -240 million years ;) ",
- default=False)
- design_year: IntProperty(name="Design Year", description="when was this item designed", default=1960)
- # use_age : BoolProperty( name = "use item age", description = "use item age", default = False)
- condition: EnumProperty(
- items=conditions,
- default='UNSPECIFIED',
- description='age of the object',
- )
-
- adult: BoolProperty(name="Adult Content", description="adult content", default=False)
-
- work_hours: FloatProperty(name="Work Hours", description="How long did it take you to finish the asset?",
- default=0.0, min=0.0, max=8760)
-
- modifiers: StringProperty(
- name="Modifiers Used",
- description="if you need specific modifiers, autofilled",
- default="",
- )
-
- materials: StringProperty(
- name="Material Names",
- description="names of materials in the file, autofilled",
- default="",
- )
- shaders: StringProperty(
- name="Shaders Used",
- description="shaders used in asset, autofilled",
- default="",
- )
-
- dimensions: FloatVectorProperty(
- name="Dimensions",
- description="dimensions of the whole asset hierarchy",
- default=(0, 0, 0),
- )
- bbox_min: FloatVectorProperty(
- name="Dimensions",
- description="dimensions of the whole asset hierarchy",
- default=(-.25, -.25, 0),
- )
- bbox_max: FloatVectorProperty(
- name="Dimensions",
- description="dimensions of the whole asset hierarchy",
- default=(.25, .25, .5),
- )
-
- texture_resolution_min: IntProperty(name="Texture Resolution Min",
- description="texture resolution min, autofilled", default=0)
- texture_resolution_max: IntProperty(name="Texture Resolution Max",
- description="texture resolution max, autofilled", default=0)
-
- pbr: BoolProperty(name="PBR Compatible", description="Is compatible with PBR standard", default=False)
-
- uv: BoolProperty(name="Has UV", description="has an UV set", default=False)
- # printable_3d : BoolProperty( name = "3d printable", description = "can be 3d printed", default = False)
- animated: BoolProperty(name="Animated", description="is animated", default=False)
- face_count: IntProperty(name="Face Count", description="face count, autofilled", default=0)
- face_count_render: IntProperty(name="Render Face Count", description="render face count, autofilled", default=0)
-
- object_count: IntProperty(name="Number of Objects", description="how many objects are in the asset, autofilled",
- default=0)
- mesh_poly_type: EnumProperty(
- name='Dominant Poly Type',
- items=mesh_poly_types,
- default='OTHER',
- description='',
- )
-
- rig: BoolProperty(name="Rig", description="asset is rigged, autofilled", default=False)
- simulation: BoolProperty(name="Simulation", description="asset uses simulation, autofilled", default=False)
-
- # THUMBNAIL STATES
- is_generating_thumbnail: BoolProperty(name="Generating Thumbnail",
- description="True when background process is running", default=False,
- update=autothumb.update_upload_model_preview)
-
- has_autotags: BoolProperty(name="Has Autotagging Done", description="True when autotagging done", default=False)
-
-
-class BlenderKitModelSearchProps(PropertyGroup, BlenderKitCommonSearchProps):
- search_keywords: StringProperty(
- name="Search",
- description="Search for these keywords",
- default="",
- update=search.search_update
- )
- search_style: EnumProperty(
- name="Style",
- items=search_model_styles,
- description="Keywords defining style (realistic, painted, polygonal, other)",
- default="ANY",
- update=search.search_update
- )
- search_style_other: StringProperty(
- name="Style",
- description="Search style - other",
- default="",
- update=search.search_update
- )
- search_engine: EnumProperty(
- items=engines,
- default='CYCLES',
- description='Output engine',
- update=search.search_update
- )
- search_engine_other: StringProperty(
- name="Engine",
- description="Engine not specified by addon",
- default="",
- update=search.search_update
- )
-
- # CONDITION
- search_condition: EnumProperty(
- items=conditions,
- default='UNSPECIFIED',
- description='Condition of the object',
- update=search.search_update
- )
-
- search_adult: BoolProperty(
- name="Adult Content",
- description="You're adult and agree with searching adult content",
- default=False,
- update=search.search_update
- )
-
- # DESIGN YEAR
- search_design_year: BoolProperty(name="Sesigned in Year",
- description="When the object was approximately designed. \n"
- "Useful for search of historical or future objects",
- default=False,
- update=search.search_update,
- )
-
- search_design_year_min: IntProperty(name="Minimum Design Year",
- description="Minimum design year",
- default=1950, min=-100000000, max=1000000000,
- update=search.search_update,
- )
-
- search_design_year_max: IntProperty(name="Maximum Design Year",
- description="Maximum design year",
- default=2017,
- min=0,
- max=10000000,
- update=search.search_update,
- )
-
- # POLYCOUNT
- search_polycount: BoolProperty(name="Use Polycount",
- description="Limit polycount",
- default=False,
- update=search.search_update, )
-
- search_polycount_min: IntProperty(name="Min Polycount",
- description="Minimum poly count",
- default=0,
- min=0,
- max=100000000,
- update=search.search_update, )
-
- search_polycount_max: IntProperty(name="Max Polycount",
- description="Maximum poly count",
- default=100000000,
- min=0,
- max=100000000,
- update=search.search_update,
- )
-
- append_method: EnumProperty(
- name="Import Method",
- items=(
- ('LINK_COLLECTION', 'Link', 'Link Collection'),
- ('APPEND_OBJECTS', 'Append', 'Append as Objects'),
- ),
- description="Appended objects are editable in your scene. Linked assets are saved in original files, "
- "aren't editable but also don't increase your file size",
- default="APPEND_OBJECTS"
- )
- append_link: EnumProperty(
- name="How to Attach",
- items=(
- ('LINK', 'Link', ''),
- ('APPEND', 'Append', ''),
- ),
- description="choose if the assets will be linked or appended",
- default="LINK"
- )
- import_as: EnumProperty(
- name="Import as",
- items=(
- ('GROUP', 'group', ''),
- ('INDIVIDUAL', 'objects', ''),
-
- ),
- description="choose if the assets will be linked or appended",
- default="GROUP"
- )
- randomize_rotation: BoolProperty(name='Randomize Rotation',
- description="randomize rotation at placement",
- default=False)
- randomize_rotation_amount: FloatProperty(name="Randomization Max Angle",
- description="maximum angle for random rotation",
- default=math.pi / 36,
- min=0,
- max=2 * math.pi,
- subtype='ANGLE')
- offset_rotation_amount: FloatProperty(name="Offset Rotation",
- description="offset rotation, hidden prop",
- default=0,
- min=0,
- max=360,
- subtype='ANGLE')
- offset_rotation_step: FloatProperty(name="Offset Rotation Step",
- description="offset rotation, hidden prop",
- default=math.pi / 2,
- min=0,
- max=180,
- subtype='ANGLE')
-
- perpendicular_snap: BoolProperty(name='Perpendicular snap',
- description="Limit snapping that is close to perpendicular angles to be perpendicular",
- default=True)
-
- perpendicular_snap_threshold: FloatProperty(name="Threshold",
- description="Limit perpendicular snap to be below these values",
- default=.25,
- min=0,
- max=.5,
- )
-
-
-class BlenderKitHDRSearchProps(PropertyGroup, BlenderKitCommonSearchProps):
- search_keywords: StringProperty(
- name="Search",
- description="Search for these keywords",
- default="",
- update=search.search_update
- )
-
- true_hdr: BoolProperty(
- name='Real HDRs only',
- description='Search only for real HDRs, this means images that have a range higher than 0-1 in their pixels.',
- default=True,
- update=search.search_update
- )
-
-
-class BlenderKitSceneSearchProps(PropertyGroup, BlenderKitCommonSearchProps):
- search_keywords: StringProperty(
- name="Search",
- description="Search for these keywords",
- default="",
- update=search.search_update
- )
- search_style: EnumProperty(
- name="Style",
- items=search_model_styles,
- description="Restrict search for style",
- default="ANY",
- update=search.search_update
- )
- search_style_other: StringProperty(
- name="Style",
- description="Search style - other",
- default="",
- update=search.search_update
- )
- search_engine: EnumProperty(
- items=engines,
- default='CYCLES',
- description='Output engine',
- update=search.search_update
- )
- search_engine_other: StringProperty(
- name="Engine",
- description="Engine not specified by addon",
- default="",
- update=search.search_update
- )
- append_link: EnumProperty(
- name="Append or link",
- items=(
- ('LINK', 'Link', ''),
- ('APPEND', 'Append', ''),
- ),
- description="choose if the scene will be linked or appended",
- default="APPEND"
- )
- switch_after_append: BoolProperty(
- name='Switch to scene after download',
- default=True
- )
-
-
-def fix_subdir(self, context):
- '''Fixes project subdicrectory settings if people input invalid path.'''
-
- # pp = pathlib.PurePath(self.project_subdir)
- pp = self.project_subdir[:]
- pp = pp.replace('\\', '')
- pp = pp.replace('/', '')
- pp = pp.replace(':', '')
- pp = '//' + pp
- if self.project_subdir != pp:
- self.project_subdir = pp
-
- ui_panels.ui_message(title="Fixed to relative path",
- message="This path should be always realative.\n" \
- " It's a directory BlenderKit creates where your .blend is \n " \
- "and uses it for storing assets.")
-
-
-class BlenderKitAddonPreferences(AddonPreferences):
- # this must match the addon name, use '__package__'
- # when defining this in a submodule of a python package.
- bl_idname = __name__
-
- default_global_dict = paths.default_global_dict()
-
- enable_oauth = True
-
- api_key: StringProperty(
- name="BlenderKit API Key",
- description="Your blenderkit API Key. Get it from your page on the website",
- default="",
- subtype="PASSWORD",
- update=utils.save_prefs
- )
-
- api_key_refresh: StringProperty(
- name="BlenderKit refresh API Key",
- description="API key used to refresh the token regularly",
- default="",
- subtype="PASSWORD",
- )
-
- api_key_timeout: IntProperty(
- name='api key timeout',
- description='time where the api key will need to be refreshed',
- default=0,
- )
-
- api_key_life: IntProperty(
- name='api key life time',
- description='maximum lifetime of the api key, in seconds',
- default=0,
- )
-
- refresh_in_progress: BoolProperty(
- name="Api key refresh in progress",
- description="Api key is currently being refreshed. Don't refresh it again",
- default=False
- )
-
- login_attempt: BoolProperty(
- name="Login/Signup attempt",
- description="When this is on, BlenderKit is trying to connect and login",
- default=False
- )
-
- show_on_start: BoolProperty(
- name="Show assetbar when starting blender",
- description="Show assetbar when starting blender",
- default=False
- )
-
- tips_on_start: BoolProperty(
- name="Show tips when starting blender",
- description="Show tips when starting blender",
- default=True
- )
-
- search_in_header: BoolProperty(
- name="Show BlenderKit search in 3D view header",
- description="Show BlenderKit search in 3D view header",
- default=True
- )
-
- global_dir: StringProperty(
- name="Global Files Directory",
- description="Global storage for your assets, will use subdirectories for the contents",
- subtype='DIR_PATH',
- default=default_global_dict,
- update=utils.save_prefs
- )
-
- project_subdir: StringProperty(
- name="Project Assets Subdirectory",
- description="where data will be stored for individual projects",
- # subtype='DIR_PATH',
- default="//assets",
- update=fix_subdir
- )
-
- directory_behaviour: EnumProperty(
- name="Use Directories",
- items=(
- ('BOTH', 'Global and subdir',
- 'store files both in global lib and subdirectory of current project. '
- 'Warning - each file can be many times on your harddrive, but helps you keep your projects in one piece'),
- ('GLOBAL', 'Global',
- "store downloaded files only in global directory. \n "
- "This can bring problems when moving your projects, \n"
- "since assets won't be in subdirectory of current project"),
- ('LOCAL', 'Local',
- 'store downloaded files only in local directory.\n'
- ' This can use more bandwidth when you reuse assets in different projects. ')
-
- ),
- description="Which directories will be used for storing downloaded data",
- default="BOTH",
- )
- thumbnail_use_gpu: BoolProperty(
- name="Use GPU for Thumbnails Rendering (For assets upload)",
- description="By default this is off so you can continue your work without any lag",
- default=False
- )
-
- panel_behaviour: EnumProperty(
- name="Panels Locations",
- items=(
- ('BOTH', 'Both Types',
- ''),
- ('UNIFIED', 'Unified 3D View Panel',
- ""),
- ('LOCAL', 'Relative to Data',
- '')
-
- ),
- description="Which directories will be used for storing downloaded data",
- default="UNIFIED",
- )
-
- max_assetbar_rows: IntProperty(name="Max Assetbar Rows",
- description="max rows of assetbar in the 3D view",
- default=1,
- min=1,
- max=20)
-
- thumb_size: IntProperty(name="Assetbar thumbnail Size", default=96, min=-1, max=256)
-
- #counts usages so it can encourage user after some time to do things.
- asset_counter: IntProperty(name="Usage Counter",
- description="Counts usages so it asks for registration only after reaching a limit",
- default=0,
- min=0,
- max=20000)
-
- notifications_counter: IntProperty(
- name='Notifications Counter',
- description='count users notifications',
- default=0,
- )
- # this is now made obsolete by the new popup upon registration -ensures the user knows about the first search.
- # first_run: BoolProperty(
- # name="First run",
- # description="Detects if addon was already registered/run.",
- # default=True,
- # update=utils.save_prefs
- # )
-
- use_timers: BoolProperty(
- name="Use timers",
- description="Use timers for BlenderKit. Usefull for debugging since timers seem to be unstable",
- default=True,
- update=utils.save_prefs
- )
-
- # single_timer: BoolProperty(
- # name="Use timers",
- # description="Use timers for BlenderKit. Usefull for debugging since timers seem to be unstable",
- # default=True,
- # update=utils.save_prefs
- # )
-
- experimental_features: BoolProperty(
- name="Enable experimental features",
- description="Enable all experimental features of BlenderKit. Use at your own risk",
- default=False,
- update=utils.save_prefs
- )
-
- categories_fix: BoolProperty(
- name="Enable category fixing mode",
- description="Enable category fixing mode",
- default=False,
- update=utils.save_prefs
- )
-
- # allow_proximity : BoolProperty(
- # name="allow proximity data reports",
- # description="This sends anonymized proximity data \n \
- # and allows us to make relations between database objects \n \
- # without user interaction",
- # default=False
- # )
-
- def draw(self, context):
- layout = self.layout
- layout.prop(self, "show_on_start")
-
- if self.api_key.strip() == '':
- if self.enable_oauth:
- ui_panels.draw_login_buttons(layout)
- else:
- op = layout.operator("wm.url_open", text="Register online and get your API Key",
- icon='QUESTION')
- op.url = paths.BLENDERKIT_SIGNUP_URL
- else:
- if self.enable_oauth:
- layout.operator("wm.blenderkit_logout", text="Logout",
- icon='URL')
-
- # if not self.enable_oauth:
- layout.prop(self, "api_key", text='Your API Key')
- # layout.label(text='After you paste API Key, categories are downloaded, so blender will freeze for a few seconds.')
- layout.prop(self, "global_dir")
- layout.prop(self, "project_subdir")
- # layout.prop(self, "temp_dir")
- layout.prop(self, "directory_behaviour")
- # layout.prop(self, "allow_proximity")
- # layout.prop(self, "panel_behaviour")
- layout.prop(self, "thumb_size")
- layout.prop(self, "max_assetbar_rows")
- layout.prop(self, "tips_on_start")
- layout.prop(self, "search_in_header")
- layout.prop(self, "thumbnail_use_gpu")
-
- if bpy.context.preferences.view.show_developer_ui:
- layout.prop(self, "use_timers")
- layout.prop(self, "experimental_features")
- layout.prop(self, "categories_fix")
-
-
-# # @bpy.app.handlers.persistent
-# def blenderkit_timer():
-#
-#
-# if not user_preferences.use_timers:
-# search.search_timer()
-# download.download_timer()
-# tasks_queue.queue_worker()
-# bg_blender.bg_update()
-# registration
-classes = (
-
- BlenderKitAddonPreferences,
- BlenderKitUIProps,
-
- BlenderKitModelSearchProps,
- BlenderKitModelUploadProps,
-
- BlenderKitSceneSearchProps,
- BlenderKitSceneUploadProps,
-
- BlenderKitHDRSearchProps,
- BlenderKitHDRUploadProps,
-
- BlenderKitMaterialUploadProps,
- BlenderKitMaterialSearchProps,
-
- BlenderKitTextureUploadProps,
-
- BlenderKitBrushSearchProps,
- BlenderKitBrushUploadProps,
-
- BlenderKitRatingProps,
-)
-
-
-
-def register():
- for cls in classes:
- bpy.utils.register_class(cls)
-
- bpy.types.WindowManager.blenderkitUI = PointerProperty(
- type=BlenderKitUIProps)
-
- # MODELS
- bpy.types.WindowManager.blenderkit_models = PointerProperty(
- type=BlenderKitModelSearchProps)
- bpy.types.Object.blenderkit = PointerProperty( # for uploads, not now...
- type=BlenderKitModelUploadProps)
- bpy.types.Object.bkit_ratings = PointerProperty( # for uploads, not now...
- type=BlenderKitRatingProps)
-
- # SCENES
- bpy.types.WindowManager.blenderkit_scene = PointerProperty(
- type=BlenderKitSceneSearchProps)
- bpy.types.Scene.blenderkit = PointerProperty( # for uploads, not now...
- type=BlenderKitSceneUploadProps)
- bpy.types.Scene.bkit_ratings = PointerProperty( # for uploads, not now...
- type=BlenderKitRatingProps)
-
- # HDRs
- bpy.types.WindowManager.blenderkit_HDR = PointerProperty(
- type=BlenderKitHDRSearchProps)
- bpy.types.Image.blenderkit = PointerProperty( # for uploads, not now...
- type=BlenderKitHDRUploadProps)
- bpy.types.Image.bkit_ratings = PointerProperty( # for uploads, not now...
- type=BlenderKitRatingProps)
-
- # MATERIALS
- bpy.types.WindowManager.blenderkit_mat = PointerProperty(
- type=BlenderKitMaterialSearchProps)
- bpy.types.Material.blenderkit = PointerProperty( # for uploads, not now...
- type=BlenderKitMaterialUploadProps)
- bpy.types.Material.bkit_ratings = PointerProperty( # for uploads, not now...
- type=BlenderKitRatingProps)
-
- # BRUSHES
- bpy.types.WindowManager.blenderkit_brush = PointerProperty(
- type=BlenderKitBrushSearchProps)
- bpy.types.Brush.blenderkit = PointerProperty( # for uploads, not now...
- type=BlenderKitBrushUploadProps)
- bpy.types.Brush.bkit_ratings = PointerProperty( # for uploads, not now...
- type=BlenderKitRatingProps)
-
- search.register_search()
- asset_inspector.register_asset_inspector()
- download.register_download()
- upload.register_upload()
- ratings.register_ratings()
- autothumb.register_thumbnailer()
- ui.register_ui()
- icons.register_icons()
- ui_panels.register_ui_panels()
- bg_blender.register()
- utils.load_prefs()
- overrides.register_overrides()
- bkit_oauth.register()
- tasks_queue.register()
- asset_bar_op.register()
-
- user_preferences = bpy.context.preferences.addons['blenderkit'].preferences
- if user_preferences.use_timers and not bpy.app.background:
- bpy.app.timers.register(check_timers_timer, persistent=True)
-
- bpy.app.handlers.load_post.append(scene_load)
- # detect if the user just enabled the addon in preferences, thus enable to run
- for w in bpy.context.window_manager.windows:
- for a in w.screen.areas:
- if a.type == 'PREFERENCES':
- tasks_queue.add_task((bpy.ops.wm.blenderkit_welcome, ('INVOKE_DEFAULT',)), fake_context=True,
- fake_context_area='PREFERENCES')
- #save preferences after manually enabling the addon
- tasks_queue.add_task((bpy.ops.wm.save_userpref, ()), fake_context=False,)
-
-
-def unregister():
- if bpy.app.timers.is_registered(check_timers_timer):
- bpy.app.timers.unregister(check_timers_timer)
- ui_panels.unregister_ui_panels()
- ui.unregister_ui()
-
- icons.unregister_icons()
- search.unregister_search()
- asset_inspector.unregister_asset_inspector()
- download.unregister_download()
- upload.unregister_upload()
- ratings.unregister_ratings()
- autothumb.unregister_thumbnailer()
- bg_blender.unregister()
- overrides.unregister_overrides()
- bkit_oauth.unregister()
- tasks_queue.unregister()
- asset_bar_op.unregister()
-
- del bpy.types.WindowManager.blenderkit_models
- del bpy.types.WindowManager.blenderkit_scene
- del bpy.types.WindowManager.blenderkit_HDR
- del bpy.types.WindowManager.blenderkit_brush
- del bpy.types.WindowManager.blenderkit_mat
-
- del bpy.types.Scene.blenderkit
- del bpy.types.Object.blenderkit
- del bpy.types.Image.blenderkit
- del bpy.types.Material.blenderkit
- del bpy.types.Brush.blenderkit
-
- for cls in classes:
- bpy.utils.unregister_class(cls)
-
- bpy.app.handlers.load_post.remove(scene_load)
diff --git a/blenderkit/append_link.py b/blenderkit/append_link.py
deleted file mode 100644
index 6fe710be..00000000
--- a/blenderkit/append_link.py
+++ /dev/null
@@ -1,403 +0,0 @@
-# ##### 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 utils, ui
-
-import bpy
-import uuid
-
-
-def append_brush(file_name, brushname=None, link=False, fake_user=True):
- '''append a brush'''
- with bpy.data.libraries.load(file_name, link=link, relative=True) as (data_from, data_to):
- for m in data_from.brushes:
- if m == brushname or brushname is None:
- data_to.brushes = [m]
- brushname = m
- brush = bpy.data.brushes[brushname]
- if fake_user:
- brush.use_fake_user = True
- return brush
-
-
-def append_material(file_name, matname=None, link=False, fake_user=True):
- '''append a material type asset'''
- # first, we have to check if there is a material with same name
- # in previous step there's check if the imported material
- # is already in the scene, so we know same name != same material
-
- mats_before = bpy.data.materials[:]
- try:
- with bpy.data.libraries.load(file_name, link=link, relative=True) as (data_from, data_to):
- found = False
- for m in data_from.materials:
- if m == matname or matname is None:
- data_to.materials = [m]
- # print(m, type(m))
- matname = m
- found = 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(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 , due to possible name changes
- mat = None
- for m in bpy.data.materials:
- if m not in mats_before:
- mat = m
- break;
- #still not found?
- if mat is None:
- mat = bpy.data.materials.get(matname)
-
- if fake_user:
- mat.use_fake_user = True
- return mat
-
-
-def append_scene(file_name, scenename=None, link=False, fake_user=False):
- '''append a scene type asset'''
- with bpy.data.libraries.load(file_name, link=link, relative=True) as (data_from, data_to):
- for s in data_from.scenes:
- if s == scenename or scenename is None:
- data_to.scenes = [s]
- scenename = s
- scene = bpy.data.scenes[scenename]
- if fake_user:
- scene.use_fake_user = True
- # scene has to have a new uuid, so user reports aren't screwed.
- scene['uuid'] = str(uuid.uuid4())
-
- #reset ui_props of the scene to defaults:
- ui_props = bpy.context.window_manager.blenderkitUI
- ui_props.down_up = 'SEARCH'
-
- return scene
-
-
-def get_node_sure(node_tree, ntype=''):
- '''
- Gets a node of certain type, but creates a new one if not pre
- '''
- node = None
- for n in node_tree.nodes:
- if ntype == n.bl_rna.identifier:
- node = n
- return node
- if not node:
- node = node_tree.nodes.new(type=ntype)
-
- return node
-
-def hdr_swap(name, hdr):
- '''
- Try to replace the hdr in current world setup. If this fails, create a new world.
- :param name: Name of the resulting world (renamse the current one if swap is successfull)
- :param hdr: Image type
- :return: None
- '''
- w = bpy.context.scene.world
- if w:
- w.use_nodes = True
- w.name = name
- nt = w.node_tree
- for n in nt.nodes:
- if 'ShaderNodeTexEnvironment' == n.bl_rna.identifier:
- env_node = n
- env_node.image = hdr
- return
- new_hdr_world(name,hdr)
-
-
-def new_hdr_world(name, hdr):
- '''
- creates a new world, links in the hdr with mapping node, and links the world to scene
- :param name: Name of the world datablock
- :param hdr: Image type
- :return: None
- '''
- w = bpy.data.worlds.new(name=name)
- w.use_nodes = True
- bpy.context.scene.world = w
-
- nt = w.node_tree
- env_node = nt.nodes.new(type='ShaderNodeTexEnvironment')
- env_node.image = hdr
- background = get_node_sure(nt, 'ShaderNodeBackground')
- tex_coord = get_node_sure(nt, 'ShaderNodeTexCoord')
- mapping = get_node_sure(nt, 'ShaderNodeMapping')
-
- nt.links.new(env_node.outputs['Color'], background.inputs['Color'])
- nt.links.new(tex_coord.outputs['Generated'], mapping.inputs['Vector'])
- nt.links.new(mapping.outputs['Vector'], env_node.inputs['Vector'])
- env_node.location.x = -400
- mapping.location.x = -600
- tex_coord.location.x = -800
-
-
-def load_HDR(file_name, name):
- '''Load a HDR into file and link it to scene world. '''
- already_linked = False
- for i in bpy.data.images:
- if i.filepath == file_name:
- hdr = i
- already_linked = True
- break;
-
- if not already_linked:
- hdr = bpy.data.images.load(file_name)
-
- hdr_swap(name, hdr)
- return hdr
-
-
-def link_collection(file_name, obnames=[], location=(0, 0, 0), link=False, parent = None, **kwargs):
- '''link an instanced group - model type asset'''
- sel = utils.selection_get()
-
- with bpy.data.libraries.load(file_name, link=link, relative=True) as (data_from, data_to):
- scols = []
- for col in data_from.collections:
- if col == kwargs['name']:
- data_to.collections = [col]
-
- rotation = (0, 0, 0)
- if kwargs.get('rotation') is not None:
- rotation = kwargs['rotation']
-
- bpy.ops.object.empty_add(type='PLAIN_AXES', location=location, rotation=rotation)
- main_object = bpy.context.view_layer.objects.active
- main_object.instance_type = 'COLLECTION'
-
- if parent is not None:
- main_object.parent = bpy.data.objects.get(parent)
-
- main_object.matrix_world.translation = location
-
- for col in bpy.data.collections:
- if col.library is not None:
- fp = bpy.path.abspath(col.library.filepath)
- fp1 = bpy.path.abspath(file_name)
- if fp == fp1:
- main_object.instance_collection = col
- break;
-
- #sometimes, the lib might already be without the actual link.
- if not main_object.instance_collection and kwargs['name']:
- col = bpy.data.collections.get(kwargs['name'])
- if col:
- main_object.instance_collection = col
-
- main_object.name = main_object.instance_collection.name
-
- # bpy.ops.wm.link(directory=file_name + "/Collection/", filename=kwargs['name'], link=link, instance_collections=True,
- # autoselect=True)
- # main_object = bpy.context.view_layer.objects.active
- # if kwargs.get('rotation') is not None:
- # main_object.rotation_euler = kwargs['rotation']
- # main_object.location = location
-
- utils.selection_set(sel)
- return main_object, []
-
-
-def append_particle_system(file_name, obnames=[], location=(0, 0, 0), link=False, **kwargs):
- '''link an instanced group - model type asset'''
-
- pss = []
- with bpy.data.libraries.load(file_name, link=link, relative=True) as (data_from, data_to):
- for ps in data_from.particles:
- pss.append(ps)
- data_to.particles = pss
-
- s = bpy.context.scene
- sel = utils.selection_get()
-
- target_object = bpy.context.scene.objects.get(kwargs['target_object'])
- if target_object is not None and target_object.type == 'MESH':
- target_object.select_set(True)
- bpy.context.view_layer.objects.active = target_object
-
- for ps in pss:
- # now let's tune this ps to the particular objects area:
- totarea = 0
- for p in target_object.data.polygons:
- totarea += p.area
- count = int(ps.count * totarea)
-
- if ps.child_type in ('INTERPOLATED', 'SIMPLE'):
- total_count = count * ps.rendered_child_count
- disp_count = count * ps.child_nbr
- else:
- total_count = count
-
- bbox_threshold = 25000
- display_threshold = 200000
- total_max_threshold = 2000000
- # emitting too many parent particles just kills blender now.
-
- #this part tuned child count, we'll leave children to artists only.
- # if count > total_max_threshold:
- # ratio = round(count / total_max_threshold)
- #
- # if ps.child_type in ('INTERPOLATED', 'SIMPLE'):
- # ps.rendered_child_count *= ratio
- # else:
- # ps.child_type = 'INTERPOLATED'
- # ps.rendered_child_count = ratio
- # count = max(2, int(count / ratio))
-
- #1st level of optimizaton - switch t bounding boxes.
- if total_count>bbox_threshold:
- target_object.display_type = 'BOUNDS'
- # 2nd level of optimization - reduce percentage of displayed particles.
- ps.display_percentage = min(ps.display_percentage, max(1, int(100 * display_threshold / total_count)))
- #here we can also tune down number of children displayed.
- #set the count
- ps.count = count
- #add the modifier
- bpy.ops.object.particle_system_add()
- # 3rd level - hide particle system from viewport - is done on the modifier..
- if total_count > total_max_threshold:
- target_object.modifiers[-1].show_viewport = False
-
- target_object.particle_systems[-1].settings = ps
-
- target_object.select_set(False)
- utils.selection_set(sel)
- return target_object, []
-
-
-def append_objects(file_name, obnames=[], location=(0, 0, 0), link=False, **kwargs):
- '''append objects into scene individually'''
- #simplified version of append
- if kwargs.get('name'):
- # by now used for appending into scene
- scene = bpy.context.scene
- sel = utils.selection_get()
- bpy.ops.object.select_all(action='DESELECT')
-
- path = file_name + "\\Collection\\"
- collection_name = kwargs.get('name')
- fc = utils.get_fake_context(bpy.context, area_type='VIEW_3D')
- bpy.ops.wm.append(fc, filename=collection_name, directory=path)
-
- return_obs = []
- to_hidden_collection = []
- collection = None
- for ob in bpy.context.scene.objects:
- if ob.select_get():
- return_obs.append(ob)
- if not ob.parent:
- main_object = ob
- ob.location = location
- # check for object that should be hidden
- if ob.users_collection[0].name == collection_name:
- collection = ob.users_collection[0]
- collection['is_blenderkit_asset'] = True
-
- else:
- to_hidden_collection.append(ob)
-
- if kwargs.get('rotation'):
- main_object.rotation_euler = kwargs['rotation']
-
- if kwargs.get('parent') is not None:
- main_object.parent = bpy.data.objects[kwargs['parent']]
- main_object.matrix_world.translation = location
-
-
- #move objects that should be hidden to a sub collection
- if len(to_hidden_collection)>0 and collection is not None:
- hidden_collection_name = collection_name+'_hidden'
- h_col = bpy.data.collections.new(name = hidden_collection_name)
- collection.children.link(h_col)
- for ob in to_hidden_collection:
- ob.users_collection[0].objects.unlink(ob)
- h_col.objects.link(ob)
- utils.exclude_collection(hidden_collection_name)
-
- bpy.ops.object.select_all(action='DESELECT')
- utils.selection_set(sel)
- #let collection also store info that it was created by BlenderKit, for purging reasons
-
- return main_object, return_obs
-
- #this is used for uploads:
- with bpy.data.libraries.load(file_name, link=link, relative=True) as (data_from, data_to):
- sobs = []
- # for col in data_from.collections:
- # if col == kwargs.get('name'):
- for ob in data_from.objects:
- if ob in obnames or obnames == []:
- sobs.append(ob)
- data_to.objects = sobs
- # data_to.objects = data_from.objects#[name for name in data_from.objects if name.startswith("house")]
-
- # link them to scene
- scene = bpy.context.scene
- sel = utils.selection_get()
- bpy.ops.object.select_all(action='DESELECT')
-
- return_obs = [] # this might not be needed, but better be sure to rewrite the list.
- main_object = None
- hidden_objects = []
- #
- for obj in data_to.objects:
- if obj is not None:
- # if obj.name not in scene.objects:
- scene.collection.objects.link(obj)
- if obj.parent is None:
- obj.location = location
- main_object = obj
- obj.select_set(True)
- # we need to unhide object so make_local op can use those too.
- if link == True:
- if obj.hide_viewport:
- hidden_objects.append(obj)
- obj.hide_viewport = False
- return_obs.append(obj)
-
- # Only after all objects are in scene! Otherwise gets broken relationships
- if link == True:
- bpy.ops.object.make_local(type='SELECT_OBJECT')
- for ob in hidden_objects:
- ob.hide_viewport = True
-
- if kwargs.get('rotation') is not None:
- main_object.rotation_euler = kwargs['rotation']
-
- if kwargs.get('parent') is not None:
- main_object.parent = bpy.data.objects[kwargs['parent']]
- main_object.matrix_world.translation = location
-
- bpy.ops.object.select_all(action='DESELECT')
-
- utils.selection_set(sel)
-
-
- return main_object, return_obs
diff --git a/blenderkit/asset_bar_op.py b/blenderkit/asset_bar_op.py
deleted file mode 100644
index 09124f87..00000000
--- a/blenderkit/asset_bar_op.py
+++ /dev/null
@@ -1,1034 +0,0 @@
-import bpy
-
-from bpy.types import Operator
-
-from blenderkit.bl_ui_widgets.bl_ui_label import *
-from blenderkit.bl_ui_widgets.bl_ui_button import *
-from blenderkit.bl_ui_widgets.bl_ui_image import *
-# from blenderkit.bl_ui_widgets.bl_ui_checkbox import *
-# from blenderkit.bl_ui_widgets.bl_ui_slider import *
-# from blenderkit.bl_ui_widgets.bl_ui_up_down import *
-from blenderkit.bl_ui_widgets.bl_ui_drag_panel import *
-from blenderkit.bl_ui_widgets.bl_ui_draw_op import *
-# from blenderkit.bl_ui_widgets.bl_ui_textbox import *
-import random
-import math
-import time
-
-import blenderkit
-from blenderkit import ui, paths, utils, search, comments_utils
-
-from bpy.props import (
- IntProperty,
- BoolProperty,
- StringProperty
-)
-
-active_area_pointer = 0
-
-
-def get_area_height(self):
- if type(self.context) != dict:
- if self.context is None:
- self.context = bpy.context
- self.context = self.context.copy()
- # print(self.context)
- if self.context.get('area') is not None:
- return self.context['area'].height
- # else:
- # maxw, maxa, region = utils.get_largest_area()
- # if maxa:
- # self.context['area'] = maxa
- # self.context['window'] = maxw
- # self.context['region'] = region
- # self.update(self.x,self.y)
- #
- # return self.context['area'].height
- # print('no area found')
- return 100
-
-
-BL_UI_Widget.get_area_height = get_area_height
-
-
-def modal_inside(self, context, event):
- ui_props = bpy.context.window_manager.blenderkitUI
- if ui_props.turn_off:
- ui_props.turn_off = False
- self.finish()
-
- if self._finished:
- return {'FINISHED'}
-
- if context.area:
- context.area.tag_redraw()
- else:
- self.finish()
- return {'FINISHED'}
-
- self.update_timer += 1
-
- if self.update_timer > self.update_timer_limit:
- self.update_timer = 0
- # print('timer', time.time())
- self.update_images()
-
- # progress bar
- sr = bpy.context.window_manager.get('search results')
- ui_scale = bpy.context.preferences.view.ui_scale
- for asset_button in self.asset_buttons:
- if sr is not None and len(sr) > asset_button.asset_index:
- asset_data = sr[asset_button.asset_index]
-
- if asset_data['downloaded'] > 0:
- asset_button.progress_bar.width = int(self.button_size * ui_scale * asset_data['downloaded'] / 100)
- asset_button.progress_bar.visible = True
- else:
- asset_button.progress_bar.visible = False
-
- if self.handle_widget_events(event):
- return {'RUNNING_MODAL'}
-
- if event.type in {"ESC"}:
- self.finish()
-
- self.mouse_x = event.mouse_region_x
- self.mouse_y = event.mouse_region_y
- if event.type == 'WHEELUPMOUSE' and self.panel.is_in_rect(self.mouse_x, self.mouse_y):
- self.scroll_offset -= 2
- self.scroll_update()
- return {'RUNNING_MODAL'}
-
- elif event.type == 'WHEELDOWNMOUSE' and self.panel.is_in_rect(self.mouse_x, self.mouse_y):
- self.scroll_offset += 2
- self.scroll_update()
- return {'RUNNING_MODAL'}
-
- if self.check_ui_resized(context) or self.check_new_search_results(context):
- # print(self.check_ui_resized(context), print(self.check_new_search_results(context)))
- self.update_ui_size(context)
- self.update_layout(context, event)
-
- # this was here to check if sculpt stroke is running, but obviously that didn't help,
- # since the RELEASE event is cought by operator and thus there is no way to detect a stroke has ended...
- if bpy.context.mode in ('SCULPT', 'PAINT_TEXTURE'):
- if event.type == 'MOUSEMOVE': # ASSUME THAT SCULPT OPERATOR ACTUALLY STEALS THESE EVENTS,
- # SO WHEN THERE ARE SOME WE CAN APPEND BRUSH...
- bpy.context.window_manager['appendable'] = True
- if event.type == 'LEFTMOUSE':
- if event.value == 'PRESS':
- bpy.context.window_manager['appendable'] = False
- return {"PASS_THROUGH"}
-
-
-def asset_bar_modal(self, context, event):
- return modal_inside(self, context, event)
-
-
-def asset_bar_invoke(self, context, event):
- if not self.on_invoke(context, event):
- return {"CANCELLED"}
-
- args = (self, context)
-
- self.register_handlers(args, context)
-
- self.update_timer_limit = 30
- self.update_timer = 0
- # print('adding timer')
- # self._timer = context.window_manager.event_timer_add(10.0, window=context.window)
- global active_area_pointer
- context.window_manager.modal_handler_add(self)
- self.active_window_pointer = context.window.as_pointer()
- self.active_area_pointer = context.area.as_pointer()
- active_area_pointer = context.area.as_pointer()
- self.active_region_pointer = context.region.as_pointer()
-
- return {"RUNNING_MODAL"}
-
-
-BL_UI_OT_draw_operator.modal = asset_bar_modal
-BL_UI_OT_draw_operator.invoke = asset_bar_invoke
-
-
-def set_mouse_down_right(self, mouse_down_right_func):
- self.mouse_down_right_func = mouse_down_right_func
-
-
-def mouse_down_right(self, x, y):
- if self.is_in_rect(x, y):
- self.__state = 1
- try:
- self.mouse_down_right_func(self)
- except Exception as e:
- print(e)
-
- return True
-
- return False
-
-
-BL_UI_Button.mouse_down_right = mouse_down_right
-BL_UI_Button.set_mouse_down_right = set_mouse_down_right
-
-asset_bar_operator = None
-
-
-# BL_UI_Button.handle_event = handle_event
-
-def get_tooltip_data(asset_data):
- gimg = None
- tooltip_data = asset_data.get('tooltip_data')
- if tooltip_data is None:
- author_text = ''
-
- if bpy.context.window_manager.get('bkit authors') is not None:
- a = bpy.context.window_manager['bkit authors'].get(asset_data['author']['id'])
- if a is not None and a != '':
- if a.get('gravatarImg') is not None:
- gimg = utils.get_hidden_image(a['gravatarImg'], a['gravatarHash']).name
-
- if len(a['firstName']) > 0 or len(a['lastName']) > 0:
- author_text = f"by {a['firstName']} {a['lastName']}"
-
- aname = asset_data['displayName']
- aname = aname[0].upper() + aname[1:]
- if len(aname) > 36:
- aname = f"{aname[:33]}..."
-
- rc = asset_data.get('ratingsCount')
- show_rating_threshold = 0
- rcount = 0
- quality = '-'
- if rc:
- rcount = min(rc.get('quality', 0), rc.get('workingHours', 0))
- if rcount > show_rating_threshold:
- quality = str(round(asset_data['ratingsAverage'].get('quality')))
- tooltip_data = {
- 'aname': aname,
- 'author_text': author_text,
- 'quality': quality,
- 'gimg': gimg
- }
- asset_data['tooltip_data'] = tooltip_data
- gimg = tooltip_data['gimg']
- if gimg is not None:
- gimg = bpy.data.images[gimg]
-
-
-class BlenderKitAssetBarOperator(BL_UI_OT_draw_operator):
- bl_idname = "view3d.blenderkit_asset_bar_widget"
- bl_label = "BlenderKit asset bar refresh"
- bl_description = "BlenderKit asset bar refresh"
- bl_options = {'REGISTER'}
-
- do_search: BoolProperty(name="Run Search", description='', default=True, options={'SKIP_SAVE'})
- keep_running: BoolProperty(name="Keep Running", description='', default=True, options={'SKIP_SAVE'})
- free_only: BoolProperty(name="Free first", description='', default=False, options={'SKIP_SAVE'})
-
- category: StringProperty(
- name="Category",
- description="search only subtree of this category",
- default="", options={'SKIP_SAVE'})
-
- tooltip: bpy.props.StringProperty(default='Runs search and displays the asset bar at the same time')
-
- @classmethod
- def description(cls, context, properties):
- return properties.tooltip
-
- def new_text(self, text, x, y, width=100, height=15, text_size=None, halign='LEFT'):
- label = BL_UI_Label(x, y, width, height)
- label.text = text
- if text_size is None:
- text_size = 14
- label.text_size = text_size
- label.text_color = self.text_color
- label._halign = halign
- return label
-
- def init_tooltip(self):
- self.tooltip_widgets = []
- self.tooltip_height = self.tooltip_size
- self.tooltip_width = self.tooltip_size
- ui_props = bpy.context.window_manager.blenderkitUI
- if ui_props.asset_type == 'HDR':
- self.tooltip_width = self.tooltip_size * 2
- # total_size = tooltip# + 2 * self.margin
- self.tooltip_panel = BL_UI_Drag_Panel(0, 0, self.tooltip_width, self.tooltip_height)
- self.tooltip_panel.bg_color = (0.0, 0.0, 0.0, 0.5)
- self.tooltip_panel.visible = False
-
- tooltip_image = BL_UI_Image(0, 0, 1, 1)
- img_path = paths.get_addon_thumbnail_path('thumbnail_notready.jpg')
- tooltip_image.set_image(img_path)
- tooltip_image.set_image_size((self.tooltip_width, self.tooltip_height))
- tooltip_image.set_image_position((0, 0))
- self.tooltip_image = tooltip_image
- self.tooltip_widgets.append(tooltip_image)
-
- bottom_panel_fraction = 0.15
- labels_start = self.tooltip_height * (1 - bottom_panel_fraction)
-
- dark_panel = BL_UI_Widget(0, labels_start, self.tooltip_width, self.tooltip_height * bottom_panel_fraction)
- dark_panel.bg_color = (0.0, 0.0, 0.0, 0.7)
- self.tooltip_dark_panel = dark_panel
- self.tooltip_widgets.append(dark_panel)
-
- name_label = self.new_text('', self.margin, labels_start + self.margin,
- text_size=self.asset_name_text_size)
- self.asset_name = name_label
- self.tooltip_widgets.append(name_label)
-
- self.gravatar_size = int(self.tooltip_height * bottom_panel_fraction - self.margin)
-
- authors_name = self.new_text('author', self.tooltip_width - self.gravatar_size - self.margin,
- self.tooltip_height - self.author_text_size - self.margin, labels_start,
- text_size=self.author_text_size, halign='RIGHT')
- self.authors_name = authors_name
- self.tooltip_widgets.append(authors_name)
-
- gravatar_image = BL_UI_Image(self.tooltip_width - self.gravatar_size, self.tooltip_height - self.gravatar_size,
- 1, 1)
- img_path = paths.get_addon_thumbnail_path('thumbnail_notready.jpg')
- gravatar_image.set_image(img_path)
- gravatar_image.set_image_size((self.gravatar_size - 1 * self.margin, self.gravatar_size - 1 * self.margin))
- gravatar_image.set_image_position((0, 0))
- self.gravatar_image = gravatar_image
- self.tooltip_widgets.append(gravatar_image)
-
- quality_star = BL_UI_Image(self.margin, self.tooltip_height - self.margin - self.asset_name_text_size,
- 1, 1)
- img_path = paths.get_addon_thumbnail_path('star_grey.png')
- quality_star.set_image(img_path)
- quality_star.set_image_size((self.asset_name_text_size, self.asset_name_text_size))
- quality_star.set_image_position((0, 0))
- # self.quality_star = quality_star
- self.tooltip_widgets.append(quality_star)
- label = self.new_text('', 2 * self.margin + self.asset_name_text_size,
- self.tooltip_height - int(self.asset_name_text_size + self.margin * .5),
- text_size=self.asset_name_text_size)
- self.tooltip_widgets.append(label)
- self.quality_label = label
-
- # label = self.new_text('Right click for menu.', self.margin,
- # self.tooltip_height - int(self.author_text_size) - self.margin,
- # text_size=int(self.author_text_size*.7))
- # self.tooltip_widgets.append(label)
-
- def hide_tooltip(self):
- self.tooltip_panel.visible = False
- for w in self.tooltip_widgets:
- w.visible = False
-
- def show_tooltip(self):
- self.tooltip_panel.visible = True
- for w in self.tooltip_widgets:
- w.visible = True
-
- def show_notifications(self, widget):
- bpy.ops.wm.show_notifications()
- if comments_utils.check_notifications_read():
- widget.visible = False
-
- def check_new_search_results(self, context):
- sr = bpy.context.window_manager.get('search results')
- if not hasattr(self, 'search_results_count'):
- if not sr:
- self.search_results_count = 0
- return True
-
- self.search_results_count = len(sr)
-
- if sr is not None and len(sr) != self.search_results_count:
- self.search_results_count = len(sr)
- return True
- return False
-
- def get_region_size(self, context):
- # just check the size of region..
-
- region = context.region
- area = context.area
- ui_width = 0
- tools_width = 0
- for r in area.regions:
- if r.type == 'UI':
- ui_width = r.width
- if r.type == 'TOOLS':
- tools_width = r.width
- total_width = region.width - tools_width - ui_width
- return total_width, region.height
-
- def check_ui_resized(self, context):
- # TODO this should only check if region was resized, not really care about the UI elements size.
- region_width, region_height = self.get_region_size(context)
-
- if not hasattr(self, 'total_width'):
- self.total_width = region_width
- self.region_height = region_height
-
- if region_height != self.region_height or region_width != self.total_width:
- self.region_height = region_height
- self.total_width = region_width
- return True
- return False
-
- def update_ui_size(self, context):
-
- if bpy.app.background or not context.area:
- return
-
- region = context.region
- area = context.area
-
- ui_props = bpy.context.window_manager.blenderkitUI
- user_preferences = bpy.context.preferences.addons['blenderkit'].preferences
- ui_scale = bpy.context.preferences.view.ui_scale
-
- self.margin = ui_props.bl_rna.properties['margin'].default * ui_scale
- self.margin = int(9 * ui_scale)
- self.button_margin = int(0 * ui_scale)
- self.asset_name_text_size = int(20 * ui_scale)
- self.author_text_size = int(self.asset_name_text_size * .7 * ui_scale)
- self.assetbar_margin = int(2 * ui_scale)
- self.tooltip_size = int(512 * ui_scale)
-
- if ui_props.asset_type == 'HDR':
- self.tooltip_width = self.tooltip_size * 2
- else:
- self.tooltip_width = self.tooltip_size
-
- self.thumb_size = user_preferences.thumb_size * ui_scale
- self.button_size = 2 * self.button_margin + self.thumb_size
- self.other_button_size = 30 * ui_scale
- self.icon_size = 24 * ui_scale
- self.validation_icon_margin = 3 * ui_scale
- reg_multiplier = 1
- if not bpy.context.preferences.system.use_region_overlap:
- reg_multiplier = 0
-
- ui_width = 0
- tools_width = 0
- reg_multiplier = 1
- if not bpy.context.preferences.system.use_region_overlap:
- reg_multiplier = 0
- for r in area.regions:
- if r.type == 'UI':
- ui_width = r.width * reg_multiplier
- if r.type == 'TOOLS':
- tools_width = r.width * reg_multiplier
- self.bar_x = tools_width + self.margin + ui_props.bar_x_offset * ui_scale
- self.bar_end = ui_width + 180 * ui_scale + self.other_button_size
- self.bar_width = region.width - self.bar_x - self.bar_end
-
- self.wcount = math.floor((self.bar_width) / (self.button_size))
-
- self.max_hcount = math.floor(context.window.width / self.button_size)
- self.max_wcount = user_preferences.max_assetbar_rows
-
- search_results = bpy.context.window_manager.get('search results')
- # 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))
- self.hcount = max(self.hcount, 1)
- 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
- self.bar_y = ui_props.bar_y_offset * ui_scale
- if ui_props.down_up == 'UPLOAD':
- self.reports_y = region.height - self.bar_y - 600
- ui_props.reports_y = region.height - self.bar_y - 600
- self.reports_x = self.bar_x
- ui_props.reports_x = self.bar_x
- else: # ui.bar_y - ui.bar_height - 100
-
- self.reports_y = region.height - self.bar_y - self.bar_height - 50
- ui_props.reports_y = region.height - self.bar_y - self.bar_height - 50
- self.reports_x = self.bar_x
- ui_props.reports_x = self.bar_x
- # print(self.bar_y, self.bar_height, region.height)
-
- def update_layout(self, context, event):
- # restarting asset_bar completely since the widgets are too hard to get working with updates.
-
- self.position_and_hide_buttons()
- self.scroll_update()
-
- self.button_close.set_location(self.bar_width - self.other_button_size, -self.other_button_size)
- if hasattr(self, 'button_notifications'):
- self.button_notifications.set_location(self.bar_width - self.other_button_size * 2, -self.other_button_size)
- self.button_scroll_up.set_location(self.bar_width, 0)
- self.panel.width = self.bar_width
- self.panel.height = self.bar_height
-
- self.panel.set_location(self.bar_x, self.panel.y)
-
- # update Tooltip size
- if self.tooltip_dark_panel.width != self.tooltip_width:
- self.tooltip_dark_panel.width = self.tooltip_width
- self.tooltip_panel.width = self.tooltip_width
- self.tooltip_image.width = self.tooltip_width
- self.tooltip_image.set_image_size((self.tooltip_width, self.tooltip_height))
- self.gravatar_image.set_location(self.tooltip_width - self.gravatar_size,
- self.tooltip_height - self.gravatar_size)
- self.authors_name.set_location(self.tooltip_width - self.gravatar_size - self.margin,
- self.tooltip_height - self.author_text_size - self.margin)
-
- # to hide arrows accordingly
-
- def asset_button_init(self, asset_x, asset_y, button_idx):
- ui_scale = bpy.context.preferences.view.ui_scale
-
- button_bg_color = (0.2, 0.2, 0.2, .1)
- button_hover_color = (0.8, 0.8, 0.8, .2)
-
- new_button = BL_UI_Button(asset_x, asset_y, self.button_size, self.button_size)
-
- # asset_data = sr[asset_idx]
- # iname = blenderkit.utils.previmg_name(asset_idx)
- # img = bpy.data.images.get(iname)
-
- new_button.bg_color = button_bg_color
- new_button.hover_bg_color = button_hover_color
- new_button.text = "" # asset_data['name']
- # if img:
- # new_button.set_image(img.filepath)
-
- new_button.set_image_size((self.thumb_size, self.thumb_size))
- new_button.set_image_position((self.button_margin, self.button_margin))
- new_button.button_index = button_idx
- new_button.search_index = button_idx
- new_button.set_mouse_down(self.drag_drop_asset)
- new_button.set_mouse_down_right(self.asset_menu)
- new_button.set_mouse_enter(self.enter_button)
- new_button.set_mouse_exit(self.exit_button)
- new_button.text_input = self.handle_key_input
- # add validation icon to button
-
- validation_icon = BL_UI_Image(
- asset_x + self.button_size - self.icon_size - self.button_margin - self.validation_icon_margin,
- asset_y + self.button_size - self.icon_size - self.button_margin - self.validation_icon_margin, 0, 0)
-
- # v_icon = ui.verification_icons[asset_data.get('verificationStatus', 'validated')]
- # if v_icon is not None:
- # img_fp = paths.get_addon_thumbnail_path(v_icon)
- # validation_icon.set_image(img_fp)
- validation_icon.set_image_size((self.icon_size, self.icon_size))
- validation_icon.set_image_position((0, 0))
- self.validation_icons.append(validation_icon)
- new_button.validation_icon = validation_icon
-
- progress_bar = BL_UI_Widget(asset_x, asset_y + self.button_size - 3, self.button_size, 3)
- progress_bar.bg_color = (0.0, 1.0, 0.0, 0.3)
- new_button.progress_bar = progress_bar
- self.progress_bars.append(progress_bar)
-
- if utils.profile_is_validator():
- red_alert = BL_UI_Widget(asset_x, asset_y, self.button_size, self.button_size)
- red_alert.bg_color = (1.0, 0.0, 0.0, 0.0)
- red_alert.visible = False
- new_button.red_alert = red_alert
- self.red_alerts.append(red_alert)
- # if result['downloaded'] > 0:
- # ui_bgl.draw_rect(x, y, int(ui_props.thumb_size * result['downloaded'] / 100.0), 2, green)
-
- return new_button
-
- def init_ui(self):
- ui_scale = bpy.context.preferences.view.ui_scale
-
- button_bg_color = (0.2, 0.2, 0.2, .1)
- button_hover_color = (0.8, 0.8, 0.8, .2)
-
- self.buttons = []
- self.asset_buttons = []
- self.validation_icons = []
- self.progress_bars = []
- self.red_alerts = []
- self.widgets_panel = []
-
- self.panel = BL_UI_Drag_Panel(0, 0, self.bar_width, self.bar_height)
- self.panel.bg_color = (0.0, 0.0, 0.0, 0.5)
-
- # sr = bpy.context.window_manager.get('search results', [])
- # if sr is not None:
- # we init max possible buttons.
- button_idx = 0
- for x in range(0, self.max_wcount):
- for y in range(0, self.max_hcount):
- # asset_x = self.assetbar_margin + a * (self.button_size)
- # asset_y = self.assetbar_margin + b * (self.button_size)
- # button_idx = x + y * self.max_wcount
- asset_idx = button_idx + self.scroll_offset
- # if asset_idx < len(sr):
- new_button = self.asset_button_init(0, 0, button_idx)
- new_button.asset_index = asset_idx
- self.asset_buttons.append(new_button)
- button_idx += 1
-
- self.button_close = BL_UI_Button(self.bar_width - self.other_button_size, -self.other_button_size,
- self.other_button_size,
- self.other_button_size)
- self.button_close.bg_color = button_bg_color
- self.button_close.hover_bg_color = button_hover_color
- self.button_close.text = ""
- img_fp = paths.get_addon_thumbnail_path('vs_rejected.png')
- self.button_close.set_image(img_fp)
- self.button_close.set_mouse_down(self.cancel_press)
-
- self.widgets_panel.append(self.button_close)
-
- self.scroll_width = 30
- self.button_scroll_down = BL_UI_Button(-self.scroll_width, 0, self.scroll_width, self.bar_height)
- self.button_scroll_down.bg_color = button_bg_color
- self.button_scroll_down.hover_bg_color = button_hover_color
- self.button_scroll_down.text = ""
- self.button_scroll_down.set_image(paths.get_addon_thumbnail_path('arrow_left.png'))
- self.button_scroll_down.set_image_size((self.scroll_width, self.button_size))
- self.button_scroll_down.set_image_position((0, int((self.bar_height - self.button_size) / 2)))
-
- self.button_scroll_down.set_mouse_down(self.scroll_down)
-
- self.widgets_panel.append(self.button_scroll_down)
-
- self.button_scroll_up = BL_UI_Button(self.bar_width, 0, self.scroll_width, self.bar_height)
- self.button_scroll_up.bg_color = button_bg_color
- self.button_scroll_up.hover_bg_color = button_hover_color
- self.button_scroll_up.text = ""
- self.button_scroll_up.set_image(paths.get_addon_thumbnail_path('arrow_right.png'))
- self.button_scroll_up.set_image_size((self.scroll_width, self.button_size))
- self.button_scroll_up.set_image_position((0, int((self.bar_height - self.button_size) / 2)))
-
- self.button_scroll_up.set_mouse_down(self.scroll_up)
-
- self.widgets_panel.append(self.button_scroll_up)
-
- # notifications
- if not comments_utils.check_notifications_read():
- self.button_notifications = BL_UI_Button(self.bar_width - self.other_button_size * 2,
- -self.other_button_size, self.other_button_size,
- self.other_button_size)
- self.button_notifications.bg_color = button_bg_color
- self.button_notifications.hover_bg_color = button_hover_color
- self.button_notifications.text = ""
- img_fp = paths.get_addon_thumbnail_path('bell.png')
- self.button_notifications.set_image(img_fp)
- self.button_notifications.set_mouse_down(self.show_notifications)
- self.widgets_panel.append(self.button_notifications)
-
- self.update_images()
-
- def position_and_hide_buttons(self):
- # position and layout buttons
- sr = bpy.context.window_manager.get('search results', [])
- if sr is None:
- sr = []
-
- i = 0
- for y in range(0, self.hcount):
- for x in range(0, self.wcount):
- asset_x = self.assetbar_margin + x * (self.button_size)
- asset_y = self.assetbar_margin + y * (self.button_size)
- button_idx = x + y * self.wcount
- asset_idx = button_idx + self.scroll_offset
- if len(self.asset_buttons) <= button_idx:
- break
- button = self.asset_buttons[button_idx]
- button.set_location(asset_x, asset_y)
- button.validation_icon.set_location(
- asset_x + self.button_size - self.icon_size - self.button_margin - self.validation_icon_margin,
- asset_y + self.button_size - self.icon_size - self.button_margin - self.validation_icon_margin)
- button.progress_bar.set_location(asset_x, asset_y + self.button_size - 3)
- if asset_idx < len(sr):
- button.visible = True
- button.validation_icon.visible = True
- # button.progress_bar.visible = True
- else:
- button.visible = False
- button.validation_icon.visible = False
- button.progress_bar.visible = False
- if utils.profile_is_validator():
- button.red_alert.set_location(asset_x, asset_y)
- i += 1
-
- for a in range(i, len(self.asset_buttons)):
- button = self.asset_buttons[a]
- button.visible = False
- button.validation_icon.visible = False
- button.progress_bar.visible = False
-
- self.button_scroll_down.height = self.bar_height
- self.button_scroll_down.set_image_position((0, int((self.bar_height - self.button_size) / 2)))
- self.button_scroll_down.height = self.bar_height
- self.button_scroll_down.set_image_position((0, int((self.bar_height - self.button_size) / 2)))
-
- def __init__(self):
- super().__init__()
-
- self.update_ui_size(bpy.context)
-
- # todo move all this to update UI size
- ui_props = bpy.context.window_manager.blenderkitUI
-
- self.draw_tooltip = False
- # let's take saved scroll offset and use it to keep scroll between operator runs
- self.scroll_offset = ui_props.scroll_offset
-
- self.text_color = (0.9, 0.9, 0.9, 1.0)
-
- self.init_ui()
- self.init_tooltip()
- self.hide_tooltip()
-
- def setup_widgets(self, context, event):
- widgets_panel = []
- widgets_panel.extend(self.widgets_panel)
- widgets_panel.extend(self.buttons)
- widgets_panel.extend(self.asset_buttons)
- widgets_panel.extend(self.validation_icons)
- widgets_panel.extend(self.progress_bars)
- widgets_panel.extend(self.red_alerts)
-
- widgets = [self.panel]
-
- widgets += widgets_panel
- widgets.append(self.tooltip_panel)
- widgets += self.tooltip_widgets
-
- self.init_widgets(context, widgets)
- self.panel.add_widgets(widgets_panel)
- self.tooltip_panel.add_widgets(self.tooltip_widgets)
-
- def on_invoke(self, context, event):
-
- self.context = context
-
- if self.do_search or context.window_manager.get('search results') is None:
- # TODO: move the search behaviour to separate operator, since asset bar can be already woken up from a timer.
-
- # we erase search keywords for cateogry search now, since these combinations usually return nothing now.
- # when the db gets bigger, this can be deleted.
- if self.category != '':
- sprops = utils.get_search_props()
- sprops.search_keywords = ''
- search.search(category=self.category)
-
- ui_props = context.window_manager.blenderkitUI
- if ui_props.assetbar_on:
- # TODO solve this otehrwise to enable more asset bars?
-
- # we don't want to run the assetbar many times, that's why it has a switch on/off behaviour,
- # unless being called with 'keep_running' prop.
-
- if not self.keep_running:
- # this sends message to the originally running operator, so it quits, and then it ends this one too.
- # If it initiated a search, the search will finish in a thread. The switch off procedure is run
- # by the 'original' operator, since if we get here, it means
- # same operator is already running.
- ui_props.turn_off = True
- # if there was an error, we need to turn off these props so we can restart after 2 clicks
- ui_props.assetbar_on = False
-
- else:
- pass
- return False
-
- ui_props.assetbar_on = True
- global asset_bar_operator
-
- asset_bar_operator = self
-
- self.active_index = -1
-
- self.setup_widgets(context, event)
- self.position_and_hide_buttons()
- self.hide_tooltip()
-
- self.panel.set_location(self.bar_x,
- self.bar_y)
- # to hide arrows accordingly
- self.scroll_update()
-
- self.window = context.window
- self.area = context.area
- self.scene = bpy.context.scene
- # global active_window_pointer, active_area_pointer, active_region_pointer
- # ui.active_window_pointer = self.window.as_pointer()
- # ui.active_area_pointer = self.area.as_pointer()
- # ui.active_region_pointer = self.region.as_pointer()
-
- return True
-
- def on_finish(self, context):
- # redraw all areas, since otherwise it stays to hang for some more time.
- # bpy.types.SpaceView3D.draw_handler_remove(self._handle_2d_tooltip, 'WINDOW')
- # to pass the operator to validation icons
- global asset_bar_operator
- asset_bar_operator = None
-
- # context.window_manager.event_timer_remove(self._timer)
-
- scene = bpy.context.scene
- ui_props = bpy.context.window_manager.blenderkitUI
- ui_props.assetbar_on = False
- ui_props.scroll_offset = self.scroll_offset
-
- wm = bpy.data.window_managers[0]
-
- for w in wm.windows:
- for a in w.screen.areas:
- a.tag_redraw()
- self._finished = True
-
- # handlers
-
- def enter_button(self, widget):
- # print('enter button', self.active_index, widget.button_index)
- # print(widget.button_index+ self.scroll_offset, self.active_index)
- search_index = widget.button_index + self.scroll_offset
- if search_index < self.search_results_count:
- self.show_tooltip()
- # print(self.active_index, search_index)
- if self.active_index != search_index:
- self.active_index = search_index
-
- scene = bpy.context.scene
- wm = bpy.context.window_manager
- sr = wm['search results']
- asset_data = sr[search_index] # + self.scroll_offset]
-
- self.draw_tooltip = True
- # self.tooltip = asset_data['tooltip']
- ui_props = bpy.context.window_manager.blenderkitUI
- ui_props.active_index = search_index # + self.scroll_offset
-
- img = ui.get_large_thumbnail_image(asset_data)
- if img:
- self.tooltip_image.set_image(img.filepath)
-
- get_tooltip_data(asset_data)
- an = asset_data['name']
- max_name_length = 30
- if len(an) > max_name_length + 3:
- an = an[:30] + '...'
- self.asset_name.text = an
- self.authors_name.text = asset_data['tooltip_data']['author_text']
- self.quality_label.text = asset_data['tooltip_data']['quality']
- # print(asset_data['tooltip_data']['quality'])
- gimg = asset_data['tooltip_data']['gimg']
- if gimg is not None:
- gimg = bpy.data.images[gimg]
- if gimg:
- self.gravatar_image.set_image(gimg.filepath
- )
- # print('moving tooltip')
- properties_width = 0
- for r in bpy.context.area.regions:
- if r.type == 'UI':
- properties_width = r.width
- tooltip_x = min(int(widget.x_screen),
- int(bpy.context.region.width - self.tooltip_panel.width - properties_width))
- tooltip_y = int(widget.y_screen + widget.height)
- # self.init_tooltip()
- self.tooltip_panel.set_location(tooltip_x, tooltip_y)
- self.tooltip_panel.layout_widgets()
- # print(tooltip_x, tooltip_y)
- # bpy.ops.wm.blenderkit_asset_popup('INVOKE_DEFAULT')
-
- def exit_button(self, widget):
- # print(f'exit {widget.search_index} , {self.active_index}')
- # this condition checks if there wasn't another button already entered, which can happen with small button gaps
- if self.active_index == widget.button_index + self.scroll_offset:
- scene = bpy.context.scene
- ui_props = bpy.context.window_manager.blenderkitUI
- ui_props.draw_tooltip = False
- self.draw_tooltip = False
- self.hide_tooltip()
- # popup asset card on mouse down
- # if utils.experimental_enabled():
- # h = widget.get_area_height()
- # print(h,h-self.mouse_y,self.panel.y_screen, self.panel.y,widget.y_screen, widget.y)
- # if utils.experimental_enabled() and self.mouse_y<widget.y_screen:
- # self.active_index = widget.button_index + self.scroll_offset
- # bpy.ops.wm.blenderkit_asset_popup('INVOKE_DEFAULT')
-
- def drag_drop_asset(self, widget):
- bpy.ops.view3d.asset_drag_drop('INVOKE_DEFAULT', asset_search_index=widget.search_index + self.scroll_offset)
-
- def cancel_press(self, widget):
- self.finish()
-
- def asset_menu(self, widget):
- self.hide_tooltip()
- bpy.ops.wm.blenderkit_asset_popup('INVOKE_DEFAULT')
- # bpy.ops.wm.call_menu(name='OBJECT_MT_blenderkit_asset_menu')
-
- def search_more(self):
- sro = bpy.context.window_manager.get('search results orig')
- if sro is None:
- return
- if sro.get('next') is None:
- return
- search_props = utils.get_search_props()
- if search_props.is_searching:
- return
-
- blenderkit.search.search(get_next=True)
-
- def update_validation_icon(self, asset_button, asset_data):
- if utils.profile_is_validator():
- ar = bpy.context.window_manager.get('asset ratings')
- rating = ar.get(asset_data['id'])
- if rating is not None:
- rating = rating.to_dict()
-
- v_icon = ui.verification_icons[asset_data.get('verificationStatus', 'validated')]
- if v_icon is not None:
- img_fp = paths.get_addon_thumbnail_path(v_icon)
- asset_button.validation_icon.set_image(img_fp)
- asset_button.validation_icon.visible = True
- elif rating in (None, {}):
- v_icon = 'star_grey.png'
- img_fp = paths.get_addon_thumbnail_path(v_icon)
- asset_button.validation_icon.set_image(img_fp)
- asset_button.validation_icon.visible = True
- else:
- asset_button.validation_icon.visible = False
- else:
- if asset_data.get('canDownload', True) == 0:
- img_fp = paths.get_addon_thumbnail_path('locked.png')
- asset_button.validation_icon.set_image(img_fp)
- else:
- asset_button.validation_icon.visible = False
-
- def update_images(self):
- sr = bpy.context.window_manager.get('search results')
- if not sr:
- return
- for asset_button in self.asset_buttons:
- if asset_button.visible:
- asset_button.asset_index = asset_button.button_index + self.scroll_offset
- # print(asset_button.asset_index, len(sr))
- if asset_button.asset_index < len(sr):
- asset_button.visible = True
-
- asset_data = sr[asset_button.asset_index]
- if asset_data is None:
- continue
- iname = blenderkit.utils.previmg_name(asset_button.asset_index)
- # show indices for debug purposes
- # asset_button.text = str(asset_button.asset_index)
- img = bpy.data.images.get(iname)
- if img is None or len(img.pixels) == 0:
- img_filepath = paths.get_addon_thumbnail_path('thumbnail_notready.jpg')
- else:
- img_filepath = img.filepath
- # print(asset_button.button_index, img_filepath)
-
- asset_button.set_image(img_filepath)
- self.update_validation_icon(asset_button, asset_data)
-
- if utils.profile_is_validator() and asset_data['verificationStatus'] == 'uploaded':
- over_limit = utils.is_upload_old(asset_data)
- if over_limit:
- redness = min(over_limit * .05, 0.7)
- asset_button.red_alert.bg_color = (1, 0, 0, redness)
- asset_button.red_alert.visible = True
- else:
- asset_button.red_alert.visible = False
- elif utils.profile_is_validator():
- asset_button.red_alert.visible = False
- else:
- asset_button.visible = False
- asset_button.validation_icon.visible = False
- if utils.profile_is_validator():
- asset_button.red_alert.visible = False
-
- def scroll_update(self):
- sr = bpy.context.window_manager.get('search results')
- sro = bpy.context.window_manager.get('search results orig')
- # empty results
- if sr is None:
- self.button_scroll_down.visible = False
- self.button_scroll_up.visible = False
- return
-
- self.scroll_offset = min(self.scroll_offset, len(sr) - (self.wcount * self.hcount))
- self.scroll_offset = max(self.scroll_offset, 0)
- self.update_images()
-
- # print(sro)
- if sro['count'] > len(sr) and len(sr) - self.scroll_offset < (self.wcount * self.hcount) + 15:
- self.search_more()
-
- if self.scroll_offset == 0:
- self.button_scroll_down.visible = False
- else:
- self.button_scroll_down.visible = True
-
- if self.scroll_offset >= sro['count'] - (self.wcount * self.hcount):
- self.button_scroll_up.visible = False
- else:
- self.button_scroll_up.visible = True
-
- def search_by_author(self, asset_index):
- sr = bpy.context.window_manager['search results']
- asset_data = sr[asset_index]
- a = asset_data['author']['id']
- if a is not None:
- sprops = utils.get_search_props()
- sprops.search_keywords = ''
- sprops.search_verification_status = 'ALL'
- # utils.p('author:', a)
- search.search(author_id=a)
- return True
-
- def handle_key_input(self, event):
- if event.type == 'A':
- self.search_by_author(self.active_index)
- return True
- if event.type == 'X' and self.active_index > -1:
- # delete downloaded files for this asset
- sr = bpy.context.window_manager['search results']
- asset_data = sr[self.active_index]
- print('delete asset from local drive:' + asset_data['name'])
- paths.delete_asset_debug(asset_data)
- asset_data['downloaded'] = 0
- return True
- if event.type == 'W' and self.active_index > -1:
- sr = bpy.context.window_manager['search results']
- asset_data = sr[self.active_index]
- a = bpy.context.window_manager['bkit authors'].get(asset_data['author']['id'])
- if a is not None:
- utils.p('author:', a)
- if a.get('aboutMeUrl') is not None:
- bpy.ops.wm.url_open(url=a['aboutMeUrl'])
- return True
- # FastRateMenu
- if event.type == 'R' and self.active_index > -1:
- sr = bpy.context.window_manager['search results']
- asset_data = sr[self.active_index]
- if not utils.user_is_owner(asset_data=asset_data):
- bpy.ops.wm.blenderkit_menu_rating_upload(asset_name = asset_data['name'], asset_id =asset_data['id'], asset_type = asset_data['assetType'])
- return True
- return False
-
- def scroll_up(self, widget):
- self.scroll_offset += self.wcount * self.hcount
- self.scroll_update()
-
- def scroll_down(self, widget):
- self.scroll_offset -= self.wcount * self.hcount
- self.scroll_update()
-
-
-def register():
- bpy.utils.register_class(BlenderKitAssetBarOperator)
-
-
-def unregister():
- bpy.utils.unregister_class(BlenderKitAssetBarOperator)
diff --git a/blenderkit/asset_inspector.py b/blenderkit/asset_inspector.py
deleted file mode 100644
index cbb9517a..00000000
--- a/blenderkit/asset_inspector.py
+++ /dev/null
@@ -1,393 +0,0 @@
-# ##### 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 utils
-
-import bpy
-from object_print3d_utils import operators as ops
-
-RENDER_OBTYPES = ['MESH', 'CURVE', 'SURFACE', 'METABALL', 'TEXT']
-
-
-def check_material(props, mat):
- e = bpy.context.scene.render.engine
- shaders = []
- textures = []
- props.texture_count = 0
- props.node_count = 0
- props.total_megapixels = 0
- props.is_procedural = True
-
- if e == 'CYCLES':
-
- if mat.node_tree is not None:
- checknodes = mat.node_tree.nodes[:]
- while len(checknodes) > 0:
- n = checknodes.pop()
- props.node_count += 1
- if n.type == 'GROUP': # dive deeper here.
- checknodes.extend(n.node_tree.nodes)
- if len(n.outputs) == 1 and n.outputs[0].type == 'SHADER' and n.type != 'GROUP':
- if n.type not in shaders:
- shaders.append(n.type)
- if n.type == 'TEX_IMAGE':
-
- if n.image is not None:
- mattype = 'image based'
- props.is_procedural = False
- if n.image not in textures:
- textures.append(n.image)
- props.texture_count += 1
- props.total_megapixels += (n.image.size[0] * n.image.size[1])
-
- maxres = max(n.image.size[0], n.image.size[1])
- props.texture_resolution_max = max(props.texture_resolution_max, maxres)
- minres = min(n.image.size[0], n.image.size[1])
- if props.texture_resolution_min == 0:
- props.texture_resolution_min = minres
- else:
- props.texture_resolution_min = min(props.texture_resolution_min, minres)
-
- props.shaders = ''
- for s in shaders:
- if s.startswith('BSDF_'):
- s = s[5:]
- s = s.lower().replace('_', ' ')
- props.shaders += (s + ', ')
-
-
-def check_render_engine(props, obs):
- ob = obs[0]
- m = None
-
- e = bpy.context.scene.render.engine
- mattype = None
- materials = []
- shaders = []
- textures = []
- props.uv = False
- props.texture_count = 0
- props.total_megapixels = 0
- props.node_count = 0
- for ob in obs: # TODO , this is duplicated here for other engines, otherwise this should be more clever.
- for ms in ob.material_slots:
- if ms.material is not None:
- m = ms.material
- if m.name not in materials:
- materials.append(m.name)
- if ob.type == 'MESH' and len(ob.data.uv_layers) > 0:
- props.uv = True
-
- if e == 'BLENDER_RENDER':
- props.engine = 'BLENDER_INTERNAL'
- elif e == 'CYCLES':
-
- props.engine = 'CYCLES'
-
- for mname in materials:
- m = bpy.data.materials[mname]
- if m is not None and m.node_tree is not None:
- checknodes = m.node_tree.nodes[:]
- while len(checknodes) > 0:
- n = checknodes.pop()
- props.node_count +=1
- if n.type == 'GROUP': # dive deeper here.
- checknodes.extend(n.node_tree.nodes)
- if len(n.outputs) == 1 and n.outputs[0].type == 'SHADER' and n.type != 'GROUP':
- if n.type not in shaders:
- shaders.append(n.type)
- if n.type == 'TEX_IMAGE':
-
-
- if n.image is not None and n.image not in textures:
- props.is_procedural = False
- mattype = 'image based'
-
- textures.append(n.image)
- props.texture_count += 1
- props.total_megapixels += (n.image.size[0] * n.image.size[1])
-
- maxres = max(n.image.size[0], n.image.size[1])
- props.texture_resolution_max = max(props.texture_resolution_max, maxres)
- minres = min(n.image.size[0], n.image.size[1])
- if props.texture_resolution_min == 0:
- props.texture_resolution_min = minres
- else:
- props.texture_resolution_min = min(props.texture_resolution_min, minres)
-
-
- # if mattype == None:
- # mattype = 'procedural'
- # tags['material type'] = mattype
-
- elif e == 'BLENDER_GAME':
- props.engine = 'BLENDER_GAME'
-
- # write to object properties.
- props.materials = ''
- props.shaders = ''
- for m in materials:
- props.materials += (m + ', ')
- for s in shaders:
- if s.startswith('BSDF_'):
- s = s[5:]
- s = s.lower()
- s = s.replace('_', ' ')
- props.shaders += (s + ', ')
-
-
-def check_printable(props, obs):
- if len(obs) == 1:
- check_cls = (
- ops.Print3DCheckSolid,
- ops.Print3DCheckIntersections,
- ops.Print3DCheckDegenerate,
- ops.Print3DCheckDistorted,
- ops.Print3DCheckThick,
- ops.Print3DCheckSharp,
- # ops.Print3DCheckOverhang,
- )
-
- ob = obs[0]
-
- info = []
- for cls in check_cls:
- cls.main_check(ob, info)
-
- printable = True
- for item in info:
- passed = item[0].endswith(' 0')
- if not passed:
- # print(item[0])
- printable = False
-
- props.printable_3d = printable
-
-
-def check_rig(props, obs):
- for ob in obs:
- if ob.type == 'ARMATURE':
- props.rig = True
-
-
-def check_anim(props, obs):
- animated = False
- for ob in obs:
- if ob.animation_data is not None:
- a = ob.animation_data.action
- if a is not None:
- for c in a.fcurves:
- if len(c.keyframe_points) > 1:
- animated = True
-
- # c.keyframe_points.remove(c.keyframe_points[0])
- if animated:
- props.animated = True
-
-
-def check_meshprops(props, obs):
- ''' checks polycount, manifold, mesh parts (not implemented)'''
- fc = 0
- fcr = 0
- tris = 0
- quads = 0
- ngons = 0
- vc = 0
-
- edges_counts = {}
- manifold = True
-
- for ob in obs:
- if ob.type == 'MESH' or ob.type == 'CURVE':
- ob_eval = None
- if ob.type == 'CURVE':
- # depsgraph = bpy.context.evaluated_depsgraph_get()
- # object_eval = ob.evaluated_get(depsgraph)
- mesh = ob.to_mesh()
- else:
- mesh = ob.data
- fco = len(mesh.polygons)
- fc += fco
- vc += len(mesh.vertices)
- fcor = fco
- for f in mesh.polygons:
- # face sides counter
- if len(f.vertices) == 3:
- tris += 1
- elif len(f.vertices) == 4:
- quads += 1
- elif len(f.vertices) > 4:
- ngons += 1
-
- # manifold counter
- for i, v in enumerate(f.vertices):
- v1 = f.vertices[i - 1]
- e = (min(v, v1), max(v, v1))
- edges_counts[e] = edges_counts.get(e, 0) + 1
-
- # all meshes have to be manifold for this to work.
- manifold = manifold and not any(i in edges_counts.values() for i in [0, 1, 3, 4])
-
- for m in ob.modifiers:
- if m.type == 'SUBSURF' or m.type == 'MULTIRES':
- fcor *= 4 ** m.render_levels
- if m.type == 'SOLIDIFY': # this is rough estimate, not to waste time with evaluating all nonmanifold edges
- fcor *= 2
- if m.type == 'ARRAY':
- fcor *= m.count
- if m.type == 'MIRROR':
- fcor *= 2
- if m.type == 'DECIMATE':
- fcor *= m.ratio
- fcr += fcor
-
- if ob_eval:
- ob_eval.to_mesh_clear()
-
- # write out props
- props.face_count = fc
- props.face_count_render = fcr
- # print(tris, quads, ngons)
- if quads > 0 and tris == 0 and ngons == 0:
- props.mesh_poly_type = 'QUAD'
- elif quads > tris and quads > ngons:
- props.mesh_poly_type = 'QUAD_DOMINANT'
- elif tris > quads and tris > quads:
- props.mesh_poly_type = 'TRI_DOMINANT'
- elif quads == 0 and tris > 0 and ngons == 0:
- props.mesh_poly_type = 'TRI'
- elif ngons > quads and ngons > tris:
- props.mesh_poly_type = 'NGON'
- else:
- props.mesh_poly_type = 'OTHER'
-
- props.manifold = manifold
-
-
-def countObs(props, obs):
- ob_types = {}
- count = len(obs)
- for ob in obs:
- otype = ob.type.lower()
- ob_types[otype] = ob_types.get(otype, 0) + 1
- props.object_count = count
-
-
-def check_modifiers(props, obs):
- # modif_mapping = {
- # }
- modifiers = []
- for ob in obs:
- for m in ob.modifiers:
- mtype = m.type
- mtype = mtype.replace('_', ' ')
- mtype = mtype.lower()
- # mtype = mtype.capitalize()
- if mtype not in modifiers:
- modifiers.append(mtype)
- if m.type == 'SMOKE':
- if m.smoke_type == 'FLOW':
- smt = m.flow_settings.smoke_flow_type
- if smt == 'BOTH' or smt == 'FIRE':
- modifiers.append('fire')
-
- # for mt in modifiers:
- effectmodifiers = ['soft body', 'fluid simulation', 'particle system', 'collision', 'smoke', 'cloth',
- 'dynamic paint']
- for m in modifiers:
- if m in effectmodifiers:
- props.simulation = True
- if ob.rigid_body is not None:
- props.simulation = True
- modifiers.append('rigid body')
- finalstr = ''
- for m in modifiers:
- finalstr += m
- finalstr += ','
- props.modifiers = finalstr
-
-
-def get_autotags():
- """ call all analysis functions """
- ui = bpy.context.window_manager.blenderkitUI
- if ui.asset_type == 'MODEL':
- ob = utils.get_active_model()
- obs = utils.get_hierarchy(ob)
- props = ob.blenderkit
- if props.name == "":
- props.name = ob.name
-
- # reset some properties here, because they might not get re-filled at all when they aren't needed anymore.
- props.texture_resolution_max = 0
- props.texture_resolution_min = 0
- # disabled printing checking, some 3d print addon bug.
- # check_printable( props, obs)
- check_render_engine(props, obs)
-
- dim, bbox_min, bbox_max = utils.get_dimensions(obs)
- props.dimensions = dim
- props.bbox_min = bbox_min
- props.bbox_max = bbox_max
-
- check_rig(props, obs)
- check_anim(props, obs)
- check_meshprops(props, obs)
- check_modifiers(props, obs)
- countObs(props, obs)
- elif ui.asset_type == 'MATERIAL':
- # reset some properties here, because they might not get re-filled at all when they aren't needed anymore.
-
- mat = utils.get_active_asset()
- props = mat.blenderkit
- props.texture_resolution_max = 0
- props.texture_resolution_min = 0
- check_material(props, mat)
- elif ui.asset_type == 'HDR':
- # reset some properties here, because they might not get re-filled at all when they aren't needed anymore.
-
- hdr = utils.get_active_asset()
- props = hdr.blenderkit
- props.texture_resolution_max = max(hdr.size[0],hdr.size[1])
-
-
-class AutoFillTags(bpy.types.Operator):
- """Fill tags for asset. Now run before upload, no need to interact from user side"""
- bl_idname = "object.blenderkit_auto_tags"
- bl_label = "Generate Auto Tags for BlenderKit"
- bl_options = {'REGISTER', 'UNDO', 'INTERNAL'}
-
- @classmethod
- def poll(cls, context):
- return utils.uploadable_asset_poll()
-
- def execute(self, context):
- get_autotags()
- return {'FINISHED'}
-
-
-def register_asset_inspector():
- bpy.utils.register_class(AutoFillTags)
-
-
-def unregister_asset_inspector():
- bpy.utils.unregister_class(AutoFillTags)
-
-
-if __name__ == "__main__":
- register()
diff --git a/blenderkit/asset_pack_bg.py b/blenderkit/asset_pack_bg.py
deleted file mode 100644
index c59ca08d..00000000
--- a/blenderkit/asset_pack_bg.py
+++ /dev/null
@@ -1,8 +0,0 @@
-import sys
-import json
-from blenderkit import resolutions
-
-BLENDERKIT_EXPORT_DATA = sys.argv[-1]
-
-if __name__ == "__main__":
- resolutions.run_bg(sys.argv[-1])
diff --git a/blenderkit/autothumb.py b/blenderkit/autothumb.py
deleted file mode 100644
index 330d31a2..00000000
--- a/blenderkit/autothumb.py
+++ /dev/null
@@ -1,671 +0,0 @@
-# ##### 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'
-
- # if this isn't here, blender crashes.
- 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)
-
- 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")
- # if this isn't here, blender crashes.
- 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)
-
- thumb_dir = os.path.dirname(bpy.data.filepath)
- an_slug = paths.slugify(asset.name)
-
- thumb_path = os.path.join(thumb_dir, an_slug)
- rel_thumb_path = os.path.join('//', an_slug)
-
- # auto increase number of the generated thumbnail.
- i = 0
- while os.path.isfile(thumb_path + '.png'):
- thumb_path = os.path.join(thumb_dir, an_slug + '_' + str(i).zfill(4))
- rel_thumb_path = os.path.join('//', an_slug + '_' + 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()
- an_slug = paths.slugify(asset_data['name'])
-
- tempdir = tempfile.mkdtemp()
-
- thumb_path = os.path.join(tempdir,an_slug)
-
- 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 = bpy.context.window_manager.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
deleted file mode 100644
index 37d7c783..00000000
--- a/blenderkit/autothumb_material_bg.py
+++ /dev/null
@@ -1,171 +0,0 @@
-# ##### 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 utils, append_link, bg_blender, upload_bg, download
-
-import sys, json, math, os
-import bpy
-from pathlib import Path
-
-
-BLENDERKIT_EXPORT_DATA = sys.argv[-1]
-
-
-def render_thumbnails():
- bpy.ops.render.render(write_still=True, animation=False)
-
-
-def unhide_collection(cname):
- collection = bpy.context.scene.collection.children[cname]
- collection.hide_viewport = False
- collection.hide_render = False
- collection.hide_select = False
-
-
-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)
- if data.get('do_download'):
- #need to save the file, so that asset doesn't get downloaded into addon directory
- temp_blend_path = os.path.join(data['tempdir'], 'temp.blend')
-
- # if this isn't here, blender crashes.
- bpy.context.preferences.filepaths.file_preview_type = 'NONE'
-
- bpy.ops.wm.save_as_mainfile(filepath=temp_blend_path)
-
- 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)
-
-
- s = bpy.context.scene
-
- colmapdict = {
- 'BALL': 'Ball',
- 'BALL_COMPLEX': 'Ball complex',
- 'FLUID': 'Fluid',
- 'CLOTH': 'Cloth',
- 'HAIR': 'Hair'
- }
- unhide_collection(colmapdict[data["thumbnail_type"]])
- if data['thumbnail_background']:
- unhide_collection('Background')
- bpy.data.materials["bg checker colorable"].node_tree.nodes['input_level'].outputs['Value'].default_value \
- = data['thumbnail_background_lightness']
- tscale = data["thumbnail_scale"]
- scaler = bpy.context.view_layer.objects['scaler']
- scaler.scale = (tscale, tscale, tscale)
- utils.activate(scaler)
- bpy.ops.object.transform_apply(location=False, rotation=False, scale=True)
-
- bpy.context.view_layer.update()
-
- for ob in bpy.context.visible_objects:
- if ob.name[:15] == 'MaterialPreview':
- utils.activate(ob)
- bpy.ops.object.transform_apply(location=False, rotation=False, scale=True)
-
- ob.material_slots[0].material = mat
- ob.data.use_auto_texspace = False
- ob.data.texspace_size.x = 1 #/ tscale
- ob.data.texspace_size.y = 1 #/ tscale
- ob.data.texspace_size.z = 1 #/ tscale
- if data["adaptive_subdivision"] == True:
- ob.cycles.use_adaptive_subdivision = True
-
- else:
- ob.cycles.use_adaptive_subdivision = False
- ts = data['texture_size_meters']
- 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()
-
- s.cycles.volume_step_size = tscale * .1
-
- if user_preferences.thumbnail_use_gpu:
- bpy.context.scene.cycles.device = 'GPU'
-
- s.cycles.samples = data['thumbnail_samples']
- bpy.context.view_layer.cycles.use_denoising = data['thumbnail_denoising']
-
- # import blender's HDR here
- hdr_path = Path('datafiles/studiolights/world/interior.exr')
- bpath = Path(bpy.utils.resource_path('LOCAL'))
- ipath = bpath / hdr_path
- ipath = str(ipath)
-
- # this stuff is for mac and possibly linux. For blender // means relative path.
- # for Mac, // means start of absolute path
- if ipath.startswith('//'):
- ipath = ipath[1:]
-
- img = bpy.data.images['interior.exr']
- img.filepath = ipath
- img.reload()
-
- 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 = data['thumbnail_path']
- bg_blender.progress('rendering thumbnail')
- # bpy.ops.wm.save_as_mainfile(filepath='C:/tmp/test.blend')
- # fal
- 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')
-
-
- except Exception as e:
- print(e)
- import traceback
-
- traceback.print_exc()
-
- sys.exit(1)
diff --git a/blenderkit/autothumb_model_bg.py b/blenderkit/autothumb_model_bg.py
deleted file mode 100644
index 2ce76830..00000000
--- a/blenderkit/autothumb_model_bg.py
+++ /dev/null
@@ -1,207 +0,0 @@
-# ##### 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 utils, append_link, bg_blender, download, upload_bg, upload
-
-import sys, json, math, os
-import bpy
-import mathutils
-
-BLENDERKIT_EXPORT_DATA = sys.argv[-1]
-
-
-def get_obnames():
- with open(BLENDERKIT_EXPORT_DATA, 'r',encoding='utf-8') as s:
- data = json.load(s)
- obnames = eval(data['models'])
- return obnames
-
-
-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
- # reset parent rotation, so we see how it really snaps.
- parent.rotation_euler = (0, 0, 0)
- bpy.context.view_layer.update()
- minx, miny, minz, maxx, maxy, maxz = utils.get_bounds_worldspace(obs)
-
- cx = (maxx - minx) / 2 + minx
- cy = (maxy - miny) / 2 + miny
- for ob in s.collection.objects:
- ob.select_set(False)
-
- bpy.context.view_layer.objects.active = parent
- parent.location += mathutils.Vector((-cx, -cy, -minz))
-
- camZ = s.camera.parent.parent
- camZ.location.z = (maxz - minz) / 2
- dx = (maxx - minx)
- dy = (maxy - miny)
- dz = (maxz - minz)
- r = math.sqrt(dx * dx + dy * dy + dz * dz)
-
- scaler = bpy.context.view_layer.objects['scaler']
- scaler.scale = (r, r, r)
- coef = .7
- r *= coef
- camZ.scale = (r, r, r)
- bpy.context.view_layer.update()
-
-
-def render_thumbnails():
- bpy.ops.render.render(write_still=True, animation=False)
-
-
-if __name__ == "__main__":
- try:
- with open(BLENDERKIT_EXPORT_DATA, 'r',encoding='utf-8') as s:
- data = json.load(s)
-
- user_preferences = bpy.context.preferences.addons['blenderkit'].preferences
-
-
- if data.get('do_download'):
- # if this isn't here, blender crashes.
- bpy.context.preferences.filepaths.file_preview_type = 'NONE'
-
- #need to save the file, so that asset doesn't get downloaded into addon directory
- temp_blend_path = os.path.join(data['tempdir'], 'temp.blend')
- bpy.ops.wm.save_as_mainfile(filepath = temp_blend_path)
-
- 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)
- bpy.context.view_layer.update()
-
-
- camdict = {
- 'GROUND': 'camera ground',
- 'WALL': 'camera wall',
- 'CEILING': 'camera ceiling',
- 'FLOAT': 'camera float'
- }
-
- bpy.context.scene.camera = bpy.data.objects[camdict[data['thumbnail_snap_to']]]
- center_obs_for_thumbnail(allobs)
- bpy.context.scene.render.filepath = data['thumbnail_path']
- if user_preferences.thumbnail_use_gpu:
- bpy.context.scene.cycles.device = 'GPU'
-
- fdict = {
- 'DEFAULT': 1,
- 'FRONT': 2,
- 'SIDE': 3,
- 'TOP': 4,
- }
- s = bpy.context.scene
- s.frame_set(fdict[data['thumbnail_angle']])
-
- snapdict = {
- 'GROUND': 'Ground',
- 'WALL': 'Wall',
- 'CEILING': 'Ceiling',
- 'FLOAT': 'Float'
- }
-
- collection = bpy.context.scene.collection.children[snapdict[data['thumbnail_snap_to']]]
- collection.hide_viewport = False
- collection.hide_render = False
- collection.hide_select = False
-
- main_object.rotation_euler = (0, 0, 0)
- bpy.data.materials['bkit background'].node_tree.nodes['Value'].outputs['Value'].default_value \
- = data['thumbnail_background_lightness']
- s.cycles.samples = data['thumbnail_samples']
- bpy.context.view_layer.cycles.use_denoising = data['thumbnail_denoising']
- bpy.context.view_layer.update()
-
- # import blender's HDR here
- # hdr_path = Path('datafiles/studiolights/world/interior.exr')
- # bpath = Path(bpy.utils.resource_path('LOCAL'))
- # ipath = bpath / hdr_path
- # ipath = str(ipath)
-
- # this stuff is for mac and possibly linux. For blender // means relative path.
- # for Mac, // means start of absolute path
- # if ipath.startswith('//'):
- # ipath = ipath[1:]
- #
- # img = bpy.data.images['interior.exr']
- # img.filepath = ipath
- # img.reload()
-
- bpy.context.scene.render.resolution_x = int(data['thumbnail_resolution'])
- bpy.context.scene.render.resolution_y = int(data['thumbnail_resolution'])
-
- bg_blender.progress('rendering thumbnail')
- render_thumbnails()
- fpath = data['thumbnail_path'] + '.jpg'
- if data.get('upload_after_render') and data.get('asset_data'):
- # try to patch for the sake of older assets where thumbnail update doesn't work for the reasont
- # that original thumbnail files aren't available.
- # upload.patch_individual_metadata(data['asset_data']['id'], {}, user_preferences)
- bg_blender.progress('uploading thumbnail')
- file = {
- "type": "thumbnail",
- "index": 0,
- "file_path": fpath
- }
- upload_data = {
- "name": data['asset_data']['name'],
- "token": user_preferences.api_key,
- "id": data['asset_data']['id']
- }
-
- upload_bg.upload_file(upload_data, file)
-
- bg_blender.progress('background autothumbnailer finished successfully')
-
- except:
- import traceback
-
- traceback.print_exc()
- sys.exit(1)
diff --git a/blenderkit/bg_blender.py b/blenderkit/bg_blender.py
deleted file mode 100644
index 8495c076..00000000
--- a/blenderkit/bg_blender.py
+++ /dev/null
@@ -1,278 +0,0 @@
-# ##### 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 utils
-
-import bpy
-import sys, threading, os
-import re
-
-from bpy.props import (
- EnumProperty,
-)
-
-bg_processes = []
-
-
-class threadCom: # object passed to threads to read background process stdout info
- ''' Object to pass data between thread and '''
-
- def __init__(self, eval_path_computing, eval_path_state, eval_path, process_type, proc, location=None, name=''):
- # self.obname=ob.name
- self.name = name
- self.eval_path_computing = eval_path_computing # property that gets written to.
- self.eval_path_state = eval_path_state # property that gets written to.
- self.eval_path = eval_path # property that gets written to.
- self.process_type = process_type
- self.outtext = ''
- self.proc = proc
- self.lasttext = ''
- self.message = '' # the message to be sent.
- self.progress = 0.0
- self.location = location
- self.error = False
- self.log = ''
-
-
-def threadread(tcom):
- '''reads stdout of background process.
- this threads basically waits for a stdout line to come in,
- 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)
- s = inline.find('progress{')
- if s > -1:
- e = inline.find('}')
- tcom.outtext = inline[s + 9:e]
- found = True
- if tcom.outtext.find('%') > -1:
- tcom.progress = float(re.findall('\d+\.\d+|\d+', tcom.outtext)[0])
- return
- if s == -1:
- s = inline.find('Remaining')
- if s > -1:
- # e=inline.find('}')
- tcom.outtext = inline[s: s + 18]
- found = True
- return
- if len(inline) > 3:
- print(inline, len(inline))
- # if inline.find('Error'):
- # tcom.error = True
- # tcom.outtext = inline[2:]
-
-
-def progress(text, n=None):
- '''function for reporting during the script, works for background operations in the header.'''
- # for i in range(n+1):
- # sys.stdout.flush()
- text = str(text)
- if n is None:
- n = ''
- else:
- n = ' ' + ' ' + str(int(n * 1000) / 1000) + '% '
- spaces = ' ' * (len(text) + 55)
- try:
- sys.stdout.write('progress{%s%s}\n' % (text, n))
-
- sys.stdout.flush()
- except Exception as e:
- print('background progress reporting race condition')
- print(e)
-
-
-# @bpy.app.handlers.persistent
-def bg_update():
- '''monitoring of background process'''
- text = ''
- #utils.p('timer search')
- # utils.p('start bg_blender timer bg_update')
-
- s = bpy.context.scene
-
- global bg_processes
- if len(bg_processes) == 0:
- # utils.p('end bg_blender timer bg_update')
-
- 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]
- tcom = p[1]
- if not readthread.is_alive():
- readthread.join()
- # readthread.
- estring = None
- if tcom.error:
- estring = tcom.eval_path_computing + ' = False'
- tcom.lasttext = tcom.outtext
- if tcom.outtext != '':
- tcom.outtext = ''
- text =tcom.lasttext.replace("'","")
- estring = tcom.eval_path_state + ' = text'
- # print(tcom.lasttext)
- if 'finished successfully' in tcom.lasttext:
- bg_processes.remove(p)
- estring = tcom.eval_path_computing + ' = False'
- 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:
- # utils.p('end bg_blender timer bg_update')
-
- return .3
- # utils.p('end bg_blender timer bg_update')
-
- return 1.
-
-
-process_types = (
- ('UPLOAD', 'Upload', ''),
- ('THUMBNAILER', 'Thumbnailer', ''),
-)
-
-process_sources = (
- ('MODEL', 'Model', 'set of objects'),
- ('SCENE', 'Scene', 'set of scenes'),
- ('HDR', 'HDR', 'HDR image'),
- ('MATERIAL', 'Material', 'any .blend Material'),
- ('TEXTURE', 'Texture', 'a texture, or texture set'),
- ('BRUSH', 'Brush', 'brush, can be any type of blender brush'),
-)
-
-
-class KillBgProcess(bpy.types.Operator):
- '''Remove processes in background'''
- bl_idname = "object.kill_bg_process"
- bl_label = "Kill Background Process"
- bl_options = {'REGISTER'}
-
- process_type: EnumProperty(
- name="Type",
- items=process_types,
- description="Type of process",
- default="UPLOAD",
- )
-
- process_source: EnumProperty(
- name="Source",
- items=process_sources,
- description="Source of process",
- default="MODEL",
- )
-
- def execute(self, context):
- s = bpy.context.scene
-
- cls = bpy.ops.object.convert.__class__
- # first do the easy stuff...TODO all cases.
- props = utils.get_upload_props()
- if self.process_type == 'UPLOAD':
- props.uploading = False
- if self.process_type == 'THUMBNAILER':
- props.is_generating_thumbnail = False
- global blenderkit_bg_process
- # print('killing', self.process_source, self.process_type)
- # then go kill the process. this wasn't working for unsetting props and that was the reason for changing to the method above.
-
- processes = bg_processes
- for p in processes:
-
- tcom = p[1]
- # print(tcom.process_type, self.process_type)
- if tcom.process_type == self.process_type:
- source = eval(tcom.eval_path)
- kill = False
- #TODO HDR - add killing of process
- if source.bl_rna.name == 'Object' and self.process_source == 'MODEL':
- if source.name == bpy.context.active_object.name:
- kill = True
- if source.bl_rna.name == 'Scene' and self.process_source == 'SCENE':
- if source.name == bpy.context.scene.name:
- kill = True
- if source.bl_rna.name == 'Image' and self.process_source == 'HDR':
- ui_props = bpy.context.window_manager.blenderkitUI
- if source.name == ui_props.hdr_upload_image.name:
- kill = False
-
- if source.bl_rna.name == 'Material' and self.process_source == 'MATERIAL':
- if source.name == bpy.context.active_object.active_material.name:
- kill = True
- if source.bl_rna.name == 'Brush' and self.process_source == 'BRUSH':
- brush = utils.get_active_brush()
- if brush is not None and source.name == brush.name:
- kill = True
- if kill:
- estring = tcom.eval_path_computing + ' = False'
- exec(estring)
- processes.remove(p)
- tcom.proc.kill()
-
- return {'FINISHED'}
-
-
-def add_bg_process(location=None, name=None, eval_path_computing='', eval_path_state='', eval_path='', process_type='',
- process=None):
- '''adds process for monitoring'''
- global bg_processes
- tcom = threadCom(eval_path_computing, eval_path_state, eval_path, process_type, process, location, name)
- readthread = threading.Thread(target=threadread, args=([tcom]), daemon=True)
- readthread.start()
-
- bg_processes.append([readthread, tcom])
- # if not bpy.app.timers.is_registered(bg_update):
- # bpy.app.timers.register(bg_update, persistent=True)
-
-
-def register():
- bpy.utils.register_class(KillBgProcess)
- user_preferences = bpy.context.preferences.addons['blenderkit'].preferences
- if user_preferences.use_timers and not bpy.app.background:
- bpy.app.timers.register(bg_update)
-
-
-def unregister():
- bpy.utils.unregister_class(KillBgProcess)
- if bpy.app.timers.is_registered(bg_update):
- bpy.app.timers.unregister(bg_update)
diff --git a/blenderkit/bkit_oauth.py b/blenderkit/bkit_oauth.py
deleted file mode 100644
index b4e944a3..00000000
--- a/blenderkit/bkit_oauth.py
+++ /dev/null
@@ -1,201 +0,0 @@
-# ##### 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 tasks_queue, utils, paths, search, categories, oauth, ui, ui_panels, colors, reports
-
-import bpy
-
-import threading
-import requests
-import time
-import logging
-
-
-bk_logger = logging.getLogger('blenderkit')
-
-from bpy.props import (
- BoolProperty,
-)
-
-CLIENT_ID = "IdFRwa3SGA8eMpzhRVFMg5Ts8sPK93xBjif93x0F"
-PORTS = [62485, 65425, 55428, 49452, 35452, 25152, 5152, 1234]
-
-active_authenticator = None
-
-
-def login_thread(signup=False):
- global active_authenticator
- r_url = paths.get_oauth_landing_url()
- url = paths.get_bkit_url()
- authenticator = oauth.SimpleOAuthAuthenticator(server_url=url, client_id=CLIENT_ID, ports=PORTS)
- # we store authenticator globally to be able to ping the server if connection fails.
- active_authenticator = authenticator
- thread = threading.Thread(target=login, args=([signup, url, r_url, authenticator]), daemon=True)
- thread.start()
-
-
-def login(signup, url, r_url, authenticator):
- try:
- auth_token, refresh_token, oauth_response = authenticator.get_new_token(register=signup, redirect_url=r_url)
- except Exception as e:
- tasks_queue.add_task((reports.add_report, (e, 20, colors.RED)))
-
- bk_logger.debug('tokens retrieved')
- tasks_queue.add_task((write_tokens, (auth_token, refresh_token, oauth_response)))
-
-
-def refresh_token_thread():
- preferences = bpy.context.preferences.addons['blenderkit'].preferences
- if len(preferences.api_key_refresh) > 0 and preferences.refresh_in_progress == False:
- preferences.refresh_in_progress = True
- url = paths.get_bkit_url()
- thread = threading.Thread(target=refresh_token, args=([preferences.api_key_refresh, url]), daemon=True)
- thread.start()
- else:
- reports.add_report('Already Refreshing token, will be ready soon. If this fails, please login again in Login panel.')
-
-
-def refresh_token(api_key_refresh, url):
- authenticator = oauth.SimpleOAuthAuthenticator(server_url=url, client_id=CLIENT_ID, ports=PORTS)
- auth_token, refresh_token, oauth_response = authenticator.get_refreshed_token(api_key_refresh)
- if auth_token is not None and refresh_token is not None:
- tasks_queue.add_task((write_tokens, (auth_token, refresh_token, oauth_response)))
- return auth_token, refresh_token, oauth_response
-
-
-def write_tokens(auth_token, refresh_token, oauth_response):
- bk_logger.debug('writing tokens')
- preferences = bpy.context.preferences.addons['blenderkit'].preferences
- preferences.api_key_refresh = refresh_token
- preferences.api_key = auth_token
- preferences.api_key_timeout = time.time() + oauth_response['expires_in']
- preferences.api_key_life = oauth_response['expires_in']
- preferences.login_attempt = False
- preferences.refresh_in_progress = False
- props = utils.get_search_props()
- if props is not None:
- props.report = ''
- reports.add_report('BlenderKit Re-Login success')
- search.get_profile()
-
- categories.fetch_categories_thread(auth_token, force = False)
-
-
-class RegisterLoginOnline(bpy.types.Operator):
- """Login online on BlenderKit webpage"""
-
- bl_idname = "wm.blenderkit_login"
- bl_label = "BlenderKit login/signup"
- bl_options = {'REGISTER', 'UNDO'}
-
- signup: BoolProperty(
- name="create a new account",
- description="True for register, otherwise login",
- default=False,
- options={'SKIP_SAVE'}
- )
-
- message: bpy.props.StringProperty(
- name="Message",
- description="",
- default="You were logged out from BlenderKit.\n Clicking OK takes you to web login. ")
-
- @classmethod
- def poll(cls, context):
- return True
-
- def draw(self, context):
- layout = self.layout
- utils.label_multiline(layout, text=self.message, width = 300)
-
- def execute(self, context):
- preferences = bpy.context.preferences.addons['blenderkit'].preferences
- preferences.login_attempt = True
- login_thread(self.signup)
- return {'FINISHED'}
-
- def invoke(self, context, event):
- wm = bpy.context.window_manager
- preferences = bpy.context.preferences.addons['blenderkit'].preferences
- preferences.api_key_refresh = ''
- preferences.api_key = ''
- return wm.invoke_props_dialog(self)
-
-
-class Logout(bpy.types.Operator):
- """Logout from BlenderKit immediately"""
-
- bl_idname = "wm.blenderkit_logout"
- bl_label = "BlenderKit logout"
- bl_options = {'REGISTER', 'UNDO'}
-
- @classmethod
- def poll(cls, context):
- return True
-
- def execute(self, context):
- preferences = bpy.context.preferences.addons['blenderkit'].preferences
- preferences.login_attempt = False
- preferences.api_key_refresh = ''
- preferences.api_key = ''
- if bpy.context.window_manager.get('bkit profile'):
- del (bpy.context.window_manager['bkit profile'])
- return {'FINISHED'}
-
-
-class CancelLoginOnline(bpy.types.Operator):
- """Cancel login attempt"""
-
- bl_idname = "wm.blenderkit_login_cancel"
- bl_label = "BlenderKit login cancel"
- bl_options = {'REGISTER', 'UNDO'}
-
- @classmethod
- def poll(cls, context):
- return True
-
- def execute(self, context):
- global active_authenticator
- preferences = bpy.context.preferences.addons['blenderkit'].preferences
- preferences.login_attempt = False
- try:
- if active_authenticator is not None:
- requests.get(active_authenticator.redirect_uri)
- active_authenticator = None
- except Exception as e:
- print('stopped login attempt')
- print(e)
- return {'FINISHED'}
-
-
-classes = (
- RegisterLoginOnline,
- CancelLoginOnline,
- Logout,
-)
-
-
-def register():
- for c in classes:
- bpy.utils.register_class(c)
-
-
-def unregister():
- for c in classes:
- bpy.utils.unregister_class(c)
diff --git a/blenderkit/bl_ui_widgets/__init__.py b/blenderkit/bl_ui_widgets/__init__.py
deleted file mode 100644
index a1db444d..00000000
--- a/blenderkit/bl_ui_widgets/__init__.py
+++ /dev/null
@@ -1,36 +0,0 @@
-bl_info = {
- "name": "BL UI Widgets",
- "description": "UI Widgets to draw in the 3D view",
- "author": "Jayanam",
- "version": (0, 6, 4, 2),
- "blender": (2, 80, 0),
- "location": "View3D",
- "category": "Object"}
-
-# Blender imports
-import bpy
-
-from bpy.props import *
-
-
-addon_keymaps = []
-
-def register():
-
- bpy.utils.register_class(DP_OT_draw_operator)
- kcfg = bpy.context.window_manager.keyconfigs.addon
- if kcfg:
- km = kcfg.keymaps.new(name='3D View', space_type='VIEW_3D')
-
-
- addon_keymaps.append((km, kmi))
-
-def unregister():
- for km, kmi in addon_keymaps:
- km.keymap_items.remove(kmi)
- addon_keymaps.clear()
-
- bpy.utils.unregister_class(DP_OT_draw_operator)
-
-if __name__ == "__main__":
- register()
diff --git a/blenderkit/bl_ui_widgets/bl_ui_button.py b/blenderkit/bl_ui_widgets/bl_ui_button.py
deleted file mode 100644
index db81b9f8..00000000
--- a/blenderkit/bl_ui_widgets/bl_ui_button.py
+++ /dev/null
@@ -1,205 +0,0 @@
-from . bl_ui_widget import *
-
-import blf
-import bpy
-
-class BL_UI_Button(BL_UI_Widget):
-
- def __init__(self, x, y, width, height):
- super().__init__(x, y, width, height)
- self._text_color = (1.0, 1.0, 1.0, 1.0)
- self._hover_bg_color = (0.5, 0.5, 0.5, 1.0)
- self._select_bg_color = (0.7, 0.7, 0.7, 1.0)
-
- self._text = "Button"
- self._text_size = 16
- self._textpos = (x, y)
-
- self.__state = 0
- self.__image = None
- self.__image_size = (24, 24)
- self.__image_position = (4, 2)
-
- @property
- def text_color(self):
- return self._text_color
-
- @text_color.setter
- def text_color(self, value):
- self._text_color = value
-
- @property
- def text(self):
- return self._text
-
- @text.setter
- def text(self, value):
- self._text = value
-
- @property
- def text_size(self):
- return self._text_size
-
- @text_size.setter
- def text_size(self, value):
- self._text_size = value
-
- @property
- def hover_bg_color(self):
- return self._hover_bg_color
-
- @hover_bg_color.setter
- def hover_bg_color(self, value):
- self._hover_bg_color = value
-
- @property
- def select_bg_color(self):
- return self._select_bg_color
-
- @select_bg_color.setter
- def select_bg_color(self, value):
- self._select_bg_color = value
-
- def set_image_size(self, imgage_size):
- self.__image_size = imgage_size
-
- def set_image_position(self, image_position):
- self.__image_position = image_position
-
- def set_image(self, rel_filepath):
- #first try to access the image, for cases where it can get removed
- try:
- self.__image
- self.__image.filepath
- self.__image.pixels
- except:
- self.__image = None
- try:
- if self.__image is None or self.__image.filepath != rel_filepath:
- self.__image = bpy.data.images.load(rel_filepath, check_existing=True)
- self.__image.gl_load()
-
- if self.__image and len(self.__image.pixels) == 0:
- self.__image.reload()
- self.__image.gl_load()
-
- except Exception as e:
- self.__image = None
-
- def update(self, x, y):
- super().update(x, y)
- self._textpos = [x, y]
-
- def draw(self):
- if not self._is_visible:
- return
- area_height = self.get_area_height()
-
- self.shader.bind()
-
- self.set_colors()
-
- bgl.glEnable(bgl.GL_BLEND)
-
- self.batch_panel.draw(self.shader)
-
- self.draw_image()
-
- bgl.glDisable(bgl.GL_BLEND)
-
- # Draw text
- self.draw_text(area_height)
-
- def set_colors(self):
- color = self._bg_color
- text_color = self._text_color
-
- # pressed
- if self.__state == 1:
- color = self._select_bg_color
-
- # hover
- elif self.__state == 2:
- color = self._hover_bg_color
-
- self.shader.uniform_float("color", color)
-
- def draw_text(self, area_height):
- font_id = 1
- blf.size(font_id, self._text_size, 72)
- size = blf.dimensions(0, self._text)
-
- textpos_y = area_height - self._textpos[1] - (self.height + size[1]) / 2.0
- blf.position(font_id, self._textpos[0] + (self.width - size[0]) / 2.0, textpos_y + 1, 0)
-
- r, g, b, a = self._text_color
- blf.color(font_id, r, g, b, a)
-
- blf.draw(font_id, self._text)
-
- def draw_image(self):
- if self.__image is not None:
- try:
- y_screen_flip = self.get_area_height() - self.y_screen
-
- off_x, off_y = self.__image_position
- sx, sy = self.__image_size
-
- # bottom left, top left, top right, bottom right
- vertices = (
- (self.x_screen + off_x, y_screen_flip - off_y),
- (self.x_screen + off_x, y_screen_flip - sy - off_y),
- (self.x_screen + off_x + sx, y_screen_flip - sy - off_y),
- (self.x_screen + off_x + sx, y_screen_flip - off_y))
-
- self.shader_img = gpu.shader.from_builtin('2D_IMAGE')
- self.batch_img = batch_for_shader(self.shader_img, 'TRI_FAN',
- { "pos" : vertices,
- "texCoord": ((0, 1), (0, 0), (1, 0), (1, 1))
- },)
-
- # send image to gpu if it isn't there already
- if self.__image.gl_load():
- raise Exception()
-
- bgl.glActiveTexture(bgl.GL_TEXTURE0)
- bgl.glBindTexture(bgl.GL_TEXTURE_2D, self.__image.bindcode)
-
- self.shader_img.bind()
- self.shader_img.uniform_int("image", 0)
- self.batch_img.draw(self.shader_img)
- return True
- except:
- pass
-
- return False
-
- def set_mouse_down(self, mouse_down_func):
- self.mouse_down_func = mouse_down_func
-
- def mouse_down(self, x, y):
- if self.is_in_rect(x,y):
- self.__state = 1
- try:
- self.mouse_down_func(self)
- except Exception as e:
- print(e)
-
- return True
-
- return False
-
- def mouse_move(self, x, y):
- if self.is_in_rect(x,y):
- if(self.__state != 1):
-
- # hover state
- self.__state = 2
- else:
- self.__state = 0
-
- def mouse_up(self, x, y):
- if self.is_in_rect(x,y):
- self.__state = 2
- else:
- self.__state = 0
diff --git a/blenderkit/bl_ui_widgets/bl_ui_drag_panel.py b/blenderkit/bl_ui_widgets/bl_ui_drag_panel.py
deleted file mode 100644
index 1c318bab..00000000
--- a/blenderkit/bl_ui_widgets/bl_ui_drag_panel.py
+++ /dev/null
@@ -1,59 +0,0 @@
-from . bl_ui_widget import *
-
-class BL_UI_Drag_Panel(BL_UI_Widget):
-
- def __init__(self, x, y, width, height):
- super().__init__(x,y, width, height)
- self.drag_offset_x = 0
- self.drag_offset_y = 0
- self.is_drag = False
- self.widgets = []
-
- def set_location(self, x, y):
- super().set_location(x,y)
- self.layout_widgets()
-
- def add_widget(self, widget):
- self.widgets.append(widget)
-
- def add_widgets(self, widgets):
- self.widgets = widgets
- self.layout_widgets()
-
-
- def layout_widgets(self):
- for widget in self.widgets:
- widget.update(self.x_screen + widget.x, self.y_screen + widget.y)
-
- def update(self, x, y):
- super().update(x - self.drag_offset_x, y + self.drag_offset_y)
-
- def child_widget_focused(self, x, y):
- for widget in self.widgets:
- if widget.is_in_rect(x, y):
- return True
- return False
-
- def mouse_down(self, x, y):
- if self.child_widget_focused(x, y):
- return False
-
- if self.is_in_rect(x,y):
- height = self.get_area_height()
- self.is_drag = True
- self.drag_offset_x = x - self.x_screen
- self.drag_offset_y = y - (height - self.y_screen)
- return True
-
- return False
-
- def mouse_move(self, x, y):
- if self.is_drag:
- height = self.get_area_height()
- self.update(x, height - y)
- self.layout_widgets()
-
- def mouse_up(self, x, y):
- self.is_drag = False
- self.drag_offset_x = 0
- self.drag_offset_y = 0
diff --git a/blenderkit/bl_ui_widgets/bl_ui_draw_op.py b/blenderkit/bl_ui_widgets/bl_ui_draw_op.py
deleted file mode 100644
index bc2e9cb5..00000000
--- a/blenderkit/bl_ui_widgets/bl_ui_draw_op.py
+++ /dev/null
@@ -1,93 +0,0 @@
-import bpy
-
-from bpy.types import Operator
-
-class BL_UI_OT_draw_operator(Operator):
- bl_idname = "object.bl_ui_ot_draw_operator"
- bl_label = "bl ui widgets operator"
- bl_description = "Operator for bl ui widgets"
- bl_options = {'REGISTER'}
-
- def __init__(self):
- self.draw_handle = None
- self.draw_event = None
- self._finished = False
-
- self.widgets = []
-
- def init_widgets(self, context, widgets):
- self.widgets = widgets
- for widget in self.widgets:
- widget.init(context)
-
- def on_invoke(self, context, event):
- pass
-
- def on_finish(self, context):
- self._finished = True
-
- def invoke(self, context, event):
-
- self.on_invoke(context, event)
-
- args = (self, context)
-
- self.register_handlers(args, context)
-
- context.window_manager.modal_handler_add(self)
-
- self.active_window_pointer = context.window.as_pointer()
- self.active_area_pointer = context.area.as_pointer()
- self.active_region_pointer = context.region.as_pointer()
- return {"RUNNING_MODAL"}
-
- def register_handlers(self, args, context):
- self.draw_handle = bpy.types.SpaceView3D.draw_handler_add(self.draw_callback_px, args, "WINDOW", "POST_PIXEL")
- self.draw_event = context.window_manager.event_timer_add(0.1, window=context.window)
-
- def unregister_handlers(self, context):
-
- context.window_manager.event_timer_remove(self.draw_event)
-
- bpy.types.SpaceView3D.draw_handler_remove(self.draw_handle, "WINDOW")
-
- self.draw_handle = None
- self.draw_event = None
-
- def handle_widget_events(self, event):
- result = False
- for widget in self.widgets:
- if widget.handle_event(event):
- result = True
- return result
-
- def modal(self, context, event):
-
- if self._finished:
- return {'FINISHED'}
-
- if context.area:
- context.area.tag_redraw()
-
- if self.handle_widget_events(event):
- return {'RUNNING_MODAL'}
-
- if event.type in {"ESC"}:
- self.finish()
-
- return {"PASS_THROUGH"}
-
- def finish(self):
- self.unregister_handlers(bpy.context)
- self.on_finish(bpy.context)
-
- # Draw handler to paint onto the screen
- def draw_callback_px(self, op, context):
- try:
- if context.area.as_pointer() == self.active_area_pointer:
- for widget in self.widgets:
- widget.draw()
- except:
- pass;
- # context.window_manager.event_timer_remove(self.draw_event)
- # bpy.types.SpaceView3D.draw_handler_remove(self.draw_handle, "WINDOW") \ No newline at end of file
diff --git a/blenderkit/bl_ui_widgets/bl_ui_image.py b/blenderkit/bl_ui_widgets/bl_ui_image.py
deleted file mode 100644
index a11c52c5..00000000
--- a/blenderkit/bl_ui_widgets/bl_ui_image.py
+++ /dev/null
@@ -1,97 +0,0 @@
-from . bl_ui_widget import *
-
-import blf
-import bpy
-
-class BL_UI_Image(BL_UI_Widget):
-
- def __init__(self, x, y, width, height):
- super().__init__(x, y, width, height)
-
- self.__state = 0
- self.__image = None
- self.__image_size = (24, 24)
- self.__image_position = (4, 2)
-
- def set_image_size(self, imgage_size):
- self.__image_size = imgage_size
-
- def set_image_position(self, image_position):
- self.__image_position = image_position
-
- def set_image(self, rel_filepath):
- try:
- if self.__image is None or self.__image.filepath != rel_filepath:
- self.__image = bpy.data.images.load(rel_filepath, check_existing=True)
- self.__image.gl_load()
- except:
- pass
-
- def update(self, x, y):
- super().update(x, y)
-
- def draw(self):
- if not self._is_visible:
- return
-
- area_height = self.get_area_height()
-
- self.shader.bind()
-
- bgl.glEnable(bgl.GL_BLEND)
-
- self.batch_panel.draw(self.shader)
-
- self.draw_image()
-
- bgl.glDisable(bgl.GL_BLEND)
-
-
- def draw_image(self):
- if self.__image is not None:
- try:
- y_screen_flip = self.get_area_height() - self.y_screen
-
- off_x, off_y = self.__image_position
- sx, sy = self.__image_size
-
- # bottom left, top left, top right, bottom right
- vertices = (
- (self.x_screen + off_x, y_screen_flip - off_y),
- (self.x_screen + off_x, y_screen_flip - sy - off_y),
- (self.x_screen + off_x + sx, y_screen_flip - sy - off_y),
- (self.x_screen + off_x + sx, y_screen_flip - off_y))
-
- self.shader_img = gpu.shader.from_builtin('2D_IMAGE')
- self.batch_img = batch_for_shader(self.shader_img, 'TRI_FAN',
- { "pos" : vertices,
- "texCoord": ((0, 1), (0, 0), (1, 0), (1, 1))
- },)
-
- # send image to gpu if it isn't there already
- if self.__image.gl_load():
- raise Exception()
-
- bgl.glActiveTexture(bgl.GL_TEXTURE0)
- bgl.glBindTexture(bgl.GL_TEXTURE_2D, self.__image.bindcode)
-
- self.shader_img.bind()
- self.shader_img.uniform_int("image", 0)
- self.batch_img.draw(self.shader_img)
- return True
- except:
- pass
-
- return False
-
- def set_mouse_down(self, mouse_down_func):
- self.mouse_down_func = mouse_down_func
-
- def mouse_down(self, x, y):
- return False
-
- def mouse_move(self, x, y):
- return
-
- def mouse_up(self, x, y):
- return
diff --git a/blenderkit/bl_ui_widgets/bl_ui_label.py b/blenderkit/bl_ui_widgets/bl_ui_label.py
deleted file mode 100644
index 37ba09f6..00000000
--- a/blenderkit/bl_ui_widgets/bl_ui_label.py
+++ /dev/null
@@ -1,72 +0,0 @@
-from . bl_ui_widget import *
-
-import blf
-
-class BL_UI_Label(BL_UI_Widget):
-
- def __init__(self, x, y, width, height):
- super().__init__(x, y, width, height)
-
- self._text_color = (1.0, 1.0, 1.0, 1.0)
- self._text = "Label"
- self._text_size = 16
- self._ralign = 'LEFT'
- self._valign = 'TOP'
-
- @property
- def text_color(self):
- return self._text_color
-
- @text_color.setter
- def text_color(self, value):
- self._text_color = value
-
- @property
- def text(self):
- return self._text
-
- @text.setter
- def text(self, value):
- self._text = value
-
- @property
- def text_size(self):
- return self._text_size
-
- @text_size.setter
- def text_size(self, value):
- self._text_size = value
-
- def is_in_rect(self, x, y):
- return False
-
- def draw(self):
- if not self._is_visible:
- return
-
-
- area_height = self.get_area_height()
-
- font_id = 1
- blf.size(font_id, self._text_size, 72)
- size = blf.dimensions(font_id, self._text)
-
- textpos_y = area_height - self.y_screen - self.height
-
- r, g, b, a = self._text_color
- x = self.x_screen
- y = textpos_y
- if self._halign != 'LEFT':
- width, height = blf.dimensions(font_id, self._text)
- if self._halign == 'RIGHT':
- x -= width
- elif self._halign == 'CENTER':
- x -= width // 2
- if self._valign == 'CENTER':
- y -= height // 2
- # bottom could be here but there's no reason for it
- blf.position(font_id, x, y, 0)
-
- blf.color(font_id, r, g, b, a)
-
- blf.draw(font_id, self._text)
diff --git a/blenderkit/bl_ui_widgets/bl_ui_widget.py b/blenderkit/bl_ui_widgets/bl_ui_widget.py
deleted file mode 100644
index 90fefddf..00000000
--- a/blenderkit/bl_ui_widgets/bl_ui_widget.py
+++ /dev/null
@@ -1,195 +0,0 @@
-import gpu
-import bgl
-
-from gpu_extras.batch import batch_for_shader
-
-class BL_UI_Widget:
-
- def __init__(self, x, y, width, height):
- self.x = x
- self.y = y
- self.x_screen = x
- self.y_screen = y
- self.width = width
- self.height = height
- self._bg_color = (0.8, 0.8, 0.8, 1.0)
- self._tag = None
- self.context = None
- self.__inrect = False
- self._mouse_down = False
- self._mouse_down_right = False
- self._is_visible = True
-
- def set_location(self, x, y):
- self.x = x
- self.y = y
- self.x_screen = x
- self.y_screen = y
- self.update(x,y)
-
- @property
- def bg_color(self):
- return self._bg_color
-
- @bg_color.setter
- def bg_color(self, value):
- self._bg_color = value
-
- @property
- def visible(self):
- return self._is_visible
-
- @visible.setter
- def visible(self, value):
- self._is_visible = value
-
- @property
- def tag(self):
- return self._tag
-
- @tag.setter
- def tag(self, value):
- self._tag = value
-
- def draw(self):
- if not self._is_visible:
- return
-
- self.shader.bind()
- self.shader.uniform_float("color", self._bg_color)
-
- bgl.glEnable(bgl.GL_BLEND)
- self.batch_panel.draw(self.shader)
- bgl.glDisable(bgl.GL_BLEND)
-
- def init(self, context):
- self.context = context
- self.update(self.x, self.y)
-
- def update(self, x, y):
-
- area_height = self.get_area_height()
- self.x_screen = x
- self.y_screen = y
-
- indices = ((0, 1, 2), (0, 2, 3))
-
- y_screen_flip = area_height - self.y_screen
-
- # bottom left, top left, top right, bottom right
- vertices = (
- (self.x_screen, y_screen_flip),
- (self.x_screen, y_screen_flip - self.height),
- (self.x_screen + self.width, y_screen_flip - self.height),
- (self.x_screen + self.width, y_screen_flip))
-
- self.shader = gpu.shader.from_builtin('2D_UNIFORM_COLOR')
- self.batch_panel = batch_for_shader(self.shader, 'TRIS', {"pos" : vertices}, indices=indices)
-
- def handle_event(self, event):
- if not self._is_visible:
- return False
- x = event.mouse_region_x
- y = event.mouse_region_y
-
- if (event.type == 'LEFTMOUSE'):
- if (event.value == 'PRESS'):
- self._mouse_down = True
- return self.mouse_down(x, y)
- else:
- self._mouse_down = False
- self.mouse_up(x, y)
-
- elif (event.type == 'RIGHTMOUSE'):
-
- if (event.value == 'PRESS'):
- self._mouse_down_right = True
- return self.mouse_down_right(x, y)
- else:
- self._mouse_down_right = False
- self.mouse_up(x, y)
-
- elif (event.type == 'MOUSEMOVE'):
- self.mouse_move(x, y)
- inrect = self.is_in_rect(x, y)
-
- # we enter the rect
- if not self.__inrect and inrect:
- self.__inrect = True
- self.mouse_enter(event, x, y)
-
- # we are leaving the rect
- elif self.__inrect and not inrect:
- self.__inrect = False
- self.mouse_exit(event, x, y)
-
- return False
-
- elif event.value == 'PRESS' and self.__inrect and (event.ascii != '' or event.type in self.get_input_keys()):
-
- return self.text_input(event)
-
- return False
-
- def get_input_keys(self) :
- return []
-
- def get_area_height(self):
- return self.context.area.height
-
- def is_in_rect(self, x, y):
- area_height = self.get_area_height()
-
- widget_y = area_height - self.y_screen
- if (
- (self.x_screen <= x <= (self.x_screen + self.width)) and
- (widget_y >= y >= (widget_y - self.height))
- ):
- # print('is in rect!?')
- # print('area height', area_height)
- # print ('x sceen ',self.x_screen,'x ', x, 'width', self.width)
- # print ('widghet y', widget_y,'y', y, 'height',self.height)
- return True
-
- return False
-
- def text_input(self, event):
- return False
-
- def mouse_down(self, x, y):
- return self.is_in_rect(x,y)
-
- def mouse_down_right(self, x, y):
- return self.is_in_rect(x,y)
-
- def mouse_up(self, x, y):
- pass
-
- def set_mouse_enter(self, mouse_enter_func):
- self.mouse_enter_func = mouse_enter_func
-
- def call_mouse_enter(self):
- try:
- if self.mouse_enter_func:
- self.mouse_enter_func(self)
- except:
- pass
-
- def mouse_enter(self, event, x, y):
- self.call_mouse_enter()
-
- def set_mouse_exit(self, mouse_exit_func):
- self.mouse_exit_func = mouse_exit_func
-
- def call_mouse_exit(self):
- try:
- if self.mouse_exit_func:
- self.mouse_exit_func(self)
- except:
- pass
-
- def mouse_exit(self, event, x, y):
- self.call_mouse_exit()
-
- def mouse_move(self, x, y):
- pass
diff --git a/blenderkit/blendfiles/cleaned.blend b/blenderkit/blendfiles/cleaned.blend
deleted file mode 100644
index 4ad5f57c..00000000
--- a/blenderkit/blendfiles/cleaned.blend
+++ /dev/null
Binary files differ
diff --git a/blenderkit/blendfiles/material_thumbnailer_cycles.blend b/blenderkit/blendfiles/material_thumbnailer_cycles.blend
deleted file mode 100644
index 1faa5807..00000000
--- a/blenderkit/blendfiles/material_thumbnailer_cycles.blend
+++ /dev/null
Binary files differ
diff --git a/blenderkit/blendfiles/thumbnailer.blend b/blenderkit/blendfiles/thumbnailer.blend
deleted file mode 100644
index 6903130d..00000000
--- a/blenderkit/blendfiles/thumbnailer.blend
+++ /dev/null
Binary files differ
diff --git a/blenderkit/categories.py b/blenderkit/categories.py
deleted file mode 100644
index c97c339c..00000000
--- a/blenderkit/categories.py
+++ /dev/null
@@ -1,274 +0,0 @@
-# ##### 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, tasks_queue, rerequests, ui, colors, reports
-
-import requests
-import json
-import os
-import bpy
-import time
-
-import shutil
-import threading
-import logging
-
-bk_logger = logging.getLogger('blenderkit')
-
-
-def count_to_parent(parent):
- for c in parent['children']:
- count_to_parent(c)
- parent['assetCount'] += c['assetCount']
-
-
-def fix_category_counts(categories):
- for c in categories:
- count_to_parent(c)
-
-
-def filter_category(category):
- ''' filter categories with no assets, so they aren't shown in search panel'''
- if category['assetCount'] < 1:
- return True
- else:
- to_remove = []
- for c in category['children']:
- if filter_category(c):
- to_remove.append(c)
- for c in to_remove:
- category['children'].remove(c)
-
-
-def filter_categories(categories):
- for category in categories:
- filter_category(category)
-
-
-def get_category_path(categories, category):
- '''finds the category in all possible subcategories and returns the path to it'''
- category_path = []
- check_categories = categories[:]
- parents = {}
- while len(check_categories) > 0:
- ccheck = check_categories.pop()
- # print(ccheck['name'])
- if not ccheck.get('children'):
- continue
-
- for ch in ccheck['children']:
- # print(ch['name'])
- parents[ch['slug']] = ccheck['slug']
-
- if ch['slug'] == category:
- category_path = [ch['slug']]
- slug = ch['slug']
- while parents.get(slug):
- slug = parents.get(slug)
- category_path.insert(0, slug)
- return category_path
- check_categories.append(ch)
- return category_path
-
-def get_category_name_path(categories, category):
- '''finds the category in all possible subcategories and returns the path to it'''
- category_path = []
- check_categories = categories[:]
- parents = {}
- # utils.pprint(categories)
- while len(check_categories) > 0:
- ccheck = check_categories.pop()
- # print(ccheck['name'])
- if not ccheck.get('children'):
- continue
-
- for ch in ccheck['children']:
- # print(ch['name'])
- parents[ch['slug']] = ccheck
-
- if ch['slug'] == category:
- category_path = [ch['name']]
- slug = ch['slug']
- while parents.get(slug):
- parent = parents.get(slug)
- slug = parent['slug']
-
- category_path.insert(0, parent['name'])
- return category_path
- check_categories.append(ch)
- return category_path
-
-def get_category(categories, cat_path=()):
- for category in cat_path:
- for c in categories:
- if c['slug'] == category:
- categories = c['children']
- if category == cat_path[-1]:
- return (c)
- break;
-
-
-# def get_upload_asset_type(self):
-# typemapper = {
-# bpy.types.Object.blenderkit: 'model',
-# bpy.types.Scene.blenderkit: 'scene',
-# bpy.types.Image.blenderkit: 'hdr',
-# bpy.types.Material.blenderkit: 'material',
-# bpy.types.Brush.blenderkit: 'brush'
-# }
-# asset_type = typemapper[type(self)]
-# return asset_type
-
-def update_category_enums(self, context):
- '''Fixes if lower level is empty - sets it to None, because enum value can be higher.'''
- enums = get_subcategory_enums(self, context)
- if enums[0][0] == 'NONE' and self.subcategory != 'NONE':
- self.subcategory = 'NONE'
-
-
-def update_subcategory_enums(self, context):
- '''Fixes if lower level is empty - sets it to None, because enum value can be higher.'''
- enums = get_subcategory1_enums(self, context)
- if enums[0][0] == 'NONE' and self.subcategory1 != 'NONE':
- self.subcategory1 = 'NONE'
-
-
-def get_category_enums(self, context):
- wm = bpy.context.window_manager
- props = bpy.context.window_manager.blenderkitUI
- asset_type = props.asset_type.lower()
- # asset_type = self.asset_type#get_upload_asset_type(self)
- asset_categories = get_category(wm['bkit_categories'], cat_path=(asset_type,))
- items = []
- for c in asset_categories['children']:
- items.append((c['slug'], c['name'], c['description']))
- if len(items) == 0:
- items.append(('NONE', '', 'no categories on this level defined'))
- return items
-
-
-def get_subcategory_enums(self, context):
- wm = bpy.context.window_manager
- props = bpy.context.window_manager.blenderkitUI
- asset_type = props.asset_type.lower()
- items = []
- if self.category != '':
- asset_categories = get_category(wm['bkit_categories'], cat_path=(asset_type, self.category,))
- for c in asset_categories['children']:
- items.append((c['slug'], c['name'], c['description']))
- if len(items) == 0:
- items.append(('NONE', '', 'no categories on this level defined'))
- # print('subcategory', items)
- return items
-
-
-def get_subcategory1_enums(self, context):
- wm = bpy.context.window_manager
- props = bpy.context.window_manager.blenderkitUI
- asset_type = props.asset_type.lower()
- items = []
- if self.category != '' and self.subcategory != '':
- asset_categories = get_category(wm['bkit_categories'], cat_path=(asset_type, self.category, self.subcategory,))
- if asset_categories:
- for c in asset_categories['children']:
- items.append((c['slug'], c['name'], c['description']))
- if len(items) == 0:
- items.append(('NONE', '', 'no categories on this level defined'))
- return items
-
-
-def copy_categories():
- # this creates the categories system on only
- tempdir = paths.get_temp_dir()
- categories_filepath = os.path.join(tempdir, 'categories.json')
- if not os.path.exists(categories_filepath):
- source_path = paths.get_addon_file(subpath='data' + os.sep + 'categories.json')
- # print('attempt to copy categories from: %s to %s' % (categories_filepath, source_path))
- try:
- shutil.copy(source_path, categories_filepath)
- except:
- print("couldn't copy categories file")
-
-
-def load_categories():
- copy_categories()
- tempdir = paths.get_temp_dir()
- categories_filepath = os.path.join(tempdir, 'categories.json')
-
- wm = bpy.context.window_manager
- try:
- with open(categories_filepath, 'r', encoding='utf-8') as catfile:
- wm['bkit_categories'] = json.load(catfile)
-
- wm['active_category'] = {
- 'MODEL': ['model'],
- 'SCENE': ['scene'],
- 'HDR': ['hdr'],
- 'MATERIAL': ['material'],
- 'BRUSH': ['brush'],
- }
- except:
- print('categories failed to read')
-
-
-#
-catfetch_counter = 0
-
-
-def fetch_categories(API_key, force=False):
- url = paths.get_api_url() + 'categories/'
-
- headers = utils.get_headers(API_key)
-
- tempdir = paths.get_temp_dir()
- categories_filepath = os.path.join(tempdir, 'categories.json')
- if os.path.exists(categories_filepath):
- catfile_age = time.time() - os.path.getmtime(categories_filepath)
- else:
- catfile_age = 10000000
-
- # global catfetch_counter
- # catfetch_counter += 1
- # bk_logger.debug('fetching categories: ', catfetch_counter)
- # bk_logger.debug('age of cat file', catfile_age)
- try:
- # read categories only once per day maximum, or when forced to do so.
- if catfile_age > 86400 or force:
- bk_logger.debug('requesting categories from server')
- r = rerequests.get(url, headers=headers)
- rdata = r.json()
- categories = rdata['results']
- fix_category_counts(categories)
- # filter_categories(categories) #TODO this should filter categories for search, but not for upload. by now off.
- with open(categories_filepath, 'w', encoding='utf-8') as s:
- json.dump(categories, s, ensure_ascii=False, indent=4)
- tasks_queue.add_task((load_categories, ()))
- except Exception as e:
- t = 'BlenderKit failed to download fresh categories from the server'
- tasks_queue.add_task((reports.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')
- shutil.copy(source_path, categories_filepath)
-
-
-def fetch_categories_thread(API_key, force=False):
- cat_thread = threading.Thread(target=fetch_categories, args=([API_key, force]), daemon=True)
- cat_thread.start()
diff --git a/blenderkit/colors.py b/blenderkit/colors.py
deleted file mode 100644
index fe2fb1ac..00000000
--- a/blenderkit/colors.py
+++ /dev/null
@@ -1,25 +0,0 @@
-# ##### 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 #####
-
-# this module defines color palette for BlenderKit UI
-
-WHITE = (1, 1, 1, .9)
-
-TEXT = (.9, .9, .9, .6)
-GREEN = (.9, 1, .9, .6)
-RED = (1, .5, .5, .8)
diff --git a/blenderkit/comments_utils.py b/blenderkit/comments_utils.py
deleted file mode 100644
index 654bbb54..00000000
--- a/blenderkit/comments_utils.py
+++ /dev/null
@@ -1,232 +0,0 @@
-# ##### 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 #####
-
-# mainly update functions and callbacks for ratings properties, here to avoid circular imports.
-import bpy
-from blenderkit import utils, paths, tasks_queue, rerequests
-
-import threading
-import requests
-import logging
-
-bk_logger = logging.getLogger('blenderkit')
-
-
-def upload_comment_thread(url, comment='', api_key=None):
- ''' Upload rating thread function / disconnected from blender data.'''
- headers = utils.get_headers(api_key)
-
- bk_logger.debug('upload comment ' + comment)
-
- # rating_url = url + rating_name + '/'
- data = {
- "content_type": "",
- "object_pk": "",
- "timestamp": "",
- "security_hash": "",
- "honeypot": "",
- "name": "",
- "email": "",
- "url": "",
- "comment": comment,
- "followup": False,
- "reply_to": None
- }
-
- # try:
- r = rerequests.put(url, data=data, verify=True, headers=headers)
- # print(r)
- # print(dir(r))
- # print(r.text)
- # except requests.exceptions.RequestException as e:
- # print('ratings upload failed: %s' % str(e))
-
-
-def upload_comment_flag_thread( asset_id = '', comment_id='', flag='like', api_key=None):
- ''' Upload rating thread function / disconnected from blender data.'''
- headers = utils.get_headers(api_key)
-
- bk_logger.debug('upload comment flag' + str(comment_id))
-
- # rating_url = url + rating_name + '/'
- data = {
- "comment": comment_id,
- "flag": flag,
- }
- url = paths.get_api_url() + 'comments/feedback/'
-
- # try:
- r = rerequests.post(url, data=data, verify=True, headers=headers)
- # print(r.text)
-
- #here it's important we read back, so likes are updated accordingly:
- get_comments(asset_id, api_key)
-
-
-def send_comment_flag_to_thread(asset_id = '', comment_id='', flag='like', api_key = None):
- '''Sens rating into thread rating, main purpose is for tasks_queue.
- One function per property to avoid lost data due to stashing.'''
- thread = threading.Thread(target=upload_comment_flag_thread, args=(asset_id, comment_id, flag, api_key))
- thread.start()
-
-def send_comment_to_thread(url, comment, api_key):
- '''Sens rating into thread rating, main purpose is for tasks_queue.
- One function per property to avoid lost data due to stashing.'''
- thread = threading.Thread(target=upload_comment_thread, args=(url, comment, api_key))
- thread.start()
-
-
-def store_comments_local(asset_id, comments):
- context = bpy.context
- ac = context.window_manager.get('asset comments', {})
- ac[asset_id] = comments
- context.window_manager['asset comments'] = ac
-
-
-def get_comments_local(asset_id):
- context = bpy.context
- context.window_manager['asset comments'] = context.window_manager.get('asset comments', {})
- comments = context.window_manager['asset comments'].get(asset_id)
- if comments:
- return comments
- return None
-
-def get_comments_thread(asset_id, api_key):
- thread = threading.Thread(target=get_comments, args=([asset_id, api_key]), daemon=True)
- thread.start()
-
-def get_comments(asset_id, api_key):
- '''
- Retrieve comments from BlenderKit server. Can be run from a thread
- Parameters
- ----------
- asset_id
- headers
-
- Returns
- -------
- ratings - dict of type:value ratings
- '''
- headers = utils.get_headers(api_key)
-
- url = paths.get_api_url() + 'comments/assets-uuidasset/' + asset_id + '/'
- params = {}
- r = rerequests.get(url, params=params, verify=True, headers=headers)
- if r is None:
- return
- # print(r.status_code)
- if r.status_code == 200:
- rj = r.json()
- # store comments - send them to task queue
- # print('retrieved comments')
- # print(rj)
- tasks_queue.add_task((store_comments_local, (asset_id, rj['results'])))
-
- # if len(rj['results'])==0:
- # # store empty ratings too, so that server isn't checked repeatedly
- # tasks_queue.add_task((store_rating_local_empty,(asset_id,)))
- # return ratings
-
-
-def store_notifications_count_local(all_count):
- '''Store total count of notifications on server in preferences'''
- user_preferences = bpy.context.preferences.addons['blenderkit'].preferences
- user_preferences.notifications_counter = all_count
-
-def store_notifications_local(notifications):
- '''Store notifications in Blender'''
- bpy.context.window_manager['bkit notifications'] = notifications
-
-def count_all_notifications():
- '''Return count of all notifications on server'''
- user_preferences = bpy.context.preferences.addons['blenderkit'].preferences
- return user_preferences.notifications_counter
-
-
-def check_notifications_read():
- '''checks if all notifications were already read, and removes them if so'''
- notifications = bpy.context.window_manager.get('bkit notifications')
- if notifications is None or notifications.get('count') == 0:
- return True
- for n in notifications['results']:
- if n['unread'] == 1:
- return False
- bpy.context.window_manager['bkit notifications'] = None
- return True
-
-def get_notifications_thread(api_key, all_count = 1000):
- thread = threading.Thread(target=get_notifications, args=([api_key, all_count]), daemon=True)
- thread.start()
-
-def get_notifications(api_key, all_count = 1000):
- '''
- Retrieve notifications from BlenderKit server. Can be run from a thread.
-
- Parameters
- ----------
- api_key
- all_count
-
- Returns
- -------
- '''
- headers = utils.get_headers(api_key)
-
- params = {}
-
- url = paths.get_api_url() + 'notifications/all_count/'
- r = rerequests.get(url, params=params, verify=True, headers=headers)
- if r.status_code ==200:
- rj = r.json()
- # print(rj)
- # no new notifications?
- if all_count >= rj['allCount']:
- tasks_queue.add_task((store_notifications_count_local, ([rj['allCount']])))
-
- return
- url = paths.get_api_url() + 'notifications/unread/'
- r = rerequests.get(url, params=params, verify=True, headers=headers)
- if r is None:
- return
- if r.status_code == 200:
- rj = r.json()
- # store notifications - send them to task queue
- tasks_queue.add_task((store_notifications_local, ([rj])))
-
-def mark_notification_read_thread(api_key, notification_id):
- thread = threading.Thread(target=mark_notification_read, args=([api_key, notification_id]), daemon=True)
- thread.start()
-
-def mark_notification_read(api_key, notification_id):
- '''
- mark notification as read
- '''
- headers = utils.get_headers(api_key)
-
- url = paths.get_api_url() + f'notifications/mark-as-read/{notification_id}/'
- params = {}
- r = rerequests.get(url, params=params, verify=True, headers=headers)
- if r is None:
- return
- # print(r.text)
- # if r.status_code == 200:
- # rj = r.json()
- # # store notifications - send them to task queue
- # print(rj)
- # tasks_queue.add_task((mark_notification_read_local, ([notification_id])))
-
diff --git a/blenderkit/data/categories.json b/blenderkit/data/categories.json
deleted file mode 100644
index 376d26d4..00000000
--- a/blenderkit/data/categories.json
+++ /dev/null
@@ -1,6176 +0,0 @@
-[
- {
- "name": "brush",
- "slug": "brush",
- "active": true,
- "thumbnail": null,
- "thumbnailWidth": null,
- "thumbnailHeight": null,
- "order": 0,
- "alternateTitle": "brush",
- "alternateUrl": "",
- "description": "",
- "metaKeywords": "",
- "metaExtra": "",
- "children": [
- {
- "name": "anatomy",
- "slug": "anatomy-brush",
- "active": true,
- "thumbnail": null,
- "thumbnailWidth": null,
- "thumbnailHeight": null,
- "order": 0,
- "alternateTitle": "anatomy",
- "alternateUrl": "",
- "description": "",
- "metaKeywords": "",
- "metaExtra": "",
- "children": [],
- "assetCount": 10,
- "assetCountCumulative": 10
- },
- {
- "name": "animal",
- "slug": "animal-brush",
- "active": true,
- "thumbnail": null,
- "thumbnailWidth": null,
- "thumbnailHeight": null,
- "order": 0,
- "alternateTitle": "animal",
- "alternateUrl": "",
- "description": "",
- "metaKeywords": "",
- "metaExtra": "",
- "children": [],
- "assetCount": 5,
- "assetCountCumulative": 5
- },
- {
- "name": "art",
- "slug": "art-brush",
- "active": true,
- "thumbnail": null,
- "thumbnailWidth": null,
- "thumbnailHeight": null,
- "order": 0,
- "alternateTitle": "art",
- "alternateUrl": "",
- "description": "",
- "metaKeywords": "",
- "metaExtra": "",
- "children": [],
- "assetCount": 3,
- "assetCountCumulative": 3
- },
- {
- "name": "clothing",
- "slug": "clothing-brush",
- "active": true,
- "thumbnail": null,
- "thumbnailWidth": null,
- "thumbnailHeight": null,
- "order": 0,
- "alternateTitle": "clothing",
- "alternateUrl": "",
- "description": "",
- "metaKeywords": "",
- "metaExtra": "",
- "children": [],
- "assetCount": 10,
- "assetCountCumulative": 10
- },
- {
- "name": "crack",
- "slug": "crack",
- "active": true,
- "thumbnail": null,
- "thumbnailWidth": null,
- "thumbnailHeight": null,
- "order": 0,
- "alternateTitle": "crack",
- "alternateUrl": "",
- "description": "",
- "metaKeywords": "",
- "metaExtra": "",
- "children": [],
- "assetCount": 0,
- "assetCountCumulative": 0
- },
- {
- "name": "cut",
- "slug": "cut",
- "active": true,
- "thumbnail": null,
- "thumbnailWidth": null,
- "thumbnailHeight": null,
- "order": 0,
- "alternateTitle": "cut",
- "alternateUrl": "",
- "description": "",
- "metaKeywords": "",
- "metaExtra": "",
- "children": [],
- "assetCount": 2,
- "assetCountCumulative": 2
- },
- {
- "name": "damage",
- "slug": "damage",
- "active": true,
- "thumbnail": null,
- "thumbnailWidth": null,
- "thumbnailHeight": null,
- "order": 0,
- "alternateTitle": "damage",
- "alternateUrl": "",
- "description": "",
- "metaKeywords": "",
- "metaExtra": "",
- "children": [],
- "assetCount": 4,
- "assetCountCumulative": 4
- },
- {
- "name": "dirt",
- "slug": "dirt-brush",
- "active": true,
- "thumbnail": null,
- "thumbnailWidth": null,
- "thumbnailHeight": null,
- "order": 0,
- "alternateTitle": "dirt",
- "alternateUrl": "",
- "description": "",
- "metaKeywords": "",
- "metaExtra": "",
- "children": [],
- "assetCount": 2,
- "assetCountCumulative": 2
- },
- {
- "name": "fabric",
- "slug": "fabric-brush",
- "active": true,
- "thumbnail": null,
- "thumbnailWidth": null,
- "thumbnailHeight": null,
- "order": 0,
- "alternateTitle": "fabric",
- "alternateUrl": "",
- "description": "",
- "metaKeywords": "",
- "metaExtra": "",
- "children": [],
- "assetCount": 8,
- "assetCountCumulative": 8
- },
- {
- "name": "geometric",
- "slug": "geometric",
- "active": true,
- "thumbnail": null,
- "thumbnailWidth": null,
- "thumbnailHeight": null,
- "order": 0,
- "alternateTitle": "geometric",
- "alternateUrl": "",
- "description": "",
- "metaKeywords": "",
- "metaExtra": "",
- "children": [],
- "assetCount": 5,
- "assetCountCumulative": 5
- },
- {
- "name": "human",
- "slug": "human-brush",
- "active": true,
- "thumbnail": null,
- "thumbnailWidth": null,
- "thumbnailHeight": null,
- "order": 0,
- "alternateTitle": "human",
- "alternateUrl": "",
- "description": "",
- "metaKeywords": "",
- "metaExtra": "",
- "children": [],
- "assetCount": 7,
- "assetCountCumulative": 7
- },
- {
- "name": "industrial",
- "slug": "industrial-brush",
- "active": true,
- "thumbnail": null,
- "thumbnailWidth": null,
- "thumbnailHeight": null,
- "order": 0,
- "alternateTitle": "industrial",
- "alternateUrl": "",
- "description": "",
- "metaKeywords": "",
- "metaExtra": "",
- "children": [],
- "assetCount": 12,
- "assetCountCumulative": 12
- },
- {
- "name": "landscape",
- "slug": "landscape-brush",
- "active": true,
- "thumbnail": null,
- "thumbnailWidth": null,
- "thumbnailHeight": null,
- "order": 0,
- "alternateTitle": "landscape",
- "alternateUrl": "",
- "description": "",
- "metaKeywords": "",
- "metaExtra": "",
- "children": [],
- "assetCount": 25,
- "assetCountCumulative": 25
- },
- {
- "name": "misc",
- "slug": "misc",
- "active": true,
- "thumbnail": null,
- "thumbnailWidth": null,
- "thumbnailHeight": null,
- "order": 0,
- "alternateTitle": "misc",
- "alternateUrl": "",
- "description": "",
- "metaKeywords": "",
- "metaExtra": "",
- "children": [],
- "assetCount": 10,
- "assetCountCumulative": 10
- },
- {
- "name": "nature",
- "slug": "nature-brush",
- "active": true,
- "thumbnail": null,
- "thumbnailWidth": null,
- "thumbnailHeight": null,
- "order": 0,
- "alternateTitle": "nature",
- "alternateUrl": "",
- "description": "",
- "metaKeywords": "",
- "metaExtra": "",
- "children": [],
- "assetCount": 0,
- "assetCountCumulative": 0
- },
- {
- "name": "pattern",
- "slug": "pattern",
- "active": true,
- "thumbnail": null,
- "thumbnailWidth": null,
- "thumbnailHeight": null,
- "order": 0,
- "alternateTitle": "pattern",
- "alternateUrl": "",
- "description": "",
- "metaKeywords": "",
- "metaExtra": "",
- "children": [],
- "assetCount": 8,
- "assetCountCumulative": 8
- },
- {
- "name": "rock",
- "slug": "rock-brush",
- "active": true,
- "thumbnail": null,
- "thumbnailWidth": null,
- "thumbnailHeight": null,
- "order": 0,
- "alternateTitle": "rock",
- "alternateUrl": "",
- "description": "",
- "metaKeywords": "",
- "metaExtra": "",
- "children": [],
- "assetCount": 8,
- "assetCountCumulative": 8
- },
- {
- "name": "rust",
- "slug": "rust-brush",
- "active": true,
- "thumbnail": null,
- "thumbnailWidth": null,
- "thumbnailHeight": null,
- "order": 0,
- "alternateTitle": "rust",
- "alternateUrl": "",
- "description": "",
- "metaKeywords": "",
- "metaExtra": "",
- "children": [],
- "assetCount": 0,
- "assetCountCumulative": 0
- },
- {
- "name": "sculpture",
- "slug": "sculpture-brush",
- "active": true,
- "thumbnail": null,
- "thumbnailWidth": null,
- "thumbnailHeight": null,
- "order": 0,
- "alternateTitle": "sculpture",
- "alternateUrl": "",
- "description": "",
- "metaKeywords": "",
- "metaExtra": "",
- "children": [],
- "assetCount": 3,
- "assetCountCumulative": 3
- },
- {
- "name": "stitches",
- "slug": "stitches",
- "active": true,
- "thumbnail": null,
- "thumbnailWidth": null,
- "thumbnailHeight": null,
- "order": 0,
- "alternateTitle": "stitches",
- "alternateUrl": "",
- "description": "",
- "metaKeywords": "",
- "metaExtra": "",
- "children": [],
- "assetCount": 10,
- "assetCountCumulative": 10
- },
- {
- "name": "stone",
- "slug": "stone-brush",
- "active": true,
- "thumbnail": null,
- "thumbnailWidth": null,
- "thumbnailHeight": null,
- "order": 0,
- "alternateTitle": "stone",
- "alternateUrl": "",
- "description": "",
- "metaKeywords": "",
- "metaExtra": "",
- "children": [],
- "assetCount": 0,
- "assetCountCumulative": 0
- },
- {
- "name": "tree",
- "slug": "tree-brush",
- "active": true,
- "thumbnail": null,
- "thumbnailWidth": null,
- "thumbnailHeight": null,
- "order": 0,
- "alternateTitle": "tree",
- "alternateUrl": "",
- "description": "",
- "metaKeywords": "",
- "metaExtra": "",
- "children": [],
- "assetCount": 5,
- "assetCountCumulative": 5
- },
- {
- "name": "wood",
- "slug": "wood-brush",
- "active": true,
- "thumbnail": null,
- "thumbnailWidth": null,
- "thumbnailHeight": null,
- "order": 0,
- "alternateTitle": "wood",
- "alternateUrl": "",
- "description": "",
- "metaKeywords": "",
- "metaExtra": "",
- "children": [],
- "assetCount": 0,
- "assetCountCumulative": 0
- }
- ],
- "assetCount": 137,
- "assetCountCumulative": 137
- },
- {
- "name": "HDR",
- "slug": "hdr",
- "active": true,
- "thumbnail": null,
- "thumbnailWidth": null,
- "thumbnailHeight": null,
- "order": 0,
- "alternateTitle": "HDR",
- "alternateUrl": "",
- "description": "",
- "metaKeywords": "",
- "metaExtra": "",
- "children": [
- {
- "name": "Indoor",
- "slug": "indoor",
- "active": true,
- "thumbnail": null,
- "thumbnailWidth": null,
- "thumbnailHeight": null,
- "order": 0,
- "alternateTitle": "Indoor",
- "alternateUrl": "",
- "description": "",
- "metaKeywords": "",
- "metaExtra": "",
- "children": [
- {
- "name": "Industrial",
- "slug": "hdr-industrial",
- "active": true,
- "thumbnail": null,
- "thumbnailWidth": null,
- "thumbnailHeight": null,
- "order": 0,
- "alternateTitle": "Industrial",
- "alternateUrl": "",
- "description": "",
- "metaKeywords": "",
- "metaExtra": "",
- "children": [],
- "assetCount": 63,
- "assetCountCumulative": 63
- },
- {
- "name": "Public",
- "slug": "hdr-public",
- "active": true,
- "thumbnail": null,
- "thumbnailWidth": null,
- "thumbnailHeight": null,
- "order": 0,
- "alternateTitle": "Public",
- "alternateUrl": "",
- "description": "",
- "metaKeywords": "",
- "metaExtra": "",
- "children": [],
- "assetCount": 31,
- "assetCountCumulative": 31
- },
- {
- "name": "Residential",
- "slug": "residential",
- "active": true,
- "thumbnail": null,
- "thumbnailWidth": null,
- "thumbnailHeight": null,
- "order": 0,
- "alternateTitle": "Residential",
- "alternateUrl": "",
- "description": "",
- "metaKeywords": "",
- "metaExtra": "",
- "children": [],
- "assetCount": 85,
- "assetCountCumulative": 85
- },
- {
- "name": "Studio",
- "slug": "hdr-studio",
- "active": true,
- "thumbnail": null,
- "thumbnailWidth": null,
- "thumbnailHeight": null,
- "order": 0,
- "alternateTitle": "Studio",
- "alternateUrl": "",
- "description": "",
- "metaKeywords": "",
- "metaExtra": "",
- "children": [],
- "assetCount": 13,
- "assetCountCumulative": 13
- }
- ],
- "assetCount": 447,
- "assetCountCumulative": 447
- },
- {
- "name": "Outdoor",
- "slug": "hdr-outdoor",
- "active": true,
- "thumbnail": null,
- "thumbnailWidth": null,
- "thumbnailHeight": null,
- "order": 0,
- "alternateTitle": "Outdoor",
- "alternateUrl": "",
- "description": "",
- "metaKeywords": "",
- "metaExtra": "",
- "children": [
- {
- "name": "Nature",
- "slug": "hdr-nature",
- "active": true,
- "thumbnail": null,
- "thumbnailWidth": null,
- "thumbnailHeight": null,
- "order": 0,
- "alternateTitle": "Nature",
- "alternateUrl": "",
- "description": "",
- "metaKeywords": "",
- "metaExtra": "",
- "children": [],
- "assetCount": 41,
- "assetCountCumulative": 41
- },
- {
- "name": "Urban",
- "slug": "hdr-urban",
- "active": true,
- "thumbnail": null,
- "thumbnailWidth": null,
- "thumbnailHeight": null,
- "order": 0,
- "alternateTitle": "Urban",
- "alternateUrl": "",
- "description": "",
- "metaKeywords": "",
- "metaExtra": "",
- "children": [],
- "assetCount": 80,
- "assetCountCumulative": 80
- }
- ],
- "assetCount": 121,
- "assetCountCumulative": 121
- }
- ],
- "assetCount": 580,
- "assetCountCumulative": 580
- },
- {
- "name": "material",
- "slug": "material",
- "active": true,
- "thumbnail": null,
- "thumbnailWidth": null,
- "thumbnailHeight": null,
- "order": 0,
- "alternateTitle": "materials",
- "alternateUrl": "",
- "description": "",
- "metaKeywords": "",
- "metaExtra": "",
- "children": [
- {
- "name": "animal",
- "slug": "animal-material",
- "active": true,
- "thumbnail": null,
- "thumbnailWidth": null,
- "thumbnailHeight": null,
- "order": 0,
- "alternateTitle": "animal",
- "alternateUrl": "",
- "description": "",
- "metaKeywords": "",
- "metaExtra": "",
- "children": [],
- "assetCount": 51,
- "assetCountCumulative": 51
- },
- {
- "name": "asphalt",
- "slug": "asphalt",
- "active": true,
- "thumbnail": null,
- "thumbnailWidth": null,
- "thumbnailHeight": null,
- "order": 0,
- "alternateTitle": "asphalt",
- "alternateUrl": "",
- "description": "",
- "metaKeywords": "",
- "metaExtra": "",
- "children": [],
- "assetCount": 58,
- "assetCountCumulative": 58
- },
- {
- "name": "bricks",
- "slug": "bricks",
- "active": true,
- "thumbnail": null,
- "thumbnailWidth": null,
- "thumbnailHeight": null,
- "order": 0,
- "alternateTitle": "bricks",
- "alternateUrl": "",
- "description": "",
- "metaKeywords": "",
- "metaExtra": "",
- "children": [],
- "assetCount": 93,
- "assetCountCumulative": 93
- },
- {
- "name": "ceramic",
- "slug": "ceramic",
- "active": true,
- "thumbnail": null,
- "thumbnailWidth": null,
- "thumbnailHeight": null,
- "order": 0,
- "alternateTitle": "ceramic",
- "alternateUrl": "",
- "description": "",
- "metaKeywords": "",
- "metaExtra": "",
- "children": [],
- "assetCount": 30,
- "assetCountCumulative": 30
- },
- {
- "name": "concrete",
- "slug": "concrete",
- "active": true,
- "thumbnail": null,
- "thumbnailWidth": null,
- "thumbnailHeight": null,
- "order": 0,
- "alternateTitle": "concrete",
- "alternateUrl": "",
- "description": "",
- "metaKeywords": "",
- "metaExtra": "",
- "children": [],
- "assetCount": 132,
- "assetCountCumulative": 132
- },
- {
- "name": "dirt",
- "slug": "dirt",
- "active": true,
- "thumbnail": null,
- "thumbnailWidth": null,
- "thumbnailHeight": null,
- "order": 0,
- "alternateTitle": "dirt",
- "alternateUrl": "",
- "description": "",
- "metaKeywords": "",
- "metaExtra": "",
- "children": [],
- "assetCount": 37,
- "assetCountCumulative": 37
- },
- {
- "name": "fabric",
- "slug": "fabric",
- "active": true,
- "thumbnail": null,
- "thumbnailWidth": null,
- "thumbnailHeight": null,
- "order": 0,
- "alternateTitle": "fabric",
- "alternateUrl": "",
- "description": "",
- "metaKeywords": "",
- "metaExtra": "",
- "children": [],
- "assetCount": 205,
- "assetCountCumulative": 205
- },
- {
- "name": "floor",
- "slug": "floor",
- "active": true,
- "thumbnail": null,
- "thumbnailWidth": null,
- "thumbnailHeight": null,
- "order": 0,
- "alternateTitle": "floor",
- "alternateUrl": "",
- "description": "",
- "metaKeywords": "",
- "metaExtra": "",
- "children": [],
- "assetCount": 72,
- "assetCountCumulative": 72
- },
- {
- "name": "food",
- "slug": "food-material",
- "active": true,
- "thumbnail": null,
- "thumbnailWidth": null,
- "thumbnailHeight": null,
- "order": 0,
- "alternateTitle": "food",
- "alternateUrl": "",
- "description": "",
- "metaKeywords": "",
- "metaExtra": "",
- "children": [],
- "assetCount": 44,
- "assetCountCumulative": 44
- },
- {
- "name": "fx",
- "slug": "fx",
- "active": true,
- "thumbnail": null,
- "thumbnailWidth": null,
- "thumbnailHeight": null,
- "order": 0,
- "alternateTitle": "fx",
- "alternateUrl": "",
- "description": "",
- "metaKeywords": "",
- "metaExtra": "",
- "children": [],
- "assetCount": 54,
- "assetCountCumulative": 54
- },
- {
- "name": "glass",
- "slug": "glass",
- "active": true,
- "thumbnail": null,
- "thumbnailWidth": null,
- "thumbnailHeight": null,
- "order": 0,
- "alternateTitle": "glass",
- "alternateUrl": "",
- "description": "",
- "metaKeywords": "",
- "metaExtra": "",
- "children": [],
- "assetCount": 73,
- "assetCountCumulative": 73
- },
- {
- "name": "grass",
- "slug": "grass",
- "active": true,
- "thumbnail": null,
- "thumbnailWidth": null,
- "thumbnailHeight": null,
- "order": 0,
- "alternateTitle": "grass",
- "alternateUrl": "",
- "description": "",
- "metaKeywords": "",
- "metaExtra": "",
- "children": [],
- "assetCount": 15,
- "assetCountCumulative": 15
- },
- {
- "name": "ground",
- "slug": "ground-material",
- "active": true,
- "thumbnail": null,
- "thumbnailWidth": null,
- "thumbnailHeight": null,
- "order": 0,
- "alternateTitle": "ground",
- "alternateUrl": "",
- "description": "",
- "metaKeywords": "",
- "metaExtra": "",
- "children": [],
- "assetCount": 134,
- "assetCountCumulative": 134
- },
- {
- "name": "human",
- "slug": "human",
- "active": true,
- "thumbnail": null,
- "thumbnailWidth": null,
- "thumbnailHeight": null,
- "order": 0,
- "alternateTitle": "human",
- "alternateUrl": "",
- "description": "",
- "metaKeywords": "",
- "metaExtra": "",
- "children": [],
- "assetCount": 5,
- "assetCountCumulative": 5
- },
- {
- "name": "ice",
- "slug": "ice",
- "active": true,
- "thumbnail": null,
- "thumbnailWidth": null,
- "thumbnailHeight": null,
- "order": 0,
- "alternateTitle": "ice",
- "alternateUrl": "",
- "description": "",
- "metaKeywords": "",
- "metaExtra": "",
- "children": [],
- "assetCount": 28,
- "assetCountCumulative": 28
- },
- {
- "name": "leather",
- "slug": "leather",
- "active": true,
- "thumbnail": null,
- "thumbnailWidth": null,
- "thumbnailHeight": null,
- "order": 0,
- "alternateTitle": "leather",
- "alternateUrl": "",
- "description": "",
- "metaKeywords": "",
- "metaExtra": "",
- "children": [],
- "assetCount": 88,
- "assetCountCumulative": 88
- },
- {
- "name": "liquid",
- "slug": "liquid",
- "active": true,
- "thumbnail": null,
- "thumbnailWidth": null,
- "thumbnailHeight": null,
- "order": 0,
- "alternateTitle": "liquid",
- "alternateUrl": "",
- "description": "",
- "metaKeywords": "",
- "metaExtra": "",
- "children": [],
- "assetCount": 25,
- "assetCountCumulative": 25
- },
- {
- "name": "marble",
- "slug": "marble",
- "active": true,
- "thumbnail": null,
- "thumbnailWidth": null,
- "thumbnailHeight": null,
- "order": 0,
- "alternateTitle": "marble",
- "alternateUrl": "",
- "description": "",
- "metaKeywords": "",
- "metaExtra": "",
- "children": [],
- "assetCount": 14,
- "assetCountCumulative": 14
- },
- {
- "name": "metal",
- "slug": "metal",
- "active": true,
- "thumbnail": null,
- "thumbnailWidth": null,
- "thumbnailHeight": null,
- "order": 0,
- "alternateTitle": "metal",
- "alternateUrl": "",
- "description": "",
- "metaKeywords": "",
- "metaExtra": "",
- "children": [],
- "assetCount": 349,
- "assetCountCumulative": 349
- },
- {
- "name": "organic",
- "slug": "organic",
- "active": true,
- "thumbnail": null,
- "thumbnailWidth": null,
- "thumbnailHeight": null,
- "order": 0,
- "alternateTitle": "organic",
- "alternateUrl": "",
- "description": "",
- "metaKeywords": "",
- "metaExtra": "",
- "children": [],
- "assetCount": 50,
- "assetCountCumulative": 50
- },
- {
- "name": "ornaments",
- "slug": "ornaments",
- "active": true,
- "thumbnail": null,
- "thumbnailWidth": null,
- "thumbnailHeight": null,
- "order": 0,
- "alternateTitle": "ornaments",
- "alternateUrl": "",
- "description": "",
- "metaKeywords": "",
- "metaExtra": "",
- "children": [],
- "assetCount": 40,
- "assetCountCumulative": 40
- },
- {
- "name": "paper",
- "slug": "paper",
- "active": true,
- "thumbnail": null,
- "thumbnailWidth": null,
- "thumbnailHeight": null,
- "order": 0,
- "alternateTitle": "paper",
- "alternateUrl": "",
- "description": "",
- "metaKeywords": "",
- "metaExtra": "",
- "children": [],
- "assetCount": 64,
- "assetCountCumulative": 64
- },
- {
- "name": "paving",
- "slug": "paving",
- "active": true,
- "thumbnail": null,
- "thumbnailWidth": null,
- "thumbnailHeight": null,
- "order": 0,
- "alternateTitle": "paving",
- "alternateUrl": "",
- "description": "",
- "metaKeywords": "",
- "metaExtra": "",
- "children": [],
- "assetCount": 51,
- "assetCountCumulative": 51
- },
- {
- "name": "plaster",
- "slug": "plaster",
- "active": true,
- "thumbnail": null,
- "thumbnailWidth": null,
- "thumbnailHeight": null,
- "order": 0,
- "alternateTitle": "plaster",
- "alternateUrl": "",
- "description": "",
- "metaKeywords": "",
- "metaExtra": "",
- "children": [],
- "assetCount": 76,
- "assetCountCumulative": 76
- },
- {
- "name": "plastic",
- "slug": "plastic",
- "active": true,
- "thumbnail": null,
- "thumbnailWidth": null,
- "thumbnailHeight": null,
- "order": 0,
- "alternateTitle": "plastic",
- "alternateUrl": "",
- "description": "",
- "metaKeywords": "",
- "metaExtra": "",
- "children": [],
- "assetCount": 77,
- "assetCountCumulative": 77
- },
- {
- "name": "rock",
- "slug": "rock",
- "active": true,
- "thumbnail": null,
- "thumbnailWidth": null,
- "thumbnailHeight": null,
- "order": 0,
- "alternateTitle": "rock",
- "alternateUrl": "",
- "description": "",
- "metaKeywords": "",
- "metaExtra": "",
- "children": [],
- "assetCount": 55,
- "assetCountCumulative": 55
- },
- {
- "name": "roofing",
- "slug": "roofing",
- "active": true,
- "thumbnail": null,
- "thumbnailWidth": null,
- "thumbnailHeight": null,
- "order": 0,
- "alternateTitle": "roofing",
- "alternateUrl": "",
- "description": "",
- "metaKeywords": "",
- "metaExtra": "",
- "children": [],
- "assetCount": 24,
- "assetCountCumulative": 24
- },
- {
- "name": "rubber",
- "slug": "rubber",
- "active": true,
- "thumbnail": null,
- "thumbnailWidth": null,
- "thumbnailHeight": null,
- "order": 0,
- "alternateTitle": "rubber",
- "alternateUrl": "",
- "description": "",
- "metaKeywords": "",
- "metaExtra": "",
- "children": [],
- "assetCount": 12,
- "assetCountCumulative": 12
- },
- {
- "name": "rust",
- "slug": "rust",
- "active": true,
- "thumbnail": null,
- "thumbnailWidth": null,
- "thumbnailHeight": null,
- "order": 0,
- "alternateTitle": "rust",
- "alternateUrl": "",
- "description": "",
- "metaKeywords": "",
- "metaExtra": "",
- "children": [],
- "assetCount": 25,
- "assetCountCumulative": 25
- },
- {
- "name": "sand",
- "slug": "sand",
- "active": true,
- "thumbnail": null,
- "thumbnailWidth": null,
- "thumbnailHeight": null,
- "order": 0,
- "alternateTitle": "sand",
- "alternateUrl": "",
- "description": "",
- "metaKeywords": "",
- "metaExtra": "",
- "children": [],
- "assetCount": 28,
- "assetCountCumulative": 28
- },
- {
- "name": "soil",
- "slug": "soil",
- "active": true,
- "thumbnail": null,
- "thumbnailWidth": null,
- "thumbnailHeight": null,
- "order": 0,
- "alternateTitle": "soil",
- "alternateUrl": "",
- "description": "",
- "metaKeywords": "",
- "metaExtra": "",
- "children": [],
- "assetCount": 13,
- "assetCountCumulative": 13
- },
- {
- "name": "stone",
- "slug": "stone",
- "active": true,
- "thumbnail": null,
- "thumbnailWidth": null,
- "thumbnailHeight": null,
- "order": 0,
- "alternateTitle": "stone",
- "alternateUrl": "",
- "description": "",
- "metaKeywords": "",
- "metaExtra": "",
- "children": [],
- "assetCount": 153,
- "assetCountCumulative": 153
- },
- {
- "name": "tech",
- "slug": "tech",
- "active": true,
- "thumbnail": null,
- "thumbnailWidth": null,
- "thumbnailHeight": null,
- "order": 0,
- "alternateTitle": "tech",
- "alternateUrl": "",
- "description": "",
- "metaKeywords": "",
- "metaExtra": "",
- "children": [],
- "assetCount": 115,
- "assetCountCumulative": 115
- },
- {
- "name": "tiles",
- "slug": "tiles",
- "active": true,
- "thumbnail": null,
- "thumbnailWidth": null,
- "thumbnailHeight": null,
- "order": 0,
- "alternateTitle": "tiles",
- "alternateUrl": "",
- "description": "",
- "metaKeywords": "",
- "metaExtra": "",
- "children": [],
- "assetCount": 164,
- "assetCountCumulative": 164
- },
- {
- "name": "wood",
- "slug": "wood",
- "active": true,
- "thumbnail": null,
- "thumbnailWidth": null,
- "thumbnailHeight": null,
- "order": 0,
- "alternateTitle": "wood",
- "alternateUrl": "",
- "description": "",
- "metaKeywords": "",
- "metaExtra": "",
- "children": [],
- "assetCount": 351,
- "assetCountCumulative": 351
- }
- ],
- "assetCount": 2805,
- "assetCountCumulative": 2805
- },
- {
- "name": "model",
- "slug": "model",
- "active": true,
- "thumbnail": null,
- "thumbnailWidth": null,
- "thumbnailHeight": null,
- "order": 0,
- "alternateTitle": "model",
- "alternateUrl": "",
- "description": "",
- "metaKeywords": "",
- "metaExtra": "",
- "children": [
- {
- "name": "Architecture",
- "slug": "architecture",
- "active": true,
- "thumbnail": null,
- "thumbnailWidth": null,
- "thumbnailHeight": null,
- "order": 0,
- "alternateTitle": "Architecture",
- "alternateUrl": "",
- "description": "",
- "metaKeywords": "",
- "metaExtra": "",
- "children": [
- {
- "name": "Building",
- "slug": "building",
- "active": true,
- "thumbnail": null,
- "thumbnailWidth": null,
- "thumbnailHeight": null,
- "order": 0,
- "alternateTitle": "Building",
- "alternateUrl": "",
- "description": "",
- "metaKeywords": "",
- "metaExtra": "",
- "children": [
- {
- "name": "Commercial",
- "slug": "public",
- "active": true,
- "thumbnail": null,
- "thumbnailWidth": null,
- "thumbnailHeight": null,
- "order": 0,
- "alternateTitle": "Commercial",
- "alternateUrl": "",
- "description": "",
- "metaKeywords": "",
- "metaExtra": "",
- "children": [],
- "assetCount": 39,
- "assetCountCumulative": 39
- },
- {
- "name": "Historic",
- "slug": "historic",
- "active": true,
- "thumbnail": null,
- "thumbnailWidth": null,
- "thumbnailHeight": null,
- "order": 0,
- "alternateTitle": "Historic",
- "alternateUrl": "",
- "description": "",
- "metaKeywords": "",
- "metaExtra": "",
- "children": [],
- "assetCount": 15,
- "assetCountCumulative": 15
- },
- {
- "name": "Other",
- "slug": "stadium",
- "active": true,
- "thumbnail": null,
- "thumbnailWidth": null,
- "thumbnailHeight": null,
- "order": 0,
- "alternateTitle": "Other",
- "alternateUrl": "",
- "description": "",
- "metaKeywords": "",
- "metaExtra": "",
- "children": [],
- "assetCount": 12,
- "assetCountCumulative": 12
- },
- {
- "name": "Private",
- "slug": "house",
- "active": true,
- "thumbnail": null,
- "thumbnailWidth": null,
- "thumbnailHeight": null,
- "order": 0,
- "alternateTitle": "Private",
- "alternateUrl": "",
- "description": "",
- "metaKeywords": "",
- "metaExtra": "",
- "children": [],
- "assetCount": 31,
- "assetCountCumulative": 31
- },
- {
- "name": "Sci-fi",
- "slug": "sci-fi",
- "active": true,
- "thumbnail": null,
- "thumbnailWidth": null,
- "thumbnailHeight": null,
- "order": 0,
- "alternateTitle": "Sci-fi",
- "alternateUrl": "",
- "description": "",
- "metaKeywords": "",
- "metaExtra": "",
- "children": [],
- "assetCount": 1,
- "assetCountCumulative": 1
- }
- ],
- "assetCount": 112,
- "assetCountCumulative": 112
- },
- {
- "name": "Door",
- "slug": "door",
- "active": true,
- "thumbnail": null,
- "thumbnailWidth": null,
- "thumbnailHeight": null,
- "order": 0,
- "alternateTitle": "Door",
- "alternateUrl": "",
- "description": "",
- "metaKeywords": "",
- "metaExtra": "",
- "children": [],
- "assetCount": 124,
- "assetCountCumulative": 124
- },
- {
- "name": "Exterior element",
- "slug": "exterior",
- "active": true,
- "thumbnail": null,
- "thumbnailWidth": null,
- "thumbnailHeight": null,
- "order": 0,
- "alternateTitle": "Exterior element",
- "alternateUrl": "",
- "description": "",
- "metaKeywords": "",
- "metaExtra": "",
- "children": [
- {
- "name": "Bench",
- "slug": "bench",
- "active": true,
- "thumbnail": null,
- "thumbnailWidth": null,
- "thumbnailHeight": null,
- "order": 0,
- "alternateTitle": "Bench",
- "alternateUrl": "",
- "description": "",
- "metaKeywords": "",
- "metaExtra": "",
- "children": [],
- "assetCount": 55,
- "assetCountCumulative": 55
- },
- {
- "name": "Facade element",
- "slug": "facade-element",
- "active": true,
- "thumbnail": null,
- "thumbnailWidth": null,
- "thumbnailHeight": null,
- "order": 0,
- "alternateTitle": "Facade element",
- "alternateUrl": "",
- "description": "",
- "metaKeywords": "",
- "metaExtra": "",
- "children": [],
- "assetCount": 58,
- "assetCountCumulative": 58
- },
- {
- "name": "Fence",
- "slug": "fence",
- "active": true,
- "thumbnail": null,
- "thumbnailWidth": null,
- "thumbnailHeight": null,
- "order": 0,
- "alternateTitle": "Fence",
- "alternateUrl": "",
- "description": "",
- "metaKeywords": "",
- "metaExtra": "",
- "children": [],
- "assetCount": 32,
- "assetCountCumulative": 32
- },
- {
- "name": "Fountain",
- "slug": "fountain",
- "active": true,
- "thumbnail": null,
- "thumbnailWidth": null,
- "thumbnailHeight": null,
- "order": 0,
- "alternateTitle": "Fountain",
- "alternateUrl": "",
- "description": "",
- "metaKeywords": "",
- "metaExtra": "",
- "children": [],
- "assetCount": 4,
- "assetCountCumulative": 4
- },
- {
- "name": "Other",
- "slug": "exterior-other",
- "active": true,
- "thumbnail": null,
- "thumbnailWidth": null,
- "thumbnailHeight": null,
- "order": 0,
- "alternateTitle": "Other",
- "alternateUrl": "",
- "description": "",
- "metaKeywords": "",
- "metaExtra": "",
- "children": [],
- "assetCount": 147,
- "assetCountCumulative": 147
- },
- {
- "name": "Playground",
- "slug": "playground",
- "active": true,
- "thumbnail": null,
- "thumbnailWidth": null,
- "thumbnailHeight": null,
- "order": 0,
- "alternateTitle": "Playground",
- "alternateUrl": "",
- "description": "",
- "metaKeywords": "",
- "metaExtra": "",
- "children": [],
- "assetCount": 8,
- "assetCountCumulative": 8
- },
- {
- "name": "Swimming pool",
- "slug": "swimming-pool",
- "active": true,
- "thumbnail": null,
- "thumbnailWidth": null,
- "thumbnailHeight": null,
- "order": 0,
- "alternateTitle": "Swimming pool",
- "alternateUrl": "",
- "description": "",
- "metaKeywords": "",
- "metaExtra": "",
- "children": [],
- "assetCount": 7,
- "assetCountCumulative": 7
- },
- {
- "name": "Urban Environment",
- "slug": "cityspace",
- "active": true,
- "thumbnail": null,
- "thumbnailWidth": null,
- "thumbnailHeight": null,
- "order": 0,
- "alternateTitle": "Urban Environment",
- "alternateUrl": "",
- "description": "",
- "metaKeywords": "",
- "metaExtra": "",
- "children": [],
- "assetCount": 67,
- "assetCountCumulative": 67
- }
- ],
- "assetCount": 384,
- "assetCountCumulative": 384
- },
- {
- "name": "Floor Covering",
- "slug": "floor-covering",
- "active": true,
- "thumbnail": null,
- "thumbnailWidth": null,
- "thumbnailHeight": null,
- "order": 0,
- "alternateTitle": "Floor Covering",
- "alternateUrl": "",
- "description": "",
- "metaKeywords": "",
- "metaExtra": "",
- "children": [],
- "assetCount": 5,
- "assetCountCumulative": 5
- },
- {
- "name": "Molding / Carving",
- "slug": "molding-carving",
- "active": true,
- "thumbnail": null,
- "thumbnailWidth": null,
- "thumbnailHeight": null,
- "order": 0,
- "alternateTitle": "Molding / Carving",
- "alternateUrl": "",
- "description": "",
- "metaKeywords": "",
- "metaExtra": "",
- "children": [],
- "assetCount": 1,
- "assetCountCumulative": 1
- },
- {
- "name": "Other",
- "slug": "elements",
- "active": true,
- "thumbnail": null,
- "thumbnailWidth": null,
- "thumbnailHeight": null,
- "order": 0,
- "alternateTitle": "Other",
- "alternateUrl": "",
- "description": "",
- "metaKeywords": "",
- "metaExtra": "",
- "children": [],
- "assetCount": 64,
- "assetCountCumulative": 64
- },
- {
- "name": "Scenes",
- "slug": "landmark",
- "active": true,
- "thumbnail": null,
- "thumbnailWidth": null,
- "thumbnailHeight": null,
- "order": 0,
- "alternateTitle": "Scenes",
- "alternateUrl": "",
- "description": "",
- "metaKeywords": "",
- "metaExtra": "",
- "children": [],
- "assetCount": 5,
- "assetCountCumulative": 5
- },
- {
- "name": "Stairs",
- "slug": "stairs",
- "active": true,
- "thumbnail": null,
- "thumbnailWidth": null,
- "thumbnailHeight": null,
- "order": 0,
- "alternateTitle": "Stairs",
- "alternateUrl": "",
- "description": "",
- "metaKeywords": "",
- "metaExtra": "",
- "children": [],
- "assetCount": 23,
- "assetCountCumulative": 23
- },
- {
- "name": "Structure",
- "slug": "street",
- "active": true,
- "thumbnail": null,
- "thumbnailWidth": null,
- "thumbnailHeight": null,
- "order": 0,
- "alternateTitle": "Structure",
- "alternateUrl": "",
- "description": "",
- "metaKeywords": "",
- "metaExtra": "",
- "children": [],
- "assetCount": 51,
- "assetCountCumulative": 51
- },
- {
- "name": "Wall Panel",
- "slug": "wall-panel",
- "active": true,
- "thumbnail": null,
- "thumbnailWidth": null,
- "thumbnailHeight": null,
- "order": 0,
- "alternateTitle": "Wall Panel",
- "alternateUrl": "",
- "description": "",
- "metaKeywords": "",
- "metaExtra": "",
- "children": [
- {
- "name": "3D Panel",
- "slug": "3d-panel",
- "active": true,
- "thumbnail": null,
- "thumbnailWidth": null,
- "thumbnailHeight": null,
- "order": 0,
- "alternateTitle": "3D Panel",
- "alternateUrl": "",
- "description": "",
- "metaKeywords": "",
- "metaExtra": "",
- "children": [],
- "assetCount": 4,
- "assetCountCumulative": 4
- },
- {
- "name": "Stone Panel",
- "slug": "stone-panel",
- "active": true,
- "thumbnail": null,
- "thumbnailWidth": null,
- "thumbnailHeight": null,
- "order": 0,
- "alternateTitle": "Stone Panel",
- "alternateUrl": "",
- "description": "",
- "metaKeywords": "",
- "metaExtra": "",
- "children": [],
- "assetCount": 2,
- "assetCountCumulative": 2
- },
- {
- "name": "Upholstery Panel",
- "slug": "upholstery-panel",
- "active": true,
- "thumbnail": null,
- "thumbnailWidth": null,
- "thumbnailHeight": null,
- "order": 0,
- "alternateTitle": "Upholstery Panel",
- "alternateUrl": "",
- "description": "",
- "metaKeywords": "",
- "metaExtra": "",
- "children": [],
- "assetCount": 1,
- "assetCountCumulative": 1
- },
- {
- "name": "Wood Panel",
- "slug": "wood-panel",
- "active": true,
- "thumbnail": null,
- "thumbnailWidth": null,
- "thumbnailHeight": null,
- "order": 0,
- "alternateTitle": "Wood Panel",
- "alternateUrl": "",
- "description": "",
- "metaKeywords": "",
- "metaExtra": "",
- "children": [],
- "assetCount": 4,
- "assetCountCumulative": 4
- }
- ],
- "assetCount": 11,
- "assetCountCumulative": 11
- },
- {
- "name": "Window",
- "slug": "window",
- "active": true,
- "thumbnail": null,
- "thumbnailWidth": null,
- "thumbnailHeight": null,
- "order": 0,
- "alternateTitle": "Window",
- "alternateUrl": "",
- "description": "",
- "metaKeywords": "",
- "metaExtra": "",
- "children": [],
- "assetCount": 67,
- "assetCountCumulative": 67
- }
- ],
- "assetCount": 849,
- "assetCountCumulative": 849
- },
- {
- "name": "Character",
- "slug": "character",
- "active": true,
- "thumbnail": null,
- "thumbnailWidth": null,
- "thumbnailHeight": null,
- "order": 0,
- "alternateTitle": "Character",
- "alternateUrl": "",
- "description": "",
- "metaKeywords": "",
- "metaExtra": "",
- "children": [
- {
- "name": "Anatomy",
- "slug": "anatomy",
- "active": true,
- "thumbnail": null,
- "thumbnailWidth": null,
- "thumbnailHeight": null,
- "order": 0,
- "alternateTitle": "Anatomy",
- "alternateUrl": "",
- "description": "",
- "metaKeywords": "",
- "metaExtra": "",
- "children": [
- {
- "name": "Full Body",
- "slug": "full-body",
- "active": true,
- "thumbnail": null,
- "thumbnailWidth": null,
- "thumbnailHeight": null,
- "order": 0,
- "alternateTitle": "Full Body",
- "alternateUrl": "",
- "description": "",
- "metaKeywords": "",
- "metaExtra": "",
- "children": [],
- "assetCount": 4,
- "assetCountCumulative": 4
- },
- {
- "name": "Head",
- "slug": "head",
- "active": true,
- "thumbnail": null,
- "thumbnailWidth": null,
- "thumbnailHeight": null,
- "order": 0,
- "alternateTitle": "Head",
- "alternateUrl": "",
- "description": "",
- "metaKeywords": "",
- "metaExtra": "",
- "children": [],
- "assetCount": 9,
- "assetCountCumulative": 9
- },
- {
- "name": "Internal organ",
- "slug": "internal-organ",
- "active": true,
- "thumbnail": null,
- "thumbnailWidth": null,
- "thumbnailHeight": null,
- "order": 0,
- "alternateTitle": "Internal organ",
- "alternateUrl": "",
- "description": "",
- "metaKeywords": "",
- "metaExtra": "",
- "children": [],
- "assetCount": 1,
- "assetCountCumulative": 1
- },
- {
- "name": "Limbs",
- "slug": "limbs",
- "active": true,
- "thumbnail": null,
- "thumbnailWidth": null,
- "thumbnailHeight": null,
- "order": 0,
- "alternateTitle": "Limbs",
- "alternateUrl": "",
- "description": "",
- "metaKeywords": "",
- "metaExtra": "",
- "children": [],
- "assetCount": 0,
- "assetCountCumulative": 0
- },
- {
- "name": "Musculature",
- "slug": "musculature",
- "active": true,
- "thumbnail": null,
- "thumbnailWidth": null,
- "thumbnailHeight": null,
- "order": 0,
- "alternateTitle": "Musculature",
- "alternateUrl": "",
- "description": "",
- "metaKeywords": "",
- "metaExtra": "",
- "children": [],
- "assetCount": 0,
- "assetCountCumulative": 0
- },
- {
- "name": "Skeleton",
- "slug": "skeleton",
- "active": true,
- "thumbnail": null,
- "thumbnailWidth": null,
- "thumbnailHeight": null,
- "order": 0,
- "alternateTitle": "Skeleton",
- "alternateUrl": "",
- "description": "",
- "metaKeywords": "",
- "metaExtra": "",
- "children": [],
- "assetCount": 2,
- "assetCountCumulative": 2
- }
- ],
- "assetCount": 16,
- "assetCountCumulative": 16
- },
- {
- "name": "Animal",
- "slug": "animal-nature",
- "active": true,
- "thumbnail": null,
- "thumbnailWidth": null,
- "thumbnailHeight": null,
- "order": 0,
- "alternateTitle": "Animal",
- "alternateUrl": "",
- "description": "",
- "metaKeywords": "",
- "metaExtra": "",
- "children": [
- {
- "name": "Bird",
- "slug": "bird",
- "active": true,
- "thumbnail": null,
- "thumbnailWidth": null,
- "thumbnailHeight": null,
- "order": 0,
- "alternateTitle": "Bird",
- "alternateUrl": "",
- "description": "",
- "metaKeywords": "",
- "metaExtra": "",
- "children": [],
- "assetCount": 1,
- "assetCountCumulative": 1
- },
- {
- "name": "Dinosaur",
- "slug": "dinosaur",
- "active": true,
- "thumbnail": null,
- "thumbnailWidth": null,
- "thumbnailHeight": null,
- "order": 0,
- "alternateTitle": "Dinosaur",
- "alternateUrl": "",
- "description": "",
- "metaKeywords": "",
- "metaExtra": "",
- "children": [],
- "assetCount": 0,
- "assetCountCumulative": 0
- },
- {
- "name": "Fish",
- "slug": "fish",
- "active": true,
- "thumbnail": null,
- "thumbnailWidth": null,
- "thumbnailHeight": null,
- "order": 0,
- "alternateTitle": "Fish",
- "alternateUrl": "",
- "description": "",
- "metaKeywords": "",
- "metaExtra": "",
- "children": [],
- "assetCount": 1,
- "assetCountCumulative": 1
- },
- {
- "name": "Insect",
- "slug": "insect",
- "active": true,
- "thumbnail": null,
- "thumbnailWidth": null,
- "thumbnailHeight": null,
- "order": 0,
- "alternateTitle": "Insect",
- "alternateUrl": "",
- "description": "",
- "metaKeywords": "",
- "metaExtra": "",
- "children": [],
- "assetCount": 6,
- "assetCountCumulative": 6
- },
- {
- "name": "Mammal",
- "slug": "mammal",
- "active": true,
- "thumbnail": null,
- "thumbnailWidth": null,
- "thumbnailHeight": null,
- "order": 0,
- "alternateTitle": "Mammal",
- "alternateUrl": "",
- "description": "",
- "metaKeywords": "",
- "metaExtra": "",
- "children": [],
- "assetCount": 12,
- "assetCountCumulative": 12
- },
- {
- "name": "Other",
- "slug": "animal",
- "active": true,
- "thumbnail": null,
- "thumbnailWidth": null,
- "thumbnailHeight": null,
- "order": 0,
- "alternateTitle": "Other",
- "alternateUrl": "",
- "description": "",
- "metaKeywords": "",
- "metaExtra": "",
- "children": [],
- "assetCount": 2,
- "assetCountCumulative": 2
- },
- {
- "name": "Reptile",
- "slug": "reptile",
- "active": true,
- "thumbnail": null,
- "thumbnailWidth": null,
- "thumbnailHeight": null,
- "order": 0,
- "alternateTitle": "Reptile",
- "alternateUrl": "",
- "description": "",
- "metaKeywords": "",
- "metaExtra": "",
- "children": [],
- "assetCount": 0,
- "assetCountCumulative": 0
- }
- ],
- "assetCount": 25,
- "assetCountCumulative": 25
- },
- {
- "name": "Clothing",
- "slug": "clothing",
- "active": true,
- "thumbnail": null,
- "thumbnailWidth": null,
- "thumbnailHeight": null,
- "order": 0,
- "alternateTitle": "Clothing",
- "alternateUrl": "",
- "description": "",
- "metaKeywords": "",
- "metaExtra": "",
- "children": [
- {
- "name": "Accessories",
- "slug": "clothing-accessories",
- "active": true,
- "thumbnail": null,
- "thumbnailWidth": null,
- "thumbnailHeight": null,
- "order": 0,
- "alternateTitle": "Accessories",
- "alternateUrl": "",
- "description": "",
- "metaKeywords": "",
- "metaExtra": "",
- "children": [],
- "assetCount": 22,
- "assetCountCumulative": 22
- },
- {
- "name": "Footwear",
- "slug": "footwear",
- "active": true,
- "thumbnail": null,
- "thumbnailWidth": null,
- "thumbnailHeight": null,
- "order": 0,
- "alternateTitle": "Footwear",
- "alternateUrl": "",
- "description": "",
- "metaKeywords": "",
- "metaExtra": "",
- "children": [],
- "assetCount": 25,
- "assetCountCumulative": 25
- },
- {
- "name": "Headwear",
- "slug": "headwear",
- "active": true,
- "thumbnail": null,
- "thumbnailWidth": null,
- "thumbnailHeight": null,
- "order": 0,
- "alternateTitle": "Headwear",
- "alternateUrl": "",
- "description": "",
- "metaKeywords": "",
- "metaExtra": "",
- "children": [],
- "assetCount": 15,
- "assetCountCumulative": 15
- },
- {
- "name": "Lingerie",
- "slug": "lingerie",
- "active": true,
- "thumbnail": null,
- "thumbnailWidth": null,
- "thumbnailHeight": null,
- "order": 0,
- "alternateTitle": "Lingerie",
- "alternateUrl": "",
- "description": "",
- "metaKeywords": "",
- "metaExtra": "",
- "children": [],
- "assetCount": 0,
- "assetCountCumulative": 0
- },
- {
- "name": "Man Clothing",
- "slug": "man-clothing",
- "active": true,
- "thumbnail": null,
- "thumbnailWidth": null,
- "thumbnailHeight": null,
- "order": 0,
- "alternateTitle": "Man Clothing",
- "alternateUrl": "",
- "description": "",
- "metaKeywords": "",
- "metaExtra": "",
- "children": [],
- "assetCount": 10,
- "assetCountCumulative": 10
- },
- {
- "name": "Woman Clothing",
- "slug": "woman-clothing",
- "active": true,
- "thumbnail": null,
- "thumbnailWidth": null,
- "thumbnailHeight": null,
- "order": 0,
- "alternateTitle": "Woman Clothing",
- "alternateUrl": "",
- "description": "",
- "metaKeywords": "",
- "metaExtra": "",
- "children": [],
- "assetCount": 14,
- "assetCountCumulative": 14
- }
- ],
- "assetCount": 87,
- "assetCountCumulative": 87
- },
- {
- "name": "Humanoids",
- "slug": "people",
- "active": true,
- "thumbnail": null,
- "thumbnailWidth": null,
- "thumbnailHeight": null,
- "order": 0,
- "alternateTitle": "Humanoids",
- "alternateUrl": "",
- "description": "",
- "metaKeywords": "",
- "metaExtra": "",
- "children": [
- {
- "name": "Child",
- "slug": "child",
- "active": true,
- "thumbnail": null,
- "thumbnailWidth": null,
- "thumbnailHeight": null,
- "order": 0,
- "alternateTitle": "Child",
- "alternateUrl": "",
- "description": "",
- "metaKeywords": "",
- "metaExtra": "",
- "children": [],
- "assetCount": 6,
- "assetCountCumulative": 6
- },
- {
- "name": "Fantasy Hero",
- "slug": "fantasy",
- "active": true,
- "thumbnail": null,
- "thumbnailWidth": null,
- "thumbnailHeight": null,
- "order": 0,
- "alternateTitle": "Fantasy Hero",
- "alternateUrl": "",
- "description": "",
- "metaKeywords": "",
- "metaExtra": "",
- "children": [],
- "assetCount": 17,
- "assetCountCumulative": 17
- },
- {
- "name": "Medical",
- "slug": "humanoids-medical",
- "active": true,
- "thumbnail": null,
- "thumbnailWidth": null,
- "thumbnailHeight": null,
- "order": 0,
- "alternateTitle": "Medical",
- "alternateUrl": "",
- "description": "",
- "metaKeywords": "",
- "metaExtra": "",
- "children": [],
- "assetCount": 0,
- "assetCountCumulative": 0
- },
- {
- "name": "Men",
- "slug": "man",
- "active": true,
- "thumbnail": null,
- "thumbnailWidth": null,
- "thumbnailHeight": null,
- "order": 0,
- "alternateTitle": "Men",
- "alternateUrl": "",
- "description": "",
- "metaKeywords": "",
- "metaExtra": "",
- "children": [],
- "assetCount": 27,
- "assetCountCumulative": 27
- },
- {
- "name": "Military",
- "slug": "humanoids-military",
- "active": true,
- "thumbnail": null,
- "thumbnailWidth": null,
- "thumbnailHeight": null,
- "order": 0,
- "alternateTitle": "Military",
- "alternateUrl": "",
- "description": "",
- "metaKeywords": "",
- "metaExtra": "",
- "children": [],
- "assetCount": 3,
- "assetCountCumulative": 3
- },
- {
- "name": "Police",
- "slug": "police",
- "active": true,
- "thumbnail": null,
- "thumbnailWidth": null,
- "thumbnailHeight": null,
- "order": 0,
- "alternateTitle": "Police",
- "alternateUrl": "",
- "description": "",
- "metaKeywords": "",
- "metaExtra": "",
- "children": [],
- "assetCount": 1,
- "assetCountCumulative": 1
- },
- {
- "name": "Sci-Fi",
- "slug": "sci-fi-character",
- "active": true,
- "thumbnail": null,
- "thumbnailWidth": null,
- "thumbnailHeight": null,
- "order": 0,
- "alternateTitle": "Sci-Fi",
- "alternateUrl": "",
- "description": "",
- "metaKeywords": "",
- "metaExtra": "",
- "children": [],
- "assetCount": 12,
- "assetCountCumulative": 12
- },
- {
- "name": "Sports",
- "slug": "humanoids-sports",
- "active": true,
- "thumbnail": null,
- "thumbnailWidth": null,
- "thumbnailHeight": null,
- "order": 0,
- "alternateTitle": "Sports",
- "alternateUrl": "",
- "description": "",
- "metaKeywords": "",
- "metaExtra": "",
- "children": [],
- "assetCount": 0,
- "assetCountCumulative": 0
- },
- {
- "name": "Women",
- "slug": "woman",
- "active": true,
- "thumbnail": null,
- "thumbnailWidth": null,
- "thumbnailHeight": null,
- "order": 0,
- "alternateTitle": "Women",
- "alternateUrl": "",
- "description": "",
- "metaKeywords": "",
- "metaExtra": "",
- "children": [],
- "assetCount": 18,
- "assetCountCumulative": 18
- }
- ],
- "assetCount": 84,
- "assetCountCumulative": 84
- },
- {
- "name": "Monster / Creature",
- "slug": "monster-creature",
- "active": true,
- "thumbnail": null,
- "thumbnailWidth": null,
- "thumbnailHeight": null,
- "order": 0,
- "alternateTitle": "Monster / Creature",
- "alternateUrl": "",
- "description": "",
- "metaKeywords": "",
- "metaExtra": "",
- "children": [],
- "assetCount": 5,
- "assetCountCumulative": 5
- },
- {
- "name": "Robot",
- "slug": "robot",
- "active": true,
- "thumbnail": null,
- "thumbnailWidth": null,
- "thumbnailHeight": null,
- "order": 0,
- "alternateTitle": "Robot",
- "alternateUrl": "",
- "description": "",
- "metaKeywords": "",
- "metaExtra": "",
- "children": [],
- "assetCount": 16,
- "assetCountCumulative": 16
- }
- ],
- "assetCount": 233,
- "assetCountCumulative": 233
- },
- {
- "name": "Decoration",
- "slug": "decoration",
- "active": true,
- "thumbnail": null,
- "thumbnailWidth": null,
- "thumbnailHeight": null,
- "order": 0,
- "alternateTitle": "Decoration",
- "alternateUrl": "",
- "description": "",
- "metaKeywords": "",
- "metaExtra": "",
- "children": [
- {
- "name": "Bag / Suitcase",
- "slug": "bag-case",
- "active": true,
- "thumbnail": null,
- "thumbnailWidth": null,
- "thumbnailHeight": null,
- "order": 0,
- "alternateTitle": "Bag / Suitcase",
- "alternateUrl": "",
- "description": "",
- "metaKeywords": "",
- "metaExtra": "",
- "children": [],
- "assetCount": 22,
- "assetCountCumulative": 22
- },
- {
- "name": "Bed sheet",
- "slug": "bed-sheet",
- "active": true,
- "thumbnail": null,
- "thumbnailWidth": null,
- "thumbnailHeight": null,
- "order": 0,
- "alternateTitle": "Bed sheet",
- "alternateUrl": "",
- "description": "",
- "metaKeywords": "",
- "metaExtra": "",
- "children": [],
- "assetCount": 0,
- "assetCountCumulative": 0
- },
- {
- "name": "Blanket",
- "slug": "blanket",
- "active": true,
- "thumbnail": null,
- "thumbnailWidth": null,
- "thumbnailHeight": null,
- "order": 0,
- "alternateTitle": "Blanket",
- "alternateUrl": "",
- "description": "",
- "metaKeywords": "",
- "metaExtra": "",
- "children": [],
- "assetCount": 0,
- "assetCountCumulative": 0
- },
- {
- "name": "Book",
- "slug": "literature",
- "active": true,
- "thumbnail": null,
- "thumbnailWidth": null,
- "thumbnailHeight": null,
- "order": 0,
- "alternateTitle": "Book",
- "alternateUrl": "",
- "description": "",
- "metaKeywords": "",
- "metaExtra": "",
- "children": [],
- "assetCount": 69,
- "assetCountCumulative": 69
- },
- {
- "name": "Carpet",
- "slug": "carpet",
- "active": true,
- "thumbnail": null,
- "thumbnailWidth": null,
- "thumbnailHeight": null,
- "order": 0,
- "alternateTitle": "Carpet",
- "alternateUrl": "",
- "description": "",
- "metaKeywords": "",
- "metaExtra": "",
- "children": [],
- "assetCount": 35,
- "assetCountCumulative": 35
- },
- {
- "name": "Clock / Watch",
- "slug": "design",
- "active": true,
- "thumbnail": null,
- "thumbnailWidth": null,
- "thumbnailHeight": null,
- "order": 0,
- "alternateTitle": "Clock / Watch",
- "alternateUrl": "",
- "description": "",
- "metaKeywords": "",
- "metaExtra": "",
- "children": [],
- "assetCount": 42,
- "assetCountCumulative": 42
- },
- {
- "name": "Curtain",
- "slug": "curtain",
- "active": true,
- "thumbnail": null,
- "thumbnailWidth": null,
- "thumbnailHeight": null,
- "order": 0,
- "alternateTitle": "Curtain",
- "alternateUrl": "",
- "description": "",
- "metaKeywords": "",
- "metaExtra": "",
- "children": [],
- "assetCount": 35,
- "assetCountCumulative": 35
- },
- {
- "name": "Decoration Set",
- "slug": "photo",
- "active": true,
- "thumbnail": null,
- "thumbnailWidth": null,
- "thumbnailHeight": null,
- "order": 0,
- "alternateTitle": "Decoration Set",
- "alternateUrl": "",
- "description": "",
- "metaKeywords": "",
- "metaExtra": "",
- "children": [],
- "assetCount": 45,
- "assetCountCumulative": 45
- },
- {
- "name": "Fabrics",
- "slug": "fabrics",
- "active": true,
- "thumbnail": null,
- "thumbnailWidth": null,
- "thumbnailHeight": null,
- "order": 0,
- "alternateTitle": "Fabrics",
- "alternateUrl": "",
- "description": "",
- "metaKeywords": "",
- "metaExtra": "",
- "children": [],
- "assetCount": 10,
- "assetCountCumulative": 10
- },
- {
- "name": "Fireplace",
- "slug": "fireplace",
- "active": true,
- "thumbnail": null,
- "thumbnailWidth": null,
- "thumbnailHeight": null,
- "order": 0,
- "alternateTitle": "Fireplace",
- "alternateUrl": "",
- "description": "",
- "metaKeywords": "",
- "metaExtra": "",
- "children": [],
- "assetCount": 29,
- "assetCountCumulative": 29
- },
- {
- "name": "Food / Drinks",
- "slug": "food-drink",
- "active": true,
- "thumbnail": null,
- "thumbnailWidth": null,
- "thumbnailHeight": null,
- "order": 0,
- "alternateTitle": "Food / Drinks",
- "alternateUrl": "",
- "description": "",
- "metaKeywords": "",
- "metaExtra": "",
- "children": [
- {
- "name": "Beverage",
- "slug": "drink",
- "active": true,
- "thumbnail": null,
- "thumbnailWidth": null,
- "thumbnailHeight": null,
- "order": 0,
- "alternateTitle": "Beverage",
- "alternateUrl": "",
- "description": "",
- "metaKeywords": "",
- "metaExtra": "",
- "children": [],
- "assetCount": 100,
- "assetCountCumulative": 100
- },
- {
- "name": "Food",
- "slug": "food",
- "active": true,
- "thumbnail": null,
- "thumbnailWidth": null,
- "thumbnailHeight": null,
- "order": 0,
- "alternateTitle": "Food",
- "alternateUrl": "",
- "description": "",
- "metaKeywords": "",
- "metaExtra": "",
- "children": [],
- "assetCount": 62,
- "assetCountCumulative": 62
- },
- {
- "name": "Fruit / Vegetable",
- "slug": "fruitvegetable",
- "active": true,
- "thumbnail": null,
- "thumbnailWidth": null,
- "thumbnailHeight": null,
- "order": 0,
- "alternateTitle": "Fruit/Vegetable",
- "alternateUrl": "",
- "description": "",
- "metaKeywords": "",
- "metaExtra": "",
- "children": [],
- "assetCount": 71,
- "assetCountCumulative": 71
- },
- {
- "name": "Kitchenware",
- "slug": "container",
- "active": true,
- "thumbnail": null,
- "thumbnailWidth": null,
- "thumbnailHeight": null,
- "order": 0,
- "alternateTitle": "Kitchenware",
- "alternateUrl": "",
- "description": "",
- "metaKeywords": "",
- "metaExtra": "",
- "children": [],
- "assetCount": 164,
- "assetCountCumulative": 164
- },
- {
- "name": "Other",
- "slug": "drugs",
- "active": true,
- "thumbnail": null,
- "thumbnailWidth": null,
- "thumbnailHeight": null,
- "order": 0,
- "alternateTitle": "Other",
- "alternateUrl": "",
- "description": "",
- "metaKeywords": "",
- "metaExtra": "",
- "children": [],
- "assetCount": 14,
- "assetCountCumulative": 14
- },
- {
- "name": "Sweets / Dessert",
- "slug": "sweetsdessert",
- "active": true,
- "thumbnail": null,
- "thumbnailWidth": null,
- "thumbnailHeight": null,
- "order": 0,
- "alternateTitle": "Sweets/Dessert",
- "alternateUrl": "",
- "description": "",
- "metaKeywords": "",
- "metaExtra": "",
- "children": [],
- "assetCount": 26,
- "assetCountCumulative": 26
- },
- {
- "name": "Tableware set",
- "slug": "tableware-set",
- "active": true,
- "thumbnail": null,
- "thumbnailWidth": null,
- "thumbnailHeight": null,
- "order": 0,
- "alternateTitle": "Tableware set",
- "alternateUrl": "",
- "description": "",
- "metaKeywords": "",
- "metaExtra": "",
- "children": [],
- "assetCount": 157,
- "assetCountCumulative": 157
- }
- ],
- "assetCount": 599,
- "assetCountCumulative": 599
- },
- {
- "name": "Holiday Decoration",
- "slug": "supplies",
- "active": true,
- "thumbnail": null,
- "thumbnailWidth": null,
- "thumbnailHeight": null,
- "order": 0,
- "alternateTitle": "Holiday Decoration",
- "alternateUrl": "",
- "description": "",
- "metaKeywords": "",
- "metaExtra": "",
- "children": [],
- "assetCount": 38,
- "assetCountCumulative": 38
- },
- {
- "name": "Mirror",
- "slug": "mirror",
- "active": true,
- "thumbnail": null,
- "thumbnailWidth": null,
- "thumbnailHeight": null,
- "order": 0,
- "alternateTitle": "Mirror",
- "alternateUrl": "",
- "description": "",
- "metaKeywords": "",
- "metaExtra": "",
- "children": [],
- "assetCount": 50,
- "assetCountCumulative": 50
- },
- {
- "name": "Miscellaneous",
- "slug": "art",
- "active": true,
- "thumbnail": null,
- "thumbnailWidth": null,
- "thumbnailHeight": null,
- "order": 0,
- "alternateTitle": "Miscellaneous",
- "alternateUrl": "",
- "description": "",
- "metaKeywords": "",
- "metaExtra": "",
- "children": [],
- "assetCount": 272,
- "assetCountCumulative": 272
- },
- {
- "name": "Money",
- "slug": "money",
- "active": true,
- "thumbnail": null,
- "thumbnailWidth": null,
- "thumbnailHeight": null,
- "order": 0,
- "alternateTitle": "Money",
- "alternateUrl": "",
- "description": "",
- "metaKeywords": "",
- "metaExtra": "",
- "children": [],
- "assetCount": 21,
- "assetCountCumulative": 21
- },
- {
- "name": "Other textile",
- "slug": "other-textile",
- "active": true,
- "thumbnail": null,
- "thumbnailWidth": null,
- "thumbnailHeight": null,
- "order": 0,
- "alternateTitle": "Other textile",
- "alternateUrl": "",
- "description": "",
- "metaKeywords": "",
- "metaExtra": "",
- "children": [],
- "assetCount": 20,
- "assetCountCumulative": 20
- },
- {
- "name": "Picture",
- "slug": "painting",
- "active": true,
- "thumbnail": null,
- "thumbnailWidth": null,
- "thumbnailHeight": null,
- "order": 0,
- "alternateTitle": "Picture",
- "alternateUrl": "",
- "description": "",
- "metaKeywords": "",
- "metaExtra": "",
- "children": [],
- "assetCount": 160,
- "assetCountCumulative": 160
- },
- {
- "name": "Pillow",
- "slug": "pillow",
- "active": true,
- "thumbnail": null,
- "thumbnailWidth": null,
- "thumbnailHeight": null,
- "order": 0,
- "alternateTitle": "Pillow",
- "alternateUrl": "",
- "description": "",
- "metaKeywords": "",
- "metaExtra": "",
- "children": [],
- "assetCount": 56,
- "assetCountCumulative": 56
- },
- {
- "name": "Sculpture",
- "slug": "sculpture",
- "active": true,
- "thumbnail": null,
- "thumbnailWidth": null,
- "thumbnailHeight": null,
- "order": 0,
- "alternateTitle": "Sculpture",
- "alternateUrl": "",
- "description": "",
- "metaKeywords": "",
- "metaExtra": "",
- "children": [],
- "assetCount": 110,
- "assetCountCumulative": 110
- },
- {
- "name": "Vase",
- "slug": "drawing",
- "active": true,
- "thumbnail": null,
- "thumbnailWidth": null,
- "thumbnailHeight": null,
- "order": 0,
- "alternateTitle": "Vase",
- "alternateUrl": "",
- "description": "",
- "metaKeywords": "",
- "metaExtra": "",
- "children": [],
- "assetCount": 132,
- "assetCountCumulative": 132
- }
- ],
- "assetCount": 1749,
- "assetCountCumulative": 1749
- },
- {
- "name": "Industrial",
- "slug": "industrial",
- "active": true,
- "thumbnail": null,
- "thumbnailWidth": null,
- "thumbnailHeight": null,
- "order": 0,
- "alternateTitle": "Industrial",
- "alternateUrl": "",
- "description": "",
- "metaKeywords": "",
- "metaExtra": "",
- "children": [
- {
- "name": "Container",
- "slug": "container-industrial",
- "active": true,
- "thumbnail": null,
- "thumbnailWidth": null,
- "thumbnailHeight": null,
- "order": 0,
- "alternateTitle": "Container",
- "alternateUrl": "",
- "description": "",
- "metaKeywords": "",
- "metaExtra": "",
- "children": [],
- "assetCount": 83,
- "assetCountCumulative": 83
- },
- {
- "name": "Equipment",
- "slug": "utility-industrial",
- "active": true,
- "thumbnail": null,
- "thumbnailWidth": null,
- "thumbnailHeight": null,
- "order": 0,
- "alternateTitle": "Equipment",
- "alternateUrl": "",
- "description": "",
- "metaKeywords": "",
- "metaExtra": "",
- "children": [],
- "assetCount": 17,
- "assetCountCumulative": 17
- },
- {
- "name": "Machinery",
- "slug": "machine",
- "active": true,
- "thumbnail": null,
- "thumbnailWidth": null,
- "thumbnailHeight": null,
- "order": 0,
- "alternateTitle": "Machinery",
- "alternateUrl": "",
- "description": "",
- "metaKeywords": "",
- "metaExtra": "",
- "children": [],
- "assetCount": 23,
- "assetCountCumulative": 23
- },
- {
- "name": "Other",
- "slug": "agriculture",
- "active": true,
- "thumbnail": null,
- "thumbnailWidth": null,
- "thumbnailHeight": null,
- "order": 0,
- "alternateTitle": "Other",
- "alternateUrl": "",
- "description": "",
- "metaKeywords": "",
- "metaExtra": "",
- "children": [],
- "assetCount": 50,
- "assetCountCumulative": 50
- },
- {
- "name": "Parts",
- "slug": "construction",
- "active": true,
- "thumbnail": null,
- "thumbnailWidth": null,
- "thumbnailHeight": null,
- "order": 0,
- "alternateTitle": "Parts",
- "alternateUrl": "",
- "description": "",
- "metaKeywords": "",
- "metaExtra": "",
- "children": [],
- "assetCount": 81,
- "assetCountCumulative": 81
- },
- {
- "name": "Sign",
- "slug": "communication",
- "active": true,
- "thumbnail": null,
- "thumbnailWidth": null,
- "thumbnailHeight": null,
- "order": 0,
- "alternateTitle": "Sign",
- "alternateUrl": "",
- "description": "",
- "metaKeywords": "",
- "metaExtra": "",
- "children": [],
- "assetCount": 36,
- "assetCountCumulative": 36
- },
- {
- "name": "Tools",
- "slug": "tool",
- "active": true,
- "thumbnail": null,
- "thumbnailWidth": null,
- "thumbnailHeight": null,
- "order": 0,
- "alternateTitle": "Tools",
- "alternateUrl": "",
- "description": "",
- "metaKeywords": "",
- "metaExtra": "",
- "children": [
- {
- "name": "Handtools",
- "slug": "handtools",
- "active": true,
- "thumbnail": null,
- "thumbnailWidth": null,
- "thumbnailHeight": null,
- "order": 0,
- "alternateTitle": "Handtools",
- "alternateUrl": "",
- "description": "",
- "metaKeywords": "",
- "metaExtra": "",
- "children": [],
- "assetCount": 34,
- "assetCountCumulative": 34
- },
- {
- "name": "Powertools",
- "slug": "powertools",
- "active": true,
- "thumbnail": null,
- "thumbnailWidth": null,
- "thumbnailHeight": null,
- "order": 0,
- "alternateTitle": "Powertools",
- "alternateUrl": "",
- "description": "",
- "metaKeywords": "",
- "metaExtra": "",
- "children": [],
- "assetCount": 16,
- "assetCountCumulative": 16
- }
- ],
- "assetCount": 50,
- "assetCountCumulative": 50
- }
- ],
- "assetCount": 340,
- "assetCountCumulative": 340
- },
- {
- "name": "Interior",
- "slug": "interior",
- "active": true,
- "thumbnail": null,
- "thumbnailWidth": null,
- "thumbnailHeight": null,
- "order": 0,
- "alternateTitle": "Interior",
- "alternateUrl": "",
- "description": "",
- "metaKeywords": "",
- "metaExtra": "",
- "children": [
- {
- "name": "Armchair",
- "slug": "furniture",
- "active": true,
- "thumbnail": null,
- "thumbnailWidth": null,
- "thumbnailHeight": null,
- "order": 0,
- "alternateTitle": "Armchair",
- "alternateUrl": "",
- "description": "",
- "metaKeywords": "",
- "metaExtra": "",
- "children": [],
- "assetCount": 223,
- "assetCountCumulative": 223
- },
- {
- "name": "Bathroom furniture",
- "slug": "bathroom",
- "active": true,
- "thumbnail": null,
- "thumbnailWidth": null,
- "thumbnailHeight": null,
- "order": 0,
- "alternateTitle": "Bathroom furniture",
- "alternateUrl": "",
- "description": "",
- "metaKeywords": "",
- "metaExtra": "",
- "children": [
- {
- "name": "Accessories",
- "slug": "utility",
- "active": true,
- "thumbnail": null,
- "thumbnailWidth": null,
- "thumbnailHeight": null,
- "order": 0,
- "alternateTitle": "Accessories",
- "alternateUrl": "",
- "description": "",
- "metaKeywords": "",
- "metaExtra": "",
- "children": [],
- "assetCount": 93,
- "assetCountCumulative": 93
- },
- {
- "name": "Bathhub",
- "slug": "bathhub",
- "active": true,
- "thumbnail": null,
- "thumbnailWidth": null,
- "thumbnailHeight": null,
- "order": 0,
- "alternateTitle": "Bathhub",
- "alternateUrl": "",
- "description": "",
- "metaKeywords": "",
- "metaExtra": "",
- "children": [],
- "assetCount": 19,
- "assetCountCumulative": 19
- },
- {
- "name": "Faucet",
- "slug": "bathroomfurniture-faucet",
- "active": true,
- "thumbnail": null,
- "thumbnailWidth": null,
- "thumbnailHeight": null,
- "order": 0,
- "alternateTitle": "Faucet",
- "alternateUrl": "",
- "description": "",
- "metaKeywords": "",
- "metaExtra": "",
- "children": [],
- "assetCount": 21,
- "assetCountCumulative": 21
- },
- {
- "name": "Furniture Set",
- "slug": "bathroomfurniture-furniture-set",
- "active": true,
- "thumbnail": null,
- "thumbnailWidth": null,
- "thumbnailHeight": null,
- "order": 0,
- "alternateTitle": "Furniture Set",
- "alternateUrl": "",
- "description": "",
- "metaKeywords": "",
- "metaExtra": "",
- "children": [],
- "assetCount": 19,
- "assetCountCumulative": 19
- },
- {
- "name": "Laundry",
- "slug": "laundry",
- "active": true,
- "thumbnail": null,
- "thumbnailWidth": null,
- "thumbnailHeight": null,
- "order": 0,
- "alternateTitle": "Laundry",
- "alternateUrl": "",
- "description": "",
- "metaKeywords": "",
- "metaExtra": "",
- "children": [],
- "assetCount": 11,
- "assetCountCumulative": 11
- },
- {
- "name": "Shower",
- "slug": "shower",
- "active": true,
- "thumbnail": null,
- "thumbnailWidth": null,
- "thumbnailHeight": null,
- "order": 0,
- "alternateTitle": "Shower",
- "alternateUrl": "",
- "description": "",
- "metaKeywords": "",
- "metaExtra": "",
- "children": [],
- "assetCount": 21,
- "assetCountCumulative": 21
- },
- {
- "name": "Toilet / Bidet",
- "slug": "toilet-bidet",
- "active": true,
- "thumbnail": null,
- "thumbnailWidth": null,
- "thumbnailHeight": null,
- "order": 0,
- "alternateTitle": "Toilet / Bidet",
- "alternateUrl": "",
- "description": "",
- "metaKeywords": "",
- "metaExtra": "",
- "children": [],
- "assetCount": 19,
- "assetCountCumulative": 19
- },
- {
- "name": "Towel rail",
- "slug": "towel-rail",
- "active": true,
- "thumbnail": null,
- "thumbnailWidth": null,
- "thumbnailHeight": null,
- "order": 0,
- "alternateTitle": "Towel rail",
- "alternateUrl": "",
- "description": "",
- "metaKeywords": "",
- "metaExtra": "",
- "children": [],
- "assetCount": 10,
- "assetCountCumulative": 10
- },
- {
- "name": "Wash Basin",
- "slug": "wash-basin",
- "active": true,
- "thumbnail": null,
- "thumbnailWidth": null,
- "thumbnailHeight": null,
- "order": 0,
- "alternateTitle": "Wash Basin",
- "alternateUrl": "",
- "description": "",
- "metaKeywords": "",
- "metaExtra": "",
- "children": [],
- "assetCount": 35,
- "assetCountCumulative": 35
- }
- ],
- "assetCount": 255,
- "assetCountCumulative": 255
- },
- {
- "name": "Bed",
- "slug": "bed",
- "active": true,
- "thumbnail": null,
- "thumbnailWidth": null,
- "thumbnailHeight": null,
- "order": 0,
- "alternateTitle": "Bed",
- "alternateUrl": "",
- "description": "",
- "metaKeywords": "",
- "metaExtra": "",
- "children": [],
- "assetCount": 57,
- "assetCountCumulative": 57
- },
- {
- "name": "Cabinets",
- "slug": "cabinets",
- "active": true,
- "thumbnail": null,
- "thumbnailWidth": null,
- "thumbnailHeight": null,
- "order": 0,
- "alternateTitle": "Cabinets",
- "alternateUrl": "",
- "description": "",
- "metaKeywords": "",
- "metaExtra": "",
- "children": [
- {
- "name": "Bookcase",
- "slug": "bookcase",
- "active": true,
- "thumbnail": null,
- "thumbnailWidth": null,
- "thumbnailHeight": null,
- "order": 0,
- "alternateTitle": "Bookcase",
- "alternateUrl": "",
- "description": "",
- "metaKeywords": "",
- "metaExtra": "",
- "children": [],
- "assetCount": 31,
- "assetCountCumulative": 31
- },
- {
- "name": "Commode",
- "slug": "commode",
- "active": true,
- "thumbnail": null,
- "thumbnailWidth": null,
- "thumbnailHeight": null,
- "order": 0,
- "alternateTitle": "Commode",
- "alternateUrl": "",
- "description": "",
- "metaKeywords": "",
- "metaExtra": "",
- "children": [],
- "assetCount": 108,
- "assetCountCumulative": 108
- },
- {
- "name": "Shelving",
- "slug": "shelving",
- "active": true,
- "thumbnail": null,
- "thumbnailWidth": null,
- "thumbnailHeight": null,
- "order": 0,
- "alternateTitle": "Shelving",
- "alternateUrl": "",
- "description": "",
- "metaKeywords": "",
- "metaExtra": "",
- "children": [],
- "assetCount": 103,
- "assetCountCumulative": 103
- },
- {
- "name": "TV Cabinets",
- "slug": "tv-cabinets",
- "active": true,
- "thumbnail": null,
- "thumbnailWidth": null,
- "thumbnailHeight": null,
- "order": 0,
- "alternateTitle": "TV Cabinets",
- "alternateUrl": "",
- "description": "",
- "metaKeywords": "",
- "metaExtra": "",
- "children": [],
- "assetCount": 43,
- "assetCountCumulative": 43
- }
- ],
- "assetCount": 327,
- "assetCountCumulative": 327
- },
- {
- "name": "Chair",
- "slug": "chair",
- "active": true,
- "thumbnail": null,
- "thumbnailWidth": null,
- "thumbnailHeight": null,
- "order": 0,
- "alternateTitle": "Chair",
- "alternateUrl": "",
- "description": "",
- "metaKeywords": "",
- "metaExtra": "",
- "children": [
- {
- "name": "Bar Chair",
- "slug": "bar-chair",
- "active": true,
- "thumbnail": null,
- "thumbnailWidth": null,
- "thumbnailHeight": null,
- "order": 0,
- "alternateTitle": "Bar Chair",
- "alternateUrl": "",
- "description": "",
- "metaKeywords": "",
- "metaExtra": "",
- "children": [],
- "assetCount": 72,
- "assetCountCumulative": 72
- },
- {
- "name": "Regular Chair",
- "slug": "regular-chair",
- "active": true,
- "thumbnail": null,
- "thumbnailWidth": null,
- "thumbnailHeight": null,
- "order": 0,
- "alternateTitle": "Regular Chair",
- "alternateUrl": "",
- "description": "",
- "metaKeywords": "",
- "metaExtra": "",
- "children": [],
- "assetCount": 265,
- "assetCountCumulative": 265
- }
- ],
- "assetCount": 364,
- "assetCountCumulative": 364
- },
- {
- "name": "Console",
- "slug": "bedroom",
- "active": true,
- "thumbnail": null,
- "thumbnailWidth": null,
- "thumbnailHeight": null,
- "order": 0,
- "alternateTitle": "Console",
- "alternateUrl": "",
- "description": "",
- "metaKeywords": "",
- "metaExtra": "",
- "children": [],
- "assetCount": 39,
- "assetCountCumulative": 39
- },
- {
- "name": "Dressing Table",
- "slug": "living-room",
- "active": true,
- "thumbnail": null,
- "thumbnailWidth": null,
- "thumbnailHeight": null,
- "order": 0,
- "alternateTitle": "Dressing Table",
- "alternateUrl": "",
- "description": "",
- "metaKeywords": "",
- "metaExtra": "",
- "children": [],
- "assetCount": 13,
- "assetCountCumulative": 13
- },
- {
- "name": "Kids furniture",
- "slug": "kids-room",
- "active": true,
- "thumbnail": null,
- "thumbnailWidth": null,
- "thumbnailHeight": null,
- "order": 0,
- "alternateTitle": "Kids furniture",
- "alternateUrl": "",
- "description": "",
- "metaKeywords": "",
- "metaExtra": "",
- "children": [
- {
- "name": "Bed",
- "slug": "kidsfurniture-bed",
- "active": true,
- "thumbnail": null,
- "thumbnailWidth": null,
- "thumbnailHeight": null,
- "order": 0,
- "alternateTitle": "Bed",
- "alternateUrl": "",
- "description": "",
- "metaKeywords": "",
- "metaExtra": "",
- "children": [],
- "assetCount": 14,
- "assetCountCumulative": 14
- },
- {
- "name": "Chair",
- "slug": "kidsfurniture-chair",
- "active": true,
- "thumbnail": null,
- "thumbnailWidth": null,
- "thumbnailHeight": null,
- "order": 0,
- "alternateTitle": "Chair",
- "alternateUrl": "",
- "description": "",
- "metaKeywords": "",
- "metaExtra": "",
- "children": [],
- "assetCount": 3,
- "assetCountCumulative": 3
- },
- {
- "name": "Furniture Set",
- "slug": "furniture-set",
- "active": true,
- "thumbnail": null,
- "thumbnailWidth": null,
- "thumbnailHeight": null,
- "order": 0,
- "alternateTitle": "Furniture Set",
- "alternateUrl": "",
- "description": "",
- "metaKeywords": "",
- "metaExtra": "",
- "children": [],
- "assetCount": 8,
- "assetCountCumulative": 8
- },
- {
- "name": "Miscellaneous",
- "slug": "miscellaneous",
- "active": true,
- "thumbnail": null,
- "thumbnailWidth": null,
- "thumbnailHeight": null,
- "order": 0,
- "alternateTitle": "Miscellaneous",
- "alternateUrl": "",
- "description": "",
- "metaKeywords": "",
- "metaExtra": "",
- "children": [],
- "assetCount": 13,
- "assetCountCumulative": 13
- },
- {
- "name": "Table",
- "slug": "tablechair",
- "active": true,
- "thumbnail": null,
- "thumbnailWidth": null,
- "thumbnailHeight": null,
- "order": 0,
- "alternateTitle": "Table",
- "alternateUrl": "",
- "description": "",
- "metaKeywords": "",
- "metaExtra": "",
- "children": [],
- "assetCount": 1,
- "assetCountCumulative": 1
- },
- {
- "name": "Toy",
- "slug": "toy",
- "active": true,
- "thumbnail": null,
- "thumbnailWidth": null,
- "thumbnailHeight": null,
- "order": 0,
- "alternateTitle": "Toy",
- "alternateUrl": "",
- "description": "",
- "metaKeywords": "",
- "metaExtra": "",
- "children": [],
- "assetCount": 37,
- "assetCountCumulative": 37
- },
- {
- "name": "Wardrobe",
- "slug": "kidsfurniture-wardrobe",
- "active": true,
- "thumbnail": null,
- "thumbnailWidth": null,
- "thumbnailHeight": null,
- "order": 0,
- "alternateTitle": "Wardrobe",
- "alternateUrl": "",
- "description": "",
- "metaKeywords": "",
- "metaExtra": "",
- "children": [],
- "assetCount": 4,
- "assetCountCumulative": 4
- }
- ],
- "assetCount": 82,
- "assetCountCumulative": 82
- },
- {
- "name": "Kitchen Furniture",
- "slug": "kitchen",
- "active": true,
- "thumbnail": null,
- "thumbnailWidth": null,
- "thumbnailHeight": null,
- "order": 0,
- "alternateTitle": "Kitchen Furniture",
- "alternateUrl": "",
- "description": "",
- "metaKeywords": "",
- "metaExtra": "",
- "children": [
- {
- "name": "Faucet",
- "slug": "faucet",
- "active": true,
- "thumbnail": null,
- "thumbnailWidth": null,
- "thumbnailHeight": null,
- "order": 0,
- "alternateTitle": "Faucet",
- "alternateUrl": "",
- "description": "",
- "metaKeywords": "",
- "metaExtra": "",
- "children": [],
- "assetCount": 33,
- "assetCountCumulative": 33
- },
- {
- "name": "Kitchen Appliance",
- "slug": "kitchen-appliance",
- "active": true,
- "thumbnail": null,
- "thumbnailWidth": null,
- "thumbnailHeight": null,
- "order": 0,
- "alternateTitle": "Kitchen Appliance",
- "alternateUrl": "",
- "description": "",
- "metaKeywords": "",
- "metaExtra": "",
- "children": [],
- "assetCount": 126,
- "assetCountCumulative": 126
- },
- {
- "name": "Kitchen Set",
- "slug": "kitchen-set",
- "active": true,
- "thumbnail": null,
- "thumbnailWidth": null,
- "thumbnailHeight": null,
- "order": 0,
- "alternateTitle": "Kitchen Set",
- "alternateUrl": "",
- "description": "",
- "metaKeywords": "",
- "metaExtra": "",
- "children": [],
- "assetCount": 43,
- "assetCountCumulative": 43
- },
- {
- "name": "Sink",
- "slug": "sink",
- "active": true,
- "thumbnail": null,
- "thumbnailWidth": null,
- "thumbnailHeight": null,
- "order": 0,
- "alternateTitle": "Sink",
- "alternateUrl": "",
- "description": "",
- "metaKeywords": "",
- "metaExtra": "",
- "children": [],
- "assetCount": 18,
- "assetCountCumulative": 18
- },
- {
- "name": "Storage",
- "slug": "storage",
- "active": true,
- "thumbnail": null,
- "thumbnailWidth": null,
- "thumbnailHeight": null,
- "order": 0,
- "alternateTitle": "Storage",
- "alternateUrl": "",
- "description": "",
- "metaKeywords": "",
- "metaExtra": "",
- "children": [],
- "assetCount": 127,
- "assetCountCumulative": 127
- }
- ],
- "assetCount": 361,
- "assetCountCumulative": 361
- },
- {
- "name": "Lights",
- "slug": "lighting",
- "active": true,
- "thumbnail": null,
- "thumbnailWidth": null,
- "thumbnailHeight": null,
- "order": 0,
- "alternateTitle": "Lights",
- "alternateUrl": "",
- "description": "",
- "metaKeywords": "",
- "metaExtra": "",
- "children": [
- {
- "name": "Ceiling Light",
- "slug": "ceiling-light",
- "active": true,
- "thumbnail": null,
- "thumbnailWidth": null,
- "thumbnailHeight": null,
- "order": 0,
- "alternateTitle": "Ceiling Light",
- "alternateUrl": "",
- "description": "",
- "metaKeywords": "",
- "metaExtra": "",
- "children": [],
- "assetCount": 187,
- "assetCountCumulative": 187
- },
- {
- "name": "Floor Lamp",
- "slug": "floor-lamp",
- "active": true,
- "thumbnail": null,
- "thumbnailWidth": null,
- "thumbnailHeight": null,
- "order": 0,
- "alternateTitle": "Floor Lamp",
- "alternateUrl": "",
- "description": "",
- "metaKeywords": "",
- "metaExtra": "",
- "children": [],
- "assetCount": 57,
- "assetCountCumulative": 57
- },
- {
- "name": "IES Light",
- "slug": "ies-light",
- "active": true,
- "thumbnail": null,
- "thumbnailWidth": null,
- "thumbnailHeight": null,
- "order": 0,
- "alternateTitle": "IES Light",
- "alternateUrl": "",
- "description": "",
- "metaKeywords": "",
- "metaExtra": "",
- "children": [],
- "assetCount": 10,
- "assetCountCumulative": 10
- },
- {
- "name": "Industrial Light",
- "slug": "industrial-light",
- "active": true,
- "thumbnail": null,
- "thumbnailWidth": null,
- "thumbnailHeight": null,
- "order": 0,
- "alternateTitle": "Industrial Light",
- "alternateUrl": "",
- "description": "",
- "metaKeywords": "",
- "metaExtra": "",
- "children": [],
- "assetCount": 19,
- "assetCountCumulative": 19
- },
- {
- "name": "Outdoor Light",
- "slug": "outdoor-light",
- "active": true,
- "thumbnail": null,
- "thumbnailWidth": null,
- "thumbnailHeight": null,
- "order": 0,
- "alternateTitle": "Outdoor Light",
- "alternateUrl": "",
- "description": "",
- "metaKeywords": "",
- "metaExtra": "",
- "children": [],
- "assetCount": 36,
- "assetCountCumulative": 36
- },
- {
- "name": "Table Lamp",
- "slug": "table-lamps",
- "active": true,
- "thumbnail": null,
- "thumbnailWidth": null,
- "thumbnailHeight": null,
- "order": 0,
- "alternateTitle": "Table Lamp",
- "alternateUrl": "",
- "description": "",
- "metaKeywords": "",
- "metaExtra": "",
- "children": [],
- "assetCount": 99,
- "assetCountCumulative": 99
- },
- {
- "name": "Wall Light",
- "slug": "wall-light",
- "active": true,
- "thumbnail": null,
- "thumbnailWidth": null,
- "thumbnailHeight": null,
- "order": 0,
- "alternateTitle": "Wall Light",
- "alternateUrl": "",
- "description": "",
- "metaKeywords": "",
- "metaExtra": "",
- "children": [],
- "assetCount": 51,
- "assetCountCumulative": 51
- }
- ],
- "assetCount": 480,
- "assetCountCumulative": 480
- },
- {
- "name": "Office Furniture",
- "slug": "office",
- "active": true,
- "thumbnail": null,
- "thumbnailWidth": null,
- "thumbnailHeight": null,
- "order": 0,
- "alternateTitle": "Office Furniture",
- "alternateUrl": "",
- "description": "",
- "metaKeywords": "",
- "metaExtra": "",
- "children": [
- {
- "name": "Chair",
- "slug": "office-chair",
- "active": true,
- "thumbnail": null,
- "thumbnailWidth": null,
- "thumbnailHeight": null,
- "order": 0,
- "alternateTitle": "Chair",
- "alternateUrl": "",
- "description": "",
- "metaKeywords": "",
- "metaExtra": "",
- "children": [],
- "assetCount": 26,
- "assetCountCumulative": 26
- },
- {
- "name": "Desk",
- "slug": "desk",
- "active": true,
- "thumbnail": null,
- "thumbnailWidth": null,
- "thumbnailHeight": null,
- "order": 0,
- "alternateTitle": "Desk",
- "alternateUrl": "",
- "description": "",
- "metaKeywords": "",
- "metaExtra": "",
- "children": [],
- "assetCount": 63,
- "assetCountCumulative": 63
- },
- {
- "name": "Stationery",
- "slug": "stationery",
- "active": true,
- "thumbnail": null,
- "thumbnailWidth": null,
- "thumbnailHeight": null,
- "order": 0,
- "alternateTitle": "Stationery",
- "alternateUrl": "",
- "description": "",
- "metaKeywords": "",
- "metaExtra": "",
- "children": [],
- "assetCount": 52,
- "assetCountCumulative": 52
- },
- {
- "name": "Storage",
- "slug": "office-storage",
- "active": true,
- "thumbnail": null,
- "thumbnailWidth": null,
- "thumbnailHeight": null,
- "order": 0,
- "alternateTitle": "Storage",
- "alternateUrl": "",
- "description": "",
- "metaKeywords": "",
- "metaExtra": "",
- "children": [],
- "assetCount": 32,
- "assetCountCumulative": 32
- },
- {
- "name": "Table",
- "slug": "office-table",
- "active": true,
- "thumbnail": null,
- "thumbnailWidth": null,
- "thumbnailHeight": null,
- "order": 0,
- "alternateTitle": "Table",
- "alternateUrl": "",
- "description": "",
- "metaKeywords": "",
- "metaExtra": "",
- "children": [],
- "assetCount": 1,
- "assetCountCumulative": 1
- }
- ],
- "assetCount": 186,
- "assetCountCumulative": 186
- },
- {
- "name": "Outdoor Furniture",
- "slug": "outdoor-furniture",
- "active": true,
- "thumbnail": null,
- "thumbnailWidth": null,
- "thumbnailHeight": null,
- "order": 0,
- "alternateTitle": "Outdoor Furniture",
- "alternateUrl": "",
- "description": "",
- "metaKeywords": "",
- "metaExtra": "",
- "children": [],
- "assetCount": 36,
- "assetCountCumulative": 36
- },
- {
- "name": "Pouf",
- "slug": "pouf",
- "active": true,
- "thumbnail": null,
- "thumbnailWidth": null,
- "thumbnailHeight": null,
- "order": 0,
- "alternateTitle": "Pouf",
- "alternateUrl": "",
- "description": "",
- "metaKeywords": "",
- "metaExtra": "",
- "children": [],
- "assetCount": 43,
- "assetCountCumulative": 43
- },
- {
- "name": "Restaurant / Bar",
- "slug": "restaurant-bar",
- "active": true,
- "thumbnail": null,
- "thumbnailWidth": null,
- "thumbnailHeight": null,
- "order": 0,
- "alternateTitle": "Restaurant / Bar",
- "alternateUrl": "",
- "description": "",
- "metaKeywords": "",
- "metaExtra": "",
- "children": [],
- "assetCount": 40,
- "assetCountCumulative": 40
- },
- {
- "name": "Seating Set",
- "slug": "seating",
- "active": true,
- "thumbnail": null,
- "thumbnailWidth": null,
- "thumbnailHeight": null,
- "order": 0,
- "alternateTitle": "Seating Set",
- "alternateUrl": "",
- "description": "",
- "metaKeywords": "",
- "metaExtra": "",
- "children": [
- {
- "name": "Chair-table Set",
- "slug": "chair-table-set",
- "active": true,
- "thumbnail": null,
- "thumbnailWidth": null,
- "thumbnailHeight": null,
- "order": 0,
- "alternateTitle": "Chair-table Set",
- "alternateUrl": "",
- "description": "",
- "metaKeywords": "",
- "metaExtra": "",
- "children": [],
- "assetCount": 18,
- "assetCountCumulative": 18
- },
- {
- "name": "Sofa-table Set",
- "slug": "sofa-table-set",
- "active": true,
- "thumbnail": null,
- "thumbnailWidth": null,
- "thumbnailHeight": null,
- "order": 0,
- "alternateTitle": "Sofa-table Set",
- "alternateUrl": "",
- "description": "",
- "metaKeywords": "",
- "metaExtra": "",
- "children": [],
- "assetCount": 5,
- "assetCountCumulative": 5
- }
- ],
- "assetCount": 31,
- "assetCountCumulative": 31
- },
- {
- "name": "Shopping / Retail",
- "slug": "shopping-retail",
- "active": true,
- "thumbnail": null,
- "thumbnailWidth": null,
- "thumbnailHeight": null,
- "order": 0,
- "alternateTitle": "Shopping / Retail",
- "alternateUrl": "",
- "description": "",
- "metaKeywords": "",
- "metaExtra": "",
- "children": [],
- "assetCount": 3,
- "assetCountCumulative": 3
- },
- {
- "name": "Sideboard / Drawers Chest",
- "slug": "hall",
- "active": true,
- "thumbnail": null,
- "thumbnailWidth": null,
- "thumbnailHeight": null,
- "order": 0,
- "alternateTitle": "Sideboard / Drawers Chest",
- "alternateUrl": "",
- "description": "",
- "metaKeywords": "",
- "metaExtra": "",
- "children": [],
- "assetCount": 124,
- "assetCountCumulative": 124
- },
- {
- "name": "Sofa",
- "slug": "sofa",
- "active": true,
- "thumbnail": null,
- "thumbnailWidth": null,
- "thumbnailHeight": null,
- "order": 0,
- "alternateTitle": "Sofa",
- "alternateUrl": "",
- "description": "",
- "metaKeywords": "",
- "metaExtra": "",
- "children": [],
- "assetCount": 208,
- "assetCountCumulative": 208
- },
- {
- "name": "Table",
- "slug": "table",
- "active": true,
- "thumbnail": null,
- "thumbnailWidth": null,
- "thumbnailHeight": null,
- "order": 0,
- "alternateTitle": "Table",
- "alternateUrl": "",
- "description": "",
- "metaKeywords": "",
- "metaExtra": "",
- "children": [],
- "assetCount": 444,
- "assetCountCumulative": 444
- },
- {
- "name": "Wardrobe",
- "slug": "wardrobe",
- "active": true,
- "thumbnail": null,
- "thumbnailWidth": null,
- "thumbnailHeight": null,
- "order": 0,
- "alternateTitle": "Wardrobe",
- "alternateUrl": "",
- "description": "",
- "metaKeywords": "",
- "metaExtra": "",
- "children": [],
- "assetCount": 62,
- "assetCountCumulative": 62
- }
- ],
- "assetCount": 3378,
- "assetCountCumulative": 3378
- },
- {
- "name": "Military",
- "slug": "military",
- "active": true,
- "thumbnail": null,
- "thumbnailWidth": null,
- "thumbnailHeight": null,
- "order": 0,
- "alternateTitle": "Military",
- "alternateUrl": "",
- "description": "",
- "metaKeywords": "",
- "metaExtra": "",
- "children": [
- {
- "name": "Aircraft",
- "slug": "air",
- "active": true,
- "thumbnail": null,
- "thumbnailWidth": null,
- "thumbnailHeight": null,
- "order": 0,
- "alternateTitle": "Aircraft",
- "alternateUrl": "",
- "description": "",
- "metaKeywords": "",
- "metaExtra": "",
- "children": [],
- "assetCount": 15,
- "assetCountCumulative": 15
- },
- {
- "name": "Vehicles",
- "slug": "ground",
- "active": true,
- "thumbnail": null,
- "thumbnailWidth": null,
- "thumbnailHeight": null,
- "order": 0,
- "alternateTitle": "Vehicles",
- "alternateUrl": "",
- "description": "",
- "metaKeywords": "",
- "metaExtra": "",
- "children": [],
- "assetCount": 4,
- "assetCountCumulative": 4
- },
- {
- "name": "Watercraft",
- "slug": "naval",
- "active": true,
- "thumbnail": null,
- "thumbnailWidth": null,
- "thumbnailHeight": null,
- "order": 0,
- "alternateTitle": "Watercraft",
- "alternateUrl": "",
- "description": "",
- "metaKeywords": "",
- "metaExtra": "",
- "children": [],
- "assetCount": 1,
- "assetCountCumulative": 1
- },
- {
- "name": "Weapon / Armor",
- "slug": "weapon",
- "active": true,
- "thumbnail": null,
- "thumbnailWidth": null,
- "thumbnailHeight": null,
- "order": 0,
- "alternateTitle": "Weapon / Armor",
- "alternateUrl": "",
- "description": "",
- "metaKeywords": "",
- "metaExtra": "",
- "children": [
- {
- "name": "Historic",
- "slug": "historic-military",
- "active": true,
- "thumbnail": null,
- "thumbnailWidth": null,
- "thumbnailHeight": null,
- "order": 0,
- "alternateTitle": "Historic",
- "alternateUrl": "",
- "description": "",
- "metaKeywords": "",
- "metaExtra": "",
- "children": [],
- "assetCount": 67,
- "assetCountCumulative": 67
- },
- {
- "name": "Modern",
- "slug": "equipment",
- "active": true,
- "thumbnail": null,
- "thumbnailWidth": null,
- "thumbnailHeight": null,
- "order": 0,
- "alternateTitle": "Modern",
- "alternateUrl": "",
- "description": "",
- "metaKeywords": "",
- "metaExtra": "",
- "children": [],
- "assetCount": 47,
- "assetCountCumulative": 47
- },
- {
- "name": "Sci-Fi",
- "slug": "military-sci-fi",
- "active": true,
- "thumbnail": null,
- "thumbnailWidth": null,
- "thumbnailHeight": null,
- "order": 0,
- "alternateTitle": "Sci-Fi",
- "alternateUrl": "",
- "description": "",
- "metaKeywords": "",
- "metaExtra": "",
- "children": [],
- "assetCount": 22,
- "assetCountCumulative": 22
- }
- ],
- "assetCount": 144,
- "assetCountCumulative": 144
- }
- ],
- "assetCount": 164,
- "assetCountCumulative": 164
- },
- {
- "name": "Nature",
- "slug": "nature",
- "active": true,
- "thumbnail": null,
- "thumbnailWidth": null,
- "thumbnailHeight": null,
- "order": 0,
- "alternateTitle": "Nature",
- "alternateUrl": "",
- "description": "",
- "metaKeywords": "",
- "metaExtra": "",
- "children": [
- {
- "name": "Atmosphere",
- "slug": "atmosphere",
- "active": true,
- "thumbnail": null,
- "thumbnailWidth": null,
- "thumbnailHeight": null,
- "order": 0,
- "alternateTitle": "Atmosphere",
- "alternateUrl": "",
- "description": "",
- "metaKeywords": "",
- "metaExtra": "",
- "children": [
- {
- "name": "Cloud",
- "slug": "weather",
- "active": true,
- "thumbnail": null,
- "thumbnailWidth": null,
- "thumbnailHeight": null,
- "order": 0,
- "alternateTitle": "Cloud",
- "alternateUrl": "",
- "description": "",
- "metaKeywords": "",
- "metaExtra": "",
- "children": [],
- "assetCount": 3,
- "assetCountCumulative": 3
- },
- {
- "name": "Fog",
- "slug": "fog",
- "active": true,
- "thumbnail": null,
- "thumbnailWidth": null,
- "thumbnailHeight": null,
- "order": 0,
- "alternateTitle": "Fog",
- "alternateUrl": "",
- "description": "",
- "metaKeywords": "",
- "metaExtra": "",
- "children": [],
- "assetCount": 0,
- "assetCountCumulative": 0
- },
- {
- "name": "Smoke / Fire",
- "slug": "smoke-fire",
- "active": true,
- "thumbnail": null,
- "thumbnailWidth": null,
- "thumbnailHeight": null,
- "order": 0,
- "alternateTitle": "Smoke / Fire",
- "alternateUrl": "",
- "description": "",
- "metaKeywords": "",
- "metaExtra": "",
- "children": [],
- "assetCount": 2,
- "assetCountCumulative": 2
- },
- {
- "name": "Wind Setup",
- "slug": "wind-setup",
- "active": true,
- "thumbnail": null,
- "thumbnailWidth": null,
- "thumbnailHeight": null,
- "order": 0,
- "alternateTitle": "Wind Setup",
- "alternateUrl": "",
- "description": "",
- "metaKeywords": "",
- "metaExtra": "",
- "children": [],
- "assetCount": 0,
- "assetCountCumulative": 0
- }
- ],
- "assetCount": 5,
- "assetCountCumulative": 5
- },
- {
- "name": "Grass",
- "slug": "nature-grass",
- "active": true,
- "thumbnail": null,
- "thumbnailWidth": null,
- "thumbnailHeight": null,
- "order": 0,
- "alternateTitle": "Grass",
- "alternateUrl": "",
- "description": "",
- "metaKeywords": "",
- "metaExtra": "",
- "children": [],
- "assetCount": 30,
- "assetCountCumulative": 30
- },
- {
- "name": "Landscape",
- "slug": "landscape-nature",
- "active": true,
- "thumbnail": null,
- "thumbnailWidth": null,
- "thumbnailHeight": null,
- "order": 0,
- "alternateTitle": "Landscape",
- "alternateUrl": "",
- "description": "",
- "metaKeywords": "",
- "metaExtra": "",
- "children": [
- {
- "name": "Environment Elements",
- "slug": "environment-elements",
- "active": true,
- "thumbnail": null,
- "thumbnailWidth": null,
- "thumbnailHeight": null,
- "order": 0,
- "alternateTitle": "Environment Elements",
- "alternateUrl": "",
- "description": "",
- "metaKeywords": "",
- "metaExtra": "",
- "children": [],
- "assetCount": 128,
- "assetCountCumulative": 128
- },
- {
- "name": "Terrain",
- "slug": "landscape",
- "active": true,
- "thumbnail": null,
- "thumbnailWidth": null,
- "thumbnailHeight": null,
- "order": 0,
- "alternateTitle": "Terrain",
- "alternateUrl": "",
- "description": "",
- "metaKeywords": "",
- "metaExtra": "",
- "children": [],
- "assetCount": 26,
- "assetCountCumulative": 26
- }
- ],
- "assetCount": 155,
- "assetCountCumulative": 155
- },
- {
- "name": "Plant",
- "slug": "plant",
- "active": true,
- "thumbnail": null,
- "thumbnailWidth": null,
- "thumbnailHeight": null,
- "order": 0,
- "alternateTitle": "Plant",
- "alternateUrl": "",
- "description": "",
- "metaKeywords": "",
- "metaExtra": "",
- "children": [
- {
- "name": "Bouquet",
- "slug": "bouquet",
- "active": true,
- "thumbnail": null,
- "thumbnailWidth": null,
- "thumbnailHeight": null,
- "order": 0,
- "alternateTitle": "Bouquet",
- "alternateUrl": "",
- "description": "",
- "metaKeywords": "",
- "metaExtra": "",
- "children": [],
- "assetCount": 12,
- "assetCountCumulative": 12
- },
- {
- "name": "Fitowall",
- "slug": "fitowall",
- "active": true,
- "thumbnail": null,
- "thumbnailWidth": null,
- "thumbnailHeight": null,
- "order": 0,
- "alternateTitle": "Fitowall",
- "alternateUrl": "",
- "description": "",
- "metaKeywords": "",
- "metaExtra": "",
- "children": [],
- "assetCount": 4,
- "assetCountCumulative": 4
- },
- {
- "name": "Indoor",
- "slug": "nature-indoor",
- "active": true,
- "thumbnail": null,
- "thumbnailWidth": null,
- "thumbnailHeight": null,
- "order": 0,
- "alternateTitle": "Indoor",
- "alternateUrl": "",
- "description": "",
- "metaKeywords": "",
- "metaExtra": "",
- "children": [],
- "assetCount": 109,
- "assetCountCumulative": 109
- },
- {
- "name": "Outdoor",
- "slug": "nature-outdoor",
- "active": true,
- "thumbnail": null,
- "thumbnailWidth": null,
- "thumbnailHeight": null,
- "order": 0,
- "alternateTitle": "Outdoor",
- "alternateUrl": "",
- "description": "",
- "metaKeywords": "",
- "metaExtra": "",
- "children": [],
- "assetCount": 73,
- "assetCountCumulative": 73
- }
- ],
- "assetCount": 201,
- "assetCountCumulative": 201
- },
- {
- "name": "Tree",
- "slug": "tree",
- "active": true,
- "thumbnail": null,
- "thumbnailWidth": null,
- "thumbnailHeight": null,
- "order": 0,
- "alternateTitle": "Tree",
- "alternateUrl": "",
- "description": "",
- "metaKeywords": "",
- "metaExtra": "",
- "children": [],
- "assetCount": 246,
- "assetCountCumulative": 246
- }
- ],
- "assetCount": 638,
- "assetCountCumulative": 638
- },
- {
- "name": "Science",
- "slug": "science",
- "active": true,
- "thumbnail": null,
- "thumbnailWidth": null,
- "thumbnailHeight": null,
- "order": 0,
- "alternateTitle": "Science",
- "alternateUrl": "",
- "description": "",
- "metaKeywords": "",
- "metaExtra": "",
- "children": [
- {
- "name": "Lab Equipment",
- "slug": "medicine",
- "active": true,
- "thumbnail": null,
- "thumbnailWidth": null,
- "thumbnailHeight": null,
- "order": 0,
- "alternateTitle": "Lab Equipment",
- "alternateUrl": "",
- "description": "",
- "metaKeywords": "",
- "metaExtra": "",
- "children": [],
- "assetCount": 15,
- "assetCountCumulative": 15
- },
- {
- "name": "Medical Equipment",
- "slug": "medical",
- "active": true,
- "thumbnail": null,
- "thumbnailWidth": null,
- "thumbnailHeight": null,
- "order": 0,
- "alternateTitle": "Medical Equipment",
- "alternateUrl": "",
- "description": "",
- "metaKeywords": "",
- "metaExtra": "",
- "children": [],
- "assetCount": 8,
- "assetCountCumulative": 8
- },
- {
- "name": "Microbiology",
- "slug": "microbiology",
- "active": true,
- "thumbnail": null,
- "thumbnailWidth": null,
- "thumbnailHeight": null,
- "order": 0,
- "alternateTitle": "Microbiology",
- "alternateUrl": "",
- "description": "",
- "metaKeywords": "",
- "metaExtra": "",
- "children": [],
- "assetCount": 6,
- "assetCountCumulative": 6
- },
- {
- "name": "Miscellaneous",
- "slug": "science-miscellaneous",
- "active": true,
- "thumbnail": null,
- "thumbnailWidth": null,
- "thumbnailHeight": null,
- "order": 0,
- "alternateTitle": "Miscellaneous",
- "alternateUrl": "",
- "description": "",
- "metaKeywords": "",
- "metaExtra": "",
- "children": [],
- "assetCount": 21,
- "assetCountCumulative": 21
- },
- {
- "name": "Pharmacy",
- "slug": "pharmacy",
- "active": true,
- "thumbnail": null,
- "thumbnailWidth": null,
- "thumbnailHeight": null,
- "order": 0,
- "alternateTitle": "Pharmacy",
- "alternateUrl": "",
- "description": "",
- "metaKeywords": "",
- "metaExtra": "",
- "children": [],
- "assetCount": 10,
- "assetCountCumulative": 10
- }
- ],
- "assetCount": 60,
- "assetCountCumulative": 60
- },
- {
- "name": "Space",
- "slug": "space",
- "active": true,
- "thumbnail": null,
- "thumbnailWidth": null,
- "thumbnailHeight": null,
- "order": 0,
- "alternateTitle": "Space",
- "alternateUrl": "",
- "description": "",
- "metaKeywords": "",
- "metaExtra": "",
- "children": [
- {
- "name": "Miscellaneous",
- "slug": "sci-fi-space",
- "active": true,
- "thumbnail": null,
- "thumbnailWidth": null,
- "thumbnailHeight": null,
- "order": 0,
- "alternateTitle": "Miscellaneous",
- "alternateUrl": "",
- "description": "",
- "metaKeywords": "",
- "metaExtra": "",
- "children": [],
- "assetCount": 6,
- "assetCountCumulative": 6
- },
- {
- "name": "Planet",
- "slug": "planets",
- "active": true,
- "thumbnail": null,
- "thumbnailWidth": null,
- "thumbnailHeight": null,
- "order": 0,
- "alternateTitle": "Planet",
- "alternateUrl": "",
- "description": "",
- "metaKeywords": "",
- "metaExtra": "",
- "children": [],
- "assetCount": 31,
- "assetCountCumulative": 31
- },
- {
- "name": "Satellite",
- "slug": "satellite",
- "active": true,
- "thumbnail": null,
- "thumbnailWidth": null,
- "thumbnailHeight": null,
- "order": 0,
- "alternateTitle": "Satellite",
- "alternateUrl": "",
- "description": "",
- "metaKeywords": "",
- "metaExtra": "",
- "children": [],
- "assetCount": 3,
- "assetCountCumulative": 3
- },
- {
- "name": "Spacecraft",
- "slug": "spacecraft",
- "active": true,
- "thumbnail": null,
- "thumbnailWidth": null,
- "thumbnailHeight": null,
- "order": 0,
- "alternateTitle": "Spacecraft",
- "alternateUrl": "",
- "description": "",
- "metaKeywords": "",
- "metaExtra": "",
- "children": [],
- "assetCount": 23,
- "assetCountCumulative": 23
- },
- {
- "name": "Station",
- "slug": "astronomy",
- "active": true,
- "thumbnail": null,
- "thumbnailWidth": null,
- "thumbnailHeight": null,
- "order": 0,
- "alternateTitle": "Station",
- "alternateUrl": "",
- "description": "",
- "metaKeywords": "",
- "metaExtra": "",
- "children": [],
- "assetCount": 0,
- "assetCountCumulative": 0
- }
- ],
- "assetCount": 63,
- "assetCountCumulative": 63
- },
- {
- "name": "Sport / Hobby",
- "slug": "sports",
- "active": true,
- "thumbnail": null,
- "thumbnailWidth": null,
- "thumbnailHeight": null,
- "order": 0,
- "alternateTitle": "Sport / Hobby",
- "alternateUrl": "",
- "description": "",
- "metaKeywords": "",
- "metaExtra": "",
- "children": [
- {
- "name": "Fishing",
- "slug": "outdoor",
- "active": true,
- "thumbnail": null,
- "thumbnailWidth": null,
- "thumbnailHeight": null,
- "order": 0,
- "alternateTitle": "Fishing",
- "alternateUrl": "",
- "description": "",
- "metaKeywords": "",
- "metaExtra": "",
- "children": [],
- "assetCount": 0,
- "assetCountCumulative": 0
- },
- {
- "name": "Gym",
- "slug": "individual",
- "active": true,
- "thumbnail": null,
- "thumbnailWidth": null,
- "thumbnailHeight": null,
- "order": 0,
- "alternateTitle": "Gym",
- "alternateUrl": "",
- "description": "",
- "metaKeywords": "",
- "metaExtra": "",
- "children": [],
- "assetCount": 43,
- "assetCountCumulative": 43
- },
- {
- "name": "Hobby Accessories",
- "slug": "team",
- "active": true,
- "thumbnail": null,
- "thumbnailWidth": null,
- "thumbnailHeight": null,
- "order": 0,
- "alternateTitle": "Hobby Accessories",
- "alternateUrl": "",
- "description": "",
- "metaKeywords": "",
- "metaExtra": "",
- "children": [],
- "assetCount": 21,
- "assetCountCumulative": 21
- },
- {
- "name": "Miscellaneous",
- "slug": "exercise",
- "active": true,
- "thumbnail": null,
- "thumbnailWidth": null,
- "thumbnailHeight": null,
- "order": 0,
- "alternateTitle": "Miscellaneous",
- "alternateUrl": "",
- "description": "",
- "metaKeywords": "",
- "metaExtra": "",
- "children": [],
- "assetCount": 30,
- "assetCountCumulative": 30
- },
- {
- "name": "Music",
- "slug": "music",
- "active": true,
- "thumbnail": null,
- "thumbnailWidth": null,
- "thumbnailHeight": null,
- "order": 0,
- "alternateTitle": "Music",
- "alternateUrl": "",
- "description": "",
- "metaKeywords": "",
- "metaExtra": "",
- "children": [
- {
- "name": "Accessories",
- "slug": "accessories",
- "active": true,
- "thumbnail": null,
- "thumbnailWidth": null,
- "thumbnailHeight": null,
- "order": 0,
- "alternateTitle": "Accessories",
- "alternateUrl": "",
- "description": "",
- "metaKeywords": "",
- "metaExtra": "",
- "children": [],
- "assetCount": 22,
- "assetCountCumulative": 22
- },
- {
- "name": "Instruments",
- "slug": "instruments",
- "active": true,
- "thumbnail": null,
- "thumbnailWidth": null,
- "thumbnailHeight": null,
- "order": 0,
- "alternateTitle": "Instruments",
- "alternateUrl": "",
- "description": "",
- "metaKeywords": "",
- "metaExtra": "",
- "children": [],
- "assetCount": 36,
- "assetCountCumulative": 36
- },
- {
- "name": "Stage",
- "slug": "stage",
- "active": true,
- "thumbnail": null,
- "thumbnailWidth": null,
- "thumbnailHeight": null,
- "order": 0,
- "alternateTitle": "Stage",
- "alternateUrl": "",
- "description": "",
- "metaKeywords": "",
- "metaExtra": "",
- "children": [],
- "assetCount": 2,
- "assetCountCumulative": 2
- },
- {
- "name": "Studio",
- "slug": "studio",
- "active": true,
- "thumbnail": null,
- "thumbnailWidth": null,
- "thumbnailHeight": null,
- "order": 0,
- "alternateTitle": "Studio",
- "alternateUrl": "",
- "description": "",
- "metaKeywords": "",
- "metaExtra": "",
- "children": [],
- "assetCount": 1,
- "assetCountCumulative": 1
- }
- ],
- "assetCount": 65,
- "assetCountCumulative": 65
- },
- {
- "name": "Sport",
- "slug": "extreme",
- "active": true,
- "thumbnail": null,
- "thumbnailWidth": null,
- "thumbnailHeight": null,
- "order": 0,
- "alternateTitle": "Sport",
- "alternateUrl": "",
- "description": "",
- "metaKeywords": "",
- "metaExtra": "",
- "children": [],
- "assetCount": 50,
- "assetCountCumulative": 50
- }
- ],
- "assetCount": 209,
- "assetCountCumulative": 209
- },
- {
- "name": "Technology",
- "slug": "technology",
- "active": true,
- "thumbnail": null,
- "thumbnailWidth": null,
- "thumbnailHeight": null,
- "order": 0,
- "alternateTitle": "Technology",
- "alternateUrl": "",
- "description": "",
- "metaKeywords": "",
- "metaExtra": "",
- "children": [
- {
- "name": "Audio Devices",
- "slug": "audio",
- "active": true,
- "thumbnail": null,
- "thumbnailWidth": null,
- "thumbnailHeight": null,
- "order": 0,
- "alternateTitle": "Audio Devices",
- "alternateUrl": "",
- "description": "",
- "metaKeywords": "",
- "metaExtra": "",
- "children": [],
- "assetCount": 79,
- "assetCountCumulative": 79
- },
- {
- "name": "Computer",
- "slug": "computer",
- "active": true,
- "thumbnail": null,
- "thumbnailWidth": null,
- "thumbnailHeight": null,
- "order": 0,
- "alternateTitle": "Computer",
- "alternateUrl": "",
- "description": "",
- "metaKeywords": "",
- "metaExtra": "",
- "children": [
- {
- "name": "Components / Hardware",
- "slug": "components-hardware",
- "active": true,
- "thumbnail": null,
- "thumbnailWidth": null,
- "thumbnailHeight": null,
- "order": 0,
- "alternateTitle": "Components / Hardware",
- "alternateUrl": "",
- "description": "",
- "metaKeywords": "",
- "metaExtra": "",
- "children": [],
- "assetCount": 88,
- "assetCountCumulative": 88
- },
- {
- "name": "Desktop",
- "slug": "desktop",
- "active": true,
- "thumbnail": null,
- "thumbnailWidth": null,
- "thumbnailHeight": null,
- "order": 0,
- "alternateTitle": "Desktop",
- "alternateUrl": "",
- "description": "",
- "metaKeywords": "",
- "metaExtra": "",
- "children": [],
- "assetCount": 16,
- "assetCountCumulative": 16
- },
- {
- "name": "Game Console",
- "slug": "game-console",
- "active": true,
- "thumbnail": null,
- "thumbnailWidth": null,
- "thumbnailHeight": null,
- "order": 0,
- "alternateTitle": "Game Console",
- "alternateUrl": "",
- "description": "",
- "metaKeywords": "",
- "metaExtra": "",
- "children": [],
- "assetCount": 15,
- "assetCountCumulative": 15
- },
- {
- "name": "Keyboard",
- "slug": "keyboard",
- "active": true,
- "thumbnail": null,
- "thumbnailWidth": null,
- "thumbnailHeight": null,
- "order": 0,
- "alternateTitle": "Keyboard",
- "alternateUrl": "",
- "description": "",
- "metaKeywords": "",
- "metaExtra": "",
- "children": [],
- "assetCount": 11,
- "assetCountCumulative": 11
- },
- {
- "name": "Laptop",
- "slug": "laptop",
- "active": true,
- "thumbnail": null,
- "thumbnailWidth": null,
- "thumbnailHeight": null,
- "order": 0,
- "alternateTitle": "Laptop",
- "alternateUrl": "",
- "description": "",
- "metaKeywords": "",
- "metaExtra": "",
- "children": [],
- "assetCount": 17,
- "assetCountCumulative": 17
- },
- {
- "name": "Monitor",
- "slug": "monitor",
- "active": true,
- "thumbnail": null,
- "thumbnailWidth": null,
- "thumbnailHeight": null,
- "order": 0,
- "alternateTitle": "Monitor",
- "alternateUrl": "",
- "description": "",
- "metaKeywords": "",
- "metaExtra": "",
- "children": [],
- "assetCount": 12,
- "assetCountCumulative": 12
- },
- {
- "name": "Mouse",
- "slug": "mouse",
- "active": true,
- "thumbnail": null,
- "thumbnailWidth": null,
- "thumbnailHeight": null,
- "order": 0,
- "alternateTitle": "Mouse",
- "alternateUrl": "",
- "description": "",
- "metaKeywords": "",
- "metaExtra": "",
- "children": [],
- "assetCount": 6,
- "assetCountCumulative": 6
- },
- {
- "name": "Peripheral",
- "slug": "peripheral",
- "active": true,
- "thumbnail": null,
- "thumbnailWidth": null,
- "thumbnailHeight": null,
- "order": 0,
- "alternateTitle": "Peripheral",
- "alternateUrl": "",
- "description": "",
- "metaKeywords": "",
- "metaExtra": "",
- "children": [],
- "assetCount": 10,
- "assetCountCumulative": 10
- }
- ],
- "assetCount": 180,
- "assetCountCumulative": 180
- },
- {
- "name": "Devices",
- "slug": "devices",
- "active": true,
- "thumbnail": null,
- "thumbnailWidth": null,
- "thumbnailHeight": null,
- "order": 0,
- "alternateTitle": "Devices",
- "alternateUrl": "",
- "description": "",
- "metaKeywords": "",
- "metaExtra": "",
- "children": [
- {
- "name": "Celullar Phone",
- "slug": "celullar-phone",
- "active": true,
- "thumbnail": null,
- "thumbnailWidth": null,
- "thumbnailHeight": null,
- "order": 0,
- "alternateTitle": "Celullar Phone",
- "alternateUrl": "",
- "description": "",
- "metaKeywords": "",
- "metaExtra": "",
- "children": [],
- "assetCount": 4,
- "assetCountCumulative": 4
- },
- {
- "name": "Corded Phone",
- "slug": "corded-phone",
- "active": true,
- "thumbnail": null,
- "thumbnailWidth": null,
- "thumbnailHeight": null,
- "order": 0,
- "alternateTitle": "Corded Phone",
- "alternateUrl": "",
- "description": "",
- "metaKeywords": "",
- "metaExtra": "",
- "children": [],
- "assetCount": 6,
- "assetCountCumulative": 6
- },
- {
- "name": "Smartphone",
- "slug": "phone",
- "active": true,
- "thumbnail": null,
- "thumbnailWidth": null,
- "thumbnailHeight": null,
- "order": 0,
- "alternateTitle": "Smartphone",
- "alternateUrl": "",
- "description": "",
- "metaKeywords": "",
- "metaExtra": "",
- "children": [],
- "assetCount": 23,
- "assetCountCumulative": 23
- },
- {
- "name": "Smart Watch",
- "slug": "smart-watch",
- "active": true,
- "thumbnail": null,
- "thumbnailWidth": null,
- "thumbnailHeight": null,
- "order": 0,
- "alternateTitle": "Smart Watch",
- "alternateUrl": "",
- "description": "",
- "metaKeywords": "",
- "metaExtra": "",
- "children": [],
- "assetCount": 0,
- "assetCountCumulative": 0
- },
- {
- "name": "Tablet",
- "slug": "tablet",
- "active": true,
- "thumbnail": null,
- "thumbnailWidth": null,
- "thumbnailHeight": null,
- "order": 0,
- "alternateTitle": "Tablet",
- "alternateUrl": "",
- "description": "",
- "metaKeywords": "",
- "metaExtra": "",
- "children": [],
- "assetCount": 6,
- "assetCountCumulative": 6
- }
- ],
- "assetCount": 39,
- "assetCountCumulative": 39
- },
- {
- "name": "Household Appliances",
- "slug": "household-appliances",
- "active": true,
- "thumbnail": null,
- "thumbnailWidth": null,
- "thumbnailHeight": null,
- "order": 0,
- "alternateTitle": "Household Appliances",
- "alternateUrl": "",
- "description": "",
- "metaKeywords": "",
- "metaExtra": "",
- "children": [],
- "assetCount": 50,
- "assetCountCumulative": 50
- },
- {
- "name": "Miscellaneous",
- "slug": "industrial-exterior",
- "active": true,
- "thumbnail": null,
- "thumbnailWidth": null,
- "thumbnailHeight": null,
- "order": 0,
- "alternateTitle": "Miscellaneous",
- "alternateUrl": "",
- "description": "",
- "metaKeywords": "",
- "metaExtra": "",
- "children": [],
- "assetCount": 170,
- "assetCountCumulative": 170
- },
- {
- "name": "Photography",
- "slug": "photography",
- "active": true,
- "thumbnail": null,
- "thumbnailWidth": null,
- "thumbnailHeight": null,
- "order": 0,
- "alternateTitle": "Photography",
- "alternateUrl": "",
- "description": "",
- "metaKeywords": "",
- "metaExtra": "",
- "children": [],
- "assetCount": 20,
- "assetCountCumulative": 20
- },
- {
- "name": "Robotics",
- "slug": "ai",
- "active": true,
- "thumbnail": null,
- "thumbnailWidth": null,
- "thumbnailHeight": null,
- "order": 0,
- "alternateTitle": "Robotics",
- "alternateUrl": "",
- "description": "",
- "metaKeywords": "",
- "metaExtra": "",
- "children": [],
- "assetCount": 47,
- "assetCountCumulative": 47
- },
- {
- "name": "Video devices",
- "slug": "video",
- "active": true,
- "thumbnail": null,
- "thumbnailWidth": null,
- "thumbnailHeight": null,
- "order": 0,
- "alternateTitle": "Video devices",
- "alternateUrl": "",
- "description": "",
- "metaKeywords": "",
- "metaExtra": "",
- "children": [],
- "assetCount": 21,
- "assetCountCumulative": 21
- }
- ],
- "assetCount": 606,
- "assetCountCumulative": 606
- },
- {
- "name": "Transport",
- "slug": "vehicle",
- "active": true,
- "thumbnail": null,
- "thumbnailWidth": null,
- "thumbnailHeight": null,
- "order": 0,
- "alternateTitle": "Transport",
- "alternateUrl": "",
- "description": "",
- "metaKeywords": "",
- "metaExtra": "",
- "children": [
- {
- "name": "Aircraft",
- "slug": "aircraft",
- "active": true,
- "thumbnail": null,
- "thumbnailWidth": null,
- "thumbnailHeight": null,
- "order": 0,
- "alternateTitle": "Aircraft",
- "alternateUrl": "",
- "description": "",
- "metaKeywords": "",
- "metaExtra": "",
- "children": [
- {
- "name": "Accessories / Part",
- "slug": "part-aircraft",
- "active": true,
- "thumbnail": null,
- "thumbnailWidth": null,
- "thumbnailHeight": null,
- "order": 0,
- "alternateTitle": "Accessories / Part",
- "alternateUrl": "",
- "description": "",
- "metaKeywords": "",
- "metaExtra": "",
- "children": [],
- "assetCount": 4,
- "assetCountCumulative": 4
- },
- {
- "name": "Air Baloon",
- "slug": "air-baloon",
- "active": true,
- "thumbnail": null,
- "thumbnailWidth": null,
- "thumbnailHeight": null,
- "order": 0,
- "alternateTitle": "Air Baloon",
- "alternateUrl": "",
- "description": "",
- "metaKeywords": "",
- "metaExtra": "",
- "children": [],
- "assetCount": 0,
- "assetCountCumulative": 0
- },
- {
- "name": "Airplane",
- "slug": "commercial",
- "active": true,
- "thumbnail": null,
- "thumbnailWidth": null,
- "thumbnailHeight": null,
- "order": 0,
- "alternateTitle": "Airplane",
- "alternateUrl": "",
- "description": "",
- "metaKeywords": "",
- "metaExtra": "",
- "children": [],
- "assetCount": 3,
- "assetCountCumulative": 3
- },
- {
- "name": "Drone",
- "slug": "drone",
- "active": true,
- "thumbnail": null,
- "thumbnailWidth": null,
- "thumbnailHeight": null,
- "order": 0,
- "alternateTitle": "Drone",
- "alternateUrl": "",
- "description": "",
- "metaKeywords": "",
- "metaExtra": "",
- "children": [],
- "assetCount": 3,
- "assetCountCumulative": 3
- },
- {
- "name": "Glider",
- "slug": "glider",
- "active": true,
- "thumbnail": null,
- "thumbnailWidth": null,
- "thumbnailHeight": null,
- "order": 0,
- "alternateTitle": "Glider",
- "alternateUrl": "",
- "description": "",
- "metaKeywords": "",
- "metaExtra": "",
- "children": [],
- "assetCount": 0,
- "assetCountCumulative": 0
- },
- {
- "name": "Helicopter",
- "slug": "helicopter",
- "active": true,
- "thumbnail": null,
- "thumbnailWidth": null,
- "thumbnailHeight": null,
- "order": 0,
- "alternateTitle": "Helicopter",
- "alternateUrl": "",
- "description": "",
- "metaKeywords": "",
- "metaExtra": "",
- "children": [],
- "assetCount": 3,
- "assetCountCumulative": 3
- },
- {
- "name": "Historic Plane",
- "slug": "historic-aircraft",
- "active": true,
- "thumbnail": null,
- "thumbnailWidth": null,
- "thumbnailHeight": null,
- "order": 0,
- "alternateTitle": "Historic Plane",
- "alternateUrl": "",
- "description": "",
- "metaKeywords": "",
- "metaExtra": "",
- "children": [],
- "assetCount": 0,
- "assetCountCumulative": 0
- },
- {
- "name": "Private Jet",
- "slug": "private",
- "active": true,
- "thumbnail": null,
- "thumbnailWidth": null,
- "thumbnailHeight": null,
- "order": 0,
- "alternateTitle": "Private Jet",
- "alternateUrl": "",
- "description": "",
- "metaKeywords": "",
- "metaExtra": "",
- "children": [],
- "assetCount": 0,
- "assetCountCumulative": 0
- },
- {
- "name": "Seaplane",
- "slug": "jet",
- "active": true,
- "thumbnail": null,
- "thumbnailWidth": null,
- "thumbnailHeight": null,
- "order": 0,
- "alternateTitle": "Seaplane",
- "alternateUrl": "",
- "description": "",
- "metaKeywords": "",
- "metaExtra": "",
- "children": [],
- "assetCount": 1,
- "assetCountCumulative": 1
- }
- ],
- "assetCount": 16,
- "assetCountCumulative": 16
- },
- {
- "name": "Bicycle",
- "slug": "bicycle",
- "active": true,
- "thumbnail": null,
- "thumbnailWidth": null,
- "thumbnailHeight": null,
- "order": 0,
- "alternateTitle": "Bicycle",
- "alternateUrl": "",
- "description": "",
- "metaKeywords": "",
- "metaExtra": "",
- "children": [],
- "assetCount": 9,
- "assetCountCumulative": 9
- },
- {
- "name": "Car",
- "slug": "car",
- "active": true,
- "thumbnail": null,
- "thumbnailWidth": null,
- "thumbnailHeight": null,
- "order": 0,
- "alternateTitle": "Car",
- "alternateUrl": "",
- "description": "",
- "metaKeywords": "",
- "metaExtra": "",
- "children": [
- {
- "name": "Buggy",
- "slug": "buggy",
- "active": true,
- "thumbnail": null,
- "thumbnailWidth": null,
- "thumbnailHeight": null,
- "order": 0,
- "alternateTitle": "Buggy",
- "alternateUrl": "",
- "description": "",
- "metaKeywords": "",
- "metaExtra": "",
- "children": [],
- "assetCount": 2,
- "assetCountCumulative": 2
- },
- {
- "name": "Concept",
- "slug": "concept",
- "active": true,
- "thumbnail": null,
- "thumbnailWidth": null,
- "thumbnailHeight": null,
- "order": 0,
- "alternateTitle": "Concept",
- "alternateUrl": "",
- "description": "",
- "metaKeywords": "",
- "metaExtra": "",
- "children": [],
- "assetCount": 17,
- "assetCountCumulative": 17
- },
- {
- "name": "Historical",
- "slug": "historic-vehicle",
- "active": true,
- "thumbnail": null,
- "thumbnailWidth": null,
- "thumbnailHeight": null,
- "order": 0,
- "alternateTitle": "Historical",
- "alternateUrl": "",
- "description": "",
- "metaKeywords": "",
- "metaExtra": "",
- "children": [],
- "assetCount": 14,
- "assetCountCumulative": 14
- },
- {
- "name": "Luxury / Supercar",
- "slug": "luxury-supercar",
- "active": true,
- "thumbnail": null,
- "thumbnailWidth": null,
- "thumbnailHeight": null,
- "order": 0,
- "alternateTitle": "Luxury / Supercar",
- "alternateUrl": "",
- "description": "",
- "metaKeywords": "",
- "metaExtra": "",
- "children": [],
- "assetCount": 27,
- "assetCountCumulative": 27
- },
- {
- "name": "Racing",
- "slug": "racing",
- "active": true,
- "thumbnail": null,
- "thumbnailWidth": null,
- "thumbnailHeight": null,
- "order": 0,
- "alternateTitle": "Racing",
- "alternateUrl": "",
- "description": "",
- "metaKeywords": "",
- "metaExtra": "",
- "children": [],
- "assetCount": 2,
- "assetCountCumulative": 2
- },
- {
- "name": "Sci-Fi",
- "slug": "transport-sci-fi",
- "active": true,
- "thumbnail": null,
- "thumbnailWidth": null,
- "thumbnailHeight": null,
- "order": 0,
- "alternateTitle": "Sci-Fi",
- "alternateUrl": "",
- "description": "",
- "metaKeywords": "",
- "metaExtra": "",
- "children": [],
- "assetCount": 4,
- "assetCountCumulative": 4
- },
- {
- "name": "Standard",
- "slug": "standard",
- "active": true,
- "thumbnail": null,
- "thumbnailWidth": null,
- "thumbnailHeight": null,
- "order": 0,
- "alternateTitle": "Standard",
- "alternateUrl": "",
- "description": "",
- "metaKeywords": "",
- "metaExtra": "",
- "children": [],
- "assetCount": 40,
- "assetCountCumulative": 40
- }
- ],
- "assetCount": 112,
- "assetCountCumulative": 112
- },
- {
- "name": "Emergency",
- "slug": "emergency",
- "active": true,
- "thumbnail": null,
- "thumbnailWidth": null,
- "thumbnailHeight": null,
- "order": 0,
- "alternateTitle": "Emergency",
- "alternateUrl": "",
- "description": "",
- "metaKeywords": "",
- "metaExtra": "",
- "children": [
- {
- "name": "Ambulance",
- "slug": "ambulance",
- "active": true,
- "thumbnail": null,
- "thumbnailWidth": null,
- "thumbnailHeight": null,
- "order": 0,
- "alternateTitle": "Ambulance",
- "alternateUrl": "",
- "description": "",
- "metaKeywords": "",
- "metaExtra": "",
- "children": [],
- "assetCount": 0,
- "assetCountCumulative": 0
- },
- {
- "name": "Fire Department",
- "slug": "fire-department",
- "active": true,
- "thumbnail": null,
- "thumbnailWidth": null,
- "thumbnailHeight": null,
- "order": 0,
- "alternateTitle": "Fire Department",
- "alternateUrl": "",
- "description": "",
- "metaKeywords": "",
- "metaExtra": "",
- "children": [],
- "assetCount": 0,
- "assetCountCumulative": 0
- },
- {
- "name": "Police",
- "slug": "transport-police",
- "active": true,
- "thumbnail": null,
- "thumbnailWidth": null,
- "thumbnailHeight": null,
- "order": 0,
- "alternateTitle": "Police",
- "alternateUrl": "",
- "description": "",
- "metaKeywords": "",
- "metaExtra": "",
- "children": [],
- "assetCount": 1,
- "assetCountCumulative": 1
- }
- ],
- "assetCount": 1,
- "assetCountCumulative": 1
- },
- {
- "name": "Heavy Vehicle",
- "slug": "heavy-vehicle",
- "active": true,
- "thumbnail": null,
- "thumbnailWidth": null,
- "thumbnailHeight": null,
- "order": 0,
- "alternateTitle": "Heavy Vehicle",
- "alternateUrl": "",
- "description": "",
- "metaKeywords": "",
- "metaExtra": "",
- "children": [
- {
- "name": "Industrial",
- "slug": "industrial-vehicle",
- "active": true,
- "thumbnail": null,
- "thumbnailWidth": null,
- "thumbnailHeight": null,
- "order": 0,
- "alternateTitle": "Industrial",
- "alternateUrl": "",
- "description": "",
- "metaKeywords": "",
- "metaExtra": "",
- "children": [],
- "assetCount": 4,
- "assetCountCumulative": 4
- },
- {
- "name": "Trailer",
- "slug": "trailer",
- "active": true,
- "thumbnail": null,
- "thumbnailWidth": null,
- "thumbnailHeight": null,
- "order": 0,
- "alternateTitle": "Trailer",
- "alternateUrl": "",
- "description": "",
- "metaKeywords": "",
- "metaExtra": "",
- "children": [],
- "assetCount": 0,
- "assetCountCumulative": 0
- },
- {
- "name": "Truck",
- "slug": "truck",
- "active": true,
- "thumbnail": null,
- "thumbnailWidth": null,
- "thumbnailHeight": null,
- "order": 0,
- "alternateTitle": "Truck",
- "alternateUrl": "",
- "description": "",
- "metaKeywords": "",
- "metaExtra": "",
- "children": [],
- "assetCount": 3,
- "assetCountCumulative": 3
- },
- {
- "name": "Van",
- "slug": "van",
- "active": true,
- "thumbnail": null,
- "thumbnailWidth": null,
- "thumbnailHeight": null,
- "order": 0,
- "alternateTitle": "Van",
- "alternateUrl": "",
- "description": "",
- "metaKeywords": "",
- "metaExtra": "",
- "children": [],
- "assetCount": 2,
- "assetCountCumulative": 2
- }
- ],
- "assetCount": 10,
- "assetCountCumulative": 10
- },
- {
- "name": "Motocycle",
- "slug": "motorcycle",
- "active": true,
- "thumbnail": null,
- "thumbnailWidth": null,
- "thumbnailHeight": null,
- "order": 0,
- "alternateTitle": "Motocycle",
- "alternateUrl": "",
- "description": "",
- "metaKeywords": "",
- "metaExtra": "",
- "children": [
- {
- "name": "Historical",
- "slug": "historical",
- "active": true,
- "thumbnail": null,
- "thumbnailWidth": null,
- "thumbnailHeight": null,
- "order": 0,
- "alternateTitle": "Historical",
- "alternateUrl": "",
- "description": "",
- "metaKeywords": "",
- "metaExtra": "",
- "children": [],
- "assetCount": 0,
- "assetCountCumulative": 0
- },
- {
- "name": "Sport",
- "slug": "sport",
- "active": true,
- "thumbnail": null,
- "thumbnailWidth": null,
- "thumbnailHeight": null,
- "order": 0,
- "alternateTitle": "Sport",
- "alternateUrl": "",
- "description": "",
- "metaKeywords": "",
- "metaExtra": "",
- "children": [],
- "assetCount": 0,
- "assetCountCumulative": 0
- },
- {
- "name": "Standard",
- "slug": "transport-standard",
- "active": true,
- "thumbnail": null,
- "thumbnailWidth": null,
- "thumbnailHeight": null,
- "order": 0,
- "alternateTitle": "Standard",
- "alternateUrl": "",
- "description": "",
- "metaKeywords": "",
- "metaExtra": "",
- "children": [],
- "assetCount": 2,
- "assetCountCumulative": 2
- }
- ],
- "assetCount": 2,
- "assetCountCumulative": 2
- },
- {
- "name": "Public Transport",
- "slug": "public-transport",
- "active": true,
- "thumbnail": null,
- "thumbnailWidth": null,
- "thumbnailHeight": null,
- "order": 0,
- "alternateTitle": "Public Transport",
- "alternateUrl": "",
- "description": "",
- "metaKeywords": "",
- "metaExtra": "",
- "children": [
- {
- "name": "Bus",
- "slug": "bus",
- "active": true,
- "thumbnail": null,
- "thumbnailWidth": null,
- "thumbnailHeight": null,
- "order": 0,
- "alternateTitle": "Bus",
- "alternateUrl": "",
- "description": "",
- "metaKeywords": "",
- "metaExtra": "",
- "children": [],
- "assetCount": 2,
- "assetCountCumulative": 2
- },
- {
- "name": "Taxi",
- "slug": "taxi",
- "active": true,
- "thumbnail": null,
- "thumbnailWidth": null,
- "thumbnailHeight": null,
- "order": 0,
- "alternateTitle": "Taxi",
- "alternateUrl": "",
- "description": "",
- "metaKeywords": "",
- "metaExtra": "",
- "children": [],
- "assetCount": 0,
- "assetCountCumulative": 0
- }
- ],
- "assetCount": 2,
- "assetCountCumulative": 2
- },
- {
- "name": "Railed vehicle",
- "slug": "railed-vehicle",
- "active": true,
- "thumbnail": null,
- "thumbnailWidth": null,
- "thumbnailHeight": null,
- "order": 0,
- "alternateTitle": "Railed vehicle",
- "alternateUrl": "",
- "description": "",
- "metaKeywords": "",
- "metaExtra": "",
- "children": [
- {
- "name": "Cargo",
- "slug": "cargo",
- "active": true,
- "thumbnail": null,
- "thumbnailWidth": null,
- "thumbnailHeight": null,
- "order": 0,
- "alternateTitle": "Cargo",
- "alternateUrl": "",
- "description": "",
- "metaKeywords": "",
- "metaExtra": "",
- "children": [],
- "assetCount": 1,
- "assetCountCumulative": 1
- },
- {
- "name": "Passenger",
- "slug": "train",
- "active": true,
- "thumbnail": null,
- "thumbnailWidth": null,
- "thumbnailHeight": null,
- "order": 0,
- "alternateTitle": "Passenger",
- "alternateUrl": "",
- "description": "",
- "metaKeywords": "",
- "metaExtra": "",
- "children": [],
- "assetCount": 3,
- "assetCountCumulative": 3
- }
- ],
- "assetCount": 5,
- "assetCountCumulative": 5
- },
- {
- "name": "Small Electric Vehicles",
- "slug": "small-electric-vehicles",
- "active": true,
- "thumbnail": null,
- "thumbnailWidth": null,
- "thumbnailHeight": null,
- "order": 0,
- "alternateTitle": "Small Electric Vehicles",
- "alternateUrl": "",
- "description": "",
- "metaKeywords": "",
- "metaExtra": "",
- "children": [],
- "assetCount": 0,
- "assetCountCumulative": 0
- },
- {
- "name": "Vehicle Parts",
- "slug": "part-vehicle",
- "active": true,
- "thumbnail": null,
- "thumbnailWidth": null,
- "thumbnailHeight": null,
- "order": 0,
- "alternateTitle": "Vehicle Parts",
- "alternateUrl": "",
- "description": "",
- "metaKeywords": "",
- "metaExtra": "",
- "children": [],
- "assetCount": 167,
- "assetCountCumulative": 167
- },
- {
- "name": "Watercraft",
- "slug": "watercraft",
- "active": true,
- "thumbnail": null,
- "thumbnailWidth": null,
- "thumbnailHeight": null,
- "order": 0,
- "alternateTitle": "Watercraft",
- "alternateUrl": "",
- "description": "",
- "metaKeywords": "",
- "metaExtra": "",
- "children": [
- {
- "name": "Accessories / Part",
- "slug": "part-watercraft",
- "active": true,
- "thumbnail": null,
- "thumbnailWidth": null,
- "thumbnailHeight": null,
- "order": 0,
- "alternateTitle": "Accessories / Part",
- "alternateUrl": "",
- "description": "",
- "metaKeywords": "",
- "metaExtra": "",
- "children": [],
- "assetCount": 0,
- "assetCountCumulative": 0
- },
- {
- "name": "Boat",
- "slug": "recreational",
- "active": true,
- "thumbnail": null,
- "thumbnailWidth": null,
- "thumbnailHeight": null,
- "order": 0,
- "alternateTitle": "Boat",
- "alternateUrl": "",
- "description": "",
- "metaKeywords": "",
- "metaExtra": "",
- "children": [],
- "assetCount": 4,
- "assetCountCumulative": 4
- },
- {
- "name": "Hovercraft",
- "slug": "hovercraft",
- "active": true,
- "thumbnail": null,
- "thumbnailWidth": null,
- "thumbnailHeight": null,
- "order": 0,
- "alternateTitle": "Hovercraft",
- "alternateUrl": "",
- "description": "",
- "metaKeywords": "",
- "metaExtra": "",
- "children": [],
- "assetCount": 1,
- "assetCountCumulative": 1
- },
- {
- "name": "Ship",
- "slug": "industrial-watercraft",
- "active": true,
- "thumbnail": null,
- "thumbnailWidth": null,
- "thumbnailHeight": null,
- "order": 0,
- "alternateTitle": "Ship",
- "alternateUrl": "",
- "description": "",
- "metaKeywords": "",
- "metaExtra": "",
- "children": [],
- "assetCount": 1,
- "assetCountCumulative": 1
- },
- {
- "name": "Submarine",
- "slug": "historic-watercraft",
- "active": true,
- "thumbnail": null,
- "thumbnailWidth": null,
- "thumbnailHeight": null,
- "order": 0,
- "alternateTitle": "Submarine",
- "alternateUrl": "",
- "description": "",
- "metaKeywords": "",
- "metaExtra": "",
- "children": [],
- "assetCount": 0,
- "assetCountCumulative": 0
- },
- {
- "name": "Yacht",
- "slug": "personal",
- "active": true,
- "thumbnail": null,
- "thumbnailWidth": null,
- "thumbnailHeight": null,
- "order": 0,
- "alternateTitle": "Yacht",
- "alternateUrl": "",
- "description": "",
- "metaKeywords": "",
- "metaExtra": "",
- "children": [],
- "assetCount": 1,
- "assetCountCumulative": 1
- }
- ],
- "assetCount": 7,
- "assetCountCumulative": 7
- }
- ],
- "assetCount": 334,
- "assetCountCumulative": 334
- }
- ],
- "assetCount": 8630,
- "assetCountCumulative": 8630
- },
- {
- "name": "Scene",
- "slug": "scene",
- "active": true,
- "thumbnail": null,
- "thumbnailWidth": null,
- "thumbnailHeight": null,
- "order": 0,
- "alternateTitle": "scene",
- "alternateUrl": "",
- "description": "",
- "metaKeywords": "",
- "metaExtra": "",
- "children": [
- {
- "name": "Indoor",
- "slug": "scene-indoor",
- "active": true,
- "thumbnail": null,
- "thumbnailWidth": null,
- "thumbnailHeight": null,
- "order": 0,
- "alternateTitle": "Indoor",
- "alternateUrl": "",
- "description": "",
- "metaKeywords": "",
- "metaExtra": "",
- "children": [],
- "assetCount": 38,
- "assetCountCumulative": 38
- },
- {
- "name": "Outdoor",
- "slug": "scene-outdoor",
- "active": true,
- "thumbnail": null,
- "thumbnailWidth": null,
- "thumbnailHeight": null,
- "order": 0,
- "alternateTitle": "Outdoor",
- "alternateUrl": "",
- "description": "",
- "metaKeywords": "",
- "metaExtra": "",
- "children": [],
- "assetCount": 8,
- "assetCountCumulative": 8
- },
- {
- "name": "templates",
- "slug": "templates",
- "active": true,
- "thumbnail": null,
- "thumbnailWidth": null,
- "thumbnailHeight": null,
- "order": 0,
- "alternateTitle": "templates",
- "alternateUrl": "",
- "description": "",
- "metaKeywords": "",
- "metaExtra": "",
- "children": [
- {
- "name": "brush templates",
- "slug": "brush-templates",
- "active": true,
- "thumbnail": null,
- "thumbnailWidth": null,
- "thumbnailHeight": null,
- "order": 0,
- "alternateTitle": "brush templates",
- "alternateUrl": "",
- "description": "",
- "metaKeywords": "",
- "metaExtra": "",
- "children": [],
- "assetCount": 4,
- "assetCountCumulative": 4
- }
- ],
- "assetCount": 4,
- "assetCountCumulative": 4
- }
- ],
- "assetCount": 50,
- "assetCountCumulative": 50
- },
- {
- "name": "texture",
- "slug": "texture",
- "active": true,
- "thumbnail": null,
- "thumbnailWidth": null,
- "thumbnailHeight": null,
- "order": 0,
- "alternateTitle": "texture",
- "alternateUrl": "",
- "description": "",
- "metaKeywords": "",
- "metaExtra": "",
- "children": [
- {
- "name": "Animals",
- "slug": "animals",
- "active": true,
- "thumbnail": null,
- "thumbnailWidth": null,
- "thumbnailHeight": null,
- "order": 0,
- "alternateTitle": "Animals",
- "alternateUrl": "",
- "description": "",
- "metaKeywords": "",
- "metaExtra": "",
- "children": [
- {
- "name": "Mammals",
- "slug": "mammals",
- "active": true,
- "thumbnail": null,
- "thumbnailWidth": null,
- "thumbnailHeight": null,
- "order": 0,
- "alternateTitle": "Mammals",
- "alternateUrl": "",
- "description": "",
- "metaKeywords": "",
- "metaExtra": "",
- "children": [],
- "assetCount": 0,
- "assetCountCumulative": 0
- },
- {
- "name": "Plants",
- "slug": "plants",
- "active": true,
- "thumbnail": null,
- "thumbnailWidth": null,
- "thumbnailHeight": null,
- "order": 0,
- "alternateTitle": "Plants",
- "alternateUrl": "",
- "description": "",
- "metaKeywords": "",
- "metaExtra": "",
- "children": [],
- "assetCount": 0,
- "assetCountCumulative": 0
- }
- ],
- "assetCount": 0,
- "assetCountCumulative": 12
- }
- ],
- "assetCount": 0,
- "assetCountCumulative": 0
- }
-] \ No newline at end of file
diff --git a/blenderkit/download.py b/blenderkit/download.py
deleted file mode 100644
index 7fda06a2..00000000
--- a/blenderkit/download.py
+++ /dev/null
@@ -1,1467 +0,0 @@
-# ##### 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, append_link, utils, ui, colors, tasks_queue, rerequests, resolutions, ui_panels, search, reports
-
-import threading
-import time
-import requests
-import shutil, sys, os
-import uuid
-import copy
-import logging
-
-bk_logger = logging.getLogger('blenderkit')
-
-import bpy
-from bpy.props import (
- IntProperty,
- FloatProperty,
- FloatVectorProperty,
- StringProperty,
- EnumProperty,
- BoolProperty,
- PointerProperty,
-)
-from bpy.app.handlers import persistent
-
-download_threads = []
-
-
-def check_missing():
- '''checks for missing files, and possibly starts re-download of these into the scene'''
- s = bpy.context.scene
- # missing libs:
- # TODO: put these into a panel and let the user decide if these should be downloaded.
- missing = []
- for l in bpy.data.libraries:
- fp = l.filepath
- if fp.startswith('//'):
- fp = bpy.path.abspath(fp)
- if not os.path.exists(fp) and l.get('asset_data') is not None:
- missing.append(l)
-
- # print('missing libraries', missing)
-
- for l in missing:
- asset_data = l['asset_data']
-
- downloaded = check_existing(asset_data, resolution=asset_data.get('resolution'))
- if downloaded:
- try:
- l.reload()
- except:
- download(l['asset_data'], redownload=True)
- else:
- download(l['asset_data'], redownload=True)
-
-
-def check_unused():
- '''find assets that have been deleted from scene but their library is still present.'''
- # this is obviously broken. Blender should take care of the extra data automaticlaly
- # first clean up collections
- for c in bpy.data.collections:
- if len(c.all_objects) == 0 and c.get('is_blenderkit_asset'):
- bpy.data.collections.remove(c)
- return;
- used_libs = []
- for ob in bpy.data.objects:
- if ob.instance_collection is not None and ob.instance_collection.library is not None:
- # used_libs[ob.instance_collection.name] = True
- if ob.instance_collection.library not in used_libs:
- used_libs.append(ob.instance_collection.library)
-
- for ps in ob.particle_systems:
- set = ps.settings
- if ps.settings.render_type == 'GROUP' \
- and ps.settings.instance_collection is not None \
- and ps.settings.instance_collection.library not in used_libs:
- used_libs.append(ps.settings.instance_collection)
-
- for l in bpy.data.libraries:
- if l not in used_libs and l.getn('asset_data'):
- print('attempt to remove this library: ', l.filepath)
- # have to unlink all groups, since the file is a 'user' even if the groups aren't used at all...
- for user_id in l.users_id:
- if type(user_id) == bpy.types.Collection:
- bpy.data.collections.remove(user_id)
- l.user_clear()
-
-
-@persistent
-def scene_save(context):
- ''' does cleanup of blenderkit props and sends a message to the server about assets used.'''
- # TODO this can be optimized by merging these 2 functions, since both iterate over all objects.
- if not bpy.app.background:
- check_unused()
- report_usages()
-
-
-@persistent
-def scene_load(context):
- '''restart broken downloads on scene load'''
- t = time.time()
- s = bpy.context.scene
- global download_threads
- download_threads = []
-
- # commenting this out - old restore broken download on scene start. Might come back if downloads get recorded in scene
- # reset_asset_ids = {}
- # reset_obs = {}
- # for ob in bpy.context.scene.collection.objects:
- # if ob.name[:12] == 'downloading ':
- # obn = ob.name
- #
- # asset_data = ob['asset_data']
- #
- # # obn.replace('#', '')
- # # if asset_data['id'] not in reset_asset_ids:
- #
- # if reset_obs.get(asset_data['id']) is None:
- # reset_obs[asset_data['id']] = [obn]
- # reset_asset_ids[asset_data['id']] = asset_data
- # else:
- # reset_obs[asset_data['id']].append(obn)
- # for asset_id in reset_asset_ids:
- # asset_data = reset_asset_ids[asset_id]
- # done = False
- # if check_existing(asset_data, resolution = should be here):
- # for obname in reset_obs[asset_id]:
- # downloader = s.collection.objects[obname]
- # done = try_finished_append(asset_data,
- # model_location=downloader.location,
- # model_rotation=downloader.rotation_euler)
- #
- # if not done:
- # downloading = check_downloading(asset_data)
- # if not downloading:
- # print('redownloading %s' % asset_data['name'])
- # download(asset_data, downloaders=reset_obs[asset_id], delete=True)
-
- # check for group users that have been deleted, remove the groups /files from the file...
- # TODO scenes fixing part... download the assets not present on drive,
- # and erase from scene linked files that aren't used in the scene.
- # print('continue downlaods ', time.time() - t)
- t = time.time()
- check_missing()
- # print('missing check', time.time() - t)
-
-
-def get_scene_id():
- '''gets scene id and possibly also generates a new one'''
- bpy.context.scene['uuid'] = bpy.context.scene.get('uuid', str(uuid.uuid4()))
- return bpy.context.scene['uuid']
-
-
-def report_usages():
- '''report the usage of assets to the server.'''
- mt = time.time()
- user_preferences = bpy.context.preferences.addons['blenderkit'].preferences
- api_key = user_preferences.api_key
- sid = get_scene_id()
- headers = utils.get_headers(api_key)
- url = paths.get_api_url() + paths.BLENDERKIT_REPORT_URL
-
- assets = {}
- asset_obs = []
- scene = bpy.context.scene
- asset_usages = {}
-
- for ob in scene.collection.objects:
- if ob.get('asset_data') != None:
- asset_obs.append(ob)
-
- for ob in asset_obs:
- asset_data = ob['asset_data']
- abid = asset_data['assetBaseId']
-
- if assets.get(abid) is None:
- asset_usages[abid] = {'count': 1}
- assets[abid] = asset_data
- else:
- asset_usages[abid]['count'] += 1
-
- # brushes
- for b in bpy.data.brushes:
- if b.get('asset_data') != None:
- abid = b['asset_data']['assetBaseId']
- asset_usages[abid] = {'count': 1}
- assets[abid] = b['asset_data']
- # materials
- for ob in scene.collection.objects:
- for ms in ob.material_slots:
- m = ms.material
-
- if m is not None and m.get('asset_data') is not None:
-
- abid = m['asset_data']['assetBaseId']
- if assets.get(abid) is None:
- asset_usages[abid] = {'count': 1}
- assets[abid] = m['asset_data']
- else:
- asset_usages[abid]['count'] += 1
-
- assets_list = []
- assets_reported = scene.get('assets reported', {})
-
- new_assets_count = 0
- for k in asset_usages.keys():
- if k not in assets_reported.keys():
- data = asset_usages[k]
- list_item = {
- 'asset': k,
- 'usageCount': data['count'],
- 'proximitySet': data.get('proximity', [])
- }
- assets_list.append(list_item)
- new_assets_count += 1
- if k not in assets_reported.keys():
- assets_reported[k] = True
-
- scene['assets reported'] = assets_reported
-
- if new_assets_count == 0:
- bk_logger.debug('no new assets were added')
- return;
- usage_report = {
- 'scene': sid,
- 'reportType': 'save',
- 'assetusageSet': assets_list
- }
-
- au = scene.get('assets used', {})
- ad = scene.get('assets deleted', {})
-
- ak = assets.keys()
- for k in au.keys():
- if k not in ak:
- ad[k] = au[k]
- else:
- if k in ad:
- ad.pop(k)
-
- # scene['assets used'] = {}
- for k in ak: # rewrite assets used.
- scene['assets used'][k] = assets[k]
-
- ###########check ratings herer too:
- scene['assets rated'] = scene.get('assets rated', {})
- for k in assets.keys():
- scene['assets rated'][k] = scene['assets rated'].get(k, False)
- thread = threading.Thread(target=utils.requests_post_thread, args=(url, usage_report, headers))
- thread.start()
- mt = time.time() - mt
- # print('report generation: ', mt)
-
-
-def udpate_asset_data_in_dicts(asset_data):
- '''
- updates asset data in all relevant dictionaries, after a threaded download task \
- - where the urls were retrieved, and now they can be reused
- Parameters
- ----------
- asset_data - data coming back from thread, thus containing also download urls
- '''
- scene = bpy.context.scene
- scene['assets used'] = scene.get('assets used', {})
- scene['assets used'][asset_data['assetBaseId']] = asset_data.copy()
-
- scene['assets rated'] = scene.get('assets rated', {})
- id = asset_data['assetBaseId']
- scene['assets rated'][id] = scene['assets rated'].get(id, False)
- sr = bpy.context.window_manager['search results']
- if not sr:
- return;
- for i, r in enumerate(sr):
- if r['assetBaseId'] == asset_data['assetBaseId']:
- for f in asset_data['files']:
- if f.get('url'):
- for f1 in r['files']:
- if f1['fileType'] == f['fileType']:
- f1['url'] = f['url']
-
-
-def append_asset(asset_data, **kwargs): # downloaders=[], location=None,
- '''Link asset to the scene.
-
-
- '''
- file_names = paths.get_download_filepaths(asset_data, kwargs['resolution'])
- props = None
- #####
- # how to do particle drop:
- # link the group we are interested in( there are more groups in File!!!! , have to get the correct one!)
- s = bpy.context.scene
- wm = bpy.context.window_manager
- user_preferences = bpy.context.preferences.addons['blenderkit'].preferences
-
- if user_preferences.api_key == '':
- user_preferences.asset_counter += 1
-
- if asset_data['assetType'] == 'scene':
- sprops = wm.blenderkit_scene
-
- scene = append_link.append_scene(file_names[0], link=sprops.append_link == 'LINK', fake_user=False)
- # print('scene appended')
- if scene is not None:
- props = scene.blenderkit
- asset_main = scene
- if sprops.switch_after_append:
- bpy.context.window_manager.windows[0].scene = scene
-
- if asset_data['assetType'] == 'hdr':
- hdr = append_link.load_HDR(file_name=file_names[0], name=asset_data['name'])
- props = hdr.blenderkit
- asset_main = hdr
-
- if asset_data['assetType'] == 'model':
- downloaders = kwargs.get('downloaders')
- sprops = wm.blenderkit_models
- # TODO this is here because combinations of linking objects or appending groups are rather not-usefull
- if sprops.append_method == 'LINK_COLLECTION':
- sprops.append_link = 'LINK'
- sprops.import_as = 'GROUP'
- else:
- sprops.append_link = 'APPEND'
- sprops.import_as = 'INDIVIDUAL'
-
- # copy for override
- al = sprops.append_link
- # set consistency for objects already in scene, otherwise this literally breaks blender :)
- ain, resolution = asset_in_scene(asset_data)
- # this is commented out since it already happens in start_download function.
- # if resolution:
- # kwargs['resolution'] = resolution
- # override based on history
- if ain is not False:
- if ain == 'LINKED':
- al = 'LINK'
- else:
- al = 'APPEND'
- if asset_data['assetType'] == 'model':
- source_parent = get_asset_in_scene(asset_data)
- if source_parent:
- asset_main, new_obs = duplicate_asset(source=source_parent, **kwargs)
- asset_main.location = kwargs['model_location']
- asset_main.rotation_euler = kwargs['model_rotation']
- # this is a case where asset is already in scene and should be duplicated instead.
- # there is a big chance that the duplication wouldn't work perfectly(hidden or unselectable objects)
- # so here we need to check and return if there was success
- # also, if it was successful, no other operations are needed , basically all asset data is already ready from the original asset
- if new_obs:
- # update here assets rated/used because there might be new download urls?
- udpate_asset_data_in_dicts(asset_data)
- bpy.ops.wm.undo_push_context(message='add %s to scene' % asset_data['name'])
-
- return
-
- # first get conditions for append link
- link = al == 'LINK'
- # then append link
- if downloaders:
- for downloader in downloaders:
- # this cares for adding particle systems directly to target mesh, but I had to block it now,
- # because of the sluggishnes of it. Possibly re-enable when it's possible to do this faster?
- if 'particle_plants' in asset_data['tags']:
- append_link.append_particle_system(file_names[-1],
- target_object=kwargs['target_object'],
- rotation=downloader['rotation'],
- link=False,
- name=asset_data['name'])
- return
-
- if link:
- asset_main, new_obs = append_link.link_collection(file_names[-1],
- location=downloader['location'],
- rotation=downloader['rotation'],
- link=link,
- name=asset_data['name'],
- parent=kwargs.get('parent'))
-
- else:
-
- asset_main, new_obs = append_link.append_objects(file_names[-1],
- location=downloader['location'],
- rotation=downloader['rotation'],
- link=link,
- name=asset_data['name'],
- parent=kwargs.get('parent'))
- if asset_main.type == 'EMPTY' and link:
- bmin = asset_data['bbox_min']
- bmax = asset_data['bbox_max']
- size_min = min(1.0, (bmax[0] - bmin[0] + bmax[1] - bmin[1] + bmax[2] - bmin[2]) / 3)
- asset_main.empty_display_size = size_min
-
- elif kwargs.get('model_location') is not None:
- if link:
- asset_main, new_obs = append_link.link_collection(file_names[-1],
- location=kwargs['model_location'],
- rotation=kwargs['model_rotation'],
- link=link,
- name=asset_data['name'],
- parent=kwargs.get('parent'))
- else:
- asset_main, new_obs = append_link.append_objects(file_names[-1],
- location=kwargs['model_location'],
- rotation=kwargs['model_rotation'],
- link=link,
- name=asset_data['name'],
- parent=kwargs.get('parent'))
-
- # scale Empty for assets, so they don't clutter the scene.
- if asset_main.type == 'EMPTY' and link:
- bmin = asset_data['bbox_min']
- bmax = asset_data['bbox_max']
- size_min = min(1.0, (bmax[0] - bmin[0] + bmax[1] - bmin[1] + bmax[2] - bmin[2]) / 3)
- asset_main.empty_display_size = size_min
-
- if link:
- group = asset_main.instance_collection
-
- lib = group.library
- lib['asset_data'] = asset_data
-
- elif asset_data['assetType'] == 'brush':
- # TODO if already in scene, should avoid reappending.
- inscene = False
- for b in bpy.data.brushes:
-
- if b.blenderkit.id == asset_data['id']:
- inscene = True
- brush = b
- break;
- if not inscene:
- brush = append_link.append_brush(file_names[-1], link=False, fake_user=False)
-
- thumbnail_name = asset_data['thumbnail'].split(os.sep)[-1]
- tempdir = paths.get_temp_dir('brush_search')
- thumbpath = os.path.join(tempdir, thumbnail_name)
- asset_thumbs_dir = paths.get_download_dirs('brush')[0]
- asset_thumb_path = os.path.join(asset_thumbs_dir, thumbnail_name)
- shutil.copy(thumbpath, asset_thumb_path)
- brush.icon_filepath = asset_thumb_path
-
- if bpy.context.view_layer.objects.active.mode == 'SCULPT':
- bpy.context.tool_settings.sculpt.brush = brush
- elif bpy.context.view_layer.objects.active.mode == 'TEXTURE_PAINT': # could be just else, but for future possible more types...
- bpy.context.tool_settings.image_paint.brush = brush
- # TODO set brush by by asset data(user can be downloading while switching modes.)
-
- # bpy.context.tool_settings.image_paint.brush = brush
- props = brush.blenderkit
- asset_main = brush
-
- elif asset_data['assetType'] == 'material':
- inscene = False
- sprops = wm.blenderkit_mat
-
- for m in bpy.data.materials:
- if m.blenderkit.id == asset_data['id']:
- inscene = True
- material = m
- break;
- if not inscene:
- link = sprops.append_method == 'LINK'
- material = append_link.append_material(file_names[-1], link=link, fake_user=False)
- target_object = bpy.data.objects[kwargs['target_object']]
-
- if len(target_object.material_slots) == 0:
- target_object.data.materials.append(material)
- else:
- target_object.material_slots[kwargs['material_target_slot']].material = material
-
- asset_main = material
-
- asset_data['resolution'] = kwargs['resolution']
- 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'])
-
-
-def replace_resolution_linked(file_paths, asset_data):
- # replace one asset resolution for another.
- # this is the much simpler case
- # - find the library,
- # - replace the path and name of the library, reload.
- file_name = os.path.basename(file_paths[-1])
-
- for l in bpy.data.libraries:
- if not l.get('asset_data'):
- continue;
- if not l['asset_data']['assetBaseId'] == asset_data['assetBaseId']:
- continue;
-
- bk_logger.debug('try to re-link library')
-
- if not os.path.isfile(file_paths[-1]):
- bk_logger.debug('library file doesnt exist')
- break;
- l.filepath = os.path.join(os.path.dirname(l.filepath), file_name)
- l.name = file_name
- udpate_asset_data_in_dicts(asset_data)
-
-
-def replace_resolution_appended(file_paths, asset_data, resolution):
- # In this case the texture paths need to be replaced.
- # Find the file path pattern that is present in texture paths
- # replace the pattern with the new one.
- file_name = os.path.basename(file_paths[-1])
-
- new_filename_pattern = os.path.splitext(file_name)[0]
- all_patterns = []
- for suff in paths.resolution_suffix.values():
- pattern = f"{asset_data['id']}{os.sep}textures{suff}{os.sep}"
- all_patterns.append(pattern)
- new_pattern = f"{asset_data['id']}{os.sep}textures{paths.resolution_suffix[resolution]}{os.sep}"
-
- # replace the pattern with the new one.
- # print(existing_filename_patterns)
- # print(new_filename_pattern)
- # print('existing images:')
- for i in bpy.data.images:
-
- for old_pattern in all_patterns:
- if i.filepath.find(old_pattern) > -1:
- fp = i.filepath.replace(old_pattern, new_pattern)
- fpabs = bpy.path.abspath(fp)
- if not os.path.exists(fpabs):
- # this currently handles .png's that have been swapped to .jpg's during resolution generation process.
- # should probably also handle .exr's and similar others.
- # bk_logger.debug('need to find a replacement')
- base, ext = os.path.splitext(fp)
- if resolution == 'blend' and i.get('original_extension'):
- fp = base + i.get('original_extension')
- elif ext in ('.png', '.PNG'):
- fp = base + '.jpg'
- i.filepath = fp
- i.filepath_raw = fp # bpy.path.abspath(fp)
- for pf in i.packed_files:
- pf.filepath = fp
- i.reload()
- udpate_asset_data_in_dicts(asset_data)
-
-
-# @bpy.app.handlers.persistent
-def download_timer():
- # TODO might get moved to handle all blenderkit stuff, not to slow down.
- '''
- check for running and finished downloads.
- Running downloads get checked for progress which is passed to UI.
- Finished downloads are processed and linked/appended to scene.
- '''
- global download_threads
- # utils.p('start download timer')
-
- # bk_logger.debug('timer download')
-
- if len(download_threads) == 0:
- # utils.p('end download timer')
-
- return 2
- s = bpy.context.scene
- for threaddata in download_threads:
- t = threaddata[0]
- asset_data = threaddata[1]
- tcom = threaddata[2]
-
- progress_bars = []
- downloaders = []
-
- if t.is_alive(): # set downloader size
- sr = bpy.context.window_manager.get('search results')
- if sr is not None:
- for r in sr:
- if asset_data['id'] == r['id']:
- r['downloaded'] = 0.5 # tcom.progress
- if not t.is_alive():
- if tcom.error:
- sprops = utils.get_search_props()
- sprops.report = tcom.report
- download_threads.remove(threaddata)
- # utils.p('end download timer')
-
- return
- file_paths = paths.get_download_filepaths(asset_data, tcom.passargs['resolution'])
-
- if len(file_paths) == 0:
- bk_logger.debug('library names not found in asset data after download')
- download_threads.remove(threaddata)
- break;
-
- wm = bpy.context.window_manager
-
- at = asset_data['assetType']
- if ((bpy.context.mode == 'OBJECT' and \
- (at == 'model' or at == 'material'))) \
- or ((at == 'brush') \
- and wm.get('appendable') == True) or at == 'scene' or at == 'hdr':
- # don't do this stuff in editmode and other modes, just wait...
- download_threads.remove(threaddata)
-
- # duplicate file if the global and subdir are used in prefs
- if len(file_paths) == 2: # todo this should try to check if both files exist and are ok.
- utils.copy_asset(file_paths[0], file_paths[1])
- # shutil.copyfile(file_paths[0], file_paths[1])
-
- bk_logger.debug('appending asset')
- # progress bars:
-
- # we need to check if mouse isn't down, which means an operator can be running.
- # Especially for sculpt mode, where appending a brush during a sculpt stroke causes crasehes
- #
-
- if tcom.passargs.get('redownload'):
- # handle lost libraries here:
- for l in bpy.data.libraries:
- if l.get('asset_data') is not None and l['asset_data']['id'] == asset_data['id']:
- l.filepath = file_paths[-1]
- l.reload()
-
- if tcom.passargs.get('replace_resolution'):
- # try to relink
- # HDRs are always swapped, so their swapping is handled without the replace_resolution option
-
- ain, resolution = asset_in_scene(asset_data)
-
- if ain == 'LINKED':
- replace_resolution_linked(file_paths, asset_data)
-
-
- elif ain == 'APPENDED':
- replace_resolution_appended(file_paths, asset_data, tcom.passargs['resolution'])
-
-
-
- else:
- done = try_finished_append(asset_data, **tcom.passargs)
- if not done:
- at = asset_data['assetType']
- tcom.passargs['retry_counter'] = tcom.passargs.get('retry_counter', 0) + 1
- download(asset_data, **tcom.passargs)
-
- if bpy.context.window_manager['search results'] is not None and done:
- for sres in bpy.context.window_manager['search results']:
- if asset_data['id'] == sres['id']:
- sres['downloaded'] = 100
-
- bk_logger.debug('finished download thread')
- # utils.p('end download timer')
-
- return .5
-
-
-def delete_unfinished_file(file_name):
- '''
- Deletes download if it wasn't finished. If the folder it's containing is empty, it also removes the directory
- Parameters
- ----------
- file_name
-
- Returns
- -------
- None
- '''
- try:
- os.remove(file_name)
- except Exception as e:
- print(e)
- asset_dir = os.path.dirname(file_name)
- if len(os.listdir(asset_dir)) == 0:
- os.rmdir(asset_dir)
- return
-
-
-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_names = paths.get_download_filepaths(asset_data, resolution) # prefer global dir if possible.
- if len(file_names) == 0:
- return None
-
- file_name = file_names[0]
-
- if check_existing(asset_data, resolution=resolution):
- # 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
-
- 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')
-
- if total_length is None or int(total_length) < 1000: # no content length header
- download_canceled = True
- print(response.content)
- else:
- total_length = int(total_length)
- dl = 0
- last_percent = 0
- percent = 0
- for data in response.iter_content(chunk_size=4096 * 10):
- dl += len(data)
-
- # the exact output you're looking for:
- fs_str = utils.files_size_to_text(total_length)
-
- percent = int(dl * 100 / total_length)
- if percent > last_percent:
- last_percent = percent
- # sys.stdout.write('\r')
- # sys.stdout.write(f'Downloading {asset_data['name']} {fs_str} {percent}% ') # + int(dl * 50 / total_length) * 'x')
- print(
- f'Downloading {asset_data["name"]} {fs_str} {percent}% ') # + int(dl * 50 / total_length) * 'x')
- # sys.stdout.flush()
-
- # print(int(dl*50/total_length)*'x'+'\r')
- f.write(data)
- if download_canceled:
- delete_unfinished_file(file_name)
- return None
-
- return file_name
-
-
-class Downloader(threading.Thread):
- def __init__(self, asset_data, tcom, scene_id, api_key, resolution='blend'):
- super(Downloader, self).__init__()
- self.asset_data = asset_data
- self.tcom = tcom
- self.scene_id = scene_id
- self.api_key = api_key
- self.resolution = resolution
- self._stop_event = threading.Event()
-
- def stop(self):
- self._stop_event.set()
-
- def stopped(self):
- return self._stop_event.is_set()
-
- # def main_download_thread(asset_data, tcom, scene_id, api_key):
- def run(self):
- '''try to download file from blenderkit'''
- # utils.p('start downloader thread')
- asset_data = self.asset_data
- tcom = self.tcom
- scene_id = self.scene_id
- api_key = self.api_key
- tcom.report = 'Looking for asset'
- # TODO get real link here...
- has_url = get_download_url(asset_data, scene_id, api_key, resolution=self.resolution, tcom=tcom)
-
- if not has_url:
- tasks_queue.add_task(
- (reports.add_report, ('Failed to obtain download URL for %s.' % asset_data['name'], 5, colors.RED)))
- return;
- if tcom.error:
- return
- # only now we can check if the file already exists. This should have 2 levels, for materials and for brushes
- # different than for the non free content. delete is here when called after failed append tries.
-
- if check_existing(asset_data, resolution=self.resolution) and not tcom.passargs.get('delete'):
- # this sends the thread for processing, where another check should occur, since the file might be corrupted.
- tcom.downloaded = 100
- bk_logger.debug('not downloading, trying to append again')
- return
-
- file_name = paths.get_download_filepaths(asset_data, self.resolution)[0] # prefer global dir if possible.
- # for k in asset_data:
- # print(asset_data[k])
- if self.stopped():
- bk_logger.debug('stopping download: ' + asset_data['name'])
- return
-
- download_canceled = False
- with open(file_name, "wb") as f:
- bk_logger.debug("Downloading %s" % file_name)
- headers = utils.get_headers(api_key)
- res_file_info, self.resolution = paths.get_res_file(asset_data, self.resolution)
- response = requests.get(res_file_info['url'], stream=True)
- total_length = response.headers.get('Content-Length')
- if total_length is None: # no content length header
- print('no content length')
- print(response.content)
- tcom.report = response.content
- download_canceled = True
- else:
- # bk_logger.debug(total_length)
- if int(total_length) < 1000: # means probably no file returned.
- tasks_queue.add_task((reports.add_report, (response.content, 20, colors.RED)))
-
- tcom.report = response.content
-
- tcom.file_size = int(total_length)
- fsmb = tcom.file_size // (1024 * 1024)
- fskb = tcom.file_size % 1024
- if fsmb == 0:
- t = '%iKB' % fskb
- else:
- t = ' %iMB' % fsmb
-
- tcom.report = f'Downloading {t} {self.resolution}'
-
- dl = 0
- totdata = []
- for data in response.iter_content(chunk_size=4096 * 32): # crashed here... why? investigate:
- dl += len(data)
- tcom.downloaded = dl
- tcom.progress = int(100 * tcom.downloaded / tcom.file_size)
- f.write(data)
- if self.stopped():
- bk_logger.debug('stopping download: ' + asset_data['name'])
- download_canceled = True
- break
-
- if download_canceled:
- delete_unfinished_file(file_name)
- return
- # unpack the file immediately after download
-
- tcom.report = f'Unpacking files'
- self.asset_data['resolution'] = self.resolution
- resolutions.send_to_bg(self.asset_data, file_name, command='unpack')
- # utils.p('end downloader thread')
-
-
-class ThreadCom: # object passed to threads to read background process stdout info
- def __init__(self):
- self.file_size = 1000000000000000 # property that gets written to.
- self.downloaded = 0
- self.lasttext = ''
- self.error = False
- self.report = ''
- self.progress = 0.0
- self.passargs = {}
-
-
-def download(asset_data, **kwargs):
- '''start the download thread'''
- user_preferences = bpy.context.preferences.addons['blenderkit'].preferences
- api_key = user_preferences.api_key
- scene_id = get_scene_id()
-
- tcom = ThreadCom()
- tcom.passargs = kwargs
-
- if kwargs.get('retry_counter', 0) > 3:
- sprops = utils.get_search_props()
- report = f"Maximum retries exceeded for {asset_data['name']}"
- sprops.report = report
- reports.add_report(report, 5, colors.RED)
-
- bk_logger.debug(sprops.report)
- return
-
- # incoming data can be either directly dict from python, or blender id property
- # (recovering failed downloads on reload)
- if type(asset_data) == dict:
- asset_data = copy.deepcopy(asset_data)
- else:
- asset_data = asset_data.to_dict()
- readthread = Downloader(asset_data, tcom, scene_id, api_key, resolution=kwargs['resolution'])
- readthread.start()
-
- global download_threads
- download_threads.append(
- [readthread, asset_data, tcom])
-
-
-def check_downloading(asset_data, **kwargs):
- ''' check if an asset is already downloading, if yes, just make a progress bar with downloader object.'''
- global download_threads
-
- downloading = False
-
- for p in download_threads:
- p_asset_data = p[1]
- if p_asset_data['id'] == asset_data['id']:
- at = asset_data['assetType']
- if at in ('model', 'material'):
- downloader = {'location': kwargs['model_location'],
- 'rotation': kwargs['model_rotation']}
- p[2].passargs['downloaders'].append(downloader)
- downloading = True
-
- return downloading
-
-
-def check_existing(asset_data, resolution='blend', can_return_others=False):
- ''' check if the object exists on the hard drive'''
- fexists = False
-
- if asset_data.get('files') == None:
- # this is because of some very odl files where asset data had no files structure.
- return False
-
- file_names = paths.get_download_filepaths(asset_data, resolution, can_return_others=can_return_others)
-
- bk_logger.debug('check if file already exists' + str(file_names))
- if len(file_names) == 2:
- # TODO this should check also for failed or running downloads.
- # If download is running, assign just the running thread. if download isn't running but the file is wrong size,
- # delete file and restart download (or continue downoad? if possible.)
- if os.path.isfile(file_names[0]): # and not os.path.isfile(file_names[1])
- utils.copy_asset(file_names[0], file_names[1])
- elif not os.path.isfile(file_names[0]) and os.path.isfile(
- file_names[1]): # only in case of changed settings or deleted/moved global dict.
- utils.copy_asset(file_names[1], file_names[0])
-
- if len(file_names) > 0 and os.path.isfile(file_names[0]):
- fexists = True
- return fexists
-
-
-def try_finished_append(asset_data, **kwargs): # location=None, material_target=None):
- ''' try to append asset, if not successfully delete source files.
- This means probably wrong download, so download should restart'''
- file_names = paths.get_download_filepaths(asset_data, kwargs['resolution'])
- done = False
- bk_logger.debug('try to append already existing asset')
- if len(file_names) > 0:
- if os.path.isfile(file_names[-1]):
- kwargs['name'] = asset_data['name']
- try:
- append_asset(asset_data, **kwargs)
- done = True
- except Exception as e:
- # TODO: this should distinguis if the appending failed (wrong file)
- # or something else happened(shouldn't delete the files)
- print(e)
- done = False
- for f in file_names:
- try:
- os.remove(f)
- except Exception as e:
- # e = sys.exc_info()[0]
- print(e)
- pass;
- return done
-
- return done
-
-
-def get_asset_in_scene(asset_data):
- '''tries to find an appended copy of particular asset and duplicate it - so it doesn't have to be appended again.'''
- scene = bpy.context.scene
- for ob in bpy.context.scene.objects:
- ad1 = ob.get('asset_data')
- if not ad1:
- continue
- if ad1.get('assetBaseId') == asset_data['assetBaseId']:
- return ob
- return None
-
-
-def check_all_visible(obs):
- '''checks all objects are visible, so they can be manipulated/copied.'''
- for ob in obs:
- if not ob.visible_get():
- return False
- return True
-
-
-def check_selectible(obs):
- '''checks if all objects can be selected and selects them if possible.
- this isn't only select_hide, but all possible combinations of collections e.t.c. so hard to check otherwise.'''
- for ob in obs:
- ob.select_set(True)
- if not ob.select_get():
- return False
- return True
-
-
-def duplicate_asset(source, **kwargs):
- '''
- Duplicate asset when it's already appended in the scene,
- so that blender's append doesn't create duplicated data.
- '''
- bk_logger.debug('duplicate asset instead')
- # we need to save selection
- sel = utils.selection_get()
- bpy.ops.object.select_all(action='DESELECT')
-
- # check visibility
- obs = utils.get_hierarchy(source)
- if not check_all_visible(obs):
- return None
- # check selectability and select in one run
- if not check_selectible(obs):
- return None
-
- # duplicate the asset objects
- bpy.ops.object.duplicate(linked=True)
-
- nobs = bpy.context.selected_objects[:]
- # get asset main object
- for ob in nobs:
- if ob.parent not in nobs:
- asset_main = ob
- break
-
- # in case of replacement,there might be a paarent relationship that can be restored
- if kwargs.get('parent'):
- parent = bpy.data.objects[kwargs['parent']]
- asset_main.parent = parent # even if parent is None this is ok without if condition
- else:
- asset_main.parent = None
- # restore original selection
- utils.selection_set(sel)
- return asset_main, nobs
-
-
-def asset_in_scene(asset_data):
- '''checks if the asset is already in scene. If yes, modifies asset data so the asset can be reached again.'''
- scene = bpy.context.scene
- au = scene.get('assets used', {})
-
- id = asset_data['assetBaseId']
- if id in au.keys():
- ad = au[id]
- if ad.get('files'):
- for fi in ad['files']:
- if fi.get('file_name') != None:
-
- for fi1 in asset_data['files']:
- if fi['fileType'] == fi1['fileType']:
- fi1['file_name'] = fi['file_name']
- fi1['url'] = fi['url']
-
- # browse all collections since linked collections can have same name.
- if asset_data['assetType'] == 'MODEL':
- for c in bpy.data.collections:
- if c.name == ad['name']:
- # there can also be more linked collections with same name, we need to check id.
- if c.library and c.library.get('asset_data') and c.library['asset_data'][
- 'assetBaseId'] == id:
- print('asset linked')
- return 'LINKED', ad.get('resolution')
- elif asset_data['assetType'] == 'MATERIAL':
- for m in bpy.data.materials:
- if not m.get('asset_data'):
- continue
- if m['asset_data']['assetBaseId'] == asset_data[
- 'assetBaseId'] and bpy.context.active_object.active_material.library:
- return 'LINKED', ad.get('resolution')
-
- print('asset appended')
- return 'APPENDED', ad.get('resolution')
- return False, None
-
-
-def fprint(text):
- print('###################################################################################')
- print('\n\n\n')
- print(text)
- print('\n\n\n')
- print('###################################################################################')
-
-
-def get_download_url(asset_data, scene_id, api_key, tcom=None, resolution='blend'):
- ''''retrieves the download url. The server checks if user can download the item.'''
- mt = time.time()
- utils.pprint('getting download url')
-
- headers = utils.get_headers(api_key)
-
- data = {
- 'scene_uuid': scene_id
- }
- r = None
-
- res_file_info, resolution = paths.get_res_file(asset_data, resolution)
-
- try:
- r = rerequests.get(res_file_info['downloadUrl'], params=data, headers=headers)
- except Exception as e:
- print(e)
- if tcom is not None:
- tcom.error = True
- if r == None:
- if tcom is not None:
- tcom.report = 'Connection Error'
- tcom.error = True
- return 'Connection Error'
-
- if r.status_code < 400:
- data = r.json()
- url = data['filePath']
-
- res_file_info['url'] = url
- res_file_info['file_name'] = paths.extract_filename_from_url(url)
-
- # print(res_file_info, url)
- print(url)
- return True
-
- # let's print it into UI
- tasks_queue.add_task((reports.add_report, (str(r), 10, colors.RED)))
-
- if r.status_code == 403:
- 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((reports.add_report, (r1, 5, colors.RED)))
- if tcom is not None:
- tcom.report = report
- tcom.error = True
-
- if r.status_code == 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 = report
- tcom.error = True
-
- elif r.status_code >= 500:
- # bk_logger.debug(r.text)
- if tcom is not None:
- tcom.report = 'Server error'
- tcom.error = True
- return False
-
-
-def start_download(asset_data, **kwargs):
- '''
- check if file isn't downloading or doesn't exist, then start new download
- '''
- # first check if the asset is already in scene. We can use that asset without checking with server
- ain, resolution = asset_in_scene(asset_data)
- # quota_ok = ain is not False
-
- # if resolution:
- # kwargs['resolution'] = resolution
- # otherwise, check on server
-
- s = bpy.context.scene
- done = False
- # is the asseet being currently downloaded?
- downloading = check_downloading(asset_data, **kwargs)
- if not downloading:
- # check if there are files already. This check happens 2x once here(for free assets),
- # once in thread(for non-free)
- fexists = check_existing(asset_data, resolution=kwargs['resolution'])
- bk_logger.debug('does file exist?' + str(fexists))
- bk_logger.debug('asset is in scene' + str(ain))
- if ain and not kwargs.get('replace_resolution'):
- # this goes to appending asset - where it should duplicate the original asset already in scene.
- done = try_finished_append(asset_data, **kwargs)
- # else:
- # props = utils.get_search_props()
- # props.report = str('asset ')
- if not done:
- at = asset_data['assetType']
- if at in ('model', 'material'):
- downloader = {'location': kwargs['model_location'],
- 'rotation': kwargs['model_rotation']}
- download(asset_data, downloaders=[downloader], **kwargs)
-
- else:
- download(asset_data, **kwargs)
-
-
-asset_types = (
- ('MODEL', 'Model', 'set of objects'),
- ('SCENE', 'Scene', 'scene'),
- ('HDR', 'Hdr', 'hdr'),
- ('MATERIAL', 'Material', 'any .blend Material'),
- ('TEXTURE', 'Texture', 'a texture, or texture set'),
- ('BRUSH', 'Brush', 'brush, can be any type of blender brush'),
- ('ADDON', 'Addon', 'addnon'),
-)
-
-
-class BlenderkitKillDownloadOperator(bpy.types.Operator):
- """Kill a download"""
- bl_idname = "scene.blenderkit_download_kill"
- bl_label = "BlenderKit Kill Asset Download"
- bl_options = {'REGISTER', 'INTERNAL'}
-
- thread_index: IntProperty(name="Thread index", description='index of the thread to kill', default=-1)
-
- def execute(self, context):
- global download_threads
- td = download_threads[self.thread_index]
- download_threads.remove(td)
- td[0].stop()
- return {'FINISHED'}
-
-
-def available_resolutions_callback(self, context):
- '''
- Returns
- checks active asset for available resolutions and offers only those available
- TODO: this currently returns always the same list of resolutions, make it actually work
- '''
- # print('callback called', self.asset_data)
- pat_items = (
- ('512', '512', '', 1),
- ('1024', '1024', '', 2),
- ('2048', '2048', '', 3),
- ('4096', '4096', '', 4),
- ('8192', '8192', '', 5),
- )
- items = []
- for item in pat_items:
- if int(self.max_resolution) >= int(item[0]):
- items.append(item)
- items.append(('ORIGINAL', 'Original', '', 6))
- return items
-
-
-def show_enum_values(obj, prop_name):
- print([item.identifier for item in obj.bl_rna.properties[prop_name].enum_items])
-
-
-class BlenderkitDownloadOperator(bpy.types.Operator):
- """Download and link asset to scene. Only link if asset already available locally"""
- bl_idname = "scene.blenderkit_download"
- bl_label = "Download"
- bl_options = {'REGISTER', 'UNDO', 'INTERNAL'}
-
- # asset_type: EnumProperty(
- # name="Type",
- # items=asset_types,
- # description="Type of download",
- # default="MODEL",
- # )
- asset_index: IntProperty(name="Asset Index", description='asset index in search results', default=-1)
-
- asset_base_id: StringProperty(
- name="Asset base Id",
- description="Asset base id, used instead of search result index",
- default="")
-
- target_object: StringProperty(
- name="Target Object",
- description="Material or object target for replacement",
- default="")
-
- material_target_slot: IntProperty(name="Asset Index", description='asset index in search results', default=0)
- model_location: FloatVectorProperty(name='Asset Location', default=(0, 0, 0))
- model_rotation: FloatVectorProperty(name='Asset Rotation', default=(0, 0, 0))
-
- replace: BoolProperty(name='Replace', description='replace selection with the asset', default=False)
-
- replace_resolution: BoolProperty(name='Replace resolution', description='replace resolution of the active asset',
- default=False)
-
- invoke_resolution: BoolProperty(name='Replace resolution popup',
- description='pop up to ask which resolution to download', default=False)
- invoke_scene_settings: BoolProperty(name='Scene import settings popup',
- description='pop up scene import settings', default=False)
-
- resolution: EnumProperty(
- items=available_resolutions_callback,
- default=512,
- description='Replace resolution'
- )
-
- # needs to be passed to the operator to not show all resolution possibilities
- max_resolution: IntProperty(
- name="Max resolution",
- description="",
- default=0)
- # has_res_0_5k: BoolProperty(name='512',
- # description='', default=False)
-
- cast_parent: StringProperty(
- name="Particles Target Object",
- description="",
- default="")
-
- # close_window: BoolProperty(name='Close window',
- # description='Try to close the window below mouse before download',
- # default=False)
- # @classmethod
- # def poll(cls, context):
- # return bpy.context.window_manager.BlenderKitModelThumbnails is not ''
- tooltip: bpy.props.StringProperty(
- default='Download and link asset to scene. Only link if asset already available locally')
-
- @classmethod
- def description(cls, context, properties):
- return properties.tooltip
-
- def get_asset_data(self, context):
- # get asset data - it can come from scene, or from search results.
- s = bpy.context.scene
-
- if self.asset_index > -1:
- # either get the data from search results
- sr = bpy.context.window_manager['search results']
- asset_data = sr[
- self.asset_index].to_dict() # TODO CHECK ALL OCCURRENCES OF PASSING BLENDER ID PROPS TO THREADS!
- asset_base_id = asset_data['assetBaseId']
- else:
- # or from the scene.
- asset_base_id = self.asset_base_id
-
- au = s.get('assets used')
- if au == None:
- s['assets used'] = {}
- if asset_base_id in s.get('assets used'):
- # already used assets have already download link and especially file link.
- asset_data = s['assets used'][asset_base_id].to_dict()
- else:
- # when not in scene nor in search results, we need to get it from the server
- params = {
- 'asset_base_id': self.asset_base_id
- }
- preferences = bpy.context.preferences.addons['blenderkit'].preferences
-
- results = search.get_search_simple(params, page_size=1, max_results=1,
- api_key=preferences.api_key)
- asset_data = search.parse_result(results[0])
-
- return asset_data
-
- def execute(self, context):
- sprops = utils.get_search_props()
-
- self.asset_data = self.get_asset_data(context)
-
- # print('after getting asset data')
- # print(self.asset_base_id)
-
- atype = self.asset_data['assetType']
- if bpy.context.mode != 'OBJECT' and (
- atype == 'model' or atype == 'material') and bpy.context.view_layer.objects.active is not None:
- bpy.ops.object.mode_set(mode='OBJECT')
-
- if self.resolution == 0 or self.resolution == '':
- resolution = sprops.resolution
- else:
- resolution = self.resolution
-
- resolution = resolutions.resolution_props_to_server[resolution]
- if self.replace: # cleanup first, assign later.
- obs = utils.get_selected_replace_adepts()
- # print(obs)
- for ob in obs:
- # print('replace attempt ', ob.name)
- if self.asset_base_id != '':
- # this is for a case when replace is called from a panel,
- # this uses active object as replacement source instead of target.
- if ob.get('asset_data') is not None and \
- (ob['asset_data']['assetBaseId'] == self.asset_base_id and ob['asset_data'][
- 'resolution'] == resolution):
- # print('skipping this one')
- continue;
- parent = ob.parent
- if parent:
- parent = ob.parent.name # after this, parent is either name or None.
-
- kwargs = {
- 'cast_parent': self.cast_parent,
- 'target_object': ob.name,
- 'material_target_slot': ob.active_material_index,
- 'model_location': tuple(ob.matrix_world.translation),
- 'model_rotation': tuple(ob.matrix_world.to_euler()),
- 'replace': True,
- 'replace_resolution': False,
- 'parent': parent,
- 'resolution': resolution
- }
- # TODO - move this After download, not before, so that the replacement
- utils.delete_hierarchy(ob)
- start_download(self.asset_data, **kwargs)
- else:
- # replace resolution needs to replace all instances of the resolution in the scene
- # and deleting originals has to be thus done after the downlaod
-
- kwargs = {
- 'cast_parent': self.cast_parent,
- 'target_object': self.target_object,
- 'material_target_slot': self.material_target_slot,
- 'model_location': tuple(self.model_location),
- 'model_rotation': tuple(self.model_rotation),
- 'replace': False,
- 'replace_resolution': self.replace_resolution,
- 'resolution': resolution
- }
-
- start_download(self.asset_data, **kwargs)
- return {'FINISHED'}
-
- def draw(self, context):
- layout = self.layout
- if self.invoke_resolution:
- layout.prop(self, 'resolution', expand=True, icon_only=False)
- if self.invoke_scene_settings:
- ui_panels.draw_scene_import_settings(self, context)
-
- def invoke(self, context, event):
- # if self.close_window:
- # context.window.cursor_warp(event.mouse_x-1000, event.mouse_y - 1000);
-
- # print(self.asset_base_id)
- wm = context.window_manager
- # only make a pop up in case of switching resolutions
- if self.invoke_resolution:
- # show_enum_values(self, 'resolution')
- self.asset_data = self.get_asset_data(context)
- sprops = utils.get_search_props()
-
- # set initial resolutions enum activation
- if sprops.resolution != 'ORIGINAL' and int(sprops.resolution) <= int(self.max_resolution):
- self.resolution = sprops.resolution
- elif int(self.max_resolution) > 0:
- self.resolution = str(self.max_resolution)
- else:
- self.resolution = 'ORIGINAL'
- return wm.invoke_props_dialog(self)
-
- if self.invoke_scene_settings:
- return wm.invoke_props_dialog(self)
- # if self.close_window:
- # time.sleep(0.1)
- # context.area.tag_redraw()
- # time.sleep(0.1)
- #
- # context.window.cursor_warp(event.mouse_x, event.mouse_y);
-
- return self.execute(context)
-
-
-def register_download():
- bpy.utils.register_class(BlenderkitDownloadOperator)
- bpy.utils.register_class(BlenderkitKillDownloadOperator)
- bpy.app.handlers.load_post.append(scene_load)
- bpy.app.handlers.save_pre.append(scene_save)
- user_preferences = bpy.context.preferences.addons['blenderkit'].preferences
- if user_preferences.use_timers and not bpy.app.background:
- bpy.app.timers.register(download_timer)
-
-
-def unregister_download():
- bpy.utils.unregister_class(BlenderkitDownloadOperator)
- bpy.utils.unregister_class(BlenderkitKillDownloadOperator)
- bpy.app.handlers.load_post.remove(scene_load)
- bpy.app.handlers.save_pre.remove(scene_save)
- if bpy.app.timers.is_registered(download_timer):
- bpy.app.timers.unregister(download_timer)
diff --git a/blenderkit/icons.py b/blenderkit/icons.py
deleted file mode 100644
index 379062ed..00000000
--- a/blenderkit/icons.py
+++ /dev/null
@@ -1,78 +0,0 @@
-# ##### 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 #####
-
-
-import os
-import bpy
-
-# We can store multiple preview collections here,
-# however in this example we only store "main"
-icon_collections = {}
-
-icons_read = {
- 'fp.png': 'free',
- 'flp.png': 'full',
- 'trophy.png': 'trophy',
- 'dumbbell.png': 'dumbbell',
- 'cc0.png': 'cc0',
- 'royalty_free.png': 'royalty_free',
- 'filter.png': 'filter',
- 'filter_active.png': 'filter_active',
- 'bell.png': 'bell',
-}
-
-verification_icons = {
- 'vs_ready.png':'ready',
- 'vs_deleted.png':'deleted' ,
- 'vs_uploaded.png': 'uploaded',
- 'vs_uploading.png': 'uploading',
- 'vs_on_hold.png': 'on_hold',
- 'vs_validated.png': 'validated',
- 'vs_rejected.png': 'rejected'
-
-}
-
-icons_read.update(verification_icons)
-
-def register_icons():
- # Note that preview collections returned by bpy.utils.previews
- # are regular py objects - you can use them to store custom data.
- import bpy.utils.previews
- pcoll = bpy.utils.previews.new()
-
- # path to the folder where the icon is
- # the path is calculated relative to this py file inside the addon folder
- icons_dir = os.path.join(os.path.dirname(__file__), "thumbnails")
-
- # load a preview thumbnail of a file and store in the previews collection
- for ir in icons_read.keys():
- pcoll.load(icons_read[ir], os.path.join(icons_dir, ir), 'IMAGE')
-
- # iprev = pcoll.new(icons_read[ir])
- # img = bpy.data.images.load(os.path.join(icons_dir, ir))
- # iprev.image_size = (img.size[0], img.size[1])
- # iprev.image_pixels_float = img.pixels[:]
-
- icon_collections["main"] = pcoll
- icon_collections["previews"] = bpy.utils.previews.new()
-
-
-def unregister_icons():
- for pcoll in icon_collections.values():
- bpy.utils.previews.remove(pcoll)
- icon_collections.clear()
diff --git a/blenderkit/image_utils.py b/blenderkit/image_utils.py
deleted file mode 100644
index b17878c3..00000000
--- a/blenderkit/image_utils.py
+++ /dev/null
@@ -1,505 +0,0 @@
-import bpy
-import os
-import time
-
-
-def get_orig_render_settings():
- rs = bpy.context.scene.render
- ims = rs.image_settings
-
- vs = bpy.context.scene.view_settings
-
- orig_settings = {
- 'file_format': ims.file_format,
- 'quality': ims.quality,
- 'color_mode': ims.color_mode,
- 'compression': ims.compression,
- 'exr_codec': ims.exr_codec,
- 'view_transform': vs.view_transform
- }
- return orig_settings
-
-
-def set_orig_render_settings(orig_settings):
- rs = bpy.context.scene.render
- ims = rs.image_settings
- vs = bpy.context.scene.view_settings
-
- ims.file_format = orig_settings['file_format']
- ims.quality = orig_settings['quality']
- ims.color_mode = orig_settings['color_mode']
- ims.compression = orig_settings['compression']
- ims.exr_codec = orig_settings['exr_codec']
-
- vs.view_transform = orig_settings['view_transform']
-
-
-def img_save_as(img, filepath='//', file_format='JPEG', quality=90, color_mode='RGB', compression=15,
- view_transform='Raw', exr_codec='DWAA'):
- '''Uses Blender 'save render' to save images - BLender isn't really able so save images with other methods correctly.'''
-
- ors = get_orig_render_settings()
-
- rs = bpy.context.scene.render
- vs = bpy.context.scene.view_settings
-
- ims = rs.image_settings
- ims.file_format = file_format
- ims.quality = quality
- ims.color_mode = color_mode
- ims.compression = compression
- ims.exr_codec = exr_codec
- vs.view_transform = view_transform
-
- img.save_render(filepath=bpy.path.abspath(filepath), scene=bpy.context.scene)
-
- set_orig_render_settings(ors)
-
-
-def set_colorspace(img, colorspace):
- '''sets image colorspace, but does so in a try statement, because some people might actually replace the default
- colorspace settings, and it literally can't be guessed what these people use, even if it will mostly be the filmic addon.
- '''
- try:
- if colorspace == 'Non-Color':
- img.colorspace_settings.is_data = True
- else:
- img.colorspace_settings.name = colorspace
- except:
- print(f'Colorspace {colorspace} not found.')
-
-def analyze_image_is_true_hdr(image):
- import numpy
- scene = bpy.context.scene
- ui_props = bpy.context.window_manager.blenderkitUI
- size = image.size
- imageWidth = size[0]
- imageHeight = size[1]
- tempBuffer = numpy.empty(imageWidth * imageHeight * 4, dtype=numpy.float32)
- image.pixels.foreach_get(tempBuffer)
- image.blenderkit.true_hdr = numpy.amax(tempBuffer) > 1.05
-
-def generate_hdr_thumbnail():
- import numpy
- scene = bpy.context.scene
- ui_props = bpy.context.window_manager.blenderkitUI
- hdr_image = ui_props.hdr_upload_image # bpy.data.images.get(ui_props.hdr_upload_image)
-
- base, ext = os.path.splitext(hdr_image.filepath)
- thumb_path = base + '.jpg'
- thumb_name = os.path.basename(thumb_path)
-
- max_thumbnail_size = 2048
- size = hdr_image.size
- ratio = size[0] / size[1]
-
- imageWidth = size[0]
- imageHeight = size[1]
- thumbnailWidth = min(size[0], max_thumbnail_size)
- thumbnailHeight = min(size[1], int(max_thumbnail_size / ratio))
-
- tempBuffer = numpy.empty(imageWidth * imageHeight * 4, dtype=numpy.float32)
- inew = bpy.data.images.new(thumb_name, imageWidth, imageHeight, alpha=False, float_buffer=False)
-
- hdr_image.pixels.foreach_get(tempBuffer)
-
- hdr_image.blenderkit.true_hdr = numpy.amax(tempBuffer) > 1.05
-
- inew.filepath = thumb_path
- set_colorspace(inew, 'Linear')
- inew.pixels.foreach_set(tempBuffer)
-
- bpy.context.view_layer.update()
- if thumbnailWidth < imageWidth:
- inew.scale(thumbnailWidth, thumbnailHeight)
-
- img_save_as(inew, filepath=inew.filepath)
-
-
-def find_color_mode(image):
- if not isinstance(image, bpy.types.Image):
- raise (TypeError)
- else:
- depth_mapping = {
- 8: 'BW',
- 24: 'RGB',
- 32: 'RGBA', # can also be bw.. but image.channels doesn't work.
- 96: 'RGB',
- 128: 'RGBA',
- }
- return depth_mapping.get(image.depth, 'RGB')
-
-
-def find_image_depth(image):
- if not isinstance(image, bpy.types.Image):
- raise (TypeError)
- else:
- depth_mapping = {
- 8: '8',
- 24: '8',
- 32: '8', # can also be bw.. but image.channels doesn't work.
- 96: '16',
- 128: '16',
- }
- return depth_mapping.get(image.depth, '8')
-
-
-def can_erase_alpha(na):
- alpha = na[3::4]
- alpha_sum = alpha.sum()
- if alpha_sum == alpha.size:
- print('image can have alpha erased')
- # print(alpha_sum, alpha.size)
- return alpha_sum == alpha.size
-
-
-def is_image_black(na):
- r = na[::4]
- g = na[1::4]
- b = na[2::4]
-
- rgbsum = r.sum() + g.sum() + b.sum()
-
- # print('rgb sum', rgbsum, r.sum(), g.sum(), b.sum())
- if rgbsum == 0:
- print('image can have alpha channel dropped')
- return rgbsum == 0
-
-
-def is_image_bw(na):
- r = na[::4]
- g = na[1::4]
- b = na[2::4]
-
- rg_equal = r == g
- gb_equal = g == b
- rgbequal = rg_equal.all() and gb_equal.all()
- if rgbequal:
- print('image is black and white, can have channels reduced')
-
- return rgbequal
-
-
-def numpytoimage(a, iname, width=0, height=0, channels=3):
- t = time.time()
- foundimage = False
-
- for image in bpy.data.images:
-
- if image.name[:len(iname)] == iname and image.size[0] == a.shape[0] and image.size[1] == a.shape[1]:
- i = image
- foundimage = True
- if not foundimage:
- if channels == 4:
- bpy.ops.image.new(name=iname, width=width, height=height, color=(0, 0, 0, 1), alpha=True,
- generated_type='BLANK', float=True)
- if channels == 3:
- bpy.ops.image.new(name=iname, width=width, height=height, color=(0, 0, 0), alpha=False,
- generated_type='BLANK', float=True)
-
- i = None
-
- for image in bpy.data.images:
- # print(image.name[:len(iname)],iname, image.size[0],a.shape[0],image.size[1],a.shape[1])
- if image.name[:len(iname)] == iname and image.size[0] == width and image.size[1] == height:
- i = image
- if i is None:
- i = bpy.data.images.new(iname, width, height, alpha=False, float_buffer=False, stereo3d=False, is_data=False,
- tiled=False)
-
- # dropping this re-shaping code - just doing flat array for speed and simplicity
- # d = a.shape[0] * a.shape[1]
- # a = a.swapaxes(0, 1)
- # a = a.reshape(d)
- # a = a.repeat(channels)
- # a[3::4] = 1
- i.pixels.foreach_set(a) # this gives big speedup!
- print('\ntime ' + str(time.time() - t))
- return i
-
-
-def imagetonumpy_flat(i):
- t = time.time()
-
- import numpy
-
- width = i.size[0]
- height = i.size[1]
- # print(i.channels)
-
- size = width * height * i.channels
- na = numpy.empty(size, numpy.float32)
- i.pixels.foreach_get(na)
-
- # dropping this re-shaping code - just doing flat array for speed and simplicity
- # na = na[::4]
- # na = na.reshape(height, width, i.channels)
- # na = na.swapaxnes(0, 1)
-
- # print('\ntime of image to numpy ' + str(time.time() - t))
- return na
-
-
-def imagetonumpy(i):
- t = time.time()
-
- import numpy as np
-
- width = i.size[0]
- height = i.size[1]
- # print(i.channels)
-
- size = width * height * i.channels
- na = np.empty(size, np.float32)
- i.pixels.foreach_get(na)
-
- # dropping this re-shaping code - just doing flat array for speed and simplicity
- # na = na[::4]
- na = na.reshape(height, width, i.channels)
- na = na.swapaxes(0, 1)
-
- # print('\ntime of image to numpy ' + str(time.time() - t))
- return na
-
-
-def downscale(i):
- minsize = 128
-
- sx, sy = i.size[:]
- sx = round(sx / 2)
- sy = round(sy / 2)
- if sx > minsize and sy > minsize:
- i.scale(sx, sy)
-
-
-def get_rgb_mean(i):
- '''checks if normal map values are ok.'''
- import numpy
-
- na = imagetonumpy_flat(i)
-
- r = na[::4]
- g = na[1::4]
- b = na[2::4]
-
- rmean = r.mean()
- gmean = g.mean()
- bmean = b.mean()
-
- rmedian = numpy.median(r)
- gmedian = numpy.median(g)
- bmedian = numpy.median(b)
-
- # return(rmedian,gmedian, bmedian)
- return (rmean, gmean, bmean)
-
-
-def check_nmap_mean_ok(i):
- '''checks if normal map values are in standard range.'''
-
- rmean, gmean, bmean = get_rgb_mean(i)
-
- # we could/should also check blue, but some ogl substance exports have 0-1, while 90% nmaps have 0.5 - 1.
- nmap_ok = 0.45 < rmean < 0.55 and .45 < gmean < .55
-
- return nmap_ok
-
-
-def check_nmap_ogl_vs_dx(i, mask=None, generated_test_images=False):
- '''
- checks if normal map is directX or OpenGL.
- Returns - String value - DirectX and OpenGL
- '''
- import numpy
- width = i.size[0]
- height = i.size[1]
-
- rmean, gmean, bmean = get_rgb_mean(i)
-
- na = imagetonumpy(i)
-
- if mask:
- mask = imagetonumpy(mask)
-
- red_x_comparison = numpy.zeros((width, height), numpy.float32)
- green_y_comparison = numpy.zeros((width, height), numpy.float32)
-
- if generated_test_images:
- red_x_comparison_img = numpy.empty((width, height, 4), numpy.float32) # images for debugging purposes
- green_y_comparison_img = numpy.empty((width, height, 4), numpy.float32) # images for debugging purposes
-
- ogl = numpy.zeros((width, height), numpy.float32)
- dx = numpy.zeros((width, height), numpy.float32)
-
- if generated_test_images:
- ogl_img = numpy.empty((width, height, 4), numpy.float32) # images for debugging purposes
- dx_img = numpy.empty((width, height, 4), numpy.float32) # images for debugging purposes
-
- for y in range(0, height):
- for x in range(0, width):
- # try to mask with UV mask image
- if mask is None or mask[x, y, 3] > 0:
-
- last_height_x = ogl[max(x - 1, 0), min(y, height - 1)]
- last_height_y = ogl[max(x, 0), min(y - 1, height - 1)]
-
- diff_x = ((na[x, y, 0] - rmean) / ((na[x, y, 2] - 0.5)))
- diff_y = ((na[x, y, 1] - gmean) / ((na[x, y, 2] - 0.5)))
- calc_height = (last_height_x + last_height_y) \
- - diff_x - diff_y
- calc_height = calc_height / 2
- ogl[x, y] = calc_height
- if generated_test_images:
- rgb = calc_height * .1 + .5
- ogl_img[x, y] = [rgb, rgb, rgb, 1]
-
- # green channel
- last_height_x = dx[max(x - 1, 0), min(y, height - 1)]
- last_height_y = dx[max(x, 0), min(y - 1, height - 1)]
-
- diff_x = ((na[x, y, 0] - rmean) / ((na[x, y, 2] - 0.5)))
- diff_y = ((na[x, y, 1] - gmean) / ((na[x, y, 2] - 0.5)))
- calc_height = (last_height_x + last_height_y) \
- - diff_x + diff_y
- calc_height = calc_height / 2
- dx[x, y] = calc_height
- if generated_test_images:
- rgb = calc_height * .1 + .5
- dx_img[x, y] = [rgb, rgb, rgb, 1]
-
- ogl_std = ogl.std()
- dx_std = dx.std()
-
- # print(mean_ogl, mean_dx)
- # print(max_ogl, max_dx)
- print(ogl_std, dx_std)
- print(i.name)
- # if abs(mean_ogl) > abs(mean_dx):
- if abs(ogl_std) > abs(dx_std):
- print('this is probably a DirectX texture')
- else:
- print('this is probably an OpenGL texture')
-
- if generated_test_images:
- # red_x_comparison_img = red_x_comparison_img.swapaxes(0,1)
- # red_x_comparison_img = red_x_comparison_img.flatten()
- #
- # green_y_comparison_img = green_y_comparison_img.swapaxes(0,1)
- # green_y_comparison_img = green_y_comparison_img.flatten()
- #
- # numpytoimage(red_x_comparison_img, 'red_' + i.name, width=width, height=height, channels=1)
- # numpytoimage(green_y_comparison_img, 'green_' + i.name, width=width, height=height, channels=1)
-
- ogl_img = ogl_img.swapaxes(0, 1)
- ogl_img = ogl_img.flatten()
-
- dx_img = dx_img.swapaxes(0, 1)
- dx_img = dx_img.flatten()
-
- numpytoimage(ogl_img, 'OpenGL', width=width, height=height, channels=1)
- numpytoimage(dx_img, 'DirectX', width=width, height=height, channels=1)
-
- if abs(ogl_std) > abs(dx_std):
- return 'DirectX'
- return 'OpenGL'
-
-
-def make_possible_reductions_on_image(teximage, input_filepath, do_reductions=False, do_downscale=False):
- '''checks the image and saves it to drive with possibly reduced channels.
- Also can remove the image from the asset if the image is pure black
- - it finds it's usages and replaces the inputs where the image is used
- with zero/black color.
- currently implemented file type conversions:
- PNG->JPG
- '''
- colorspace = teximage.colorspace_settings.name
- teximage.colorspace_settings.name = 'Non-Color'
- # teximage.colorspace_settings.name = 'sRGB' color correction mambo jambo.
-
- JPEG_QUALITY = 90
- # is_image_black(na)
- # is_image_bw(na)
-
- rs = bpy.context.scene.render
- ims = rs.image_settings
-
- orig_file_format = ims.file_format
- orig_quality = ims.quality
- orig_color_mode = ims.color_mode
- orig_compression = ims.compression
- orig_depth = ims.color_depth
-
- # if is_image_black(na):
- # # just erase the image from the asset here, no need to store black images.
- # pass;
-
- # fp = teximage.filepath
-
- # setup image depth, 8 or 16 bit.
- # this should normally divide depth with number of channels, but blender always states that number of channels is 4, even if there are only 3
-
- print(teximage.name)
- print(teximage.depth)
- print(teximage.channels)
-
- bpy.context.scene.display_settings.display_device = 'None'
-
- image_depth = find_image_depth(teximage)
-
- ims.color_mode = find_color_mode(teximage)
- # image_depth = str(max(min(int(teximage.depth / 3), 16), 8))
- print('resulting depth set to:', image_depth)
-
- fp = input_filepath
- if do_reductions:
- na = imagetonumpy_flat(teximage)
-
- if can_erase_alpha(na):
- print(teximage.file_format)
- if teximage.file_format == 'PNG':
- print('changing type of image to JPG')
- base, ext = os.path.splitext(fp)
- teximage['original_extension'] = ext
-
- fp = fp.replace('.png', '.jpg')
- fp = fp.replace('.PNG', '.jpg')
-
- teximage.name = teximage.name.replace('.png', '.jpg')
- teximage.name = teximage.name.replace('.PNG', '.jpg')
-
- teximage.file_format = 'JPEG'
- ims.quality = JPEG_QUALITY
- ims.color_mode = 'RGB'
-
- if is_image_bw(na):
- ims.color_mode = 'BW'
-
- ims.file_format = teximage.file_format
- ims.color_depth = image_depth
-
- # all pngs with max compression
- if ims.file_format == 'PNG':
- ims.compression = 100
- # all jpgs brought to reasonable quality
- if ims.file_format == 'JPG':
- ims.quality = JPEG_QUALITY
-
- if do_downscale:
- downscale(teximage)
-
- # it's actually very important not to try to change the image filepath and packed file filepath before saving,
- # blender tries to re-pack the image after writing to image.packed_image.filepath and reverts any changes.
- teximage.save_render(filepath=bpy.path.abspath(fp), scene=bpy.context.scene)
- if len(teximage.packed_files) > 0:
- teximage.unpack(method='REMOVE')
- teximage.filepath = fp
- teximage.filepath_raw = fp
- teximage.reload()
-
- teximage.colorspace_settings.name = colorspace
-
- ims.file_format = orig_file_format
- ims.quality = orig_quality
- ims.color_mode = orig_color_mode
- ims.compression = orig_compression
- ims.color_depth = orig_depth
diff --git a/blenderkit/oauth.py b/blenderkit/oauth.py
deleted file mode 100644
index ad45ef6e..00000000
--- a/blenderkit/oauth.py
+++ /dev/null
@@ -1,117 +0,0 @@
-# ##### 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 #####
-
-
-import json
-import webbrowser
-from http.server import BaseHTTPRequestHandler, HTTPServer
-from urllib.parse import parse_qs, quote as urlquote, urlparse
-
-import requests
-
-
-class PortsBlockedException(Exception):
- pass
-
-
-class SimpleOAuthAuthenticator(object):
- def __init__(self, server_url, client_id, ports):
- self.server_url = server_url
- self.client_id = client_id
- self.ports = ports
-
- def _get_tokens(self, authorization_code=None, refresh_token=None, grant_type="authorization_code"):
- data = {
- "grant_type": grant_type,
- "state": "random_state_string",
- "client_id": self.client_id,
- "scopes": "read write",
- }
- if hasattr(self, 'redirect_uri'):
- data["redirect_uri"] = self.redirect_uri
- if authorization_code:
- data['code'] = authorization_code
- if refresh_token:
- data['refresh_token'] = refresh_token
-
- response = requests.post(
- '%s/o/token/' % self.server_url,
- data=data
- )
- if response.status_code != 200:
- print("error retrieving refresh tokens %s" % response.status_code)
- print(response.content)
- return None, None, None
-
- response_json = json.loads(response.content)
- refresh_token = response_json['refresh_token']
- access_token = response_json['access_token']
- return access_token, refresh_token, response_json
-
- def get_new_token(self, register=True, redirect_url=None):
- class HTTPServerHandler(BaseHTTPRequestHandler):
- html_template = '<html>%(head)s<h1>%(message)s</h1></html>'
-
- def do_GET(self):
- self.send_response(200)
- self.send_header('Content-type', 'text/html')
- self.end_headers()
- if 'code' in self.path:
- self.auth_code = self.path.split('=')[1]
- # Display to the user that they no longer need the browser window
- if redirect_url:
- redirect_string = (
- '<head><meta http-equiv="refresh" content="0;url=%(redirect_url)s"></head>'
- '<script> window.location.href="%(redirect_url)s"; </script>' % {'redirect_url': redirect_url}
- )
- else:
- redirect_string = ""
- self.wfile.write(bytes(self.html_template % {'head': redirect_string, 'message': 'You may now close this window.'}, 'utf-8'))
- qs = parse_qs(urlparse(self.path).query)
- self.server.authorization_code = qs['code'][0]
- else:
- self.wfile.write(bytes(self.html_template % {'head': '', 'message': 'Authorization failed.'}, 'utf-8'))
-
- for port in self.ports:
- try:
- httpServer = HTTPServer(('localhost', port), HTTPServerHandler)
- except Exception as e:
- print(f"Port {port}: {e}")
- continue
- break
- else:
- print("All available ports are blocked")
- raise PortsBlockedException(f"All available ports are blocked: {self.ports}")
- print(f"Choosen port {port}")
- self.redirect_uri = f"http://localhost:{port}/consumer/exchange/"
- authorize_url = (
- "/o/authorize?client_id=%s&state=random_state_string&response_type=code&"
- "redirect_uri=%s" % (self.client_id, self.redirect_uri)
- )
- if register:
- authorize_url = "%s/accounts/register/?next=%s" % (self.server_url, urlquote(authorize_url))
- else:
- authorize_url = "%s%s" % (self.server_url, authorize_url)
- webbrowser.open_new(authorize_url)
-
- httpServer.handle_request()
- authorization_code = httpServer.authorization_code
- return self._get_tokens(authorization_code=authorization_code)
-
- def get_refreshed_token(self, refresh_token):
- return self._get_tokens(refresh_token=refresh_token, grant_type="refresh_token")
diff --git a/blenderkit/overrides.py b/blenderkit/overrides.py
deleted file mode 100644
index 03872d70..00000000
--- a/blenderkit/overrides.py
+++ /dev/null
@@ -1,300 +0,0 @@
-# ##### 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 utils
-
-import bpy, mathutils
-from bpy.types import (
- Operator)
-
-
-def getNodes(nt, node_type='OUTPUT_MATERIAL'):
- chnodes = nt.nodes[:]
- nodes = []
- while len(chnodes) > 0:
- n = chnodes.pop()
- if n.type == node_type:
- nodes.append(n)
- if n.type == 'GROUP':
- chnodes.extend(n.node_tree.nodes)
- return nodes
-
-
-def getShadersCrawl(nt, chnodes):
- shaders = []
- done_nodes = chnodes[:]
-
- while len(chnodes) > 0:
- check_node = chnodes.pop()
- is_shader = False
- for o in check_node.outputs:
- if o.type == 'SHADER':
- is_shader = True
- for i in check_node.inputs:
- if i.type == 'SHADER':
- is_shader = False # this is for mix nodes and group inputs..
- if len(i.links) > 0:
- for l in i.links:
- fn = l.from_node
- if fn not in done_nodes:
- done_nodes.append(fn)
- chnodes.append(fn)
- if fn.type == 'GROUP':
- group_outputs = getNodes(fn.node_tree, node_type='GROUP_OUTPUT')
- shaders.extend(getShadersCrawl(fn.node_tree, group_outputs))
-
- if check_node.type == 'GROUP':
- is_shader = False
-
- if is_shader:
- shaders.append((check_node, nt))
-
- return (shaders)
-
-
-def addColorCorrectors(material):
- nt = material.node_tree
- output = getNodes(nt, 'OUTPUT_MATERIAL')[0]
- shaders = getShadersCrawl(nt, [output])
-
- correctors = []
- for shader, nt in shaders:
-
- if shader.type != 'BSDF_TRANSPARENT': # exclude transparent for color tweaks
- for i in shader.inputs:
- if i.type == 'RGBA':
- if len(i.links) > 0:
- l = i.links[0]
- if not (l.from_node.type == 'GROUP' and l.from_node.node_tree.name == 'bkit_asset_tweaker'):
- from_socket = l.from_socket
- to_socket = l.to_socket
-
- g = nt.nodes.new(type='ShaderNodeGroup')
- g.node_tree = bpy.data.node_groups['bkit_asset_tweaker']
- g.location = shader.location
- g.location.x -= 100
-
- nt.links.new(from_socket, g.inputs[0])
- nt.links.new(g.outputs[0], to_socket)
- else:
- g = l.from_node
- tweakers.append(g)
- else:
- g = nt.nodes.new(type='ShaderNodeGroup')
- g.node_tree = bpy.data.node_groups['bkit_asset_tweaker']
- g.location = shader.location
- g.location.x -= 100
-
- nt.links.new(g.outputs[0], i)
- correctors.append(g)
-
-
-# def modelProxy():
-# utils.p('No proxies in Blender anymore')
-# return False
-#
-# s = bpy.context.scene
-# ao = bpy.context.active_object
-# if utils.is_linked_asset(ao):
-# utils.activate(ao)
-#
-# g = ao.instance_collection
-#
-# rigs = []
-#
-# for ob in g.objects:
-# if ob.type == 'ARMATURE':
-# rigs.append(ob)
-#
-# if len(rigs) == 1:
-#
-# ao.instance_collection = None
-# bpy.ops.object.duplicate()
-# new_ao = bpy.context.view_layer.objects.active
-# new_ao.instance_collection = g
-# new_ao.empty_display_type = 'SPHERE'
-# new_ao.empty_display_size *= 0.1
-#
-# # bpy.ops.object.proxy_make(object=rigs[0].name)
-# proxy = bpy.context.active_object
-# bpy.context.view_layer.objects.active = ao
-# ao.select_set(True)
-# new_ao.select_set(True)
-# new_ao.use_extra_recalc_object = True
-# new_ao.use_extra_recalc_data = True
-# bpy.ops.object.parent_set(type='OBJECT', keep_transform=True)
-# return True
-# else: # TODO report this to ui
-# utils.p('not sure what to proxify')
-# return False
-
-
-eevee_transp_nodes = [
- 'BSDF_GLASS',
- 'BSDF_REFRACTION',
- 'BSDF_TRANSPARENT',
- 'PRINCIPLED_VOLUME',
- 'VOLUME_ABSORPTION',
- 'VOLUME_SCATTER'
-]
-
-
-def ensure_eevee_transparency(m):
- ''' ensures alpha for transparent materials when the user didn't set it up correctly'''
- # if the blend mode is opaque, it means user probably ddidn't know or forgot to
- # set up material properly
- if m.blend_method == 'OPAQUE':
- alpha = False
- for n in m.node_tree.nodes:
- if n.type in eevee_transp_nodes:
- alpha = True
- elif n.type == 'BSDF_PRINCIPLED':
- i = n.inputs['Transmission']
- if i.default_value > 0 or len(i.links) > 0:
- alpha = True
- if alpha:
- m.blend_method = 'HASHED'
- m.shadow_method = 'HASHED'
-
-
-class BringToScene(Operator):
- """Bring linked object hierarchy to scene and make it editable"""
-
- bl_idname = "object.blenderkit_bring_to_scene"
- bl_label = "BlenderKit bring objects to scene"
- bl_options = {'REGISTER', 'UNDO'}
-
- @classmethod
- def poll(cls, context):
- return bpy.context.view_layer.objects.active is not None
-
- def execute(self, context):
-
- s = bpy.context.scene
- sobs = s.collection.all_objects
- aob = bpy.context.active_object
- dg = aob.instance_collection
- vlayer = bpy.context.view_layer
- instances_emptys = []
-
- # first, find instances of this collection in the scene
- for ob in sobs:
- if ob.instance_collection == dg and ob not in instances_emptys:
- instances_emptys.append(ob)
- ob.instance_collection = None
- ob.instance_type = 'NONE'
- # dg.make_local
- parent = None
- obs = []
- for ob in dg.objects:
- dg.objects.unlink(ob)
- try:
- s.collection.objects.link(ob)
- ob.select_set(True)
- obs.append(ob)
- if ob.parent == None:
- parent = ob
- bpy.context.view_layer.objects.active = parent
- except Exception as e:
- print(e)
-
- bpy.ops.object.make_local(type='ALL')
-
- for i, ob in enumerate(obs):
- if ob.name in vlayer.objects:
- obs[i] = vlayer.objects[ob.name]
- try:
- ob.select_set(True)
- except Exception as e:
- print('failed to select an object from the collection, getting a replacement.')
- print(e)
-
- related = []
-
- for i, ob in enumerate(instances_emptys):
- if i > 0:
- bpy.ops.object.duplicate(linked=True)
-
- related.append([ob, bpy.context.active_object, mathutils.Vector(bpy.context.active_object.scale)])
-
- for relation in related:
- bpy.ops.object.select_all(action='DESELECT')
- bpy.context.view_layer.objects.active = relation[0]
- relation[0].select_set(True)
- relation[1].select_set(True)
- relation[1].matrix_world = relation[0].matrix_world
- relation[1].scale.x = relation[2].x * relation[0].scale.x
- relation[1].scale.y = relation[2].y * relation[0].scale.y
- relation[1].scale.z = relation[2].z * relation[0].scale.z
- bpy.ops.object.parent_set(type='OBJECT', keep_transform=True)
-
- return {'FINISHED'}
-
-
-# class ModelProxy(Operator):
-# """Attempt to create proxy armature from the asset"""
-# bl_idname = "object.blenderkit_make_proxy"
-# bl_label = "BlenderKit Make Proxy"
-#
-# @classmethod
-# def poll(cls, context):
-# return bpy.context.view_layer.objects.active is not None
-#
-# def execute(self, context):
-# result = modelProxy()
-# if not result:
-# self.report({'INFO'}, 'No proxy made.There is no armature or more than one in the model.')
-# return {'FINISHED'}
-
-
-class ColorCorrector(Operator):
- """Add color corector to the asset. """
- bl_idname = "object.blenderkit_color_corrector"
- bl_label = "Add color corrector"
-
- @classmethod
- def poll(cls, context):
- return bpy.context.view_layer.objects.active is not None
-
- def execute(self, context):
- ao = bpy.context.active_object
- g = ao.instance_collection
- ao['color correctors'] = []
- mats = []
-
- for o in g.objects:
- for ms in o.material_slots:
- if ms.material not in mats:
- mats.append(ms.material)
- for mat in mats:
- correctors = addColorCorrectors(mat)
-
- return 'FINISHED'
-
-
-def register_overrides():
- bpy.utils.register_class(BringToScene)
- # bpy.utils.register_class(ModelProxy)
- bpy.utils.register_class(ColorCorrector)
-
-
-def unregister_overrides():
- bpy.utils.unregister_class(BringToScene)
- # bpy.utils.unregister_class(ModelProxy)
- bpy.utils.unregister_class(ColorCorrector)
diff --git a/blenderkit/paths.py b/blenderkit/paths.py
deleted file mode 100644
index 0170ae7b..00000000
--- a/blenderkit/paths.py
+++ /dev/null
@@ -1,407 +0,0 @@
-# ##### 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 #####
-
-import bpy, os, sys, tempfile, shutil
-from blenderkit import tasks_queue, ui, utils, reports
-
-_presets = os.path.join(bpy.utils.user_resource('SCRIPTS'), "presets")
-BLENDERKIT_LOCAL = "http://localhost:8001"
-BLENDERKIT_MAIN = "https://www.blenderkit.com"
-BLENDERKIT_DEVEL = "https://devel.blenderkit.com"
-BLENDERKIT_API = "/api/v1/"
-BLENDERKIT_REPORT_URL = "usage_report/"
-BLENDERKIT_USER_ASSETS = "/my-assets"
-BLENDERKIT_PLANS = "/plans/pricing/"
-BLENDERKIT_MANUAL = "https://youtu.be/pSay3yaBWV0"
-BLENDERKIT_MODEL_UPLOAD_INSTRUCTIONS_URL = "https://www.blenderkit.com/docs/upload/"
-BLENDERKIT_MATERIAL_UPLOAD_INSTRUCTIONS_URL = "https://www.blenderkit.com/docs/uploading-material/"
-BLENDERKIT_BRUSH_UPLOAD_INSTRUCTIONS_URL = "https://www.blenderkit.com/docs/uploading-brush/"
-BLENDERKIT_HDR_UPLOAD_INSTRUCTIONS_URL = "https://www.blenderkit.com/docs/uploading-hdr/"
-BLENDERKIT_SCENE_UPLOAD_INSTRUCTIONS_URL = "https://www.blenderkit.com/docs/uploading-scene/"
-BLENDERKIT_LOGIN_URL = "https://www.blenderkit.com/accounts/login"
-BLENDERKIT_OAUTH_LANDING_URL = "/oauth-landing/"
-BLENDERKIT_SIGNUP_URL = "https://www.blenderkit.com/accounts/register"
-BLENDERKIT_SETTINGS_FILENAME = os.path.join(_presets, "bkit.json")
-
-
-def cleanup_old_folders():
- '''function to clean up any historical folders for BlenderKit. By now removes the temp folder.'''
- orig_temp = os.path.join(os.path.expanduser('~'), 'blenderkit_data', 'temp')
- if os.path.isdir(orig_temp):
- try:
- shutil.rmtree(orig_temp)
- except Exception as e:
- print(e)
- print("couldn't delete old temp directory")
-
-
-def get_bkit_url():
- # bpy.app.debug_value = 2
- d = bpy.app.debug_value
- # d = 2
- if d == 1:
- url = BLENDERKIT_LOCAL
- elif d == 2:
- url = BLENDERKIT_DEVEL
- else:
- url = BLENDERKIT_MAIN
- return url
-
-
-def find_in_local(text=''):
- fs = []
- for p, d, f in os.walk('.'):
- for file in f:
- if text in file:
- fs.append(file)
- return fs
-
-
-def get_api_url():
- return get_bkit_url() + BLENDERKIT_API
-
-
-def get_oauth_landing_url():
- return get_bkit_url() + BLENDERKIT_OAUTH_LANDING_URL
-
-
-def get_author_gallery_url(author_id):
- return f'{get_bkit_url()}/asset-gallery?query=author_id:{author_id}'
-
-def get_asset_gallery_url(asset_id):
- return f'{get_bkit_url()}/asset-gallery-detail/{asset_id}/'
-
-def default_global_dict():
- from os.path import expanduser
- home = expanduser("~")
- return home + os.sep + 'blenderkit_data'
-
-
-def get_categories_filepath():
- tempdir = get_temp_dir()
- return os.path.join(tempdir, 'categories.json')
-
-dirs_exist_dict = {}#cache these results since this is used very often
-# this causes the function to fail if user deletes the directory while blender is running,
-# but comes back when blender is restarted.
-def get_temp_dir(subdir=None):
-
- user_preferences = bpy.context.preferences.addons['blenderkit'].preferences
- #first try cached results
- if subdir is not None:
- d = dirs_exist_dict.get(subdir)
- if d is not None:
- return d
- else:
- d = dirs_exist_dict.get('top')
- if d is not None:
- return d
-
- # tempdir = user_preferences.temp_dir
- tempdir = os.path.join(tempfile.gettempdir(), 'bkit_temp')
- if tempdir.startswith('//'):
- tempdir = bpy.path.abspath(tempdir)
- try:
- if not os.path.exists(tempdir):
- os.makedirs(tempdir)
- dirs_exist_dict['top'] = tempdir
-
- if subdir is not None:
- tempdir = os.path.join(tempdir, subdir)
- if not os.path.exists(tempdir):
- os.makedirs(tempdir)
- dirs_exist_dict[subdir] = tempdir
-
- cleanup_old_folders()
- except:
- tasks_queue.add_task((reports.add_report, ('Cache directory not found. Resetting Cache folder path.',)))
-
- p = default_global_dict()
- if p == user_preferences.global_dir:
- message = 'Global dir was already default, plese set a global directory in addon preferences to a dir where you have write permissions.'
- tasks_queue.add_task((reports.add_report, (message,)))
- return None
- user_preferences.global_dir = p
- tempdir = get_temp_dir(subdir=subdir)
- return tempdir
-
-
-
-def get_download_dirs(asset_type):
- ''' get directories where assets will be downloaded'''
- subdmapping = {'brush': 'brushes', 'texture': 'textures', 'model': 'models', 'scene': 'scenes',
- 'material': 'materials', 'hdr':'hdrs'}
-
- user_preferences = bpy.context.preferences.addons['blenderkit'].preferences
- dirs = []
- if user_preferences.directory_behaviour == 'BOTH' or 'GLOBAL':
- ddir = user_preferences.global_dir
- if ddir.startswith('//'):
- ddir = bpy.path.abspath(ddir)
- if not os.path.exists(ddir):
- os.makedirs(ddir)
-
- subd = subdmapping[asset_type]
- subdir = os.path.join(ddir, subd)
- if not os.path.exists(subdir):
- os.makedirs(subdir)
- dirs.append(subdir)
- if (
- user_preferences.directory_behaviour == 'BOTH' or user_preferences.directory_behaviour == 'LOCAL') and bpy.data.is_saved: # it's important local get's solved as second, since for the linking process only last filename will be taken. For download process first name will be taken and if 2 filenames were returned, file will be copied to the 2nd path.
- ddir = user_preferences.project_subdir
- if ddir.startswith('//'):
- ddir = bpy.path.abspath(ddir)
- if not os.path.exists(ddir):
- os.makedirs(ddir)
-
- subd = subdmapping[asset_type]
-
- subdir = os.path.join(ddir, subd)
- if not os.path.exists(subdir):
- os.makedirs(subdir)
- dirs.append(subdir)
-
- return dirs
-
-
-def slugify(slug):
- """
- Normalizes string, converts to lowercase, removes non-alpha characters,
- and converts spaces to hyphens.
- """
- import unicodedata, re
- slug = slug.lower()
-
- characters = '<>:"/\\|?\*., ()#'
- for ch in characters:
- slug = slug.replace(ch, '_')
- # import re
- # slug = unicodedata.normalize('NFKD', slug)
- # slug = slug.encode('ascii', 'ignore').lower()
- slug = re.sub(r'[^a-z0-9]+.- ', '-', slug).strip('-')
- slug = re.sub(r'[-]+', '-', slug)
- slug = re.sub(r'/', '_', slug)
- slug = re.sub(r'\\\'\"', '_', slug)
- if len(slug)>50:
- slug = slug[:50]
- return slug
-
-
-def extract_filename_from_url(url):
- # print(url)
- if url is not None:
- imgname = url.split('/')[-1]
- imgname = imgname.split('?')[0]
- return imgname
- return ''
-
-
-resolution_suffix = {
- 'blend': '',
- 'resolution_0_5K': '_05k',
- 'resolution_1K': '_1k',
- 'resolution_2K': '_2k',
- 'resolution_4K': '_4k',
- 'resolution_8K': '_8k',
-}
-resolutions = {
- 'resolution_0_5K': 512,
- 'resolution_1K': 1024,
- 'resolution_2K': 2048,
- 'resolution_4K': 4096,
- 'resolution_8K': 8192,
-}
-
-
-def round_to_closest_resolution(res):
- rdist = 1000000
- # while res/2>1:
- # p2res*=2
- # res = res/2
- # print(p2res, res)
- for rkey in resolutions:
- # print(resolutions[rkey], rdist)
- d = abs(res - resolutions[rkey])
- if d < rdist:
- rdist = d
- p2res = rkey
-
- return p2res
-
-
-def get_res_file(asset_data, resolution, find_closest_with_url = False):
- '''
- Returns closest resolution that current asset can offer.
- If there are no resolutions, return orig file.
- If orig file is requested, return it.
- params
- asset_data
- resolution - ideal resolution
- find_closest_with_url:
- returns only resolutions that already containt url in the asset data, used in scenes where asset is/was already present.
- Returns:
- resolution file
- resolution, so that other processess can pass correctly which resolution is downloaded.
- '''
- orig = None
- res = None
- closest = None
- target_resolution = resolutions.get(resolution)
- mindist = 100000000
-
- for f in asset_data['files']:
- if f['fileType'] == 'blend':
- orig = f
- if resolution == 'blend':
- #orig file found, return.
- return orig , 'blend'
-
- if f['fileType'] == resolution:
- #exact match found, return.
- return f, resolution
- # find closest resolution if the exact match won't be found.
- rval = resolutions.get(f['fileType'])
- if rval and target_resolution:
- rdiff = abs(target_resolution - rval)
- if rdiff < mindist:
- closest = f
- mindist = rdiff
- # print('\n\n\n\n\n\n\n\n')
- # print(closest)
- # print('\n\n\n\n\n\n\n\n')
- if not res and not closest:
- # utils.pprint(f'will download blend instead of resolution {resolution}')
- return orig , 'blend'
- # utils.pprint(f'found closest resolution {closest["fileType"]} instead of the requested {resolution}')
- return closest, closest['fileType']
-
-def server_2_local_filename(asset_data, filename):
- '''
- Convert file name on server to file name local.
- This should get replaced
- '''
- # print(filename)
- fn = filename.replace('blend_', '')
- fn = fn.replace('resolution_', '')
- # print('after replace ', fn)
- n = slugify(asset_data['name']) + '_' + fn
- return n
-
-def get_texture_directory(asset_data, resolution = 'blend'):
- tex_dir_path = f"//textures{resolution_suffix[resolution]}{os.sep}"
- return tex_dir_path
-
-def get_download_filepaths(asset_data, resolution='blend', can_return_others = False):
- '''Get all possible paths of the asset and resolution. Usually global and local directory.'''
- dirs = get_download_dirs(asset_data['assetType'])
- res_file, resolution = get_res_file(asset_data, resolution, find_closest_with_url = can_return_others)
- name_slug = slugify(asset_data['name'])
- asset_folder_name = f"{name_slug}_{asset_data['id']}"
-
- # utils.pprint('get download filenames ', dict(res_file))
- file_names = []
-
- if not res_file:
- return file_names
- # fn = asset_data['file_name'].replace('blend_', '')
- if res_file.get('url') is not None:
- #Tweak the names a bit:
- # remove resolution and blend words in names
- #
- fn = extract_filename_from_url(res_file['url'])
- n = server_2_local_filename(asset_data,fn)
- for d in dirs:
- asset_folder_path = os.path.join(d,asset_folder_name)
- if not os.path.exists(asset_folder_path):
- os.makedirs(asset_folder_path)
-
- file_name = os.path.join(asset_folder_path, n)
- file_names.append(file_name)
-
- utils.p('file paths', file_names)
- return file_names
-
-
-def delete_asset_debug(asset_data):
- '''TODO fix this for resolutions - should get ALL files from ALL resolutions.'''
- from blenderkit import download
- user_preferences = bpy.context.preferences.addons['blenderkit'].preferences
- api_key = user_preferences.api_key
-
- download.get_download_url(asset_data, download.get_scene_id(), api_key)
-
- file_names = get_download_filepaths(asset_data)
- for f in file_names:
- asset_dir = os.path.dirname(f)
-
- if os.path.isdir(asset_dir):
-
- try:
- print(asset_dir)
- shutil.rmtree(asset_dir)
- except:
- e = sys.exc_info()[0]
- print(e)
- pass;
-
-
-def get_clean_filepath():
- script_path = os.path.dirname(os.path.realpath(__file__))
- subpath = "blendfiles" + os.sep + "cleaned.blend"
- cp = os.path.join(script_path, subpath)
- return cp
-
-
-def get_thumbnailer_filepath():
- script_path = os.path.dirname(os.path.realpath(__file__))
- # fpath = os.path.join(p, subpath)
- subpath = "blendfiles" + os.sep + "thumbnailer.blend"
- return os.path.join(script_path, subpath)
-
-
-def get_material_thumbnailer_filepath():
- script_path = os.path.dirname(os.path.realpath(__file__))
- # fpath = os.path.join(p, subpath)
- subpath = "blendfiles" + os.sep + "material_thumbnailer_cycles.blend"
- return os.path.join(script_path, subpath)
- """
- for p in bpy.utils.script_paths():
- testfname= os.path.join(p, subpath)#p + '%saddons%sobject_fracture%sdata.blend' % (s,s,s)
- if os.path.isfile( testfname):
- fname=testfname
- return(fname)
- return None
- """
-
-
-def get_addon_file(subpath=''):
- script_path = os.path.dirname(os.path.realpath(__file__))
- # fpath = os.path.join(p, subpath)
- return os.path.join(script_path, subpath)
-
-script_path = os.path.dirname(os.path.realpath(__file__))
-
-def get_addon_thumbnail_path(name):
- global script_path
- # fpath = os.path.join(p, subpath)
- ext = name.split('.')[-1]
- next = ''
- if not (ext == 'jpg' or ext == 'png'): # already has ext?
- next = '.jpg'
- subpath = "thumbnails" + os.sep + name + next
- return os.path.join(script_path, subpath)
diff --git a/blenderkit/ratings.py b/blenderkit/ratings.py
deleted file mode 100644
index 3baabb5b..00000000
--- a/blenderkit/ratings.py
+++ /dev/null
@@ -1,297 +0,0 @@
-# ##### 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, rerequests, tasks_queue, ratings_utils, icons
-
-import bpy
-import requests, threading
-import logging
-
-bk_logger = logging.getLogger('blenderkit')
-
-from bpy.props import (
- IntProperty,
- FloatProperty,
- StringProperty,
- EnumProperty,
- BoolProperty,
- PointerProperty,
-)
-from bpy.types import (
- Operator,
- Panel,
-)
-
-
-def pretty_print_POST(req):
- """
- pretty print a request
- """
- print('{}\n{}\n{}\n\n{}'.format(
- '-----------START-----------',
- req.method + ' ' + req.url,
- '\n'.join('{}: {}'.format(k, v) for k, v in req.headers.items()),
- req.body,
- ))
-
-
-def upload_review_thread(url, reviews, headers):
- r = rerequests.put(url, data=reviews, verify=True, headers=headers)
-
- # except requests.exceptions.RequestException as e:
- # print('reviews upload failed: %s' % str(e))
-
-
-
-def upload_rating(asset):
- user_preferences = bpy.context.preferences.addons['blenderkit'].preferences
- api_key = user_preferences.api_key
- headers = utils.get_headers(api_key)
-
- bkit_ratings = asset.bkit_ratings
- # print('rating asset', asset_data['name'], asset_data['assetBaseId'])
- url = paths.get_api_url() + 'assets/' + asset['asset_data']['id'] + '/rating/'
-
- ratings = [
-
- ]
-
- if bkit_ratings.rating_quality > 0.1:
- ratings = (('quality', bkit_ratings.rating_quality),)
- tasks_queue.add_task((ratings_utils.send_rating_to_thread_quality, (url, ratings, headers)), wait=2.5,
- only_last=True)
- if bkit_ratings.rating_work_hours > 0.1:
- ratings = (('working_hours', round(bkit_ratings.rating_work_hours, 1)),)
- tasks_queue.add_task((ratings_utils.send_rating_to_thread_work_hours, (url, ratings, headers)), wait=2.5,
- only_last=True)
-
- thread = threading.Thread(target=ratings_utils.upload_rating_thread, args=(url, ratings, headers))
- thread.start()
-
- url = paths.get_api_url() + 'assets/' + asset['asset_data']['id'] + '/review'
-
- reviews = {
- 'reviewText': bkit_ratings.rating_compliments,
- 'reviewTextProblems': bkit_ratings.rating_problems,
- }
- if not (bkit_ratings.rating_compliments == '' and bkit_ratings.rating_compliments == ''):
- thread = threading.Thread(target=upload_review_thread, args=(url, reviews, headers))
- thread.start()
-
- # the info that the user rated an item is stored in the scene
- s = bpy.context.scene
- s['assets rated'] = s.get('assets rated', {})
- if bkit_ratings.rating_quality > 0.1 and bkit_ratings.rating_work_hours > 0.1:
- s['assets rated'][asset['asset_data']['assetBaseId']] = True
-
-
-def get_assets_for_rating():
- '''
- gets assets from scene that could/should be rated by the user.
- TODO this is only a draft.
-
- '''
- assets = []
- for ob in bpy.context.scene.objects:
- if ob.get('asset_data'):
- assets.append(ob)
- for m in bpy.data.materials:
- if m.get('asset_data'):
- assets.append(m)
- for b in bpy.data.brushes:
- if b.get('asset_data'):
- assets.append(b)
- return assets
-
-
-asset_types = (
- ('MODEL', 'Model', 'set of objects'),
- ('SCENE', 'Scene', 'scene'),
- ('HDR', 'HDR', 'hdr'),
- ('MATERIAL', 'Material', 'any .blend Material'),
- ('TEXTURE', 'Texture', 'a texture, or texture set'),
- ('BRUSH', 'Brush', 'brush, can be any type of blender brush'),
- ('ADDON', 'Addon', 'addnon'),
-)
-
-
-# TODO drop this operator, not needed anymore.
-class UploadRatingOperator(bpy.types.Operator):
- """Upload rating to the web db"""
- bl_idname = "object.blenderkit_rating_upload"
- bl_label = "Send Rating"
- bl_options = {'REGISTER', 'UNDO', 'INTERNAL'}
-
- # type of upload - model, material, textures, e.t.c.
- # asset_type: EnumProperty(
- # name="Type",
- # items=asset_types,
- # description="Type of asset",
- # default="MODEL",
- # )
-
- # @classmethod
- # def poll(cls, context):
- # return bpy.context.active_object != None and bpy.context.active_object.get('asset_id') is not None
- def draw(self, context):
- layout = self.layout
- layout.label(text='Rating sent to server. Thanks for rating!')
-
- def execute(self, context):
- return {'FINISHED'}
-
- def invoke(self, context, event):
- wm = context.window_manager
- asset = utils.get_active_asset()
- upload_rating(asset)
- return wm.invoke_props_dialog(self)
-
-
-def draw_ratings_menu(self, context, layout):
- pcoll = icons.icon_collections["main"]
-
- profile_name = ''
- profile = bpy.context.window_manager.get('bkit profile')
- if profile and len(profile['user']['firstName']) > 0:
- profile_name = ' ' + profile['user']['firstName']
-
- col = layout.column()
- # layout.template_icon_view(bkit_ratings, property, show_labels=False, scale=6.0, scale_popup=5.0)
- row = col.row()
- row.label(text='Quality:', icon='SOLO_ON')
- row = col.row()
- row.label(text='Please help the community by rating quality:')
-
- row = col.row()
- row.prop(self, 'rating_quality_ui', expand=True, icon_only=True, emboss=False)
- if self.rating_quality > 0:
- # row = col.row()
-
- row.label(text=f' Thanks{profile_name}!', icon='FUND')
- # row.label(text=str(self.rating_quality))
- col.separator()
- col.separator()
-
- row = col.row()
- row.label(text='Complexity:', icon_value=pcoll['dumbbell'].icon_id)
- row = col.row()
- row.label(text=f"How many hours did this {self.asset_type} save you?")
-
- if utils.profile_is_validator():
- row = col.row()
- row.prop(self, 'rating_work_hours')
-
- if self.asset_type in ('model', 'scene'):
- row = col.row()
-
- row.prop(self, 'rating_work_hours_ui', expand=True, icon_only=False, emboss=True)
- if float(self.rating_work_hours_ui) > 100:
- utils.label_multiline(col,
- text=f"\nThat's huge! please be sure to give such rating only to godly {self.asset_type}s.\n",
- width=500)
- elif float(self.rating_work_hours_ui) > 18:
- col.separator()
-
- utils.label_multiline(col,
- text=f"\nThat's a lot! please be sure to give such rating only to amazing {self.asset_type}s.\n",
- width=500)
-
-
- elif self.asset_type == 'hdr':
- row = col.row()
- row.prop(self, 'rating_work_hours_ui_1_10', expand=True, icon_only=False, emboss=True)
- else:
- row = col.row()
- row.prop(self, 'rating_work_hours_ui_1_5', expand=True, icon_only=False, emboss=True)
-
- if self.rating_work_hours > 0:
- row = col.row()
- row.label(text=f'Thanks{profile_name}, you are amazing!', icon='FUND')
-
-
-class FastRateMenu(Operator, ratings_utils.RatingsProperties):
- """Rating of the assets , also directly from the asset bar - without need to download assets"""
- bl_idname = "wm.blenderkit_menu_rating_upload"
- bl_label = "Ratings"
- bl_options = {'REGISTER', 'UNDO', 'INTERNAL'}
-
- @classmethod
- def poll(cls, context):
- scene = bpy.context.scene
- ui_props = bpy.context.window_manager.blenderkitUI
- return True;
-
- def draw(self, context):
- layout = self.layout
- draw_ratings_menu(self, context, layout)
-
- def execute(self, context):
- scene = bpy.context.scene
- ui_props = bpy.context.window_manager.blenderkitUI
- #get asset id
- if ui_props.active_index > -1:
- sr = bpy.context.window_manager['search results']
- asset_data = dict(sr[ui_props.active_index])
- self.asset_id = asset_data['id']
- self.asset_type = asset_data['assetType']
-
- if self.asset_id == '':
- return {'CANCELLED'}
-
- wm = context.window_manager
-
- self.prefill_ratings()
-
- if self.asset_type in ('model', 'scene'):
- # spawn a wider one for validators for the enum buttons
- return wm.invoke_popup(self, width=500)
- else:
- return wm.invoke_popup(self)
-
-
-def rating_menu_draw(self, context):
- layout = self.layout
-
- ui_props = context.window_manager.blenderkitUI
- sr = bpy.context.window_manager['search results']
-
- asset_search_index = ui_props.active_index
- if asset_search_index > -1:
- asset_data = dict(sr['results'][asset_search_index])
-
- col = layout.column()
- layout.label(text='Admin rating Tools:')
- col.operator_context = 'INVOKE_DEFAULT'
-
- op = col.operator('wm.blenderkit_menu_rating_upload', text='Add Rating')
- op.asset_id = asset_data['id']
- op.asset_name = asset_data['name']
- op.asset_type = asset_data['assetType']
-
-
-def register_ratings():
- bpy.utils.register_class(UploadRatingOperator)
- bpy.utils.register_class(FastRateMenu)
- # bpy.types.OBJECT_MT_blenderkit_asset_menu.append(rating_menu_draw)
-
-
-def unregister_ratings():
- pass;
- # bpy.utils.unregister_class(StarRatingOperator)
- bpy.utils.unregister_class(UploadRatingOperator)
- bpy.utils.unregister_class(FastRateMenu)
diff --git a/blenderkit/ratings_utils.py b/blenderkit/ratings_utils.py
deleted file mode 100644
index 798d5c7e..00000000
--- a/blenderkit/ratings_utils.py
+++ /dev/null
@@ -1,367 +0,0 @@
-# ##### 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 #####
-
-# mainly update functions and callbacks for ratings properties, here to avoid circular imports.
-import bpy
-from blenderkit import utils, paths, tasks_queue, rerequests
-
-from bpy.props import (
- IntProperty,
- FloatProperty,
- FloatVectorProperty,
- StringProperty,
- EnumProperty,
- BoolProperty,
- PointerProperty,
-)
-
-import threading
-import requests
-import logging
-
-bk_logger = logging.getLogger('blenderkit')
-
-
-def upload_rating_thread(url, ratings, headers):
- ''' Upload rating thread function / disconnected from blender data.'''
- bk_logger.debug('upload rating ' + url + str(ratings))
- for rating_name, score in ratings:
- if (score != -1 and score != 0):
- rating_url = url + rating_name + '/'
- data = {
- "score": score, # todo this kind of mixing is too much. Should have 2 bkit structures, upload, use
- }
-
- try:
- r = rerequests.put(rating_url, data=data, verify=True, headers=headers)
-
- except requests.exceptions.RequestException as e:
- print('ratings upload failed: %s' % str(e))
-
-
-def send_rating_to_thread_quality(url, ratings, headers):
- '''Sens rating into thread rating, main purpose is for tasks_queue.
- One function per property to avoid lost data due to stashing.'''
- thread = threading.Thread(target=upload_rating_thread, args=(url, ratings, headers))
- thread.start()
-
-
-def send_rating_to_thread_work_hours(url, ratings, headers):
- '''Sens rating into thread rating, main purpose is for tasks_queue.
- One function per property to avoid lost data due to stashing.'''
- thread = threading.Thread(target=upload_rating_thread, args=(url, ratings, headers))
- thread.start()
-
-
-def store_rating_local_empty(asset_id):
- context = bpy.context
- ar = context.window_manager['asset ratings']
- ar[asset_id] = ar.get(asset_id, {})
-
-
-def store_rating_local(asset_id, type='quality', value=0):
- context = bpy.context
- ar = context.window_manager['asset ratings']
- ar[asset_id] = ar.get(asset_id, {})
- ar[asset_id][type] = value
-
-
-def get_rating(asset_id, headers):
- '''
- Retrieve ratings from BlenderKit server. Can be run from a thread
- Parameters
- ----------
- asset_id
- headers
-
- Returns
- -------
- ratings - dict of type:value ratings
- '''
- url = paths.get_api_url() + 'assets/' + asset_id + '/rating/'
- params = {}
- r = rerequests.get(url, params=params, verify=True, headers=headers)
- if r is None:
- return
- if r.status_code == 200:
- rj = r.json()
- ratings = {}
- # print(rj)
- # store ratings - send them to task queue
- for r in rj['results']:
- ratings[r['ratingType']] = r['score']
- tasks_queue.add_task((store_rating_local,(asset_id, r['ratingType'], r['score'])))
- # store_rating_local(asset_id, type = r['ratingType'], value = r['score'])
-
- if len(rj['results'])==0:
- # store empty ratings too, so that server isn't checked repeatedly
- tasks_queue.add_task((store_rating_local_empty,(asset_id,)))
- # return ratings
-
-
-def get_rating_local(asset_id):
- context = bpy.context
- context.window_manager['asset ratings'] = context.window_manager.get('asset ratings', {})
- rating = context.window_manager['asset ratings'].get(asset_id)
- if rating:
- return rating.to_dict()
- return None
-
-
-def update_ratings_quality(self, context):
- user_preferences = bpy.context.preferences.addons['blenderkit'].preferences
- api_key = user_preferences.api_key
-
- headers = utils.get_headers(api_key)
-
- if not (hasattr(self, 'rating_quality')):
- # first option is for rating of assets that are from scene
- asset = self.id_data
- bkit_ratings = asset.bkit_ratings
- asset_id = asset['asset_data']['id']
- else:
- # this part is for operator rating:
- bkit_ratings = self
- asset_id = self.asset_id
-
- if bkit_ratings.rating_quality > 0.1:
- url = paths.get_api_url() + f'assets/{asset_id}/rating/'
-
- store_rating_local(asset_id, type='quality', value=bkit_ratings.rating_quality)
-
- ratings = [('quality', bkit_ratings.rating_quality)]
- tasks_queue.add_task((send_rating_to_thread_quality, (url, ratings, headers)), wait=2.5, only_last=True)
-
-
-def update_ratings_work_hours(self, context):
- user_preferences = bpy.context.preferences.addons['blenderkit'].preferences
- api_key = user_preferences.api_key
- headers = utils.get_headers(api_key)
- if not (hasattr(self, 'rating_work_hours')):
- # first option is for rating of assets that are from scene
- asset = self.id_data
- bkit_ratings = asset.bkit_ratings
- asset_id = asset['asset_data']['id']
- else:
- # this part is for operator rating:
- bkit_ratings = self
- asset_id = self.asset_id
-
- if bkit_ratings.rating_work_hours > 0.45:
- url = paths.get_api_url() + f'assets/{asset_id}/rating/'
-
- store_rating_local(asset_id, type='working_hours', value=bkit_ratings.rating_work_hours)
-
- ratings = [('working_hours', round(bkit_ratings.rating_work_hours, 1))]
- tasks_queue.add_task((send_rating_to_thread_work_hours, (url, ratings, headers)), wait=2.5, only_last=True)
-
-
-def update_quality_ui(self, context):
- '''Converts the _ui the enum into actual quality number.'''
- user_preferences = bpy.context.preferences.addons['blenderkit'].preferences
- if user_preferences.api_key == '':
- # ui_panels.draw_not_logged_in(self, message='Please login/signup to rate assets.')
- # bpy.ops.wm.call_menu(name='OBJECT_MT_blenderkit_login_menu')
- # return
- bpy.ops.wm.blenderkit_login('INVOKE_DEFAULT',
- message='Please login/signup to rate assets. Clicking OK takes you to web login.')
- # self.rating_quality_ui = '0'
- self.rating_quality = int(self.rating_quality_ui)
-
-
-def update_ratings_work_hours_ui(self, context):
- user_preferences = bpy.context.preferences.addons['blenderkit'].preferences
- if user_preferences.api_key == '':
- # ui_panels.draw_not_logged_in(self, message='Please login/signup to rate assets.')
- # bpy.ops.wm.call_menu(name='OBJECT_MT_blenderkit_login_menu')
- # return
- bpy.ops.wm.blenderkit_login('INVOKE_DEFAULT',
- message='Please login/signup to rate assets. Clicking OK takes you to web login.')
- # self.rating_work_hours_ui = '0'
- self.rating_work_hours = float(self.rating_work_hours_ui)
-
-
-def update_ratings_work_hours_ui_1_5(self, context):
- user_preferences = bpy.context.preferences.addons['blenderkit'].preferences
- if user_preferences.api_key == '':
- # ui_panels.draw_not_logged_in(self, message='Please login/signup to rate assets.')
- # bpy.ops.wm.call_menu(name='OBJECT_MT_blenderkit_login_menu')
- # return
- bpy.ops.wm.blenderkit_login('INVOKE_DEFAULT',
- message='Please login/signup to rate assets. Clicking OK takes you to web login.')
- # self.rating_work_hours_ui_1_5 = '0'
- self.rating_work_hours = float(self.rating_work_hours_ui_1_5)
-
-
-def update_ratings_work_hours_ui_1_10(self, context):
- user_preferences = bpy.context.preferences.addons['blenderkit'].preferences
- if user_preferences.api_key == '':
- # ui_panels.draw_not_logged_in(self, message='Please login/signup to rate assets.')
- # bpy.ops.wm.call_menu(name='OBJECT_MT_blenderkit_login_menu')
- # return
- bpy.ops.wm.blenderkit_login('INVOKE_DEFAULT',
- message='Please login/signup to rate assets. Clicking OK takes you to web login.')
- # self.rating_work_hours_ui_1_5 = '0'
- # print('updating 1-5')
- # print(float(self.rating_work_hours_ui_1_5))
- self.rating_work_hours = float(self.rating_work_hours_ui_1_10)
-
-
-def stars_enum_callback(self, context):
- '''regenerates the enum property used to display rating stars, so that there are filled/empty stars correctly.'''
- items = []
- for a in range(0, 10):
- if self.rating_quality < a + 1:
- icon = 'SOLO_OFF'
- else:
- icon = 'SOLO_ON'
- # has to have something before the number in the value, otherwise fails on registration.
- items.append((f'{a + 1}', f'{a + 1}', '', icon, a + 1))
- return items
-
-
-class RatingsProperties():
- message: StringProperty(
- name="message",
- description="message",
- default="Rating asset",
- options={'SKIP_SAVE'})
-
- asset_id: StringProperty(
- name="Asset Base Id",
- description="Unique id of the asset (hidden)",
- default="",
- options={'SKIP_SAVE'})
-
- asset_name: StringProperty(
- name="Asset Name",
- description="Name of the asset (hidden)",
- default="",
- options={'SKIP_SAVE'})
-
- asset_type: StringProperty(
- name="Asset type",
- description="asset type",
- default="",
- options={'SKIP_SAVE'})
-
- rating_quality: IntProperty(name="Quality",
- description="quality of the material",
- default=0,
- min=-1, max=10,
- update=update_ratings_quality,
- options={'SKIP_SAVE'})
-
- # the following enum is only to ease interaction - enums support 'drag over' and enable to draw the stars easily.
- rating_quality_ui: EnumProperty(name='rating_quality_ui',
- items=stars_enum_callback,
- description='Rating stars 0 - 10',
- default=0,
- update=update_quality_ui,
- options={'SKIP_SAVE'})
-
- rating_work_hours: FloatProperty(name="Work Hours",
- description="How many hours did this work take?",
- default=0.00,
- min=0.0, max=300,
- update=update_ratings_work_hours,
- options={'SKIP_SAVE'}
- )
-
- high_rating_warning = "This is a high rating, please be sure to give such rating only to amazing assets"
-
- possible_wh_values = [0,.5,1,2,3,4,5,6,8,10,15,20,30,50,100,150,200,250]
- items_models = [('0', '0', ''),
- ('.5', '0.5', ''),
- ('1', '1', ''),
- ('2', '2', ''),
- ('3', '3', ''),
- ('4', '4', ''),
- ('5', '5', ''),
- ('6', '6', ''),
- ('8', '8', ''),
- ('10', '10', ''),
- ('15', '15', ''),
- ('20', '20', ''),
- ('30', '30', high_rating_warning),
- ('50', '50', high_rating_warning),
- ('100', '100', high_rating_warning),
- ('150', '150', high_rating_warning),
- ('200', '200', high_rating_warning),
- ('250', '250', high_rating_warning),
- ]
- rating_work_hours_ui: EnumProperty(name="Work Hours",
- description="How many hours did this work take?",
- items=items_models,
- default='0', update=update_ratings_work_hours_ui,
- options={'SKIP_SAVE'}
- )
- possible_wh_values_1_5 = [0,.2, .5,1,2,3,4,5]
-
- items_1_5 = [('0', '0', ''),
- ('.2', '0.2', ''),
- ('.5', '0.5', ''),
- ('1', '1', ''),
- ('2', '2', ''),
- ('3', '3', ''),
- ('4', '4', ''),
- ('5', '5', '')
- ]
- rating_work_hours_ui_1_5: EnumProperty(name="Work Hours",
- description="How many hours did this work take?",
- items=items_1_5,
- default='0',
- update=update_ratings_work_hours_ui_1_5,
- options={'SKIP_SAVE'}
- )
- possible_wh_values_1_10 = [0,1,2,3,4,5,6,7,8,9,10]
-
- items_1_10= [('0', '0', ''),
- ('1', '1', ''),
- ('2', '2', ''),
- ('3', '3', ''),
- ('4', '4', ''),
- ('5', '5', ''),
- ('6', '6', ''),
- ('7', '7', ''),
- ('8', '8', ''),
- ('9', '9', ''),
- ('10', '10', '')
- ]
- rating_work_hours_ui_1_10: EnumProperty(name="Work Hours",
- description="How many hours did this work take?",
- items= items_1_10,
- default='0',
- update=update_ratings_work_hours_ui_1_10,
- options={'SKIP_SAVE'}
- )
-
- def prefill_ratings(self):
- # pre-fill ratings
- ratings = get_rating_local(self.asset_id)
- if ratings and ratings.get('quality'):
- self.rating_quality = ratings['quality']
- if ratings and ratings.get('working_hours'):
- wh = int(ratings['working_hours'])
- whs = str(wh)
- if wh in self.possible_wh_values:
- self.rating_work_hours_ui = whs
- if wh < 6 and wh in self.possible_wh_values_1_5:
- self.rating_work_hours_ui_1_5 = whs
- if wh < 11 and wh in self.possible_wh_values_1_10:
- self.rating_work_hours_ui_1_10 = whs
diff --git a/blenderkit/reports.py b/blenderkit/reports.py
deleted file mode 100644
index 7a455863..00000000
--- a/blenderkit/reports.py
+++ /dev/null
@@ -1,67 +0,0 @@
-# ##### 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 #####
-
-import time
-import bpy
-from blenderkit import colors, asset_bar_op, ui_bgl, utils
-
-reports = []
-
-
-def add_report(text='', timeout=5, color=colors.GREEN):
- global reports
- # check for same reports and just make them longer by the timeout.
- for old_report in reports:
- if old_report.text == text:
- old_report.timeout = old_report.age + timeout
- return
- report = Report(text=text, timeout=timeout, color=color)
- reports.append(report)
-
-
-class Report():
- def __init__(self, area_pointer=0, text='', timeout=5, color=(.5, 1, .5, 1)):
- self.text = text
- self.timeout = timeout
- self.start_time = time.time()
- self.color = color
- self.draw_color = color
- self.age = 0
- if asset_bar_op.active_area_pointer == 0:
- w, a, r = utils.get_largest_area(area_type='VIEW_3D')
-
- self.active_area_pointer = a.as_pointer()
- else:
- self.active_area_pointer = asset_bar_op.active_area_pointer
-
- def fade(self):
- fade_time = 1
- self.age = time.time() - self.start_time
- if self.age + fade_time > self.timeout:
- alpha_multiplier = (self.timeout - self.age) / fade_time
- self.draw_color = (self.color[0], self.color[1], self.color[2], self.color[3] * alpha_multiplier)
- if self.age > self.timeout:
- global reports
- try:
- reports.remove(self)
- except Exception as e:
- pass;
-
- def draw(self, x, y):
- if (bpy.context.area is not None and bpy.context.area.as_pointer() == self.active_area_pointer):
- ui_bgl.draw_text(self.text, x, y + 8, 16, self.draw_color)
diff --git a/blenderkit/rerequests.py b/blenderkit/rerequests.py
deleted file mode 100644
index 2a8adc41..00000000
--- a/blenderkit/rerequests.py
+++ /dev/null
@@ -1,115 +0,0 @@
-# ##### 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 ui, utils, paths, tasks_queue, bkit_oauth, reports
-
-import requests
-import bpy
-import logging
-
-bk_logger = logging.getLogger('rerequests')
-
-
-class FakeResponse():
- def __init__(self, text='', status_code = 400):
- self.text = text
- self.status_code = status_code
- def json(self):
- return {}
-
-def rerequest(method, url, recursion=0, **kwargs):
- # first get any additional args from kwargs
- immediate = False
- if kwargs.get('immediate'):
- immediate = kwargs['immediate']
- kwargs.pop('immediate')
- # first normal attempt
- try:
- response = requests.request(method, url, **kwargs)
- except Exception as e:
- print(e)
- tasks_queue.add_task((reports.add_report, (
- 'Connection error.', 10)))
- return FakeResponse()
-
- bk_logger.debug(url + str(kwargs))
- bk_logger.debug(response.status_code)
-
- if response.status_code == 401:
- try:
- rdata = response.json()
- except:
- rdata = {}
-
- tasks_queue.add_task((reports.add_report, (method + ' request Failed.' + str(rdata.get('detail')),)))
-
- if rdata.get('detail') == 'Invalid token.':
- user_preferences = bpy.context.preferences.addons['blenderkit'].preferences
- if user_preferences.api_key != '':
- if user_preferences.enable_oauth and user_preferences.api_key_refresh != '':
- tasks_queue.add_task((reports.add_report, (
- 'refreshing token. If this fails, please login in BlenderKit Login panel.', 10)))
- refresh_url = paths.get_bkit_url()
- auth_token, refresh_token, oauth_response = bkit_oauth.refresh_token(
- user_preferences.api_key_refresh, refresh_url)
-
- # bk_logger.debug(auth_token, refresh_token)
- if auth_token is not None:
- if immediate == True:
- # this can write tokens occasionally into prefs. used e.g. in upload. Only possible
- # in non-threaded tasks
- bpy.context.preferences.addons['blenderkit'].preferences.api_key = auth_token
- bpy.context.preferences.addons['blenderkit'].preferences.api_key_refresh = refresh_token
- else:
- tasks_queue.add_task((bkit_oauth.write_tokens, (auth_token, refresh_token, oauth_response)))
-
- kwargs['headers'] = utils.get_headers(auth_token)
- response = requests.request(method, url, **kwargs)
- bk_logger.debug('reresult', response.status_code)
- if response.status_code >= 400:
- bk_logger.debug('reresult', response.text)
- tasks_queue.add_task((reports.add_report, (
- response.text, 10)))
-
- else:
- tasks_queue.add_task((reports.add_report, (
- 'Refreshing token failed.Please login manually.', 10)))
- # tasks_queue.add_task((bkit_oauth.write_tokens, ('', '', '')))
- tasks_queue.add_task((bpy.ops.wm.blenderkit_login, ('INVOKE_DEFAULT',)), fake_context=True)
- return response
-
-
-def get(url, **kwargs):
- response = rerequest('get', url, **kwargs)
- return response
-
-
-def post(url, **kwargs):
- response = rerequest('post', url, **kwargs)
- return response
-
-
-def put(url, **kwargs):
- response = rerequest('put', url, **kwargs)
- return response
-
-
-def patch(url, **kwargs):
- response = rerequest('patch', url, **kwargs)
- return response
diff --git a/blenderkit/resolutions.py b/blenderkit/resolutions.py
deleted file mode 100644
index 23aa939d..00000000
--- a/blenderkit/resolutions.py
+++ /dev/null
@@ -1,716 +0,0 @@
-# ##### 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, append_link, bg_blender, utils, download, search, rerequests, upload_bg, image_utils
-
-import sys, json, os, time
-import subprocess
-import tempfile
-import bpy
-import requests
-import math
-import threading
-
-resolutions = {
- 'resolution_0_5K': 512,
- 'resolution_1K': 1024,
- 'resolution_2K': 2048,
- 'resolution_4K': 4096,
- 'resolution_8K': 8192,
-}
-rkeys = list(resolutions.keys())
-
-resolution_props_to_server = {
-
- '512': 'resolution_0_5K',
- '1024': 'resolution_1K',
- '2048': 'resolution_2K',
- '4096': 'resolution_4K',
- '8192': 'resolution_8K',
- 'ORIGINAL': 'blend',
-}
-
-
-def get_current_resolution():
- actres = 0
- for i in bpy.data.images:
- if i.name != 'Render Result':
- actres = max(actres, i.size[0], i.size[1])
- return actres
-
-
-def save_image_safely(teximage, filepath):
- '''
- Blender makes it really hard to save images...
- Would be worth investigating PIL or similar instead
- Parameters
- ----------
- teximage
-
- Returns
- -------
-
- '''
- JPEG_QUALITY = 98
-
- rs = bpy.context.scene.render
- ims = rs.image_settings
-
- orig_file_format = ims.file_format
- orig_quality = ims.quality
- orig_color_mode = ims.color_mode
- orig_compression = ims.compression
-
- ims.file_format = teximage.file_format
- if teximage.file_format == 'PNG':
- ims.color_mode = 'RGBA'
- elif teximage.channels == 3:
- ims.color_mode = 'RGB'
- else:
- ims.color_mode = 'BW'
-
- # all pngs with max compression
- if ims.file_format == 'PNG':
- ims.compression = 100
- # all jpgs brought to reasonable quality
- if ims.file_format == 'JPG':
- ims.quality = JPEG_QUALITY
- # it's actually very important not to try to change the image filepath and packed file filepath before saving,
- # blender tries to re-pack the image after writing to image.packed_image.filepath and reverts any changes.
- teximage.save_render(filepath=bpy.path.abspath(filepath), scene=bpy.context.scene)
-
- teximage.filepath = filepath
- for packed_file in teximage.packed_files:
- packed_file.filepath = filepath
- teximage.filepath_raw = filepath
- teximage.reload()
-
- ims.file_format = orig_file_format
- ims.quality = orig_quality
- ims.color_mode = orig_color_mode
- ims.compression = orig_compression
-
-
-def extxchange_to_resolution(filepath):
- base, ext = os.path.splitext(filepath)
- if ext in ('.png', '.PNG'):
- ext = 'jpg'
-
-
-
-
-
-
-def upload_resolutions(files, asset_data):
- preferences = bpy.context.preferences.addons['blenderkit'].preferences
-
- upload_data = {
- "name": asset_data['name'],
- "token": preferences.api_key,
- "id": asset_data['id']
- }
-
- uploaded = upload_bg.upload_files(upload_data, files)
-
- if uploaded:
- bg_blender.progress('upload finished successfully')
- else:
- bg_blender.progress('upload failed.')
-
-
-def unpack_asset(data):
- utils.p('unpacking asset')
- asset_data = data['asset_data']
- # utils.pprint(asset_data)
-
- blend_file_name = os.path.basename(bpy.data.filepath)
- ext = os.path.splitext(blend_file_name)[1]
-
- resolution = asset_data.get('resolution', 'blend')
- # TODO - passing resolution inside asset data might not be the best solution
- tex_dir_path = paths.get_texture_directory(asset_data, resolution=resolution)
- tex_dir_abs = bpy.path.abspath(tex_dir_path)
- if not os.path.exists(tex_dir_abs):
- try:
- os.mkdir(tex_dir_abs)
- except Exception as e:
- print(e)
- bpy.data.use_autopack = False
- for image in bpy.data.images:
- if image.name != 'Render Result':
- # suffix = paths.resolution_suffix(data['suffix'])
- fp = get_texture_filepath(tex_dir_path, image, resolution=resolution)
- utils.p('unpacking file', image.name)
- utils.p(image.filepath, fp)
-
- for pf in image.packed_files:
- pf.filepath = fp # bpy.path.abspath(fp)
- image.filepath = fp # bpy.path.abspath(fp)
- image.filepath_raw = fp # bpy.path.abspath(fp)
- # image.save()
- if len(image.packed_files) > 0:
- # image.unpack(method='REMOVE')
- image.unpack(method='WRITE_ORIGINAL')
-
- #mark asset browser asset
- data_block = None
- if asset_data['assetType'] == 'model':
- for ob in bpy.data.objects:
- if ob.parent is None and ob in bpy.context.visible_objects:
- ob.asset_mark()
- # for c in bpy.data.collections:
- # if c.get('asset_data') is not None:
- # c.asset_mark()
- # data_block = c
- elif asset_data['assetType'] == 'material':
- for m in bpy.data.materials:
- m.asset_mark()
- data_block = m
- elif asset_data['assetType'] == 'scene':
- bpy.context.scene.asset_mark()
- elif asset_data['assetType'] =='brush':
- for b in bpy.data.brushes:
- if b.get('asset_data') is not None:
- b.asset_mark()
- data_block = b
- if data_block is not None:
- tags = data_block.asset_data.tags
- for t in tags:
- tags.remove(t)
- tags.new('description: ' + asset_data['description'])
- tags.new('tags: ' + ','.join(asset_data['tags']))
- #
- # if this isn't here, blender crashes when saving file.
- bpy.context.preferences.filepaths.file_preview_type = 'NONE'
-
- bpy.ops.wm.save_as_mainfile(filepath = bpy.data.filepath, compress=False)
- # now try to delete the .blend1 file
- try:
-
- os.remove(bpy.data.filepath + '1')
- except Exception as e:
- print(e)
-
-
-def patch_asset_empty(asset_id, api_key):
- '''
- This function patches the asset for the purpose of it getting a reindex.
- Should be removed once this is fixed on the server and
- the server is able to reindex after uploads of resolutions
- Returns
- -------
- '''
- upload_data = {
- }
- url = paths.get_api_url() + 'assets/' + str(asset_id) + '/'
- headers = utils.get_headers(api_key)
- try:
- r = rerequests.patch(url, json=upload_data, headers=headers, verify=True) # files = files,
- except requests.exceptions.RequestException as e:
- print(e)
- return {'CANCELLED'}
- return {'FINISHED'}
-
-
-def reduce_all_images(target_scale=1024):
- for img in bpy.data.images:
- if img.name != 'Render Result':
- print('scaling ', img.name, img.size[0], img.size[1])
- # make_possible_reductions_on_image(i)
- if max(img.size) > target_scale:
- ratio = float(target_scale) / float(max(img.size))
- print(ratio)
- # i.save()
- fp = '//tempimagestorage'
- # print('generated filename',fp)
- # for pf in img.packed_files:
- # pf.filepath = fp # bpy.path.abspath(fp)
-
- img.filepath = fp
- img.filepath_raw = fp
- print(int(img.size[0] * ratio), int(img.size[1] * ratio))
- img.scale(int(img.size[0] * ratio), int(img.size[1] * ratio))
- img.update()
- # img.save()
- # img.reload()
- img.pack()
-
-
-def get_texture_filepath(tex_dir_path, image, resolution='blend'):
- image_file_name = bpy.path.basename(image.filepath)
- if image_file_name == '':
- image_file_name = image.name.split('.')[0]
-
- suffix = paths.resolution_suffix[resolution]
-
- fp = os.path.join(tex_dir_path, image_file_name)
- # check if there is allready an image with same name and thus also assigned path
- # (can happen easily with genearted tex sets and more materials)
- done = False
- fpn = fp
- i = 0
- while not done:
- is_solo = True
- for image1 in bpy.data.images:
- if image != image1 and image1.filepath == fpn:
- is_solo = False
- fpleft, fpext = os.path.splitext(fp)
- fpn = fpleft + str(i).zfill(3) + fpext
- i += 1
- if is_solo:
- done = True
-
- return fpn
-
-
-def generate_lower_resolutions_hdr(asset_data, fpath):
- '''generates lower resolutions for HDR images'''
- hdr = bpy.data.images.load(fpath)
- actres = max(hdr.size[0], hdr.size[1])
- p2res = paths.round_to_closest_resolution(actres)
- original_filesize = os.path.getsize(fpath) # for comparison on the original level
- i = 0
- finished = False
- files = []
- while not finished:
- dirn = os.path.dirname(fpath)
- fn_strip, ext = os.path.splitext(fpath)
- ext = '.exr'
- if i>0:
- image_utils.downscale(hdr)
-
-
- hdr_resolution_filepath = fn_strip + paths.resolution_suffix[p2res] + ext
- image_utils.img_save_as(hdr, filepath=hdr_resolution_filepath, file_format='OPEN_EXR', quality=20, color_mode='RGB', compression=15,
- view_transform='Raw', exr_codec = 'DWAA')
-
- if os.path.exists(hdr_resolution_filepath):
- reduced_filesize = os.path.getsize(hdr_resolution_filepath)
-
- # compare file sizes
- print(f'HDR size was reduced from {original_filesize} to {reduced_filesize}')
- if reduced_filesize < original_filesize:
- # this limits from uploaidng especially same-as-original resolution files in case when there is no advantage.
- # usually however the advantage can be big also for same as original resolution
- files.append({
- "type": p2res,
- "index": 0,
- "file_path": hdr_resolution_filepath
- })
-
- print('prepared resolution file: ', p2res)
-
- if rkeys.index(p2res) == 0:
- finished = True
- else:
- p2res = rkeys[rkeys.index(p2res) - 1]
- i+=1
-
- print('uploading resolution files')
- upload_resolutions(files, asset_data)
-
- preferences = bpy.context.preferences.addons['blenderkit'].preferences
- patch_asset_empty(asset_data['id'], preferences.api_key)
-
-
-def generate_lower_resolutions(data):
- asset_data = data['asset_data']
- actres = get_current_resolution()
- # first let's skip procedural assets
- base_fpath = bpy.data.filepath
-
- s = bpy.context.scene
-
- print('current resolution of the asset ', actres)
- if actres > 0:
- p2res = paths.round_to_closest_resolution(actres)
- orig_res = p2res
- print(p2res)
- finished = False
- files = []
- # now skip assets that have lowest possible resolution already
- if p2res != [0]:
- original_textures_filesize = 0
- for i in bpy.data.images:
- abspath = bpy.path.abspath(i.filepath)
- if os.path.exists(abspath):
- original_textures_filesize += os.path.getsize(abspath)
-
- while not finished:
-
- blend_file_name = os.path.basename(base_fpath)
-
- dirn = os.path.dirname(base_fpath)
- fn_strip, ext = os.path.splitext(blend_file_name)
-
- fn = fn_strip + paths.resolution_suffix[p2res] + ext
- fpath = os.path.join(dirn, fn)
-
- tex_dir_path = paths.get_texture_directory(asset_data, resolution=p2res)
-
- tex_dir_abs = bpy.path.abspath(tex_dir_path)
- if not os.path.exists(tex_dir_abs):
- os.mkdir(tex_dir_abs)
-
- reduced_textures_filessize = 0
- for i in bpy.data.images:
- if i.name != 'Render Result':
-
- print('scaling ', i.name, i.size[0], i.size[1])
- fp = get_texture_filepath(tex_dir_path, i, resolution=p2res)
-
- if p2res == orig_res:
- # first, let's link the image back to the original one.
- i['blenderkit_original_path'] = i.filepath
- # first round also makes reductions on the image, while keeping resolution
- image_utils.make_possible_reductions_on_image(i, fp, do_reductions=True, do_downscale=False)
-
- else:
- # lower resolutions only downscale
- image_utils.make_possible_reductions_on_image(i, fp, do_reductions=False, do_downscale=True)
-
- abspath = bpy.path.abspath(i.filepath)
- if os.path.exists(abspath):
- reduced_textures_filessize += os.path.getsize(abspath)
-
- i.pack()
- # save
- print(fpath)
- # if this isn't here, blender crashes.
- bpy.context.preferences.filepaths.file_preview_type = 'NONE'
-
- # save the file
- bpy.ops.wm.save_as_mainfile(filepath=fpath, compress=True, copy=True)
- # compare file sizes
- print(f'textures size was reduced from {original_textures_filesize} to {reduced_textures_filessize}')
- if reduced_textures_filessize < original_textures_filesize:
- # this limits from uploaidng especially same-as-original resolution files in case when there is no advantage.
- # usually however the advantage can be big also for same as original resolution
- files.append({
- "type": p2res,
- "index": 0,
- "file_path": fpath
- })
-
- print('prepared resolution file: ', p2res)
- if rkeys.index(p2res) == 0:
- finished = True
- else:
- p2res = rkeys[rkeys.index(p2res) - 1]
- print('uploading resolution files')
- upload_resolutions(files, data['asset_data'])
- preferences = bpy.context.preferences.addons['blenderkit'].preferences
- patch_asset_empty(data['asset_data']['id'], preferences.api_key)
- return
-
-
-def regenerate_thumbnail_material(data):
- # this should re-generate material thumbnail and re-upload it.
- # first let's skip procedural assets
- base_fpath = bpy.data.filepath
- blend_file_name = os.path.basename(base_fpath)
- bpy.ops.mesh.primitive_cube_add()
- aob = bpy.context.active_object
- bpy.ops.object.material_slot_add()
- aob.material_slots[0].material = bpy.data.materials[0]
- props = aob.active_material.blenderkit
- props.thumbnail_generator_type = 'BALL'
- props.thumbnail_background = False
- props.thumbnail_resolution = '256'
- # 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")
- # TODO: here it should call start_material_thumbnailer , but with the wait property on, so it can upload afterwards.
- bpy.ops.object.blenderkit_generate_material_thumbnail()
- time.sleep(130)
- # save
- # this does the actual job
-
- return
-
-
-def assets_db_path():
- dpath = os.path.dirname(bpy.data.filepath)
- fpath = os.path.join(dpath, 'all_assets.json')
- return fpath
-
-
-def get_assets_search():
- # bpy.app.debug_value = 2
-
- results = []
- preferences = bpy.context.preferences.addons['blenderkit'].preferences
- url = paths.get_api_url() + 'search/all'
- i = 0
- while url is not None:
- headers = utils.get_headers(preferences.api_key)
- print('fetching assets from assets endpoint')
- print(url)
- retries = 0
- while retries < 3:
- r = rerequests.get(url, headers=headers)
-
- try:
- adata = r.json()
- url = adata.get('next')
- print(i)
- i += 1
- except Exception as e:
- print(e)
- print('failed to get next')
- if retries == 2:
- url = None
- if adata.get('results') != None:
- results.extend(adata['results'])
- retries = 3
- print(f'fetched page {i}')
- retries += 1
-
- fpath = assets_db_path()
- with open(fpath, 'w', encoding = 'utf-8') as s:
- json.dump(results, s, ensure_ascii=False, indent=4)
-
-
-def get_assets_for_resolutions(page_size=100, max_results=100000000):
- preferences = bpy.context.preferences.addons['blenderkit'].preferences
-
- dpath = os.path.dirname(bpy.data.filepath)
- filepath = os.path.join(dpath, 'assets_for_resolutions.json')
- params = {
- 'order': '-created',
- 'textureResolutionMax_gte': '100',
- # 'last_resolution_upload_lt':'2020-9-01'
- }
- search.get_search_simple(params, filepath=filepath, page_size=page_size, max_results=max_results,
- api_key=preferences.api_key)
- return filepath
-
-
-def get_materials_for_validation(page_size=100, max_results=100000000):
- preferences = bpy.context.preferences.addons['blenderkit'].preferences
- dpath = os.path.dirname(bpy.data.filepath)
- filepath = os.path.join(dpath, 'materials_for_validation.json')
- params = {
- 'order': '-created',
- 'asset_type': 'material',
- 'verification_status': 'uploaded'
- }
- search.get_search_simple(params, filepath=filepath, page_size=page_size, max_results=max_results,
- api_key=preferences.api_key)
- return filepath
-
-
-
-
-def load_assets_list(filepath):
- if os.path.exists(filepath):
- with open(filepath, 'r', encoding='utf-8') as s:
- assets = json.load(s)
- return assets
-
-
-def check_needs_resolutions(a):
- if a['verificationStatus'] == 'validated' and a['assetType'] in ('material', 'model', 'scene', 'hdr'):
- # the search itself now picks the right assets so there's no need to filter more than asset types.
- # TODO needs to check first if the upload date is older than resolution upload date, for that we need resolution upload date.
- for f in a['files']:
- if f['fileType'].find('resolution') > -1:
- return False
-
- return True
- return False
-
-
-def download_asset(asset_data, resolution='blend', unpack=False, api_key=''):
- '''
- Download an asset non-threaded way.
- Parameters
- ----------
- asset_data - search result from elastic or assets endpoints from API
-
- Returns
- -------
- path to the resulting asset file or None if asset isn't accessible
- '''
-
- has_url = download.get_download_url(asset_data, download.get_scene_id(), api_key, tcom=None,
- resolution='blend')
- if has_url:
- 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
-
- return None
-
-
-def generate_resolution_thread(asset_data, api_key):
- '''
- A thread that downloads file and only then starts an instance of Blender that generates the resolution
- Parameters
- ----------
- asset_data
-
- Returns
- -------
-
- '''
-
- fpath = download_asset(asset_data, unpack=True, api_key=api_key)
-
- if fpath:
- if asset_data['assetType'] != 'hdr':
- print('send to bg ', fpath)
- proc = send_to_bg(asset_data, fpath, command='generate_resolutions', wait=True);
- else:
- generate_lower_resolutions_hdr(asset_data, fpath)
- # send_to_bg by now waits for end of the process.
- # time.sleep((5))
-
-
-def iterate_for_resolutions(filepath, process_count=12, api_key='', do_checks = True):
- ''' iterate through all assigned assets, check for those which need generation and send them to res gen'''
- assets = load_assets_list(filepath)
- print(len(assets))
- threads = []
- for asset_data in assets:
- asset_data = search.parse_result(asset_data)
- if asset_data is not None:
-
- if not do_checks or check_needs_resolutions(asset_data):
- print('downloading and generating resolution for %s' % asset_data['name'])
- # this is just a quick hack for not using original dirs in blendrkit...
- generate_resolution_thread(asset_data, api_key)
- # thread = threading.Thread(target=generate_resolution_thread, args=(asset_data, api_key))
- # thread.start()
- #
- # threads.append(thread)
- # print('processes ', len(threads))
- # while len(threads) > process_count - 1:
- # for t in threads:
- # if not t.is_alive():
- # threads.remove(t)
- # break;
- # else:
- # print(f'Failed to generate resolution:{asset_data["name"]}')
- else:
- print('not generated resolutions:', asset_data['name'])
-
-
-def send_to_bg(asset_data, fpath, command='generate_resolutions', wait=True):
- '''
- Send varioust task to a new blender instance that runs and closes after finishing the task.
- This function waits until the process finishes.
- The function tries to set the same bpy.app.debug_value in the instance of Blender that is run.
- Parameters
- ----------
- asset_data
- fpath - file that will be processed
- command - command which should be run in background.
-
- Returns
- -------
- None
- '''
- data = {
- 'fpath': fpath,
- 'debug_value': bpy.app.debug_value,
- 'asset_data': asset_data,
- 'command': command,
- }
- binary_path = bpy.app.binary_path
- tempdir = tempfile.mkdtemp()
- datafile = os.path.join(tempdir + 'resdata.json')
- script_path = os.path.dirname(os.path.realpath(__file__))
- with open(datafile, 'w', encoding = 'utf-8') as s:
- json.dump(data, s, ensure_ascii=False, indent=4)
-
- print('opening Blender instance to do processing - ', command)
-
- if wait:
- proc = subprocess.run([
- binary_path,
- "--background",
- "-noaudio",
- fpath,
- "--python", os.path.join(script_path, "resolutions_bg.py"),
- "--", datafile
- ], bufsize=1, stdout=sys.stdout, stdin=subprocess.PIPE, creationflags=utils.get_process_flags())
-
- else:
- # TODO this should be fixed to allow multithreading.
- proc = subprocess.Popen([
- binary_path,
- "--background",
- "-noaudio",
- fpath,
- "--python", os.path.join(script_path, "resolutions_bg.py"),
- "--", datafile
- ], bufsize=1, stdout=subprocess.PIPE, stdin=subprocess.PIPE, creationflags=utils.get_process_flags())
- return proc
-
-
-def write_data_back(asset_data):
- '''ensures that the data in the resolution file is the same as in the database.'''
- pass;
-
-
-def run_bg(datafile):
- print('background file operation')
- with open(datafile, 'r',encoding='utf-8') as f:
- data = json.load(f)
- bpy.app.debug_value = data['debug_value']
- write_data_back(data['asset_data'])
- if data['command'] == 'generate_resolutions':
- generate_lower_resolutions(data)
- elif data['command'] == 'unpack':
- unpack_asset(data)
- elif data['command'] == 'regen_thumbnail':
- regenerate_thumbnail_material(data)
-
-# load_assets_list()
-# generate_lower_resolutions()
-# class TestOperator(bpy.types.Operator):
-# """Tooltip"""
-# bl_idname = "object.test_anything"
-# bl_label = "Test Operator"
-#
-# @classmethod
-# def poll(cls, context):
-# return True
-#
-# def execute(self, context):
-# iterate_for_resolutions()
-# return {'FINISHED'}
-#
-#
-# def register():
-# bpy.utils.register_class(TestOperator)
-#
-#
-# def unregister():
-# bpy.utils.unregister_class(TestOperator)
diff --git a/blenderkit/resolutions_bg.py b/blenderkit/resolutions_bg.py
deleted file mode 100644
index c59ca08d..00000000
--- a/blenderkit/resolutions_bg.py
+++ /dev/null
@@ -1,8 +0,0 @@
-import sys
-import json
-from blenderkit import resolutions
-
-BLENDERKIT_EXPORT_DATA = sys.argv[-1]
-
-if __name__ == "__main__":
- resolutions.run_bg(sys.argv[-1])
diff --git a/blenderkit/search.py b/blenderkit/search.py
deleted file mode 100644
index cf5068f4..00000000
--- a/blenderkit/search.py
+++ /dev/null
@@ -1,1685 +0,0 @@
-# ##### 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, categories, ui, colors, bkit_oauth, version_checker, tasks_queue, rerequests, \
- resolutions, image_utils, ratings_utils, comments_utils, reports
-
-import blenderkit
-from bpy.app.handlers import persistent
-
-from bpy.props import ( # TODO only keep the ones actually used when cleaning
- IntProperty,
- FloatProperty,
- FloatVectorProperty,
- StringProperty,
- EnumProperty,
- BoolProperty,
- PointerProperty,
-)
-from bpy.types import (
- Operator,
- Panel,
- AddonPreferences,
- PropertyGroup,
- UIList
-)
-
-import requests, os, random
-import time
-import threading
-import platform
-import bpy
-import copy
-import json
-import math
-import unicodedata
-import urllib
-import queue
-import logging
-
-bk_logger = logging.getLogger('blenderkit')
-
-search_start_time = 0
-prev_time = 0
-
-
-def check_errors(rdata):
- if rdata.get('statusCode') and int(rdata.get('statusCode')) > 299:
- utils.p(rdata)
- if rdata.get('detail') == 'Invalid token.':
- user_preferences = bpy.context.preferences.addons['blenderkit'].preferences
- if user_preferences.api_key != '':
- if user_preferences.enable_oauth:
- bkit_oauth.refresh_token_thread()
- return False, rdata.get('detail')
- return False, 'Use login panel to connect your profile.'
- else:
- return False, rdata.get('detail')
- if rdata.get('statusCode') is None and rdata.get('results') is None:
- return False, 'Connection error'
- return True, ''
-
-
-search_threads = []
-thumb_workers_sml = []
-thumb_workers_full = []
-thumb_sml_download_threads = queue.Queue()
-thumb_full_download_threads = queue.Queue()
-reports_queue = queue.Queue()
-all_thumbs_loaded = True
-
-rtips_string = """YYou can disable tips in the add-on preferences.
-Ratings help us distribute funds to creators.
-Creators also gain credits for free assets from subscribers.
-Click or drag model or material in scene to link/append
-Right click in the asset bar for more options
-Use Append in import settings if you want to edit downloaded objects.
-Please rate responsively and plentifully. This helps us distribute rewards to the authors.
-All materials are free.
-Storage for public assets is unlimited.
-Locked models are available if you subscribe to Full plan.
-Login to upload your own models, materials or brushes.
-Use 'A' key over the asset bar to search assets by the same author.
-Use semicolon - ; to hide or show the AssetBar.
-Support the authors by subscribing to Full plan.
-Use the W key over the asset bar to open the Author's webpage.
-Use the R key for fast rating of assets.
-Use the X key to delete the asset from your hard drive.
-"""
-rtips = rtips_string.splitlines()
-
-
-def refresh_token_timer():
- ''' this timer gets run every time the token needs refresh. It refreshes tokens and also categories.'''
- utils.p('refresh timer')
- user_preferences = bpy.context.preferences.addons['blenderkit'].preferences
- fetch_server_data()
- categories.load_categories()
-
- return max(3600, user_preferences.api_key_life - 3600)
-
-
-def refresh_notifications_timer():
- ''' this timer gets notifications.'''
- preferences = bpy.context.preferences.addons['blenderkit'].preferences
- fetch_server_data()
- all_notifications_count = comments_utils.count_all_notifications()
- comments_utils.get_notifications_thread(preferences.api_key, all_count = all_notifications_count)
- return 7200
-
-
-def update_ad(ad):
- if not ad.get('assetBaseId'):
- try:
- ad['assetBaseId'] = ad['asset_base_id'] # this should stay ONLY for compatibility with older scenes
- ad['assetType'] = ad['asset_type'] # this should stay ONLY for compatibility with older scenes
- ad['verificationStatus'] = ad[
- 'verification_status'] # this should stay ONLY for compatibility with older scenes
- ad['author'] = {}
- ad['author']['id'] = ad['author_id'] # this should stay ONLY for compatibility with older scenes
- ad['canDownload'] = ad['can_download'] # this should stay ONLY for compatibility with older scenes
- except Exception as e:
- bk_logger.error('BlenderKit failed to update older asset data')
- return ad
-
-
-def update_assets_data(): # updates assets data on scene load.
- '''updates some properties that were changed on scenes with older assets.
- The properties were mainly changed from snake_case to CamelCase to fit the data that is coming from the server.
- '''
- data = bpy.data
-
- datablocks = [
- bpy.data.objects,
- bpy.data.materials,
- bpy.data.brushes,
- ]
- for dtype in datablocks:
- for block in dtype:
- if block.get('asset_data') != None:
- update_ad(block['asset_data'])
-
- dicts = [
- 'assets used',
- # 'assets rated',# assets rated stores only true/false, not asset data.
- ]
- for s in bpy.data.scenes:
- for bkdict in dicts:
-
- d = s.get(bkdict)
- if not d:
- continue;
-
- for asset_id in d.keys():
- update_ad(d[asset_id])
- # bpy.context.scene['assets used'][ad] = ad
-
-
-def purge_search_results():
- ''' clean up search results on save/load.'''
-
- s = bpy.context.scene
-
- sr_props = [
- 'search results',
- 'search results orig',
- ]
- asset_types = ['model', 'material', 'scene', 'hdr', 'brush']
- for at in asset_types:
- sr_props.append('bkit {at} search')
- sr_props.append('bkit {at} search orig')
- for sr_prop in sr_props:
- if s.get(sr_prop):
- del (s[sr_prop])
-
-
-@persistent
-def scene_load(context):
- '''
- Loads categories , checks timers registration, and updates scene asset data.
- Should (probably)also update asset data from server (after user consent)
- '''
- wm = bpy.context.window_manager
- purge_search_results()
- fetch_server_data()
- categories.load_categories()
- if not bpy.app.timers.is_registered(refresh_token_timer) and not bpy.app.background:
- bpy.app.timers.register(refresh_token_timer, persistent=True, first_interval=36000)
- # if utils.experimental_enabled() and not bpy.app.timers.is_registered(
- # refresh_notifications_timer) and not bpy.app.background:
- # bpy.app.timers.register(refresh_notifications_timer, persistent=True, first_interval=5)
-
- update_assets_data()
-
-
-def fetch_server_data():
- ''' download categories , profile, and refresh token if needed.'''
- if not bpy.app.background:
- user_preferences = bpy.context.preferences.addons['blenderkit'].preferences
- api_key = user_preferences.api_key
- # Only refresh new type of tokens(by length), and only one hour before the token timeouts.
- if user_preferences.enable_oauth and \
- len(user_preferences.api_key) < 38 and len(user_preferences.api_key) > 0 and \
- user_preferences.api_key_timeout < time.time() + 3600:
- bkit_oauth.refresh_token_thread()
- if api_key != '' and bpy.context.window_manager.get('bkit profile') == None:
- get_profile()
- if bpy.context.window_manager.get('bkit_categories') is None:
- categories.fetch_categories_thread(api_key, force=False)
- # all_notifications_count = comments_utils.count_all_notifications()
- # comments_utils.get_notifications_thread(api_key, all_count = all_notifications_count)
-
-first_time = True
-first_search_parsing = True
-last_clipboard = ''
-
-
-def check_clipboard():
- '''
- Checks clipboard for an exact string containing asset ID.
- The string is generated on www.blenderkit.com as for example here:
- https://www.blenderkit.com/get-blenderkit/54ff5c85-2c73-49e9-ba80-aec18616a408/
- '''
-
- # clipboard monitoring to search assets from web
- if platform.system() != 'Linux':
- global last_clipboard
- if bpy.context.window_manager.clipboard != last_clipboard:
- last_clipboard = bpy.context.window_manager.clipboard
- instr = 'asset_base_id:'
- # first check if contains asset id, then asset type
- if last_clipboard[:len(instr)] == instr:
- atstr = 'asset_type:'
- ati = last_clipboard.find(atstr)
- # this only checks if the asset_type keyword is there but let's the keywords update function do the parsing.
- if ati > -1:
- search_props = utils.get_search_props()
- search_props.search_keywords = last_clipboard
- # don't run search after this - assigning to keywords runs the search_update function.
-
-
-def parse_result(r):
- '''
- needed to generate some extra data in the result(by now)
- Parameters
- ----------
- r - search result, also called asset_data
- '''
- scene = bpy.context.scene
-
- # TODO remove this fix when filesSize is fixed.
- # this is a temporary fix for too big numbers from the server.
- # try:
- # r['filesSize'] = int(r['filesSize'] / 1024)
- # except:
- # utils.p('asset with no files-size')
- asset_type = r['assetType']
- if len(r['files']) > 0: # TODO remove this condition so all assets are parsed.
- get_author(r)
-
- r['available_resolutions'] = []
- allthumbs = []
- durl, tname, small_tname = '', '', ''
-
- if r['assetType'] == 'hdr':
- tname = paths.extract_filename_from_url(r['thumbnailLargeUrlNonsquared'])
- else:
- tname = paths.extract_filename_from_url(r['thumbnailMiddleUrl'])
- small_tname = paths.extract_filename_from_url(r['thumbnailSmallUrl'])
- allthumbs.append(tname) # TODO just first thumb is used now.
- # if r['fileType'] == 'thumbnail':
- # tname = paths.extract_filename_from_url(f['fileThumbnailLarge'])
- # small_tname = paths.extract_filename_from_url(f['fileThumbnail'])
- # allthumbs.append(tname) # TODO just first thumb is used now.
-
- for f in r['files']:
- # if f['fileType'] == 'thumbnail':
- # tname = paths.extract_filename_from_url(f['fileThumbnailLarge'])
- # small_tname = paths.extract_filename_from_url(f['fileThumbnail'])
- # allthumbs.append(tname) # TODO just first thumb is used now.
-
- if f['fileType'] == 'blend':
- durl = f['downloadUrl'].split('?')[0]
- # fname = paths.extract_filename_from_url(f['filePath'])
-
- if f['fileType'].find('resolution') > -1:
- r['available_resolutions'].append(resolutions.resolutions[f['fileType']])
-
- # code for more thumbnails
- # tdict = {}
- # for i, t in enumerate(allthumbs):
- # tdict['thumbnail_%i'] = t
-
- r['max_resolution'] = 0
- if r['available_resolutions']: # should check only for non-empty sequences
- r['max_resolution'] = max(r['available_resolutions'])
-
- # tooltip = generate_tooltip(r)
- # for some reason, the id was still int on some occurances. investigate this.
- r['author']['id'] = str(r['author']['id'])
-
- # some helper props, but generally shouldn't be renaming/duplifiying original properties,
- # so blender's data is same as on server.
- asset_data = {'thumbnail': tname,
- 'thumbnail_small': small_tname,
- # 'tooltip': tooltip,
-
- }
- asset_data['downloaded'] = 0
-
- # parse extra params needed for blender here
- params = r['dictParameters'] # utils.params_to_dict(r['parameters'])
-
- if asset_type == 'model':
- if params.get('boundBoxMinX') != None:
- bbox = {
- 'bbox_min': (
- float(params['boundBoxMinX']),
- float(params['boundBoxMinY']),
- float(params['boundBoxMinZ'])),
- 'bbox_max': (
- float(params['boundBoxMaxX']),
- float(params['boundBoxMaxY']),
- float(params['boundBoxMaxZ']))
- }
-
- else:
- bbox = {
- 'bbox_min': (-.5, -.5, 0),
- 'bbox_max': (.5, .5, 1)
- }
- asset_data.update(bbox)
- if asset_type == 'material':
- asset_data['texture_size_meters'] = params.get('textureSizeMeters', 1.0)
-
- # asset_data.update(tdict)
-
- au = scene.get('assets used', {})
- if au == {}:
- scene['assets used'] = au
- if r['assetBaseId'] in au.keys():
- asset_data['downloaded'] = 100
- # transcribe all urls already fetched from the server
- r_previous = au[r['assetBaseId']]
- if r_previous.get('files'):
- for f in r_previous['files']:
- if f.get('url'):
- for f1 in r['files']:
- if f1['fileType'] == f['fileType']:
- f1['url'] = f['url']
-
- # attempt to switch to use original data gradually, since the parsing as itself should become obsolete.
- asset_data.update(r)
- return asset_data
-
-
-# @bpy.app.handlers.persistent
-def search_timer():
- # this makes a first search after opening blender. showing latest assets.
- # utils.p('timer search')
- # utils.p('start search timer')
- global first_time
- preferences = bpy.context.preferences.addons['blenderkit'].preferences
- if first_time and not bpy.app.background: # first time
-
- first_time = False
- if preferences.show_on_start:
- # TODO here it should check if there are some results, and only open assetbar if this is the case, not search.
- # if bpy.context.window_manager.get('search results') is None:
- search()
- # preferences.first_run = False
- if preferences.tips_on_start:
- utils.get_largest_area()
- ui.update_ui_size(ui.active_area_pointer, ui.active_region_pointer)
- reports.add_report(text='BlenderKit Tip: ' + random.choice(rtips), timeout=12, color=colors.GREEN)
- # utils.p('end search timer')
-
- return 3.0
-
- # if preferences.first_run:
- # search()
- # preferences.first_run = False
-
- # check_clipboard()
-
- # finish loading thumbs from queues
- global all_thumbs_loaded
- if not all_thumbs_loaded:
- ui_props = bpy.context.window_manager.blenderkitUI
- search_name = f'bkit {ui_props.asset_type.lower()} search'
- wm = bpy.context.window_manager
- if wm.get(search_name) is not None:
- all_loaded = True
- for ri, r in enumerate(wm[search_name]):
- if not r.get('thumb_small_loaded'):
- preview_loaded = load_preview(r, ri)
- all_loaded = all_loaded and preview_loaded
-
- all_thumbs_loaded = all_loaded
-
- global search_threads, first_search_parsing
- if len(search_threads) == 0:
- # utils.p('end search timer')
- props = utils.get_search_props()
- props.is_searching = False
- return 1.0
- # don't do anything while dragging - this could switch asset during drag, and make results list length different,
- # causing a lot of throuble.
- if bpy.context.window_manager.blenderkitUI.dragging:
- # utils.p('end search timer')
-
- return 0.5
-
- for thread in search_threads:
- # TODO this doesn't check all processes when one gets removed,
- # but most of the time only one is running anyway
- if not thread[0].is_alive():
-
- #check for notifications only for users that actually use the add-on
- if first_search_parsing:
- first_search_parsing = False
- all_notifications_count = comments_utils.count_all_notifications()
- comments_utils.get_notifications_thread(preferences.api_key, all_count=all_notifications_count)
- if utils.experimental_enabled() and not bpy.app.timers.is_registered(
- refresh_notifications_timer) and not bpy.app.background:
- bpy.app.timers.register(refresh_notifications_timer, persistent=True, first_interval=5)
-
- search_threads.remove(thread) #
- icons_dir = thread[1]
- scene = bpy.context.scene
- # these 2 lines should update the previews enum and set the first result as active.
- wm = bpy.context.window_manager
- asset_type = thread[2]
-
- props = utils.get_search_props()
- search_name = f'bkit {asset_type} search'
-
- if not thread[0].params.get('get_next'):
- # wm[search_name] = []
- result_field = []
- else:
- result_field = []
- for r in wm[search_name]:
- result_field.append(r.to_dict())
-
- global reports_queue
-
- while not reports_queue.empty():
- props.report = str(reports_queue.get())
- # utils.p('end search timer')
-
- return .2
-
- rdata = thread[0].result
-
- ok, error = check_errors(rdata)
- if ok:
- ui_props = bpy.context.window_manager.blenderkitUI
- orig_len = len(result_field)
-
- for ri, r in enumerate(rdata['results']):
- asset_data = parse_result(r)
- if asset_data != None:
- result_field.append(asset_data)
- all_thumbs_loaded = all_thumbs_loaded and load_preview(asset_data, ri + orig_len)
-
- # Get ratings from BlenderKit server
- user_preferences = bpy.context.preferences.addons['blenderkit'].preferences
- api_key = user_preferences.api_key
- headers = utils.get_headers(api_key)
- if utils.profile_is_validator():
- for r in rdata['results']:
- if ratings_utils.get_rating_local(r['id']) is None:
- rating_thread = threading.Thread(target=ratings_utils.get_rating, args=([r['id'], headers]),
- daemon=True)
- rating_thread.start()
-
- wm[search_name] = result_field
- wm['search results'] = result_field
-
- # rdata=['results']=[]
- wm[search_name + ' orig'] = rdata
- wm['search results orig'] = rdata
-
- if len(result_field) < ui_props.scroll_offset or not (thread[0].params.get('get_next')):
- # jump back
- ui_props.scroll_offset = 0
- props.search_error = False
- props.report = f"Found {wm['search results orig']['count']} results."
- if len(wm['search results']) == 0:
- tasks_queue.add_task((reports.add_report, ('No matching results found.',)))
- else:
- tasks_queue.add_task((reports.add_report, (f"Found {wm['search results orig']['count']} results.",)))
- # undo push
- # bpy.ops.wm.undo_push_context(message='Get BlenderKit search')
- # show asset bar automatically, but only on first page - others are loaded also when asset bar is hidden.
- if not ui_props.assetbar_on and not thread[0].params.get('get_next'):
- bpy.ops.object.run_assetbar_fix_context()
-
- else:
- bk_logger.error(error)
- props.report = error
- props.search_error = True
-
- props.is_searching = False
- # print('finished search thread')
- mt('preview loading finished')
- # utils.p('end search timer')
- if not all_thumbs_loaded:
- return .1
- return .3
-
-
-def load_preview(asset, index):
- # FIRST START SEARCH
- props = bpy.context.window_manager.blenderkitUI
- directory = paths.get_temp_dir('%s_search' % props.asset_type.lower())
-
- tpath = os.path.join(directory, asset['thumbnail_small'])
- if not asset['thumbnail_small'] or asset['thumbnail_small'] == '' or not os.path.exists(tpath):
- # tpath = paths.get_addon_thumbnail_path('thumbnail_notready.jpg')
- asset['thumb_small_loaded'] = False
-
- iname = utils.previmg_name(index)
-
- # if os.path.exists(tpath): # sometimes we are unlucky...
- img = bpy.data.images.get(iname)
-
- if img is None or len(img.pixels) == 0:
- if not os.path.exists(tpath):
- return False
- # wrap into try statement since sometimes
- try:
- img = bpy.data.images.load(tpath)
-
- img.name = iname
- if len(img.pixels)>0:
- return True
- except:
- pass
- return False
- elif img.filepath != tpath:
- if not os.path.exists(tpath):
- # unload loaded previews from previous results
- bpy.data.images.remove(img)
- return False
- # had to add this check for autopacking files...
- if bpy.data.use_autopack and img.packed_file is not None:
- img.unpack(method='USE_ORIGINAL')
- img.filepath = tpath
- try:
- img.reload()
- except:
- return False
-
- if asset['assetType'] == 'hdr':
- # to display hdr thumbnails correctly, we use non-color, otherwise looks shifted
- image_utils.set_colorspace(img, 'Non-Color')
- else:
- image_utils.set_colorspace(img, 'sRGB')
- asset['thumb_small_loaded'] = True
- return True
-
-
-def load_previews():
- scene = bpy.context.scene
- # FIRST START SEARCH
- props = bpy.context.window_manager.blenderkitUI
- directory = paths.get_temp_dir('%s_search' % props.asset_type.lower())
- s = bpy.context.scene
- results = bpy.context.window_manager.get('search results')
- #
- if results is not None:
- i = 0
- for r in results:
- load_preview(r, i)
- i += 1
-
-
-# line splitting for longer texts...
-def split_subs(text, threshold=40):
- if text == '':
- return []
- # temporarily disable this, to be able to do this in drawing code
-
- text = text.rstrip()
- text = text.replace('\r\n', '\n')
-
- lines = []
-
- while len(text) > threshold:
- # first handle if there's an \n line ending
- i_rn = text.find('\n')
- if 1 < i_rn < threshold:
- i = i_rn
- text = text.replace('\n', '', 1)
- else:
- i = text.rfind(' ', 0, threshold)
- i1 = text.rfind(',', 0, threshold)
- i2 = text.rfind('.', 0, threshold)
- i = max(i, i1, i2)
- if i <= 0:
- i = threshold
- lines.append(text[:i])
- text = text[i:]
- lines.append(text)
- return lines
-
-
-def list_to_str(input):
- output = ''
- for i, text in enumerate(input):
- output += text
- if i < len(input) - 1:
- output += ', '
- return output
-
-
-def writeblock(t, input, width=40): # for longer texts
- dlines = split_subs(input, threshold=width)
- for i, l in enumerate(dlines):
- t += '%s\n' % l
- return t
-
-
-def writeblockm(tooltip, mdata, key='', pretext=None, width=40): # for longer texts
- if mdata.get(key) == None:
- return tooltip
- else:
- intext = mdata[key]
- if type(intext) == list:
- intext = list_to_str(intext)
- if type(intext) == float:
- intext = round(intext, 3)
- intext = str(intext)
- if intext.rstrip() == '':
- return tooltip
- if pretext == None:
- pretext = key
- if pretext != '':
- pretext = pretext + ': '
- text = pretext + intext
- dlines = split_subs(text, threshold=width)
- for i, l in enumerate(dlines):
- tooltip += '%s\n' % l
-
- return tooltip
-
-
-def has(mdata, prop):
- if mdata.get(prop) is not None and mdata[prop] is not None and mdata[prop] is not False:
- return True
- else:
- return False
-
-
-def generate_tooltip(mdata):
- col_w = 40
- if type(mdata['parameters']) == list:
- mparams = utils.params_to_dict(mdata['parameters'])
- else:
- mparams = mdata['parameters']
- t = ''
- t = writeblock(t, mdata['displayName'], width=int(col_w * .6))
- # t += '\n'
-
- # t = writeblockm(t, mdata, key='description', pretext='', width=col_w)
- return t
-
-
-def get_random_tip():
- t = ''
- tip = 'Tip: ' + random.choice(rtips)
- t = writeblock(t, tip)
- return t
-
-
-def generate_author_textblock(adata):
- t = ''
-
- if adata not in (None, ''):
- col_w = 2000
- if len(adata['firstName'] + adata['lastName']) > 0:
- t = '%s %s\n' % (adata['firstName'], adata['lastName'])
- t += '\n'
- if adata.get('aboutMe') is not None:
- t = writeblockm(t, adata, key='aboutMe', pretext='', width=col_w)
- return t
-
-
-def download_image(session, url, filepath):
- r = None
- try:
- r = session.get(url, stream=False)
- except Exception as e:
- bk_logger.error('Thumbnail download failed')
- bk_logger.error(str(e))
- if r and r.status_code == 200:
- with open(filepath, 'wb') as f:
- f.write(r.content)
-
-
-def thumb_download_worker(queue_sml, queue_full):
- # print('thumb downloader', self.url)
- # utils.p('start thumbdownloader thread')
- while 1:
- session = None
- # start a session only for single search usually, if users starts scrolling, the session might last longer if
- # queue gets filled.
- if not queue_sml.empty() or not queue_full.empty():
- if session is None:
- session = requests.Session()
- while not queue_sml.empty():
- # first empty the small thumbs queue
- url, filepath = queue_sml.get()
- download_image(session, url, filepath)
- exit_full = False
- # download full resolution image, but only if no small thumbs are waiting. If there are small
- while not queue_full.empty() and queue_sml.empty():
- url, filepath = queue_full.get()
- download_image(session, url, filepath)
-
- if queue_sml.empty() and queue_full.empty():
- if session is not None:
- session.close()
- session = None
- time.sleep(.5)
-
-
-def write_gravatar(a_id, gravatar_path):
- '''
- Write down gravatar path, as a result of thread-based gravatar image download.
- This should happen on timer in queue.
- '''
- # print('write author', a_id, type(a_id))
- authors = bpy.context.window_manager['bkit authors']
- if authors.get(a_id) is not None:
- adata = authors.get(a_id)
- adata['gravatarImg'] = gravatar_path
-
-
-def fetch_gravatar(adata = None):
-
- '''
- Gets avatars from blenderkit server
- Parameters
- ----------
- adata - author data from elastic search result
-
- '''
- # utils.p('fetch gravatar')
- # print(adata)
- # fetch new avatars if available already
- if adata.get('avatar128') is not None:
- avatar_path = paths.get_temp_dir(subdir='bkit_g/') + adata['id'] + '.jpg'
- if os.path.exists(avatar_path):
- tasks_queue.add_task((write_gravatar, (adata['id'], avatar_path)))
- return;
-
- url = paths.get_bkit_url() + adata['avatar128']
- r = rerequests.get(url, stream=False)
- # print(r.body)
- if r.status_code == 200:
- # print(url)
- # print(r.headers['content-disposition'])
- with open(avatar_path, 'wb') as f:
- f.write(r.content)
- tasks_queue.add_task((write_gravatar, (adata['id'], avatar_path)))
- elif r.status_code == '404':
- adata['avatar128'] = None
- utils.p('avatar for author not available.')
- return
-
- # older gravatar code
- if adata.get('gravatarHash') is not None:
- gravatar_path = paths.get_temp_dir(subdir='bkit_g/') + adata['gravatarHash'] + '.jpg'
-
- if os.path.exists(gravatar_path):
- tasks_queue.add_task((write_gravatar, (adata['id'], gravatar_path)))
- return;
-
- url = "https://www.gravatar.com/avatar/" + adata['gravatarHash'] + '?d=404'
- r = rerequests.get(url, stream=False)
- if r.status_code == 200:
- with open(gravatar_path, 'wb') as f:
- f.write(r.content)
- tasks_queue.add_task((write_gravatar, (adata['id'], gravatar_path)))
- elif r.status_code == '404':
- adata['gravatarHash'] = None
- utils.p('gravatar for author not available.')
-
-
-fetching_gravatars = {}
-
-
-def get_author(r):
- ''' Writes author info (now from search results) and fetches gravatar if needed.
- this is now tweaked to be able to get authors from
- '''
- global fetching_gravatars
-
- a_id = str(r['author']['id'])
- preferences = bpy.context.preferences.addons['blenderkit'].preferences
- authors = bpy.context.window_manager.get('bkit authors', {})
- if authors == {}:
- bpy.context.window_manager['bkit authors'] = authors
- a = authors.get(a_id)
- if a is None: # or a is '' or (a.get('gravatarHash') is not None and a.get('gravatarImg') is None):
- a = r['author']
- a['id'] = a_id
- a['tooltip'] = generate_author_textblock(a)
-
- authors[a_id] = a
- if fetching_gravatars.get(a['id']) is None:
- fetching_gravatars[a['id']] = True
-
- thread = threading.Thread(target=fetch_gravatar, args=(a.copy(),), daemon=True)
- thread.start()
- return a
-
-
-def write_profile(adata):
- utils.p('writing profile information')
- user = adata['user']
- # we have to convert to MiB here, numbers too big for python int type
- if user.get('sumAssetFilesSize') is not None:
- user['sumAssetFilesSize'] /= (1024 * 1024)
- if user.get('sumPrivateAssetFilesSize') is not None:
- user['sumPrivateAssetFilesSize'] /= (1024 * 1024)
- if user.get('remainingPrivateQuota') is not None:
- user['remainingPrivateQuota'] /= (1024 * 1024)
-
- if adata.get('canEditAllAssets') is True:
- user['exmenu'] = True
- else:
- user['exmenu'] = False
-
- bpy.context.window_manager['bkit profile'] = adata
-
-
-def request_profile(api_key):
- a_url = paths.get_api_url() + 'me/'
- headers = utils.get_headers(api_key)
- r = rerequests.get(a_url, headers=headers)
- adata = r.json()
- if adata.get('user') is None:
- utils.p(adata)
- utils.p('getting profile failed')
- return None
- return adata
-
-
-def fetch_profile(api_key):
- utils.p('fetch profile')
- try:
- adata = request_profile(api_key)
- if adata is not None:
- tasks_queue.add_task((write_profile, (adata,)))
- except Exception as e:
- bk_logger.error(e)
-
-
-def get_profile():
- preferences = bpy.context.preferences.addons['blenderkit'].preferences
- a = bpy.context.window_manager.get('bkit profile')
- thread = threading.Thread(target=fetch_profile, args=(preferences.api_key,), daemon=True)
- thread.start()
-
- return a
-
-
-def query_to_url(query={}, params={}):
- # build a new request
- url = paths.get_api_url() + 'search/'
-
- # build request manually
- # TODO use real queries
- requeststring = '?query='
- #
- if query.get('query') not in ('', None):
- requeststring += query['query'].lower()
- for i, q in enumerate(query):
- if q != 'query':
- requeststring += '+'
- requeststring += q + ':' + str(query[q]).lower()
-
- # add dict_parameters to make results smaller
- # result ordering: _score - relevance, score - BlenderKit score
- order = []
- if params['free_first']:
- order = ['-is_free', ]
- if query.get('query') is None and query.get('category_subtree') == None:
- # assumes no keywords and no category, thus an empty search that is triggered on start.
- # orders by last core file upload
- if query.get('verification_status') == 'uploaded':
- # for validators, sort uploaded from oldest
- order.append('created')
- else:
- order.append('-last_upload')
- elif query.get('author_id') is not None and utils.profile_is_validator():
-
- order.append('-created')
- else:
- if query.get('category_subtree') is not None:
- order.append('-score,_score')
- else:
- order.append('_score')
- if requeststring.find('+order:') == -1:
- requeststring += '+order:' + ','.join(order)
- requeststring += '&dict_parameters=1'
-
- requeststring += '&page_size=' + str(params['page_size'])
- requeststring += '&addon_version=%s' % params['addon_version']
- if params.get('scene_uuid') is not None:
- requeststring += '&scene_uuid=%s' % params['scene_uuid']
- # print('params', 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
-
- def __init__(self, query, params, tempdir='', headers=None, urlquery=''):
- super(Searcher, self).__init__()
- self.query = query
- self.params = params
- self._stop_event = threading.Event()
- self.result = {}
- self.tempdir = tempdir
- self.headers = headers
- self.urlquery = urlquery
-
- def stop(self):
- self._stop_event.set()
-
- def stopped(self):
- return self._stop_event.is_set()
-
- def run(self):
- global reports_queue, thumb_sml_download_threads, thumb_full_download_threads
-
- maxthreads = 50
- query = self.query
- params = self.params
-
- t = time.time()
- # utils.p('start search thread')
-
- mt('search thread started')
- # tempdir = paths.get_temp_dir('%s_search' % query['asset_type'])
- # json_filepath = os.path.join(tempdir, '%s_searchresult.json' % query['asset_type'])
-
- rdata = {}
- rdata['results'] = []
-
- try:
- utils.p(self.urlquery)
- r = rerequests.get(self.urlquery, headers=self.headers) # , params = rparameters)
- except requests.exceptions.RequestException as e:
- bk_logger.error(e)
- reports_queue.put(str(e))
- # utils.p('end search thread')
-
- return
-
- mt('search response is back ')
- try:
- rdata = r.json()
- except Exception as e:
- if hasattr(r, 'text'):
- error_description = parse_html_formated_error(r.text)
- reports_queue.put(error_description)
- tasks_queue.add_task((reports.add_report, (error_description, 10, colors.RED)))
-
- bk_logger.error(e)
- return
- mt('data parsed ')
- if not rdata.get('results'):
- # utils.pprint(rdata)
- # if the result was converted to json and didn't return results,
- # it means it's a server error that has a clear message.
- # That's why it gets processed in the update timer, where it can be passed in messages to user.
- self.result = rdata
- # utils.p('end search thread')
-
- return
- # print('number of results: ', len(rdata.get('results', [])))
- if self.stopped():
- utils.p('stopping search : ' + str(query))
- # utils.p('end search thread')
-
- return
-
- mt('search finished')
- i = 0
-
- thumb_small_urls = []
- thumb_small_filepaths = []
- thumb_full_urls = []
- thumb_full_filepaths = []
- # END OF PARSING
- for d in rdata.get('results', []):
- thumb_small_urls.append(d["thumbnailSmallUrl"])
- imgname = paths.extract_filename_from_url(d['thumbnailSmallUrl'])
- imgpath = os.path.join(self.tempdir, imgname)
- thumb_small_filepaths.append(imgpath)
-
- if d["assetType"] == 'hdr':
- larege_thumb_url = d['thumbnailLargeUrlNonsquared']
-
- else:
- larege_thumb_url = d['thumbnailMiddleUrl']
-
- thumb_full_urls.append(larege_thumb_url)
- imgname = paths.extract_filename_from_url(larege_thumb_url)
- imgpath = os.path.join(self.tempdir, imgname)
- thumb_full_filepaths.append(imgpath)
-
- # for f in d['files']:
- # # TODO move validation of published assets to server, too manmy checks here.
- # if f['fileType'] == 'thumbnail' and f['fileThumbnail'] != None and f['fileThumbnailLarge'] != None:
- # if f['fileThumbnail'] == None:
- # f['fileThumbnail'] = 'NONE'
- # if f['fileThumbnailLarge'] == None:
- # f['fileThumbnailLarge'] = 'NONE'
- #
- # thumb_small_urls.append(f['fileThumbnail'])
- # thumb_full_urls.append(f['fileThumbnailLarge'])
- #
- # imgname = paths.extract_filename_from_url(f['fileThumbnail'])
- # imgpath = os.path.join(self.tempdir, imgname)
- # thumb_small_filepaths.append(imgpath)
- #
- # imgname = paths.extract_filename_from_url(f['fileThumbnailLarge'])
- # imgpath = os.path.join(self.tempdir, imgname)
- # thumb_full_filepaths.append(imgpath)
-
- sml_thbs = zip(thumb_small_filepaths, thumb_small_urls)
- full_thbs = zip(thumb_full_filepaths, thumb_full_urls)
-
- # we save here because a missing thumbnail check is in the previous loop
- # we can also prepend previous results. These have downloaded thumbnails already...
-
- self.result = rdata
-
- if self.stopped():
- utils.p('stopping search : ' + str(query))
- # utils.p('end search thread')
- return
-
- # this loop handles downloading of small thumbnails
- for imgpath, url in sml_thbs:
- if not os.path.exists(imgpath):
- thumb_sml_download_threads.put((url, imgpath))
-
- if self.stopped():
- utils.p('stopping search : ' + str(query))
- # utils.p('end search thread')
- return
-
- if self.stopped():
- # utils.p('end search thread')
-
- utils.p('stopping search : ' + str(query))
- return
-
- # start downloading full thumbs in the end
- tsession = requests.Session()
-
- for imgpath, url in full_thbs:
- if not os.path.exists(imgpath):
- thumb_full_download_threads.put((url, imgpath))
- # utils.p('end search thread')
- mt('thumbnails finished')
-
-
-def build_query_common(query, props):
- '''add shared parameters to query'''
- query_common = {}
- if props.search_keywords != '':
- # keywords = urllib.parse.urlencode(props.search_keywords)
- keywords = props.search_keywords.replace('&', '%26')
- query_common["query"] = keywords
-
- if props.search_verification_status != 'ALL' and utils.profile_is_validator():
- query_common['verification_status'] = props.search_verification_status.lower()
-
- if props.unrated_only and utils.profile_is_validator():
- query["quality_count"] = 0
-
- if props.search_file_size:
- query_common["files_size_gte"] = props.search_file_size_min * 1024 * 1024
- query_common["files_size_lte"] = props.search_file_size_max * 1024 * 1024
-
- if props.quality_limit > 0:
- query["quality_gte"] = props.quality_limit
-
- query.update(query_common)
-
-
-def build_query_model():
- '''use all search input to request results from server'''
-
- props = bpy.context.window_manager.blenderkit_models
- query = {
- "asset_type": 'model',
- # "engine": props.search_engine,
- # "adult": props.search_adult,
- }
- if props.search_style != 'ANY':
- if props.search_style != 'OTHER':
- query["model_style"] = props.search_style
- else:
- query["model_style"] = props.search_style_other
-
- # the 'free_only' parametr gets moved to the search command and is used for ordering the assets as free first
- # if props.free_only:
- # query["is_free"] = True
-
- if props.search_condition != 'UNSPECIFIED':
- query["condition"] = props.search_condition
-
- if props.search_design_year:
- query["designYear_gte"] = props.search_design_year_min
- query["designYear_lte"] = props.search_design_year_max
- if props.search_polycount:
- query["faceCount_gte"] = props.search_polycount_min
- query["faceCount_lte"] = props.search_polycount_max
- if props.search_texture_resolution:
- query["textureResolutionMax_gte"] = props.search_texture_resolution_min
- query["textureResolutionMax_lte"] = props.search_texture_resolution_max
-
- build_query_common(query, props)
-
- return query
-
-
-def build_query_scene():
- '''use all search input to request results from server'''
-
- props = bpy.context.window_manager.blenderkit_scene
- query = {
- "asset_type": 'scene',
- # "engine": props.search_engine,
- # "adult": props.search_adult,
- }
- build_query_common(query, props)
- return query
-
-
-def build_query_HDR():
- '''use all search input to request results from server'''
-
- props = bpy.context.window_manager.blenderkit_HDR
- query = {
- "asset_type": 'hdr',
-
- # "engine": props.search_engine,
- # "adult": props.search_adult,
- }
- if props.true_hdr:
- query["trueHDR"] = props.true_hdr
- build_query_common(query, props)
- return query
-
-
-def build_query_material():
- props = bpy.context.window_manager.blenderkit_mat
- query = {
- "asset_type": 'material',
-
- }
- # if props.search_engine == 'NONE':
- # query["engine"] = ''
- # if props.search_engine != 'OTHER':
- # query["engine"] = props.search_engine
- # else:
- # query["engine"] = props.search_engine_other
- if props.search_style != 'ANY':
- if props.search_style != 'OTHER':
- query["style"] = props.search_style
- else:
- query["style"] = props.search_style_other
- if props.search_procedural == 'TEXTURE_BASED':
- # todo this procedural hack should be replaced with the parameter
- query["textureResolutionMax_gte"] = 0
- # query["procedural"] = False
- if props.search_texture_resolution:
- query["textureResolutionMax_gte"] = props.search_texture_resolution_min
- query["textureResolutionMax_lte"] = props.search_texture_resolution_max
-
-
-
- elif props.search_procedural == "PROCEDURAL":
- # todo this procedural hack should be replaced with the parameter
- query["files_size_lte"] = 1024 * 1024
- # query["procedural"] = True
-
- build_query_common(query, props)
-
- return query
-
-
-def build_query_texture():
- props = bpy.context.scene.blenderkit_tex
- query = {
- "asset_type": 'texture',
-
- }
-
- if props.search_style != 'ANY':
- if props.search_style != 'OTHER':
- query["search_style"] = props.search_style
- else:
- query["search_style"] = props.search_style_other
-
- build_query_common(query, props)
-
- return query
-
-
-def build_query_brush():
- props = bpy.context.window_manager.blenderkit_brush
-
- brush_type = ''
- if bpy.context.sculpt_object is not None:
- brush_type = 'sculpt'
-
- elif bpy.context.image_paint_object: # could be just else, but for future p
- brush_type = 'texture_paint'
-
- query = {
- "asset_type": 'brush',
-
- "mode": brush_type
- }
-
- build_query_common(query, props)
-
- return query
-
-
-def mt(text):
- global search_start_time, prev_time
- alltime = time.time() - search_start_time
- since_last = time.time() - prev_time
- prev_time = time.time()
- utils.p(text, alltime, since_last)
-
-
-def add_search_process(query, params):
- global search_threads, thumb_workers_sml, thumb_workers_full, all_thumbs_loaded
-
- while (len(search_threads) > 0):
- old_thread = search_threads.pop(0)
- old_thread[0].stop()
- # TODO CARE HERE FOR ALSO KILLING THE Thumbnail THREADS.?
- # AT LEAST NOW SEARCH DONE FIRST WON'T REWRITE AN NEWER ONE
- tempdir = paths.get_temp_dir('%s_search' % query['asset_type'])
- headers = utils.get_headers(params['api_key'])
-
- if params.get('get_next'):
- urlquery = params['next']
- else:
- urlquery = query_to_url(query, params)
-
- if thumb_workers_sml == []:
- for a in range(0, 8):
- thread = threading.Thread(target=thumb_download_worker,
- args=(thumb_sml_download_threads, thumb_full_download_threads),
- daemon=True)
- thread.start()
- thumb_workers_sml.append(thread)
-
- all_thumbs_loaded = False
-
- thread = Searcher(query, params, tempdir=tempdir, headers=headers, urlquery=urlquery)
- thread.start()
-
- search_threads.append([thread, tempdir, query['asset_type'], {}]) # 4th field is for results
-
- mt('search thread started')
-
-
-def get_search_simple(parameters, filepath=None, page_size=100, max_results=100000000, api_key=''):
- '''
- Searches and returns the
-
-
- Parameters
- ----------
- parameters - dict of blenderkit elastic parameters
- filepath - a file to save the results. If None, results are returned
- page_size - page size for retrieved results
- max_results - max results of the search
- api_key - BlenderKit api key
-
- Returns
- -------
- Returns search results as a list, and optionally saves to filepath
-
- '''
- headers = utils.get_headers(api_key)
- url = paths.get_api_url() + 'search/'
- requeststring = url + '?query='
- for p in parameters.keys():
- requeststring += f'+{p}:{parameters[p]}'
-
- requeststring += '&page_size=' + str(page_size)
- requeststring += '&dict_parameters=1'
-
- bk_logger.debug(requeststring)
- response = rerequests.get(requeststring, headers=headers) # , params = rparameters)
- # print(response.json())
- search_results = response.json()
-
- results = []
- results.extend(search_results['results'])
- page_index = 2
- page_count = math.ceil(search_results['count'] / page_size)
- while search_results.get('next') and len(results) < max_results:
- bk_logger.info(f'getting page {page_index} , total pages {page_count}')
- response = rerequests.get(search_results['next'], headers=headers) # , params = rparameters)
- search_results = response.json()
- # print(search_results)
- results.extend(search_results['results'])
- page_index += 1
-
- if not filepath:
- return results
-
- with open(filepath, 'w', encoding='utf-8') as s:
- json.dump(results, s, ensure_ascii=False, indent=4)
- bk_logger.info(f'retrieved {len(results)} assets from elastic search')
- return results
-
-
-def get_single_asset(asset_base_id):
- preferences = bpy.context.preferences.addons['blenderkit'].preferences
- params = {
- 'asset_base_id': asset_base_id
- }
- results = get_search_simple(params, api_key=preferences.api_key)
- if len(results) > 0:
- return results[0]
- return None
-
-
-def search(category='', get_next=False, author_id=''):
- ''' initialize searching'''
- global search_start_time
- # print(category,get_next,author_id)
- user_preferences = bpy.context.preferences.addons['blenderkit'].preferences
- search_start_time = time.time()
- # mt('start')
- scene = bpy.context.scene
- wm = bpy.context.window_manager
- ui_props = bpy.context.window_manager.blenderkitUI
-
- props = utils.get_search_props()
- if ui_props.asset_type == 'MODEL':
- if not hasattr(wm, 'blenderkit_models'):
- return;
- query = build_query_model()
-
- if ui_props.asset_type == 'SCENE':
- if not hasattr(wm, 'blenderkit_scene'):
- return;
- query = build_query_scene()
-
- if ui_props.asset_type == 'HDR':
- if not hasattr(wm, 'blenderkit_HDR'):
- return;
- query = build_query_HDR()
-
- if ui_props.asset_type == 'MATERIAL':
- if not hasattr(wm, 'blenderkit_mat'):
- return;
-
- query = build_query_material()
-
- if ui_props.asset_type == 'TEXTURE':
- if not hasattr(wm, 'blenderkit_tex'):
- return;
- # props = scene.blenderkit_tex
- # query = build_query_texture()
-
- if ui_props.asset_type == 'BRUSH':
- if not hasattr(wm, 'blenderkit_brush'):
- return;
- query = build_query_brush()
-
- # crop long searches
- if query.get('query'):
- if len(query['query']) > 50:
- query['query'] = strip_accents(query['query'])
-
- if len(query['query']) > 150:
- idx = query['query'].find(' ', 142)
- query['query'] = query['query'][:idx]
-
- # it's possible get_next was requested more than once.
- # print(category,props.is_searching, get_next)
- # print(query)
- if props.is_searching and get_next == True:
- # print('return because of get next and searching is happening')
- return;
-
- if category != '':
- if utils.profile_is_validator() and user_preferences.categories_fix:
- query['category'] = category
- else:
- query['category_subtree'] = category
-
- if author_id != '':
- query['author_id'] = author_id
-
- elif props.own_only:
- # if user searches for [another] author, 'only my assets' is invalid. that's why in elif.
- profile = bpy.context.window_manager.get('bkit profile')
- if profile is not None:
- query['author_id'] = str(profile['user']['id'])
-
- # utils.p('searching')
- props.is_searching = True
-
- page_size = min(30, ui_props.wcount * user_preferences.max_assetbar_rows)
-
- params = {
- 'scene_uuid': bpy.context.scene.get('uuid', None),
- 'addon_version': version_checker.get_addon_version(),
- 'api_key': user_preferences.api_key,
- 'get_next': get_next,
- 'free_first': props.free_only,
- 'page_size': page_size,
- }
-
- orig_results = bpy.context.window_manager.get(f'bkit {ui_props.asset_type.lower()} search orig')
- if orig_results is not None and get_next:
- params['next'] = orig_results['next']
- add_search_process(query, params)
- tasks_queue.add_task((reports.add_report, ('BlenderKit searching....', 2)))
-
- props.report = 'BlenderKit searching....'
-
-
-def update_filters():
- sprops = utils.get_search_props()
- ui_props = bpy.context.window_manager.blenderkitUI
- fcommon = sprops.own_only or \
- sprops.search_texture_resolution or \
- sprops.search_file_size or \
- sprops.search_procedural != 'BOTH' or \
- sprops.free_only or \
- sprops.quality_limit > 0
-
- if ui_props.asset_type == 'MODEL':
- sprops.use_filters = fcommon or \
- sprops.search_style != 'ANY' or \
- sprops.search_condition != 'UNSPECIFIED' or \
- sprops.search_design_year or \
- sprops.search_polycount
- elif ui_props.asset_type == 'MATERIAL':
- sprops.use_filters = fcommon
- elif ui_props.asset_type == 'HDR':
- sprops.use_filters = sprops.true_hdr
-
-
-def search_update(self, context):
- utils.p('search updater')
- # if self.search_keywords != '':
- update_filters()
- ui_props = bpy.context.window_manager.blenderkitUI
- if ui_props.down_up != 'SEARCH':
- ui_props.down_up = 'SEARCH'
-
- # here we tweak the input if it comes form the clipboard. we need to get rid of asset type and set it in UI
- sprops = utils.get_search_props()
- instr = 'asset_base_id:'
- atstr = 'asset_type:'
- kwds = sprops.search_keywords
- idi = kwds.find(instr)
- ati = kwds.find(atstr)
- # if the asset type already isn't there it means this update function
- # was triggered by it's last iteration and needs to cancel
- if ati > -1:
- at = kwds[ati:].lower()
- # uncertain length of the remaining string - find as better method to check the presence of asset type
- if at.find('model') > -1:
- ui_props.asset_type = 'MODEL'
- elif at.find('material') > -1:
- ui_props.asset_type = 'MATERIAL'
- elif at.find('brush') > -1:
- ui_props.asset_type = 'BRUSH'
- elif at.find('scene') > -1:
- ui_props.asset_type = 'SCENE'
- elif at.find('hdr') > -1:
- ui_props.asset_type = 'HDR'
- # now we trim the input copypaste by anything extra that is there,
- # this is also a way for this function to recognize that it already has parsed the clipboard
- # the search props can have changed and this needs to transfer the data to the other field
- # this complex behaviour is here for the case where the user needs to paste manually into blender?
- sprops = utils.get_search_props()
- sprops.search_keywords = kwds[:ati].rstrip()
- # return here since writing into search keywords triggers this update function once more.
- return
-
- # print('search update search')
- search()
-
-
-# accented_string is of type 'unicode'
-def strip_accents(s):
- return ''.join(c for c in unicodedata.normalize('NFD', s)
- if unicodedata.category(c) != 'Mn')
-
-
-class SearchOperator(Operator):
- """Tooltip"""
- bl_idname = "view3d.blenderkit_search"
- bl_label = "BlenderKit asset search"
- bl_description = "Search online for assets"
- bl_options = {'REGISTER', 'UNDO', 'INTERNAL'}
-
- esc: BoolProperty(name="Escape window",
- description="Escape window right after start",
- default=False,
- options={'SKIP_SAVE'}
- )
-
- own: BoolProperty(name="own assets only",
- description="Find all own assets",
- default=False,
- options={'SKIP_SAVE'})
-
- category: StringProperty(
- name="category",
- description="search only subtree of this category",
- default="",
- options={'SKIP_SAVE'}
- )
-
- author_id: StringProperty(
- name="Author ID",
- description="Author ID - search only assets by this author",
- default="",
- options={'SKIP_SAVE'}
- )
-
- get_next: BoolProperty(name="next page",
- description="get next page from previous search",
- default=False,
- options={'SKIP_SAVE'}
- )
-
- keywords: StringProperty(
- name="Keywords",
- description="Keywords",
- default="",
- options={'SKIP_SAVE'}
- )
-
- # close_window: BoolProperty(name='Close window',
- # description='Try to close the window below mouse before download',
- # default=False)
-
- tooltip: bpy.props.StringProperty(default='Runs search and displays the asset bar at the same time')
-
- @classmethod
- def description(cls, context, properties):
- return properties.tooltip
-
- @classmethod
- def poll(cls, context):
- return True
-
- def execute(self, context):
- # TODO ; this should all get transferred to properties of the search operator, so sprops don't have to be fetched here at all.
- if self.esc:
- bpy.ops.view3d.close_popup_button('INVOKE_DEFAULT')
- sprops = utils.get_search_props()
- if self.author_id != '':
- sprops.search_keywords = ''
- if self.keywords != '':
- sprops.search_keywords = self.keywords
-
- search(category=self.category, get_next=self.get_next, author_id=self.author_id)
- # bpy.ops.view3d.blenderkit_asset_bar_widget()
-
- return {'FINISHED'}
-
- # def invoke(self, context, event):
- # if self.close_window:
- # context.window.cursor_warp(event.mouse_x, event.mouse_y - 100);
- # context.area.tag_redraw()
- #
- # context.window.cursor_warp(event.mouse_x, event.mouse_y);
- # return self. execute(context)
-
-
-class UrlOperator(Operator):
- """"""
- bl_idname = "wm.blenderkit_url"
- bl_label = ""
- bl_description = "Search online for assets"
- bl_options = {'REGISTER', 'UNDO', 'INTERNAL'}
-
- tooltip: bpy.props.StringProperty(default='Open a web page')
- url: bpy.props.StringProperty(default='Runs search and displays the asset bar at the same time')
-
- @classmethod
- def description(cls, context, properties):
- return properties.tooltip
-
- def execute(self, context):
- bpy.ops.wm.url_open(url=self.url)
- return {'FINISHED'}
-
-
-class TooltipLabelOperator(Operator):
- """"""
- bl_idname = "wm.blenderkit_tooltip"
- bl_label = ""
- bl_description = "Empty operator to be able to create tooltips on labels in UI"
- bl_options = {'REGISTER', 'UNDO', 'INTERNAL'}
-
- tooltip: bpy.props.StringProperty(default='Open a web page')
-
- @classmethod
- def description(cls, context, properties):
- return properties.tooltip
-
- def execute(self, context):
- return {'FINISHED'}
-
-
-classes = [
- SearchOperator,
- UrlOperator,
- TooltipLabelOperator
-]
-
-
-def register_search():
- bpy.app.handlers.load_post.append(scene_load)
-
- for c in classes:
- bpy.utils.register_class(c)
-
- user_preferences = bpy.context.preferences.addons['blenderkit'].preferences
- if user_preferences.use_timers and not bpy.app.background:
- bpy.app.timers.register(search_timer)
-
- categories.load_categories()
-
-
-def unregister_search():
- bpy.app.handlers.load_post.remove(scene_load)
-
- for c in classes:
- bpy.utils.unregister_class(c)
-
- if bpy.app.timers.is_registered(search_timer):
- bpy.app.timers.unregister(search_timer)
diff --git a/blenderkit/tasks_queue.py b/blenderkit/tasks_queue.py
deleted file mode 100644
index 248e9be7..00000000
--- a/blenderkit/tasks_queue.py
+++ /dev/null
@@ -1,125 +0,0 @@
-# ##### 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 utils
-
-import bpy
-from bpy.app.handlers import persistent
-
-import queue
-import logging
-bk_logger = logging.getLogger('blenderkit')
-
-@persistent
-def scene_load(context):
- user_preferences = bpy.context.preferences.addons['blenderkit'].preferences
- if user_preferences.use_timers and not bpy.app.background:
- if not (bpy.app.timers.is_registered(queue_worker)):
- bpy.app.timers.register(queue_worker)
-
-
-def get_queue():
- # we pick just a random one of blender types, to try to get a persistent queue
- t = bpy.types.Scene
-
- if not hasattr(t, 'task_queue'):
- t.task_queue = queue.Queue()
- return t.task_queue
-
-class task_object:
- def __init__(self, command = '', arguments = (), wait = 0, only_last = False, fake_context = False, fake_context_area = 'VIEW_3D'):
- self.command = command
- self.arguments = arguments
- self.wait = wait
- self.only_last = only_last
- self.fake_context = fake_context
- self.fake_context_area = fake_context_area
-
-def add_task(task, wait = 0, only_last = False, fake_context = False, fake_context_area = 'VIEW_3D'):
- q = get_queue()
- taskob = task_object(task[0],task[1], wait = wait, only_last = only_last, fake_context = fake_context, fake_context_area = fake_context_area)
- q.put(taskob)
-
-
-def queue_worker():
- # utils.p('start queue worker timer')
-
- #bk_logger.debug('timer queue worker')
- time_step = 2.0
- q = get_queue()
-
- back_to_queue = [] #delayed events
- stashed = {}
- # first round we get all tasks that are supposed to be stashed and run only once (only_last option)
- # stashing finds tasks with the property only_last and same command and executes only the last one.
- while not q.empty():
- # print('queue while 1')
-
- task = q.get()
- if task.only_last:
- #this now makes the keys not only by task, but also first argument.
- # by now stashing is only used for ratings, where the first argument is url.
- # This enables fast rating of multiple assets while allowing larger delay for uploading of ratings.
- # this avoids a duplicate request error on the server
- stashed[str(task.command)+str(task.arguments[0])] = task
- else:
- back_to_queue.append(task)
- if len(stashed.keys())>1:
- bk_logger.debug('task queue stashed task:' +str(stashed))
- #return tasks to que except for stashed
- for task in back_to_queue:
- q.put(task)
- #return stashed tasks to queue
- for k in stashed.keys():
- q.put(stashed[k])
- #second round, execute or put back waiting tasks.
- back_to_queue = []
- while not q.empty():
- # print('window manager', bpy.context.window_manager)
- task = q.get()
-
- if task.wait>0:
- task.wait-=time_step
- back_to_queue.append(task)
- else:
- bk_logger.debug('task queue task:'+ str( task.command) +str( task.arguments))
- try:
- if task.fake_context:
- fc = utils.get_fake_context(bpy.context, area_type = task.fake_context_area)
- task.command(fc,*task.arguments)
- else:
- task.command(*task.arguments)
- except Exception as e:
- bk_logger.error('task queue failed task:'+ str(task.command)+str(task.arguments)+ str(e))
- # bk_logger.exception('Got exception on main handler')
- # raise
- # print('queue while 2')
- for task in back_to_queue:
- q.put(task)
- # utils.p('end queue worker timer')
-
- return 2.0
-
-
-def register():
- bpy.app.handlers.load_post.append(scene_load)
-
-
-def unregister():
- bpy.app.handlers.load_post.remove(scene_load)
diff --git a/blenderkit/thumbnails/arrow_left.png b/blenderkit/thumbnails/arrow_left.png
deleted file mode 100644
index 97565169..00000000
--- a/blenderkit/thumbnails/arrow_left.png
+++ /dev/null
Binary files differ
diff --git a/blenderkit/thumbnails/arrow_right.png b/blenderkit/thumbnails/arrow_right.png
deleted file mode 100644
index fd16550b..00000000
--- a/blenderkit/thumbnails/arrow_right.png
+++ /dev/null
Binary files differ
diff --git a/blenderkit/thumbnails/bar_slider.png b/blenderkit/thumbnails/bar_slider.png
deleted file mode 100644
index ec627318..00000000
--- a/blenderkit/thumbnails/bar_slider.png
+++ /dev/null
Binary files differ
diff --git a/blenderkit/thumbnails/bell.png b/blenderkit/thumbnails/bell.png
deleted file mode 100644
index 2b724a26..00000000
--- a/blenderkit/thumbnails/bell.png
+++ /dev/null
Binary files differ
diff --git a/blenderkit/thumbnails/cc0.png b/blenderkit/thumbnails/cc0.png
deleted file mode 100644
index 3f5450f8..00000000
--- a/blenderkit/thumbnails/cc0.png
+++ /dev/null
Binary files differ
diff --git a/blenderkit/thumbnails/dumbbell.png b/blenderkit/thumbnails/dumbbell.png
deleted file mode 100644
index ffc709c6..00000000
--- a/blenderkit/thumbnails/dumbbell.png
+++ /dev/null
Binary files differ
diff --git a/blenderkit/thumbnails/filter.png b/blenderkit/thumbnails/filter.png
deleted file mode 100644
index e128c35e..00000000
--- a/blenderkit/thumbnails/filter.png
+++ /dev/null
Binary files differ
diff --git a/blenderkit/thumbnails/filter_active.png b/blenderkit/thumbnails/filter_active.png
deleted file mode 100644
index de4fb3be..00000000
--- a/blenderkit/thumbnails/filter_active.png
+++ /dev/null
Binary files differ
diff --git a/blenderkit/thumbnails/flp.png b/blenderkit/thumbnails/flp.png
deleted file mode 100644
index 7ac3c3d7..00000000
--- a/blenderkit/thumbnails/flp.png
+++ /dev/null
Binary files differ
diff --git a/blenderkit/thumbnails/fp.png b/blenderkit/thumbnails/fp.png
deleted file mode 100644
index 4e356ab1..00000000
--- a/blenderkit/thumbnails/fp.png
+++ /dev/null
Binary files differ
diff --git a/blenderkit/thumbnails/intro.jpg b/blenderkit/thumbnails/intro.jpg
deleted file mode 100644
index 52ce56e3..00000000
--- a/blenderkit/thumbnails/intro.jpg
+++ /dev/null
Binary files differ
diff --git a/blenderkit/thumbnails/locked.png b/blenderkit/thumbnails/locked.png
deleted file mode 100644
index f308392c..00000000
--- a/blenderkit/thumbnails/locked.png
+++ /dev/null
Binary files differ
diff --git a/blenderkit/thumbnails/private.png b/blenderkit/thumbnails/private.png
deleted file mode 100644
index 7ac3c3d7..00000000
--- a/blenderkit/thumbnails/private.png
+++ /dev/null
Binary files differ
diff --git a/blenderkit/thumbnails/royalty_free.png b/blenderkit/thumbnails/royalty_free.png
deleted file mode 100644
index b85fa910..00000000
--- a/blenderkit/thumbnails/royalty_free.png
+++ /dev/null
Binary files differ
diff --git a/blenderkit/thumbnails/star_grey.png b/blenderkit/thumbnails/star_grey.png
deleted file mode 100644
index 53c5bd05..00000000
--- a/blenderkit/thumbnails/star_grey.png
+++ /dev/null
Binary files differ
diff --git a/blenderkit/thumbnails/star_white.png b/blenderkit/thumbnails/star_white.png
deleted file mode 100644
index 14e030cc..00000000
--- a/blenderkit/thumbnails/star_white.png
+++ /dev/null
Binary files differ
diff --git a/blenderkit/thumbnails/thumbnail_not_available.jpg b/blenderkit/thumbnails/thumbnail_not_available.jpg
deleted file mode 100644
index c5b5172a..00000000
--- a/blenderkit/thumbnails/thumbnail_not_available.jpg
+++ /dev/null
Binary files differ
diff --git a/blenderkit/thumbnails/thumbnail_notready.jpg b/blenderkit/thumbnails/thumbnail_notready.jpg
deleted file mode 100644
index 5be976b9..00000000
--- a/blenderkit/thumbnails/thumbnail_notready.jpg
+++ /dev/null
Binary files differ
diff --git a/blenderkit/thumbnails/trophy.png b/blenderkit/thumbnails/trophy.png
deleted file mode 100644
index 01d77a22..00000000
--- a/blenderkit/thumbnails/trophy.png
+++ /dev/null
Binary files differ
diff --git a/blenderkit/thumbnails/vs_deleted.png b/blenderkit/thumbnails/vs_deleted.png
deleted file mode 100644
index a7f4e134..00000000
--- a/blenderkit/thumbnails/vs_deleted.png
+++ /dev/null
Binary files differ
diff --git a/blenderkit/thumbnails/vs_on_hold.png b/blenderkit/thumbnails/vs_on_hold.png
deleted file mode 100644
index eb797517..00000000
--- a/blenderkit/thumbnails/vs_on_hold.png
+++ /dev/null
Binary files differ
diff --git a/blenderkit/thumbnails/vs_ready.png b/blenderkit/thumbnails/vs_ready.png
deleted file mode 100644
index ac52a3cd..00000000
--- a/blenderkit/thumbnails/vs_ready.png
+++ /dev/null
Binary files differ
diff --git a/blenderkit/thumbnails/vs_rejected.png b/blenderkit/thumbnails/vs_rejected.png
deleted file mode 100644
index 6ff663cf..00000000
--- a/blenderkit/thumbnails/vs_rejected.png
+++ /dev/null
Binary files differ
diff --git a/blenderkit/thumbnails/vs_uploaded.png b/blenderkit/thumbnails/vs_uploaded.png
deleted file mode 100644
index 6ef39cb4..00000000
--- a/blenderkit/thumbnails/vs_uploaded.png
+++ /dev/null
Binary files differ
diff --git a/blenderkit/thumbnails/vs_uploading.png b/blenderkit/thumbnails/vs_uploading.png
deleted file mode 100644
index e7276e4d..00000000
--- a/blenderkit/thumbnails/vs_uploading.png
+++ /dev/null
Binary files differ
diff --git a/blenderkit/thumbnails/vs_validated.png b/blenderkit/thumbnails/vs_validated.png
deleted file mode 100644
index b2d8fdd2..00000000
--- a/blenderkit/thumbnails/vs_validated.png
+++ /dev/null
Binary files differ
diff --git a/blenderkit/ui.py b/blenderkit/ui.py
deleted file mode 100644
index f4ccf591..00000000
--- a/blenderkit/ui.py
+++ /dev/null
@@ -1,1937 +0,0 @@
-# ##### 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, ratings, utils, search, upload, ui_bgl, download, bg_blender, colors, tasks_queue, \
- ui_panels, icons, ratings_utils, reports
-
-import bpy
-
-import math, random
-
-from bpy.props import (
- BoolProperty,
- StringProperty,
- IntProperty,
- FloatVectorProperty
-)
-
-from bpy_extras import view3d_utils
-import mathutils
-from mathutils import Vector
-import time
-import datetime
-import os
-
-import logging
-
-draw_time = 0
-eval_time = 0
-
-bk_logger = logging.getLogger('blenderkit')
-
-handler_2d = None
-handler_3d = None
-active_area_pointer = None
-active_window_pointer = None
-active_region_pointer = None
-
-mappingdict = {
- 'MODEL': 'model',
- 'SCENE': 'scene',
- 'HDR': 'hdr',
- 'MATERIAL': 'material',
- 'TEXTURE': 'texture',
- 'BRUSH': 'brush'
-}
-
-verification_icons = {
- 'ready': 'vs_ready.png',
- 'deleted': 'vs_deleted.png',
- 'uploaded': 'vs_uploaded.png',
- 'uploading': 'vs_uploading.png',
- 'on_hold': 'vs_on_hold.png',
- 'validated': None,
- 'rejected': 'vs_rejected.png'
-
-}
-
-
-# class UI_region():
-# def _init__(self, parent = None, x = 10,y = 10 , width = 10, height = 10, img = None, col = None):
-
-def get_approximate_text_width(st):
- size = 10
- for s in st:
- if s in 'i|':
- size += 2
- elif s in ' ':
- size += 4
- elif s in 'sfrt':
- size += 5
- elif s in 'ceghkou':
- size += 6
- elif s in 'PadnBCST3E':
- size += 7
- elif s in 'GMODVXYZ':
- size += 8
- elif s in 'w':
- size += 9
- elif s in 'm':
- size += 10
- else:
- size += 7
- return size # Convert to picas
-
-
-
-def get_asset_under_mouse(mousex, mousey):
- s = bpy.context.scene
- wm = bpy.context.window_manager
- ui_props = bpy.context.window_manager.blenderkitUI
- r = bpy.context.region
-
- search_results = wm.get('search results')
- if search_results is not None:
-
- h_draw = min(ui_props.hcount, math.ceil(len(search_results) / ui_props.wcount))
- for b in range(0, h_draw):
- w_draw = min(ui_props.wcount, len(search_results) - b * ui_props.wcount - ui_props.scroll_offset)
- for a in range(0, w_draw):
- x = ui_props.bar_x + a * (ui_props.margin + ui_props.thumb_size) + ui_props.margin + ui_props.drawoffset
- y = ui_props.bar_y - ui_props.margin - (ui_props.thumb_size + ui_props.margin) * (b + 1)
- w = ui_props.thumb_size
- h = ui_props.thumb_size
-
- if x < mousex < x + w and y < mousey < y + h:
- return a + ui_props.wcount * b + ui_props.scroll_offset
-
- # return search_results[a]
-
- return -3
-
-
-def draw_bbox(location, rotation, bbox_min, bbox_max, progress=None, color=(0, 1, 0, 1)):
- ui_props = bpy.context.window_manager.blenderkitUI
-
- rotation = mathutils.Euler(rotation)
-
- smin = Vector(bbox_min)
- smax = Vector(bbox_max)
- v0 = Vector(smin)
- v1 = Vector((smax.x, smin.y, smin.z))
- v2 = Vector((smax.x, smax.y, smin.z))
- v3 = Vector((smin.x, smax.y, smin.z))
- v4 = Vector((smin.x, smin.y, smax.z))
- v5 = Vector((smax.x, smin.y, smax.z))
- v6 = Vector((smax.x, smax.y, smax.z))
- v7 = Vector((smin.x, smax.y, smax.z))
-
- arrowx = smin.x + (smax.x - smin.x) / 2
- arrowy = smin.y - (smax.x - smin.x) / 2
- v8 = Vector((arrowx, arrowy, smin.z))
-
- vertices = [v0, v1, v2, v3, v4, v5, v6, v7, v8]
- for v in vertices:
- v.rotate(rotation)
- v += Vector(location)
-
- lines = [[0, 1], [1, 2], [2, 3], [3, 0], [4, 5], [5, 6], [6, 7], [7, 4], [0, 4], [1, 5],
- [2, 6], [3, 7], [0, 8], [1, 8]]
- ui_bgl.draw_lines(vertices, lines, color)
- if progress != None:
- color = (color[0], color[1], color[2], .2)
- progress = progress * .01
- vz0 = (v4 - v0) * progress + v0
- vz1 = (v5 - v1) * progress + v1
- vz2 = (v6 - v2) * progress + v2
- vz3 = (v7 - v3) * progress + v3
- rects = (
- (v0, v1, vz1, vz0),
- (v1, v2, vz2, vz1),
- (v2, v3, vz3, vz2),
- (v3, v0, vz0, vz3))
- for r in rects:
- ui_bgl.draw_rect_3d(r, color)
-
-
-def get_rating_scalevalues(asset_type):
- xs = []
- if asset_type == 'model':
- scalevalues = (0.5, 1, 2, 5, 10, 25, 50, 100, 250)
- for v in scalevalues:
- a = math.log2(v)
- x = (a + 1) * (1. / 9.)
- xs.append(x)
- else:
- scalevalues = (0.2, 1, 2, 3, 4, 5)
- for v in scalevalues:
- a = v
- x = v / 5.
- xs.append(x)
- return scalevalues, xs
-
-
-def draw_ratings_bgl():
- # return;
- ui = bpy.context.window_manager.blenderkitUI
-
- rating_possible, rated, asset, asset_data = is_rating_possible()
- if rating_possible: # (not rated or ui_props.rating_menu_on):
- # print('rating is pssible', asset_data['name'])
- bkit_ratings = asset.bkit_ratings
-
- if ui.rating_button_on:
- # print('should draw button')
- img = utils.get_thumbnail('star_white.png')
-
- ui_bgl.draw_image(ui.rating_x,
- ui.rating_y - ui.rating_button_width,
- ui.rating_button_width,
- ui.rating_button_width,
- img, 1)
-
- # if ui_props.asset_type != 'BRUSH':
- # thumbnail_image = props.thumbnail
- # else:
- # b = utils.get_active_brush()
- # thumbnail_image = b.icon_filepath
-
- directory = paths.get_temp_dir('%s_search' % asset_data['assetType'])
- tpath = os.path.join(directory, asset_data['thumbnail_small'])
- img = utils.get_hidden_image(tpath, 'rating_preview')
- ui_bgl.draw_image(ui.rating_x + ui.rating_button_width,
- ui.rating_y - ui.rating_button_width,
- ui.rating_button_width,
- ui.rating_button_width,
- img, 1)
- return
-
-
-def draw_text_block(x=0, y=0, width=40, font_size=10, line_height=15, text='', color=colors.TEXT):
- lines = text.split('\n')
- nlines = []
- for l in lines:
- nlines.extend(search.split_subs(l, ))
-
- column_lines = 0
- for l in nlines:
- ytext = y - column_lines * line_height
- column_lines += 1
- ui_bgl.draw_text(l, x, ytext, font_size, color)
-
-
-def draw_tooltip(x, y, name='', author='', quality='-', img=None, gravatar=None):
- region = bpy.context.region
- scale = bpy.context.preferences.view.ui_scale
- t = time.time()
-
- if not img or max(img.size[0], img.size[1]) == 0:
- return;
-
- x += 20
- y -= 20
- # first get image size scaled
- isizex = int(512 * scale * img.size[0] / min(img.size[0], img.size[1]))
- isizey = int(512 * scale * img.size[1] / min(img.size[0], img.size[1]))
-
- ttipmargin = 5 * scale
- # then do recurrent re-scaling, to know where to fit the tooltip
- estimated_height = 2 * ttipmargin + isizey
- if estimated_height > y:
- scaledown = y / (estimated_height)
- scale *= scaledown
-
- isizex = int(512 * scale * img.size[0] / min(img.size[0], img.size[1]))
- isizey = int(512 * scale * img.size[1] / min(img.size[0], img.size[1]))
-
- ttipmargin = 5 * scale
- textmargin = 12 * scale
-
- if gravatar is not None:
- overlay_height_base = 90
- else:
- overlay_height_base = 70
-
- overlay_height = overlay_height_base * scale
- name_height = int(20 * scale)
-
- width = isizex + 2 * ttipmargin
-
- properties_width = 0
- for r in bpy.context.area.regions:
- if r.type == 'UI':
- properties_width = r.width
-
- # limit to area borders
- x = min(x + width, region.width - properties_width) - width
-
- # define_colors
- background_color = bpy.context.preferences.themes[0].user_interface.wcol_tooltip.inner
- background_overlay = (background_color[0], background_color[1], background_color[2], .8)
- textcol = bpy.context.preferences.themes[0].user_interface.wcol_tooltip.text
- textcol = (textcol[0], textcol[1], textcol[2], 1)
-
- # background
- ui_bgl.draw_rect(x - ttipmargin,
- y - 2 * ttipmargin - isizey,
- isizex + ttipmargin * 2,
- 2 * ttipmargin + isizey,
- background_color)
-
- # main preview image
- ui_bgl.draw_image(x, y - isizey - ttipmargin, isizex, isizey, img, 1)
-
- # text overlay background
- ui_bgl.draw_rect(x - ttipmargin,
- y - 2 * ttipmargin - isizey,
- isizex + ttipmargin * 2,
- ttipmargin + overlay_height,
- background_overlay)
-
- # draw name
- name_x = x + textmargin
- name_y = y - isizey + overlay_height - textmargin - name_height
- ui_bgl.draw_text(name, name_x, name_y, name_height, textcol)
-
- # draw gravatar
- author_x_text = x + isizex - textmargin
- gravatar_size = overlay_height - 2 * textmargin
- gravatar_y = y - isizey - ttipmargin + textmargin
- if gravatar is not None:
- author_x_text -= gravatar_size + textmargin
- ui_bgl.draw_image(x + isizex - gravatar_size - textmargin,
- gravatar_y, # + textmargin,
- gravatar_size, gravatar_size, gravatar, 1)
-
- # draw author's name
- author_text_size = int(name_height * .7)
- ui_bgl.draw_text(author, author_x_text, gravatar_y, author_text_size, textcol, halign='RIGHT')
-
- # draw quality
- quality_text_size = int(name_height * 1)
- img = utils.get_thumbnail('star_grey.png')
- ui_bgl.draw_image(name_x, gravatar_y, quality_text_size, quality_text_size, img, .6)
- ui_bgl.draw_text(str(quality), name_x + quality_text_size + 5, gravatar_y, quality_text_size, textcol)
-
-
-def draw_tooltip_with_author(asset_data, x, y):
- # TODO move this lazy loading into a function and don't duplicate through the code
-
- img = get_large_thumbnail_image(asset_data)
- gimg = None
- tooltip_data = asset_data.get('tooltip_data')
- if tooltip_data is None:
- author_text = ''
-
- if bpy.context.window_manager.get('bkit authors') is not None:
- a = bpy.context.window_manager['bkit authors'].get(asset_data['author']['id'])
- if a is not None and a != '':
- if a.get('gravatarImg') is not None:
- gimg = utils.get_hidden_image(a['gravatarImg'], a['gravatarHash']).name
-
- if len(a['firstName']) > 0 or len(a['lastName']) > 0:
- author_text = f"by {a['firstName']} {a['lastName']}"
-
- aname = asset_data['displayName']
- aname = aname[0].upper() + aname[1:]
- if len(aname) > 36:
- aname = f"{aname[:33]}..."
-
- rc = asset_data.get('ratingsCount')
- show_rating_threshold = 0
- rcount = 0
- quality = '-'
- if rc:
- rcount = min(rc.get('quality', 0), rc.get('workingHours', 0))
- if rcount > show_rating_threshold:
- quality = round(asset_data['ratingsAverage'].get('quality'))
- tooltip_data = {
- 'aname': aname,
- 'author_text': author_text,
- 'quality': quality,
- 'gimg': gimg
- }
- asset_data['tooltip_data'] = tooltip_data
- gimg = tooltip_data['gimg']
- if gimg is not None:
- gimg = bpy.data.images[gimg]
-
- draw_tooltip(x, y, name=tooltip_data['aname'],
- author=tooltip_data['author_text'],
- quality=tooltip_data['quality'],
- img=img,
- gravatar=gimg)
-
-
-def draw_callback_2d(self, context):
- if not utils.guard_from_crash():
- return
-
- a = context.area
- w = context.window
- try:
- # self.area might throw error just by itself.
- a1 = self.area
- w1 = self.window
- go = True
- if len(a.spaces[0].region_quadviews) > 0:
- # print(dir(bpy.context.region_data))
- # print('quad', a.spaces[0].region_3d, a.spaces[0].region_quadviews[0])
- if a.spaces[0].region_3d != context.region_data:
- go = False
- except:
- # bpy.types.SpaceView3D.draw_handler_remove(self._handle_2d, 'WINDOW')
- # bpy.types.SpaceView3D.draw_handler_remove(self._handle_3d, 'WINDOW')
- go = False
- if go and a == a1 and w == w1:
-
- props = context.window_manager.blenderkitUI
- if props.down_up == 'SEARCH':
- draw_ratings_bgl()
- draw_asset_bar(self, context)
- elif props.down_up == 'UPLOAD':
- draw_callback_2d_upload_preview(self, context)
-
-
-def draw_downloader(x, y, percent=0, img=None, text=''):
- if img is not None:
- ui_bgl.draw_image(x, y, 50, 50, img, .5)
-
- ui_bgl.draw_rect(x, y, 50, int(0.5 * percent), (.2, 1, .2, .3))
- ui_bgl.draw_rect(x - 3, y - 3, 6, 6, (1, 0, 0, .3))
- # if asset_data is not None:
- # ui_bgl.draw_text(asset_data['name'], x, y, colors.TEXT)
- # ui_bgl.draw_text(asset_data['filesSize'])
- if text:
- ui_bgl.draw_text(text, x, y - 15, 12, colors.TEXT)
-
-
-def draw_progress(x, y, text='', percent=None, color=colors.GREEN):
- ui_bgl.draw_rect(x, y, percent, 5, color)
- ui_bgl.draw_text(text, x, y + 8, 16, color)
-
-
-def draw_callback_3d_progress(self, context):
- # 'star trek' mode is here
-
- if not utils.guard_from_crash():
- return
- for threaddata in download.download_threads:
- asset_data = threaddata[1]
- tcom = threaddata[2]
- if tcom.passargs.get('downloaders'):
- for d in tcom.passargs['downloaders']:
- if asset_data['assetType'] == 'model':
- draw_bbox(d['location'], d['rotation'], asset_data['bbox_min'], asset_data['bbox_max'],
- progress=tcom.progress)
-
-
-def draw_callback_2d_progress(self, context):
- if not utils.guard_from_crash():
- return
-
- green = (.2, 1, .2, .3)
- offset = 0
- row_height = 35
-
- ui = bpy.context.window_manager.blenderkitUI
-
- x = ui.reports_x
- y = ui.reports_y
- index = 0
- for threaddata in download.download_threads:
- asset_data = threaddata[1]
- tcom = threaddata[2]
-
- directory = paths.get_temp_dir('%s_search' % asset_data['assetType'])
- tpath = os.path.join(directory, asset_data['thumbnail_small'])
- img = utils.get_hidden_image(tpath, asset_data['id'])
-
- if tcom.passargs.get('downloaders'):
- for d in tcom.passargs['downloaders']:
-
- loc = view3d_utils.location_3d_to_region_2d(bpy.context.region, bpy.context.space_data.region_3d,
- d['location'])
- # print('drawing downloader')
- if loc is not None:
- if asset_data['assetType'] == 'model':
- # models now draw with star trek mode, no need to draw percent for the image.
- draw_downloader(loc[0], loc[1], percent=tcom.progress, img=img, text=tcom.report)
- else:
- draw_downloader(loc[0], loc[1], percent=tcom.progress, img=img, text=tcom.report)
- # utils.p('end drawing downlaoders downloader')
- else:
- draw_progress(x, y - index * 30, text='downloading %s' % asset_data['name'],
- percent=tcom.progress)
- index += 1
-
- for process in bg_blender.bg_processes:
- tcom = process[1]
- n = ''
- if tcom.name is not None:
- n = tcom.name + ': '
- draw_progress(x, y - index * 30, '%s' % n + tcom.lasttext,
- tcom.progress)
- index += 1
- for report in reports.reports:
- # print('drawing reports', x, y, report.text)
- report.draw(x, y - index * 30)
- index += 1
- report.fade()
-
-
-def draw_callback_2d_upload_preview(self, context):
- ui_props = context.window_manager.blenderkitUI
-
- props = utils.get_upload_props()
-
- # assets which don't need asset preview
- if ui_props.asset_type == 'HDR':
- return
-
- if props != None and ui_props.draw_tooltip:
-
- if ui_props.asset_type != 'BRUSH':
- ui_props.thumbnail_image = props.thumbnail
- else:
- b = utils.get_active_brush()
- ui_props.thumbnail_image = b.icon_filepath
-
- img = utils.get_hidden_image(ui_props.thumbnail_image, 'upload_preview')
-
- draw_tooltip(ui_props.bar_x, ui_props.bar_y, name=props.name, img=img)
-
-
-
-def get_large_thumbnail_image(asset_data):
- '''Get thumbnail image from asset data'''
- scene = bpy.context.scene
- ui_props = bpy.context.window_manager.blenderkitUI
- iname = utils.previmg_name(ui_props.active_index, fullsize=True)
- directory = paths.get_temp_dir('%s_search' % mappingdict[ui_props.asset_type])
- tpath = os.path.join(directory, asset_data['thumbnail'])
- # if asset_data['assetType'] == 'hdr':
- # tpath = os.path.join(directory, asset_data['thumbnail'])
- if not asset_data['thumbnail']:
- tpath = paths.get_addon_thumbnail_path('thumbnail_not_available.jpg')
-
- if asset_data['assetType'] == 'hdr':
- colorspace = 'Non-Color'
- else:
- colorspace = 'sRGB'
- img = utils.get_hidden_image(tpath, iname, colorspace=colorspace)
- return img
-
-
-def draw_asset_bar(self, context):
- s = bpy.context.scene
- ui_props = context.window_manager.blenderkitUI
- user_preferences = bpy.context.preferences.addons['blenderkit'].preferences
- is_validator = utils.profile_is_validator()
- r = self.region
- # hc = bpy.context.preferences.themes[0].view_3d.space.header
- # hc = bpy.context.preferences.themes[0].user_interface.wcol_menu_back.inner
- # hc = (hc[0], hc[1], hc[2], .2)
- hc = (1, 1, 1, .07)
- # grey1 = (hc.r * .55, hc.g * .55, hc.b * .55, 1)
- grey2 = (hc[0] * .8, hc[1] * .8, hc[2] * .8, .5)
- # grey1 = (hc.r, hc.g, hc.b, 1)
- white = (1, 1, 1, 0.2)
- green = (.2, 1, .2, .7)
- highlight = bpy.context.preferences.themes[0].user_interface.wcol_menu_item.inner_sel
- highlight = (1, 1, 1, .2)
- # highlight = (1, 1, 1, 0.8)
- # background of asset bar
- # if ui_props.hcount>0:
- # #this fixes a draw issue introduced in blender 2.91. draws a very small version of the image to avoid problems
- # # with alpha. Not sure why this works.
- # img = utils.get_thumbnail('arrow_left.png')
- # ui_bgl.draw_image(0, 0, 1,
- # 1,
- # img,
- # 1)
- if ui_props.hcount > 0 and ui_props.wcount > 0:
- search_results = bpy.context.window_manager.get('search results')
- search_results_orig = bpy.context.window_manager.get('search results orig')
- if search_results == None:
- return
- h_draw = min(ui_props.hcount, math.ceil(len(search_results) / ui_props.wcount))
-
- if ui_props.wcount > len(search_results):
- bar_width = len(search_results) * (ui_props.thumb_size + ui_props.margin) + ui_props.margin
- else:
- bar_width = ui_props.bar_width
- row_height = ui_props.thumb_size + ui_props.margin
- ui_bgl.draw_rect(ui_props.bar_x, ui_props.bar_y - ui_props.bar_height, bar_width,
- ui_props.bar_height, hc)
-
- if search_results is not None:
- if ui_props.scroll_offset > 0 or ui_props.wcount * ui_props.hcount < len(search_results):
- ui_props.drawoffset = 35
- else:
- ui_props.drawoffset = 0
-
- if ui_props.wcount * ui_props.hcount < len(search_results):
- # arrows
- arrow_y = ui_props.bar_y - int((ui_props.bar_height + ui_props.thumb_size) / 2) + ui_props.margin
- if ui_props.scroll_offset > 0:
-
- if ui_props.active_index == -2:
- ui_bgl.draw_rect(ui_props.bar_x, ui_props.bar_y - ui_props.bar_height, 25,
- ui_props.bar_height, highlight)
- img = utils.get_thumbnail('arrow_left.png')
- ui_bgl.draw_image(ui_props.bar_x, arrow_y, 25,
- ui_props.thumb_size,
- img,
- 1)
-
- if search_results_orig['count'] - ui_props.scroll_offset > (ui_props.wcount * ui_props.hcount) + 1:
- if ui_props.active_index == -1:
- ui_bgl.draw_rect(ui_props.bar_x + ui_props.bar_width - 25,
- ui_props.bar_y - ui_props.bar_height, 25,
- ui_props.bar_height,
- highlight)
- img1 = utils.get_thumbnail('arrow_right.png')
- ui_bgl.draw_image(ui_props.bar_x + ui_props.bar_width - 25,
- arrow_y, 25,
- ui_props.thumb_size, img1, 1)
- ar = context.window_manager.get('asset ratings')
- for b in range(0, h_draw):
- w_draw = min(ui_props.wcount, len(search_results) - b * ui_props.wcount - ui_props.scroll_offset)
-
- y = ui_props.bar_y - (b + 1) * (row_height)
- for a in range(0, w_draw):
- x = ui_props.bar_x + a * (
- ui_props.margin + ui_props.thumb_size) + ui_props.margin + ui_props.drawoffset
-
- #
- index = a + ui_props.scroll_offset + b * ui_props.wcount
- iname = utils.previmg_name(index)
- img = bpy.data.images.get(iname)
- if img is not None and img.size[0] > 0 and img.size[1] > 0:
- w = int(ui_props.thumb_size * img.size[0] / max(img.size[0], img.size[1]))
- h = int(ui_props.thumb_size * img.size[1] / max(img.size[0], img.size[1]))
- crop = (0, 0, 1, 1)
- if img.size[0] > img.size[1]:
- offset = (1 - img.size[1] / img.size[0]) / 2
- crop = (offset, 0, 1 - offset, 1)
-
- ui_bgl.draw_image(x, y, w, w, img, 1,
- crop=crop)
- if index == ui_props.active_index:
- ui_bgl.draw_rect(x - ui_props.highlight_margin, y - ui_props.highlight_margin,
- w + 2 * ui_props.highlight_margin, w + 2 * ui_props.highlight_margin,
- highlight)
- # if index == ui_props.active_index:
- # ui_bgl.draw_rect(x - highlight_margin, y - highlight_margin,
- # w + 2*highlight_margin, h + 2*highlight_margin , highlight)
-
- else:
- ui_bgl.draw_rect(x, y, ui_props.thumb_size, ui_props.thumb_size, grey2)
- ui_bgl.draw_text('loading', x + ui_props.thumb_size // 2, y + ui_props.thumb_size // 2,
- ui_props.thumb_size // 6, white, halign='CENTER', valign='CENTER')
-
- result = search_results[index]
- # code to inform validators that the validation is waiting too long and should be done asap
- if result['verificationStatus'] == 'uploaded':
- if is_validator:
- over_limit = utils.is_upload_old(result)
- if over_limit:
- redness = min(over_limit * .05, 0.5)
- red = (1, 0, 0, redness)
- ui_bgl.draw_rect(x, y, ui_props.thumb_size, ui_props.thumb_size, red)
-
- if result['downloaded'] > 0:
- ui_bgl.draw_rect(x, y, int(ui_props.thumb_size * result['downloaded'] / 100.0), 2, green)
- # object type icons - just a test..., adds clutter/ not so userfull:
- # icons = ('type_finished.png', 'type_template.png', 'type_particle_system.png')
-
- if (result.get('canDownload', True)) == 0:
- img = utils.get_thumbnail('locked.png')
- ui_bgl.draw_image(x + 2, y + 2, 24, 24, img, 1)
-
- # pcoll = icons.icon_collections["main"]
- # v_icon = pcoll['rejected']
- v_icon = verification_icons[result.get('verificationStatus', 'validated')]
-
- if v_icon is None and is_validator:
- # poke for validators to rate
- rating = ar.get(result['id'])
- if rating is not None:
- rating = rating.to_dict()
- if rating in (None, {}):
- v_icon = 'star_grey.png'
-
- if v_icon is not None:
- img = utils.get_thumbnail(v_icon)
- ui_bgl.draw_image(x + ui_props.thumb_size - 26, y + 2, 24, 24, img, 1)
-
- # if user_preferences.api_key == '':
- # report = 'Register on BlenderKit website to upload your own assets.'
- # ui_bgl.draw_text(report, ui_props.bar_x + ui_props.margin,
- # ui_props.bar_y - 25 - ui_props.margin - ui_props.bar_height, 15)
- # elif len(search_results) == 0:
- # report = 'BlenderKit - No matching results found.'
- # ui_bgl.draw_text(report, ui_props.bar_x + ui_props.margin,
- # ui_props.bar_y - 25 - ui_props.margin, 15)
- if ui_props.draw_tooltip and len(search_results) > ui_props.active_index:
- r = search_results[ui_props.active_index]
- draw_tooltip_with_author(r, ui_props.mouse_x, ui_props.mouse_y)
- s = bpy.context.scene
- props = utils.get_search_props()
- # if props.report != '' and props.is_searching or props.search_error:
- # ui_bgl.draw_text(props.report, ui_props.bar_x,
- # ui_props.bar_y - 15 - ui_props.margin - ui_props.bar_height, 15)
-
-
-def object_in_particle_collection(o):
- '''checks if an object is in a particle system as instance, to not snap to it and not to try to attach material.'''
- for p in bpy.data.particles:
- if p.render_type == 'COLLECTION':
- if p.instance_collection:
- for o1 in p.instance_collection.objects:
- if o1 == o:
- return True
- if p.render_type == 'COLLECTION':
- if p.instance_object == o:
- return True
- return False
-
-
-def deep_ray_cast(depsgraph, ray_origin, vec):
- # this allows to ignore some objects, like objects with bounding box draw style or particle objects
- object = None
- # while object is None or object.draw
- has_hit, snapped_location, snapped_normal, face_index, object, matrix = bpy.context.scene.ray_cast(
- depsgraph, ray_origin, vec)
- empty_set = False, Vector((0, 0, 0)), Vector((0, 0, 1)), None, None, None
- if not object:
- return empty_set
- try_object = object
- while try_object and (try_object.display_type == 'BOUNDS' or object_in_particle_collection(try_object)):
- ray_origin = snapped_location + vec.normalized() * 0.0003
- try_has_hit, try_snapped_location, try_snapped_normal, try_face_index, try_object, try_matrix = bpy.context.scene.ray_cast(
- depsgraph, ray_origin, vec)
- if try_has_hit:
- # this way only good hits are returned, otherwise
- has_hit, snapped_location, snapped_normal, face_index, object, matrix = try_has_hit, try_snapped_location, try_snapped_normal, try_face_index, try_object, try_matrix
- if not (object.display_type == 'BOUNDS' or object_in_particle_collection(
- try_object)): # or not object.visible_get()):
- return has_hit, snapped_location, snapped_normal, face_index, object, matrix
- return empty_set
-
-
-def mouse_raycast(context, mx, my):
- r = context.region
- rv3d = context.region_data
- coord = mx, my
- # get the ray from the viewport and mouse
- view_vector = view3d_utils.region_2d_to_vector_3d(r, rv3d, coord)
- if rv3d.view_perspective == 'CAMERA' and rv3d.is_perspective == False:
- # ortographic cameras don'w work with region_2d_to_origin_3d
- view_position = rv3d.view_matrix.inverted().translation
- ray_origin = view3d_utils.region_2d_to_location_3d(r, rv3d, coord, depth_location=view_position)
- else:
- ray_origin = view3d_utils.region_2d_to_origin_3d(r, rv3d, coord, clamp=1.0)
-
- ray_target = ray_origin + (view_vector * 1000000000)
-
- vec = ray_target - ray_origin
-
- has_hit, snapped_location, snapped_normal, face_index, object, matrix = deep_ray_cast(
- bpy.context.view_layer.depsgraph, ray_origin, vec)
-
- # backface snapping inversion
- if view_vector.angle(snapped_normal) < math.pi / 2:
- snapped_normal = -snapped_normal
- # print(has_hit, snapped_location, snapped_normal, face_index, object, matrix)
- # rote = mathutils.Euler((0, 0, math.pi))
- randoffset = math.pi
- if has_hit:
- props = bpy.context.window_manager.blenderkit_models
- up = Vector((0, 0, 1))
-
- if props.perpendicular_snap:
- if snapped_normal.z > 1 - props.perpendicular_snap_threshold:
- snapped_normal = Vector((0, 0, 1))
- elif snapped_normal.z < -1 + props.perpendicular_snap_threshold:
- snapped_normal = Vector((0, 0, -1))
- elif abs(snapped_normal.z) < props.perpendicular_snap_threshold:
- snapped_normal.z = 0
- snapped_normal.normalize()
-
- snapped_rotation = snapped_normal.to_track_quat('Z', 'Y').to_euler()
-
- if props.randomize_rotation and snapped_normal.angle(up) < math.radians(10.0):
- randoffset = props.offset_rotation_amount + math.pi + (
- random.random() - 0.5) * props.randomize_rotation_amount
- else:
- randoffset = props.offset_rotation_amount # we don't rotate this way on walls and ceilings. + math.pi
- # snapped_rotation.z += math.pi + (random.random() - 0.5) * .2
-
- else:
- snapped_rotation = mathutils.Quaternion((0, 0, 0, 0)).to_euler()
-
- snapped_rotation.rotate_axis('Z', randoffset)
-
- return has_hit, snapped_location, snapped_normal, snapped_rotation, face_index, object, matrix
-
-
-def floor_raycast(context, mx, my):
- r = context.region
- rv3d = context.region_data
- coord = mx, my
-
- # get the ray from the viewport and mouse
- view_vector = view3d_utils.region_2d_to_vector_3d(r, rv3d, coord)
- ray_origin = view3d_utils.region_2d_to_origin_3d(r, rv3d, coord)
- ray_target = ray_origin + (view_vector * 1000)
-
- # various intersection plane normals are needed for corner cases that might actually happen quite often - in front and side view.
- # default plane normal is scene floor.
- plane_normal = (0, 0, 1)
- if math.isclose(view_vector.x, 0, abs_tol=1e-4) and math.isclose(view_vector.z, 0, abs_tol=1e-4):
- plane_normal = (0, 1, 0)
- elif math.isclose(view_vector.z, 0, abs_tol=1e-4):
- plane_normal = (1, 0, 0)
-
- snapped_location = mathutils.geometry.intersect_line_plane(ray_origin, ray_target, (0, 0, 0), plane_normal,
- False)
- if snapped_location != None:
- has_hit = True
- snapped_normal = Vector((0, 0, 1))
- face_index = None
- object = None
- matrix = None
- snapped_rotation = snapped_normal.to_track_quat('Z', 'Y').to_euler()
- props = bpy.context.window_manager.blenderkit_models
- if props.randomize_rotation:
- randoffset = props.offset_rotation_amount + math.pi + (
- random.random() - 0.5) * props.randomize_rotation_amount
- else:
- randoffset = props.offset_rotation_amount + math.pi
- snapped_rotation.rotate_axis('Z', randoffset)
-
- return has_hit, snapped_location, snapped_normal, snapped_rotation, face_index, object, matrix
-
-
-def is_rating_possible():
- ao = bpy.context.active_object
- ui = bpy.context.window_manager.blenderkitUI
- preferences = bpy.context.preferences.addons['blenderkit'].preferences
- # first test if user is logged in.
- if preferences.api_key == '':
- return False, False, None, None
- if bpy.context.scene.get('assets rated') is not None and ui.down_up == 'SEARCH':
- if bpy.context.mode in ('SCULPT', 'PAINT_TEXTURE'):
- b = utils.get_active_brush()
- ad = b.get('asset_data')
- if ad is not None:
- rated = bpy.context.scene['assets rated'].get(ad['assetBaseId'])
- return True, rated, b, ad
- if ao is not None:
- ad = None
- # crawl parents to reach active asset. there could have been parenting so we need to find the first onw
- ao_check = ao
- while ad is None or (ad is None and ao_check.parent is not None):
- s = bpy.context.scene
- ad = ao_check.get('asset_data')
- if ad is not None and ad.get('assetBaseId') is not None:
-
- s['assets rated'] = s.get('assets rated', {})
- rated = s['assets rated'].get(ad['assetBaseId'])
- # originally hidden for already rated assets
- return True, rated, ao_check, ad
- elif ao_check.parent is not None:
- ao_check = ao_check.parent
- else:
- break
- # check also materials
- m = ao.active_material
- if m is not None:
- ad = m.get('asset_data')
-
- if ad is not None and ad.get('assetBaseId'):
- rated = bpy.context.scene['assets rated'].get(ad['assetBaseId'])
- return True, rated, m, ad
-
- # if t>2 and t<2.5:
- # ui_props.rating_on = False
-
- return False, False, None, None
-
-
-def interact_rating(r, mx, my, event):
- ui = bpy.context.window_manager.blenderkitUI
- rating_possible, rated, asset, asset_data = is_rating_possible()
- if rating_possible:
- bkit_ratings = asset.bkit_ratings
-
- t = time.time() - ui.last_rating_time
- if bpy.context.mode in ('SCULPT', 'PAINT_TEXTURE'):
- accept_value = 'PRESS'
- else:
- accept_value = 'RELEASE'
-
- if ui.rating_button_on and event.type == 'LEFTMOUSE' and event.value == accept_value:
- if mouse_in_area(mx, my,
- ui.rating_x,
- ui.rating_y - ui.rating_button_width,
- ui.rating_button_width * 2,
- ui.rating_button_width):
- # ui.rating_menu_on = True
- ctx = utils.get_fake_context(bpy.context, area_type='VIEW_3D')
- bpy.ops.wm.blenderkit_menu_rating_upload(ctx, 'INVOKE_DEFAULT', asset_name=asset_data['name'],
- asset_id=asset_data['id'],
- asset_type=asset_data['assetType'])
- return True
- return False
-
-
-def mouse_in_area(mx, my, x, y, w, h):
- if x < mx < x + w and y < my < y + h:
- return True
- else:
- return False
-
-
-def mouse_in_asset_bar(mx, my):
- ui_props = bpy.context.window_manager.blenderkitUI
- # search_results = bpy.context.window_manager.get('search results')
- # if search_results == None:
- # return False
- #
- # w_draw1 = min(ui_props.wcount + 1, len(search_results) - b * ui_props.wcount - ui_props.scroll_offset)
- # end = ui_props.bar_x + (w_draw1) * (
- # ui_props.margin + ui_props.thumb_size) + ui_props.margin + ui_props.drawoffset + 25
-
- if ui_props.bar_y - ui_props.bar_height < my < ui_props.bar_y \
- and mx > ui_props.bar_x and mx < ui_props.bar_x + ui_props.bar_width:
- return True
- else:
- return False
-
-
-def mouse_in_region(r, mx, my):
- if 0 < my < r.height and 0 < mx < r.width:
- return True
- else:
- return False
-
-
-def update_ui_size(area, region):
- if bpy.app.background or not area:
- return
- ui = bpy.context.window_manager.blenderkitUI
- user_preferences = bpy.context.preferences.addons['blenderkit'].preferences
- ui_scale = bpy.context.preferences.view.ui_scale
-
- ui.margin = ui.bl_rna.properties['margin'].default * ui_scale
- ui.thumb_size = user_preferences.thumb_size * 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':
- ui.bar_x = r.width * reg_multiplier + ui.margin + ui.bar_x_offset * ui_scale
- elif r.type == 'UI':
- ui.bar_end = r.width * reg_multiplier + 100 * ui_scale
-
- ui.bar_width = region.width - ui.bar_x - ui.bar_end
- ui.wcount = math.floor(
- (ui.bar_width - 2 * ui.drawoffset) / (ui.thumb_size + ui.margin))
-
- search_results = bpy.context.window_manager.get('search results')
- if search_results != None and ui.wcount > 0:
- ui.hcount = min(user_preferences.max_assetbar_rows, math.ceil(len(search_results) / ui.wcount))
- else:
- ui.hcount = 1
- ui.bar_height = (ui.thumb_size + ui.margin) * ui.hcount + ui.margin
- ui.bar_y = region.height - ui.bar_y_offset * ui_scale
- if ui.down_up == 'UPLOAD':
- ui.reports_y = ui.bar_y - 600
- ui.reports_x = ui.bar_x
- else:
- ui.reports_y = ui.bar_y - ui.bar_height - 100
- ui.reports_x = ui.bar_x
-
- ui.rating_x = ui.bar_x
- ui.rating_y = ui.bar_y - ui.bar_height
-
-
-class ParticlesDropDialog(bpy.types.Operator):
- """Tooltip"""
- bl_idname = "object.blenderkit_particles_drop"
- bl_label = "BlenderKit particle plants object drop"
- bl_options = {'REGISTER', 'INTERNAL'}
-
- asset_search_index: IntProperty(name="Asset index",
- description="Index of the asset in asset bar",
- default=0,
- )
-
- model_location: FloatVectorProperty(name="Location",
- default=(0, 0, 0))
-
- model_rotation: FloatVectorProperty(name="Rotation",
- default=(0, 0, 0),
- subtype='QUATERNION')
-
- target_object: StringProperty(
- name="Target object",
- description="The object to which the particles will get applied",
- default="", options={'SKIP_SAVE'})
-
- @classmethod
- def poll(cls, context):
- return True
-
- def draw(self, context):
- layout = self.layout
- message = 'This asset is a particle setup. BlenderKit can apply particles to the active/drag-drop object.' \
- 'The number of particles is caluclated automatically, but if there are 2 many particles,' \
- ' BlenderKit can do the following steps to make sure Blender continues to run:' \
- '\n1.Switch to bounding box view of the particles.' \
- '\n2.Turn down number of particles that are shown in the view.' \
- '\n3.Hide the particle system completely from the 3D view.' \
- "as a result of this, it's possible you'll see the particle setup only in render view or " \
- "rendered images. You should still be careful and test particle systems on smaller objects first."
- utils.label_multiline(layout, text=message, width=400)
-
- def execute(self, context):
- bpy.ops.scene.blenderkit_download(True,
- # asset_type=ui_props.asset_type,
- asset_index=self.asset_search_index,
- model_location=self.model_rotation,
- model_rotation=self.model_rotation,
- target_object=self.target_object)
- return {'FINISHED'}
-
- def invoke(self, context, event):
- wm = context.window_manager
- return wm.invoke_props_dialog(self, width=400)
-
-
-# class MaterialDropDialog(bpy.types.Operator):
-# """Tooltip"""
-# bl_idname = "object.blenderkit_material_drop"
-# bl_label = "BlenderKit material drop on linked objects"
-# bl_options = {'REGISTER', 'INTERNAL'}
-#
-# asset_search_index: IntProperty(name="Asset index",
-# description="Index of the asset in asset bar",
-# default=0,
-# )
-#
-# model_location: FloatVectorProperty(name="Location",
-# default=(0, 0, 0))
-#
-# model_rotation: FloatVectorProperty(name="Rotation",
-# default=(0, 0, 0),
-# subtype='QUATERNION')
-#
-# target_object: StringProperty(
-# name="Target object",
-# description="The object to which the particles will get applied",
-# default="", options={'SKIP_SAVE'})
-#
-# target_material_slot: IntProperty(name="Target material slot",
-# description="Index of the material on the object to be changed",
-# default=0,
-# )
-#
-# @classmethod
-# def poll(cls, context):
-# return True
-#
-# def draw(self, context):
-# layout = self.layout
-# message = "This asset is linked to the scene from an external file and cannot have material appended." \
-# " Do you want to bring it into Blender Scene?"
-# utils.label_multiline(layout, text=message, width=400)
-#
-# def execute(self, context):
-# for c in bpy.data.collections:
-# for o in c.objects:
-# if o.name != self.target_object:
-# continue;
-# for empty in bpy.context.visible_objects:
-# if not(empty.instance_type == 'COLLECTION' and empty.instance_collection == c):
-# continue;
-# utils.activate(empty)
-# break;
-# bpy.ops.object.blenderkit_bring_to_scene()
-# bpy.ops.scene.blenderkit_download(True,
-# # asset_type=ui_props.asset_type,
-# asset_index=self.asset_search_index,
-# model_location=self.model_rotation,
-# model_rotation=self.model_rotation,
-# target_object=self.target_object,
-# material_target_slot = self.target_slot)
-# return {'FINISHED'}
-#
-# def invoke(self, context, event):
-# wm = context.window_manager
-# return wm.invoke_props_dialog(self, width=400)
-
-class AssetBarOperator(bpy.types.Operator):
- '''runs search and displays the asset bar at the same time'''
- bl_idname = "view3d.blenderkit_asset_bar"
- bl_label = "BlenderKit Asset Bar UI"
- bl_options = {'REGISTER', 'UNDO', 'INTERNAL'}
-
- do_search: BoolProperty(name="Run Search", description='', default=True, options={'SKIP_SAVE'})
- keep_running: BoolProperty(name="Keep Running", description='', default=True, options={'SKIP_SAVE'})
- free_only: BoolProperty(name="Free first", description='', default=False, options={'SKIP_SAVE'})
-
- category: StringProperty(
- name="Category",
- description="search only subtree of this category",
- default="", options={'SKIP_SAVE'})
-
- tooltip: bpy.props.StringProperty(default='runs search and displays the asset bar at the same time')
-
- @classmethod
- def description(cls, context, properties):
- return properties.tooltip
-
- def search_more(self):
- sro = bpy.context.window_manager.get('search results orig')
- if sro is None:
- return;
- if sro.get('next') is None:
- return
- search_props = utils.get_search_props()
- if search_props.is_searching:
- return
-
- search.search(get_next=True)
-
- def exit_modal(self):
- try:
- bpy.types.SpaceView3D.draw_handler_remove(self._handle_2d, 'WINDOW')
- except:
- pass;
- ui_props = bpy.context.window_manager.blenderkitUI
-
- # ui_props.tooltip = ''
- ui_props.active_index = -3
- ui_props.draw_drag_image = False
- ui_props.draw_snapped_bounds = False
- ui_props.has_hit = False
- ui_props.assetbar_on = False
-
- def modal(self, context, event):
-
- # This is for case of closing the area or changing type:
- ui_props = context.window_manager.blenderkitUI
- user_preferences = bpy.context.preferences.addons['blenderkit'].preferences
-
- areas = []
-
- # timers testing - seems timers might be causing crashes. testing it this way now.
- if not user_preferences.use_timers:
- search.search_timer()
- download.download_timer()
- tasks_queue.queue_worker()
- bg_blender.bg_update()
-
- if bpy.context.scene != self.scene:
- self.exit_modal()
- return {'CANCELLED'}
-
- for w in context.window_manager.windows:
- areas.extend(w.screen.areas)
-
- if self.area not in areas or self.area.type != 'VIEW_3D' or self.has_quad_views != (
- len(self.area.spaces[0].region_quadviews) > 0):
- # print('search areas') bpy.context.area.spaces[0].region_quadviews
- # stopping here model by now - because of:
- # switching layouts or maximizing area now fails to assign new area throwing the bug
- # internal error: modal gizmo-map handler has invalid area
- self.exit_modal()
- return {'CANCELLED'}
-
- newarea = None
- for a in context.window.screen.areas:
- if a.type == 'VIEW_3D':
- self.area = a
- for r in a.regions:
- if r.type == 'WINDOW':
- self.region = r
- newarea = a
- break
- # context.area = a
-
- # we check again and quit if things weren't fixed this way.
- if newarea == None:
- self.exit_modal()
- return {'CANCELLED'}
-
- update_ui_size(self.area, self.region)
-
- # this was here to check if sculpt stroke is running, but obviously that didn't help,
- # since the RELEASE event is cought by operator and thus there is no way to detect a stroke has ended...
- if bpy.context.mode in ('SCULPT', 'PAINT_TEXTURE'):
- if event.type == 'MOUSEMOVE': # ASSUME THAT SCULPT OPERATOR ACTUALLY STEALS THESE EVENTS,
- # SO WHEN THERE ARE SOME WE CAN APPEND BRUSH...
- bpy.context.window_manager['appendable'] = True
- if event.type == 'LEFTMOUSE':
- if event.value == 'PRESS':
- bpy.context.window_manager['appendable'] = False
-
- self.area.tag_redraw()
- s = context.scene
-
- if ui_props.turn_off:
- ui_props.turn_off = False
- self.exit_modal()
- ui_props.draw_tooltip = False
- return {'CANCELLED'}
-
- if context.region != self.region:
- # print(time.time(), 'pass through because of region')
- # print(context.region.type, self.region.type)
- return {'PASS_THROUGH'}
-
- if ui_props.down_up == 'UPLOAD':
-
- ui_props.mouse_x = 0
- ui_props.mouse_y = self.region.height
-
- ui_props.draw_tooltip = True
-
- # only generate tooltip once in a while
- if (
- event.type == 'LEFTMOUSE' or event.type == 'RIGHTMOUSE') and event.value == 'RELEASE' or event.type == 'ENTER':
- ao = bpy.context.active_object
- if ui_props.asset_type == 'MODEL' and ao != None \
- or ui_props.asset_type == 'MATERIAL' and ao != None and ao.active_material != None \
- or ui_props.asset_type == 'BRUSH' and utils.get_active_brush() is not None \
- or ui_props.asset_type == 'SCENE' or ui_props.asset_type == 'HDR':
- export_data, upload_data = upload.get_upload_data(context=context, asset_type=ui_props.asset_type)
- # if upload_data:
- # # print(upload_data)
- # ui_props.tooltip = upload_data['displayName'] # search.generate_tooltip(upload_data)
-
- return {'PASS_THROUGH'}
-
- # TODO add one more condition here to take less performance.
- r = self.region
- s = bpy.context.scene
- sr = bpy.context.window_manager.get('search results')
- search_results_orig = bpy.context.window_manager.get('search results orig')
- # If there aren't any results, we need no interaction(yet)
- if sr is None:
- return {'PASS_THROUGH'}
- if len(sr) - ui_props.scroll_offset < (ui_props.wcount * user_preferences.max_assetbar_rows) + 15:
- self.search_more()
-
- if event.type == 'WHEELUPMOUSE' or event.type == 'WHEELDOWNMOUSE' or event.type == 'TRACKPADPAN':
- # scrolling
- mx = event.mouse_region_x
- my = event.mouse_region_y
-
- if not mouse_in_asset_bar(mx, my):
- return {'PASS_THROUGH'}
-
- # note - TRACKPADPAN is unsupported in blender by now.
- # if event.type == 'TRACKPADPAN' :
- # print(dir(event))
- # print(event.value, event.oskey, event.)
- if (event.type == 'WHEELDOWNMOUSE') and len(sr) - ui_props.scroll_offset > (
- ui_props.wcount * ui_props.hcount):
- if ui_props.hcount > 1:
- ui_props.scroll_offset += ui_props.wcount
- else:
- ui_props.scroll_offset += 1
- if len(sr) - ui_props.scroll_offset < (ui_props.wcount * ui_props.hcount):
- ui_props.scroll_offset = len(sr) - (ui_props.wcount * ui_props.hcount)
-
- if event.type == 'WHEELUPMOUSE' and ui_props.scroll_offset > 0:
- if ui_props.hcount > 1:
- ui_props.scroll_offset -= ui_props.wcount
- else:
- ui_props.scroll_offset -= 1
- if ui_props.scroll_offset < 0:
- ui_props.scroll_offset = 0
-
- return {'RUNNING_MODAL'}
- if event.type == 'MOUSEMOVE': # Apply
-
- r = self.region
- mx = event.mouse_region_x
- my = event.mouse_region_y
-
- ui_props.mouse_x = mx
- ui_props.mouse_y = my
-
- if not mouse_in_asset_bar(mx, my): #
-
- ui_props.active_index = -3
- ui_props.draw_drag_image = False
- ui_props.draw_snapped_bounds = False
- ui_props.draw_tooltip = False
- bpy.context.window.cursor_set("DEFAULT")
- return {'PASS_THROUGH'}
-
- sr = bpy.context.window_manager['search results']
-
- bpy.context.window.cursor_set("HAND")
-
- if sr != None and ui_props.wcount * ui_props.hcount > len(sr) and ui_props.scroll_offset > 0:
- ui_props.scroll_offset = 0
-
- asset_search_index = get_asset_under_mouse(mx, my)
- ui_props.active_index = asset_search_index
- if asset_search_index > -1:
-
- asset_data = sr[asset_search_index]
- ui_props.draw_tooltip = True
-
- # ui_props.tooltip = asset_data['tooltip']
- # bpy.ops.wm.call_menu(name='OBJECT_MT_blenderkit_asset_menu')
-
- else:
- ui_props.draw_tooltip = False
-
- if mx > ui_props.bar_x + ui_props.bar_width - 50 and search_results_orig[
- 'count'] - ui_props.scroll_offset > (
- ui_props.wcount * ui_props.hcount) + 1:
- ui_props.active_index = -1
- return {'RUNNING_MODAL'}
- if mx < ui_props.bar_x + 50 and ui_props.scroll_offset > 0:
- ui_props.active_index = -2
- return {'RUNNING_MODAL'}
-
- return {'RUNNING_MODAL'}
-
- if event.type == 'RIGHTMOUSE':
- mx = event.mouse_x - r.x
- my = event.mouse_y - r.y
-
- if event.value == 'PRESS' and mouse_in_asset_bar(mx, my) and ui_props.active_index > -1:
- # context.window.cursor_warp(event.mouse_x - 300, event.mouse_y - 10);
-
- bpy.ops.wm.blenderkit_asset_popup('INVOKE_DEFAULT')
- # context.window.cursor_warp(event.mouse_x, event.mouse_y);
-
- # bpy.ops.wm.call_menu(name='OBJECT_MT_blenderkit_asset_menu')
- return {'RUNNING_MODAL'}
-
- if event.type == 'LEFTMOUSE':
-
- r = self.region
- mx = event.mouse_region_x
- my = event.mouse_region_y
-
- ui_props = context.window_manager.blenderkitUI
- if event.value == 'PRESS' and ui_props.active_index > -1:
- # start dragging models and materials
- bpy.ops.view3d.asset_drag_drop('INVOKE_DEFAULT',
- asset_search_index=ui_props.active_index)
- # ui_props.draw_tooltip = False
-
- if ui_props.rating_on:
- res = interact_rating(r, mx, my, event)
- if res:
- return {'RUNNING_MODAL'}
-
- if not mouse_in_asset_bar(mx, my):
- return {'PASS_THROUGH'}
-
- # this can happen by switching result asset types - length of search result changes
- if ui_props.scroll_offset > 0 and (ui_props.wcount * ui_props.hcount) > len(sr) - ui_props.scroll_offset:
- ui_props.scroll_offset = len(sr) - (ui_props.wcount * ui_props.hcount)
-
- if event.value == 'RELEASE': # Confirm
- # ui_props.drag_init = False
-
- # scroll with buttons by a whole page
- if mx > ui_props.bar_x + ui_props.bar_width - 50 and len(
- sr) - ui_props.scroll_offset > ui_props.wcount * ui_props.hcount:
- ui_props.scroll_offset = min(
- ui_props.scroll_offset + (ui_props.wcount * ui_props.hcount),
- len(sr) - ui_props.wcount * ui_props.hcount)
- return {'RUNNING_MODAL'}
- if mx < ui_props.bar_x + 50 and ui_props.scroll_offset > 0:
- ui_props.scroll_offset = max(0, ui_props.scroll_offset - ui_props.wcount * ui_props.hcount)
- return {'RUNNING_MODAL'}
-
- if ui_props.active_index == -3:
- return {'RUNNING_MODAL'}
- else:
- return {'RUNNING_MODAL'}
-
- if event.type == 'W' and ui_props.active_index > -1:
- sr = bpy.context.window_manager['search results']
- asset_data = sr[ui_props.active_index]
- a = bpy.context.window_manager['bkit authors'].get(asset_data['author']['id'])
- if a is not None:
- utils.p('author:', a)
- if a.get('aboutMeUrl') is not None:
- bpy.ops.wm.url_open(url=a['aboutMeUrl'])
- return {'RUNNING_MODAL'}
- if event.type == 'A' and ui_props.active_index > -1:
- sr = bpy.context.window_manager['search results']
- asset_data = sr[ui_props.active_index]
- a = asset_data['author']['id']
- if a is not None:
- sprops = utils.get_search_props()
- sprops.search_keywords = ''
- sprops.search_verification_status = 'ALL'
- utils.p('author:', a)
- search.search(author_id=a)
- return {'RUNNING_MODAL'}
-
- if event.type == 'X' and ui_props.active_index > -1:
- # delete downloaded files for this asset
- sr = bpy.context.window_manager['search results']
- asset_data = sr[ui_props.active_index]
- bk_logger.info('delete asset from local drive:' + asset_data['name'])
- paths.delete_asset_debug(asset_data)
- asset_data['downloaded'] = 0
- return {'RUNNING_MODAL'}
- return {'PASS_THROUGH'}
-
- def invoke(self, context, event):
- # FIRST START SEARCH
- ui_props = context.window_manager.blenderkitUI
- sr = bpy.context.window_manager.get('search results')
-
- if self.do_search:
- # we erase search keywords for cateogry search now, since these combinations usually return nothing now.
- # when the db gets bigger, this can be deleted.
- if self.category != '':
- sprops = utils.get_search_props()
- sprops.search_keywords = ''
- search.search(category=self.category)
-
- if ui_props.assetbar_on:
- # we don't want to run the assetbar more than once, that's why it has a switch on/off behaviour,
- # unless being called with 'keep_running' prop.
- if not self.keep_running:
- # this sends message to the originally running operator, so it quits, and then it ends this one too.
- # If it initiated a search, the search will finish in a thread. The switch off procedure is run
- # by the 'original' operator, since if we get here, it means
- # same operator is already running.
- ui_props.turn_off = True
- # if there was an error, we need to turn off these props so we can restart after 2 clicks
- ui_props.assetbar_on = False
-
- else:
- pass
- return {'FINISHED'}
-
- ui_props.dragging = False # only for cases where assetbar ended with an error.
- ui_props.assetbar_on = True
- ui_props.turn_off = False
-
- if sr is None:
- bpy.context.window_manager['search results'] = []
-
- if context.area.type != 'VIEW_3D':
- self.report({'WARNING'}, "View3D not found, cannot run operator")
- return {'CANCELLED'}
-
- # the arguments we pass the the callback
- args = (self, context)
-
- self.window = context.window
- self.area = context.area
- self.scene = bpy.context.scene
-
- self.has_quad_views = len(bpy.context.area.spaces[0].region_quadviews) > 0
-
- for r in self.area.regions:
- if r.type == 'WINDOW':
- self.region = r
-
- global active_window_pointer, active_area_pointer, active_region_pointer
- active_window_pointer = self.window.as_pointer()
- active_area_pointer = self.area.as_pointer()
- active_region_pointer = self.region.as_pointer()
-
- update_ui_size(self.area, self.region)
-
- self._handle_2d = bpy.types.SpaceView3D.draw_handler_add(draw_callback_2d, args, 'WINDOW', 'POST_PIXEL')
-
- 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
-
- context.window_manager.modal_handler_add(self)
- return {'RUNNING_MODAL'}
-
- def execute(self, context):
- return {'RUNNING_MODAL'}
-
-
-class TransferBlenderkitData(bpy.types.Operator):
- """Regenerate cobweb"""
- bl_idname = "object.blenderkit_data_trasnfer"
- bl_label = "Transfer BlenderKit data"
- bl_description = "Transfer blenderKit metadata from one object to another when fixing uploads with wrong parenting"
- bl_options = {'REGISTER', 'UNDO'}
-
- def execute(self, context):
- source_ob = bpy.context.active_object
- for target_ob in bpy.context.selected_objects:
- if target_ob != source_ob:
- target_ob.property_unset('blenderkit')
- for k in source_ob.keys():
- target_ob[k] = source_ob[k]
- source_ob.property_unset('blenderkit')
- return {'FINISHED'}
-
-
-class UndoWithContext(bpy.types.Operator):
- """Regenerate cobweb"""
- bl_idname = "wm.undo_push_context"
- bl_label = "BlnenderKit undo push"
- bl_description = "BlenderKit undo push with fixed context"
- bl_options = {'REGISTER', 'UNDO', 'INTERNAL'}
-
- # def modal(self, context, event):
- # return {'RUNNING_MODAL'}
-
- message: StringProperty('Undo Message', default='BlenderKit operation')
-
- def execute(self, context):
- # C_dict = utils.get_fake_context(context)
- # w, a, r = get_largest_area(area_type=area_type)
- # wm = bpy.context.window_manager#bpy.data.window_managers[0]
- # w = wm.windows[0]
- #
- # C_dict = {'window': w, 'screen': w.screen}
- # bpy.ops.ed.undo_push(C_dict, 'INVOKE_REGION_WIN', message=self.message)
- # bpy.ops.ed.undo_push('INVOKE_REGION_WIN', message=self.message)
-
- return {'FINISHED'}
-
-
-def draw_callback_dragging(self, context):
- try:
- img = bpy.data.images.get(self.iname)
- except:
- # self._handle = bpy.types.SpaceView3D.draw_handler_add(draw_callback_dragging, args, 'WINDOW', 'POST_PIXEL')
- # self._handle_3d = bpy.types.SpaceView3D.draw_handler_add(draw_callback_3d_dragging, args, 'WINDOW',
- # bpy.types.SpaceView3D.draw_handler_remove(self._handle,
- # bpy.types.SpaceView3D.draw_handler_remove(self._handle_3d, 'WINDOW')
-
- return
- linelength = 35
- scene = bpy.context.scene
- ui_props = bpy.context.window_manager.blenderkitUI
- ui_bgl.draw_image(self.mouse_x + linelength, self.mouse_y - linelength - ui_props.thumb_size,
- ui_props.thumb_size, ui_props.thumb_size, img, 1)
- ui_bgl.draw_line2d(self.mouse_x, self.mouse_y, self.mouse_x + linelength,
- self.mouse_y - linelength, 2, colors.WHITE)
-
-
-def draw_callback_3d_dragging(self, context):
- ''' Draw snapped bbox while dragging. '''
- if not utils.guard_from_crash():
- return
- ui_props = context.window_manager.blenderkitUI
- # print(ui_props.asset_type, self.has_hit, self.snapped_location)
- if ui_props.asset_type == 'MODEL':
- if self.has_hit:
- draw_bbox(self.snapped_location, self.snapped_rotation, self.snapped_bbox_min, self.snapped_bbox_max)
-
-
-def find_and_activate_instancers(object):
- for ob in bpy.context.visible_objects:
- if ob.instance_type == 'COLLECTION' and ob.instance_collection and object.name in ob.instance_collection.objects:
- utils.activate(ob)
- return ob
-
-
-class AssetDragOperator(bpy.types.Operator):
- """Drag & drop assets into scene"""
- bl_idname = "view3d.asset_drag_drop"
- bl_label = "BlenderKit asset drag drop"
-
- asset_search_index: IntProperty(name="Active Index", default=0)
- drag_length: IntProperty(name="Drag_length", default=0)
-
- object_name = None
-
- def handlers_remove(self):
- bpy.types.SpaceView3D.draw_handler_remove(self._handle, 'WINDOW')
- bpy.types.SpaceView3D.draw_handler_remove(self._handle_3d, 'WINDOW')
-
- def mouse_release(self):
- scene = bpy.context.scene
- ui_props = bpy.context.window_manager.blenderkitUI
-
- if ui_props.asset_type == 'MODEL':
- if not self.drag:
- self.snapped_location = scene.cursor.location
- self.snapped_rotation = (0, 0, 0)
-
- target_object = ''
- if self.object_name is not None:
- target_object = self.object_name
- target_slot = ''
-
- if 'particle_plants' in self.asset_data['tags']:
- bpy.ops.object.blenderkit_particles_drop("INVOKE_DEFAULT",
- asset_search_index=self.asset_search_index,
- model_location=self.snapped_location,
- model_rotation=self.snapped_rotation,
- target_object=target_object)
- else:
- bpy.ops.scene.blenderkit_download(True,
- # asset_type=ui_props.asset_type,
- asset_index=self.asset_search_index,
- model_location=self.snapped_location,
- model_rotation=self.snapped_rotation,
- target_object=target_object)
- if ui_props.asset_type == 'MATERIAL':
- object = None
- target_object = ''
- target_slot = ''
- if not self.drag:
- # click interaction
- object = bpy.context.active_object
- if object is None:
- ui_panels.ui_message(title='Nothing selected',
- message=f"Select something to assign materials by clicking.")
- return
- target_object = object.name
- target_slot = object.active_material_index
- self.snapped_location = object.location
- elif self.object_name is not None and self.has_hit:
-
- # first, test if object can have material applied.
- object = bpy.data.objects[self.object_name]
- # this enables to run Bring to scene automatically when dropping on a linked objects.
- # it's however quite a slow operation, that's why not enabled (and finished) now.
- # if object is not None and object.is_library_indirect:
- # find_and_activate_instancers(object)
- # bpy.ops.object.blenderkit_bring_to_scene()
- if object is not None and \
- not object.is_library_indirect and \
- object.type in utils.supported_material_drag:
-
- target_object = object.name
- # create final mesh to extract correct material slot
- depsgraph = bpy.context.evaluated_depsgraph_get()
- object_eval = object.evaluated_get(depsgraph)
-
- if object.type == 'MESH':
- temp_mesh = object_eval.to_mesh()
- target_slot = temp_mesh.polygons[self.face_index].material_index
- object_eval.to_mesh_clear()
- else:
- ui_props.snapped_location = object.location
- target_slot = object.active_material_index
-
- if not object:
- return
- if object.is_library_indirect:
- ui_panels.ui_message(title='This object is linked from outer file',
- message="Please select the model,"
- "go to the 'Selected Model' panel "
- "in BlenderKit and hit 'Bring to Scene' first.")
- return
- if object.type not in utils.supported_material_drag:
- if object.type in utils.supported_material_click:
- ui_panels.ui_message(title='Unsupported object type',
- message=f"Use click interaction for {object.type.lower()} object.")
- return
- else:
- ui_panels.ui_message(title='Unsupported object type',
- message=f"Can't assign materials to {object.type.lower()} object.")
- return
-
- if target_object != '':
- # position is for downloader:
- loc = self.snapped_location
- rotation = (0, 0, 0)
-
- utils.automap(target_object, target_slot=target_slot,
- tex_size=self.asset_data.get('texture_size_meters', 1.0))
- bpy.ops.scene.blenderkit_download(True,
- # asset_type=ui_props.asset_type,
- asset_index=self.asset_search_index,
- model_location=loc,
- model_rotation=rotation,
- target_object=target_object,
- material_target_slot=target_slot)
-
- if ui_props.asset_type == 'HDR':
- bpy.ops.scene.blenderkit_download('INVOKE_DEFAULT',
- asset_index=self.asset_search_index,
- # replace_resolution=True,
- invoke_resolution=True,
- max_resolution=self.asset_data.get('max_resolution', 0)
- )
-
- if ui_props.asset_type == 'SCENE':
- bpy.ops.scene.blenderkit_download('INVOKE_DEFAULT',
- asset_index=self.asset_search_index,
- # replace_resolution=True,
- invoke_resolution=False,
- invoke_scene_settings=True
- )
-
- if ui_props.asset_type == 'BRUSH':
- bpy.ops.scene.blenderkit_download( # asset_type=ui_props.asset_type,
- asset_index=self.asset_search_index,
- )
-
- def modal(self, context, event):
- scene = bpy.context.scene
- ui_props = bpy.context.window_manager.blenderkitUI
- context.area.tag_redraw()
-
- # if event.type == 'MOUSEMOVE':
- if not hasattr(self, 'start_mouse_x'):
- self.start_mouse_x = event.mouse_region_x
- self.start_mouse_y = event.mouse_region_y
-
- self.mouse_x = event.mouse_region_x
- self.mouse_y = event.mouse_region_y
-
- # are we dragging already?
- drag_threshold = 10
- if not self.drag and \
- (abs(self.start_mouse_x - self.mouse_x) > drag_threshold or \
- abs(self.start_mouse_y - self.mouse_y) > drag_threshold):
- self.drag = True
-
- if self.drag and ui_props.assetbar_on:
- # turn off asset bar here, shout start again after finishing drag drop.
- ui_props.turn_off = True
-
- if (event.type == 'ESC' or \
- not mouse_in_region(context.region, self.mouse_x, self.mouse_y)) and \
- (not self.drag or self.steps < 5):
- # this case is for canceling from inside popup card when there's an escape attempt to close the window
- return {'PASS_THROUGH'}
-
- if event.type in {'RIGHTMOUSE', 'ESC'} or \
- not mouse_in_region(context.region, self.mouse_x, self.mouse_y):
- self.handlers_remove()
- bpy.context.window.cursor_set("DEFAULT")
- ui_props.dragging = False
- bpy.ops.view3d.blenderkit_asset_bar_widget('INVOKE_REGION_WIN',
- do_search=False)
-
- return {'CANCELLED'}
-
- sprops = bpy.context.window_manager.blenderkit_models
- if event.type == 'WHEELUPMOUSE':
- sprops.offset_rotation_amount += sprops.offset_rotation_step
- return {'RUNNING_MODAL'}
- elif event.type == 'WHEELDOWNMOUSE':
- sprops.offset_rotation_amount -= sprops.offset_rotation_step
- return {'RUNNING_MODAL'}
-
- if event.type == 'MOUSEMOVE':
-
- #### TODO - this snapping code below is 3x in this file.... refactor it.
- self.has_hit, self.snapped_location, self.snapped_normal, self.snapped_rotation, self.face_index, object, self.matrix = mouse_raycast(
- context, event.mouse_region_x, event.mouse_region_y)
- if object is not None:
- self.object_name = object.name
-
- # MODELS can be dragged on scene floor
- if not self.has_hit and ui_props.asset_type == 'MODEL':
- self.has_hit, self.snapped_location, self.snapped_normal, self.snapped_rotation, self.face_index, object, self.matrix = floor_raycast(
- context,
- event.mouse_region_x, event.mouse_region_y)
- if object is not None:
- self.object_name = object.name
-
- if ui_props.asset_type == 'MODEL':
- self.snapped_bbox_min = Vector(self.asset_data['bbox_min'])
- self.snapped_bbox_max = Vector(self.asset_data['bbox_max'])
- #return {'RUNNING_MODAL'}
-
- if event.type == 'LEFTMOUSE' and event.value == 'RELEASE':
- self.mouse_release() # does the main job with assets
- self.handlers_remove()
- bpy.context.window.cursor_set("DEFAULT")
-
- bpy.ops.object.run_assetbar_fix_context(keep_running=True, do_search=False)
- ui_props.dragging = False
- return {'FINISHED'}
-
- self.steps += 1
-
- #pass event to assetbar so it can close itself
- if ui_props.assetbar_on and ui_props.turn_off:
- return {'PASS_THROUGH'}
-
- return {'RUNNING_MODAL'}
-
- def invoke(self, context, event):
- if context.area.type == 'VIEW_3D':
- # the arguments we pass the the callback
- args = (self, context)
- # Add the region OpenGL drawing callback
- # draw in view space with 'POST_VIEW' and 'PRE_VIEW'
- self.iname = utils.previmg_name(self.asset_search_index)
-
- self.mouse_x = 0
- self.mouse_y = 0
- self.steps = 0
-
- self.has_hit = False
- self.snapped_location = (0, 0, 0)
- self.snapped_normal = (0, 0, 1)
- self.snapped_rotation = (0, 0, 0)
- self.face_index = 0
- object = None
- self.matrix = None
-
- sr = bpy.context.window_manager['search results']
- self.asset_data = sr[self.asset_search_index]
-
- if not self.asset_data.get('canDownload'):
- message = "Let's support asset creators and Open source."
- link_text = 'Unlock the asset.'
- url = paths.get_bkit_url() + '/get-blenderkit/' + self.asset_data['id'] + '/?from_addon=True'
- bpy.ops.wm.blenderkit_url_dialog('INVOKE_REGION_WIN', url=url, message=message,
- link_text=link_text)
-
- return {'CANCELLED'}
-
- self._handle = bpy.types.SpaceView3D.draw_handler_add(draw_callback_dragging, args, 'WINDOW', 'POST_PIXEL')
- self._handle_3d = bpy.types.SpaceView3D.draw_handler_add(draw_callback_3d_dragging, args, 'WINDOW',
- 'POST_VIEW')
-
- bpy.context.window.cursor_set("NONE")
- ui_props = bpy.context.window_manager.blenderkitUI
- ui_props.dragging = True
- self.drag = False
- context.window_manager.modal_handler_add(self)
- return {'RUNNING_MODAL'}
- else:
- self.report({'WARNING'}, "View3D not found, cannot run operator")
- return {'CANCELLED'}
-
-
-class RunAssetBarWithContext(bpy.types.Operator):
- """Regenerate cobweb"""
- bl_idname = "object.run_assetbar_fix_context"
- bl_label = "BlnenderKit assetbar with fixed context"
- 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'}
-
- def execute(self, context):
- C_dict = utils.get_fake_context(context)
- if C_dict.get('window'): # no 3d view, no asset bar.
- preferences = bpy.context.preferences.addons['blenderkit'].preferences
- if 1:#preferences.experimental_features:
- 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=self.keep_running,
- do_search=self.do_search)
- return {'FINISHED'}
-
-
-classes = (
- AssetBarOperator,
- # AssetBarExperiment,
- AssetDragOperator,
- RunAssetBarWithContext,
- TransferBlenderkitData,
- UndoWithContext,
- ParticlesDropDialog
-)
-
-# store keymap items here to access after registration
-addon_keymapitems = []
-
-
-# @persistent
-def pre_load(context):
- ui_props = bpy.context.window_manager.blenderkitUI
- ui_props.assetbar_on = False
- ui_props.turn_off = True
- preferences = bpy.context.preferences.addons['blenderkit'].preferences
- preferences.login_attempt = False
-
-
-def register_ui():
- global handler_2d, handler_3d
-
- for c in classes:
- bpy.utils.register_class(c)
-
- args = (None, bpy.context)
-
- handler_2d = bpy.types.SpaceView3D.draw_handler_add(draw_callback_2d_progress, args, 'WINDOW', 'POST_PIXEL')
- handler_3d = bpy.types.SpaceView3D.draw_handler_add(draw_callback_3d_progress, args, 'WINDOW', 'POST_VIEW')
-
- wm = bpy.context.window_manager
-
- # spaces solved by registering shortcut to Window. Couldn't register object mode before somehow.
- if not wm.keyconfigs.addon:
- return
- km = wm.keyconfigs.addon.keymaps.new(name="Window", space_type='EMPTY')
- # asset bar shortcut
- 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)
- # fast rating shortcut
- wm = bpy.context.window_manager
- km = wm.keyconfigs.addon.keymaps['Window']
- # kmi = km.keymap_items.new(ratings.FastRateMenu.bl_idname, 'R', 'PRESS', ctrl=False, shift=False)
- # addon_keymapitems.append(kmi)
- # kmi = km.keymap_items.new(upload.FastMetadata.bl_idname, 'F', 'PRESS', ctrl=True, shift=False)
- # addon_keymapitems.append(kmi)
-
-
-def unregister_ui():
- global handler_2d, handler_3d
- pre_load(bpy.context)
-
- bpy.types.SpaceView3D.draw_handler_remove(handler_2d, 'WINDOW')
- bpy.types.SpaceView3D.draw_handler_remove(handler_3d, 'WINDOW')
-
- for c in classes:
- bpy.utils.unregister_class(c)
-
- wm = bpy.context.window_manager
- if not wm.keyconfigs.addon:
- return
-
- km = wm.keyconfigs.addon.keymaps.get('Window')
- if km:
- for kmi in addon_keymapitems:
- km.keymap_items.remove(kmi)
- del addon_keymapitems[:]
diff --git a/blenderkit/ui_bgl.py b/blenderkit/ui_bgl.py
deleted file mode 100644
index 07362b55..00000000
--- a/blenderkit/ui_bgl.py
+++ /dev/null
@@ -1,155 +0,0 @@
-# ##### 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 #####
-
-import bgl, blf
-
-import bpy, blf
-import gpu
-from gpu_extras.batch import batch_for_shader
-
-def draw_rect(x, y, width, height, color):
- xmax = x + width
- ymax = y + height
- points = ((x, y), # (x, y)
- (x, ymax), # (x, y)
- (xmax, ymax), # (x, y)
- (xmax, y), # (x, y)
- )
- indices = ((0, 1, 2), (2, 3, 0))
-
- shader = gpu.shader.from_builtin('2D_UNIFORM_COLOR')
- batch = batch_for_shader(shader, 'TRIS', {"pos": points}, indices=indices)
-
- shader.bind()
- shader.uniform_float("color", color)
- bgl.glEnable(bgl.GL_BLEND)
- batch.draw(shader)
-
-
-def draw_line2d(x1, y1, x2, y2, width, color):
- coords = (
- (x1, y1), (x2, y2))
-
- indices = (
- (0, 1),)
- bgl.glEnable(bgl.GL_BLEND)
-
- shader = gpu.shader.from_builtin('2D_UNIFORM_COLOR')
- batch = batch_for_shader(shader, 'LINES', {"pos": coords}, indices=indices)
- shader.bind()
- shader.uniform_float("color", color)
- batch.draw(shader)
-
-
-def draw_lines(vertices, indices, color):
- bgl.glEnable(bgl.GL_BLEND)
-
- shader = gpu.shader.from_builtin('3D_UNIFORM_COLOR')
- batch = batch_for_shader(shader, 'LINES', {"pos": vertices}, indices=indices)
- shader.bind()
- shader.uniform_float("color", color)
- batch.draw(shader)
-
-
-def draw_rect_3d(coords, color):
- indices = [(0, 1, 2), (2, 3, 0)]
- shader = gpu.shader.from_builtin('3D_UNIFORM_COLOR')
- batch = batch_for_shader(shader, 'TRIS', {"pos": coords}, indices=indices)
- shader.uniform_float("color", color)
- batch.draw(shader)
-
-cached_images = {}
-def draw_image(x, y, width, height, image, transparency, crop=(0, 0, 1, 1), batch = None):
- # draw_rect(x,y, width, height, (.5,0,0,.5))
- if not image:
- return;
- ci = cached_images.get(image.filepath)
- if ci is not None:
- if ci['x'] == x and ci['y'] ==y:
- batch = ci['batch']
- image_shader = ci['image_shader']
- if not batch:
-
- coords = [
- (x, y), (x + width, y),
- (x, y + height), (x + width, y + height)]
-
- uvs = [(crop[0], crop[1]),
- (crop[2], crop[1]),
- (crop[0], crop[3]),
- (crop[2], crop[3]),
- ]
-
- indices = [(0, 1, 2), (2, 1, 3)]
-
- image_shader = shader = gpu.shader.from_builtin('2D_IMAGE')
- batch = batch_for_shader(image_shader, 'TRIS',
- {"pos": coords,
- "texCoord": uvs},
- indices=indices)
-
-
- # tell shader to use the image that is bound to image unit 0
- image_shader.uniform_int("image", 0)
- cached_images[image.filepath] = {
- 'x': x,
- 'y': y,
- 'batch': batch,
- 'image_shader': image_shader
- }
- # send image to gpu if it isn't there already
- if image.gl_load():
- raise Exception()
-
- # texture identifier on gpu
- texture_id = image.bindcode
-
- # in case someone disabled it before
- bgl.glEnable(bgl.GL_BLEND)
-
- # bind texture to image unit 0
- bgl.glActiveTexture(bgl.GL_TEXTURE0)
- bgl.glBindTexture(bgl.GL_TEXTURE_2D, texture_id)
-
- image_shader.bind()
-
- batch.draw(image_shader)
-
- # bgl.glDisable(bgl.GL_TEXTURE_2D)
- return batch
-
-
-def draw_text(text, x, y, size, color=(1, 1, 1, 0.5), halign = 'LEFT', valign = 'TOP'):
- font_id = 1
- # bgl.glColor4f(*color)
- if type(text) != str:
- text = str(text)
- blf.color(font_id, color[0], color[1], color[2], color[3])
- blf.size(font_id, size, 72)
- if halign != 'LEFT':
- width,height = blf.dimensions(font_id, text)
- if halign == 'RIGHT':
- x-=width
- elif halign == 'CENTER':
- x-=width//2
- if valign=='CENTER':
- y-=height//2
- #bottom could be here but there's no reason for it
- blf.position(font_id, x, y, 0)
-
- blf.draw(font_id, text)
diff --git a/blenderkit/ui_panels.py b/blenderkit/ui_panels.py
deleted file mode 100644
index c3734554..00000000
--- a/blenderkit/ui_panels.py
+++ /dev/null
@@ -1,2681 +0,0 @@
-# ##### 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, comments_utils, ratings, ratings_utils, utils, download, categories, icons, search, \
- resolutions, ui, \
- tasks_queue, \
- autothumb, upload
-
-from bpy.types import (
- Panel
-)
-from bpy.props import (
- IntProperty,
- FloatProperty,
- FloatVectorProperty,
- StringProperty,
- EnumProperty,
- BoolProperty,
- PointerProperty,
-)
-
-import bpy
-import os
-import random
-import logging
-import platform
-import ctypes
-
-bk_logger = logging.getLogger('blenderkit')
-
-
-# this was moved to separate interface:
-
-def draw_ratings(layout, context, asset):
- # layout.operator("wm.url_open", text="Read rating instructions", icon='QUESTION').url = 'https://support.google.com/?hl=en'
- # the following shouldn't happen at all in an optimal case,
- # this function should run only when asset was already checked to be existing
- if asset == None:
- return;
-
- col = layout.column()
- bkit_ratings = asset.bkit_ratings
-
- # layout.template_icon_view(bkit_ratings, property, show_labels=False, scale=6.0, scale_popup=5.0)
-
- row = col.row()
- row.prop(bkit_ratings, 'rating_quality_ui', expand=True, icon_only=True, emboss=False)
- if bkit_ratings.rating_quality > 0:
- col.separator()
- col.prop(bkit_ratings, 'rating_work_hours')
- # w = context.region.width
-
- # layout.label(text='problems')
- # layout.prop(bkit_ratings, 'rating_problems', text='')
- # layout.label(text='compliments')
- # layout.prop(bkit_ratings, 'rating_compliments', text='')
-
- # row = layout.row()
- # op = row.operator("object.blenderkit_rating_upload", text="Send rating", icon='URL')
- # return op
- # re-enable layout if included in longer panel
-
-
-def draw_not_logged_in(source, message='Please Login/Signup to use this feature'):
- title = "You aren't logged in"
-
- def draw_message(source, context):
- layout = source.layout
- utils.label_multiline(layout, text=message)
- draw_login_buttons(layout)
-
- bpy.context.window_manager.popup_menu(draw_message, title=title, icon='INFO')
-
-
-def draw_upload_common(layout, props, asset_type, context):
- op = layout.operator("wm.url_open", text=f"Read {asset_type.lower()} upload instructions",
- icon='QUESTION')
- if asset_type == 'MODEL':
- op.url = paths.BLENDERKIT_MODEL_UPLOAD_INSTRUCTIONS_URL
- if asset_type == 'MATERIAL':
- op.url = paths.BLENDERKIT_MATERIAL_UPLOAD_INSTRUCTIONS_URL
- if asset_type == 'BRUSH':
- op.url = paths.BLENDERKIT_BRUSH_UPLOAD_INSTRUCTIONS_URL
- if asset_type == 'SCENE':
- op.url = paths.BLENDERKIT_SCENE_UPLOAD_INSTRUCTIONS_URL
- if asset_type == 'HDR':
- op.url = paths.BLENDERKIT_HDR_UPLOAD_INSTRUCTIONS_URL
-
- row = layout.row(align=True)
- if props.upload_state != '':
- utils.label_multiline(layout, text=props.upload_state, width=context.region.width)
- if props.uploading:
- op = layout.operator('object.kill_bg_process', text="", icon='CANCEL')
- op.process_source = asset_type
- op.process_type = 'UPLOAD'
- layout = layout.column()
- layout.enabled = False
- # if props.upload_state.find('Error') > -1:
- # layout.label(text = props.upload_state)
-
- if props.asset_base_id == '':
- optext = 'Upload %s' % asset_type.lower()
- op = layout.operator("object.blenderkit_upload", text=optext, icon='EXPORT')
- op.asset_type = asset_type
- op.reupload = False
- # make sure everything gets uploaded.
- op.main_file = True
- op.metadata = True
- op.thumbnail = True
-
- if props.asset_base_id != '':
- op = layout.operator("object.blenderkit_upload", text='Reupload asset', icon='EXPORT')
- op.asset_type = asset_type
- op.reupload = True
-
- op = layout.operator("object.blenderkit_upload", text='Upload as new asset', icon='EXPORT')
- op.asset_type = asset_type
- op.reupload = False
-
- # layout.label(text = 'asset id, overwrite only for reuploading')
- layout.label(text='asset has a version online.')
- # row = layout.row()
- # row.enabled = False
- # row.prop(props, 'asset_base_id', icon='FILE_TICK')
- # row = layout.row()
- # row.enabled = False
- # row.prop(props, 'id', icon='FILE_TICK')
- layout.prop(props, 'category')
- if props.category != 'NONE' and props.subcategory != 'NONE':
- layout.prop(props, 'subcategory')
- if props.subcategory != 'NONE' and props.subcategory1 != 'NONE':
- layout.prop(props, 'subcategory1')
-
- layout.prop(props, 'is_private', expand=True)
- if props.is_private == 'PUBLIC':
- layout.prop(props, 'license')
- layout.prop(props, 'is_free', expand=True)
-
- prop_needed(layout, props, 'name', props.name)
- if props.is_private == 'PUBLIC':
- prop_needed(layout, props, 'description', props.description)
- prop_needed(layout, props, 'tags', props.tags)
- else:
- 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'
-
-
-def prop_needed(layout, props, name, value='', is_not_filled=''):
- row = layout.row()
- if value == is_not_filled:
- # row.label(text='', icon = 'ERROR')
- icon = 'ERROR'
- row.alert = True
- row.prop(props, name) # , icon=icon)
- row.alert = False
- else:
- # row.label(text='', icon = 'FILE_TICK')
- icon = None
- row.prop(props, name)
-
-
-def draw_panel_hdr_upload(self, context):
- layout = self.layout
- ui_props = bpy.context.window_manager.blenderkitUI
-
- # layout.prop_search(ui_props, "hdr_upload_image", bpy.data, "images")
- layout.prop(ui_props, "hdr_upload_image")
-
- hdr = utils.get_active_HDR()
-
- if hdr is not None:
- props = hdr.blenderkit
-
- layout = self.layout
-
- draw_upload_common(layout, props, 'HDR', context)
-
-
-def draw_panel_hdr_search(self, context):
- s = context.scene
- wm = context.window_manager
- props = wm.blenderkit_HDR
-
- layout = self.layout
- row = layout.row()
- row.prop(props, "search_keywords", text="", icon='VIEWZOOM')
- draw_assetbar_show_hide(row, props)
- layout.prop(props, "own_only")
-
- 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:
- ob = ob.parent
- props = ob.blenderkit
-
- layout = self.layout
-
- draw_upload_common(layout, props, 'MODEL', 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')
-
- # row = layout.row(align=True)
- if props.is_generating_thumbnail:
- row = layout.row(align=True)
- row.label(text=props.thumbnail_generating_state)
- op = row.operator('object.kill_bg_process', text="", icon='CANCEL')
- op.process_source = 'MODEL'
- op.process_type = 'THUMBNAILER'
- elif props.thumbnail_generating_state != '':
- utils.label_multiline(layout, text=props.thumbnail_generating_state)
-
- # prop_needed(layout, props, 'style', props.style)
- # prop_needed(layout, props, 'production_level', props.production_level)
- layout.prop(props, 'style')
- layout.prop(props, 'production_level')
-
- layout.prop(props, 'condition')
- layout.prop(props, 'pbr')
- layout.label(text='design props:')
- layout.prop(props, 'manufacturer')
- layout.prop(props, 'designer')
- layout.prop(props, 'design_collection')
- layout.prop(props, 'design_variant')
- layout.prop(props, 'use_design_year')
- if props.use_design_year:
- layout.prop(props, 'design_year')
-
- row = layout.row()
- row.prop(props, 'work_hours')
-
- layout.prop(props, 'adult')
-
-
-def draw_panel_scene_upload(self, context):
- s = bpy.context.scene
- props = s.blenderkit
-
- layout = self.layout
- # if bpy.app.debug_value != -1:
- # layout.label(text='Scene upload not Implemented')
- # return
- draw_upload_common(layout, props, 'SCENE', context)
-
- # layout = layout.column()
-
- # row = layout.row()
-
- # if props.dimensions[0] + props.dimensions[1] == 0 and props.face_count == 0:
- # icon = 'ERROR'
- # layout.operator("object.blenderkit_auto_tags", text='Auto fill tags', icon=icon)
- # else:
- # layout.operator("object.blenderkit_auto_tags", text='Auto fill tags')
-
- 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')
-
- # row = layout.row(align=True)
- # if props.is_generating_thumbnail:
- # row = layout.row(align=True)
- # row.label(text = props.thumbnail_generating_state)
- # op = row.operator('object.kill_bg_process', text="", icon='CANCEL')
- # op.process_source = 'MODEL'
- # op.process_type = 'THUMBNAILER'
- # 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')
- if props.use_design_year:
- layout.prop(props, 'design_year')
- layout.prop(props, 'condition')
- row = layout.row()
- row.prop(props, 'work_hours')
- layout.prop(props, 'adult')
-
-
-def draw_assetbar_show_hide(layout, props):
- s = bpy.context.scene
- ui_props = bpy.context.window_manager.blenderkitUI
-
- if ui_props.assetbar_on:
- icon = 'HIDE_OFF'
- ttip = 'Click to Hide Asset Bar'
- else:
- icon = 'HIDE_ON'
- ttip = 'Click to Show Asset Bar'
-
- preferences = bpy.context.preferences.addons['blenderkit'].preferences
- if 1:#preferences.experimental_features:
- op = layout.operator('view3d.blenderkit_asset_bar_widget', text='', icon=icon)
- else:
- op = layout.operator('view3d.blenderkit_asset_bar', text='', icon=icon)
- op.keep_running = False
- op.do_search = False
- op.tooltip = ttip
-
-
-def draw_panel_model_search(self, context):
- wm = bpy.context.window_manager
- props = wm.blenderkit_models
-
- layout = self.layout
-
- row = layout.row()
- row.prop(props, "search_keywords", text="", icon='VIEWZOOM')
- draw_assetbar_show_hide(row, props)
-
- icon = 'NONE'
- if props.report == 'You need Full plan to get this item.':
- icon = 'ERROR'
- utils.label_multiline(layout, text=props.report, icon=icon)
- if props.report == 'You need Full plan to get this item.':
- layout.operator("wm.url_open", text="Get Full plan", icon='URL').url = paths.BLENDERKIT_PLANS
-
- # layout.prop(props, "search_style")
- # layout.prop(props, "own_only")
- # layout.prop(props, "free_only")
-
- # if props.search_style == 'OTHER':
- # layout.prop(props, "search_style_other")
- # layout.prop(props, "search_engine")
- # col = layout.column()
- # layout.prop(props, 'append_link', expand=True, icon_only=False)
- # layout.prop(props, 'import_as', expand=True, icon_only=False)
-
- # draw_panel_categories(self, context)
-
-
-def draw_panel_scene_search(self, context):
- wm = bpy.context.window_manager
- props = wm.blenderkit_scene
- layout = self.layout
- # layout.label(text = "common search properties:")
- row = layout.row()
- row.prop(props, "search_keywords", text="", icon='VIEWZOOM')
- draw_assetbar_show_hide(row, props)
- layout.prop(props, "own_only")
- utils.label_multiline(layout, text=props.report)
-
- # layout.prop(props, "search_style")
- # if props.search_style == 'OTHER':
- # layout.prop(props, "search_style_other")
- # layout.prop(props, "search_engine")
- layout.separator()
- # draw_panel_categories(self, context)
-
-
-class VIEW3D_PT_blenderkit_model_properties(Panel):
- bl_category = "BlenderKit"
- bl_idname = "VIEW3D_PT_blenderkit_model_properties"
- bl_space_type = 'VIEW_3D'
- bl_region_type = 'UI'
- bl_label = "Selected Model"
- bl_context = "objectmode"
-
- @classmethod
- def poll(cls, context):
- p = bpy.context.view_layer.objects.active is not None
- return p
-
- def draw(self, context):
- # draw asset properties here
- layout = self.layout
-
- o = utils.get_active_model()
- # o = bpy.context.active_object
- if o.get('asset_data') is None:
- utils.label_multiline(layout,
- text='To upload this asset to BlenderKit, go to the Find and Upload Assets panel.')
- layout.prop(o, 'name')
-
- if o.get('asset_data') is not None:
- ad = o['asset_data']
- layout.label(text=str(ad['name']))
- if o.instance_type == 'COLLECTION' and o.instance_collection is not None:
- layout.operator('object.blenderkit_bring_to_scene', text='Bring to scene')
-
- layout.label(text='Asset tools:')
- draw_asset_context_menu(self.layout, context, ad, from_panel=True)
- # if 'rig' in ad['tags']:
- # # layout.label(text = 'can make proxy')
- # layout.operator('object.blenderkit_make_proxy', text = 'Make Armature proxy')
- # fast upload, blocked by now
- # else:
- # op = layout.operator("object.blenderkit_upload", text='Store as private', icon='EXPORT')
- # op.asset_type = 'MODEL'
- # op.fast = True
- # fun override project, not finished
- # layout.operator('object.blenderkit_color_corrector')
-
-
-class NODE_PT_blenderkit_material_properties(Panel):
- bl_category = "BlenderKit"
- bl_idname = "NODE_PT_blenderkit_material_properties"
- bl_space_type = 'NODE_EDITOR'
- bl_region_type = 'UI'
- bl_label = "Selected Material"
- bl_context = "objectmode"
-
- @classmethod
- def poll(cls, context):
- p = bpy.context.view_layer.objects.active is not None and bpy.context.active_object.active_material is not None
- return p
-
- def draw(self, context):
- # draw asset properties here
- layout = self.layout
-
- m = bpy.context.active_object.active_material
- # o = bpy.context.active_object
- if m.get('asset_data') is None and m.blenderkit.id == '':
- utils.label_multiline(layout,
- text='To upload this asset to BlenderKit, go to the Find and Upload Assets panel.')
- layout.prop(m, 'name')
-
- if m.get('asset_data') is not None:
- ad = m['asset_data']
- layout.label(text=str(ad['name']))
-
- layout.label(text='Asset tools:')
- draw_asset_context_menu(self.layout, context, ad, from_panel=True)
- # if 'rig' in ad['tags']:
- # # layout.label(text = 'can make proxy')
- # layout.operator('object.blenderkit_make_proxy', text = 'Make Armature proxy')
- # fast upload, blocked by now
- # else:
- # op = layout.operator("object.blenderkit_upload", text='Store as private', icon='EXPORT')
- # op.asset_type = 'MODEL'
- # op.fast = True
- # fun override project, not finished
- # layout.operator('object.blenderkit_color_corrector')
-
-
-def draw_rating_asset(self, context, asset):
- layout = self.layout
- col = layout.box()
- # split = layout.split(factor=0.5)
- # col1 = split.column()
- # col2 = split.column()
- # print('%s_search' % asset['asset_data']['assetType'])
- directory = paths.get_temp_dir('%s_search' % asset['asset_data']['assetType'])
- tpath = os.path.join(directory, asset['asset_data']['thumbnail_small'])
- for image in bpy.data.images:
- if image.filepath == tpath:
- # split = row.split(factor=1.0, align=False)
- col.template_icon(icon_value=image.preview.icon_id, scale=6.0)
- break;
- # layout.label(text = '', icon_value=image.preview.icon_id, scale = 10)
- col.label(text=asset.name)
- draw_ratings(col, context, asset=asset)
-
-
-class VIEW3D_PT_blenderkit_ratings(Panel):
- bl_category = "BlenderKit"
- bl_idname = "VIEW3D_PT_blenderkit_ratings"
- bl_space_type = 'VIEW_3D'
- bl_region_type = 'UI'
- bl_label = "Please rate"
- bl_context = "objectmode"
-
- @classmethod
- def poll(cls, context):
- #
- p = bpy.context.view_layer.objects.active is not None
- return p
-
- def draw(self, context):
- # TODO make a list of assets inside asset appending code, to happen only when assets are added to the scene.
- # draw asset properties here
- layout = self.layout
- assets = ratings.get_assets_for_rating()
- if len(assets) > 0:
- utils.label_multiline(layout, text='Please help BlenderKit community by rating these assets:')
-
- for a in assets:
- if a.bkit_ratings.rating_work_hours == 0:
- draw_rating_asset(self, context, asset=a)
-
-
-def draw_login_progress(layout):
- layout.label(text='Login through browser')
- layout.label(text='in progress.')
- layout.operator("wm.blenderkit_login_cancel", text="Cancel", icon='CANCEL')
-
-
-class VIEW3D_PT_blenderkit_profile(Panel):
- bl_category = "BlenderKit"
- bl_idname = "VIEW3D_PT_blenderkit_profile"
- bl_space_type = 'VIEW_3D'
- bl_region_type = 'UI'
- bl_label = "BlenderKit Profile"
-
- @classmethod
- def poll(cls, context):
-
- return True
-
- def draw(self, context):
- # draw asset properties here
- layout = self.layout
- user_preferences = bpy.context.preferences.addons['blenderkit'].preferences
-
- if user_preferences.login_attempt:
- draw_login_progress(layout)
- return
-
- if user_preferences.api_key != '':
- me = bpy.context.window_manager.get('bkit profile')
- if me is not None:
- me = me['user']
- # user name
- if len(me['firstName']) > 0 or len(me['lastName']) > 0:
- layout.label(text=f"Me: {me['firstName']} {me['lastName']}")
- else:
- layout.label(text=f"Me: {me['email']}")
- # layout.label(text='Email: %s' % (me['email']))
-
- # plan information
-
- if me.get('currentPlanName') is not None:
- pn = me['currentPlanName']
- pcoll = icons.icon_collections["main"]
- if pn == 'Free':
- my_icon = pcoll['free']
- else:
- my_icon = pcoll['full']
-
- row = layout.row()
- row.label(text='My plan:')
- row.label(text='%s plan' % pn, icon_value=my_icon.icon_id)
- if pn == 'Free':
- layout.operator("wm.url_open", text="Change plan",
- icon='URL').url = paths.get_bkit_url() + paths.BLENDERKIT_PLANS
-
- # storage statistics
- # if me.get('sumAssetFilesSize') is not None: # TODO remove this when production server has these too.
- # layout.label(text='My public assets: %i MiB' % (me['sumAssetFilesSize']))
- # if me.get('sumPrivateAssetFilesSize') is not None:
- # layout.label(text='My private assets: %i MiB' % (me['sumPrivateAssetFilesSize']))
- if me.get('remainingPrivateQuota') is not None:
- layout.label(text='My free storage: %i MiB' % (me['remainingPrivateQuota']))
-
- layout.operator("wm.url_open", text="See my uploads",
- icon='URL').url = paths.get_bkit_url() + paths.BLENDERKIT_USER_ASSETS
-
-
-class MarkNotificationRead(bpy.types.Operator):
- """Mark notification as read here and also on BlenderKit server"""
- bl_idname = "wm.blenderkit_mark_notification_read"
- bl_label = "Mark notification as read"
- bl_options = {'REGISTER', 'UNDO', 'INTERNAL'}
-
- notification_id: bpy.props.IntProperty(
- name="Id",
- description="notification id",
- default=-1)
-
- @classmethod
- def poll(cls, context):
- return True
-
- def execute(self, context):
- notifications = bpy.context.window_manager['bkit notifications']
- for n in notifications['results']:
- if n['id'] == self.notification_id:
- n['unread'] = 0
- comments_utils.check_notifications_read()
- user_preferences = bpy.context.preferences.addons['blenderkit'].preferences
- api_key = user_preferences.api_key
- comments_utils.mark_notification_read_thread(api_key, self.notification_id)
-
- return {'FINISHED'}
-
-class MarkAllNotificationsRead(bpy.types.Operator):
- """Mark notification as read here and also on BlenderKit server"""
- bl_idname = "wm.blenderkit_mark_notifications_read_all"
- bl_label = "Mark all notifications as read"
- bl_options = {'REGISTER', 'UNDO', 'INTERNAL'}
-
-
- @classmethod
- def poll(cls, context):
- return True
-
- def execute(self, context):
- user_preferences = bpy.context.preferences.addons['blenderkit'].preferences
- api_key = user_preferences.api_key
- notifications = bpy.context.window_manager['bkit notifications']
- for n in notifications.get('results'):
- if n['unread'] == 1:
- n['unread'] = 0
- comments_utils.mark_notification_read_thread(api_key, n['id'])
-
- comments_utils.check_notifications_read()
- return {'FINISHED'}
-
-class NotificationOpenTarget(bpy.types.Operator):
- """"""
- bl_idname = "wm.blenderkit_open_notification_target"
- bl_label = ""
- bl_description = "Open notification target and mark notification as read"
- bl_options = {'REGISTER', 'UNDO', 'INTERNAL'}
-
- tooltip: bpy.props.StringProperty(default='Open a web page')
- url: bpy.props.StringProperty(default='Runs search and displays the asset bar at the same time')
- notification_id: bpy.props.IntProperty(
- name="Id",
- description="notification id",
- default=-1)
-
- @classmethod
- def description(cls, context, properties):
- return properties.tooltip
-
- def execute(self, context):
- bpy.ops.wm.blenderkit_mark_notification_read(notification_id=self.notification_id)
- bpy.ops.wm.url_open(url=self.url)
- return {'FINISHED'}
-
-
-class LikeComment(bpy.types.Operator):
- """Mark notification as read here and also on BlenderKit server"""
- bl_idname = "wm.blenderkit_like_comment"
- bl_label = "BlenderKit like/dislike comment"
- bl_options = {'REGISTER', 'UNDO', 'INTERNAL'}
-
- asset_id: StringProperty(
- name="Asset Base Id",
- description="Unique id of the asset (hidden)",
- default="",
- options={'SKIP_SAVE'})
-
- comment_id: bpy.props.IntProperty(
- name="Id",
- description="comment id",
- default=-1)
-
- flag: bpy.props.StringProperty(
- name="flag",
- description="Like/dislike comment",
- default="like")
-
- @classmethod
- def poll(cls, context):
- return True
-
- def execute(self, context):
- user_preferences = bpy.context.preferences.addons['blenderkit'].preferences
- api_key = user_preferences.api_key
- comments_utils.send_comment_flag_to_thread(asset_id=self.asset_id, comment_id=self.comment_id, flag=self.flag,
- api_key=api_key)
- return {'FINISHED'}
-
-
-def draw_notification(self, notification, width=600):
- layout = self.layout
- box = layout.box()
- firstline = f"{notification['actor']['string']} {notification['verb']} {notification['target']['string']}"
- box1 = box.box()
- # row = box1.row()
-
- split_last = 0.7
- if notification['description']:
- split_last = 0
-
-
- rows = utils.label_multiline(box1, text=firstline, width=width, split_last = split_last)
-
-
- if notification['description']:
- rows = utils.label_multiline(box, text=notification['description'], width=width, split_last = 0.7)
-
-
- if notification['target']:
- # row = layout.row()
- # split = row.split(factor=.8)
- # split.label(text='')
- # split = split.split()
- # split = rows[-1].split(factor=0.8)
- # split = split.split()
- # split.alignment = 'RIGHT'
- # row = split.row(align = True)
- row = rows[-1]
- row = row.row(align=False)
-
- # row = row.split(factor = 0.7)
-
- op = row.operator('wm.blenderkit_open_notification_target', text='Open page', icon='HIDE_OFF')
- op.tooltip = 'Open the browser on the asset page to comment'
- op.url = paths.get_bkit_url() + notification['target']['url']
- op.notification_id = notification['id']
- # split =
- op = row.operator("wm.blenderkit_mark_notification_read", text="", icon='CANCEL')
- op.notification_id = notification['id']
-
-
-def draw_notifications(self, context, width=600):
- layout = self.layout
- notifications = bpy.context.window_manager.get('bkit notifications')
- if notifications is not None and notifications.get('count')>0:
- row = layout.row()
- # row.alert = True
- split = row.split(factor = 0.7)
- split.label(text='')
- split = split.split()
- split.operator('wm.blenderkit_mark_notifications_read_all', text = 'Mark All Read', icon = 'CANCEL')
- for notification in notifications['results']:
- if notification['unread'] == 1:
- draw_notification(self, notification, width=width)
-
-
-class ShowNotifications(bpy.types.Operator):
- """Show notifications"""
- bl_idname = "wm.show_notifications"
- bl_label = "Show BlenderKit notifications"
- bl_options = {'REGISTER', 'UNDO'}
-
- notification_id: bpy.props.IntProperty(
- name="Id",
- description="notification id",
- default=-1)
-
- @classmethod
- def poll(cls, context):
- return True
-
- def draw(self, context):
- draw_notifications(self, context, width=600)
-
- def execute(self, context):
- wm = bpy.context.window_manager
- return wm.invoke_popup(self, width=600)
-
-
-class VIEW3D_PT_blenderkit_notifications(Panel):
- bl_category = "BlenderKit"
- bl_idname = "VIEW3D_PT_blenderkit_notifications"
- bl_space_type = 'VIEW_3D'
- bl_region_type = 'UI'
- bl_label = "BlenderKit Notifications"
-
- @classmethod
- def poll(cls, context):
- notifications = bpy.context.window_manager.get('bkit notifications')
- if notifications is not None and len(notifications['results']) > 0:
- return True
- return False
-
- def draw(self, context):
- draw_notifications(self, context)
-
-
-class VIEW3D_PT_blenderkit_login(Panel):
- bl_category = "BlenderKit"
- bl_idname = "VIEW3D_PT_blenderkit_login"
- bl_space_type = 'VIEW_3D'
- bl_region_type = 'UI'
- bl_label = "BlenderKit Login"
-
- @classmethod
- def poll(cls, context):
- return True
-
- def draw(self, context):
- layout = self.layout
- user_preferences = bpy.context.preferences.addons['blenderkit'].preferences
-
- if user_preferences.login_attempt:
- draw_login_progress(layout)
- return
-
- if user_preferences.enable_oauth:
- draw_login_buttons(layout)
-
-
-def draw_panel_model_rating(self, context):
- # o = bpy.context.active_object
- o = utils.get_active_model()
- # print('ratings active',o)
- draw_ratings(self.layout, context, asset=o) # , props)
- # op.asset_type = 'MODEL'
-
-
-def draw_panel_material_upload(self, context):
- o = bpy.context.active_object
- mat = bpy.context.active_object.active_material
-
- props = mat.blenderkit
- layout = self.layout
-
- draw_upload_common(layout, props, 'MATERIAL', context)
-
- # THUMBNAIL
- row = layout.column()
- if props.is_generating_thumbnail:
- row.enabled = 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_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')
- op = row.operator('object.kill_bg_process', text="", icon='CANCEL')
- op.process_source = 'MATERIAL'
- op.process_type = 'THUMBNAILER'
- elif props.thumbnail_generating_state != '':
- utils.label_multiline(layout, text=props.thumbnail_generating_state)
-
- layout.prop(props, 'style')
- # if props.style == 'OTHER':
- # layout.prop(props, 'style_other')
- # layout.prop(props, 'engine')
- # if props.engine == 'OTHER':
- # layout.prop(props, 'engine_other')
- # layout.prop(props,'shaders')#TODO autofill on upload
- # row = layout.row()
-
- layout.prop(props, 'pbr')
- layout.prop(props, 'uv')
- 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()
- # # row.scale_y = 1.5
- # row.template_preview(bpy.data.textures[tname], preview_id='test')
-
-
-def draw_panel_material_search(self, context):
- wm = context.window_manager
- props = wm.blenderkit_mat
-
- layout = self.layout
- row = layout.row()
- row.prop(props, "search_keywords", text="", icon='VIEWZOOM')
- draw_assetbar_show_hide(row, props)
- utils.label_multiline(layout, text=props.report)
-
- # layout.prop(props, 'search_style')F
- # if props.search_style == 'OTHER':
- # layout.prop(props, 'search_style_other')
- # layout.prop(props, 'search_engine')
- # if props.search_engine == 'OTHER':
- # layout.prop(props, 'search_engine_other')
-
- # draw_panel_categories(self, context)
-
-
-def draw_panel_material_ratings(self, context):
- asset = bpy.context.active_object.active_material
- draw_ratings(self.layout, context, asset) # , props)
- # op.asset_type = 'MATERIAL'
-
-
-def draw_panel_brush_upload(self, context):
- brush = utils.get_active_brush()
- if brush is not None:
- props = brush.blenderkit
-
- layout = self.layout
-
- draw_upload_common(layout, props, 'BRUSH', context)
-
-
-def draw_panel_brush_search(self, context):
- wm = context.window_manager
- props = wm.blenderkit_brush
-
- layout = self.layout
- row = layout.row()
- row.prop(props, "search_keywords", text="", icon='VIEWZOOM')
- draw_assetbar_show_hide(row, props)
- layout.prop(props, "own_only")
-
- utils.label_multiline(layout, text=props.report)
- # draw_panel_categories(self, context)
-
-
-def draw_panel_brush_ratings(self, context):
- # props = utils.get_brush_props(context)
- brush = utils.get_active_brush()
- draw_ratings(self.layout, context, asset=brush) # , props)
- #
- # op.asset_type = 'BRUSH'
-
-
-def draw_login_buttons(layout, invoke=False):
- user_preferences = bpy.context.preferences.addons['blenderkit'].preferences
-
- if user_preferences.login_attempt:
- draw_login_progress(layout)
- else:
- if invoke:
- layout.operator_context = 'INVOKE_DEFAULT'
- else:
- layout.operator_context = 'EXEC_DEFAULT'
- if user_preferences.api_key == '':
- layout.operator("wm.blenderkit_login", text="Login",
- icon='URL').signup = False
- layout.operator("wm.blenderkit_login", text="Sign up",
- icon='URL').signup = True
-
- else:
- layout.operator("wm.blenderkit_login", text="Login as someone else",
- icon='URL').signup = False
- layout.operator("wm.blenderkit_logout", text="Logout",
- icon='URL')
-
-
-class VIEW3D_PT_blenderkit_advanced_model_search(Panel):
- bl_category = "BlenderKit"
- bl_idname = "VIEW3D_PT_blenderkit_advanced_model_search"
- bl_parent_id = "VIEW3D_PT_blenderkit_unified"
- bl_space_type = 'VIEW_3D'
- bl_region_type = 'UI'
- bl_label = "Search filters"
- bl_options = {'DEFAULT_CLOSED'}
-
- @classmethod
- def poll(cls, context):
- s = context.scene
- ui_props = bpy.context.window_manager.blenderkitUI
- return ui_props.down_up == 'SEARCH' and ui_props.asset_type == 'MODEL'
-
- def draw(self, context):
- wm = bpy.context.window_manager
-
- props = wm.blenderkit_models
- layout = self.layout
- layout.separator()
-
- # layout.label(text = "common searches keywords:")
- # layout.prop(props, "search_global_keywords", text = "")
- # layout.prop(props, "search_modifier_keywords")
- # if props.search_engine == 'OTHER':
- # layout.prop(props, "search_engine_keyword")
-
- layout.prop(props, "own_only")
- layout.prop(props, "free_only")
- layout.prop(props, "search_style")
-
- # DESIGN YEAR
- layout.prop(props, "search_design_year", text='Designed in Year')
- if props.search_design_year:
- row = layout.row(align=True)
- row.prop(props, "search_design_year_min", text='Min')
- row.prop(props, "search_design_year_max", text='Max')
-
- # POLYCOUNT
- layout.prop(props, "search_polycount", text='Poly Count ')
- if props.search_polycount:
- row = layout.row(align=True)
- row.prop(props, "search_polycount_min", text='Min')
- row.prop(props, "search_polycount_max", text='Max')
-
- # TEXTURE RESOLUTION
- layout.prop(props, "search_texture_resolution", text='Texture Resolutions')
- if props.search_texture_resolution:
- row = layout.row(align=True)
- row.prop(props, "search_texture_resolution_min", text='Min')
- row.prop(props, "search_texture_resolution_max", text='Max')
-
- # FILE SIZE
- layout.prop(props, "search_file_size", text='File Size (MB)')
- if props.search_file_size:
- row = layout.row(align=True)
- row.prop(props, "search_file_size_min", text='Min')
- row.prop(props, "search_file_size_max", text='Max')
-
- # AGE
- layout.prop(props, "search_condition", text='Condition') # , text ='condition of object new/old e.t.c.')
- layout.prop(props, "quality_limit", slider=True) # , text ='condition of object new/old e.t.c.')
-
- # layout.prop(props, "search_procedural", expand=True)
- # ADULT
- # layout.prop(props, "search_adult") # , text ='condition of object new/old e.t.c.')
-
-
-class VIEW3D_PT_blenderkit_advanced_material_search(Panel):
- bl_category = "BlenderKit"
- bl_idname = "VIEW3D_PT_blenderkit_advanced_material_search"
- bl_parent_id = "VIEW3D_PT_blenderkit_unified"
- bl_space_type = 'VIEW_3D'
- bl_region_type = 'UI'
- bl_label = "Search filters"
- bl_options = {'DEFAULT_CLOSED'}
-
- @classmethod
- def poll(cls, context):
- s = context.scene
- ui_props = bpy.context.window_manager.blenderkitUI
- return ui_props.down_up == 'SEARCH' and ui_props.asset_type == 'MATERIAL'
-
- def draw(self, context):
- wm = context.window_manager
- props = wm.blenderkit_mat
- layout = self.layout
- layout.separator()
-
- layout.prop(props, "own_only")
-
- layout.label(text='Texture:')
- col = layout.column()
- col.prop(props, "search_procedural", expand=True)
-
- if props.search_procedural == 'TEXTURE_BASED':
- # TEXTURE RESOLUTION
- layout.prop(props, "search_texture_resolution", text='Texture Resolution')
- if props.search_texture_resolution:
- row = layout.row(align=True)
- row.prop(props, "search_texture_resolution_min", text='Min')
- row.prop(props, "search_texture_resolution_max", text='Max')
-
- # FILE SIZE
- layout.prop(props, "search_file_size", text='File size (MB)')
- if props.search_file_size:
- row = layout.row(align=True)
- row.prop(props, "search_file_size_min", text='Min')
- row.prop(props, "search_file_size_max", text='Max')
- layout.prop(props, "quality_limit", slider=True)
-
-
-class VIEW3D_PT_blenderkit_advanced_HDR_search(Panel):
- bl_category = "BlenderKit"
- bl_idname = "VIEW3D_PT_blenderkit_advanced_HDR_search"
- bl_parent_id = "VIEW3D_PT_blenderkit_unified"
- bl_space_type = 'VIEW_3D'
- bl_region_type = 'UI'
- bl_label = "Search filters"
- bl_options = {'DEFAULT_CLOSED'}
-
- @classmethod
- def poll(cls, context):
- s = context.scene
- ui_props = bpy.context.window_manager.blenderkitUI
- return ui_props.down_up == 'SEARCH' and ui_props.asset_type == 'HDR'
-
- def draw(self, context):
- wm = context.window_manager
- props = wm.blenderkit_HDR
- layout = self.layout
- layout.separator()
-
- layout.prop(props, "own_only")
- layout.prop(props, "true_hdr")
-
-
-class VIEW3D_PT_blenderkit_categories(Panel):
- bl_category = "BlenderKit"
- bl_idname = "VIEW3D_PT_blenderkit_categories"
- bl_space_type = 'VIEW_3D'
- bl_region_type = 'UI'
- bl_label = "Categories"
- bl_parent_id = "VIEW3D_PT_blenderkit_unified"
- bl_options = {'DEFAULT_CLOSED'}
-
- @classmethod
- def poll(cls, context):
- s = context.scene
- ui_props = bpy.context.window_manager.blenderkitUI
- mode = True
- if ui_props.asset_type == 'BRUSH' and not (context.sculpt_object or context.image_paint_object):
- mode = False
- return ui_props.down_up == 'SEARCH' and mode
-
- def draw(self, context):
- draw_panel_categories(self, context)
-
-
-def draw_scene_import_settings(self, context):
- wm = bpy.context.window_manager
- props = wm.blenderkit_scene
- layout = self.layout
- layout.prop(props, 'switch_after_append')
- # layout.label(text='Import method:')
- row = layout.row()
- row.prop(props, 'append_link', expand=True, icon_only=False)
-
-
-class VIEW3D_PT_blenderkit_import_settings(Panel):
- bl_category = "BlenderKit"
- bl_idname = "VIEW3D_PT_blenderkit_import_settings"
- bl_space_type = 'VIEW_3D'
- bl_region_type = 'UI'
- bl_label = "Import settings"
- bl_parent_id = "VIEW3D_PT_blenderkit_unified"
- bl_options = {'DEFAULT_CLOSED'}
-
- @classmethod
- def poll(cls, context):
- s = context.scene
- ui_props = bpy.context.window_manager.blenderkitUI
- return ui_props.down_up == 'SEARCH' and ui_props.asset_type in ['MATERIAL', 'MODEL', 'SCENE', 'HDR']
-
- def draw(self, context):
- layout = self.layout
-
- s = context.scene
- wm = bpy.context.window_manager
- ui_props = bpy.context.window_manager.blenderkitUI
-
- if ui_props.asset_type == 'MODEL':
- # noinspection PyCallByClass
- props = wm.blenderkit_models
- layout.prop(props, 'randomize_rotation')
- if props.randomize_rotation:
- layout.prop(props, 'randomize_rotation_amount')
- layout.prop(props, 'perpendicular_snap')
- # if props.perpendicular_snap:
- # layout.prop(props,'perpendicular_snap_threshold')
-
- layout.label(text='Import method:')
- row = layout.row()
- row.prop(props, 'append_method', expand=True, icon_only=False)
-
- if ui_props.asset_type == 'MATERIAL':
- props = wm.blenderkit_mat
- layout.prop(props, 'automap')
- layout.label(text='Import method:')
- row = layout.row()
-
- row.prop(props, 'append_method', expand=True, icon_only=False)
- if ui_props.asset_type == 'SCENE':
- draw_scene_import_settings(self, context)
-
- if ui_props.asset_type == 'HDR':
- props = wm.blenderkit_HDR
-
- if ui_props.asset_type in ['MATERIAL', 'MODEL', 'HDR']:
- layout.prop(props, 'resolution')
- # layout.prop(props, 'unpack_files')
-
-
-class VIEW3D_PT_blenderkit_unified(Panel):
- bl_category = "BlenderKit"
- bl_idname = "VIEW3D_PT_blenderkit_unified"
- bl_space_type = 'VIEW_3D'
- bl_region_type = 'UI'
- bl_label = "Find and Upload Assets"
-
- @classmethod
- def poll(cls, context):
- user_preferences = bpy.context.preferences.addons['blenderkit'].preferences
- return user_preferences.panel_behaviour == 'BOTH' or user_preferences.panel_behaviour == 'UNIFIED'
-
- def draw(self, context):
- s = context.scene
- ui_props = bpy.context.window_manager.blenderkitUI
- user_preferences = bpy.context.preferences.addons['blenderkit'].preferences
- wm = bpy.context.window_manager
- layout = self.layout
- # layout.prop_tabs_enum(ui_props, "asset_type", icon_only = True)
-
- row = layout.row()
- # row.scale_x = 1.6
- # row.scale_y = 1.6
- #
- row.prop(ui_props, 'down_up', expand=True, icon_only=False)
- # row.label(text='')
- # row = row.split().row()
- # layout.alert = True
- # layout.alignment = 'CENTER'
- row = layout.row(align=True)
- row.scale_x = 1.6
- row.scale_y = 1.6
- # split = row.split(factor=.
-
- expand_icon = 'TRIA_DOWN'
- 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)
-
- if ui_props.asset_type_fold:
- pass
- # expanded interface with names in column
- split = split.row()
- split.scale_x = 8
- split.scale_y = 1.6
- # split = row
- # split = layout.row()
- else:
- split = split.column()
-
- split.prop(ui_props, 'asset_type', expand=True, icon_only=ui_props.asset_type_fold)
- # row = layout.column(align = False)
- # layout.prop(ui_props, 'asset_type', expand=False, text='')
-
- w = context.region.width
- if user_preferences.login_attempt:
- draw_login_progress(layout)
- return
-
- if len(user_preferences.api_key) < 20 and user_preferences.asset_counter > 20:
- if user_preferences.enable_oauth:
- draw_login_buttons(layout)
- else:
- op = layout.operator("wm.url_open", text="Get your API Key",
- icon='QUESTION')
- op.url = paths.BLENDERKIT_SIGNUP_URL
- layout.label(text='Paste your API Key:')
- layout.prop(user_preferences, 'api_key', text='')
- layout.separator()
- # if bpy.data.filepath == '':
- # layout.alert = True
- # utils.label_multiline(layout, text="It's better to save your file first.", width=w)
- # layout.alert = False
- # layout.separator()
-
- if ui_props.down_up == 'SEARCH':
- if utils.profile_is_validator():
- search_props = utils.get_search_props()
- layout.prop(search_props, 'search_verification_status')
- layout.prop(search_props, "unrated_only")
-
- if ui_props.asset_type == 'MODEL':
- # noinspection PyCallByClass
- draw_panel_model_search(self, context)
- if ui_props.asset_type == 'SCENE':
- # noinspection PyCallByClass
- draw_panel_scene_search(self, context)
- if ui_props.asset_type == 'HDR':
- # noinspection PyCallByClass
- draw_panel_hdr_search(self, context)
- elif ui_props.asset_type == 'MATERIAL':
- draw_panel_material_search(self, context)
- elif ui_props.asset_type == 'BRUSH':
- if context.sculpt_object or context.image_paint_object:
- # noinspection PyCallByClass
- draw_panel_brush_search(self, context)
- else:
- utils.label_multiline(layout, text='Switch to paint or sculpt mode.', width=context.region.width)
- return
-
-
- elif ui_props.down_up == 'UPLOAD':
- if not ui_props.assetbar_on:
- text = 'Show asset preview - ;'
- else:
- text = 'Hide asset preview - ;'
- op = layout.operator('view3d.blenderkit_asset_bar', text=text, icon='EXPORT')
- op.keep_running = False
- op.do_search = False
- op.tooltip = 'Show/Hide asset preview'
-
- e = s.render.engine
- if e not in ('CYCLES', 'BLENDER_EEVEE'):
- rtext = 'Only Cycles and EEVEE render engines are currently supported. ' \
- 'Please use Cycles for all assets you upload to BlenderKit.'
- utils.label_multiline(layout, rtext, icon='ERROR', width=w)
- return;
-
- if ui_props.asset_type == 'MODEL':
- # utils.label_multiline(layout, "Uploaded models won't be available in b2.79", icon='ERROR')
- if bpy.context.view_layer.objects.active is not None:
- draw_panel_model_upload(self, context)
- else:
- layout.label(text='selet object to upload')
- elif ui_props.asset_type == 'SCENE':
- draw_panel_scene_upload(self, context)
- elif ui_props.asset_type == 'HDR':
- draw_panel_hdr_upload(self, context)
-
- elif ui_props.asset_type == 'MATERIAL':
- # utils.label_multiline(layout, "Uploaded materials won't be available in b2.79", icon='ERROR')
-
- if bpy.context.view_layer.objects.active is not None and bpy.context.active_object.active_material is not None:
- draw_panel_material_upload(self, context)
- else:
- utils.label_multiline(layout, text='select object with material to upload materials', width=w)
-
- elif ui_props.asset_type == 'BRUSH':
- if context.sculpt_object or context.image_paint_object:
- draw_panel_brush_upload(self, context)
- else:
- layout.label(text='Switch to paint or sculpt mode.')
-
-
-class BlenderKitWelcomeOperator(bpy.types.Operator):
- """Login online on BlenderKit webpage"""
-
- bl_idname = "wm.blenderkit_welcome"
- bl_label = "Welcome to BlenderKit!"
- bl_options = {'REGISTER', 'UNDO', 'INTERNAL'}
-
- step: IntProperty(
- name="step",
- description="Tutorial Step",
- default=0,
- options={'SKIP_SAVE'}
- )
-
- @classmethod
- def poll(cls, context):
- return True
-
- def draw(self, context):
- layout = self.layout
- if self.step == 0:
- user_preferences = bpy.context.preferences.addons['blenderkit'].preferences
-
- # message = "BlenderKit connects from Blender to an online, " \
- # "community built shared library of models, " \
- # "materials, and brushes. " \
- # "Use addon preferences to set up where files will be saved in the Global directory setting."
- #
- # utils.label_multiline(layout, text=message, width=300)
-
- layout.template_icon(icon_value=self.img.preview.icon_id, scale=18)
-
- # utils.label_multiline(layout, text="\n Let's start by searching for some cool materials?", width=300)
- op = layout.operator("wm.url_open", text='Watch Video Tutorial', icon='QUESTION')
- op.url = paths.BLENDERKIT_MANUAL
-
- else:
- message = "Operator Tutorial called with invalid step"
-
- def execute(self, context):
- if self.step == 0:
- # move mouse:
- # bpy.context.window_manager.windows[0].cursor_warp(1000, 1000)
- # show n-key sidebar (spaces[index] has to be found for view3d too:
- # bpy.context.window_manager.windows[0].screen.areas[5].spaces[0].show_region_ui = False
- ui_props = bpy.context.window_manager.blenderkitUI
- # random_searches = [
- # ('MATERIAL', 'ice'),
- # ('MODEL', 'car'),
- # ('MODEL', 'vase'),
- # ('MODEL', 'grass'),
- # ('MODEL', 'plant'),
- # ('MODEL', 'man'),
- # ('MATERIAL', 'metal'),
- # ('MATERIAL', 'wood'),
- # ('MATERIAL', 'floor'),
- # ('MATERIAL', 'bricks'),
- # ]
- # random_search = random.choice(random_searches)
- # ui_props.asset_type = random_search[0]
- ui_props.asset_type = 'MODEL'
-
- score_limit = 450
- if ui_props.asset_type == 'MATERIAL':
- props = bpy.context.window_manager.blenderkit_mat
-
- elif ui_props.asset_type == 'MODEL':
- props = bpy.context.window_manager.blenderkit_models
- score_limit = 1000
-
- props.search_keywords = '' # random_search[1]
- props.search_keywords += f'+is_free:true+score_gte:{score_limit}+order:-created' # random_search[1]
- # search.search()
- return {'FINISHED'}
-
- def invoke(self, context, event):
- wm = bpy.context.window_manager
- img = utils.get_thumbnail('intro.jpg')
- utils.img_to_preview(img, copy_original=True)
- self.img = img
- w, a, r = utils.get_largest_area(area_type='VIEW_3D')
- if a is not None:
- a.spaces.active.show_region_ui = True
-
- return wm.invoke_props_dialog(self, width=500)
-
-
-def draw_asset_context_menu(layout, context, asset_data, from_panel=False):
- ui_props = context.window_manager.blenderkitUI
-
- author_id = str(asset_data['author'].get('id'))
- wm = bpy.context.window_manager
-
- layout.operator_context = 'INVOKE_DEFAULT'
-
- if from_panel:
- op = layout.operator('wm.blenderkit_menu_rating_upload', text='Add Rating')
- op.asset_name = asset_data['name']
- op.asset_id = asset_data['id']
- op.asset_type = asset_data['assetType']
-
- if from_panel and wm.get('bkit authors') is not None and author_id is not None:
- a = bpy.context.window_manager['bkit authors'].get(author_id)
- if a is not None:
- # utils.p('author:', a)
- op = layout.operator('wm.url_open', text="Open Author's Website")
- if a.get('aboutMeUrl') is not None:
- op.url = a['aboutMeUrl']
- else:
- op.url = paths.get_author_gallery_url(a['id'])
- op = layout.operator('view3d.blenderkit_search', text="Show Assets By Author")
- op.keywords = ''
- op.author_id = author_id
-
- op = layout.operator('view3d.blenderkit_search', text='Search Similar')
- op.esc = True
- op.tooltip = 'Search for similar assets in the library'
- # 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 += ' '.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
- if aob is None:
- aob = bpy.context.selected_objects[0]
- op = layout.operator('scene.blenderkit_download', text='Replace Active Models')
- op.tooltip = "Replace all selected models with this one."
-
- # this checks if the menu got called from right-click in assetbar(then index is 0 - x) or
- # from a panel(then replacement happens from the active model)
- if from_panel:
- # called from addon panel
- op.asset_base_id = asset_data['assetBaseId']
- else:
- op.asset_index = ui_props.active_index
-
- # op.asset_type = ui_props.asset_type
- op.model_location = aob.location
- op.model_rotation = aob.rotation_euler
- op.target_object = aob.name
- op.material_target_slot = aob.active_material_index
- op.replace = True
- op.replace_resolution = False
-
- # resolution replacement operator
- # if asset_data['downloaded'] == 100: # only show for downloaded/used assets
- # if ui_props.asset_type in ('MODEL', 'MATERIAL'):
- # layout.menu(OBJECT_MT_blenderkit_resolution_menu.bl_idname)
-
- if ui_props.asset_type in ('MODEL', 'MATERIAL', 'HDR') and \
- utils.get_param(asset_data, 'textureResolutionMax') is not None and \
- utils.get_param(asset_data, 'textureResolutionMax') > 512:
-
- s = bpy.context.scene
-
- col = layout.column()
- col.operator_context = 'INVOKE_DEFAULT'
-
- if from_panel:
- # Called from addon panel
-
- if asset_data.get('resolution'):
- op = col.operator('scene.blenderkit_download', text='Replace asset resolution')
- op.asset_base_id = asset_data['assetBaseId']
- if asset_data['assetType'] == 'MODEL':
- o = utils.get_active_model()
- op.model_location = o.location
- op.model_rotation = o.rotation_euler
- op.target_object = o.name
- op.material_target_slot = o.active_material_index
-
- elif asset_data['assetType'] == 'MATERIAL':
- aob = bpy.context.active_object
- op.model_location = aob.location
- op.model_rotation = aob.rotation_euler
- op.target_object = aob.name
- op.material_target_slot = aob.active_material_index
- op.replace_resolution = True
- op.replace = False
-
- op.invoke_resolution = True
- op.max_resolution = asset_data.get('max_resolution',
- 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.
- # called from asset bar:
- op = col.operator('scene.blenderkit_download', text='Replace asset resolution')
-
- op.asset_index = ui_props.active_index
- # op.asset_type = ui_props.asset_type
- op.replace_resolution = True
- op.replace = False
- op.invoke_resolution = True
- o = utils.get_active_model()
- if o and o.get('asset_data'):
- if o['asset_data']['assetBaseId'] == bpy.context.window_manager['search results'][
- ui_props.active_index]:
- op.model_location = o.location
- op.model_rotation = o.rotation_euler
- else:
- op.model_location = (0, 0, 0)
- op.model_rotation = (0, 0, 0)
- op.max_resolution = asset_data.get('max_resolution',
- 0) # str(utils.get_param(asset_data, 'textureResolutionMax'))
- # print('operator res ', resolution)
- # op.resolution = resolution
-
- wm = bpy.context.window_manager
- profile = wm.get('bkit profile')
- if profile is not None:
- # validation
-
- if author_id == str(profile['user']['id']) or utils.profile_is_validator():
- layout.label(text='Management tools:')
-
- row = layout.row()
- row.operator_context = 'INVOKE_DEFAULT'
- op = layout.operator('wm.blenderkit_fast_metadata', text='Edit Metadata', icon='GREASEPENCIL')
- op.asset_id = asset_data['id']
- op.asset_type = asset_data['assetType']
-
- if author_id == str(profile['user']['id']):
- row.operator_context = 'EXEC_DEFAULT'
- op = layout.operator('wm.blenderkit_url', text='Edit Metadata (browser)', icon='GREASEPENCIL')
- op.url = paths.get_bkit_url() + paths.BLENDERKIT_USER_ASSETS + f"/{asset_data['assetBaseId']}/?edit#"
-
- row.operator_context = 'INVOKE_DEFAULT'
-
- if asset_data['assetType'] == 'model':
- op = layout.operator('object.blenderkit_regenerate_thumbnail', text='Regenerate thumbnail')
- op.asset_index = ui_props.active_index
- elif 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'
- op = row.operator('object.blenderkit_change_status', text='Delete')
- op.asset_id = asset_data['id']
- op.state = 'deleted'
-
- if utils.profile_is_validator():
- layout.label(text='Dev Tools:')
-
- op = layout.operator('object.blenderkit_print_asset_debug', text='Print asset debug')
- op.asset_id = asset_data['id']
-
-
-# def draw_asset_resolution_replace(self, context, resolution):
-# layout = self.layout
-# ui_props = bpy.context.window_manager.blenderkitUI
-#
-# op = layout.operator('scene.blenderkit_download', text=resolution)
-# if ui_props.active_index == -3:
-# # This happens if the command is called from addon panel
-# o = utils.get_active_model()
-# op.asset_base_id = o['asset_data']['assetBaseId']
-#
-# else:
-# op.asset_index = ui_props.active_index
-#
-# op.asset_type = ui_props.asset_type
-# if len(bpy.context.selected_objects) > 0: # and ui_props.asset_type == 'MODEL':
-# aob = bpy.context.active_object
-# op.model_location = aob.location
-# op.model_rotation = aob.rotation_euler
-# op.target_object = aob.name
-# op.material_target_slot = aob.active_material_index
-# op.replace_resolution = True
-# print('operator res ', resolution)
-# op.resolution = resolution
-
-
-# class OBJECT_MT_blenderkit_resolution_menu(bpy.types.Menu):
-# bl_label = "Replace Asset Resolution"
-# bl_idname = "OBJECT_MT_blenderkit_resolution_menu"
-#
-# def draw(self, context):
-# ui_props = context.window_manager.blenderkitUI
-#
-# # sr = bpy.context.window_manager['search results']
-#
-# # sr = bpy.context.window_manager['search results']
-# # asset_data = sr[ui_props.active_index]
-#
-# for k in resolutions.resolution_props_to_server.keys():
-# draw_asset_resolution_replace(self, context, k)
-
-
-class OBJECT_MT_blenderkit_asset_menu(bpy.types.Menu):
- bl_label = "Asset options:"
- bl_idname = "OBJECT_MT_blenderkit_asset_menu"
-
- def draw(self, context):
- ui_props = context.window_manager.blenderkitUI
-
- sr = bpy.context.window_manager['search results']
- asset_data = sr[ui_props.active_index]
- draw_asset_context_menu(self.layout, context, asset_data, from_panel=False)
-
-
-def numeric_to_str(s):
- if s:
- if s < 1:
- s = str(round(s, 1))
- else:
- s = str(round(s))
- else:
- s = '-'
- return s
-
-
-def push_op_left(layout, strength=3):
- for a in range(0, strength):
- layout.label(text='')
-
-
-def label_or_url_or_operator(layout, text='', tooltip='', url='', operator=None, operator_kwargs={}, icon_value=None,
- icon=None):
- '''automatically switch between different layout options for linking or tooltips'''
- layout.emboss = 'NONE'
-
- if operator is not None:
- if icon:
- op = layout.operator(operator, text=text, icon=icon)
- elif icon_value:
- op = layout.operator(operator, text=text, icon_value=icon_value)
- else:
- op = layout.operator(operator, text=text)
- for kwarg in operator_kwargs.keys():
- if type(operator_kwargs[kwarg]) == str:
- quoatation = "'"
- else:
- quoatation = ""
- exec(f"op.{kwarg} = {quoatation}{operator_kwargs[kwarg]}{quoatation}")
- push_op_left(layout, strength=2)
-
- return
- if url != '':
- if icon:
- op = layout.operator('wm.blenderkit_url', text=text, icon=icon)
- elif icon_value:
- op = layout.operator('wm.blenderkit_url', text=text, icon_value=icon_value)
- else:
- op = layout.operator('wm.blenderkit_url', text=text)
- op.url = url
- op.tooltip = tooltip
- push_op_left(layout, strength=5)
-
- return
- if tooltip != '':
- if icon:
- op = layout.operator('wm.blenderkit_tooltip', text=text, icon=icon)
- elif icon_value:
- op = layout.operator('wm.blenderkit_tooltip', text=text, icon_value=icon_value)
- else:
- op = layout.operator('wm.blenderkit_tooltip', text=text)
- op.tooltip = tooltip
-
- # these are here to move the text to left, since operators can only center text by default
- push_op_left(layout, strength=3)
- return
- if icon:
- layout.label(text=text, icon=icon)
- elif icon_value:
- layout.label(text=text, icon_value=icon_value)
- else:
- layout.label(text=text)
-
-
-class AssetPopupCard(bpy.types.Operator, ratings_utils.RatingsProperties):
- """Generate Cycles thumbnail for model assets"""
- bl_idname = "wm.blenderkit_asset_popup"
- bl_label = "BlenderKit asset popup"
-
- width = 800
-
- @classmethod
- def poll(cls, context):
- return True
-
- def draw_menu(self, context, layout):
- # layout = layout.column()
- draw_asset_context_menu(layout, context, self.asset_data, from_panel=False)
-
- def draw_property(self, layout, left, right, icon=None, icon_value=None, url='', tooltip='', operator=None,
- operator_kwargs={}):
- right = str(right)
- row = layout.row()
- split = row.split(factor=0.35)
- split.alignment = 'RIGHT'
- split.label(text=left)
- split = split.split()
- split.alignment = 'LEFT'
- # split for questionmark:
- if url != '':
- split = split.split(factor=0.6)
- label_or_url_or_operator(split, text=right, tooltip=tooltip, url=url, operator=operator,
- operator_kwargs=operator_kwargs, icon_value=icon_value, icon=icon)
- # additional questionmark icon where it's important?
- if url != '':
- split = split.split()
- op = split.operator('wm.blenderkit_url', text='', icon='QUESTION')
- op.url = url
- op.tooltip = tooltip
-
- def draw_asset_parameter(self, layout, key='', pretext='', do_search=False, decimal = True):
- parameter = utils.get_param(self.asset_data, key)
- if parameter == None:
- return
- if type(parameter) == int:
- if decimal:
- parameter = f"{parameter:,d}"
- else:
- parameter = f"{parameter}"
- elif type(parameter) == float:
- parameter = f"{parameter:,.1f}"
- if do_search:
- kwargs = {
- 'esc': True,
- 'keywords': f'+{key}:{parameter}',
- 'tooltip': f'search by {parameter}',
- }
- self.draw_property(layout, pretext, parameter, operator='view3d.blenderkit_search', operator_kwargs=kwargs)
- else:
- self.draw_property(layout, pretext, parameter)
-
- def draw_description(self, layout, width=250):
- if len(self.asset_data['description']) > 0:
- box = layout.box()
- box.scale_y = 0.4
- box.label(text='Description')
- box.separator()
- link_more = utils.label_multiline(box, self.asset_data['description'], width=width, max_lines=10)
- if link_more:
- row = box.row()
- row.scale_y = 2
- op = row.operator('wm.blenderkit_url', text='See full description', icon='URL')
- op.url = paths.get_asset_gallery_url(self.asset_data['assetBaseId'])
- op.tooltip = 'Read full description on website'
- box.separator()
-
- def draw_properties(self, layout, width=250):
-
- # if type(self.asset_data['parameters']) == list:
- # mparams = utils.params_to_dict(self.asset_data['parameters'])
- # else:
- # mparams = self.asset_data['parameters']
- mparams = self.asset_data['dictParameters']
-
- pcoll = icons.icon_collections["main"]
-
- box = layout.box()
-
- box.scale_y = 0.4
- box.label(text='Properties')
- box.separator()
-
- if self.asset_data.get('license') == 'cc_zero':
- t = 'CC Zero '
- icon = pcoll['cc0']
-
- else:
- t = 'Royalty free'
- icon = pcoll['royalty_free']
-
- self.draw_property(box,
- 'License', t,
- # icon_value=icon.icon_id,
- url="https://www.blenderkit.com/docs/licenses/",
- tooltip='All BlenderKit assets are available for commercial use. \n' \
- 'Click to read more about BlenderKit licenses on the website'
- )
-
- if upload.can_edit_asset(asset_data=self.asset_data):
- icon = pcoll[self.asset_data['verificationStatus']]
- verification_status_tooltips = {
- 'uploading': "Your asset got stuck during upload. Probably, your file was too large "
- "or your connection too slow or interrupting. If you have repeated issues, "
- "please contact us and let us know, it might be a bug",
- 'uploaded': "Your asset uploaded successfully. Yay! If it's public, "
- "it's awaiting validation. If it's private, use it",
- 'on_hold': "Your asset needs some (usually smaller) fixes, "
- "so we can make it public for everybody."
- " Please check your email to see the feedback "
- "that we send to every creator personally",
- 'rejected': "The asset has serious quality issues, " \
- "and it's probable that it might be good to start " \
- "all over again or try with something simpler. " \
- "You also get personal feedback into your e-mail, " \
- "since we believe that together, we can all learn " \
- "to become awesome 3D artists",
- 'deleted': "You deleted this asset",
- 'validated': "Your asset passed our validation process, "
- "and is now available to BlenderKit users"
-
- }
- self.draw_property(box,
- 'Verification',
- self.asset_data['verificationStatus'],
- icon_value=icon.icon_id,
- url="https://www.blenderkit.com/docs/validation-status/",
- tooltip=verification_status_tooltips[self.asset_data['verificationStatus']]
-
- )
- # resolution/s
- resolution = utils.get_param(self.asset_data, 'textureResolutionMax')
-
- if resolution is not None:
- fs = self.asset_data['files']
-
- ress = f"{int(round(resolution / 1024, 0))}K"
- self.draw_property(box, 'Resolution', ress,
- tooltip='Maximal resolution of textures in this asset.\n' \
- 'Most texture asset have also lower resolutions generated.\n' \
- 'Go to BlenderKit add-on import settings to set default resolution')
-
- if fs and len(fs) > 2 and utils.profile_is_validator():
- resolutions = ''
- list.sort(fs, key=lambda f: f['fileType'])
- for f in fs:
- if f['fileType'].find('resolution') > -1:
- resolutions += f['fileType'][11:] + ' '
- resolutions = resolutions.replace('_', '.')
- self.draw_property(box, 'Generated', resolutions)
-
- self.draw_asset_parameter(box, key='designer', pretext='Designer', do_search=True)
- self.draw_asset_parameter(box, key='manufacturer', pretext='Manufacturer',
- do_search=True)
- self.draw_asset_parameter(box, key='designCollection', pretext='Collection', do_search=True)
- self.draw_asset_parameter(box, key='designVariant', pretext='Variant')
- self.draw_asset_parameter(box, key='designYear', pretext='Design year', decimal = False)
-
- self.draw_asset_parameter(box, key='faceCount', pretext='Face count')
- # self.draw_asset_parameter(box, key='thumbnailScale', pretext='Preview scale')
- # self.draw_asset_parameter(box, key='purePbr', pretext='Pure PBR')
- # self.draw_asset_parameter(box, key='productionLevel', pretext='Readiness')
- # self.draw_asset_parameter(box, key='condition', pretext='Condition')
- if utils.profile_is_validator():
- self.draw_asset_parameter(box, key='materialStyle', pretext='Style')
- self.draw_asset_parameter(box, key='modelStyle', pretext='Style')
-
- if utils.get_param(self.asset_data, 'dimensionX'):
- t = utils.fmt_dimensions(mparams)
- self.draw_property(box, 'Size', t)
- if self.asset_data.get('filesSize'):
- fs = self.asset_data['filesSize']
- fsmb = fs // (1024 * 1024)
- fskb = fs % 1024
- if fsmb == 0:
- self.draw_property(box, 'Original size', f'{fskb} KB')
- else:
- self.draw_property(box, 'Original size', f'{fsmb} MB')
- # Tags section
- # row = box.row()
- # letters_on_row = 0
- # max_on_row = width / 10
- # for tag in self.asset_data['tags']:
- # if tag in ('manifold', 'uv', 'non-manifold'):
- # # these are sometimes accidentally stored in the lib
- # continue
- #
- # # row.emboss='NONE'
- # # we need to split wisely
- # remaining_row = (max_on_row - letters_on_row) / max_on_row
- # split_factor = (len(tag) / max_on_row) / remaining_row
- # row = row.split(factor=split_factor)
- # letters_on_row += len(tag)
- # if letters_on_row > max_on_row:
- # letters_on_row = len(tag)
- # row = box.row()
- # remaining_row = (max_on_row - letters_on_row) / max_on_row
- # split_factor = (len(tag) / max_on_row) / remaining_row
- # row = row.split(factor=split_factor)
- #
- # op = row.operator('wm')
- # op = row.operator('view3d.blenderkit_search', text=tag)
- # op.tooltip = f'Search items with tag {tag}'
- # # build search string from description and tags:
- # op.keywords = f'+tags:{tag}'
-
- # self.draw_property(box, 'Tags', self.asset_data['tags']) #TODO make them clickable!
-
- # Free/Full plan or private Access
- plans_tooltip = 'BlenderKit has 2 plans:\n' \
- ' * Free plan - more than 50% of all assets\n' \
- ' * Full plan - unlimited access to everything\n' \
- 'Click to go to subscriptions page'
- plans_link = 'https://www.blenderkit.com/plans/pricing/'
- if self.asset_data['isPrivate']:
- t = 'Private'
- self.draw_property(box, 'Access', t, icon='LOCKED')
- elif self.asset_data['isFree']:
- t = 'Free plan'
- icon = pcoll['free']
- self.draw_property(box, 'Access', t,
- icon_value=icon.icon_id,
- tooltip=plans_tooltip,
- url=plans_link)
- else:
- t = 'Full plan'
- icon = pcoll['full']
- self.draw_property(box, 'Access', t,
- icon_value=icon.icon_id,
- tooltip=plans_tooltip,
- url=plans_link)
- if utils.profile_is_validator():
- date = self.asset_data['created'][:10]
- date = f"{date[8:10]}. {date[5:7]}. {date[:4]}"
- self.draw_property(box, 'Created', date)
- if utils.asset_from_newer_blender_version(self.asset_data):
- # row = box.row()
- box.alert = True
- self.draw_property(box,
- 'Blender version',
- self.asset_data['sourceAppVersion'],
- # icon='ERROR',
- tooltip='Asset is from a newer Blender version and might work incorrectly in your scene',
- )
- box.alert = False
- box.separator()
-
- def draw_author_area(self, context, layout, width=330):
- self.draw_author(context, layout, width=width)
-
- def draw_author(self, context, layout, width=330):
- image_split = 0.25
- text_width = width
- authors = bpy.context.window_manager['bkit authors']
- a = authors.get(self.asset_data['author']['id'])
- if a is not None: # or a is '' or (a.get('gravatarHash') is not None and a.get('gravatarImg') is None):
-
- row = layout.row()
- author_box = row.box()
- author_box.scale_y = 0.6 # get text lines closer to each other
- author_box.label(text='Author') # just one extra line to give spacing
- if hasattr(self, 'gimg'):
-
- author_left = author_box.split(factor=image_split)
- author_left.template_icon(icon_value=self.gimg.preview.icon_id, scale=7)
- text_area = author_left.split()
- text_width = int(text_width * (1 - image_split))
- else:
- text_area = author_box
-
- author_right = text_area.column()
- row = author_right.row()
- col = row.column()
-
- utils.label_multiline(col, text=a['tooltip'], width=text_width)
- # check if author didn't fill any data about himself and prompt him if that's the case
- if utils.user_is_owner(asset_data=self.asset_data) and a.get('aboutMe') is not None and len(
- a.get('aboutMe', '')) == 0:
- row = col.row()
- row.enabled = False
- row.label(text='Please introduce yourself to the community!')
-
- op = col.operator('wm.blenderkit_url', text='Edit your profile')
- op.url = 'https://www.blenderkit.com/profile'
- op.tooltip = 'Edit your profile on BlenderKit webpage'
-
- button_row = author_box.row()
- button_row.scale_y = 2.0
-
- if a.get('aboutMeUrl') is not None:
- url = a['aboutMeUrl']
- text = url
- if len(url) > 45:
- text = url[:45] + '...'
- op = button_row.operator('wm.url_open', text=text)
- op.url = url
- button_row = author_box.row()
- button_row.scale_y = 2.0
-
- url = paths.get_author_gallery_url(a['id'])
- text = "Author's Profile"
-
- op = button_row.operator('wm.url_open', text=text)
- op.url = url
-
- op = button_row.operator('view3d.blenderkit_search', text="Find Assets By Author")
- op.esc = True
- op.keywords = ''
- op.author_id = self.asset_data['author']['id']
-
- def draw_thumbnail_box(self, layout, width=250):
- layout.emboss = 'NORMAL'
-
- box_thumbnail = layout.box()
-
- box_thumbnail.scale_y = .4
- box_thumbnail.template_icon(icon_value=self.img.preview.icon_id, scale=width * .12)
-
- # op = row.operator('view3d.asset_drag_drop', text='Drag & Drop from here', depress=True)
- # From here on, only ratings are drawn, which won't be displayed for private assets from now on.
-
- if not self.asset_data['isPrivate']:
- row = box_thumbnail.row()
- row.alignment = 'EXPAND'
-
- # display_ratings = can_display_ratings(self.asset_data)
- rc = self.asset_data.get('ratingsCount')
- show_rating_threshold = 0
- show_rating_prompt_threshold = 5
-
- if rc:
- rcount = min(rc['quality'], rc['workingHours'])
- else:
- rcount = 0
- if rcount >= show_rating_threshold or upload.can_edit_asset(asset_data=self.asset_data):
- s = numeric_to_str(self.asset_data['score'])
- q = numeric_to_str(self.asset_data['ratingsAverage'].get('quality'))
- c = numeric_to_str(self.asset_data['ratingsAverage'].get('workingHours'))
- else:
- s = '-'
- q = '-'
- c = '-'
-
- pcoll = icons.icon_collections["main"]
-
- row.emboss = 'NONE'
- op = row.operator('wm.blenderkit_tooltip', text=str(s), icon_value=pcoll['trophy'].icon_id)
- op.tooltip = 'Asset score calculated from user ratings. \n\n' \
- 'Score = average quality × median complexity × 10*\n\n *Happiness multiplier'
- row.label(text=' ')
-
- tooltip_extension = f'.\n\nRatings results are shown for assets with more than {show_rating_threshold} ratings'
- op = row.operator('wm.blenderkit_tooltip', text=str(q), icon='SOLO_ON')
- op.tooltip = f"Quality, average from {rc['quality']} rating{'' if rc['quality'] == 1 else 's'}" \
- f"{tooltip_extension if rcount <= show_rating_threshold else ''}"
- row.label(text=' ')
-
- op = row.operator('wm.blenderkit_tooltip', text=str(c), icon_value=pcoll['dumbbell'].icon_id)
- op.tooltip = f"Complexity, median from {rc['workingHours']} rating{'' if rc['workingHours'] == 1 else 's'}" \
- f"{tooltip_extension if rcount <= show_rating_threshold else ''}"
-
- if rcount <= show_rating_prompt_threshold:
- box_thumbnail.alert = True
- box_thumbnail.label(text=f"")
- box_thumbnail.label(
- text=f"This asset has only {rcount} rating{'' if rcount == 1 else 's'}, please rate.")
- # box_thumbnail.label(text=f"Please rate this asset.")
-
- row = box_thumbnail.row()
- row.alert = False
-
- row.scale_y = 3
- ui_props = bpy.context.window_manager.blenderkitUI
- if self.asset_data.get('canDownload', True):
- row.prop(ui_props, 'drag_init_button', icon='MOUSE_LMB_DRAG', text='Click / Drag from here', emboss=True)
- else:
- op = layout.operator('wm.blenderkit_url', text='Unlock this asset', icon='UNLOCKED')
- op.url = paths.get_bkit_url() + '/get-blenderkit/' + self.asset_data['id'] + '/?from_addon=True'
-
- def draw_menu_desc_author(self, context, layout, width=330):
- box = layout.column()
-
- box.emboss = 'NORMAL'
- # left - tooltip & params
- row = box.row()
- split_factor = 0.7
- split_left = row.split(factor=split_factor)
- col = split_left.column()
- width_left = int(width * split_factor)
- self.draw_description(col, width=width_left)
-
- self.draw_properties(col, width=width_left)
-
- # right - menu
- split_right = split_left.split()
- col = split_right.column()
- self.draw_menu(context, col)
-
- # author
- self.draw_author_area(context, box, width=width)
-
- # self.draw_author_area(context, box, width=width)
- #
- # col = box.column_flow(columns=2)
- # self.draw_menu(context, col)
- #
- #
- # # self.draw_description(box, width=int(width))
- # self.draw_properties(box, width=int(width))
-
- # define enum flags
-
- def draw_titlebar(self, context, layout):
- top_drag_bar = layout.box()
- bcats = bpy.context.window_manager['bkit_categories']
-
- cat_path = categories.get_category_path(bcats,
- self.asset_data['category'])[1:]
-
- cat_path_names = categories.get_category_name_path(bcats,
- self.asset_data['category'])[1:]
-
- aname = self.asset_data['displayName']
- aname = aname[0].upper() + aname[1:]
-
- if 1:
- name_row = top_drag_bar.row()
- # name_row = name_row.split(factor=0.5)
- # name_row = name_row.column()
- # name_row = name_row.row()
- for i, c in enumerate(cat_path):
- cat_name = cat_path_names[i]
- op = name_row.operator('view3d.blenderkit_asset_bar_widget', text=cat_name + ' >', emboss=True)
- op.do_search = True
- op.keep_running = True
- op.tooltip = f"Browse {cat_name} category"
- op.category = c
- # name_row.label(text='>')
-
- name_row.label(text=aname)
- push_op_left(name_row, strength=3)
- op = name_row.operator('view3d.close_popup_button', text='', icon='CANCEL')
-
- def draw_comment(self, context, layout, comment, width=330):
- row = layout.row()
- # print(comment)
- if comment['level'] > 0:
- split = row.split(factor=0.05 * comment['level'])
- split.label(text='')
- row = split.split()
- box = row.box()
- box.emboss = 'NORMAL'
- row = box.row()
- split = row.split(factor=0.8)
- is_moderator = comment['userModerator']
- if is_moderator:
- role_text = f" - moderator"
- else:
- role_text = ""
- row = split.row()
- row.enabled = False
- row.label(text=f"{comment['submitDate']} - {comment['userName']}{role_text}")
- removal = False
- likes = 0
- dislikes = 0
- for l in comment['flags']:
- if l['flag'] == 'like':
- likes += 1
- if l['flag'] == 'dislike':
- dislikes += 1
- if l['flag'] == 'removal':
- removal = True
- # row = box.row()
- split1 = split.split()
- # split1.emboss = 'NONE'
- op = split1.operator('wm.blenderkit_like_comment', text=str(likes), icon='TRIA_UP')
- op.asset_id = self.asset_data['assetBaseId']
- op.comment_id = comment['id']
- op.flag = 'like'
- op = split1.operator('wm.blenderkit_like_comment', text=str(dislikes), icon='TRIA_DOWN')
- op.asset_id = self.asset_data['assetBaseId']
- op.comment_id = comment['id']
- op.flag = 'dislike'
- # op = split1.operator('wm.blenderkit_like_comment', text='report', icon='ERROR')
- # op.asset_id = self.asset_data['assetBaseId']
- # op.comment_id = comment['id']
- # op.flag = 'removal'
- if removal:
- row.alert = True
- row.label(text='', icon='ERROR')
-
- rows = utils.label_multiline(box, text=comment['comment'], width=width * (1 - 0.05 * comment['level']))
-
- row = rows[-1]
- split = row.split(factor=.8)
- split.label(text='')
- split = split.split()
- op = split.operator('wm.blenderkit_url', text='Reply', icon='GREASEPENCIL')
- op.tooltip = 'Open the browser on the asset page to comment'
- op.url = paths.get_bkit_url() + f"/asset-gallery-detail/{self.asset_data['id']}/"
- # box.label(text=str(comment['flags']))
-
- def draw(self, context):
- layout = self.layout
- # top draggable bar with name of the asset
- top_row = layout.row()
- self.draw_titlebar(context, top_row)
- # left side
- row = layout.row(align=True)
- split_ratio = 0.45
- split_left = row.split(factor=split_ratio)
- left_column = split_left.column()
- self.draw_thumbnail_box(left_column, width=int(self.width * split_ratio))
- # self.draw_description(left_column, width = int(self.width*split_ratio))
- # right split
- split_right = split_left.split()
- self.draw_menu_desc_author(context, split_right, width=int(self.width * (1 - split_ratio)))
-
- if not utils.user_is_owner(asset_data=self.asset_data):
- # Draw ratings, but not for owners of assets - doesn't make sense.
- ratings_box = layout.box()
- ratings.draw_ratings_menu(self, context, ratings_box)
- # else:
- # ratings_box.label('Here you should find ratings, but you can not rate your own assets ;)')
-
- tip_box = layout.box()
- tip_box.label(text=self.tip)
- # comments
- if utils.profile_is_validator():
- comments = bpy.context.window_manager.get('asset comments', {})
- self.comments = comments.get(self.asset_data['assetBaseId'], [])
- if self.comments is not None:
- for comment in self.comments:
- self.draw_comment(context, layout, comment, width=self.width)
-
- def prefill_ratings(self):
- # pre-fill ratings
- ratings = ratings_utils.get_rating_local(self.asset_id)
- if ratings and ratings.get('quality'):
- self.rating_quality = ratings['quality']
- if ratings and ratings.get('working_hours'):
- wh = int(ratings['working_hours'])
- whs = str(wh)
- if wh in self.possible_wh_values:
- self.rating_work_hours_ui = whs
- if wh < 6 and wh in self.possible_wh_values_1_5:
- self.rating_work_hours_ui_1_5 = whs
- if wh < 11 and wh in self.possible_wh_values_1_10:
- self.rating_work_hours_ui_1_10 = whs
-
- def execute(self, context):
- wm = context.window_manager
- ui_props = context.window_manager.blenderkitUI
- ui_props.draw_tooltip = False
- sr = bpy.context.window_manager['search results']
- asset_data = sr[ui_props.active_index]
- self.asset_data = asset_data
-
- self.img = ui.get_large_thumbnail_image(asset_data)
- utils.img_to_preview(self.img, copy_original=True)
-
- self.asset_type = asset_data['assetType']
- self.asset_id = asset_data['id']
- # self.tex = utils.get_hidden_texture(self.img)
- # self.tex.update_tag()
-
- authors = bpy.context.window_manager['bkit authors']
- a = authors.get(asset_data['author']['id'])
-
- if a is not None and a.get('gravatarImg') is not None:
- self.gimg = utils.get_hidden_image(a['gravatarImg'], a['gravatarHash'])
-
- bl_label = asset_data['name']
- self.tip = search.get_random_tip()
- self.tip = self.tip.replace('\n', '')
-
- # pre-fill ratings
- self.prefill_ratings()
-
- # get comments
- if utils.profile_is_validator():
- user_preferences = bpy.context.preferences.addons['blenderkit'].preferences
- api_key = user_preferences.api_key
- comments = comments_utils.get_comments_local(asset_data['assetBaseId'])
- # if comments is None:
- comments_utils.get_comments_thread(asset_data['assetBaseId'], api_key)
- comments = bpy.context.window_manager.get('asset comments', {})
- self.comments = comments.get(asset_data['assetBaseId'], [])
-
- return wm.invoke_popup(self, width=self.width)
-
-
-class OBJECT_MT_blenderkit_login_menu(bpy.types.Menu):
- bl_label = "BlenderKit login/signup:"
- bl_idname = "OBJECT_MT_blenderkit_login_menu"
-
- def draw(self, context):
- layout = self.layout
-
- # utils.label_multiline(layout, text=message)
- draw_login_buttons(layout)
-
-
-class SetCategoryOperator(bpy.types.Operator):
- """Visit subcategory"""
- bl_idname = "view3d.blenderkit_set_category"
- bl_label = "BlenderKit Set Active Category"
- bl_options = {'REGISTER', 'UNDO', 'INTERNAL'}
-
- category: bpy.props.StringProperty(
- name="Category",
- description="set this category active",
- default="")
-
- asset_type: bpy.props.StringProperty(
- name="Asset Type",
- description="asset type",
- default="")
-
- @classmethod
- def poll(cls, context):
- return True
-
- def execute(self, context):
- acat = bpy.context.window_manager['active_category'][self.asset_type]
- if self.category == '':
- acat.remove(acat[-1])
- else:
- acat.append(self.category)
- # we have to write back to wm. Thought this should happen with original list.
- bpy.context.window_manager['active_category'][self.asset_type] = acat
- return {'FINISHED'}
-
-
-class ClosePopupButton(bpy.types.Operator):
- """Close popup window"""
- bl_idname = "view3d.close_popup_button"
- bl_label = "Close popup"
- bl_options = {'REGISTER', 'INTERNAL'}
-
- @classmethod
- def poll(cls, context):
- return True
-
- def win_close(self):
- VK_ESCAPE = 0x1B
- ctypes.windll.user32.keybd_event(VK_ESCAPE)
- return True
-
- def mouse_trick(self, context, x, y):
- # import time
- context.area.tag_redraw()
- w = context.window
- w.cursor_warp(w.x + 15, w.y + w.height - 15);
- # time.sleep(.12)
- w.cursor_warp(x, y);
- context.area.tag_redraw()
-
- def invoke(self, context, event):
- if platform.system() == 'Windows':
- self.win_close()
- else:
- self.mouse_trick(context, event.mouse_x, event.mouse_y)
- return {'FINISHED'}
-
-
-class UrlPopupDialog(bpy.types.Operator):
- """Generate Cycles thumbnail for model assets"""
- bl_idname = "wm.blenderkit_url_dialog"
- bl_label = "BlenderKit message:"
- bl_options = {'REGISTER', 'INTERNAL'}
-
- url: bpy.props.StringProperty(
- name="Url",
- description="url",
- default="")
-
- link_text: bpy.props.StringProperty(
- name="Url",
- description="url",
- default="Go to website")
-
- message: bpy.props.StringProperty(
- name="Text",
- description="text",
- default="")
-
- # @classmethod
- # def poll(cls, context):
- # return bpy.context.view_layer.objects.active is not None
-
- def draw(self, context):
- layout = self.layout
- utils.label_multiline(layout, text=self.message, width=300)
-
- layout.active_default = True
- op = layout.operator("wm.url_open", text=self.link_text, icon='QUESTION')
- if not utils.user_logged_in():
- utils.label_multiline(layout,
- text='Already subscribed? You need to login to access your Full Plan.',
- width=300)
-
- layout.operator_context = 'EXEC_DEFAULT'
- layout.operator("wm.blenderkit_login", text="Login",
- icon='URL').signup = False
- op.url = self.url
-
- def execute(self, context):
- return {'FINISHED'}
-
- def invoke(self, context, event):
- wm = context.window_manager
- return wm.invoke_props_dialog(self, width=300)
-
-
-class LoginPopupDialog(bpy.types.Operator):
- """Popup a dialog which enables the user to log in after being logged out automatically."""
- bl_idname = "wm.blenderkit_login_dialog"
- bl_label = "BlenderKit login"
- bl_options = {'REGISTER', 'INTERNAL'}
-
- message: bpy.props.StringProperty(
- name="Message",
- description="",
- default="Your were logged out from BlenderKit. Please login again. ")
-
- # @classmethod
- # def poll(cls, context):
- # return bpy.context.view_layer.objects.active is not None
-
- def draw(self, context):
- layout = self.layout
- utils.label_multiline(layout, text=self.message)
-
- layout.active_default = True
- op = layout.operator
- op = layout.operator("wm.url_open", text=self.link_text, icon='QUESTION')
- op.url = self.url
-
- def execute(self, context):
- # start_thumbnailer(self, context)
- return {'FINISHED'}
-
- def invoke(self, context, event):
- wm = context.window_manager
-
- return wm.invoke_props_dialog(self)
-
-
-def draw_panel_categories(self, context):
- s = context.scene
- ui_props = bpy.context.window_manager.blenderkitUI
- user_preferences = bpy.context.preferences.addons['blenderkit'].preferences
- layout = self.layout
- # row = layout.row()
- # row.prop(ui_props, 'asset_type', expand=True, icon_only=True)
- wm = bpy.context.window_manager
- if wm.get('bkit_categories') == None:
- return
- col = layout.column(align=True)
- if wm.get('active_category') is not None:
- acat = wm['active_category'][ui_props.asset_type]
- if len(acat) > 1:
- # we are in subcategory, so draw the parent button
- op = col.operator('view3d.blenderkit_set_category', text='...', icon='FILE_PARENT')
- op.asset_type = ui_props.asset_type
- op.category = ''
- cats = categories.get_category(wm['bkit_categories'], cat_path=acat)
- # draw freebies only in models parent category
- # if ui_props.asset_type == 'MODEL' and len(acat) == 1:
- # op = col.operator('view3d.blenderkit_asset_bar_widget', text='freebies')
- # op.free_only = True
-
- for c in cats['children']:
- if c['assetCount'] > 0 or (utils.profile_is_validator() and user_preferences.categories_fix):
- row = col.row(align=True)
- if len(c['children']) > 0 and c['assetCount'] > 15 or (
- utils.profile_is_validator() and user_preferences.categories_fix):
- row = row.split(factor=.8, align=True)
- # row = split.split()
- ctext = '%s (%i)' % (c['name'], c['assetCount'])
-
- preferences = bpy.context.preferences.addons['blenderkit'].preferences
- if 1:#preferences.experimental_features:
- op = row.operator('view3d.blenderkit_asset_bar_widget', text=ctext)
- else:
- op = row.operator('view3d.blenderkit_asset_bar', text=ctext)
- op.do_search = True
- op.keep_running = True
- op.tooltip = f"Browse {c['name']} category"
- op.category = c['slug']
- if len(c['children']) > 0 and c['assetCount'] > 15 or (
- utils.profile_is_validator() and user_preferences.categories_fix):
- # row = row.split()
- op = row.operator('view3d.blenderkit_set_category', text='>>')
- op.asset_type = ui_props.asset_type
- op.category = c['slug']
- # for c1 in c['children']:
- # if c1['assetCount']>0:
- # row = col.row()
- # split = row.split(percentage=.2)
- # row = split.split()
- # row = split.split()
- # ctext = '%s (%i)' % (c1['name'], c1['assetCount'])
- # op = row.operator('view3d.blenderkit_search', text=ctext)
- # op.category = c1['slug']
-
-
-class VIEW3D_PT_blenderkit_downloads(Panel):
- bl_category = "BlenderKit"
- bl_idname = "VIEW3D_PT_blenderkit_downloads"
- bl_space_type = 'VIEW_3D'
- bl_region_type = 'UI'
- bl_label = "Downloads"
-
- @classmethod
- def poll(cls, context):
- return len(download.download_threads) > 0
-
- def draw(self, context):
- layout = self.layout
- for i, threaddata in enumerate(download.download_threads):
- tcom = threaddata[2]
- asset_data = threaddata[1]
- row = layout.row()
- row.label(text=asset_data['name'])
- row.label(text=str(int(tcom.progress)) + ' %')
- op = row.operator('scene.blenderkit_download_kill', text='', icon='CANCEL')
- op.thread_index = i
- if tcom.passargs.get('retry_counter', 0) > 0:
- row = layout.row()
- row.label(text='failed. retrying ... ', icon='ERROR')
- row.label(text=str(tcom.passargs["retry_counter"]))
-
- layout.separator()
-
-
-def header_search_draw(self, context):
- '''Top bar menu in 3D view'''
-
- if not utils.guard_from_crash():
- return;
-
- preferences = bpy.context.preferences.addons['blenderkit'].preferences
- if not preferences.search_in_header:
- return
- if context.mode not in ('PAINT_TEXTURE', 'OBJECT', 'SCULPT'):
- return
-
- layout = self.layout
- s = bpy.context.scene
- wm = bpy.context.window_manager
- ui_props = bpy.context.window_manager.blenderkitUI
- if ui_props.asset_type == 'MODEL':
- props = wm.blenderkit_models
- if ui_props.asset_type == 'MATERIAL':
- props = wm.blenderkit_mat
- if ui_props.asset_type == 'BRUSH':
- props = wm.blenderkit_brush
- if ui_props.asset_type == 'HDR':
- props = wm.blenderkit_HDR
- if ui_props.asset_type == 'SCENE':
- props = wm.blenderkit_scene
-
- # the center snap menu is in edit and object mode if tool settings are off.
- # if context.space_data.show_region_tool_header == True or context.mode[:4] not in ('EDIT', 'OBJE'):
- # layout.separator_spacer()
- layout.prop(ui_props, "asset_type", expand=True, icon_only=True, text='', icon='URL')
- layout.prop(props, "search_keywords", text="", icon='VIEWZOOM')
- draw_assetbar_show_hide(layout, props)
- layout.popover(panel="VIEW3D_PT_blenderkit_categories", text="", icon='OUTLINER')
-
- pcoll = icons.icon_collections["main"]
-
- if props.use_filters:
- icon_id = pcoll['filter_active'].icon_id
- else:
- icon_id = pcoll['filter'].icon_id
-
- if ui_props.asset_type == 'MODEL':
- layout.popover(panel="VIEW3D_PT_blenderkit_advanced_model_search", text="", icon_value=icon_id)
-
- elif ui_props.asset_type == 'MATERIAL':
- layout.popover(panel="VIEW3D_PT_blenderkit_advanced_material_search", text="", icon_value=icon_id)
- elif ui_props.asset_type == 'HDR':
- layout.popover(panel="VIEW3D_PT_blenderkit_advanced_HDR_search", text="", icon_value=icon_id)
-
- notifications = bpy.context.window_manager.get('bkit notifications')
- if notifications is not None and notifications['count'] > 0:
- layout.operator('wm.show_notifications', text="", icon_value=pcoll['bell'].icon_id)
- # layout.popover(panel="VIEW3D_PT_blenderkit_notifications", text="", icon_value=pcoll['bell'].icon_id)
-
- if utils.profile_is_validator():
- search_props = utils.get_search_props()
- layout.prop(search_props, 'search_verification_status', text='')
-
-
-def ui_message(title, message):
- def draw_message(self, context):
- layout = self.layout
- utils.label_multiline(layout, text=message, width=400)
-
- bpy.context.window_manager.popup_menu(draw_message, title=title, icon='INFO')
-
-
-# We can store multiple preview collections here,
-# however in this example we only store "main"
-preview_collections = {}
-
-classes = (
- SetCategoryOperator,
- VIEW3D_PT_blenderkit_profile,
- VIEW3D_PT_blenderkit_login,
- VIEW3D_PT_blenderkit_notifications,
- VIEW3D_PT_blenderkit_unified,
- VIEW3D_PT_blenderkit_advanced_model_search,
- VIEW3D_PT_blenderkit_advanced_material_search,
- VIEW3D_PT_blenderkit_advanced_HDR_search,
- VIEW3D_PT_blenderkit_categories,
- VIEW3D_PT_blenderkit_import_settings,
- VIEW3D_PT_blenderkit_model_properties,
- NODE_PT_blenderkit_material_properties,
- # VIEW3D_PT_blenderkit_ratings,
- VIEW3D_PT_blenderkit_downloads,
- # OBJECT_MT_blenderkit_resolution_menu,
- OBJECT_MT_blenderkit_asset_menu,
- OBJECT_MT_blenderkit_login_menu,
- AssetPopupCard,
- UrlPopupDialog,
- ClosePopupButton,
- BlenderKitWelcomeOperator,
- MarkNotificationRead,
- LikeComment,
- ShowNotifications,
- NotificationOpenTarget,
- MarkAllNotificationsRead,
-)
-
-
-def header_draw(self, context):
- layout = self.layout
-
- self.draw_tool_settings(context)
-
- layout.separator_spacer()
- header_search_draw(self,context)
- layout.separator_spacer()
-
- self.draw_mode_settings(context)
-
-
-def register_ui_panels():
- for c in classes:
- bpy.utils.register_class(c)
-
- bpy.types.VIEW3D_HT_tool_header.draw = header_draw
- # bpy.types.VIEW3D_HT_tool_header.append(header_search_draw)
- # bpy.types.VIEW3D_MT_editor_menus.append(header_search_draw)
-
-
-def unregister_ui_panels():
- bpy.types.VIEW3D_HT_tool_header.remove(header_search_draw)
- # bpy.types.VIEW3D_MT_editor_menus.remove(header_search_draw)
- for c in classes:
- # print('unregister', c)
- bpy.utils.unregister_class(c)
diff --git a/blenderkit/upload.py b/blenderkit/upload.py
deleted file mode 100644
index 3d8b705b..00000000
--- a/blenderkit/upload.py
+++ /dev/null
@@ -1,1387 +0,0 @@
-# ##### 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 asset_inspector, paths, utils, bg_blender, autothumb, version_checker, search, ui_panels, ui, \
- overrides, colors, rerequests, categories, upload_bg, tasks_queue, image_utils, asset_bar_op, reports
-
-import tempfile, os, subprocess, json, re
-
-import bpy
-import requests
-import threading
-import sys
-
-BLENDERKIT_EXPORT_DATA_FILE = "data.json"
-
-from bpy.props import ( # TODO only keep the ones actually used when cleaning
- EnumProperty,
- BoolProperty,
- StringProperty,
-)
-from bpy.types import (
- Operator,
- Panel,
- AddonPreferences,
- PropertyGroup,
- UIList
-)
-
-licenses = (
- ('royalty_free', 'Royalty Free', 'royalty free commercial license'),
- ('cc_zero', 'Creative Commons Zero', 'Creative Commons Zero'),
-)
-
-
-def comma2array(text):
- commasep = text.split(',')
- ar = []
- for i, s in enumerate(commasep):
- s = s.strip()
- if s != '':
- ar.append(s)
- return ar
-
-
-def get_app_version():
- ver = bpy.app.version
- return '%i.%i.%i' % (ver[0], ver[1], ver[2])
-
-
-def add_version(data):
- app_version = get_app_version()
- addon_version = version_checker.get_addon_version()
- data["sourceAppName"] = "blender"
- data["sourceAppVersion"] = app_version
- data["addonVersion"] = addon_version
-
-
-def write_to_report(props, text):
- props.report = props.report + ' - ' + text + '\n\n'
-
-
-def check_missing_data_model(props):
- autothumb.update_upload_model_preview(None, None)
- if not props.has_thumbnail:
- write_to_report(props, 'Add thumbnail:')
- props.report += props.thumbnail_generating_state + '\n'
- if props.engine == 'NONE':
- write_to_report(props, 'Set at least one rendering/output engine')
-
- # if not any(props.dimensions):
- # write_to_report(props, 'Run autotags operator or fill in dimensions manually')
-
-
-def check_missing_data_scene(props):
- autothumb.update_upload_model_preview(None, None)
- if not props.has_thumbnail:
- write_to_report(props, 'Add thumbnail:')
- props.report += props.thumbnail_generating_state + '\n'
- if props.engine == 'NONE':
- write_to_report(props, 'Set at least one rendering/output engine')
-
-
-def check_missing_data_material(props):
- autothumb.update_upload_material_preview(None, None)
- if not props.has_thumbnail:
- write_to_report(props, 'Add thumbnail:')
- props.report += props.thumbnail_generating_state
- if props.engine == 'NONE':
- write_to_report(props, 'Set rendering/output engine')
-
-
-def check_missing_data_brush(props):
- autothumb.update_upload_brush_preview(None, None)
- if not props.has_thumbnail:
- write_to_report(props, 'Add thumbnail:')
- props.report += props.thumbnail_generating_state
-
-
-def check_missing_data(asset_type, props):
- '''
- checks if user did everything allright for particular assets and notifies him back if not.
- Parameters
- ----------
- asset_type
- props
-
- Returns
- -------
-
- '''
- props.report = ''
-
- if props.name == '':
- write_to_report(props, f'Set {asset_type.lower()} name.\n'
- f'It has to be in English and \n'
- f'can not be longer than 40 letters.\n')
- if len(props.name) > 40:
- write_to_report(props, f'The name is too long. maximum is 40 letters')
-
- if props.is_private == 'PUBLIC':
-
- if len(props.description) < 20:
- write_to_report(props, "The description is too short or empty. \n"
- "Please write a description that describes \n "
- "your asset as good as possible.\n"
- "Description helps to bring your asset up\n in relevant search results. ")
- if props.tags == '':
- write_to_report(props, 'Write at least 3 tags.\n'
- 'Tags help to bring your asset up in relevant search results.')
-
- if asset_type == 'MODEL':
- check_missing_data_model(props)
- if asset_type == 'SCENE':
- check_missing_data_scene(props)
- elif asset_type == 'MATERIAL':
- check_missing_data_material(props)
- elif asset_type == 'BRUSH':
- check_missing_data_brush(props)
-
- if props.report != '':
- props.report = f'Please fix these issues before {props.is_private.lower()} upload:\n\n' + props.report
-
-
-def sub_to_camel(content):
- replaced = re.sub(r"_.",
- lambda m: m.group(0)[1].upper(), content)
- return (replaced)
-
-
-def camel_to_sub(content):
- replaced = re.sub(r"[A-Z]", lambda m: '_' + m.group(0).lower(), content)
- return replaced
-
-
-def get_upload_data(caller=None, context=None, asset_type=None):
- '''
- works though metadata from addom props and prepares it for upload to dicts.
- Parameters
- ----------
- caller - upload operator or none
- context - context
- asset_type - asset type in capitals (blender enum)
-
- Returns
- -------
- export_ddta- all extra data that the process needs to upload and communicate with UI from a thread.
- - eval_path_computing - string path to UI prop that denots if upload is still running
- - eval_path_state - string path to UI prop that delivers messages about upload to ui
- - eval_path - path to object holding upload data to be able to access it with various further commands
- - models - in case of model upload, list of objects
- - thumbnail_path - path to thumbnail file
-
- upload_data - asset_data generated from the ui properties
-
- '''
- user_preferences = bpy.context.preferences.addons['blenderkit'].preferences
- api_key = user_preferences.api_key
-
- export_data = {
- # "type": asset_type,
- }
- upload_params = {}
- if asset_type == 'MODEL':
- # Prepare to save the file
- mainmodel = utils.get_active_model()
-
- props = mainmodel.blenderkit
-
- obs = utils.get_hierarchy(mainmodel)
- obnames = []
- for ob in obs:
- obnames.append(ob.name)
- export_data["models"] = obnames
- export_data["thumbnail_path"] = bpy.path.abspath(props.thumbnail)
-
- eval_path_computing = "bpy.data.objects['%s'].blenderkit.uploading" % mainmodel.name
- eval_path_state = "bpy.data.objects['%s'].blenderkit.upload_state" % mainmodel.name
- eval_path = "bpy.data.objects['%s']" % mainmodel.name
-
- engines = [props.engine.lower()]
- if props.engine1 != 'NONE':
- engines.append(props.engine1.lower())
- if props.engine2 != 'NONE':
- engines.append(props.engine2.lower())
- if props.engine3 != 'NONE':
- engines.append(props.engine3.lower())
- if props.engine == 'OTHER':
- engines.append(props.engine_other.lower())
-
- style = props.style.lower()
- # if style == 'OTHER':
- # style = props.style_other.lower()
-
- pl_dict = {'FINISHED': 'finished', 'TEMPLATE': 'template'}
-
- upload_data = {
- "assetType": 'model',
-
- }
- upload_params = {
- "productionLevel": props.production_level.lower(),
- "model_style": style,
- "engines": engines,
- "modifiers": comma2array(props.modifiers),
- "materials": comma2array(props.materials),
- "shaders": comma2array(props.shaders),
- "uv": props.uv,
- "dimensionX": round(props.dimensions[0], 4),
- "dimensionY": round(props.dimensions[1], 4),
- "dimensionZ": round(props.dimensions[2], 4),
-
- "boundBoxMinX": round(props.bbox_min[0], 4),
- "boundBoxMinY": round(props.bbox_min[1], 4),
- "boundBoxMinZ": round(props.bbox_min[2], 4),
-
- "boundBoxMaxX": round(props.bbox_max[0], 4),
- "boundBoxMaxY": round(props.bbox_max[1], 4),
- "boundBoxMaxZ": round(props.bbox_max[2], 4),
-
- "animated": props.animated,
- "rig": props.rig,
- "simulation": props.simulation,
- "purePbr": props.pbr,
- "faceCount": props.face_count,
- "faceCountRender": props.face_count_render,
- "manifold": props.manifold,
- "objectCount": props.object_count,
-
- "procedural": props.is_procedural,
- "nodeCount": props.node_count,
- "textureCount": props.texture_count,
- "megapixels": round(props.total_megapixels / 1000000),
- # "scene": props.is_scene,
- }
- if props.use_design_year:
- upload_params["designYear"] = props.design_year
- if props.condition != 'UNSPECIFIED':
- upload_params["condition"] = props.condition.lower()
- if props.pbr:
- pt = props.pbr_type
- pt = pt.lower()
- upload_params["pbrType"] = pt
-
- if props.texture_resolution_max > 0:
- upload_params["textureResolutionMax"] = props.texture_resolution_max
- upload_params["textureResolutionMin"] = props.texture_resolution_min
- if props.mesh_poly_type != 'OTHER':
- upload_params["meshPolyType"] = props.mesh_poly_type.lower() # .replace('_',' ')
-
- optional_params = ['manufacturer', 'designer', 'design_collection', 'design_variant']
- for p in optional_params:
- if eval('props.%s' % p) != '':
- upload_params[sub_to_camel(p)] = eval('props.%s' % p)
-
- if asset_type == 'SCENE':
- # Prepare to save the file
- s = bpy.context.scene
-
- props = s.blenderkit
-
- export_data["scene"] = s.name
- export_data["thumbnail_path"] = bpy.path.abspath(props.thumbnail)
-
- eval_path_computing = "bpy.data.scenes['%s'].blenderkit.uploading" % s.name
- eval_path_state = "bpy.data.scenes['%s'].blenderkit.upload_state" % s.name
- eval_path = "bpy.data.scenes['%s']" % s.name
-
- engines = [props.engine.lower()]
- if props.engine1 != 'NONE':
- engines.append(props.engine1.lower())
- if props.engine2 != 'NONE':
- engines.append(props.engine2.lower())
- if props.engine3 != 'NONE':
- engines.append(props.engine3.lower())
- if props.engine == 'OTHER':
- engines.append(props.engine_other.lower())
-
- style = props.style.lower()
- # if style == 'OTHER':
- # style = props.style_other.lower()
-
- pl_dict = {'FINISHED': 'finished', 'TEMPLATE': 'template'}
-
- upload_data = {
- "assetType": 'scene',
-
- }
- upload_params = {
- "productionLevel": props.production_level.lower(),
- "model_style": style,
- "engines": engines,
- "modifiers": comma2array(props.modifiers),
- "materials": comma2array(props.materials),
- "shaders": comma2array(props.shaders),
- "uv": props.uv,
-
- "animated": props.animated,
- # "simulation": props.simulation,
- "purePbr": props.pbr,
- "faceCount": 1, # props.face_count,
- "faceCountRender": 1, # props.face_count_render,
- "objectCount": 1, # props.object_count,
-
- # "scene": props.is_scene,
- }
- if props.use_design_year:
- upload_params["designYear"] = props.design_year
- if props.condition != 'UNSPECIFIED':
- upload_params["condition"] = props.condition.lower()
- if props.pbr:
- pt = props.pbr_type
- pt = pt.lower()
- upload_params["pbrType"] = pt
-
- if props.texture_resolution_max > 0:
- upload_params["textureResolutionMax"] = props.texture_resolution_max
- upload_params["textureResolutionMin"] = props.texture_resolution_min
- if props.mesh_poly_type != 'OTHER':
- upload_params["meshPolyType"] = props.mesh_poly_type.lower() # .replace('_',' ')
-
- elif asset_type == 'MATERIAL':
- mat = bpy.context.active_object.active_material
- props = mat.blenderkit
-
- # props.name = mat.name
-
- export_data["material"] = str(mat.name)
- export_data["thumbnail_path"] = bpy.path.abspath(props.thumbnail)
- # mat analytics happen here, since they don't take up any time...
- asset_inspector.check_material(props, mat)
-
- eval_path_computing = "bpy.data.materials['%s'].blenderkit.uploading" % mat.name
- eval_path_state = "bpy.data.materials['%s'].blenderkit.upload_state" % mat.name
- eval_path = "bpy.data.materials['%s']" % mat.name
-
- engine = props.engine
- if engine == 'OTHER':
- engine = props.engine_other
- engine = engine.lower()
- style = props.style.lower()
- # if style == 'OTHER':
- # style = props.style_other.lower()
-
- upload_data = {
- "assetType": 'material',
-
- }
-
- upload_params = {
- "material_style": style,
- "engine": engine,
- "shaders": comma2array(props.shaders),
- "uv": props.uv,
- "animated": props.animated,
- "purePbr": props.pbr,
- "textureSizeMeters": props.texture_size_meters,
- "procedural": props.is_procedural,
- "nodeCount": props.node_count,
- "textureCount": props.texture_count,
- "megapixels": round(props.total_megapixels / 1000000),
-
- }
-
- if props.pbr:
- upload_params["pbrType"] = props.pbr_type.lower()
-
- if props.texture_resolution_max > 0:
- upload_params["textureResolutionMax"] = props.texture_resolution_max
- upload_params["textureResolutionMin"] = props.texture_resolution_min
-
- elif asset_type == 'BRUSH':
- brush = utils.get_active_brush()
-
- props = brush.blenderkit
- # props.name = brush.name
-
- export_data["brush"] = str(brush.name)
- export_data["thumbnail_path"] = bpy.path.abspath(brush.icon_filepath)
-
- eval_path_computing = "bpy.data.brushes['%s'].blenderkit.uploading" % brush.name
- eval_path_state = "bpy.data.brushes['%s'].blenderkit.upload_state" % brush.name
- eval_path = "bpy.data.brushes['%s']" % brush.name
-
- # mat analytics happen here, since they don't take up any time...
-
- brush_type = ''
- if bpy.context.sculpt_object is not None:
- brush_type = 'sculpt'
-
- elif bpy.context.image_paint_object: # could be just else, but for future p
- brush_type = 'texture_paint'
-
- upload_params = {
- "mode": brush_type,
- }
-
- upload_data = {
- "assetType": 'brush',
- }
-
- elif asset_type == 'HDR':
- ui_props = bpy.context.window_manager.blenderkitUI
-
- # imagename = ui_props.hdr_upload_image
- image = ui_props.hdr_upload_image # bpy.data.images.get(imagename)
- if not image:
- return None, None
-
- props = image.blenderkit
-
- image_utils.analyze_image_is_true_hdr(image)
-
- # props.name = brush.name
- base, ext = os.path.splitext(image.filepath)
- thumb_path = base + '.jpg'
- export_data["thumbnail_path"] = bpy.path.abspath(thumb_path)
-
- export_data["hdr"] = str(image.name)
- export_data["hdr_filepath"] = str(bpy.path.abspath(image.filepath))
- # export_data["thumbnail_path"] = bpy.path.abspath(brush.icon_filepath)
-
- eval_path_computing = "bpy.data.images['%s'].blenderkit.uploading" % image.name
- eval_path_state = "bpy.data.images['%s'].blenderkit.upload_state" % image.name
- eval_path = "bpy.data.images['%s']" % image.name
-
- # mat analytics happen here, since they don't take up any time...
-
- upload_params = {
- "textureResolutionMax": props.texture_resolution_max,
- "trueHDR": props.true_hdr
- }
-
- upload_data = {
- "assetType": 'hdr',
- }
-
- elif asset_type == 'TEXTURE':
- style = props.style
- # if style == 'OTHER':
- # style = props.style_other
-
- upload_data = {
- "assetType": 'texture',
-
- }
- upload_params = {
- "style": style,
- "animated": props.animated,
- "purePbr": props.pbr,
- "resolution": props.resolution,
- }
- if props.pbr:
- pt = props.pbr_type
- pt = pt.lower()
- upload_data["pbrType"] = pt
-
- add_version(upload_data)
-
- # caller can be upload operator, but also asset bar called from tooltip generator
- if caller and caller.properties.main_file == True:
- upload_data["name"] = props.name
- upload_data["displayName"] = props.name
- else:
- upload_data["displayName"] = props.name
-
- upload_data["description"] = props.description
- upload_data["tags"] = comma2array(props.tags)
- # category is always only one value by a slug, that's why we go down to the lowest level and overwrite.
- if props.category == '':
- upload_data["category"] = asset_type.lower()
- else:
- upload_data["category"] = props.category
- if props.subcategory != 'NONE':
- upload_data["category"] = props.subcategory
- if props.subcategory1 != 'NONE':
- upload_data["category"] = props.subcategory1
-
- upload_data["license"] = props.license
- upload_data["isFree"] = props.is_free == 'FREE'
- upload_data["isPrivate"] = props.is_private == 'PRIVATE'
- upload_data["token"] = user_preferences.api_key
-
- upload_data['parameters'] = upload_params
-
- # if props.asset_base_id != '':
- export_data['assetBaseId'] = props.asset_base_id
- export_data['id'] = props.id
- export_data['eval_path_computing'] = eval_path_computing
- export_data['eval_path_state'] = eval_path_state
- export_data['eval_path'] = eval_path
-
- return export_data, upload_data
-
-
-def patch_individual_metadata(asset_id, metadata_dict, api_key):
- upload_data = metadata_dict
- url = paths.get_api_url() + 'assets/' + str(asset_id) + '/'
- headers = utils.get_headers(api_key)
- try:
- r = rerequests.patch(url, json=upload_data, headers=headers, verify=True) # files = files,
- except requests.exceptions.RequestException as e:
- print(e)
- return {'CANCELLED'}
- return {'FINISHED'}
-
-
-# class OBJECT_MT_blenderkit_fast_metadata_menu(bpy.types.Menu):
-# bl_label = "Fast category change"
-# bl_idname = "OBJECT_MT_blenderkit_fast_metadata_menu"
-#
-# def draw(self, context):
-# layout = self.layout
-# ui_props = context.window_manager.blenderkitUI
-#
-# # sr = bpy.context.window_manager['search results']
-# sr = bpy.context.window_manager['search results']
-# asset_data = sr[ui_props.active_index]
-# categories = bpy.context.window_manager['bkit_categories']
-# wm = bpy.context.win
-# for c in categories:
-# if c['name'].lower() == asset_data['assetType']:
-# for ch in c['children']:
-# op = layout.operator('wm.blenderkit_fast_metadata', text = ch['name'])
-# op = layout.operator('wm.blenderkit_fast_metadata', text = ch['name'])
-
-
-def update_free_full(self, context):
- if self.asset_type == 'material':
- if self.free_full == 'FULL':
- self.free_full = 'FREE'
- ui_panels.ui_message(title="All BlenderKit materials are free",
- message="Any material uploaded to BlenderKit is free." \
- " However, it can still earn money for the author," \
- " based on our fair share system. " \
- "Part of subscription is sent to artists based on usage by paying users.")
-
-
-def can_edit_asset(active_index=-1, asset_data=None):
- if active_index < 0 and not asset_data:
- return False
- profile = bpy.context.window_manager.get('bkit profile')
- if profile is None:
- return False
- if utils.profile_is_validator():
- return True
- if not asset_data:
- sr = bpy.context.window_manager['search results']
- asset_data = dict(sr[active_index])
- if int(asset_data['author']['id']) == int(profile['user']['id']):
- return True
- return False
-
-
-class FastMetadata(bpy.types.Operator):
- """Edit metadata of the asset"""
- bl_idname = "wm.blenderkit_fast_metadata"
- bl_label = "Update metadata"
- bl_options = {'REGISTER', 'UNDO', 'INTERNAL'}
-
- asset_id: StringProperty(
- name="Asset Base Id",
- description="Unique name of the asset (hidden)",
- default=""
- )
- asset_type: StringProperty(
- name="Asset Type",
- description="Asset Type",
- default=""
- )
- name: StringProperty(
- name="Name",
- description="Main name of the asset",
- default="",
- )
- description: StringProperty(
- name="Description",
- description="Description of the asset",
- default="")
- tags: StringProperty(
- name="Tags",
- description="List of tags, separated by commas (optional)",
- default="",
- )
- category: EnumProperty(
- name="Category",
- description="main category to put into",
- items=categories.get_category_enums,
- update=categories.update_category_enums
- )
- subcategory: EnumProperty(
- name="Subcategory",
- description="main category to put into",
- items=categories.get_subcategory_enums,
- update=categories.update_subcategory_enums
- )
- subcategory1: EnumProperty(
- name="Subcategory",
- description="main category to put into",
- items=categories.get_subcategory1_enums
- )
- license: EnumProperty(
- items=licenses,
- default='royalty_free',
- description='License. Please read our help for choosing the right licenses',
- )
- is_private: EnumProperty(
- name="Thumbnail Style",
- items=(
- ('PRIVATE', 'Private', "You asset will be hidden to public. The private assets are limited by a quota."),
- ('PUBLIC', 'Public', '"Your asset will go into the validation process automatically')
- ),
- description="If not marked private, your asset will go into the validation process automatically\n"
- "Private assets are limited by quota",
- default="PUBLIC",
- )
-
- free_full: EnumProperty(
- name="Free or Full Plan",
- items=(
- ('FREE', 'Free', "You consent you want to release this asset as free for everyone"),
- ('FULL', 'Full', 'Your asset will be in the full plan')
- ),
- description="Choose whether the asset should be free or in the Full Plan",
- default="FULL",
- update=update_free_full
- )
-
- ####################
-
- @classmethod
- def poll(cls, context):
- scene = bpy.context.scene
- ui_props = bpy.context.window_manager.blenderkitUI
- return True
-
- def draw(self, context):
- layout = self.layout
- # col = layout.column()
- layout.label(text=self.message)
- row = layout.row()
-
- layout.prop(self, 'category')
- if self.category != 'NONE' and self.subcategory != 'NONE':
- layout.prop(self, 'subcategory')
- if self.subcategory != 'NONE' and self.subcategory1 != 'NONE':
- enums = categories.get_subcategory1_enums(self, context)
- if enums[0][0] != 'NONE':
- layout.prop(self, 'subcategory1')
- layout.prop(self, 'name')
- layout.prop(self, 'description')
- layout.prop(self, 'tags')
- layout.prop(self, 'is_private', expand=True)
- layout.prop(self, 'free_full', expand=True)
- if self.is_private == 'PUBLIC':
- layout.prop(self, 'license')
-
- def execute(self, context):
- user_preferences = bpy.context.preferences.addons['blenderkit'].preferences
- props = bpy.context.window_manager.blenderkitUI
- if self.subcategory1 != 'NONE':
- category = self.subcategory1
- elif self.subcategory != 'NONE':
- category = self.subcategory
- else:
- category = self.category
- utils.update_tags(self, context)
-
- mdict = {
- 'category': category,
- 'displayName': self.name,
- 'description': self.description,
- 'tags': comma2array(self.tags),
- 'isPrivate': self.is_private == 'PRIVATE',
- 'isFree': self.free_full == 'FREE',
- 'license': self.license,
- }
-
- thread = threading.Thread(target=patch_individual_metadata,
- args=(self.asset_id, mdict, user_preferences.api_key))
- thread.start()
- tasks_queue.add_task((reports.add_report, (f'Uploading metadata for {self.name}. '
- f'Refresh search results to see that changes applied correctly.', 8,)))
-
- return {'FINISHED'}
-
- def invoke(self, context, event):
- scene = bpy.context.scene
- ui_props = bpy.context.window_manager.blenderkitUI
- if ui_props.active_index > -1:
- sr = bpy.context.window_manager['search results']
- asset_data = dict(sr[ui_props.active_index])
- else:
-
- active_asset = utils.get_active_asset_by_type(asset_type=self.asset_type)
- asset_data = active_asset.get('asset_data')
-
- if not can_edit_asset(asset_data=asset_data):
- return {'CANCELLED'}
- self.asset_id = asset_data['id']
- self.asset_type = asset_data['assetType']
- cat_path = categories.get_category_path(bpy.context.window_manager['bkit_categories'],
- asset_data['category'])
- try:
- if len(cat_path) > 1:
- self.category = cat_path[1]
- if len(cat_path) > 2:
- 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']
- self.tags = ','.join(asset_data['tags'])
- if asset_data['isPrivate']:
- self.is_private = 'PRIVATE'
- else:
- self.is_private = 'PUBLIC'
-
- if asset_data['isFree']:
- self.free_full = 'FREE'
- else:
- self.free_full = 'FULL'
- self.license = asset_data['license']
-
- wm = context.window_manager
-
- return wm.invoke_props_dialog(self, width=600)
-
-
-def verification_status_change_thread(asset_id, state, api_key):
- upload_data = {
- "verificationStatus": state
- }
- url = paths.get_api_url() + 'assets/' + str(asset_id) + '/'
- headers = utils.get_headers(api_key)
- try:
- r = rerequests.patch(url, json=upload_data, headers=headers, verify=True) # files = files,
- except requests.exceptions.RequestException as e:
- print(e)
- return {'CANCELLED'}
- return {'FINISHED'}
-
-
-def get_upload_location(props):
- '''
- not used by now, gets location of uploaded asset - potentially usefull if we draw a nice upload gizmo in viewport.
- Parameters
- ----------
- props
-
- Returns
- -------
-
- '''
- scene = bpy.context.scene
- ui_props = bpy.context.window_manager.blenderkitUI
- if ui_props.asset_type == 'MODEL':
- if bpy.context.view_layer.objects.active is not None:
- ob = utils.get_active_model()
- return ob.location
- if ui_props.asset_type == 'SCENE':
- return None
- elif ui_props.asset_type == 'MATERIAL':
- if bpy.context.view_layer.objects.active is not None and bpy.context.active_object.active_material is not None:
- return bpy.context.active_object.location
- elif ui_props.asset_type == 'TEXTURE':
- return None
- elif ui_props.asset_type == 'BRUSH':
- return None
- return None
-
-
-def check_storage_quota(props):
- if props.is_private == 'PUBLIC':
- return True
-
- profile = bpy.context.window_manager.get('bkit profile')
- if profile is None or profile.get('remainingPrivateQuota') is None:
- preferences = bpy.context.preferences.addons['blenderkit'].preferences
- adata = search.request_profile(preferences.api_key)
- if adata is None:
- props.report = 'Please log-in first.'
- return False
- search.write_profile(adata)
- profile = adata
- quota = profile['user'].get('remainingPrivateQuota')
- if quota is None or quota > 0:
- return True
- props.report = 'Private storage quota exceeded.'
- return False
-
-
-def auto_fix(asset_type=''):
- # this applies various procedures to ensure coherency in the database.
- asset = utils.get_active_asset()
- props = utils.get_upload_props()
- if asset_type == 'MATERIAL':
- overrides.ensure_eevee_transparency(asset)
- asset.name = props.name
-
-
-upload_threads = []
-
-
-class Uploader(threading.Thread):
- '''
- Upload thread -
- - first uploads metadata
- - blender gets started to process the file if .blend is uploaded
- - if files need to be uploaded, uploads them
- - thumbnail goes first
- - files get uploaded
-
- Returns
- -------
-
- '''
-
- def __init__(self, upload_data=None, export_data=None, upload_set=None):
- super(Uploader, self).__init__()
- self.upload_data = upload_data
- self.export_data = export_data
- self.upload_set = upload_set
- self._stop_event = threading.Event()
-
- def stop(self):
- self._stop_event.set()
-
- def stopped(self):
- return self._stop_event.is_set()
-
- def send_message(self, message):
- message = str(message).replace("'", "")
-
- # this adds a UI report but also writes above the upload panel fields.
- tasks_queue.add_task((reports.add_report, (message,)))
- estring = f"{self.export_data['eval_path_state']} = '{message}'"
- tasks_queue.add_task((exec, (estring,)))
-
- def end_upload(self, message):
- estring = self.export_data['eval_path_computing'] + ' = False'
- tasks_queue.add_task((exec, (estring,)))
- self.send_message(message)
-
- def run(self):
- # utils.pprint(upload_data)
- self.upload_data['parameters'] = utils.dict_to_params(
- self.upload_data['parameters']) # weird array conversion only for upload, not for tooltips.
-
- script_path = os.path.dirname(os.path.realpath(__file__))
-
- # first upload metadata to server, so it can be saved inside the current file
- url = paths.get_api_url() + 'assets/'
-
- headers = utils.get_headers(self.upload_data['token'])
-
- # self.upload_data['license'] = 'ovejajojo'
- json_metadata = self.upload_data # json.dumps(self.upload_data, ensure_ascii=False).encode('utf8')
-
- # tasks_queue.add_task((reports.add_report, ('Posting metadata',)))
- self.send_message('Posting metadata')
- if self.export_data['assetBaseId'] == '':
- try:
- r = rerequests.post(url, json=json_metadata, headers=headers, verify=True,
- immediate=True) # files = files,
-
- # tasks_queue.add_task((reports.add_report, ('uploaded metadata',)))
- utils.p(r.text)
- self.send_message('uploaded metadata')
-
- except requests.exceptions.RequestException as e:
- print(e)
- self.end_upload(e)
- return {'CANCELLED'}
-
- else:
- url += self.export_data['id'] + '/'
- try:
- if 'MAINFILE' in self.upload_set:
- json_metadata["verificationStatus"] = "uploading"
- r = rerequests.patch(url, json=json_metadata, headers=headers, verify=True,
- immediate=True) # files = files,
- self.send_message('uploaded metadata')
-
- # tasks_queue.add_task((reports.add_report, ('uploaded metadata',)))
- # parse the request
- # print('uploaded metadata')
- print(r.text)
- except requests.exceptions.RequestException as e:
- print(e)
- self.end_upload(e)
- return {'CANCELLED'}
-
- if self.stopped():
- self.end_upload('Upload cancelled by user')
- return
- # props.upload_state = 'step 1'
- if self.upload_set == ['METADATA']:
- self.end_upload('Metadata posted successfully')
- return {'FINISHED'}
- try:
- rj = r.json()
- # utils.pprint(rj)
- # if r.status_code not in (200, 201):
- # if r.status_code == 401:
- # ###reports.add_report(r.detail, 5, colors.RED)
- # return {'CANCELLED'}
- # if props.asset_base_id == '':
- # props.asset_base_id = rj['assetBaseId']
- # props.id = rj['id']
- if self.export_data['assetBaseId'] == '':
- self.export_data['assetBaseId'] = rj['assetBaseId']
- self.export_data['id'] = rj['id']
- # here we need to send asset ID's back into UI to be written in asset data.
- estring = f"{self.export_data['eval_path']}.blenderkit.asset_base_id = '{rj['assetBaseId']}'"
- tasks_queue.add_task((exec, (estring,)))
- estring = f"{self.export_data['eval_path']}.blenderkit.id = '{rj['id']}'"
- tasks_queue.add_task((exec, (estring,)))
- # after that, the user's file needs to be saved to save the
- # estring = f"bpy.ops.wm.save_as_mainfile(filepath={self.export_data['source_filepath']}, compress=False, copy=True)"
- # tasks_queue.add_task((exec, (estring,)))
-
- self.upload_data['assetBaseId'] = self.export_data['assetBaseId']
- self.upload_data['id'] = self.export_data['id']
-
- # props.uploading = True
-
- if 'MAINFILE' in self.upload_set:
- if self.upload_data['assetType'] == 'hdr':
- fpath = self.export_data['hdr_filepath']
- else:
- fpath = os.path.join(self.export_data['temp_dir'], self.upload_data['assetBaseId'] + '.blend')
-
- clean_file_path = paths.get_clean_filepath()
-
- data = {
- 'export_data': self.export_data,
- 'upload_data': self.upload_data,
- 'debug_value': self.export_data['debug_value'],
- 'upload_set': self.upload_set,
- }
- datafile = os.path.join(self.export_data['temp_dir'], BLENDERKIT_EXPORT_DATA_FILE)
-
- with open(datafile, 'w', encoding='utf-8') as s:
- json.dump(data, s, ensure_ascii=False, indent=4)
-
- self.send_message('preparing scene - running blender instance')
-
- proc = subprocess.run([
- self.export_data['binary_path'],
- "--background",
- "-noaudio",
- clean_file_path,
- "--python", os.path.join(script_path, "upload_bg.py"),
- "--", datafile
- ], bufsize=1, stdout=sys.stdout, stdin=subprocess.PIPE, creationflags=utils.get_process_flags())
-
- if self.stopped():
- self.end_upload('Upload stopped by user')
- return
-
- files = []
- if 'THUMBNAIL' in self.upload_set:
- files.append({
- "type": "thumbnail",
- "index": 0,
- "file_path": self.export_data["thumbnail_path"]
- })
- if 'MAINFILE' in self.upload_set:
- files.append({
- "type": "blend",
- "index": 0,
- "file_path": fpath
- })
-
- if not os.path.exists(fpath):
- self.send_message("File packing failed, please try manual packing first")
- return {'CANCELLED'}
-
- self.send_message('Uploading files')
-
- uploaded = upload_bg.upload_files(self.upload_data, files)
-
- if uploaded:
- # mark on server as uploaded
- if 'MAINFILE' in self.upload_set:
- confirm_data = {
- "verificationStatus": "uploaded"
- }
-
- url = paths.get_api_url() + 'assets/'
-
- headers = utils.get_headers(self.upload_data['token'])
-
- url += self.upload_data["id"] + '/'
-
- r = rerequests.patch(url, json=confirm_data, headers=headers, verify=True) # files = files,
-
- self.end_upload('Upload finished successfully')
- else:
- self.end_upload('Upload failed')
- except Exception as e:
- self.end_upload(e)
- print(e)
- return {'CANCELLED'}
-
-
-def start_upload(self, context, asset_type, reupload, upload_set):
- '''start upload process, by processing data, then start a thread that cares about the rest of the upload.'''
-
- # fix the name first
- props = utils.get_upload_props()
-
- utils.name_update(props)
-
- storage_quota_ok = check_storage_quota(props)
- if not storage_quota_ok:
- self.report({'ERROR_INVALID_INPUT'}, props.report)
- return {'CANCELLED'}
-
- location = get_upload_location(props)
- props.upload_state = 'preparing upload'
-
- auto_fix(asset_type=asset_type)
-
- # do this for fixing long tags in some upload cases
- props.tags = props.tags[:]
-
- # check for missing metadata
- check_missing_data(asset_type, props)
- # if previous check did find any problems then
- if props.report != '':
- return {'CANCELLED'}
-
- if not reupload:
- props.asset_base_id = ''
- props.id = ''
-
- export_data, upload_data = get_upload_data(caller=self, context=context, asset_type=asset_type)
-
- # check if thumbnail exists, generate for HDR:
- if 'THUMBNAIL' in upload_set:
- if asset_type == 'HDR':
- image_utils.generate_hdr_thumbnail()
- # get upload data because the image utils function sets true_hdr
- export_data, upload_data = get_upload_data(caller=self, context=context, asset_type=asset_type)
-
- elif not os.path.exists(export_data["thumbnail_path"]):
- props.upload_state = 'Thumbnail not found'
- props.uploading = False
- return {'CANCELLED'}
-
- if upload_set == {'METADATA'}:
- props.upload_state = "Updating metadata. Please don't close Blender until upload finishes"
- else:
- props.upload_state = "Starting upload. Please don't close Blender until upload finishes"
- props.uploading = True
-
- # save a copy of the file for processing. Only for blend files
- basename, ext = os.path.splitext(bpy.data.filepath)
- if not ext:
- ext = ".blend"
- export_data['temp_dir'] = tempfile.mkdtemp()
- export_data['source_filepath'] = os.path.join(export_data['temp_dir'], "export_blenderkit" + ext)
- if asset_type != 'HDR':
- # if this isn't here, blender crashes.
- bpy.context.preferences.filepaths.file_preview_type = 'NONE'
-
- bpy.ops.wm.save_as_mainfile(filepath=export_data['source_filepath'], compress=False, copy=True)
-
- export_data['binary_path'] = bpy.app.binary_path
- export_data['debug_value'] = bpy.app.debug_value
-
- upload_thread = Uploader(upload_data=upload_data, export_data=export_data, upload_set=upload_set)
-
- upload_thread.start()
-
- upload_threads.append(upload_thread)
- return {'FINISHED'}
-
-
-asset_types = (
- ('MODEL', 'Model', 'Set of objects'),
- ('SCENE', 'Scene', 'Scene'),
- ('HDR', 'HDR', 'HDR image'),
- ('MATERIAL', 'Material', 'Any .blend Material'),
- ('TEXTURE', 'Texture', 'A texture, or texture set'),
- ('BRUSH', 'Brush', 'Brush, can be any type of blender brush'),
- ('ADDON', 'Addon', 'Addnon'),
-)
-
-
-class UploadOperator(Operator):
- """Tooltip"""
- bl_idname = "object.blenderkit_upload"
- bl_description = "Upload or re-upload asset + thumbnail + metadata"
-
- bl_label = "BlenderKit asset upload"
- bl_options = {'REGISTER', 'INTERNAL'}
-
- # type of upload - model, material, textures, e.t.c.
- asset_type: EnumProperty(
- name="Type",
- items=asset_types,
- description="Type of upload",
- default="MODEL",
- )
-
- reupload: BoolProperty(
- name="reupload",
- description="reupload but also draw so that it asks what to reupload",
- default=False,
- options={'SKIP_SAVE'}
- )
-
- metadata: BoolProperty(
- name="metadata",
- default=True,
- options={'SKIP_SAVE'}
- )
-
- thumbnail: BoolProperty(
- name="thumbnail",
- default=False,
- options={'SKIP_SAVE'}
- )
-
- main_file: BoolProperty(
- name="main file",
- default=False,
- options={'SKIP_SAVE'}
- )
-
- @classmethod
- def poll(cls, context):
- return utils.uploadable_asset_poll()
-
- def execute(self, context):
- bpy.ops.object.blenderkit_auto_tags()
- props = utils.get_upload_props()
-
- # in case of name change, we have to reupload everything, since the name is stored in blender file,
- # and is used for linking to scene
- # if props.name_changed:
- # # TODO: this needs to be replaced with new double naming scheme (metadata vs blend data)
- # # print('has to reupload whole data, name has changed.')
- # self.main_file = True
- # props.name_changed = False
-
- upload_set = []
- if not self.reupload:
- upload_set = ['METADATA', 'THUMBNAIL', 'MAINFILE']
- else:
- if self.metadata:
- upload_set.append('METADATA')
- if self.thumbnail:
- upload_set.append('THUMBNAIL')
- if self.main_file:
- upload_set.append('MAINFILE')
-
- # this is accessed later in get_upload_data and needs to be written.
- # should pass upload_set all the way to it probably
- if 'MAINFILE' in upload_set:
- self.main_file = True
-
- result = start_upload(self, context, self.asset_type, self.reupload, upload_set=upload_set, )
-
- if props.report != '':
- # self.report({'ERROR_INVALID_INPUT'}, props.report)
- self.report({'ERROR_INVALID_CONTEXT'}, props.report)
-
- return result
-
- def draw(self, context):
- props = utils.get_upload_props()
- layout = self.layout
-
- if self.reupload:
- # layout.prop(self, 'metadata')
- layout.prop(self, 'main_file')
- layout.prop(self, 'thumbnail')
-
- if props.asset_base_id != '' and not self.reupload:
- utils.label_multiline(layout, text="Really upload as new?\n"
- "Do this only when you create\n"
- "a new asset from an old one.\n"
- "For updates of thumbnail or model use reupload.\n",
- width=400, icon='ERROR')
-
-
- if props.is_private == 'PUBLIC':
- if self.asset_type == 'MODEL':
- utils.label_multiline(layout, text='You marked the asset as public.\n'
- 'This means it will be validated by our team.\n\n'
- 'Please test your upload after it finishes:\n'
- '- Open a new file\n'
- '- Find the asset and download it\n'
- '- Check if it snaps correctly to surfaces\n'
- '- Check if it has all textures and renders as expected\n'
- '- Check if it has correct size in world units (for models)'
- , width=400)
- elif self.asset_type == 'HDR':
- if not props.true_hdr:
- utils.label_multiline(layout, text="This image isn't HDR,\n"
- "It has a low dynamic range.\n"
- "BlenderKit library accepts 360 degree images\n"
- "however the default filter setting for search\n"
- "is to show only true HDR images\n"
- , icon='ERROR', width=400)
-
- utils.label_multiline(layout, text='You marked the asset as public.\n'
- 'This means it will be validated by our team.\n\n'
- 'Please test your upload after it finishes:\n'
- '- Open a new file\n'
- '- Find the asset and download it\n'
- '- Check if it works as expected\n'
- , width=400)
- else:
- utils.label_multiline(layout, text='You marked the asset as public.\n'
- 'This means it will be validated by our team.\n\n'
- 'Please test your upload after it finishes:\n'
- '- Open a new file\n'
- '- Find the asset and download it\n'
- '- Check if it works as expected\n'
- , width=400)
-
- def invoke(self, context, event):
-
- if not utils.user_logged_in():
- ui_panels.draw_not_logged_in(self, message='To upload assets you need to login/signup.')
- return {'CANCELLED'}
-
- if self.asset_type == 'HDR':
- props = utils.get_upload_props()
- # getting upload data for images ensures true_hdr check so users can be informed about their handling
- # simple 360 photos or renders with LDR are hidden by default..
- export_data, upload_data = get_upload_data(asset_type='HDR')
-
- # if props.is_private == 'PUBLIC':
- return context.window_manager.invoke_props_dialog(self)
- # else:
- # return self.execute(context)
-
-
-class AssetDebugPrint(Operator):
- """Change verification status"""
- bl_idname = "object.blenderkit_print_asset_debug"
- bl_description = "BlenderKit print asset data for debug purposes"
- bl_label = "BlenderKit print asset data"
- bl_options = {'REGISTER', 'UNDO', 'INTERNAL'}
-
- # type of upload - model, material, textures, e.t.c.
- asset_id: StringProperty(
- name="asset id",
- )
-
- @classmethod
- def poll(cls, context):
- return True
-
- def execute(self, context):
- preferences = bpy.context.preferences.addons['blenderkit'].preferences
-
- if not bpy.context.window_manager['search results']:
- print('no search results found')
- return {'CANCELLED'};
- # update status in search results for validator's clarity
- sr = bpy.context.window_manager['search results']
-
- result = None
- for r in sr:
- if r['id'] == self.asset_id:
- result = r.to_dict()
- if not result:
- ad = bpy.context.active_object.get('asset_data')
- if ad:
- result = ad.to_dict()
- if result:
- t = bpy.data.texts.new(result['name'])
- t.write(json.dumps(result, indent=4, sort_keys=True))
- print(json.dumps(result, indent=4, sort_keys=True))
- return {'FINISHED'}
-
-
-class AssetVerificationStatusChange(Operator):
- """Change verification status"""
- bl_idname = "object.blenderkit_change_status"
- bl_description = "Change asset status"
- bl_label = "Change verification status"
- bl_options = {'REGISTER', 'UNDO', 'INTERNAL'}
-
- # type of upload - model, material, textures, e.t.c.
- asset_id: StringProperty(
- name="asset id",
- )
-
- state: StringProperty(
- name="verification_status",
- default='uploaded'
- )
-
- @classmethod
- def poll(cls, context):
- return True
-
- def draw(self, context):
- layout = self.layout
- # if self.state == 'deleted':
- layout.label(text='Really delete asset from BlenderKit online storage?')
- # layout.prop(self, 'state')
-
- def execute(self, context):
- preferences = bpy.context.preferences.addons['blenderkit'].preferences
-
- if not bpy.context.window_manager['search results']:
- return {'CANCELLED'};
- # update status in search results for validator's clarity
- sr = bpy.context.window_manager['search results']
-
- for r in sr:
- if r['id'] == self.asset_id:
- r['verificationStatus'] = self.state
-
- thread = threading.Thread(target=verification_status_change_thread,
- args=(self.asset_id, self.state, preferences.api_key))
- thread.start()
- if asset_bar_op.asset_bar_operator is not None:
- asset_bar_op.asset_bar_operator.update_layout(context, None)
- return {'FINISHED'}
-
- def invoke(self, context, event):
- # print(self.state)
- if self.state == 'deleted':
- wm = context.window_manager
- return wm.invoke_props_dialog(self)
- return {'RUNNING_MODAL'}
-
-
-def register_upload():
- bpy.utils.register_class(UploadOperator)
- bpy.utils.register_class(FastMetadata)
- bpy.utils.register_class(AssetDebugPrint)
- bpy.utils.register_class(AssetVerificationStatusChange)
-
-
-def unregister_upload():
- bpy.utils.unregister_class(UploadOperator)
- bpy.utils.unregister_class(FastMetadata)
- bpy.utils.unregister_class(AssetDebugPrint)
- bpy.utils.unregister_class(AssetVerificationStatusChange)
diff --git a/blenderkit/upload_bg.py b/blenderkit/upload_bg.py
deleted file mode 100644
index cfcb8224..00000000
--- a/blenderkit/upload_bg.py
+++ /dev/null
@@ -1,187 +0,0 @@
-# ##### 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, append_link, bg_blender, utils, rerequests, tasks_queue, ui, reports
-
-import sys, json, os, time
-import requests
-import logging
-
-import bpy
-
-BLENDERKIT_EXPORT_DATA = sys.argv[-1]
-
-
-def print_gap():
- print('\n\n\n\n')
-
-
-class upload_in_chunks(object):
- def __init__(self, filename, chunksize=1 << 13, report_name='file'):
- self.filename = filename
- self.chunksize = chunksize
- self.totalsize = os.path.getsize(filename)
- self.readsofar = 0
- self.report_name = report_name
-
- def __iter__(self):
- with open(self.filename, 'rb') as file:
- while True:
- data = file.read(self.chunksize)
- if not data:
- sys.stderr.write("\n")
- break
- self.readsofar += len(data)
- percent = self.readsofar * 1e2 / self.totalsize
- tasks_queue.add_task((reports.add_report, (f"Uploading {self.report_name} {percent}%",)))
-
- # bg_blender.progress('uploading %s' % self.report_name, percent)
- # sys.stderr.write("\r{percent:3.0f}%".format(percent=percent))
- yield data
-
- def __len__(self):
- return self.totalsize
-
-
-def upload_file(upload_data, f):
- headers = utils.get_headers(upload_data['token'])
- version_id = upload_data['id']
-
- message = f"uploading {f['type']} {os.path.basename(f['file_path'])}"
- tasks_queue.add_task((reports.add_report, (message,)))
-
- upload_info = {
- 'assetId': version_id,
- 'fileType': f['type'],
- 'fileIndex': f['index'],
- 'originalFilename': os.path.basename(f['file_path'])
- }
- upload_create_url = paths.get_api_url() + 'uploads/'
- upload = rerequests.post(upload_create_url, json=upload_info, headers=headers, verify=True)
- upload = upload.json()
- #
- chunk_size = 1024 * 1024 * 2
- # utils.pprint(upload)
- # file gets uploaded here:
- uploaded = False
- # s3 upload is now the only option
- for a in range(0, 5):
- if not uploaded:
- try:
- upload_response = requests.put(upload['s3UploadUrl'],
- data=upload_in_chunks(f['file_path'], chunk_size, f['type']),
- stream=True, verify=True)
-
- if 250 > upload_response.status_code > 199:
- 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)
- # print(upload_response.text)
- tasks_queue.add_task((reports.add_report, (f"Finished file upload: {os.path.basename(f['file_path'])}",)))
- return True
- else:
- print(upload_response.text)
- message = f"Upload failed, retry. File : {f['type']} {os.path.basename(f['file_path'])}"
- tasks_queue.add_task((reports.add_report, (message,)))
-
- except Exception as e:
- print(e)
- message = f"Upload failed, retry. File : {f['type']} {os.path.basename(f['file_path'])}"
- tasks_queue.add_task((reports.add_report, (message,)))
- time.sleep(1)
-
- # confirm single file upload to bkit server
-
-
-
-
- return False
-
-
-def upload_files(upload_data, files):
- '''uploads several files in one run'''
- uploaded_all = True
- for f in files:
- uploaded = upload_file(upload_data, f)
- if not uploaded:
- uploaded_all = False
- tasks_queue.add_task((reports.add_report, (f"Uploaded all files for asset {upload_data['name']}",)))
- return uploaded_all
-
-
-if __name__ == "__main__":
- try:
- # bg_blender.progress('preparing scene - append data')
- with open(BLENDERKIT_EXPORT_DATA, 'r',encoding='utf-8') as s:
- data = json.load(s)
-
- bpy.app.debug_value = data.get('debug_value', 0)
- export_data = data['export_data']
- upload_data = data['upload_data']
-
- bpy.data.scenes.new('upload')
- for s in bpy.data.scenes:
- if s.name != 'upload':
- bpy.data.scenes.remove(s)
-
- if upload_data['assetType'] == 'model':
- obnames = export_data['models']
- main_source, allobs = append_link.append_objects(file_name=export_data['source_filepath'],
- obnames=obnames,
- rotation=(0, 0, 0))
- g = bpy.data.collections.new(upload_data['name'])
- for o in allobs:
- g.objects.link(o)
- bpy.context.scene.collection.children.link(g)
- elif upload_data['assetType'] == 'scene':
- sname = export_data['scene']
- main_source = append_link.append_scene(file_name=export_data['source_filepath'],
- scenename=sname)
- bpy.data.scenes.remove(bpy.data.scenes['upload'])
- main_source.name = sname
- elif upload_data['assetType'] == 'material':
- matname = export_data['material']
- main_source = append_link.append_material(file_name=export_data['source_filepath'], matname=matname)
-
- elif upload_data['assetType'] == 'brush':
- brushname = export_data['brush']
- main_source = append_link.append_brush(file_name=export_data['source_filepath'], brushname=brushname)
-
- bpy.ops.file.pack_all()
-
- main_source.blenderkit.uploading = False
- #write ID here.
- main_source.blenderkit.asset_base_id = export_data['assetBaseId']
- main_source.blenderkit.id = export_data['id']
-
- fpath = os.path.join(export_data['temp_dir'], upload_data['assetBaseId'] + '.blend')
-
- #if this isn't here, blender crashes.
- bpy.context.preferences.filepaths.file_preview_type = 'NONE'
-
- bpy.ops.wm.save_as_mainfile(filepath=fpath, compress=True, copy=False)
- os.remove(export_data['source_filepath'])
-
-
- except Exception as e:
- print(e)
- # bg_blender.progress(e)
- sys.exit(1)
diff --git a/blenderkit/utils.py b/blenderkit/utils.py
deleted file mode 100644
index 952219ad..00000000
--- a/blenderkit/utils.py
+++ /dev/null
@@ -1,1001 +0,0 @@
-# ##### 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, rerequests, image_utils
-
-import bpy
-from mathutils import Vector
-import json
-import os
-import sys
-import shutil
-import logging
-import traceback
-import inspect
-import datetime
-
-bk_logger = logging.getLogger('blenderkit')
-
-ABOVE_NORMAL_PRIORITY_CLASS = 0x00008000
-BELOW_NORMAL_PRIORITY_CLASS = 0x00004000
-HIGH_PRIORITY_CLASS = 0x00000080
-IDLE_PRIORITY_CLASS = 0x00000040
-NORMAL_PRIORITY_CLASS = 0x00000020
-REALTIME_PRIORITY_CLASS = 0x00000100
-
-supported_material_click = ('MESH', 'CURVE', 'META', 'FONT', 'SURFACE', 'VOLUME', 'GPENCIL')
-supported_material_drag = ('MESH', 'CURVE', 'META', 'FONT', 'SURFACE', 'VOLUME', 'GPENCIL')
-
-
-# supported_material_drag = ('MESH')
-
-
-def experimental_enabled():
- preferences = bpy.context.preferences.addons['blenderkit'].preferences
- return preferences.experimental_features
-
-
-def get_process_flags():
- flags = BELOW_NORMAL_PRIORITY_CLASS
- if sys.platform != 'win32': # TODO test this on windows
- flags = 0
- return flags
-
-
-def activate(ob):
- bpy.ops.object.select_all(action='DESELECT')
- ob.select_set(True)
- bpy.context.view_layer.objects.active = ob
-
-def selection_get():
- aob = bpy.context.view_layer.objects.active
- selobs = bpy.context.view_layer.objects.selected[:]
- return (aob, selobs)
-
-
-def selection_set(sel):
- bpy.ops.object.select_all(action='DESELECT')
- bpy.context.view_layer.objects.active = sel[0]
- for ob in sel[1]:
- ob.select_set(True)
-
-
-def get_active_model():
- if bpy.context.view_layer.objects.active is not None:
- ob = bpy.context.view_layer.objects.active
- while ob.parent is not None:
- ob = ob.parent
- return ob
- return None
-
-
-def get_active_HDR():
- scene = bpy.context.scene
- ui_props = bpy.context.window_manager.blenderkitUI
- image = ui_props.hdr_upload_image
- return image
-
-
-def get_selected_models():
- '''
- Detect all hierarchies that contain asset data from selection. Only parents that have actual ['asset data'] get returned
- Returns
- list of objects containing asset data.
-
- '''
- obs = bpy.context.selected_objects[:]
- done = {}
- parents = []
- for ob in obs:
- if ob not in done:
- while ob.parent is not None and ob not in done and ob.blenderkit.asset_base_id == '' and ob.instance_collection is None:
- done[ob] = True
- ob = ob.parent
-
- if ob not in parents and ob not in done:
- if ob.blenderkit.name != '' or ob.instance_collection is not None:
- parents.append(ob)
- done[ob] = True
-
- # if no blenderkit - like objects were found, use the original selection.
- if len(parents) == 0:
- parents = obs
- return parents
-
-
-def get_selected_replace_adepts():
- '''
- Detect all hierarchies that contain either asset data from selection, or selected objects themselves.
- Returns
- list of objects for replacement.
-
- '''
- obs = bpy.context.selected_objects[:]
- done = {}
- parents = []
- for selected_ob in obs:
- ob = selected_ob
- if ob not in done:
- while ob.parent is not None and ob not in done and ob.blenderkit.asset_base_id == '' and ob.instance_collection is None:
- done[ob] = True
- # print('step,',ob.name)
- ob = ob.parent
-
- # print('fin', ob.name)
- if ob not in parents and ob not in done:
- if ob.blenderkit.name != '' or ob.instance_collection is not None:
- parents.append(ob)
-
- done[ob] = True
- # print(parents)
- # if no blenderkit - like objects were found, use the original selection.
- if len(parents) == 0:
- parents = obs
- pprint('replace adepts')
- pprint(str(parents))
- return parents
-
-
-def exclude_collection(name, state=True):
- '''
- Set the exclude state of collection
- Parameters
- ----------
- name - name of collection
- state - default True.
-
- Returns
- -------
- None
- '''
- vl = bpy.context.view_layer.layer_collection
- cc = [vl]
- found = False
- while len(cc) > 0 and not found:
- c = cc.pop()
- if c.name == name:
- c.exclude = state
- found = True
- cc.extend(c.children)
-
-def get_search_props():
- scene = bpy.context.scene
- wm = bpy.context.window_manager
- if scene is None:
- return;
- uiprops = bpy.context.window_manager.blenderkitUI
- props = None
- if uiprops.asset_type == 'MODEL':
- if not hasattr(wm, 'blenderkit_models'):
- return;
- props = wm.blenderkit_models
- if uiprops.asset_type == 'SCENE':
- if not hasattr(wm, 'blenderkit_scene'):
- return;
- props = wm.blenderkit_scene
- if uiprops.asset_type == 'HDR':
- if not hasattr(wm, 'blenderkit_HDR'):
- return;
- props = wm.blenderkit_HDR
- if uiprops.asset_type == 'MATERIAL':
- if not hasattr(wm, 'blenderkit_mat'):
- return;
- props = wm.blenderkit_mat
-
- if uiprops.asset_type == 'TEXTURE':
- if not hasattr(wm, 'blenderkit_tex'):
- return;
- # props = scene.blenderkit_tex
-
- if uiprops.asset_type == 'BRUSH':
- if not hasattr(wm, 'blenderkit_brush'):
- return;
- props = wm.blenderkit_brush
- return props
-
-
-def get_active_asset_by_type(asset_type='model'):
- asset_type = asset_type.lower()
- if asset_type == 'model':
- if bpy.context.view_layer.objects.active is not None:
- ob = get_active_model()
- return ob
- if asset_type == 'scene':
- return bpy.context.scene
- if asset_type == 'hdr':
- return get_active_HDR()
- if asset_type == 'material':
- if bpy.context.view_layer.objects.active is not None and bpy.context.active_object.active_material is not None:
- return bpy.context.active_object.active_material
- if asset_type == 'texture':
- return None
- if asset_type == 'brush':
- b = get_active_brush()
- if b is not None:
- return b
- return None
-
-
-def get_active_asset():
- scene = bpy.context.scene
- ui_props = bpy.context.window_manager.blenderkitUI
- if ui_props.asset_type == 'MODEL':
- if bpy.context.view_layer.objects.active is not None:
- ob = get_active_model()
- return ob
- if ui_props.asset_type == 'SCENE':
- return bpy.context.scene
- if ui_props.asset_type == 'HDR':
- return get_active_HDR()
- elif ui_props.asset_type == 'MATERIAL':
- if bpy.context.view_layer.objects.active is not None and bpy.context.active_object.active_material is not None:
- return bpy.context.active_object.active_material
- elif ui_props.asset_type == 'TEXTURE':
- return None
- elif ui_props.asset_type == 'BRUSH':
- b = get_active_brush()
- if b is not None:
- return b
- return None
-
-
-def get_upload_props():
- scene = bpy.context.scene
- ui_props = bpy.context.window_manager.blenderkitUI
- if ui_props.asset_type == 'MODEL':
- if bpy.context.view_layer.objects.active is not None:
- ob = get_active_model()
- return ob.blenderkit
- if ui_props.asset_type == 'SCENE':
- s = bpy.context.scene
- return s.blenderkit
- if ui_props.asset_type == 'HDR':
-
- hdr = ui_props.hdr_upload_image # bpy.data.images.get(ui_props.hdr_upload_image)
- if not hdr:
- return None
- return hdr.blenderkit
- elif ui_props.asset_type == 'MATERIAL':
- if bpy.context.view_layer.objects.active is not None and bpy.context.active_object.active_material is not None:
- return bpy.context.active_object.active_material.blenderkit
- elif ui_props.asset_type == 'TEXTURE':
- return None
- elif ui_props.asset_type == 'BRUSH':
- b = get_active_brush()
- if b is not None:
- return b.blenderkit
- return None
-
-
-def previmg_name(index, fullsize=False):
- if not fullsize:
- return '.bkit_preview_' + str(index).zfill(3)
- else:
- return '.bkit_preview_full_' + str(index).zfill(3)
-
-
-def get_active_brush():
- context = bpy.context
- brush = None
- if context.sculpt_object:
- brush = context.tool_settings.sculpt.brush
- elif context.image_paint_object: # could be just else, but for future possible more types...
- brush = context.tool_settings.image_paint.brush
- return brush
-
-
-def load_prefs():
- user_preferences = bpy.context.preferences.addons['blenderkit'].preferences
- # if user_preferences.api_key == '':
- fpath = paths.BLENDERKIT_SETTINGS_FILENAME
- if os.path.exists(fpath):
- try:
- with open(fpath, 'r', encoding='utf-8') as s:
- prefs = json.load(s)
- user_preferences.api_key = prefs.get('API_key', '')
- user_preferences.global_dir = prefs.get('global_dir', paths.default_global_dict())
- user_preferences.api_key_refresh = prefs.get('API_key_refresh', '')
- except Exception as e:
- print('failed to read addon preferences.')
- print(e)
- os.remove(fpath)
-
-
-def save_prefs(self, context):
- # first check context, so we don't do this on registration or blender startup
- if not bpy.app.background: # (hasattr kills blender)
- user_preferences = bpy.context.preferences.addons['blenderkit'].preferences
- # we test the api key for length, so not a random accidentally typed sequence gets saved.
- lk = len(user_preferences.api_key)
- if 0 < lk < 25:
- # reset the api key in case the user writes some nonsense, e.g. a search string instead of the Key
- user_preferences.api_key = ''
- props = get_search_props()
- props.report = 'Login failed. Please paste a correct API Key.'
-
- prefs = {
- 'API_key': user_preferences.api_key,
- 'API_key_refresh': user_preferences.api_key_refresh,
- 'global_dir': user_preferences.global_dir,
- }
- try:
- fpath = paths.BLENDERKIT_SETTINGS_FILENAME
- if not os.path.exists(paths._presets):
- os.makedirs(paths._presets)
- with open(fpath, 'w', encoding='utf-8') as s:
- json.dump(prefs, s, ensure_ascii=False, indent=4)
- except Exception as e:
- print(e)
-
-
-def uploadable_asset_poll():
- '''returns true if active asset type can be uploaded'''
- ui_props = bpy.context.window_manager.blenderkitUI
- if ui_props.asset_type == 'MODEL':
- return bpy.context.view_layer.objects.active is not None
- if ui_props.asset_type == 'MATERIAL':
- return bpy.context.view_layer.objects.active is not None and bpy.context.active_object.active_material is not None
- if ui_props.asset_type == 'HDR':
- return ui_props.hdr_upload_image is not None
- return True
-
-
-def get_hidden_texture(name, force_reload=False):
- t = bpy.data.textures.get(name)
- if t is None:
- 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
-
-
-def img_to_preview(img, copy_original=False):
- if bpy.app.version[0] >= 3:
- img.preview_ensure()
- if not copy_original:
- return;
- if img.preview.image_size != img.size:
- img.preview.image_size = (img.size[0], img.size[1])
- img.preview.image_pixels_float = img.pixels[:]
- # img.preview.icon_size = (img.size[0], img.size[1])
- # img.preview.icon_pixels_float = img.pixels[:]
-
-
-def get_hidden_image(tpath, bdata_name, force_reload=False, colorspace='sRGB'):
- if bdata_name[0] == '.':
- hidden_name = bdata_name
- else:
- hidden_name = '.%s' % bdata_name
- img = bpy.data.images.get(hidden_name)
-
- if tpath.startswith('//'):
- tpath = bpy.path.abspath(tpath)
-
- if img == None or (img.filepath != tpath):
- if tpath.startswith('//'):
- tpath = bpy.path.abspath(tpath)
- if not os.path.exists(tpath) or os.path.isdir(tpath):
- tpath = paths.get_addon_thumbnail_path('thumbnail_notready.jpg')
-
- if img is None:
- img = bpy.data.images.load(tpath)
- img_to_preview(img)
- img.name = hidden_name
- else:
- if img.filepath != tpath:
- if img.packed_file is not None:
- img.unpack(method='USE_ORIGINAL')
-
- img.filepath = tpath
- img.reload()
- img_to_preview(img)
- image_utils.set_colorspace(img, colorspace)
-
- elif force_reload:
- if img.packed_file is not None:
- img.unpack(method='USE_ORIGINAL')
- img.reload()
- img_to_preview(img)
- image_utils.set_colorspace(img, colorspace)
-
- return img
-
-
-def get_thumbnail(name):
- p = paths.get_addon_thumbnail_path(name)
- name = '.%s' % name
- img = bpy.data.images.get(name)
- if img == None:
- img = bpy.data.images.load(p)
- image_utils.set_colorspace(img, 'sRGB')
- img.name = name
- img.name = name
-
- return img
-
-
-def files_size_to_text(size):
- fsmb = size / (1024 * 1024)
- fskb = size % 1024
- if fsmb == 0:
- return f'{round(fskb)}KB'
- else:
- return f'{round(fsmb, 1)}MB'
-
-
-def get_brush_props(context):
- brush = get_active_brush()
- if brush is not None:
- return brush.blenderkit
- return None
-
-
-def p(text, text1='', text2='', text3='', text4='', text5='', level='DEBUG'):
- '''debug printing depending on blender's debug value'''
-
- if 1: # bpy.app.debug_value != 0:
- # print('-----BKit debug-----\n')
- # traceback.print_stack()
- texts = [text1, text2, text3, text4, text5]
- text = str(text)
- for t in texts:
- if t != '':
- text += ' ' + str(t)
-
- bk_logger.debug(text)
- # print('---------------------\n')
-
-
-def copy_asset(fp1, fp2):
- '''synchronizes the asset between folders, including it's texture subdirectories'''
- if 1:
- bk_logger.debug('copy asset')
- bk_logger.debug(fp1 + ' ' + fp2)
- if not os.path.exists(fp2):
- shutil.copyfile(fp1, fp2)
- bk_logger.debug('copied')
- source_dir = os.path.dirname(fp1)
- target_dir = os.path.dirname(fp2)
- for subdir in os.scandir(source_dir):
- if not subdir.is_dir():
- continue
- target_subdir = os.path.join(target_dir, subdir.name)
- if os.path.exists(target_subdir):
- continue
- bk_logger.debug(str(subdir) + ' ' + str(target_subdir))
- shutil.copytree(subdir, target_subdir)
- bk_logger.debug('copied')
-
- # except Exception as e:
- # print('BlenderKit failed to copy asset')
- # print(fp1, fp2)
- # print(e)
-
-
-def pprint(data, data1=None, data2=None, data3=None, data4=None):
- '''pretty print jsons'''
- p(json.dumps(data, indent=4, sort_keys=True))
-
-
-def get_hierarchy(ob):
- '''get all objects in a tree'''
- obs = []
- doobs = [ob]
- # pprint('get hierarchy')
- pprint(ob.name)
- while len(doobs) > 0:
- o = doobs.pop()
- doobs.extend(o.children)
- obs.append(o)
- return obs
-
-
-def select_hierarchy(ob, state=True):
- obs = get_hierarchy(ob)
- for ob in obs:
- ob.select_set(state)
- return obs
-
-
-def delete_hierarchy(ob):
- obs = get_hierarchy(ob)
- bpy.ops.object.delete({"selected_objects": obs})
-
-
-def get_bounds_snappable(obs, use_modifiers=False):
- # progress('getting bounds of object(s)')
- parent = obs[0]
- while parent.parent is not None:
- parent = parent.parent
- maxx = maxy = maxz = -10000000
- minx = miny = minz = 10000000
-
- s = bpy.context.scene
-
- obcount = 0 # calculates the mesh obs. Good for non-mesh objects
- matrix_parent = parent.matrix_world
- for ob in obs:
- # bb=ob.bound_box
- mw = ob.matrix_world
- subp = ob.parent
- # while parent.parent is not None:
- # mw =
-
- if ob.type == 'MESH' or ob.type == 'CURVE':
- # If to_mesh() works we can use it on curves and any other ob type almost.
- # disabled to_mesh for 2.8 by now, not wanting to use dependency graph yet.
- depsgraph = bpy.context.evaluated_depsgraph_get()
-
- object_eval = ob.evaluated_get(depsgraph)
- if ob.type == 'CURVE':
- mesh = object_eval.to_mesh()
- else:
- mesh = object_eval.data
-
- # to_mesh(context.depsgraph, apply_modifiers=self.applyModifiers, calc_undeformed=False)
- obcount += 1
- if mesh is not None:
- for c in mesh.vertices:
- coord = c.co
- parent_coord = matrix_parent.inverted() @ mw @ Vector(
- (coord[0], coord[1], coord[2])) # copy this when it works below.
- minx = min(minx, parent_coord.x)
- miny = min(miny, parent_coord.y)
- minz = min(minz, parent_coord.z)
- maxx = max(maxx, parent_coord.x)
- maxy = max(maxy, parent_coord.y)
- maxz = max(maxz, parent_coord.z)
- # bpy.data.meshes.remove(mesh)
- if ob.type == 'CURVE':
- object_eval.to_mesh_clear()
-
- if obcount == 0:
- minx, miny, minz, maxx, maxy, maxz = 0, 0, 0, 0, 0, 0
-
- minx *= parent.scale.x
- maxx *= parent.scale.x
- miny *= parent.scale.y
- maxy *= parent.scale.y
- minz *= parent.scale.z
- maxz *= parent.scale.z
-
- return minx, miny, minz, maxx, maxy, maxz
-
-
-def get_bounds_worldspace(obs, use_modifiers=False):
- # progress('getting bounds of object(s)')
- s = bpy.context.scene
- maxx = maxy = maxz = -10000000
- minx = miny = minz = 10000000
- obcount = 0 # calculates the mesh obs. Good for non-mesh objects
- for ob in obs:
- # bb=ob.bound_box
- mw = ob.matrix_world
- if ob.type == 'MESH' or ob.type == 'CURVE':
- depsgraph = bpy.context.evaluated_depsgraph_get()
- ob_eval = ob.evaluated_get(depsgraph)
- mesh = ob_eval.to_mesh()
- obcount += 1
- if mesh is not None:
- for c in mesh.vertices:
- coord = c.co
- world_coord = mw @ Vector((coord[0], coord[1], coord[2]))
- minx = min(minx, world_coord.x)
- miny = min(miny, world_coord.y)
- minz = min(minz, world_coord.z)
- maxx = max(maxx, world_coord.x)
- maxy = max(maxy, world_coord.y)
- maxz = max(maxz, world_coord.z)
- ob_eval.to_mesh_clear()
-
- if obcount == 0:
- minx, miny, minz, maxx, maxy, maxz = 0, 0, 0, 0, 0, 0
- return minx, miny, minz, maxx, maxy, maxz
-
-
-def is_linked_asset(ob):
- return ob.get('asset_data') and ob.instance_collection != None
-
-
-def get_dimensions(obs):
- minx, miny, minz, maxx, maxy, maxz = get_bounds_snappable(obs)
- bbmin = Vector((minx, miny, minz))
- bbmax = Vector((maxx, maxy, maxz))
- dim = Vector((maxx - minx, maxy - miny, maxz - minz))
- return dim, bbmin, bbmax
-
-
-def requests_post_thread(url, json, headers):
- r = rerequests.post(url, json=json, verify=True, headers=headers)
-
-
-def get_headers(api_key):
- headers = {
- "accept": "application/json",
- }
- if api_key != '':
- headers["Authorization"] = "Bearer %s" % api_key
- return headers
-
-
-def scale_2d(v, s, p):
- '''scale a 2d vector with a pivot'''
- return (p[0] + s[0] * (v[0] - p[0]), p[1] + s[1] * (v[1] - p[1]))
-
-
-def scale_uvs(ob, scale=1.0, pivot=Vector((.5, .5))):
- mesh = ob.data
- if len(mesh.uv_layers) > 0:
- uv = mesh.uv_layers[mesh.uv_layers.active_index]
-
- # Scale a UV map iterating over its coordinates to a given scale and with a pivot point
- for uvindex in range(len(uv.data)):
- uv.data[uvindex].uv = scale_2d(uv.data[uvindex].uv, scale, pivot)
-
-
-# map uv cubic and switch of auto tex space and set it to 1,1,1
-def automap(target_object=None, target_slot=None, tex_size=1, bg_exception=False, just_scale=False):
- wm = bpy.context.window_manager
- mat_props = wm.blenderkit_mat
- if mat_props.automap:
- tob = bpy.data.objects[target_object]
- # only automap mesh models
- if tob.type == 'MESH' and len(tob.data.polygons) > 0:
- # check polycount for a rare case where no polys are in editmesh
- actob = bpy.context.active_object
- bpy.context.view_layer.objects.active = tob
-
- # auto tex space
- if tob.data.use_auto_texspace:
- tob.data.use_auto_texspace = False
-
- if not just_scale:
- tob.data.texspace_size = (1, 1, 1)
-
- if 'automap' not in tob.data.uv_layers:
- bpy.ops.mesh.uv_texture_add()
- uvl = tob.data.uv_layers[-1]
- uvl.name = 'automap'
-
- # TODO limit this to active material
- # tob.data.uv_textures['automap'].active = True
-
- scale = tob.scale.copy()
-
- if target_slot is not None:
- tob.active_material_index = target_slot
- bpy.ops.object.mode_set(mode='EDIT')
- bpy.ops.mesh.select_all(action='DESELECT')
-
- # this exception is just for a 2.8 background thunmbnailer crash, can be removed when material slot select works...
- if bg_exception:
- bpy.ops.mesh.select_all(action='SELECT')
- else:
- bpy.ops.object.material_slot_select()
-
- scale = (scale.x + scale.y + scale.z) / 3.0
-
- if tex_size == 0:# prevent division by zero, it's possible to have 0 in tex size by unskilled uploaders
- tex_size = 1
-
- if not just_scale:
-
- bpy.ops.uv.cube_project(
- cube_size=scale * 2.0 / (tex_size),
- correct_aspect=False) # it's * 2.0 because blender can't tell size of a unit cube :)
-
- bpy.ops.object.editmode_toggle()
- tob.data.uv_layers.active = tob.data.uv_layers['automap']
- tob.data.uv_layers["automap"].active_render = True
- # this by now works only for thumbnail preview, but should be extended to work on arbitrary objects.
- # by now, it takes the basic uv map = 1 meter. also, it now doeasn't respect more materials on one object,
- # it just scales whole UV.
- if just_scale:
- scale_uvs(tob, scale=Vector((1 / tex_size, 1 / tex_size)))
- bpy.context.view_layer.objects.active = actob
-
-
-def name_update(props):
- '''
- Update asset name function, gets run also before upload. Makes sure name doesn't change in case of reuploads,
- and only displayName gets written to server.
- '''
- scene = bpy.context.scene
- ui_props = bpy.context.window_manager.blenderkitUI
-
- # props = get_upload_props()
- if props.name_old != props.name:
- props.name_changed = True
- props.name_old = props.name
- nname = props.name.strip()
- nname = nname.replace('_', ' ')
-
- if nname.isupper():
- nname = nname.lower()
- nname = nname[0].upper() + nname[1:]
- props.name = nname
- # here we need to fix the name for blender data = ' or " give problems in path evaluation down the road.
- fname = props.name
- fname = fname.replace('\'', '')
- fname = fname.replace('\"', '')
- asset = get_active_asset()
- if ui_props.asset_type != 'HDR':
- # Here we actually rename assets datablocks, but don't do that with HDR's and possibly with others
- asset.name = fname
-
-def fmt_dimensions(p):
- '''formats dimensions to correct string'''
- dims = [p['dimensionX'],p['dimensionY'],p['dimensionZ']]
- maxl = max(dims)
- if maxl>1:
- unit = 'm'
- unitscale = 1
- elif maxl>.01:
- unit = 'cm'
- unitscale = 100
- else:
- unit = 'mm'
- unitscale = 1000
- s = f'{fmt_length(dims[0]*unitscale)}×{fmt_length(dims[1]*unitscale)}×{fmt_length(dims[2]*unitscale)} {unit}'
- return s
-
-def fmt_length(prop):
- prop = str(round(prop, 2))
- return prop
-
-
-def get_param(asset_data, parameter_name, default=None):
- if not asset_data.get('dictParameters'):
- # this can appear in older version files.
- return default
-
- return asset_data['dictParameters'].get(parameter_name, default)
-
- # for p in asset_data['parameters']:
- # if p.get('parameterType') == parameter_name:
- # return p['value']
- # return default
-
-
-def params_to_dict(params):
- params_dict = {}
- for p in params:
- params_dict[p['parameterType']] = p['value']
- return params_dict
-
-
-def dict_to_params(inputs, parameters=None):
- if parameters == None:
- parameters = []
- for k in inputs.keys():
- if type(inputs[k]) == list:
- strlist = ""
- for idx, s in enumerate(inputs[k]):
- strlist += s
- if idx < len(inputs[k]) - 1:
- strlist += ','
-
- value = "%s" % strlist
- elif type(inputs[k]) != bool:
- value = inputs[k]
- else:
- value = str(inputs[k])
- parameters.append(
- {
- "parameterType": k,
- "value": value
- })
- return parameters
-
-
-def update_tags(self, context):
- props = self
-
- commasep = props.tags.split(',')
- ntags = []
- for tag in commasep:
- if len(tag) > 19:
- short_tags = tag.split(' ')
- for short_tag in short_tags:
- if len(short_tag) > 19:
- short_tag = short_tag[:18]
- ntags.append(short_tag)
- else:
- ntags.append(tag)
- if len(ntags) == 1:
- ntags = ntags[0].split(' ')
- ns = ''
- for t in ntags:
- if t != '':
- ns += t + ','
- ns = ns[:-1]
- if props.tags != ns:
- props.tags = ns
-
-
-def user_logged_in():
- a = bpy.context.window_manager.get('bkit profile')
- if a is not None:
- return True
- return False
-
-
-def profile_is_validator():
- a = bpy.context.window_manager.get('bkit profile')
- if a is not None and a['user'].get('exmenu'):
- return True
- return False
-
-
-def user_is_owner(asset_data=None):
- '''Checks if the current logged in user is owner of the asset'''
- profile = bpy.context.window_manager.get('bkit profile')
- if profile is None:
- return False
- if int(asset_data['author']['id']) == int(profile['user']['id']):
- return True
- return False
-
-
-def asset_from_newer_blender_version(asset_data):
- '''checks if asset is from a newer blender version, to avoid incompatibility'''
- bver = bpy.app.version
- aver = asset_data['sourceAppVersion'].split('.')
- bver_f = bver[0] + bver[1] * .01 + bver[2] * .0001
- if len(aver)>=3:
- aver_f = int(aver[0]) + int(aver[1]) * .01 + int(aver[2]) * .0001
- return aver_f>bver_f
- return False
-
-def guard_from_crash():
- '''
- Blender tends to crash when trying to run some functions
- with the addon going through unregistration process.
- This function is used in these functions (like draw callbacks)
- so these don't run during unregistration.
- '''
- if bpy.context.preferences.addons.get('blenderkit') is None:
- return False;
- if bpy.context.preferences.addons['blenderkit'].preferences is None:
- return False;
- return True
-
-
-def get_largest_area(area_type='VIEW_3D'):
- maxsurf = 0
- maxa = None
- maxw = None
- region = None
- for w in bpy.data.window_managers[0].windows:
- for a in w.screen.areas:
- if a.type == area_type:
- asurf = a.width * a.height
- if asurf > maxsurf:
- maxa = a
- maxw = w
- maxsurf = asurf
-
- for r in a.regions:
- if r.type == 'WINDOW':
- region = r
- global active_area_pointer, active_window_pointer, active_region_pointer
- active_window_pointer = maxw.as_pointer()
- active_area_pointer = maxa.as_pointer()
- active_region_pointer = region.as_pointer()
- return maxw, maxa, region
-
-
-def get_fake_context(context, area_type='VIEW_3D'):
- C_dict = {} # context.copy() #context.copy was a source of problems - incompatibility with addons that also define context
- C_dict.update(region='WINDOW')
-
- # try:
- # context = context.copy()
- # # print('bk context copied successfully')
- # except Exception as e:
- # print(e)
- # print('BlenderKit: context.copy() failed. Can be a colliding addon.')
- context = {}
-
- if context.get('area') is None or context.get('area').type != area_type:
- w, a, r = get_largest_area(area_type=area_type)
- if w:
- # sometimes there is no area of the requested type. Let's face it, some people use Blender without 3d view.
- override = {'window': w, 'screen': w.screen, 'area': a, 'region': r}
- C_dict.update(override)
- # print(w,a,r)
- return C_dict
-
-
-# def is_url(text):
-
-
-def label_multiline(layout, text='', icon='NONE', width=-1, max_lines=10, split_last = 0):
- '''
- draw a ui label, but try to split it in multiple lines.
-
- Parameters
- ----------
- layout
- text
- icon
- width width to split by in character count
- max_lines maximum lines to draw
-
- Returns
- -------
- rows of the text(to add extra elements)
- '''
- rows = []
- if text.strip() == '':
- return [layout.row()]
- text = text.replace('\r\n', '\n')
- lines = text.split('\n')
- if width > 0:
- threshold = int(width / 5.5)
- else:
- threshold = 35
- li = 0
- for l in lines:
- # if is_url(l):
- li += 1
- while len(l) > threshold:
- i = l.rfind(' ', 0, threshold)
- if i < 1:
- i = threshold
- l1 = l[:i]
- row = layout.row()
- row.label(text=l1, icon=icon)
- rows.append(row)
- icon = 'NONE'
- l = l[i:].lstrip()
- li += 1
- if li > max_lines:
- break;
- if li > max_lines:
- break;
- row = layout.row()
- if split_last > 0:
- row = row.split(factor=split_last)
- row.label(text=l, icon=icon)
- rows.append(row)
- icon = 'NONE'
- # if li > max_lines:
- return rows
-
-
-def is_upload_old(asset_data):
- '''
- estimates if the asset is far too long in the 'uploaded' state
- This returns the number of days the validation is over the limit.
- '''
- date_time_str = asset_data["created"][:10]
- # date_time_str = 'Jun 28 2018 7:40AM'
- date_time_obj = datetime.datetime.strptime(date_time_str, '%Y-%m-%d')
- today = date_time_obj.today()
- age = today - date_time_obj
- old = datetime.timedelta(days=5)
- if age > old:
- return (age.days - old.days)
- return 0
-
-def trace():
- traceback.print_stack()
diff --git a/blenderkit/version_checker.py b/blenderkit/version_checker.py
deleted file mode 100644
index 37aeadc4..00000000
--- a/blenderkit/version_checker.py
+++ /dev/null
@@ -1,80 +0,0 @@
-# ##### 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 #####
-
-import bpy
-from blenderkit import paths
-
-import requests, os, json, threading
-
-
-def get_addon_version():
- # should return addon version, but since Blender 3.0 this is synced with Blender version
- ver = bpy.app.version
- return '%i.%i.%i' % (ver[0], ver[1], ver[2])
-
-
- # import blenderkit
- # ver = blenderkit.bl_info['version']
- # return '%i.%i.%i' % (ver[0], ver[1], ver[2])
-
-
-def check_version(url, api_key, module):
- headers = {
- "accept": "application/json",
- "Authorization": "Bearer %s" % api_key}
-
- print('checking online version of module %s' % str(module.bl_info['name']))
- try:
- r = requests.get(url, headers=headers)
- data = r.json()
- ver_online = {
- 'addonVersion2.8': data['addonVersion']
- }
- tempdir = paths.get_temp_dir()
-
- ver_filepath = os.path.join(tempdir, 'addon_version.json')
- with open(ver_filepath, 'w', encoding = 'utf-8') as s:
- json.dump(ver_online, s, ensure_ascii=False, indent=4)
- except:
- print("couldn't check online for version updates")
-
-
-def compare_versions(module):
- try:
- ver_local = module.bl_info['version']
- ver_local_float = ver_local[0] + .01 * ver_local[1] + .0001 * ver_local[2]
-
- tempdir = paths.get_temp_dir()
- ver_filepath = os.path.join(tempdir, 'addon_version.json')
- with open(ver_filepath, 'r',encoding='utf-8') as s:
- data = json.load(s)
-
- ver_online = data['addonVersion2.8'].split('.')
- ver_online_float = int(ver_online[0]) + .01 * int(ver_online[1]) + .0001 * int(ver_online[2])
-
- # print('versions: installed-%s, online-%s' % (str(ver_local_float), str(ver_online_float)))
- if ver_online_float > ver_local_float:
- return True
- except:
- print("couldn't compare addon versions")
- return False
-
-
-def check_version_thread(url, API_key, module):
- thread = threading.Thread(target=check_version, args=([url, API_key, module]), daemon=True)
- thread.start()