custom_shelf initial commit

This commit is contained in:
Cos_Min
2022-03-04 12:28:53 +01:00
parent 705b6f2017
commit 232e5cc7f6
45 changed files with 4221 additions and 0 deletions
@@ -0,0 +1,144 @@
import bpy
from mathutils import Vector, Matrix
from math import radians, degrees
from random import uniform
import bmesh
C = bpy.context
# Assumes we have a mesh object selected in OBJECT mode
def transfer_value(Value, OldMin, OldMax, NewMin, NewMax):
'''map a value from a range to another (transfer/translate value)'''
return (((Value - OldMin) * (NewMax - NewMin)) / (OldMax - OldMin)) + NewMin
def chunks(lst, n):
"""Yield successive n-sized chunks from lst."""
for i in range(0, len(lst), n):
yield lst[i:i + n]
ob = C.object
M = ob.matrix_world
# Get the active mesh
me = ob.data
## get active group
#C.vertex_groups.active_index
if ob.type != 'MESH':
print('ERROR : not a mesh')
mode = bpy.context.mode
if mode == 'EDIT_MESH':
#me = bpy.context.edit_object.data
bm = bmesh.from_edit_mesh(me) #get Bmesh from edit
elif mode == 'OBJECT':
bm = bmesh.new() # create an empty BMesh
bm.from_mesh(me) # fill it in from a Mesh
bm.verts.ensure_lookup_table()
bm.edges.ensure_lookup_table()
bm.faces.ensure_lookup_table()
#deselect everything first
for f in bm.edges:f.select = False
for e in bm.edges:e.select = False
for v in bm.verts:v.select = False
# Modify the BMesh, can do anything here...
chunks = []
sc = 1
ec = 0
for v in bm.verts:
v.select_set(False)
#v.co.x += 1.0
if len(v.link_edges) == 1:#vertices chain tip
#sc+=1
#if sc == 2:
# sc = 0
chunks.append(v.index)
if len(chunks)%2 != 0:
print("list not pair")
for i in chunks:
bm.verts[i].select_set(True)
n = 2
pairs = [chunks[i:i + n] for i in range(0, len(chunks), n)]
#print("pairs", pairs)#Dbg
vlists = []
cursorloc = bpy.context.scene.cursor.location
for p in pairs:
vlen = p[1] - p[0]#number of vertices in chunk
#get_closest to 3d cursor
if (cursorloc - M @ bm.verts[p[0]].co).length < (cursorloc - M @ bm.verts[p[1]].co).length:
print(f'{p[0]} to {p[1]}')
bm.verts[p[1]].select = False
vlists.append( [i for i in range(p[0], p[1]+1)] )
else:# Last point is closer to cursor (so invert direction)
print(f'{p[1]} to {p[0]}')
bm.verts[p[0]].select = False
vlists.append( [i for i in reversed(range(p[0], p[1]+1))] )
if mode == 'EDIT_MESH':
bmesh.update_edit_mesh(me, True)
elif mode == 'OBJECT':
bm.to_mesh(me)
bm.free()
# tweak vertex group (need to be in object mode)
'''
## classic progressive
for vl in vlists:
vnum = len(vl)
for i, vid in enumerate(vl):
weight = i/vnum# progressive
weight = abs(weight-1)# reverse
weight = transfer_value(weight, 0, 1, 0.8, 1)# clamp between two value
C.object.vertex_groups.active.add([vid], weight, "REPLACE")
'''
## with a divider to limit
## ex with 5 points:
## divider 2 : 0, 0, 0 , 0.5, 1.0
## divider 3 : 0, 0, 0.33 , 0.66, 1.0
percentage = 0.4 #(0 for full progressive)
#divider = 3
for vl in vlists:
vnum = len(vl)
# limit = int(vnum/divider)
limit = int(vnum*percentage)
rest = vnum - limit
mini = uniform(0.86, 0.96)#random minimum
for i, vid in enumerate(vl):
#weight = i/vnum
w = weight = (i - limit)/(vnum-limit-1)
if weight < 0: weight=0
weight = abs(weight-1)#reverse
# mini = 0.9
weight = transfer_value(weight, 0, 1, mini, 1)# clamp between two last value
print(i, w, "->", weight)#Dbg
C.object.vertex_groups.active.add([vid], weight, "REPLACE")
print("vnum", vnum)#Dbg
print("random minimum", mini)#Dbg
print("limit", limit)#Dbg
print("rest", rest)#Dbg
print("full", rest + limit)#Dbg
#too smooth with 0.8 on fast movements
## transfer_value(weight, 0, 1, 0.8, 1)
@@ -0,0 +1,132 @@
## create visibility on objects or armature selection from a bone of a rig ##
import bpy
def add_driver(source, target, prop, dataPath, index = -1, negative = False, func = ''):
''' Add driver to source prop (at index), driven by target dataPath '''
source.driver_remove(prop, index)
if index != -1:
d = source.driver_add( prop, index ).driver
else:
d = source.driver_add( prop ).driver
v = d.variables.new()
v.targets[0].id = target
v.targets[0].data_path = dataPath
d.expression = func + "(" + v.name + ")" if func else v.name
d.expression = d.expression if not negative else "-1 * " + d.expression
def create_hide_custom_prop(src_object, prop_name, prop_bone = ''):
'''
add source propertie with boolean option
place the hide prop on src_object with name prop_name
'''
rig = bpy.data.objects.get(src_object)
if not rig:
print(f"No objects named {src_object}")
return 1
if rig.type != 'ARMATURE':
print(f"Not an armature : {src_object}")
return 1
#add target bone
if prop_bone:
holder = rig.pose.bones.get(prop_bone)
else:
holder = rig.pose.bones.get('root')
if not holder:
print(f'problem finding bone {prop_bone} (or root)')
return 1
# create
if not holder.get('_RNA_UI'):
holder['_RNA_UI'] = {}
if not prop_name in holder.keys() :
holder[prop_name] = 0
holder['_RNA_UI'][prop_name] = {"default": 0,"min":0,"max":1,"soft_min":0,"soft_max":1}
else:
print(f'{prop_name} : already exists on root key')
return
return 0
def drive_selection_visibility(rig, prop_name, prop_bone = ''):
# add driver on selection
prefixs = ('MCH','DEF','ORG', 'WGT')
rig = bpy.data.objects.get(src_object)
if not rig:
print(f"No objects named {src_object}")
return 1
if rig.type != 'ARMATURE':
print(f"Not an armature : {src_object}")
return 1
#add target bone
if not prop_bone:
prop_bone = 'root'
if not rig.pose.bones.get(prop_bone):
print(f'no bones {prop_bone} on rig {rig.name}')
return 1
meshes = [i for i in bpy.context.selected_objects if i.type in ('MESH','CURVE','TEXT') and not i.name.startswith(('WGT', 'WDGT'))]
armatures = [i for i in bpy.context.selected_objects if i.type == 'ARMATURE']
if bpy.context.mode == 'POSE':
obarm = bpy.context.active_object
for bone in bpy.context.selected_pose_bones_from_active_object:
prop = 'bones["%s"].hide'%bone.name
index = -1
layer = bone.bone.layers
protect_layer = rig.data.layers_protected
### dont check for protected, strictly use selection.
# if bone.name.startswith(prefixs) or any([i==j==1 for i,j in zip(layer,protect_layer)]) :
# print(f'Skipped : Prefixed or protected bone : {bone.name}')
# rig.data.driver_remove(prop, index)
# continue
print(f'New : Driver on bone {bone.name}')
add_driver(obarm.data, rig, prop, f'pose.bones["{prop_bone}"]["{prop_name}"]', index)
return
for ob in meshes :
print('Object : ', obarm.name)
add_driver(ob, rig, 'hide_viewport', f'pose.bones["{prop_bone}"]["{prop_name}"]', -1)
add_driver(ob, rig, 'hide_render', f'pose.bones["{prop_bone}"]["{prop_name}"]', -1)
for obarm in armatures:
print('Armature : ', obarm.name)
## mask armature object
## add_driver(obarm, rig, 'hide_viewport', f'pose.bones["{prop_bone}"]["{prop_name}"]', -1)
## bette mask pose bones since its a proxy...
for bone in obarm.pose.bones :
prop = 'bones["%s"].hide'%bone.name
index = -1
layer = bone.bone.layers
protect_layer = rig.data.layers_protected
if bone.name.startswith(prefixs) or any([i==j==1 for i,j in zip(layer,protect_layer)]) :
print(f'Skipped : Prefixed or protected bone : {bone.name}')
rig.data.driver_remove(prop, index)
else :
print(f'New : Driver on bone {bone.name}')
add_driver(obarm.data, rig, prop, f'pose.bones["{prop_bone}"]["{prop_name}"]', index)
### ----
## write the name of the rig source (will put the propertie on the root of this armature)
prop_rig = 'name_of_the_rig'
## write the name of the propertie to attach
prop_name = "hide_something"#'hide_headband'
## prop_bone (bone holding the propertie), 'root' if left string empty.
prop_bone = ''
create_hide_custom_prop(prop_rig, prop_name, prop_bone = prop_bone)
drive_selection_visibility(prop_rig, prop_name, prop_bone = prop_bone)
@@ -0,0 +1,32 @@
# coding: utf-8
import bpy
import bmesh
ob = bpy.context.object
me = ob.data
bm = bmesh.new() # create an empty BMesh
bm.from_mesh(me) # fill it in from a Mesh
bm.verts.ensure_lookup_table()
bm.edges.ensure_lookup_table()
bm.faces.ensure_lookup_table()
#deselect everything first
for f in bm.edges:f.select = False
for e in bm.edges:e.select = False
for v in bm.verts:v.select = False
#Checker deselect on each separate mesh portion
ct = 0
for v in bm.verts:
ct+=1
if len(v.link_edges) == 1:#star/end of chain
# v.select_set(False)#already deselected
ct = 0#reset ct for next
else:
print(v.index, 'select', ct%2)
v.select_set(ct%2)
bm.to_mesh(me)
bm.free()
+286
View File
@@ -0,0 +1,286 @@
bl_info = {
"name": "FTP sync",
"author": "Christophe Seux",
"version": (1, 0),
"blender": (2, 81, 0),
"warning": "",
"wiki_url": "",
"category": "User",
}
import bpy
from bpy.types import Operator, AddonPreferences
from bpy.props import FloatVectorProperty
import os
from os.path import *
import re
### EXCLUSIONS
EXCLUSIONS = ['.*','*.db','*.blend1','*~','*sync-conflict*','*.DS_Store']
'''
TO DO
Accepts empty folders, option for type of sync (override etc)
'''
class Preferences(AddonPreferences):
bl_idname = __name__
domain : bpy.props.StringProperty(name="FTP Domain")
login : bpy.props.StringProperty(name="FTP Login")
password : bpy.props.StringProperty(name="FTP Password",subtype='PASSWORD')
ftp_folder : bpy.props.StringProperty(name="FTP folder", default = '/')
local_folder : bpy.props.StringProperty(name="Local Folder", subtype='DIR_PATH')
active_connection : bpy.props.BoolProperty(name="Active Connection", default = True)
sync_type : bpy.props.EnumProperty(
name="Sync Type",
items = [(i,i.title(),"") for i in ("ONLY_NEW", "OVERRIDE", "MORE_RECENT")],
default = "ONLY_NEW")
def draw(self, context):
layout = self.layout
layout.prop(self, "domain")
layout.prop(self, "login")
layout.prop(self, "password")
layout.prop(self, "ftp_folder")
layout.prop(self, "active_connection")
layout.prop(self, "local_folder")
def connect_to_ftp(domain, login, passwd, active = True) :
from ftplib import FTP
ftp = FTP(domain,timeout = 10)
ftp.login(user = login, passwd = passwd)
ftp.set_pasv(active)
return ftp
def is_exclude(name, patterns) :
from fnmatch import fnmatch
if not isinstance(patterns, (list,tuple)) :
patterns = [patterns]
return any([fnmatch(name, p) for p in patterns])
def get_ftp_files(ftp, root, exclusions) :
files = []
ftp.cwd(root)
for k,v in ftp.mlsd() :
if is_exclude(k, exclusions) : continue
path = '/'.join([root,k])
if v['type'] == 'file' :
files.append(path)
elif v['type'] == 'dir' :
files+= get_ftp_files(ftp, path, exclusions)
return sorted(files)
def get_files(root, exclusions) :
'''Recursively get files in passed directory if not in exclusion list'''
files = []
for f in os.scandir(root) :
if is_exclude(f.path, exclusions) : continue
if f.is_file() :
files.append(f.path)
elif f.is_dir() :
files+= get_files(f.path, exclusions)
return sorted(files)
def createDirs(ftp, dirpath):
from ftplib import error_perm
"""
Create dir with subdirs (progressive dir creation).
:param ftp: connected FTP
:param dirpath: path (like 'test/test1/test2')
:type ftp: FTP
:type dirpath: str
:rtype: None
"""
dirpath = dirpath.replace('\\', '/')
tmp = dirpath.split('/')
dirs = []
for _ in tmp:
if len(dirs) == 0:
dirs.append(_)
continue
dirs.append(dirs[-1] + '/' + _)
for _ in dirs:
try:
ftp.mkd(_)
except error_perm as e:
e_str = str(e)
if '550' in e_str and 'File exists' in e_str:
continue
def exist_ftp_file(ftp, filepath) :
filepath = filepath.replace('\\', '/')
#ftp.cwd('/')
if filepath.startswith('/') : filepath = filepath[1:]
split_path = filepath.split('/')
for i,path in enumerate(split_path) :
list_dir = {k:v['type'] for k,v in ftp.mlsd()}
#print(i,path, list(list_dir.keys()))
if path in list_dir.keys() :
if i == len(split_path)-1 :
return True
elif list_dir[path] == 'dir' :
ftp.cwd(path)
else :
return False
else :
return False
class SendToFtp(Operator):
"""Send To FTP"""
bl_idname = "ftpsync.send"
bl_label = "Send to FTP"
bl_options = {'REGISTER', 'UNDO'}
def execute(self, context):
prefs = context.preferences.addons[__name__].preferences
ftp = connect_to_ftp(prefs.domain, prefs.login, prefs.password, prefs.active_connection)
print('\nStart Uploading to the FTP')
print('Source Folder :', prefs.local_folder)
ftp_folder = prefs.ftp_folder.replace('\\','/').strip('/')
if not ftp_folder.startswith('/') : ftp_folder = '/'+ftp_folder
local_folder = prefs.local_folder.replace('\\','/')
for f in get_files(local_folder, EXCLUSIONS) :
'''recursively get files in directory if not in exclusion list'''
ftp.cwd(ftp_folder)
dst_file = re.sub(local_folder, '', f.replace('\\','/')).strip('/')
if prefs.sync_type == 'ONLY_NEW' and exist_ftp_file(ftp,dst_file) :
continue
ftp.cwd(ftp_folder)
createDirs(ftp,dirname(dst_file))
try :
with open(f, 'rb') as fp:
ftp.storbinary('STOR '+dst_file, fp)
print('New file :', dst_file)
except :
print('Impossible to copy %s'%fp)
ftp.close()
return {'FINISHED'}
class GetFromFtp(Operator,):
"""Create a new Mesh Object"""
bl_idname = "ftpsync.get"
bl_label = "Get from FTP"
bl_options = {'REGISTER', 'UNDO'}
def execute(self, context):
prefs = context.preferences.addons[__name__].preferences
ftp = connect_to_ftp(prefs.domain, prefs.login, prefs.password, prefs.active_connection)
if not prefs.local_folder :
print('You have to set the local folder in your prefs')
return {'FINISHED'}
print('\nStart Downloading from FTP')
print('Destination Folder :', prefs.local_folder)
ftp_folder = prefs.ftp_folder.replace('\\','/').strip('/')
if not ftp_folder.startswith('/') : ftp_folder = '/'+ftp_folder
local_folder = prefs.local_folder.replace('\\','/')
for f in get_ftp_files(ftp, ftp_folder, EXCLUSIONS) :
dst_file = join(local_folder, re.sub(ftp_folder,'',f.replace('\\','/')).strip('/'))
if prefs.sync_type == 'ONLY_NEW' and exists(dst_file) :
continue
if not exists(dirname(dst_file)) :
os.makedirs(dirname(dst_file))
with open(dst_file, 'wb') as fp:
ftp.retrbinary('RETR '+f, fp.write)
print('New file :', dst_file)
ftp.close()
return {'FINISHED'}
class Menu(bpy.types.Menu):
bl_label = "ADM"
bl_idname = "ADM_MT_menu"
def draw(self, context):
layout = self.layout
prefs = context.preferences.addons[__name__].preferences
layout.operator("ftpsync.get", icon='IMPORT')
layout.operator("ftpsync.send", icon='EXPORT')
layout.prop(prefs, "sync_type", text = '')
def menu_draw(self,context):
self.layout.menu("ADM_MT_menu")
cls = [Preferences, SendToFtp, GetFromFtp,Menu]
def register():
bpy.types.TOPBAR_MT_editor_menus.append(menu_draw)
for c in cls :
bpy.utils.register_class(c)
def unregister():
bpy.types.TOPBAR_MT_editor_menus.remove(menu_draw)
for c in cls :
bpy.utils.unregister_class(c)
if __name__ == "__main__":
register()
+51
View File
@@ -0,0 +1,51 @@
# coding: utf-8
import bpy
import bmesh
from math import degrees, radians
ob = bpy.context.object
me = ob.data
if ob.type != 'MESH':
print('ERROR : not a mesh')
mode = bpy.context.mode
if mode == 'EDIT_MESH':
#me = bpy.context.edit_object.data
bm = bmesh.from_edit_mesh(me) #get Bmesh from edit
elif mode == 'OBJECT':
bm = bmesh.new() # create an empty BMesh
bm.from_mesh(me) # fill it in from a Mesh
bm.verts.ensure_lookup_table()
bm.edges.ensure_lookup_table()
bm.faces.ensure_lookup_table()
#deselect everything first
for f in bm.edges:f.select = False
for e in bm.edges:e.select = False
for v in bm.verts:v.select = False
#keep vertex that mark angle above this degree tolerance
degree_tolerance = 20
tol = radians(degree_tolerance)
#Checker
ct = 0
for v in bm.verts:
if len(v.link_edges) == 2:#star/end of chain
if v.calc_edge_angle() < tol:
#v.select_set(True)#full select
v.select_set(ct%2)#checker select
else:
ct = 0#reset counter
ct+=1
if mode == 'EDIT_MESH':
bmesh.update_edit_mesh(me, True)
elif mode == 'OBJECT':
bm.to_mesh(me)
bm.free()
@@ -0,0 +1,17 @@
vl = [10,11,12,13,14,15,16,17,18,19,20,21,22,23]
vl = [10,11,12,13,14]
print()
divider = 0.8
vnum = len(vl)
#limit = int(vnum/divider)
limit = int(vnum*divider)
rest = vnum - limit
print("vnum", vnum)#Dbg
print("limit", limit)#Dbg
print("rest", rest)#Dbg
for i, vid in enumerate(vl):
total = (vnum-limit-1)
w = (i - limit)/total
print(i, f'{w:.2f}')#Dbg