custom_shelf initial commit
This commit is contained in:
@@ -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()
|
||||
@@ -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()
|
||||
@@ -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
|
||||
Reference in New Issue
Block a user