custom_shelf initial commit
parent
705b6f2017
commit
232e5cc7f6
|
@ -0,0 +1,3 @@
|
||||||
|
shelves
|
||||||
|
*.pyc
|
||||||
|
__pycache__
|
|
@ -0,0 +1,279 @@
|
||||||
|
import bpy
|
||||||
|
from bpy.types import Operator, Panel, PropertyGroup
|
||||||
|
from .utils import *
|
||||||
|
from bpy.props import *
|
||||||
|
from .properties import CustomShelfProps
|
||||||
|
|
||||||
|
|
||||||
|
class Types():
|
||||||
|
def initialyse(self,label) :
|
||||||
|
package = __package__.replace('_','').replace('-','').lower()
|
||||||
|
idname = label.replace(' ','_').replace('-','_').lower()
|
||||||
|
|
||||||
|
self.bl_label = title(label)
|
||||||
|
self.bl_idname = '%s.%s'%(package,idname)
|
||||||
|
|
||||||
|
#self.bl_props = bpy.context.scene.CustomShelf
|
||||||
|
#self.prefs = ""
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
class CSHELF_OT_shelf_popup(Types,Operator) :
|
||||||
|
#props = None
|
||||||
|
@property
|
||||||
|
def props(self) :
|
||||||
|
#print(getattr(bpy.context.window_manager.CustomShelf,self.prop))
|
||||||
|
return getattr(bpy.context.window_manager.CustomShelf,self.prop)
|
||||||
|
|
||||||
|
def get_props(self):
|
||||||
|
properties = self.props.bl_rna.properties
|
||||||
|
#props =
|
||||||
|
return {k:v for k,v in self.args.items() if k in properties}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
def set_props(self,prop,value):
|
||||||
|
setattr(bpy.context.window_manager.CustomShelf,prop,value)
|
||||||
|
|
||||||
|
|
||||||
|
def initialyse(self,label,info) :
|
||||||
|
super().initialyse(CSHELF_OT_shelf_popup,label)
|
||||||
|
|
||||||
|
self.args = dic_to_args(info)
|
||||||
|
|
||||||
|
|
||||||
|
Props = type("props",(PropertyGroup,),{'__annotations__' : dict(self.args)})
|
||||||
|
bpy.utils.register_class(Props)
|
||||||
|
|
||||||
|
setattr(CustomShelfProps,arg_name(label),PointerProperty(type=Props))
|
||||||
|
#self.props = getattr(bpy.context.window_manager,self.bl_idname)
|
||||||
|
self.info = info
|
||||||
|
self.prop = arg_name(label)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
#print(self.args)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
#properties = self.get_props().bl_rna.properties
|
||||||
|
#props = {k:v for k,v in self.args.items() if k in properties or hasattr(v,"bl_idname")}
|
||||||
|
|
||||||
|
return #props
|
||||||
|
|
||||||
|
def invoke(self, context, event):
|
||||||
|
if event.alt :
|
||||||
|
for t in [t for t in bpy.data.texts if t.filepath == self.script] :
|
||||||
|
bpy.data.texts.remove(t)
|
||||||
|
|
||||||
|
text = bpy.data.texts.load(self.script)
|
||||||
|
|
||||||
|
areas = []
|
||||||
|
text_editor = None
|
||||||
|
for area in context.screen.areas :
|
||||||
|
if area.type == 'TEXT_EDITOR' :
|
||||||
|
text_editor = area
|
||||||
|
|
||||||
|
else :
|
||||||
|
areas.append(area)
|
||||||
|
if not text_editor :
|
||||||
|
bpy.ops.screen.area_split(direction = "VERTICAL")
|
||||||
|
|
||||||
|
for area in context.screen.areas :
|
||||||
|
if area not in areas :
|
||||||
|
text_editor = area
|
||||||
|
text_editor.type = "TEXT_EDITOR"
|
||||||
|
text_editor.spaces[0].show_syntax_highlight = True
|
||||||
|
text_editor.spaces[0].show_word_wrap = True
|
||||||
|
text_editor.spaces[0].show_line_numbers = True
|
||||||
|
|
||||||
|
context_copy = context.copy()
|
||||||
|
context_copy['area'] = text_editor
|
||||||
|
#bpy.ops.text.properties(context_copy)
|
||||||
|
#bpy.ops.view3d.toolshelf(context_copy)
|
||||||
|
|
||||||
|
text_editor.spaces[0].text = text
|
||||||
|
|
||||||
|
return {"FINISHED"}
|
||||||
|
else :
|
||||||
|
if self.args :
|
||||||
|
#set default value for collection
|
||||||
|
for k,v in self.info.items() :
|
||||||
|
if isinstance(v,dict) and v.get('collection') :
|
||||||
|
collection = getattr(bpy.data, id_type[v["collection"]])
|
||||||
|
|
||||||
|
setattr(self.props, k,collection.get(v["value"]))
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
return context.window_manager.invoke_props_dialog(self)
|
||||||
|
else :
|
||||||
|
return self.execute(context)
|
||||||
|
|
||||||
|
|
||||||
|
def bl_report(self,*args) :
|
||||||
|
if len(args) and args[0] in ('INFO','ERROR', 'WARNING') :
|
||||||
|
return self.report({args[0]},' '.join(args[1:]))
|
||||||
|
else :
|
||||||
|
print(*args)
|
||||||
|
|
||||||
|
def execute(self,context):
|
||||||
|
import tempfile
|
||||||
|
|
||||||
|
info,lines = read_info(self.script)
|
||||||
|
|
||||||
|
values = dict(info)
|
||||||
|
for arg in self.args :
|
||||||
|
value = getattr(self.props,arg)
|
||||||
|
|
||||||
|
if hasattr(value,"name") :
|
||||||
|
values[arg] = {'value': value.name}
|
||||||
|
|
||||||
|
elif isinstance(info.get(arg),dict) :
|
||||||
|
values[arg] = {'value': value}
|
||||||
|
|
||||||
|
else :
|
||||||
|
values[arg] = value
|
||||||
|
|
||||||
|
|
||||||
|
info_str = "info = %s\n\n"%values
|
||||||
|
|
||||||
|
tmp_folder = tempfile.gettempdir()
|
||||||
|
script_path = join(tmp_folder, "shelf.py")
|
||||||
|
|
||||||
|
with open(script_path,"w",encoding = 'utf-8') as f :
|
||||||
|
f.write(info_str+'\n'.join(lines))
|
||||||
|
|
||||||
|
exec(compile(open(script_path,encoding = 'utf-8').read(), script_path, 'exec'),{'info' : values,'print':self.bl_report})
|
||||||
|
|
||||||
|
return {'FINISHED'}
|
||||||
|
|
||||||
|
def draw(self,context) :
|
||||||
|
layout = self.layout
|
||||||
|
bl_props = context.scene.CustomShelf
|
||||||
|
|
||||||
|
#print(self.get_props())
|
||||||
|
|
||||||
|
for k in self.args :
|
||||||
|
#filter
|
||||||
|
|
||||||
|
row = layout.row(align = True)
|
||||||
|
row.label(text=title(k))
|
||||||
|
|
||||||
|
row.prop(self.props,k,text='')
|
||||||
|
#row.prop_search(self.props,k,self.props,k+"_prop_search",text='')
|
||||||
|
#op = self.args[k+"_prop_search"]
|
||||||
|
#row.operator(op.bl_idname,text="",icon ="COLLAPSEMENU" )
|
||||||
|
|
||||||
|
|
||||||
|
def check(self,context) :
|
||||||
|
return True
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
"""
|
||||||
|
class ShelfPropSearch(Types,Operator):
|
||||||
|
bl_property = "enum"
|
||||||
|
|
||||||
|
enum = None
|
||||||
|
prop = ""
|
||||||
|
|
||||||
|
def execute(self,context) :
|
||||||
|
|
||||||
|
parent_props = getattr(bpy.context.window_manager.CustomShelf,self.parent.prop)
|
||||||
|
|
||||||
|
setattr(parent_props,self.prop,self.enum)
|
||||||
|
|
||||||
|
return {"FINISHED"}
|
||||||
|
|
||||||
|
def initialyse(self,label,enum_list) :
|
||||||
|
super().initialyse(ShelfPropSearch,"prop_search_"+label)
|
||||||
|
|
||||||
|
self.enum = EnumProperty(items = [(i,i,"") for i in enum_list])
|
||||||
|
self.prop = label
|
||||||
|
#print(self.enum)
|
||||||
|
|
||||||
|
def invoke(self, context, event):
|
||||||
|
#print("invoke",self.parent)
|
||||||
|
context.window_manager.invoke_search_popup(self)
|
||||||
|
return {"FINISHED"}
|
||||||
|
|
||||||
|
"""
|
||||||
|
|
||||||
|
|
||||||
|
class CSHELF_PT_shelf_panel(Types,Panel):
|
||||||
|
bl_space_type = "VIEW_3D"
|
||||||
|
bl_region_type = "UI"
|
||||||
|
#bl_category = "SHELF"
|
||||||
|
settings = {}
|
||||||
|
scripts = []
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def search_filter(search,str) :
|
||||||
|
return search.lower() in str.lower()
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def poll(self, context):
|
||||||
|
bl_props = getattr(context.scene.CustomShelf,self.cat)
|
||||||
|
prefs = bpy.context.preferences.addons[__package__].preferences
|
||||||
|
search = bl_props.search
|
||||||
|
shelf = [self.search_filter(search,s["text"]) for s in self.scripts]
|
||||||
|
|
||||||
|
#print("settings",self.settings)
|
||||||
|
is_taged = True
|
||||||
|
if self.settings and self.settings.get('tags') :
|
||||||
|
tag = prefs.tag_filter
|
||||||
|
if not tag :
|
||||||
|
tag = os.getenv("CUSTOM_SHELF_TAG")
|
||||||
|
|
||||||
|
is_taged = tag in self.settings['tags']
|
||||||
|
|
||||||
|
return any(shelf) and is_taged
|
||||||
|
|
||||||
|
|
||||||
|
def draw(self,context) :
|
||||||
|
layout = self.layout
|
||||||
|
bl_props = getattr(context.scene.CustomShelf,self.cat)
|
||||||
|
search = bl_props.search
|
||||||
|
|
||||||
|
for script in self.scripts :
|
||||||
|
args = script.copy()
|
||||||
|
if not self.search_filter(search,script["text"]) : continue
|
||||||
|
|
||||||
|
if bl_props.filter and script["text"].startswith('_') : continue
|
||||||
|
|
||||||
|
args["text"] = title(script["text"])
|
||||||
|
|
||||||
|
#print(script)
|
||||||
|
layout.operator(**args)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
class CSHELF_PT_shelf_header(Panel):
|
||||||
|
bl_label = "Custom Shelf"
|
||||||
|
bl_space_type = 'VIEW_3D'
|
||||||
|
bl_region_type = 'UI'
|
||||||
|
bl_options = {"HIDE_HEADER"}
|
||||||
|
|
||||||
|
|
||||||
|
def draw(self, context):
|
||||||
|
layout = self.layout
|
||||||
|
bl_props = bpy.context.scene.CustomShelf
|
||||||
|
prefs = bpy.context.preferences.addons[__package__].preferences
|
||||||
|
|
||||||
|
cat_props = getattr(bl_props, self.cat)
|
||||||
|
|
||||||
|
row = layout.row(align = True)
|
||||||
|
|
||||||
|
row.operator_menu_enum("customshelf.set_tag_filter",'tag_filter',icon= "DOWNARROW_HLT",text='')
|
||||||
|
row.operator("customshelf.refresh", text='',emboss=True,icon= "FILE_REFRESH")
|
||||||
|
#row.separator()
|
||||||
|
|
||||||
|
row.prop(cat_props, "filter",text='',emboss=True,icon= "FILTER")
|
||||||
|
#row.separator()
|
||||||
|
|
||||||
|
row.prop(cat_props,"search",icon = "VIEWZOOM",text='')
|
||||||
|
#row.separator()
|
||||||
|
|
||||||
|
row.operator("customshelf.open_shelf_folder", text='',emboss=True,icon= "FILE").path = self.path
|
|
@ -0,0 +1,83 @@
|
||||||
|
bl_info = {
|
||||||
|
"name": "Custom Shelf",
|
||||||
|
"author": "Christophe Seux",
|
||||||
|
"description": "Adds buttons to launch custom python scripts in the User panel.",
|
||||||
|
"version": (0, 3, 2),
|
||||||
|
"blender": (2, 82, 0),
|
||||||
|
"location": "View3D > User Panel",
|
||||||
|
"doc_url": "https://gitlab.com/autour-de-minuit/blender/custom_shelf/README.md"
|
||||||
|
"tracker_url": "https://gitlab.com/autour-de-minuit/blender/custom_shelf/-/issues",
|
||||||
|
"support": "COMMUNITY",
|
||||||
|
"category": "User"}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
if "bpy" in locals():
|
||||||
|
import importlib
|
||||||
|
importlib.reload(operators)
|
||||||
|
importlib.reload(panels)
|
||||||
|
importlib.reload(functions)
|
||||||
|
importlib.reload(properties)
|
||||||
|
|
||||||
|
from .utils import report
|
||||||
|
from .functions import read_shelves
|
||||||
|
from .import operators
|
||||||
|
#from .import shelves
|
||||||
|
from .import properties
|
||||||
|
from .import panels
|
||||||
|
from .properties import CustomShelfSettings,CustomShelfProps
|
||||||
|
|
||||||
|
import bpy
|
||||||
|
import os
|
||||||
|
|
||||||
|
|
||||||
|
class_to_register = [
|
||||||
|
properties.AdditionnalShelves,
|
||||||
|
properties.CustomShelfProps,
|
||||||
|
properties.CustomShelfSettings,
|
||||||
|
properties.CustomShelfPrefs,
|
||||||
|
operators.CSHELF_OT_refresh,
|
||||||
|
operators.CSHELF_OT_add_shelf_folder,
|
||||||
|
operators.CSHELF_OT_remove_shelf_folder,
|
||||||
|
operators.CSHELF_OT_open_shelf_folder,
|
||||||
|
operators.CSHELF_OT_add_script,
|
||||||
|
operators.CSHELF_OT_set_tag_filter,
|
||||||
|
panels.CSHELF_PT_text_editor
|
||||||
|
]
|
||||||
|
|
||||||
|
def register():
|
||||||
|
for bl_classes in class_to_register :
|
||||||
|
bpy.utils.register_class(bl_classes)
|
||||||
|
|
||||||
|
bpy.types.Scene.CustomShelf = bpy.props.PointerProperty(type=CustomShelfSettings)
|
||||||
|
bpy.types.WindowManager.CustomShelf = bpy.props.PointerProperty(type=CustomShelfProps)
|
||||||
|
|
||||||
|
env_shelves = os.getenv("CUSTOM_SHELVES")
|
||||||
|
if env_shelves:
|
||||||
|
shelves = env_shelves.split(os.pathsep)
|
||||||
|
prefs = bpy.context.preferences.addons[__name__].preferences
|
||||||
|
# prefs.global_shelves = ''
|
||||||
|
|
||||||
|
# prefs.additionnal_shelves.clear()
|
||||||
|
for path in shelves:
|
||||||
|
s = next((s for s in prefs.additionnal_shelves if s.path == path), None)
|
||||||
|
if not s:
|
||||||
|
s = prefs.additionnal_shelves.add()
|
||||||
|
s.path = path
|
||||||
|
|
||||||
|
read_shelves()
|
||||||
|
|
||||||
|
def unregister():
|
||||||
|
# unregister panel :
|
||||||
|
for p in CustomShelfSettings.panel_list :
|
||||||
|
try :
|
||||||
|
bpy.utils.unregister_class(p)
|
||||||
|
except :
|
||||||
|
pass
|
||||||
|
|
||||||
|
del bpy.types.Scene.CustomShelf
|
||||||
|
del bpy.types.WindowManager.CustomShelf
|
||||||
|
|
||||||
|
|
||||||
|
for bl_classes in class_to_register :
|
||||||
|
bpy.utils.unregister_class(bl_classes)
|
|
@ -0,0 +1,59 @@
|
||||||
|
info = {
|
||||||
|
'icon' : 'LIGHT_SPOT',
|
||||||
|
'description' : 'Save as lighting file from currently opened anim file',
|
||||||
|
}
|
||||||
|
|
||||||
|
import bpy
|
||||||
|
import os
|
||||||
|
from os import listdir
|
||||||
|
from os.path import join, dirname, basename, exists, isfile, isdir, splitext
|
||||||
|
import re, fnmatch, glob
|
||||||
|
from mathutils import Vector, Matrix
|
||||||
|
from math import radians, degrees
|
||||||
|
|
||||||
|
import datetime
|
||||||
|
|
||||||
|
C = bpy.context
|
||||||
|
D = bpy.data
|
||||||
|
scene = C.scene
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
def create_lighting_file():
|
||||||
|
'''Create lighting_v001 file from anim'''
|
||||||
|
# if D.is_dirty:
|
||||||
|
# print('Current file is not saved')
|
||||||
|
# return
|
||||||
|
|
||||||
|
if 'lighting' in D.filepath:
|
||||||
|
print('File already detected as lighting')
|
||||||
|
return
|
||||||
|
|
||||||
|
if not 'animation' in D.filepath:
|
||||||
|
print('Not in animation folder')
|
||||||
|
return
|
||||||
|
|
||||||
|
name = re.sub(r'anim_v\d{3}', 'lighting_v001', basename(D.filepath))
|
||||||
|
print('name: ', name)
|
||||||
|
|
||||||
|
fp = dirname(dirname(D.filepath))
|
||||||
|
print('shot path: ', fp)
|
||||||
|
fp = join(fp, 'lighting', name)
|
||||||
|
print('light filepath: ', fp)
|
||||||
|
|
||||||
|
if exists(fp):
|
||||||
|
print('lighting_v001 already exists in folder')
|
||||||
|
return
|
||||||
|
|
||||||
|
|
||||||
|
info = D.texts.get('info')
|
||||||
|
if not info:
|
||||||
|
info = D.texts.new('info')
|
||||||
|
|
||||||
|
info.write(f'\nCreated from {basename(D.filepath)} at {str( datetime.datetime.now() )}')
|
||||||
|
print('Saving current file as lighting')
|
||||||
|
bpy.ops.wm.save_as_mainfile(filepath=fp)
|
||||||
|
|
||||||
|
print('Done')
|
||||||
|
|
||||||
|
create_lighting_file()
|
|
@ -0,0 +1,43 @@
|
||||||
|
info = {
|
||||||
|
'icon' : 'CHECKMARK',
|
||||||
|
'description' : 'Check objects modifiers',
|
||||||
|
}
|
||||||
|
|
||||||
|
import bpy
|
||||||
|
import os, re, shutil
|
||||||
|
from os.path import dirname, basename, join, abspath, splitext, exists, isfile, isdir
|
||||||
|
from os import listdir
|
||||||
|
import datetime
|
||||||
|
C = bpy.context
|
||||||
|
D = bpy.data
|
||||||
|
scn = scene = C.scene
|
||||||
|
|
||||||
|
## in scene syubloop ob.instance_collection.all_objects:
|
||||||
|
|
||||||
|
print(f'\n== Modifiers check {basename(D.filepath)} ==')
|
||||||
|
for ob in bpy.data.objects:
|
||||||
|
if ob.type != 'ARMATURE' and ob.hide_viewport != ob.hide_render:
|
||||||
|
print(f'/!\ {ob.name} view != render : hide_viewport {ob.hide_viewport}, hide_render {ob.hide_render}')
|
||||||
|
|
||||||
|
if ob.type in ('MESH', 'CURVE', 'TEXT') and len(ob.modifiers) > 0:
|
||||||
|
for mod in ob.modifiers:
|
||||||
|
#mod.show_viewport = True
|
||||||
|
#mod.show_render = True
|
||||||
|
if mod.type == 'SUBSURF':
|
||||||
|
# print (ob.name)
|
||||||
|
if mod.render_levels < mod.levels:
|
||||||
|
print(f'[M] {ob.name} subsurf render levels inferior to viewport : view {mod.levels} > render {mod.render_levels}')
|
||||||
|
elif mod.render_levels == 0:
|
||||||
|
print(f'[M] {ob.name} subsurf useless : render levels set to 0 : view {mod.levels} > render {mod.render_levels}')
|
||||||
|
'''
|
||||||
|
if mod.type == 'ARMATURE':
|
||||||
|
if mod.object:
|
||||||
|
print (mod.object.name)#print armatrue target
|
||||||
|
#ob.modifiers.remove(mod) #remove modifier
|
||||||
|
|
||||||
|
if mod.type == 'SOLIDIFY':
|
||||||
|
mod.thickness
|
||||||
|
mod.offset
|
||||||
|
'''
|
||||||
|
|
||||||
|
print('== check done.')
|
|
@ -0,0 +1,68 @@
|
||||||
|
info = {
|
||||||
|
'icon' : 'DECORATE_LIBRARY_OVERRIDE',
|
||||||
|
'description' : 'Check available library update',
|
||||||
|
'update' : False,
|
||||||
|
'fullreport': False,
|
||||||
|
}
|
||||||
|
|
||||||
|
import bpy
|
||||||
|
import os
|
||||||
|
from os import listdir, scandir
|
||||||
|
from os.path import abspath, relpath, join, dirname, basename, exists, isfile, isdir, splitext
|
||||||
|
import re, fnmatch, glob
|
||||||
|
from mathutils import Vector, Matrix
|
||||||
|
from math import radians, degrees
|
||||||
|
C = bpy.context
|
||||||
|
D = bpy.data
|
||||||
|
scene = C.scene
|
||||||
|
|
||||||
|
auto_update = info['update']#False
|
||||||
|
fullreport = info['fullreport']
|
||||||
|
|
||||||
|
## 1: all execpt num, 2: num, 3:ext
|
||||||
|
rseq = re.compile('^(.*?)(\d+)(\D*)$')
|
||||||
|
|
||||||
|
|
||||||
|
print('\n--library check--')
|
||||||
|
for ob in bpy.context.scene.objects:
|
||||||
|
if not ob.type == 'EMPTY': continue
|
||||||
|
if not ob.instance_collection: continue
|
||||||
|
if not ob.instance_collection.library: continue
|
||||||
|
lib = ob.instance_collection.library
|
||||||
|
if not lib: continue
|
||||||
|
fp = lib.filepath
|
||||||
|
if not fp: continue
|
||||||
|
rel_dir = dirname(fp)
|
||||||
|
abs_lib = abspath(bpy.path.abspath(fp))
|
||||||
|
if not exists(abs_lib):
|
||||||
|
print(f'Lib not found: obj: {ob.name} > instance: {ob.instance_collection.name}\n-> {abs_lib}')
|
||||||
|
continue
|
||||||
|
lib_dir = dirname(abs_lib)
|
||||||
|
lib_name = basename(abs_lib)
|
||||||
|
|
||||||
|
#same length ? #last modified ? #last by name without #must be 90% identical ? # parts without rightmost number must be identical (good enough)
|
||||||
|
regname = rseq.search(lib_name)
|
||||||
|
if not regname:
|
||||||
|
print(f'X - {lib_name} : could not identify version using regex: {rseq.pattern}')
|
||||||
|
continue
|
||||||
|
name_base = regname.group(1)
|
||||||
|
|
||||||
|
# filelist = [f for f in scandir(lib_dir) if f.is_file() and len(f.name) == len(lib_name)]
|
||||||
|
|
||||||
|
# more strict with filename
|
||||||
|
filelist = [f for f in scandir(lib_dir) if f.is_file() and len(f.name) == len(lib_name) and rseq.search(f.name) and rseq.search(f.name).group(1) == name_base]
|
||||||
|
filelist.sort(key=lambda x : x.name)#sort in place alphabetically
|
||||||
|
|
||||||
|
last = filelist[-1]
|
||||||
|
if last.name != lib.name:
|
||||||
|
print(f'/!\ Lib update found : obj: {ob.name} > instance: {ob.instance_collection.name}\n {lib_name} >> {last.name}\n')
|
||||||
|
|
||||||
|
if auto_update:
|
||||||
|
### use relocate or do it automagically...
|
||||||
|
nfp = join(dirname(fp), last.name)
|
||||||
|
lib.filepath = nfp
|
||||||
|
continue
|
||||||
|
|
||||||
|
if fullreport: print(f'Lib OK : obj: {ob.name} > instance: {ob.instance_collection.name} > file: {lib_name}')
|
||||||
|
## make breakdown popup for addon version
|
||||||
|
|
|
@ -0,0 +1,121 @@
|
||||||
|
info = {
|
||||||
|
'icon' : 'TOOL_SETTINGS',
|
||||||
|
'description' : 'Set render path for PNG - RGB out',
|
||||||
|
'stamp' : False
|
||||||
|
}
|
||||||
|
|
||||||
|
import bpy
|
||||||
|
import os
|
||||||
|
from os import listdir
|
||||||
|
from os.path import join, dirname, basename, exists, isfile, isdir, splitext
|
||||||
|
import re, fnmatch, glob
|
||||||
|
from mathutils import Vector, Matrix
|
||||||
|
from math import radians, degrees
|
||||||
|
from time import time
|
||||||
|
|
||||||
|
C = bpy.context
|
||||||
|
D = bpy.data
|
||||||
|
scn = scene = C.scene
|
||||||
|
rd = scn.render
|
||||||
|
ev = scn.eevee
|
||||||
|
|
||||||
|
def render_anim(GL=False):
|
||||||
|
#openGl render
|
||||||
|
if GL:
|
||||||
|
bpy.ops.render.opengl(animation=True, view_context=False)#view_context False > look throughcam
|
||||||
|
else:
|
||||||
|
bpy.ops.render.render(animation=True)
|
||||||
|
|
||||||
|
def set_stamp(active_note=False):
|
||||||
|
rd.use_stamp = True
|
||||||
|
|
||||||
|
## Enable
|
||||||
|
rd.use_stamp_filename = True
|
||||||
|
rd.use_stamp_frame = True
|
||||||
|
rd.use_stamp_render_time = True
|
||||||
|
rd.use_stamp_time = True
|
||||||
|
rd.use_stamp_date = True
|
||||||
|
|
||||||
|
## Disable
|
||||||
|
rd.use_stamp_sequencer_strip = False
|
||||||
|
rd.use_stamp_marker = False
|
||||||
|
rd.use_stamp_scene = False
|
||||||
|
rd.use_stamp_lens = False
|
||||||
|
rd.use_stamp_camera = False
|
||||||
|
rd.use_stamp_hostname = False
|
||||||
|
rd.use_stamp_memory = False
|
||||||
|
rd.use_stamp_frame_range = False
|
||||||
|
|
||||||
|
## Notes
|
||||||
|
rd.use_stamp_note = active_note
|
||||||
|
|
||||||
|
if rd.use_stamp_note:
|
||||||
|
info = D.texts.get('info')
|
||||||
|
txt = ''
|
||||||
|
if info:
|
||||||
|
for l in info.lines[:2]:
|
||||||
|
if 'Created from' in l.body:
|
||||||
|
txt = l.body
|
||||||
|
if txt:
|
||||||
|
rd.stamp_note_text = txt
|
||||||
|
else:#disable notes
|
||||||
|
rd.use_stamp_note = False
|
||||||
|
|
||||||
|
def setup_render_params(use_stamp=False):
|
||||||
|
'''set dimensions, percentage, fps...'''
|
||||||
|
if use_stamp:
|
||||||
|
set_stamp(active_note=True)
|
||||||
|
else:
|
||||||
|
rd.use_stamp = False
|
||||||
|
|
||||||
|
rd.resolution_x = 1920
|
||||||
|
rd.resolution_y = 1080
|
||||||
|
rd.resolution_percentage = 100
|
||||||
|
rd.use_border = False
|
||||||
|
rd.fps = 25
|
||||||
|
rd.use_sequencer = False
|
||||||
|
|
||||||
|
#sampling
|
||||||
|
ev.taa_render_samples = 128#push up sample for shadow mainly
|
||||||
|
|
||||||
|
#AO
|
||||||
|
ev.use_gtao = True
|
||||||
|
ev.gtao_distance = 0.7
|
||||||
|
ev.gtao_factor = 0.7
|
||||||
|
ev.gtao_quality = 0.2
|
||||||
|
ev.use_gtao_bent_normals = False#bent normal makes it lighter than shadows !
|
||||||
|
#not sure...
|
||||||
|
ev.use_gtao_bounce = True#no bounce is darker... (less AO on claer object)
|
||||||
|
|
||||||
|
#setup color management
|
||||||
|
scene.view_settings.look = 'Filmic - Medium High Contrast'
|
||||||
|
|
||||||
|
#no disable auto folder opening after auto video making
|
||||||
|
if hasattr(scene, 'MVopen'):
|
||||||
|
scene.MVopen = False
|
||||||
|
|
||||||
|
|
||||||
|
def set_render_path():
|
||||||
|
setup_render_params(use_stamp=info['stamp'])
|
||||||
|
rd.image_settings.file_format = 'PNG'
|
||||||
|
rd.image_settings.color_mode = 'RGB'#A
|
||||||
|
rd.image_settings.color_depth = '8'
|
||||||
|
rd.use_file_extension = True
|
||||||
|
rd.use_overwrite = True#erase file
|
||||||
|
rd.use_placeholder = False
|
||||||
|
rd.image_settings.compression = 50#default = 15, high = slower and lighter (same quality)
|
||||||
|
|
||||||
|
filename = splitext(basename(D.filepath))[0]
|
||||||
|
# fp = join(dirname(D.filepath), 'render')
|
||||||
|
|
||||||
|
|
||||||
|
rd.filepath = join(dirname(D.filepath), 'images', filename, filename + '.')# '.####' #let a dot to separate padding number
|
||||||
|
|
||||||
|
set_render_path()
|
||||||
|
|
||||||
|
|
||||||
|
'''
|
||||||
|
start = time()
|
||||||
|
render_anim(GL=False)
|
||||||
|
print(f'elapsed {time() - start}')
|
||||||
|
'''
|
|
@ -0,0 +1,155 @@
|
||||||
|
info = {
|
||||||
|
'icon' : 'TOOL_SETTINGS',
|
||||||
|
'description' : 'Set filepath and video settings for direct video out',
|
||||||
|
'stamp' : False
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
import bpy
|
||||||
|
import os
|
||||||
|
from os import listdir
|
||||||
|
from os.path import join, dirname, basename, exists, isfile, isdir, splitext
|
||||||
|
import re, fnmatch, glob
|
||||||
|
from mathutils import Vector, Matrix
|
||||||
|
from math import radians, degrees
|
||||||
|
from time import time
|
||||||
|
|
||||||
|
C = bpy.context
|
||||||
|
D = bpy.data
|
||||||
|
scn = scene = C.scene
|
||||||
|
rd = scn.render
|
||||||
|
ev = scn.eevee
|
||||||
|
|
||||||
|
def set_stamp(active_note=False):
|
||||||
|
rd.use_stamp = True
|
||||||
|
|
||||||
|
## Enable
|
||||||
|
rd.use_stamp_filename = True
|
||||||
|
rd.use_stamp_frame = True
|
||||||
|
rd.use_stamp_render_time = True
|
||||||
|
rd.use_stamp_time = True
|
||||||
|
rd.use_stamp_date = True
|
||||||
|
|
||||||
|
## Disable
|
||||||
|
rd.use_stamp_sequencer_strip = False
|
||||||
|
rd.use_stamp_marker = False
|
||||||
|
rd.use_stamp_scene = False
|
||||||
|
rd.use_stamp_lens = False
|
||||||
|
rd.use_stamp_camera = False
|
||||||
|
rd.use_stamp_hostname = False
|
||||||
|
rd.use_stamp_memory = False
|
||||||
|
rd.use_stamp_frame_range = False
|
||||||
|
|
||||||
|
## Notes
|
||||||
|
rd.use_stamp_note = active_note
|
||||||
|
|
||||||
|
if rd.use_stamp_note:
|
||||||
|
info = D.texts.get('info')
|
||||||
|
txt = ''
|
||||||
|
if info:
|
||||||
|
for l in info.lines[:2]:
|
||||||
|
if 'Created from' in l.body:
|
||||||
|
txt = l.body
|
||||||
|
if txt:
|
||||||
|
rd.stamp_note_text = txt
|
||||||
|
else:#disable notes
|
||||||
|
rd.use_stamp_note = False
|
||||||
|
|
||||||
|
|
||||||
|
def setup_render_params(use_stamp=False):
|
||||||
|
'''set dimensions, percentage, fps...'''
|
||||||
|
if use_stamp:
|
||||||
|
set_stamp(active_note=True)
|
||||||
|
else:
|
||||||
|
rd.use_stamp = False
|
||||||
|
|
||||||
|
rd.resolution_x = 1920
|
||||||
|
rd.resolution_y = 1080
|
||||||
|
rd.resolution_percentage = 100
|
||||||
|
rd.use_border = False
|
||||||
|
rd.fps = 25
|
||||||
|
rd.use_sequencer = False
|
||||||
|
|
||||||
|
#sampling
|
||||||
|
ev.taa_render_samples = 128#push up sample for shadow mainly
|
||||||
|
|
||||||
|
#AO
|
||||||
|
ev.use_gtao = True
|
||||||
|
ev.gtao_distance = 0.7
|
||||||
|
ev.gtao_factor = 0.7
|
||||||
|
ev.gtao_quality = 0.2
|
||||||
|
ev.use_gtao_bent_normals = False#bent normal makes it lighter than shadows !
|
||||||
|
#not sure...
|
||||||
|
ev.use_gtao_bounce = True#no bounce is darker... (less AO on claer object)
|
||||||
|
|
||||||
|
#setup color management
|
||||||
|
scene.view_settings.look = 'Filmic - Medium High Contrast'
|
||||||
|
|
||||||
|
#no disable auto folder opening after auto video making
|
||||||
|
if hasattr(scene, 'MVopen'):
|
||||||
|
scene.MVopen = False
|
||||||
|
|
||||||
|
def render_anim(GL=False):
|
||||||
|
#openGl render
|
||||||
|
if GL:
|
||||||
|
bpy.ops.render.opengl(animation=True, view_context=False)#view_context False > look throughcam
|
||||||
|
else:
|
||||||
|
bpy.ops.render.render(animation=True)
|
||||||
|
|
||||||
|
|
||||||
|
def check_name(name, fp):
|
||||||
|
filelist = [splitext(f)[0] for f in os.listdir(fp)]#raw names
|
||||||
|
if not name in filelist:
|
||||||
|
return name
|
||||||
|
|
||||||
|
ct = 2
|
||||||
|
new = name + '_' + str(ct).zfill(2)
|
||||||
|
while new in filelist:
|
||||||
|
new = name + '_' + str(ct).zfill(2)
|
||||||
|
ct+=1
|
||||||
|
if ct > 99:
|
||||||
|
return None
|
||||||
|
return new
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
def set_video_path():
|
||||||
|
setup_render_params(use_stamp=info['stamp'])
|
||||||
|
rd.image_settings.file_format = 'FFMPEG'
|
||||||
|
rd.image_settings.color_mode = 'RGB'
|
||||||
|
rd.ffmpeg.codec = 'H264'
|
||||||
|
rd.ffmpeg.constant_rate_factor = 'HIGH'#default 'MEDIUM'
|
||||||
|
rd.ffmpeg.format = 'MPEG4'#'MKV', 'QUICKTIME'
|
||||||
|
rd.ffmpeg.ffmpeg_preset = 'GOOD'#default = 'GOOD'(compromise), BEST(light - slow), REALTIME(fat-fast)'
|
||||||
|
|
||||||
|
ext = ''
|
||||||
|
if rd.ffmpeg.format == 'MPEG4':
|
||||||
|
ext = '.mp4'
|
||||||
|
elif rd.ffmpeg.format == 'QUICKTIME':
|
||||||
|
ext = '.mov'
|
||||||
|
elif rd.ffmpeg.format == 'MKV':
|
||||||
|
ext = '.mkv'
|
||||||
|
|
||||||
|
filename = splitext(basename(D.filepath))[0]
|
||||||
|
fp = join(dirname(D.filepath), 'images')
|
||||||
|
|
||||||
|
if not exists(fp):
|
||||||
|
print(f'not found : {fp} ')
|
||||||
|
return
|
||||||
|
|
||||||
|
filename = check_name(filename, fp)
|
||||||
|
if not filename:
|
||||||
|
print('name not available')
|
||||||
|
return
|
||||||
|
|
||||||
|
rd.filepath = join(dirname(D.filepath), 'images', filename + ext)
|
||||||
|
|
||||||
|
|
||||||
|
set_video_path()
|
||||||
|
|
||||||
|
|
||||||
|
'''
|
||||||
|
start = time()
|
||||||
|
render_anim(GL=False)
|
||||||
|
print(f'elapsed {time() - start}')
|
||||||
|
'''
|
|
@ -0,0 +1,39 @@
|
||||||
|
info = {
|
||||||
|
'icon' : 'RENDER_ANIMATION',
|
||||||
|
'description' : 'render timed',
|
||||||
|
}
|
||||||
|
|
||||||
|
import bpy
|
||||||
|
import os
|
||||||
|
from os import listdir
|
||||||
|
from os.path import join, dirname, basename, exists, isfile, isdir, splitext
|
||||||
|
import re, fnmatch, glob
|
||||||
|
from mathutils import Vector, Matrix
|
||||||
|
from math import radians, degrees
|
||||||
|
from time import time
|
||||||
|
import datetime
|
||||||
|
|
||||||
|
C = bpy.context
|
||||||
|
D = bpy.data
|
||||||
|
scn = scene = C.scene
|
||||||
|
rd = scn.render
|
||||||
|
|
||||||
|
|
||||||
|
def render_anim(GL=False):
|
||||||
|
#openGl render
|
||||||
|
if GL:
|
||||||
|
bpy.ops.render.opengl(animation=True, view_context=True)#view_context False > look throughcam
|
||||||
|
else:
|
||||||
|
bpy.ops.render.render(animation=True)
|
||||||
|
|
||||||
|
start = time()
|
||||||
|
|
||||||
|
render_anim()
|
||||||
|
|
||||||
|
secs = time() - start
|
||||||
|
timesec_str = f'elapsed {secs} seconds'
|
||||||
|
time_str = str(datetime.timedelta(seconds=secs))
|
||||||
|
|
||||||
|
print(timesec_str)
|
||||||
|
print(time_str)
|
||||||
|
C.window_manager.clipboard = time_str
|
|
@ -0,0 +1,39 @@
|
||||||
|
info = {
|
||||||
|
'icon' : 'RENDER_ANIMATION',
|
||||||
|
'description' : 'render this viewport openGL',
|
||||||
|
}
|
||||||
|
|
||||||
|
import bpy
|
||||||
|
import os
|
||||||
|
from os import listdir
|
||||||
|
from os.path import join, dirname, basename, exists, isfile, isdir, splitext
|
||||||
|
import re, fnmatch, glob
|
||||||
|
from mathutils import Vector, Matrix
|
||||||
|
from math import radians, degrees
|
||||||
|
from time import time
|
||||||
|
import datetime
|
||||||
|
|
||||||
|
C = bpy.context
|
||||||
|
D = bpy.data
|
||||||
|
scn = scene = C.scene
|
||||||
|
rd = scn.render
|
||||||
|
|
||||||
|
|
||||||
|
def render_anim(GL=False):
|
||||||
|
#openGl render
|
||||||
|
if GL:
|
||||||
|
bpy.ops.render.opengl(animation=True, view_context=True)#view_context False > look throughcam
|
||||||
|
else:
|
||||||
|
bpy.ops.render.render(animation=True)
|
||||||
|
|
||||||
|
start = time()
|
||||||
|
|
||||||
|
render_anim(GL=True)
|
||||||
|
|
||||||
|
secs = time() - start
|
||||||
|
timesec_str = f'elapsed {secs} seconds'
|
||||||
|
time_str = str(datetime.timedelta(seconds=secs))
|
||||||
|
|
||||||
|
print(timesec_str)
|
||||||
|
print(time_str)
|
||||||
|
C.window_manager.clipboard = time_str
|
|
@ -0,0 +1,62 @@
|
||||||
|
info = {
|
||||||
|
'icon' : 'RENDER_STILL',
|
||||||
|
'description' : 'render still image timed',
|
||||||
|
}
|
||||||
|
|
||||||
|
import bpy
|
||||||
|
import os
|
||||||
|
from os import listdir
|
||||||
|
from os.path import join, dirname, basename, exists, isfile, isdir, splitext
|
||||||
|
import re, fnmatch, glob
|
||||||
|
from mathutils import Vector, Matrix
|
||||||
|
from math import radians, degrees
|
||||||
|
from time import time
|
||||||
|
import datetime
|
||||||
|
|
||||||
|
C = bpy.context
|
||||||
|
D = bpy.data
|
||||||
|
scn = scene = C.scene
|
||||||
|
rd = scn.render
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
def pad_with_current_frame(match):
|
||||||
|
return str(C.scene.frame_current).zfill(len(match.group(0)))
|
||||||
|
|
||||||
|
def add_frame_padding(name):
|
||||||
|
'''
|
||||||
|
return string padded with current blender frame
|
||||||
|
if # found, use this padding to write theframe
|
||||||
|
'''
|
||||||
|
import re
|
||||||
|
if not '#' in name: return name + str(C.scene.frame_current).zfill(4)
|
||||||
|
# return re.sub(r'\#{1,10}', pad_with_current_frame, name)# all '#...' in string
|
||||||
|
return re.sub(r'\#{1,10}(?!.*\#)', pad_with_current_frame, name)# only last '#...'
|
||||||
|
|
||||||
|
|
||||||
|
def render(anim=False, GL=False):
|
||||||
|
if GL:#openGl render
|
||||||
|
bpy.ops.render.opengl(animation=anim, write_still=True, view_context=True)#view_context False > look throughcam
|
||||||
|
else:
|
||||||
|
bpy.ops.render.render(animation=anim, write_still=True)
|
||||||
|
|
||||||
|
start = time()
|
||||||
|
|
||||||
|
orgfp = C.scene.render.filepath
|
||||||
|
C.scene.render.filepath = add_frame_padding(C.scene.render.filepath)
|
||||||
|
|
||||||
|
try:
|
||||||
|
render(anim=False)
|
||||||
|
except Exception as e:
|
||||||
|
print(e)
|
||||||
|
finally:
|
||||||
|
C.scene.render.filepath = orgfp
|
||||||
|
|
||||||
|
secs = time() - start
|
||||||
|
timesec_str = f'elapsed {secs} seconds'
|
||||||
|
time_str = str(datetime.timedelta(seconds=secs))
|
||||||
|
|
||||||
|
print(timesec_str)
|
||||||
|
print(time_str)
|
||||||
|
C.window_manager.clipboard = time_str
|
||||||
|
|
|
@ -0,0 +1,109 @@
|
||||||
|
info = {
|
||||||
|
'icon' : 'PACKAGE',
|
||||||
|
'description' : 'pack all videos',
|
||||||
|
'dryrun' : False
|
||||||
|
}
|
||||||
|
|
||||||
|
import os, re, shutil
|
||||||
|
from os.path import dirname, basename, join, abspath, splitext, exists, isfile, isdir
|
||||||
|
from os import listdir
|
||||||
|
import datetime
|
||||||
|
|
||||||
|
|
||||||
|
dryrun = info['dryrun']
|
||||||
|
|
||||||
|
resid = re.compile(r'\d{2}-\d{3}-\d{2}$')
|
||||||
|
|
||||||
|
shots = '/z/___SERIE/WIND-UPS/episodes/00/shots/'
|
||||||
|
|
||||||
|
packzone = '/z/___SERIE/WIND-UPS/medias/pack'
|
||||||
|
old_folder = join(packzone, '_old')
|
||||||
|
|
||||||
|
new_list = []
|
||||||
|
warning = []
|
||||||
|
|
||||||
|
extension_filter = ('mp4', 'mov',)#, 'mkv'
|
||||||
|
exclusion_words = ('test', 'old',)#use this for loose word search
|
||||||
|
rexclude = re.compile(r'[ _-]old|test')#old if after separator (' _-') and word test anywhere (might be too loose)
|
||||||
|
|
||||||
|
for f in os.scandir(shots):
|
||||||
|
if not resid.match(f.name):continue
|
||||||
|
|
||||||
|
imgfp = f'/z/___SERIE/WIND-UPS/episodes/00/shots/{f.name}/lighting/images'
|
||||||
|
if not exists(imgfp):continue
|
||||||
|
|
||||||
|
#need to be video file type
|
||||||
|
all_videos = [v for v in os.scandir(imgfp) if v.name.lower().endswith(extension_filter) and not rexclude.search(v.name)]#any(x in v.name for x in exclusion_words)
|
||||||
|
if not all_videos:continue
|
||||||
|
|
||||||
|
print(f'\n{f.name}')
|
||||||
|
|
||||||
|
bytime = sorted(all_videos, key=lambda x: x.stat().st_mtime)
|
||||||
|
byname = sorted(all_videos, key=lambda x: x.name)
|
||||||
|
# print('bytime: ', bytime[-1].name)
|
||||||
|
# print('byname: ', byname[-1].name)
|
||||||
|
|
||||||
|
if bytime[-1] != byname[-1]:
|
||||||
|
warn = f'''/!\\ {f.name}
|
||||||
|
last by name and by modif time different in {imgfp}
|
||||||
|
by time : {bytime[-1].name} ({datetime.datetime.fromtimestamp(bytime[-1].stat().st_mtime)})
|
||||||
|
by name : {byname[-1].name} ({datetime.datetime.fromtimestamp(byname[-1].stat().st_mtime)})
|
||||||
|
'''
|
||||||
|
print(warn)
|
||||||
|
warning.append(warn)
|
||||||
|
|
||||||
|
good_file = bytime[-1]
|
||||||
|
topack = good_file.path
|
||||||
|
topackname = good_file.name
|
||||||
|
dest = join(packzone, topackname)
|
||||||
|
|
||||||
|
# run checks and copy if needed
|
||||||
|
if exists(dest):
|
||||||
|
dup = [basename(dest)]
|
||||||
|
else:
|
||||||
|
print(f"compare with {'_'.join(splitext(good_file.name)[0].split('_')[:3])}")
|
||||||
|
# ex : twu_00-008-01_lighting_v001.mp4 (split on third to)
|
||||||
|
dup = [i for i in listdir(packzone) if isfile(join(packzone, i)) and '_'.join(splitext(i)[0].split('_')[:3]) == '_'.join(splitext(good_file.name)[0].split('_')[:3])]
|
||||||
|
|
||||||
|
# check duplication against this file
|
||||||
|
if dup:
|
||||||
|
print(f'same shot already exists:{dup}')
|
||||||
|
newer = True
|
||||||
|
for i in dup:
|
||||||
|
dfp = join(packzone, i)
|
||||||
|
# print(f'{os.stat(dfp).st_mtime} VS file {good_file.stat().st_mtime}')
|
||||||
|
if os.stat(dfp).st_mtime >= good_file.stat().st_mtime:
|
||||||
|
# print(f'{i} is same or most recent')
|
||||||
|
newer = False
|
||||||
|
|
||||||
|
if not newer:
|
||||||
|
print(f'{good_file.name} is not newer, skip')
|
||||||
|
continue
|
||||||
|
|
||||||
|
#src is most recent, do the copy and erase/move older
|
||||||
|
for d in dup:
|
||||||
|
if not exists(old_folder):os.mkdir(old_folder)
|
||||||
|
old_src = join(packzone, d)
|
||||||
|
old_dest = join(old_folder, d)
|
||||||
|
if exists(old_dest):
|
||||||
|
print(f' - deleting {d} (already in _old)')
|
||||||
|
if not dryrun: os.remove(old_src)
|
||||||
|
else:
|
||||||
|
print(f' - moving {d} to _old')
|
||||||
|
if not dryrun: shutil.move(old_src, old_dest)
|
||||||
|
|
||||||
|
# if made it here, safely do the copy
|
||||||
|
print(f'- packing new file : {topack}')
|
||||||
|
if not dryrun: shutil.copy2(topack, dest)
|
||||||
|
new_list.append(f'{topackname} : {topack}')
|
||||||
|
|
||||||
|
|
||||||
|
print('DONE\n----')
|
||||||
|
|
||||||
|
if warning:
|
||||||
|
print('\nWarnings')
|
||||||
|
for w in warning: print(w)
|
||||||
|
|
||||||
|
if new_list:
|
||||||
|
print(f'\n{len(new_list)} New:')
|
||||||
|
for n in new_list: print(n)
|
|
@ -0,0 +1,119 @@
|
||||||
|
info = {
|
||||||
|
'icon' : 'COPYDOWN',
|
||||||
|
'description' : 'Go in render node mode waiting for jobs in watchfolder',
|
||||||
|
'checkfiles' : False,
|
||||||
|
'stamp' : False,
|
||||||
|
'cmds' : ''
|
||||||
|
}
|
||||||
|
import os
|
||||||
|
from os import listdir
|
||||||
|
from os.path import join, dirname, basename, exists, isfile, isdir, splitext
|
||||||
|
import re
|
||||||
|
from time import sleep
|
||||||
|
import time
|
||||||
|
import datetime
|
||||||
|
## Blender
|
||||||
|
import bpy
|
||||||
|
from mathutils import Vector, Matrix
|
||||||
|
C = bpy.context
|
||||||
|
D = bpy.data
|
||||||
|
scn = scene = C.scene
|
||||||
|
|
||||||
|
|
||||||
|
def submit_to_watchfolder(checkfiles=False):
|
||||||
|
'''submitter (with or without file_copy)'''
|
||||||
|
|
||||||
|
if D.is_dirty or not D.is_saved:
|
||||||
|
print('!!! Not submitted : File must be saved first')
|
||||||
|
return
|
||||||
|
|
||||||
|
totalframes = scn.frame_end - scn.frame_start
|
||||||
|
if not overwrite:
|
||||||
|
#check if output path exists and have file
|
||||||
|
out = scn.render.filepath
|
||||||
|
outfolder = dirname(out)
|
||||||
|
if exists(outfolder):
|
||||||
|
outname = basename(out)
|
||||||
|
refile = re.compile(outname.rstrip('#') + r'\d{4}')
|
||||||
|
outlist = [f for f in listdir(outfolder) if refile.match(f)]
|
||||||
|
if outlist:
|
||||||
|
print(f'!!! Abort submission : Overwrite is False and {len(outlist)} files already exists ({totalframes} wanted)')
|
||||||
|
return
|
||||||
|
|
||||||
|
print(f'{totalframes} frames to render')
|
||||||
|
watchfolder = '/z/___SERIE/WIND-UPS/episodes/00/watchfolder'
|
||||||
|
if not exists(watchfolder):
|
||||||
|
print(f'watchfolder not found at {watchfolder}')
|
||||||
|
watchfolder = '/mnt/admserveur/prod/___SERIE/WIND-UPS/episodes/00/watchfolder'
|
||||||
|
if not exists(watchfolder):
|
||||||
|
print(f'ABORT : watchfolder not found at {watchfolder}')
|
||||||
|
return 1
|
||||||
|
|
||||||
|
#save a copy to the file ?
|
||||||
|
|
||||||
|
the_file = D.filepath
|
||||||
|
finalname = basename(D.filepath)
|
||||||
|
|
||||||
|
'''
|
||||||
|
if use_copy:
|
||||||
|
#save a copy
|
||||||
|
head, tail = os.path.split(D.filepath)
|
||||||
|
name, ext = splitext(tail)
|
||||||
|
uid = datetime.datetime.now().strftime('_rdtemp%Y%m%d%H%M%S')
|
||||||
|
finalname = name + uid + ext
|
||||||
|
the_file = join(head, finalname)
|
||||||
|
bpy.ops.wm.save_as_mainfile(filepath=the_file, copy=True)
|
||||||
|
|
||||||
|
link = join(watchfolder, finalname)
|
||||||
|
print(f'Create symlink:\n src: {finalname}\n dest: {link}')#the_file, complete path to finalname
|
||||||
|
os.symlink(the_file, link)#impossible to create symlink... so fuck-it.
|
||||||
|
'''
|
||||||
|
|
||||||
|
noext = splitext(finalname)[0]
|
||||||
|
|
||||||
|
stby = join(watchfolder, 'stby--' + noext)# + '.txt'
|
||||||
|
todo = join(watchfolder, 'todo--' + noext)# + '.txt'
|
||||||
|
running = join(watchfolder, 'runn--' + noext)# + '.txt'
|
||||||
|
done = join(watchfolder, 'done--' + noext)# + '.txt'
|
||||||
|
|
||||||
|
bad = 0
|
||||||
|
statelist = []
|
||||||
|
if exists(stby):
|
||||||
|
statelist.append(basename(stby))
|
||||||
|
if exists(todo):
|
||||||
|
statelist.append(basename(todo))
|
||||||
|
if exists(running):
|
||||||
|
statelist.append(basename(running))
|
||||||
|
if exists(done):
|
||||||
|
statelist.append(basename(done))
|
||||||
|
|
||||||
|
for f in os.listdir(watchfolder):
|
||||||
|
if splitext(f)[0][len('stby--'):] == noext:
|
||||||
|
print('Something already exists')
|
||||||
|
|
||||||
|
|
||||||
|
if exists(todo):
|
||||||
|
bad = 1
|
||||||
|
print(f'{noext}: Job already submitted')
|
||||||
|
|
||||||
|
if len(statelist) > 1:
|
||||||
|
print('problem : job exists in multiple states')
|
||||||
|
for state in statelist:
|
||||||
|
print(state)
|
||||||
|
bad = 1
|
||||||
|
|
||||||
|
if bad:
|
||||||
|
print('skip')
|
||||||
|
return
|
||||||
|
|
||||||
|
with open(todo, 'w') as fd:
|
||||||
|
fd.write(the_file+'\n')
|
||||||
|
if info['stamp']:
|
||||||
|
fd.write('stamp\n')
|
||||||
|
if info['cmds']:
|
||||||
|
fd.write(info['cmds'])
|
||||||
|
|
||||||
|
print(f'Created job {todo}')
|
||||||
|
|
||||||
|
print(f'Submit {basename(D.filepath)}')
|
||||||
|
submit_to_watchfolder(checkfiles=info['checkfiles'])
|
|
@ -0,0 +1,215 @@
|
||||||
|
info = {
|
||||||
|
'icon' : 'RENDER_RESULT',
|
||||||
|
'description' : 'Pass in render node watchmode (freeze and get job from watchfolder when a todo is available)',
|
||||||
|
}
|
||||||
|
|
||||||
|
import os
|
||||||
|
from os import listdir
|
||||||
|
from os.path import join, dirname, basename, exists, isfile, isdir, splitext
|
||||||
|
import re, fnmatch, glob
|
||||||
|
from math import radians, degrees
|
||||||
|
from time import sleep
|
||||||
|
import datetime
|
||||||
|
import time
|
||||||
|
import platform
|
||||||
|
|
||||||
|
# Blender
|
||||||
|
import bpy
|
||||||
|
from mathutils import Vector, Matrix
|
||||||
|
C = bpy.context
|
||||||
|
D = bpy.data
|
||||||
|
scene = C.scene
|
||||||
|
|
||||||
|
## General timer to disable ?
|
||||||
|
## How to push a key to stop the mega-loop ? (or barbaric ctrl-C in cmd)
|
||||||
|
|
||||||
|
def logit(message):
|
||||||
|
try:
|
||||||
|
print(message)
|
||||||
|
with open(log, 'a') as fd:
|
||||||
|
if not message.endswith('\n'):
|
||||||
|
message += '\n'
|
||||||
|
fd.write(message)#add trailing new line
|
||||||
|
except Exception as e:
|
||||||
|
print('Log failed', e)
|
||||||
|
|
||||||
|
def set_stamp(active_note=False):
|
||||||
|
rd = bpy.context.scene.render
|
||||||
|
rd.use_stamp = True
|
||||||
|
|
||||||
|
## Enable
|
||||||
|
rd.use_stamp_filename = True
|
||||||
|
rd.use_stamp_frame = True
|
||||||
|
rd.use_stamp_render_time = True
|
||||||
|
rd.use_stamp_time = True
|
||||||
|
rd.use_stamp_date = True
|
||||||
|
|
||||||
|
## Disable
|
||||||
|
rd.use_stamp_sequencer_strip = False
|
||||||
|
rd.use_stamp_marker = False
|
||||||
|
rd.use_stamp_scene = False
|
||||||
|
rd.use_stamp_lens = False
|
||||||
|
rd.use_stamp_camera = False
|
||||||
|
rd.use_stamp_hostname = False
|
||||||
|
rd.use_stamp_memory = False
|
||||||
|
rd.use_stamp_frame_range = False
|
||||||
|
|
||||||
|
## Notes
|
||||||
|
rd.use_stamp_note = active_note
|
||||||
|
|
||||||
|
if rd.use_stamp_note:
|
||||||
|
info = D.texts.get('info')
|
||||||
|
txt = ''
|
||||||
|
if info:
|
||||||
|
for l in info.lines[:2]:
|
||||||
|
if 'Created from' in l.body:
|
||||||
|
txt = l.body
|
||||||
|
if txt:
|
||||||
|
rd.stamp_note_text = txt
|
||||||
|
else:#disable notes
|
||||||
|
rd.use_stamp_note = False
|
||||||
|
|
||||||
|
def run(fp):
|
||||||
|
running = fp.replace('todo--', 'runn--')
|
||||||
|
|
||||||
|
stamp = False
|
||||||
|
warn = []
|
||||||
|
# Check if a running job already exists
|
||||||
|
if exists(running):
|
||||||
|
logit(f'Problem ! job already exists : {running}')
|
||||||
|
return 1
|
||||||
|
|
||||||
|
# Get blend path
|
||||||
|
with open(fp, 'r') as fd:
|
||||||
|
# blend = fd.read().strip()
|
||||||
|
lines = fd.readlines()
|
||||||
|
blend = lines[0].strip()
|
||||||
|
stamp = 'stamp' in lines
|
||||||
|
for l in lines[1:]:#skip filepath
|
||||||
|
if '=' in l:#skip simple keyword qand focus on assignation
|
||||||
|
try:
|
||||||
|
exec(l)
|
||||||
|
except Exception as e:
|
||||||
|
print(f'could not exec {l}:\n->{e}')
|
||||||
|
warn.append(f'could not exec {l}:\n->{e}')
|
||||||
|
|
||||||
|
if not blend:
|
||||||
|
logit(f'Problem reading {fp}')
|
||||||
|
return 1
|
||||||
|
|
||||||
|
# check path
|
||||||
|
logit(f'Target blend : {blend}')
|
||||||
|
if not exists(blend):
|
||||||
|
logit(f'ERROR ! job {basename(running)} give an invalid filepath')
|
||||||
|
#delete/rename running job
|
||||||
|
os.rename(running, running.replace('runn--', 'error-'))
|
||||||
|
return 1
|
||||||
|
|
||||||
|
# Pass on job mode
|
||||||
|
try:
|
||||||
|
os.rename(fp, running)
|
||||||
|
except Exception as e:
|
||||||
|
logit(f'Could not rename job to running mode : {fp} ')
|
||||||
|
return 1
|
||||||
|
|
||||||
|
|
||||||
|
# write the timestamp and launch the job
|
||||||
|
with open(running, 'a') as fd:
|
||||||
|
startmess = f'starting : {str(datetime.datetime.now())}\n'
|
||||||
|
logit(startmess)
|
||||||
|
fd.write(startmess)
|
||||||
|
|
||||||
|
# Blender time ! open, check output and render the job
|
||||||
|
|
||||||
|
start_time = time.time()# get start time
|
||||||
|
bpy.ops.wm.open_mainfile(filepath=blend)
|
||||||
|
scn = bpy.context.scene
|
||||||
|
#here change Ao/filepath/stuff if needed
|
||||||
|
if stamp:
|
||||||
|
set_stamp(active_note=True)
|
||||||
|
else:
|
||||||
|
bpy.context.scene.render.use_stamp = False
|
||||||
|
|
||||||
|
bpy.ops.render.render(animation=True)#anim render
|
||||||
|
print('Rendering done, launching make_video in 2 sec...')
|
||||||
|
## log end time and exit
|
||||||
|
elapsed_time = time.time() - start_time# seconds
|
||||||
|
full_time = str(datetime.timedelta(seconds=elapsed_time))# hh:mm:ss format
|
||||||
|
timecode = datetime.datetime.now().strftime('%m%d%H%M')#timecode format '02041139' month+day+hour+minutes
|
||||||
|
sleep(2)
|
||||||
|
|
||||||
|
#no disable auto folder opening after auto video making
|
||||||
|
if hasattr(scn, 'MVopen'):
|
||||||
|
scn.MVopen = False
|
||||||
|
|
||||||
|
bpy.ops.render.make_video()
|
||||||
|
|
||||||
|
with open(running, 'a') as fd:
|
||||||
|
endmess = f'ended : {str(datetime.datetime.now())}\nrender time: {full_time}\n'
|
||||||
|
logit(endmess)
|
||||||
|
fd.write(endmess)
|
||||||
|
if warn:
|
||||||
|
fd.write('\n'.join(warn)+'\n')
|
||||||
|
|
||||||
|
# rename and move into done folder
|
||||||
|
donename = basename(running).replace('runn--', 'done--') + f'__{timecode}'#.replace('.txt', f'__{timecode}.txt')
|
||||||
|
done = join(donefolder, donename)
|
||||||
|
|
||||||
|
try:
|
||||||
|
os.rename(running, done)
|
||||||
|
except Exception as e:
|
||||||
|
logit(f'Could not move/rename run job to done : {done} (from {running}) ')
|
||||||
|
return
|
||||||
|
|
||||||
|
print(f'Finished job {basename(running)}, Continue in 5 seconds')
|
||||||
|
sleep(5)
|
||||||
|
return 0
|
||||||
|
|
||||||
|
###================
|
||||||
|
|
||||||
|
def watchmode():
|
||||||
|
if not bpy.data.is_saved or bpy.data.is_dirty:
|
||||||
|
print('You need to start from a saved blender file (preferably empty)')
|
||||||
|
return
|
||||||
|
|
||||||
|
if not exists(watchfolder):
|
||||||
|
print(f'!!!! Abort, watchfolder ({watchfolder}) not found')
|
||||||
|
return
|
||||||
|
|
||||||
|
if not exists(logfolder):
|
||||||
|
os.mkdir(logfolder)
|
||||||
|
print('log folder created')
|
||||||
|
|
||||||
|
while True:#infinite loop
|
||||||
|
job_status = 1
|
||||||
|
|
||||||
|
todolist = [f for f in os.listdir(watchfolder) if f.startswith('todo--')]
|
||||||
|
if todolist:
|
||||||
|
first = todolist[0]
|
||||||
|
print(f'{len(todolist)} todo, starting: {first}')
|
||||||
|
fp = join(watchfolder, first)
|
||||||
|
job_status = run(fp)
|
||||||
|
print('job_status: ', job_status)
|
||||||
|
#reopen folder
|
||||||
|
bpy.ops.wm.open_mainfile(filepath=org_blend)
|
||||||
|
|
||||||
|
sleep(300)#5min wait before looping
|
||||||
|
|
||||||
|
|
||||||
|
org_blend = bpy.data.filepath
|
||||||
|
machine = platform.node().split('.')[0]
|
||||||
|
if machine:
|
||||||
|
machine = 'node_' + machine + '.txt'
|
||||||
|
if not machine:
|
||||||
|
print('Cannot find machine name')
|
||||||
|
machine = 'nodes.txt'
|
||||||
|
|
||||||
|
print(f'use machine name : {machine}')
|
||||||
|
|
||||||
|
watchfolder = '/z/___SERIE/WIND-UPS/episodes/00/watchfolder'
|
||||||
|
donefolder = join(watchfolder, 'done')
|
||||||
|
logfolder = join(watchfolder, 'logs')
|
||||||
|
log = join(logfolder, machine)
|
||||||
|
|
||||||
|
watchmode()
|
||||||
|
print('End of batch.\nEOF')
|
|
@ -0,0 +1,30 @@
|
||||||
|
# coding: utf-8
|
||||||
|
import bpy
|
||||||
|
import os
|
||||||
|
from os import listdir
|
||||||
|
from os.path import join, dirname, basename, exists, isfile, isdir, splitext
|
||||||
|
import re, fnmatch, glob
|
||||||
|
from mathutils import Vector, Matrix
|
||||||
|
from math import radians, degrees
|
||||||
|
C = bpy.context
|
||||||
|
D = bpy.data
|
||||||
|
scene = C.scene
|
||||||
|
|
||||||
|
print('====')
|
||||||
|
|
||||||
|
'''
|
||||||
|
for i in range(0, 8):
|
||||||
|
ao = i * 0.25
|
||||||
|
scene.eevee.gtao_distance = ao
|
||||||
|
scene.render.filepath = join(dirname(D.filepath), f'{splitext(basename(D.filepath))[0]}-{scene.frame_current}_AO{i}-{ao}')
|
||||||
|
bpy.ops.render.render(animation=False, write_still=True)
|
||||||
|
'''
|
||||||
|
|
||||||
|
for i in range(0, 8):
|
||||||
|
fac = 1 - i * 0.1
|
||||||
|
#scene.eevee.gtao_distance = ao
|
||||||
|
scene.eevee.gtao_factor = fac
|
||||||
|
scene.render.filepath = join(dirname(D.filepath), f'{splitext(basename(D.filepath))[0]}-{scene.frame_current}_AOfac{i}-{fac}')
|
||||||
|
bpy.ops.render.render(animation=False, write_still=True)
|
||||||
|
|
||||||
|
print('done')
|
|
@ -0,0 +1,37 @@
|
||||||
|
# coding: utf-8
|
||||||
|
import bpy
|
||||||
|
import os
|
||||||
|
from os import listdir
|
||||||
|
from os.path import join, dirname, basename, exists, isfile, isdir, splitext
|
||||||
|
import re, fnmatch, glob
|
||||||
|
from mathutils import Vector, Matrix
|
||||||
|
from math import radians, degrees
|
||||||
|
C = bpy.context
|
||||||
|
D = bpy.data
|
||||||
|
scene = C.scene
|
||||||
|
|
||||||
|
print('====')
|
||||||
|
def more_sss(val=0.04):
|
||||||
|
for mat in D.materials:
|
||||||
|
if not mat.use_nodes:continue
|
||||||
|
nodes = mat.node_tree
|
||||||
|
if not nodes: continue
|
||||||
|
nodes = nodes.nodes
|
||||||
|
|
||||||
|
for n in nodes:
|
||||||
|
if n.type == 'GROUP':
|
||||||
|
sss = n.inputs.get('Subsurface')
|
||||||
|
if not sss: continue
|
||||||
|
#print(f'{mat.name} : {sss.default_value}')
|
||||||
|
current = sss.default_value
|
||||||
|
new = current + val
|
||||||
|
print(f'{mat.name} : {current} >> {new}')
|
||||||
|
sss.default_value = new
|
||||||
|
|
||||||
|
|
||||||
|
value = 0.06
|
||||||
|
for i in range(0, 8):
|
||||||
|
scene.render.filepath = join(dirname(D.filepath), f'{splitext(basename(D.filepath))[0]}-{scene.frame_current}_sss_plus_0.{str(i*6).zfill(2)}')
|
||||||
|
bpy.ops.render.render(animation=False, write_still=True)
|
||||||
|
more_sss(value)
|
||||||
|
|
|
@ -0,0 +1,32 @@
|
||||||
|
# coding: utf-8
|
||||||
|
import bpy
|
||||||
|
import os
|
||||||
|
from os import listdir
|
||||||
|
from os.path import join, dirname, basename, exists, isfile, isdir, splitext
|
||||||
|
import re, fnmatch, glob
|
||||||
|
from mathutils import Vector, Matrix
|
||||||
|
from math import radians, degrees
|
||||||
|
C = bpy.context
|
||||||
|
D = bpy.data
|
||||||
|
scene = C.scene
|
||||||
|
|
||||||
|
print('====')
|
||||||
|
def print_sss():
|
||||||
|
for mat in D.materials:
|
||||||
|
if not mat.use_nodes:continue
|
||||||
|
nodes = mat.node_tree
|
||||||
|
if not nodes: continue
|
||||||
|
nodes = nodes.nodes
|
||||||
|
|
||||||
|
for n in nodes:
|
||||||
|
if n.type == 'GROUP':
|
||||||
|
sss = n.inputs.get('Subsurface')
|
||||||
|
if not sss: continue
|
||||||
|
print(f'{mat.name} : {sss.default_value}')
|
||||||
|
if n.type == 'BSDF_PRINCIPLED':
|
||||||
|
sss = n.inputs.get('Subsurface')
|
||||||
|
if not sss: continue
|
||||||
|
print(f'PRINCIPLED : {mat.name} : {sss.default_value}')
|
||||||
|
|
||||||
|
|
||||||
|
print_sss()
|
|
@ -0,0 +1,58 @@
|
||||||
|
info = {
|
||||||
|
'icon' : 'LINENUMBERS_ON',
|
||||||
|
'description' : 'Note and render',
|
||||||
|
'render' : False,
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
import bpy
|
||||||
|
import os
|
||||||
|
from os import listdir
|
||||||
|
from os.path import join, dirname, basename, exists, isfile, isdir, splitext
|
||||||
|
import re, fnmatch, glob
|
||||||
|
from mathutils import Vector, Matrix
|
||||||
|
from math import radians, degrees
|
||||||
|
C = bpy.context
|
||||||
|
D = bpy.data
|
||||||
|
scene = C.scene
|
||||||
|
|
||||||
|
print('====')
|
||||||
|
|
||||||
|
scene.render.use_stamp_note = True
|
||||||
|
|
||||||
|
dist = scene.eevee.gtao_distance
|
||||||
|
fac = scene.eevee.gtao_factor
|
||||||
|
print('dist: ', dist)
|
||||||
|
print('fac: ', fac)
|
||||||
|
|
||||||
|
dist = round(dist, 2)
|
||||||
|
fac = round(fac, 2)
|
||||||
|
|
||||||
|
scene.render.stamp_note_text = f'AO dist {dist} fac {fac}'
|
||||||
|
|
||||||
|
if scene.view_settings.look != 'Filmic - Medium Contrast':
|
||||||
|
scene.render.stamp_note_text += f' - {scene.view_settings.look}'
|
||||||
|
|
||||||
|
|
||||||
|
org = scene.render.filepath
|
||||||
|
print(f'filepath was : {org}')
|
||||||
|
|
||||||
|
## full filename
|
||||||
|
# scene.render.filepath = join(dirname(D.filepath), f'{splitext(basename(D.filepath))[0]}-{scene.frame_current}_AO-dist{dist}-fac{fac}')
|
||||||
|
|
||||||
|
## custom filename
|
||||||
|
ep = int(re.search(r"(\d{3})", splitext(basename(D.filepath))[0]).group(1))
|
||||||
|
print('ep: ', ep)
|
||||||
|
scene.render.filepath = join(dirname(D.filepath), f'twu_{ep}-{scene.frame_current}_AO-dist{dist}-fac{fac}')
|
||||||
|
|
||||||
|
i = 2#increment if needed
|
||||||
|
while exists(scene.render.filepath + '.png'):
|
||||||
|
scene.render.filepath = join(dirname(D.filepath), f'twu_{ep}-{scene.frame_current}_AO-dist{dist}-fac{fac}_{i}')
|
||||||
|
|
||||||
|
if info['render']:
|
||||||
|
bpy.ops.render.render(animation=False, write_still=True)
|
||||||
|
# restore old sauce ?
|
||||||
|
scene.render.filepath = org
|
||||||
|
|
||||||
|
print('done')
|
||||||
|
|
|
@ -0,0 +1,44 @@
|
||||||
|
info = {
|
||||||
|
'icon' : 'FILE_TEXT',
|
||||||
|
'description' : 'explore API in console at datapath of the popup',
|
||||||
|
'data_path' : ''
|
||||||
|
}
|
||||||
|
|
||||||
|
import bpy, mathutils
|
||||||
|
|
||||||
|
exclude = (
|
||||||
|
### add lines here to exclude specific attribute
|
||||||
|
'bl_rna', 'identifier','name_property','rna_type','properties',#basic
|
||||||
|
## To avoid recursion/crash on direct object call (comment for API check on deeper props)
|
||||||
|
'data', 'edges', 'faces', 'edge_keys', 'polygons', 'loops', 'face_maps', 'original',
|
||||||
|
## Avoid some specific properties
|
||||||
|
#'matrix_local', 'matrix_parent_inverse', 'matrix_basis','location','rotation_euler', 'rotation_quaternion', 'rotation_axis_angle', 'scale', 'translation',
|
||||||
|
)
|
||||||
|
|
||||||
|
def list_attr(path, ct=0):
|
||||||
|
for attr in dir(eval(path)):
|
||||||
|
if not attr.startswith('__') and not attr in exclude:
|
||||||
|
try:
|
||||||
|
value = getattr(eval(path),attr)
|
||||||
|
except AttributeError:
|
||||||
|
value = None
|
||||||
|
if value != None:
|
||||||
|
if not callable(value):
|
||||||
|
if type(value) in ( type(0),type(0.0),type(True),type('str'),type(mathutils.Vector()),type(mathutils.Color()), type(mathutils.Matrix()) ):
|
||||||
|
print(ct*' ' + attr, value)
|
||||||
|
else:
|
||||||
|
print(ct*' ' + attr,value,type(value))
|
||||||
|
ct+=1
|
||||||
|
# print (ct*' ' + '>')
|
||||||
|
list_attr('%s.%s'%(path,attr), ct)#Comment this line to kill recursion
|
||||||
|
|
||||||
|
|
||||||
|
print('---')
|
||||||
|
# write datapath as string. ex : "bpy.data.objects['Cube'].modifiers['Softbody']"
|
||||||
|
|
||||||
|
dt = info['data_path']
|
||||||
|
if dt:
|
||||||
|
list_attr(dt)
|
||||||
|
print ('Done')
|
||||||
|
else:
|
||||||
|
print('no data path given')
|
|
@ -0,0 +1,56 @@
|
||||||
|
info = {
|
||||||
|
'icon' : 'EVENT_X',
|
||||||
|
'description' : 'Delete all blend backup version (.blend1 and next backups) in passed directory',
|
||||||
|
'folderpath' : '',
|
||||||
|
'recursive' : True,
|
||||||
|
'test' : False,
|
||||||
|
}
|
||||||
|
|
||||||
|
# coding: utf-8
|
||||||
|
import bpy
|
||||||
|
import os
|
||||||
|
import re, fnmatch, glob
|
||||||
|
from pathlib import Path
|
||||||
|
import shutil
|
||||||
|
C = bpy.context
|
||||||
|
D = bpy.data
|
||||||
|
scene = C.scene
|
||||||
|
|
||||||
|
|
||||||
|
def kill_blend_backup_version(fp, recursive=True, test=False):
|
||||||
|
if not fp:
|
||||||
|
print('ERROR', 'no filepath given')
|
||||||
|
return
|
||||||
|
|
||||||
|
re_blendbackup = re.compile(r'\.blend\d+')
|
||||||
|
fpo = Path(fp)
|
||||||
|
|
||||||
|
if not fpo.exists():
|
||||||
|
print('ERROR', f'invalid path: {fp}')
|
||||||
|
return
|
||||||
|
|
||||||
|
ct = 0
|
||||||
|
print(f'Removing blend backup files in : {fpo}')
|
||||||
|
if recursive:
|
||||||
|
for root, dirs, files in os.walk(fp):
|
||||||
|
for name in files:
|
||||||
|
fo = Path(root) / name
|
||||||
|
if re_blendbackup.match(fo.suffix):
|
||||||
|
ct += 1
|
||||||
|
print(fo)
|
||||||
|
if not test:
|
||||||
|
fo.unlink()
|
||||||
|
|
||||||
|
else:
|
||||||
|
for f in os.listdir(fp):
|
||||||
|
fo = Path(fp) / f
|
||||||
|
if re_blendbackup.match(fo.suffix):
|
||||||
|
ct += 1
|
||||||
|
print(fo)
|
||||||
|
if not test:
|
||||||
|
fo.unlink()
|
||||||
|
|
||||||
|
if ct:
|
||||||
|
print('INFO', f'Deleted {ct} file(s)')
|
||||||
|
|
||||||
|
kill_blend_backup_version(info['folderpath'], recursive = info['recursive'], test = info['test'])
|
|
@ -0,0 +1,71 @@
|
||||||
|
info = {
|
||||||
|
'icon' : 'LIBRARY_DATA_BROKEN',
|
||||||
|
'description' : 'Correct path (print in console, apply as option) when weirdly auto added unwanted parent directory (still break rotate order for armatures...)',
|
||||||
|
'apply' : False
|
||||||
|
}
|
||||||
|
|
||||||
|
import bpy
|
||||||
|
import os
|
||||||
|
from os import listdir
|
||||||
|
from os.path import join, dirname, basename, exists, isfile, isdir, splitext, abspath
|
||||||
|
import re, fnmatch, glob
|
||||||
|
from mathutils import Vector, Matrix
|
||||||
|
from math import radians, degrees
|
||||||
|
C = bpy.context
|
||||||
|
D = bpy.data
|
||||||
|
scene = C.scene
|
||||||
|
|
||||||
|
""" rel = D.libraries[0].filepath.replace('//','')
|
||||||
|
parent = rel.count('..')
|
||||||
|
print('parent: ', parent)
|
||||||
|
abspath(join(dirname(D.filepath), rel)) """
|
||||||
|
|
||||||
|
|
||||||
|
## relpath
|
||||||
|
|
||||||
|
def to_abs(rfp):
|
||||||
|
return abspath(bpy.path.abspath(rfp))
|
||||||
|
|
||||||
|
print('\n--- Cheking libraries filepath ---')
|
||||||
|
apply = False
|
||||||
|
apply = info['apply']#get it from info dic
|
||||||
|
|
||||||
|
folder = dirname(D.filepath)
|
||||||
|
for lib in D.libraries:
|
||||||
|
libfp = lib.filepath
|
||||||
|
if exists(to_abs(libfp)):
|
||||||
|
print('clean :', libfp)
|
||||||
|
else:
|
||||||
|
print('broken:', libfp)
|
||||||
|
pct = libfp.count('..')
|
||||||
|
for i in range(1, pct+1):
|
||||||
|
newrel = ('').join(libfp.split('../',i))
|
||||||
|
if exists(to_abs(newrel)):
|
||||||
|
print(f'found at -{i} parent folder')
|
||||||
|
print(f'-> {newrel}')
|
||||||
|
if apply:
|
||||||
|
lib.filepath = newrel
|
||||||
|
print()
|
||||||
|
break
|
||||||
|
if i == pct:
|
||||||
|
print(f'tested with {pct} folder up and nothing found')
|
||||||
|
print('Done.')
|
||||||
|
|
||||||
|
'''
|
||||||
|
## abspath technique
|
||||||
|
folder = dirname(D.filepath)
|
||||||
|
for lib in D.libraries:
|
||||||
|
curfp = abspath(bpy.path.abspath(lib.filepath))
|
||||||
|
if exists(curfp):
|
||||||
|
print('clean :', lib.filepath)
|
||||||
|
else:
|
||||||
|
print('broken:', lib.filepath)
|
||||||
|
pct = lib.filepath.count('..')
|
||||||
|
print('up folder: ', pct)
|
||||||
|
endlib = lib.filepath.lstrip(os.sep+'.')
|
||||||
|
for i in range(1, pct+1):
|
||||||
|
start = '/'.join(folder.split(os.sep)[:-i])
|
||||||
|
nfp = join(start, endlib)
|
||||||
|
if exists(nfp):
|
||||||
|
print(f'{i} parent folder VS {pct} in current')
|
||||||
|
'''
|
|
@ -0,0 +1,60 @@
|
||||||
|
info = {
|
||||||
|
'icon' : 'ARMATURE_DATA',
|
||||||
|
'description' : 'run some check on proxys and armautre and print in console',
|
||||||
|
}
|
||||||
|
|
||||||
|
# coding: utf-8
|
||||||
|
import bpy
|
||||||
|
import os
|
||||||
|
from os import listdir
|
||||||
|
from os.path import join, dirname, basename, exists, isfile, isdir, splitext
|
||||||
|
import re, fnmatch, glob
|
||||||
|
from mathutils import Vector, Matrix
|
||||||
|
from math import radians, degrees
|
||||||
|
C = bpy.context
|
||||||
|
D = bpy.data
|
||||||
|
scene = bpy.context.scene
|
||||||
|
|
||||||
|
print('\nCollection instance check -- ')
|
||||||
|
problems = []
|
||||||
|
for o in bpy.context.scene.objects:
|
||||||
|
if o.instance_collection:
|
||||||
|
for instob in o.instance_collection.objects:
|
||||||
|
badname = withproxy = withcolproxy = alert = ''
|
||||||
|
|
||||||
|
if instob.type == 'ARMATURE':
|
||||||
|
mess = f'{o.name} -> rig: {instob.name}'
|
||||||
|
arm = instob.data
|
||||||
|
# print("arm", arm.name)#Dbg
|
||||||
|
if instob.name != arm.name:
|
||||||
|
badname = 1
|
||||||
|
mess += f' != arm data: {arm.name}'
|
||||||
|
rigname = arm.name.replace('_rig','_proxy') if 'rig' in arm.name else arm.name + '_proxy'
|
||||||
|
|
||||||
|
proxy = bpy.context.scene.objects.get(rigname)
|
||||||
|
|
||||||
|
if proxy:
|
||||||
|
withproxy = 1
|
||||||
|
mess += ' ->> Proxy in scene'
|
||||||
|
if not proxy:
|
||||||
|
collec_proxy = instob.name.replace('_rig','_proxy') if 'rig' in instob.name else instob.name + '_proxy'
|
||||||
|
proxy = bpy.context.scene.objects.get(collec_proxy)
|
||||||
|
if proxy :
|
||||||
|
withcolproxy = 1
|
||||||
|
mess += ' ->> Proxy from colname in scene'
|
||||||
|
|
||||||
|
if proxy and proxy.animation_data and proxy.animation_data.action and len(proxy.animation_data.action.fcurves) > 0:
|
||||||
|
mess += ' (animated)'
|
||||||
|
|
||||||
|
if badname and withcolproxy:
|
||||||
|
alert = '!!!'
|
||||||
|
elif badname and withproxy:
|
||||||
|
alert = '!!'
|
||||||
|
elif badname:
|
||||||
|
alert = '!'
|
||||||
|
mess = f'{alert} {mess}'
|
||||||
|
if badname or withproxy or withcolproxy:
|
||||||
|
problems.append(mess)
|
||||||
|
print(mess)
|
||||||
|
|
||||||
|
print('Done')
|
|
@ -0,0 +1,140 @@
|
||||||
|
info = {
|
||||||
|
'icon' : 'DRIVER',
|
||||||
|
'description' : 'create visibility on driver objects or armature selection from a bone of a rig',
|
||||||
|
'prop_rig' : 'name_of_the_rig'
|
||||||
|
'prop_name' : 'hide_something'
|
||||||
|
'prop_bone' : 'root'
|
||||||
|
}
|
||||||
|
|
||||||
|
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 = info['prop_rig']# name_of_the_rig
|
||||||
|
|
||||||
|
## write the name of the propertie to attach
|
||||||
|
prop_name = info["prop_name"]#'hide_headband'# hide_something
|
||||||
|
|
||||||
|
## prop_bone (bone holding the propertie), 'root' if left string empty.
|
||||||
|
prop_bone = info["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,27 @@
|
||||||
|
info = {
|
||||||
|
'icon' : 'ACTION_TWEAK',
|
||||||
|
'description' : 'Rename selected armature actions (with name of the rig, replacing "_rig" with "_act")',
|
||||||
|
}
|
||||||
|
|
||||||
|
# coding: utf-8
|
||||||
|
import bpy
|
||||||
|
import os
|
||||||
|
from os import listdir
|
||||||
|
from os.path import join, dirname, basename, exists, isfile, isdir, splitext
|
||||||
|
import re, fnmatch, glob
|
||||||
|
from mathutils import Vector, Matrix
|
||||||
|
from math import radians, degrees
|
||||||
|
C = bpy.context
|
||||||
|
D = bpy.data
|
||||||
|
scene = C.scene
|
||||||
|
|
||||||
|
for o in C.selected_objects:
|
||||||
|
if o.type == 'ARMATURE':
|
||||||
|
print(o.animation_data.action.name)
|
||||||
|
if '_rig' in o.name:
|
||||||
|
action = o.animation_data.action
|
||||||
|
if action.name.replace('_act','') != o.name.replace('_rig', ''):
|
||||||
|
newname = o.name.replace('_rig', '_act')
|
||||||
|
print(f'action rename {action.name} > {newname}')
|
||||||
|
action.name = newname
|
||||||
|
|
|
@ -0,0 +1,21 @@
|
||||||
|
info = {
|
||||||
|
'icon' : 'HIDE_OFF',
|
||||||
|
'description' : 'Force viewport visibility ON for objects with hide_viewport prop animated',
|
||||||
|
}
|
||||||
|
|
||||||
|
# coding: utf-8
|
||||||
|
import bpy
|
||||||
|
import os
|
||||||
|
from os import listdir
|
||||||
|
from os.path import join, dirname, basename, exists, isfile, isdir, splitext
|
||||||
|
import re, fnmatch, glob
|
||||||
|
from mathutils import Vector, Matrix
|
||||||
|
from math import radians, degrees
|
||||||
|
C = bpy.context
|
||||||
|
D = bpy.data
|
||||||
|
scene = C.scene
|
||||||
|
|
||||||
|
import bpy
|
||||||
|
for o in bpy.context.scene.objects:
|
||||||
|
if o.animation_data and o.animation_data.action and o.animation_data.action.fcurves.find('hide_viewport'):
|
||||||
|
o.hide_viewport = False
|
|
@ -0,0 +1,22 @@
|
||||||
|
info = {
|
||||||
|
'icon' : 'OUTLINER_OB_ARMATURE',
|
||||||
|
'description' : 'Hide/Unhide all rigs',
|
||||||
|
}
|
||||||
|
|
||||||
|
import bpy
|
||||||
|
C = bpy.context
|
||||||
|
D = bpy.data
|
||||||
|
scene = C.scene
|
||||||
|
|
||||||
|
#hide armature
|
||||||
|
hide = True
|
||||||
|
|
||||||
|
arms = [o for o in C.scene.objects if o.type == 'ARMATURE']
|
||||||
|
if arms:
|
||||||
|
hide = not arms[0].hide_viewport
|
||||||
|
|
||||||
|
#invert of the state of the first armature found for all of them
|
||||||
|
for o in C.scene.objects:
|
||||||
|
if o.type == 'ARMATURE':
|
||||||
|
o.hide_viewport = hide
|
||||||
|
|
|
@ -0,0 +1,5 @@
|
||||||
|
import bpy
|
||||||
|
from bpy import data as D
|
||||||
|
ob = D.objects['boom_hairs_group_ref']
|
||||||
|
|
||||||
|
ob.hide_viewport = not ob.hide_viewport
|
|
@ -0,0 +1,192 @@
|
||||||
|
from .utils import *
|
||||||
|
from bpy.props import *
|
||||||
|
from bpy.types import Panel,Operator
|
||||||
|
from .properties import CustomShelfSettings,CustomShelfPrefs
|
||||||
|
from .Types import *
|
||||||
|
|
||||||
|
|
||||||
|
SHELF_DIR = join(dirname(__file__),'shelves')
|
||||||
|
|
||||||
|
|
||||||
|
def operator(idname,args) :
|
||||||
|
op_execute = args['execute']
|
||||||
|
def execute(self, context):
|
||||||
|
op_execute(context,self)
|
||||||
|
return {"FINISHED"}
|
||||||
|
|
||||||
|
package = __package__.replace('_','').replace('-','').lower()
|
||||||
|
|
||||||
|
args['bl_idname'] = "%s.%s"%(package,idname)
|
||||||
|
args['bl_label'] = title(idname)
|
||||||
|
args['execute'] = execute
|
||||||
|
|
||||||
|
op = type('operator',(Operator,),args)
|
||||||
|
|
||||||
|
bpy.utils.register_class(op)
|
||||||
|
|
||||||
|
return op
|
||||||
|
|
||||||
|
def register_bl_class(bl_class,args) :
|
||||||
|
a = type('test',(bl_class,),{})
|
||||||
|
a.initialyse(a,*args)
|
||||||
|
bpy.utils.register_class(a)
|
||||||
|
|
||||||
|
return a
|
||||||
|
|
||||||
|
def filter_dir(dir_list) :
|
||||||
|
return [d for d in dir_list if d.is_dir() and not d.name.startswith('_')]
|
||||||
|
|
||||||
|
def get_shelves_folder():
|
||||||
|
prefs = bpy.context.preferences.addons[__package__].preferences
|
||||||
|
shelves_path = prefs.global_shelves
|
||||||
|
additionnal_shelves_path = [s.path for s in prefs.additionnal_shelves]
|
||||||
|
return [p for p in additionnal_shelves_path+[shelves_path] if exists(p)]
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
def get_dirs(paths) :
|
||||||
|
if isinstance(paths,(tuple,list)) :
|
||||||
|
dirs = []
|
||||||
|
for dir in paths:
|
||||||
|
if not isinstance(dir, str) :
|
||||||
|
dir = dir.path
|
||||||
|
dirs += filter_dir(scandir(dir))
|
||||||
|
return dirs
|
||||||
|
else :
|
||||||
|
return filter_dir(scandir(paths))
|
||||||
|
|
||||||
|
def get_categories():
|
||||||
|
return [d.name for d in get_dirs(get_shelves_folder())]
|
||||||
|
|
||||||
|
def get_tabs() :
|
||||||
|
return [d.name for d in get_dirs(get_dirs(get_shelves_folder()))]
|
||||||
|
|
||||||
|
def get_category_path(category) :
|
||||||
|
category_dir = [f for f in get_shelves_folder() if category in [d.name for d in get_dirs(f)]]
|
||||||
|
if category_dir :
|
||||||
|
return join(category_dir[0],category)
|
||||||
|
|
||||||
|
def get_category_tabs(category):
|
||||||
|
return [d.name for d in get_dirs(get_category_path(category))]
|
||||||
|
|
||||||
|
|
||||||
|
def read_shelves() :
|
||||||
|
tag_filter_items = []
|
||||||
|
|
||||||
|
# unregister panel :
|
||||||
|
for p in CustomShelfSettings.panel_list :
|
||||||
|
if p.__name__ not in get_tabs() :
|
||||||
|
try :
|
||||||
|
bpy.utils.unregister_class(p)
|
||||||
|
except :
|
||||||
|
pass
|
||||||
|
CustomShelfSettings.panel_list.remove(p)
|
||||||
|
|
||||||
|
|
||||||
|
for cat in get_dirs(get_shelves_folder()) :
|
||||||
|
cat_name = cat.name.replace('_',' ').title()
|
||||||
|
|
||||||
|
header_info = {
|
||||||
|
"bl_region_type" : "UI",#petit ajout pour voir si foncitonne...
|
||||||
|
"bl_category" : cat_name,
|
||||||
|
"bl_idname" : "%s_PT_%s_header"%(__package__.upper(),cat.name),# -> "%s.%s_header" makes _PT_ warning
|
||||||
|
"cat" : cat.name,
|
||||||
|
"path" : cat.path
|
||||||
|
}
|
||||||
|
header = type("Header",(CSHELF_PT_shelf_header,),header_info)
|
||||||
|
|
||||||
|
bpy.utils.register_class(header)
|
||||||
|
|
||||||
|
|
||||||
|
#enable bool prefs
|
||||||
|
# panel_props = {t.name : BoolProperty(default = False) for t in get_dirs(cat.path)}# give "should be an annotation" field warning
|
||||||
|
## need annoattion type : https://blender.stackexchange.com/questions/118118/blender-2-8-field-property-declaration-and-dynamic-class-creation
|
||||||
|
panel_props = {'__annotations__': {t.name : BoolProperty(default = False) for t in get_dirs(cat.path)}}
|
||||||
|
PanelProps = type("CustomShelfPrefs",(PropertyGroup,),panel_props)
|
||||||
|
bpy.utils.register_class(PanelProps)
|
||||||
|
|
||||||
|
setattr(CustomShelfPrefs,cat.name,PointerProperty(type = PanelProps))
|
||||||
|
|
||||||
|
#search and filter
|
||||||
|
props = {
|
||||||
|
"search" : StringProperty(options = {"TEXTEDIT_UPDATE"}),
|
||||||
|
"filter" : BoolProperty(default = True)
|
||||||
|
}
|
||||||
|
# Props = type("props",(PropertyGroup,), props)# annotation error
|
||||||
|
Props = type("props",(PropertyGroup,),{'__annotations__': props})
|
||||||
|
bpy.utils.register_class(Props)
|
||||||
|
|
||||||
|
setattr(CustomShelfSettings,cat.name,PointerProperty(type = Props))
|
||||||
|
|
||||||
|
#enable bool prefs
|
||||||
|
#Prefs = type("CustomShelfPrefs",(PropertyGroup,),{})
|
||||||
|
#bpy.utils.register_class(Prefs)
|
||||||
|
|
||||||
|
#setattr(CustomShelfPrefs,cat.name,PointerProperty(type = Prefs))
|
||||||
|
|
||||||
|
for tab in get_dirs(cat.path) :
|
||||||
|
#get_settings
|
||||||
|
settings_json = join(tab.path,'settings.json')
|
||||||
|
|
||||||
|
tab_settings = {}
|
||||||
|
if exists(settings_json) :
|
||||||
|
tab_settings = read_json(settings_json)
|
||||||
|
if not tab_settings or not tab_settings.get('tags') : continue
|
||||||
|
for tag in tab_settings['tags'] :
|
||||||
|
if tag not in tag_filter_items :
|
||||||
|
tag_filter_items.append(tag)
|
||||||
|
|
||||||
|
panel_info = {
|
||||||
|
"bl_region_type" : "UI",#Added, maybe need it in 2.8+...
|
||||||
|
"bl_category" : cat_name,
|
||||||
|
"bl_idname" : "%s_PT_%s_%s"%(__package__.upper(),cat.name,tab.name),#"%s.%s_%s" -> makes _PT_ warning
|
||||||
|
"bl_label" : tab.name.replace('_',' ').title(),
|
||||||
|
"cat" : cat.name,
|
||||||
|
"settings" :tab_settings
|
||||||
|
}
|
||||||
|
|
||||||
|
space_info = {k:v for k,v in tab_settings.items() if k in ("bl_space_type","bl_region_type")}
|
||||||
|
panel_info.update(space_info)
|
||||||
|
|
||||||
|
panel = type("Panel",(CSHELF_PT_shelf_panel,),panel_info)
|
||||||
|
|
||||||
|
bpy.utils.register_class(panel)
|
||||||
|
|
||||||
|
CustomShelfSettings.panel_list.append(panel)
|
||||||
|
|
||||||
|
scripts = []
|
||||||
|
for script in scandir(tab.path) :
|
||||||
|
if not script.name.endswith('.py') : continue
|
||||||
|
|
||||||
|
script_name = splitext(script.name)[0]
|
||||||
|
|
||||||
|
info,lines = read_info(script.path)
|
||||||
|
|
||||||
|
info["description"] = info.get('description',"")
|
||||||
|
|
||||||
|
icon = info.get('icon',"WORDWRAP_OFF")
|
||||||
|
|
||||||
|
popup = type(script_name,(CSHELF_OT_shelf_popup,),{"script":script.path,'bl_description':info["description"]})
|
||||||
|
popup.initialyse(popup,script_name,info)
|
||||||
|
bpy.utils.register_class(popup)
|
||||||
|
|
||||||
|
|
||||||
|
scripts.append({"operator" : popup.bl_idname,"text" : script_name,"icon" : icon})
|
||||||
|
|
||||||
|
panel.scripts = sorted(scripts,key = lambda x :x['text'])
|
||||||
|
|
||||||
|
#Register tag filter enum
|
||||||
|
#setattr(Pre, v)
|
||||||
|
prefs = bpy.context.preferences.addons[__package__].preferences
|
||||||
|
|
||||||
|
tag_filter_items.sort()
|
||||||
|
tag_filter_items.insert(0, '__clear__')
|
||||||
|
|
||||||
|
prefs['tag_filter_items'] = tag_filter_items
|
||||||
|
|
||||||
|
#bpy.utils.unregister_class(CustomShelfPrefs)
|
||||||
|
#bpy.utils.register_class(CustomShelfPrefs)
|
||||||
|
#CustomShelfSettings.folders = list(get_shelves_folder().keys())
|
||||||
|
|
||||||
|
#folder_enum = EnumProperty(items = lambda s,c : [(f,f,"") for f in CustomShelfSettings.folders])
|
||||||
|
#setattr(CustomShelfSettings,'folders_enum',folder_enum)
|
|
@ -0,0 +1,292 @@
|
||||||
|
from .utils import *
|
||||||
|
from .functions import *
|
||||||
|
from bpy.types import Operator
|
||||||
|
from bpy.props import *
|
||||||
|
from .properties import CustomShelfSettings
|
||||||
|
|
||||||
|
class CSHELF_OT_refresh(Operator):
|
||||||
|
bl_idname = "customshelf.refresh"
|
||||||
|
bl_label = 'Refresh Shelves'
|
||||||
|
|
||||||
|
def execute(self,context) :
|
||||||
|
read_shelves()
|
||||||
|
|
||||||
|
return {'FINISHED'}
|
||||||
|
|
||||||
|
|
||||||
|
def get_tag_items(self,context) :
|
||||||
|
prefs = bpy.context.preferences.addons[__package__].preferences
|
||||||
|
return [(i,i.title(),"") for i in prefs['tag_filter_items']]
|
||||||
|
|
||||||
|
class CSHELF_OT_set_tag_filter(Operator):
|
||||||
|
bl_idname = "customshelf.set_tag_filter"
|
||||||
|
bl_label = 'Refresh Shelves'
|
||||||
|
|
||||||
|
tag_filter : EnumProperty(items = get_tag_items)
|
||||||
|
|
||||||
|
def execute(self, context):
|
||||||
|
prefs = bpy.context.preferences.addons[__package__].preferences
|
||||||
|
|
||||||
|
tag_filter = self.tag_filter
|
||||||
|
if tag_filter == '__clear__' :
|
||||||
|
tag_filter = ""
|
||||||
|
|
||||||
|
prefs.tag_filter = tag_filter
|
||||||
|
return {'FINISHED'}
|
||||||
|
|
||||||
|
|
||||||
|
class CSHELF_OT_add_shelf_folder(Operator):
|
||||||
|
bl_idname = "customshelf.add_shelves_folder"
|
||||||
|
bl_label = 'Refresh Shelves'
|
||||||
|
|
||||||
|
def execute(self, context):
|
||||||
|
prefs = bpy.context.preferences.addons[__package__].preferences
|
||||||
|
path = prefs.additionnal_shelves.add()
|
||||||
|
return {'FINISHED'}
|
||||||
|
|
||||||
|
class CSHELF_OT_remove_shelf_folder(Operator):
|
||||||
|
bl_idname = "customshelf.remove_shelves_folder"
|
||||||
|
bl_label = 'Refresh Shelves'
|
||||||
|
|
||||||
|
index : IntProperty()
|
||||||
|
|
||||||
|
def execute(self, context):
|
||||||
|
prefs = bpy.context.preferences.addons[__package__].preferences
|
||||||
|
prefs.additionnal_shelves.remove(self.index)
|
||||||
|
|
||||||
|
return {'FINISHED'}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
class CSHELF_OT_open_shelf_folder(Operator):
|
||||||
|
bl_idname = "customshelf.open_shelf_folder"
|
||||||
|
bl_label = 'Run Function'
|
||||||
|
|
||||||
|
path : StringProperty()
|
||||||
|
|
||||||
|
def execute(self,context) :
|
||||||
|
open_folder(self.path)
|
||||||
|
|
||||||
|
return {'FINISHED'}
|
||||||
|
|
||||||
|
|
||||||
|
class CSHELF_OT_add_script(Operator) :
|
||||||
|
bl_idname = "customshelf.add_script"
|
||||||
|
bl_label = 'Add script to a shelf'
|
||||||
|
|
||||||
|
add_category : BoolProperty()
|
||||||
|
add_tab : BoolProperty()
|
||||||
|
new_category : StringProperty()
|
||||||
|
new_tab : StringProperty()
|
||||||
|
|
||||||
|
name : StringProperty()
|
||||||
|
description : StringProperty()
|
||||||
|
icon : StringProperty()
|
||||||
|
show_icons : BoolProperty()
|
||||||
|
|
||||||
|
def add_folder(self,context,op) :
|
||||||
|
folder = self.folder.add()
|
||||||
|
folder.name = self.name
|
||||||
|
#CustomShelfSettings
|
||||||
|
|
||||||
|
def remove_folder(self,context,op) :
|
||||||
|
bl_props = context.scene.CustomShelf
|
||||||
|
index = self.folders.find(self.folders_enum)
|
||||||
|
self.folders.remove(index)
|
||||||
|
|
||||||
|
def get_all_icons (self) :
|
||||||
|
ui_layout = bpy.types.UILayout
|
||||||
|
icons = ui_layout.bl_rna.functions["prop"].parameters["icon"].enum_items.keys()
|
||||||
|
|
||||||
|
prefixes = ('BRUSH_','MATCAP_','COLORSET_')
|
||||||
|
exception = ('BRUSH_DATA')
|
||||||
|
|
||||||
|
return [i for i in icons if not i.startswith(prefixes) or i in exception]
|
||||||
|
|
||||||
|
def get_icons(self,context,op) :
|
||||||
|
icons = [
|
||||||
|
["SCENE_DATA","RENDERLAYERS","MATERIAL_DATA","GROUP_UVS","TEXTURE","WORLD","SPEAKER","TEXT","NODETREE","NODE_INSERT_OFF","PARTICLES","SORTALPHA"],
|
||||||
|
["MODIFIER","MOD_WAVE","MOD_SUBSURF","MOD_FLUIDSIM","MOD_OCEAN","BLANK1",
|
||||||
|
"ARMATURE_DATA","BONE_DATA","GROUP_BONE"],
|
||||||
|
["SEQUENCE","CAMERA_DATA","SCENE","BLANK1",
|
||||||
|
"FILE_NEW","CONSOLE","BLENDER","APPEND_BLEND"],
|
||||||
|
["GROUP","MESH_CUBE","MESH_PLANE","MESH_CIRCLE","MESH_UVSPHERE","MESH_GRID","EMPTY_DATA","OUTLINER_DATA_MESH",
|
||||||
|
"LIGHT_SUN","LIGHT_SPOT","LIGHT"],
|
||||||
|
["TRIA_RIGHT_BAR","REC","PLAY","PREV_KEYFRAME","NEXT_KEYFRAME","PAUSE","X","ADD","REMOVE","RESTRICT_VIEW_OFF","RESTRICT_VIEW_ON","RESTRICT_SELECT_OFF",],
|
||||||
|
["BRUSH_DATA","GREASEPENCIL","LINE_DATA","PARTICLEMODE","SCULPTMODE_HLT","WPAINT_HLT","TPAINT_HLT","VIEWZOOM","HAND","KEY_HLT","KEY_DEHLT",],
|
||||||
|
["PLUGIN","SCRIPT","PREFERENCES",
|
||||||
|
"ACTION","SOLO_OFF",
|
||||||
|
"RNDCURVE","SNAP_ON",
|
||||||
|
"FORCE_WIND","COPY_ID","EYEDROPPER","AUTO","UNLOCKED","LOCKED",
|
||||||
|
"UNPINNED","PINNED","PLUGIN","HELP","GHOST_ENABLED","GHOST_DISABLED","COLOR",
|
||||||
|
"LINKED","UNLINKED","LINKED","ZOOM_ALL",
|
||||||
|
"FREEZE","STYLUS_PRESSURE","FILE_TICK",
|
||||||
|
"QUIT","RECOVER_LAST","TIME","PREVIEW_RANGE",
|
||||||
|
"OUTLINER","NLA","EDITMODE_HLT",
|
||||||
|
"BOIDS","RNA","CAMERA_STEREO"],
|
||||||
|
]
|
||||||
|
|
||||||
|
self.icons = icons
|
||||||
|
|
||||||
|
|
||||||
|
def set_icon(self,context,op) :
|
||||||
|
#bl_props = context.scene.CustomShelf
|
||||||
|
self.icon = op.icon
|
||||||
|
self.icons = []
|
||||||
|
|
||||||
|
|
||||||
|
def draw(self,context) :
|
||||||
|
bl_props = context.scene.CustomShelf
|
||||||
|
layout = self.layout
|
||||||
|
|
||||||
|
row = layout.row()
|
||||||
|
row.operator("customshelf.get_icons",text= '',icon = self.icon)
|
||||||
|
row.prop(self,"name",text ="")
|
||||||
|
|
||||||
|
col = layout.column(align = True)
|
||||||
|
|
||||||
|
|
||||||
|
for icon_list in self.icons :
|
||||||
|
i=0
|
||||||
|
for icon in icon_list :
|
||||||
|
if not i%12 :
|
||||||
|
row = col.row(align= True)
|
||||||
|
|
||||||
|
row.operator("customshelf.set_icon",icon=icon,emboss = False,text="").icon = icon
|
||||||
|
i += 1
|
||||||
|
row = col.row(align= True)
|
||||||
|
|
||||||
|
layout.prop(self,"description",text="")
|
||||||
|
layout.separator()
|
||||||
|
|
||||||
|
#Category Row
|
||||||
|
folder_row = layout.row(align = True)
|
||||||
|
folder_row.label(text="", icon='FILE_FOLDER')
|
||||||
|
folder_row.separator()
|
||||||
|
if not self.add_category :
|
||||||
|
folder_row.prop(bl_props,"category_enum",expand = True)
|
||||||
|
|
||||||
|
else :
|
||||||
|
|
||||||
|
folder_row.prop(self,"new_category",text='')
|
||||||
|
|
||||||
|
folder_row.prop(self,"add_category",icon = 'ADD',text='')
|
||||||
|
|
||||||
|
# Tabs row
|
||||||
|
tab_row = layout.row(align = True)
|
||||||
|
tab_row.label(text='', icon='MENU_PANEL')
|
||||||
|
tab_row.separator()
|
||||||
|
if not self.add_tab :
|
||||||
|
category_tabs = get_category_tabs(bl_props.category_enum)
|
||||||
|
for t in [t for t in category_tabs if t in self.tabs] :
|
||||||
|
tab_row.prop_enum(bl_props,"tab_enum",t)
|
||||||
|
|
||||||
|
else :
|
||||||
|
tab_row.prop(self,"new_tab",text='')
|
||||||
|
|
||||||
|
tab_row.prop(self,"add_tab",icon = 'ADD',text='')
|
||||||
|
#folder_row.operator("customshelf.remove_folder",icon = 'REMOVE',text='')
|
||||||
|
|
||||||
|
def write_script(self,f) :
|
||||||
|
keys = ['icon','description']
|
||||||
|
keys += [k for k in self.info if k not in keys]
|
||||||
|
f.write("info = "+dic_to_str(self.info,keys))
|
||||||
|
|
||||||
|
print(self.lines)
|
||||||
|
f.write('\n'.join(self.lines))
|
||||||
|
|
||||||
|
|
||||||
|
def execute(self,context) :
|
||||||
|
preferences = context.preferences
|
||||||
|
bl_props = context.scene.CustomShelf
|
||||||
|
addon_prefs = preferences.addons[__package__].preferences
|
||||||
|
|
||||||
|
if self.new_category :
|
||||||
|
category_path = get_category_path(self.new_category)
|
||||||
|
if not exists(category_path):
|
||||||
|
mkdir(new_category)
|
||||||
|
else :
|
||||||
|
category_path = get_category_path(bl_props.category_enum)
|
||||||
|
|
||||||
|
if self.new_tab :
|
||||||
|
tab_path = join(category_path, self.new_tab)
|
||||||
|
if not exists(tab_path):
|
||||||
|
mkdir(tab_path)
|
||||||
|
else :
|
||||||
|
tab_path = join(category_path,bl_props.tab_enum)
|
||||||
|
|
||||||
|
|
||||||
|
script_name = self.name.replace(' ','_').replace('-','_')
|
||||||
|
|
||||||
|
script_path = join(tab_path,script_name+'.py')
|
||||||
|
if os.path.exists(script_path):
|
||||||
|
os.remove(script_path)
|
||||||
|
|
||||||
|
|
||||||
|
self.info['icon'] = self.icon
|
||||||
|
self.info['description'] = self.description
|
||||||
|
|
||||||
|
with open(script_path,"w") as f :
|
||||||
|
self.write_script(f)
|
||||||
|
|
||||||
|
line_index = self.active_text.current_line_index
|
||||||
|
bpy.data.texts.remove(self.active_text)
|
||||||
|
text = bpy.data.texts.load(script_path)
|
||||||
|
text.current_line_index = line_index
|
||||||
|
|
||||||
|
context.space_data.text = text
|
||||||
|
|
||||||
|
read_shelves()
|
||||||
|
|
||||||
|
return {"FINISHED"}
|
||||||
|
|
||||||
|
def check(self,context) :
|
||||||
|
return True
|
||||||
|
|
||||||
|
def invoke(self,context,event) :
|
||||||
|
bl_props = context.scene.CustomShelf
|
||||||
|
self.active_text = context.space_data.text
|
||||||
|
|
||||||
|
self.info,self.lines = read_info([l.body for l in self.active_text.lines])
|
||||||
|
|
||||||
|
icon = "LONGDISPLAY"
|
||||||
|
if self.info.get('icon') :
|
||||||
|
icon = self.info["icon"]
|
||||||
|
|
||||||
|
description = "Some description"
|
||||||
|
if self.info.get('description') :
|
||||||
|
description = self.info["description"]
|
||||||
|
|
||||||
|
|
||||||
|
self.icon = icon
|
||||||
|
self.name = splitext(self.active_text.name)[0]
|
||||||
|
self.show_icons = False
|
||||||
|
self.description = description
|
||||||
|
|
||||||
|
self.add_shelf = False
|
||||||
|
self.new_shelf = ""
|
||||||
|
self.icons = []
|
||||||
|
|
||||||
|
operator("get_icons",{'execute':self.get_icons})
|
||||||
|
operator("set_icon",{'execute':self.set_icon,'icon':StringProperty()})
|
||||||
|
operator("add_folder",{'execute':self.add_folder})
|
||||||
|
#operator("remove_folder",{'execute':self.remove_folder})
|
||||||
|
|
||||||
|
self.tabs = get_tabs()
|
||||||
|
self.categories = get_categories()
|
||||||
|
|
||||||
|
CustomShelfSettings.category_enum = EnumProperty(items = [(i,i,"") for i in self.categories])
|
||||||
|
CustomShelfSettings.tab_enum = EnumProperty(items = [(i,i,"") for i in self.tabs])
|
||||||
|
|
||||||
|
if self.active_text.filepath :
|
||||||
|
tab_path = dirname(self.active_text.filepath)
|
||||||
|
category = basename(dirname(tab_path))
|
||||||
|
tab = basename(tab_path)
|
||||||
|
|
||||||
|
if category in self.categories :
|
||||||
|
bl_props.category_enum = category
|
||||||
|
if tab in self.tabs :
|
||||||
|
bl_props.tab_enum = tab
|
||||||
|
|
||||||
|
return context.window_manager.invoke_props_dialog(self,width = 500)
|
|
@ -0,0 +1,91 @@
|
||||||
|
from .utils import *
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
class CSHELF_PT_text_editor(bpy.types.Panel):
|
||||||
|
bl_space_type = "TEXT_EDITOR"
|
||||||
|
bl_region_type = "UI"
|
||||||
|
bl_category = "Dev"
|
||||||
|
bl_label = "Custom Shelf"
|
||||||
|
|
||||||
|
def draw(self, context):
|
||||||
|
layout = self.layout
|
||||||
|
shelves = bpy.context.scene.CustomShelf
|
||||||
|
|
||||||
|
row = layout.row(align = True)
|
||||||
|
|
||||||
|
row.operator("customshelf.add_script", text='Add Script',emboss=True,icon= "LONGDISPLAY")
|
||||||
|
row.separator()
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
"""
|
||||||
|
class CustomShelfPanel(bpy.types.Panel):
|
||||||
|
bl_label = "Custom Shelf"
|
||||||
|
bl_category = "SHELF"
|
||||||
|
bl_space_type = 'VIEW_3D'
|
||||||
|
bl_region_type = 'TOOLS'
|
||||||
|
# bl_context = "posemode"
|
||||||
|
|
||||||
|
|
||||||
|
def draw_header(self, context):
|
||||||
|
view = context.space_data
|
||||||
|
layout = self.layout
|
||||||
|
row=layout.row(align=True)
|
||||||
|
row.operator("customshelf.refresh", \
|
||||||
|
text='',emboss=False,icon= "FILE_REFRESH")
|
||||||
|
|
||||||
|
|
||||||
|
def draw(self, context):
|
||||||
|
layout = self.layout
|
||||||
|
|
||||||
|
window = context.window
|
||||||
|
scene = context.scene
|
||||||
|
rd = scene.render
|
||||||
|
|
||||||
|
shelves = bpy.context.scene.CustomShelf.folders
|
||||||
|
|
||||||
|
for key,value in sorted(shelves.items()) :
|
||||||
|
if key.startswith(('_','-')) :
|
||||||
|
continue
|
||||||
|
|
||||||
|
box = layout.box()
|
||||||
|
#box.alignment='EXPAND'
|
||||||
|
box_col = box.column(align =False)
|
||||||
|
|
||||||
|
if value['expand'] == True :
|
||||||
|
expandIcon = 'TRIA_DOWN'
|
||||||
|
else :
|
||||||
|
expandIcon = 'TRIA_RIGHT'
|
||||||
|
|
||||||
|
row = box_col.row(align = True)
|
||||||
|
|
||||||
|
row.operator("customshelf.expand",text=key.upper(), icon = expandIcon,emboss=False).folder = key
|
||||||
|
|
||||||
|
#subrow = row.row(align = True)
|
||||||
|
#subrow.alignment = 'CENTER'
|
||||||
|
#subrow.label(key.upper())
|
||||||
|
|
||||||
|
#col.separator()
|
||||||
|
if value['expand'] == True :
|
||||||
|
|
||||||
|
#text =' '.join([' ' for a in range(0,len(key))])
|
||||||
|
#row.prop(context.scene.CustomShelf,key,emboss = False,text=' ')
|
||||||
|
|
||||||
|
|
||||||
|
for script,settings in sorted(value['scripts'].items()) :
|
||||||
|
if script.startswith(('_','-')) :
|
||||||
|
continue
|
||||||
|
#print(s[1])
|
||||||
|
func = box_col.operator("customshelf.run_function", \
|
||||||
|
text=script,icon = settings['icon'])
|
||||||
|
func.path = settings['path']
|
||||||
|
|
||||||
|
|
||||||
|
#box_col.separator()
|
||||||
|
|
||||||
|
|
||||||
|
#layout.separator()
|
||||||
|
#col.separator()
|
||||||
|
|
||||||
|
"""
|
|
@ -0,0 +1,48 @@
|
||||||
|
from .utils import *
|
||||||
|
from bpy.props import *
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
class CustomShelfSettings(bpy.types.PropertyGroup) :
|
||||||
|
panel_list = []
|
||||||
|
#category_enum = EnumProperty(items = )
|
||||||
|
#tab_enum = EnumProperty(items = lambda s,c : [(f,f,"") for f in s.tabs])
|
||||||
|
|
||||||
|
#search = StringProperty(options = {"TEXTEDIT_UPDATE"})
|
||||||
|
#filter = BoolProperty(default = True)
|
||||||
|
#folders = PointerProperty(type= CustomShelfFolders)
|
||||||
|
#variables = bpy.props.PointerProperty(type= CustomShelfVariables)
|
||||||
|
|
||||||
|
class CustomShelfProps(bpy.types.PropertyGroup) :
|
||||||
|
pass
|
||||||
|
|
||||||
|
class AdditionnalShelves(bpy.types.PropertyGroup) :
|
||||||
|
path: StringProperty(name="Path",subtype = 'FILE_PATH')
|
||||||
|
|
||||||
|
|
||||||
|
class CustomShelfPrefs(bpy.types.AddonPreferences):
|
||||||
|
bl_idname = __package__
|
||||||
|
|
||||||
|
global_path = join(dirname(__file__),'shelves')
|
||||||
|
global_shelves : StringProperty(name="Shelves Path",subtype = 'DIR_PATH',default =global_path)
|
||||||
|
additionnal_shelves : CollectionProperty(type = AdditionnalShelves)
|
||||||
|
tag_filter : StringProperty()
|
||||||
|
|
||||||
|
|
||||||
|
def draw(self, context):
|
||||||
|
prefs = bpy.context.preferences.addons[__package__].preferences
|
||||||
|
layout = self.layout
|
||||||
|
layout.prop(self, "global_shelves")
|
||||||
|
|
||||||
|
row = layout.row(align = True)
|
||||||
|
row.operator("customshelf.add_shelves_folder",icon="ADD",text="")
|
||||||
|
row.label(text='Additionnal Shelves folder')
|
||||||
|
|
||||||
|
for i,shelf in enumerate(prefs.additionnal_shelves) :
|
||||||
|
row = layout.row(align = True)
|
||||||
|
row.prop(shelf, "path")
|
||||||
|
row.operator("customshelf.remove_shelves_folder",icon="REMOVE",text="").index = i
|
||||||
|
|
||||||
|
row = layout.row(align = True)
|
||||||
|
row.prop(self, "tag_filter")
|
||||||
|
row.operator_menu_enum("customshelf.set_tag_filter",'tag_filter',icon= "DOWNARROW_HLT",text='')
|
|
@ -0,0 +1,103 @@
|
||||||
|
info = {
|
||||||
|
'icon': 'RECOVER_LAST',
|
||||||
|
'description': 'Duplicate current low as mid',
|
||||||
|
}
|
||||||
|
|
||||||
|
import bpy
|
||||||
|
from pathlib import Path
|
||||||
|
import re
|
||||||
|
# make a proxy through python
|
||||||
|
|
||||||
|
### / unused, just because it's interesting (but relinking break hide statement)
|
||||||
|
## https://blender.stackexchange.com/questions/157562/sorting-collections-alphabetically-in-the-outliner
|
||||||
|
def sort_collection(collection, case = False):
|
||||||
|
|
||||||
|
if collection.children is None: return
|
||||||
|
|
||||||
|
children = sorted (
|
||||||
|
collection.children,
|
||||||
|
key = lambda c: c.name if case else c.name.lower()
|
||||||
|
)
|
||||||
|
|
||||||
|
for child in children:
|
||||||
|
collection.children.unlink(child)
|
||||||
|
collection.children.link(child)
|
||||||
|
sort_collection(child)
|
||||||
|
|
||||||
|
def sort_all_collection():
|
||||||
|
# case_sensitive sort, (default is False)
|
||||||
|
case_sensitive = True
|
||||||
|
|
||||||
|
for scene in bpy.data.scenes:
|
||||||
|
sort_collection(scene.collection, case_sensitive)
|
||||||
|
### unused /
|
||||||
|
|
||||||
|
|
||||||
|
def set_collection(ob, collection, unlink=True) :
|
||||||
|
''' link an object in a collection and create it if necessary, if unlink object is removed from other collections'''
|
||||||
|
scn = bpy.context.scene
|
||||||
|
col = None
|
||||||
|
visible = False
|
||||||
|
linked = False
|
||||||
|
|
||||||
|
# check if collection exist or create it
|
||||||
|
for c in bpy.data.collections :
|
||||||
|
if c.name == collection : col = c
|
||||||
|
if not col : col = bpy.data.collections.new(name=collection)
|
||||||
|
|
||||||
|
# link the collection to the scene's collection if necessary
|
||||||
|
for c in scn.collection.children :
|
||||||
|
if c.name == col.name : visible = True
|
||||||
|
if not visible : scn.collection.children.link(col)
|
||||||
|
|
||||||
|
# check if the object is already in the collection and link it if necessary
|
||||||
|
for o in col.objects :
|
||||||
|
if o == ob : linked = True
|
||||||
|
if not linked : col.objects.link(ob)
|
||||||
|
|
||||||
|
# remove object from scene's collection
|
||||||
|
for o in scn.collection.objects :
|
||||||
|
if o == ob : scn.collection.objects.unlink(ob)
|
||||||
|
|
||||||
|
# if unlink flag we remove the object from other collections
|
||||||
|
if unlink :
|
||||||
|
for c in ob.users_collection :
|
||||||
|
if c.name != collection : c.objects.unlink(ob)
|
||||||
|
return col
|
||||||
|
|
||||||
|
|
||||||
|
# mid = None
|
||||||
|
# low = None
|
||||||
|
# for c in bpy.data.collections:
|
||||||
|
# if re.search(r'vetement.*_mid', c.name, re.I):
|
||||||
|
# mid = c
|
||||||
|
# if re.search(r'vetement.*_low', c.name, re.I):
|
||||||
|
# low = c
|
||||||
|
|
||||||
|
def backup_low_to_mid():
|
||||||
|
low = mid = None
|
||||||
|
for o in bpy.context.scene.objects:
|
||||||
|
if re.search(r'vetements?_mid', o.name, re.I):
|
||||||
|
mid = o
|
||||||
|
if re.search(r'vetements?_low', o.name, re.I):
|
||||||
|
low = o
|
||||||
|
|
||||||
|
if not low:
|
||||||
|
print('ERROR', 'low not found')
|
||||||
|
return
|
||||||
|
|
||||||
|
if not mid and low:
|
||||||
|
#create mid by duplicating low
|
||||||
|
mid = low.copy()
|
||||||
|
mid.name = low.name.replace('_low', '_mid')
|
||||||
|
mid.data = low.data.copy()# also copy data !
|
||||||
|
mid.data.name = low.data.name.replace('_low', '_mid')
|
||||||
|
col = set_collection(mid, mid.name)
|
||||||
|
# hide viewport
|
||||||
|
col.hide_viewport = True
|
||||||
|
|
||||||
|
else:
|
||||||
|
print('ERROR', 'mid already exists')
|
||||||
|
return
|
||||||
|
|
||||||
|
backup_low_to_mid()
|
|
@ -0,0 +1,27 @@
|
||||||
|
info = {
|
||||||
|
'icon': 'SYNTAX_OFF',
|
||||||
|
'description': 'rename all lowercase (a bit dangerous, check if any break)',
|
||||||
|
}
|
||||||
|
|
||||||
|
import bpy
|
||||||
|
|
||||||
|
for c in bpy.data.collections:
|
||||||
|
new = c.name.lower()
|
||||||
|
if bpy.data.collections.get(new):
|
||||||
|
print(f'{new} exists!')
|
||||||
|
else:
|
||||||
|
c.name = new
|
||||||
|
|
||||||
|
for o in bpy.data.meshes:
|
||||||
|
new = o.name.lower()
|
||||||
|
if bpy.data.meshes.get(new):
|
||||||
|
print(f'{new} exists!')
|
||||||
|
else:
|
||||||
|
o.name = new
|
||||||
|
|
||||||
|
for o in bpy.data.objects:
|
||||||
|
new = o.name.lower()
|
||||||
|
if bpy.data.objects.get(new):
|
||||||
|
print(f'{new} exists!')
|
||||||
|
else:
|
||||||
|
o.name = new
|
|
@ -0,0 +1,90 @@
|
||||||
|
info = {
|
||||||
|
'icon': 'MOD_CLOTH',
|
||||||
|
'description': 'Decimate cloth',
|
||||||
|
}
|
||||||
|
|
||||||
|
import bpy
|
||||||
|
from pathlib import Path
|
||||||
|
import re
|
||||||
|
|
||||||
|
|
||||||
|
def set_panel(panel):
|
||||||
|
'''take a panel name and apply it to properties zone'''
|
||||||
|
for area in bpy.context.screen.areas:
|
||||||
|
if area.type == 'PROPERTIES':
|
||||||
|
for space in area.spaces:
|
||||||
|
if space.type == 'PROPERTIES':
|
||||||
|
space.context = panel
|
||||||
|
return (1)
|
||||||
|
return (0)
|
||||||
|
|
||||||
|
|
||||||
|
def get_panel():
|
||||||
|
'''return active panel name of the properties zone'''
|
||||||
|
for area in bpy.context.screen.areas:
|
||||||
|
if area.type == 'PROPERTIES':
|
||||||
|
for space in area.spaces:
|
||||||
|
if space.type == 'PROPERTIES':
|
||||||
|
return(space.context)
|
||||||
|
return (0)
|
||||||
|
|
||||||
|
def get_override():
|
||||||
|
for window in bpy.context.window_manager.windows:
|
||||||
|
screen = window.screen
|
||||||
|
for area in screen.areas:
|
||||||
|
if area.type == 'PROPERTIES':
|
||||||
|
#for region in area.regions:
|
||||||
|
# if region.type == 'WINDOW':
|
||||||
|
return {'window': window, 'screen': screen, 'area': area}
|
||||||
|
|
||||||
|
|
||||||
|
def simplify_cloth(poly_target):
|
||||||
|
if not get_override():
|
||||||
|
print('ERROR', f'Need a properties windows editor open !')
|
||||||
|
return
|
||||||
|
|
||||||
|
obj = bpy.context.object
|
||||||
|
|
||||||
|
mods = obj.modifiers
|
||||||
|
|
||||||
|
polycount = len(obj.data.polygons)
|
||||||
|
|
||||||
|
if polycount < poly_target:
|
||||||
|
print('ERROR', f'Already lowpoly ({polycount} polygons)')
|
||||||
|
return
|
||||||
|
|
||||||
|
target_ratio = poly_target / polycount
|
||||||
|
print('target_ratio: ', target_ratio)
|
||||||
|
|
||||||
|
sdef = None
|
||||||
|
|
||||||
|
for m in mods:
|
||||||
|
if m.type == 'SURFACE_DEFORM':
|
||||||
|
sdef = m
|
||||||
|
break
|
||||||
|
|
||||||
|
if not sdef:
|
||||||
|
print('ERROR', 'no surface deform found')
|
||||||
|
return
|
||||||
|
|
||||||
|
set_panel('MODIFIER')
|
||||||
|
properties_override = get_override()
|
||||||
|
|
||||||
|
if sdef.is_bound:
|
||||||
|
bpy.ops.object.surfacedeform_bind(properties_override, modifier="SurfaceDeform")
|
||||||
|
|
||||||
|
# create decimate and move to top
|
||||||
|
decim = obj.modifiers.new('Decimate', 'DECIMATE')
|
||||||
|
bpy.ops.object.modifier_move_to_index(modifier="Decimate", index=0)
|
||||||
|
|
||||||
|
# use target ratio
|
||||||
|
decim.ratio = target_ratio
|
||||||
|
# apply
|
||||||
|
bpy.ops.object.modifier_apply(modifier="Decimate")
|
||||||
|
|
||||||
|
# rebind
|
||||||
|
bpy.ops.object.surfacedeform_bind(properties_override, modifier="SurfaceDeform")
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
simplify_cloth(15000)
|
|
@ -0,0 +1,275 @@
|
||||||
|
info = {
|
||||||
|
'icon' : 'PHYSICS',
|
||||||
|
'description' : 'Copy dynamic physics object from selected instance_collection',
|
||||||
|
}
|
||||||
|
|
||||||
|
import bpy
|
||||||
|
from os.path import abspath, relpath, dirname, basename, join
|
||||||
|
from time import time
|
||||||
|
C = bpy.context
|
||||||
|
D = bpy.data
|
||||||
|
scene = C.scene
|
||||||
|
|
||||||
|
def set_collection(ob, collection, unlink=True) :
|
||||||
|
''' link an object in a collection and create it if necessary, if unlink object is removed from other collections'''
|
||||||
|
scn = bpy.context.scene
|
||||||
|
col = None
|
||||||
|
visible = False
|
||||||
|
linked = False
|
||||||
|
|
||||||
|
# check if collection exist or create it
|
||||||
|
for c in bpy.data.collections :
|
||||||
|
if c.name == collection : col = c
|
||||||
|
if not col : col = bpy.data.collections.new(name=collection)
|
||||||
|
|
||||||
|
# link the collection to the scene's collection if necessary
|
||||||
|
for c in scn.collection.children :
|
||||||
|
if c.name == col.name : visible = True
|
||||||
|
if not visible : scn.collection.children.link(col)
|
||||||
|
|
||||||
|
# check if the object is already in the collection and link it if necessary
|
||||||
|
for o in col.objects :
|
||||||
|
if o == ob : linked = True
|
||||||
|
if not linked : col.objects.link(ob)
|
||||||
|
|
||||||
|
# remove object from scene's collection
|
||||||
|
for o in scn.collection.objects :
|
||||||
|
if o == ob : scn.collection.objects.unlink(ob)
|
||||||
|
|
||||||
|
# if unlink flag we remove the object from other collections
|
||||||
|
if unlink :
|
||||||
|
for c in ob.users_collection :
|
||||||
|
if c.name != collection : c.objects.unlink(ob)
|
||||||
|
|
||||||
|
|
||||||
|
def append_stuff(blendfile, section, obj):
|
||||||
|
filepath = blendfile
|
||||||
|
directory = join(blendfile, section)#join(blendfile, section)
|
||||||
|
filename = obj
|
||||||
|
print('Append')
|
||||||
|
print('filepath: ', filepath)
|
||||||
|
print('directory: ', directory)
|
||||||
|
print('filename: ', filename)
|
||||||
|
|
||||||
|
print(f'''using:
|
||||||
|
bpy.ops.wm.append(
|
||||||
|
filepath='{filepath}',
|
||||||
|
filename='{filename}',
|
||||||
|
directory='{directory}',
|
||||||
|
active_collection=True,
|
||||||
|
)
|
||||||
|
''')
|
||||||
|
|
||||||
|
bpy.ops.wm.append(
|
||||||
|
filepath=filepath,
|
||||||
|
filename=filename,
|
||||||
|
directory=directory,
|
||||||
|
filter_blender=True, filter_backup=False, filter_image=False, filter_movie=False,
|
||||||
|
filter_python=False, filter_font=False, filter_sound=False, filter_text=False,
|
||||||
|
filter_archive=False, filter_btx=False, filter_collada=False, filter_alembic=False,
|
||||||
|
filter_folder=True, filter_blenlib=True, filemode=1, display_type='DEFAULT', sort_method='FILE_SORT_ALPHA',
|
||||||
|
link=False, autoselect=True, active_collection=True,#default -> active_collection=True
|
||||||
|
instance_collections=False, set_fake=False,
|
||||||
|
use_recursive=True
|
||||||
|
)
|
||||||
|
#return {'finished'}
|
||||||
|
|
||||||
|
## data way (not working as is...)
|
||||||
|
'''
|
||||||
|
with bpy.data.libraries.load(filepath, link=False) as (data_from, data_to):
|
||||||
|
data_to.objects = [o for o in data_from.objects if o == obj]
|
||||||
|
|
||||||
|
#link object to current scene
|
||||||
|
for obj in data_to.objects:
|
||||||
|
if obj is not None:
|
||||||
|
C.scene.collection.objects.link(obj)
|
||||||
|
'''
|
||||||
|
|
||||||
|
def set_soft_body(ob, mod, bake=False):
|
||||||
|
if bake:
|
||||||
|
print(f'baking {ob.name} > {mod.name}')
|
||||||
|
ptc = mod.point_cache
|
||||||
|
|
||||||
|
name = ptc.name
|
||||||
|
override = {'scene': bpy.context.scene,
|
||||||
|
'active_object': ob,#no sure of this line.. .
|
||||||
|
'point_cache': ptc}
|
||||||
|
if bake:
|
||||||
|
if name:#kill point cache related to point cache name
|
||||||
|
# delete_point_cache(name)
|
||||||
|
bpy.ops.ptcache.free_bake(override)
|
||||||
|
## bpy.ops.ptcache.free_bake_all(override)
|
||||||
|
|
||||||
|
#ptc.is_outdated = True # read-only
|
||||||
|
|
||||||
|
ptc.frame_start = scene.frame_start
|
||||||
|
ptc.frame_end = scene.frame_end
|
||||||
|
# ptc.filepath = join(dirname(D.filepath), splitext(basename)[0]+'_cache')
|
||||||
|
|
||||||
|
if not ptc.use_disk_cache:
|
||||||
|
ptc.use_disk_cache = True
|
||||||
|
print('enabling use disk cache')
|
||||||
|
|
||||||
|
if bake:
|
||||||
|
print('>>> baking')
|
||||||
|
start = time()
|
||||||
|
#bpy.ops.ptcache.bake(override, bake=False)# bake to current frame
|
||||||
|
bpy.ops.ptcache.bake(override, bake=True)
|
||||||
|
print(f'Done {time()-start:.3f}')
|
||||||
|
|
||||||
|
def set_particle_system(ob, mod, bake=False):
|
||||||
|
if bake:
|
||||||
|
print(f'baking {ob.name} > {mod.name}')
|
||||||
|
ptc = mod.particle_system.point_cache
|
||||||
|
ptc.frame_start = scene.frame_start
|
||||||
|
ptc.frame_end = scene.frame_end
|
||||||
|
# ptc.filepath = join(dirname(D.filepath), splitext(basename)[0]+'_cache')
|
||||||
|
if not ptc.use_disk_cache:
|
||||||
|
ptc.use_disk_cache = True
|
||||||
|
print('enabling use disk cache')
|
||||||
|
|
||||||
|
if bake:
|
||||||
|
override = {'scene': bpy.context.scene,
|
||||||
|
'active_object': ob,#no sure of this line.. .
|
||||||
|
'point_cache': ptc}
|
||||||
|
print('>>> baking')
|
||||||
|
start = time()
|
||||||
|
#bpy.ops.ptcache.bake(override, bake=False)# bake to current frame
|
||||||
|
bpy.ops.ptcache.bake(override, bake=True)
|
||||||
|
print(f'Done {time()-start:.3f}')
|
||||||
|
|
||||||
|
def bake_mods(ob, bake=False):
|
||||||
|
for mod in ob.modifiers:
|
||||||
|
if mod.type == 'SOFT_BODY':
|
||||||
|
set_soft_body(ob, mod, bake)
|
||||||
|
|
||||||
|
if mod.type == 'PARTICLE_SYSTEM':
|
||||||
|
set_particle_system(ob, mod, bake)
|
||||||
|
|
||||||
|
|
||||||
|
def clear_obj_driver(ob):
|
||||||
|
'''delete visibility driver if any'''
|
||||||
|
print('clearing driver', ob.name)
|
||||||
|
ob.driver_remove('hide_viewport')
|
||||||
|
ob.driver_remove('hide_render')
|
||||||
|
ob.hide_viewport = False
|
||||||
|
ob.hide_render = False
|
||||||
|
|
||||||
|
def append_physics_obj(selected=True, purge=False, bake=False):
|
||||||
|
'''On selection or all scene'''
|
||||||
|
|
||||||
|
current_active_col = C.view_layer.active_layer_collection
|
||||||
|
|
||||||
|
if selected:
|
||||||
|
obpool = C.selected_objects
|
||||||
|
else:
|
||||||
|
obpool = bpy.context.scene.objects
|
||||||
|
|
||||||
|
pool = [o for o in obpool if o.type == 'EMPTY' and o.instance_collection]#PARTICLE_SYSTEM
|
||||||
|
if selected:
|
||||||
|
for proxyarm in [o for o in obpool if o.type == 'ARMATURE' and o.name.endswith('_proxy')]:
|
||||||
|
inst = proxyarm.proxy_collection
|
||||||
|
if inst not in pool: pool.append(inst)
|
||||||
|
|
||||||
|
linked = []
|
||||||
|
|
||||||
|
#[m for m in o.modifiers if m.type == 'SOFT_BODY']
|
||||||
|
for instance in pool:
|
||||||
|
print("instance", instance.name)#Dbg
|
||||||
|
for ob in instance.instance_collection.all_objects:
|
||||||
|
# print("ob", ob.name)#Dbg
|
||||||
|
if ob.type in ('MESH', 'CURVE'):
|
||||||
|
for mod in ob.modifiers:
|
||||||
|
if mod.type in ('SOFT_BODY', 'PARTICLE_SYSTEM'):
|
||||||
|
print(f'{ob.name} > {mod.name} ({mod.type})')
|
||||||
|
|
||||||
|
## check if object already exists in object base.
|
||||||
|
dynob_name = ob.name + '_DYN'
|
||||||
|
phycol = D.collections.get('Physics_sim')#Dynamic_physics
|
||||||
|
|
||||||
|
theob = None
|
||||||
|
if phycol: theob = phycol.objects.get(dynob_name)
|
||||||
|
|
||||||
|
if theob:
|
||||||
|
print(f'{dynob_name} already as object in scene')
|
||||||
|
continue
|
||||||
|
'''
|
||||||
|
if purge:#delete whats in physics collection, crashy as hell..
|
||||||
|
print(f'--> purge: deleting {dynob_name}')
|
||||||
|
D.objects.remove(theob)
|
||||||
|
else:
|
||||||
|
continue
|
||||||
|
'''
|
||||||
|
|
||||||
|
## get library path
|
||||||
|
libpath = abspath(bpy.path.abspath(instance.instance_collection.library.filepath))
|
||||||
|
print('libpath: ', libpath)
|
||||||
|
#libpath = instance.instance_collection.library.filepath
|
||||||
|
#objpath = join(libpath, 'Object', ob.name)
|
||||||
|
|
||||||
|
if not phycol:
|
||||||
|
phycol = D.collections.new('Physics_sim')
|
||||||
|
C.scene.collection.children.link(phycol)
|
||||||
|
## make it active
|
||||||
|
C.view_layer.active_layer_collection = C.view_layer.layer_collection.children['Physics_sim']
|
||||||
|
|
||||||
|
## Append individual object, (physics object with a different name from the blend file, else it 'links' to the one withi instance_collection)
|
||||||
|
#append_stuff(libpath, 'Object', ob.name)
|
||||||
|
|
||||||
|
## test copy from existing object
|
||||||
|
newob = ob.copy()
|
||||||
|
#newob.make_local()
|
||||||
|
#newob.data.make_local()
|
||||||
|
set_collection(newob, 'Physics_sim')
|
||||||
|
linked.append(newob)
|
||||||
|
# newob.name = newob.name + '_DYN'
|
||||||
|
#clear_obj_driver(newob)##crash when clearing driver of the copied object immediately. Do it later
|
||||||
|
|
||||||
|
|
||||||
|
'''
|
||||||
|
## get the newly appended object
|
||||||
|
newob = phycol.objects.get(ob.name)#not rename currently
|
||||||
|
if not newob:
|
||||||
|
print(f'problem, did not found "{dynob_name}" in physics collection')
|
||||||
|
continue
|
||||||
|
linked.append(newob)
|
||||||
|
'''
|
||||||
|
|
||||||
|
# setup proxy visibility...
|
||||||
|
if instance.name == 'jenny':
|
||||||
|
proxy = C.scene.objects.get('jenny_proxy')
|
||||||
|
hairdyn = proxy.pose.bones['root']['_RNA_UI'].get('hair_dynamic')
|
||||||
|
if hairdyn:
|
||||||
|
if hairdyn['max'] < 2:
|
||||||
|
#setmax
|
||||||
|
proxy.pose.bones['root']['_RNA_UI']['hair_dynamic']['max'] = 2
|
||||||
|
proxy.pose.bones['root']['_RNA_UI']['hair_dynamic']['soft_max'] = 2
|
||||||
|
#set val
|
||||||
|
proxy.pose.bones['root']['hair_dynamic'] = 2
|
||||||
|
|
||||||
|
if instance.name == 'boom':
|
||||||
|
proxy = C.scene.objects.get('boom_proxy')
|
||||||
|
hairdyn = proxy.pose.bones['root']['_RNA_UI'].get('hair_placeholder')
|
||||||
|
if hairdyn:
|
||||||
|
if hairdyn['max'] < 3:
|
||||||
|
#setmax
|
||||||
|
proxy.pose.bones['root']['_RNA_UI']['hair_placeholder']['max'] = 3
|
||||||
|
proxy.pose.bones['root']['_RNA_UI']['hair_placeholder']['soft_max'] = 3
|
||||||
|
#set val
|
||||||
|
proxy.pose.bones['root']['hair_placeholder'] = 3
|
||||||
|
|
||||||
|
for o in linked:#linked
|
||||||
|
print(f'-- linked: {o.name}')
|
||||||
|
# o.make_local()#localize then modify, usually no need to make local if appended
|
||||||
|
o.name = o.name + '_DYN'
|
||||||
|
clear_obj_driver(o)
|
||||||
|
print(o.name)
|
||||||
|
#set_collection(o, 'Physics_sim')
|
||||||
|
bake_mods(o, bake=False)
|
||||||
|
|
||||||
|
#reset active collection
|
||||||
|
C.view_layer.active_layer_collection = current_active_col
|
||||||
|
|
||||||
|
|
||||||
|
print()
|
||||||
|
append_physics_obj(selected=True, purge=False, bake=False)#purge bugged
|
|
@ -0,0 +1,143 @@
|
||||||
|
info = {
|
||||||
|
'icon' : 'ACTION_TWEAK',
|
||||||
|
'description' : 'Bake action of selected objects',
|
||||||
|
'selection' : True
|
||||||
|
}
|
||||||
|
|
||||||
|
import bpy
|
||||||
|
from os.path import abspath, relpath, dirname, basename, join
|
||||||
|
from time import time
|
||||||
|
C = bpy.context
|
||||||
|
D = bpy.data
|
||||||
|
scene = C.scene
|
||||||
|
|
||||||
|
def set_collection(ob, collection, unlink=True) :
|
||||||
|
''' link an object in a collection and create it if necessary, if unlink object is removed from other collections'''
|
||||||
|
scn = bpy.context.scene
|
||||||
|
col = None
|
||||||
|
visible = False
|
||||||
|
linked = False
|
||||||
|
|
||||||
|
# check if collection exist or create it
|
||||||
|
for c in bpy.data.collections :
|
||||||
|
if c.name == collection : col = c
|
||||||
|
if not col : col = bpy.data.collections.new(name=collection)
|
||||||
|
|
||||||
|
# link the collection to the scene's collection if necessary
|
||||||
|
for c in scn.collection.children :
|
||||||
|
if c.name == col.name : visible = True
|
||||||
|
if not visible : scn.collection.children.link(col)
|
||||||
|
|
||||||
|
# check if the object is already in the collection and link it if necessary
|
||||||
|
for o in col.objects :
|
||||||
|
if o == ob : linked = True
|
||||||
|
if not linked : col.objects.link(ob)
|
||||||
|
|
||||||
|
# remove object from scene's collection
|
||||||
|
for o in scn.collection.objects :
|
||||||
|
if o == ob : scn.collection.objects.unlink(ob)
|
||||||
|
|
||||||
|
# if unlink flag we remove the object from other collections
|
||||||
|
if unlink :
|
||||||
|
for c in ob.users_collection :
|
||||||
|
if c.name != collection : c.objects.unlink(ob)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
def set_soft_body(ob, mod, bake=False):
|
||||||
|
if bake:
|
||||||
|
print(f'baking {ob.name} > {mod.name}')
|
||||||
|
ptc = mod.point_cache
|
||||||
|
|
||||||
|
name = ptc.name
|
||||||
|
override = {'scene': bpy.context.scene,
|
||||||
|
'active_object': ob,#no sure of this line.. .
|
||||||
|
'point_cache': ptc}
|
||||||
|
if bake:
|
||||||
|
if name:#kill point cache related to point cache name
|
||||||
|
bpy.ops.ptcache.free_bake(override)
|
||||||
|
## bpy.ops.ptcache.free_bake_all(override)
|
||||||
|
|
||||||
|
#ptc.is_outdated = True # read-only
|
||||||
|
|
||||||
|
ptc.frame_start = scene.frame_start
|
||||||
|
ptc.frame_end = scene.frame_end
|
||||||
|
|
||||||
|
if not ptc.use_disk_cache:
|
||||||
|
ptc.use_disk_cache = True
|
||||||
|
print('enabling use disk cache')
|
||||||
|
|
||||||
|
if bake:
|
||||||
|
print('>>> baking')
|
||||||
|
start = time()
|
||||||
|
#bpy.ops.ptcache.bake(override, bake=False)# bake to current frame
|
||||||
|
bpy.ops.ptcache.bake(override, bake=True)
|
||||||
|
print(f'Done {time()-start:.3f}')
|
||||||
|
|
||||||
|
def set_particle_system(ob, mod, bake=False):
|
||||||
|
if bake:
|
||||||
|
print(f'baking {ob.name} > {mod.name}')
|
||||||
|
ptc = mod.particle_system.point_cache
|
||||||
|
ptc.frame_start = scene.frame_start
|
||||||
|
ptc.frame_end = scene.frame_end
|
||||||
|
# ptc.filepath = join(dirname(D.filepath), splitext(basename)[0]+'_cache')
|
||||||
|
if not ptc.use_disk_cache:
|
||||||
|
ptc.use_disk_cache = True
|
||||||
|
print('enabling use disk cache')
|
||||||
|
|
||||||
|
if bake:
|
||||||
|
override = {'scene': bpy.context.scene,
|
||||||
|
'active_object': ob,#no sure of this line.. .
|
||||||
|
'point_cache': ptc}
|
||||||
|
print('>>> baking')
|
||||||
|
start = time()
|
||||||
|
#bpy.ops.ptcache.bake(override, bake=False)# bake to current frame
|
||||||
|
bpy.ops.ptcache.bake(override, bake=True)
|
||||||
|
print(f'Done {time()-start:.3f}')
|
||||||
|
|
||||||
|
def bake_mods(ob, bake=False):
|
||||||
|
for mod in ob.modifiers:
|
||||||
|
if mod.type == 'SOFT_BODY':
|
||||||
|
set_soft_body(ob, mod, bake)
|
||||||
|
|
||||||
|
if mod.type == 'PARTICLE_SYSTEM':
|
||||||
|
set_particle_system(ob, mod, bake)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
def bake_action(selected=True):
|
||||||
|
if selected:
|
||||||
|
pool = []
|
||||||
|
for o in C.selected_objects:
|
||||||
|
if o.animation_data.action:
|
||||||
|
print(f'{o.name} already has an action')
|
||||||
|
o.select_set(False)
|
||||||
|
continue
|
||||||
|
else:
|
||||||
|
pool.append(o)
|
||||||
|
if not pool:
|
||||||
|
print('no object ready for action bake')
|
||||||
|
return
|
||||||
|
|
||||||
|
else:#go in physics ob collection
|
||||||
|
phycol = D.collections.get('Physics_sim')
|
||||||
|
if not phycol:
|
||||||
|
print('No physics collection')
|
||||||
|
return
|
||||||
|
|
||||||
|
pool = [o for o in phycol.all_objects if not o.hide_viewport and not o.animation_data.action]
|
||||||
|
#redefine selection
|
||||||
|
bpy.ops.object.select_all(action='DESELECT')
|
||||||
|
for o in pool:
|
||||||
|
o.select_set(True)
|
||||||
|
C.view_layer.objects.active = o
|
||||||
|
|
||||||
|
# get viewport override ? (no need if launched from custom shelf)
|
||||||
|
|
||||||
|
print('Baking action...')
|
||||||
|
# launch action baking
|
||||||
|
bpy.ops.nla.bake(frame_start=scene.frame_start, frame_end=scene.frame_end, only_selected=False, visual_keying=True, clear_constraints=True, clear_parents=True, bake_types={'OBJECT'})
|
||||||
|
|
||||||
|
|
||||||
|
bake_action(selected=info['selection'])
|
||||||
|
print('EOF')
|
|
@ -0,0 +1,100 @@
|
||||||
|
info = {
|
||||||
|
'icon' : 'PHYSICS',
|
||||||
|
'description' : 'Bake modifier cache of selected objects',
|
||||||
|
'selection' : True
|
||||||
|
}
|
||||||
|
|
||||||
|
import bpy
|
||||||
|
from os.path import abspath, relpath, dirname, basename, join
|
||||||
|
from time import time
|
||||||
|
C = bpy.context
|
||||||
|
D = bpy.data
|
||||||
|
scene = C.scene
|
||||||
|
|
||||||
|
|
||||||
|
def set_soft_body(ob, mod, bake=False):
|
||||||
|
if bake:
|
||||||
|
print(f'baking {ob.name} > {mod.name}')
|
||||||
|
ptc = mod.point_cache
|
||||||
|
|
||||||
|
name = ptc.name
|
||||||
|
override = {'scene': bpy.context.scene,
|
||||||
|
'active_object': ob,#no sure of this line.. .
|
||||||
|
'point_cache': ptc}
|
||||||
|
if bake:
|
||||||
|
if name:#kill point cache related to point cache name
|
||||||
|
bpy.ops.ptcache.free_bake(override)
|
||||||
|
## bpy.ops.ptcache.free_bake_all(override)
|
||||||
|
|
||||||
|
#ptc.is_outdated = True # read-only
|
||||||
|
|
||||||
|
ptc.frame_start = scene.frame_start
|
||||||
|
ptc.frame_end = scene.frame_end
|
||||||
|
|
||||||
|
if not ptc.use_disk_cache:
|
||||||
|
ptc.use_disk_cache = True
|
||||||
|
print('enabling use disk cache')
|
||||||
|
|
||||||
|
if bake:
|
||||||
|
print('>>> baking')
|
||||||
|
start = time()
|
||||||
|
#bpy.ops.ptcache.bake(override, bake=False)# bake to current frame
|
||||||
|
bpy.ops.ptcache.bake(override, bake=True)
|
||||||
|
print(f'Done {time()-start:.3f}')
|
||||||
|
|
||||||
|
def set_particle_system(ob, mod, bake=False):
|
||||||
|
if bake:
|
||||||
|
print(f'baking {ob.name} > {mod.name}')
|
||||||
|
ptc = mod.particle_system.point_cache
|
||||||
|
ptc.frame_start = scene.frame_start
|
||||||
|
ptc.frame_end = scene.frame_end
|
||||||
|
# ptc.filepath = join(dirname(D.filepath), splitext(basename)[0]+'_cache')
|
||||||
|
if not ptc.use_disk_cache:
|
||||||
|
ptc.use_disk_cache = True
|
||||||
|
print('enabling use disk cache')
|
||||||
|
|
||||||
|
if bake:
|
||||||
|
override = {'scene': bpy.context.scene,
|
||||||
|
'active_object': ob,#no sure of this line.. .
|
||||||
|
'point_cache': ptc}
|
||||||
|
print('>>> baking')
|
||||||
|
start = time()
|
||||||
|
#bpy.ops.ptcache.bake(override, bake=False)# bake to current frame
|
||||||
|
bpy.ops.ptcache.bake(override, bake=True)
|
||||||
|
print(f'Done {time()-start:.3f}')
|
||||||
|
|
||||||
|
def bake_mods(ob, bake=False):
|
||||||
|
for mod in ob.modifiers:
|
||||||
|
if mod.type == 'SOFT_BODY':
|
||||||
|
set_soft_body(ob, mod, bake)
|
||||||
|
|
||||||
|
if mod.type == 'PARTICLE_SYSTEM':
|
||||||
|
set_particle_system(ob, mod, bake)
|
||||||
|
|
||||||
|
|
||||||
|
def bake_mod_cache(selected=True):
|
||||||
|
phycol = D.collections.get('Physics_sim')
|
||||||
|
if not phycol:
|
||||||
|
print('No physics collection')
|
||||||
|
return
|
||||||
|
|
||||||
|
if selected:
|
||||||
|
pool = []
|
||||||
|
for o in C.selected_objects:
|
||||||
|
if not phycol in o.users_collection:
|
||||||
|
print(f'{o.name} is not in Physics_sim collection' )
|
||||||
|
continue
|
||||||
|
pool.append(o)
|
||||||
|
|
||||||
|
if not pool:
|
||||||
|
print('no object ready for cache baking')
|
||||||
|
return
|
||||||
|
|
||||||
|
else:#go in physics ob collection
|
||||||
|
pool = [o for o in phycol.all_objects if not o.hide_viewport and not o.animation_data.action]
|
||||||
|
#automate preroll ?
|
||||||
|
for o in pool:
|
||||||
|
bake_mods(o, bake=True)
|
||||||
|
|
||||||
|
bake_mod_cache(selected=info['selection'])
|
||||||
|
print('EOF')
|
|
@ -0,0 +1,19 @@
|
||||||
|
info = {
|
||||||
|
'icon' : 'X',
|
||||||
|
'description' : 'Delete everything in collection Physics_sim',
|
||||||
|
}
|
||||||
|
|
||||||
|
import bpy
|
||||||
|
from os.path import abspath, relpath, dirname, basename, join
|
||||||
|
from time import time
|
||||||
|
C = bpy.context
|
||||||
|
D = bpy.data
|
||||||
|
scene = C.scene
|
||||||
|
|
||||||
|
|
||||||
|
phycol = D.collections.get('Physics_sim')#Dynamic_physics
|
||||||
|
if phycol:
|
||||||
|
for ob in phycol.all_objects:
|
||||||
|
D.objects.remove(ob)
|
||||||
|
|
||||||
|
D.collections.remove(phycol)
|
|
@ -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
|
|
@ -0,0 +1,182 @@
|
||||||
|
import bpy
|
||||||
|
import os
|
||||||
|
from os import listdir,mkdir,scandir
|
||||||
|
from os.path import join,dirname,splitext,isdir,exists,basename
|
||||||
|
from bpy.types import Operator, Panel, PropertyGroup
|
||||||
|
from bpy.props import *
|
||||||
|
import subprocess
|
||||||
|
import json
|
||||||
|
|
||||||
|
|
||||||
|
id_type = {"Action" : "actions", "Armature":"armatures", "Brush":"brushes", "CacheFile":"cache_files", "Camera":"cameras",
|
||||||
|
"Curve":"curves", "FreestyleLineStyle":"linestyles", "GreasePencil":"grease_pencil", "Group":"groups",
|
||||||
|
"Image":"images", "Key":"shape_keys", "Light":"lights", "Lattice":"lattices", "Library":"librairies", "Mask":"masks",
|
||||||
|
"Material":"materials", "Mesh":"meshes", "MetaBall":"metaballs", "MovieClip":"movieclips", "NodeTree":"node_groups",
|
||||||
|
"Object":"objects", "PaintCurve":"paint_curves", "Palette":"palettes", "ParticleSettings":"particles",
|
||||||
|
"Scene":"scenes", "Screen":"screens", "Sound":"sounds", "Speaker":"speakers", "Text":"texts", "Texture":"textures",
|
||||||
|
"VectorFont":"fonts", "WindowManager":"window_managers", "World":"worlds"}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
def report(var,message,type='INFO') :
|
||||||
|
print([a for a in locals()])
|
||||||
|
print([a for a in globals()])
|
||||||
|
|
||||||
|
|
||||||
|
if 'self' in var :
|
||||||
|
var['self'].report({type}, message)
|
||||||
|
|
||||||
|
else :
|
||||||
|
print('')
|
||||||
|
print(type)
|
||||||
|
print(message)
|
||||||
|
|
||||||
|
def read_json(path):
|
||||||
|
jsonFile = path
|
||||||
|
if os.path.exists(jsonFile):
|
||||||
|
try :
|
||||||
|
with open(jsonFile) as data_file:
|
||||||
|
return (json.load(data_file))
|
||||||
|
except :
|
||||||
|
print("the file %s not json readable"%jsonFile)
|
||||||
|
return
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
def search_filter(search,str) :
|
||||||
|
return search.lower() in str.lower()
|
||||||
|
|
||||||
|
def title(string) :
|
||||||
|
return string.replace('_',' ').replace('-',' ').title()
|
||||||
|
|
||||||
|
def arg_name(string) :
|
||||||
|
return string.replace(' ','_').replace('-','_').lower().strip('_')
|
||||||
|
|
||||||
|
def open_folder(path) :
|
||||||
|
try:
|
||||||
|
os.startfile(path)
|
||||||
|
except:
|
||||||
|
subprocess.Popen(['xdg-open', path])
|
||||||
|
|
||||||
|
|
||||||
|
def dic_to_args(dic):
|
||||||
|
import collections
|
||||||
|
args = collections.OrderedDict()
|
||||||
|
|
||||||
|
for k,v in dic.items() :
|
||||||
|
if k in ('icon','description') or not isinstance(k,str):
|
||||||
|
continue
|
||||||
|
|
||||||
|
if isinstance(v,str) :
|
||||||
|
if '/' in v or '\\' in v :
|
||||||
|
args[k] = StringProperty(default=v,subtype = "FILE_PATH")
|
||||||
|
else :
|
||||||
|
args[k] = StringProperty(default=v)
|
||||||
|
|
||||||
|
elif isinstance(v,bool) :
|
||||||
|
args[k] = BoolProperty(default=v)
|
||||||
|
|
||||||
|
elif isinstance(v,float) :
|
||||||
|
args[k] = FloatProperty(default=v,precision=3)
|
||||||
|
|
||||||
|
elif isinstance(v,int) :
|
||||||
|
args[k] = IntProperty(default=v)
|
||||||
|
|
||||||
|
elif isinstance(v,dict) :
|
||||||
|
if v.get("items") and isinstance(v["items"],(tuple,list)) :
|
||||||
|
args[k] = EnumProperty(items = [(i,i,"") for i in v["items"]])
|
||||||
|
|
||||||
|
elif v.get("collection") and isinstance(v["collection"],str) :
|
||||||
|
if v["collection"] not in id_type :
|
||||||
|
print("Collection %s not supported must be in %s"%(v["collection"],id_type))
|
||||||
|
#args[k] = PointerProperty(type = getattr(bpy.types,v["collection"]))
|
||||||
|
else :
|
||||||
|
args[k] = PointerProperty(type = getattr(bpy.types,v["collection"]),poll = v.get('poll'))
|
||||||
|
|
||||||
|
|
||||||
|
return args
|
||||||
|
|
||||||
|
|
||||||
|
def read_info(script_path) :
|
||||||
|
import collections
|
||||||
|
|
||||||
|
if isinstance(script_path,list) :
|
||||||
|
lines =script_path
|
||||||
|
else :
|
||||||
|
with open(script_path,encoding="utf-8") as f:
|
||||||
|
lines = f.read().splitlines()
|
||||||
|
|
||||||
|
info = {}
|
||||||
|
for i,l in enumerate(lines) :
|
||||||
|
if i >= 10 :
|
||||||
|
return info,lines
|
||||||
|
|
||||||
|
if l.startswith("info") :
|
||||||
|
info_start = i
|
||||||
|
info_str = l
|
||||||
|
|
||||||
|
while info_str.endswith(',') or not info_str.endswith('}') or i == len(lines)-1:
|
||||||
|
i+=1
|
||||||
|
info_str+= lines[i]
|
||||||
|
|
||||||
|
info_end = i
|
||||||
|
|
||||||
|
opening_brace_index = info_str.find('{')
|
||||||
|
|
||||||
|
try :
|
||||||
|
info = eval(info_str[opening_brace_index:])
|
||||||
|
except :
|
||||||
|
print('The info header in the file %s cannot be read'%script_path)
|
||||||
|
break
|
||||||
|
|
||||||
|
info = [(k,v) for k,v in info.items()]
|
||||||
|
info.sort(key = lambda x : info_str.find(x[0]))
|
||||||
|
|
||||||
|
info = collections.OrderedDict(info)
|
||||||
|
|
||||||
|
del lines[info_start:info_end+1]
|
||||||
|
|
||||||
|
break
|
||||||
|
|
||||||
|
# remove backspace
|
||||||
|
for i in range(len(lines)) :
|
||||||
|
if lines[0] :
|
||||||
|
break
|
||||||
|
else :
|
||||||
|
lines.pop(0)
|
||||||
|
|
||||||
|
for i in range(len(lines)) :
|
||||||
|
if lines[-1] :
|
||||||
|
break
|
||||||
|
else :
|
||||||
|
lines.pop(-1)
|
||||||
|
|
||||||
|
return info,lines
|
||||||
|
|
||||||
|
def dic_to_str(dic,keys= None,spaces = 4) :
|
||||||
|
if not keys :
|
||||||
|
keys = sorted(dic)
|
||||||
|
|
||||||
|
line = '{\n'
|
||||||
|
for k in keys :
|
||||||
|
v = dic[k]
|
||||||
|
|
||||||
|
line += ' '*spaces
|
||||||
|
|
||||||
|
if isinstance(k,str) :
|
||||||
|
line +="'%s'"%k
|
||||||
|
else :
|
||||||
|
line += "%s"%k
|
||||||
|
|
||||||
|
line += ' : '
|
||||||
|
|
||||||
|
if isinstance(v,str) :
|
||||||
|
line +="'%s'"%v
|
||||||
|
else :
|
||||||
|
line += "%s"%v
|
||||||
|
|
||||||
|
line += ',\n'
|
||||||
|
|
||||||
|
line += '}\n\n'
|
||||||
|
|
||||||
|
return line
|
Loading…
Reference in New Issue