Skip to content
Snippets Groups Projects
search.py 37.2 KiB
Newer Older
  • Learn to ignore specific revisions
  • Vilem Duha's avatar
    Vilem Duha committed
    # ##### 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 #####
    
    if "bpy" in locals():
        import imp
    
        imp.reload(paths)
        imp.reload(utils)
    
        imp.reload(categories)
        imp.reload(ui)
        imp.reload(version_checker)
    
    Vilem Duha's avatar
    Vilem Duha committed
        imp.reload(tasks_queue)
    
    Vilem Duha's avatar
    Vilem Duha committed
    else:
    
        from blenderkit import paths, utils, categories, ui, bkit_oauth, version_checker, tasks_queue
    
    Vilem Duha's avatar
    Vilem Duha committed
    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 tempfile
    import json
    import bpy
    
    search_start_time = 0
    prev_time = 0
    
    
    Vilem Duha's avatar
    Vilem Duha committed
    def check_errors(rdata):
        if rdata.get('statusCode') == 401:
            if rdata.get('detail') == 'Invalid token.':
    
                user_preferences = bpy.context.preferences.addons['blenderkit'].preferences
    
                if user_preferences.api_key != '':
    
                    bkit_oauth.refresh_token_thread()
    
                    return False, "You've been logged out. Logging in...."
    
    Vilem Duha's avatar
    Vilem Duha committed
                return False, 'Missing or wrong api_key in addon preferences'
        return True, ''
    
    
    search_threads = []
    thumb_sml_download_threads = {}
    thumb_full_download_threads = {}
    reports = ''
    
    
    def refresh_token_timer():
        ''' this timer gets run every 20 hours. It refreshes tokens and categories.'''
    
        print('refresh timer')
    
        fetch_server_data()
        categories.load_categories()
        return 72000
    
    Vilem Duha's avatar
    Vilem Duha committed
    @persistent
    def scene_load(context):
        wm = bpy.context.window_manager
        fetch_server_data()
        # following doesn't necessarilly happen if version isn't checked yet or similar, first run.
    
        # wm['bkit_update'] = version_checker.compare_versions(blenderkit)
    
        categories.load_categories()
        if not bpy.app.timers.is_registered(refresh_token_timer):
    
            bpy.app.timers.register(refresh_token_timer, persistent=True, first_interval=72000)
    
    Vilem Duha's avatar
    Vilem Duha committed
    
    
    def fetch_server_data():
        ''' download categories and addon version'''
        user_preferences = bpy.context.preferences.addons['blenderkit'].preferences
        url = paths.BLENDERKIT_ADDON_URL
        api_key = user_preferences.api_key
    
        # version_checker.check_version_thread(url, api_key, blenderkit)
    
        bkit_oauth.refresh_token_thread()
    
    Vilem Duha's avatar
    Vilem Duha committed
        get_profile()
    
    Vilem Duha's avatar
    Vilem Duha committed
        categories.fetch_categories_thread(api_key)
    
    
    @bpy.app.handlers.persistent
    def timer_update():  # TODO might get moved to handle all blenderkit stuff.
    
        global search_threads
        # don't do anything while dragging - this could switch asset type during drag, and make results list lenght different,
        # causing a lot of throuble literally.
        if len(search_threads) == 0 or bpy.context.scene.blenderkitUI.dragging:
            return 1
        for thread in search_threads:  # TODO this doesn't check all processess when removal... mostly 1 process will be running however.
    
            if not thread[0].is_alive():
                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.
                s = bpy.context.scene
                asset_type = thread[2]
                if asset_type == 'model':
                    props = scene.blenderkit_models
                    json_filepath = os.path.join(icons_dir, 'model_searchresult.json')
                    search_name = 'bkit model search'
                if asset_type == 'scene':
                    props = scene.blenderkit_scene
                    json_filepath = os.path.join(icons_dir, 'scene_searchresult.json')
                    search_name = 'bkit scene search'
                if asset_type == 'material':
                    props = scene.blenderkit_mat
                    json_filepath = os.path.join(icons_dir, 'material_searchresult.json')
                    search_name = 'bkit material search'
                if asset_type == 'brush':
                    props = scene.blenderkit_brush
                    json_filepath = os.path.join(icons_dir, 'brush_searchresult.json')
                    search_name = 'bkit brush search'
    
                s[search_name] = []
    
                global reports
                if reports != '':
                    props.report = str(reports)
                    return .2
                with open(json_filepath, 'r') as data_file:
                    rdata = json.load(data_file)
    
                result_field = []
                ok, error = check_errors(rdata)
                if ok:
    
                    for r in rdata['results']:
    
                        if r['assetType'] == asset_type:
                            # print(r)
                            if len(r['files']) > 0:
                                furl = None
                                tname = None
                                allthumbs = []
                                durl, tname = None, None
                                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.
    
                                    tdict = {}
                                    for i, t in enumerate(allthumbs):
                                        tdict['thumbnail_%i'] = t
                                    if f['fileType'] == 'blend':
                                        durl = f['downloadUrl'].split('?')[0]
                                        # fname = paths.extract_filename_from_url(f['filePath'])
                                if durl and tname:
    
                                    tooltip = generate_tooltip(r)
                                    # utils.pprint(print(r))
                                    asset_data = {'thumbnail': tname,
                                                  'thumbnail_small': small_tname,
                                                  # 'thumbnails':allthumbs,
                                                  'download_url': durl,
                                                  'id': r['id'],
                                                  'asset_base_id': r['assetBaseId'],
                                                  'name': r['name'],
                                                  'asset_type': r['assetType'],
                                                  'tooltip': tooltip,
                                                  'tags': r['tags'],
                                                  'can_download': r.get('canDownload', True),
    
    Vilem Duha's avatar
    Vilem Duha committed
                                                  'verification_status': r['verificationStatus'],
                                                  'author_id': str(r['author']['id'])
                                                  # 'author': r['author']['firstName'] + ' ' + r['author']['lastName']
    
    Vilem Duha's avatar
    Vilem Duha committed
                                                  # 'description': r['description'],
                                                  # 'author': r['description'],
                                                  }
                                    asset_data['downloaded'] = 0
    
                                    # parse extra params needed for blender here
                                    params = 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)
                                    if r['assetBaseId'] in scene.get('assets used', {}).keys():
                                        asset_data['downloaded'] = 100
    
                                    result_field.append(asset_data)
    
                                    # results = rdata['results']
                    s[search_name] = result_field
                    s['search results'] = result_field
    
    Vilem Duha's avatar
    Vilem Duha committed
                    s[search_name + ' orig'] = rdata
    
    Vilem Duha's avatar
    Vilem Duha committed
                    s['search results orig'] = rdata
                    load_previews()
                    ui_props = bpy.context.scene.blenderkitUI
                    if len(result_field) < ui_props.scrolloffset:
                        ui_props.scrolloffset = 0
                    props.is_searching = False
                    props.search_error = False
                    props.report = 'Open assetbar to see %i results. ' % len(s['search results'])
                    if len(s['search results']) == 0:
                        props.report = 'No matching results found.'
    
                # (rdata['next'])
                # if rdata['next'] != None:
                # search(False, get_next = True)
                else:
                    print('error', error)
                    props.report = error
                    props.search_error = True
    
    
    Vilem Duha's avatar
    Vilem Duha committed
                # print('finished search thread')
    
    Vilem Duha's avatar
    Vilem Duha committed
                mt('preview loading finished')
        return .2
    
    
    def load_previews():
        mappingdict = {
            'MODEL': 'model',
            'SCENE': 'scene',
            'MATERIAL': 'material',
            'TEXTURE': 'texture',
            'BRUSH': 'brush'
        }
        scene = bpy.context.scene
        # FIRST START SEARCH
        props = scene.blenderkitUI
    
        directory = paths.get_temp_dir('%s_search' % mappingdict[props.asset_type])
        s = bpy.context.scene
        results = s.get('search results')
        #
        if results is not None:
            inames = []
            tpaths = []
    
            i = 0
            for r in results:
    
                tpath = os.path.join(directory, r['thumbnail_small'])
    
                iname = utils.previmg_name(i)
    
                if os.path.exists(tpath):  # sometimes we are unlucky...
                    img = bpy.data.images.get(iname)
                    if img is None:
                        img = bpy.data.images.load(tpath)
                        img.name = iname
                    elif img.filepath != tpath:
                        # had to add this check for autopacking files...
                        if img.packed_file is not None:
                            img.unpack(method='USE_ORIGINAL')
                        img.filepath = tpath
                        img.reload()
    
                    img.colorspace_settings.name = 'Linear'
    
    Vilem Duha's avatar
    Vilem Duha committed
                i += 1
    
    Vilem Duha's avatar
    Vilem Duha committed
        # print('previews loaded')
    
    Vilem Duha's avatar
    Vilem Duha committed
    
    
    #  line splitting for longer texts...
    def split_subs(text):
        if text == '':
            return []
        threshold = 40
        text = text.rstrip()
        lines = []
        while len(text) > threshold:
            i = text.rfind(' ', 0, threshold)
            i1 = text.rfind(',', 0, threshold)
            i = max(i, i1)
            if i == -1:
                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):  # for longer texts
        dlines = split_subs(input)
        for i, l in enumerate(dlines):
            t += '%s\n' % l
        return t
    
    
    def writeblockm(tooltip, mdata, key='', pretext=None):  # 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)
    
    Vilem Duha's avatar
    Vilem Duha committed
            intext = str(intext)
            if intext.rstrip() == '':
                return tooltip
            if pretext == None:
                pretext = key
            if pretext != '':
                pretext = pretext + ': '
            text = pretext + intext
            dlines = split_subs(text)
            for i, l in enumerate(dlines):
                tooltip += '%s\n' % l
    
        return tooltip
    
    
    def fmt_length(prop):
        prop = str(round(prop, 2)) + 'm'
        return prop
    
    
    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 params_to_dict(params):
        params_dict = {}
        for p in params:
            params_dict[p['parameterType']] = p['value']
        return params_dict
    
    
    def generate_tooltip(mdata):
        if type(mdata['parameters']) == list:
            mparams = params_to_dict(mdata['parameters'])
        else:
            mparams = mdata['parameters']
        t = ''
        t = writeblock(t, mdata['name'])
    
        t = writeblockm(t, mdata, key='description', pretext='')
        if mdata['description'] != '':
            t += '\n'
    
        bools = (('rig', None), ('animated', None), ('manifold', 'non-manifold'), ('scene', None), ('simulation', None),
                 ('uv', None))
        for b in bools:
            if mparams.get(b[0]):
                mdata['tags'].append(b[0])
            elif b[1] != None:
                mdata['tags'].append(b[1])
    
        bools_data = ('adult',)
        for b in bools_data:
            if mdata.get(b) and mdata[b]:
                mdata['tags'].append(b)
        t = writeblockm(t, mparams, key='designer', pretext='designer')
        t = writeblockm(t, mparams, key='manufacturer', pretext='manufacturer')
        t = writeblockm(t, mparams, key='designCollection', pretext='design collection')
    
        # t = writeblockm(t, mparams, key='engines', pretext='engine')
        # t = writeblockm(t, mparams, key='model_style', pretext='style')
        # t = writeblockm(t, mparams, key='material_style', pretext='style')
        # t = writeblockm(t, mdata, key='tags')
        # t = writeblockm(t, mparams, key='condition', pretext='condition')
        # t = writeblockm(t, mparams, key='productionLevel', pretext='production level')
        if has(mdata, 'purePbr'):
            t = writeblockm(t, mparams, key='pbrType', pretext='pbr')
    
        t = writeblockm(t, mparams, key='designYear', pretext='design year')
    
        if has(mparams, 'dimensionX'):
            t += 'size: %s, %s, %s\n' % (fmt_length(mparams['dimensionX']),
                                         fmt_length(mparams['dimensionY']),
                                         fmt_length(mparams['dimensionZ']))
        if has(mparams, 'faceCount'):
            t += 'face count: %s, render: %s\n' % (mparams['faceCount'], mparams['faceCountRender'])
    
        # t = writeblockm(t, mparams, key='meshPolyType', pretext='mesh type')
        # t = writeblockm(t, mparams, key='objectCount', pretext='nubmber of objects')
    
        # t = writeblockm(t, mparams, key='materials')
        # t = writeblockm(t, mparams, key='modifiers')
        # t = writeblockm(t, mparams, key='shaders')
    
        if has(mparams, 'textureSizeMeters'):
    
            t += 'texture size: %s\n' % fmt_length(mparams['textureSizeMeters'])
    
    Vilem Duha's avatar
    Vilem Duha committed
    
        if has(mparams, 'textureResolutionMax') and mparams['textureResolutionMax'] > 0:
            if mparams['textureResolutionMin'] == mparams['textureResolutionMax']:
                t = writeblockm(t, mparams, key='textureResolutionMin', pretext='texture resolution')
            else:
                t += 'tex resolution: %i - %i\n' % (mparams['textureResolutionMin'], mparams['textureResolutionMax'])
    
        if has(mparams, 'thumbnailScale'):
            t = writeblockm(t, mparams, key='thumbnailScale', pretext='preview scale')
    
        # t += 'uv: %s\n' % mdata['uv']
        t += '\n'
        # t = writeblockm(t, mdata, key='license')
    
        # generator is for both upload preview and search, this is only after search
        if mdata.get('versionNumber'):
            # t = writeblockm(t, mdata, key='versionNumber', pretext='version')
    
    Vilem Duha's avatar
    Vilem Duha committed
            a_id = mdata['author'].get('id')
            if a_id != None:
                adata = bpy.context.window_manager['bkit authors'].get(str(a_id))
                if adata != None:
                    t += generate_author_textblock(adata)
    
        t += '\n'
        t += get_random_tip(mdata)
    
    Vilem Duha's avatar
    Vilem Duha committed
        return t
    
    
    def get_random_tip(mdata):
        at = mdata['assetType']
        t = ''
    
    Vilem Duha's avatar
    Vilem Duha committed
        if at == 'brush' or at == 'texture':
            t += 'click to link %s' % mdata['assetType']
        if at == 'model' or at == 'material':
    
    Vilem Duha's avatar
    Vilem Duha committed
            tips = ['Click or drag in scene to link/append %s' % mdata['assetType'],
    
                    "'A' key to search assets by same author",
    
    Vilem Duha's avatar
    Vilem Duha committed
                    "'W' key to open Authors webpage",
                    ]
            tip = 'Tip: ' + random.choice(tips)
            t = writeblock(t, tip)
    
    Vilem Duha's avatar
    Vilem Duha committed
    def generate_author_textblock(adata):
        t = ''
        if adata not in (None, ''):
            t += 'author: %s %s\n' % (adata['firstName'], adata['lastName'])
    
            t = writeblockm(t, adata, key='aboutMe', pretext='about me')
    
    Vilem Duha's avatar
    Vilem Duha committed
            t += '\n'
            t = writeblockm(t, adata, key='aboutMeUrl', pretext='')
    
    Vilem Duha's avatar
    Vilem Duha committed
        return t
    
    
    def get_items_models(self, context):
        global search_items_models
        return search_items_models
    
    
    def get_items_brushes(self, context):
        global search_items_brushes
        return search_items_brushes
    
    
    def get_items_materials(self, context):
        global search_items_materials
        return search_items_materials
    
    
    def get_items_textures(self, context):
        global search_items_textures
        return search_items_textures
    
    
    class ThumbDownloader(threading.Thread):
        query = None
    
        def __init__(self, url, path):
            super(ThumbDownloader, self).__init__()
            self.url = url
            self.path = path
            self._stop_event = threading.Event()
    
        def stop(self):
            self._stop_event.set()
    
        def stopped(self):
            return self._stop_event.is_set()
    
        def run(self):
            r = requests.get(self.url, stream=False)
            if r.status_code == 200:
                with open(self.path, 'wb') as f:
                    f.write(r.content)
                # ORIGINALLY WE DOWNLOADED THUMBNAILS AS STREAM, BUT THIS WAS TOO SLOW.
                # with open(path, 'wb') as f:
                #     for chunk in r.iter_content(1048576*4):
                #         f.write(chunk)
    
    
    
    Vilem Duha's avatar
    Vilem Duha committed
    def write_author(a_id, adata):
    
        # utils.p('writing author back')
    
    Vilem Duha's avatar
    Vilem Duha committed
        authors = bpy.context.window_manager['bkit authors']
        if authors.get(a_id) in (None, ''):
    
            adata['tooltip'] = generate_author_textblock(adata)
    
    Vilem Duha's avatar
    Vilem Duha committed
            authors[a_id] = adata
    
    
    def fetch_author(a_id, api_key):
        utils.p('fetch author')
        try:
            a_url = paths.get_api_url() + 'accounts/' + a_id + '/'
            headers = utils.get_headers(api_key)
            r = requests.get(a_url, headers=headers)
            adata = r.json()
            if not hasattr(adata, 'id'):
                utils.p(adata)
            # utils.p(adata)
            tasks_queue.add_task((write_author, (a_id, adata)))
        except Exception as e:
            utils.p(e)
        utils.p('finish fetch')
    
    
    def get_author(r):
        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 '':
            authors[a_id] = ''
            thread = threading.Thread(target=fetch_author, args=(a_id, preferences.api_key), daemon=True)
            thread.start()
        return a
    
    
    def write_profile(adata):
        utils.p('writing profile')
    
        user = adata['user']
        # we have to convert to MB here, numbers too big for python int type
    
        if user.get('sumAssetFilesSize') is not None:
    
            user['sumAssetFilesSize'] /= (1024 * 1024)
            user['sumPrivateAssetFilesSize'] /= (1024 * 1024)
            user['remainingPrivateQuota'] /= (1024 * 1024)
    
    Vilem Duha's avatar
    Vilem Duha committed
        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 = requests.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
    
    
    
    Vilem Duha's avatar
    Vilem Duha committed
    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,)))
    
    Vilem Duha's avatar
    Vilem Duha committed
        except Exception as e:
            utils.p(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()
    
    Vilem Duha's avatar
    Vilem Duha committed
        return a
    
    
    
    Vilem Duha's avatar
    Vilem Duha committed
    class Searcher(threading.Thread):
        query = None
    
        def __init__(self, query, params):
            super(Searcher, self).__init__()
            self.query = query
            self.params = params
            self._stop_event = threading.Event()
    
        def stop(self):
            self._stop_event.set()
    
        def stopped(self):
            return self._stop_event.is_set()
    
        def run(self):
            maxthreads = 300
            maximages = 50
            query = self.query
            params = self.params
            global reports
    
            t = time.time()
            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'])
    
    
            headers = utils.get_headers(params['api_key'])
    
    Vilem Duha's avatar
    Vilem Duha committed
            rdata = {}
            rdata['results'] = []
    
            if params['get_next']:
                with open(json_filepath, 'r') as infile:
                    try:
                        origdata = json.load(infile)
                        urlquery = origdata['next']
                        if urlquery == None:
                            return;
                    except:
                        # in case no search results found on drive we don't do next page loading.
                        params['get_next'] = False
            if not params['get_next']:
                # build a new request
    
    Vilem Duha's avatar
    Vilem Duha committed
    
                # build request manually
                # TODO use real queries
                requeststring = '?query=' + query['keywords'].lower() + '+'
                #
    
                for i, q in enumerate(query):
                    requeststring += q + ':' + str(query[q]).lower()
                    if i < len(query) - 1:
    
    Vilem Duha's avatar
    Vilem Duha committed
                        requeststring += '+'
    
                requeststring += '&addon_version=%s' % params['addon_version']
                if params.get('scene_uuid') is not None:
                    requeststring += '&scene_uuid=%s' % params['scene_uuid']
    
                urlquery = url + requeststring
    
            try:
    
                utils.p(urlquery)
    
    Vilem Duha's avatar
    Vilem Duha committed
                r = requests.get(urlquery, headers=headers)
                reports = ''
    
    Vilem Duha's avatar
    Vilem Duha committed
                # utils.p(r.text)
    
    Vilem Duha's avatar
    Vilem Duha committed
            except requests.exceptions.RequestException as e:
                print(e)
                reports = e
                # props.report = e
                return
            mt('response is back ')
            try:
                rdata = r.json()
            except Exception as inst:
                reports = r.text
                print(inst)
    
            mt('data parsed ')
    
            # filter results here:
            # todo remove this in future
            nresults = []
            for d in rdata.get('results', []):
                # TODO this code is for filtering brush types, should vanish after we implement filter in Elastic
                mode = None
                if query['asset_type'] == 'brush':
                    for p in d['parameters']:
                        if p['parameterType'] == 'mode':
                            mode = p['value']
                if query['asset_type'] != 'brush' or (
                        query.get('brushType') != None and query['brushType']) == mode:
                    nresults.append(d)
            rdata['results'] = nresults
    
    
    Vilem Duha's avatar
    Vilem Duha committed
            # print('number of results: ', len(rdata.get('results', [])))
    
    Vilem Duha's avatar
    Vilem Duha committed
            if self.stopped():
    
    Vilem Duha's avatar
    Vilem Duha committed
                utils.p('stopping search : ' + query['keywords'])
    
    Vilem Duha's avatar
    Vilem Duha committed
                return
    
            mt('search finished')
            i = 0
    
            thumb_small_urls = []
            thumb_small_filepaths = []
            thumb_full_urls = []
            thumb_full_filepaths = []
            # END OF PARSING
    
    Vilem Duha's avatar
    Vilem Duha committed
            getting_authors = {}
    
    Vilem Duha's avatar
    Vilem Duha committed
            for d in rdata.get('results', []):
    
    Vilem Duha's avatar
    Vilem Duha committed
                if getting_authors.get(d['author']['id']) is None:
                    get_author(d)
                    getting_authors[d['author']['id']] = True
    
    
    Vilem Duha's avatar
    Vilem Duha committed
                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(tempdir, imgname)
                        thumb_small_filepaths.append(imgpath)
    
                        imgname = paths.extract_filename_from_url(f['fileThumbnailLarge'])
                        imgpath = os.path.join(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 allready thumbnails downloaded...
            if params['get_next']:
                rdata['results'][0:0] = origdata['results']
    
            with open(json_filepath, 'w') as outfile:
                json.dump(rdata, outfile)
    
            killthreads_sml = []
            for k in thumb_sml_download_threads.keys():
                if k not in thumb_small_filepaths:
                    killthreads_sml.append(k)  # do actual killing here?
    
            killthreads_full = []
            for k in thumb_full_download_threads.keys():
                if k not in thumb_full_filepaths:
                    killthreads_full.append(k)  # do actual killing here?
            # TODO do the killing/ stopping here! remember threads might have finished inbetween!
    
            if self.stopped():
    
    Vilem Duha's avatar
    Vilem Duha committed
                utils.p('stopping search : ' + query['keywords'])
    
    Vilem Duha's avatar
    Vilem Duha committed
                return
    
            # this loop handles downloading of small thumbnails
            for imgpath, url in sml_thbs:
                if imgpath not in thumb_sml_download_threads and not os.path.exists(imgpath):
                    thread = ThumbDownloader(url, imgpath)
                    # thread = threading.Thread(target=download_thumbnail, args=([url, imgpath]),
                    #                           daemon=True)
                    thread.start()
                    thumb_sml_download_threads[imgpath] = thread
                    # threads.append(thread)
    
                    if len(thumb_sml_download_threads) > maxthreads:
                        while len(thumb_sml_download_threads) > maxthreads:
                            threads_copy = thumb_sml_download_threads.copy()  # because for loop can erase some of the items.
                            for tk, thread in threads_copy.items():
                                if not thread.is_alive():
                                    thread.join()
    
    Vilem Duha's avatar
    Vilem Duha committed
                                    # utils.p(x)
    
    Vilem Duha's avatar
    Vilem Duha committed
                                    del (thumb_sml_download_threads[tk])
    
    Vilem Duha's avatar
    Vilem Duha committed
                                    # utils.p('fetched thumbnail ', i)
    
    Vilem Duha's avatar
    Vilem Duha committed
                                    i += 1
            if self.stopped():
    
    Vilem Duha's avatar
    Vilem Duha committed
                utils.p('stopping search : ' + query['keywords'])
    
    Vilem Duha's avatar
    Vilem Duha committed
                return
            idx = 0
            while len(thumb_sml_download_threads) > 0:
                threads_copy = thumb_sml_download_threads.copy()  # because for loop can erase some of the items.
                for tk, thread in threads_copy.items():
                    if not thread.is_alive():
                        thread.join()
                        del (thumb_sml_download_threads[tk])
                        i += 1
    
            if self.stopped():
    
    Vilem Duha's avatar
    Vilem Duha committed
                utils.p('stopping search : ' + query['keywords'])
    
    Vilem Duha's avatar
    Vilem Duha committed
                return
    
            # start downloading full thumbs in the end
            for imgpath, url in full_thbs:
                if imgpath not in thumb_full_download_threads and not os.path.exists(imgpath):
                    thread = ThumbDownloader(url, imgpath)
                    # thread = threading.Thread(target=download_thumbnail, args=([url, imgpath]),
                    #                           daemon=True)
                    thread.start()
                    thumb_full_download_threads[imgpath] = thread
            mt('thumbnails finished')
    
    
    def build_query_common(query, props):
        query_common = {
            "keywords": props.search_keywords
        }
        query.update(query_common)
    
    
    # def query_add_range(query, name, rmin, rmax):
    
    def build_query_model():
        '''use all search input to request results from server'''
    
        props = bpy.context.scene.blenderkit_models
        query = {
            "asset_type": 'model',
    
            # "engine": props.search_engine,
            # "adult": props.search_adult,
    
    Vilem Duha's avatar
    Vilem Duha committed
        }
        if props.search_style != 'ANY':
            if props.search_style != 'OTHER':
    
                query["model_style"] = props.search_style
    
    Vilem Duha's avatar
    Vilem Duha committed
            else:
    
                query["model_style"] = props.search_style_other
    
    Vilem Duha's avatar
    Vilem Duha committed
        if props.search_advanced:
            if props.search_condition != 'UNSPECIFIED':
                query["condition"] = props.search_condition
            if props.search_design_year:
                query["designYearMin"] = props.search_design_year_min
                query["designYearMax"] = props.search_design_year_max
            if props.search_polycount:
                query["polyCountMin"] = props.search_polycount_min
                query["polyCountMax"] = props.search_polycount_max
            if props.search_texture_resolution:
                query["textureResolutionMin"] = props.search_texture_resolution_min
                query["textureResolutionMax"] = 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.scene.blenderkit_scene
        query = {
            "asset_type": 'scene',
    
            # "engine": props.search_engine,
    
    Vilem Duha's avatar
    Vilem Duha committed
            # "adult": props.search_adult,
        }
        build_query_common(query, props)
        return query
    
    
    def build_query_material():
        props = bpy.context.scene.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
    
    Vilem Duha's avatar
    Vilem Duha committed
        if props.search_style != 'ANY':
            if props.search_style != 'OTHER':
                query["style"] = props.search_style
            else:
                query["style"] = props.search_style_other
        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.scene.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',
    
            "brushType": 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()
    
    Vilem Duha's avatar
    Vilem Duha committed
        utils.p(text, alltime, since_last)
    
    Vilem Duha's avatar
    Vilem Duha committed
    
    
    def add_search_process(query, params):
        global search_threads
    
        while (len(search_threads) > 0):
            old_thread = search_threads.pop(0)
            old_thread[0].stop()
            # TODO CARE HERE FOR ALSO KILLING THE THREADS...AT LEAST NOW SEARCH DONE FIRST WON'T REWRITE AN OLDER ONE
    
        tempdir = paths.get_temp_dir('%s_search' % query['asset_type'])
        thread = Searcher(query, params)
        thread.start()
    
        search_threads.append([thread, tempdir, query['asset_type']])
    
        mt('thread started')
    
    
    
    def search(category='', get_next=False, author_id=''):
    
    Vilem Duha's avatar
    Vilem Duha committed
        ''' initialize searching'''
        global search_start_time
    
        user_preferences = bpy.context.preferences.addons['blenderkit'].preferences
    
    
    Vilem Duha's avatar
    Vilem Duha committed
        search_start_time = time.time()
        mt('start')
        scene = bpy.context.scene
        uiprops = scene.blenderkitUI
    
        if uiprops.asset_type == 'MODEL':
            if not hasattr(scene, 'blenderkit'):
                return;
            props = scene.blenderkit_models
            query = build_query_model()
    
        if uiprops.asset_type == 'SCENE':
            if not hasattr(scene, 'blenderkit_scene'):
                return;
            props = scene.blenderkit_scene
            query = build_query_scene()
    
        if uiprops.asset_type == 'MATERIAL':
            if not hasattr(scene, 'blenderkit_mat'):
                return;
            props = scene.blenderkit_mat
            query = build_query_material()
    
        if uiprops.asset_type == 'TEXTURE':
            if not hasattr(scene, 'blenderkit_tex'):
                return;
            # props = scene.blenderkit_tex
            # query = build_query_texture()
    
        if uiprops.asset_type == 'BRUSH':
            if not hasattr(scene, 'blenderkit_brush'):