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