From 232e5cc7f6880e048ae941f4c7d5b56ba40e6159 Mon Sep 17 00:00:00 2001 From: Cos_Min Date: Fri, 4 Mar 2022 12:28:53 +0100 Subject: [PATCH] custom_shelf initial commit --- .gitignore | 3 + Types.py | 279 +++++++++++++++++ __init__.py | 83 +++++ devshelf/Tool/rendu/create_lighting_file.py | 59 ++++ devshelf/Tool/rendu/debug_check.py | 43 +++ devshelf/Tool/rendu/library_check.py | 68 ++++ devshelf/Tool/rendu/rd1_setup_render_path.py | 121 ++++++++ devshelf/Tool/rendu/rd1_setup_video_path.py | 155 ++++++++++ devshelf/Tool/rendu/rd2_render.py | 39 +++ devshelf/Tool/rendu/rd2_render_openGL.py | 39 +++ devshelf/Tool/rendu/rd2_render_still.py | 62 ++++ devshelf/Tool/rendu/rd5_pack_video.py | 109 +++++++ devshelf/Tool/submitter/f1_submit_job.py | 119 +++++++ .../Tool/submitter/f2_render_node_mode.py | 215 +++++++++++++ devshelf/Tool/test/AO_batch_test.py | 30 ++ devshelf/Tool/test/SSS_batch_test_add.py | 37 +++ devshelf/Tool/test/SSS_print_values.py | 32 ++ devshelf/Tool/test/update_notes_and_render.py | 58 ++++ devshelf/dev/dev/API_explorer.py | 44 +++ devshelf/dev/dev/blend1_killer.py | 56 ++++ devshelf/dev/dev/correct_broken_lib_path.py | 71 +++++ devshelf/dev/rigs/check_proxys.py | 60 ++++ devshelf/dev/rigs/custom_visibility_driver.py | 140 +++++++++ devshelf/dev/rigs/rename_actions_from_rig.py | 27 ++ .../dev/rigs/show_all_animated_hide_prop.py | 21 ++ devshelf/dev/rigs/toggle_hide_all_rigs.py | 22 ++ devshelf/dev/temp/hide_poil_ref.py | 5 + functions.py | 192 ++++++++++++ operators.py | 292 ++++++++++++++++++ panels.py | 91 ++++++ properties.py | 48 +++ shelf_store/Abs_tool/clean/backup_low.py | 103 ++++++ .../Abs_tool/clean/rename_lowercase.py | 27 ++ shelf_store/Abs_tool/clean/simplify_cloth.py | 90 ++++++ shelf_store/physics_links/phys_obj_create.py | 275 +++++++++++++++++ .../phys_obj_custom_action_bake.py | 143 +++++++++ .../phys_obj_custom_bake_cache.py | 100 ++++++ shelf_store/physics_links/phys_obj_delete.py | 19 ++ .../various/Get_vert_chain_by_chunk.py | 144 +++++++++ .../various/custom_visibility_driver.py | 132 ++++++++ .../various/filtered_checker_deselect.py | 32 ++ shelf_store/various/ftp_sync_V0.py | 286 +++++++++++++++++ shelf_store/various/select_by_angle.py | 51 +++ .../weight_from_cursor_with_limiter.py | 17 + utils.py | 182 +++++++++++ 45 files changed, 4221 insertions(+) create mode 100644 .gitignore create mode 100644 Types.py create mode 100644 __init__.py create mode 100644 devshelf/Tool/rendu/create_lighting_file.py create mode 100644 devshelf/Tool/rendu/debug_check.py create mode 100644 devshelf/Tool/rendu/library_check.py create mode 100644 devshelf/Tool/rendu/rd1_setup_render_path.py create mode 100644 devshelf/Tool/rendu/rd1_setup_video_path.py create mode 100644 devshelf/Tool/rendu/rd2_render.py create mode 100644 devshelf/Tool/rendu/rd2_render_openGL.py create mode 100644 devshelf/Tool/rendu/rd2_render_still.py create mode 100644 devshelf/Tool/rendu/rd5_pack_video.py create mode 100644 devshelf/Tool/submitter/f1_submit_job.py create mode 100644 devshelf/Tool/submitter/f2_render_node_mode.py create mode 100644 devshelf/Tool/test/AO_batch_test.py create mode 100644 devshelf/Tool/test/SSS_batch_test_add.py create mode 100644 devshelf/Tool/test/SSS_print_values.py create mode 100644 devshelf/Tool/test/update_notes_and_render.py create mode 100644 devshelf/dev/dev/API_explorer.py create mode 100644 devshelf/dev/dev/blend1_killer.py create mode 100644 devshelf/dev/dev/correct_broken_lib_path.py create mode 100644 devshelf/dev/rigs/check_proxys.py create mode 100644 devshelf/dev/rigs/custom_visibility_driver.py create mode 100644 devshelf/dev/rigs/rename_actions_from_rig.py create mode 100644 devshelf/dev/rigs/show_all_animated_hide_prop.py create mode 100644 devshelf/dev/rigs/toggle_hide_all_rigs.py create mode 100644 devshelf/dev/temp/hide_poil_ref.py create mode 100644 functions.py create mode 100644 operators.py create mode 100644 panels.py create mode 100644 properties.py create mode 100644 shelf_store/Abs_tool/clean/backup_low.py create mode 100644 shelf_store/Abs_tool/clean/rename_lowercase.py create mode 100644 shelf_store/Abs_tool/clean/simplify_cloth.py create mode 100644 shelf_store/physics_links/phys_obj_create.py create mode 100644 shelf_store/physics_links/phys_obj_custom_action_bake.py create mode 100644 shelf_store/physics_links/phys_obj_custom_bake_cache.py create mode 100644 shelf_store/physics_links/phys_obj_delete.py create mode 100644 shelf_store/various/Get_vert_chain_by_chunk.py create mode 100644 shelf_store/various/custom_visibility_driver.py create mode 100644 shelf_store/various/filtered_checker_deselect.py create mode 100644 shelf_store/various/ftp_sync_V0.py create mode 100644 shelf_store/various/select_by_angle.py create mode 100644 shelf_store/various/weight_from_cursor_with_limiter.py create mode 100644 utils.py diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..81f69e3 --- /dev/null +++ b/.gitignore @@ -0,0 +1,3 @@ +shelves +*.pyc +__pycache__ diff --git a/Types.py b/Types.py new file mode 100644 index 0000000..d1b9d0c --- /dev/null +++ b/Types.py @@ -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 diff --git a/__init__.py b/__init__.py new file mode 100644 index 0000000..1d39527 --- /dev/null +++ b/__init__.py @@ -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) diff --git a/devshelf/Tool/rendu/create_lighting_file.py b/devshelf/Tool/rendu/create_lighting_file.py new file mode 100644 index 0000000..04e72d8 --- /dev/null +++ b/devshelf/Tool/rendu/create_lighting_file.py @@ -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() \ No newline at end of file diff --git a/devshelf/Tool/rendu/debug_check.py b/devshelf/Tool/rendu/debug_check.py new file mode 100644 index 0000000..3b0ff3e --- /dev/null +++ b/devshelf/Tool/rendu/debug_check.py @@ -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.') \ No newline at end of file diff --git a/devshelf/Tool/rendu/library_check.py b/devshelf/Tool/rendu/library_check.py new file mode 100644 index 0000000..b026d9b --- /dev/null +++ b/devshelf/Tool/rendu/library_check.py @@ -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 + \ No newline at end of file diff --git a/devshelf/Tool/rendu/rd1_setup_render_path.py b/devshelf/Tool/rendu/rd1_setup_render_path.py new file mode 100644 index 0000000..2dd2b3a --- /dev/null +++ b/devshelf/Tool/rendu/rd1_setup_render_path.py @@ -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}') +''' \ No newline at end of file diff --git a/devshelf/Tool/rendu/rd1_setup_video_path.py b/devshelf/Tool/rendu/rd1_setup_video_path.py new file mode 100644 index 0000000..12458fe --- /dev/null +++ b/devshelf/Tool/rendu/rd1_setup_video_path.py @@ -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}') +''' \ No newline at end of file diff --git a/devshelf/Tool/rendu/rd2_render.py b/devshelf/Tool/rendu/rd2_render.py new file mode 100644 index 0000000..2160c0d --- /dev/null +++ b/devshelf/Tool/rendu/rd2_render.py @@ -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 diff --git a/devshelf/Tool/rendu/rd2_render_openGL.py b/devshelf/Tool/rendu/rd2_render_openGL.py new file mode 100644 index 0000000..5c3437c --- /dev/null +++ b/devshelf/Tool/rendu/rd2_render_openGL.py @@ -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 \ No newline at end of file diff --git a/devshelf/Tool/rendu/rd2_render_still.py b/devshelf/Tool/rendu/rd2_render_still.py new file mode 100644 index 0000000..27188cd --- /dev/null +++ b/devshelf/Tool/rendu/rd2_render_still.py @@ -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 + diff --git a/devshelf/Tool/rendu/rd5_pack_video.py b/devshelf/Tool/rendu/rd5_pack_video.py new file mode 100644 index 0000000..f432ef3 --- /dev/null +++ b/devshelf/Tool/rendu/rd5_pack_video.py @@ -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) diff --git a/devshelf/Tool/submitter/f1_submit_job.py b/devshelf/Tool/submitter/f1_submit_job.py new file mode 100644 index 0000000..d6bac98 --- /dev/null +++ b/devshelf/Tool/submitter/f1_submit_job.py @@ -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']) \ No newline at end of file diff --git a/devshelf/Tool/submitter/f2_render_node_mode.py b/devshelf/Tool/submitter/f2_render_node_mode.py new file mode 100644 index 0000000..16dcdd8 --- /dev/null +++ b/devshelf/Tool/submitter/f2_render_node_mode.py @@ -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') \ No newline at end of file diff --git a/devshelf/Tool/test/AO_batch_test.py b/devshelf/Tool/test/AO_batch_test.py new file mode 100644 index 0000000..248afe7 --- /dev/null +++ b/devshelf/Tool/test/AO_batch_test.py @@ -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') \ No newline at end of file diff --git a/devshelf/Tool/test/SSS_batch_test_add.py b/devshelf/Tool/test/SSS_batch_test_add.py new file mode 100644 index 0000000..ce74fbc --- /dev/null +++ b/devshelf/Tool/test/SSS_batch_test_add.py @@ -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) + diff --git a/devshelf/Tool/test/SSS_print_values.py b/devshelf/Tool/test/SSS_print_values.py new file mode 100644 index 0000000..98af499 --- /dev/null +++ b/devshelf/Tool/test/SSS_print_values.py @@ -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() \ No newline at end of file diff --git a/devshelf/Tool/test/update_notes_and_render.py b/devshelf/Tool/test/update_notes_and_render.py new file mode 100644 index 0000000..d1d5b48 --- /dev/null +++ b/devshelf/Tool/test/update_notes_and_render.py @@ -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') + diff --git a/devshelf/dev/dev/API_explorer.py b/devshelf/dev/dev/API_explorer.py new file mode 100644 index 0000000..c2b5d72 --- /dev/null +++ b/devshelf/dev/dev/API_explorer.py @@ -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') \ No newline at end of file diff --git a/devshelf/dev/dev/blend1_killer.py b/devshelf/dev/dev/blend1_killer.py new file mode 100644 index 0000000..39eaa31 --- /dev/null +++ b/devshelf/dev/dev/blend1_killer.py @@ -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']) \ No newline at end of file diff --git a/devshelf/dev/dev/correct_broken_lib_path.py b/devshelf/dev/dev/correct_broken_lib_path.py new file mode 100644 index 0000000..f0918b8 --- /dev/null +++ b/devshelf/dev/dev/correct_broken_lib_path.py @@ -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') +''' \ No newline at end of file diff --git a/devshelf/dev/rigs/check_proxys.py b/devshelf/dev/rigs/check_proxys.py new file mode 100644 index 0000000..f49b37c --- /dev/null +++ b/devshelf/dev/rigs/check_proxys.py @@ -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') \ No newline at end of file diff --git a/devshelf/dev/rigs/custom_visibility_driver.py b/devshelf/dev/rigs/custom_visibility_driver.py new file mode 100644 index 0000000..70a142d --- /dev/null +++ b/devshelf/dev/rigs/custom_visibility_driver.py @@ -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) \ No newline at end of file diff --git a/devshelf/dev/rigs/rename_actions_from_rig.py b/devshelf/dev/rigs/rename_actions_from_rig.py new file mode 100644 index 0000000..7feb540 --- /dev/null +++ b/devshelf/dev/rigs/rename_actions_from_rig.py @@ -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 + \ No newline at end of file diff --git a/devshelf/dev/rigs/show_all_animated_hide_prop.py b/devshelf/dev/rigs/show_all_animated_hide_prop.py new file mode 100644 index 0000000..ce3c971 --- /dev/null +++ b/devshelf/dev/rigs/show_all_animated_hide_prop.py @@ -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 \ No newline at end of file diff --git a/devshelf/dev/rigs/toggle_hide_all_rigs.py b/devshelf/dev/rigs/toggle_hide_all_rigs.py new file mode 100644 index 0000000..065c56c --- /dev/null +++ b/devshelf/dev/rigs/toggle_hide_all_rigs.py @@ -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 + \ No newline at end of file diff --git a/devshelf/dev/temp/hide_poil_ref.py b/devshelf/dev/temp/hide_poil_ref.py new file mode 100644 index 0000000..bb3a4f2 --- /dev/null +++ b/devshelf/dev/temp/hide_poil_ref.py @@ -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 \ No newline at end of file diff --git a/functions.py b/functions.py new file mode 100644 index 0000000..0b70a73 --- /dev/null +++ b/functions.py @@ -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) diff --git a/operators.py b/operators.py new file mode 100644 index 0000000..6a98c6b --- /dev/null +++ b/operators.py @@ -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) diff --git a/panels.py b/panels.py new file mode 100644 index 0000000..5ac6074 --- /dev/null +++ b/panels.py @@ -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() + +""" diff --git a/properties.py b/properties.py new file mode 100644 index 0000000..099d7b2 --- /dev/null +++ b/properties.py @@ -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='') diff --git a/shelf_store/Abs_tool/clean/backup_low.py b/shelf_store/Abs_tool/clean/backup_low.py new file mode 100644 index 0000000..29efb6d --- /dev/null +++ b/shelf_store/Abs_tool/clean/backup_low.py @@ -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() \ No newline at end of file diff --git a/shelf_store/Abs_tool/clean/rename_lowercase.py b/shelf_store/Abs_tool/clean/rename_lowercase.py new file mode 100644 index 0000000..06bb823 --- /dev/null +++ b/shelf_store/Abs_tool/clean/rename_lowercase.py @@ -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 \ No newline at end of file diff --git a/shelf_store/Abs_tool/clean/simplify_cloth.py b/shelf_store/Abs_tool/clean/simplify_cloth.py new file mode 100644 index 0000000..771cf36 --- /dev/null +++ b/shelf_store/Abs_tool/clean/simplify_cloth.py @@ -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) \ No newline at end of file diff --git a/shelf_store/physics_links/phys_obj_create.py b/shelf_store/physics_links/phys_obj_create.py new file mode 100644 index 0000000..ec670b0 --- /dev/null +++ b/shelf_store/physics_links/phys_obj_create.py @@ -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 \ No newline at end of file diff --git a/shelf_store/physics_links/phys_obj_custom_action_bake.py b/shelf_store/physics_links/phys_obj_custom_action_bake.py new file mode 100644 index 0000000..bde7d62 --- /dev/null +++ b/shelf_store/physics_links/phys_obj_custom_action_bake.py @@ -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') \ No newline at end of file diff --git a/shelf_store/physics_links/phys_obj_custom_bake_cache.py b/shelf_store/physics_links/phys_obj_custom_bake_cache.py new file mode 100644 index 0000000..6e19314 --- /dev/null +++ b/shelf_store/physics_links/phys_obj_custom_bake_cache.py @@ -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') \ No newline at end of file diff --git a/shelf_store/physics_links/phys_obj_delete.py b/shelf_store/physics_links/phys_obj_delete.py new file mode 100644 index 0000000..dc2d5ff --- /dev/null +++ b/shelf_store/physics_links/phys_obj_delete.py @@ -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) \ No newline at end of file diff --git a/shelf_store/various/Get_vert_chain_by_chunk.py b/shelf_store/various/Get_vert_chain_by_chunk.py new file mode 100644 index 0000000..3ecb4bb --- /dev/null +++ b/shelf_store/various/Get_vert_chain_by_chunk.py @@ -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) \ No newline at end of file diff --git a/shelf_store/various/custom_visibility_driver.py b/shelf_store/various/custom_visibility_driver.py new file mode 100644 index 0000000..708134a --- /dev/null +++ b/shelf_store/various/custom_visibility_driver.py @@ -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) \ No newline at end of file diff --git a/shelf_store/various/filtered_checker_deselect.py b/shelf_store/various/filtered_checker_deselect.py new file mode 100644 index 0000000..d0bfd96 --- /dev/null +++ b/shelf_store/various/filtered_checker_deselect.py @@ -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() diff --git a/shelf_store/various/ftp_sync_V0.py b/shelf_store/various/ftp_sync_V0.py new file mode 100644 index 0000000..c514c8c --- /dev/null +++ b/shelf_store/various/ftp_sync_V0.py @@ -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() diff --git a/shelf_store/various/select_by_angle.py b/shelf_store/various/select_by_angle.py new file mode 100644 index 0000000..eb41a18 --- /dev/null +++ b/shelf_store/various/select_by_angle.py @@ -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() \ No newline at end of file diff --git a/shelf_store/various/weight_from_cursor_with_limiter.py b/shelf_store/various/weight_from_cursor_with_limiter.py new file mode 100644 index 0000000..2369d6d --- /dev/null +++ b/shelf_store/various/weight_from_cursor_with_limiter.py @@ -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 \ No newline at end of file diff --git a/utils.py b/utils.py new file mode 100644 index 0000000..8004936 --- /dev/null +++ b/utils.py @@ -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