Skip to content
Snippets Groups Projects
  • Vilem Duha's avatar
    c72e6624
    BlenderKit: fix category display · c72e6624
    Vilem Duha authored
    was actually showing slugs, now shows names with links
    fix long descriptions issue - now has a 'more' button to read the rest online
    (label_multiline now has  a max_lines parameter and returns True if max lenght was reached)
    fix avatars to match server
    c72e6624
    History
    BlenderKit: fix category display
    Vilem Duha authored
    was actually showing slugs, now shows names with links
    fix long descriptions issue - now has a 'more' button to read the rest online
    (label_multiline now has  a max_lines parameter and returns True if max lenght was reached)
    fix avatars to match server
categories.py 9.33 KiB
# ##### 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

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.scene.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.scene.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.scene.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((ui.add_report,(t, 15, colors.RED)))
        bk_logger.debug(t)
        bk_logger.exception(e)
        if not os.path.exists(categories_filepath):
            source_path = paths.get_addon_file(subpath='data' + os.sep + 'categories.json')
            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()