From 2afbdc806e23fbd0ee8aedddafab6eb84b3cccb3 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Vil=C3=A9m=20Duha?= <vilda.novak@gmail.com>
Date: Thu, 28 Jan 2021 13:59:29 +0100
Subject: [PATCH] BlederKit: fix snapping with particles

Now the snap also ignores particles.
Also fix a case if user didn't fill a name in profile, now it draws at least users email.
---
 blenderkit/__init__.py     | 23 +++------
 blenderkit/asset_bar_op.py |  1 +
 blenderkit/autothumb.py    | 11 ++---
 blenderkit/resolutions.py  |  2 +-
 blenderkit/search.py       |  7 +--
 blenderkit/ui.py           | 99 +++++++++++++++++++++++++++++++++-----
 blenderkit/ui_panels.py    | 22 +++++++--
 blenderkit/upload.py       |  1 -
 8 files changed, 122 insertions(+), 44 deletions(-)

diff --git a/blenderkit/__init__.py b/blenderkit/__init__.py
index 90a50e6e7..1a8c3cef9 100644
--- a/blenderkit/__init__.py
+++ b/blenderkit/__init__.py
@@ -582,16 +582,11 @@ def name_update(self, context):
 def update_free(self, context):
     if self.is_free == False:
         self.is_free = True
-        title = "All BlenderKit materials are free"
-        message = "Any material uploaded to BlenderKit is 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 draw_message(self, context):
-            utils.label_multiline(self.layout, text=message, icon='NONE', width=-1)
-
-        bpy.context.window_manager.popup_menu(draw_message, title=title, icon='INFO')
+                  "Part of subscription is sent to artists based on usage by paying users.")
 
 
 class BlenderKitCommonUploadProps(object):
@@ -1517,15 +1512,11 @@ def fix_subdir(self, context):
     if self.project_subdir != pp:
         self.project_subdir = pp
 
-        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."
-
-        def draw_message(self, context):
-            utils.label_multiline(self.layout, text=message, icon='NONE', width=400)
+        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.")
 
-        bpy.context.window_manager.popup_menu(draw_message, title=title, icon='INFO')
 
 
 class BlenderKitAddonPreferences(AddonPreferences):
diff --git a/blenderkit/asset_bar_op.py b/blenderkit/asset_bar_op.py
index 2c445ad4b..f0092960c 100644
--- a/blenderkit/asset_bar_op.py
+++ b/blenderkit/asset_bar_op.py
@@ -516,6 +516,7 @@ class BlenderKitAssetBarOperator(BL_UI_OT_draw_operator):
         sro = bpy.context.window_manager.get('search results orig')
         if sro is not None and sro.get('next') is not None:
             blenderkit.search.search(get_next=True)
+
     def update_images(self):
         sr = bpy.context.window_manager['search results']
 
diff --git a/blenderkit/autothumb.py b/blenderkit/autothumb.py
index da6e6d29d..2a649427e 100644
--- a/blenderkit/autothumb.py
+++ b/blenderkit/autothumb.py
@@ -17,7 +17,7 @@
 # ##### END GPL LICENSE BLOCK #####
 
 
-from blenderkit import paths, utils, bg_blender
+from blenderkit import paths, utils, bg_blender, ui_panels
 
 import tempfile, os, subprocess, json, sys
 
@@ -262,13 +262,10 @@ class GenerateThumbnailOperator(bpy.types.Operator):
     def invoke(self, context, event):
         wm = context.window_manager
         if bpy.data.filepath == '':
-            title = "Can't render thumbnail"
-            message = "please save your file first"
+            ui_panels.ui_message(
+                        title = "Can't render thumbnail",
+                        message = "please save your file first")
 
-            def draw_message(self, context):
-                self.layout.label(text=message)
-
-            bpy.context.window_manager.popup_menu(draw_message, title=title, icon='INFO')
             return {'FINISHED'}
 
         return wm.invoke_props_dialog(self)
diff --git a/blenderkit/resolutions.py b/blenderkit/resolutions.py
index a5b5d7230..018889e79 100644
--- a/blenderkit/resolutions.py
+++ b/blenderkit/resolutions.py
@@ -601,7 +601,7 @@ def assets_db_path():
 
 
 def get_assets_search():
-    bpy.app.debug_value = 2
+    # bpy.app.debug_value = 2
 
     results = []
     preferences = bpy.context.preferences.addons['blenderkit'].preferences
diff --git a/blenderkit/search.py b/blenderkit/search.py
index d37b00455..c902104c7 100644
--- a/blenderkit/search.py
+++ b/blenderkit/search.py
@@ -237,7 +237,7 @@ def parse_result(r):
     # except:
     #     utils.p('asset with no files-size')
     asset_type = r['assetType']
-    if len(r['files']) > 0:
+    if len(r['files']) > 0:#TODO remove this condition so all assets are parsed.
         r['available_resolutions'] = []
         allthumbs = []
         durl, tname, small_tname = '', '', ''
@@ -403,7 +403,6 @@ def timer_update():
             result_field = []
             ok, error = check_errors(rdata)
             if ok:
-
                 bpy.ops.object.run_assetbar_fix_context()
                 for r in rdata['results']:
                     asset_data = parse_result(r)
@@ -1278,7 +1277,6 @@ def add_search_process(query, params, orig_result):
         old_thread[0].stop()
         # TODO CARE HERE FOR ALSO KILLING THE Thumbnail 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, orig_result)
     thread.start()
@@ -1343,7 +1341,6 @@ def search(category='', get_next=False, author_id=''):
     ''' initialize searching'''
     global search_start_time
     user_preferences = bpy.context.preferences.addons['blenderkit'].preferences
-
     search_start_time = time.time()
     # mt('start')
     scene = bpy.context.scene
@@ -1418,7 +1415,7 @@ def search(category='', get_next=False, author_id=''):
 
     # if free_only:
     #     query['keywords'] += '+is_free:true'
-    orig_results = scene.get(f'bkit {ui_props.asset_type.lower()} search orig', {})
+    orig_results = bpy.context.window_manager.get(f'bkit {ui_props.asset_type.lower()} search orig', {})
     if orig_results != {}:
         # ensure it's a copy in dict for what we are passing to thread:
         orig_results = orig_results.to_dict()
diff --git a/blenderkit/ui.py b/blenderkit/ui.py
index 6d797f733..72d0ad7e0 100644
--- a/blenderkit/ui.py
+++ b/blenderkit/ui.py
@@ -883,6 +883,19 @@ def draw_callback_3d(self, context):
         if ui.draw_snapped_bounds:
             draw_bbox(ui.snapped_location, ui.snapped_rotation, ui.snapped_bbox_min, ui.snapped_bbox_max)
 
+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
@@ -890,19 +903,22 @@ def deep_ray_cast(depsgraph, ray_origin, vec):
     # 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 False, Vector((0, 0, 0)), Vector((0, 0, 1)), None, None, None
+        return empty_set
 
     try_object = object
-    while try_object and try_object.display_type == 'BOUNDS':
+
+    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
-
-    return has_hit, snapped_location, snapped_normal, face_index, object, 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
@@ -1197,6 +1213,67 @@ class ParticlesDropDialog(bpy.types.Operator):
         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'''
@@ -1707,7 +1784,7 @@ class AssetBarOperator(bpy.types.Operator):
         ui_props = context.scene.blenderkitUI
         sr = bpy.context.window_manager.get('search results')
 
-        if self.do_search or sr is None:
+        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 != '':
@@ -1715,11 +1792,8 @@ class AssetBarOperator(bpy.types.Operator):
                 sprops.search_keywords = ''
             search.search(category=self.category)
 
-        if sr is None:
-            bpy.context.window_manager['search results'] = []
-
         if ui_props.assetbar_on:
-            # we don't want to run the assetbar many times, that's why it has a switch on/off behaviour,
+            # 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.
@@ -1738,7 +1812,8 @@ class AssetBarOperator(bpy.types.Operator):
         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")
@@ -1875,6 +1950,8 @@ class AssetDragOperator(bpy.types.Operator):
                 temp_mesh = object_eval.to_mesh()
                 target_slot = temp_mesh.polygons[self.face_index].material_index
                 object_eval.to_mesh_clear()
+            # elif object.is_library_indirect:#case for bring to scene objects, will be solved through prefs and direct
+            # action
             else:
                 self.report({'WARNING'}, "Invalid or library object as input:")
                 target_object = ''
diff --git a/blenderkit/ui_panels.py b/blenderkit/ui_panels.py
index deb3c0bb0..91abd4460 100644
--- a/blenderkit/ui_panels.py
+++ b/blenderkit/ui_panels.py
@@ -546,7 +546,10 @@ class VIEW3D_PT_blenderkit_profile(Panel):
             if me is not None:
                 me = me['user']
                 # user name
-                layout.label(text='Me: %s %s' % (me['firstName'], me['lastName']))
+                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
@@ -1520,10 +1523,18 @@ class UrlPopupDialog(bpy.types.Operator):
 
     def draw(self, context):
         layout = self.layout
-        utils.label_multiline(layout, text=self.message)
+        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):
@@ -1533,7 +1544,7 @@ class UrlPopupDialog(bpy.types.Operator):
     def invoke(self, context, event):
         wm = context.window_manager
 
-        return wm.invoke_props_dialog(self)
+        return wm.invoke_props_dialog(self,width = 300)
 
 
 class LoginPopupDialog(bpy.types.Operator):
@@ -1688,7 +1699,12 @@ def header_search_draw(self, context):
         layout.prop(props, "search_keywords", text="", icon='VIEWZOOM')
         draw_assetbar_show_hide(layout, props)
 
+def ui_message(title, message):
+    def draw_message(self, context):
+        layout = self.layout
+        utils.label_multiline(layout, text=message)
 
+    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 = {}
diff --git a/blenderkit/upload.py b/blenderkit/upload.py
index 8a905cecc..1901d92cc 100644
--- a/blenderkit/upload.py
+++ b/blenderkit/upload.py
@@ -673,7 +673,6 @@ class FastMetadata(bpy.types.Operator):
         except Exception as e:
             print(e)
         self.message = f"Fast edit metadata of {asset_data['name']}"
-        self.message = str(cat_path)
         self.name = asset_data['displayName']
         self.description = asset_data['description']
         self.tags = ','.join(asset_data['tags'])
-- 
GitLab