Palette linker
1.8.0 - feat: palette linker (beta), with pop-up from material stack dropdown - feat: palette name fuzzy match - code: add an open addon preference opsgpv2
parent
78b70c8fca
commit
6c19fa54af
|
@ -1,5 +1,12 @@
|
|||
# Changelog
|
||||
|
||||
|
||||
1.8.0
|
||||
|
||||
- feat: palette linker (beta), with pop-up from material stack dropdown
|
||||
- feat: palette name fuzzy match
|
||||
- code: add an open addon preference ops
|
||||
|
||||
1.7.8
|
||||
|
||||
- fix: reset rotation in draw cam mode keep view in the same place (counter camera rotation)
|
||||
|
|
|
@ -352,7 +352,7 @@ class GPTB_OT_links_checker(bpy.types.Operator):
|
|||
split.label(text=l[0], icon=l[1])
|
||||
# layout.label(text=l[0], icon=l[1])
|
||||
else:
|
||||
split=layout.split(factor=0.75, align=True)
|
||||
split=layout.split(factor=0.70, align=True)
|
||||
split.label(text=l[0], icon=l[1])
|
||||
## resolve() return somethin different than os.path.abspath.
|
||||
# split.operator('wm.path_open', text='Open folder', icon='FILE_FOLDER').filepath = Path(bpy.path.abspath(l[0])).resolve().parent.as_posix()
|
||||
|
|
|
@ -323,8 +323,6 @@ class GPTB_OT_reset_cam_rot(bpy.types.Operator):
|
|||
|
||||
def execute(self, context):
|
||||
cam = context.scene.camera
|
||||
|
||||
# self.cam_init_euler = self.cam.rotation_euler.copy()
|
||||
if not cam.parent or cam.parent.type != 'CAMERA':
|
||||
self.report({'ERROR'}, "No parents to refer to for rotation reset")
|
||||
return {"CANCELLED"}
|
||||
|
@ -362,40 +360,8 @@ class GPTB_OT_reset_cam_rot(bpy.types.Operator):
|
|||
new_cam_offset = mathutils.Vector((new_cam_offset[0], new_cam_offset[1] * self.ratio_inv)) # restore screen ratio
|
||||
|
||||
context.space_data.region_3d.view_camera_offset = new_cam_offset
|
||||
|
||||
|
||||
return {"FINISHED"}
|
||||
|
||||
""" # old simple cam draw fit to cam
|
||||
class GPTB_OT_reset_cam_rot(bpy.types.Operator):
|
||||
bl_idname = "gp.reset_cam_rot"
|
||||
bl_label = "Reset rotation"
|
||||
bl_description = "Reset rotation of the draw manipulation camera"
|
||||
bl_options = {"REGISTER"}
|
||||
|
||||
@classmethod
|
||||
def poll(cls, context):
|
||||
return context.scene.camera and not context.scene.camera.name.startswith('Cam')
|
||||
# return context.region_data.view_perspective == 'CAMERA'# check if in camera
|
||||
|
||||
def execute(self, context):
|
||||
# dcam_name = 'draw_cam'
|
||||
# camcol_name = 'manip_cams'
|
||||
drawcam = context.scene.camera
|
||||
if drawcam.parent.type == 'CAMERA':
|
||||
## align to parent camera
|
||||
drawcam.matrix_world = drawcam.parent.matrix_world#wrong, get the parent rotation offset
|
||||
# drawcam.rotation_euler = drawcam.parent.rotation_euler#wrong, get the parent rotation offset
|
||||
elif drawcam.parent:
|
||||
## there is a parent, so align the Y of the camera to object's Z
|
||||
# drawcam.rotation_euler.rotate(drawcam.parent.matrix_world)# wrong
|
||||
pass
|
||||
else:
|
||||
self.report({'ERROR'}, "No parents to refer to for rotation reset")
|
||||
return {"CANCELLED"}
|
||||
return {"FINISHED"}
|
||||
"""
|
||||
|
||||
class GPTB_OT_toggle_mute_animation(bpy.types.Operator):
|
||||
bl_idname = "gp.toggle_mute_animation"
|
||||
bl_label = "Toggle Animation Mute"
|
||||
|
@ -630,6 +596,16 @@ class GPTB_OT_check_canvas_alignement(bpy.types.Operator):
|
|||
# self.report({ret}, message)
|
||||
return {'FINISHED'}
|
||||
|
||||
class GPTB_OT_open_addon_prefs(bpy.types.Operator):
|
||||
bl_idname = "gptb.open_addon_prefs"
|
||||
bl_label = "Open Addon Prefs"
|
||||
bl_description = "Open user preferences window in addon tab and prefill the search with addon name"
|
||||
bl_options = {"REGISTER", "INTERNAL"}
|
||||
|
||||
def execute(self, context):
|
||||
utils.open_addon_prefs()
|
||||
return {'FINISHED'}
|
||||
|
||||
classes = (
|
||||
GPTB_OT_copy_text,
|
||||
GPTB_OT_flipx_view,
|
||||
|
@ -642,6 +618,7 @@ GPTB_OT_toggle_hide_gp_modifier,
|
|||
GPTB_OT_list_disabled_anims,
|
||||
GPTB_OT_clear_active_frame,
|
||||
GPTB_OT_check_canvas_alignement,
|
||||
GPTB_OT_open_addon_prefs,
|
||||
)
|
||||
|
||||
def register():
|
||||
|
|
|
@ -114,6 +114,9 @@ def register_keymaps():
|
|||
km = addon.keymaps.new(name = "Dopesheet", space_type = "DOPESHEET_EDITOR")
|
||||
kmi = km.keymap_items.new('gp.duplicate_send_to_layer', type='D', value="PRESS", ctrl=True, shift=True)
|
||||
addon_keymaps.append((km,kmi))
|
||||
|
||||
|
||||
# km = addon.keymaps.new(name = "Dopesheet", space_type = "DOPESHEET_EDITOR") # try duplicating km (seem to be error at unregsiter)
|
||||
kmi = km.keymap_items.new('gp.duplicate_send_to_layer', type='X', value="PRESS", ctrl=True, shift=True)
|
||||
kmi.properties.delete_source = True
|
||||
addon_keymaps.append((km,kmi))
|
||||
|
@ -130,13 +133,17 @@ GPTB_OT_duplicate_send_to_layer,
|
|||
)
|
||||
|
||||
def register():
|
||||
if not bpy.app.background:
|
||||
for cls in classes:
|
||||
bpy.utils.register_class(cls)
|
||||
register_keymaps()
|
||||
if bpy.app.background:
|
||||
return
|
||||
|
||||
for cls in classes:
|
||||
bpy.utils.register_class(cls)
|
||||
register_keymaps()
|
||||
|
||||
def unregister():
|
||||
if not bpy.app.background:
|
||||
unregister_keymaps()
|
||||
for cls in reversed(classes):
|
||||
bpy.utils.unregister_class(cls)
|
||||
if bpy.app.background:
|
||||
return
|
||||
|
||||
unregister_keymaps()
|
||||
for cls in reversed(classes):
|
||||
bpy.utils.unregister_class(cls)
|
|
@ -57,7 +57,7 @@ class GPTB_OT_load_default_palette(bpy.types.Operator):
|
|||
line.name = 'line'
|
||||
|
||||
# load json
|
||||
pfp = Path(bpy.path.abspath(get_addon_prefs().palettes_path))
|
||||
pfp = Path(bpy.path.abspath(get_addon_prefs().palette_path))
|
||||
if not pfp.exists():
|
||||
self.report({'ERROR'}, f'Palette path not found')
|
||||
return {"CANCELLED"}
|
||||
|
@ -164,9 +164,7 @@ class GPTB_OT_save_palette(bpy.types.Operator, ExportHelper):
|
|||
|
||||
def load_blend_palette(context, filepath):
|
||||
'''Load materials on current active object from current chosen blend'''
|
||||
#from pathlib import Path
|
||||
#palette_fp = C.preferences.addons['GP_toolbox'].preferences['palettes_path']
|
||||
#fp = Path(palette_fp) / 'christina.blend'
|
||||
|
||||
print(f'-- import palette from : {filepath} --')
|
||||
for ob in context.selected_objects:
|
||||
if ob.type != 'GPENCIL':
|
||||
|
|
|
@ -23,6 +23,182 @@ from bpy.props import (
|
|||
PointerProperty,
|
||||
)
|
||||
|
||||
#--- OPERATORS
|
||||
def print_materials_sources(ob):
|
||||
for m in ob.data.materials:
|
||||
if m.library:
|
||||
print(f'{m.name} - {Path(m.library.filepath).name}')
|
||||
else:
|
||||
print(m.name)
|
||||
|
||||
def replace_mat_slots(src_mat, obj):
|
||||
for ms in obj.material_slots:
|
||||
if ms.material.name == src_mat.name:
|
||||
# Only on different linked, else mat.name differ (.001))
|
||||
ms.material = src_mat
|
||||
|
||||
class GPTB_OT_import_obj_palette(Operator):
|
||||
bl_idname = "gp.import_obj_palette"
|
||||
bl_label = "Import Object Palette"
|
||||
bl_description = "Import object palette from blend"
|
||||
bl_options = {"REGISTER", "INTERNAL"}
|
||||
|
||||
def execute(self, context):
|
||||
## get targets
|
||||
selection = [o for o in context.selected_objects if o.type == 'GPENCIL']
|
||||
if not selection:
|
||||
self.report({'ERROR'}, 'Need to have at least one GP object selected in scene')
|
||||
return {"CANCELLED"}
|
||||
|
||||
# Avoid looping on linked duplicate
|
||||
objs = []
|
||||
datas = []
|
||||
for o in selection:
|
||||
if o.data in datas:
|
||||
continue
|
||||
objs.append(o)
|
||||
datas.append(o.data)
|
||||
del datas # datas.clear()
|
||||
|
||||
pl_prop = context.scene.bl_palettes_props
|
||||
blend_path = pl_prop.blends[pl_prop.bl_idx].blend_path
|
||||
target_objs = [pl_prop.objects[pl_prop.ob_idx].name]
|
||||
# Future improvement
|
||||
# target_objs = [o.name for o in pl_prop.objects if o.select]
|
||||
if not target_objs:
|
||||
self.report({'ERROR'}, 'Need at least one palette source selected')
|
||||
return {"CANCELLED"}
|
||||
|
||||
mode = pl_prop.import_type
|
||||
|
||||
if mode == 'LINK' and not bpy.data.is_saved: # autorise for absolute path
|
||||
self.report({'ERROR'}, 'Blend file must be saved to use link mode')
|
||||
return {"CANCELLED"}
|
||||
|
||||
if mode != 'LINK':
|
||||
self.report({'ERROR'}, 'Not supported yet, use link')
|
||||
return {'CANCELLED'}
|
||||
|
||||
# get relative path
|
||||
blend_path = bpy.path.relpath(blend_path)
|
||||
|
||||
# TODO append object to list all material that belongs to it...
|
||||
|
||||
linked_objs = utils.link_objects_in_blend(blend_path, target_objs, link=True)
|
||||
|
||||
if not linked_objs:
|
||||
self.report({'ERROR'}, f'Could not link/append obj from {blend_path}')
|
||||
return {"CANCELLED"}
|
||||
|
||||
for i in range(len(linked_objs))[::-1]: # reversed(range(len(l))) / range(len(l))[::-1]
|
||||
if linked_objs[i].type != 'GPENCIL':
|
||||
print(f'{linked_objs[i].name} type is "{linked_objs[i].type}"')
|
||||
bpy.data.objects.remove(linked_objs.pop(i))
|
||||
|
||||
if not linked_objs:
|
||||
self.report({'ERROR'}, f'Linked object was not a Grease Pencil')
|
||||
return {"CANCELLED"}
|
||||
|
||||
|
||||
print('blend_path: ', blend_path)
|
||||
# if materials have been renamed, there must be already be appended / linked
|
||||
|
||||
# to_clear = []
|
||||
ct = 0
|
||||
for src_ob in linked_objs:
|
||||
ct += len(src_ob.data.materials)
|
||||
|
||||
if mode == 'LINK': # link new mats and update already linked ones
|
||||
## link mats
|
||||
for ob in objs:
|
||||
for src_ob in linked_objs:
|
||||
for src_mat in src_ob.data.materials:
|
||||
print(f'- {src_mat.name}')
|
||||
mat = ob.data.materials.get(src_mat.name)
|
||||
|
||||
if mat and mat.library == src_mat.library:
|
||||
# print('already exists')
|
||||
continue # same material, skip
|
||||
|
||||
elif mat:
|
||||
# print('already but not same lib')
|
||||
## same material but not from same lib
|
||||
## remap_user will replace this mat in all objects blend...
|
||||
mat.user_remap(src_mat)
|
||||
## (we might want to keep links in other objects untouched ?)
|
||||
## else use a basic material slot swap (loop, can be added on multiple slots)
|
||||
# replace_mat_slots(ob, src_mat)
|
||||
|
||||
else:
|
||||
# print('Not in dest')
|
||||
## material not in dest, append
|
||||
ob.data.materials.append(src_mat)
|
||||
|
||||
elif mode == 'APPEND':
|
||||
## append, overwrite all already existing materials with new ones
|
||||
pass
|
||||
|
||||
# ct = 0
|
||||
# for o in selection:
|
||||
# for mat in ob.data.materials:
|
||||
# if mat in o.data.materials[:]:
|
||||
# continue
|
||||
# o.data.materials.append(mat)
|
||||
# ct += 1
|
||||
|
||||
elif mode == 'APPEND_REUSE':
|
||||
## append, Skip existing material
|
||||
pass
|
||||
|
||||
if ct:
|
||||
self.report({'INFO'}, f'{ct} Materials appended')
|
||||
# else:
|
||||
# self.report({'WARNING'}, 'All materials are already in other selected object')
|
||||
|
||||
# unlink objects and their gp data
|
||||
for src_ob in linked_objs:
|
||||
bpy.data.grease_pencils.remove(src_ob.data)
|
||||
|
||||
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
class GPTB_OT_palette_fuzzy_search_obj(Operator):
|
||||
bl_idname = "gptb.palette_fuzzy_search_obj"
|
||||
bl_label = "Palette Fuzzy Match"
|
||||
bl_description = "Try to find a palette with name closest to active object"
|
||||
bl_options = {"REGISTER"}
|
||||
|
||||
def execute(self, context):
|
||||
if not context.object:
|
||||
self.report({'ERROR'}, 'No active object to search name from')
|
||||
return {"CANCELLED"}
|
||||
|
||||
bl_props = context.scene.bl_palettes_props
|
||||
|
||||
final_ratio = 0
|
||||
new_idx = None
|
||||
for i, o in enumerate(bl_props.objects):
|
||||
ratio = utils.fuzzy_match_ratio(context.object.name, o.name, case_sensitive=False)
|
||||
if ratio > final_ratio:
|
||||
new_idx = i
|
||||
final_ratio = ratio
|
||||
|
||||
limit = 0.3
|
||||
if final_ratio < limit:
|
||||
self.report({'ERROR'}, f'Could not find a name matching at least {limit*100:.0f}% "{context.object.name}"')
|
||||
return {"CANCELLED"}
|
||||
|
||||
if new_idx is None:
|
||||
self.report({'ERROR'}, f'Could not find match')
|
||||
return {"CANCELLED"}
|
||||
|
||||
bl_props.ob_idx = new_idx
|
||||
self.report({'INFO'}, f'Select {bl_props.objects[bl_props.ob_idx].name} (match at {final_ratio*100:.1f}% with "{context.object.name}")')
|
||||
return {"FINISHED"}
|
||||
|
||||
#--- UI LIST
|
||||
|
||||
class GPTB_UL_blend_list(UIList):
|
||||
# order_by_distance : BoolProperty(default=True)
|
||||
|
||||
|
@ -68,6 +244,7 @@ class GPTB_UL_object_list(UIList):
|
|||
# order_by_distance : BoolProperty(default=True)
|
||||
|
||||
def draw_item(self, context, layout, data, item, icon, active_data, active_propname):
|
||||
self.use_filter_show = True # force open the search feature
|
||||
layout.label(text=item.name)
|
||||
|
||||
def draw_filter(self, context, layout):
|
||||
|
@ -75,6 +252,7 @@ class GPTB_UL_object_list(UIList):
|
|||
subrow = row.row(align=True)
|
||||
subrow.prop(self, "filter_name", text="") # Only show items matching this name (use ‘*’ as wildcard)
|
||||
# reverse order
|
||||
subrow.operator('gptb.palette_fuzzy_search_obj', text='', icon='ZOOM_SELECTED') # built-in reverse
|
||||
icon = 'SORT_DESC' if self.use_filter_sort_reverse else 'SORT_ASC'
|
||||
subrow.prop(self, "use_filter_sort_reverse", text="", icon=icon) # built-in reverse
|
||||
|
||||
|
@ -109,20 +287,37 @@ def reload_blends(self, context):
|
|||
item.blend_name = 'No Palette Path Specified'
|
||||
reload_objects(self, context)
|
||||
return
|
||||
|
||||
palettes_dir = Path(palette_fp)
|
||||
|
||||
|
||||
palettes_dir = Path(os.path.abspath(bpy.path.abspath(palette_fp)))
|
||||
if not palettes_dir.exists():
|
||||
item = uilist.add()
|
||||
item.blend_name = 'Palette Path not found'
|
||||
reload_objects(self, context)
|
||||
return
|
||||
|
||||
blends = [(o.path, Path(o).stem, "") for o in os.scandir(palettes_dir)
|
||||
if o.is_file()
|
||||
and o.name.endswith('.blend')
|
||||
and re.search(r'v\d{3}', o.name, re.I)]
|
||||
# list blends
|
||||
pattern = r'[vV](\d{2,3})' # rightest = r'[vV](\d+)(?!.*[vV]\d)'
|
||||
blends = [] # recursive
|
||||
for root, _dirs, files in os.walk(palettes_dir):
|
||||
for f in files:
|
||||
fp = Path(root) / f
|
||||
if not f.endswith('.blend'):
|
||||
continue
|
||||
if not re.search(pattern, f):
|
||||
continue
|
||||
if not fp.is_file():
|
||||
continue
|
||||
blends.append((str(fp), fp.stem, ""))
|
||||
|
||||
blends.sort(key=lambda x: x[1], reverse=False)
|
||||
## only in palette folder.
|
||||
# blends = [(o.path, Path(o).stem, "") for o in os.scandir(palettes_dir)
|
||||
# if o.is_file()
|
||||
# and o.name.endswith('.blend')
|
||||
# and re.search(pattern, o.name)]
|
||||
|
||||
# blends.sort(key=lambda x: x[1], reverse=False) # sort alphabetically
|
||||
blends.sort(key=lambda x: int(re.search(pattern, x[1]).group(1)), reverse=False) # sort by version
|
||||
# print('blends found', len(blends))
|
||||
|
||||
for bl in blends: # populate list
|
||||
|
@ -161,6 +356,8 @@ def reload_objects(self, context):
|
|||
obj_uil.clear()
|
||||
pal_prop['ob_idx'] = 0
|
||||
|
||||
file_libs = [l.filepath for l in bpy.data.libraries if l.filepath]
|
||||
|
||||
if not len(blend_uil) or (len(blend_uil) == 1 and not bool(blend_uil[0].blend_path)):
|
||||
item = obj_uil.add()
|
||||
item.name = 'No blend to list object'
|
||||
|
@ -173,31 +370,41 @@ def reload_objects(self, context):
|
|||
|
||||
path_to_blend = Path(blend_uil[pal_prop.bl_idx].blend_path)
|
||||
|
||||
ob_list = utils.check_objects_in_blend(str(path_to_blend)) # get list of string of all object except camera
|
||||
## get list of string of all object except camera
|
||||
ob_list = utils.check_objects_in_blend(str(path_to_blend), avoid_camera=True)
|
||||
|
||||
ob_list.sort(reverse=False) # filter object by name ?
|
||||
# remove camera
|
||||
# print('blends found', len(blends))
|
||||
ob_list.sort(reverse=False) # filter object by name
|
||||
|
||||
for ob_name in ob_list: # populate list
|
||||
item = obj_uil.add()
|
||||
item.name = ob_name
|
||||
# print('path_to_blend: ', path_to_blend)
|
||||
item.path = str(path_to_blend / 'Object' / ob_name)
|
||||
|
||||
pal_prop.ob_idx = len(obj_uil) - 1
|
||||
|
||||
## those temp libraries are not saved (auto-cleared)
|
||||
## But best to keep library list tidy while file is opened
|
||||
for lib in reversed(bpy.data.libraries):
|
||||
if lib.filepath and not lib.users_id:
|
||||
if lib.filepath not in file_libs:
|
||||
bpy.data.libraries.remove(lib)
|
||||
|
||||
# return len(ob_list) # must return None if used in update
|
||||
del ob_list
|
||||
|
||||
|
||||
## PROPS
|
||||
#--- PROPERTIES
|
||||
|
||||
class GPTB_PG_blend_prop(PropertyGroup):
|
||||
blend_name : StringProperty() # stem of the path
|
||||
blend_path : StringProperty() # ful path
|
||||
# select: BoolProperty(update=update_select) # use and update to set the plane selection
|
||||
blend_path : StringProperty() # full path
|
||||
|
||||
class GPTB_PG_object_prop(PropertyGroup):
|
||||
name : StringProperty() # stem of the path
|
||||
path : StringProperty() # Object / Material ?
|
||||
## select feature to get multiple at once
|
||||
# select : BoolProperty(default=False) # Object / Material ?
|
||||
|
||||
class GPTB_PG_palette_settings(PropertyGroup):
|
||||
bl_idx : IntProperty(update=reload_objects) # update_on_index_change to reload object
|
||||
|
@ -226,88 +433,9 @@ class GPTB_PG_palette_settings(PropertyGroup):
|
|||
('APPEND_REUSE', 'Append (Reuse)', 'Append materials to selected objects\nkeep those already there', 2),
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
|
||||
# fav_blend: StringProperty() ## mark a blend as prefered ? (need to be stored in prefereneces to restore in other blend...)
|
||||
|
||||
class GPTB_OT_import_obj_palette(Operator):
|
||||
bl_idname = "gp.import_obj_palette"
|
||||
bl_label = "Import Object Palette"
|
||||
bl_description = "Import object palette from blend"
|
||||
bl_options = {"REGISTER", "INTERNAL"}
|
||||
|
||||
def execute(self, context):
|
||||
pl_prop = context.scene.bl_palettes_props
|
||||
path = pl_prop.objects[pl_prop.ob_idx].path
|
||||
self.report({'INFO'}, f'Path to object: {path}')
|
||||
return {"FINISHED"}
|
||||
|
||||
## PANEL
|
||||
|
||||
class GPTB_PT_palettes_linker_ui(Panel):
|
||||
bl_idname = 'GPTB_PT_palettes_linker_ui'
|
||||
bl_space_type = "VIEW_3D"
|
||||
bl_region_type = "UI"
|
||||
bl_category = "Gpencil"#Dev
|
||||
bl_label = "Palettes Mat Linker"
|
||||
|
||||
def draw(self, context):
|
||||
layout = self.layout
|
||||
scn = bpy.context.scene
|
||||
pl_prop = scn.bl_palettes_props
|
||||
col= layout.column()
|
||||
prefs = utils.get_addon_prefs()
|
||||
## Here put the path thing (only to use a non-library)
|
||||
|
||||
# maybe in submenu...
|
||||
row = col.row()
|
||||
expand_icon = 'TRIA_DOWN' if pl_prop.show_path else 'TRIA_RIGHT'
|
||||
row.prop(pl_prop, 'show_path', text='', icon=expand_icon, emboss=False)
|
||||
row.prop(pl_prop, 'use_project_path', text='Use Project Palettes')
|
||||
row.operator("gp.palettes_reload_blends", icon="FILE_REFRESH", text="")
|
||||
|
||||
if pl_prop.use_project_path:
|
||||
## gp toolbox addon prefs path
|
||||
if not prefs.palette_path:
|
||||
col.label(text='Gp Toolbox Palette Directory Needed')
|
||||
col.label(text='(saved with preferences)')
|
||||
if pl_prop.show_path or not prefs.palette_path:
|
||||
col.prop(prefs, 'palette_path', text='Project Dir')
|
||||
else:
|
||||
## local path
|
||||
if not pl_prop.custom_dir:
|
||||
col.label(text='Need to specify directory')
|
||||
if pl_prop.show_path or not pl_prop.custom_dir:
|
||||
col.prop(pl_prop, 'custom_dir', text='Custom Dir')
|
||||
|
||||
|
||||
row = col.row()
|
||||
row.template_list("GPTB_UL_blend_list", "", pl_prop, "blends", pl_prop, "bl_idx",
|
||||
rows=2)
|
||||
# side panel
|
||||
# subcol = row.column(align=True)
|
||||
# subcol.operator("gp.palettes_reload_blends", icon="FILE_REFRESH", text="")
|
||||
|
||||
## Show object UI list only once blend Uilist is filled ?
|
||||
if not len(pl_prop.blends) or (len(pl_prop.blends) == 1 and not bool(pl_prop.blends[0].blend_path)):
|
||||
col.label(text='Select a blend to list available object')
|
||||
|
||||
row = col.row()
|
||||
row.template_list("GPTB_UL_object_list", "", pl_prop, "objects", pl_prop, "ob_idx",
|
||||
rows=4)
|
||||
|
||||
## Show link button in the border of the UI list ?
|
||||
# col.prop(pl_prop, 'import_type')
|
||||
split = col.split(align=True, factor=0.4 )
|
||||
split.prop(pl_prop, 'import_type', text='')
|
||||
|
||||
split.enabled = len(pl_prop.objects) and bool(pl_prop.objects[pl_prop.ob_idx].path)
|
||||
split.operator('gp.import_obj_palette', text='Palette')
|
||||
|
||||
# button to launch link with combined props (active only if the two items are valids)
|
||||
# str(Path(self.blends) / 'Object' / self.objects
|
||||
|
||||
|
||||
classes = (
|
||||
# blend list
|
||||
|
@ -316,6 +444,7 @@ GPTB_UL_blend_list,
|
|||
GPTB_OT_palettes_reload_blends,
|
||||
|
||||
# object in blend list
|
||||
GPTB_OT_palette_fuzzy_search_obj,
|
||||
GPTB_PG_object_prop,
|
||||
GPTB_UL_object_list,
|
||||
|
||||
|
@ -323,7 +452,7 @@ GPTB_UL_object_list,
|
|||
GPTB_PG_palette_settings,
|
||||
|
||||
GPTB_OT_import_obj_palette,
|
||||
GPTB_PT_palettes_linker_ui,
|
||||
# TEST_OT_import_obj_palette_test,
|
||||
)
|
||||
|
||||
def register():
|
||||
|
@ -335,4 +464,4 @@ def unregister():
|
|||
for cls in reversed(classes):
|
||||
bpy.utils.unregister_class(cls)
|
||||
|
||||
del bpy.types.Scene.bl_palettes_props
|
||||
del bpy.types.Scene.bl_palettes_props
|
||||
|
|
255
UI_tools.py
255
UI_tools.py
|
@ -2,13 +2,13 @@
|
|||
from .utils import get_addon_prefs
|
||||
import bpy
|
||||
from pathlib import Path
|
||||
|
||||
from bpy.types import Panel
|
||||
|
||||
## UI in properties
|
||||
|
||||
### dataprop_panel not used --> transferred to sidebar
|
||||
"""
|
||||
class GPTB_PT_dataprop_panel(bpy.types.Panel):
|
||||
class GPTB_PT_dataprop_panel(Panel):
|
||||
bl_space_type = 'PROPERTIES'
|
||||
bl_region_type = 'WINDOW'
|
||||
# bl_space_type = 'VIEW_3D'
|
||||
|
@ -35,7 +35,7 @@ class GPTB_PT_dataprop_panel(bpy.types.Panel):
|
|||
|
||||
## UI in Gpencil sidebar menu
|
||||
|
||||
class GPTB_PT_sidebar_panel(bpy.types.Panel):
|
||||
class GPTB_PT_sidebar_panel(Panel):
|
||||
bl_label = "GP Toolbox"
|
||||
bl_space_type = "VIEW_3D"
|
||||
bl_region_type = "UI"
|
||||
|
@ -165,7 +165,7 @@ class GPTB_PT_sidebar_panel(bpy.types.Panel):
|
|||
# row.operator("my_operator.multi_op", text='', icon='TRIA_LEFT').left = 1
|
||||
# row.operator("my_operator.multi_op", text='', icon='TRIA_RIGHT').left = 0
|
||||
|
||||
class GPTB_PT_anim_manager(bpy.types.Panel):
|
||||
class GPTB_PT_anim_manager(Panel):
|
||||
bl_label = "Animation Manager"
|
||||
bl_space_type = "VIEW_3D"
|
||||
bl_region_type = "UI"
|
||||
|
@ -221,7 +221,7 @@ class GPTB_PT_anim_manager(bpy.types.Panel):
|
|||
text, icon = ('Cursor Follow On', 'PIVOT_CURSOR') if context.scene.gptoolprops.cursor_follow else ('Cursor Follow Off', 'CURSOR')
|
||||
col.prop(context.scene.gptoolprops, 'cursor_follow', text=text, icon=icon)
|
||||
|
||||
class GPTB_PT_toolbox_playblast(bpy.types.Panel):
|
||||
class GPTB_PT_toolbox_playblast(Panel):
|
||||
bl_label = "Playblast"
|
||||
bl_space_type = "VIEW_3D"
|
||||
bl_region_type = "UI"
|
||||
|
@ -243,7 +243,7 @@ class GPTB_PT_toolbox_playblast(bpy.types.Panel):
|
|||
# row.operator('render.playblast_anim', text = 'Playblast', icon = 'RENDER_ANIMATION').use_view = False # old (but robust) blocking playblast
|
||||
row.operator('render.playblast_anim', text = 'Viewport').use_view = True
|
||||
|
||||
class GPTB_PT_tint_layers(bpy.types.Panel):
|
||||
class GPTB_PT_tint_layers(Panel):
|
||||
bl_label = "Tint Layers"
|
||||
bl_space_type = "VIEW_3D"
|
||||
bl_region_type = "UI"
|
||||
|
@ -268,7 +268,7 @@ class GPTB_PT_tint_layers(bpy.types.Panel):
|
|||
col.operator("gp.auto_tint_gp_layers", text = "Reset tint", icon = "COLOR").reset = True
|
||||
|
||||
|
||||
class GPTB_PT_checker(bpy.types.Panel):
|
||||
class GPTB_PT_checker(Panel):
|
||||
bl_label = "Checker"
|
||||
bl_space_type = "VIEW_3D"
|
||||
bl_region_type = "UI"
|
||||
|
@ -298,7 +298,7 @@ class GPTB_PT_checker(bpy.types.Panel):
|
|||
row.operator('gp.links_checker', text = 'Check links', icon = 'UNLINKED')
|
||||
|
||||
|
||||
class GPTB_PT_color(bpy.types.Panel):
|
||||
class GPTB_PT_color(Panel):
|
||||
bl_label = "Color"
|
||||
bl_space_type = "VIEW_3D"
|
||||
bl_region_type = "UI"
|
||||
|
@ -310,11 +310,14 @@ class GPTB_PT_color(bpy.types.Panel):
|
|||
layout = self.layout
|
||||
col = layout.column()
|
||||
## Create empty frame on layer
|
||||
col.operator('gp.palette_linker', text=f'Link Materials Palette TO Object', icon='COLOR') ## ops
|
||||
|
||||
# Material panel as a pop-up (would work if palette dir is separated)
|
||||
# col.operator("wm.call_panel", text="Link Materials Palette", icon='COLOR').name = "GPTB_PT_palettes_linker_ui"
|
||||
col.operator('gp.create_empty_frames', icon='DECORATE_KEYFRAME')
|
||||
# col.operator("wm.call_panel", text="Link Material Palette", icon='COLOR').name = "GPTB_PT_palettes_list_popup"
|
||||
|
||||
""" # unused : added in Animation Manager
|
||||
class GPTB_PT_extra(bpy.types.Panel):
|
||||
class GPTB_PT_extra(Panel):
|
||||
bl_label = "Extra"
|
||||
bl_space_type = "VIEW_3D"
|
||||
bl_region_type = "UI"
|
||||
|
@ -331,7 +334,7 @@ class GPTB_PT_extra(bpy.types.Panel):
|
|||
|
||||
"""
|
||||
## unused -- (integrated in sidebar_panel)
|
||||
class GPTB_PT_cam_ref_panel(bpy.types.Panel):
|
||||
class GPTB_PT_cam_ref_panel(Panel):
|
||||
bl_label = "Background imgs"
|
||||
bl_space_type = "VIEW_3D"
|
||||
bl_region_type = "UI"
|
||||
|
@ -366,10 +369,13 @@ def palette_manager_menu(self, context):
|
|||
prefs = get_addon_prefs()
|
||||
|
||||
layout.operator("gp.copy_active_to_selected_palette", text='Append Materials To Selected', icon='MATERIAL')
|
||||
layout.operator("gp.clean_material_stack", text='Clean material Stack', icon='NODE_MATERIAL')
|
||||
layout.separator()
|
||||
layout.operator("wm.call_panel", text="Pop Palette Linker", icon='COLOR').name = "GPTB_PT_palettes_list_popup"
|
||||
layout.operator("gp.load_blend_palette", text='Load Mats From Single Blend', icon='RESTRICT_COLOR_ON').filepath = prefs.palette_path
|
||||
layout.separator()
|
||||
layout.operator("gp.load_palette", text='Load json Palette', icon='IMPORT').filepath = prefs.palette_path
|
||||
layout.operator("gp.save_palette", text='Save json Palette', icon='EXPORT').filepath = prefs.palette_path
|
||||
layout.operator("gp.load_blend_palette", text='Load color Palette', icon='COLOR').filepath = prefs.palette_path
|
||||
layout.operator("gp.clean_material_stack", text='Clean material Stack', icon='NODE_MATERIAL')
|
||||
|
||||
|
||||
def expose_use_channel_color_pref(self, context):
|
||||
|
@ -380,6 +386,150 @@ def expose_use_channel_color_pref(self, context):
|
|||
layout.label(text='Use Channel Colors (User preferences):')
|
||||
layout.prop(context.preferences.edit, 'use_anim_channel_group_colors')
|
||||
|
||||
|
||||
#--- Palette Linker Panels
|
||||
|
||||
def palettes_path_ui(self, context):
|
||||
layout = self.layout
|
||||
scn = bpy.context.scene
|
||||
pl_prop = scn.bl_palettes_props
|
||||
col= layout.column()
|
||||
prefs = get_addon_prefs()
|
||||
## Here put the path thing (only to use a non-library)
|
||||
|
||||
# maybe in submenu...
|
||||
row = col.row()
|
||||
# expand_icon = 'TRIA_DOWN' if pl_prop.show_path else 'TRIA_RIGHT'
|
||||
# row.prop(pl_prop, 'show_path', text='', icon=expand_icon, emboss=False)
|
||||
row.prop(pl_prop, 'use_project_path', text='Use Project Palettes')
|
||||
# row.operator("gp.palettes_reload_blends", icon="FILE_REFRESH", text="")
|
||||
|
||||
if pl_prop.use_project_path:
|
||||
## gp toolbox addon prefs path
|
||||
if not prefs.palette_path:
|
||||
col.label(text='GP toolbox Palette Directory Needed', icon='INFO')
|
||||
col.operator('gptb.open_addon_prefs', icon='PREFERENCES')
|
||||
|
||||
# if not prefs.palette_path: # or pl_prop.show_path
|
||||
# col.prop(prefs, 'palette_path', text='Project Dir')
|
||||
#col.label(text='(saved with preferences)')
|
||||
else:
|
||||
## local path
|
||||
if not pl_prop.custom_dir:
|
||||
col.label(text='Need to specify directory')
|
||||
col.prop(pl_prop, 'custom_dir', text='Custom Dir')
|
||||
# if not pl_prop.custom_dir or pl_prop.show_path:
|
||||
# col.prop(pl_prop, 'custom_dir', text='Custom Dir')
|
||||
|
||||
|
||||
def palettes_lists_ui(self, context, popup=False):
|
||||
layout = self.layout
|
||||
scn = bpy.context.scene
|
||||
pl_prop = scn.bl_palettes_props
|
||||
col= layout.column()
|
||||
row=col.row()
|
||||
# refresh button
|
||||
txt = 'Project Palettes' if pl_prop.use_project_path else 'Custom Palettes'
|
||||
row.label(text=txt)
|
||||
row.operator("gp.palettes_reload_blends", icon="FILE_REFRESH", text="")
|
||||
|
||||
col= layout.column()
|
||||
row = col.row()
|
||||
|
||||
if popup:
|
||||
blends_minimum_row = 5
|
||||
objects_minimum_row = 25
|
||||
else:
|
||||
blends_minimum_row = 2
|
||||
objects_minimum_row = 4
|
||||
row.template_list("GPTB_UL_blend_list", "", pl_prop, "blends", pl_prop, "bl_idx",
|
||||
rows=blends_minimum_row)
|
||||
# side panel
|
||||
# subcol = row.column(align=True)
|
||||
# subcol.operator("gp.palettes_reload_blends", icon="FILE_REFRESH", text="")
|
||||
|
||||
## Show object UI list only once blend Uilist is filled ?
|
||||
if not len(pl_prop.blends) or (len(pl_prop.blends) == 1 and not bool(pl_prop.blends[0].blend_path)):
|
||||
col.label(text='Select blend refresh available objects')
|
||||
|
||||
row = col.row()
|
||||
row.template_list("GPTB_UL_object_list", "", pl_prop, "objects", pl_prop, "ob_idx",
|
||||
rows=objects_minimum_row)
|
||||
|
||||
## Show link button in the border of the UI list ?
|
||||
# col.prop(pl_prop, 'import_type')
|
||||
split = col.split(align=True, factor=0.4)
|
||||
split.prop(pl_prop, 'import_type', text='')
|
||||
|
||||
split.enabled = len(pl_prop.objects) and bool(pl_prop.objects[pl_prop.ob_idx].path)
|
||||
split.operator('gp.import_obj_palette', text='Palette')
|
||||
|
||||
# button to launch link with combined props (active only if the two items are valids)
|
||||
# str(Path(self.blends) / 'Object' / self.objects
|
||||
|
||||
|
||||
class GPTB_PT_palettes_linker_main_ui(Panel):
|
||||
bl_space_type = 'TOPBAR' # dummy
|
||||
bl_region_type = 'HEADER'
|
||||
# bl_space_type = "VIEW_3D"
|
||||
# bl_region_type = "UI"
|
||||
# bl_category = "Gpencil"
|
||||
bl_label = "Palettes Mat Linker"
|
||||
|
||||
def draw(self, context):
|
||||
layout = self.layout
|
||||
## link button for tests
|
||||
# layout.operator('gp.import_obj_palette', text='Palette')
|
||||
|
||||
# Subpanel are appended to this main UI
|
||||
|
||||
## Or just as One fat panel
|
||||
# palettes_path_ui(self, context)
|
||||
# palettes_lists_ui(self, context)
|
||||
|
||||
class GPTB_PT_palettes_path_ui(Panel):
|
||||
bl_space_type = "VIEW_3D"
|
||||
bl_region_type = "UI"
|
||||
bl_category = "Gpencil"
|
||||
bl_label = "Palettes Source" # Source Path
|
||||
# bl_parent_id = "GPTB_PT_palettes_linker_main_ui"
|
||||
bl_parent_id = "GPTB_PT_color"
|
||||
bl_options = {'DEFAULT_CLOSED'}
|
||||
|
||||
def draw(self, context):
|
||||
layout = self.layout
|
||||
palettes_path_ui(self, context)
|
||||
# layout.label()
|
||||
|
||||
# pop-up version of object lists
|
||||
class GPTB_PT_palettes_list_popup(Panel):
|
||||
bl_space_type = 'TOPBAR' # dummy
|
||||
bl_region_type = 'HEADER'
|
||||
bl_category = "Gpencil"
|
||||
bl_label = "Palettes Lists"
|
||||
bl_ui_units_x = 18
|
||||
|
||||
def draw(self, context):
|
||||
palettes_lists_ui(self, context, popup=True)
|
||||
|
||||
class GPTB_PT_palettes_list_ui(Panel):
|
||||
bl_space_type = "VIEW_3D"
|
||||
bl_region_type = "UI"
|
||||
bl_category = "Gpencil"
|
||||
bl_label = "Palettes Lists"
|
||||
bl_parent_id = "GPTB_PT_color"
|
||||
bl_options = {'DEFAULT_CLOSED'}
|
||||
|
||||
def draw_header(self, context):
|
||||
layout = self.layout
|
||||
# layout.label(text="My Select Panel")
|
||||
layout.operator("wm.call_panel", text="", icon='COLOR').name = "GPTB_PT_palettes_list_popup"
|
||||
|
||||
def draw(self, context):
|
||||
palettes_lists_ui(self, context, popup=False)
|
||||
|
||||
|
||||
## bl 3+ UI
|
||||
def asset_browser_ui(self, context):
|
||||
'''Only shows in blender >= 3.0.0'''
|
||||
|
||||
|
@ -429,6 +579,12 @@ GPTB_PT_anim_manager,
|
|||
GPTB_PT_color,
|
||||
GPTB_PT_tint_layers,
|
||||
GPTB_PT_toolbox_playblast,
|
||||
|
||||
# palettes linker
|
||||
GPTB_PT_palettes_linker_main_ui, # main panel
|
||||
GPTB_PT_palettes_list_popup, # popup (dummy region)
|
||||
GPTB_PT_palettes_path_ui, # subpanels
|
||||
GPTB_PT_palettes_list_ui, # subpanels
|
||||
# GPTB_PT_extra,
|
||||
)
|
||||
|
||||
|
@ -467,34 +623,61 @@ def GPdata_toolbox_panel(self, context):
|
|||
col.prop(settings, 'autotint_offset')
|
||||
col.operator("gp.auto_tint_gp_layers", icon = "COLOR").reset = False
|
||||
col.operator("gp.auto_tint_gp_layers", text = "Reset tint", icon = "COLOR").reset = True
|
||||
"""
|
||||
"""
|
||||
|
||||
"""
|
||||
Put back UI for interpolate
|
||||
|
||||
|
||||
# Grease Pencil stroke interpolation tools native pop hover panel from 2.92
|
||||
class VIEW3D_PT_tools_grease_pencil_interpolate(Panel):
|
||||
bl_space_type = 'VIEW_3D'
|
||||
bl_region_type = 'HEADER'
|
||||
bl_label = "Interpolate"
|
||||
|
||||
### old
|
||||
@classmethod
|
||||
def poll(cls, context):
|
||||
if context.gpencil_data is None:
|
||||
return False
|
||||
|
||||
"""
|
||||
col = layout.column(align = True)
|
||||
col.operator("gpencil.stroke_change_color", text="Move to Color",icon = "COLOR")
|
||||
col.operator("transform.shear", text="Shear")
|
||||
col.operator("gpencil.stroke_cyclical_set", text="Toggle Cyclic").type = 'TOGGLE'
|
||||
col.operator("gpencil.stroke_subdivide", text="Subdivide",icon = "OUTLINER_DATA_MESH")
|
||||
gpd = context.gpencil_data
|
||||
valid_mode = bool(gpd.use_stroke_edit_mode or gpd.is_stroke_paint_mode)
|
||||
return bool(context.editable_gpencil_strokes) and valid_mode
|
||||
|
||||
row = layout.row(align = True)
|
||||
row.operator("gpencil.stroke_join", text="Join").type = 'JOIN'
|
||||
row.operator("grease_pencil.stroke_separate", text = "Separate")
|
||||
col.operator("gpencil.stroke_flip", text="Flip Direction",icon = "ARROW_LEFTRIGHT")
|
||||
def draw(self, context):
|
||||
layout = self.layout
|
||||
settings = context.tool_settings.gpencil_interpolate
|
||||
|
||||
col = layout.column(align = True)
|
||||
col.operator("gptools.randomise",icon = 'RNDCURVE')
|
||||
col.operator("gptools.thickness",icon = 'LINE_DATA')
|
||||
col.operator("gptools.angle_split",icon = 'MOD_BEVEL',text='Angle Splitting')
|
||||
col.operator("gptools.stroke_uniform_density",icon = 'MESH_DATA',text = 'Density')
|
||||
col = layout.column(align=True)
|
||||
col.label(text="Interpolate Strokes")
|
||||
col.operator("gpencil.interpolate", text="Interpolate")
|
||||
col.operator("gpencil.interpolate_sequence", text="Sequence")
|
||||
col.operator("gpencil.interpolate_reverse", text="Remove Breakdowns")
|
||||
|
||||
row = layout.row(align = True)
|
||||
row.prop(settings,"extra_tools",text='',icon = "DOWNARROW_HLT" if settings.extra_tools else "RIGHTARROW",emboss = False)
|
||||
row.label("Extra tools")
|
||||
col = layout.column(align=True)
|
||||
col.label(text="Options:")
|
||||
col.prop(settings, "interpolate_all_layers")
|
||||
|
||||
if settings.extra_tools :
|
||||
layout.operator_menu_enum("gpencil.stroke_arrange", text="Arrange Strokes...", property="direction")
|
||||
"""
|
||||
gpd = context.gpencil_data
|
||||
if gpd.use_stroke_edit_mode:
|
||||
col.prop(settings, "interpolate_selected_only")
|
||||
|
||||
col = layout.column(align=True)
|
||||
col.label(text="Sequence Options:")
|
||||
col.prop(settings, "step")
|
||||
col.prop(settings, "type")
|
||||
if settings.type == 'CUSTOM':
|
||||
# TODO: Options for loading/saving curve presets?
|
||||
col.template_curve_mapping(settings, "interpolation_curve", brush=True,
|
||||
use_negative_slope=True)
|
||||
elif settings.type != 'LINEAR':
|
||||
col.prop(settings, "easing")
|
||||
|
||||
if settings.type == 'BACK':
|
||||
layout.prop(settings, "back")
|
||||
elif settings.type == 'ELASTIC':
|
||||
sub = layout.column(align=True)
|
||||
sub.prop(settings, "amplitude")
|
||||
sub.prop(settings, "period")
|
||||
|
||||
"""
|
||||
|
|
|
@ -13,9 +13,9 @@
|
|||
|
||||
bl_info = {
|
||||
"name": "GP toolbox",
|
||||
"description": "Set of tools for Grease Pencil in animation production",
|
||||
"description": "Tool set for Grease Pencil in animation production",
|
||||
"author": "Samuel Bernou, Christophe Seux",
|
||||
"version": (1, 7, 8),
|
||||
"version": (1, 8, 0),
|
||||
"blender": (2, 91, 0),
|
||||
"location": "Sidebar (N menu) > Gpencil > Toolbox / Gpencil properties",
|
||||
"warning": "",
|
||||
|
@ -633,7 +633,7 @@ def register():
|
|||
OP_playblast_bg.register()
|
||||
OP_playblast.register()
|
||||
OP_palettes.register()
|
||||
# OP_palettes_linker.register()
|
||||
OP_palettes_linker.register()
|
||||
OP_brushes.register()
|
||||
OP_cursor_snap_canvas.register()
|
||||
OP_copy_paste.register()
|
||||
|
@ -680,7 +680,7 @@ def unregister():
|
|||
OP_copy_paste.unregister()
|
||||
OP_cursor_snap_canvas.unregister()
|
||||
OP_brushes.unregister()
|
||||
# OP_palettes_linker.unregister()
|
||||
OP_palettes_linker.unregister()
|
||||
OP_palettes.unregister()
|
||||
OP_file_checker.unregister()
|
||||
OP_helpers.unregister()
|
||||
|
|
74
utils.py
74
utils.py
|
@ -252,7 +252,9 @@ def remapping(value, leftMin, leftMax, rightMin, rightMax):
|
|||
# Convert the 0-1 range into a value in the right range.
|
||||
return rightMin + (valueScaled * rightSpan)
|
||||
|
||||
#### GP funcs
|
||||
# -----------------
|
||||
### GP funcs
|
||||
# -----------------
|
||||
|
||||
def get_gp_draw_plane(context, obj=None):
|
||||
''' return tuple with plane coordinate and normal
|
||||
|
@ -546,15 +548,20 @@ def extrapolate_points_by_length(a,b, length):
|
|||
### Vector utils 2d
|
||||
# -----------------
|
||||
|
||||
|
||||
def is_vector_close(a, b, rel_tol=1e-03):
|
||||
'''compare Vector or sequence of value
|
||||
by default tolerance is set on 1e-03 (0.001)
|
||||
'''
|
||||
return all([math.isclose(i, j, rel_tol=rel_tol) for i, j in zip(a,b)])
|
||||
|
||||
def single_vector_length_2d(v):
|
||||
return sqrt((v[0] * v[0]) + (v[1] * v[1]))
|
||||
|
||||
|
||||
def vector_length_2d(A,B):
|
||||
''''take two Vector and return length'''
|
||||
return sqrt((A[0] - B[0])**2 + (A[1] - B[1])**2)
|
||||
|
||||
|
||||
def vector_length_coeff_2d(size, A, B):
|
||||
'''
|
||||
Calculate the vector lenght
|
||||
|
@ -569,7 +576,7 @@ def vector_length_coeff_2d(size, A, B):
|
|||
|
||||
def cross_vector_coord_2d(foo, bar, size):
|
||||
'''Return the coord in space of a cross vector between the two point with specified size'''
|
||||
###middle location between 2 vector is calculated by adding the two vector and divide by two
|
||||
##middle location between 2 vector is calculated by adding the two vector and divide by two
|
||||
##mid = (foo + bar) / 2
|
||||
between = foo - bar
|
||||
#create a generic Up vector (on Y or Z)
|
||||
|
@ -646,6 +653,17 @@ def get_addon_prefs():
|
|||
addon_prefs = preferences.addons[addon_name].preferences
|
||||
return (addon_prefs)
|
||||
|
||||
def open_addon_prefs():
|
||||
'''Open addon prefs windows with focus on current addon'''
|
||||
from .__init__ import bl_info
|
||||
wm = bpy.context.window_manager
|
||||
wm.addon_filter = 'All'
|
||||
if not 'COMMUNITY' in wm.addon_support: # reactivate community
|
||||
wm.addon_support = set([i for i in wm.addon_support] + ['COMMUNITY'])
|
||||
wm.addon_search = bl_info['name']
|
||||
bpy.context.preferences.active_section = 'ADDONS'
|
||||
bpy.ops.preferences.addon_expand(module=__package__)
|
||||
bpy.ops.screen.userpref_show('INVOKE_DEFAULT')
|
||||
|
||||
def open_file(file_path) :
|
||||
'''Open filepath with default browser'''
|
||||
|
@ -703,10 +721,24 @@ def detect_OS():
|
|||
print("Cannot detect OS, python 'sys.platform' give :", myOS)
|
||||
return None
|
||||
|
||||
def is_vector_close(a, b, rel_tol=1e-03):
|
||||
'''compare Vector or sequence of value
|
||||
by default tolerance is set on 1e-03 (0.001)'''
|
||||
return all([math.isclose(i, j, rel_tol=rel_tol) for i, j in zip(a,b)])
|
||||
def fuzzy_match(s1, s2, tol=0.8, case_sensitive=False):
|
||||
'''Tell if two strings are similar using a similarity ratio (0 to 1) value passed as third arg'''
|
||||
from difflib import SequenceMatcher
|
||||
# can also use difflib.get_close_matches(word, possibilities, n=3, cutoff=0.6)
|
||||
if case_sensitive:
|
||||
similarity = SequenceMatcher(None, s1, s2)
|
||||
else:
|
||||
similarity = SequenceMatcher(None, s1.lower(), s2.lower())
|
||||
return similarity.ratio() > tol
|
||||
|
||||
def fuzzy_match_ratio(s1, s2, case_sensitive=False):
|
||||
'''Tell how much two passed strings are similar 1.0 being exactly similar'''
|
||||
from difflib import SequenceMatcher
|
||||
if case_sensitive:
|
||||
similarity = SequenceMatcher(None, s1, s2)
|
||||
else:
|
||||
similarity = SequenceMatcher(None, s1.lower(), s2.lower())
|
||||
return similarity.ratio()
|
||||
|
||||
def convert_attr(Attr):
|
||||
'''Convert given value to a Json serializable format'''
|
||||
|
@ -749,8 +781,9 @@ def show_message_box(_message = "", _title = "Message Box", _icon = 'INFO'):
|
|||
_message = [_message]
|
||||
bpy.context.window_manager.popup_menu(draw, title = _title, icon = _icon)
|
||||
|
||||
|
||||
# -----------------
|
||||
### UI utils
|
||||
# -----------------
|
||||
|
||||
## kmi draw for addon without delete button
|
||||
def draw_kmi(km, kmi, layout):
|
||||
|
@ -857,17 +890,36 @@ def draw_kmi(km, kmi, layout):
|
|||
# draw_km(display_keymaps, kc, kmm, None, layout + 1)
|
||||
# layout.context_pointer_set("keymap", km)
|
||||
|
||||
# -----------------
|
||||
### linking utility
|
||||
# -----------------
|
||||
|
||||
"""
|
||||
def link_objects_in_blend(filepath, obj_name, link=True):
|
||||
'''Link an object by name from a file, if link is False, append instead of linking'''
|
||||
with bpy.data.libraries.load(filepath, link=link) as (data_from, data_to):
|
||||
data_to.objects = [o for o in data_from.objects if o == obj_name] # c.startswith(obj_name)
|
||||
return data_to.objects
|
||||
"""
|
||||
def link_objects_in_blend(filepath, obj_name_list, link=True):
|
||||
'''Link an object by name from a file, if link is False, append instead of linking'''
|
||||
if isinstance(obj_name_list, str):
|
||||
obj_name_list = [obj_name_list]
|
||||
with bpy.data.libraries.load(filepath, link=link) as (data_from, data_to):
|
||||
data_to.objects = [o for o in data_from.objects if o in obj_name_list]
|
||||
return data_to.objects
|
||||
|
||||
def check_materials_in_blend(filepath):
|
||||
'''Return a list of all material in remote blend file'''
|
||||
with bpy.data.libraries.load(filepath, link=False) as (data_from, data_to):
|
||||
l = [m for m in data_from.materials]
|
||||
return l
|
||||
|
||||
def check_objects_in_blend(filepath, avoid_camera=True):
|
||||
'''return a list of object name in file'''
|
||||
'''Return a list of object name in remote blend file'''
|
||||
with bpy.data.libraries.load(filepath, link=False) as (data_from, data_to):
|
||||
if avoid_camera:
|
||||
l = [o for o in data_from.objects if not 'camera' in o.lower()]
|
||||
l = [o for o in data_from.objects if not any(x in o.lower() for x in ('camera', 'draw_cam', 'obj_cam'))]
|
||||
else:
|
||||
l = [o for o in data_from.objects]
|
||||
return l
|
Loading…
Reference in New Issue