Merge branch 'master' into xr-actions-D9124

This commit is contained in:
Peter Kim 2021-02-16 20:15:04 +09:00
commit 41d2d208a0
42 changed files with 5217 additions and 2245 deletions

View File

@ -254,7 +254,7 @@ def udate_down_up(self, context):
s = context.scene
wm = bpy.context.window_manager
props = s.blenderkitUI
if wm['search results'] == None and props.down_up == 'SEARCH':
if wm.get('search results') == None and props.down_up == 'SEARCH':
search.search()
def switch_search_results(self, context):
@ -574,7 +574,7 @@ class BlenderKitCommonSearchProps(object):
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()
utils.name_update(self)
@ -650,12 +650,12 @@ class BlenderKitCommonUploadProps(object):
('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.",
"Private assets are limited by quota",
default="PUBLIC",
)
is_procedural: BoolProperty(name="Procedural",
description="Asset is procedural - has no texture.",
description="Asset is procedural - has no texture",
default=True
)
node_count: IntProperty(name="Node count", description="Total nodes in the asset", default=0)
@ -664,7 +664,7 @@ class BlenderKitCommonUploadProps(object):
# 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.",
# "Private assets are limited by quota",
# default=False)
is_free: BoolProperty(name="Free for Everyone",
@ -870,7 +870,7 @@ class BlenderKitMaterialUploadProps(PropertyGroup, BlenderKitCommonUploadProps):
thumbnail_resolution: EnumProperty(
name="Resolution",
items=thumbnail_resolutions,
description="Thumbnail resolution.",
description="Thumbnail resolution",
default="512",
)
@ -1055,7 +1055,7 @@ class BlenderKitModelUploadProps(PropertyGroup, BlenderKitCommonUploadProps):
thumbnail_resolution: EnumProperty(
name="Resolution",
items=thumbnail_resolutions,
description="Thumbnail resolution.",
description="Thumbnail resolution",
default="512",
)
@ -1402,7 +1402,7 @@ class BlenderKitModelSearchProps(PropertyGroup, BlenderKitCommonSearchProps):
),
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="LINK_COLLECTION"
default="APPEND_OBJECTS"
)
append_link: EnumProperty(
name="How to Attach",
@ -1446,11 +1446,11 @@ class BlenderKitModelSearchProps(PropertyGroup, BlenderKitCommonSearchProps):
subtype='ANGLE')
perpendicular_snap: BoolProperty(name='Perpendicular snap',
description="Limit snapping that is close to perpendicular angles to be perpendicular.",
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.",
description="Limit perpendicular snap to be below these values",
default=.25,
min=0,
max=.5,
@ -1498,7 +1498,19 @@ class BlenderKitSceneSearchProps(PropertyGroup, BlenderKitCommonSearchProps):
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 = False
)
def fix_subdir(self, context):
'''Fixes project subdicrectory settings if people input invalid path.'''
@ -1538,7 +1550,7 @@ class BlenderKitAddonPreferences(AddonPreferences):
api_key_refresh: StringProperty(
name="BlenderKit refresh API Key",
description="API key used to refresh the token regularly.",
description="API key used to refresh the token regularly",
default="",
subtype="PASSWORD",
)
@ -1557,7 +1569,7 @@ class BlenderKitAddonPreferences(AddonPreferences):
refresh_in_progress: BoolProperty(
name="Api key refresh in progress",
description="Api key is currently being refreshed. Don't refresh it again.",
description="Api key is currently being refreshed. Don't refresh it again",
default=False
)
@ -1664,7 +1676,7 @@ class BlenderKitAddonPreferences(AddonPreferences):
use_timers: BoolProperty(
name="Use timers",
description="Use timers for BlenderKit. Usefull for debugging since timers seem to be unstable.",
description="Use timers for BlenderKit. Usefull for debugging since timers seem to be unstable",
default=True,
update=utils.save_prefs
)

View File

@ -87,6 +87,11 @@ def append_scene(file_name, scenename=None, link=False, fake_user=False):
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.scene.blenderkitUI
ui_props.down_up = 'SEARCH'
return scene

View File

@ -367,7 +367,7 @@ def get_autotags():
class AutoFillTags(bpy.types.Operator):
"""Fill tags for asset. Now run before upload, no need to interact from user side."""
"""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'}

View File

@ -153,7 +153,7 @@ class Logout(bpy.types.Operator):
class CancelLoginOnline(bpy.types.Operator):
"""Cancel login attempt."""
"""Cancel login attempt"""
bl_idname = "wm.blenderkit_login_cancel"
bl_label = "BlenderKit login cancel"

File diff suppressed because it is too large Load Diff

View File

@ -26,6 +26,7 @@ import shutil, sys, os
import uuid
import copy
import logging
bk_logger = logging.getLogger('blenderkit')
import bpy
@ -303,8 +304,7 @@ def append_asset(asset_data, **kwargs): # downloaders=[], location=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!)
#
scene = bpy.context.scene
s = bpy.context.scene
user_preferences = bpy.context.preferences.addons['blenderkit'].preferences
@ -312,16 +312,22 @@ def append_asset(asset_data, **kwargs): # downloaders=[], location=None,
user_preferences.asset_counter += 1
if asset_data['assetType'] == 'scene':
scene = append_link.append_scene(file_names[0], link=False, fake_user=False)
props = scene.blenderkit
asset_main = scene
sprops = s.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
print(sprops.switch_after_append)
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'])
hdr = append_link.load_HDR(file_name=file_names[0], name=asset_data['name'])
props = hdr.blenderkit
asset_main = hdr
s = bpy.context.scene
if asset_data['assetType'] == 'model':
downloaders = kwargs.get('downloaders')
@ -899,7 +905,7 @@ def check_existing(asset_data, resolution='blend', can_return_others=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))
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,
@ -1147,7 +1153,7 @@ def start_download(asset_data, **kwargs):
# 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('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.
@ -1220,7 +1226,7 @@ def show_enum_values(obj, prop_name):
class BlenderkitDownloadOperator(bpy.types.Operator):
"""Download and link asset to scene. Only link if asset already available locally."""
"""Download and link asset to scene. Only link if asset already available locally"""
bl_idname = "scene.blenderkit_download"
bl_label = "BlenderKit Asset Download"
bl_options = {'REGISTER', 'UNDO', 'INTERNAL'}
@ -1235,7 +1241,7 @@ class BlenderkitDownloadOperator(bpy.types.Operator):
asset_base_id: StringProperty(
name="Asset base Id",
description="Asset base id, used instead of search result index.",
description="Asset base id, used instead of search result index",
default="")
target_object: StringProperty(

View File

@ -54,6 +54,17 @@ def img_save_as(img, filepath='//', file_format='JPEG', quality=90, color_mode='
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 generate_hdr_thumbnail():
scene = bpy.context.scene
@ -79,7 +90,7 @@ def generate_hdr_thumbnail():
hdr_image.pixels.foreach_get(tempBuffer)
inew.filepath = thumb_path
inew.colorspace_settings.name = 'Linear'
set_colorspace(inew, 'Linear')
inew.pixels.foreach_set(tempBuffer)
bpy.context.view_layer.update()

View File

@ -171,7 +171,7 @@ def ensure_eevee_transparency(m):
class BringToScene(Operator):
"""Bring linked object hierarchy to scene and make it editable."""
"""Bring linked object hierarchy to scene and make it editable"""
bl_idname = "object.blenderkit_bring_to_scene"
bl_label = "BlenderKit bring objects to scene"

View File

@ -135,7 +135,7 @@ def update_ratings_work_hours(self, context):
bkit_ratings = self
url = paths.get_api_url() + f'assets/{self.asset_id}/rating/'
if bkit_ratings.rating_work_hours > 0.05:
if bkit_ratings.rating_work_hours > 0.45:
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)
@ -295,9 +295,9 @@ def update_ratings_work_hours_ui_1_5(self, context):
class FastRateMenu(Operator):
"""Fast rating of the assets directly in the asset bar - without need to download assets."""
"""Fast rating of the assets directly in the asset bar - without need to download assets"""
bl_idname = "wm.blenderkit_menu_rating_upload"
bl_label = "Send Rating"
bl_label = "Rate asset"
bl_options = {'REGISTER', 'UNDO', 'INTERNAL'}
message: StringProperty(
@ -337,9 +337,11 @@ class FastRateMenu(Operator):
rating_work_hours: FloatProperty(name="Work Hours",
description="How many hours did this work take?",
default=0.00,
min=0.0, max=150, update=update_ratings_work_hours
min=0.0, max=300, update=update_ratings_work_hours
)
high_rating_warning = "This is a high rating, please be sure to give such rating only to amazing assets"
rating_work_hours_ui: EnumProperty(name="Work Hours",
description="How many hours did this work take?",
items=[('0', '0', ''),
@ -354,11 +356,12 @@ class FastRateMenu(Operator):
('10', '10', ''),
('15', '15', ''),
('20', '20', ''),
('50', '50', ''),
('100', '100', ''),
('150', '150', ''),
('200', '200', ''),
('250', '250', ''),
('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),
],
default='0', update=update_ratings_work_hours_ui
)
@ -391,14 +394,34 @@ class FastRateMenu(Operator):
col.label(text=self.message)
row = col.row()
row.prop(self, 'rating_quality_ui', expand=True, icon_only=True, emboss=False)
# row.label(text=str(self.rating_quality))
col.separator()
col.prop(self, 'rating_work_hours')
if utils.profile_is_validator():
row = layout.row()
row.label(text=f"How many hours did this {self.asset_type} save you?")
if self.asset_type in ('model', 'scene'):
row = layout.row()
if self.asset_type == 'model':
row.prop(self, 'rating_work_hours_ui', expand=True, icon_only=False, emboss=True)
else:
row.prop(self, 'rating_work_hours_ui_1_5', expand=True, icon_only=False, emboss=True)
if utils.profile_is_validator():
col.prop(self, 'rating_work_hours')
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(layout,
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:
layout.separator()
utils.label_multiline(layout,
text=f"\nThat's a lot! please be sure to give such rating only to amazing {self.asset_type}s.\n",
width=500)
else:
row = layout.row()
row.prop(self, 'rating_work_hours_ui_1_5', expand=True, icon_only=False, emboss=True)
def execute(self, context):
user_preferences = bpy.context.preferences.addons['blenderkit'].preferences
@ -414,13 +437,13 @@ class FastRateMenu(Operator):
if self.rating_quality_ui == '':
self.rating_quality = 0
else:
self.rating_quality = int(self.rating_quality_ui)
self.rating_quality = float(self.rating_quality_ui)
if self.rating_quality > 0.1:
rtgs = (('quality', self.rating_quality),)
tasks_queue.add_task((send_rating_to_thread_quality, (url, rtgs, headers)), wait=2.5, only_last=True)
if self.rating_work_hours > 0.1:
if self.rating_work_hours > 0.45:
rtgs = (('working_hours', round(self.rating_work_hours, 1)),)
tasks_queue.add_task((send_rating_to_thread_work_hours, (url, rtgs, headers)), wait=2.5, only_last=True)
return {'FINISHED'}
@ -437,7 +460,7 @@ class FastRateMenu(Operator):
self.message = f"Rate asset {self.asset_name}"
wm = context.window_manager
if utils.profile_is_validator() and self.asset_type == 'model':
if self.asset_type in ('model','scene'):
# spawn a wider one for validators for the enum buttons
return wm.invoke_props_dialog(self, width=500)
else:

View File

@ -17,7 +17,7 @@
# ##### END GPL LICENSE BLOCK #####
from blenderkit import paths, utils, categories, ui, colors, bkit_oauth, version_checker, tasks_queue, rerequests, \
resolutions
resolutions, image_utils
import blenderkit
from bpy.app.handlers import persistent
@ -474,9 +474,9 @@ def load_previews():
img.reload()
if r['assetType'] == 'hdr':
# to display hdr thumbnails correctly, we use non-color, otherwise looks shifted
img.colorspace_settings.name = 'Non-Color'
image_utils.set_colorspace(img, 'Non-Color')
else:
img.colorspace_settings.name = 'sRGB'
image_utils.set_colorspace(img, 'sRGB')
i += 1
# print('previews loaded')
@ -660,6 +660,7 @@ def generate_tooltip(mdata):
if f['fileType'].find('resolution') > -1:
t += 'Asset has lower resolutions available\n'
break;
# 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', width = col_w)
@ -671,22 +672,27 @@ def generate_tooltip(mdata):
# t += '\n'
rc = mdata.get('ratingsCount')
if utils.profile_is_validator() and rc:
if rc:
t+='\n'
if rc:
rcount = min(rc['quality'], rc['workingHours'])
else:
rcount = 0
if rcount < 10:
t += f"Please rate this asset, \nit doesn't have enough ratings.\n"
else:
t += f"Quality rating: {int(mdata['ratingsAverage']['quality']) * '*'}\n"
t += f"Hours saved rating: {int(mdata['ratingsAverage']['workingHours'])}\n"
show_rating_threshold = 5
if rcount < show_rating_threshold:
t += f"Only assets with enough ratings \nshow the rating value. Please rate.\n"
if rc['quality'] >= show_rating_threshold:
# t += f"{int(mdata['ratingsAverage']['quality']) * '*'}\n"
t += f"* {round(mdata['ratingsAverage']['quality'],1)}\n"
if rc['workingHours'] >= show_rating_threshold:
t += f"Hours saved: {int(mdata['ratingsAverage']['workingHours'])}\n"
if utils.profile_is_validator():
t += f"Score: {int(mdata['score'])}\n"
t += f"Ratings count {rc['quality']}*/{rc['workingHours']}wh value " \
f"{mdata['ratingsAverage']['quality']}*/{mdata['ratingsAverage']['workingHours']}wh\n"
f"{(mdata['ratingsAverage']['quality'],1)}*/{(mdata['ratingsAverage']['workingHours'],1)}wh\n"
if len(t.split('\n')) < 11:
t += '\n'
t += get_random_tip(mdata)

View File

@ -338,7 +338,7 @@ def draw_tooltip(x, y, text='', author='', img=None, gravatar=None):
textcol = (textcol[0], textcol[1], textcol[2], 1)
textcol_mild = (textcol[0] * .8, textcol[1] * .8, textcol[2] * .8, 1)
textcol_strong = (textcol[0] * 1.3, textcol[1] * 2.3, textcol[2] * 1.3, 1)
textcol_strong = (0.4, 1, 0.3, 1)
# textcol_strong = (0.4, 1, 0.3, 1)
white = (1, 1, 1, .1)
# background
@ -1672,6 +1672,12 @@ class AssetBarOperator(bpy.types.Operator):
target_slot = temp_mesh.polygons[face_index].material_index
object_eval.to_mesh_clear()
else:
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.")
self.report({'WARNING'}, "Invalid or library object as input:")
target_object = ''
target_slot = ''
@ -1854,7 +1860,7 @@ 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_description = "Transfer blenderKit metadata from one object to another when fixing uploads with wrong parenting"
bl_options = {'REGISTER', 'UNDO'}
def execute(self, context):
@ -1914,6 +1920,12 @@ def draw_callback_3d_dragging(self, context):
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):
"""Draw a line with the mouse"""
@ -1942,6 +1954,11 @@ class AssetDragOperator(bpy.types.Operator):
if ui_props.asset_type == 'MATERIAL':
# 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 == 'MESH':
target_object = object.name
# create final mesh to extract correct material slot
@ -1953,6 +1970,12 @@ class AssetDragOperator(bpy.types.Operator):
# elif object.is_library_indirect:#case for bring to scene objects, will be solved through prefs and direct
# action
else:
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.")
self.report({'WARNING'}, "Invalid or library object as input:")
target_object = ''
target_slot = ''
@ -2007,6 +2030,11 @@ class AssetDragOperator(bpy.types.Operator):
target_object=target_object)
else:
if ui_props.asset_type =='SCENE':
ui_panels.ui_message(title = 'Scene will be appended after download',
message = 'After the scene is appended, you have to switch to it manually.'
'If you want to switch to scenes automatically after appending,'
' you can set it in import settings.')
bpy.ops.scene.blenderkit_download( # asset_type=ui_props.asset_type,
asset_index=self.asset_search_index)

View File

@ -112,7 +112,7 @@ def draw_upload_common(layout, props, asset_type, context):
op = layout.operator("object.blenderkit_upload", text=optext, icon='EXPORT')
op.asset_type = asset_type
op.reupload = False
#make sure everything gets uploaded.
# make sure everything gets uploaded.
op.main_file = True
op.metadata = True
op.thumbnail = True
@ -163,6 +163,7 @@ def prop_needed(layout, props, name, value, is_not_filled=''):
icon = None
row.prop(props, name)
def draw_panel_hdr_upload(self, context):
layout = self.layout
ui_props = bpy.context.scene.blenderkitUI
@ -172,7 +173,6 @@ def draw_panel_hdr_upload(self, context):
hdr = utils.get_active_HDR()
if hdr is not None:
props = hdr.blenderkit
@ -184,6 +184,7 @@ def draw_panel_hdr_upload(self, context):
layout.prop(props, 'description')
layout.prop(props, 'tags')
def draw_panel_hdr_search(self, context):
s = context.scene
props = s.blenderkit_HDR
@ -196,6 +197,7 @@ def draw_panel_hdr_search(self, context):
utils.label_multiline(layout, text=props.report)
def draw_panel_model_upload(self, context):
ob = bpy.context.active_object
while ob.parent is not None:
@ -316,7 +318,7 @@ def draw_assetbar_show_hide(layout, props):
preferences = bpy.context.preferences.addons['blenderkit'].preferences
if preferences.experimental_features:
op = layout.operator('view3d.blenderkit_asset_bar_widget', text = '', icon = icon)
op = layout.operator('view3d.blenderkit_asset_bar_widget', text='', icon=icon)
op.keep_running = False
op.do_search = False
op.tooltip = ttip
@ -407,8 +409,8 @@ class VIEW3D_PT_blenderkit_model_properties(Panel):
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='Ratings:')
draw_panel_model_rating(self, context)
# layout.label(text='Ratings:')
# draw_panel_model_rating(self, context)
layout.label(text='Asset tools:')
draw_asset_context_menu(self.layout, context, ad, from_panel=True)
@ -510,7 +512,7 @@ class VIEW3D_PT_blenderkit_ratings(Panel):
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:
if a.bkit_ratings.rating_work_hours == 0:
draw_rating_asset(self, context, asset=a)
@ -546,7 +548,7 @@ class VIEW3D_PT_blenderkit_profile(Panel):
if me is not None:
me = me['user']
# user name
if len(me['firstName'])>0 or len(me['lastName'])>0:
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']}")
@ -781,7 +783,6 @@ class VIEW3D_PT_blenderkit_advanced_model_search(Panel):
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:
@ -897,7 +898,7 @@ class VIEW3D_PT_blenderkit_import_settings(Panel):
def poll(cls, context):
s = context.scene
ui_props = s.blenderkitUI
return ui_props.down_up == 'SEARCH' and ui_props.asset_type in ['MATERIAL', 'MODEL', 'HDR']
return ui_props.down_up == 'SEARCH' and ui_props.asset_type in ['MATERIAL', 'MODEL', 'SCENE', 'HDR']
def draw(self, context):
layout = self.layout
@ -926,10 +927,17 @@ class VIEW3D_PT_blenderkit_import_settings(Panel):
row = layout.row()
row.prop(props, 'append_method', expand=True, icon_only=False)
if ui_props.asset_type == 'SCENE':
props = s.blenderkit_scene
layout.prop(props, 'switch_after_append')
layout.label(text='Import method:')
row = layout.row()
row.prop(props, 'append_link', expand=True, icon_only=False)
if ui_props.asset_type == 'HDR':
props = s.blenderkit_HDR
layout.prop(props, 'resolution')
if ui_props.asset_type in ['MATERIAL', 'MODEL', 'HDR']:
layout.prop(props, 'resolution')
# layout.prop(props, 'unpack_files')
@ -1060,32 +1068,6 @@ class VIEW3D_PT_blenderkit_unified(Panel):
else:
layout.label(text='Switch to paint or sculpt mode.')
elif ui_props.down_up == 'RATING': # the poll functions didn't work here, don't know why.
if ui_props.asset_type == 'MODEL':
# TODO improve poll here to parenting structures
if bpy.context.view_layer.objects.active is not None and bpy.context.active_object.get(
'asset_data') != None:
ad = bpy.context.active_object.get('asset_data')
layout.label(text=ad['name'])
draw_panel_model_rating(self, context)
if 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 and \
bpy.context.active_object.active_material.blenderkit.asset_base_id != '':
layout.label(text=bpy.context.active_object.active_material.blenderkit.name + ' :')
# noinspection PyCallByClass
draw_panel_material_ratings(self, context)
if ui_props.asset_type == 'BRUSH':
if context.sculpt_object or context.image_paint_object:
props = utils.get_brush_props(context)
if props.asset_base_id != '':
layout.label(text=props.name + ' :')
# noinspection PyCallByClass
draw_panel_brush_ratings(self, context)
if ui_props.asset_type == 'TEXTURE':
layout.label(text='not yet implemented')
class BlenderKitWelcomeOperator(bpy.types.Operator):
"""Login online on BlenderKit webpage"""
@ -1199,7 +1181,6 @@ def draw_asset_context_menu(layout, context, asset_data, from_panel=False):
# 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:
@ -1247,7 +1228,8 @@ def draw_asset_context_menu(layout, context, asset_data, from_panel=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]:
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:
@ -1302,14 +1284,10 @@ def draw_asset_context_menu(layout, context, asset_data, from_panel=False):
if utils.profile_is_validator():
layout.label(text='Admin 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.scene.blenderkitUI
@ -1392,12 +1370,13 @@ class OBJECT_MT_blenderkit_asset_menu(bpy.types.Menu):
# box2.label(text='************')
# box2.label(text='dadydadadada')
class AssetPopupCard(bpy.types.Operator):
"""Generate Cycles thumbnail for model assets"""
bl_idname = "wm.blenderkit_asset_popup"
bl_label = "BlenderKit asset popup"
# bl_options = {'REGISTER', 'INTERNAL'}
bl_options = {'REGISTER',}
bl_options = {'REGISTER', }
@classmethod
def poll(cls, context):
@ -1418,11 +1397,10 @@ class AssetPopupCard(bpy.types.Operator):
split = split.split(factor=0.5)
col1 = split.column()
box = col1.box()
utils.label_multiline(box,asset_data['tooltip'], width = 300)
utils.label_multiline(box, asset_data['tooltip'], width=300)
col2 = split.column()
pcoll = icons.icon_collections["main"]
my_icon = pcoll['test']
col2.template_icon(icon_value=my_icon.icon_id, scale=20.0)
@ -1430,7 +1408,7 @@ class AssetPopupCard(bpy.types.Operator):
box2 = col2.box()
# draw_ratings(box2, context, asset_data)
box2.label(text = 'Ratings')
box2.label(text='Ratings')
# print(tp, dir(tp))
# if not hasattr(self, 'first_draw'):# try to redraw because of template preview which needs update
# for region in context.area.regions:
@ -1451,8 +1429,9 @@ class AssetPopupCard(bpy.types.Operator):
# self.tex = utils.get_hidden_texture(self.img)
# self.tex.update_tag()
bl_label = asset_data['name']
return wm.invoke_props_dialog(self, width = 700)
bl_label = asset_data['name']
return wm.invoke_props_dialog(self, width=700)
class OBJECT_MT_blenderkit_login_menu(bpy.types.Menu):
bl_label = "BlenderKit login/signup:"
@ -1523,14 +1502,14 @@ class UrlPopupDialog(bpy.types.Operator):
def draw(self, context):
layout = self.layout
utils.label_multiline(layout, text=self.message, width = 300)
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)
width=300)
layout.operator_context = 'EXEC_DEFAULT'
layout.operator("wm.blenderkit_login", text="Login",
@ -1544,7 +1523,7 @@ class UrlPopupDialog(bpy.types.Operator):
def invoke(self, context, event):
wm = context.window_manager
return wm.invoke_props_dialog(self,width = 300)
return wm.invoke_props_dialog(self, width=300)
class LoginPopupDialog(bpy.types.Operator):
@ -1695,16 +1674,19 @@ def header_search_draw(self, context):
# 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(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)
def ui_message(title, message):
def draw_message(self, context):
layout = self.layout
utils.label_multiline(layout, text=message)
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 = {}

View File

@ -467,7 +467,7 @@ def get_upload_data(caller=None, context=None, asset_type=None):
add_version(upload_data)
# caller can be upload operator, but also asset bar called from tooltip generator
if caller and caller.main_file == True:
if caller and caller.properties.main_file == True:
upload_data["name"] = props.name
upload_data["displayName"] = props.name
else:
@ -536,6 +536,32 @@ def patch_individual_metadata(asset_id, metadata_dict, api_key):
# 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 == -1 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])
# print(profile, asset_data)
if asset_data['author']['id'] == profile['user']['id']:
return True
return False
class FastMetadata(bpy.types.Operator):
"""Fast change of the category of object directly in asset bar."""
bl_idname = "wm.blenderkit_fast_metadata"
@ -594,11 +620,22 @@ class FastMetadata(bpy.types.Operator):
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 = scene.blenderkitUI
return ui_props.active_index > -1
return can_edit_asset(active_index=ui_props.active_index)
def draw(self, context):
layout = self.layout
@ -617,6 +654,7 @@ class FastMetadata(bpy.types.Operator):
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')
@ -639,6 +677,7 @@ class FastMetadata(bpy.types.Operator):
'description': self.description,
'tags': comma2array(self.tags),
'isPrivate': self.is_private == 'PRIVATE',
'isFree': self.free_full == 'FREE',
'license': self.license,
}
@ -661,6 +700,8 @@ class FastMetadata(bpy.types.Operator):
if result['id'] == self.asset_id:
asset_data = dict(result)
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'],
@ -680,6 +721,11 @@ class FastMetadata(bpy.types.Operator):
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
@ -940,6 +986,10 @@ class Uploader(threading.Thread):
"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)
@ -994,9 +1044,10 @@ 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
utils.name_update()
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)
@ -1022,8 +1073,7 @@ def start_upload(self, context, asset_type, reupload, upload_set):
props.id = ''
export_data, upload_data = get_upload_data(caller=self, context=context, asset_type=asset_type)
# print(export_data)
# print(upload_data)
# check if thumbnail exists, generate for HDR:
if 'THUMBNAIL' in upload_set:
if asset_type == 'HDR':
@ -1138,6 +1188,11 @@ class UploadOperator(Operator):
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, )
return {'FINISHED'}
@ -1215,6 +1270,8 @@ class AssetDebugPrint(Operator):
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'}

View File

@ -17,7 +17,7 @@
# ##### END GPL LICENSE BLOCK #####
from blenderkit import paths, rerequests
from blenderkit import paths, rerequests, image_utils
import bpy
from mathutils import Vector
@ -43,6 +43,7 @@ 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
@ -215,7 +216,7 @@ def get_upload_props():
return s.blenderkit
if ui_props.asset_type == 'HDR':
hdr = ui_props.hdr_upload_image#bpy.data.images.get(ui_props.hdr_upload_image)
hdr = ui_props.hdr_upload_image # bpy.data.images.get(ui_props.hdr_upload_image)
if not hdr:
return None
return hdr.blenderkit
@ -254,7 +255,7 @@ def load_prefs():
fpath = paths.BLENDERKIT_SETTINGS_FILENAME
if os.path.exists(fpath):
try:
with open(fpath, 'r', encoding = 'utf-8') as s:
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())
@ -286,7 +287,7 @@ def save_prefs(self, context):
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:
with open(fpath, 'w', encoding='utf-8') as s:
json.dump(prefs, s, ensure_ascii=False, indent=4)
except Exception as e:
print(e)
@ -303,6 +304,7 @@ def uploadable_asset_poll():
return ui_props.hdr_upload_image is not None
return True
def get_hidden_texture(img, force_reload=False):
# i = get_hidden_image(tpath, bdata_name, force_reload=force_reload)
# bdata_name = f".{bdata_name}"
@ -314,7 +316,7 @@ def get_hidden_texture(img, force_reload=False):
return t
def get_hidden_image(tpath, bdata_name, force_reload=False, colorspace = 'sRGB'):
def get_hidden_image(tpath, bdata_name, force_reload=False, colorspace='sRGB'):
if bdata_name[0] == '.':
hidden_name = bdata_name
else:
@ -340,12 +342,13 @@ def get_hidden_image(tpath, bdata_name, force_reload=False, colorspace = 'sRGB')
img.filepath = tpath
img.reload()
img.colorspace_settings.name = colorspace
image_utils.set_colorspace(img, colorspace)
elif force_reload:
if img.packed_file is not None:
img.unpack(method='USE_ORIGINAL')
img.reload()
img.colorspace_settings.name = colorspace
image_utils.set_colorspace(img, colorspace)
return img
@ -355,7 +358,7 @@ def get_thumbnail(name):
img = bpy.data.images.get(name)
if img == None:
img = bpy.data.images.load(p)
img.colorspace_settings.name = 'sRGB'
image_utils.set_colorspace(img, 'sRGB')
img.name = name
img.name = name
@ -378,16 +381,16 @@ def get_brush_props(context):
return None
def p(text, text1='', text2='', text3='', text4='', text5='', level = 'DEBUG'):
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:
if 1: # bpy.app.debug_value != 0:
# print('-----BKit debug-----\n')
# traceback.print_stack()
texts = [text1,text2,text3,text4,text5]
texts = [text1, text2, text3, text4, text5]
text = str(text)
for t in texts:
if t!= '':
if t != '':
text += ' ' + str(t)
bk_logger.debug(text)
@ -398,7 +401,7 @@ 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)
bk_logger.debug(fp1 + ' ' + fp2)
if not os.path.exists(fp2):
shutil.copyfile(fp1, fp2)
bk_logger.debug('copied')
@ -410,7 +413,7 @@ def copy_asset(fp1, fp2):
target_subdir = os.path.join(target_dir, subdir.name)
if os.path.exists(target_subdir):
continue
bk_logger.debug(str(subdir) +' '+ str(target_subdir))
bk_logger.debug(str(subdir) + ' ' + str(target_subdir))
shutil.copytree(subdir, target_subdir)
bk_logger.debug('copied')
@ -638,11 +641,15 @@ def automap(target_object=None, target_slot=None, tex_size=1, bg_exception=False
bpy.context.view_layer.objects.active = actob
def name_update():
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 = scene.blenderkitUI
props = get_upload_props()
# props = get_upload_props()
if props.name_old != props.name:
props.name_changed = True
props.name_old = props.name
@ -674,7 +681,6 @@ def get_param(asset_data, parameter_name):
return None
def params_to_dict(params):
params_dict = {}
for p in params:
@ -705,6 +711,7 @@ def dict_to_params(inputs, parameters=None):
})
return parameters
def update_tags(self, context):
props = self
@ -729,6 +736,7 @@ def update_tags(self, context):
if props.tags != ns:
props.tags = ns
def user_logged_in():
a = bpy.context.window_manager.get('bkit profile')
if a is not None:
@ -826,5 +834,6 @@ def label_multiline(layout, text='', icon='NONE', width=-1):
layout.label(text=l, icon=icon)
icon = 'NONE'
def trace():
traceback.print_stack()
traceback.print_stack()

View File

@ -142,7 +142,7 @@ class OperatorSegmentsInfo(bpy.types.Operator):
class OperatorOriginToSpline0Start(bpy.types.Operator):
bl_idname = "curvetools.operatororigintospline0start"
bl_label = "OriginToSpline0Start"
bl_description = "Sets the origin of the active/selected curve to the starting point of the (first) spline. Nice for curve modifiers."
bl_description = "Sets the origin of the active/selected curve to the starting point of the (first) spline. Nice for curve modifiers"
@classmethod
@ -327,7 +327,7 @@ class OperatorSplinesSetResolution(bpy.types.Operator):
class OperatorSplinesRemoveZeroSegment(bpy.types.Operator):
bl_idname = "curvetools.operatorsplinesremovezerosegment"
bl_label = "SplinesRemoveZeroSegment"
bl_description = "Removes splines with no segments -- they seem to creep up, sometimes.."
bl_description = "Removes splines with no segments -- they seem to creep up, sometimes"
@classmethod

View File

@ -21,7 +21,7 @@ bl_info = {
"name": "Grease Pencil Tools",
"description": "Extra tools for Grease Pencil",
"author": "Samuel Bernou, Antonio Vazquez, Daniel Martinez Lara, Matias Mendiola",
"version": (1, 3, 1),
"version": (1, 3, 3),
"blender": (2, 91, 0),
"location": "Sidebar > Grease Pencil > Grease Pencil Tools",
"warning": "",

View File

@ -128,9 +128,9 @@ class GreasePencilAddonPrefs(bpy.types.AddonPreferences):
rc_angle_step: FloatProperty(
name="Angle Steps",
description="Step the rotation using this angle when using rotate canvas step modifier",
default=0.0872664600610733, # 5
default=0.2617993877991494, # 15
min=0.01745329238474369, # 1
max=3.1415927410125732, # # 180
max=3.1415927410125732, # 180
soft_min=0.01745329238474369, # 1
soft_max=1.5707963705062866, # 90
step=10, precision=1, subtype='ANGLE', unit='ROTATION')
@ -153,7 +153,7 @@ class GreasePencilAddonPrefs(bpy.types.AddonPreferences):
row.label(text='Box Deform:')
row.operator("wm.call_menu", text="", icon='QUESTION').name = "GPT_MT_box_deform_doc"
box.prop(self, "use_clic_drag")
# box.separator()
box.prop(self, "default_deform_type")
box.label(text="Deformer type can be changed during modal with 'M' key, this is for default behavior", icon='INFO')
@ -179,7 +179,7 @@ class GreasePencilAddonPrefs(bpy.types.AddonPreferences):
if not self.use_ctrl and not self.use_alt and not self.use_shift:
box.label(text="Choose at least one modifier to combine with click (default: Ctrl+Alt)", icon="ERROR")# INFO
if not all((self.use_ctrl, self.use_alt, self.use_shift)):
row = box.row(align = True)
snap_key_list = []
@ -204,7 +204,7 @@ class GreasePencilAddonPrefs(bpy.types.AddonPreferences):
draw_ts_pref(prefs.ts, box)
# def box_deform_tuto(layout):
class GPT_MT_box_deform_doc(bpy.types.Menu):
# bl_idname = "OBJECT_MT_custom_menu"
bl_label = "Box Deform Infos Sheet"

View File

@ -5,6 +5,7 @@ import math
import mathutils
from bpy_extras.view3d_utils import location_3d_to_region_2d
from bpy.props import BoolProperty, EnumProperty
from time import time
## draw utils
import gpu
import bgl
@ -13,7 +14,7 @@ from gpu_extras.batch import batch_for_shader
from gpu_extras.presets import draw_circle_2d
def step_value(value, step):
'''return the step closer to the passed value'''
'''return the step closer to the passed value'''
abs_angle = abs(value)
diff = abs_angle % step
lower_step = abs_angle - diff
@ -25,6 +26,8 @@ def step_value(value, step):
def draw_callback_px(self, context):
# 50% alpha, 2 pixel width line
if context.area != self.current_area:
return
shader = gpu.shader.from_builtin('2D_UNIFORM_COLOR')
bgl.glEnable(bgl.GL_BLEND)
bgl.glLineWidth(2)
@ -90,7 +93,7 @@ class RC_OT_RotateCanvas(bpy.types.Operator):
# Calculates the angle between initial and current vectors
self.angle = self.vector_initial.angle_signed(self.vector_current)#radian
# print (math.degrees(self.angle), self.vector_initial, self.vector_current)
## handle snap key
snap = False
@ -116,7 +119,8 @@ class RC_OT_RotateCanvas(bpy.types.Operator):
context.space_data.region_3d.view_rotation = rot.to_quaternion()
if event.type in {'RIGHTMOUSE', 'LEFTMOUSE', 'MIDDLEMOUSE'} and event.value == 'RELEASE':
if not self.angle:
# Trigger reset : Less than 150ms and less than 2 degrees move
if time() - self.timer < 0.15 and abs(math.degrees(self.angle)) < 2:
# self.report({'INFO'}, 'Reset')
aim = context.space_data.region_3d.view_rotation @ mathutils.Vector((0.0, 0.0, 1.0))#view vector
z_up_quat = aim.to_track_quat('Z','Y')#track Z, up Y
@ -143,6 +147,7 @@ class RC_OT_RotateCanvas(bpy.types.Operator):
return {'RUNNING_MODAL'}
def invoke(self, context, event):
self.current_area = context.area
prefs = get_addon_prefs()
self.hud = prefs.canvas_use_hud
self.angle = 0.0
@ -190,6 +195,7 @@ class RC_OT_RotateCanvas(bpy.types.Operator):
# round to closer degree and convert back to radians
self.snap_step = math.radians(round(math.degrees(prefs.rc_angle_step)))
self.timer = time()
args = (self, context)
if self.hud:
self._handle = bpy.types.SpaceView3D.draw_handler_add(draw_callback_px, args, 'WINDOW', 'POST_PIXEL')
@ -210,7 +216,7 @@ class RC_OT_Set_rotation(bpy.types.Operator):
return context.space_data.region_3d.view_perspective == 'CAMERA'
def execute(self, context):
cam_ob = context.scene.camera
cam_ob = context.scene.camera
cam_ob['stored_rotation'] = cam_ob.rotation_euler
if not cam_ob.get('_RNA_UI'):
cam_ob['_RNA_UI'] = {}
@ -233,7 +239,7 @@ class RC_OT_Reset_rotation(bpy.types.Operator):
return context.space_data.region_3d.view_perspective == 'CAMERA' and context.scene.camera.get('stored_rotation')
def execute(self, context):
cam_ob = context.scene.camera
cam_ob = context.scene.camera
cam_ob.rotation_euler = cam_ob['stored_rotation']
return {'FINISHED'}
@ -252,4 +258,4 @@ def register():
def unregister():
for cls in reversed(classes):
bpy.utils.unregister_class(cls)
bpy.utils.unregister_class(cls)

View File

@ -122,6 +122,7 @@ class GPTS_OT_time_scrub(bpy.types.Operator):
self.current_area = context.area
self.key = prefs.keycode
self.evaluate_gp_obj_key = prefs.evaluate_gp_obj_key
self.always_snap = prefs.always_snap
self.dpi = context.preferences.system.dpi
self.ui_scale = context.preferences.system.ui_scale
@ -143,7 +144,7 @@ class GPTS_OT_time_scrub(bpy.types.Operator):
self.snap_on = False
self.mouse = (event.mouse_region_x, event.mouse_region_y)
self.init_mouse_x = self.cursor_x = event.mouse_region_x
# self.init_mouse_y = event.mouse_region_y # only to display init frame text
self.init_frame = self.new_frame = context.scene.frame_current
self.lock_range = context.scene.lock_frame_selection_to_range
@ -153,7 +154,7 @@ class GPTS_OT_time_scrub(bpy.types.Operator):
else:
self.f_start = context.scene.frame_start
self.f_end = context.scene.frame_end
self.offset = 0
self.pos = []
@ -190,14 +191,15 @@ class GPTS_OT_time_scrub(bpy.types.Operator):
for frame in layer.frames:
if frame.frame_number not in self.pos:
self.pos.append(frame.frame_number)
# Add start and end to snap on
if not ob or not self.pos:
# Disable inverted behavior if no frame to snap
self.always_snap = False
# Also snap on play bounds (sliced off for keyframe display)
self.pos += [self.f_start, self.f_end]
# Disable Onion skin
self.active_space_data = context.space_data
self.onion_skin = None
@ -205,7 +207,7 @@ class GPTS_OT_time_scrub(bpy.types.Operator):
if context.space_data.type == 'VIEW_3D': # and 'GPENCIL' in context.mode
self.onion_skin = self.active_space_data.overlay.use_gpencil_onion_skin
self.active_space_data.overlay.use_gpencil_onion_skin = False
if ob and ob.type == 'GPENCIL':
if ob.data.use_multiedit:
self.multi_frame = ob.data.use_multiedit
@ -333,7 +335,7 @@ class GPTS_OT_time_scrub(bpy.types.Operator):
# (after drawing batch so those are still showed)
if self.lock_range:
self.pos = [i for i in self.pos if self.f_start <= i <= self.f_end]
# convert frame list to array for numpy snap utility
self.pos = np.asarray(self.pos)
@ -389,8 +391,14 @@ class GPTS_OT_time_scrub(bpy.types.Operator):
if self.snap_alt and event.alt:
mod_snap = True
if self.snap_on or mod_snap:
self.new_frame = nearest(self.pos, self.new_frame)
## Snapping
if self.always_snap:
# inverted snapping behavior
if not self.snap_on and not mod_snap:
self.new_frame = nearest(self.pos, self.new_frame)
else:
if self.snap_on or mod_snap:
self.new_frame = nearest(self.pos, self.new_frame)
# frame range restriction
if self.lock_range:
@ -492,6 +500,11 @@ class GPTS_timeline_settings(bpy.types.PropertyGroup):
description="Shortcut to trigger the scrub in viewport during press",
default="MIDDLEMOUSE")
always_snap: BoolProperty(
name="Always Snap",
description="Always snap to keys if any, modifier is used deactivate the snapping\nDisabled if no keyframe found",
default=False)
use_in_timeline_editor: BoolProperty(
name="Shortcut in timeline editors",
description="Add the same shortcut to scrub in timeline editor windows",
@ -649,7 +662,11 @@ def draw_ts_pref(prefs, layout):
else:
box.label(text='[ NOW TYPE KEY OR CLICK TO USE, WITH MODIFIER ]')
snap_text = 'Snap to keyframes: '
if prefs.always_snap:
snap_text = 'Disable keyframes snap: '
else:
snap_text = 'Keyframes snap: '
snap_text += 'Left Mouse' if prefs.keycode == 'RIGHTMOUSE' else 'Right Mouse'
if not prefs.use_ctrl:
snap_text += ' or Ctrl'
@ -662,6 +679,7 @@ def draw_ts_pref(prefs, layout):
box.label(
text="Recommended to choose at least one modifier to combine with clicks (default: Ctrl+Alt)", icon="ERROR")
box.prop(prefs, 'always_snap')
box.prop(prefs, 'use_in_timeline_editor',
text='Add same shortcut to scrub within timeline editors')

View File

@ -1821,7 +1821,7 @@ class SceneCoat3D(PropertyGroup):
)
bake_resolution: EnumProperty(
name="Bake Resolution",
description="Bake resolution.",
description="Bake resolution",
items=(("res_64", "64 x 64", ""),
("res_128", "128 x 128", ""),
("res_256", "256 x 256", ""),
@ -1835,7 +1835,7 @@ class SceneCoat3D(PropertyGroup):
)
folder_size: EnumProperty(
name="Applink folder size",
description="Applink folder size.",
description="Applink folder size",
items=(("10", "10", ""),
("100", "100", ""),
("500", "500", ""),

View File

@ -158,7 +158,7 @@ class PanelProperties(bpy.types.PropertyGroup):
description="Distance of 2 objects in Angstrom")
replace_objs: EnumProperty(
name="Shape",
description="Choose a different atom shape.",
description="Choose a different atom shape",
items=(('0',"Unchanged", "Do not change the shape"),
('1a',"Sphere (Mesh)", "Replace with a sphere (Mesh)"),
('1b',"Sphere (NURBS)", "Replace with a sphere (NURBS)"),
@ -179,7 +179,7 @@ class PanelProperties(bpy.types.PropertyGroup):
default='0',)
replace_objs_material: EnumProperty(
name="Material",
description="Choose a different material.",
description="Choose a different material",
items=(('0',"Unchanged", "Leave the material unchanged"),
('1',"Normal", "Use normal material (no transparency and reflection)"),
('2',"Transparent", "Use transparent material"),
@ -188,7 +188,7 @@ class PanelProperties(bpy.types.PropertyGroup):
default='0',)
replace_objs_special: EnumProperty(
name="Special",
description="Choose a special atom shape.",
description="Choose a special atom shape",
items=(('0',"None", "Use no special shape."),
('1',"F2+ center", "Replace with a F2+ center"),
('2',"F+ center", "Replace with a F+ center"),
@ -211,7 +211,7 @@ class PanelProperties(bpy.types.PropertyGroup):
default='0',update=Callback_radius_type)
radius_type_ionic: EnumProperty(
name="Charge",
description="Charge state of the ions if existing.",
description="Charge state of the ions if existing",
items=(('0',"-4", "Charge state -4"),
('1',"-3", "Charge state -3"),
('2',"-2", "Charge state -2"),
@ -262,7 +262,7 @@ class DatafileApply(Operator):
class DefaultAtom(Operator):
bl_idname = "atom_blend.default_atoms"
bl_label = "Default"
bl_description = ("Use default shapes and colors for atoms.")
bl_description = ("Use default shapes and colors for atoms")
# Are we in the OBJECT mode?
@classmethod
@ -288,7 +288,7 @@ class DefaultAtom(Operator):
class ReplaceAtom(Operator):
bl_idname = "atom_blend.replace_atom"
bl_label = "Replace"
bl_description = ("Replace selected atoms with atoms of different shape.")
bl_description = ("Replace selected atoms with atoms of different shape")
# Are we in the OBJECT mode?
@classmethod
@ -337,7 +337,7 @@ class SeparateAtom(Operator):
class DistanceButton(Operator):
bl_idname = "atom_blend.button_distance"
bl_label = "Measure ..."
bl_description = "Measure the distance between two atoms (objects)."
bl_description = "Measure the distance between two atoms (objects)"
def execute(self, context):
scn = bpy.context.scene.atom_blend

View File

@ -92,10 +92,10 @@ class IMPORT_OT_xyz(Operator, ImportHelper):
description = "Do you want to load all frames?")
skip_frames: IntProperty(
name="", default=0, min=0,
description="Number of frames you want to skip.")
description="Number of frames you want to skip")
images_per_key: IntProperty(
name="", default=1, min=1,
description="Choose the number of images between 2 keys.")
description="Choose the number of images between 2 keys")
# This thing here just guarantees that the menu entry is not active when the
# check box in the addon preferences is not activated! See __init__.py

View File

@ -15,7 +15,7 @@
bl_info = {
'name': 'glTF 2.0 format',
'author': 'Julien Duroure, Scurest, Norbert Nopper, Urs Hanselmann, Moritz Becher, Benjamin Schmithüsen, Jim Eckerlein, and many external contributors',
"version": (1, 6, 3),
"version": (1, 6, 4),
'blender': (2, 91, 0),
'location': 'File > Import-Export',
'description': 'Import-Export as glTF 2.0',
@ -70,21 +70,42 @@ from bpy_extras.io_utils import ImportHelper, ExportHelper
extension_panel_unregister_functors = []
def ensure_filepath_matches_export_format(filepath, export_format):
import os
filename = os.path.basename(filepath)
if not filename:
return filepath
stem, ext = os.path.splitext(filename)
if stem.startswith('.') and not ext:
stem, ext = '', stem
desired_ext = '.glb' if export_format == 'GLB' else '.gltf'
ext_lower = ext.lower()
if ext_lower not in ['.glb', '.gltf']:
return filepath + desired_ext
elif ext_lower != desired_ext:
filepath = filepath[:-len(ext)] # strip off ext
return filepath + desired_ext
else:
return filepath
def on_export_format_changed(self, context):
# Update the file extension when the format (.glb/.gltf) changes
# Update the filename in the file browser when the format (.glb/.gltf)
# changes
sfile = context.space_data
if sfile is None:
return # Avoid error when export from background
operator = sfile.active_operator
if operator.bl_idname != "EXPORT_SCENE_OT_gltf":
if not isinstance(sfile, bpy.types.SpaceFileBrowser):
return
if operator.check(context):
# Weird hack to force the filepicker to notice filename changed
from os.path import basename
filepath = operator.filepath
bpy.ops.file.filenum(increment=-1)
if basename(operator.filepath) != basename(filepath):
bpy.ops.file.filenum(increment=1)
if not sfile.active_operator:
return
if sfile.active_operator.bl_idname != "EXPORT_SCENE_OT_gltf":
return
sfile.params.filename = ensure_filepath_matches_export_format(
sfile.params.filename,
self.export_format,
)
class ExportGLTF2_Base:
@ -384,28 +405,12 @@ class ExportGLTF2_Base:
def check(self, _context):
# Ensure file extension matches format
import os
filename = os.path.basename(self.filepath)
if filename:
filepath = self.filepath
desired_ext = '.glb' if self.export_format == 'GLB' else '.gltf'
stem, ext = os.path.splitext(filename)
if stem.startswith('.') and not ext:
stem, ext = '', stem
ext_lower = ext.lower()
if ext_lower not in ['.glb', '.gltf']:
filepath = filepath + desired_ext
elif ext_lower != desired_ext:
filepath = filepath[:-len(ext)] # strip off ext
filepath += desired_ext
if filepath != self.filepath:
self.filepath = filepath
return True
return False
old_filepath = self.filepath
self.filepath = ensure_filepath_matches_export_format(
self.filepath,
self.export_format,
)
return self.filepath != old_filepath
def invoke(self, context, event):
settings = context.scene.get(self.scene_key)

View File

@ -118,6 +118,9 @@ def register_keymaps():
keyconfigs = bpy.context.window_manager.keyconfigs
kc_defaultconf = keyconfigs.default
kc_addonconf = keyconfigs.addon
# In background mode.
if kc_defaultconf is None or kc_addonconf is None:
return
# TODO: find the user defined tool_mouse.
from bl_keymap_utils.io import keyconfig_init_from_data
@ -128,8 +131,11 @@ def register_keymaps():
#snap_modalkeymap.assign("MESH_OT_snap_utilities_line")
def unregister_keymaps():
keyconfigs = bpy.context.window_manager.keyconfigs
defaultmap = keyconfigs.get("Blender").keymaps
addonmap = keyconfigs.get("Blender addon").keymaps
defaultmap = getattr(keyconfigs.get("Blender"), "keymaps", None)
addonmap = getattr(keyconfigs.get("Blender addon"), "keymaps", None)
# In background mode.
if defaultmap is None or addonmap is None:
return
for keyconfig_data in keys.generate_snap_utilities_global_keymaps():
km_name, km_args, km_content = keyconfig_data

View File

@ -164,11 +164,11 @@ class SnapWidgetGroupCommon(bpy.types.GizmoGroup):
return context_mode_check(context, cls.bl_idname)
def init_tool(self, context, gizmo_name):
self.widget = self.gizmos.new(gizmo_name)
self._widget = self.gizmos.new(gizmo_name)
def __del__(self):
if hasattr(self, "widget"):
object.__getattribute__(self.widget, 'end_snapwidget')()
if hasattr(self, "_widget"):
object.__getattribute__(self._widget, 'end_snapwidget')()
class SnapPointWidgetGroup(SnapWidgetGroupCommon):

View File

@ -704,8 +704,8 @@ class reaction_diffusion(bpy.types.Operator):
class edges_deformation(bpy.types.Operator):
bl_idname = "object.edges_deformation"
bl_label = "Edges Deformation"
bl_description = ("Compute Weight based on the deformation of edges"+
"according to visible modifiers.")
bl_description = ("Compute Weight based on the deformation of edges "
"according to visible modifiers")
bl_options = {'REGISTER', 'UNDO'}
bounds : bpy.props.EnumProperty(
@ -846,8 +846,8 @@ class edges_deformation(bpy.types.Operator):
class edges_bending(bpy.types.Operator):
bl_idname = "object.edges_bending"
bl_label = "Edges Bending"
bl_description = ("Compute Weight based on the bending of edges"+
"according to visible modifiers.")
bl_description = ("Compute Weight based on the bending of edges "
"according to visible modifiers")
bl_options = {'REGISTER', 'UNDO'}
bounds : bpy.props.EnumProperty(
@ -1678,7 +1678,7 @@ class vertex_colors_to_vertex_groups(bpy.types.Operator):
bl_idname = "object.vertex_colors_to_vertex_groups"
bl_label = "Vertex Color"
bl_options = {'REGISTER', 'UNDO'}
bl_description = ("Convert the active Vertex Color into a Vertex Group.")
bl_description = ("Convert the active Vertex Color into a Vertex Group")
red : bpy.props.BoolProperty(
name="red channel", default=False, description="convert red channel")
@ -1770,7 +1770,7 @@ class vertex_group_to_vertex_colors(bpy.types.Operator):
bl_idname = "object.vertex_group_to_vertex_colors"
bl_label = "Vertex Group"
bl_options = {'REGISTER', 'UNDO'}
bl_description = ("Convert the active Vertex Group into a Vertex Color.")
bl_description = ("Convert the active Vertex Group into a Vertex Color")
channel : bpy.props.EnumProperty(
items=[('Blue', 'Blue Channel', 'Convert to Blue Channel'),
@ -1857,8 +1857,8 @@ class curvature_to_vertex_groups(bpy.types.Operator):
bl_idname = "object.curvature_to_vertex_groups"
bl_label = "Curvature"
bl_options = {'REGISTER', 'UNDO'}
bl_description = ("Generate a Vertex Group based on the curvature of the"
"mesh. Is based on Dirty Vertex Color.")
bl_description = ("Generate a Vertex Group based on the curvature of the "
"mesh. Is based on Dirty Vertex Color")
invert : bpy.props.BoolProperty(
name="invert", default=False, description="invert values")
@ -1905,8 +1905,8 @@ class face_area_to_vertex_groups(bpy.types.Operator):
bl_idname = "object.face_area_to_vertex_groups"
bl_label = "Area"
bl_options = {'REGISTER', 'UNDO'}
bl_description = ("Generate a Vertex Group based on the area of individual"
"faces.")
bl_description = ("Generate a Vertex Group based on the area of individual "
"faces")
invert : bpy.props.BoolProperty(
name="invert", default=False, description="invert values")

View File

@ -483,7 +483,7 @@ def assign_angle_presets(self, context):
self.angle = angle_presets[self.angle_presets]
class OffsetEdges(bpy.types.Operator):
"""Offset Edges."""
"""Offset Edges"""
bl_idname = "mesh.offset_edges"
bl_label = "Offset Edges"
bl_options = {'REGISTER', 'UNDO'}
@ -531,7 +531,7 @@ class OffsetEdges(bpy.types.Operator):
name="Flat Face Threshold", default=radians(0.05), precision=5,
step=1.0e-4, subtype='ANGLE',
description="If difference of angle between two adjacent faces is "
"below this value, those faces are regarded as flat.",
"below this value, those faces are regarded as flat",
options={'HIDDEN'})
caches_valid: bpy.props.BoolProperty(
name="Caches Valid", default=False,

View File

@ -22,7 +22,7 @@ bl_info = {
"name": "Collection Manager",
"description": "Manage collections and their objects",
"author": "Ryan Inch",
"version": (2, 19, 2),
"version": (2, 19, 3),
"blender": (2, 80, 0),
"location": "View3D - Object Mode (Shortcut - M)",
"warning": '', # used for warning icon and text in addons panel

View File

@ -40,12 +40,6 @@ def update_qcd_status(self, context):
if self.enable_qcd:
qcd_init.register_qcd()
if self.enable_qcd_view_hotkeys:
qcd_init.register_qcd_view_hotkeys()
if self.enable_qcd_view_edit_mode_hotkeys:
qcd_init.register_qcd_view_edit_mode_hotkeys()
else:
qcd_init.unregister_qcd()

View File

@ -109,7 +109,7 @@ def add_line_to_bisection(context):
class PDT_OT_LineOnBisection(bpy.types.Operator):
"""Create Bisector between 2 Selected Edges."""
"""Create Bisector between 2 Selected Edges"""
bl_idname = "pdt.linetobisect"
bl_label = "Add Edges Bisector"

View File

@ -83,7 +83,7 @@ PDT_FeatureError = pdt_exception.FeatureError
class PDT_OT_CommandReRun(Operator):
"""Repeat Current Displayed Command."""
"""Repeat Current Displayed Command"""
bl_idname = "pdt.command_rerun"
bl_label = "Re-run Current Command"

View File

@ -33,7 +33,7 @@ from .pdt_msg_strings import (
class PDT_OT_PlacementAbs(Operator):
"""Use Absolute, or Global Placement."""
"""Use Absolute, or Global Placement"""
bl_idname = "pdt.absolute"
bl_label = "Absolute Mode"
@ -114,7 +114,7 @@ class PDT_OT_PlacementAbs(Operator):
class PDT_OT_PlacementDelta(Operator):
"""Use Delta, or Incremental Placement."""
"""Use Delta, or Incremental Placement"""
bl_idname = "pdt.delta"
bl_label = "Delta Mode"
@ -211,7 +211,7 @@ class PDT_OT_PlacementDelta(Operator):
class PDT_OT_PlacementDis(Operator):
"""Use Directional, or Distance @ Angle Placement."""
"""Use Directional, or Distance @ Angle Placement"""
bl_idname = "pdt.distance"
bl_label = "Distance@Angle Mode"
@ -300,7 +300,7 @@ class PDT_OT_PlacementDis(Operator):
class PDT_OT_PlacementPer(Operator):
"""Use Percentage Placement."""
"""Use Percentage Placement"""
bl_idname = "pdt.percent"
bl_label = "Percentage Mode"
@ -357,7 +357,7 @@ class PDT_OT_PlacementPer(Operator):
class PDT_OT_PlacementNormal(Operator):
"""Use Normal, or Perpendicular Placement."""
"""Use Normal, or Perpendicular Placement"""
bl_idname = "pdt.normal"
bl_label = "Normal Mode"
@ -406,7 +406,7 @@ class PDT_OT_PlacementNormal(Operator):
class PDT_OT_PlacementCen(Operator):
"""Use Placement at Arc Centre."""
"""Use Placement at Arc Centre"""
bl_idname = "pdt.centre"
bl_label = "Centre Mode"
@ -451,7 +451,7 @@ class PDT_OT_PlacementCen(Operator):
class PDT_OT_PlacementInt(Operator):
"""Use Intersection, or Convergence Placement."""
"""Use Intersection, or Convergence Placement"""
bl_idname = "pdt.intersect"
bl_label = "Intersect Mode"
@ -497,7 +497,7 @@ class PDT_OT_PlacementInt(Operator):
class PDT_OT_JoinVerts(Operator):
"""Join 2 Free Vertices into an Edge."""
"""Join 2 Free Vertices into an Edge"""
bl_idname = "pdt.join"
bl_label = "Join 2 Vertices"
@ -531,7 +531,7 @@ class PDT_OT_JoinVerts(Operator):
class PDT_OT_Fillet(Operator):
"""Fillet Edges by Vertex, Set Use Verts to False for Extruded Structure."""
"""Fillet Edges by Vertex, Set Use Verts to False for Extruded Structure"""
bl_idname = "pdt.fillet"
bl_label = "Fillet"
@ -587,7 +587,7 @@ class PDT_OT_Fillet(Operator):
class PDT_OT_Angle2(Operator):
"""Measure Distance and Angle in Working Plane, Also sets Deltas."""
"""Measure Distance and Angle in Working Plane, Also sets Deltas"""
bl_idname = "pdt.angle2"
bl_label = "Measure 2D"
@ -614,7 +614,7 @@ class PDT_OT_Angle2(Operator):
class PDT_OT_Angle3(Operator):
"""Measure Distance and Angle in 3D Space."""
"""Measure Distance and Angle in 3D Space"""
bl_idname = "pdt.angle3"
bl_label = "Measure 3D"
@ -641,7 +641,7 @@ class PDT_OT_Angle3(Operator):
class PDT_OT_Origin(Operator):
"""Move Object Origin to Cursor Location."""
"""Move Object Origin to Cursor Location"""
bl_idname = "pdt.origin"
bl_label = "Move Origin"
@ -668,7 +668,7 @@ class PDT_OT_Origin(Operator):
class PDT_OT_Taper(Operator):
"""Taper Vertices at Angle in Chosen Axis Mode."""
"""Taper Vertices at Angle in Chosen Axis Mode"""
bl_idname = "pdt.taper"
bl_label = "Taper"

View File

@ -126,7 +126,7 @@ def extend_vertex(context):
class PDT_OT_EdgeToFace(bpy.types.Operator):
"""Extend Selected Edge to Projected Intersection with Selected Face."""
"""Extend Selected Edge to Projected Intersection with Selected Face"""
bl_idname = "pdt.edge_to_face"
bl_label = "Extend Edge to Face"

View File

@ -30,7 +30,7 @@ from .pdt_msg_strings import PDT_ERR_NO_LIBRARY, PDT_ERR_OBJECTMODE
class PDT_OT_LibShow(Operator):
"""Show Library File Details."""
"""Show Library File Details"""
bl_idname = "pdt.lib_show"
bl_label = "Show Library Details"
@ -59,7 +59,7 @@ class PDT_OT_LibShow(Operator):
class PDT_OT_Append(Operator):
"""Append from Library at cursor Location."""
"""Append from Library at cursor Location"""
bl_idname = "pdt.append"
bl_label = "Append"
@ -141,7 +141,7 @@ class PDT_OT_Append(Operator):
class PDT_OT_Link(Operator):
"""Link from Library at Object's Origin."""
"""Link from Library at Object's Origin"""
bl_idname = "pdt.link"
bl_label = "Link"

View File

@ -38,7 +38,7 @@ from .pdt_msg_strings import (
class PDT_OT_ModalDrawOperator(bpy.types.Operator):
"""Show/Hide Pivot Point."""
"""Show/Hide Pivot Point"""
bl_idname = "pdt.modaldraw"
bl_label = "PDT Modal Draw"
@ -113,7 +113,7 @@ class PDT_OT_ModalDrawOperator(bpy.types.Operator):
class PDT_OT_ViewPlaneRotate(Operator):
"""Rotate Selected Vertices about Pivot Point in View Plane."""
"""Rotate Selected Vertices about Pivot Point in View Plane"""
bl_idname = "pdt.viewplanerot"
bl_label = "PDT View Rotate"
@ -177,7 +177,7 @@ class PDT_OT_ViewPlaneRotate(Operator):
class PDT_OT_ViewPlaneScale(Operator):
"""Scale Selected Vertices about Pivot Point."""
"""Scale Selected Vertices about Pivot Point"""
bl_idname = "pdt.viewscale"
bl_label = "PDT View Scale"
@ -246,7 +246,7 @@ class PDT_OT_ViewPlaneScale(Operator):
class PDT_OT_PivotToCursor(Operator):
"""Set The Pivot Point to Cursor Location."""
"""Set The Pivot Point to Cursor Location"""
bl_idname = "pdt.pivotcursor"
bl_label = "PDT Pivot To Cursor"
@ -274,7 +274,7 @@ class PDT_OT_PivotToCursor(Operator):
class PDT_OT_CursorToPivot(Operator):
"""Set The Cursor Location to Pivot Point."""
"""Set The Cursor Location to Pivot Point"""
bl_idname = "pdt.cursorpivot"
bl_label = "PDT Cursor To Pivot"
@ -300,7 +300,7 @@ class PDT_OT_CursorToPivot(Operator):
class PDT_OT_PivotSelected(Operator):
"""Set Pivot Point to Selected Geometry."""
"""Set Pivot Point to Selected Geometry"""
bl_idname = "pdt.pivotselected"
bl_label = "PDT Pivot to Selected"
@ -361,7 +361,7 @@ class PDT_OT_PivotSelected(Operator):
class PDT_OT_PivotOrigin(Operator):
"""Set Pivot Point at Object Origin."""
"""Set Pivot Point at Object Origin"""
bl_idname = "pdt.pivotorigin"
bl_label = "PDT Pivot to Object Origin"
@ -410,7 +410,7 @@ class PDT_OT_PivotOrigin(Operator):
class PDT_OT_PivotWrite(Operator):
"""Write Pivot Point Location to Object."""
"""Write Pivot Point Location to Object"""
bl_idname = "pdt.pivotwrite"
bl_label = "PDT Write PP to Object?"
@ -467,7 +467,7 @@ class PDT_OT_PivotWrite(Operator):
class PDT_OT_PivotRead(Operator):
"""Read Pivot Point Location from Object."""
"""Read Pivot Point Location from Object"""
bl_idname = "pdt.pivotread"
bl_label = "PDT Read PP"

View File

@ -211,7 +211,7 @@ class PDT_OT_ViewIso(Operator):
bl_idname = "pdt.viewiso"
bl_label = "Isometric View"
bl_options = {"REGISTER", "UNDO"}
bl_description = "Isometric View."
bl_description = "Isometric View"
def execute(self, context):
"""Set Isometric View.
@ -238,7 +238,7 @@ class PDT_OT_Reset3DView(Operator):
bl_idname = "pdt.reset_3d_view"
bl_label = "Reset 3D View"
bl_options = {"REGISTER", "UNDO"}
bl_description = "Reset 3D View to Blender Defaults."
bl_description = "Reset 3D View to Blender Defaults"
def execute(self, context):
"""Reset 3D View to Blender Defaults.

View File

@ -245,7 +245,7 @@ def intersect_all(context):
return
class PDT_OT_IntersectAllEdges(bpy.types.Operator):
"""Cut Selected Edges at All Intersections."""
"""Cut Selected Edges at All Intersections"""
bl_idname = "pdt.intersectall"
bl_label = "Intersect All Edges"

View File

@ -433,7 +433,7 @@ class RenderPovSettingsScene(PropertyGroup):
" to scattering in the direction of the light and negative "
"values lead to scattering in the opposite direction of the "
"light. Larger values of e (or smaller values in the negative"
" case) increase the directional property of the scattering.",
" case) increase the directional property of the scattering",
precision=2,
step=0.01,
min=-1.0,

View File

@ -5987,7 +5987,7 @@ class PovrayRender(bpy.types.RenderEngine):
#################################Operators########################################
##################################################################################
class RenderPovTexturePreview(Operator):
"""Export only files necessary to texture preview and render image."""
"""Export only files necessary to texture preview and render image"""
bl_idname = "tex.preview_update"
bl_label = "Update preview"

View File

@ -838,7 +838,7 @@ class LIGHT_MT_POV_presets(Menu):
class LIGHT_OT_POV_add_preset(AddPresetBase, Operator):
"""Use this class to define pov world buttons."""
"""Use this class to define pov world buttons"""
'''Add a Light Preset'''
bl_idname = "object.light_preset_add"
@ -1184,7 +1184,7 @@ del properties_data_light
class WORLD_PT_POV_world(WorldButtonsPanel, Panel):
"""Use this class to define pov world buttons."""
"""Use this class to define pov world buttons"""
bl_label = "World"
COMPAT_ENGINES = {'POVRAY_RENDER'}
@ -1564,7 +1564,7 @@ class POV_RADIOSITY_MT_presets(Menu):
class RENDER_OT_POV_radiosity_add_preset(AddPresetBase, Operator):
"""Use this class to define pov radiosity add presets button."""
"""Use this class to define pov radiosity add presets button"""
'''Add a Radiosity Preset'''
bl_idname = "scene.radiosity_preset_add"
@ -4364,7 +4364,7 @@ class CAMERA_PT_POV_replacement_text(CameraDataButtonsPanel, Panel):
class TEXT_OT_POV_insert(Operator):
"""Use this class to create blender text editor operator to insert pov snippets like other pov IDEs."""
"""Use this class to create blender text editor operator to insert pov snippets like other pov IDEs"""
bl_idname = "text.povray_insert"
bl_label = "Insert"

View File

@ -20,7 +20,7 @@
bl_info = {
"name": "Rigify",
"version": (0, 6, 2),
"version": (0, 6, 3),
"author": "Nathan Vegdahl, Lucio Rossi, Ivan Cappiello, Alexander Gavrilov",
"blender": (2, 82, 0),
"description": "Automatic rigging from building-block components",

View File

@ -63,6 +63,24 @@ def set_bone_layers(bone, layers, combine=False):
# UI utilities
#=============================================
def layout_layer_buttons(layout, params, option, active_layers):
"Draw a layer selection button UI with certain layers marked with dots."
outer = layout.row()
for x in [0, 8]:
col = outer.column(align=True)
for y in [0, 16]:
row = col.row(align=True)
for i in range(x+y, x+y+8):
row.prop(
params, option, index=i, toggle=True, text="",
icon="LAYER_ACTIVE" if active_layers[i] else "NONE"
)
class ControlLayersOption:
def __init__(self, name, toggle_name=None, toggle_default=True, description="Set of control layers"):
self.name = name
@ -131,8 +149,9 @@ class ControlLayersOption:
if not active:
return
row = box.row(align=True)
row.prop(params, self.layers_option, text="")
active_layers = bpy.context.active_pose_bone.bone.layers[:]
layout_layer_buttons(box, params, self.layers_option, active_layers)
ControlLayersOption.FK = ControlLayersOption('fk', description="Layers for the FK controls to be on")