Compare commits

...

3 Commits

Author SHA1 Message Date
Nathan 6c3b78075b work: restore shift+spacebar for media play/pause
maybe put in maya config? idk what funiman's preference is
2026-05-29 14:58:59 -06:00
Raincloud 2f8e5f472f work: save startup because I'm sick of the arnoldesque materials 2026-04-24 14:39:38 -06:00
Raincloud b401eb4bcf work: save startup blend for preferences wrong window and size AGAIN
thanks JESSE
2026-04-21 12:47:12 -06:00
165 changed files with 12326 additions and 20940 deletions
+10 -10
View File
@@ -10,13 +10,13 @@ D:\Work\9 iClone\Amazon\
D:\Amazon\00_external-files\ D:\Amazon\00_external-files\
N:\1. CHARACTERS\remapping\ N:\1. CHARACTERS\remapping\
[Recent] [Recent]
D:\2026-04-14 BCONNA2026_MARI\ A:\1 Amazon_Active_Projects\260528_NonConSplitPickcart\Deliverable\Stills\
G:\Flamenco\renders\ A:\1 Amazon_Active_Projects\260528_NonConSplitPickcart\Blends\stills\
C:\Users\Nathan\Downloads\ A:\1 Amazon_Active_Projects\260513_FSAF_edits_2026\Blends\stills\
D:\portfolio\ N:\1. CHARACTERS\remapping\
P:\250827_FestivalTurf\Blends\animations\02 Preparing the Base\Watering Ground\ A:\1 Amazon_Active_Projects\260420_FLOW_2026\Blends\animations\
P:\250827_FestivalTurf\Assets\Blends\ A:\1 Amazon_Active_Projects\260526_Walker_Leader\Blends\stills\img-BG\
T:\250827_FestivalTurf\Blends\animations\02 Preparing the Base\ A:\1 Amazon_Active_Projects\260526_Walker_Leader\Blends\stills\
P:\250827_FestivalTurf\Blends\animations\06 Infill And Powerbrooming\ T:\1 BlenderAssets\
A:\1 Amazon_Active_Projects\260127_Pick-and-Stage_2026_edits\Blends\stills\2025_old\img-BG\ A:\1 Amazon_Active_Projects\260526_Walker_Leader\Blends\animations\
A:\1 Amazon_Active_Projects\260127_Pick-and-Stage_2026_edits\Blends\stills\2025_old\ A:\1 Amazon_Active_Projects\260526_Walker_Leader\Assets\Blends\
+9 -224
View File
@@ -1,15 +1,15 @@
{ {
"flamenco_version": { "flamenco_version": {
"version": "3.8.2", "version": "3.10-alpha0 (0c2ec48d)",
"shortversion": "3.8.2", "shortversion": "3.10-alpha0",
"name": "Flamenco", "name": "Flamenco",
"git": "51a41a19" "git": "0c2ec48d"
}, },
"shared_storage": { "shared_storage": {
"location": "R:\\mnt\\user\\Cerberus Max\\Flamenco", "location": "F:\\jobs",
"audience": "users", "audience": "users",
"platform": "windows", "platform": "windows",
"shaman_enabled": false "shaman_enabled": true
}, },
"job_types": { "job_types": {
"job_types": [ "job_types": [
@@ -283,197 +283,6 @@
"etag": "fe38e5ae9b3e640119d0d55fc7cbf4f8f15f2982", "etag": "fe38e5ae9b3e640119d0d55fc7cbf4f8f15f2982",
"description": "Create a viewport preview/playblast using Cycles OPTIX GPU rendering and generate a video file" "description": "Create a viewport preview/playblast using Cycles OPTIX GPU rendering and generate a video file"
}, },
{
"name": "HoloMARI",
"label": "HoloMARI (MARI render_one)",
"settings": [
{
"key": "mari_jobs_json",
"type": "string",
"description": "JSON object with a \"jobs\" array: {\"jobs\":[{\"cam_name\":...,\"H\":...,\"V\":...}, ...]}. Optional \"frame\" per job for ANIM.",
"eval": "__import__('json').dumps({'jobs':[{'cam_name':o.name,'H':int(o['H']),'V':int(o['V'])} for o in C.scene.objects if getattr(o,'type',None)=='CAMERA' and o.name.startswith('MARI_CAMERA') and 'H' in o.keys() and 'V' in o.keys()]})",
"eval_info": {
"show_link_button": true,
"description": "Auto-collect MARI cameras (name MARI_CAMERA*, custom props H,V). Matches MARI Advanced / HoloMARI job JSON."
},
"required": true,
"visible": "submission"
},
{
"key": "mari_action",
"type": "string",
"choices": [
"STILL",
"ANIM"
],
"default": "STILL",
"description": "MARI action passed to render_one / export_mari: STILL or ANIM",
"visible": "submission"
},
{
"key": "mari_mode",
"type": "string",
"choices": [
"CIRCLE",
"FRAME"
],
"default": "CIRCLE",
"description": "MARI layout mode for export_mari type=FRAME|CIRCLE",
"eval": "(lambda m: 'FRAME' if str(m) == 'FRAME' else 'CIRCLE')(str(getattr(getattr(C.scene, 'mari_props', None), 'frame', 'CIRCLE') or 'CIRCLE'))",
"eval_info": {
"show_link_button": true,
"description": "FRAME vs CIRCLE from scene mari_props.frame"
},
"required": true,
"visible": "submission"
},
{
"key": "chunk_size",
"type": "int32",
"default": 1.0,
"description": "Number of MARI render_one units (cameras/frames) per Blender task",
"propargs": {
"max": 4096.0,
"min": 1.0
},
"visible": "submission"
},
{
"key": "frames",
"type": "string",
"description": "Frame range for ANIM expansion. Examples: '47', '1-30'",
"eval": "f'{C.scene.frame_start}-{C.scene.frame_end}'",
"eval_info": {
"show_link_button": true,
"description": "Scene frame range (used when expanding ANIM image sequences)"
},
"required": true
},
{
"key": "frame_start",
"type": "int32",
"eval": "C.scene.frame_start",
"visible": "hidden"
},
{
"key": "frame_end",
"type": "int32",
"eval": "C.scene.frame_end",
"visible": "hidden"
},
{
"key": "frame_step",
"type": "int32",
"eval": "max(1, C.scene.frame_step)",
"visible": "hidden"
},
{
"key": "render_output_root",
"type": "string",
"description": "Root folder for MARI output; images go under <root>/<mari_props.render_settings_name> (name comes from the scene, not this field).",
"required": true,
"subtype": "dir_path",
"visible": "submission"
},
{
"key": "add_path_components",
"type": "int32",
"default": 0.0,
"description": "Number of path components of the current blend file to append under the render root",
"propargs": {
"max": 32.0,
"min": 0.0
},
"required": true,
"visible": "submission"
},
{
"key": "render_output_path",
"type": "string",
"description": "Job artifact path (mari_job.txt). MARI JPEG/output folder = render_output_root + MARI folder name.",
"editable": false,
"eval": "str(Path(abspath(settings.render_output_root), last_n_dir_parts(settings.add_path_components), jobname, '{timestamp}', 'mari_job.txt'))",
"subtype": "file_path"
},
{
"key": "mari_output_dir_abs",
"type": "string",
"description": "Absolute MARI output folder (render root + scene folder name); used by the zip step.",
"editable": false,
"eval": "str(Path(abspath(settings.render_output_root)) / str(getattr(getattr(C.scene, 'mari_props', None), 'render_settings_name', '') or '').strip())",
"visible": "hidden"
},
{
"key": "mari_output_zip_abs",
"type": "string",
"description": "Absolute path for the final .zip next to the output folder.",
"editable": false,
"eval": "str(Path(abspath(settings.render_output_root)) / (str(getattr(getattr(C.scene, 'mari_props', None), 'render_settings_name', '') or '').strip() + '.zip'))",
"visible": "hidden"
},
{
"key": "zip_render_output",
"type": "bool",
"default": true,
"description": "After all MARI chunk tasks finish, create a .zip of the output folder (requires a worker that runs misc tasks; uses tar).",
"required": false,
"visible": "submission"
},
{
"key": "use_cycles_optix",
"type": "bool",
"default": false,
"description": "When enabled, sets Cycles preferences to OPTIX and scene Cycles device to GPU (same idea as cycles_optix_gpu.js).",
"label": "Cycles: OPTIX + GPU device",
"required": false,
"visible": "submission"
},
{
"key": "experimental_gp3",
"type": "bool",
"description": "Experimental Flag: Grease Pencil 3",
"label": "Experimental: GPv3",
"required": false
},
{
"key": "experimental_new_anim",
"type": "bool",
"description": "Experimental Flag: New Animation Data-block",
"label": "Experimental: Baklava",
"required": false
},
{
"key": "blender_args_before",
"type": "string",
"description": "CLI arguments for Blender, placed before the .blend filename",
"label": "Blender CLI args: Before",
"required": false
},
{
"key": "blender_args_after",
"type": "string",
"description": "CLI arguments for Blender, placed after the .blend filename",
"label": "After",
"required": false
},
{
"key": "blendfile",
"type": "string",
"description": "Path of the Blend file",
"required": true,
"visible": "web"
},
{
"key": "format",
"type": "string",
"eval": "C.scene.render.image_settings.file_format",
"required": true,
"visible": "web"
}
],
"etag": "d19240e637ce81fab40f8e4218ce0249db67d95e",
"description": "MARI Advanced render_one batches (export .mari3d prep + per-camera jobs). Optional Cycles OPTIX+GPU setup (off by default)."
},
{ {
"name": "TalkingHeads Custom Render", "name": "TalkingHeads Custom Render",
"label": "TalkingHeads Custom Render", "label": "TalkingHeads Custom Render",
@@ -517,18 +326,6 @@
"subtype": "dir_path", "subtype": "dir_path",
"visible": "submission" "visible": "submission"
}, },
{
"key": "daily_path",
"type": "string",
"description": "Daily folder name under the render root, e.g. 'daily_250813'. If empty, auto-fills to today's date.",
"eval": "__import__('datetime').datetime.now().strftime('daily_%y%m%d')",
"eval_info": {
"show_link_button": true,
"description": "Auto-fill with today's daily folder name"
},
"required": false,
"visible": "submission"
},
{ {
"key": "use_submodule", "key": "use_submodule",
"type": "bool", "type": "bool",
@@ -555,7 +352,7 @@
"type": "string", "type": "string",
"description": "Final file path of where render output will be saved", "description": "Final file path of where render output will be saved",
"editable": false, "editable": false,
"eval": "str(Path(abspath(settings.render_output_root or '//'), ((str(settings.submodule or '').strip()) if (settings.use_submodule and str(settings.submodule or '').strip()) else ((__import__('os').path.basename(__import__('os').path.dirname(bpy.data.filepath))) if settings.use_submodule else '')), (settings.daily_path or __import__('datetime').datetime.now().strftime('daily_%y%m%d')), jobname, jobname + '_######'))", "eval": "((lambda Path, abspath, os_path, settings_obj, jobname: str(Path(abspath(settings_obj.render_output_root or '//')) / (((str(settings_obj.submodule or '').strip()) if (settings_obj.use_submodule and str(settings_obj.submodule or '').strip()) else ((os_path.basename(os_path.dirname(bpy.data.filepath))) if settings_obj.use_submodule else ''))) / jobname / (jobname + '_######')))(__import__('pathlib').Path, __import__('os').path.abspath, __import__('os').path, settings, (job.name if 'job' in dir() and job and job.name else __import__('os').path.splitext(__import__('os').path.basename(bpy.data.filepath))[0])))",
"subtype": "file_path" "subtype": "file_path"
}, },
{ {
@@ -596,7 +393,7 @@
"visible": "web" "visible": "web"
} }
], ],
"etag": "7773b03fd0d493d6d823d7d45bb3ef7f987384f3", "etag": "5f716e7195e55712daee20c237d95f748e2c3d1e",
"description": "Render a sequence of frames, and create a preview video file" "description": "Render a sequence of frames, and create a preview video file"
}, },
{ {
@@ -634,18 +431,6 @@
"subtype": "dir_path", "subtype": "dir_path",
"visible": "submission" "visible": "submission"
}, },
{
"key": "daily_path",
"type": "string",
"description": "Daily folder name under the render root, e.g. 'daily_250813'. If empty, auto-fills to today's date.",
"eval": "__import__('datetime').datetime.now().strftime('daily_%y%m%d')",
"eval_info": {
"show_link_button": true,
"description": "Auto-fill with today's daily folder name"
},
"required": false,
"visible": "submission"
},
{ {
"key": "use_submodule", "key": "use_submodule",
"type": "bool", "type": "bool",
@@ -672,7 +457,7 @@
"type": "string", "type": "string",
"description": "Final file path of where render output will be saved", "description": "Final file path of where render output will be saved",
"editable": false, "editable": false,
"eval": "str(Path(abspath(settings.render_output_root or '//'), ((str(settings.submodule or '').strip()) if (settings.use_submodule and str(settings.submodule or '').strip()) else ((__import__('os').path.basename(__import__('os').path.dirname(bpy.data.filepath))) if settings.use_submodule else '')), (settings.daily_path or __import__('datetime').datetime.now().strftime('daily_%y%m%d')), jobname, jobname + '_######'))", "eval": "((lambda Path, abspath, os_path, settings_obj, jobname: str(Path(abspath(settings_obj.render_output_root or '//')) / (((str(settings_obj.submodule or '').strip()) if (settings_obj.use_submodule and str(settings_obj.submodule or '').strip()) else ((os_path.basename(os_path.dirname(bpy.data.filepath))) if settings_obj.use_submodule else ''))) / jobname / (jobname + '_######')))(__import__('pathlib').Path, __import__('os').path.abspath, __import__('os').path, settings, (job.name if 'job' in dir() and job and job.name else __import__('os').path.splitext(__import__('os').path.basename(bpy.data.filepath))[0])))",
"subtype": "file_path" "subtype": "file_path"
}, },
{ {
@@ -741,7 +526,7 @@
"visible": "hidden" "visible": "hidden"
} }
], ],
"etag": "5e49db0874b8ee1211f6dbc0a59d66137b6996b2", "etag": "758c56b672b0970896f30b85ee5165a5c096aec4",
"description": "OPTIX GPU rendering + extra checkboxes for some experimental features + extra CLI args for Blender" "description": "OPTIX GPU rendering + extra checkboxes for some experimental features + extra CLI args for Blender"
}, },
{ {
+2
View File
@@ -17,3 +17,5 @@
{NVIDIA Corporation/NVIDIA GeForce RTX 3090/NVIDIA 595.79}=SUPPORTED {NVIDIA Corporation/NVIDIA GeForce RTX 3090/NVIDIA 595.79}=SUPPORTED
{NVIDIA Corporation/NVIDIA GeForce GTX 1060 with Max-Q Design/NVIDIA 582.28}=SUPPORTED {NVIDIA Corporation/NVIDIA GeForce GTX 1060 with Max-Q Design/NVIDIA 582.28}=SUPPORTED
{Intel/Intel(R) HD Graphics 630/4.6.0 - Build 27.20.100.9664}=SUPPORTED {Intel/Intel(R) HD Graphics 630/4.6.0 - Build 27.20.100.9664}=SUPPORTED
{NVIDIA Corporation/NVIDIA GeForce RTX 4080 SUPER/NVIDIA 596.36}=SUPPORTED
{NVIDIA Corporation/NVIDIA GeForce RTX 4080 SUPER/NVIDIA 610.47}=SUPPORTED
+198 -198
View File
@@ -1,200 +1,200 @@
D:\2026-04-14 BCONNA2026_MARI\Sample Stanford Bunny2.blend A:\1 Amazon_Active_Projects\260420_FLOW_2026\Blends\animations\FlowCSA_360_1aC.blend
D:\2026-04-14 BCONNA2026_MARI\Sample Stanford Bunny1.blend A:\1 Amazon_Active_Projects\260528_NonConSplitPickcart\Blends\stills\iNonConSplitPickcart_4b.blend
C:\Users\Nathan\Downloads\Sample Stanford Bunny1.blend A:\1 Amazon_Active_Projects\260528_NonConSplitPickcart\Blends\stills\iNonConSplitPickcart_4a.blend
C:\Users\Nathan\Downloads\Sample Stanford Bunny.blend A:\1 Amazon_Active_Projects\260513_FSAF_edits_2026\Blends\stills\iAmbassadorM2_3.blend
D:\portfolio\demo.blend A:\1 Amazon_Active_Projects\260420_FLOW_2026\Blends\animations\FlowCSA_360_1aB.blend
D:\2025-12-28 ytshorts gigaproj\Blends\animations\FreddyEpstein\FreddyEpstein.blend A:\1 Amazon_Active_Projects\260528_NonConSplitPickcart\Blends\stills\iNonConSplitPickcart_3b.blend
P:\250827_FestivalTurf\Blends\animations\02 Preparing the Base\06_Watering.blend A:\1 Amazon_Active_Projects\260528_NonConSplitPickcart\Blends\stills\iNonConSplitPickcart_3a.blend
T:\250827_FestivalTurf\Blends\animations\02 Preparing the Base\06_Watering.blend A:\1 Amazon_Active_Projects\260528_NonConSplitPickcart\Blends\stills\iNonConSplitPickcart_3.blend
P:\250827_FestivalTurf\Blends\animations\06 Infill And Powerbrooming\Visual 2B_SPA.blend A:\1 Amazon_Active_Projects\260528_NonConSplitPickcart\Blends\stills\iNonConSplitPickcart_2.blend
P:\250827_FestivalTurf\Blends\animations\FT-lipsync_SPA.blend A:\1 Amazon_Active_Projects\260528_NonConSplitPickcart\Blends\stills\iNonConSplitPickcart_1.blend
P:\250827_FestivalTurf\Blends\animations\06 Infill And Powerbrooming\Visual 2D_SPA.blend A:\1 Amazon_Active_Projects\260526_Walker_Leader\Assets\Blends\City_Sidewalk_1.blend
P:\250827_FestivalTurf\Blends\animations\06 Infill And Powerbrooming\Visual 2B.blend A:\1 Amazon_Active_Projects\260526_Walker_Leader\Blends\stills\iWalker_CORTEX_1d.blend
P:\250827_FestivalTurf\Blends\animations\06 Infill And Powerbrooming\Visual 2C.blend A:\1 Amazon_Active_Projects\260526_Walker_Leader\Blends\stills\iWalker_AP_1.blend
P:\250827_FestivalTurf\Blends\animations\06 Infill And Powerbrooming\Visual 2D.blend A:\1 Amazon_Active_Projects\260526_Walker_Leader\Blends\animations\Walker_AP_animation 3.blend
A:\1 Amazon_Active_Projects\260127_Pick-and-Stage_2026_edits\Blends\stills\2025_old\iP&S2025_23.blend A:\1 Amazon_Active_Projects\260526_Walker_Leader\Blends\animations\Walker_LOADOUT_1.blend
P:\250827_FestivalTurf\Blends\animations\01 Develop Your Turf Plan\SPA\FT-lipsync.blend A:\1 Amazon_Active_Projects\260526_Walker_Leader\Blends\animations\Walker_AP_animation 4D.blend
P:\250827_FestivalTurf\Blends\animations\01 Develop Your Turf Plan\SPA\01_Blueprint_SPA.blend A:\1 Amazon_Active_Projects\260526_Walker_Leader\Blends\animations\Walker_AP_animation 4C.blend
P:\250827_FestivalTurf\Blends\animations\04 Securing Your Seam\SPA\06_FT_Hammering_insert_SPA.blend A:\1 Amazon_Active_Projects\260526_Walker_Leader\Blends\animations\Walker_AP_animation 4B.blend
P:\250827_FestivalTurf\Blends\animations\07 Final Touches And Maintenance\SPA\Visual_0_opening.blend A:\1 Amazon_Active_Projects\260526_Walker_Leader\Blends\animations\Walker_AP_animation 4a.blend
P:\250827_FestivalTurf\Blends\animations\06 Infill And Powerbrooming\Visual 3B.blend A:\1 Amazon_Active_Projects\260526_Walker_Leader\Blends\animations\Walker_AP_animation 2A.blend
P:\250827_FestivalTurf\Blends\animations\06 Infill And Powerbrooming\SPA\Visual 3B_SPA.blend A:\1 Amazon_Active_Projects\260526_Walker_Leader\Blends\animations\Walker_AP_animation 2A_old.blend
P:\250827_FestivalTurf\Blends\animations\05 Stretch Cut Nail\SPA\04_stretching pattern.blend A:\1 Amazon_Active_Projects\260526_Walker_Leader\Blends\animations\Walker_AP_animation 2B.blend
P:\250827_FestivalTurf\Blends\animations\05 Stretch Cut Nail\SPA\Visual 6_INSERT.blend A:\1 Amazon_Active_Projects\260526_Walker_Leader\Blends\stills\iWalker_AP_17.blend
P:\250827_FestivalTurf\Blends\animations\05 Stretch Cut Nail\SPA\Visual 8 insert.blend A:\1 Amazon_Active_Projects\260526_Walker_Leader\Blends\stills\iWalker_AP_16.blend
P:\250827_FestivalTurf\Blends\animations\05 Stretch Cut Nail\SPA\03_FT Shuffle.blend A:\1 Amazon_Active_Projects\260526_Walker_Leader\Blends\stills\iWalker_AP_11.blend
P:\250827_FestivalTurf\Blends\animations\05 Stretch Cut Nail\SPA\Visual 8 insert2.blend A:\1 Amazon_Active_Projects\260526_Walker_Leader\Blends\stills\iWalker_AP_7.blend
P:\250827_FestivalTurf\Blends\animations\05 Stretch Cut Nail\SPA\01_intro_SPA.blend A:\1 Amazon_Active_Projects\260526_Walker_Leader\Assets\Blends\Traffic-Post.blend
A:\1 Amazon_Active_Projects\260127_Pick-and-Stage_2026_edits\Blends\animations\BD2\P&S_BD2_animation 12b.blend A:\1 Amazon_Active_Projects\260526_Walker_Leader\Blends\stills\iWalker_AP_9.blend
A:\1 Amazon_Active_Projects\260127_Pick-and-Stage_2026_edits\Blends\animations\BD2\P&S_BD2_animation 12a.blend A:\1 Amazon_Active_Projects\260526_Walker_Leader\Blends\stills\iWalker_AP_15.blend
P:\250827_FestivalTurf\Blends\animations\02 Preparing the Base\07_compacting.blend A:\1 Amazon_Active_Projects\260526_Walker_Leader\Blends\stills\iWalker_AP_12.blend
P:\250827_FestivalTurf\Blends\animations\02 Preparing the Base\03_dirt to gravel.blend A:\1 Amazon_Active_Projects\260526_Walker_Leader\Blends\stills\iWalker_AP_13.blend
P:\250827_FestivalTurf\Blends\animations\02 Preparing the Base\09_level ground.blend A:\1 Amazon_Active_Projects\260526_Walker_Leader\Blends\stills\iWalker_AP_14.blend
P:\250827_FestivalTurf\Blends\animations\02 Preparing the Base\05_raking.blend A:\1 Amazon_Active_Projects\260526_Walker_Leader\Blends\stills\iWalker_AP_10.blend
P:\250827_FestivalTurf\Assets\Blends\Props\Tools.blend A:\1 Amazon_Active_Projects\260526_Walker_Leader\Blends\animations\Walker_AP_animation 1.blend
P:\250827_FestivalTurf\Blends\animations\01 Develop Your Turf Plan\01_Blueprint.blend A:\1 Amazon_Active_Projects\260526_Walker_Leader\Blends\animations\Walker_AP_animation 1A.blend
A:\1 Amazon_Active_Projects\260326_RSR-Fluid-Unload\Blends\animations\RSR_fluid_unload_animation 6defgh.blend A:\1 Amazon_Active_Projects\260526_Walker_Leader\Blends\animations\Walker_AP_animation 1B.blend
A:\1 Amazon_Active_Projects\260326_RSR-Fluid-Unload\Blends\animations\RSR_fluid_unload_animation 6bc.blend A:\1 Amazon_Active_Projects\260526_Walker_Leader\Assets\Blends\Delivery_Truck_Openable_v1.1.blend
A:\1 Amazon_Active_Projects\260326_RSR-Fluid-Unload\Blends\animations\RSR_fluid_unload_animation 6a.blend A:\1 Amazon_Active_Projects\260526_Walker_Leader\Assets\Blends\Delivery_Truck_Openable.blend
P:\250827_FestivalTurf\Blends\animations\02 Preparing the Base\04_Rock Yard.blend A:\1 Amazon_Active_Projects\260526_Walker_Leader\Blends\stills\iWalker_AP_4.blend
A:\1 Amazon_Active_Projects\260326_RSR-Fluid-Unload\Blends\animations\RSR_fluid_unload_animation 6I.blend A:\1 Amazon_Active_Projects\260420_FLOW_2026\Blends\animations\FlowCSA_360_1aA.blend
P:\250827_FestivalTurf\Blends\animations\02 Preparing the Base\01_intro.blend A:\1 Amazon_Active_Projects\260420_FLOW_2026\Assets\Blends\lipsync.blend
P:\250827_FestivalTurf\Blends\animations\07 Final Touches And Maintenance\Visual_new_final.blend A:\1 Amazon_Active_Projects\260420_FLOW_2026\Blends\animations\FlowCSA_360_1a.blend
P:\250827_FestivalTurf\Blends\animations\07 Final Touches And Maintenance\Visual_8.blend A:\1 Amazon_Active_Projects\260506_Safety_School_Transformation_2026\Blends\animations\SS_2026_animation_5c.blend
P:\250827_FestivalTurf\Blends\animations\07 Final Touches And Maintenance\Visual_7.blend A:\1 Amazon_Active_Projects\260506_Safety_School_Transformation_2026\Blends\animations\SS_2026_animation_17bA.blend
P:\250827_FestivalTurf\Blends\animations\07 Final Touches And Maintenance\Visual_7_phone_insert.blend A:\1 Amazon_Active_Projects\260506_Safety_School_Transformation_2026\Blends\animations\SS_2026_animation_13aB.blend
P:\250827_FestivalTurf\Blends\animations\07 Final Touches And Maintenance\Visual_7_reframe.blend A:\1 Amazon_Active_Projects\260506_Safety_School_Transformation_2026\Assets\Blends\lipsync.blend
P:\250827_FestivalTurf\Blends\animations\07 Final Touches And Maintenance\Visual_7_phone_insert_alt.blend A:\1 Amazon_Active_Projects\260506_Safety_School_Transformation_2026\Blends\animations\SS_2026_animation_16d.blend
P:\250827_FestivalTurf\Blends\animations\07 Final Touches And Maintenance\Visual_5_heat_damage_insert_1.blend A:\1 Amazon_Active_Projects\260506_Safety_School_Transformation_2026\Blends\animations\SS_2026_animation_13a.blend
P:\250827_FestivalTurf\Blends\animations\07 Final Touches And Maintenance\Visual_5.blend A:\1 Amazon_Active_Projects\260506_Safety_School_Transformation_2026\Blends\animations\SS_2026_animation_13a_CM.blend
P:\250827_FestivalTurf\Blends\animations\07 Final Touches And Maintenance\Visual_4_leaf_blower_insert.blend A:\1 Amazon_Active_Projects\260506_Safety_School_Transformation_2026\Blends\animations\SS_2026_animation_5b.blend
P:\250827_FestivalTurf\Blends\animations\07 Final Touches And Maintenance\Visual_4_leaf_blower.blend A:\1 Amazon_Active_Projects\1 BlenderAssets\Amazon\Char\Cartoon3\Kyle_v1.2.blend
P:\250827_FestivalTurf\Blends\animations\07 Final Touches And Maintenance\Visual_3_PE_spread.blend A:\1 Amazon_Active_Projects\260506_Safety_School_Transformation_2026\Blends\animations\SS_2026_animation_4c.blend
P:\250827_FestivalTurf\Blends\animations\07 Final Touches And Maintenance\Visual_2_push_broom.blend A:\1 Amazon_Active_Projects\260506_Safety_School_Transformation_2026\Assets\Blends\lanyard_rigged.blend
P:\250827_FestivalTurf\Blends\animations\07 Final Touches And Maintenance\Visual_2_power_broom.blend A:\1 Amazon_Active_Projects\260506_Safety_School_Transformation_2026\Blends\animations\SS_2026_animation_4d.blend
A:\1 Amazon_Active_Projects\260326_RSR-Fluid-Unload\Blends\animations\RSR_fluid_unload_SHORT_3b.blend A:\1 Amazon_Active_Projects\260506_Safety_School_Transformation_2026\Blends\animations\SS_2026_animation_16c_vertical.blend
A:\1 Amazon_Active_Projects\260326_RSR-Fluid-Unload\Blends\animations\RSR_fluid_unload_SHORT_5b.blend A:\1 Amazon_Active_Projects\260506_Safety_School_Transformation_2026\Blends\animations\SS_2026_animation_16c.blend
A:\1 Amazon_Active_Projects\260326_RSR-Fluid-Unload\Blends\animations\RSR_fluid_unload_SHORT_5aA.blend A:\1 Amazon_Active_Projects\260506_Safety_School_Transformation_2026\Blends\animations\SS_2026_animation_16e.blend
A:\1 Amazon_Active_Projects\260326_RSR-Fluid-Unload\Blends\animations\RSR_fluid_unload_SHORT_4bC.blend A:\1 Amazon_Active_Projects\260506_Safety_School_Transformation_2026\Blends\animations\SS_2026_animation_6a.blend
A:\1 Amazon_Active_Projects\260326_RSR-Fluid-Unload\Blends\animations\RSR_fluid_unload_SHORT_4bB.blend T:\260506_Safety_School_Transformation_2026\Blends\animations\SS_2026_animation_13a.blend
A:\1 Amazon_Active_Projects\260326_RSR-Fluid-Unload\Blends\animations\RSR_fluid_unload_SHORT_4bA.blend F:\jobs\SS_2026_animation_16d-vs40\_outside_project\A\1 Amazon_Active_Projects\1 BlenderAssets\Amazon\Char\Cartoon1\Regina_v4.4.blend
A:\1 Amazon_Active_Projects\260326_RSR-Fluid-Unload\Blends\animations\RSR_fluid_unload_SHORT_4aB.blend A:\1 Amazon_Active_Projects\1 BlenderAssets\Amazon\Props\Tote_v2.0.blend
A:\1 Amazon_Active_Projects\260326_RSR-Fluid-Unload\Blends\animations\RSR_fluid_unload_SHORT_4aA_part2.blend A:\1 Amazon_Active_Projects\1 BlenderAssets\Amazon\Set-Dressing\P2B_filler.blend
A:\1 Amazon_Active_Projects\260326_RSR-Fluid-Unload\Blends\animations\RSR_fluid_unload_SHORT_4aA.blend
A:\1 Amazon_Active_Projects\260326_RSR-Fluid-Unload\Blends\animations\RSR_fluid_unload_SHORT_3c.blend
A:\1 Amazon_Active_Projects\260326_RSR-Fluid-Unload\Blends\animations\RSR_fluid_unload_SHORT_3a_insert2.blend
A:\1 Amazon_Active_Projects\260326_RSR-Fluid-Unload\Blends\animations\RSR_fluid_unload_SHORT_3a_insert1.blend
A:\1 Amazon_Active_Projects\260326_RSR-Fluid-Unload\Blends\animations\RSR_fluid_unload_SHORT_3a.blend
A:\1 Amazon_Active_Projects\260326_RSR-Fluid-Unload\Blends\animations\RSR_fluid_unload_SHORT_2c.blend
A:\1 Amazon_Active_Projects\260326_RSR-Fluid-Unload\Blends\animations\RSR_fluid_unload_SHORT_2b.blend
A:\1 Amazon_Active_Projects\260326_RSR-Fluid-Unload\Blends\animations\RSR_fluid_unload_SHORT_2a.blend
A:\1 Amazon_Active_Projects\260326_RSR-Fluid-Unload\Blends\animations\RSR_fluid_unload_animation 4.blend
A:\1 Amazon_Active_Projects\260326_RSR-Fluid-Unload\Blends\animations\RSR_fluid_unload_animation 3A.blend
A:\1 Amazon_Active_Projects\260326_RSR-Fluid-Unload\Blends\animations\RSR_fluid_unload_animation 5a.blend
A:\1 Amazon_Active_Projects\260326_RSR-Fluid-Unload\Blends\animations\RSR_fluid_unload_animation 5d.blend
A:\1 Amazon_Active_Projects\260326_RSR-Fluid-Unload\Blends\animations\RSR_fluid_unload_animation 5bc.blend
F:\jobs\RSR_fluid_unload_animation 5a\RSR_fluid_unload_animation 5a.flamenco.blend
A:\@GMT-2026.04.02-18.00.00\1 Amazon_Active_Projects\260326_RSR-Fluid-Unload\Blends\animations\RSR_fluid_unload_animation 5a.blend
A:\@GMT-2026.04.02-17.00.00\1 Amazon_Active_Projects\260326_RSR-Fluid-Unload\Blends\animations\RSR_fluid_unload_animation 5a.blend
A:\1 Amazon_Active_Projects\260326_RSR-Fluid-Unload\Blends\animations\RSR_fluid_unload_animation 4b.blend
A:\1 Amazon_Active_Projects\260326_RSR-Fluid-Unload\Blends\animations\RSR_fluid_unload_animation 3C.blend
A:\1 Amazon_Active_Projects\260326_RSR-Fluid-Unload\Blends\animations\RSR_fluid_unload_animation 2.blend
A:\1 Amazon_Active_Projects\260326_RSR-Fluid-Unload\Blends\animations\RSR_fluid_unload_animation 1D.blend
A:\1 Amazon_Active_Projects\260326_RSR-Fluid-Unload\Blends\animations\RSR_fluid_unload_animation 1c.blend
A:\1 Amazon_Active_Projects\260326_RSR-Fluid-Unload\Blends\animations\RSR_fluid_unload_animation 1b_c.blend
A:\1 Amazon_Active_Projects\260326_RSR-Fluid-Unload\Blends\animations\RSR_fluid_unload_animation 1a.blend
A:\1 Amazon_Active_Projects\260326_RSR-Fluid-Unload\Blends\animations\RSR_fluid_unload_animation 1b_b.blend
A:\1 Amazon_Active_Projects\260326_RSR-Fluid-Unload\Blends\animations\RSR_fluid_unload_animation 1b_a.blend
A:\1 Amazon_Active_Projects\260326_RSR-Fluid-Unload\Blends\animations\RSR_fluid_unload_animation 1b.blend
A:\1 Amazon_Active_Projects\260225_Problem-Solve_2026\Blends\animations\PS_2026_animation short 1b.blend
A:\1 Amazon_Active_Projects\260326_RSR-Fluid-Unload\Blends\stills\iRSR_fluid_unload_7d.blend
A:\1 Amazon_Active_Projects\260326_RSR-Fluid-Unload\Blends\stills\iRSR_fluid_unload_7c.blend
A:\1 Amazon_Active_Projects\260326_RSR-Fluid-Unload\Blends\stills\iRSR_fluid_unload_24c.blend
A:\1 Amazon_Active_Projects\260326_RSR-Fluid-Unload\Blends\stills\iRSR_fluid_unload_25b.blend
A:\1 Amazon_Active_Projects\260326_RSR-Fluid-Unload\Blends\stills\iRSR_fluid_unload_25a.blend
A:\1 Amazon_Active_Projects\260326_RSR-Fluid-Unload\Blends\stills\iRSR_fluid_unload_24d.blend
A:\1 Amazon_Active_Projects\260326_RSR-Fluid-Unload\Blends\stills\iRSR_fluid_unload_24e.blend
A:\1 Amazon_Active_Projects\260317_NONCON_2026\Blends\animations\noncon_OOG_animation 1_CYCLES.blend
A:\1 Amazon_Active_Projects\260326_RSR-Fluid-Unload\Blends\stills\iRSR_fluid_unload_16e.blend
A:\1 Amazon_Active_Projects\260326_RSR-Fluid-Unload\Blends\stills\iRSR_fluid_unload_24a.blend
A:\1 Amazon_Active_Projects\260326_RSR-Fluid-Unload\Blends\stills\iRSR_fluid_unload_4b.blend
A:\1 Amazon_Active_Projects\260326_RSR-Fluid-Unload\Blends\stills\iRSR_fluid_unload_24b.blend
A:\1 Amazon_Active_Projects\260326_RSR-Fluid-Unload\Blends\stills\iRSR_fluid_unload_16o.blend
A:\1 Amazon_Active_Projects\1 BlenderAssets\Amazon\Props\Pallete_Broken_Planks.blend
A:\1 Amazon_Active_Projects\1 BlenderAssets\Amazon\Props\Pallete_Broken.blend
A:\1 Amazon_Active_Projects\260326_RSR-Fluid-Unload\Blends\stills\iRSR_fluid_unload_24b_.blend
C:\Users\Nathan\AppData\Local\Temp\2026-04-02_13-44_iRSR_fluid_unload_24b.blend
T:\1 BlenderAssets\Amazon\Props\Pallete_Broken.blend
A:\1 Amazon_Active_Projects\1 BlenderAssets\Amazon\amazon-3Dworld-assets_v4.0.blend A:\1 Amazon_Active_Projects\1 BlenderAssets\Amazon\amazon-3Dworld-assets_v4.0.blend
C:\Users\Nathan\AppData\Local\Temp\2026-04-02_12-09_RSR_fluid_unload_SHORT_3a.blend C:\Users\Nathan\AppData\Local\Temp\2026-05-21_11-46_SS_2026_animation_16d.blend
C:\Users\Nathan\AppData\Local\Temp\2026-04-02_12-11_RSR_fluid_unload_SHORT_3a.blend A:\1 Amazon_Active_Projects\260506_Safety_School_Transformation_2026\Blends\animations\SS_2026_animation_12A.blend
C:\Users\Nathan\AppData\Local\Temp\2026-04-02_12-13_RSR_fluid_unload_SHORT_3a.blend A:\1 Amazon_Active_Projects\260506_Safety_School_Transformation_2026\Blends\animations\SS_2026_animation_13bB.blend
A:\1 Amazon_Active_Projects\260326_RSR-Fluid-Unload\Blends\stills\iRSR_fluid_unload_19c.blend A:\1 Amazon_Active_Projects\260506_Safety_School_Transformation_2026\Blends\animations\SS_2026_animation_4b.blend
A:\1 Amazon_Active_Projects\260326_RSR-Fluid-Unload\Blends\stills\iRSR_fluid_unload_23b.blend A:\1 Amazon_Active_Projects\260506_Safety_School_Transformation_2026\Blends\animations\SS_2026_animation_13bA.blend
A:\1 Amazon_Active_Projects\260326_RSR-Fluid-Unload\Blends\stills\iRSR_fluid_unload_23a.blend F:\jobs\SS_2026_animation_13bA\_outside_project\A\1 Amazon_Active_Projects\1 BlenderAssets\Amazon\Char\Cartoon3\Ren_v1.2.blend
A:\1 Amazon_Active_Projects\1 BlenderAssets\Amazon\Props\Long-Pallete.blend A:\1 Amazon_Active_Projects\260506_Safety_School_Transformation_2026\Blends\animations\SS_2026_animation_13b.blend
A:\1 Amazon_Active_Projects\1 BlenderAssets\Amazon\Props\Shuttle_v2.0.blend F:\jobs\SS_2026_animation_13a\_outside_project\A\1 Amazon_Active_Projects\1 BlenderAssets\Amazon\Char\Cartoon3\Ren_v1.2.blend
A:\1 Amazon_Active_Projects\260326_RSR-Fluid-Unload\Blends\stills\iRSR_fluid_unload_19d.blend A:\1 Amazon_Active_Projects\1 BlenderAssets\Amazon\Char\Cartoon3\Ren_v1.2.blend
A:\1 Amazon_Active_Projects\260326_RSR-Fluid-Unload\Blends\stills\iRSR_fluid_unload_7.blend A:\1 Amazon_Active_Projects\260506_Safety_School_Transformation_2026\Blends\animations\SS_2026_animation_8cB.blend
A:\1 Amazon_Active_Projects\260317_NONCON_2026\Blends\stills\i_noncon_OOG_2c.blend A:\1 Amazon_Active_Projects\260506_Safety_School_Transformation_2026\Blends\animations\SS_2026_animation_15bB.blend
A:\1 Amazon_Active_Projects\260326_RSR-Fluid-Unload\Blends\stills\iRSR_fluid_unload_19e.blend A:\1 Amazon_Active_Projects\260506_Safety_School_Transformation_2026\Blends\animations\SS_2026_animation_8cB_vertical.blend
A:\1 Amazon_Active_Projects\260206_Dock_Unified\Blends\animations\Dock\DOCK_shrinkwrap_s3.blend P:\260423_MukBuddy\Blends\animations\Pak Buddy Animation_v2_vert.blend
A:\1 Amazon_Active_Projects\260206_Dock_Unified\Blends\animations\Dock\DOCK_shrinkwrap_s4.blend C:\Users\Nathan\AppData\Local\Temp\bat-ss2026-2b-pack\_outside_project\A\1 Amazon_Active_Projects\1 BlenderAssets\Amazon\Char\Cartoon1\Martha_v4.4.blend
A:\1 Amazon_Active_Projects\260206_Dock_Unified\Blends\animations\Dock\DOCK_shrinkwrap_s2.blend F:\jobs\FlowCSA_360_1bD-7c6o\_outside_project\A\1 Amazon_Active_Projects\1 BlenderAssets\Amazon\Char\Cartoon3\Bebe_v1.2.blend
A:\1 Amazon_Active_Projects\260206_Dock_Unified\Blends\animations\Dock\DOCK_shrinkwrap_s1.blend A:\1 Amazon_Active_Projects\260420_FLOW_2026\Blends\animations\FlowCSA_360_1bD.blend
A:\1 Amazon_Active_Projects\260326_RSR-Fluid-Unload\Assets\Blends\PAM.blend F:\jobs\FlowCSA_360_1bD-to93\_outside_project\A\1 Amazon_Active_Projects\1 BlenderAssets\Amazon\Char\Cartoon3\Bebe_v1.2.blend
P:\250827_FestivalTurf\Blends\animations\07 Final Touches And Maintenance\Visual_2_broom.blend A:\1 Amazon_Active_Projects\260420_FLOW_2026\Blends\animations\FlowCSA_360_1b_ADTA_D.blend
P:\250827_FestivalTurf\Blends\animations\07 Final Touches And Maintenance\Visual_1_respray_insert.blend F:\jobs\FlowCSA_360_1b_ADTA_D-ao45\_outside_project\A\1 Amazon_Active_Projects\1 BlenderAssets\Amazon\Char\Cartoon3\Bebe_v1.2.blend
P:\250827_FestivalTurf\Blends\animations\07 Final Touches And Maintenance\Visual_1_respray.blend F:\jobs\FlowCSA_360_1b_ADTA_D-n9z0\_outside_project\A\1 Amazon_Active_Projects\1 BlenderAssets\Amazon\Char\Cartoon3\Bebe_v1.2.blend
P:\250827_FestivalTurf\Blends\animations\07 Final Touches And Maintenance\Visual_0_5_sheering_insert.blend F:\jobs\FlowCSA_360_1b_ADTA_D\_outside_project\A\1 Amazon_Active_Projects\1 BlenderAssets\Amazon\Char\Cartoon3\Bebe_v1.2.blend
P:\250827_FestivalTurf\Blends\animations\07 Final Touches And Maintenance\Visual_0_5_talking.blend F:\jobs\FlowCSA_360_1b_ADTA_D-jyur\_outside_project\A\1 Amazon_Active_Projects\1 BlenderAssets\Amazon\Char\Cartoon3\Bebe_v1.2.blend
P:\250827_FestivalTurf\Blends\animations\06 Infill And Powerbrooming\Visual 5B_insert.blend A:\1 Amazon_Active_Projects\260420_FLOW_2026\Blends\animations\FlowCSA_360_1b_ADTA_B.blend
P:\250827_FestivalTurf\Blends\animations\06 Infill And Powerbrooming\Visual 5.blend A:\1 Amazon_Active_Projects\260420_FLOW_2026\Blends\animations\FlowCSA_360_1bB.blend
A:\1 Amazon_Active_Projects\260127_Pick-and-Stage_2026_edits\Blends\animations\2025_old\P&S_safety_refresher_1d_rev2.blend A:\1 Amazon_Active_Projects\260420_FLOW_2026\Blends\animations\FlowCSA_360_1b_ADTA_A.blend
A:\1 Amazon_Active_Projects\260127_Pick-and-Stage_2026_edits\Blends\animations\2025_old\P&S_safety_refresher_1c_rev2.blend C:\Users\Nathan\AppData\Local\Temp\2026-05-13_13-27_FlowCSA_360_1b_ADTA_A.blend
A:\1 Amazon_Active_Projects\260127_Pick-and-Stage_2026_edits\Blends\animations\2025_old\P&S_safety_refresher_1c.blend C:\Users\Nathan\AppData\Local\Temp\2026-05-13_12-55_FlowCSA_360_1b_ADTA_A.blend
A:\1 Amazon_Active_Projects\260317_NONCON_2026\Blends\animations\SPA_noncon_OOG_short_animation 9a.blend C:\Users\Nathan\AppData\Local\Temp\2026-05-13_13-03_FlowCSA_360_1b_ADTA_A.blend
A:\1 Amazon_Active_Projects\260317_NONCON_2026\Blends\animations\noncon_OOG_short_animation 9a_v3.blend C:\Users\Nathan\AppData\Local\Temp\2026-05-13_13-11_FlowCSA_360_1b_ADTA_A.blend
A:\1 Amazon_Active_Projects\260317_NONCON_2026\Blends\animations\noncon_OOG_short_animation 9a.blend A:\1 Amazon_Active_Projects\260420_FLOW_2026\Blends\animations\FlowCSA_360_1bA.blend
A:\1 Amazon_Active_Projects\260317_NONCON_2026\Blends\animations\noncon_OOG_short_animation 3a.blend C:\Users\Nathan\AppData\Local\Temp\2026-05-12_17-26_FlowCSA_360_1b_ADTA_B.blend
A:\1 Amazon_Active_Projects\260317_NONCON_2026\Assets\Blends\lipsync.blend F:\jobs\FlowCSA_360_1b_ADTA_C-jh9z\_outside_project\A\1 Amazon_Active_Projects\1 BlenderAssets\Amazon\Char\Cartoon3\Bebe_v1.2.blend
A:\1 Amazon_Active_Projects\260317_NONCON_2026\Blends\animations\noncon_OOG_short_animation 1b.blend F:\jobs\FlowCSA_360_1b_ADTA_C\_outside_project\A\1 Amazon_Active_Projects\1 BlenderAssets\Amazon\Char\Cartoon3\Bebe_v1.2.blend
A:\1 Amazon_Active_Projects\260127_Pick-and-Stage_2026_edits\Blends\animations\2026_new\P&S_2026_animation short 1c.blend A:\1 Amazon_Active_Projects\260420_FLOW_2026\Blends\animations\FlowCSA_360_1b_ADTA_C.blend
A:\1 Amazon_Active_Projects\260127_Pick-and-Stage_2026_edits\Blends\animations\2025_old\P&S2025_anim_2a.blend F:\jobs\FlowCSA_360_1b_ADTA_C-sgxq\_outside_project\A\1 Amazon_Active_Projects\1 BlenderAssets\Amazon\Char\Cartoon3\Bebe_v1.2.blend
A:\1 Amazon_Active_Projects\260127_Pick-and-Stage_2026_edits\Blends\animations\2025_old\P&S2025_anim_2b.blend F:\jobs\FlowCSA_360_1b_ADTA_C-n6rw\FlowCSA_360_1b_ADTA_C.blend
A:\1 Amazon_Active_Projects\260127_Pick-and-Stage_2026_edits\Blends\animations\2025_old\P&S2025_anim_2c.blend F:\jobs\FlowCSA_360_1b_ADTA_C-n6rw\_outside_project\A\1 Amazon_Active_Projects\1 BlenderAssets\Amazon\Char\Cartoon3\Bebe_v1.2.blend
A:\1 Amazon_Active_Projects\260127_Pick-and-Stage_2026_edits\Blends\stills\iP&S_2026_2b.blend C:\Users\Nathan\AppData\Local\Temp\bat-flow-not-relative-only\_outside_project\a\1 Amazon_Active_Projects\1 BlenderAssets\Amazon\Char\Cartoon3\Bebe_v1.2.blend
A:\1 Amazon_Active_Projects\260127_Pick-and-Stage_2026_edits\Blends\stills\iP&S_2026_2a.blend A:\1 Amazon_Active_Projects\260420_FLOW_2026\Blends\animations\FlowCSA_360_1bC.blend
A:\1 Amazon_Active_Projects\260127_Pick-and-Stage_2026_edits\Blends\stills\iP&S_2026_1b.blend A:\1 Amazon_Active_Projects\260512_Agile_Gemba\Assets\Blends\Marker.blend
T:\260127_Pick-and-Stage_2026_edits\Blends\stills\iP&S_2026_2b.blend C:\Users\Nathan\Downloads\SS_2026_animation_2b.blend
A:\1 Amazon_Active_Projects\260127_Pick-and-Stage_2026_edits\Blends\stills\2026_new\iP&S_2026_2b.blend A:\1 Amazon_Active_Projects\260422_Upstream_edits_2026\Blends\stills\iUP_M6_13.blend
A:\1 Amazon_Active_Projects\260127_Pick-and-Stage_2026_edits\Blends\stills\2026_new\iP&S_2026_2a.blend A:\1 Amazon_Active_Projects\260422_Upstream_edits_2026\Blends\stills\iUP_M6_12.blend
C:\Users\Nathan\AppData\Local\Temp\2026-03-27_15-23_2026-03-27_15-13_P&S2025_anim_2a.blend F:\jobs\Pak Buddy Animation_v2\Pak Buddy Animation_v2.flamenco.blend
C:\Users\Nathan\AppData\Local\Temp\2026-03-27_15-13_P&S2025_anim_2a.blend C:\Users\Nathan\Downloads\House.blend
C:\Users\Nathan\AppData\Local\Temp\2026-03-27_15-19_P&S2025_anim_2a.blend C:\Users\Nathan\Downloads\UP_M5_3.blend
C:\Users\Nathan\AppData\Local\Temp\2026-03-27_15-21_P&S2025_anim_2a.blend C:\Users\Nathan\Downloads\UP_M2_animation_d.blend
A:\1 Amazon_Active_Projects\260127_Pick-and-Stage_2026_edits\Blends\animations\2025_old\P&S2025_anim_3a.blend G:\Amazon\2024\240402_Amazon_Coaching&Upstream\Upstream\blends\mod 2\animation 1\iUP_M2_6.blend
A:\1 Amazon_Active_Projects\260127_Pick-and-Stage_2026_edits\Blends\animations\2025_old\P&S2025_anim_3b.blend A:\1 Amazon_Active_Projects\260422_Upstream_edits_2026\Blends\stills\iUP_M5_6.blend
A:\1 Amazon_Active_Projects\260127_Pick-and-Stage_2026_edits\Blends\animations\2025_old\P&S2025_anim_1a.blend A:\1 Amazon_Active_Projects\260422_Upstream_edits_2026\Blends\stills\iUP_M2_5.blend
P:\250827_FestivalTurf\Blends\animations\06 Infill And Powerbrooming\Visual 6.blend A:\1 Amazon_Active_Projects\260422_Upstream_edits_2026\Blends\stills\iUP_M5_13.blend
P:\250827_FestivalTurf\Blends\animations\05 Stretch Cut Nail\Visual 6.blend P:\250827_FestivalTurf\Blends\animations\06 Infill And Powerbrooming\comp_RR\Visual 2B_SPA_comp_RR.blend
P:\250827_FestivalTurf\Blends\animations\06 Infill And Powerbrooming\Visual 5B_alt_vo.blend P:\250827_FestivalTurf\Blends\animations\06 Infill And Powerbrooming\Visual 2B_SPA.blend
P:\250827_FestivalTurf\Blends\animations\06 Infill And Powerbrooming\Visual 5B.blend C:\Users\Nathan\Downloads\Visual 2B_SPA\Visual 2B_SPA.blend
P:\250827_FestivalTurf\Blends\animations\06 Infill And Powerbrooming\Visual 4B.blend F:\jobs\Visual 2B_SPA-393g\Visual 2B_SPA.blend
P:\250827_FestivalTurf\Blends\animations\06 Infill And Powerbrooming\Visual 4A.blend F:\jobs\Visual_5_SPA-ii75\Visual_5_SPA.blend
P:\250827_FestivalTurf\Blends\animations\06 Infill And Powerbrooming\Visual 4A_2.blend P:\250827_FestivalTurf\Blends\animations\07 Final Touches And Maintenance\Visual_5_SPA.blend
F:\jobs\Visual_5_SPA-ki6s\Visual_5_SPA.blend
C:\Users\Nathan\Downloads\Visual_5_SPA.blend
C:\Users\Nathan\Downloads\Visual_5_SPA_106-109.blend
P:\250827_FestivalTurf\Blends\animations\07 Final Touches And Maintenance\Visual_2_broom_SPA.blend
C:\Users\Nathan\Downloads\Visual_new_final_SPA\Visual_new_final_SPA.blend
P:\250827_FestivalTurf\Blends\animations\07 Final Touches And Maintenance\Visual_new_final_SPA.blend
C:\Users\Nathan\Downloads\Visual_new_final\Visual_new_final.blend
P:\250827_FestivalTurf\Blends\animations\07 Final Touches And Maintenance\Visual_new_final.blend
C:\Users\Nathan\Downloads\Visual_new_final.blend
C:\Users\Nathan\Downloads\Visual_new_final_lady_dancer\Visual_new_final_lady_dancer.blend
C:\Users\Nathan\Downloads\Visual_new_final_kid_and_dog\Visual_new_final_kid_and_dog.blend
T:\250827_FestivalTurf\Blends\animations\07 Final Touches And Maintenance\Visual_new_final_alt_shots.blend
F:\jobs\Visual_5_SPA-x1ig\Visual_5_SPA.blend
F:\jobs\Visual 3A-fec4\Visual 3A.blend
P:\250827_FestivalTurf\Blends\animations\06 Infill And Powerbrooming\Visual 3A.blend P:\250827_FestivalTurf\Blends\animations\06 Infill And Powerbrooming\Visual 3A.blend
E:\250827_FestivalTurf\Blends\animations\06 Infill And Powerbrooming\Visual 3A.blend C:\Users\Nathan\Downloads\Visual_new_final_SPA.blend
P:\250827_FestivalTurf\Blends\animations\06 Infill And Powerbrooming\Visual 3A_.blend P:\250827_FestivalTurf\Blends\animations\07 Final Touches And Maintenance\Visual_0_5_talking_SPA.blend
T:\250827_FestivalTurf\Blends\animations\06 Infill And Powerbrooming\Visual 3A.blend P:\250827_FestivalTurf\Blends\animations\07 Final Touches And Maintenance\Visual_0_opening_SPA.blend
T:\250827_FestivalTurf\Blends\animations\06 Infill And Powerbrooming\Visual 3B.blend C:\Users\Nathan\Downloads\Visual 6_SPA.blend
P:\250827_FestivalTurf\Blends\animations\06 Infill And Powerbrooming\01_opening.blend C:\Users\Nathan\Downloads\Visual 5B_alt_vo_SPA.blend
T:\250827_FestivalTurf\Blends\animations\06 Infill And Powerbrooming\Visual 2D.blend C:\Users\Nathan\Downloads\Visual 4A_SPA.blend
P:\250827_FestivalTurf\Blends\animations\06 Infill And Powerbrooming\Visual 2A.blend C:\Users\Nathan\Downloads\Visual 3B_SPA.blend
T:\250827_FestivalTurf\Blends\animations\06 Infill And Powerbrooming\Visual 2C.blend C:\Users\Nathan\Downloads\Visual 2D_SPA.blend
T:\250827_FestivalTurf\Blends\animations\06 Infill And Powerbrooming\Visual 2B.blend F:\jobs\Visual 5_strech_SPA\Visual 5_strech_SPA.blend
P:\250827_FestivalTurf\Assets\Blends\Char\FT-rig_moustache.blend P:\250827_FestivalTurf\Blends\animations\06 Infill And Powerbrooming\Visual 5_strech_SPA.blend
P:\250827_FestivalTurf\Assets\Blends\Char\FT-rig_moustache_fixed.blend P:\250827_FestivalTurf\Blends\animations\06 Infill And Powerbrooming\Visual 4A_SPA.blend
T:\250827_FestivalTurf\Blends\animations\05 Stretch Cut Nail\Visual 11.blend F:\jobs\Visual 3A-snms\Visual 3A.blend
P:\250827_FestivalTurf\Blends\animations\05 Stretch Cut Nail\Visual 11.blend C:\Users\Nathan\Downloads\01_opening_SPA.blend
P:\250827_FestivalTurf\Blends\animations\05 Stretch Cut Nail\Visual 10.blend P:\250827_FestivalTurf\Blends\animations\06 Infill And Powerbrooming\01_opening_SPA.blend
P:\250827_FestivalTurf\Blends\animations\05 Stretch Cut Nail\Visual 9.blend F:\jobs\Visual 5_strech_SPA\Visual 5_strech_SPA.flamenco.blend
P:\250827_FestivalTurf\Blends\animations\05 Stretch Cut Nail\Visual 8.blend P:\250827_FestivalTurf\Blends\animations\06 Infill And Powerbrooming\Visual 5B.blend
P:\250827_FestivalTurf\Blends\animations\05 Stretch Cut Nail\Visual 8 insert2.blend F:\jobs\Visual 3A-q4ro\Visual 3A.blend
P:\250827_FestivalTurf\Blends\animations\05 Stretch Cut Nail\Visual 8 insert.blend F:\jobs\Visual 3A\Visual 3A.blend
P:\250827_FestivalTurf\Blends\animations\05 Stretch Cut Nail\01_intro.blend P:\250827_FestivalTurf\Blends\animations\06 Infill And Powerbrooming\Visual 3B.blend
P:\250827_FestivalTurf\Blends\animations\05 Stretch Cut Nail\Visual 7.blend A:\1 Amazon_Active_Projects\260225_Problem-Solve_2026\Assets\Blends\AMZ-warehouse_v6.0_small.blend
P:\250827_FestivalTurf\Blends\animations\05 Stretch Cut Nail\Visual 6_INSERT.blend A:\1 Amazon_Active_Projects\260422_Upstream_edits_2026\Blends\animations\0TEST.blend
P:\250827_FestivalTurf\Blends\animations\05 Stretch Cut Nail\04_talking.blend A:\1 Amazon_Active_Projects\260422_Upstream_edits_2026\Blends\animations\UP_M1_animation1C.blend
P:\250827_FestivalTurf\Blends\animations\05 Stretch Cut Nail\04_stretching pattern.blend A:\1 Amazon_Active_Projects\260422_Upstream_edits_2026\Blends\animations\0TEMP.blend
P:\250827_FestivalTurf\Blends\animations\05 Stretch Cut Nail\03_FT Shuffle_Intro.blend A:\1 Amazon_Active_Projects\260225_Problem-Solve_2026\Blends\animations\PS_2026_animation 1CD.blend
P:\250827_FestivalTurf\Blends\animations\05 Stretch Cut Nail\03_FT Shuffle.blend A:\1 Amazon_Active_Projects\260422_Upstream_edits_2026\Blends\animations\UP_M1_animation1D.blend
P:\250827_FestivalTurf\Blends\animations\05 Stretch Cut Nail\02_talking.blend C:\Users\Nathan\Downloads\Upstream_LL_animation_new_C.blend
P:\250827_FestivalTurf\Blends\animations\05 Stretch Cut Nail\02_kicker insert.blend A:\1 Amazon_Active_Projects\260422_Upstream_edits_2026\Blends\animations\Upstream_LL_animation_new_C.blend
P:\260217_Jarvis-Defense\Blends\animations\Shot_Q.blend A:\1 Amazon_Active_Projects\260127_Pick-and-Stage_2026_edits\Blends\animations\2025_old\P&S2025_anim_5D.blend
F:\jobs\Shot_5c\Shot_Q.flamenco.blend A:\1 Amazon_Active_Projects\260127_Pick-and-Stage_2026_edits\Blends\animations\2025_old\P&S2025_anim_3c.blend
F:\jobs\Shot5c-e3zp\Shot_Q.flamenco.blend A:\1 Amazon_Active_Projects\260127_Pick-and-Stage_2026_edits\Blends\animations\2024_old\P&S_safety_refresher_3b.blend
P:\260217_Jarvis-Defense\Blends\animations\Shot_4.blend A:\1 Amazon_Active_Projects\260127_Pick-and-Stage_2026_edits\Blends\animations\2024_old\P&S_safety_refresher_3a.blend
P:\260217_Jarvis-Defense\Blends\animations\comp_RR\Shot_4a_holdout.blend A:\1 Amazon_Active_Projects\260317_NONCON_2026\Blends\stills\SOM\SOM_i_noncon_OOG_7b.blend
P:\260217_Jarvis-Defense\Blends\animations\comp_RR\Shot_4_comp.blend A:\1 Amazon_Active_Projects\260317_NONCON_2026\Blends\stills\i_noncon_OOG_7b.blend
F:\jobs\Shot_1b-qrzg\Shot_1.flamenco.blend A:\1 Amazon_Active_Projects\260317_NONCON_2026\Blends\stills\SPA\SPA_i_noncon_OOG_7b.blend
A:\1 Amazon_Active_Projects\260317_NONCON_2026\Blends\animations\SOM_noncon_OOG_short_animation 13a.blend
A:\1 Amazon_Active_Projects\260317_NONCON_2026\Blends\animations\noncon_OOG_short_animation 13a.blend
A:\1 Amazon_Active_Projects\260317_NONCON_2026\Blends\animations\HAT_noncon_OOG_short_animation 13a.blend
A:\1 Amazon_Active_Projects\260317_NONCON_2026\Blends\animations\noncon_OOG_animation 1_CYCLES.blend
A:\1 Amazon_Active_Projects\260317_NONCON_2026\Blends\stills\SOM\SOM_i_noncon_OOG_9k.blend
A:\1 Amazon_Active_Projects\260317_NONCON_2026\Blends\stills\i_noncon_OOG_9k.blend
A:\1 Amazon_Active_Projects\260317_NONCON_2026\Blends\stills\HAT\HAT_i_noncon_OOG_9k.blend
A:\1 Amazon_Active_Projects\260317_NONCON_2026\Blends\stills\SOM\SOM_i_noncon_OOG_9m.blend
A:\1 Amazon_Active_Projects\260317_NONCON_2026\Blends\stills\i_noncon_OOG_9m.blend
A:\1 Amazon_Active_Projects\260317_NONCON_2026\Blends\stills\i_noncon_OOG_9l.blend
A:\1 Amazon_Active_Projects\260317_NONCON_2026\Blends\stills\HAT\HAT_i_noncon_OOG_9m.blend
A:\1 Amazon_Active_Projects\260317_NONCON_2026\Blends\stills\SOM\SOM_i_noncon_OOG_9l.blend
A:\1 Amazon_Active_Projects\260317_NONCON_2026\Blends\stills\HAT\HAT_i_noncon_OOG_9l.blend
A:\1 Amazon_Active_Projects\260422_Upstream_edits_2026\Blends\animations\UP_M4_S01_b.blend
A:\1 Amazon_Active_Projects\260422_Upstream_edits_2026\Blends\animations\UP_M4_S01_c.blend
A:\1 Amazon_Active_Projects\260127_Pick-and-Stage_2026_edits\Blends\stills\2025_old\iP&S2025_30b.blend
A:\1 Amazon_Active_Projects\260127_Pick-and-Stage_2026_edits\Blends\stills\2025_old\iP&S2025_31a.blend
A:\1 Amazon_Active_Projects\260127_Pick-and-Stage_2026_edits\Blends\stills\2025_old\iP&S2025_26.blend
A:\1 Amazon_Active_Projects\260127_Pick-and-Stage_2026_edits\Blends\stills\2025_old\iP&S2025_22b.blend
A:\1 Amazon_Active_Projects\260127_Pick-and-Stage_2026_edits\Blends\stills\2025_old\iP&S2025_21.blend
A:\1 Amazon_Active_Projects\260127_Pick-and-Stage_2026_edits\Blends\stills\2025_old\iP&S2025_22_a.blend
A:\1 Amazon_Active_Projects\260127_Pick-and-Stage_2026_edits\Blends\stills\2025_old\iP&S2025_19.blend
A:\1 Amazon_Active_Projects\260127_Pick-and-Stage_2026_edits\Blends\stills\2025_old\i22.blend
A:\1 Amazon_Active_Projects\260127_Pick-and-Stage_2026_edits\Blends\animations\2025_old\Ambassador_M3_A4_picker_s1.blend
A:\1 Amazon_Active_Projects\260127_Pick-and-Stage_2026_edits\Blends\animations\2025_old\Ambassador_M3_A4_picker_s3.blend
C:\Users\Nathan\Downloads\Ambassador_M3_A4_picker_s3.blend
A:\1 Amazon_Active_Projects\260127_Pick-and-Stage_2026_edits\Blends\animations\2025_old\Ambassador_M3_A4_coach-s2.blend
A:\1 Amazon_Active_Projects\260127_Pick-and-Stage_2026_edits\Blends\animations\2025_old\Ambassador_M3_A4_coach-s1.blend
A:\1 Amazon_Active_Projects\260422_Upstream_edits_2026\Blends\animations\M4\UP_M4_S01_2hands.blend
G:\Amazon\2024\240402_Amazon_Coaching&Upstream\Upstream\blends\mod 4\UP_M4_S01.blend
G:\Amazon\2024\240402_Amazon_Coaching&Upstream\Upstream\blends\mod 4\UP_M4_S01_2hands.blend
Binary file not shown.
BIN
View File
Binary file not shown.
BIN
View File
Binary file not shown.
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -1,7 +1,7 @@
schema_version = "1.0.0" schema_version = "1.0.0"
id = "simple_renaming_panel" id = "simple_renaming_panel"
version = "2.1.5" version = "2.1.6"
name = "Simple Renaming" name = "Simple Renaming"
tagline = "Effortlessly rename multiple objects with this simple addon" tagline = "Effortlessly rename multiple objects with this simple addon"
maintainer = "Matthias Patscheider <patscheider.matthias@gmail.com>" maintainer = "Matthias Patscheider <patscheider.matthias@gmail.com>"
@@ -150,12 +150,20 @@ def register():
default='X', # Set a default value default='X', # Set a default value
) )
id_store.renaming_new_name = StringProperty(name="New Name", default='') id_store.renaming_new_name = StringProperty(
name="New Name",
description="Name pattern for renaming. Use @n for a numbered variable (configure its digits in Preferences). Use # in the Numerate field (right) to set the auto-suffix digit count",
default='',
)
id_store.renaming_search = StringProperty(name='Search', default='') id_store.renaming_search = StringProperty(name='Search', default='')
id_store.renaming_replace = StringProperty(name='Replace', default='') id_store.renaming_replace = StringProperty(name='Replace', default='')
id_store.renaming_suffix = StringProperty(name="Suffix", default='') id_store.renaming_suffix = StringProperty(name="Suffix", default='')
id_store.renaming_prefix = StringProperty(name="Prefix", default='') id_store.renaming_prefix = StringProperty(name="Prefix", default='')
id_store.renaming_numerate = StringProperty(name="Numerate", default='###') id_store.renaming_numerate = StringProperty(
name="Numerate Pattern",
description="Pattern for the auto-numeration suffix appended when Numerate is enabled. Each # represents one digit (e.g. ### → 001, 002, …). This does not affect the @n variable",
default='###',
)
id_store.renaming_sorting = bpy.props.BoolProperty( id_store.renaming_sorting = bpy.props.BoolProperty(
name="Sort Target Objects", name="Sort Target Objects",
@@ -169,10 +177,9 @@ def register():
id_store.renaming_matchcase = BoolProperty(name="Match Case", description="", default=True) id_store.renaming_matchcase = BoolProperty(name="Match Case", description="", default=True)
id_store.renaming_useRegex = BoolProperty(name="Use Regex", description="", default=False) id_store.renaming_useRegex = BoolProperty(name="Use Regex", description="", default=False)
id_store.renaming_use_enumerate = BoolProperty(name="Numerate", id_store.renaming_use_enumerate = BoolProperty(
description="Enable and Disable the numeration of objects. This can " name="Numerate",
"be especially useful in combination with the numeration " description="Automatically appends an incrementing numeric suffix to each renamed object. Configure the suffix format with # in the Numerate field, and the digit count for the @n variable in Preferences",
"variable @n",
default=True, default=True,
) )
id_store.renaming_base_numerate = IntProperty(name="Step Size", default=1) id_store.renaming_base_numerate = IntProperty(name="Step Size", default=1)
@@ -53,7 +53,11 @@ class BUTTON_OT_change_key(bpy.types.Operator):
def add_keymap(): def add_keymap():
km = bpy.context.window_manager.keyconfigs.active.keymaps.new(name="Window") wm = bpy.context.window_manager
kc = wm.keyconfigs.addon
if kc is None:
return
km = kc.keymaps.new(name="Window")
prefs = bpy.context.preferences.addons[base_package].preferences prefs = bpy.context.preferences.addons[base_package].preferences
kmi = km.keymap_items.new(idname='wm.call_panel', type=prefs.renaming_panel_type, value='PRESS', kmi = km.keymap_items.new(idname='wm.call_panel', type=prefs.renaming_panel_type, value='PRESS',
@@ -76,22 +80,33 @@ def add_key_to_keymap(idname, kmi, km, active=True):
def remove_key(context, idname, properties_name): def remove_key(context, idname, properties_name):
"""Removes addon hotkeys from the keymap""" """Removes addon hotkeys from the keymap"""
wm = bpy.context.window_manager wm = bpy.context.window_manager
km = wm.keyconfigs.active.keymaps['Window'] kc = wm.keyconfigs.addon
if kc is None:
return
km = kc.keymaps.get('Window')
if km is None:
return
for kmi in km.keymap_items: items_to_remove = [kmi for kmi in km.keymap_items
if kmi.idname == idname and kmi.properties.name == properties_name: if kmi.idname == idname and kmi.properties.name == properties_name]
for kmi in items_to_remove:
km.keymap_items.remove(kmi) km.keymap_items.remove(kmi)
def remove_keymap(): def remove_keymap():
"""Removes keys from the keymap. Currently, this is only called when unregistering the addon. """ """Removes keys from the keymap. Currently, this is only called when unregistering the addon. """
# only works for menus and pie menus
wm = bpy.context.window_manager wm = bpy.context.window_manager
km = wm.keyconfigs.active.keymaps['Window'] kc = wm.keyconfigs.addon
if kc is None:
return
km = kc.keymaps.get('Window')
if km is None:
return
for kmi in km.keymap_items: items_to_remove = [kmi for kmi in km.keymap_items
if hasattr(kmi.properties, 'name') and kmi.properties.name in ['VIEW3D_PT_tools_renaming_panel', if hasattr(kmi.properties, 'name') and kmi.properties.name in
'VIEW3D_PT_tools_type_suffix']: {'VIEW3D_PT_tools_renaming_panel', 'VIEW3D_PT_tools_type_suffix'}]
for kmi in items_to_remove:
km.keymap_items.remove(kmi) km.keymap_items.remove(kmi)
@@ -28,7 +28,12 @@ def add_key(km, idname, properties_name, button_assignment_type, button_assignme
def update_key(context, operation, operator_name, property_prefix): def update_key(context, operation, operator_name, property_prefix):
# This functions gets called when the hotkey assignment is updated in the preferences # This functions gets called when the hotkey assignment is updated in the preferences
wm = context.window_manager wm = context.window_manager
km = wm.keyconfigs.active.keymaps["Window"] kc = wm.keyconfigs.addon
if kc is None:
return
km = kc.keymaps.get("Window")
if km is None:
return
prefs = context.preferences.addons[base_package].preferences prefs = context.preferences.addons[base_package].preferences
@@ -129,7 +134,7 @@ class VIEW3D_OT_renaming_preferences(bpy.types.AddonPreferences):
numerate_digits: bpy.props.IntProperty( numerate_digits: bpy.props.IntProperty(
name="Digits", name="Digits",
description="Defines digits used for numerating. Number 1 with digits 3 would result in 001", description="Number of digits used for the @n variable in the New Name field (e.g. @n with 3 digits: 001). To configure the auto-suffix digit count, use # characters in the Numerate field instead",
default=3, default=3,
) )
numerate_step: bpy.props.IntProperty( numerate_step: bpy.props.IntProperty(
@@ -58,7 +58,7 @@
"id": "atomic_data_manager", "id": "atomic_data_manager",
"name": "Atomic Data Manager", "name": "Atomic Data Manager",
"tagline": "Smart cleanup and inspection of Blender data-blocks", "tagline": "Smart cleanup and inspection of Blender data-blocks",
"version": "2.6.2", "version": "2.6.3",
"type": "add-on", "type": "add-on",
"maintainer": "RaincloudTheDragon", "maintainer": "RaincloudTheDragon",
"license": [ "license": [
@@ -70,9 +70,9 @@
"management", "management",
"cleanup" "cleanup"
], ],
"archive_url": "https://github.com/RaincloudTheDragon/atomic-data-manager/releases/download/v2.6.2/Atomic_Data_Manager.v2.6.2.zip", "archive_url": "https://github.com/RaincloudTheDragon/atomic-data-manager/releases/download/v2.6.3/Atomic_Data_Manager.v2.6.3.zip",
"archive_size": 121191, "archive_size": 122700,
"archive_hash": "sha256:1f4af882cdf73d3bb0b8cf1badc094b179bf9e982486ee516c45a6a2d478c05d" "archive_hash": "sha256:b444463a0443864077abcfe97332406e606a697d3567ac32f85843760da11372"
}, },
{ {
"schema_version": "1.0.0", "schema_version": "1.0.0",
@@ -0,0 +1,100 @@
{
"version": "v1",
"blocklist": [],
"data": [
{
"schema_version": "1.0.0",
"id": "basedplayblast",
"name": "BasedPlayblast",
"tagline": "Easily create playblasts from Blender and Flamenco",
"version": "2.6.3",
"type": "add-on",
"maintainer": "RaincloudTheDragon <raincloudthedragon@gmail.com>",
"license": [
"GPL-3.0-or-later"
],
"blender_version_min": "4.2.0",
"website": "https://github.com/RaincloudTheDragon/BasedPlayblast",
"permissions": {
"files": "Import/export files and data"
},
"tags": [
"Animation",
"Render",
"Workflow",
"Video"
],
"archive_url": "https://github.com/RaincloudTheDragon/BasedPlayblast/releases/download/v2.6.3/BasedPlayblast.v2.6.3.zip",
"archive_size": 49732,
"archive_hash": "sha256:078b406105ce6f4802e75233569841e2f73d082e09cd1d954696681ebf72b627"
},
{
"schema_version": "1.0.0",
"id": "rainclouds_bulk_scene_tools",
"name": "Raincloud's Bulk Scene Tools",
"tagline": "Bulk utilities for optimizing scene data",
"version": "0.17.0",
"type": "add-on",
"maintainer": "RaincloudTheDragon <raincloudthedragon@gmail.com>",
"license": [
"GPL-3.0-or-later"
],
"blender_version_min": "4.2.0",
"website": "https://github.com/RaincloudTheDragon/Rainys-Bulk-Scene-Tools",
"permissions": {
"files": "Read and write external resources referenced by scenes"
},
"tags": [
"Scene",
"Workflow",
"Materials"
],
"archive_url": "https://github.com/RaincloudTheDragon/Rainys-Bulk-Scene-Tools/releases/download/v0.17.0/Rainys_Bulk_Scene_Tools.v0.17.0.zip",
"archive_size": 80981,
"archive_hash": "sha256:419433069465b45ea903bd7bb46d89aa28b9c96c541d587d5f3be651a762811f"
},
{
"schema_version": "1.0.0",
"id": "atomic_data_manager",
"name": "Atomic Data Manager",
"tagline": "Smart cleanup and inspection of Blender data-blocks",
"version": "2.6.3",
"type": "add-on",
"maintainer": "RaincloudTheDragon",
"license": [
"GPL-3.0-or-later"
],
"blender_version_min": "4.2.0",
"tags": [
"utility",
"management",
"cleanup"
],
"archive_url": "https://github.com/RaincloudTheDragon/atomic-data-manager/releases/download/v2.6.3/Atomic_Data_Manager.v2.6.3.zip",
"archive_size": 122700,
"archive_hash": "sha256:b444463a0443864077abcfe97332406e606a697d3567ac32f85843760da11372"
},
{
"schema_version": "1.0.0",
"id": "sheepit_project_submitter",
"name": "SheepIt Project Submitter",
"tagline": "Submit projects to SheepIt render farm",
"version": "0.0.8",
"type": "add-on",
"maintainer": "RaincloudTheDragon",
"license": [
"GPL-3.0-or-later"
],
"blender_version_min": "3.0.0",
"tags": [
"render",
"farm",
"submission",
"utility"
],
"archive_url": "https://github.com/RaincloudTheDragon/sheepit_project_submitter/releases/download/v0.0.8/SheepIt_Project_Submitter.v0.0.8.zip",
"archive_size": 47667,
"archive_hash": "sha256:93cd8f18456079130c48c66cfd40235f7fe6414f929f59f90670e7a864821110"
}
]
}
@@ -0,0 +1,100 @@
{
"version": "v1",
"blocklist": [],
"data": [
{
"schema_version": "1.0.0",
"id": "basedplayblast",
"name": "BasedPlayblast",
"tagline": "Easily create playblasts from Blender and Flamenco",
"version": "2.6.3",
"type": "add-on",
"maintainer": "RaincloudTheDragon <raincloudthedragon@gmail.com>",
"license": [
"GPL-3.0-or-later"
],
"blender_version_min": "4.2.0",
"website": "https://github.com/RaincloudTheDragon/BasedPlayblast",
"permissions": {
"files": "Import/export files and data"
},
"tags": [
"Animation",
"Render",
"Workflow",
"Video"
],
"archive_url": "https://github.com/RaincloudTheDragon/BasedPlayblast/releases/download/v2.6.3/BasedPlayblast.v2.6.3.zip",
"archive_size": 49732,
"archive_hash": "sha256:078b406105ce6f4802e75233569841e2f73d082e09cd1d954696681ebf72b627"
},
{
"schema_version": "1.0.0",
"id": "rainclouds_bulk_scene_tools",
"name": "Raincloud's Bulk Scene Tools",
"tagline": "Bulk utilities for optimizing scene data",
"version": "0.17.0",
"type": "add-on",
"maintainer": "RaincloudTheDragon <raincloudthedragon@gmail.com>",
"license": [
"GPL-3.0-or-later"
],
"blender_version_min": "4.2.0",
"website": "https://github.com/RaincloudTheDragon/Rainys-Bulk-Scene-Tools",
"permissions": {
"files": "Read and write external resources referenced by scenes"
},
"tags": [
"Scene",
"Workflow",
"Materials"
],
"archive_url": "https://github.com/RaincloudTheDragon/Rainys-Bulk-Scene-Tools/releases/download/v0.17.0/Rainys_Bulk_Scene_Tools.v0.17.0.zip",
"archive_size": 80981,
"archive_hash": "sha256:419433069465b45ea903bd7bb46d89aa28b9c96c541d587d5f3be651a762811f"
},
{
"schema_version": "1.0.0",
"id": "atomic_data_manager",
"name": "Atomic Data Manager",
"tagline": "Smart cleanup and inspection of Blender data-blocks",
"version": "2.6.3",
"type": "add-on",
"maintainer": "RaincloudTheDragon",
"license": [
"GPL-3.0-or-later"
],
"blender_version_min": "4.2.0",
"tags": [
"utility",
"management",
"cleanup"
],
"archive_url": "https://github.com/RaincloudTheDragon/atomic-data-manager/releases/download/v2.6.3/Atomic_Data_Manager.v2.6.3.zip",
"archive_size": 122700,
"archive_hash": "sha256:b444463a0443864077abcfe97332406e606a697d3567ac32f85843760da11372"
},
{
"schema_version": "1.0.0",
"id": "sheepit_project_submitter",
"name": "SheepIt Project Submitter",
"tagline": "Submit projects to SheepIt render farm",
"version": "0.0.8",
"type": "add-on",
"maintainer": "RaincloudTheDragon",
"license": [
"GPL-3.0-or-later"
],
"blender_version_min": "3.0.0",
"tags": [
"render",
"farm",
"submission",
"utility"
],
"archive_url": "https://github.com/RaincloudTheDragon/sheepit_project_submitter/releases/download/v0.0.8/SheepIt_Project_Submitter.v0.0.8.zip",
"archive_size": 47667,
"archive_hash": "sha256:93cd8f18456079130c48c66cfd40235f7fe6414f929f59f90670e7a864821110"
}
]
}
@@ -0,0 +1,100 @@
{
"version": "v1",
"blocklist": [],
"data": [
{
"schema_version": "1.0.0",
"id": "basedplayblast",
"name": "BasedPlayblast",
"tagline": "Easily create playblasts from Blender and Flamenco",
"version": "2.6.3",
"type": "add-on",
"maintainer": "RaincloudTheDragon <raincloudthedragon@gmail.com>",
"license": [
"GPL-3.0-or-later"
],
"blender_version_min": "4.2.0",
"website": "https://github.com/RaincloudTheDragon/BasedPlayblast",
"permissions": {
"files": "Import/export files and data"
},
"tags": [
"Animation",
"Render",
"Workflow",
"Video"
],
"archive_url": "https://github.com/RaincloudTheDragon/BasedPlayblast/releases/download/v2.6.3/BasedPlayblast.v2.6.3.zip",
"archive_size": 49732,
"archive_hash": "sha256:078b406105ce6f4802e75233569841e2f73d082e09cd1d954696681ebf72b627"
},
{
"schema_version": "1.0.0",
"id": "rainclouds_bulk_scene_tools",
"name": "Raincloud's Bulk Scene Tools",
"tagline": "Bulk utilities for optimizing scene data",
"version": "0.17.0",
"type": "add-on",
"maintainer": "RaincloudTheDragon <raincloudthedragon@gmail.com>",
"license": [
"GPL-3.0-or-later"
],
"blender_version_min": "4.2.0",
"website": "https://github.com/RaincloudTheDragon/Rainys-Bulk-Scene-Tools",
"permissions": {
"files": "Read and write external resources referenced by scenes"
},
"tags": [
"Scene",
"Workflow",
"Materials"
],
"archive_url": "https://github.com/RaincloudTheDragon/Rainys-Bulk-Scene-Tools/releases/download/v0.17.0/Rainys_Bulk_Scene_Tools.v0.17.0.zip",
"archive_size": 80981,
"archive_hash": "sha256:419433069465b45ea903bd7bb46d89aa28b9c96c541d587d5f3be651a762811f"
},
{
"schema_version": "1.0.0",
"id": "atomic_data_manager",
"name": "Atomic Data Manager",
"tagline": "Smart cleanup and inspection of Blender data-blocks",
"version": "2.6.3",
"type": "add-on",
"maintainer": "RaincloudTheDragon",
"license": [
"GPL-3.0-or-later"
],
"blender_version_min": "4.2.0",
"tags": [
"utility",
"management",
"cleanup"
],
"archive_url": "https://github.com/RaincloudTheDragon/atomic-data-manager/releases/download/v2.6.3/Atomic_Data_Manager.v2.6.3.zip",
"archive_size": 122700,
"archive_hash": "sha256:b444463a0443864077abcfe97332406e606a697d3567ac32f85843760da11372"
},
{
"schema_version": "1.0.0",
"id": "sheepit_project_submitter",
"name": "SheepIt Project Submitter",
"tagline": "Submit projects to SheepIt render farm",
"version": "0.0.8",
"type": "add-on",
"maintainer": "RaincloudTheDragon",
"license": [
"GPL-3.0-or-later"
],
"blender_version_min": "3.0.0",
"tags": [
"render",
"farm",
"submission",
"utility"
],
"archive_url": "https://github.com/RaincloudTheDragon/sheepit_project_submitter/releases/download/v0.0.8/SheepIt_Project_Submitter.v0.0.8.zip",
"archive_size": 47667,
"archive_hash": "sha256:93cd8f18456079130c48c66cfd40235f7fe6414f929f59f90670e7a864821110"
}
]
}
@@ -0,0 +1,100 @@
{
"version": "v1",
"blocklist": [],
"data": [
{
"schema_version": "1.0.0",
"id": "basedplayblast",
"name": "BasedPlayblast",
"tagline": "Easily create playblasts from Blender and Flamenco",
"version": "2.6.3",
"type": "add-on",
"maintainer": "RaincloudTheDragon <raincloudthedragon@gmail.com>",
"license": [
"GPL-3.0-or-later"
],
"blender_version_min": "4.2.0",
"website": "https://github.com/RaincloudTheDragon/BasedPlayblast",
"permissions": {
"files": "Import/export files and data"
},
"tags": [
"Animation",
"Render",
"Workflow",
"Video"
],
"archive_url": "https://github.com/RaincloudTheDragon/BasedPlayblast/releases/download/v2.6.3/BasedPlayblast.v2.6.3.zip",
"archive_size": 49732,
"archive_hash": "sha256:078b406105ce6f4802e75233569841e2f73d082e09cd1d954696681ebf72b627"
},
{
"schema_version": "1.0.0",
"id": "rainclouds_bulk_scene_tools",
"name": "Raincloud's Bulk Scene Tools",
"tagline": "Bulk utilities for optimizing scene data",
"version": "0.17.0",
"type": "add-on",
"maintainer": "RaincloudTheDragon <raincloudthedragon@gmail.com>",
"license": [
"GPL-3.0-or-later"
],
"blender_version_min": "4.2.0",
"website": "https://github.com/RaincloudTheDragon/Rainys-Bulk-Scene-Tools",
"permissions": {
"files": "Read and write external resources referenced by scenes"
},
"tags": [
"Scene",
"Workflow",
"Materials"
],
"archive_url": "https://github.com/RaincloudTheDragon/Rainys-Bulk-Scene-Tools/releases/download/v0.17.0/Rainys_Bulk_Scene_Tools.v0.17.0.zip",
"archive_size": 80981,
"archive_hash": "sha256:419433069465b45ea903bd7bb46d89aa28b9c96c541d587d5f3be651a762811f"
},
{
"schema_version": "1.0.0",
"id": "atomic_data_manager",
"name": "Atomic Data Manager",
"tagline": "Smart cleanup and inspection of Blender data-blocks",
"version": "2.6.3",
"type": "add-on",
"maintainer": "RaincloudTheDragon",
"license": [
"GPL-3.0-or-later"
],
"blender_version_min": "4.2.0",
"tags": [
"utility",
"management",
"cleanup"
],
"archive_url": "https://github.com/RaincloudTheDragon/atomic-data-manager/releases/download/v2.6.3/Atomic_Data_Manager.v2.6.3.zip",
"archive_size": 122700,
"archive_hash": "sha256:b444463a0443864077abcfe97332406e606a697d3567ac32f85843760da11372"
},
{
"schema_version": "1.0.0",
"id": "sheepit_project_submitter",
"name": "SheepIt Project Submitter",
"tagline": "Submit projects to SheepIt render farm",
"version": "0.0.8",
"type": "add-on",
"maintainer": "RaincloudTheDragon",
"license": [
"GPL-3.0-or-later"
],
"blender_version_min": "3.0.0",
"tags": [
"render",
"farm",
"submission",
"utility"
],
"archive_url": "https://github.com/RaincloudTheDragon/sheepit_project_submitter/releases/download/v0.0.8/SheepIt_Project_Submitter.v0.0.8.zip",
"archive_size": 47667,
"archive_hash": "sha256:93cd8f18456079130c48c66cfd40235f7fe6414f929f59f90670e7a864821110"
}
]
}
@@ -0,0 +1,100 @@
{
"version": "v1",
"blocklist": [],
"data": [
{
"schema_version": "1.0.0",
"id": "basedplayblast",
"name": "BasedPlayblast",
"tagline": "Easily create playblasts from Blender and Flamenco",
"version": "2.6.3",
"type": "add-on",
"maintainer": "RaincloudTheDragon <raincloudthedragon@gmail.com>",
"license": [
"GPL-3.0-or-later"
],
"blender_version_min": "4.2.0",
"website": "https://github.com/RaincloudTheDragon/BasedPlayblast",
"permissions": {
"files": "Import/export files and data"
},
"tags": [
"Animation",
"Render",
"Workflow",
"Video"
],
"archive_url": "https://github.com/RaincloudTheDragon/BasedPlayblast/releases/download/v2.6.3/BasedPlayblast.v2.6.3.zip",
"archive_size": 49732,
"archive_hash": "sha256:078b406105ce6f4802e75233569841e2f73d082e09cd1d954696681ebf72b627"
},
{
"schema_version": "1.0.0",
"id": "rainclouds_bulk_scene_tools",
"name": "Raincloud's Bulk Scene Tools",
"tagline": "Bulk utilities for optimizing scene data",
"version": "0.17.0",
"type": "add-on",
"maintainer": "RaincloudTheDragon <raincloudthedragon@gmail.com>",
"license": [
"GPL-3.0-or-later"
],
"blender_version_min": "4.2.0",
"website": "https://github.com/RaincloudTheDragon/Rainys-Bulk-Scene-Tools",
"permissions": {
"files": "Read and write external resources referenced by scenes"
},
"tags": [
"Scene",
"Workflow",
"Materials"
],
"archive_url": "https://github.com/RaincloudTheDragon/Rainys-Bulk-Scene-Tools/releases/download/v0.17.0/Rainys_Bulk_Scene_Tools.v0.17.0.zip",
"archive_size": 80981,
"archive_hash": "sha256:419433069465b45ea903bd7bb46d89aa28b9c96c541d587d5f3be651a762811f"
},
{
"schema_version": "1.0.0",
"id": "atomic_data_manager",
"name": "Atomic Data Manager",
"tagline": "Smart cleanup and inspection of Blender data-blocks",
"version": "2.6.3",
"type": "add-on",
"maintainer": "RaincloudTheDragon",
"license": [
"GPL-3.0-or-later"
],
"blender_version_min": "4.2.0",
"tags": [
"utility",
"management",
"cleanup"
],
"archive_url": "https://github.com/RaincloudTheDragon/atomic-data-manager/releases/download/v2.6.3/Atomic_Data_Manager.v2.6.3.zip",
"archive_size": 122700,
"archive_hash": "sha256:b444463a0443864077abcfe97332406e606a697d3567ac32f85843760da11372"
},
{
"schema_version": "1.0.0",
"id": "sheepit_project_submitter",
"name": "SheepIt Project Submitter",
"tagline": "Submit projects to SheepIt render farm",
"version": "0.0.8",
"type": "add-on",
"maintainer": "RaincloudTheDragon",
"license": [
"GPL-3.0-or-later"
],
"blender_version_min": "3.0.0",
"tags": [
"render",
"farm",
"submission",
"utility"
],
"archive_url": "https://github.com/RaincloudTheDragon/sheepit_project_submitter/releases/download/v0.0.8/SheepIt_Project_Submitter.v0.0.8.zip",
"archive_size": 47667,
"archive_hash": "sha256:93cd8f18456079130c48c66cfd40235f7fe6414f929f59f90670e7a864821110"
}
]
}
@@ -0,0 +1,100 @@
{
"version": "v1",
"blocklist": [],
"data": [
{
"schema_version": "1.0.0",
"id": "basedplayblast",
"name": "BasedPlayblast",
"tagline": "Easily create playblasts from Blender and Flamenco",
"version": "2.6.3",
"type": "add-on",
"maintainer": "RaincloudTheDragon <raincloudthedragon@gmail.com>",
"license": [
"GPL-3.0-or-later"
],
"blender_version_min": "4.2.0",
"website": "https://github.com/RaincloudTheDragon/BasedPlayblast",
"permissions": {
"files": "Import/export files and data"
},
"tags": [
"Animation",
"Render",
"Workflow",
"Video"
],
"archive_url": "https://github.com/RaincloudTheDragon/BasedPlayblast/releases/download/v2.6.3/BasedPlayblast.v2.6.3.zip",
"archive_size": 49732,
"archive_hash": "sha256:078b406105ce6f4802e75233569841e2f73d082e09cd1d954696681ebf72b627"
},
{
"schema_version": "1.0.0",
"id": "rainclouds_bulk_scene_tools",
"name": "Raincloud's Bulk Scene Tools",
"tagline": "Bulk utilities for optimizing scene data",
"version": "0.17.0",
"type": "add-on",
"maintainer": "RaincloudTheDragon <raincloudthedragon@gmail.com>",
"license": [
"GPL-3.0-or-later"
],
"blender_version_min": "4.2.0",
"website": "https://github.com/RaincloudTheDragon/Rainys-Bulk-Scene-Tools",
"permissions": {
"files": "Read and write external resources referenced by scenes"
},
"tags": [
"Scene",
"Workflow",
"Materials"
],
"archive_url": "https://github.com/RaincloudTheDragon/Rainys-Bulk-Scene-Tools/releases/download/v0.17.0/Rainys_Bulk_Scene_Tools.v0.17.0.zip",
"archive_size": 80981,
"archive_hash": "sha256:419433069465b45ea903bd7bb46d89aa28b9c96c541d587d5f3be651a762811f"
},
{
"schema_version": "1.0.0",
"id": "atomic_data_manager",
"name": "Atomic Data Manager",
"tagline": "Smart cleanup and inspection of Blender data-blocks",
"version": "2.6.3",
"type": "add-on",
"maintainer": "RaincloudTheDragon",
"license": [
"GPL-3.0-or-later"
],
"blender_version_min": "4.2.0",
"tags": [
"utility",
"management",
"cleanup"
],
"archive_url": "https://github.com/RaincloudTheDragon/atomic-data-manager/releases/download/v2.6.3/Atomic_Data_Manager.v2.6.3.zip",
"archive_size": 122700,
"archive_hash": "sha256:b444463a0443864077abcfe97332406e606a697d3567ac32f85843760da11372"
},
{
"schema_version": "1.0.0",
"id": "sheepit_project_submitter",
"name": "SheepIt Project Submitter",
"tagline": "Submit projects to SheepIt render farm",
"version": "0.0.8",
"type": "add-on",
"maintainer": "RaincloudTheDragon",
"license": [
"GPL-3.0-or-later"
],
"blender_version_min": "3.0.0",
"tags": [
"render",
"farm",
"submission",
"utility"
],
"archive_url": "https://github.com/RaincloudTheDragon/sheepit_project_submitter/releases/download/v0.0.8/SheepIt_Project_Submitter.v0.0.8.zip",
"archive_size": 47667,
"archive_hash": "sha256:93cd8f18456079130c48c66cfd40235f7fe6414f929f59f90670e7a864821110"
}
]
}
@@ -0,0 +1,100 @@
{
"version": "v1",
"blocklist": [],
"data": [
{
"schema_version": "1.0.0",
"id": "basedplayblast",
"name": "BasedPlayblast",
"tagline": "Easily create playblasts from Blender and Flamenco",
"version": "2.6.3",
"type": "add-on",
"maintainer": "RaincloudTheDragon <raincloudthedragon@gmail.com>",
"license": [
"GPL-3.0-or-later"
],
"blender_version_min": "4.2.0",
"website": "https://github.com/RaincloudTheDragon/BasedPlayblast",
"permissions": {
"files": "Import/export files and data"
},
"tags": [
"Animation",
"Render",
"Workflow",
"Video"
],
"archive_url": "https://github.com/RaincloudTheDragon/BasedPlayblast/releases/download/v2.6.3/BasedPlayblast.v2.6.3.zip",
"archive_size": 49732,
"archive_hash": "sha256:078b406105ce6f4802e75233569841e2f73d082e09cd1d954696681ebf72b627"
},
{
"schema_version": "1.0.0",
"id": "rainclouds_bulk_scene_tools",
"name": "Raincloud's Bulk Scene Tools",
"tagline": "Bulk utilities for optimizing scene data",
"version": "0.17.0",
"type": "add-on",
"maintainer": "RaincloudTheDragon <raincloudthedragon@gmail.com>",
"license": [
"GPL-3.0-or-later"
],
"blender_version_min": "4.2.0",
"website": "https://github.com/RaincloudTheDragon/Rainys-Bulk-Scene-Tools",
"permissions": {
"files": "Read and write external resources referenced by scenes"
},
"tags": [
"Scene",
"Workflow",
"Materials"
],
"archive_url": "https://github.com/RaincloudTheDragon/Rainys-Bulk-Scene-Tools/releases/download/v0.17.0/Rainys_Bulk_Scene_Tools.v0.17.0.zip",
"archive_size": 80981,
"archive_hash": "sha256:419433069465b45ea903bd7bb46d89aa28b9c96c541d587d5f3be651a762811f"
},
{
"schema_version": "1.0.0",
"id": "atomic_data_manager",
"name": "Atomic Data Manager",
"tagline": "Smart cleanup and inspection of Blender data-blocks",
"version": "2.6.3",
"type": "add-on",
"maintainer": "RaincloudTheDragon",
"license": [
"GPL-3.0-or-later"
],
"blender_version_min": "4.2.0",
"tags": [
"utility",
"management",
"cleanup"
],
"archive_url": "https://github.com/RaincloudTheDragon/atomic-data-manager/releases/download/v2.6.3/Atomic_Data_Manager.v2.6.3.zip",
"archive_size": 122700,
"archive_hash": "sha256:b444463a0443864077abcfe97332406e606a697d3567ac32f85843760da11372"
},
{
"schema_version": "1.0.0",
"id": "sheepit_project_submitter",
"name": "SheepIt Project Submitter",
"tagline": "Submit projects to SheepIt render farm",
"version": "0.0.8",
"type": "add-on",
"maintainer": "RaincloudTheDragon",
"license": [
"GPL-3.0-or-later"
],
"blender_version_min": "3.0.0",
"tags": [
"render",
"farm",
"submission",
"utility"
],
"archive_url": "https://github.com/RaincloudTheDragon/sheepit_project_submitter/releases/download/v0.0.8/SheepIt_Project_Submitter.v0.0.8.zip",
"archive_size": 47667,
"archive_hash": "sha256:93cd8f18456079130c48c66cfd40235f7fe6414f929f59f90670e7a864821110"
}
]
}
@@ -0,0 +1,100 @@
{
"version": "v1",
"blocklist": [],
"data": [
{
"schema_version": "1.0.0",
"id": "basedplayblast",
"name": "BasedPlayblast",
"tagline": "Easily create playblasts from Blender and Flamenco",
"version": "2.6.3",
"type": "add-on",
"maintainer": "RaincloudTheDragon <raincloudthedragon@gmail.com>",
"license": [
"GPL-3.0-or-later"
],
"blender_version_min": "4.2.0",
"website": "https://github.com/RaincloudTheDragon/BasedPlayblast",
"permissions": {
"files": "Import/export files and data"
},
"tags": [
"Animation",
"Render",
"Workflow",
"Video"
],
"archive_url": "https://github.com/RaincloudTheDragon/BasedPlayblast/releases/download/v2.6.3/BasedPlayblast.v2.6.3.zip",
"archive_size": 49732,
"archive_hash": "sha256:078b406105ce6f4802e75233569841e2f73d082e09cd1d954696681ebf72b627"
},
{
"schema_version": "1.0.0",
"id": "rainclouds_bulk_scene_tools",
"name": "Raincloud's Bulk Scene Tools",
"tagline": "Bulk utilities for optimizing scene data",
"version": "0.17.0",
"type": "add-on",
"maintainer": "RaincloudTheDragon <raincloudthedragon@gmail.com>",
"license": [
"GPL-3.0-or-later"
],
"blender_version_min": "4.2.0",
"website": "https://github.com/RaincloudTheDragon/Rainys-Bulk-Scene-Tools",
"permissions": {
"files": "Read and write external resources referenced by scenes"
},
"tags": [
"Scene",
"Workflow",
"Materials"
],
"archive_url": "https://github.com/RaincloudTheDragon/Rainys-Bulk-Scene-Tools/releases/download/v0.17.0/Rainys_Bulk_Scene_Tools.v0.17.0.zip",
"archive_size": 80981,
"archive_hash": "sha256:419433069465b45ea903bd7bb46d89aa28b9c96c541d587d5f3be651a762811f"
},
{
"schema_version": "1.0.0",
"id": "atomic_data_manager",
"name": "Atomic Data Manager",
"tagline": "Smart cleanup and inspection of Blender data-blocks",
"version": "2.6.3",
"type": "add-on",
"maintainer": "RaincloudTheDragon",
"license": [
"GPL-3.0-or-later"
],
"blender_version_min": "4.2.0",
"tags": [
"utility",
"management",
"cleanup"
],
"archive_url": "https://github.com/RaincloudTheDragon/atomic-data-manager/releases/download/v2.6.3/Atomic_Data_Manager.v2.6.3.zip",
"archive_size": 122700,
"archive_hash": "sha256:b444463a0443864077abcfe97332406e606a697d3567ac32f85843760da11372"
},
{
"schema_version": "1.0.0",
"id": "sheepit_project_submitter",
"name": "SheepIt Project Submitter",
"tagline": "Submit projects to SheepIt render farm",
"version": "0.0.8",
"type": "add-on",
"maintainer": "RaincloudTheDragon",
"license": [
"GPL-3.0-or-later"
],
"blender_version_min": "3.0.0",
"tags": [
"render",
"farm",
"submission",
"utility"
],
"archive_url": "https://github.com/RaincloudTheDragon/sheepit_project_submitter/releases/download/v0.0.8/SheepIt_Project_Submitter.v0.0.8.zip",
"archive_size": 47667,
"archive_hash": "sha256:93cd8f18456079130c48c66cfd40235f7fe6414f929f59f90670e7a864821110"
}
]
}
@@ -0,0 +1,100 @@
{
"version": "v1",
"blocklist": [],
"data": [
{
"schema_version": "1.0.0",
"id": "basedplayblast",
"name": "BasedPlayblast",
"tagline": "Easily create playblasts from Blender and Flamenco",
"version": "2.6.3",
"type": "add-on",
"maintainer": "RaincloudTheDragon <raincloudthedragon@gmail.com>",
"license": [
"GPL-3.0-or-later"
],
"blender_version_min": "4.2.0",
"website": "https://github.com/RaincloudTheDragon/BasedPlayblast",
"permissions": {
"files": "Import/export files and data"
},
"tags": [
"Animation",
"Render",
"Workflow",
"Video"
],
"archive_url": "https://github.com/RaincloudTheDragon/BasedPlayblast/releases/download/v2.6.3/BasedPlayblast.v2.6.3.zip",
"archive_size": 49732,
"archive_hash": "sha256:078b406105ce6f4802e75233569841e2f73d082e09cd1d954696681ebf72b627"
},
{
"schema_version": "1.0.0",
"id": "rainclouds_bulk_scene_tools",
"name": "Raincloud's Bulk Scene Tools",
"tagline": "Bulk utilities for optimizing scene data",
"version": "0.17.0",
"type": "add-on",
"maintainer": "RaincloudTheDragon <raincloudthedragon@gmail.com>",
"license": [
"GPL-3.0-or-later"
],
"blender_version_min": "4.2.0",
"website": "https://github.com/RaincloudTheDragon/Rainys-Bulk-Scene-Tools",
"permissions": {
"files": "Read and write external resources referenced by scenes"
},
"tags": [
"Scene",
"Workflow",
"Materials"
],
"archive_url": "https://github.com/RaincloudTheDragon/Rainys-Bulk-Scene-Tools/releases/download/v0.17.0/Rainys_Bulk_Scene_Tools.v0.17.0.zip",
"archive_size": 80981,
"archive_hash": "sha256:419433069465b45ea903bd7bb46d89aa28b9c96c541d587d5f3be651a762811f"
},
{
"schema_version": "1.0.0",
"id": "atomic_data_manager",
"name": "Atomic Data Manager",
"tagline": "Smart cleanup and inspection of Blender data-blocks",
"version": "2.6.3",
"type": "add-on",
"maintainer": "RaincloudTheDragon",
"license": [
"GPL-3.0-or-later"
],
"blender_version_min": "4.2.0",
"tags": [
"utility",
"management",
"cleanup"
],
"archive_url": "https://github.com/RaincloudTheDragon/atomic-data-manager/releases/download/v2.6.3/Atomic_Data_Manager.v2.6.3.zip",
"archive_size": 122700,
"archive_hash": "sha256:b444463a0443864077abcfe97332406e606a697d3567ac32f85843760da11372"
},
{
"schema_version": "1.0.0",
"id": "sheepit_project_submitter",
"name": "SheepIt Project Submitter",
"tagline": "Submit projects to SheepIt render farm",
"version": "0.0.8",
"type": "add-on",
"maintainer": "RaincloudTheDragon",
"license": [
"GPL-3.0-or-later"
],
"blender_version_min": "3.0.0",
"tags": [
"render",
"farm",
"submission",
"utility"
],
"archive_url": "https://github.com/RaincloudTheDragon/sheepit_project_submitter/releases/download/v0.0.8/SheepIt_Project_Submitter.v0.0.8.zip",
"archive_size": 47667,
"archive_hash": "sha256:93cd8f18456079130c48c66cfd40235f7fe6414f929f59f90670e7a864821110"
}
]
}
@@ -0,0 +1,100 @@
{
"version": "v1",
"blocklist": [],
"data": [
{
"schema_version": "1.0.0",
"id": "basedplayblast",
"name": "BasedPlayblast",
"tagline": "Easily create playblasts from Blender and Flamenco",
"version": "2.6.3",
"type": "add-on",
"maintainer": "RaincloudTheDragon <raincloudthedragon@gmail.com>",
"license": [
"GPL-3.0-or-later"
],
"blender_version_min": "4.2.0",
"website": "https://github.com/RaincloudTheDragon/BasedPlayblast",
"permissions": {
"files": "Import/export files and data"
},
"tags": [
"Animation",
"Render",
"Workflow",
"Video"
],
"archive_url": "https://github.com/RaincloudTheDragon/BasedPlayblast/releases/download/v2.6.3/BasedPlayblast.v2.6.3.zip",
"archive_size": 49732,
"archive_hash": "sha256:078b406105ce6f4802e75233569841e2f73d082e09cd1d954696681ebf72b627"
},
{
"schema_version": "1.0.0",
"id": "rainclouds_bulk_scene_tools",
"name": "Raincloud's Bulk Scene Tools",
"tagline": "Bulk utilities for optimizing scene data",
"version": "0.17.0",
"type": "add-on",
"maintainer": "RaincloudTheDragon <raincloudthedragon@gmail.com>",
"license": [
"GPL-3.0-or-later"
],
"blender_version_min": "4.2.0",
"website": "https://github.com/RaincloudTheDragon/Rainys-Bulk-Scene-Tools",
"permissions": {
"files": "Read and write external resources referenced by scenes"
},
"tags": [
"Scene",
"Workflow",
"Materials"
],
"archive_url": "https://github.com/RaincloudTheDragon/Rainys-Bulk-Scene-Tools/releases/download/v0.17.0/Rainys_Bulk_Scene_Tools.v0.17.0.zip",
"archive_size": 80981,
"archive_hash": "sha256:419433069465b45ea903bd7bb46d89aa28b9c96c541d587d5f3be651a762811f"
},
{
"schema_version": "1.0.0",
"id": "atomic_data_manager",
"name": "Atomic Data Manager",
"tagline": "Smart cleanup and inspection of Blender data-blocks",
"version": "2.6.3",
"type": "add-on",
"maintainer": "RaincloudTheDragon",
"license": [
"GPL-3.0-or-later"
],
"blender_version_min": "4.2.0",
"tags": [
"utility",
"management",
"cleanup"
],
"archive_url": "https://github.com/RaincloudTheDragon/atomic-data-manager/releases/download/v2.6.3/Atomic_Data_Manager.v2.6.3.zip",
"archive_size": 122700,
"archive_hash": "sha256:b444463a0443864077abcfe97332406e606a697d3567ac32f85843760da11372"
},
{
"schema_version": "1.0.0",
"id": "sheepit_project_submitter",
"name": "SheepIt Project Submitter",
"tagline": "Submit projects to SheepIt render farm",
"version": "0.0.8",
"type": "add-on",
"maintainer": "RaincloudTheDragon",
"license": [
"GPL-3.0-or-later"
],
"blender_version_min": "3.0.0",
"tags": [
"render",
"farm",
"submission",
"utility"
],
"archive_url": "https://github.com/RaincloudTheDragon/sheepit_project_submitter/releases/download/v0.0.8/SheepIt_Project_Submitter.v0.0.8.zip",
"archive_size": 47667,
"archive_hash": "sha256:93cd8f18456079130c48c66cfd40235f7fe6414f929f59f90670e7a864821110"
}
]
}
@@ -0,0 +1,100 @@
{
"version": "v1",
"blocklist": [],
"data": [
{
"schema_version": "1.0.0",
"id": "basedplayblast",
"name": "BasedPlayblast",
"tagline": "Easily create playblasts from Blender and Flamenco",
"version": "2.6.3",
"type": "add-on",
"maintainer": "RaincloudTheDragon <raincloudthedragon@gmail.com>",
"license": [
"GPL-3.0-or-later"
],
"blender_version_min": "4.2.0",
"website": "https://github.com/RaincloudTheDragon/BasedPlayblast",
"permissions": {
"files": "Import/export files and data"
},
"tags": [
"Animation",
"Render",
"Workflow",
"Video"
],
"archive_url": "https://github.com/RaincloudTheDragon/BasedPlayblast/releases/download/v2.6.3/BasedPlayblast.v2.6.3.zip",
"archive_size": 49732,
"archive_hash": "sha256:078b406105ce6f4802e75233569841e2f73d082e09cd1d954696681ebf72b627"
},
{
"schema_version": "1.0.0",
"id": "rainclouds_bulk_scene_tools",
"name": "Raincloud's Bulk Scene Tools",
"tagline": "Bulk utilities for optimizing scene data",
"version": "0.17.0",
"type": "add-on",
"maintainer": "RaincloudTheDragon <raincloudthedragon@gmail.com>",
"license": [
"GPL-3.0-or-later"
],
"blender_version_min": "4.2.0",
"website": "https://github.com/RaincloudTheDragon/Rainys-Bulk-Scene-Tools",
"permissions": {
"files": "Read and write external resources referenced by scenes"
},
"tags": [
"Scene",
"Workflow",
"Materials"
],
"archive_url": "https://github.com/RaincloudTheDragon/Rainys-Bulk-Scene-Tools/releases/download/v0.17.0/Rainys_Bulk_Scene_Tools.v0.17.0.zip",
"archive_size": 80981,
"archive_hash": "sha256:419433069465b45ea903bd7bb46d89aa28b9c96c541d587d5f3be651a762811f"
},
{
"schema_version": "1.0.0",
"id": "atomic_data_manager",
"name": "Atomic Data Manager",
"tagline": "Smart cleanup and inspection of Blender data-blocks",
"version": "2.6.3",
"type": "add-on",
"maintainer": "RaincloudTheDragon",
"license": [
"GPL-3.0-or-later"
],
"blender_version_min": "4.2.0",
"tags": [
"utility",
"management",
"cleanup"
],
"archive_url": "https://github.com/RaincloudTheDragon/atomic-data-manager/releases/download/v2.6.3/Atomic_Data_Manager.v2.6.3.zip",
"archive_size": 122700,
"archive_hash": "sha256:b444463a0443864077abcfe97332406e606a697d3567ac32f85843760da11372"
},
{
"schema_version": "1.0.0",
"id": "sheepit_project_submitter",
"name": "SheepIt Project Submitter",
"tagline": "Submit projects to SheepIt render farm",
"version": "0.0.8",
"type": "add-on",
"maintainer": "RaincloudTheDragon",
"license": [
"GPL-3.0-or-later"
],
"blender_version_min": "3.0.0",
"tags": [
"render",
"farm",
"submission",
"utility"
],
"archive_url": "https://github.com/RaincloudTheDragon/sheepit_project_submitter/releases/download/v0.0.8/SheepIt_Project_Submitter.v0.0.8.zip",
"archive_size": 47667,
"archive_hash": "sha256:93cd8f18456079130c48c66cfd40235f7fe6414f929f59f90670e7a864821110"
}
]
}
@@ -0,0 +1,100 @@
{
"version": "v1",
"blocklist": [],
"data": [
{
"schema_version": "1.0.0",
"id": "basedplayblast",
"name": "BasedPlayblast",
"tagline": "Easily create playblasts from Blender and Flamenco",
"version": "2.6.3",
"type": "add-on",
"maintainer": "RaincloudTheDragon <raincloudthedragon@gmail.com>",
"license": [
"GPL-3.0-or-later"
],
"blender_version_min": "4.2.0",
"website": "https://github.com/RaincloudTheDragon/BasedPlayblast",
"permissions": {
"files": "Import/export files and data"
},
"tags": [
"Animation",
"Render",
"Workflow",
"Video"
],
"archive_url": "https://github.com/RaincloudTheDragon/BasedPlayblast/releases/download/v2.6.3/BasedPlayblast.v2.6.3.zip",
"archive_size": 49732,
"archive_hash": "sha256:078b406105ce6f4802e75233569841e2f73d082e09cd1d954696681ebf72b627"
},
{
"schema_version": "1.0.0",
"id": "rainclouds_bulk_scene_tools",
"name": "Raincloud's Bulk Scene Tools",
"tagline": "Bulk utilities for optimizing scene data",
"version": "0.17.0",
"type": "add-on",
"maintainer": "RaincloudTheDragon <raincloudthedragon@gmail.com>",
"license": [
"GPL-3.0-or-later"
],
"blender_version_min": "4.2.0",
"website": "https://github.com/RaincloudTheDragon/Rainys-Bulk-Scene-Tools",
"permissions": {
"files": "Read and write external resources referenced by scenes"
},
"tags": [
"Scene",
"Workflow",
"Materials"
],
"archive_url": "https://github.com/RaincloudTheDragon/Rainys-Bulk-Scene-Tools/releases/download/v0.17.0/Rainys_Bulk_Scene_Tools.v0.17.0.zip",
"archive_size": 80981,
"archive_hash": "sha256:419433069465b45ea903bd7bb46d89aa28b9c96c541d587d5f3be651a762811f"
},
{
"schema_version": "1.0.0",
"id": "atomic_data_manager",
"name": "Atomic Data Manager",
"tagline": "Smart cleanup and inspection of Blender data-blocks",
"version": "2.6.3",
"type": "add-on",
"maintainer": "RaincloudTheDragon",
"license": [
"GPL-3.0-or-later"
],
"blender_version_min": "4.2.0",
"tags": [
"utility",
"management",
"cleanup"
],
"archive_url": "https://github.com/RaincloudTheDragon/atomic-data-manager/releases/download/v2.6.3/Atomic_Data_Manager.v2.6.3.zip",
"archive_size": 122700,
"archive_hash": "sha256:b444463a0443864077abcfe97332406e606a697d3567ac32f85843760da11372"
},
{
"schema_version": "1.0.0",
"id": "sheepit_project_submitter",
"name": "SheepIt Project Submitter",
"tagline": "Submit projects to SheepIt render farm",
"version": "0.0.8",
"type": "add-on",
"maintainer": "RaincloudTheDragon",
"license": [
"GPL-3.0-or-later"
],
"blender_version_min": "3.0.0",
"tags": [
"render",
"farm",
"submission",
"utility"
],
"archive_url": "https://github.com/RaincloudTheDragon/sheepit_project_submitter/releases/download/v0.0.8/SheepIt_Project_Submitter.v0.0.8.zip",
"archive_size": 47667,
"archive_hash": "sha256:93cd8f18456079130c48c66cfd40235f7fe6414f929f59f90670e7a864821110"
}
]
}
@@ -0,0 +1,100 @@
{
"version": "v1",
"blocklist": [],
"data": [
{
"schema_version": "1.0.0",
"id": "basedplayblast",
"name": "BasedPlayblast",
"tagline": "Easily create playblasts from Blender and Flamenco",
"version": "2.6.3",
"type": "add-on",
"maintainer": "RaincloudTheDragon <raincloudthedragon@gmail.com>",
"license": [
"GPL-3.0-or-later"
],
"blender_version_min": "4.2.0",
"website": "https://github.com/RaincloudTheDragon/BasedPlayblast",
"permissions": {
"files": "Import/export files and data"
},
"tags": [
"Animation",
"Render",
"Workflow",
"Video"
],
"archive_url": "https://github.com/RaincloudTheDragon/BasedPlayblast/releases/download/v2.6.3/BasedPlayblast.v2.6.3.zip",
"archive_size": 49732,
"archive_hash": "sha256:078b406105ce6f4802e75233569841e2f73d082e09cd1d954696681ebf72b627"
},
{
"schema_version": "1.0.0",
"id": "rainclouds_bulk_scene_tools",
"name": "Raincloud's Bulk Scene Tools",
"tagline": "Bulk utilities for optimizing scene data",
"version": "0.17.0",
"type": "add-on",
"maintainer": "RaincloudTheDragon <raincloudthedragon@gmail.com>",
"license": [
"GPL-3.0-or-later"
],
"blender_version_min": "4.2.0",
"website": "https://github.com/RaincloudTheDragon/Rainys-Bulk-Scene-Tools",
"permissions": {
"files": "Read and write external resources referenced by scenes"
},
"tags": [
"Scene",
"Workflow",
"Materials"
],
"archive_url": "https://github.com/RaincloudTheDragon/Rainys-Bulk-Scene-Tools/releases/download/v0.17.0/Rainys_Bulk_Scene_Tools.v0.17.0.zip",
"archive_size": 80981,
"archive_hash": "sha256:419433069465b45ea903bd7bb46d89aa28b9c96c541d587d5f3be651a762811f"
},
{
"schema_version": "1.0.0",
"id": "atomic_data_manager",
"name": "Atomic Data Manager",
"tagline": "Smart cleanup and inspection of Blender data-blocks",
"version": "2.6.3",
"type": "add-on",
"maintainer": "RaincloudTheDragon",
"license": [
"GPL-3.0-or-later"
],
"blender_version_min": "4.2.0",
"tags": [
"utility",
"management",
"cleanup"
],
"archive_url": "https://github.com/RaincloudTheDragon/atomic-data-manager/releases/download/v2.6.3/Atomic_Data_Manager.v2.6.3.zip",
"archive_size": 122700,
"archive_hash": "sha256:b444463a0443864077abcfe97332406e606a697d3567ac32f85843760da11372"
},
{
"schema_version": "1.0.0",
"id": "sheepit_project_submitter",
"name": "SheepIt Project Submitter",
"tagline": "Submit projects to SheepIt render farm",
"version": "0.0.8",
"type": "add-on",
"maintainer": "RaincloudTheDragon",
"license": [
"GPL-3.0-or-later"
],
"blender_version_min": "3.0.0",
"tags": [
"render",
"farm",
"submission",
"utility"
],
"archive_url": "https://github.com/RaincloudTheDragon/sheepit_project_submitter/releases/download/v0.0.8/SheepIt_Project_Submitter.v0.0.8.zip",
"archive_size": 47667,
"archive_hash": "sha256:93cd8f18456079130c48c66cfd40235f7fe6414f929f59f90670e7a864821110"
}
]
}
@@ -0,0 +1,100 @@
{
"version": "v1",
"blocklist": [],
"data": [
{
"schema_version": "1.0.0",
"id": "basedplayblast",
"name": "BasedPlayblast",
"tagline": "Easily create playblasts from Blender and Flamenco",
"version": "2.6.3",
"type": "add-on",
"maintainer": "RaincloudTheDragon <raincloudthedragon@gmail.com>",
"license": [
"GPL-3.0-or-later"
],
"blender_version_min": "4.2.0",
"website": "https://github.com/RaincloudTheDragon/BasedPlayblast",
"permissions": {
"files": "Import/export files and data"
},
"tags": [
"Animation",
"Render",
"Workflow",
"Video"
],
"archive_url": "https://github.com/RaincloudTheDragon/BasedPlayblast/releases/download/v2.6.3/BasedPlayblast.v2.6.3.zip",
"archive_size": 49732,
"archive_hash": "sha256:078b406105ce6f4802e75233569841e2f73d082e09cd1d954696681ebf72b627"
},
{
"schema_version": "1.0.0",
"id": "rainclouds_bulk_scene_tools",
"name": "Raincloud's Bulk Scene Tools",
"tagline": "Bulk utilities for optimizing scene data",
"version": "0.17.0",
"type": "add-on",
"maintainer": "RaincloudTheDragon <raincloudthedragon@gmail.com>",
"license": [
"GPL-3.0-or-later"
],
"blender_version_min": "4.2.0",
"website": "https://github.com/RaincloudTheDragon/Rainys-Bulk-Scene-Tools",
"permissions": {
"files": "Read and write external resources referenced by scenes"
},
"tags": [
"Scene",
"Workflow",
"Materials"
],
"archive_url": "https://github.com/RaincloudTheDragon/Rainys-Bulk-Scene-Tools/releases/download/v0.17.0/Rainys_Bulk_Scene_Tools.v0.17.0.zip",
"archive_size": 80981,
"archive_hash": "sha256:419433069465b45ea903bd7bb46d89aa28b9c96c541d587d5f3be651a762811f"
},
{
"schema_version": "1.0.0",
"id": "atomic_data_manager",
"name": "Atomic Data Manager",
"tagline": "Smart cleanup and inspection of Blender data-blocks",
"version": "2.6.3",
"type": "add-on",
"maintainer": "RaincloudTheDragon",
"license": [
"GPL-3.0-or-later"
],
"blender_version_min": "4.2.0",
"tags": [
"utility",
"management",
"cleanup"
],
"archive_url": "https://github.com/RaincloudTheDragon/atomic-data-manager/releases/download/v2.6.3/Atomic_Data_Manager.v2.6.3.zip",
"archive_size": 122700,
"archive_hash": "sha256:b444463a0443864077abcfe97332406e606a697d3567ac32f85843760da11372"
},
{
"schema_version": "1.0.0",
"id": "sheepit_project_submitter",
"name": "SheepIt Project Submitter",
"tagline": "Submit projects to SheepIt render farm",
"version": "0.0.8",
"type": "add-on",
"maintainer": "RaincloudTheDragon",
"license": [
"GPL-3.0-or-later"
],
"blender_version_min": "3.0.0",
"tags": [
"render",
"farm",
"submission",
"utility"
],
"archive_url": "https://github.com/RaincloudTheDragon/sheepit_project_submitter/releases/download/v0.0.8/SheepIt_Project_Submitter.v0.0.8.zip",
"archive_size": 47667,
"archive_hash": "sha256:93cd8f18456079130c48c66cfd40235f7fe6414f929f59f90670e7a864821110"
}
]
}
@@ -0,0 +1,100 @@
{
"version": "v1",
"blocklist": [],
"data": [
{
"schema_version": "1.0.0",
"id": "basedplayblast",
"name": "BasedPlayblast",
"tagline": "Easily create playblasts from Blender and Flamenco",
"version": "2.6.3",
"type": "add-on",
"maintainer": "RaincloudTheDragon <raincloudthedragon@gmail.com>",
"license": [
"GPL-3.0-or-later"
],
"blender_version_min": "4.2.0",
"website": "https://github.com/RaincloudTheDragon/BasedPlayblast",
"permissions": {
"files": "Import/export files and data"
},
"tags": [
"Animation",
"Render",
"Workflow",
"Video"
],
"archive_url": "https://github.com/RaincloudTheDragon/BasedPlayblast/releases/download/v2.6.3/BasedPlayblast.v2.6.3.zip",
"archive_size": 49732,
"archive_hash": "sha256:078b406105ce6f4802e75233569841e2f73d082e09cd1d954696681ebf72b627"
},
{
"schema_version": "1.0.0",
"id": "rainclouds_bulk_scene_tools",
"name": "Raincloud's Bulk Scene Tools",
"tagline": "Bulk utilities for optimizing scene data",
"version": "0.17.0",
"type": "add-on",
"maintainer": "RaincloudTheDragon <raincloudthedragon@gmail.com>",
"license": [
"GPL-3.0-or-later"
],
"blender_version_min": "4.2.0",
"website": "https://github.com/RaincloudTheDragon/Rainys-Bulk-Scene-Tools",
"permissions": {
"files": "Read and write external resources referenced by scenes"
},
"tags": [
"Scene",
"Workflow",
"Materials"
],
"archive_url": "https://github.com/RaincloudTheDragon/Rainys-Bulk-Scene-Tools/releases/download/v0.17.0/Rainys_Bulk_Scene_Tools.v0.17.0.zip",
"archive_size": 80981,
"archive_hash": "sha256:419433069465b45ea903bd7bb46d89aa28b9c96c541d587d5f3be651a762811f"
},
{
"schema_version": "1.0.0",
"id": "atomic_data_manager",
"name": "Atomic Data Manager",
"tagline": "Smart cleanup and inspection of Blender data-blocks",
"version": "2.6.3",
"type": "add-on",
"maintainer": "RaincloudTheDragon",
"license": [
"GPL-3.0-or-later"
],
"blender_version_min": "4.2.0",
"tags": [
"utility",
"management",
"cleanup"
],
"archive_url": "https://github.com/RaincloudTheDragon/atomic-data-manager/releases/download/v2.6.3/Atomic_Data_Manager.v2.6.3.zip",
"archive_size": 122700,
"archive_hash": "sha256:b444463a0443864077abcfe97332406e606a697d3567ac32f85843760da11372"
},
{
"schema_version": "1.0.0",
"id": "sheepit_project_submitter",
"name": "SheepIt Project Submitter",
"tagline": "Submit projects to SheepIt render farm",
"version": "0.0.8",
"type": "add-on",
"maintainer": "RaincloudTheDragon",
"license": [
"GPL-3.0-or-later"
],
"blender_version_min": "3.0.0",
"tags": [
"render",
"farm",
"submission",
"utility"
],
"archive_url": "https://github.com/RaincloudTheDragon/sheepit_project_submitter/releases/download/v0.0.8/SheepIt_Project_Submitter.v0.0.8.zip",
"archive_size": 47667,
"archive_hash": "sha256:93cd8f18456079130c48c66cfd40235f7fe6414f929f59f90670e7a864821110"
}
]
}
@@ -0,0 +1,100 @@
{
"version": "v1",
"blocklist": [],
"data": [
{
"schema_version": "1.0.0",
"id": "basedplayblast",
"name": "BasedPlayblast",
"tagline": "Easily create playblasts from Blender and Flamenco",
"version": "2.6.3",
"type": "add-on",
"maintainer": "RaincloudTheDragon <raincloudthedragon@gmail.com>",
"license": [
"GPL-3.0-or-later"
],
"blender_version_min": "4.2.0",
"website": "https://github.com/RaincloudTheDragon/BasedPlayblast",
"permissions": {
"files": "Import/export files and data"
},
"tags": [
"Animation",
"Render",
"Workflow",
"Video"
],
"archive_url": "https://github.com/RaincloudTheDragon/BasedPlayblast/releases/download/v2.6.3/BasedPlayblast.v2.6.3.zip",
"archive_size": 49732,
"archive_hash": "sha256:078b406105ce6f4802e75233569841e2f73d082e09cd1d954696681ebf72b627"
},
{
"schema_version": "1.0.0",
"id": "rainclouds_bulk_scene_tools",
"name": "Raincloud's Bulk Scene Tools",
"tagline": "Bulk utilities for optimizing scene data",
"version": "0.17.0",
"type": "add-on",
"maintainer": "RaincloudTheDragon <raincloudthedragon@gmail.com>",
"license": [
"GPL-3.0-or-later"
],
"blender_version_min": "4.2.0",
"website": "https://github.com/RaincloudTheDragon/Rainys-Bulk-Scene-Tools",
"permissions": {
"files": "Read and write external resources referenced by scenes"
},
"tags": [
"Scene",
"Workflow",
"Materials"
],
"archive_url": "https://github.com/RaincloudTheDragon/Rainys-Bulk-Scene-Tools/releases/download/v0.17.0/Rainys_Bulk_Scene_Tools.v0.17.0.zip",
"archive_size": 80981,
"archive_hash": "sha256:419433069465b45ea903bd7bb46d89aa28b9c96c541d587d5f3be651a762811f"
},
{
"schema_version": "1.0.0",
"id": "atomic_data_manager",
"name": "Atomic Data Manager",
"tagline": "Smart cleanup and inspection of Blender data-blocks",
"version": "2.6.3",
"type": "add-on",
"maintainer": "RaincloudTheDragon",
"license": [
"GPL-3.0-or-later"
],
"blender_version_min": "4.2.0",
"tags": [
"utility",
"management",
"cleanup"
],
"archive_url": "https://github.com/RaincloudTheDragon/atomic-data-manager/releases/download/v2.6.3/Atomic_Data_Manager.v2.6.3.zip",
"archive_size": 122700,
"archive_hash": "sha256:b444463a0443864077abcfe97332406e606a697d3567ac32f85843760da11372"
},
{
"schema_version": "1.0.0",
"id": "sheepit_project_submitter",
"name": "SheepIt Project Submitter",
"tagline": "Submit projects to SheepIt render farm",
"version": "0.0.8",
"type": "add-on",
"maintainer": "RaincloudTheDragon",
"license": [
"GPL-3.0-or-later"
],
"blender_version_min": "3.0.0",
"tags": [
"render",
"farm",
"submission",
"utility"
],
"archive_url": "https://github.com/RaincloudTheDragon/sheepit_project_submitter/releases/download/v0.0.8/SheepIt_Project_Submitter.v0.0.8.zip",
"archive_size": 47667,
"archive_hash": "sha256:93cd8f18456079130c48c66cfd40235f7fe6414f929f59f90670e7a864821110"
}
]
}
@@ -0,0 +1,100 @@
{
"version": "v1",
"blocklist": [],
"data": [
{
"schema_version": "1.0.0",
"id": "basedplayblast",
"name": "BasedPlayblast",
"tagline": "Easily create playblasts from Blender and Flamenco",
"version": "2.6.3",
"type": "add-on",
"maintainer": "RaincloudTheDragon <raincloudthedragon@gmail.com>",
"license": [
"GPL-3.0-or-later"
],
"blender_version_min": "4.2.0",
"website": "https://github.com/RaincloudTheDragon/BasedPlayblast",
"permissions": {
"files": "Import/export files and data"
},
"tags": [
"Animation",
"Render",
"Workflow",
"Video"
],
"archive_url": "https://github.com/RaincloudTheDragon/BasedPlayblast/releases/download/v2.6.3/BasedPlayblast.v2.6.3.zip",
"archive_size": 49732,
"archive_hash": "sha256:078b406105ce6f4802e75233569841e2f73d082e09cd1d954696681ebf72b627"
},
{
"schema_version": "1.0.0",
"id": "rainclouds_bulk_scene_tools",
"name": "Raincloud's Bulk Scene Tools",
"tagline": "Bulk utilities for optimizing scene data",
"version": "0.17.0",
"type": "add-on",
"maintainer": "RaincloudTheDragon <raincloudthedragon@gmail.com>",
"license": [
"GPL-3.0-or-later"
],
"blender_version_min": "4.2.0",
"website": "https://github.com/RaincloudTheDragon/Rainys-Bulk-Scene-Tools",
"permissions": {
"files": "Read and write external resources referenced by scenes"
},
"tags": [
"Scene",
"Workflow",
"Materials"
],
"archive_url": "https://github.com/RaincloudTheDragon/Rainys-Bulk-Scene-Tools/releases/download/v0.17.0/Rainys_Bulk_Scene_Tools.v0.17.0.zip",
"archive_size": 80981,
"archive_hash": "sha256:419433069465b45ea903bd7bb46d89aa28b9c96c541d587d5f3be651a762811f"
},
{
"schema_version": "1.0.0",
"id": "atomic_data_manager",
"name": "Atomic Data Manager",
"tagline": "Smart cleanup and inspection of Blender data-blocks",
"version": "2.6.3",
"type": "add-on",
"maintainer": "RaincloudTheDragon",
"license": [
"GPL-3.0-or-later"
],
"blender_version_min": "4.2.0",
"tags": [
"utility",
"management",
"cleanup"
],
"archive_url": "https://github.com/RaincloudTheDragon/atomic-data-manager/releases/download/v2.6.3/Atomic_Data_Manager.v2.6.3.zip",
"archive_size": 122700,
"archive_hash": "sha256:b444463a0443864077abcfe97332406e606a697d3567ac32f85843760da11372"
},
{
"schema_version": "1.0.0",
"id": "sheepit_project_submitter",
"name": "SheepIt Project Submitter",
"tagline": "Submit projects to SheepIt render farm",
"version": "0.0.8",
"type": "add-on",
"maintainer": "RaincloudTheDragon",
"license": [
"GPL-3.0-or-later"
],
"blender_version_min": "3.0.0",
"tags": [
"render",
"farm",
"submission",
"utility"
],
"archive_url": "https://github.com/RaincloudTheDragon/sheepit_project_submitter/releases/download/v0.0.8/SheepIt_Project_Submitter.v0.0.8.zip",
"archive_size": 47667,
"archive_hash": "sha256:93cd8f18456079130c48c66cfd40235f7fe6414f929f59f90670e7a864821110"
}
]
}
@@ -1,3 +1,9 @@
## [v2.6.3] - 2026-04-22
### Fixes
- **CC3 / iClone import caches**: `Scene['CC3ImportProps']` (e.g. `pbr_material_cache`) no longer blocks unused detection—when `material_all()` is empty, cache-only refs (including Blenders 2× `users` per cell) are ignored for clean and RNA material scans (`stats/ghost_users.py`).
## [v2.6.2] - 2026-04-06 ## [v2.6.2] - 2026-04-06
### Fixes ### Fixes
@@ -2,7 +2,7 @@ schema_version = "1.0.0"
id = "atomic_data_manager" id = "atomic_data_manager"
name = "Atomic Data Manager" name = "Atomic Data Manager"
version = "2.6.2" version = "2.6.3"
type = "add-on" type = "add-on"
author = "RaincloudTheDragon" author = "RaincloudTheDragon"
maintainer = "RaincloudTheDragon" maintainer = "RaincloudTheDragon"
@@ -0,0 +1,88 @@
"""
Detect 'ghost' ID references (e.g. Reallusion CC / iClone import caches on Scene)
that keep bpy.users > 0 but are not real scene usage. Used by materials_deep and
any future cleanup paths.
"""
import bpy
from .. import config
def _idprop_count_material(p, mat, seen):
"""Recursively count pointers equal to `mat` under an ID property value."""
if p is None or id(p) in seen:
return 0
if isinstance(p, (str, int, float, bool)):
return 0
seen = seen | {id(p)}
if isinstance(p, bpy.types.Material) and p == mat:
return 1
n = 0
t = type(p).__name__
if isinstance(p, (list, tuple)):
for x in p:
n += _idprop_count_material(x, mat, seen)
elif isinstance(p, dict):
for v in p.values():
n += _idprop_count_material(v, mat, seen)
elif t == "IDPropertyGroup" or (hasattr(p, "keys") and hasattr(p, "values") and t != "dict"):
try:
for k in p.keys():
try:
v = p[k]
except Exception:
continue
n += _idprop_count_material(v, mat, seen)
except Exception:
pass
return n
def count_cc3_import_cache_references(material):
"""
Count Material pointers stored in Scene ID property group CC3ImportProps
(Character Creator 3 / iClone pipeline). These are import-cache ghosts and
are not object/world/brush use.
"""
m = 0
for scene in bpy.data.scenes:
try:
if "CC3ImportProps" not in scene:
continue
except Exception:
continue
try:
cc3 = scene["CC3ImportProps"]
except Exception:
continue
m += _idprop_count_material(cc3, material, set())
return m
def material_blender_users_fully_cc3_ghosts(material):
"""
True if every material.user can be explained by CC3 import_cache pointers.
Blender often reports *two* users per pbr cache slot: the Scene and the
pbr_material_cache cell both contribute to bpy.types.Material.users, while
our walk only counts Material pointers in the idprop tree (one per cell).
So u == 2 * cc3 is normal; u == cc3 can occur when the count already matches
1:1 in some file versions.
"""
try:
u = material.users
except Exception:
return False
if u == 0:
return True
cc3 = count_cc3_import_cache_references(material)
if cc3 == 0:
return False
if cc3 == u or u == 2 * cc3:
return True
if config.enable_debug_prints:
config.debug_print(
f"[Atomic Debug] ghost_users: material '{material.name}' users={u} "
f"cc3_count={cc3} (not fully explained by CC3; keep conservative block)"
)
return False
@@ -28,6 +28,7 @@ import json
import os import os
from .. import config from .. import config
from ..utils import compat from ..utils import compat
from . import ghost_users
# Data-block types we care about for dependency analysis # Data-block types we care about for dependency analysis
@@ -1197,6 +1198,8 @@ def analyze_unused_from_graph(graph, category, include_fake_users=None):
if category == 'materials': if category == 'materials':
try: try:
if datablock.users > 0 and not datablock.use_fake_user: if datablock.users > 0 and not datablock.use_fake_user:
if not ghost_users.material_blender_users_fully_cc3_ghosts(
datablock):
continue continue
except (AttributeError, RuntimeError, ReferenceError): except (AttributeError, RuntimeError, ReferenceError):
pass pass
@@ -28,6 +28,7 @@ from .. import config
from ..utils import compat from ..utils import compat
from ..utils import version from ..utils import version
from . import users from . import users
from . import ghost_users
def shallow(data): def shallow(data):
@@ -223,9 +224,9 @@ def materials_deep():
# check if material has a fake user or if ignore fake users # check if material has a fake user or if ignore fake users
# is enabled # is enabled
if not material.use_fake_user or config.include_fake_users: if not material.use_fake_user or config.include_fake_users:
# If Blender still counts users but we found none, don't flag (name collisions
# with linked IDs, drivers, or refs we don't traverse). Fake-user purge unchanged.
if material.users > 0 and not material.use_fake_user: if material.users > 0 and not material.use_fake_user:
# CC3 / iClone import_cache ID props keep bpy.users>0 with no object/world use.
if not ghost_users.material_blender_users_fully_cc3_ghosts(material):
continue continue
unused.append(material.name) unused.append(material.name)
else: else:
@@ -1,6 +1,7 @@
import bpy import bpy
from ..stats import unused from ..stats import unused
from ..stats import users from ..stats import users
from . import ghost_users
from .. import config from .. import config
from ..utils import compat from ..utils import compat
@@ -128,6 +129,7 @@ def _has_any_unused_materials():
if not users.material_all(material.name): if not users.material_all(material.name):
if not material.use_fake_user or config.include_fake_users: if not material.use_fake_user or config.include_fake_users:
if material.users > 0 and not material.use_fake_user: if material.users > 0 and not material.use_fake_user:
if not ghost_users.material_blender_users_fully_cc3_ghosts(material):
continue continue
return True return True
else: else:
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
+27 -5
View File
@@ -20,7 +20,7 @@
bl_info = { bl_info = {
"name": "Animation Layers", "name": "Animation Layers",
"author": "Tal Hershkovich", "author": "Tal Hershkovich",
"version" : (2, 3, 8), "version" : (2, 4, 1),
"blender" : (3, 2, 0), "blender" : (3, 2, 0),
"location": "View3D - Properties - Animation Panel", "location": "View3D - Properties - Animation Panel",
"description": "Simplifying the NLA editor into an animation layers UI and workflow", "description": "Simplifying the NLA editor into an animation layers UI and workflow",
@@ -73,7 +73,10 @@ class AnimLayersSceneSettings(bpy.types.PropertyGroup):
class AnimLayersSettings(bpy.types.PropertyGroup): class AnimLayersSettings(bpy.types.PropertyGroup):
turn_on: bpy.props.BoolProperty(name="Turn Animation Layers On", description="Turn on and start Animation Layers", default=False, options={'HIDDEN'}, update = anim_layers.turn_animlayers_on, override = {'LIBRARY_OVERRIDABLE'}) turn_on: bpy.props.BoolProperty(name="Turn Animation Layers On", description="Turn on and start Animation Layers", default=False, options={'HIDDEN'}, update = anim_layers.turn_animlayers_on, override = {'LIBRARY_OVERRIDABLE'})
# Active row in obj.Anim_Layers. Post-migration, 1:1 with NLA-track index.
layer_index: bpy.props.IntProperty(update = anim_layers.update_layer_index, options={'LIBRARY_EDITABLE'}, default = 0, override = {'LIBRARY_OVERRIDABLE'}) layer_index: bpy.props.IntProperty(update = anim_layers.update_layer_index, options={'LIBRARY_EDITABLE'}, default = 0, override = {'LIBRARY_OVERRIDABLE'})
# Schema version. 0 = pre-hierarchical (legacy GROUP rows); 1 = hierarchical (parent_layer refs).
schema_version: bpy.props.IntProperty(name="Schema Version", default=0, options={'HIDDEN'}, override = {'LIBRARY_OVERRIDABLE'})
linked: bpy.props.BoolProperty(name="Linked", description="Duplicate a layer with a linked action", default=False, options={'HIDDEN'}, override = {'LIBRARY_OVERRIDABLE'}) linked: bpy.props.BoolProperty(name="Linked", description="Duplicate a layer with a linked action", default=False, options={'HIDDEN'}, override = {'LIBRARY_OVERRIDABLE'})
#Bake settings #Bake settings
@@ -132,6 +135,24 @@ class AnimLayersItems(bpy.types.PropertyGroup):
offset: bpy.props.FloatProperty(name='Offset when the action starts', description="Offseting the whole layer animation", default = 0, precision = 2, override = {'LIBRARY_OVERRIDABLE'}, update = anim_layers.layer_offset) offset: bpy.props.FloatProperty(name='Offset when the action starts', description="Offseting the whole layer animation", default = 0, precision = 2, override = {'LIBRARY_OVERRIDABLE'}, update = anim_layers.layer_offset)
repeat: bpy.props.FloatProperty(name="Repeat", description="Repeat the action", min = 0.1, default = 1, options={'HIDDEN'}, override = {'LIBRARY_OVERRIDABLE'}, update = anim_layers.layer_repeat) repeat: bpy.props.FloatProperty(name="Repeat", description="Repeat the action", min = 0.1, default = 1, options={'HIDDEN'}, override = {'LIBRARY_OVERRIDABLE'}, update = anim_layers.layer_repeat)
# Hierarchical layer fields. Every layer is NLA-backed; a layer is a
# "group" iff something else points to it via parent_layer.
expanded: bpy.props.BoolProperty(name="Expanded", description="Show this layer's children in the UI list", default=True, options={'HIDDEN'}, override = {'LIBRARY_OVERRIDABLE'})
parent_layer: bpy.props.StringProperty(name="Parent Layer", description="Name of this layer's parent (empty = root)", default="", options={'HIDDEN'}, override = {'LIBRARY_OVERRIDABLE'})
group_color: bpy.props.FloatVectorProperty(name="Group Color", subtype='COLOR', size=4, min=0.0, max=1.0, default=(0.3, 0.5, 0.8, 1.0), options={'HIDDEN'}, override = {'LIBRARY_OVERRIDABLE'})
# Dynamic-enum dropdown for picking parent_layer in the UI. The setter
# refuses cycles (self + descendants are excluded from the options).
assigned_group: bpy.props.EnumProperty(name='Parent', description='Set this layer\'s parent (None = root)',
items=anim_layers.layer_group_enum_items, get=anim_layers.layer_group_get, set=anim_layers.layer_group_set,
options={'HIDDEN'})
# Legacy field for pre-migration data. Read by migrate_object_to_hierarchical
# and ignored thereafter. Schema retained so old .blend files load cleanly.
type: bpy.props.EnumProperty(name="Item Type (legacy)", default='LAYER',
items=[('LAYER', 'Layer', 'NLA-backed layer'),
('GROUP', 'Group', 'Legacy phantom group row')],
options={'HIDDEN'}, override = {'LIBRARY_OVERRIDABLE'})
parent_group: bpy.props.StringProperty(name="Parent Group (legacy)", description="Pre-migration field. Read once by the schema-v1 migration, then unused.", default="", options={'HIDDEN'}, override = {'LIBRARY_OVERRIDABLE'})
class AnimLayersObjects(bpy.types.PropertyGroup): class AnimLayersObjects(bpy.types.PropertyGroup):
@@ -153,7 +174,6 @@ def update_panel(self, context):
bpy.utils.unregister_class(panel) bpy.utils.unregister_class(panel)
for panel in panels: for panel in panels:
#print (panel.bl_category)
panel.bl_category = context.preferences.addons[__name__].preferences.category panel.bl_category = context.preferences.addons[__name__].preferences.category
bpy.utils.register_class(panel) bpy.utils.register_class(panel)
@@ -176,7 +196,8 @@ class AnimLayersAddonPreferences(bpy.types.AddonPreferences):
items = [('ANIMLAYERS', 'Anim Layers Settings', 'Use Anim Layers properties to adjust custom frame range'), items = [('ANIMLAYERS', 'Anim Layers Settings', 'Use Anim Layers properties to adjust custom frame range'),
('NLA', 'NLA Settings', 'Use the nla properties to adjust custom frame range')]) ('NLA', 'NLA Settings', 'Use the nla properties to adjust custom frame range')])
lock_nlatracks: bpy.props.BoolProperty(name="Automatically lock the nla tracks for safety measures", description="Automatically lock nla tracks when creating layers for safety", default = True) lock_nlatracks: bpy.props.BoolProperty(name="Automatically lock the NLA tracks", description="Automatically lock nla tracks when creating layers for safety", default = True)
auto_custom_range: bpy.props.BoolProperty(name="Switch automatically to custom frame range when editing NLA Strips", description="Automatically use custom frame range when adjusting NLA Strips manually", default = False)
#Property for ClearActiveAction #Property for ClearActiveAction
proceed: bpy.props.EnumProperty(name="Choose how to proceed", description="Select an option how to proceed with Anim Layers", override = {'LIBRARY_OVERRIDABLE'}, proceed: bpy.props.EnumProperty(name="Choose how to proceed", description="Select an option how to proceed with Anim Layers", override = {'LIBRARY_OVERRIDABLE'},
@@ -260,8 +281,9 @@ class AnimLayersAddonPreferences(bpy.types.AddonPreferences):
row.label(text = "Custom Frame Range Settings") row.label(text = "Custom Frame Range Settings")
row.prop(self, "frame_range_settings", text = '') row.prop(self, "frame_range_settings", text = '')
col.prop(self, "lock_nlatracks") row = col.row()
row.prop(self, "auto_custom_range")
row.prop(self, "lock_nlatracks")
classes = (AnimLayersSettings, AnimLayersSceneSettings, AnimLayersItems, AnimLayersObjects) classes = (AnimLayersSettings, AnimLayersSceneSettings, AnimLayersItems, AnimLayersObjects)
File diff suppressed because it is too large Load Diff
@@ -1,6 +1,6 @@
{ {
"last_check": "2026-04-06 14:46:59.741334", "last_check": "2026-05-27 14:41:11.263249",
"backup_date": "March-27-2026", "backup_date": "May-27-2026",
"update_ready": false, "update_ready": false,
"ignore": false, "ignore": false,
"just_restored": false, "just_restored": false,
@@ -20,7 +20,7 @@
bl_info = { bl_info = {
"name": "Animation Layers", "name": "Animation Layers",
"author": "Tal Hershkovich", "author": "Tal Hershkovich",
"version" : (2, 3, 7), "version" : (2, 4, 0),
"blender" : (3, 2, 0), "blender" : (3, 2, 0),
"location": "View3D - Properties - Animation Panel", "location": "View3D - Properties - Animation Panel",
"description": "Simplifying the NLA editor into an animation layers UI and workflow", "description": "Simplifying the NLA editor into an animation layers UI and workflow",
@@ -153,7 +153,6 @@ def update_panel(self, context):
bpy.utils.unregister_class(panel) bpy.utils.unregister_class(panel)
for panel in panels: for panel in panels:
#print (panel.bl_category)
panel.bl_category = context.preferences.addons[__name__].preferences.category panel.bl_category = context.preferences.addons[__name__].preferences.category
bpy.utils.register_class(panel) bpy.utils.register_class(panel)
@@ -176,7 +175,8 @@ class AnimLayersAddonPreferences(bpy.types.AddonPreferences):
items = [('ANIMLAYERS', 'Anim Layers Settings', 'Use Anim Layers properties to adjust custom frame range'), items = [('ANIMLAYERS', 'Anim Layers Settings', 'Use Anim Layers properties to adjust custom frame range'),
('NLA', 'NLA Settings', 'Use the nla properties to adjust custom frame range')]) ('NLA', 'NLA Settings', 'Use the nla properties to adjust custom frame range')])
lock_nlatracks: bpy.props.BoolProperty(name="Automatically lock the nla tracks for safety measures", description="Automatically lock nla tracks when creating layers for safety", default = True) lock_nlatracks: bpy.props.BoolProperty(name="Automatically lock the NLA tracks", description="Automatically lock nla tracks when creating layers for safety", default = True)
auto_custom_range: bpy.props.BoolProperty(name="Switch automatically to custom frame range when editing NLA Strips", description="Automatically use custom frame range when adjusting NLA Strips manually", default = False)
#Property for ClearActiveAction #Property for ClearActiveAction
proceed: bpy.props.EnumProperty(name="Choose how to proceed", description="Select an option how to proceed with Anim Layers", override = {'LIBRARY_OVERRIDABLE'}, proceed: bpy.props.EnumProperty(name="Choose how to proceed", description="Select an option how to proceed with Anim Layers", override = {'LIBRARY_OVERRIDABLE'},
@@ -260,8 +260,9 @@ class AnimLayersAddonPreferences(bpy.types.AddonPreferences):
row.label(text = "Custom Frame Range Settings") row.label(text = "Custom Frame Range Settings")
row.prop(self, "frame_range_settings", text = '') row.prop(self, "frame_range_settings", text = '')
col.prop(self, "lock_nlatracks") row = col.row()
row.prop(self, "auto_custom_range")
row.prop(self, "lock_nlatracks")
classes = (AnimLayersSettings, AnimLayersSceneSettings, AnimLayersItems, AnimLayersObjects) classes = (AnimLayersSettings, AnimLayersSceneSettings, AnimLayersItems, AnimLayersObjects)
@@ -271,7 +271,6 @@ def register_layers(obj, nla_tracks):
continue continue
strip = track.strips[0] strip = track.strips[0]
use_animated_influence(strip) use_animated_influence(strip)
strip.influence = 1
#updating the ui list with the nla track names #updating the ui list with the nla track names
def visible_layers(obj, nla_tracks): def visible_layers(obj, nla_tracks):
@@ -400,7 +399,7 @@ def get_fcu_layer_keyframes(obj, context, track):
keyframes = [] keyframes = []
# fcurves = get_fcurves(track.strips[0].action) # fcurves = get_fcurves(track.strips[0].action)
# fcurves = track.strips[0].action.fcurves # fcurves = track.strips[0].action.fcurves
fcurves = get_fcurves(obj, track.strips[0].action) fcurves = get_fcurves(obj, track.strips[0].action, obj.als.data_type)
#store all the keyframe locations from the fcurves of the layer #store all the keyframe locations from the fcurves of the layer
for fcu in fcurves: for fcu in fcurves:
if fcu.group is not None: if fcu.group is not None:
@@ -1298,7 +1297,6 @@ def load_action(self, context):
subscriptions.frameend_update_callback() subscriptions.frameend_update_callback()
strip.use_sync_length = False strip.use_sync_length = False
use_animated_influence(strip) use_animated_influence(strip)
strip.influence = 1
return return
subscriptions.subscriptions_remove() subscriptions.subscriptions_remove()
strip = track.strips[0] strip = track.strips[0]
@@ -1548,6 +1546,8 @@ def strip_action_recalc(self, strip):
###################################################### HELPER FUNCTIONS ################################################ ###################################################### HELPER FUNCTIONS ################################################
def redraw_areas(areas): def redraw_areas(areas):
if not len(bpy.context.window_manager.windows):
return
for area in bpy.context.window_manager.windows[0].screen.areas: for area in bpy.context.window_manager.windows[0].screen.areas:
if area.type in areas: if area.type in areas:
area.tag_redraw() area.tag_redraw()
@@ -1623,7 +1623,7 @@ def select_layer_bones(self, context):
###################################################### CLASSES ########################################################### ###################################################### CLASSES ###########################################################
class SelectBonesInLayer(bpy.types.Operator): class SelectBonesInLayer(bpy.types.Operator):
"""Select bones with keyframes in the current layer""" """Select bones with keyframes in the current layer, use shift to add to the current selection"""
bl_idname = "anim.bones_in_layer" bl_idname = "anim.bones_in_layer"
bl_label = "Select layer bones" bl_label = "Select layer bones"
bl_icon = "BONE_DATA" bl_icon = "BONE_DATA"
@@ -1866,18 +1866,9 @@ class AutoCustomFrameRange(bpy.types.Operator):
# return {'CANCELLED'} # return {'CANCELLED'}
def restore(self, context): def restore(self, context):
if hasattr(subscriptions, 'frame_range'): print('restore')
frame_start, frame_end = subscriptions.frame_range subscriptions.frameend_update_callback()
else:
frame_start, frame_end = subscriptions.get_frame_range(context.scene)
self.strip.repeat = 1 #change strip repeat but keep self.repeat value stored
self.strip.use_reverse = False
self.strip.frame_start = frame_start
self.strip.scale = self.layer.speed
self.strip.frame_end = frame_end
# update_action_frame_range(frame_start, frame_end, layer, strip)
subscriptions.subscriptions_add(context.scene) subscriptions.subscriptions_add(context.scene)
def update_action_list(scene): def update_action_list(scene):
@@ -1949,7 +1940,6 @@ def add_animlayer(layer_name = 'Anim_Layer' , duplicate = False, index = 1, blen
new_strip.blend_type = blend_type new_strip.blend_type = blend_type
new_strip.use_sync_length = False new_strip.use_sync_length = False
use_animated_influence(new_strip) use_animated_influence(new_strip)
new_strip.influence = 1
return new_track return new_track
@@ -2599,9 +2589,7 @@ class RemoveFcurves(bpy.types.Operator):
if mod.type == 'CYCLES': if mod.type == 'CYCLES':
fcu.modifiers.remove(mod) fcu.modifiers.remove(mod)
fcu.update() fcu.update()
for area in context.window_manager.windows[0].screen.areas: redraw_areas(['GRAPH_EDITOR', 'VIEW_3D'])
if area.type == 'GRAPH_EDITOR' or area.type == 'VIEW_3D':
area.tag_redraw()
break break
return {'FINISHED'} return {'FINISHED'}
@@ -3304,25 +3292,36 @@ def copy_action(action):
return new_action return new_action
def get_obj_slot(obj, action, data_type = 'OBJECT'): def get_obj_slot(obj, action, data_type = None):
'''Get the slot in the action that this object is using either it's object, or shapekeys''' '''Get the slot in the action that this object is using either it's object, or shapekeys'''
if data_type is None:
data_type = obj.als.data_type
if not hasattr(action, 'slots'): if not hasattr(action, 'slots'):
return None return None
if not len(action.slots):
# If no slots exist, create one for the object and return it
slot = add_action_slot(obj, action)
return slot
# data_type = obj.als.data_type
for slot in action.slots: for slot in action.slots:
if slot.target_id_type != data_type: if slot.target_id_type != data_type:
continue continue
# if obj.als.data_type == 'OBJECT' and obj in slot.users():
# return slot
if data_type == 'KEY' and obj.data.shape_keys in slot.users(): if data_type == 'KEY' and obj.data.shape_keys in slot.users():
return slot return slot
elif obj in slot.users(): elif obj in slot.users():
return slot return slot
return None return add_action_slot(obj, action)
def get_fcurves(obj: bpy.types.Object, action: bpy.types.Action, data_type = 'OBJECT'): def get_fcurves(obj: bpy.types.Object, action: bpy.types.Action, data_type = None):
if data_type is None:
data_type = obj.als.data_type
if hasattr(action, 'layers'): if hasattr(action, 'layers'):
slot = get_obj_slot(obj, action, data_type) slot = get_obj_slot(obj, action, data_type)
@@ -3338,10 +3337,13 @@ def get_fcurves(obj: bpy.types.Object, action: bpy.types.Action, data_type = 'OB
return action.fcurves return action.fcurves
return [] return []
def get_channelbag(obj: bpy.types.Object, action: bpy.types.Action, data_type = 'OBJECT'): def get_channelbag(obj: bpy.types.Object, action: bpy.types.Action, data_type = None):
'''Getting the container of the fcurves, either the action or channelbag '''Getting the container of the fcurves, either the action or channelbag
Using this when adding a new group to the action''' Using this when adding a new group to the action'''
if data_type is None:
data_type = obj.als.data_type
if hasattr(action, 'layers'): if hasattr(action, 'layers'):
slot = get_obj_slot(obj, action, data_type) slot = get_obj_slot(obj, action, data_type)
channelbag = None channelbag = None
@@ -1,16 +1,16 @@
{ {
"last_check": "2026-03-27 11:33:07.238724", "last_check": "2026-05-27 14:41:11.263249",
"backup_date": "March-20-2026", "backup_date": "April-21-2026",
"update_ready": true, "update_ready": true,
"ignore": false, "ignore": false,
"just_restored": false, "just_restored": false,
"just_updated": false, "just_updated": false,
"version_text": { "version_text": {
"link": "https://gitlab.com/api/v4/projects/22294607/repository/archive.zip?sha=eef950c6d5e620a8240db5c2a7b20955fe31df6f", "link": "https://gitlab.com/api/v4/projects/22294607/repository/archive.zip?sha=321d411a449bc9acee2a759e30cd3d0f36bbd2ab",
"version": [ "version": [
2, 2,
3, 4,
8 1
] ]
} }
} }
@@ -645,6 +645,7 @@ def AL_bake(frame_start, frame_end, nla_tracks, fcu_keys, additive, step, action
baked_action = track.strips[0].action baked_action = track.strips[0].action
clean_no_user_slots(baked_action) clean_no_user_slots(baked_action)
#create the baked fcurve #create the baked fcurve
# baked_channelbag = anim_layers.get_channelbag(obj, baked_action, obj.als.data_type)
baked_channelbag = anim_layers.get_channelbag(obj, baked_action) baked_channelbag = anim_layers.get_channelbag(obj, baked_action)
baked_fcurves = baked_channelbag.fcurves baked_fcurves = baked_channelbag.fcurves
@@ -73,7 +73,6 @@ def animlayers_frame(scene, context):
scene['framerange_preview'] = scene.use_preview_range scene['framerange_preview'] = scene.use_preview_range
frameend_update_callback() frameend_update_callback()
return return
frame_start, frame_end = bake_ops.frame_start_end(scene) frame_start, frame_end = bake_ops.frame_start_end(scene)
# frame_start, frame_end = get_frame_range(scene) # frame_start, frame_end = get_frame_range(scene)
reset_subscription = False reset_subscription = False
@@ -106,7 +105,6 @@ def animlayers_frame(scene, context):
for i, track in enumerate(nla_tracks): for i, track in enumerate(nla_tracks):
if len(track.strips) != 1: if len(track.strips) != 1:
continue continue
#checks if the layer has a custom frame range #checks if the layer has a custom frame range
layer = obj.Anim_Layers[i] layer = obj.Anim_Layers[i]
if layer.custom_frame_range: if layer.custom_frame_range:
@@ -127,6 +125,7 @@ def animlayers_frame(scene, context):
if strip.frame_start < 0: if strip.frame_start < 0:
strip.frame_start = 0 strip.frame_start = 0
anim_layers.update_action_frame_range(0, frame_end, layer, strip) anim_layers.update_action_frame_range(0, frame_end, layer, strip)
return
anim_layers.update_action_frame_range(strip.frame_start, current + 10.0, layer, strip) anim_layers.update_action_frame_range(strip.frame_start, current + 10.0, layer, strip)
strip.frame_end = current + 10.0 strip.frame_end = current + 10.0
@@ -231,6 +230,9 @@ def track_layer_synchronization(obj, nla_tracks):
if obj.als.layer_index > len(obj.Anim_Layers)-1: if obj.als.layer_index > len(obj.Anim_Layers)-1:
obj.als.layer_index = len(obj.Anim_Layers)-1 obj.als.layer_index = len(obj.Anim_Layers)-1
if not bpy.context.preferences.addons[__package__].preferences.auto_custom_range:
return
#update new layer with strip settings #update new layer with strip settings
frame_start, frame_end = get_frame_range(bpy.context.scene) frame_start, frame_end = get_frame_range(bpy.context.scene)
@@ -243,7 +245,6 @@ def track_layer_synchronization(obj, nla_tracks):
continue continue
if (strip.frame_start, strip.frame_end) != (frame_start, frame_end): if (strip.frame_start, strip.frame_end) != (frame_start, frame_end):
subscriptions_remove() subscriptions_remove()
# print(f'strip.frame_start {strip.frame_start} strip.frame_end {strip.frame_end} frame_start {frame_start} frame_end {frame_end}')
bpy.ops.anim.custom_frame_range_warning('INVOKE_DEFAULT') bpy.ops.anim.custom_frame_range_warning('INVOKE_DEFAULT')
return return
@@ -311,11 +312,16 @@ def sync_frame_range(scene, track, layer):
return return
#Turn on custom frame range if the current strip is not following the scene frame range #Turn on custom frame range if the current strip is not following the scene frame range
# Should be activated when nla strips are edited manually in the nla editor, only when auto custom range is turned on, otherwise just update the strip frame range to the scene frame range
if (round(strip.frame_start, 2), round(strip.frame_end, 2)) != (round(frame_start, 2), round(frame_end, 2)): if (round(strip.frame_start, 2), round(strip.frame_end, 2)) != (round(frame_start, 2), round(frame_end, 2)):
if bpy.context.preferences.addons[__package__].preferences.auto_custom_range:
subscriptions_remove() subscriptions_remove()
# print('315 custom frame range') # print('321 custom frame range')
bpy.ops.anim.custom_frame_range_warning('INVOKE_DEFAULT') bpy.ops.anim.custom_frame_range_warning('INVOKE_DEFAULT')
return return
else:
frameend_update_callback()
return
def sync_strip_range(scene): def sync_strip_range(scene):
'''Checking all the strips if a value was changed in the nla (not including UI changes) '''Checking all the strips if a value was changed in the nla (not including UI changes)
@@ -356,7 +362,6 @@ def sync_strip_range(scene):
if (strip_frame_start, round(strip_frame_end, 2)) != (frame_start, float(frame_end)): if (strip_frame_start, round(strip_frame_end, 2)) != (frame_start, float(frame_end)):
subscriptions_remove() subscriptions_remove()
# print('357 custom_frame_range_warning ') # print('357 custom_frame_range_warning ')
# print(f'strip_frame_start {strip_frame_start} strip_frame_end {round(strip_frame_end, 2)} frame_start {frame_start} frame_end {float(frame_end)}')
bpy.ops.anim.custom_frame_range_warning('INVOKE_DEFAULT') bpy.ops.anim.custom_frame_range_warning('INVOKE_DEFAULT')
return return
@@ -567,7 +572,6 @@ def subscribe_to_preview_frame_end(scene):
# Subscribing to preview frame end since it's not registering in the depsgraph # Subscribing to preview frame end since it's not registering in the depsgraph
subscribe_preview_end = scene.path_resolve("frame_preview_end", False) subscribe_preview_end = scene.path_resolve("frame_preview_end", False)
subscribe_use_preview = scene.path_resolve("use_preview_range", False) subscribe_use_preview = scene.path_resolve("use_preview_range", False)
# print('subscribe_to_preview_frame_end')
for subscribe in [subscribe_preview_end, subscribe_use_preview]: for subscribe in [subscribe_preview_end, subscribe_use_preview]:
bpy.msgbus.subscribe_rna( bpy.msgbus.subscribe_rna(
+40 -14
View File
@@ -183,7 +183,8 @@ def smart_bake(context):
wm.progress_begin(0, total_iterations) wm.progress_begin(0, total_iterations)
processed = 0 processed = 0
for layer, track in zip(obj.Anim_Layers, anim_data.nla_tracks): layer_items = [l for l in obj.Anim_Layers if l.type == 'LAYER']
for layer, track in zip(layer_items, anim_data.nla_tracks):
if track.mute: if track.mute:
continue continue
if len(track.strips) != 1 or track.strips[0].action is None: if len(track.strips) != 1 or track.strips[0].action is None:
@@ -244,7 +245,7 @@ def smart_bake(context):
#if the strip is cutting with a different strip, then add keyframes in the cut #if the strip is cutting with a different strip, then add keyframes in the cut
for layercut in obj.Anim_Layers: for layercut in obj.Anim_Layers:
if layercut.mute or not layercut.custom_frame_range or layercut == layer: if layercut.type == 'GROUP' or layercut.mute or not layercut.custom_frame_range or layercut == layer:
continue continue
if strip_start < layercut.frame_start < strip_end: if strip_start < layercut.frame_start < strip_end:
smartkeys = smart_start_end(smartkeys, (layercut.frame_start-1), strip.frame_end-1) smartkeys = smart_start_end(smartkeys, (layercut.frame_start-1), strip.frame_end-1)
@@ -407,7 +408,7 @@ def unmute_modifiers(obj, nla_tracks, modifier_rec):
for mod in fcu.modifiers: for mod in fcu.modifiers:
if mod in modifier_rec: if mod in modifier_rec:
mod.mute = False mod.mute = False
elif obj.als.mergefcurves and track == nla_tracks[obj.als.layer_index]: elif obj.als.mergefcurves and track == nla_tracks[anim_layers.nla_idx(obj)]:
mod.mute = True mod.mute = True
def invisible_layers(b_layers): def invisible_layers(b_layers):
@@ -426,7 +427,14 @@ def select_keyframed_bones(self, context, obj):
if obj.mode != 'POSE': if obj.mode != 'POSE':
bpy.ops.object.posemode_toggle() bpy.ops.object.posemode_toggle()
bpy.ops.pose.select_all(action='DESELECT') bpy.ops.pose.select_all(action='DESELECT')
for i in range(0, obj.als.layer_index+1): # Iterate over LAYER rows up to (and including) the active row, skipping
# group headers (they have no NLA track and no bones to select).
current_row = obj.als.layer_index
for i, it in enumerate(obj.Anim_Layers):
if i > current_row:
break
if it.type != 'LAYER':
continue
obj.als['layer_index'] = i obj.als['layer_index'] = i
anim_layers.select_layer_bones(self, context) anim_layers.select_layer_bones(self, context)
@@ -446,7 +454,7 @@ def smartbake_apply(obj, nla_tracks, fcu_keys, extrapolations):
#apply smartbake for blenders bake #apply smartbake for blenders bake
#smart bake - delete unnecessery keyframes: #smart bake - delete unnecessery keyframes:
# transform_types = ['location', 'rotation_euler', 'rotation_quaternion', 'scale'] # transform_types = ['location', 'rotation_euler', 'rotation_quaternion', 'scale']
strip = nla_tracks[obj.als.layer_index].strips[0] strip = nla_tracks[anim_layers.nla_idx(obj)].strips[0]
# if strip.action is None: # if strip.action is None:
# return # return
@@ -641,10 +649,11 @@ def AL_bake(frame_start, frame_end, nla_tracks, fcu_keys, additive, step, action
return return
anim_data = anim_layers.anim_data_type(obj) anim_data = anim_layers.anim_data_type(obj)
# baked_action = anim_data.action # baked_action = anim_data.action
track = nla_tracks[obj.als.layer_index] track = nla_tracks[anim_layers.nla_idx(obj)]
baked_action = track.strips[0].action baked_action = track.strips[0].action
clean_no_user_slots(baked_action) clean_no_user_slots(baked_action)
#create the baked fcurve #create the baked fcurve
# baked_channelbag = anim_layers.get_channelbag(obj, baked_action, obj.als.data_type)
baked_channelbag = anim_layers.get_channelbag(obj, baked_action) baked_channelbag = anim_layers.get_channelbag(obj, baked_action)
baked_fcurves = baked_channelbag.fcurves baked_fcurves = baked_channelbag.fcurves
@@ -1068,6 +1077,14 @@ class MergeAnimLayerDown(bpy.types.Operator):
step: bpy.props.IntProperty(name='Step', description='Bake every number of frame steps', default=1) step: bpy.props.IntProperty(name='Step', description='Bake every number of frame steps', default=1)
actioncopy: bpy.props.BoolProperty(name='Copy original merged action', description='Create a copy of the original action that is being overwritten', default = False) actioncopy: bpy.props.BoolProperty(name='Copy original merged action', description='Create a copy of the original action that is being overwritten', default = False)
@classmethod
def poll(cls, context):
obj = context.object
if obj is None:
return False
# Disable when active row is a group header — merge only applies to NLA-backed layers.
return anim_layers.is_layer_row_active(obj)
def invoke(self, context, event): def invoke(self, context, event):
obj = context.object obj = context.object
bake_range_type(context.scene.als, context) bake_range_type(context.scene.als, context)
@@ -1142,7 +1159,8 @@ class MergeAnimLayerDown(bpy.types.Operator):
# Incase the strips are shorter then the keyframe range (because scene is shorter) # Incase the strips are shorter then the keyframe range (because scene is shorter)
# Then updating the strips length # Then updating the strips length
for layer, track in zip(obj.Anim_Layers, anim_data.nla_tracks): layer_items = [l for l in obj.Anim_Layers if l.type == 'LAYER']
for layer, track in zip(layer_items, anim_data.nla_tracks):
if layer.custom_frame_range: if layer.custom_frame_range:
continue continue
if len(track.strips) != 1: if len(track.strips) != 1:
@@ -1180,7 +1198,7 @@ class MergeAnimLayerDown(bpy.types.Operator):
if obj.als.direction == 'DOWN': if obj.als.direction == 'DOWN':
obj.als.layer_index = 0 obj.als.layer_index = 0
baked_layer = None baked_layer = None
strip = anim_data.nla_tracks[obj.als.layer_index].strips[0] strip = anim_data.nla_tracks[anim_layers.nla_idx(obj)].strips[0]
action = strip.action action = strip.action
if hasattr(strip, 'action_slot'): if hasattr(strip, 'action_slot'):
action_slot = strip.action_slot action_slot = strip.action_slot
@@ -1189,21 +1207,29 @@ class MergeAnimLayerDown(bpy.types.Operator):
#if baking to a new layer then setup the new index and layer #if baking to a new layer then setup the new index and layer
elif obj.als.operator == 'NEW': elif obj.als.operator == 'NEW':
self.actioncopy = False self.actioncopy = False
# `add_at_nla` is the NLA-track index passed to add_animlayer.
if obj.als.direction == 'UP' and additive and 'REPLACE' in blendings: if obj.als.direction == 'UP' and additive and 'REPLACE' in blendings:
obj.als.layer_index = layer_index + blendings.index('REPLACE') - 1 add_at_nla = layer_index + blendings.index('REPLACE') - 1
elif obj.als.direction == 'UP' or obj.als.direction == 'ALL': elif obj.als.direction == 'UP' or obj.als.direction == 'ALL':
obj.als.layer_index = len(obj.Anim_Layers)-1 add_at_nla = anim_layers.nla_layer_count(obj) - 1
else:
add_at_nla = anim_layers.nla_idx(obj)
layer_names = [layer.name for layer in obj.Anim_Layers] layer_names = [layer.name for layer in obj.Anim_Layers if layer.type == 'LAYER']
baked_layer = anim_layers.add_animlayer(layer_name = anim_layers.unique_name(layer_names, 'Baked_Layer') , duplicate = False, index = obj.als.layer_index, blend_type = blend) baked_layer = anim_layers.add_animlayer(layer_name = anim_layers.unique_name(layer_names, 'Baked_Layer') , duplicate = False, index = add_at_nla, blend_type = blend)
anim_layers.register_layers(obj, nla_tracks) anim_layers.register_layers(obj, nla_tracks)
obj.als.layer_index += 1 # Point layer_index at the newly-added baked layer's collection row.
if baked_layer is not None:
for ridx, it in enumerate(obj.Anim_Layers):
if it.type == 'LAYER' and it.name == baked_layer.name:
obj.als.layer_index = ridx
break
#remove subsciption again after adding a layer there was new subsciption applied #remove subsciption again after adding a layer there was new subsciption applied
subscriptions.subscriptions_remove() subscriptions.subscriptions_remove()
track = nla_tracks[obj.als.layer_index] track = nla_tracks[anim_layers.nla_idx(obj)]
#use internal bake #use internal bake
if obj.als.baketype =='NLA': if obj.als.baketype =='NLA':
modifier_rec, extrapolations = mute_modifiers(obj, nla_tracks) modifier_rec, extrapolations = mute_modifiers(obj, nla_tracks)
@@ -73,7 +73,6 @@ def animlayers_frame(scene, context):
scene['framerange_preview'] = scene.use_preview_range scene['framerange_preview'] = scene.use_preview_range
frameend_update_callback() frameend_update_callback()
return return
frame_start, frame_end = bake_ops.frame_start_end(scene) frame_start, frame_end = bake_ops.frame_start_end(scene)
# frame_start, frame_end = get_frame_range(scene) # frame_start, frame_end = get_frame_range(scene)
reset_subscription = False reset_subscription = False
@@ -106,9 +105,11 @@ def animlayers_frame(scene, context):
for i, track in enumerate(nla_tracks): for i, track in enumerate(nla_tracks):
if len(track.strips) != 1: if len(track.strips) != 1:
continue continue
#checks if the layer has a custom frame range #checks if the layer has a custom frame range
layer = obj.Anim_Layers[i] row_idx = anim_layers.layer_to_row_index(obj, i)
if row_idx < 0 or row_idx >= len(obj.Anim_Layers):
continue
layer = obj.Anim_Layers[row_idx]
if layer.custom_frame_range: if layer.custom_frame_range:
continue continue
if not reset_subscription: if not reset_subscription:
@@ -127,6 +128,7 @@ def animlayers_frame(scene, context):
if strip.frame_start < 0: if strip.frame_start < 0:
strip.frame_start = 0 strip.frame_start = 0
anim_layers.update_action_frame_range(0, frame_end, layer, strip) anim_layers.update_action_frame_range(0, frame_end, layer, strip)
return
anim_layers.update_action_frame_range(strip.frame_start, current + 10.0, layer, strip) anim_layers.update_action_frame_range(strip.frame_start, current + 10.0, layer, strip)
strip.frame_end = current + 10.0 strip.frame_end = current + 10.0
@@ -170,6 +172,10 @@ def check_handler(scene):
return return
anim_layers.add_obj_to_animlayers(obj, [item.object for item in scene.AL_objects]) anim_layers.add_obj_to_animlayers(obj, [item.object for item in scene.AL_objects])
nla_tracks = anim_data.nla_tracks nla_tracks = anim_data.nla_tracks
# When the active UIList row is a group header (no NLA track of its own),
# skip the LAYER-specific syncs below — they assume a real layer.
if not anim_layers.is_layer_row_active(obj):
return
layer = obj.Anim_Layers[obj.als.layer_index] layer = obj.Anim_Layers[obj.als.layer_index]
active_action_update(obj, anim_data, nla_tracks) active_action_update(obj, anim_data, nla_tracks)
#check if a keyframe was removed #check if a keyframe was removed
@@ -190,7 +196,7 @@ def check_handler(scene):
if track_layer_synchronization(obj, nla_tracks): if track_layer_synchronization(obj, nla_tracks):
return return
track = nla_tracks[obj.als.layer_index] track = nla_tracks[anim_layers.nla_idx(obj)]
sync_frame_range(scene, track, layer) sync_frame_range(scene, track, layer)
# sync_strip_range(scene) # sync_strip_range(scene)
@@ -218,18 +224,23 @@ def check_handler(scene):
anim_layers.hide_view_all_keyframes(obj, anim_data) anim_layers.hide_view_all_keyframes(obj, anim_data)
check_selected_bones(obj) check_selected_bones(obj)
influence_check(nla_tracks[obj.als.layer_index]) influence_check(nla_tracks[anim_layers.nla_idx(obj)])
def track_layer_synchronization(obj, nla_tracks): def track_layer_synchronization(obj, nla_tracks):
'''check if track and layers are synchronized, running only when adding/removing tracks via the nla''' '''check if track and layers are synchronized, running only when adding/removing tracks via the nla'''
if len(nla_tracks) == len(obj.Anim_Layers): if len(nla_tracks) == anim_layers.nla_layer_count(obj):
return False return False
new_layers_names = set(track.name for track in nla_tracks).difference(set(layer.name for layer in obj.Anim_Layers)) layer_items = [layer for layer in obj.Anim_Layers if layer.type == 'LAYER']
new_layers_names = set(track.name for track in nla_tracks).difference(set(layer.name for layer in layer_items))
anim_layers.visible_layers(obj, nla_tracks) anim_layers.visible_layers(obj, nla_tracks)
if obj.als.layer_index > len(obj.Anim_Layers)-1: row_count = len(obj.Anim_Layers)
obj.als.layer_index = len(obj.Anim_Layers)-1 if row_count and obj.als.layer_index > row_count - 1:
obj.als.layer_index = row_count - 1
if not bpy.context.preferences.addons[__package__].preferences.auto_custom_range:
return
#update new layer with strip settings #update new layer with strip settings
frame_start, frame_end = get_frame_range(bpy.context.scene) frame_start, frame_end = get_frame_range(bpy.context.scene)
@@ -243,7 +254,6 @@ def track_layer_synchronization(obj, nla_tracks):
continue continue
if (strip.frame_start, strip.frame_end) != (frame_start, frame_end): if (strip.frame_start, strip.frame_end) != (frame_start, frame_end):
subscriptions_remove() subscriptions_remove()
# print(f'strip.frame_start {strip.frame_start} strip.frame_end {strip.frame_end} frame_start {frame_start} frame_end {frame_end}')
bpy.ops.anim.custom_frame_range_warning('INVOKE_DEFAULT') bpy.ops.anim.custom_frame_range_warning('INVOKE_DEFAULT')
return return
@@ -260,9 +270,9 @@ def active_action_update(obj, anim_data, nla_tracks):
anim_data.action = None anim_data.action = None
subscriptions_add(bpy.context.scene) subscriptions_add(bpy.context.scene)
return return
if anim_data.action == nla_tracks[obj.als.layer_index].strips[0].action: if anim_data.action == nla_tracks[anim_layers.nla_idx(obj)].strips[0].action:
return return
if not len(nla_tracks[obj.als.layer_index].strips): if not len(nla_tracks[anim_layers.nla_idx(obj)].strips):
return return
if not anim_data.action or anim_data.is_property_readonly('action'): if not anim_data.action or anim_data.is_property_readonly('action'):
return return
@@ -311,11 +321,16 @@ def sync_frame_range(scene, track, layer):
return return
#Turn on custom frame range if the current strip is not following the scene frame range #Turn on custom frame range if the current strip is not following the scene frame range
# Should be activated when nla strips are edited manually in the nla editor, only when auto custom range is turned on, otherwise just update the strip frame range to the scene frame range
if (round(strip.frame_start, 2), round(strip.frame_end, 2)) != (round(frame_start, 2), round(frame_end, 2)): if (round(strip.frame_start, 2), round(strip.frame_end, 2)) != (round(frame_start, 2), round(frame_end, 2)):
if bpy.context.preferences.addons[__package__].preferences.auto_custom_range:
subscriptions_remove() subscriptions_remove()
# print('315 custom frame range') # print('321 custom frame range')
bpy.ops.anim.custom_frame_range_warning('INVOKE_DEFAULT') bpy.ops.anim.custom_frame_range_warning('INVOKE_DEFAULT')
return return
else:
frameend_update_callback()
return
def sync_strip_range(scene): def sync_strip_range(scene):
'''Checking all the strips if a value was changed in the nla (not including UI changes) '''Checking all the strips if a value was changed in the nla (not including UI changes)
@@ -356,7 +371,6 @@ def sync_strip_range(scene):
if (strip_frame_start, round(strip_frame_end, 2)) != (frame_start, float(frame_end)): if (strip_frame_start, round(strip_frame_end, 2)) != (frame_start, float(frame_end)):
subscriptions_remove() subscriptions_remove()
# print('357 custom_frame_range_warning ') # print('357 custom_frame_range_warning ')
# print(f'strip_frame_start {strip_frame_start} strip_frame_end {round(strip_frame_end, 2)} frame_start {frame_start} frame_end {float(frame_end)}')
bpy.ops.anim.custom_frame_range_warning('INVOKE_DEFAULT') bpy.ops.anim.custom_frame_range_warning('INVOKE_DEFAULT')
return return
@@ -429,7 +443,7 @@ def influence_sync(scene, obj, nla_tracks):
if action.name == scene.name + 'Action' and not len(scene.animation_data.nla_tracks) and not len(fcurves): if action.name == scene.name + 'Action' and not len(scene.animation_data.nla_tracks) and not len(fcurves):
bpy.data.actions.remove(action) bpy.data.actions.remove(action)
strip = nla_tracks[obj.als.layer_index].strips[0] strip = nla_tracks[anim_layers.nla_idx(obj)].strips[0]
if strip.fcurves[0].mute: if strip.fcurves[0].mute:
return return
strip.fcurves[0].lock = False strip.fcurves[0].lock = False
@@ -541,9 +555,10 @@ def frameend_update_callback():
for anim_data in anim_datas: for anim_data in anim_datas:
if anim_data is None: if anim_data is None:
continue continue
if len(anim_data.nla_tracks) != len(obj.Anim_Layers): if len(anim_data.nla_tracks) != anim_layers.nla_layer_count(obj):
continue continue
for layer, track in zip(obj.Anim_Layers, anim_data.nla_tracks): layer_items = [l for l in obj.Anim_Layers if l.type == 'LAYER']
for layer, track in zip(layer_items, anim_data.nla_tracks):
if layer.custom_frame_range: if layer.custom_frame_range:
continue continue
if len(track.strips) != 1: if len(track.strips) != 1:
@@ -567,7 +582,6 @@ def subscribe_to_preview_frame_end(scene):
# Subscribing to preview frame end since it's not registering in the depsgraph # Subscribing to preview frame end since it's not registering in the depsgraph
subscribe_preview_end = scene.path_resolve("frame_preview_end", False) subscribe_preview_end = scene.path_resolve("frame_preview_end", False)
subscribe_use_preview = scene.path_resolve("use_preview_range", False) subscribe_use_preview = scene.path_resolve("use_preview_range", False)
# print('subscribe_to_preview_frame_end')
for subscribe in [subscribe_preview_end, subscribe_use_preview]: for subscribe in [subscribe_preview_end, subscribe_use_preview]:
bpy.msgbus.subscribe_rna( bpy.msgbus.subscribe_rna(
@@ -649,10 +663,12 @@ def action_name_callback():
nla_tracks = anim_data.nla_tracks nla_tracks = anim_data.nla_tracks
if not len(nla_tracks): if not len(nla_tracks):
return return
layer = obj.Anim_Layers[obj.als.layer_index] if not anim_layers.is_layer_row_active(obj):
if not len(nla_tracks[obj.als.layer_index].strips):
return return
action = nla_tracks[obj.als.layer_index].strips[0].action layer = obj.Anim_Layers[obj.als.layer_index]
if not len(nla_tracks[anim_layers.nla_idx(obj)].strips):
return
action = nla_tracks[anim_layers.nla_idx(obj)].strips[0].action
if action is None: if action is None:
return return
if not obj.als.auto_rename or layer.name == action.name: if not obj.als.auto_rename or layer.name == action.name:
@@ -770,10 +786,10 @@ def slot_update_callback():
if not len(obj.Anim_Layers): if not len(obj.Anim_Layers):
return return
if not len(anim_data.nla_tracks[obj.als.layer_index].strips): if not len(anim_data.nla_tracks[anim_layers.nla_idx(obj)].strips):
return return
strip = anim_data.nla_tracks[obj.als.layer_index].strips[0] strip = anim_data.nla_tracks[anim_layers.nla_idx(obj)].strips[0]
anim_data.action_slot = strip.action_slot anim_data.action_slot = strip.action_slot
@@ -844,9 +860,11 @@ def strip_settings_callback():
return return
# sync_strip_range() # sync_strip_range()
if not len(anim_data.nla_tracks[obj.als.layer_index].strips): if not anim_layers.is_layer_row_active(obj):
return return
strip = anim_data.nla_tracks[obj.als.layer_index].strips[0] if not len(anim_data.nla_tracks[anim_layers.nla_idx(obj)].strips):
return
strip = anim_data.nla_tracks[anim_layers.nla_idx(obj)].strips[0]
layer = obj.Anim_Layers[obj.als.layer_index] layer = obj.Anim_Layers[obj.als.layer_index]
update_strip_layer_settings(strip, layer) update_strip_layer_settings(strip, layer)
+35 -60
View File
@@ -71,6 +71,7 @@ def draw_wgt(scale, bone, shape = 'sphere'):
shape = shape.lower() shape = shape.lower()
mesh.from_pydata(np.array(shapes[shape]['vertices'])*scale , shapes[shape]['edges'], shapes[shape]['faces']) mesh.from_pydata(np.array(shapes[shape]['vertices'])*scale , shapes[shape]['edges'], shapes[shape]['faces'])
# print('draw shape scale ', scale)
shape_obj['WGT_TempCtrl'] = True shape_obj['WGT_TempCtrl'] = True
return shape_obj return shape_obj
@@ -205,27 +206,26 @@ def tempctrl_shape_type(self, context):
ctrl.custom_shape_rotation_euler = (0, 0, 0) ctrl.custom_shape_rotation_euler = (0, 0, 0)
continue continue
if org_bone.custom_shape_transform is not None:# and ctrl.custom_shape_transform is None: if org_bone.custom_shape_transform is not None and ctrl.custom_shape_transform is None:
#check if the transform bone shape already exists #check if the transform bone shape already exists
transform_bonename = org_bone.custom_shape_transform.name transform_bonename = org_bone.custom_shape_transform.name
if transform_bonename in ctrl.id_data.pose.bones: if transform_bonename in ctrl.id_data.pose.bones:
# If it already exists then find it
ctrl.custom_shape_transform = ctrl.id_data.pose.bones[transform_bonename] ctrl.custom_shape_transform = ctrl.id_data.pose.bones[transform_bonename]
else: continue
#add an extra transform bone, if it doesnt exist #add an extra transform bone, if it doesnt exist
rig = ctrl.id_data
bpy.ops.object.mode_set(mode = 'EDIT') bpy.ops.object.mode_set(mode = 'EDIT')
boneshape = add_ctrl_bone(rig, org_bone.custom_shape_transform, rig.data.edit_bones[ctrl.name].parent, '') boneshape = add_ctrl_bone(rig, org_bone.custom_shape_transform, rig.data.edit_bones[ctrl.name].parent, '')
transform_bonename = boneshape.name bone_name = boneshape.name
add_bone_to_collection(rig, boneshape) add_bone_to_collection(rig, boneshape)
bpy.ops.object.mode_set(mode = 'POSE') bpy.ops.object.mode_set(mode = 'POSE')
posebone = rig.pose.bones[bone_name]
rig = ctrl.id_data
posebone = rig.pose.bones[transform_bonename]
constraint_add(posebone, org_bone.custom_shape_transform, 'COPY_TRANSFORMS') constraint_add(posebone, org_bone.custom_shape_transform, 'COPY_TRANSFORMS')
ctrl.custom_shape_transform = posebone ctrl.custom_shape_transform = posebone
else: else:
# scale = (length_avg / ctrl.length)*0.4 + 0.4 # scale = (length_avg / ctrl.length)*0.4 + 0.4
scale = 1 scale = 1
# print('drawing shape in scale of ', scale)
ctrl.custom_shape = draw_wgt(scale, ctrl, shape = self.shape_type) ctrl.custom_shape = draw_wgt(scale, ctrl, shape = self.shape_type)
ctrl.custom_shape_scale_xyz = tuple([self.shape_size]*3) ctrl.custom_shape_scale_xyz = tuple([self.shape_size]*3)
ctrl.custom_shape_transform = None ctrl.custom_shape_transform = None
@@ -1466,11 +1466,6 @@ def smartbake_to_ik(context, posebones, chain, sb):
#create the fcurves and add them to smartfcurves #create the fcurves and add them to smartfcurves
for ctrl in ctrls: for ctrl in ctrls:
if ctrl == chain.ik_tip and ctrl != chain.ik_ctrl:
continue
# if chain.pole_bone and ctrl == chain.base_bone:
# continue
ctrl_action = ctrl.id_data.animation_data.action ctrl_action = ctrl.id_data.animation_data.action
path = ctrl.path_from_id() path = ctrl.path_from_id()
rot = Tools.rot_mode_to_channel(ctrl.rotation_mode) rot = Tools.rot_mode_to_channel(ctrl.rotation_mode)
@@ -1909,7 +1904,7 @@ def smartbake_write_keyframes(scene, smartframes, smartfcus, chain = None, bake_
#Store the influence and set them to 0 before calculating the matrix without the constraint (to get the offset) #Store the influence and set them to 0 before calculating the matrix without the constraint (to get the offset)
con_influence = get_con_influence(constrained) con_influence = get_con_influence(constrained)
if chain and chain.pole_bone is not None: if chain:
# Adding the pole bone to the end of the list # Adding the pole bone to the end of the list
bones_matrices.update({chain.pole_bone : None}) bones_matrices.update({chain.pole_bone : None})
bones_matrices[chain.pole_bone] = bones_matrices.pop(chain.pole_bone) bones_matrices[chain.pole_bone] = bones_matrices.pop(chain.pole_bone)
@@ -1932,20 +1927,8 @@ def smartbake_write_keyframes(scene, smartframes, smartfcus, chain = None, bake_
# Calculating pole target matrix after other bones were calcluated # Calculating pole target matrix after other bones were calcluated
if chain and bone == chain.pole_bone: if chain and bone == chain.pole_bone:
base_bone = chain.base_bone
ik_tip = chain.ik_tip
angle = chain.base_bone.vector.angle(chain.ik_tip.vector)
if not round(angle):
# continue
# If the angle is flat then use the position of the pole relative to the rest position base bone
relative_matrix = base_bone.bone.matrix_local.inverted() @ chain.pole_bone.bone.matrix_local
matrix = base_bone.matrix @ relative_matrix
# print(frame, 'angle is 0 using base bone matrix')
else:
bpy.context.view_layer.update() bpy.context.view_layer.update()
matrix = find_pole_vector(base_bone, ik_tip, chain.pole_bone, chain.step) matrix = find_pole_vector(chain.base_bone, chain.ik_tip, chain.pole_bone, chain.step)
Tools.paste_bone_matrix(bone, matrix, constrained, bones, x_filter = False) Tools.paste_bone_matrix(bone, matrix, constrained, bones, x_filter = False)
pasted_bones.add(bone) pasted_bones.add(bone)
@@ -4193,7 +4176,6 @@ def assign_setup_ids(self, context, ctrls, selected, child_ctrls, setup, setup_i
#get custom shape and assign the matrix #get custom shape and assign the matrix
if bone.custom_shape_transform and self.shape_type == 'ORIGINAL': if bone.custom_shape_transform and self.shape_type == 'ORIGINAL':
obj_mat = bone.id_data.matrix_world
ot_bone = bone.custom_shape_transform.name ot_bone = bone.custom_shape_transform.name
#Get the original override transform bone #Get the original override transform bone
target = bone.id_data.pose.bones[ot_bone] target = bone.id_data.pose.bones[ot_bone]
@@ -5328,16 +5310,14 @@ def pole_prop_edit(self, context):
else: else:
add_bone_to_collection(rig, rig.data.edit_bones[chain.base_bone.name], col_name = 'IK Ctrls', visible = True) add_bone_to_collection(rig, rig.data.edit_bones[chain.base_bone.name], col_name = 'IK Ctrls', visible = True)
#remove the pole bone and switch to base bone #remove the pole bone and switch to base bone
if chain.pole_bone is None: if not hasattr(chain, 'pole_bone'):
continue continue
rig.data.edit_bones.remove(rig.data.edit_bones[chain.pole_bone_name]) rig.data.edit_bones.remove(rig.data.edit_bones[chain.pole_bone_name])
#remove from ctrls and append base bone instead #remove from ctrls and append base bone instead
if chain.pole_bone in ctrls: if chain.pole_bone in ctrls:
ctrls.remove(chain.pole_bone) ctrls.remove(chain.pole_bone)
ctrls.append(chain.base_bone) ctrls.append(chain.base_bone)
del chain.pole_bone, chain.pole_bone_name
chain.pole_bone = chain.pole_bone_name = None
# del chain.pole_bone, chain.pole_bone_name
#Remove child ctrls #Remove child ctrls
if btc.child: if btc.child:
@@ -5350,17 +5330,13 @@ def pole_prop_pose(self, context):
if not btc.pole_target: if not btc.pole_target:
chain.base_bone.btc.org = 'CTRL' chain.base_bone.btc.org = 'CTRL'
# if not hasattr(chain, 'pole_bone'): if not hasattr(chain, 'pole_bone'):
# continue
if not chain.pole_bone:
continue continue
if not chain.pole_bone_name:
del chain.pole_bone
if hasattr(chain,'pole_bone_name'):
chain.pole_bone_name
continue continue
chain.pole_bone = chain.pole_bone_name = None
# del chain.pole_bone
# if hasattr(chain,'pole_bone_name'):
# chain.pole_bone_name
# continue
pole_posebone_update(chain, ctrls) pole_posebone_update(chain, ctrls)
if chain.base_bone in ctrls: if chain.base_bone in ctrls:
@@ -5437,9 +5413,7 @@ def ik_ctrl_orientation(self, context):
def pole_posebone_update(chain, ctrls): def pole_posebone_update(chain, ctrls):
'''update pole bone properties in the pole prop and add ik prop''' '''update pole bone properties in the pole prop and add ik prop'''
# if not hasattr(chain, 'pole_bone_name'): if not hasattr(chain, 'pole_bone_name'):
# return
if not chain.pole_bone_name:
return return
chain.pole_bone = rig.pose.bones[chain.pole_bone_name] chain.pole_bone = rig.pose.bones[chain.pole_bone_name]
@@ -5473,8 +5447,6 @@ def update_axis_prop(self, context):
if not hasattr(chain, 'pole_bone_name'): if not hasattr(chain, 'pole_bone_name'):
continue continue
if not chain.pole_bone_name:
continue
pole_matrix = find_pole_vector(base_bone, ik_tip, ik_tip, chain.step) pole_matrix = find_pole_vector(base_bone, ik_tip, ik_tip, chain.step)
#pole matrix orientation to world by default #pole matrix orientation to world by default
@@ -5579,6 +5551,15 @@ def add_pole_bone(self, context, rig, chain):
new_rot = mathutils.Euler((0, 0, 0), 'XYZ') new_rot = mathutils.Euler((0, 0, 0), 'XYZ')
pole_matrix = Matrix.LocRotScale(loc, new_rot, scale) pole_matrix = Matrix.LocRotScale(loc, new_rot, scale)
#In case of a flat IK skip adding a pole bone and use base bone instead
# if pole_matrix.translation == chain.ik_tip.matrix.translation:
# print('no pole bone')
# self.report({'ERROR'}, "Can't add Pole Ctrl because of a flat IK chain")
# chain.pole_bone = None
# add_bone_to_collection(rig, chain.base_bone, col_name = 'IK Ctrls', visible = True)
# btc['pole_target'] = False
# return
pole_bone_name = chain.org_bones[-2].name + '_Pole' if len(chain.org_bones) > 1 else chain.org_bones[0].name + '_Pole' pole_bone_name = chain.org_bones[-2].name + '_Pole' if len(chain.org_bones) > 1 else chain.org_bones[0].name + '_Pole'
chain.pole_bone = rig.data.edit_bones.new(pole_bone_name) chain.pole_bone = rig.data.edit_bones.new(pole_bone_name)
@@ -5601,9 +5582,7 @@ def pole_offset(self, context):
global ik_chains, ctrls, child_names global ik_chains, ctrls, child_names
for chain in ik_chains: for chain in ik_chains:
# if not hasattr(chain, 'pole_bone_name'): if not hasattr(chain, 'pole_bone_name'):
# continue
if not chain.pole_bone_name:
continue continue
chain.pole_bone = rig.pose.bones[chain.pole_bone_name] chain.pole_bone = rig.pose.bones[chain.pole_bone_name]
@@ -5642,7 +5621,7 @@ def add_ik_prop_edit(self, context):
chain.ik_tip_name = chain.ik_tip.name chain.ik_tip_name = chain.ik_tip.name
#remove the pole bone #remove the pole bone
if btc.pole_target and chain.pole_bone_name: if btc.pole_target and hasattr(chain, 'pole_bone_name'):
#removing and adding the pole_target without calling the whole pole prop update #removing and adding the pole_target without calling the whole pole prop update
ctrls.remove(chain.pole_bone) ctrls.remove(chain.pole_bone)
rig.data.edit_bones.remove(rig.data.edit_bones[chain.pole_bone_name]) rig.data.edit_bones.remove(rig.data.edit_bones[chain.pole_bone_name])
@@ -5675,7 +5654,7 @@ def add_ik_prop_pose(self, context):
chain.ik_ctrl.btc.org = 'CTRL' chain.ik_ctrl.btc.org = 'CTRL'
chain.ik_ctrl.color.palette = context.scene.btc.color_set chain.ik_ctrl.color.palette = context.scene.btc.color_set
if btc.pole_target and chain.pole_bone_name: #hasattr(chain, 'pole_bone_name'): if btc.pole_target and hasattr(chain, 'pole_bone_name'):
pole_posebone_update(chain, ctrls) pole_posebone_update(chain, ctrls)
add_bone_to_collection(rig, chain.ik_ctrl, col_name = 'IK Ctrls', visible = True) add_bone_to_collection(rig, chain.ik_ctrl, col_name = 'IK Ctrls', visible = True)
@@ -5812,7 +5791,7 @@ class TempIK(bpy.types.Operator):
for i, ctrl in enumerate(ctrls[:-1]): for i, ctrl in enumerate(ctrls[:-1]):
ctrls[i+1].tail = ctrl.head ctrls[i+1].tail = ctrl.head
context.view_layer.update() context.view_layer.update()
# return {'FINISHED'}
switch_layers(rig, chain.ik_tip, [31]) switch_layers(rig, chain.ik_tip, [31])
add_bone_to_collection(rig, chain.ik_tip) add_bone_to_collection(rig, chain.ik_tip)
add_bone_to_collection(rig, chain.ik_ctrl, col_name = 'IK Ctrls', visible = True) add_bone_to_collection(rig, chain.ik_ctrl, col_name = 'IK Ctrls', visible = True)
@@ -5846,6 +5825,7 @@ class TempIK(bpy.types.Operator):
#################################################################### ####################################################################
bpy.ops.object.mode_set(mode = 'POSE') bpy.ops.object.mode_set(mode = 'POSE')
setup_ids = set() setup_ids = set()
if btc.root: if btc.root:
@@ -5878,22 +5858,20 @@ class TempIK(bpy.types.Operator):
# #assign the matrix # #assign the matrix
distance = (bone.id_data.matrix_world @ bone.bone.matrix_local).inverted() @ ctrl.bone.matrix_local.copy() distance = (bone.id_data.matrix_world @ bone.bone.matrix_local).inverted() @ ctrl.bone.matrix_local.copy()
ctrl.matrix = bone.id_data.matrix_world @ bone.matrix @ distance ctrl.matrix = bone.id_data.matrix_world @ bone.matrix @ distance
# context.view_layer.update()
ctrl.color.palette = btc.color_set ctrl.color.palette = btc.color_set
add_bone_setup_id(chain.ik_ctrl, False, setup, 'CTRL', setup_id, org_id = chain.ik_ctrl.btc.org_id) add_bone_setup_id(chain.ik_ctrl, False, setup, 'CTRL', setup_id, org_id = chain.ik_ctrl.btc.org_id)
if add_ik_ctrl: if add_ik_ctrl:
add_ik_prop_pose(self, context) add_ik_prop_pose(self, context)
context.view_layer.update() context.view_layer.update()
pole_prop_pose(self, context) pole_prop_pose(self, context)
# child_prop_pose(context) # child_prop_pose(context)
if context.scene.btc.child: if context.scene.btc.child:
child_prop(context.scene.btc, context) child_prop(context.scene.btc, context)
# return {'FINISHED'}
self.ik_chains = ik_chains self.ik_chains = ik_chains
tempctrl_shape_type(self, context) tempctrl_shape_type(self, context)
@@ -5974,7 +5952,7 @@ class TempIK(bpy.types.Operator):
rig.data.edit_bones[ctrl.name].parent = rig.data.edit_bones[chain.ctrls[i+1].name] rig.data.edit_bones[ctrl.name].parent = rig.data.edit_bones[chain.ctrls[i+1].name]
#updating the polebone position with the offset #updating the polebone position with the offset
if btc.pole_target and chain.pole_bone_name: #hasattr(chain, 'pole_bone_name'): if btc.pole_target and hasattr(chain, 'pole_bone_name'):
pole_bone = rig.data.edit_bones[chain.pole_bone_name] pole_bone = rig.data.edit_bones[chain.pole_bone_name]
base_bone = rig.data.edit_bones[chain.base_bone_name] base_bone = rig.data.edit_bones[chain.base_bone_name]
ik_tip = rig.data.edit_bones[chain.ik_tip_name] ik_tip = rig.data.edit_bones[chain.ik_tip_name]
@@ -6103,7 +6081,6 @@ class TempIK(bpy.types.Operator):
#add the ik constraint to the ctrls #add the ik constraint to the ctrls
ik_con = constraint_add(chain.ik_tip, chain.ik_ctrl if not btc.child else chain.ik_child_ctrl, 'IK') ik_con = constraint_add(chain.ik_tip, chain.ik_ctrl if not btc.child else chain.ik_child_ctrl, 'IK')
if chain.pole_bone:
chain.pole_angle = get_pole_angle(chain.base_bone, chain.ik_tip, chain.pole_bone.matrix.translation) chain.pole_angle = get_pole_angle(chain.base_bone, chain.ik_tip, chain.pole_bone.matrix.translation)
ik_con.chain_count = chain.length(btc.add_ik_ctrl) ik_con.chain_count = chain.length(btc.add_ik_ctrl)
@@ -6339,8 +6316,6 @@ class IK_Chain:
self.ik_ctrl_name = self.ik_ctrl.name self.ik_ctrl_name = self.ik_ctrl.name
self.base_bone_name = self.base_bone.name self.base_bone_name = self.base_bone.name
self.ik_tip_name = self.ik_tip.name self.ik_tip_name = self.ik_tip.name
self.pole_bone = None
self.pole_bone_name = None
self.step = get_step(hierarchy[0].id_data) self.step = get_step(hierarchy[0].id_data)
@@ -6362,7 +6337,7 @@ class IK_Chain:
self.parent = rig.pose.bones[self.parent_name] self.parent = rig.pose.bones[self.parent_name]
if hasattr(self, 'root_name'): if hasattr(self, 'root_name'):
self.root_ctrl = rig.pose.bones[self.root_name] self.root_ctrl = rig.pose.bones[self.root_name]
if self.pole_bone_name: if hasattr(self, 'pole_bone_name'):
self.pole_bone = rig.pose.bones[self.pole_bone_name] self.pole_bone = rig.pose.bones[self.pole_bone_name]
# if hasattr(self, 'pole_bone_ref_name'): # if hasattr(self, 'pole_bone_ref_name'):
# self.pole_bone_ref = rig.pose.bones[self.pole_bone_ref_name] # self.pole_bone_ref = rig.pose.bones[self.pole_bone_ref_name]
+1 -1
View File
@@ -20,7 +20,7 @@
bl_info = { bl_info = {
"name": "AnimToolBox", "name": "AnimToolBox",
"author": "Tal Hershkovich", "author": "Tal Hershkovich",
"version" : (0, 2, 3), "version" : (0, 2, 0),
"blender" : (3, 2, 0), "blender" : (3, 2, 0),
"location": "View3D - Properties - Animation Panel", "location": "View3D - Properties - Animation Panel",
"description": "A set of animation tools", "description": "A set of animation tools",
@@ -1,6 +1,6 @@
{ {
"last_check": "2026-02-09 15:54:54.419194", "last_check": "",
"backup_date": "February-9-2026", "backup_date": "",
"update_ready": false, "update_ready": false,
"ignore": false, "ignore": false,
"just_restored": false, "just_restored": false,
@@ -1,189 +0,0 @@
# ***** BEGIN GPL LICENSE BLOCK *****
#
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software Foundation,
# Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
#
# ***** END GPL LICENCE BLOCK *****
import bpy
class GizmoSizeUp(bpy.types.Operator):
"""Share keyframes between all the selected objects and bones"""
bl_idname = "view3d.gizmo_size_up"
bl_label = "Gizmo_Size_Up"
bl_options = {'REGISTER', 'UNDO'}
def execute(self, context):
context.preferences.view.gizmo_size += context.scene.animtoolbox.gizmo_size
return {'PASS_THROUGH'}
class GizmoSizeDown(bpy.types.Operator):
"""Share keyframes between all the selected objects and bones"""
bl_idname = "view3d.gizmo_size_down"
bl_label = "Gizmo_Size_Down"
bl_options = {'REGISTER', 'UNDO'}
def execute(self, context):
context.preferences.view.gizmo_size -= context.scene.animtoolbox.gizmo_size
return {'PASS_THROUGH'}
########################################################################################################################
def clear_isolate_pose_mode(scene):
if not len(scene.animtoolbox.isolated):
return
for obj in scene.animtoolbox.isolated:
if not obj.hidden:
continue
obj.hidden.hide_set(False)
scene.animtoolbox.isolated.clear()
scene.animtoolbox.active_obj = None
def isolate_pose_mode(scene):
context = bpy.context
#return when going out of isolate pose or when active object is not in pose mode
if not scene.animtoolbox.isolate_pose_mode or context.active_object.mode != 'POSE':
clear_isolate_pose_mode(scene)
return
#handler continue only if the active object is None otherwise it collects all armature objects
if scene.animtoolbox.active_obj is None:
scene.animtoolbox.active_obj = context.active_object
else:
return
isolated = scene.animtoolbox.isolated
for obj in context.view_layer.objects:
if obj.type != 'ARMATURE':
continue
if obj.hide_get():
continue
rig = isolated.add()
if obj.mode == 'POSE':
rig.selected = obj
else:
rig.hidden = obj
obj.hide_set(True)
class IsolatePoseMode(bpy.types.Operator):
"""Isolates armatures during pose mode"""
bl_idname = "anim.isolate_pose_mode"
bl_label = "Isolate Pose Mode"
bl_options = {'REGISTER', 'UNDO'}
def execute(self, context):
# If the modal is already running, then don't run it the second time
scene = context.scene
if scene.animtoolbox.isolate_pose_mode:
if isolate_pose_mode in bpy.app.handlers.depsgraph_update_pre:
clear_isolate_pose_mode(scene)
bpy.app.handlers.depsgraph_update_pre.remove(isolate_pose_mode)
scene.animtoolbox.isolate_pose_mode = False
return {'FINISHED'}
scene.animtoolbox.isolate_pose_mode = True
isolate_pose_mode(scene)
if isolate_pose_mode not in bpy.app.handlers.depsgraph_update_pre:
bpy.app.handlers.depsgraph_update_pre.append(isolate_pose_mode)
return {'FINISHED'}
class SwitchBoneCollectionsVisibility(bpy.types.Operator):
"""Turn all bone collections visible and then press again to switch back"""
bl_idname = "anim.switch_collections_visibility"
bl_label = "Bone Collections Visibility"
bl_options = {'REGISTER', 'UNDO'}
@classmethod
def poll(cls, context):
return bpy.app.version >= (4, 0, 0)
def execute(self, context):
obj = context.object
if obj.type != 'ARMATURE':
return {'CANCELLED'}
if not obj.animation_data:
self.report({'INFO'}, 'No animation is available')
return {'CANCELLED'}
if not obj.animation_data.action:
self.report({'INFO'}, 'No animation is available')
return {'CANCELLED'}
collections = obj.data.collections
if not len(collections):
self.report({'INFO'}, 'No collections are available')
return {'CANCELLED'}
#check if there are collections that are marked with
tagged_col = ['atb' in col.keys() for col in collections]
atb_ui = context.scene.animtoolbox
if any(tagged_col) and atb_ui.col_vis:
#collections are already marked so return to previous collection visibilty
for col in collections:
if 'atb' in col.keys():
col.is_visible = col['atb']
del col['atb']
atb_ui.col_vis = False
else:
#Mark visible collections and turn collections with animated bones on
animated_bones = set()
start = 'pose.bones["'
end = '"]'
#get all the animated bones from the fcurves
for fcu in obj.animation_data.action.fcurves:
start_index = fcu.data_path.find(start)
end_index = fcu.data_path.find(end)
#if it's not a posebone fcurve then skip
if start_index == -1 or end_index == -1:
continue
animated_bones.add(fcu.data_path[start_index + len(start):end_index])
#check if the collecetion that is turned off has animated bones
find_anim = []
for col in collections:
for bone in col.bones:
if bone.name in animated_bones and not col.is_visible:
# print(bone.name, 'in ', col.name)
find_anim.append(col)
break
if not find_anim:
self.report({'INFO'}, 'No collections with animated bones and no visibility are found')
return {'CANCELLED'}
#Turn on collections without visiblity
for col in collections:
if col in find_anim:
#tag visibility
col['atb'] = col.is_visible
col.is_visible = True
atb_ui.col_vis = True
return {'FINISHED'}
classes = (GizmoSizeUp, GizmoSizeDown, IsolatePoseMode, SwitchBoneCollectionsVisibility)
def register():
from bpy.utils import register_class
for cls in classes:
register_class(cls)
def unregister():
from bpy.utils import unregister_class
for cls in classes:
unregister_class(cls)
@@ -1,549 +0,0 @@
# ***** BEGIN GPL LICENSE BLOCK *****
#
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software Foundation,
# Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
#
# ***** END GPL LICENCE BLOCK *****
import bpy
from mathutils import Matrix, Vector
from math import radians
import numpy
def draw_wgt(boneLength, bone):
suffix = bone.id_data.name + '_' + bone.name
if 'WGTB_object' + suffix in bpy.data.objects:
obj = bpy.data.objects['WGTB_object'] + suffix
if 'WGTB_shape' + suffix in obj.data.name:
return obj
mesh = bpy.data.meshes.new('WGTB_shape_' + suffix)
obj = bpy.data.objects.new('WGTB_object_' + suffix, mesh)
#coordinates of the sphere widget shape
sphere = {"edges": [[0, 1], [1, 2], [2, 3], [3, 4], [4, 5], [5, 6], [6, 7], [7, 8], [8, 9], [9, 10], [10, 11], [11, 12], [12, 13], [13, 14], [14, 15], [15, 16], [16, 17], [17, 18], [18, 19], [19, 20], [20, 21], [21, 22], [22, 23], [0, 23], [24, 25], [25, 26], [26, 27], [27, 28], [28, 29], [29, 30], [30, 31], [31, 32], [32, 33], [33, 34], [34, 35], [35, 36], [36, 37], [37, 38], [38, 39], [39, 40], [40, 41], [41, 42], [42, 43], [43, 44], [44, 45], [45, 46], [46, 47], [24, 47], [48, 49], [49, 50], [50, 51], [51, 52], [52, 53], [53, 54], [54, 55], [55, 56], [56, 57], [57, 58], [58, 59], [59, 60], [60, 61], [61, 62], [62, 63], [63, 64], [64, 65], [65, 66], [66, 67], [67, 68], [68, 69], [69, 70], [70, 71], [48, 71]],
"vertices": [[0.0, 0.10000002384185791, 0.0], [-0.025881901383399963, 0.09659260511398315, 0.0], [-0.050000011920928955, 0.08660250902175903, 0.0], [-0.07071065902709961, 0.07071065902709961, 0.0], [-0.08660256862640381, 0.04999998211860657, 0.0], [-0.09659260511398315, 0.025881901383399963, 0.0], [-0.10000002384185791, 7.549793679118011e-09, 0.0], [-0.09659260511398315, -0.02588188648223877, 0.0], [-0.08660256862640381, -0.04999998211860657, 0.0], [-0.07071071863174438, -0.07071065902709961, 0.0], [-0.050000011920928955, -0.08660250902175903, 0.0], [-0.02588193118572235, -0.09659260511398315, 0.0], [-3.894143674187944e-08, -0.10000002384185791, 0.0], [0.025881856679916382, -0.09659260511398315, 0.0], [0.04999995231628418, -0.08660256862640381, 0.0], [0.07071065902709961, -0.07071071863174438, 0.0], [0.08660250902175903, -0.05000004172325134, 0.0], [0.09659254550933838, -0.025881946086883545, 0.0], [0.10000002384185791, -4.649123752642481e-08, 0.0], [0.09659260511398315, 0.025881856679916382, 0.0], [0.08660256862640381, 0.04999995231628418, 0.0], [0.07071071863174438, 0.07071065902709961, 0.0], [0.05000007152557373, 0.08660250902175903, 0.0], [0.025881975889205933, 0.09659254550933838, 0.0], [0.0, 7.450580596923828e-09, 0.10000002384185791], [-0.025881901383399963, 7.450580596923828e-09, 0.09659260511398315], [-0.050000011920928955, 7.450580596923828e-09, 0.08660250902175903], [-0.07071065902709961, 7.450580596923828e-09, 0.07071065902709961], [-0.08660256862640381, 3.725290298461914e-09, 0.04999998211860657], [-0.09659260511398315, 1.862645149230957e-09, 0.025881901383399963], [-0.10000002384185791, 8.881784197001252e-16, 7.549793679118011e-09], [-0.09659260511398315, -1.862645149230957e-09, -0.02588188648223877], [-0.08660256862640381, -3.725290298461914e-09, -0.04999998211860657], [-0.07071071863174438, -7.450580596923828e-09, -0.07071065902709961], [-0.050000011920928955, -7.450580596923828e-09, -0.08660250902175903], [-0.02588193118572235, -7.450580596923828e-09, -0.09659260511398315], [-3.894143674187944e-08, -7.450580596923828e-09, -0.10000002384185791], [0.025881856679916382, -7.450580596923828e-09, -0.09659260511398315], [0.04999995231628418, -7.450580596923828e-09, -0.08660256862640381], [0.07071065902709961, -7.450580596923828e-09, -0.07071071863174438], [0.08660250902175903, -3.725290298461914e-09, -0.05000004172325134], [0.09659254550933838, -1.862645149230957e-09, -0.025881946086883545], [0.10000002384185791, -3.552713678800501e-15, -4.649123752642481e-08], [0.09659260511398315, 1.862645149230957e-09, 0.025881856679916382], [0.08660256862640381, 3.725290298461914e-09, 0.04999995231628418], [0.07071071863174438, 7.450580596923828e-09, 0.07071065902709961], [0.05000007152557373, 7.450580596923828e-09, 0.08660250902175903], [0.025881975889205933, 7.450580596923828e-09, 0.09659254550933838], [-7.450580596923828e-09, 4.440892098500626e-16, 0.10000002384185791], [-9.313225746154785e-09, -0.025881901383399963, 0.09659260511398315], [-1.1175870895385742e-08, -0.050000011920928955, 0.08660250902175903], [-1.4901161193847656e-08, -0.07071065902709961, 0.07071065902709961], [-7.450580596923828e-09, -0.08660256862640381, 0.04999998211860657], [-7.450580596923828e-09, -0.09659260511398315, 0.025881901383399963], [-7.450580596923828e-09, -0.10000002384185791, 7.549793679118011e-09], [-7.450580596923828e-09, -0.09659260511398315, -0.02588188648223877], [0.0, -0.08660256862640381, -0.04999998211860657], [0.0, -0.07071071863174438, -0.07071065902709961], [3.725290298461914e-09, -0.050000011920928955, -0.08660250902175903], [5.587935447692871e-09, -0.02588193118572235, -0.09659260511398315], [7.450577044210149e-09, -3.894143674187944e-08, -0.10000002384185791], [9.313225746154785e-09, 0.025881856679916382, -0.09659260511398315], [1.1175870895385742e-08, 0.04999995231628418, -0.08660256862640381], [1.4901161193847656e-08, 0.07071065902709961, -0.07071071863174438], [7.450580596923828e-09, 0.08660250902175903, -0.05000004172325134], [7.450580596923828e-09, 0.09659254550933838, -0.025881946086883545], [7.450580596923828e-09, 0.10000002384185791, -4.649123752642481e-08], [7.450580596923828e-09, 0.09659260511398315, 0.025881856679916382], [0.0, 0.08660256862640381, 0.04999995231628418], [0.0, 0.07071071863174438, 0.07071065902709961], [-3.725290298461914e-09, 0.05000007152557373, 0.08660250902175903], [-5.587935447692871e-09, 0.025881975889205933, 0.09659254550933838]], "faces": []}
mesh.from_pydata(numpy.array(sphere['vertices'])*[boneLength, boneLength, boneLength] , sphere['edges'], sphere['faces'])
return obj
def add_driver(obj, posebone, control, target, path, multiply = ''):
if isinstance(target, tuple):
attr = posebone.driver_add(target[0], target[1])
else:
attr = posebone.driver_add(target)
var = attr.driver.variables.new()
var.targets[0].id = obj
var.targets[0].data_path = 'pose.bones["' + control +'"].'+ path
attr.driver.expression = var.name + multiply
def dup_values(source, target):
if hasattr(source, 'parent'):
target.parent = source.parent
for prop in dir(source):
if not hasattr(target, prop):
continue
value = getattr(source, prop)
if type(value) not in {int, float, bool, str, Vector, Matrix, bpy.types.Object}:
continue
if '__' in prop[:2] and '__' in prop[-2:]:
continue
if target.is_property_readonly(prop):
continue
setattr(target, prop, value)
return target
def dup_constraints(source, target):
if not source.constraints.items():
return
for source_con in source.constraints:
target_con = target.constraints.new(source_con.type)
dup_values(source_con, target_con)
def add_vis_bone_con(obj, bone_vis_name, bone_wgt_name):
bone_vis = obj.pose.bones[bone_vis_name]
con = bone_vis.constraints.new('STRETCH_TO')
con.target = obj
con.subtarget = bone_wgt_name
return bone_vis
class target:
def __init__(self, bone):
self.name = bone.name
self.point = tuple(bone.tail)
self.ctrl = 'TRGT_' + bone.name
if bone.parent:
self.parent = bone.parent.name
#print('assign parent to target ', self.name, self.ctrl, self.parent)
def __lt__(self, other):
return self.point < other.point
def __hash__(self):
return hash(self.point)
def __eq__(self, other):
#if not isinstance(other, type(self)):
# return NotImplemented
return self.point == other.point
class parent:
def __init__(self, bone):
self.name = bone.name
self.point = tuple(bone.head)
self.ctrl = 'CTRL_' + bone.name
if bone.parent:
self.parent = bone.parent.name
def __lt__(self, other):
return self.point < other.point
def __hash__(self):
return hash(self.point)
def __eq__(self, other):
#if not isinstance(other, type(self)):
# return NotImplemented
return self.point == other.point
class constraint_dup:
def __init__(self, bone, con):
self.name = con.name
self.target = con.target
self.subtarget = con.subtarget
self.bone = bone.name
def __hash__(self):
return hash(self.bone)
def __eq__(self, other):
return self.bone == other.bone
def bone_orientation(source, target, value):
source.align_orientation(target)
x, y, z = source.matrix.to_3x3().col
R = (Matrix.Translation(source.head) @ Matrix.Rotation(radians(value), 4, x) @ Matrix.Translation(-source.head))
source.transform(R, roll = False)
source.align_roll(target.vector)
def find_ctrl(bone, controls):
i = list(controls).index(bone)
bone.ctrl = list(controls)[i].ctrl
return bone.ctrl
def add_controlers(self, obj, parents, targets):
#controls = set(parents).union(targets)
controls = set(parents + targets)
#create hierarchy
for bone in controls:
editbone = obj.data.edit_bones[bone.name]
if editbone.parent is None:
continue
parentnames = [bone.name for bone in parents]
#if a target and its parent are part of the hierarchy then linked to its own bone parent
if bone in targets and bone not in parents and editbone.parent.name in parentnames:
parentbone = parent(editbone)
else:
parentbone = parent(editbone.parent)
if parentbone in controls and parentbone != bone:
bone.parent = find_ctrl(parentbone, controls)
else:
bone.parent = editbone.parent.name
edit_bones = obj.data.edit_bones
for bone in controls:
editbone = edit_bones[bone.name]
ctrl = obj.data.edit_bones.new(bone.ctrl)
ctrl.head = bone.point
ctrl.tail = bone.point
ctrl.tail[2] = bone.point[2] + (editbone.length / 3)
ctrl.bbone_x = editbone.bbone_x
ctrl.bbone_z = editbone.bbone_z
ctrl.use_deform = False
if self.bone_align:
angle = 90 if self.align_90 else 0
bone_orientation(ctrl, editbone, angle)
if angle == 90:
ctrl.align_roll(editbone.vector)
else:
ctrl.roll = editbone.roll
#apply hierarchy
for bone in parents:
editbone = edit_bones[bone.name]
ctrl_name = find_ctrl(bone, controls)
ctrl = edit_bones[ctrl_name]
editbone.parent = ctrl
for bone in controls:
ctrl = edit_bones[bone.ctrl]
if hasattr(bone, 'parent'):
ctrl.parent = edit_bones[bone.parent]
return controls
def pose_bbone_setup(bone, posebone, bbone_group = None):
#add the custom shape to the widget bones
custom_shape = draw_wgt(bone['length'], posebone)
posebone.custom_shape = custom_shape
posebone.use_custom_shape_bone_size = False
if bbone_group:
posebone.bone_group = bbone_group
posebone.rotation_mode = 'XZY'
posebone.lock_rotation[0] = True
posebone.lock_rotation[2] = True
#####MAIN####
class BboneWidgets(bpy.types.Operator):
"""Add Bbone widget controls to the selected bones"""
bl_idname = "armature.add_bbone_widgets"
bl_label = "Add_Bbone_widgets"
bl_options = {'REGISTER', 'UNDO'}
@classmethod
def poll(cls, context):
return bpy.context.object.type == 'ARMATURE'
def execute(self, context):
obj = context.object
obj.data.display_type = 'BBONE'
bones = []
parentlayers = [False if i != 24 else True for i in range(32)]
wgtlayers = [True if i == 0 else False for i in range(32)]
bpy.ops.object.mode_set(mode = 'EDIT')
obj.data.use_mirror_x = False
for bone in obj.data.edit_bones:
if not bone.select:
continue
if bone.bbone_segments == 1:
bone.bbone_segments = 10
bone.bbone_handle_type_start = 'TANGENT'
bone.bbone_handle_type_end = 'TANGENT'
bone_name = bone.name
#add parent bone to the Bbone widgets
parent = obj.data.edit_bones.new('WGTB_parent_'+ bone_name)
parent_name = parent.name
dup_values(bone, parent)
parent.name = parent_name
parent.select = False
parent.select_head = False
parent.select_tail = False
#change layer of the parent bone
if bpy.app.version < (4, 0, 0):
parent.layers = parentlayers
#add bbone widget bones
head_widget = obj.data.edit_bones.new('Bhead_'+ bone.name)
head_widget.parent = parent
#head_widget.head = bone.head
head_widget.head = bone.head + (bone.tail - bone.head) * 0.25
#head_widget.tail = (bone.tail - bone.head)/10
head_widget.length = bone.length * 0.1
head_widget.bbone_x = bone.bbone_x
head_widget.bbone_z = bone.bbone_z
head_widget.align_orientation(bone)
head_widget.inherit_scale = 'NONE'
head_name = head_widget.name
tail_widget = obj.data.edit_bones.new('Btail_'+ bone.name)
tail_widget.parent = parent
#tail_widget.head = bone.tail
tail_widget.head = bone.head + (bone.tail - bone.head) * 0.75
#tail_widget.tail = bone.tail - (bone.tail - bone.head)/10
tail_widget.length = bone.length * 0.1
tail_widget.bbone_x = bone.bbone_x
tail_widget.bbone_z = bone.bbone_z
tail_widget.align_orientation(bone)
tail_widget.inherit_scale = 'NONE'
tail_name = tail_widget.name
#add vis bones
head_vis = obj.data.edit_bones.new('Bhead_vis_'+ bone.name)
head_vis.parent = parent
head_vis.head = bone.head
head_vis.tail = head_widget.head
head_vis.bbone_x = bone.bbone_x*0.1
head_vis.bbone_z = bone.bbone_z*0.1
#head_vis_name = head_vis.name
head_vis.hide_select = True
head_vis.use_deform = False
tail_vis = obj.data.edit_bones.new('Btail_vis_'+ bone.name)
tail_vis.parent = parent
tail_vis.head = bone.tail
tail_vis.tail = tail_widget.head
tail_vis.bbone_x = bone.bbone_x*0.1
tail_vis.bbone_z = bone.bbone_z*0.1
#tail_vis_name = tail_vis.name
tail_vis.hide_select = True
tail_vis.use_deform = False
if bpy.app.version < (4, 0, 0):
tail_widget.layers = wgtlayers
head_widget.layers = wgtlayers
head_vis.layers = wgtlayers
tail_vis.layers = wgtlayers
bones.append({'name': bone_name, 'parent': parent_name, 'head': head_name, 'tail': tail_name, 'head_vis': head_vis.name, 'tail_vis': tail_vis.name, 'length': bone.length})
#####POSE MODE#######
bpy.ops.object.mode_set(mode = 'POSE')
if bpy.app.version < (4, 0, 0):
bone_groups = obj.pose.bone_groups
if 'BBone Widgets' not in bone_groups:
bbone_group = bone_groups.new(name = 'BBone Widgets')
bbone_group.color_set = 'THEME09'
else:
bbone_group = bone_groups['BBone Widgets']
else:
bbone_group = None
for bone in bones:
posebone = obj.pose.bones[bone['name']]
# Prepare parent bone in pose mode
poseparent = obj.pose.bones[bone['parent']]
#disable use deform
obj.data.bones[bone['parent']].use_deform = False
obj.data.bones[bone['head']].use_deform = False
obj.data.bones[bone['tail']].use_deform = False
pose_bbone_setup(bone, obj.pose.bones[bone['head']], bbone_group)
pose_bbone_setup(bone, obj.pose.bones[bone['tail']], bbone_group)
dup_constraints(posebone, poseparent)
#add all the drivers
add_driver(obj, posebone, bone['head'], 'bbone_curveinx', 'location.x')
add_driver(obj, posebone, bone['head'], 'bbone_curveinz', 'location.z')
add_driver(obj, posebone, bone['head'], 'bbone_easein', 'location.y', '*5/'+ str(bone['length']))
add_driver(obj, posebone, bone['head'], 'bbone_rollin', 'rotation_euler.y')
add_driver(obj, posebone, bone['head'], ('bbone_scalein', 0), 'scale.x')
add_driver(obj, posebone, bone['head'], ('bbone_scalein', 1), 'scale.y')
add_driver(obj, posebone, bone['head'], ('bbone_scalein', 2), 'scale.z')
add_driver(obj, posebone, bone['tail'], 'bbone_curveoutx', 'location.x')
add_driver(obj, posebone, bone['tail'], 'bbone_curveoutz', 'location.z')
add_driver(obj, posebone, bone['tail'], 'bbone_easeout', 'location.y', '*-5/'+ str(bone['length']))
add_driver(obj, posebone, bone['tail'], 'bbone_rollout', 'rotation_euler.y')
add_driver(obj, posebone, bone['tail'], ('bbone_scaleout', 0), 'scale.x')
add_driver(obj, posebone, bone['tail'], ('bbone_scaleout', 1), 'scale.y')
add_driver(obj, posebone, bone['tail'], ('bbone_scaleout', 2), 'scale.z')
#add constraints to visual bones
head_vis = add_vis_bone_con(obj, bone['head_vis'], bone['head'])
tail_vis = add_vis_bone_con(obj, bone['tail_vis'], bone['tail'])
if bpy.app.version < (4, 0, 0):
head_vis.bone_group = bbone_group
tail_vis.bone_group = bbone_group
return {"FINISHED"}
class ChainControls(bpy.types.Operator):
"""Add parent and target controls to the selected bones to create a chain control"""
bl_idname = "armature.add_chain_ctrls"
bl_label = "Add_Chain_Controls"
bl_options = {'REGISTER', 'UNDO'}
parents: bpy.props.BoolProperty(name = 'Add Parents', description = "Align the controls 90 degrees to the original bones", default = True)
targets: bpy.props.BoolProperty(name = 'Add Targets', description = "Align the controls 90 degrees to the original bones", default = True)
keep_hierarchy: bpy.props.BoolProperty(name = 'Keep Hierarchy', description = "Keep the controls in the hierarchy of the original bones", default = True)
bone_align: bpy.props.BoolProperty(name = 'Align to Bones', description = "Align the controls to the original bones", default = True)
align_90: bpy.props.BoolProperty(name = '+90°', description = "Align the controls 90 degrees to the original bones", default = True)
@classmethod
def poll(cls, context):
return bpy.context.object.type == 'ARMATURE'
def invoke(self, context, event):
#obj = context.object
wm = context.window_manager
return wm.invoke_props_dialog(self, width = 200)
def draw(self, context):
layout = self.layout
row = layout.row()
row.label(text = 'Add Control Bones')
row = layout.row()
row.prop(self, 'parents') #text = 'Size'
row.prop(self, 'targets')
layout.separator()
col = layout.column()
col.prop(self, 'keep_hierarchy')
row = layout.row()
row.prop(self, 'bone_align')
if self.bone_align:
row.prop(self, 'align_90', toggle=True)
def execute(self, context):
obj = context.object
targets = []
parents = []
bpy.ops.object.mode_set(mode = 'EDIT')
edit_bones = bpy.context.selected_editable_bones
#create list of parent and target objects
for bone in edit_bones:
bone.use_connect = False
if self.targets:
targets.append(target(bone))
if self.parents:
parents.append(parent(bone))
controls = add_controlers(self, obj, parents, targets)
bpy.ops.object.mode_set(mode = 'POSE')
#Add the bone group for the ctrls if doesn't exist
if bpy.app.version < (4, 0, 0):
bone_groups = obj.pose.bone_groups
if 'Ctrl Bones' not in bone_groups:
ctrl_group = bone_groups.new(name = 'Ctrl Bones')
ctrl_group.color_set = 'THEME01'
else:
ctrl_group = bone_groups['Ctrl Bones']
for bone in controls:
posebone = obj.pose.bones[bone.ctrl]
if bpy.app.version < (4, 0, 0):
posebone.bone_group = ctrl_group
else:
posebone.color.palette = 'THEME01'
if self.targets:
for bone in targets:
#update from the controls set
ctrl = find_ctrl(bone, controls)
posebone = obj.pose.bones[bone.name]
con = posebone.constraints.new('STRETCH_TO')
con.target = obj
con.subtarget = ctrl
return {"FINISHED"}
class MergeRigs(bpy.types.Operator):
"""Merge selected rigs to active and keep hierarchy and constraints for shared bones"""
bl_idname = "armature.merge"
bl_label = "Merge_Rigs"
bl_options = {'REGISTER', 'UNDO'}
@classmethod
def poll(cls, context):
return bpy.context.object.type == 'ARMATURE'
def execute(self, context):
target_obj = context.object
if target_obj.type != 'ARMATURE':
return {"CANCELLED"}
target_bones = set([bone.name for bone in target_obj.data.bones])
constraints = []
childrens = {}
#Store children and constraints
bpy.ops.object.mode_set(mode = 'POSE')
for obj in bpy.context.selected_objects:
if obj.type != 'ARMATURE':
continue
if obj == target_obj:
continue
#create a set of all the similiar bones in all the rigs
obj_bones = set([bone.name for bone in obj.data.bones])
shared_bones = target_bones.intersection(obj_bones)
#find all the constraints and children
for bone in obj.pose.bones:
#store all the constraints
for con in bone.constraints:
if not hasattr(con, 'subtarget'):
continue
if con.target == obj and con.subtarget in shared_bones:
constraints.append(constraint_dup(bone, con))
if bone.name in shared_bones:
for child in bone.children:
if child.name in childrens:
continue
childrens.update({child.name : bone.name})
#remove shared bones
bpy.ops.object.mode_set(mode = 'EDIT')
for obj in bpy.context.selected_objects:
if obj.type != 'ARMATURE':
continue
if obj == target_obj:
continue
for bone in shared_bones:
if bone not in obj.data.edit_bones:
continue
obj.data.edit_bones.remove(obj.data.edit_bones[bone])
bpy.ops.object.mode_set(mode = 'POSE')
bpy.ops.object.join()
#restore constraints
for con_dup in constraints:
if con_dup.bone in target_bones:
continue
if con_dup.bone not in target_obj.pose.bones:
continue
#print('constraint on ',con_dup.bone, con_dup.name)
posebone = target_obj.pose.bones[con_dup.bone]
if con_dup.name not in posebone.constraints:
continue
con = posebone.constraints[con_dup.name]
con.target = target_obj
con.subtarget = con_dup.subtarget
#reparent all child bones
bpy.ops.object.mode_set(mode = 'EDIT')
for child, parent in childrens.items():
target_obj.data.edit_bones[child].parent = target_obj.data.edit_bones[parent]
return {"FINISHED"}
classes = (MergeRigs,BboneWidgets, ChainControls)
def register():
from bpy.utils import register_class
for cls in classes:
register_class(cls)
# bpy.utils.register_class(BboneWidgets)
# bpy.utils.register_class(ChainControls)
# bpy.utils.register_class(RiggerToolBox_PT_Panel)
def unregister():
from bpy.utils import unregister_class
for cls in classes:
unregister_class(cls)
# bpy.utils.unregister_class(BboneWidgets)
# bpy.utils.unregister_class(ChainControls)
# bpy.utils.unregister_class(RiggerToolBox_PT_Panel)
if __name__ == "__main__":
register()
File diff suppressed because one or more lines are too long
File diff suppressed because it is too large Load Diff
@@ -1,534 +0,0 @@
# ***** BEGIN GPL LICENSE BLOCK *****
#
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software Foundation,
# Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
#
# ***** END GPL LICENCE BLOCK *****
bl_info = {
"name": "AnimToolBox",
"author": "Tal Hershkovich",
"version" : (0, 0, 8),
"blender" : (3, 2, 0),
"location": "View3D - Properties - Animation Panel",
"description": "A set of animation tools",
"wiki_url": "",
"category": "Animation"}
if "bpy" in locals():
import importlib
if "Rigger_Toolbox" in locals():
importlib.reload(Rigger_Toolbox)
if "TempCtrls" in locals():
importlib.reload(TempCtrls)
if "Tools" in locals():
importlib.reload(Tools)
if "Display" in locals():
importlib.reload(Display)
if "emp" in locals():
importlib.reload(emp)
if "multikey" in locals():
importlib.reload(multikey)
if "Rigger_Toolbox" in locals():
importlib.reload(Rigger_Toolbox)
if "ui" in locals():
importlib.reload(ui)
if "addon_updater_ops" in locals():
importlib.reload(addon_updater_ops)
import bpy
from . import addon_updater_ops
from . import TempCtrls
from . import Rigger_Toolbox
from . import Tools
from . import Display
from . import emp
from . import ui
from . import multikey
from . import Rigger_Toolbox
from pathlib import Path
from bpy.utils import register_class
from bpy.utils import unregister_class
from bpy.app.handlers import persistent
import os
class TempCtrlsItems(bpy.types.PropertyGroup):
#located at context.scene.btc.ctrl_items
controlled: bpy.props.PointerProperty(name = "controlled object", description = "rigs and objects that are being controlled", type=bpy.types.Object, override = {'LIBRARY_OVERRIDABLE'})
controller: bpy.props.PointerProperty(name = "controller object", description = "rigs and objects that are controling", type=bpy.types.Object, override = {'LIBRARY_OVERRIDABLE'})
class TempCtrlsSceneSettings(bpy.types.PropertyGroup):
#located at context.scene.btc
root: bpy.props.BoolProperty(name = "Root Empty", description = "Add a root to the empties ", default = False, override = {'LIBRARY_OVERRIDABLE'}, update = TempCtrls.root_prop)
root_bone: bpy.props.StringProperty(name = "Root bone", description = "Root empty as a root bone ", override = {'LIBRARY_OVERRIDABLE'}, update = TempCtrls.root_update)
root_object: bpy.props.PointerProperty(name = "Root object", description = "Root empty as a root object ", update = TempCtrls.root_update, type = bpy.types.Object, override = {'LIBRARY_OVERRIDABLE'})
ctrl_type: bpy.props.EnumProperty(name = 'Controllers', description="Select empties or a bone with a new rig to bake to", items = [('BONE', 'Bone','Bake to bones','BONE_DATA', 0), ('EMPTY', 'Empty', 'Bake to empties', 'EMPTY_ARROWS', 1)])
ctrl_items: bpy.props.CollectionProperty(type = TempCtrlsItems, override = {'LIBRARY_OVERRIDABLE', 'USE_INSERTION'})
bake_range_type: bpy.props.EnumProperty(name = 'Bake Range', description="Use either scene, actions length or custom frame range", default = 'KEYFRAMES', update= TempCtrls.update_range_type,
items = [('SCENE', 'Scene Range', 'Bake to the scene range'), ('KEYFRAMES', 'Keyframes Range', 'Bake all the keyframes in the layers'), ('CUSTOM', 'Custom', 'Enter a custom frame range')], override = {'LIBRARY_OVERRIDABLE'})
bake_range: bpy.props.IntVectorProperty(name='Frame Range', description='Bake to a custom frame range', size = 2, update= TempCtrls.update_bake_range)
bake_layers: bpy.props.BoolProperty(name = "Bake Layers", description = "Use keyframes from all the layers to include in the bake", default = False)
target: bpy.props.EnumProperty(name = 'Affect', description="Cleanup created constraints and empties", default = 1,
items = [('ALL', 'All Ctrl Rigs','Bake to all Ctrl Rigs', 0),
('SELECTED', 'Selected Chains','Bake to only selected chain controlls', 1),
('RELATIVE', 'Relative Ctrls Rig','Bake to the Relative Control rigs', 2)])
# ('CONSTRAINTS', 'Constraints', 'Clean all the bone constraints', 3),
# ('CONTROLLERS', 'Controllers', 'Remove all the baked empties', 4)])
selection: bpy.props.EnumProperty(name = 'Select', description="Select all controls, original bones or their relative", default = 'CONTROLLERS',
items = [('RELATIVE_CTRLS', 'Relative Ctrls','Select the Relative controller to your current selection', 0),
('RELATIVE_CONSTRAINED', 'Relative Constrained','Select the Relative original constrained bone to your current selection', 1),
('CONTROLLERS', 'All Ctrls', 'Select all the controller bones or empties', 2), ('CONSTRAINED', 'All Constrained', 'Select all the original constrained bones', 3)])
#smartbake setting
linksettings: bpy.props.BoolProperty(name = "Link Settings", description = "Link Settings", default = False, override = {'LIBRARY_OVERRIDABLE'})
bakesettings: bpy.props.BoolProperty(name = "bake settings", description = "bake settings", default = False, override = {'LIBRARY_OVERRIDABLE'})
cleansettings: bpy.props.BoolProperty(name = "clean settings", description = "clean settings", default = False, override = {'LIBRARY_OVERRIDABLE'})
smartbake: bpy.props.BoolProperty(name = "Smart Bake", description = "Keep Original Frame count", default = True, override = {'LIBRARY_OVERRIDABLE'})
inbetween_keyframes: bpy.props.IntProperty(name = "Inbetween Keyframes", description = "Add inbetween keyframes", default = 0, override = {'LIBRARY_OVERRIDABLE'})
from_origin: bpy.props.BoolProperty(name = "From Origin", description = "Use Keyframes from Original Bone", default = True, override = {'LIBRARY_OVERRIDABLE'})
from_ctrl: bpy.props.BoolProperty(name = "From Controller", description = "Use Keyframes from Controller Bone", default = False, override = {'LIBRARY_OVERRIDABLE'})
clean_ctrls: bpy.props.BoolProperty(name = "Remove Ctrls", description = "Remove Controls", default = True, override = {'LIBRARY_OVERRIDABLE'})
clean_constraints: bpy.props.BoolProperty(name = "Remove Constraints", description = "Remove Constraints", default = True, override = {'LIBRARY_OVERRIDABLE'})
rebake_to_org: bpy.props.BoolProperty(name = "ReBake connections to original bones", description = "ReBake ctrls from connected chains current anim to the original bones", default = False, override = {'LIBRARY_OVERRIDABLE'})
link_to: bpy.props.EnumProperty(name = 'Link to Chain', description="Link to begining of an active chain or the tip of the chain", default = 1,
items = [('BASE', 'Base','Link to the base of the active chain', 0), ('TIP', 'Tip', 'Link to the tip of the chain', 1)])
# link_from: bpy.props.EnumProperty(name = 'Link to Chain', description="Link to begining of an active chain or the tip of the chain", default = 0,
# items = [('BASE', 'Base','Link to the base of the active chain', 0), ('TIP', 'Tip', 'Link to the tip of the chain', 1)])
shape_size: bpy.props.FloatProperty(name='Size', description="Multiple factor for the shape size of the temp controls", update = TempCtrls.tempctrl_shapesize, min = 0.001, default = 1.5, override = {'LIBRARY_OVERRIDABLE'})#
shape_type: bpy.props.EnumProperty(name = 'Shape Type', description="Display type for the controls", items = TempCtrls.ctrl_shape_items, update = TempCtrls.tempctrl_shape_type)
color_set: bpy.props.EnumProperty(name="Bone Color Set", description="Choose a bone color set", items = TempCtrls.get_bone_color_sets, update = TempCtrls.update_bone_color, default = 9)
add_ik_ctrl: bpy.props.BoolProperty(name = 'Add an Extra IK Ctrl Bone', description = "Adds an extra bone ctrl as the ik ctrl", default = False, update = TempCtrls.add_ik_prop)
pole_target: bpy.props.BoolProperty(name = 'Add Pole Target', description = "Adding Pole Target to the IK Chain", default = True, update = TempCtrls.pole_prop)
pole_offset: bpy.props.FloatProperty(name="Offset", description="Offset the bone in the axis direction", default=1.0, update = TempCtrls.pole_offset)
child: bpy.props.BoolProperty(name = 'Add extra child Ctrls', description = "Add an child control for an overlay control", default = False, update = TempCtrls.child_prop)
orientation: bpy.props.BoolProperty(name = 'Use World Space Orientation', description = "Orient the bones to world space instead of to the original bones", default = True)
# enabled: bpy.props.BoolProperty(name = 'Switch On / Off', description = "Enabling and Disabling Temp Ctrls influence", default = True)
class TempCtrlsBoneSettings(bpy.types.PropertyGroup):
#located at obj.pose.bones[##].btc
root: bpy.props.BoolProperty(name = "Root Bone", description = "Bone is marked as the root bone", default = False, override = {'LIBRARY_OVERRIDABLE'})
child: bpy.props.BoolProperty(name = "Child Bone", description = "Bone is marked as a child bone inside the setup", default = False, override = {'LIBRARY_OVERRIDABLE'})
org_id: bpy.props.IntProperty(name = "Originate ID", description = "ID number of the bone the ctrl originates from", override = {'LIBRARY_OVERRIDABLE'})
setup_id: bpy.props.IntProperty(name = "Setup ID", description = "ID number of the current chain setup", override = {'LIBRARY_OVERRIDABLE'})
setup: bpy.props.EnumProperty(name = 'Setup Type', description="Describes what kind of setup the bone is part of", override = {'LIBRARY_OVERRIDABLE'},
items = [('NONE', 'No Setup','No Setup Applied', 0),
('WORLDSPACE', 'World Space Ctrl','World Space Ctrl setup', 1),
('WORLDSPACE_CURSOR', 'World Space Cursor Ctrl','World Space Cursor pivot', 2),
('TEMPFK', 'Temporary FK setup','Temporary FK chain setup', 3),
('TEMPFK_FLIP', 'Temporary flipped FK setup','Temporary flipped FK chain setup', 4),
('TEMPIK', 'Temporary IK setup','Temporary IK setup', 5),
('POLE', 'Temporary IK Pole setup','Temporary IK Pole', 6),
('PARENTCTRL', 'Parent Ctrl from cursor setup','Parent Ctrl from cursor setup', 7),
('ROOT', 'Root', 'Root Ctrl for all the setups', 8),
('EMPTY', 'Root', 'Root Ctrl for all the setups', 9),
('TRACK_TO', 'Track To','World Space Track to Ctrl setup', 10),
('TRACK_TO_EMPTY', 'Track To Empty','World Space Track to Empty Ctrl setup', 11)])
#using org mostly to decide if it needs a custom shape
org: bpy.props.EnumProperty(name = 'Org Type', description="Describes what if the function of the bone", override = {'LIBRARY_OVERRIDABLE'},
items = [('CTRL', 'Controller Bone','Controller Bone', 0),
('ORG', 'Original Bone','Original Bone', 1),
('MCH', 'Mechanical Bone','Mechanical Bone', 2),
('NONE', 'Nothing applied','Nothing applied', 3)])
shape: bpy.props.BoolProperty(name = "Apply shape", description = "Mark if the bone needs a shape", default = False, override = {'LIBRARY_OVERRIDABLE'})
class TempCtrlsOrgIds(bpy.types.PropertyGroup):
# A collection of the org ids used in each setup. Org ID is the direct connection
# between original bones and ctrls
pass
class TempCtrlsObjectSetups(bpy.types.PropertyGroup):
#located at obj.animtoolbox.ctrl_setups
#name: using string of the id
setup: bpy.props.EnumProperty(name = 'Setup Type', description="Describes what kind of setup the bone is part of",
items = [('NONE', 'No Setup','No Setup Applied', 0),
('WORLDSPACE', 'World Space Ctrl','World Space Ctrl setup', 1),
('WORLDSPACE_CURSOR', 'World Space Cursor Ctrl','World Space Cursor pivot', 2),
('TEMPFK', 'Temporary FK setup','Temporary FK chain setup', 3),
('TEMPFK_FLIP', 'Temporary flipped FK setup','Temporary flipped FK chain setup', 4),
('TEMPIK', 'Temporary IK setup','Temporary IK setup', 5),
('POLE', 'Temporary IK Pole setup','Temporary IK Pole', 6),
('PARENTCTRL', 'Parent Ctrl from cursor setup','Parent Ctrl from cursor setup', 7),
('ROOT', 'Root', 'Root Ctrl for all the setups', 8),
('EMPTY', 'Root', 'Root Ctrl for all the setups', 9),
('TRACK_TO', 'Track To','World Space Track to Ctrl setup', 10),
('TRACK_TO_EMPTY', 'Track To Empty','World Space Track to Empty Ctrl setup', 11)])
org_ids: bpy.props.CollectionProperty(type = TempCtrlsOrgIds)
class MultikeyProperties(bpy.types.PropertyGroup):
selectedbones: bpy.props.BoolProperty(name="Selected Bones", description="Affect only selected bones", default=True, options={'HIDDEN'})
handletype: bpy.props.BoolProperty(name="Keep handle types", description="Keep handle types", default=False, options={'HIDDEN'})
scale: bpy.props.FloatProperty(name="Scale Factor", description="Scale percentage of the average value", default=1.0, update = multikey.scale_value)
randomness: bpy.props.FloatProperty(name="Randomness", description="Random Threshold of keyframes", default=0.1, min=0.0, max = 1.0, update = multikey.random_value)
class AnimToolBoxObjectSettings(bpy.types.PropertyGroup):
controlled: bpy.props.PointerProperty(name = 'Controlled Rig', description="Adding the rig object that is being controlled by the current object", type=bpy.types.Object, override = {'LIBRARY_OVERRIDABLE'})
controller: bpy.props.PointerProperty(name = 'Controller Rig', description="Adding the rig object that is used as the temp control object", type=bpy.types.Object, override = {'LIBRARY_OVERRIDABLE'})
ctrl_setups: bpy.props.CollectionProperty(type = TempCtrlsObjectSetups, override = {'LIBRARY_OVERRIDABLE', 'USE_INSERTION'})
ctrls_enabled: bpy.props.BoolProperty(name = 'Temp Ctrls Switch', description = "Enabling and Disabling Temp Ctrls influence", default = True)
# influence: bpy.props.FloatProperty(name = "Influence Slider for the Temp Ctrls", description = "Switching the influence slider for the temp ctrls", default = 1, min = 0.0, max = 1.0)
#Used for Bake to Empties
# org_id: bpy.props.IntProperty(name = "Originate ID", description = "ID number of the bone the ctrl originates from", override = {'LIBRARY_OVERRIDABLE'})
setup_id: bpy.props.IntProperty(name = "Setup ID", description = "ID number of the current chain setup", override = {'LIBRARY_OVERRIDABLE'})
root: bpy.props.BoolProperty(name = "Root Empty", description = "Empty is marked as the root ctrl", default = False, override = {'LIBRARY_OVERRIDABLE'})
child: bpy.props.BoolProperty(name = "Child Empty", description = "Empty is marked as a child bone inside the setup", default = False, override = {'LIBRARY_OVERRIDABLE'})
keyframes_offset: bpy.props.FloatProperty(name = "Keyframes Offset", description = "Interactive slider to offset keyframes back and forth ", default = 0)
class IsolatedRigs(bpy.types.PropertyGroup):
hidden: bpy.props.PointerProperty(name = "Hidden Rigs", description = "List of Rigs that are hidden during pose mode isolation", type=bpy.types.Object, override = {'LIBRARY_OVERRIDABLE'})
selected: bpy.props.PointerProperty(name = "Selected Rigs", description = "List of Rigs that are hidden during pose mode isolation", type=bpy.types.Object, override = {'LIBRARY_OVERRIDABLE'})
class AnimToolBoxUILayout(bpy.types.PropertyGroup):
'''Layout properties for the UI'''
quick_menu: bpy.props.BoolProperty(name = "Use Quick Menu", description = "Opens header menu with only icon", default = False)
copy_paste_matrix: bpy.props.BoolProperty(name = "Copy Matrix Menu", description = "Opens the menu for copy paste matrices", default = True)
copy_paste_world: bpy.props.BoolProperty(name = "Copy Paste World Matrix", description = "Copy and Paste the World Matrix", default = False, update = Tools.copy_paste_world_update)
copy_paste_relative: bpy.props.BoolProperty(name = "Copy Paste Relative Matrix", description = "Copy and Paste the Matrix relative to the active bone", default = False, update = Tools.copy_paste_relative_update)
Inbetweens: bpy.props.BoolProperty(name = "Blendings/Inbetweens", description = "Opens the menu for Inbetweens", default = True)
gizmo_size: bpy.props.BoolProperty(name = "Gizmo size", description = "Change the Gizmo size using alt +/- hotkeys", default = False)
# temp_ctrls: bpy.props.BoolProperty(name = "Temp Ctrls", description = "Open Temp Ctrls", default = False)
temp_ctrls_switch: bpy.props.BoolProperty(name = "Temp Ctrls Switch", description = "Temp Ctrls Switch", default = True)
temp_ctrls_shapes: bpy.props.BoolProperty(name = "Temp Ctrls Shapes", description = "Temp Ctrls Shapes", default = True)
markers_retimer: bpy.props.BoolProperty(name = "Marker Retimer", description = "Flag when marker retimer turned on", default = False, override = {'LIBRARY_OVERRIDABLE'})
relative_cursor: bpy.props.BoolProperty(name = "Relative Cursor Mode", description = "Cursor moves relative to the selection", default = False)
is_dragging: bpy.props.BoolProperty(default = False)
#using Blending sliders in the window manager to avoid undo issues with modal operators
inbetween_worldmatrix: bpy.props.FloatProperty(name='Inbetween World Matrix', description="Adds an inbetween of the World Matrix to the Layer's neighbor keyframes", soft_min = -1, soft_max = 1, default=0.0, update = Tools.add_inbetween_worldmatrix, override = {'LIBRARY_OVERRIDABLE'})
blend_mirror: bpy.props.FloatProperty(name='Blend Mirror', description="Blend into the mirrored pose", soft_min = 0, soft_max = 1, default=0, step = 1, update = Tools.blend_to_mirror, override = {'LIBRARY_OVERRIDABLE'})
multikey: bpy.props.PointerProperty(type = MultikeyProperties, options={'LIBRARY_EDITABLE'}, override = {'LIBRARY_OVERRIDABLE'})
class AnimToolBoxGlobalSettings(bpy.types.PropertyGroup):
#context.scene.animtoolbox
marker_frame_range: bpy.props.BoolProperty(name = "Marker Frame Range", description = "Flag when marker frame range turned on", default = False, override = {'LIBRARY_OVERRIDABLE'})
bake_frame_range: bpy.props.BoolProperty(name = "Bake Frame Range", description = "Flag when marker bake range turned on", default = False, override = {'LIBRARY_OVERRIDABLE'})
# markers_retimer: bpy.props.BoolProperty(name = "Marker Retimer", description = "Flag when marker retimer turned on", default = False, override = {'LIBRARY_OVERRIDABLE'})
keyframes_offset: bpy.props.FloatProperty(name = "Keyframes Offset", description = "Interactive slider to offset keyframes back and forth ", soft_max = 2, soft_min = -2, default = 0, update = Tools.keyframes_offset_slider)
rotation_mode: bpy.props.EnumProperty(name = 'Rotation Mode', description="Describes what kind of setup the bone is part of", override = {'LIBRARY_OVERRIDABLE'},
items = [('QUATERNION', 'Quaternion','Quaternion Rotation Order - No Gimbal Lock', 0),
('XYZ', 'XYZ', 'XYZ Rotation Order', 1), ('XZY', 'XZY','XZY Rotation Order', 2),
('YXZ', 'YXZ','YXZ Rotation Order', 3), ('YZX', 'YZX', 'YZX Rotation Order', 4),
('ZXY', 'ZXY', 'ZXY Rotation Order', 5), ('ZYX', 'ZYX', 'ZYX Rotation Order', 6),
('AXIS_ANGLE', 'AXIS_ANGLE', 'Axis Angle Rotation Order', 7)])
isolate_pose_mode: bpy.props.BoolProperty(name = "Isolate rig in pose mode", description = "Isolates the rig during pose mode, rigs in object mode are hidden", default = False)
active_obj: bpy.props.PointerProperty(name = "Active Object", description = "Current Active Object", type=bpy.types.Object, override = {'LIBRARY_OVERRIDABLE'})
isolated: bpy.props.CollectionProperty(type = IsolatedRigs, override = {'LIBRARY_OVERRIDABLE', 'USE_INSERTION'})
#Blendings
inbetweener : bpy.props.FloatProperty(name='Inbetween Keyframe', description="Adds an inbetween Keyframe between the Layer's neighbor keyframes", soft_min = -1, soft_max = 1, default=0.0, options = set(), update = Tools.add_inbetween_key, override = {'LIBRARY_OVERRIDABLE'})
gizmo_size: bpy.props.IntProperty(name = "Add to Gizmo Size", description = "Addition to Gizmo Size", max = 100, min = -100, default = 10)
#Copy/Pase Matrix
range_type: bpy.props.EnumProperty(name = 'Paste to Frames', description="Paste to current frame or a range of frames.", update = Tools.bake_range_type,
items = [('CURRENT', 'Current Frame','Paste Matrix to only current frame', 0),
('SELECTED', 'Selected Keyframe','Paste Matrix to only selected keyframes', 1),
('RANGE', 'Frame Range','Paste Matrix to a Frame Range', 2)])
bake_frame_start: bpy.props.IntProperty(name = "Bake Frame Start", description = "Define the start frame to paste the matrix", min = 0, update = Tools.bake_frame_start_limit)
bake_frame_end: bpy.props.IntProperty(name = "Bake Frame End", description = "Define the end frame to paste the matrix", min = 0, update = Tools.bake_frame_end_limit)
filter_location: bpy.props.BoolVectorProperty(name="Location", description="Filter Location properties", default=(False, False, False), size = 3, options={'HIDDEN'}, update = Tools.filter_name_update)
filter_rotation: bpy.props.BoolVectorProperty(name="Rotation", description="Filter Rotation properties", default=(False, False, False, False), size = 4, options={'HIDDEN'}, update = Tools.filter_name_update)
filter_scale: bpy.props.BoolVectorProperty(name="Scale", description="Filter Scale properties", default=(False, False, False), size = 3, options={'HIDDEN'}, update = Tools.filter_name_update)
#The name displayed on the filter button
filter_name: bpy.props.StringProperty(name="Filter Name", description="Change the name of the button while chaging the filter options", default= "", options={'HIDDEN'})
filter_custom_props: bpy.props.BoolProperty(name = "Filter Custom Properties", description = "Filter custom properties", default = False)
filter_keyframes: bpy.props.BoolProperty(name = "Filter Aelected Keyframes", description = "Filter selected keyframes for specific tools", default = False)
col_vis: bpy.props.BoolProperty(name = "Animated collections visibility", description = "Display if animated collections are turned on or off", default = False)
@addon_updater_ops.make_annotations
class AnimToolBoxPreferences(bpy.types.AddonPreferences):
# this must match the addon name, use '__package__'
# when defining this in a submodule of a python package.
bl_idname = __package__
category: bpy.props.StringProperty(
name="Tab Category",
description="Choose a name for the category of the panel",
default="Animation",
update=ui.update_panel
)
quick_menu: bpy.props.BoolProperty(name = "Use Quick Menu", description = "Opens header menu with only icon", default = False)
riggertoolbox: bpy.props.BoolProperty(name = "RiggerToolBox", description = "Include RiggerToolbox (experimental)", default = False, update = ui.add_riggertoolbox)
multikey: bpy.props.BoolProperty(name = "Multikey", description = "Include Multikey for adju\sting multiply keyframes", default = False, update = ui.add_multikey)
#Temp Ctrls properties
in_front: bpy.props.BoolProperty(name = "Always In Front", description = "Set Temp Ctrls to be always in front", default = True)
clear_setup : bpy.props.BoolProperty(name = "Clear Selection Before Creating New Temp Ctrls", description = "Clear old setup when adding Temp ctrls to an existing chain", default = False)
#Editable motion path
keyframes_range: bpy.props.IntProperty(name = "Keyframe Range", description = "The range of distance from the keyframes while hovering over them", min = 5, max = 100, default = 15)
mp_pref: bpy.props.BoolProperty(name = "Editable Motion Path Colors Theme", description = "Set the Color them of editable motion path visualization", default = False)
mp_keyframe_color: bpy.props.FloatVectorProperty(name="Keyframes", subtype='COLOR', default=(1.0, 1.0, 0.0, 1.0), size=4, min=0.0, max=1.0, description="Handles selection color")
mp_handle_color: bpy.props.FloatVectorProperty(name="Handles", subtype='COLOR', default=(1.0, 0.8, 0.2, 1.0), size=4, min=0.0, max=1.0, description="Handles selection color")
mp_remove_color: bpy.props.FloatVectorProperty(name="Remove Keyframes", subtype='COLOR', default=(0.0, 0.5, 1.0, 1.0), size=4, min=0.0, max=1.0, description="Keyframe color displayed before removing")
mp_hover_color: bpy.props.FloatVectorProperty(name="Hover", subtype='COLOR', default=(1.0, 0.4, 0.2, 1.0), size=4, min=0.0, max=1.0, description="Color during Hovering")
mp_handle_selection_color: bpy.props.FloatVectorProperty(name="Handles Selection", subtype='COLOR', default=(0.8, 0.65, 0.6, 0.8), size=4, min=0.0, max=1.0, description="Handles selection color")
mp_key_selection_color: bpy.props.FloatVectorProperty(name="Keyframe Selection", subtype='COLOR', default=(0.8, 0.8, 0.6, 0.8), size=4, min=0.0, max=1.0, description="Keyframe selection color")
# addon updater preferences from `__init__`, be sure to copy all of them
auto_check_update: bpy.props.BoolProperty(
name = "Auto-check for Update",
description = "If enabled, auto-check for updates using an interval",
default = True,
)
updater_interval_months: bpy.props.IntProperty(
name='Months',
description = "Number of months between checking for updates",
default=0,
min=0
)
updater_interval_days: bpy.props.IntProperty(
name='Days',
description = "Number of days between checking for updates",
default=7,
min=0,
)
updater_interval_hours: bpy.props.IntProperty(
name='Hours',
description = "Number of hours between checking for updates",
default=0,
min=0,
max=23
)
updater_interval_minutes: bpy.props.IntProperty(
name='Minutes',
description = "Number of minutes between checking for updates",
default=0,
min=0,
max=59
)
#Draw the UI in the preferences
def draw(self, context):
layout = self.layout
addon_updater_ops.update_settings_ui(self, context)
row = layout.row()
col = row.column()
col.label(text="Tab Category:")
col.prop(self, "category", text="")
layout.separator()
col = layout.column()
col.prop(self, "quick_menu", text="Use Quick Icons Menu")
layout.separator()
box = layout.box()
row = box.row()
row.label(text = 'Temp Ctrls: ')
row = box.row()
row.prop(self, 'clear_setup')
row.prop(self, 'in_front')
layout.separator()
box = layout.box()
col = box.column()
col.prop(self, 'mp_pref', icon = 'DOWNARROW_HLT', text = 'Editable Motion Path Preferences')
if self.mp_pref:
col.prop(self, 'keyframes_range', text = 'Keyframe Distance Range')
col.label(text = 'Colors Theme')
row = box.row()
row.prop(self, 'mp_keyframe_color')
row.prop(self, 'mp_handle_color')
row.prop(self, 'mp_remove_color')
row = box.row()
row.prop(self, 'mp_hover_color')
row.prop(self, 'mp_key_selection_color')
row.prop(self, 'mp_handle_selection_color')
layout.separator()
col = layout.column()
col.label(text = 'Include Extras: ')
row = layout.row()
row.prop(self, "multikey", text="Multikey - Edit Multiply keyframes")
row.prop(self, "riggertoolbox", text="RiggerToolBox (Experimental)")
@persistent
def loadanimtoolbox_pre(self, context):
scene = bpy.context.scene
dns = bpy.app.driver_namespace
if scene.animtoolbox.bake_frame_range:
scene.animtoolbox.bake_frame_range = False
if scene.emp.motion_path:
scene.emp.motion_path = False
bpy.context.workspace.status_text_set(None)
if 'mp_dh' in dns:
bpy.types.SpaceView3D.draw_handler_remove(dns['mp_dh'], 'WINDOW')
bpy.app.driver_namespace.pop('mp_dh')
bpy.context.scene.emp.selected_keyframes = '{}'
if 'markers_retimer_dh' in dns:
bpy.types.SpaceView3D.draw_handler_remove(dns['markers_retimer_dh'], 'WINDOW')
bpy.app.driver_namespace.pop('markers_retimer_dh')
#remove the motion path app handler if it's still inside
if emp.mp_value_update in bpy.app.handlers.depsgraph_update_post:
bpy.app.handlers.depsgraph_update_post.remove(emp.mp_value_update)
if emp.mp_frame_change in bpy.app.handlers.frame_change_post:
bpy.app.handlers.frame_change_post.remove(emp.mp_frame_change)
if emp.mp_undo_update in bpy.app.handlers.undo_pre:
bpy.app.handlers.undo_pre.remove(emp.mp_undo_update)
@persistent
def loadanimtoolbox_post(self, context):
scene = bpy.context.scene
dns = bpy.app.driver_namespace
if scene.animtoolbox.isolate_pose_mode:
if Display.isolate_pose_mode not in bpy.app.handlers.depsgraph_update_pre:
bpy.app.handlers.depsgraph_update_pre.append(Display.isolate_pose_mode)
if scene.emp.motion_path:
scene.emp.motion_path = False
bpy.context.workspace.status_text_set(None)
if 'mp_dh' in dns:
bpy.types.SpaceView3D.draw_handler_remove(dns['mp_dh'], 'WINDOW')
bpy.app.driver_namespace.pop('mp_dh')
# Reset keyframe selection for motion paths it is not used in window manager
# Because it is used for undo
bpy.context.scene.emp.selected_keyframes = '{}'
Tools.selection_order(self, context)
classes = (TempCtrlsItems, TempCtrlsOrgIds, TempCtrlsObjectSetups, TempCtrlsSceneSettings,TempCtrlsBoneSettings, MultikeyProperties,
IsolatedRigs, AnimToolBoxObjectSettings, AnimToolBoxUILayout, AnimToolBoxGlobalSettings) + ui.classes
addon_keymaps = []
def register():
# Note that preview collections returned by bpy.utils.previews
# are regular py objects - you can use them to store custom data.
addon_updater_ops.register(bl_info)
register_class(AnimToolBoxPreferences)
addon_updater_ops.make_annotations(AnimToolBoxPreferences) # to avoid blender 2.8 warnings
TempCtrls.register()
Tools.register()
Display.register()
emp.register()
ui.register_custom_icon()
for cls in classes:
# print(cls)
register_class(cls)
bpy.types.Scene.btc = bpy.props.PointerProperty(type = TempCtrlsSceneSettings, override = {'LIBRARY_OVERRIDABLE'})
bpy.types.PoseBone.btc = bpy.props.PointerProperty(type = TempCtrlsBoneSettings, override = {'LIBRARY_OVERRIDABLE'})
bpy.types.Object.animtoolbox = bpy.props.PointerProperty(type = AnimToolBoxObjectSettings, override = {'LIBRARY_OVERRIDABLE'})
bpy.types.Scene.animtoolbox = bpy.props.PointerProperty(type = AnimToolBoxGlobalSettings, override = {'LIBRARY_OVERRIDABLE'})
bpy.types.WindowManager.atb_ui = bpy.props.PointerProperty(type = AnimToolBoxUILayout, override = {'LIBRARY_OVERRIDABLE'})
ui.update_panel(None, bpy.context)
ui.add_multikey(None, bpy.context)
ui.add_riggertoolbox(None, bpy.context)
if loadanimtoolbox_pre not in bpy.app.handlers.load_pre:
bpy.app.handlers.load_pre.append(loadanimtoolbox_pre)
if loadanimtoolbox_post not in bpy.app.handlers.load_post:
bpy.app.handlers.load_post.append(loadanimtoolbox_post)
if Tools.selection_order not in bpy.app.handlers.depsgraph_update_post:
bpy.app.handlers.depsgraph_update_post.append(Tools.selection_order)
#Make sure TAB hotkey in the NLA goes into full stack mode
wm = bpy.context.window_manager
kc = wm.keyconfigs.addon
km = kc.keymaps.new(name= '3D View', space_type= 'VIEW_3D')
if 'view3d.gizmo_size_up' not in km.keymap_items:
kmi = km.keymap_items.new('view3d.gizmo_size_up', type= 'NUMPAD_PLUS', value= 'PRESS', alt = True, repeat = True)
addon_keymaps.append((km, kmi))
if 'view3d.gizmo_size_down' not in km.keymap_items:
kmi = km.keymap_items.new('view3d.gizmo_size_down', type= 'NUMPAD_MINUS', value= 'PRESS', alt = True, repeat = True)
addon_keymaps.append((km, kmi))
#Add Tools to the Toolbar
bpy.utils.register_tool(ui.KeyframeOffsetTool, separator=True)
#Add tools to the menu
bpy.types.VIEW3D_MT_editor_menus.append(ui.draw_menu)
def unregister():
for pcoll in ui.preview_collections.values():
bpy.utils.previews.remove(pcoll)
ui.preview_collections.clear()
#addon_updater_ops.unregister()
addon_updater_ops.unregister()
unregister_class(AnimToolBoxPreferences)
TempCtrls.unregister()
# Rigger_Toolbox.unregister()
Tools.unregister()
Display.unregister()
emp.unregister()
ui.add_multikey(None, bpy.context)
ui.add_riggertoolbox(None, bpy.context)
for cls in classes:
unregister_class(cls)
bpy.utils.unregister_tool(ui.KeyframeOffsetTool)
#Remove the header menu ui
bpy.types.VIEW3D_MT_editor_menus.remove(ui.draw_menu)
del bpy.types.Scene.btc
# del bpy.types.Bone.btc
del bpy.types.Object.animtoolbox
del bpy.types.Scene.animtoolbox
if hasattr(bpy.types.Object, 'keyframes_offset'):
del bpy.types.Object.keyframes_offset
if hasattr(bpy.types.PoseBone, 'keyframes_offset'):
del bpy.types.PoseBone.keyframes_offset
if loadanimtoolbox_pre in bpy.app.handlers.load_pre:
bpy.app.handlers.load_pre.remove(loadanimtoolbox_pre)
if loadanimtoolbox_post in bpy.app.handlers.load_post:
bpy.app.handlers.load_post.remove(loadanimtoolbox_post)
if Tools.selection_order in bpy.app.handlers.depsgraph_update_post:
bpy.app.handlers.depsgraph_update_post.remove(Tools.selection_order)
for km, kmi in addon_keymaps:
km.keymap_items.remove(kmi)
addon_keymaps.clear()
if __name__ == "__main__":
register()
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
@@ -1,16 +0,0 @@
{
"last_check": "2026-02-09 15:54:54.419194",
"backup_date": "September-23-2025",
"update_ready": true,
"ignore": false,
"just_restored": false,
"just_updated": false,
"version_text": {
"link": "https://gitlab.com/api/v4/projects/45739913/repository/archive.zip?sha=7a9ad24a463bf1c8ae095ec853409979344e0738",
"version": [
0,
2,
3
]
}
}
File diff suppressed because it is too large Load Diff

Some files were not shown because too many files have changed in this diff Show More