Merge branch 'master' into temp-xr-actions-D9124
This commit is contained in:
commit
09f497a98b
|
@ -19,8 +19,8 @@
|
|||
bl_info = {
|
||||
"name": "BlenderKit Online Asset Library",
|
||||
"author": "Vilem Duha, Petr Dlouhy",
|
||||
"version": (2, 92, 0),
|
||||
"blender": (2, 92, 0),
|
||||
"version": (2, 93, 0),
|
||||
"blender": (2, 93, 0),
|
||||
"location": "View3D > Properties > BlenderKit",
|
||||
"description": "Online BlenderKit library (materials, models, brushes and more). Connects to the internet.",
|
||||
"warning": "",
|
||||
|
|
|
@ -426,8 +426,6 @@ class ReGenerateThumbnailOperator(bpy.types.Operator):
|
|||
json_args=args_dict,
|
||||
wait=False)
|
||||
return {'FINISHED'}
|
||||
start_thumbnailer(self, context)
|
||||
return {'FINISHED'}
|
||||
|
||||
def invoke(self, context, event):
|
||||
wm = context.window_manager
|
||||
|
|
|
@ -1278,6 +1278,9 @@ class BlenderkitDownloadOperator(bpy.types.Operator):
|
|||
description="",
|
||||
default="")
|
||||
|
||||
# close_window: BoolProperty(name='Close window',
|
||||
# description='Try to close the window below mouse before download',
|
||||
# default=False)
|
||||
# @classmethod
|
||||
# def poll(cls, context):
|
||||
# return bpy.context.window_manager.BlenderKitModelThumbnails is not ''
|
||||
|
@ -1376,6 +1379,9 @@ class BlenderkitDownloadOperator(bpy.types.Operator):
|
|||
layout.prop(self, 'resolution', expand=True, icon_only=False)
|
||||
|
||||
def invoke(self, context, event):
|
||||
# if self.close_window:
|
||||
# context.window.cursor_warp(event.mouse_x-1000, event.mouse_y - 1000);
|
||||
|
||||
print(self.asset_base_id)
|
||||
wm = context.window_manager
|
||||
# only make a pop up in case of switching resolutions
|
||||
|
@ -1393,6 +1399,15 @@ class BlenderkitDownloadOperator(bpy.types.Operator):
|
|||
self.resolution = 'ORIGINAL'
|
||||
return wm.invoke_props_dialog(self)
|
||||
|
||||
# if self.close_window:
|
||||
# time.sleep(0.1)
|
||||
# context.area.tag_redraw()
|
||||
# time.sleep(0.1)
|
||||
#
|
||||
# context.window.cursor_warp(event.mouse_x, event.mouse_y);
|
||||
|
||||
return self.execute(context)
|
||||
|
||||
|
||||
def register_download():
|
||||
bpy.utils.register_class(BlenderkitDownloadOperator)
|
||||
|
|
|
@ -16,7 +16,7 @@
|
|||
#
|
||||
# ##### END GPL LICENSE BLOCK #####
|
||||
|
||||
from blenderkit import paths, utils, rerequests, tasks_queue, ratings_utils
|
||||
from blenderkit import paths, utils, rerequests, tasks_queue, ratings_utils, icons
|
||||
|
||||
import bpy
|
||||
import requests, threading
|
||||
|
@ -176,44 +176,70 @@ class UploadRatingOperator(bpy.types.Operator):
|
|||
|
||||
|
||||
def draw_ratings_menu(self, context, layout):
|
||||
pcoll = icons.icon_collections["main"]
|
||||
|
||||
profile_name = ''
|
||||
profile = bpy.context.window_manager.get('bkit profile')
|
||||
if profile:
|
||||
profile_name = ' ' + profile['user']['firstName']
|
||||
|
||||
col = layout.column()
|
||||
# layout.template_icon_view(bkit_ratings, property, show_labels=False, scale=6.0, scale_popup=5.0)
|
||||
row = col.row()
|
||||
row.label(text='Quality:', icon = 'SOLO_ON')
|
||||
row = col.row()
|
||||
row.label(text='Please help the community by rating quality:')
|
||||
|
||||
row = col.row()
|
||||
row.prop(self, 'rating_quality_ui', expand=True, icon_only=True, emboss=False)
|
||||
if self.rating_quality>0:
|
||||
# row = col.row()
|
||||
|
||||
row.label(text=f' Thanks{profile_name}!', icon = 'FUND')
|
||||
# row.label(text=str(self.rating_quality))
|
||||
col.separator()
|
||||
col.separator()
|
||||
|
||||
row = layout.row()
|
||||
row = col.row()
|
||||
row.label(text='Complexity:', icon_value=pcoll['dumbbell'].icon_id)
|
||||
row = col.row()
|
||||
row.label(text=f"How many hours did this {self.asset_type} save you?")
|
||||
|
||||
if utils.profile_is_validator():
|
||||
row = col.row()
|
||||
row.prop(self, 'rating_work_hours')
|
||||
|
||||
if self.asset_type in ('model', 'scene'):
|
||||
row = layout.row()
|
||||
if utils.profile_is_validator():
|
||||
col.prop(self, 'rating_work_hours')
|
||||
row = col.row()
|
||||
|
||||
row.prop(self, 'rating_work_hours_ui', expand=True, icon_only=False, emboss=True)
|
||||
if float(self.rating_work_hours_ui) > 100:
|
||||
utils.label_multiline(layout,
|
||||
utils.label_multiline(col,
|
||||
text=f"\nThat's huge! please be sure to give such rating only to godly {self.asset_type}s.\n",
|
||||
width=500)
|
||||
elif float(self.rating_work_hours_ui) > 18:
|
||||
layout.separator()
|
||||
col.separator()
|
||||
|
||||
utils.label_multiline(layout,
|
||||
utils.label_multiline(col,
|
||||
text=f"\nThat's a lot! please be sure to give such rating only to amazing {self.asset_type}s.\n",
|
||||
width=500)
|
||||
|
||||
|
||||
elif self.asset_type == 'hdr':
|
||||
row = layout.row()
|
||||
row = col.row()
|
||||
row.prop(self, 'rating_work_hours_ui_1_10', expand=True, icon_only=False, emboss=True)
|
||||
else:
|
||||
row = layout.row()
|
||||
row = col.row()
|
||||
row.prop(self, 'rating_work_hours_ui_1_5', expand=True, icon_only=False, emboss=True)
|
||||
|
||||
if self.rating_work_hours>0:
|
||||
row = col.row()
|
||||
row.label(text=f'Thanks{profile_name}, you are amazing!', icon='FUND')
|
||||
|
||||
class FastRateMenu(Operator, ratings_utils.RatingsProperties):
|
||||
"""Rating of the assets , also directly from the asset bar - without need to download assets"""
|
||||
bl_idname = "wm.blenderkit_menu_rating_upload"
|
||||
bl_label = "Rate asset"
|
||||
bl_label = ""
|
||||
bl_options = {'REGISTER', 'UNDO', 'INTERNAL'}
|
||||
|
||||
|
||||
|
@ -226,6 +252,7 @@ class FastRateMenu(Operator, ratings_utils.RatingsProperties):
|
|||
def draw(self, context):
|
||||
layout = self.layout
|
||||
layout.label(text=self.message)
|
||||
layout.separator()
|
||||
|
||||
draw_ratings_menu(self, context, layout)
|
||||
|
||||
|
@ -265,8 +292,9 @@ class FastRateMenu(Operator, ratings_utils.RatingsProperties):
|
|||
|
||||
if self.asset_id == '':
|
||||
return {'CANCELLED'}
|
||||
self.message = f"Rate asset {self.asset_name}"
|
||||
self.message = f"{self.asset_name}"
|
||||
wm = context.window_manager
|
||||
self.prefill_ratings()
|
||||
|
||||
if self.asset_type in ('model', 'scene'):
|
||||
# spawn a wider one for validators for the enum buttons
|
||||
|
|
|
@ -30,7 +30,6 @@ from bpy.props import (
|
|||
PointerProperty,
|
||||
)
|
||||
|
||||
|
||||
import threading
|
||||
import requests
|
||||
import logging
|
||||
|
@ -69,21 +68,43 @@ def send_rating_to_thread_work_hours(url, ratings, headers):
|
|||
thread.start()
|
||||
|
||||
|
||||
def store_rating_local(asset_id, type='quality', value=0):
|
||||
context = bpy.context
|
||||
context.window_manager['asset ratings'] = context.window_manager.get('asset ratings', {})
|
||||
context.window_manager['asset ratings'][asset_id] = context.window_manager['asset ratings'].get(asset_id, {})
|
||||
context.window_manager['asset ratings'][asset_id][type] = value
|
||||
|
||||
|
||||
def get_rating_local(asset_id):
|
||||
context = bpy.context
|
||||
context.window_manager['asset ratings'] = context.window_manager.get('asset ratings', {})
|
||||
rating = context.window_manager['asset ratings'].get(asset_id)
|
||||
if rating:
|
||||
return rating.to_dict()
|
||||
return None
|
||||
|
||||
|
||||
def update_ratings_quality(self, context):
|
||||
user_preferences = bpy.context.preferences.addons['blenderkit'].preferences
|
||||
api_key = user_preferences.api_key
|
||||
|
||||
headers = utils.get_headers(api_key)
|
||||
asset = self.id_data
|
||||
if asset:
|
||||
|
||||
if not (hasattr(self, 'rating_quality')):
|
||||
# first option is for rating of assets that are from scene
|
||||
asset = self.id_data
|
||||
bkit_ratings = asset.bkit_ratings
|
||||
url = paths.get_api_url() + 'assets/' + asset['asset_data']['id'] + '/rating/'
|
||||
asset_id = asset['asset_data']['id']
|
||||
else:
|
||||
# this part is for operator rating:
|
||||
bkit_ratings = self
|
||||
url = paths.get_api_url() + f'assets/{self.asset_id}/rating/'
|
||||
asset_id = self.asset_id
|
||||
|
||||
if bkit_ratings.rating_quality > 0.1:
|
||||
url = paths.get_api_url() + f'assets/{asset_id}/rating/'
|
||||
|
||||
store_rating_local(asset_id, type='quality', value=bkit_ratings.rating_quality)
|
||||
|
||||
ratings = [('quality', bkit_ratings.rating_quality)]
|
||||
tasks_queue.add_task((send_rating_to_thread_quality, (url, ratings, headers)), wait=2.5, only_last=True)
|
||||
|
||||
|
@ -92,16 +113,21 @@ def update_ratings_work_hours(self, context):
|
|||
user_preferences = bpy.context.preferences.addons['blenderkit'].preferences
|
||||
api_key = user_preferences.api_key
|
||||
headers = utils.get_headers(api_key)
|
||||
asset = self.id_data
|
||||
if asset:
|
||||
if not (hasattr(self, 'rating_work_hours')):
|
||||
# first option is for rating of assets that are from scene
|
||||
asset = self.id_data
|
||||
bkit_ratings = asset.bkit_ratings
|
||||
url = paths.get_api_url() + 'assets/' + asset['asset_data']['id'] + '/rating/'
|
||||
asset_id = asset['asset_data']['id']
|
||||
else:
|
||||
# this part is for operator rating:
|
||||
bkit_ratings = self
|
||||
url = paths.get_api_url() + f'assets/{self.asset_id}/rating/'
|
||||
asset_id = self.asset_id
|
||||
|
||||
if bkit_ratings.rating_work_hours > 0.45:
|
||||
url = paths.get_api_url() + f'assets/{asset_id}/rating/'
|
||||
|
||||
store_rating_local(asset_id, type='working_hours', value=bkit_ratings.rating_work_hours)
|
||||
|
||||
ratings = [('working_hours', round(bkit_ratings.rating_work_hours, 1))]
|
||||
tasks_queue.add_task((send_rating_to_thread_work_hours, (url, ratings, headers)), wait=2.5, only_last=True)
|
||||
|
||||
|
@ -140,8 +166,6 @@ def update_ratings_work_hours_ui_1_5(self, context):
|
|||
bpy.ops.wm.blenderkit_login('INVOKE_DEFAULT',
|
||||
message='Please login/signup to rate assets. Clicking OK takes you to web login.')
|
||||
# self.rating_work_hours_ui_1_5 = '0'
|
||||
# print('updating 1-5')
|
||||
# print(float(self.rating_work_hours_ui_1_5))
|
||||
self.rating_work_hours = float(self.rating_work_hours_ui_1_5)
|
||||
|
||||
|
||||
|
@ -171,6 +195,7 @@ def stars_enum_callback(self, context):
|
|||
items.append((f'{a + 1}', f'{a + 1}', '', icon, a + 1))
|
||||
return items
|
||||
|
||||
|
||||
class RatingsProperties():
|
||||
message: StringProperty(
|
||||
name="message",
|
||||
|
@ -279,4 +304,17 @@ class RatingsProperties():
|
|||
default='0',
|
||||
update=update_ratings_work_hours_ui_1_10,
|
||||
options={'SKIP_SAVE'}
|
||||
)
|
||||
)
|
||||
|
||||
def prefill_ratings(self):
|
||||
# pre-fill ratings
|
||||
ratings = get_rating_local(self.asset_id)
|
||||
if ratings and ratings.get('quality'):
|
||||
self.rating_quality = ratings['quality']
|
||||
if ratings and ratings.get('working_hours'):
|
||||
wh = int(ratings['working_hours'])
|
||||
self.rating_work_hours_ui = str(wh)
|
||||
if wh < 6:
|
||||
self.rating_work_hours_ui_1_5 = str(int(ratings['working_hours']))
|
||||
if wh < 11:
|
||||
self.rating_work_hours_ui_1_10 = str(int(ratings['working_hours']))
|
||||
|
|
|
@ -628,13 +628,14 @@ class ThumbDownloader(threading.Thread):
|
|||
return self._stop_event.is_set()
|
||||
|
||||
def run(self):
|
||||
print('thumb downloader', self.url)
|
||||
# print('thumb downloader', self.url)
|
||||
r = None
|
||||
try:
|
||||
r = requests.get(self.url, stream=False)
|
||||
except Exception as e:
|
||||
bk_logger.error('Thumbnail download failed')
|
||||
bk_logger.error(str(e))
|
||||
if r.status_code == 200:
|
||||
if r and 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.
|
||||
|
@ -1405,6 +1406,11 @@ class SearchOperator(Operator):
|
|||
options={'SKIP_SAVE'}
|
||||
)
|
||||
|
||||
# close_window: BoolProperty(name='Close window',
|
||||
# description='Try to close the window below mouse before download',
|
||||
# default=False)
|
||||
|
||||
|
||||
tooltip: bpy.props.StringProperty(default='Runs search and displays the asset bar at the same time')
|
||||
|
||||
@classmethod
|
||||
|
@ -1428,6 +1434,13 @@ class SearchOperator(Operator):
|
|||
|
||||
return {'FINISHED'}
|
||||
|
||||
# def invoke(self, context, event):
|
||||
# if self.close_window:
|
||||
# context.window.cursor_warp(event.mouse_x, event.mouse_y - 100);
|
||||
# context.area.tag_redraw()
|
||||
#
|
||||
# context.window.cursor_warp(event.mouse_x, event.mouse_y);
|
||||
# return self. execute(context)
|
||||
|
||||
class UrlOperator(Operator):
|
||||
""""""
|
||||
|
|
|
@ -382,6 +382,7 @@ def draw_tooltip_with_author(asset_data, x, y):
|
|||
gimg = utils.get_hidden_image(a['gravatarImg'], a['gravatarHash'])
|
||||
|
||||
aname = asset_data['displayName']
|
||||
aname = aname[0].upper() + aname[1:]
|
||||
if len(aname)>36:
|
||||
aname = f"{aname[:33]}..."
|
||||
|
||||
|
@ -1419,10 +1420,10 @@ class AssetBarOperator(bpy.types.Operator):
|
|||
my = event.mouse_y - r.y
|
||||
|
||||
if event.value == 'PRESS' and mouse_in_asset_bar(mx, my):
|
||||
context.window.cursor_warp(event.mouse_x - 400, event.mouse_y - 20);
|
||||
# context.window.cursor_warp(event.mouse_x - 300, event.mouse_y - 10);
|
||||
|
||||
bpy.ops.wm.blenderkit_asset_popup('INVOKE_DEFAULT')
|
||||
context.window.cursor_warp(event.mouse_x, event.mouse_y);
|
||||
# context.window.cursor_warp(event.mouse_x, event.mouse_y);
|
||||
|
||||
# bpy.ops.wm.call_menu(name='OBJECT_MT_blenderkit_asset_menu')
|
||||
return {'RUNNING_MODAL'}
|
||||
|
|
|
@ -421,8 +421,6 @@ 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='Asset tools:')
|
||||
draw_asset_context_menu(self.layout, context, ad, from_panel=True)
|
||||
|
@ -465,8 +463,6 @@ class NODE_PT_blenderkit_material_properties(Panel):
|
|||
if m.get('asset_data') is not None:
|
||||
ad = m['asset_data']
|
||||
layout.label(text=str(ad['name']))
|
||||
layout.label(text='Ratings:')
|
||||
draw_panel_material_ratings(self, context)
|
||||
|
||||
layout.label(text='Asset tools:')
|
||||
draw_asset_context_menu(self.layout, context, ad, from_panel=True)
|
||||
|
@ -1390,6 +1386,7 @@ def label_or_url(layout, text='', tooltip='', url='', icon_value=None, icon=None
|
|||
else:
|
||||
op = layout.operator('wm.blenderkit_tooltip', text=text)
|
||||
op.tooltip = tooltip
|
||||
# these are here to move the text to left, since operators can only center text by default
|
||||
layout.label(text='')
|
||||
layout.label(text='')
|
||||
return
|
||||
|
@ -1406,15 +1403,15 @@ class AssetPopupCard(bpy.types.Operator, ratings_utils.RatingsProperties):
|
|||
bl_idname = "wm.blenderkit_asset_popup"
|
||||
bl_label = "BlenderKit asset popup"
|
||||
|
||||
width = 700
|
||||
width = 800
|
||||
|
||||
@classmethod
|
||||
def poll(cls, context):
|
||||
return True
|
||||
|
||||
def draw_menu(self, context, layout):
|
||||
col = layout.column()
|
||||
draw_asset_context_menu(col, context, self.asset_data, from_panel=False)
|
||||
# layout = layout.column()
|
||||
draw_asset_context_menu(layout, context, self.asset_data, from_panel=False)
|
||||
|
||||
def draw_property(self, layout, left, right, icon=None, icon_value=None, url='', tooltip=''):
|
||||
right = str(right)
|
||||
|
@ -1439,8 +1436,19 @@ class AssetPopupCard(bpy.types.Operator, ratings_utils.RatingsProperties):
|
|||
parameter = utils.get_param(self.asset_data, key)
|
||||
if parameter == None:
|
||||
return
|
||||
if type(parameter) == int:
|
||||
parameter = f"{parameter:,d}"
|
||||
elif type(parameter) == float:
|
||||
parameter = f"{parameter:,.1f}"
|
||||
self.draw_property(layout, pretext, parameter)
|
||||
|
||||
def draw_description(self, layout, width = 250):
|
||||
if len(self.asset_data['description']) > 0:
|
||||
box = layout.box()
|
||||
box.scale_y = 0.8
|
||||
box.label(text='Description')
|
||||
utils.label_multiline(box, self.asset_data['description'], width=width)
|
||||
|
||||
def draw_properties(self, layout, width=250):
|
||||
|
||||
if type(self.asset_data['parameters']) == list:
|
||||
|
@ -1448,20 +1456,14 @@ class AssetPopupCard(bpy.types.Operator, ratings_utils.RatingsProperties):
|
|||
else:
|
||||
mparams = self.asset_data['parameters']
|
||||
|
||||
layout = layout.column()
|
||||
if len(self.asset_data['description']) > 0:
|
||||
box = layout.box()
|
||||
box.scale_y = 0.8
|
||||
box.label(text='Description')
|
||||
utils.label_multiline(box, self.asset_data['description'], width=width)
|
||||
|
||||
pcoll = icons.icon_collections["main"]
|
||||
|
||||
box = layout.box()
|
||||
|
||||
box.scale_y = 0.8
|
||||
box.label(text='Properties')
|
||||
if self.asset_data.get('license') == 'cc_zero':
|
||||
t = 'CC Zero'
|
||||
t = 'CC Zero '
|
||||
icon = pcoll['cc0']
|
||||
|
||||
else:
|
||||
|
@ -1508,29 +1510,32 @@ class AssetPopupCard(bpy.types.Operator, ratings_utils.RatingsProperties):
|
|||
|
||||
)
|
||||
# resolution/s
|
||||
# fs = self.asset_data['files']
|
||||
#
|
||||
# if fs and len(fs) > 2:
|
||||
# resolutions = ''
|
||||
# list.sort(fs, key=lambda f: f['fileType'])
|
||||
# for f in fs:
|
||||
# if f['fileType'].find('resolution') > -1:
|
||||
# resolutions += f['fileType'][11:] + ' '
|
||||
# resolutions = resolutions.replace('_', '.')
|
||||
# self.draw_property(box, 'Resolutions:', resolutions)
|
||||
resolution = utils.get_param(self.asset_data, 'textureResolutionMax')
|
||||
|
||||
if resolution is not None:
|
||||
fs = self.asset_data['files']
|
||||
|
||||
ress = f"{int(round(resolution / 1024, 0))}K"
|
||||
self.draw_property(box, 'Resolution', ress,
|
||||
tooltip='Maximal resolution of textures in this asset.\n' \
|
||||
'Most texture asset have also lower resolutions generated.\n' \
|
||||
'Go to BlenderKit add-on import settings to set default resolution')
|
||||
|
||||
if fs and len(fs) > 2 and utils.profile_is_validator():
|
||||
resolutions = ''
|
||||
list.sort(fs, key=lambda f: f['fileType'])
|
||||
for f in fs:
|
||||
if f['fileType'].find('resolution') > -1:
|
||||
resolutions += f['fileType'][11:] + ' '
|
||||
resolutions = resolutions.replace('_', '.')
|
||||
self.draw_property(box, 'Generated:', resolutions)
|
||||
|
||||
self.draw_asset_parameter(box, key='designer', pretext='Designer')
|
||||
self.draw_asset_parameter(box, key='manufacturer', pretext='Manufacturer') # TODO make them clickable!
|
||||
self.draw_asset_parameter(box, key='designCollection', pretext='Collection')
|
||||
self.draw_asset_parameter(box, key='designVariant', pretext='Variant')
|
||||
self.draw_asset_parameter(box, key='designYear', pretext='Design year')
|
||||
|
||||
self.draw_asset_parameter(box, key='faceCount', pretext='Face count')
|
||||
# self.draw_asset_parameter(box, key='thumbnailScale', pretext='Preview scale')
|
||||
# self.draw_asset_parameter(box, key='purePbr', pretext='Pure PBR')
|
||||
|
@ -1544,6 +1549,14 @@ class AssetPopupCard(bpy.types.Operator, ratings_utils.RatingsProperties):
|
|||
utils.fmt_length(mparams['dimensionY']),
|
||||
utils.fmt_length(mparams['dimensionZ']))
|
||||
self.draw_property(box, 'Size:', t)
|
||||
if self.asset_data.get('filesSize'):
|
||||
fs = self.asset_data['filesSize']
|
||||
fsmb = fs // (1024 * 1024)
|
||||
fskb = fs % 1024
|
||||
if fsmb == 0:
|
||||
self.draw_property(box, 'Original size:', f'{fskb}KB')
|
||||
else:
|
||||
self.draw_property(box, 'Original size:', f'{fsmb}MB')
|
||||
# Tags section
|
||||
# row = box.row()
|
||||
# letters_on_row = 0
|
||||
|
@ -1597,6 +1610,10 @@ class AssetPopupCard(bpy.types.Operator, ratings_utils.RatingsProperties):
|
|||
icon_value=icon.icon_id,
|
||||
tooltip=plans_tooltip,
|
||||
url=plans_link)
|
||||
if utils.profile_is_validator():
|
||||
date = self.asset_data['created'][:10]
|
||||
date = f"{date[8:10]}. {date[5:7]}. {date[:4]}"
|
||||
self.draw_property(box, 'Created:', date)
|
||||
|
||||
def draw_author_area(self, context, layout, width=330):
|
||||
self.draw_author(context, layout, width=width)
|
||||
|
@ -1627,7 +1644,7 @@ class AssetPopupCard(bpy.types.Operator, ratings_utils.RatingsProperties):
|
|||
|
||||
utils.label_multiline(col, text=a['tooltip'], width=text_width)
|
||||
# check if author didn't fill any data about himself and prompt him if that's the case
|
||||
if upload.user_is_owner(asset_data=self.asset_data) and a.get('aboutMe') is not None and len(
|
||||
if utils.user_is_owner(asset_data=self.asset_data) and a.get('aboutMe') is not None and len(
|
||||
a.get('aboutMe', '')) == 0:
|
||||
row = col.row()
|
||||
row.enabled = False
|
||||
|
@ -1656,14 +1673,13 @@ class AssetPopupCard(bpy.types.Operator, ratings_utils.RatingsProperties):
|
|||
op.keywords = ''
|
||||
op.author_id = self.asset_data['author']['id']
|
||||
|
||||
def draw_thumbnail_box(self, layout):
|
||||
def draw_thumbnail_box(self, layout, width = 250):
|
||||
layout.emboss = 'NORMAL'
|
||||
|
||||
box_thumbnail = layout.box()
|
||||
|
||||
box_thumbnail.scale_y = .4
|
||||
|
||||
box_thumbnail.template_icon(icon_value=self.img.preview.icon_id, scale=34.0)
|
||||
box_thumbnail.template_icon(icon_value=self.img.preview.icon_id, scale=width*.12)
|
||||
|
||||
# row = box_thumbnail.row()
|
||||
# row.scale_y = 3
|
||||
|
@ -1721,16 +1737,36 @@ class AssetPopupCard(bpy.types.Operator, ratings_utils.RatingsProperties):
|
|||
# left - tooltip & params
|
||||
row = box.row()
|
||||
split_factor = 0.7
|
||||
split_left_left = row.split(factor=split_factor)
|
||||
self.draw_properties(split_left_left, width=int(width * split_factor))
|
||||
split_left = row.split(factor=split_factor)
|
||||
col = split_left.column()
|
||||
width_left = int(width * split_factor)
|
||||
self.draw_description(col, width=width_left)
|
||||
|
||||
self.draw_properties(col, width=width_left)
|
||||
|
||||
# right - menu
|
||||
col1 = split_left_left.split()
|
||||
self.draw_menu(context, col1)
|
||||
split_right = split_left.split()
|
||||
col = split_right.column()
|
||||
self.draw_menu(context, col)
|
||||
|
||||
# author
|
||||
self.draw_author_area(context, box, width=width)
|
||||
|
||||
|
||||
# self.draw_author_area(context, box, width=width)
|
||||
#
|
||||
# col = box.column_flow(columns=2)
|
||||
# self.draw_menu(context, col)
|
||||
#
|
||||
#
|
||||
# # self.draw_description(box, width=int(width))
|
||||
# self.draw_properties(box, width=int(width))
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
def draw(self, context):
|
||||
ui_props = context.scene.blenderkitUI
|
||||
|
||||
|
@ -1741,23 +1777,29 @@ class AssetPopupCard(bpy.types.Operator, ratings_utils.RatingsProperties):
|
|||
# top draggabe bar with name of the asset
|
||||
top_row = layout.row()
|
||||
top_drag_bar = top_row.box()
|
||||
top_drag_bar.label(text=asset_data['displayName'])
|
||||
aname = asset_data['displayName']
|
||||
aname = aname[0].upper() + aname[1:]
|
||||
top_drag_bar.label(text=aname)
|
||||
|
||||
# left side
|
||||
row = layout.row(align=True)
|
||||
|
||||
split_ratio = 0.5
|
||||
split_left = row.split(factor=0.5)
|
||||
self.draw_thumbnail_box(split_left)
|
||||
|
||||
split_ratio = 0.45
|
||||
split_left = row.split(factor=split_ratio)
|
||||
left_column = split_left.column()
|
||||
self.draw_thumbnail_box(left_column, width = int(self.width * split_ratio))
|
||||
# self.draw_description(left_column, width = int(self.width*split_ratio))
|
||||
# right split
|
||||
split_right = split_left.split()
|
||||
self.draw_menu_desc_author(context, split_right, width=int(self.width * split_ratio))
|
||||
self.draw_menu_desc_author(context, split_right, width=int(self.width * (1-split_ratio)))
|
||||
|
||||
if not utils.user_is_owner(asset_data=asset_data):
|
||||
#Draw ratings, but not for owners of assets - doesn't make sense.
|
||||
ratings_box = layout.box()
|
||||
ratings.draw_ratings_menu(self, context, ratings_box)
|
||||
# else:
|
||||
# ratings_box.label('Here you should find ratings, but you can not rate your own assets ;)')
|
||||
|
||||
ratings_box = layout.box()
|
||||
ratings_box.scale_y = 0.7
|
||||
ratings_box.label(text='Rate asset quality:')
|
||||
ratings.draw_ratings_menu(self, context, ratings_box)
|
||||
tip_box = layout.box()
|
||||
tip_box.label(text=self.tip)
|
||||
|
||||
|
@ -1781,6 +1823,10 @@ class AssetPopupCard(bpy.types.Operator, ratings_utils.RatingsProperties):
|
|||
bl_label = asset_data['name']
|
||||
self.tip = search.get_random_tip()
|
||||
self.tip = self.tip.replace('\n', '')
|
||||
|
||||
# pre-fill ratings
|
||||
self.prefill_ratings()
|
||||
|
||||
return wm.invoke_popup(self, width=self.width)
|
||||
|
||||
|
||||
|
|
|
@ -567,14 +567,6 @@ def update_free_full(self, context):
|
|||
" based on our fair share system. " \
|
||||
"Part of subscription is sent to artists based on usage by paying users.")
|
||||
|
||||
def user_is_owner(asset_data=None):
|
||||
'''Checks if the current logged in user is owner of the asset'''
|
||||
profile = bpy.context.window_manager.get('bkit profile')
|
||||
if profile is None:
|
||||
return False
|
||||
if int(asset_data['author']['id']) == int(profile['user']['id']):
|
||||
return True
|
||||
return False
|
||||
|
||||
def can_edit_asset(active_index=-1, asset_data=None):
|
||||
if active_index < 0 and not asset_data:
|
||||
|
|
|
@ -784,6 +784,14 @@ def profile_is_validator():
|
|||
return True
|
||||
return False
|
||||
|
||||
def user_is_owner(asset_data=None):
|
||||
'''Checks if the current logged in user is owner of the asset'''
|
||||
profile = bpy.context.window_manager.get('bkit profile')
|
||||
if profile is None:
|
||||
return False
|
||||
if int(asset_data['author']['id']) == int(profile['user']['id']):
|
||||
return True
|
||||
return False
|
||||
|
||||
def guard_from_crash():
|
||||
'''
|
||||
|
|
|
@ -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, 7, 5),
|
||||
"version": (1, 7, 7),
|
||||
'blender': (2, 91, 0),
|
||||
'location': 'File > Import-Export',
|
||||
'description': 'Import-Export as glTF 2.0',
|
||||
|
@ -298,6 +298,24 @@ class ExportGLTF2_Base:
|
|||
default=False
|
||||
)
|
||||
|
||||
use_visible: BoolProperty(
|
||||
name='Visible Objects',
|
||||
description='Export visible objects only',
|
||||
default=False
|
||||
)
|
||||
|
||||
use_renderable: BoolProperty(
|
||||
name='Renderable Objects',
|
||||
description='Export renderable objects only',
|
||||
default=False
|
||||
)
|
||||
|
||||
use_active_collection: BoolProperty(
|
||||
name='Active Collection',
|
||||
description='Export objects in the active collection only',
|
||||
default=False
|
||||
)
|
||||
|
||||
export_extras: BoolProperty(
|
||||
name='Custom Properties',
|
||||
description='Export custom properties as glTF extras',
|
||||
|
@ -464,6 +482,9 @@ class ExportGLTF2_Base:
|
|||
exceptional = [
|
||||
# options that don't start with 'export_'
|
||||
'use_selection',
|
||||
'use_visible',
|
||||
'use_renderable',
|
||||
'use_active_collection',
|
||||
'use_mesh_edges',
|
||||
'use_mesh_vertices',
|
||||
]
|
||||
|
@ -528,6 +549,10 @@ class ExportGLTF2_Base:
|
|||
else:
|
||||
export_settings['gltf_selected'] = self.use_selection
|
||||
|
||||
export_settings['gltf_visible'] = self.use_visible
|
||||
export_settings['gltf_renderable'] = self.use_renderable
|
||||
export_settings['gltf_active_collection'] = self.use_active_collection
|
||||
|
||||
# export_settings['gltf_selected'] = self.use_selection This can be uncomment when removing compatibility of export_selected
|
||||
export_settings['gltf_layers'] = True # self.export_layers
|
||||
export_settings['gltf_extras'] = self.export_extras
|
||||
|
@ -657,6 +682,9 @@ class GLTF_PT_export_include(bpy.types.Panel):
|
|||
|
||||
col = layout.column(heading = "Limit to", align = True)
|
||||
col.prop(operator, 'use_selection')
|
||||
col.prop(operator, 'use_visible')
|
||||
col.prop(operator, 'use_renderable')
|
||||
col.prop(operator, 'use_active_collection')
|
||||
|
||||
col = layout.column(heading = "Data", align = True)
|
||||
col.prop(operator, 'export_extras')
|
||||
|
|
|
@ -26,6 +26,9 @@ FILTERED_CAMERAS = 'filtered_cameras'
|
|||
|
||||
APPLY = 'gltf_apply'
|
||||
SELECTED = 'gltf_selected'
|
||||
VISIBLE = 'gltf_visible'
|
||||
RENDERABLE = 'gltf_renderable'
|
||||
ACTIVE_COLLECTION = 'gltf_active_collection'
|
||||
SKINS = 'gltf_skins'
|
||||
DISPLACEMENT = 'gltf_displacement'
|
||||
FORCE_SAMPLING = 'gltf_force_sampling'
|
||||
|
|
|
@ -127,6 +127,19 @@ def __filter_node(blender_object, blender_scene, export_settings):
|
|||
if export_settings[gltf2_blender_export_keys.SELECTED] and blender_object.select_get() is False:
|
||||
return False
|
||||
|
||||
if export_settings[gltf2_blender_export_keys.VISIBLE] and blender_object.visible_get() is False:
|
||||
return False
|
||||
|
||||
# render_get() doesn't exist, so unfortunately this won't take into account the Collection settings
|
||||
if export_settings[gltf2_blender_export_keys.RENDERABLE] and blender_object.hide_render is True:
|
||||
return False
|
||||
|
||||
if export_settings[gltf2_blender_export_keys.ACTIVE_COLLECTION]:
|
||||
found = any(x == blender_object for x in bpy.context.collection.all_objects)
|
||||
|
||||
if not found:
|
||||
return False
|
||||
|
||||
return True
|
||||
|
||||
|
||||
|
@ -357,6 +370,10 @@ def __gather_mesh_from_nonmesh(blender_object, library, export_settings):
|
|||
blender_mesh_owner = blender_object
|
||||
blender_mesh = blender_mesh_owner.to_mesh()
|
||||
|
||||
# In some cases (for example curve with single vertice), no blender_mesh is created (without crash)
|
||||
if blender_mesh is None:
|
||||
return None
|
||||
|
||||
except Exception:
|
||||
return None
|
||||
|
||||
|
|
|
@ -21,7 +21,7 @@
|
|||
bl_info = {
|
||||
"name": "3D-Print Toolbox",
|
||||
"author": "Campbell Barton",
|
||||
"blender": (2, 82, 0),
|
||||
"blender": (3, 0, 0),
|
||||
"location": "3D View > Sidebar",
|
||||
"description": "Utilities for 3D printing",
|
||||
"doc_url": "{BLENDER_MANUAL_URL}/addons/mesh/3d_print_toolbox.html",
|
||||
|
@ -34,7 +34,8 @@ if "bpy" in locals():
|
|||
import importlib
|
||||
importlib.reload(ui)
|
||||
importlib.reload(operators)
|
||||
importlib.reload(mesh_helpers)
|
||||
if "mesh_helpers" in locals():
|
||||
importlib.reload(mesh_helpers)
|
||||
if "export" in locals():
|
||||
importlib.reload(export)
|
||||
else:
|
||||
|
|
|
@ -31,32 +31,62 @@ from bpy.props import (
|
|||
)
|
||||
import bmesh
|
||||
|
||||
from . import (
|
||||
mesh_helpers,
|
||||
report,
|
||||
)
|
||||
from . import report
|
||||
|
||||
|
||||
def clean_float(text):
|
||||
# strip trailing zeros: 0.000 -> 0.0
|
||||
def clean_float(value: float, precision: int = 0) -> str:
|
||||
# Avoid scientific notation and strip trailing zeros: 0.000 -> 0.0
|
||||
|
||||
text = f"{value:.{precision}f}"
|
||||
index = text.rfind(".")
|
||||
|
||||
if index != -1:
|
||||
index += 2
|
||||
head, tail = text[:index], text[index:]
|
||||
tail = tail.rstrip("0")
|
||||
text = head + tail
|
||||
|
||||
return text
|
||||
|
||||
|
||||
def get_unit(unit_system: str, unit: str) -> tuple[float, str]:
|
||||
# Returns unit length relative to meter and unit symbol
|
||||
|
||||
units = {
|
||||
"METRIC": {
|
||||
"KILOMETERS": (1000.0, "km"),
|
||||
"METERS": (1.0, "m"),
|
||||
"CENTIMETERS": (0.01, "cm"),
|
||||
"MILLIMETERS": (0.001, "mm"),
|
||||
"MICROMETERS": (0.000001, "µm"),
|
||||
},
|
||||
"IMPERIAL": {
|
||||
"MILES": (1609.344, "mi"),
|
||||
"FEET": (0.3048, "\'"),
|
||||
"INCHES": (0.0254, "\""),
|
||||
"THOU": (0.0000254, "thou"),
|
||||
},
|
||||
}
|
||||
|
||||
try:
|
||||
return units[unit_system][unit]
|
||||
except KeyError:
|
||||
fallback_unit = "CENTIMETERS" if unit_system == "METRIC" else "INCHES"
|
||||
return units[unit_system][fallback_unit]
|
||||
|
||||
|
||||
# ---------
|
||||
# Mesh Info
|
||||
|
||||
|
||||
class MESH_OT_print3d_info_volume(Operator):
|
||||
bl_idname = "mesh.print3d_info_volume"
|
||||
bl_label = "3D-Print Info Volume"
|
||||
bl_description = "Report the volume of the active mesh"
|
||||
|
||||
def execute(self, context):
|
||||
from . import mesh_helpers
|
||||
|
||||
scene = context.scene
|
||||
unit = scene.unit_settings
|
||||
scale = 1.0 if unit.system == 'NONE' else unit.scale_length
|
||||
|
@ -66,14 +96,14 @@ class MESH_OT_print3d_info_volume(Operator):
|
|||
volume = bm.calc_volume()
|
||||
bm.free()
|
||||
|
||||
if unit.system == 'METRIC':
|
||||
volume_cm = volume * (scale ** 3.0) / (0.01 ** 3.0)
|
||||
volume_fmt = "{} cm".format(clean_float(f"{volume_cm:.4f}"))
|
||||
elif unit.system == 'IMPERIAL':
|
||||
volume_inch = volume * (scale ** 3.0) / (0.0254 ** 3.0)
|
||||
volume_fmt = '{} "'.format(clean_float(f"{volume_inch:.4f}"))
|
||||
if unit.system == 'NONE':
|
||||
volume_fmt = clean_float(volume, 8)
|
||||
else:
|
||||
volume_fmt = clean_float(f"{volume:.8f}")
|
||||
length, symbol = get_unit(unit.system, unit.length_unit)
|
||||
|
||||
volume_unit = volume * (scale ** 3.0) / (length ** 3.0)
|
||||
volume_str = clean_float(volume_unit, 4)
|
||||
volume_fmt = f"{volume_str} {symbol}"
|
||||
|
||||
report.update((f"Volume: {volume_fmt}³", None))
|
||||
|
||||
|
@ -86,6 +116,8 @@ class MESH_OT_print3d_info_area(Operator):
|
|||
bl_description = "Report the surface area of the active mesh"
|
||||
|
||||
def execute(self, context):
|
||||
from . import mesh_helpers
|
||||
|
||||
scene = context.scene
|
||||
unit = scene.unit_settings
|
||||
scale = 1.0 if unit.system == 'NONE' else unit.scale_length
|
||||
|
@ -95,14 +127,14 @@ class MESH_OT_print3d_info_area(Operator):
|
|||
area = mesh_helpers.bmesh_calc_area(bm)
|
||||
bm.free()
|
||||
|
||||
if unit.system == 'METRIC':
|
||||
area_cm = area * (scale ** 2.0) / (0.01 ** 2.0)
|
||||
area_fmt = "{} cm".format(clean_float(f"{area_cm:.4f}"))
|
||||
elif unit.system == 'IMPERIAL':
|
||||
area_inch = area * (scale ** 2.0) / (0.0254 ** 2.0)
|
||||
area_fmt = '{} "'.format(clean_float(f"{area_inch:.4f}"))
|
||||
if unit.system == 'NONE':
|
||||
area_fmt = clean_float(area, 8)
|
||||
else:
|
||||
area_fmt = clean_float(f"{area:.8f}")
|
||||
length, symbol = get_unit(unit.system, unit.length_unit)
|
||||
|
||||
area_unit = area * (scale ** 2.0) / (length ** 2.0)
|
||||
area_str = clean_float(area_unit, 4)
|
||||
area_fmt = f"{area_str} {symbol}"
|
||||
|
||||
report.update((f"Area: {area_fmt}²", None))
|
||||
|
||||
|
@ -137,6 +169,7 @@ class MESH_OT_print3d_check_solid(Operator):
|
|||
@staticmethod
|
||||
def main_check(obj, info):
|
||||
import array
|
||||
from . import mesh_helpers
|
||||
|
||||
bm = mesh_helpers.bmesh_copy_from_object(obj, transform=False, triangulate=False)
|
||||
|
||||
|
@ -162,6 +195,8 @@ class MESH_OT_print3d_check_intersections(Operator):
|
|||
|
||||
@staticmethod
|
||||
def main_check(obj, info):
|
||||
from . import mesh_helpers
|
||||
|
||||
faces_intersect = mesh_helpers.bmesh_check_self_intersect_object(obj)
|
||||
info.append((f"Intersect Face: {len(faces_intersect)}", (bmesh.types.BMFace, faces_intersect)))
|
||||
|
||||
|
@ -180,6 +215,7 @@ class MESH_OT_print3d_check_degenerate(Operator):
|
|||
@staticmethod
|
||||
def main_check(obj, info):
|
||||
import array
|
||||
from . import mesh_helpers
|
||||
|
||||
scene = bpy.context.scene
|
||||
print_3d = scene.print_3d
|
||||
|
@ -207,6 +243,7 @@ class MESH_OT_print3d_check_distorted(Operator):
|
|||
@staticmethod
|
||||
def main_check(obj, info):
|
||||
import array
|
||||
from . import mesh_helpers
|
||||
|
||||
scene = bpy.context.scene
|
||||
print_3d = scene.print_3d
|
||||
|
@ -238,6 +275,8 @@ class MESH_OT_print3d_check_thick(Operator):
|
|||
|
||||
@staticmethod
|
||||
def main_check(obj, info):
|
||||
from . import mesh_helpers
|
||||
|
||||
scene = bpy.context.scene
|
||||
print_3d = scene.print_3d
|
||||
|
||||
|
@ -255,6 +294,8 @@ class MESH_OT_print3d_check_sharp(Operator):
|
|||
|
||||
@staticmethod
|
||||
def main_check(obj, info):
|
||||
from . import mesh_helpers
|
||||
|
||||
scene = bpy.context.scene
|
||||
print_3d = scene.print_3d
|
||||
angle_sharp = print_3d.angle_sharp
|
||||
|
@ -282,6 +323,7 @@ class MESH_OT_print3d_check_overhang(Operator):
|
|||
@staticmethod
|
||||
def main_check(obj, info):
|
||||
from mathutils import Vector
|
||||
from . import mesh_helpers
|
||||
|
||||
scene = bpy.context.scene
|
||||
print_3d = scene.print_3d
|
||||
|
@ -355,6 +397,8 @@ class MESH_OT_print3d_clean_distorted(Operator):
|
|||
)
|
||||
|
||||
def execute(self, context):
|
||||
from . import mesh_helpers
|
||||
|
||||
obj = context.active_object
|
||||
bm = mesh_helpers.bmesh_from_object(obj)
|
||||
bm.normal_update()
|
||||
|
@ -589,7 +633,7 @@ def _scale(scale, report=None, report_suffix=""):
|
|||
if scale != 1.0:
|
||||
bpy.ops.transform.resize(value=(scale,) * 3)
|
||||
if report is not None:
|
||||
scale_fmt = clean_float(f"{scale:.6f}")
|
||||
scale_fmt = clean_float(scale, 6)
|
||||
report({'INFO'}, f"Scaled by {scale_fmt}{report_suffix}")
|
||||
|
||||
|
||||
|
@ -611,7 +655,7 @@ class MESH_OT_print3d_scale_to_volume(Operator):
|
|||
|
||||
def execute(self, context):
|
||||
scale = math.pow(self.volume, 1 / 3) / math.pow(self.volume_init, 1 / 3)
|
||||
scale_fmt = clean_float(f"{scale:.6f}")
|
||||
scale_fmt = clean_float(scale, 6)
|
||||
self.report({'INFO'}, f"Scaled by {scale_fmt}")
|
||||
_scale(scale, self.report)
|
||||
return {'FINISHED'}
|
||||
|
@ -619,6 +663,8 @@ class MESH_OT_print3d_scale_to_volume(Operator):
|
|||
def invoke(self, context, event):
|
||||
|
||||
def calc_volume(obj):
|
||||
from . import mesh_helpers
|
||||
|
||||
bm = mesh_helpers.bmesh_copy_from_object(obj, apply_modifiers=True)
|
||||
volume = bm.calc_volume(signed=True)
|
||||
bm.free()
|
||||
|
|
|
@ -29,8 +29,8 @@
|
|||
bl_info = {
|
||||
"name": "Precision Drawing Tools (PDT)",
|
||||
"author": "Alan Odom (Clockmender), Rune Morling (ermo)",
|
||||
"version": (1, 4, 0),
|
||||
"blender": (2, 83, 0),
|
||||
"version": (1, 5, 1),
|
||||
"blender": (2, 90, 0),
|
||||
"location": "View3D > UI > PDT",
|
||||
"description": "Precision Drawing Tools for Acccurate Modelling",
|
||||
"warning": "",
|
||||
|
@ -430,7 +430,7 @@ class PDTSceneProperties(PropertyGroup):
|
|||
|
||||
# Was filletrad
|
||||
fillet_radius: FloatProperty(
|
||||
name="Fillet Radius", min=0.0, default=1.0, description=PDT_DES_FILLETRAD
|
||||
name="Fillet Radius", min=0.0, default=1.0, unit="LENGTH", description=PDT_DES_FILLETRAD
|
||||
)
|
||||
# Was filletnum
|
||||
fillet_segments: IntProperty(
|
||||
|
@ -454,10 +454,10 @@ class PDTSceneProperties(PropertyGroup):
|
|||
name="Coordst2", default=(0.0, 0.0, 0.0), subtype="XYZ", description=PDT_DES_TANCEN2
|
||||
)
|
||||
tangent_radius0: FloatProperty(
|
||||
name="Arc Radius 1", min=0.00001, default=1, description=PDT_DES_RADIUS1
|
||||
name="Arc Radius 1", min=0.00001, default=1, unit="LENGTH", description=PDT_DES_RADIUS1
|
||||
)
|
||||
tangent_radius1: FloatProperty(
|
||||
name="Arc Radius 2", min=0.00001, default=1, description=PDT_DES_RADIUS2
|
||||
name="Arc Radius 2", min=0.00001, default=1, unit="LENGTH", description=PDT_DES_RADIUS2
|
||||
)
|
||||
tangent_point2: FloatVectorProperty(
|
||||
name="Coordst3", default=(0.0, 0.0, 0.0), subtype="XYZ", description=PDT_DES_TANCEN3
|
||||
|
@ -520,6 +520,7 @@ classes = (
|
|||
pdt_design.PDT_OT_PlacementAbs,
|
||||
pdt_design.PDT_OT_PlacementDelta,
|
||||
pdt_design.PDT_OT_PlacementDis,
|
||||
pdt_design.PDT_OT_PlacementView,
|
||||
pdt_design.PDT_OT_PlacementCen,
|
||||
pdt_design.PDT_OT_PlacementPer,
|
||||
pdt_design.PDT_OT_PlacementNormal,
|
||||
|
|
|
@ -220,9 +220,9 @@ def command_run(self, context):
|
|||
mode = command[1].lower()
|
||||
if (
|
||||
(operation == "F" and mode not in {"v", "e", "i"})
|
||||
or (operation in {"D", "E"} and mode not in {"d", "i"})
|
||||
or (operation in {"D", "E"} and mode not in {"d", "i", "n"}) #new
|
||||
or (operation == "M" and mode not in {"a", "d", "i", "p", "o", "x", "y", "z"})
|
||||
or (operation not in {"D", "E", "F", "M"} and mode not in {"a", "d", "i", "p"})
|
||||
or (operation not in {"D", "E", "F", "M"} and mode not in {"a", "d", "i", "p", "n"}) #new
|
||||
):
|
||||
pg.error = f"'{mode}' {PDT_ERR_NON_VALID} '{operation}'"
|
||||
context.window_manager.popup_menu(oops, title="Error", icon="ERROR")
|
||||
|
@ -322,15 +322,15 @@ def pdt_help(self, context):
|
|||
label = self.layout.label
|
||||
label(text="Primary Letters (Available Secondary Letters):")
|
||||
label(text="")
|
||||
label(text="C: Cursor (a, d, i, p)")
|
||||
label(text="D: Duplicate Geometry (d, i)")
|
||||
label(text="E: Extrude Geometry (d, i)")
|
||||
label(text="C: Cursor (a, d, i, p, v)")
|
||||
label(text="D: Duplicate Geometry (d, i, v)")
|
||||
label(text="E: Extrude Geometry (d, i, v)")
|
||||
label(text="F: Fillet (v, e, i)")
|
||||
label(text="G: Grab (Move) (a, d, i, p)")
|
||||
label(text="N: New Vertex (a, d, i, p)")
|
||||
label(text="G: Grab (Move) (a, d, i, p, v)")
|
||||
label(text="N: New Vertex (a, d, i, p, v)")
|
||||
label(text="M: Maths Functions (a, d, p, o, x, y, z)")
|
||||
label(text="P: Pivot Point (a, d, i, p)")
|
||||
label(text="V: Extrude Vertice Only (a, d, i, p)")
|
||||
label(text="P: Pivot Point (a, d, i, p, v)")
|
||||
label(text="V: Extrude Vertice Only (a, d, i, p, v)")
|
||||
label(text="S: Split Edges (a, d, i, p)")
|
||||
label(text="?: Quick Help")
|
||||
label(text="")
|
||||
|
@ -341,6 +341,8 @@ def pdt_help(self, context):
|
|||
label(text="d: Delta (Relative) Coordinates, e.g. 0.5,0,1.2")
|
||||
label(text="i: Directional (Polar) Coordinates e.g. 2.6,45")
|
||||
label(text="p: Percent e.g. 67.5")
|
||||
label(text="n: Work in View Normal Axis")
|
||||
label(text="")
|
||||
label(text="- Fillet Options:")
|
||||
label(text="v: Fillet Vertices")
|
||||
label(text="e: Fillet Edges")
|
||||
|
@ -438,6 +440,19 @@ def command_parse(context):
|
|||
except ValueError:
|
||||
values[ind] = "0.0"
|
||||
ind = ind + 1
|
||||
if mode == "n":
|
||||
# View relative mode
|
||||
if pg.plane == "XZ":
|
||||
values = [0.0, values[0], 0.0]
|
||||
elif pg.plane == "YZ":
|
||||
values = [values[0], 0.0, 0.0]
|
||||
elif pg.plane == "XY":
|
||||
values = [0.0, 0.0, values[0]]
|
||||
else:
|
||||
if "-" in values[0]:
|
||||
values = [0.0, 0.0, values[0][1:]]
|
||||
else:
|
||||
values = [0.0, 0.0, f"-{values[0]}"]
|
||||
# Apply System Rounding
|
||||
decimal_places = context.preferences.addons[__package__].preferences.pdt_input_round
|
||||
values_out = [str(round(float(v), decimal_places)) for v in values]
|
||||
|
@ -507,7 +522,7 @@ def move_cursor_pivot(context, pg, operation, mode, obj, verts, values):
|
|||
"""
|
||||
|
||||
# Absolute/Global Coordinates, or Delta/Relative Coordinates
|
||||
if mode in {"a", "d"}:
|
||||
if mode in {"a", "d", "n"}:
|
||||
try:
|
||||
vector_delta = vector_build(context, pg, obj, operation, values, 3)
|
||||
except:
|
||||
|
@ -537,8 +552,8 @@ def move_cursor_pivot(context, pg, operation, mode, obj, verts, values):
|
|||
scene.cursor.location = vector_delta
|
||||
elif operation == "P":
|
||||
pg.pivot_loc = vector_delta
|
||||
elif mode in {"d", "i"}:
|
||||
if pg.plane == "LO" and mode == "d":
|
||||
elif mode in {"d", "i", "n"}:
|
||||
if pg.plane == "LO" and mode in {"d", "n"}:
|
||||
vector_delta = view_coords(vector_delta.x, vector_delta.y, vector_delta.z)
|
||||
elif pg.plane == "LO" and mode == "i":
|
||||
vector_delta = view_dir(pg.distance, pg.angle)
|
||||
|
@ -598,7 +613,7 @@ def move_entities(context, pg, operation, mode, obj, bm, verts, values):
|
|||
except:
|
||||
raise PDT_InvalidVector
|
||||
if obj.mode == "EDIT":
|
||||
for v in verts:
|
||||
for v in [v for v in bm.verts if v.select]:
|
||||
v.co = vector_delta - obj_loc
|
||||
bmesh.ops.remove_doubles(
|
||||
bm, verts=[v for v in bm.verts if v.select], dist=0.0001
|
||||
|
@ -607,8 +622,8 @@ def move_entities(context, pg, operation, mode, obj, bm, verts, values):
|
|||
for ob in context.view_layer.objects.selected:
|
||||
ob.location = vector_delta
|
||||
|
||||
elif mode in {"d", "i"}:
|
||||
if mode == "d":
|
||||
elif mode in {"d", "i", "n"}:
|
||||
if mode in {"d", "n"}:
|
||||
# Delta/Relative Coordinates
|
||||
try:
|
||||
vector_delta = vector_build(context, pg, obj, operation, values, 3)
|
||||
|
@ -621,7 +636,7 @@ def move_entities(context, pg, operation, mode, obj, bm, verts, values):
|
|||
except:
|
||||
raise PDT_InvalidVector
|
||||
|
||||
if pg.plane == "LO" and mode == "d":
|
||||
if pg.plane == "LO" and mode in {"d", "n"}:
|
||||
vector_delta = view_coords(vector_delta.x, vector_delta.y, vector_delta.z)
|
||||
elif pg.plane == "LO" and mode == "i":
|
||||
vector_delta = view_dir(pg.distance, pg.angle)
|
||||
|
@ -678,7 +693,7 @@ def add_new_vertex(context, pg, operation, mode, obj, bm, verts, values):
|
|||
raise PDT_InvalidVector
|
||||
new_vertex = bm.verts.new(vector_delta - obj_loc)
|
||||
# Delta/Relative Coordinates
|
||||
elif mode == "d":
|
||||
elif mode in {"d", "n"}:
|
||||
try:
|
||||
vector_delta = vector_build(context, pg, obj, operation, values, 3)
|
||||
except:
|
||||
|
@ -852,7 +867,7 @@ def extrude_vertices(context, pg, operation, mode, obj, obj_loc, bm, verts, valu
|
|||
bm, verts=[v for v in bm.verts if v.select], dist=0.0001
|
||||
)
|
||||
# Delta/Relative Coordinates
|
||||
elif mode == "d":
|
||||
elif mode in {"d", "n"}:
|
||||
try:
|
||||
vector_delta = vector_build(context, pg, obj, operation, values, 3)
|
||||
except:
|
||||
|
@ -920,7 +935,7 @@ def extrude_geometry(context, pg, operation, mode, obj, bm, values):
|
|||
context.window_manager.popup_menu(oops, title="Error", icon="ERROR")
|
||||
return
|
||||
# Delta/Relative Coordinates
|
||||
if mode == "d":
|
||||
if mode in {"d", "n"}:
|
||||
try:
|
||||
vector_delta = vector_build(context, pg, obj, operation, values, 3)
|
||||
except:
|
||||
|
@ -947,7 +962,7 @@ def extrude_geometry(context, pg, operation, mode, obj, bm, values):
|
|||
faces_extr = [f for f in geom_extr if isinstance(f, bmesh.types.BMFace)]
|
||||
del ret
|
||||
|
||||
if pg.plane == "LO" and mode == "d":
|
||||
if pg.plane == "LO" and mode in {"d", "n"}:
|
||||
vector_delta = view_coords(vector_delta.x, vector_delta.y, vector_delta.z)
|
||||
elif pg.plane == "LO" and mode == "i":
|
||||
vector_delta = view_dir(pg.distance, pg.angle)
|
||||
|
@ -979,7 +994,7 @@ def duplicate_geometry(context, pg, operation, mode, obj, bm, values):
|
|||
context.window_manager.popup_menu(oops, title="Error", icon="ERROR")
|
||||
return
|
||||
# Delta/Relative Coordinates
|
||||
if mode == "d":
|
||||
if mode in {"d", "n"}:
|
||||
try:
|
||||
vector_delta = vector_build(context, pg, obj, operation, values, 3)
|
||||
except:
|
||||
|
@ -1006,7 +1021,7 @@ def duplicate_geometry(context, pg, operation, mode, obj, bm, values):
|
|||
faces_dupe = [f for f in geom_dupe if isinstance(f, bmesh.types.BMFace)]
|
||||
del ret
|
||||
|
||||
if pg.plane == "LO" and mode == "d":
|
||||
if pg.plane == "LO" and mode in {"d", "n"}:
|
||||
vector_delta = view_coords(vector_delta.x, vector_delta.y, vector_delta.z)
|
||||
elif pg.plane == "LO" and mode == "i":
|
||||
vector_delta = view_dir(pg.distance, pg.angle)
|
||||
|
|
|
@ -184,7 +184,7 @@ class PDT_OT_PlacementDelta(Operator):
|
|||
f",{str(round(pg.cartesian_coords.z, decimal_places))}"
|
||||
)
|
||||
elif operation == "EV":
|
||||
# Extrue Vertices
|
||||
# Extrude Vertices
|
||||
pg.command = (
|
||||
f"vd{str(round(pg.cartesian_coords.x, decimal_places))}"
|
||||
f",{str(round(pg.cartesian_coords.y, decimal_places))}"
|
||||
|
@ -198,7 +198,7 @@ class PDT_OT_PlacementDelta(Operator):
|
|||
f",{str(round(pg.cartesian_coords.z, decimal_places))}"
|
||||
)
|
||||
elif operation == "EG":
|
||||
# Extrue Geometry
|
||||
# Extrude Geometry
|
||||
pg.command = (
|
||||
f"ed{str(round(pg.cartesian_coords.x, decimal_places))}"
|
||||
f",{str(round(pg.cartesian_coords.y, decimal_places))}"
|
||||
|
@ -299,6 +299,82 @@ class PDT_OT_PlacementDis(Operator):
|
|||
return {"FINISHED"}
|
||||
|
||||
|
||||
class PDT_OT_PlacementView(Operator):
|
||||
"""Use Distance Input for View Normal Axis Operations"""
|
||||
|
||||
bl_idname = "pdt.view_axis"
|
||||
bl_label = "View Normal Axis Mode"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
|
||||
def execute(self, context):
|
||||
"""Manipulates Geometry, or Objects by View Normal Axis Offset (Increment).
|
||||
|
||||
Note:
|
||||
- Reads pg.operation from Operation Mode Selector as 'operation'
|
||||
- Reads pg.select, pg.plane, pg.cartesian_coords scene variables to:
|
||||
-- set position of CUrsor (CU)
|
||||
-- set position of Pivot Point (PP)
|
||||
-- MoVe geometry/objects (MV)
|
||||
-- Extrude Vertices (EV)
|
||||
-- Split Edges (SE)
|
||||
-- add a New Vertex (NV)
|
||||
-- Duplicate Geometry (DG)
|
||||
-- Extrude Geometry (EG)
|
||||
|
||||
Invalid Options result in self.report Error.
|
||||
|
||||
Args:
|
||||
context: Blender bpy.context instance.
|
||||
|
||||
Returns:
|
||||
Status Set.
|
||||
"""
|
||||
|
||||
pg = context.scene.pdt_pg
|
||||
operation = pg.operation
|
||||
decimal_places = context.preferences.addons[__package__].preferences.pdt_input_round
|
||||
|
||||
if operation == "CU":
|
||||
# Cursor
|
||||
pg.command = (
|
||||
f"cn{str(round(pg.distance, decimal_places))}"
|
||||
)
|
||||
elif operation == "PP":
|
||||
# Pivot Point
|
||||
pg.command = (
|
||||
f"pn{str(round(pg.distance, decimal_places))}"
|
||||
)
|
||||
elif operation == "MV":
|
||||
# Move Entities
|
||||
pg.command = (
|
||||
f"gn{str(round(pg.distance, decimal_places))}"
|
||||
)
|
||||
elif operation == "NV":
|
||||
# New Vertex
|
||||
pg.command = (
|
||||
f"nn{str(round(pg.distance, decimal_places))}"
|
||||
)
|
||||
elif operation == "EV":
|
||||
# Extrude Vertices
|
||||
pg.command = (
|
||||
f"vn{str(round(pg.distance, decimal_places))}"
|
||||
)
|
||||
elif operation == "DG":
|
||||
# Duplicate Entities
|
||||
pg.command = (
|
||||
f"dn{str(round(pg.distance, decimal_places))}"
|
||||
)
|
||||
elif operation == "EG":
|
||||
# Extrude Geometry
|
||||
pg.command = (
|
||||
f"en{str(round(pg.distance, decimal_places))}"
|
||||
)
|
||||
else:
|
||||
error_message = f"{operation} {PDT_ERR_NON_VALID} {PDT_LAB_DEL}"
|
||||
self.report({"ERROR"}, error_message)
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
class PDT_OT_PlacementPer(Operator):
|
||||
"""Use Percentage Placement"""
|
||||
|
||||
|
@ -703,3 +779,9 @@ class PDT_OT_Taper(Operator):
|
|||
pg = context.scene.pdt_pg
|
||||
pg.command = f"tap"
|
||||
return {"FINISHED"}
|
||||
|
||||
#class PDT_Extrude_Modal(Operator):
|
||||
# """Extrude Modal Plane Along Normal Axis"""
|
||||
# bl_idname = "pdt.extrude_modal"
|
||||
# bl_label = "Extrude Modal Normal"
|
||||
# bl_options = {"REGISTER", "UNDO"}
|
||||
|
|
|
@ -62,7 +62,8 @@ from .pdt_msg_strings import (
|
|||
PDT_LAB_TAPERAXES,
|
||||
PDT_LAB_TOOLS,
|
||||
PDT_LAB_USEVERTS,
|
||||
PDT_LAB_VARIABLES
|
||||
PDT_LAB_VARIABLES,
|
||||
PDT_LAB_VIEW
|
||||
)
|
||||
|
||||
def ui_width():
|
||||
|
@ -148,6 +149,8 @@ class PDT_PT_PanelDesign(Panel):
|
|||
row.prop(pdt_pg, "angle", text=PDT_LAB_ANGLEVALUE)
|
||||
row = box.row()
|
||||
row.operator("pdt.distance", icon="EMPTY_AXIS", text=f"{PDT_LAB_DIR} »")
|
||||
row.operator("pdt.view_axis", icon="EMPTY_AXIS", text=f"{PDT_LAB_VIEW} »")
|
||||
row = box.row()
|
||||
row.prop(pdt_pg, "flip_angle", text=PDT_LAB_FLIPANGLE)
|
||||
|
||||
# ---------------------
|
||||
|
@ -437,7 +440,7 @@ class PDT_PT_PanelTangent(Panel):
|
|||
row.label(text=f"Working {PDT_LAB_PLANE}:")
|
||||
row.prop(pdt_pg, "plane", text="")
|
||||
row = layout.row()
|
||||
row.label(text="Tan Mode")
|
||||
row.label(text="Tangent Mode")
|
||||
row.prop(pdt_pg, "tangent_mode", text="")
|
||||
row = layout.row()
|
||||
row.operator("pdt.tangentoperatesel", text="Tangents from Selection", icon="NONE")
|
||||
|
@ -448,7 +451,7 @@ class PDT_PT_PanelTangent(Panel):
|
|||
box = layout.box()
|
||||
row = box.row()
|
||||
split = row.split(factor=0.35, align=True)
|
||||
split.label(text="Tan Point")
|
||||
split.label(text="Tangent Point")
|
||||
split.prop(pdt_pg, "tangent_point2", text="")
|
||||
row = box.row()
|
||||
row.operator("pdt.tangentset3", text="from Cursor")
|
||||
|
|
|
@ -80,6 +80,7 @@ PDT_LAB_PIVOTWIDTH = "" # Intentionally left blank
|
|||
PDT_LAB_PIVOTALPHA = "" # Intentionally left blank
|
||||
PDT_LAB_PIVOTLOC = "" # Intentionally left blank
|
||||
PDT_LAB_PIVOTLOCH = "Location"
|
||||
PDT_LAB_VIEW = "View Normal Axis"
|
||||
#
|
||||
# Error Message
|
||||
#
|
||||
|
|
Loading…
Reference in New Issue