2026-03-11_1

This commit is contained in:
2026-03-17 15:29:47 -06:00
parent dc97400d1e
commit 32d4247d4d
217 changed files with 2683 additions and 157239 deletions
+9 -9
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]
A:\1 Amazon_Active_Projects\260206_PAE_2026\Blends\animations\
C:\Users\Nathan\AppData\Local\Temp\
A:\1 Amazon_Active_Projects\260206_PAE_2026\Assets\Blends\
C:\Users\Nathan\Downloads\ C:\Users\Nathan\Downloads\
\\nas\amazon\2. ASSETS\Media\Motion Graphics\Ae\profiles (for speech bubble)\ C:\Users\Nathan\Desktop\260217_Jarvis-Defense\
T:\1 BlenderAssets\Amazon\Char\Cartoon3\ C:\Users\Nathan\Desktop\260217_Jarvis-Defense\Blends\animations\
A:\1 Amazon_Active_Projects\1 BlenderAssets\Amazon\Nodes\ C:\Users\Nathan\AppData\Local\Temp\
A:\1 Amazon_Active_Projects\260206_PAE_2026\Assets\Blends\textures\Products_1\Black Package\ C:\Program Files\Blender Foundation\Blender 5.0\5.0\datafiles\assets\nodes\
A:\1 Amazon_Active_Projects\1 BlenderAssets\Amazon\Char\Cartoon2\AS\ C:\Users\Nathan\Desktop\260217_Jarvis-Defense\Assets\Blends\Char\
A:\1 Amazon_Active_Projects\1 BlenderAssets\Amazon\Char\Cartoon3\ C:\Users\Nathan\Desktop\260217_Jarvis-Defense\Assets\Blends\
C:\Users\Nathan\Desktop\260217_Jarvis-Defense\Assets\Blends\Vehicles\
C:\Users\Nathan\Desktop\260217_Jarvis-Defense\Assets\
C:\Users\Nathan\Desktop\260217_Jarvis-Defense\Assets\Blends\Props\
+1 -768
View File
@@ -6,780 +6,13 @@
"git": "51a41a19" "git": "51a41a19"
}, },
"shared_storage": { "shared_storage": {
"location": "F:\\jobs", "location": "G:\\Flamenco\\jobs",
"audience": "users", "audience": "users",
"platform": "windows", "platform": "windows",
"shaman_enabled": true "shaman_enabled": true
}, },
"job_types": { "job_types": {
"job_types": [ "job_types": [
{
"name": "BasedPlayblast",
"label": "BasedPlayblast",
"settings": [
{
"key": "frames",
"type": "string",
"description": "Frame range to playblast. Examples: '47', '1-30', '3, 5-10, 47-327'",
"eval": "f'{C.scene.frame_start}-{C.scene.frame_end}'",
"eval_info": {
"show_link_button": true,
"description": "Scene frame range"
},
"required": true
},
{
"key": "chunk_size",
"type": "int32",
"default": 5.0,
"description": "Number of frames to playblast in one Blender task",
"visible": "submission"
},
{
"key": "playblast_output_root",
"type": "string",
"default": "//",
"description": "Base directory of where playblast output is stored. Will have some job-specific parts appended to it",
"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 use in the playblast output path",
"propargs": {
"max": 32.0,
"min": 0.0
},
"required": true,
"visible": "submission"
},
{
"key": "playblast_output_path",
"type": "string",
"description": "Final file path of where playblast output will be saved",
"editable": false,
"eval": "str(Path(abspath(settings.playblast_output_root), last_n_dir_parts(settings.add_path_components), 'blast', jobname, jobname + '_######'))",
"subtype": "file_path"
},
{
"key": "resolution_percentage",
"type": "int32",
"default": 100.0,
"description": "Percentage of the render resolution to use for playblast",
"propargs": {
"max": 100.0,
"min": 1.0
},
"visible": "submission"
},
{
"key": "png_compression",
"type": "int32",
"default": 15.0,
"description": "PNG compression level (0-100, higher = smaller file)",
"propargs": {
"max": 100.0,
"min": 0.0
},
"visible": "submission"
},
{
"key": "keep_frames",
"type": "bool",
"default": false,
"description": "Keep the individual playblast frames after video creation",
"visible": "submission"
},
{
"key": "blendfile",
"type": "string",
"description": "Path of the Blend file to playblast",
"required": true,
"visible": "web"
},
{
"key": "fps",
"type": "float",
"eval": "C.scene.render.fps / C.scene.render.fps_base",
"visible": "hidden"
},
{
"key": "format",
"type": "string",
"default": "PNG",
"description": "Image format for playblast frames",
"required": true,
"visible": "web"
},
{
"key": "image_file_extension",
"type": "string",
"default": ".png",
"description": "File extension used when creating playblast images",
"required": true,
"visible": "hidden"
},
{
"key": "scene",
"type": "string",
"description": "Name of the scene to playblast.",
"eval": "C.scene.name",
"required": true,
"visible": "web"
}
],
"etag": "0cf19bc1871973734e485ebd4477e7688997cc3d",
"description": "Create a viewport preview/playblast and generate a video file"
},
{
"name": "BasedPlayblast-Optix-GPU",
"label": "BasedPlayblast OPTIX GPU",
"settings": [
{
"key": "frames",
"type": "string",
"description": "Frame range to playblast. Examples: '47', '1-30', '3, 5-10, 47-327'",
"eval": "f'{C.scene.frame_start}-{C.scene.frame_end}'",
"eval_info": {
"show_link_button": true,
"description": "Scene frame range"
},
"required": true
},
{
"key": "chunk_size",
"type": "int32",
"default": 5.0,
"description": "Number of frames to playblast in one Blender task",
"visible": "submission"
},
{
"key": "playblast_output_root",
"type": "string",
"default": "//",
"description": "Base directory of where playblast output is stored. Will have some job-specific parts appended to it",
"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 use in the playblast output path",
"propargs": {
"max": 32.0,
"min": 0.0
},
"required": true,
"visible": "submission"
},
{
"key": "playblast_output_path",
"type": "string",
"description": "Final file path of where playblast output will be saved",
"editable": false,
"eval": "str(Path(abspath(settings.playblast_output_root), last_n_dir_parts(settings.add_path_components), 'blast', jobname, jobname + '_######'))",
"subtype": "file_path"
},
{
"key": "resolution_percentage",
"type": "int32",
"default": 100.0,
"description": "Percentage of the render resolution to use for playblast",
"propargs": {
"max": 100.0,
"min": 1.0
},
"visible": "submission"
},
{
"key": "png_compression",
"type": "int32",
"default": 15.0,
"description": "PNG compression level (0-100, higher = smaller file)",
"propargs": {
"max": 100.0,
"min": 0.0
},
"visible": "submission"
},
{
"key": "keep_frames",
"type": "bool",
"default": false,
"description": "Keep the individual playblast frames after video creation",
"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 to playblast",
"required": true,
"visible": "web"
},
{
"key": "fps",
"type": "float",
"eval": "C.scene.render.fps / C.scene.render.fps_base",
"visible": "hidden"
},
{
"key": "format",
"type": "string",
"default": "PNG",
"description": "Image format for playblast frames",
"required": true,
"visible": "web"
},
{
"key": "image_file_extension",
"type": "string",
"default": ".png",
"description": "File extension used when creating playblast images",
"required": true,
"visible": "hidden"
},
{
"key": "scene",
"type": "string",
"description": "Name of the scene to playblast.",
"eval": "C.scene.name",
"required": true,
"visible": "web"
}
],
"etag": "fe38e5ae9b3e640119d0d55fc7cbf4f8f15f2982",
"description": "Create a viewport preview/playblast using Cycles OPTIX GPU rendering and generate a video file"
},
{
"name": "TalkingHeads Custom Render",
"label": "TalkingHeads Custom Render",
"settings": [
{
"key": "frames",
"type": "string",
"description": "Frame range to render. Examples: '47', '1-30', '3, 5-10, 47-327'",
"eval": "f'{C.scene.frame_start}-{C.scene.frame_end}'",
"eval_info": {
"show_link_button": true,
"description": "Scene frame range"
},
"required": true
},
{
"key": "chunk_size",
"type": "int32",
"default": 1.0,
"description": "Number of frames to render in one Blender render task",
"visible": "submission"
},
{
"key": "blendfile",
"type": "string",
"description": "Path of the Blend file to render",
"eval": "bpy.data.filepath",
"required": true,
"visible": "web"
},
{
"key": "render_output_root",
"type": "string",
"description": "Base path where renders are stored, typically the project's Renders folder. If empty, derived automatically.",
"eval": "__import__('os').path.normpath(__import__('os').path.join(((__import__('re').search(r'^(.*?)[\\/][Bb]lends[\\/]', bpy.data.filepath.replace('\\\\','/')) and __import__('re').search(r'^(.*?)[\\/][Bb]lends[\\/]', bpy.data.filepath.replace('\\\\','/')).group(1)) or __import__('os').path.dirname(bpy.data.filepath)), 'Renders'))",
"eval_info": {
"show_link_button": true,
"description": "Auto-detect the project's Renders folder"
},
"required": false,
"subtype": "dir_path",
"visible": "submission"
},
{
"key": "use_submodule",
"type": "bool",
"default": false,
"description": "Include a submodule folder under Renders. Turn off to omit submodule entirely.",
"label": "Use Submodule",
"required": false,
"visible": "submission"
},
{
"key": "submodule",
"type": "string",
"description": "Optional submodule under Renders (e.g. 'Waterspider B'). If empty, omitted.",
"eval": "(__import__('os').path.basename(__import__('os').path.dirname(bpy.data.filepath)) if settings.use_submodule else '')",
"eval_info": {
"show_link_button": true,
"description": "Auto-fill with the current .blend file's parent folder"
},
"required": false,
"visible": "submission"
},
{
"key": "render_output_path",
"type": "string",
"description": "Final file path of where render output will be saved",
"editable": false,
"eval": "((lambda Path, abspath, os_path, settings_obj, blend: 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 ''))) / blend / (blend + '_######')))(__import__('pathlib').Path, __import__('os').path.abspath, __import__('os').path, settings, __import__('os').path.splitext(__import__('os').path.basename(bpy.data.filepath))[0]))",
"subtype": "file_path"
},
{
"key": "fps",
"type": "float",
"eval": "C.scene.render.fps / C.scene.render.fps_base",
"visible": "hidden"
},
{
"key": "format",
"type": "string",
"eval": "C.scene.render.image_settings.file_format",
"required": true,
"visible": "web"
},
{
"key": "image_file_extension",
"type": "string",
"description": "File extension used when rendering images",
"eval": "C.scene.render.file_extension",
"required": true,
"visible": "hidden"
},
{
"key": "has_previews",
"type": "bool",
"description": "Whether Blender will render preview images.",
"eval": "C.scene.render.image_settings.use_preview",
"required": false,
"visible": "hidden"
},
{
"key": "scene",
"type": "string",
"description": "Name of the scene to render.",
"eval": "C.scene.name",
"required": true,
"visible": "web"
}
],
"etag": "fc0881e64631b9a8e366260aad5003d7b16e79c0",
"description": "Render a sequence of frames, and create a preview video file"
},
{
"name": "TalkingHeads cycles-optix-gpu",
"label": "TalkingHeads Cycles OPTIX GPU",
"settings": [
{
"key": "frames",
"type": "string",
"description": "Frame range to render. Examples: '47', '1-30', '3, 5-10, 47-327'",
"eval": "f'{C.scene.frame_start}-{C.scene.frame_end}'",
"eval_info": {
"show_link_button": true,
"description": "Scene frame range"
},
"required": true
},
{
"key": "chunk_size",
"type": "int32",
"default": 1.0,
"description": "Number of frames to render in one Blender render task",
"visible": "submission"
},
{
"key": "render_output_root",
"type": "string",
"description": "Base path where renders are stored, typically the project's Renders folder. If empty, derived automatically.",
"eval": "__import__('os').path.normpath(__import__('os').path.join(((__import__('re').search(r'^(.*?)[\\/][Bb]lends[\\/]', bpy.data.filepath.replace('\\\\','/')) and __import__('re').search(r'^(.*?)[\\/][Bb]lends[\\/]', bpy.data.filepath.replace('\\\\','/')).group(1)) or __import__('os').path.dirname(bpy.data.filepath)), 'Renders'))",
"eval_info": {
"show_link_button": true,
"description": "Auto-detect the project's Renders folder"
},
"required": false,
"subtype": "dir_path",
"visible": "submission"
},
{
"key": "use_submodule",
"type": "bool",
"default": false,
"description": "Include a submodule folder under Renders. Turn off to omit submodule entirely.",
"label": "Use Submodule",
"required": false,
"visible": "submission"
},
{
"key": "submodule",
"type": "string",
"description": "Optional submodule under Renders (e.g. 'Waterspider B'). If empty, omitted.",
"eval": "(__import__('os').path.basename(__import__('os').path.dirname(bpy.data.filepath)) if settings.use_submodule else '')",
"eval_info": {
"show_link_button": true,
"description": "Auto-fill with the current .blend file's parent folder"
},
"required": false,
"visible": "submission"
},
{
"key": "render_output_path",
"type": "string",
"description": "Final file path of where render output will be saved",
"editable": false,
"eval": "((lambda Path, abspath, os_path, settings_obj, blend: 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 ''))) / blend / (blend + '_######')))(__import__('pathlib').Path, __import__('os').path.abspath, __import__('os').path, settings, __import__('os').path.splitext(__import__('os').path.basename(bpy.data.filepath))[0]))",
"subtype": "file_path"
},
{
"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 to render",
"eval": "bpy.data.filepath",
"required": true,
"visible": "web"
},
{
"key": "fps",
"type": "float",
"eval": "C.scene.render.fps / C.scene.render.fps_base",
"visible": "hidden"
},
{
"key": "format",
"type": "string",
"eval": "C.scene.render.image_settings.file_format",
"required": true,
"visible": "web"
},
{
"key": "image_file_extension",
"type": "string",
"description": "File extension used when rendering images",
"eval": "C.scene.render.file_extension",
"required": true,
"visible": "hidden"
},
{
"key": "has_previews",
"type": "bool",
"description": "Whether Blender will render preview images.",
"eval": "C.scene.render.image_settings.use_preview",
"required": false,
"visible": "hidden"
}
],
"etag": "bbbc0874b45e8f3715c0cf727938604cba6c25cf",
"description": "OPTIX GPU rendering + extra checkboxes for some experimental features + extra CLI args for Blender"
},
{
"name": "TalkingHeads cycles-optix-gpu-greg-inspect",
"label": "TalkingHeads Cycles OPTIX GPU",
"settings": [
{
"key": "frames",
"type": "string",
"description": "Frame range to render. Examples: '47', '1-30', '3, 5-10, 47-327'",
"eval": "f'{C.scene.frame_start}-{C.scene.frame_end}'",
"eval_info": {
"show_link_button": true,
"description": "Scene frame range"
},
"required": true
},
{
"key": "chunk_size",
"type": "int32",
"default": 1.0,
"description": "Number of frames to render in one Blender render task",
"visible": "submission"
},
{
"key": "render_output_root",
"type": "string",
"description": "Base path where renders are stored, typically the project's Renders folder. If empty, derived automatically.",
"eval": "__import__('os').path.normpath(__import__('os').path.join(((__import__('re').search(r'^(.*?)[\\/][Bb]lends[\\/]', bpy.data.filepath.replace('\\\\','/')) and __import__('re').search(r'^(.*?)[\\/][Bb]lends[\\/]', bpy.data.filepath.replace('\\\\','/')).group(1)) or __import__('os').path.dirname(bpy.data.filepath)), 'Renders'))",
"eval_info": {
"show_link_button": true,
"description": "Auto-detect the project's Renders folder"
},
"required": false,
"subtype": "dir_path",
"visible": "submission"
},
{
"key": "use_submodule",
"type": "bool",
"default": false,
"description": "Include a submodule folder under Renders. Turn off to omit submodule entirely.",
"label": "Use Submodule",
"required": false,
"visible": "submission"
},
{
"key": "submodule",
"type": "string",
"description": "Optional submodule under Renders (e.g. 'Waterspider B'). If empty, omitted.",
"eval": "(__import__('os').path.basename(__import__('os').path.dirname(bpy.data.filepath)) if settings.use_submodule else '')",
"eval_info": {
"show_link_button": true,
"description": "Auto-fill with the current .blend file's parent folder"
},
"required": false,
"visible": "submission"
},
{
"key": "render_output_path",
"type": "string",
"description": "Final file path of where render output will be saved",
"editable": false,
"eval": "((lambda Path, abspath, os_path, settings_obj, blend: 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 ''))) / blend / (blend + '_######')))(__import__('pathlib').Path, __import__('os').path.abspath, __import__('os').path, settings, __import__('os').path.splitext(__import__('os').path.basename(bpy.data.filepath))[0]))",
"subtype": "file_path"
},
{
"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 to render",
"eval": "bpy.data.filepath",
"required": true,
"visible": "web"
},
{
"key": "fps",
"type": "float",
"eval": "C.scene.render.fps / C.scene.render.fps_base",
"visible": "hidden"
},
{
"key": "format",
"type": "string",
"eval": "C.scene.render.image_settings.file_format",
"required": true,
"visible": "web"
},
{
"key": "image_file_extension",
"type": "string",
"description": "File extension used when rendering images",
"eval": "C.scene.render.file_extension",
"required": true,
"visible": "hidden"
},
{
"key": "has_previews",
"type": "bool",
"description": "Whether Blender will render preview images.",
"eval": "C.scene.render.image_settings.use_preview",
"required": false,
"visible": "hidden"
}
],
"etag": "1ac1baa75e355de50b11b56dde8118200d2a74b6",
"description": "OPTIX GPU rendering + extra checkboxes for some experimental features + extra CLI args for Blender"
},
{
"name": "cycles-optix-gpu",
"label": "Cycles OPTIX GPU",
"settings": [
{
"key": "frames",
"type": "string",
"description": "Frame range to render. Examples: '47', '1-30', '3, 5-10, 47-327'",
"eval": "f'{C.scene.frame_start}-{C.scene.frame_end}'",
"eval_info": {
"show_link_button": true,
"description": "Scene frame range"
},
"required": true
},
{
"key": "chunk_size",
"type": "int32",
"default": 1.0,
"description": "Number of frames to render in one Blender render task",
"visible": "submission"
},
{
"key": "render_output_root",
"type": "string",
"description": "Base directory of where render output is stored. Will have some job-specific parts appended to it",
"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 use in the render output path",
"propargs": {
"max": 32.0,
"min": 0.0
},
"required": true,
"visible": "submission"
},
{
"key": "render_output_path",
"type": "string",
"description": "Final file path of where render output will be saved",
"editable": false,
"eval": "str(Path(abspath(settings.render_output_root), last_n_dir_parts(settings.add_path_components), jobname, '{timestamp}', '######'))",
"subtype": "file_path"
},
{
"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 to render",
"required": true,
"visible": "web"
},
{
"key": "fps",
"type": "float",
"eval": "C.scene.render.fps / C.scene.render.fps_base",
"visible": "hidden"
},
{
"key": "format",
"type": "string",
"eval": "C.scene.render.image_settings.file_format",
"required": true,
"visible": "web"
},
{
"key": "image_file_extension",
"type": "string",
"description": "File extension used when rendering images",
"eval": "C.scene.render.file_extension",
"required": true,
"visible": "hidden"
},
{
"key": "has_previews",
"type": "bool",
"description": "Whether Blender will render preview images.",
"eval": "C.scene.render.image_settings.use_preview",
"required": false,
"visible": "hidden"
}
],
"etag": "ebed5e28846668474299cd2a8b55fb47b69b54d7",
"description": "OPTIX GPU rendering + extra checkboxes for some experimental features + extra CLI args for Blender"
},
{ {
"name": "echo-sleep-test", "name": "echo-sleep-test",
"label": "Echo Sleep Test", "label": "Echo Sleep Test",
+3
View File
@@ -12,3 +12,6 @@
{NVIDIA Corporation/NVIDIA GeForce RTX 4080 SUPER/PCIe/SSE2/4.6.0 NVIDIA 581.57}=SUPPORTED {NVIDIA Corporation/NVIDIA GeForce RTX 4080 SUPER/PCIe/SSE2/4.6.0 NVIDIA 581.57}=SUPPORTED
{NVIDIA Corporation/NVIDIA GeForce RTX 4080 SUPER/NVIDIA 591.44}=SUPPORTED {NVIDIA Corporation/NVIDIA GeForce RTX 4080 SUPER/NVIDIA 591.44}=SUPPORTED
{NVIDIA Corporation/NVIDIA GeForce RTX 4080 SUPER/NVIDIA 591.74}=SUPPORTED {NVIDIA Corporation/NVIDIA GeForce RTX 4080 SUPER/NVIDIA 591.74}=SUPPORTED
{NVIDIA Corporation/NVIDIA GeForce RTX 3090/NVIDIA 591.86}=SUPPORTED
{NVIDIA Corporation/NVIDIA GeForce RTX 3090/NVIDIA 595.71}=SUPPORTED
{NVIDIA Corporation/NVIDIA GeForce RTX 3090/NVIDIA 595.79}=SUPPORTED
+30 -30
View File
@@ -1,30 +1,30 @@
T:\260206_PAE_2026\Blends\animations\PAE_animation 8G.blend C:\Users\Nathan\Downloads\Shot5c\Shot5c.blend
A:\1 Amazon_Active_Projects\260206_PAE_2026\Blends\animations\PAE_animation 8G.blend C:\Users\Nathan\Desktop\260217_Jarvis-Defense\Blends\animations\Shot_Q.blend
A:\1 Amazon_Active_Projects\260206_PAE_2026\Blends\animations\PAE_Animation 3C.blend D:\NSFWAssets\char\Scrag_Boy\SquirrelFucker_v1.2.blend
C:\Users\Nathan\AppData\Local\Temp\2026-02-16_16-53_PAE_Animation 3C.blend C:\Users\Nathan\Downloads\Shot_4_g.blend
A:\1 Amazon_Active_Projects\260206_PAE_2026\Blends\animations\PAE_animation 8G_alt.blend C:\Users\Nathan\Downloads\Shot_4_e.blend
C:\Users\Nathan\Downloads\PAE_animation 8F.blend C:\Users\Nathan\Downloads\Shot_4_d.blend
A:\1 Amazon_Active_Projects\260206_PAE_2026\Blends\animations\PAE_animation 8F.blend C:\Users\Nathan\Downloads\Shot_4_b.blend
A:\1 Amazon_Active_Projects\260206_PAE_2026\Blends\animations\PAE_Animation 2B.blend C:\Users\Nathan\Downloads\Shot_4_f.blend
A:\1 Amazon_Active_Projects\260206_PAE_2026\Blends\animations\PAE_Animation 9b.blend C:\Users\Nathan\Downloads\Shot_4_a.blend
C:\Users\Nathan\Downloads\PAE_Animation 9b.blend C:\Users\Nathan\Downloads\Shot_4.blend
A:\1 Amazon_Active_Projects\1 BlenderAssets\Amazon\Char\Cartoon3\Koto_v1.2.blend C:\Users\Nathan\Desktop\260217_Jarvis-Defense\Blends\animations\Shot_4.blend
A:\1 Amazon_Active_Projects\260206_PAE_2026\Blends\animations\PAE_Animation 9a.blend C:\Users\Nathan\Desktop\260217_Jarvis-Defense\Blends\animations\Jarvis-Defense_L.blend
C:\Users\Nathan\Downloads\PAE_Animation 1D.blend C:\Users\Nathan\Desktop\260217_Jarvis-Defense\Assets\Blends\Vehicles\Ford 250.blend
A:\1 Amazon_Active_Projects\260206_PAE_2026\Blends\animations\PAE_Animation 1D.blend C:\Users\Nathan\Desktop\260217_Jarvis-Defense\Blends\animations\shot 2 avoid.blend
C:\Users\Nathan\AppData\Local\Temp\2026-02-16_13-24_PAE_Animation 9b.blend C:\Users\Nathan\AppData\Local\Temp\2026-03-07_12-15_Shot_4.blend
C:\Users\Nathan\Downloads\PAE_Animation 11D.blend C:\Users\Nathan\Desktop\260217_Jarvis-Defense\Blends\animations\erratic_movement_powerwalk.blend
C:\Users\Nathan\Downloads\PAE_Animation 11C.blend C:\Users\Nathan\Desktop\260217_Jarvis-Defense\Assets\Blends\lipsync.blend
A:\1 Amazon_Active_Projects\260206_Dock_Unified\Blends\animations\Waterspider B\WS-B_1G.blend C:\Users\Nathan\Desktop\260217_Jarvis-Defense\Blends\animations\welcome_to_SLC.blend
A:\1 Amazon_Active_Projects\260206_PAE_2026\Blends\animations\PAE_Animation 10b.blend C:\Users\Nathan\Desktop\260217_Jarvis-Defense\Blends\animations\welcome_to_staten_island.blend
A:\1 Amazon_Active_Projects\260206_PAE_2026\Blends\animations\PAE_animation 7A.blend C:\Users\Nathan\Desktop\260217_Jarvis-Defense\Assets\Blends\Char\Priest_v2.0.blend
C:\Users\Nathan\Downloads\PAE_Animation 5C.blend C:\Users\Nathan\Desktop\260217_Jarvis-Defense\Assets\Blends\Char\Russell_v3.0.blend
C:\Users\Nathan\Downloads\PAE_Animation 5B.blend C:\Users\Nathan\Desktop\260217_Jarvis-Defense\Assets\Blends\Char\Kayla_Russell_v4_gw.blend
C:\Users\Nathan\Downloads\PAE_Animation 5A.blend C:\Users\Nathan\Desktop\260217_Jarvis-Defense\Blends\animations\Shot_1.blend
A:\1 Amazon_Active_Projects\260206_PAE_2026\Assets\Blends\PAE_area_dock.blend C:\Users\Nathan\Desktop\260217_Jarvis-Defense\Assets\Blends\3075 Decade Dr.blend
A:\1 Amazon_Active_Projects\260206_PAE_2026\Blends\animations\PAE_Animation 5C.blend C:\Users\Nathan\Desktop\260217_Jarvis-Defense\Blends\animations\mocap_test_driver_v1.blend
A:\1 Amazon_Active_Projects\260206_PAE_2026\Blends\animations\PAE_Animation 5B.blend C:\Users\Nathan\Desktop\260217_Jarvis-Defense\Assets\Blends\Char\Chase_v2.0.blend
A:\1 Amazon_Active_Projects\260206_PAE_2026\Blends\animations\PAE_Animation 5A.blend C:\Users\Nathan\Desktop\260217_Jarvis-Defense\Assets\Blends\Char\Russell_v2.0.blend
A:\1 Amazon_Active_Projects\260206_PAE_2026\Blends\animations\PAE_Animation 4B.blend C:\Users\Nathan\Desktop\260217_Jarvis-Defense\Assets\Blends\Vehicles\Pop Up Camper.blend
A:\1 Amazon_Active_Projects\260206_PAE_2026\Blends\animations\PAE_Animation 3E.blend C:\Users\Nathan\Desktop\260217_Jarvis-Defense\Assets\Blends\Pop Up Camper.blend
A:\1 Amazon_Active_Projects\260206_PAE_2026\Blends\animations\PAE_Animation 2A.blend C:\Users\Nathan\Desktop\260217_Jarvis-Defense\Assets\Blends\Props\Ruger Holster.blend
Binary file not shown.
BIN
View File
Binary file not shown.
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
@@ -1,9 +1,9 @@
schema_version = "1.0.0" schema_version = "1.0.0"
id = "add_camera_rigs" id = "add_camera_rigs"
name = "Add Camera Rigs" name = "Add Camera Rigs"
version = "1.8.1" version = "1.8.2"
tagline = "Adds a Camera Rig with UI" tagline = "Adds a Camera Rig with UI"
maintainer = "Community" maintainer = "Wayne Dixon <waylowinternet@gmail.com>"
type = "add-on" type = "add-on"
tags = ["Camera"] tags = ["Camera"]
blender_version_min = "4.2.0" blender_version_min = "4.2.0"
@@ -20,6 +20,15 @@ from .create_widgets import (
) )
def lock_mch_transforms(rig):
for bone in rig.pose.bones:
if bone.name.startswith("MCH-"):
bone.lock_location = (True, True, True)
bone.lock_rotation = (True, True, True)
bone.lock_rotation_w = True
bone.lock_scale = (True, True, True)
def create_prop_driver(rig, cam, prop_from, prop_to): def create_prop_driver(rig, cam, prop_from, prop_to):
"""Create driver to a property on the rig""" """Create driver to a property on the rig"""
driver = cam.data.driver_add(prop_to) driver = cam.data.driver_add(prop_to)
@@ -52,7 +61,7 @@ def create_dolly_bones(rig):
root.color.palette = 'THEME02' root.color.palette = 'THEME02'
collection_controls.assign(root) collection_controls.assign(root)
ctrl_aim_child = bones.new("MCH-Aim_shape_rotation") ctrl_aim_child = bones.new("MCH-Aim_widget")
ctrl_aim_child.head = (0.0, 10.0, 1.7) ctrl_aim_child.head = (0.0, 10.0, 1.7)
ctrl_aim_child.tail = (0.0, 11.0, 1.7) ctrl_aim_child.tail = (0.0, 11.0, 1.7)
collection_mch.assign(ctrl_aim_child) collection_mch.assign(ctrl_aim_child)
@@ -107,7 +116,7 @@ def create_crane_bones(rig):
root.color.palette = 'THEME02' root.color.palette = 'THEME02'
collection_controls.assign(root) collection_controls.assign(root)
ctrl_aim_child = bones.new("MCH-Aim_shape_rotation") ctrl_aim_child = bones.new("MCH-Aim_widget")
ctrl_aim_child.head = (0.0, 10.0, 1.7) ctrl_aim_child.head = (0.0, 10.0, 1.7)
ctrl_aim_child.tail = (0.0, 11.0, 1.7) ctrl_aim_child.tail = (0.0, 11.0, 1.7)
collection_mch.assign(ctrl_aim_child) collection_mch.assign(ctrl_aim_child)
@@ -147,7 +156,6 @@ def create_crane_bones(rig):
height.color.palette = 'THEME07' height.color.palette = 'THEME07'
collection_controls.assign(height) collection_controls.assign(height)
# Setup hierarchy # Setup hierarchy
ctrl.parent = arm ctrl.parent = arm
ctrl_offset.parent = ctrl ctrl_offset.parent = ctrl
@@ -187,13 +195,15 @@ def setup_3d_rig(rig, cam):
pb = pose_bones['Camera'] pb = pose_bones['Camera']
pb["lens"] = 50.0 pb["lens"] = 50.0
ui_data = pb.id_properties_ui("lens") ui_data = pb.id_properties_ui("lens")
ui_data.update(min=1.0, max=1000000.0, soft_max=5000.0, default=50.0, subtype="DISTANCE_CAMERA") ui_data.update(min=1.0, max=1000000.0, soft_max=5000.0,
default=50.0, subtype="DISTANCE_CAMERA")
# lens offset property # lens offset property
pb = pose_bones['Camera'] pb = pose_bones['Camera']
pb["lens_offset"] = 0.0 pb["lens_offset"] = 0.0
ui_data = pb.id_properties_ui("lens_offset") ui_data = pb.id_properties_ui("lens_offset")
ui_data.update(min=-1000000.0, max=1000000.0, soft_max = 5000.0, soft_min = -5000.0,default=0.0) ui_data.update(min=-1000000.0, max=1000000.0,
soft_max=5000.0, soft_min=-5000.0, default=0.0)
# Build the widgets # Build the widgets
root_widget = create_root_widget("Camera_Root") root_widget = create_root_widget("Camera_Root")
@@ -207,11 +217,13 @@ def setup_3d_rig(rig, cam):
pose_bones["Camera"].custom_shape = camera_widget pose_bones["Camera"].custom_shape = camera_widget
pose_bones["Camera_Offset"].custom_shape = camera_offset_widget pose_bones["Camera_Offset"].custom_shape = camera_offset_widget
#
# Set the "Override Transform" field to the mechanism position # Set the "Override Transform" field to the mechanism position
pose_bones["Aim"].custom_shape_transform = pose_bones["MCH-Aim_shape_rotation"] pose_bones["Aim"].custom_shape_transform = pose_bones["MCH-Aim_widget"]
# Add constraints to bones # Add constraints to bones
con = pose_bones['MCH-Aim_shape_rotation'].constraints.new('COPY_ROTATION') con = pose_bones['MCH-Aim_widget'].constraints.new('COPY_ROTATION')
con.target = rig con.target = rig
con.subtarget = "Camera" con.subtarget = "Camera"
@@ -249,6 +261,9 @@ def setup_3d_rig(rig, cam):
var.targets[0].transform_type = 'SCALE_AVG' var.targets[0].transform_type = 'SCALE_AVG'
var.targets[0].bone_target = 'Root' var.targets[0].bone_target = 'Root'
# lock all transforms on MCH bones
lock_mch_transforms(rig)
def create_2d_bones(rig, cam): def create_2d_bones(rig, cam):
"""Create bones for the 2D camera rig""" """Create bones for the 2D camera rig"""
@@ -476,8 +491,10 @@ def create_2d_bones(rig, cam):
pose_bones["Left_Corner"].lock_rotation = (True,) * 3 pose_bones["Left_Corner"].lock_rotation = (True,) * 3
pose_bones["Right_Corner"].lock_rotation = (True,) * 3 pose_bones["Right_Corner"].lock_rotation = (True,) * 3
# Camera settings # lock all transforms on MCH bones
lock_mch_transforms(rig)
# Camera settings
cam.data.sensor_fit = "HORIZONTAL" # Avoids distortion in portrait format cam.data.sensor_fit = "HORIZONTAL" # Avoids distortion in portrait format
cam.data.dof.focus_object = rig cam.data.dof.focus_object = rig
cam.data.dof.focus_subtarget = "DOF" cam.data.dof.focus_subtarget = "DOF"
@@ -539,7 +556,8 @@ def create_2d_bones(rig, cam):
# Orthographic scale driver # Orthographic scale driver
driver = cam.data.driver_add("ortho_scale").driver driver = cam.data.driver_add("ortho_scale").driver
driver.expression = "abs({distance_x} - (left_x - right_x))".format(distance_x=corner_distance_x) driver.expression = "abs({distance_x} - (left_x - right_x))".format(
distance_x=corner_distance_x)
for corner in ("left", "right"): for corner in ("left", "right"):
var = driver.variables.new() var = driver.variables.new()
@@ -694,6 +712,11 @@ def build_camera_rig(context, mode):
rig.data.display_type = 'BBONE' rig.data.display_type = 'BBONE'
# Change display to wire for object # Change display to wire for object
rig.display_type = 'WIRE' rig.display_type = 'WIRE'
# Set the widgets to be 1.5 width (easier to see)
if bpy.app.version >= (4, 2, 0):
for bone in rig.pose.bones:
if bone.custom_shape:
bone.custom_shape_wire_width = 1.5
# Lock camera transforms # Lock camera transforms
cam.lock_location = (True,) * 3 cam.lock_location = (True,) * 3
@@ -27,6 +27,7 @@ def get_rig_and_cam(obj):
return obj.parent, obj return obj.parent, obj
return None, None return None, None
def calculate_aim_distance(obj): def calculate_aim_distance(obj):
'''This will return the distance of the camera and the aim bone at the time it is called.''' '''This will return the distance of the camera and the aim bone at the time it is called.'''
camera_bone = obj.pose.bones['Camera'].matrix camera_bone = obj.pose.bones['Camera'].matrix
@@ -53,10 +54,12 @@ def poll_perspective(cls, context):
rig, cam = get_rig_and_cam(context.active_object) rig, cam = get_rig_and_cam(context.active_object)
if cam.data.type == 'ORTHO': if cam.data.type == 'ORTHO':
cls.poll_message_set("This operator is not supported for orthographic cameras.") cls.poll_message_set(
"This operator is not supported for orthographic cameras.")
return False return False
if rig["rig_id"].lower() == '2d_rig': if rig["rig_id"].lower() == '2d_rig':
cls.poll_message_set("This operator is not supported for 2D camera rigs.") cls.poll_message_set(
"This operator is not supported for 2D camera rigs.")
return False return False
return True return True
@@ -73,7 +76,8 @@ class ADD_CAMERA_RIGS_OT_set_scene_camera(Operator):
_rig, cam = get_rig_and_cam(context.active_object) _rig, cam = get_rig_and_cam(context.active_object)
if cam is context.scene.camera: if cam is context.scene.camera:
cls.poll_message_set("Selected camera is already the scene camera.") cls.poll_message_set(
"Selected camera is already the scene camera.")
return False return False
return True return True
@@ -120,7 +124,7 @@ class ADD_CAMERA_RIGS_OT_set_dof_bone(Operator):
cam.data.dof.focus_object = rig cam.data.dof.focus_object = rig
cam.data.dof.focus_subtarget = ( cam.data.dof.focus_subtarget = (
'DOF' if rig["rig_id"].lower() == '2d_rig' 'DOF' if rig["rig_id"].lower() == '2d_rig'
else 'MCH-Aim_shape_rotation') else 'Aim')
return {'FINISHED'} return {'FINISHED'}
@@ -169,7 +173,7 @@ class ADD_CAMERA_RIGS_OT_remove_dolly_zoom(Operator):
# reset the offset back to zero # reset the offset back to zero
rig.pose.bones["Camera"]["lens_offset"] = 0.0 rig.pose.bones["Camera"]["lens_offset"] = 0.0
#set the bone color to default # set the bone color to default
rig.pose.bones["Aim"].color.palette = 'DEFAULT' rig.pose.bones["Aim"].color.palette = 'DEFAULT'
return {'FINISHED'} return {'FINISHED'}
@@ -191,16 +195,20 @@ class ADD_CAMERA_RIGS_OT_shift_to_pivot(Operator):
aim_loc = rig.pose.bones["Aim"].matrix_basis.to_translation() aim_loc = rig.pose.bones["Aim"].matrix_basis.to_translation()
# create a transform matrix for the z loc of the aim bone # create a transform matrix for the z loc of the aim bone
mat_trans = mathutils.Matrix.Translation( [0, 0, aim_loc[2] + 1.7 ]) # Hardcoded height of rest position mat_trans = mathutils.Matrix.Translation(
[0, 0, aim_loc[2] + 1.7]) # Hardcoded height of rest position
# repostion the aim bone so it's above the root (using the original z value) # repostion the aim bone so it's above the root (using the original z value)
rig.pose.bones["Aim"].matrix = rig.pose.bones["Root"].matrix @ mat_trans rig.pose.bones["Aim"].matrix = rig.pose.bones["Root"].matrix @ mat_trans
# offset the camera matrix relative to the new aim position # offset the camera matrix relative to the new aim position
camera_offset_vector = (rig.pose.bones["Aim"].matrix_basis.to_translation() ) - aim_loc camera_offset_vector = (
camera_offset_matrix = mathutils.Matrix.Translation(camera_offset_vector) rig.pose.bones["Aim"].matrix_basis.to_translation()) - aim_loc
camera_offset_matrix = mathutils.Matrix.Translation(
camera_offset_vector)
rig.pose.bones["Camera"].matrix = rig.pose.bones["Camera"].matrix @ camera_offset_matrix rig.pose.bones["Camera"].matrix = rig.pose.bones["Camera"].matrix @ camera_offset_matrix
return {'FINISHED'} return {'FINISHED'}
class ADD_CAMERA_RIGS_OT_swap_lens(Operator): class ADD_CAMERA_RIGS_OT_swap_lens(Operator):
bl_idname = "add_camera_rigs.swap_lens" bl_idname = "add_camera_rigs.swap_lens"
bl_label = "Swap Lens" bl_label = "Swap Lens"
@@ -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.16.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.16.0/Rainys_Bulk_Scene_Tools.v0.16.0.zip",
"archive_size": 80251,
"archive_hash": "sha256:3e6fafe11caa39e48b94288c12b2a88e521c928955a854ffdd1bd0936e6bc70a"
},
{
"schema_version": "1.0.0",
"id": "atomic_data_manager",
"name": "Atomic Data Manager",
"tagline": "Smart cleanup and inspection of Blender data-blocks",
"version": "2.5.0",
"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.5.0/Atomic_Data_Manager.v2.5.0.zip",
"archive_size": 114674,
"archive_hash": "sha256:4b4834ed3910a428d4cb01f1891247ad80089b6c5324fc27c6862b09e81ff1c1"
},
{
"schema_version": "1.0.0",
"id": "sheepit_project_submitter",
"name": "SheepIt Project Submitter",
"tagline": "Submit projects to SheepIt render farm",
"version": "0.0.7",
"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.7/SheepIt_Project_Submitter.v0.0.7.zip",
"archive_size": 47250,
"archive_hash": "sha256:cb8dee48c45cc51dd8237981f4ab96d97d476b547c8c640606e9bbfd0390a055"
}
]
}
@@ -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.16.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.16.0/Rainys_Bulk_Scene_Tools.v0.16.0.zip",
"archive_size": 80251,
"archive_hash": "sha256:3e6fafe11caa39e48b94288c12b2a88e521c928955a854ffdd1bd0936e6bc70a"
},
{
"schema_version": "1.0.0",
"id": "atomic_data_manager",
"name": "Atomic Data Manager",
"tagline": "Smart cleanup and inspection of Blender data-blocks",
"version": "2.5.0",
"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.5.0/Atomic_Data_Manager.v2.5.0.zip",
"archive_size": 114674,
"archive_hash": "sha256:4b4834ed3910a428d4cb01f1891247ad80089b6c5324fc27c6862b09e81ff1c1"
},
{
"schema_version": "1.0.0",
"id": "sheepit_project_submitter",
"name": "SheepIt Project Submitter",
"tagline": "Submit projects to SheepIt render farm",
"version": "0.0.7",
"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.7/SheepIt_Project_Submitter.v0.0.7.zip",
"archive_size": 47250,
"archive_hash": "sha256:cb8dee48c45cc51dd8237981f4ab96d97d476b547c8c640606e9bbfd0390a055"
}
]
}
@@ -0,0 +1,78 @@
{
"version": "v1",
"blocklist": [],
"data": [
{
"schema_version": "1.0.0",
"id": "basedplayblast",
"name": "BasedPlayblast",
"tagline": "Easily create playblasts from Blender and Flamenco",
"version": "2.6.2",
"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.2/BasedPlayblast.v2.6.2.zip",
"archive_size": 48968,
"archive_hash": "sha256:c359a24fccb10b9d8df2941b0d75435eb0f7ac89db61836edb6d993b86354952"
},
{
"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.15.1",
"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.15.1/Rainys_Bulk_Scene_Tools.v0.15.1.zip",
"archive_size": 81044,
"archive_hash": "sha256:a72f7dbf7c35fda94a7b67df79ef131391e0fe2ac4f416703b07ef59afd7235b"
},
{
"schema_version": "1.0.0",
"id": "atomic_data_manager",
"name": "Atomic Data Manager",
"tagline": "Smart cleanup and inspection of Blender data-blocks",
"version": "2.4.1",
"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.4.1/Atomic_Data_Manager.v2.4.1.zip",
"archive_size": 108842,
"archive_hash": "sha256:4086ada3e9e8c852fd02d455f11f2f20fd19ca68acd10b101ab3aa0fae2be210"
}
]
}
@@ -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.2",
"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.2/BasedPlayblast.v2.6.2.zip",
"archive_size": 48968,
"archive_hash": "sha256:c359a24fccb10b9d8df2941b0d75435eb0f7ac89db61836edb6d993b86354952"
},
{
"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.15.1",
"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.15.1/Rainys_Bulk_Scene_Tools.v0.15.1.zip",
"archive_size": 81044,
"archive_hash": "sha256:a72f7dbf7c35fda94a7b67df79ef131391e0fe2ac4f416703b07ef59afd7235b"
},
{
"schema_version": "1.0.0",
"id": "atomic_data_manager",
"name": "Atomic Data Manager",
"tagline": "Smart cleanup and inspection of Blender data-blocks",
"version": "2.4.1",
"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.4.1/Atomic_Data_Manager.v2.4.1.zip",
"archive_size": 108842,
"archive_hash": "sha256:4086ada3e9e8c852fd02d455f11f2f20fd19ca68acd10b101ab3aa0fae2be210"
},
{
"schema_version": "1.0.0",
"id": "sheepit_project_submitter",
"name": "SheepIt Project Submitter",
"tagline": "Submit projects to SheepIt render farm",
"version": "0.0.2",
"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.2/SheepIt_Project_Submitter.v0.0.2.zip",
"archive_size": 64260,
"archive_hash": "sha256:0149a702a4caef68a5c4f7cb102ea51331d97446d8f1a57252f9ff3b44467ad0"
}
]
}
@@ -0,0 +1,78 @@
{
"version": "v1",
"blocklist": [],
"data": [
{
"schema_version": "1.0.0",
"id": "basedplayblast",
"name": "BasedPlayblast",
"tagline": "Easily create playblasts from Blender and Flamenco",
"version": "2.6.2",
"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.2/BasedPlayblast.v2.6.2.zip",
"archive_size": 48968,
"archive_hash": "sha256:c359a24fccb10b9d8df2941b0d75435eb0f7ac89db61836edb6d993b86354952"
},
{
"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.15.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.15.0/Rainys_Bulk_Scene_Tools.v0.15.0.zip",
"archive_size": 80698,
"archive_hash": "sha256:bbde2f4056d39f9b16072a45bc47a31c19e6beb24616101bdf9e556b99f9853c"
},
{
"schema_version": "1.0.0",
"id": "atomic_data_manager",
"name": "Atomic Data Manager",
"tagline": "Smart cleanup and inspection of Blender data-blocks",
"version": "2.3.0",
"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.3.0/Atomic_Data_Manager.v2.3.0.zip",
"archive_size": 92609,
"archive_hash": "sha256:be0304820428e461c3ecda4ab652d5c84d3df9c0548292870350ca86a9ba828c"
}
]
}
@@ -3,7 +3,7 @@ schema_version = "1.0.0"
id = "amzncharactertools" id = "amzncharactertools"
name = "AMZNCharacterTools" name = "AMZNCharacterTools"
tagline = "AMZNCharacterTools" tagline = "AMZNCharacterTools"
version = "0.10.1" version = "0.10.2"
type = "add-on" type = "add-on"
maintainer = "Nathan Lindsay" maintainer = "Nathan Lindsay"
@@ -89,7 +89,7 @@ def create_settings_bone():
pose_bone.select = True pose_bone.select = True
elif hasattr(pose_bone.bone, 'select'): elif hasattr(pose_bone.bone, 'select'):
# Blender 4.2 LTS, 4.5 LTS # Blender 4.2 LTS, 4.5 LTS
pose_bone.bone.select = True pose_bone.bone.select = True
armature_obj.data.bones.active = pose_bone.bone armature_obj.data.bones.active = pose_bone.bone
@@ -1,5 +1,5 @@
{ {
"last_check": "2026-02-12 10:20:22.908483", "last_check": "2026-03-05 13:07:15.606509",
"backup_date": "January-12-2026", "backup_date": "January-12-2026",
"update_ready": false, "update_ready": false,
"ignore": false, "ignore": false,
@@ -12,32 +12,36 @@
# along with this program. If not, see <http://www.gnu.org/licenses/>. # along with this program. If not, see <http://www.gnu.org/licenses/>.
import bpy import bpy
from bpy.props import StringProperty, BoolProperty, EnumProperty
from bpy.types import Panel, Operator, PropertyGroup
# Import local modules from .ui import CLASSES
from . import operators from .ui.properties import DynamicLinkManagerProperties
from . import ui from .ui.preferences import DynamicLinkManagerPreferences
def ensure_default_search_path(): def ensure_default_search_path():
"""Ensure there's always at least one search path""" addon = bpy.context.preferences.addons.get(__name__)
prefs = bpy.context.preferences.addons.get(__name__) if addon and len(addon.preferences.search_paths) == 0:
if prefs and len(prefs.preferences.search_paths) == 0: addon.preferences.search_paths.add().path = "//"
new_path = prefs.preferences.search_paths.add()
new_path.path = "//" # Default to relative path
def register(): def register():
operators.register() DynamicLinkManagerPreferences.bl_idname = __name__
ui.register() for cls in CLASSES:
# Ensure default search path exists bpy.utils.register_class(cls)
bpy.types.Scene.dynamic_link_manager = bpy.props.PointerProperty(type=DynamicLinkManagerProperties)
bpy.app.handlers.load_post.append(ensure_default_search_path) bpy.app.handlers.load_post.append(ensure_default_search_path)
def unregister(): def unregister():
ui.unregister()
operators.unregister()
# Remove the handler
if ensure_default_search_path in bpy.app.handlers.load_post: if ensure_default_search_path in bpy.app.handlers.load_post:
bpy.app.handlers.load_post.remove(ensure_default_search_path) bpy.app.handlers.load_post.remove(ensure_default_search_path)
del bpy.types.Scene.dynamic_link_manager
for cls in reversed(CLASSES):
try:
bpy.utils.unregister_class(cls)
except RuntimeError:
pass
if __name__ == "__main__": if __name__ == "__main__":
register() register()
@@ -2,8 +2,8 @@ schema_version = "1.0.0"
id = "dynamiclinkmanager" id = "dynamiclinkmanager"
name = "Dynamic Link Manager" name = "Dynamic Link Manager"
tagline = "Relink characters and library blends with ease" tagline = "Character migrator and linked library tools"
version = "0.0.1" version = "0.1.1"
type = "add-on" type = "add-on"
# Optional: Semantic Versioning # Optional: Semantic Versioning
@@ -1,525 +0,0 @@
import bpy
import os
from bpy.types import Operator
from bpy.props import StringProperty, BoolProperty, EnumProperty, IntProperty
class DLM_OT_replace_linked_asset(Operator):
"""Replace a linked asset with a new file"""
bl_idname = "dlm.replace_linked_asset"
bl_label = "Replace Linked Asset"
bl_options = {'REGISTER', 'UNDO'}
filepath: StringProperty(
name="File Path",
description="Path to the new asset file",
subtype='FILE_PATH'
)
def execute(self, context):
obj = context.active_object
if not obj:
self.report({'ERROR'}, "No object selected")
return {'CANCELLED'}
# Comprehensive debug info
debug_info = f"Object: {obj.name}, Type: {obj.type}"
# Check object library
if hasattr(obj, 'library'):
debug_info += f", Object has library attr: {obj.library is not None}"
if obj.library:
debug_info += f", Object library: {obj.library.filepath}"
# Check object data
if obj.data:
debug_info += f", Has data: {type(obj.data).__name__}, Name: {obj.data.name}"
# Check data library attribute
if hasattr(obj.data, 'library'):
debug_info += f", Data.library exists: {obj.data.library is not None}"
if obj.data.library:
debug_info += f", Data.library.filepath: {obj.data.library.filepath}"
# Check if data is in bpy.data collections
if obj.type == 'ARMATURE' and obj.data.name in bpy.data.armatures:
armature_data = bpy.data.armatures[obj.data.name]
debug_info += f", Found in bpy.data.armatures"
if hasattr(armature_data, 'library'):
debug_info += f", bpy.data library: {armature_data.library is not None}"
if armature_data.library:
debug_info += f", bpy.data library path: {armature_data.library.filepath}"
# Check if data is in bpy.data.objects
if obj.data.name in bpy.data.objects:
debug_info += f", Data also in bpy.data.objects"
# Check if object itself is linked
if hasattr(obj, 'library') and obj.library:
self.report({'INFO'}, f"Object '{obj.name}' is linked from: {obj.library.filepath}")
return {'FINISHED'}
# Check if object's data is linked
if obj.data and hasattr(obj.data, 'library') and obj.data.library:
self.report({'INFO'}, f"Object '{obj.name}' has linked data from: {obj.data.library.filepath}")
return {'FINISHED'}
# Check if armature data is linked through bpy.data system
if obj.type == 'ARMATURE' and obj.data and obj.data.name in bpy.data.armatures:
armature_data = bpy.data.armatures[obj.data.name]
if hasattr(armature_data, 'library') and armature_data.library:
self.report({'INFO'}, f"Armature '{obj.name}' data is linked from: {armature_data.library.filepath}")
return {'FINISHED'}
# If we get here, show debug info
self.report({'WARNING'}, debug_info)
self.report({'ERROR'}, "Selected object is not a linked asset")
return {'CANCELLED'}
def invoke(self, context, event):
context.window_manager.fileselect_add(self)
return {'RUNNING_MODAL'}
class DLM_OT_scan_linked_assets(Operator):
"""Scan scene for all linked assets"""
bl_idname = "dlm.scan_linked_assets"
bl_label = "Scan Linked Assets"
def execute(self, context):
# Clear previous results
context.scene.dynamic_link_manager.linked_libraries.clear()
# Dictionary to store library info with hierarchy
library_info = {}
# Function to check if file exists
def is_file_missing(filepath):
if not filepath:
return True
# Convert relative paths to absolute
if filepath.startswith('//'):
# This is a relative path, we can't easily check if it exists
# So we'll assume it's missing if it's relative
return True
return not os.path.exists(filepath)
# Function to get library name from path
def get_library_name(filepath):
if not filepath:
return "Unknown"
return os.path.basename(filepath)
# Function to detect indirect links by parsing .blend files safely
def get_indirect_libraries(filepath):
"""Get libraries that are linked from within a .blend file"""
# This function is no longer used with the new approach
# Indirect links are now detected when attempting to relink
return set()
# Scan all data collections for linked items
all_libraries = set()
library_info = {} # Store additional info about each library
# Check bpy.data.objects
for obj in bpy.data.objects:
if hasattr(obj, 'library') and obj.library:
all_libraries.add(obj.library.filepath)
if obj.data and hasattr(obj.data, 'library') and obj.data.library:
all_libraries.add(obj.data.library.filepath)
# Check bpy.data.armatures specifically
for armature in bpy.data.armatures:
if hasattr(armature, 'library') and armature.library:
all_libraries.add(armature.library.filepath)
# Check bpy.data.meshes
for mesh in bpy.data.meshes:
if hasattr(mesh, 'library') and mesh.library:
all_libraries.add(mesh.library.filepath)
# Check bpy.data.materials
for material in bpy.data.materials:
if hasattr(material, 'library') and material.library:
all_libraries.add(material.library.filepath)
# Check bpy.data.images
for image in bpy.data.images:
if hasattr(image, 'library') and image.library:
all_libraries.add(image.library.filepath)
# Check bpy.data.textures
for texture in bpy.data.textures:
if hasattr(texture, 'library') and texture.library:
all_libraries.add(texture.library.filepath)
# Check bpy.data.node_groups
for node_group in bpy.data.node_groups:
if hasattr(node_group, 'library') and node_group.library:
all_libraries.add(node_group.library.filepath)
# Analyze each library for indirect links
for filepath in all_libraries:
if filepath:
# Initialize with no indirect missing (will be updated during relink attempts)
library_info[filepath] = {
'indirect_libraries': set(),
'missing_indirect_count': 0,
'has_indirect_missing': False
}
# Store results in scene properties
context.scene.dynamic_link_manager.linked_assets_count = len(all_libraries)
# Create library items for the UI
for filepath in sorted(all_libraries):
if filepath:
lib_item = context.scene.dynamic_link_manager.linked_libraries.add()
lib_item.filepath = filepath
lib_item.name = get_library_name(filepath)
lib_item.is_missing = is_file_missing(filepath)
# Set indirect link information
if filepath in library_info:
info = library_info[filepath]
lib_item.has_indirect_missing = info['has_indirect_missing']
lib_item.indirect_missing_count = info['missing_indirect_count']
else:
lib_item.has_indirect_missing = False
lib_item.indirect_missing_count = 0
# Show detailed info
self.report({'INFO'}, f"Found {len(all_libraries)} unique linked library files")
if all_libraries:
for lib in sorted(all_libraries):
self.report({'INFO'}, f"Library: {lib}")
return {'FINISHED'}
class DLM_OT_fmt_style_find(Operator):
"""Find missing libraries in search folders using FMT-style approach"""
bl_idname = "dlm.fmt_style_find"
bl_label = "FMT-Style Find"
bl_options = {'REGISTER', 'UNDO'}
def execute(self, context):
prefs = context.preferences.addons.get(__package__)
if not prefs or not prefs.preferences.search_paths:
self.report({'ERROR'}, "No search paths configured. Add search paths in addon preferences.")
return {'CANCELLED'}
# Get missing libraries
missing_libs = [lib for lib in context.scene.dynamic_link_manager.linked_libraries if lib.is_missing]
if not missing_libs:
self.report({'INFO'}, "No missing libraries to find")
return {'FINISHED'}
self.report({'INFO'}, f"FMT-style search for {len(missing_libs)} missing libraries...")
# FMT-style directory scanning (exact copy of FMT logic)
files_dir_list = []
try:
for search_path in prefs.preferences.search_paths:
if search_path.path:
for dirpath, dirnames, filenames in os.walk(bpy.path.abspath(search_path.path)):
files_dir_list.append([dirpath, filenames])
except FileNotFoundError:
self.report({'ERROR'}, f"Error - Bad file path in search paths")
return {'CANCELLED'}
# FMT-style library finding
found_count = 0
for lib_item in missing_libs:
lib_filename = os.path.basename(lib_item.filepath)
for dir_info in files_dir_list:
dirpath, filenames = dir_info
# Exact filename match
if lib_filename in filenames:
new_path = os.path.join(dirpath, lib_filename)
self.report({'INFO'}, f"Found {lib_filename} at: {new_path}")
found_count += 1
break
# FMT-style reporting
if found_count > 0:
self.report({'INFO'}, f"FMT-style search complete: Found {found_count} libraries")
else:
self.report({'WARNING'}, "FMT-style search: No libraries found in search paths")
return {'FINISHED'}
def register():
bpy.utils.register_class(DLM_OT_replace_linked_asset)
bpy.utils.register_class(DLM_OT_scan_linked_assets)
class DLM_OT_open_linked_file(Operator):
"""Open the linked file in a new Blender instance"""
bl_idname = "dlm.open_linked_file"
bl_label = "Open Linked File"
bl_options = {'REGISTER'}
filepath: StringProperty(
name="File Path",
description="Path to the linked file",
default=""
)
def execute(self, context):
if not self.filepath:
self.report({'ERROR'}, "No file path specified")
return {'CANCELLED'}
# Try to open the linked file in a new Blender instance
try:
# Use Blender's built-in file browser to open the file
bpy.ops.wm.path_open(filepath=self.filepath)
self.report({'INFO'}, f"Opening linked file: {self.filepath}")
except Exception as e:
self.report({'ERROR'}, f"Failed to open linked file: {e}")
return {'CANCELLED'}
return {'FINISHED'}
class DLM_OT_add_search_path(Operator):
"""Add a new search path for missing libraries"""
bl_idname = "dlm.add_search_path"
bl_label = "Add Search Path"
bl_options = {'REGISTER'}
def execute(self, context):
prefs = context.preferences.addons.get(__package__)
if prefs:
new_path = prefs.preferences.search_paths.add()
new_path.path = "//" # Default to relative path
self.report({'INFO'}, f"Added search path: {new_path.path}")
return {'FINISHED'}
class DLM_OT_remove_search_path(Operator):
"""Remove a search path"""
bl_idname = "dlm.remove_search_path"
bl_label = "Remove Search Path"
bl_options = {'REGISTER'}
index: IntProperty(
name="Index",
description="Index of the search path to remove",
default=0
)
def execute(self, context):
prefs = context.preferences.addons.get(__package__)
if prefs and prefs.preferences.search_paths:
if 0 <= self.index < len(prefs.preferences.search_paths):
prefs.preferences.search_paths.remove(self.index)
self.report({'INFO'}, f"Removed search path at index {self.index}")
else:
self.report({'ERROR'}, f"Invalid index: {self.index}")
return {'FINISHED'}
class DLM_OT_attempt_relink(Operator):
"""Attempt to relink missing libraries using search paths"""
bl_idname = "dlm.attempt_relink"
bl_label = "Attempt Relink"
bl_options = {'REGISTER'}
def execute(self, context):
prefs = context.preferences.addons.get(__package__)
if not prefs or not prefs.preferences.search_paths:
self.report({'ERROR'}, "No search paths configured. Add search paths in addon preferences.")
return {'CANCELLED'}
# Get missing libraries
missing_libs = [lib for lib in context.scene.dynamic_link_manager.linked_libraries if lib.is_missing]
if not missing_libs:
self.report({'INFO'}, "No missing libraries to relink")
return {'FINISHED'}
self.report({'INFO'}, f"Attempting to relink {len(missing_libs)} missing libraries...")
# Scan search paths for missing libraries (FMT-inspired approach)
files_dir_list = []
try:
for search_path in prefs.preferences.search_paths:
if search_path.path:
for dirpath, dirnames, filenames in os.walk(bpy.path.abspath(search_path.path)):
files_dir_list.append([dirpath, filenames])
except FileNotFoundError:
self.report({'ERROR'}, f"Error - Bad file path in search paths")
return {'CANCELLED'}
# Try to find and relink each missing library (FMT-style)
relinked_count = 0
indirect_errors = []
for lib_item in missing_libs:
lib_filename = os.path.basename(lib_item.filepath)
found = False
# Search through all directories
for dir_info in files_dir_list:
dirpath, filenames = dir_info
# Look for exact filename match
if lib_filename in filenames:
new_path = os.path.join(dirpath, lib_filename)
try:
# Try to relink using Blender's system
# This will naturally detect indirect links if they exist
bpy.ops.file.find_missing_files()
found = True
relinked_count += 1
break
except Exception as e:
error_msg = str(e)
if "unable to relocate indirectly linked library" in error_msg:
indirect_errors.append(lib_item.filepath)
print(f"Indirect link detected for: {lib_item.filepath}")
else:
print(f"Error relinking {lib_item.filepath}: {e}")
if found:
break
if not found:
print(f"Could not find {lib_filename} in search paths")
# Update the UI to show indirect links
if indirect_errors:
self.report({'WARNING'}, f"Found {len(indirect_errors)} indirectly linked libraries")
# Mark these as having indirect missing
for lib_item in context.scene.dynamic_link_manager.linked_libraries:
if lib_item.filepath in indirect_errors:
lib_item.has_indirect_missing = True
lib_item.indirect_missing_count = 1
self.report({'INFO'}, f"Relink attempt complete. Relinked: {relinked_count}, Indirect: {len(indirect_errors)}")
return {'FINISHED'}
class DLM_OT_find_in_folders(Operator):
"""Find missing libraries in search folders and subfolders"""
bl_idname = "dlm.find_in_folders"
bl_label = "Find in Folders"
bl_options = {'REGISTER', 'UNDO'}
def execute(self, context):
prefs = context.preferences.addons.get(__package__)
if not prefs or not prefs.preferences.search_paths:
self.report({'ERROR'}, "No search paths configured. Add search paths in addon preferences.")
return {'CANCELLED'}
# Get missing libraries
missing_libs = [lib for lib in context.scene.dynamic_link_manager.linked_libraries if lib.is_missing]
if not missing_libs:
self.report({'INFO'}, "No missing libraries to find")
return {'FINISHED'}
self.report({'INFO'}, f"Searching for {len(missing_libs)} missing libraries...")
# FMT-style directory scanning
files_dir_list = []
try:
for search_path in prefs.preferences.search_paths:
if search_path.path:
for dirpath, dirnames, filenames in os.walk(bpy.path.abspath(search_path.path)):
files_dir_list.append([dirpath, filenames])
except FileNotFoundError:
self.report({'ERROR'}, f"Error - Bad file path in search paths")
return {'CANCELLED'}
# Try to find each missing library (FMT-style)
found_count = 0
for lib_item in missing_libs:
lib_filename = os.path.basename(lib_item.filepath)
found = False
# Search through all directories
for dir_info in files_dir_list:
dirpath, filenames = dir_info
# Look for exact filename match
if lib_filename in filenames:
new_path = os.path.join(dirpath, lib_filename)
self.report({'INFO'}, f"Found {lib_filename} at: {new_path}")
found_count += 1
found = True
break
if found:
break
if not found:
self.report({'WARNING'}, f"Could not find {lib_filename} in search paths")
if found_count > 0:
self.report({'INFO'}, f"Found {found_count} libraries in search paths")
else:
self.report({'WARNING'}, "No libraries found in search paths")
return {'FINISHED'}
class DLM_OT_browse_search_path(Operator):
"""Browse for a search path directory"""
bl_idname = "dlm.browse_search_path"
bl_label = "Browse Search Path"
bl_options = {'REGISTER'}
index: IntProperty(
name="Index",
description="Index of the search path to browse for",
default=0
)
filepath: StringProperty(
name="Search Path",
description="Path to search for missing linked libraries",
subtype='DIR_PATH'
)
def execute(self, context):
prefs = context.preferences.addons.get(__package__)
if prefs and prefs.preferences.search_paths:
if 0 <= self.index < len(prefs.preferences.search_paths):
prefs.preferences.search_paths[self.index].path = self.filepath
self.report({'INFO'}, f"Updated search path {self.index + 1}: {self.filepath}")
else:
self.report({'ERROR'}, f"Invalid index: {self.index}")
return {'FINISHED'}
def invoke(self, context, event):
prefs = context.preferences.addons.get(__package__)
if prefs and prefs.preferences.search_paths:
if 0 <= self.index < len(prefs.preferences.search_paths):
# Set the current path as default
self.filepath = prefs.preferences.search_paths[self.index].path
context.window_manager.fileselect_add(self)
return {'RUNNING_MODAL'}
def register():
bpy.utils.register_class(DLM_OT_replace_linked_asset)
bpy.utils.register_class(DLM_OT_scan_linked_assets)
bpy.utils.register_class(DLM_OT_open_linked_file)
bpy.utils.register_class(DLM_OT_add_search_path)
bpy.utils.register_class(DLM_OT_remove_search_path)
bpy.utils.register_class(DLM_OT_browse_search_path)
bpy.utils.register_class(DLM_OT_attempt_relink)
bpy.utils.register_class(DLM_OT_find_in_folders)
bpy.utils.register_class(DLM_OT_fmt_style_find)
def unregister():
bpy.utils.unregister_class(DLM_OT_fmt_style_find)
bpy.utils.unregister_class(DLM_OT_find_in_folders)
bpy.utils.unregister_class(DLM_OT_attempt_relink)
bpy.utils.unregister_class(DLM_OT_browse_search_path)
bpy.utils.unregister_class(DLM_OT_remove_search_path)
bpy.utils.unregister_class(DLM_OT_add_search_path)
bpy.utils.unregister_class(DLM_OT_open_linked_file)
bpy.utils.unregister_class(DLM_OT_scan_linked_assets)
bpy.utils.unregister_class(DLM_OT_replace_linked_asset)
@@ -0,0 +1,6 @@
# 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 3 of the License, or
# (at your option) any later version.
"""Feature logic (migrator, library) for Dynamic Link Manager."""
@@ -0,0 +1,207 @@
# 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 3 of the License, or
# (at your option) any later version.
import os
import re
import bpy
def _is_file_missing(filepath):
if not filepath:
return True
try:
abs_path = bpy.path.abspath(filepath)
except Exception:
abs_path = filepath
return not os.path.isfile(abs_path)
def _get_library_name(filepath):
return os.path.basename(filepath) if filepath else "Unknown"
def scan_linked_assets(context, report):
props = context.scene.dynamic_link_manager
props.linked_libraries.clear()
for lib in bpy.data.libraries:
try:
if lib.filepath:
lib.reload()
except Exception:
pass
direct_libs = set()
for lib in bpy.data.libraries:
try:
if getattr(lib, "parent", None) is None and lib.filepath:
direct_libs.add(lib.filepath)
except Exception:
continue
all_libraries = set(direct_libs)
props.linked_assets_count = len(all_libraries)
missing_indirect_libs = set()
for lib in bpy.data.libraries:
try:
if getattr(lib, "parent", None) is not None and lib.filepath:
try:
abs_child = bpy.path.abspath(lib.filepath)
except Exception:
abs_child = lib.filepath
if not os.path.isfile(abs_child):
root = lib.parent
while getattr(root, "parent", None) is not None:
root = root.parent
if root and root.filepath:
missing_indirect_libs.add(root.filepath)
except Exception:
continue
missing_ids_by_library = set()
for idb in list(bpy.data.objects) + list(bpy.data.meshes) + list(bpy.data.armatures) + list(bpy.data.materials) + list(bpy.data.node_groups) + list(bpy.data.images) + list(bpy.data.texts) + list(bpy.data.collections) + list(bpy.data.cameras) + list(bpy.data.lights):
try:
lib = getattr(idb, "library", None)
if lib and lib.filepath and getattr(idb, "is_library_missing", False):
missing_ids_by_library.add(lib.filepath)
except Exception:
continue
library_items = []
for filepath in sorted(all_libraries):
if not filepath:
continue
lib_item = props.linked_libraries.add()
lib_item.filepath = filepath
lib_item.name = _get_library_name(filepath)
lib_item.is_missing = _is_file_missing(filepath)
lib_item.is_indirect = (filepath in missing_indirect_libs) or (filepath in missing_ids_by_library)
library_items.append((lib_item, filepath))
library_items.sort(key=lambda x: (not x[0].is_missing, _get_library_name(x[1]).lower()))
props.linked_libraries.clear()
for lib_item, filepath in library_items:
new_item = props.linked_libraries.add()
new_item.filepath = filepath
new_item.name = _get_library_name(filepath)
new_item.is_missing = lib_item.is_missing
new_item.is_indirect = lib_item.is_indirect
report({"INFO"}, f"Found {len(all_libraries)} unique linked library files")
return {"FINISHED"}
def find_libraries_in_folders(context, report, addon_name=None):
if addon_name is None:
addon_name = __package__.rsplit(".", 1)[0] if "." in __package__ else __package__
prefs = context.preferences.addons.get(addon_name)
if not prefs or not prefs.preferences.search_paths:
report({"ERROR"}, "No search paths configured. Add search paths in addon preferences.")
return {"CANCELLED"}
missing_libs = [lib for lib in context.scene.dynamic_link_manager.linked_libraries if lib.is_missing]
if not missing_libs:
report({"INFO"}, "No missing libraries to find")
return {"FINISHED"}
report({"INFO"}, f"Searching for {len(missing_libs)} missing libraries in search paths...")
files_dir_list = []
total_dirs_scanned = 0
try:
for search_path in prefs.preferences.search_paths:
if not search_path.path:
continue
abs_path = bpy.path.abspath(search_path.path) if search_path.path.startswith("//") else search_path.path
report({"INFO"}, f"Scanning search path: {abs_path}")
if not os.path.exists(abs_path):
report({"WARNING"}, f"Search path does not exist: {abs_path}")
continue
if not os.path.isdir(abs_path):
report({"WARNING"}, f"Search path is not a directory: {abs_path}")
continue
for dirpath, dirnames, filenames in os.walk(abs_path):
files_dir_list.append([dirpath, filenames])
total_dirs_scanned += 1
if total_dirs_scanned > 1000:
report({"WARNING"}, "Reached scan limit of 1000 directories.")
break
except Exception as e:
report({"ERROR"}, f"Error scanning search paths: {e}")
return {"CANCELLED"}
found_libraries = {}
for lib_item in missing_libs:
lib_filename = os.path.basename(lib_item.filepath)
for dirpath, filenames in files_dir_list:
if lib_filename in filenames:
found_libraries[lib_filename] = os.path.join(dirpath, lib_filename)
report({"INFO"}, f"Found {lib_filename} at: {os.path.join(dirpath, lib_filename)}")
break
if found_libraries:
relinked_count = 0
for lib in bpy.data.libraries:
try:
if not lib.filepath:
continue
lib_filename = os.path.basename(lib.filepath)
if lib_filename in found_libraries:
new_path = found_libraries[lib_filename]
current_abs = bpy.path.abspath(lib.filepath)
if not os.path.isfile(current_abs) or current_abs != new_path:
lib.filepath = new_path
try:
lib.reload()
except Exception:
pass
relinked_count += 1
report({"INFO"}, f"Relinked {lib_filename} -> {new_path}")
except Exception:
continue
report({"INFO"}, f"Manually relinked {relinked_count} libraries")
else:
report({"WARNING"}, "No libraries found in search paths")
try:
bpy.ops.dlm.scan_linked_assets()
except Exception:
pass
report({"INFO"}, "Operation complete.")
return {"FINISHED"}
def attempt_relink(context, report, addon_name=None):
if addon_name is None:
addon_name = __package__.rsplit(".", 1)[0] if "." in __package__ else __package__
prefs = context.preferences.addons.get(addon_name)
if not prefs or not prefs.preferences.search_paths:
report({"ERROR"}, "No search paths configured.")
return {"CANCELLED"}
missing_libs = [lib for lib in context.scene.dynamic_link_manager.linked_libraries if lib.is_missing]
if not missing_libs:
report({"INFO"}, "No missing libraries to relink")
return {"FINISHED"}
report({"INFO"}, f"Attempting to relink {len(missing_libs)} missing libraries...")
files_dir_list = []
try:
for search_path in prefs.preferences.search_paths:
if search_path.path:
for dirpath, dirnames, filenames in os.walk(bpy.path.abspath(search_path.path)):
files_dir_list.append([dirpath, filenames])
except FileNotFoundError:
report({"ERROR"}, "Bad file path in search paths")
return {"CANCELLED"}
relinked_count = 0
for lib_item in missing_libs:
lib_filename = os.path.basename(lib_item.filepath)
for dirpath, filenames in files_dir_list:
if lib_filename in filenames:
try:
bpy.ops.file.find_missing_files()
relinked_count += 1
except Exception:
pass
break
report({"INFO"}, f"Relink attempt complete. Relinked: {relinked_count}")
return {"FINISHED"}
@@ -0,0 +1,657 @@
# 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 3 of the License, or
# (at your option) any later version.
"""Character migrator: migrate animation, constraints, relations from original to replacement armature."""
import bpy
from ..utils import descendants, collection_containing_armature
def get_pair_manual(context):
"""Return (orig_armature, rep_armature) from scene props, or (None, None)."""
props = getattr(context.scene, "dynamic_link_manager", None)
if not props:
return None, None
orig = getattr(props, "original_character", None)
rep = getattr(props, "replacement_character", None)
if orig and orig.type == "ARMATURE" and rep and rep.type == "ARMATURE":
return orig, rep
return None, None
def get_pair_automatic(context):
"""Discover one pair by convention: Name_Rigify and Name_Rigify.001. Returns (orig, rep) or (None, None)."""
pairs = []
for obj in bpy.data.objects:
if obj.type != "ARMATURE":
continue
name = obj.name
if name.endswith("_Rigify.001"):
base = name[:-len("_Rigify.001")]
orig = bpy.data.objects.get(f"{base}_Rigify")
if orig and orig.type == "ARMATURE" and orig != obj:
pairs.append((orig, obj))
return pairs[0] if pairs else (None, None)
def run_copy_attr(orig, rep):
"""Copy armature object attributes: location, rotation, scale (CopyAttr)."""
rep.location = orig.location.copy()
if orig.rotation_mode == "QUATERNION":
rep.rotation_quaternion = orig.rotation_quaternion.copy()
else:
rep.rotation_euler = orig.rotation_euler.copy()
rep.scale = orig.scale.copy()
def _has_als_anywhere(orig):
"""Return True if orig has Animation Layers (addon uses Object.als RNA PropertyGroup, obj.als.turn_on)."""
# Animation Layers addon: bpy.types.Object.als (RNA), not id props
if getattr(orig, "als", None) is not None:
return True
key = "als.turn_on"
if key in orig:
return True
if getattr(orig, "data", None) and hasattr(orig.data, "keys") and key in orig.data:
return True
try:
als = orig.get("als")
if als is not None and callable(getattr(als, "keys", None)) and "turn_on" in als.keys():
return True
except Exception:
pass
for pb in orig.pose.bones:
if key in pb:
return True
try:
als = pb.get("als")
if als is not None and callable(getattr(als, "keys", None)) and "turn_on" in als.keys():
return True
except Exception:
pass
return False
def _debug_als_lookup(orig):
"""Print full debug for AnimLayers: RNA obj.als and every id_prop on orig."""
key = "als.turn_on"
print("[DLM MigNLA] === AnimLayers debug ===")
als_rna = getattr(orig, "als", None)
print(f"[DLM MigNLA] orig.als (RNA): {als_rna!r}, turn_on={getattr(als_rna, 'turn_on', 'N/A') if als_rna else 'N/A'}")
print(f"[DLM MigNLA] 'als.turn_on' in orig (object): {key in orig}")
if getattr(orig, "data", None):
has_data_keys = hasattr(orig.data, "keys")
print(f"[DLM MigNLA] orig.data has keys(): {has_data_keys}")
if has_data_keys:
print(f"[DLM MigNLA] 'als.turn_on' in orig.data: {key in orig.data}")
print(f"[DLM MigNLA] orig.data keys: {list(orig.data.keys())}")
try:
als = orig.get("als")
has_als = als is not None and callable(getattr(als, "keys", None))
print(f"[DLM MigNLA] orig.get('als') is group: {has_als}")
if has_als:
print(f"[DLM MigNLA] orig['als'] keys: {list(als.keys())}")
print(f"[DLM MigNLA] 'turn_on' in orig['als']: {'turn_on' in als.keys()}")
except Exception as e:
print(f"[DLM MigNLA] orig.get('als') error: {e}")
print(f"[DLM MigNLA] orig (object) all keys: {list(orig.keys())}")
for k in list(orig.keys()):
try:
v = orig[k]
if callable(getattr(v, "keys", None)):
print(f"[DLM MigNLA] orig[{k!r}] (group) keys: {list(v.keys())}")
else:
print(f"[DLM MigNLA] orig[{k!r}] = {v!r}")
except Exception as e:
print(f"[DLM MigNLA] orig[{k!r}] error: {e}")
# RNA props that might be animation-layer related
try:
rna_props = list(orig.bl_rna.properties.keys())
layer_like = [p for p in rna_props if "layer" in p.lower() or "als" in p.lower() or "turn" in p.lower() or "anim" in p.lower()]
print(f"[DLM MigNLA] orig RNA props (layer/als/turn/anim): {layer_like}")
except Exception as e:
print(f"[DLM MigNLA] orig bl_rna.properties error: {e}")
# Every bone that has keys
bones_with_keys = []
for pb in orig.pose.bones:
if pb.keys():
bones_with_keys.append((pb.name, list(pb.keys())))
print(f"[DLM MigNLA] bones with id_props ({len(bones_with_keys)}): {bones_with_keys[:20]}{'...' if len(bones_with_keys) > 20 else ''}")
for bname, bkeys in bones_with_keys[:10]:
pb = orig.pose.bones[bname]
print(f"[DLM MigNLA] bone {bname!r}: keys={bkeys}")
for k in bkeys:
try:
v = pb[k]
if callable(getattr(v, "keys", None)):
print(f"[DLM MigNLA] [{k!r}] (group) keys: {list(v.keys())}")
else:
print(f"[DLM MigNLA] [{k!r}] = {v!r}")
except Exception as e:
print(f"[DLM MigNLA] [{k!r}] error: {e}")
print("[DLM MigNLA] === end AnimLayers debug ===")
def _mirror_als_turn_on(orig, rep):
"""Mirror Animation Layers state: obj.als.turn_on (RNA) and id-property fallbacks."""
# Animation Layers addon: Object.als is RNA PropertyGroup
orig_als = getattr(orig, "als", None)
rep_als = getattr(rep, "als", None)
if orig_als is not None and rep_als is not None:
try:
rep_als.turn_on = orig_als.turn_on
except Exception:
pass
key = "als.turn_on"
if key in orig:
try:
rep[key] = orig[key]
except Exception:
pass
try:
als = orig.get("als")
if als is not None and callable(getattr(als, "keys", None)) and "turn_on" in als.keys():
if "als" not in rep:
rep["als"] = {}
rep["als"]["turn_on"] = als["turn_on"]
except Exception:
pass
if getattr(orig, "data", None) and hasattr(orig.data, "keys") and key in orig.data:
try:
if rep.data is not None and hasattr(rep.data, "keys"):
rep.data[key] = orig.data[key]
except Exception:
pass
for pbone in orig.pose.bones:
if pbone.name not in rep.pose.bones:
continue
rbone = rep.pose.bones[pbone.name]
if key in pbone:
try:
rbone[key] = pbone[key]
except Exception:
pass
try:
als = pbone.get("als")
if als is not None and callable(getattr(als, "keys", None)) and "turn_on" in als.keys():
if "als" not in rbone:
rbone["als"] = {}
rbone["als"]["turn_on"] = als["turn_on"]
except Exception:
pass
def run_mig_nla(orig, rep, report=None):
"""Migrate NLA: copy tracks and strips to replacement; or mirror action slot when no NLA (MigNLA)."""
if not orig.animation_data:
return
ad = orig.animation_data
has_nla = ad.nla_tracks and len(ad.nla_tracks) > 0
active_action = getattr(ad, "action", None)
if not has_nla:
if rep.animation_data is None:
rep.animation_data_create()
rad = rep.animation_data
# Debug: Orig action slot state (Blender 4.4+ slotted actions).
def _slot_debug(label, animdata):
if animdata is None:
print(f"[DLM MigNLA] {label}: no animation_data")
return
a = getattr(animdata, "action", None)
print(f"[DLM MigNLA] {label} action={a.name if a else None}")
for p in ("action_slot", "action_slot_handle", "last_slot_identifier",
"action_blend_type", "action_extrapolation", "action_influence"):
if hasattr(animdata, p):
v = getattr(animdata, p, None)
if hasattr(v, "identifier"):
v = getattr(v, "identifier", v)
print(f"[DLM MigNLA] {p}={v!r}")
_slot_debug("Orig (before)", ad)
_slot_debug("Rep (before)", rad)
# Copy last_slot_identifier before action so slot is resolved when assigning (4.4+).
if hasattr(ad, "last_slot_identifier") and hasattr(rad, "last_slot_identifier") and ad.last_slot_identifier:
rad.last_slot_identifier = ad.last_slot_identifier
print(f"[DLM MigNLA] set rep last_slot_identifier={ad.last_slot_identifier!r}")
rad.action = active_action
# Copy Action Slot and related props (Blender 4.4+ slotted actions).
if getattr(ad, "action_slot", None) and getattr(rad, "action_slot", None):
try:
rad.action_slot = ad.action_slot
print(f"[DLM MigNLA] set rep action_slot={getattr(ad.action_slot, 'identifier', ad.action_slot)!r}")
except Exception as e:
print(f"[DLM MigNLA] rad.action_slot assign failed: {e}")
for prop in ("action_blend_type", "action_extrapolation", "action_influence"):
if hasattr(ad, prop) and hasattr(rad, prop):
setattr(rad, prop, getattr(ad, prop))
print(f"[DLM MigNLA] set rep {prop}={getattr(ad, prop)!r}")
_slot_debug("Rep (after)", rad)
_mirror_als_turn_on(orig, rep)
if report:
report({"INFO"}, "No NLA detected, active action and slot copied to Replacement Armature.")
return
if rep.animation_data is None:
rep.animation_data_create()
rep_tracks = rep.animation_data.nla_tracks
existing_names = {t.name for t in rep_tracks}
prev_track = rep_tracks[-1] if rep_tracks else None
for track in ad.nla_tracks:
new_track = rep_tracks.new(prev=prev_track)
name = track.name
if name in existing_names:
base, n = name, 1
while f"{base}.{n:03d}" in existing_names:
n += 1
name = f"{base}.{n:03d}"
new_track.name = name
existing_names.add(name)
new_track.mute = track.mute
new_track.is_solo = track.is_solo
new_track.lock = track.lock
for strip in track.strips:
if strip.type != "CLIP" or not strip.action:
continue
new_strip = new_track.strips.new(
strip.name, int(strip.frame_start), strip.action
)
new_strip.blend_type = strip.blend_type
new_strip.extrapolation = strip.extrapolation
new_strip.frame_end = strip.frame_end
new_strip.blend_in = strip.blend_in
new_strip.blend_out = strip.blend_out
new_strip.repeat = strip.repeat
new_strip.action_frame_start = strip.action_frame_start
new_strip.action_frame_end = strip.action_frame_end
new_strip.influence = strip.influence
new_strip.mute = strip.mute
new_strip.scale = strip.scale
new_strip.use_auto_blend = strip.use_auto_blend
new_strip.use_reverse = strip.use_reverse
new_strip.use_animated_influence = strip.use_animated_influence
new_strip.use_animated_time = strip.use_animated_time
new_strip.use_animated_time_cyclic = strip.use_animated_time_cyclic
new_strip.use_sync_length = strip.use_sync_length
prev_track = new_track
_mirror_als_turn_on(orig, rep)
if report:
_debug_als_lookup(orig)
has_als = _has_als_anywhere(orig)
print(f"[DLM MigNLA] AnimLayers check: has_als={has_als}")
if has_als:
report({"INFO"}, "NLA layers detected, Animation Layer attributes migrated to Replacement Armature.")
else:
report({"INFO"}, "NLA layers detected and migrated. No Animation Layers found.")
EXCLUDE_PROPS = {"_RNA_UI", "rigify_type", "rigify_parameters"}
def _is_id_prop_group(val):
"""True if val is an ID property group (nested dict-like), not a leaf or string."""
if val is None or isinstance(val, (str, bytes)):
return False
return callable(getattr(val, "keys", None))
def _copy_id_prop_recursive(orig_container, rep_container, key, debug_path="", debug=False):
"""Copy one id property from orig_container[key] into rep_container[key] (recursive for groups)."""
if key not in orig_container:
return
orig_val = orig_container[key]
try:
if _is_id_prop_group(orig_val):
if key not in rep_container:
rep_container[key] = {}
rep_group = rep_container[key]
for k in list(orig_val.keys()):
_copy_id_prop_recursive(orig_val, rep_group, k, f"{debug_path}.{key}", debug)
if debug:
print(f"[DLM MigCustProps] group {debug_path}.{key!r}: copied {len(orig_val.keys())} sub-keys")
else:
rep_container[key] = orig_val
if debug:
print(f"[DLM MigCustProps] leaf {debug_path}.{key!r} = {orig_val!r}")
except Exception as e:
print(f"[DLM MigCustProps] FAILED {debug_path}.{key!r}: {e}")
def _copy_custom_props_from(orig_obj, rep_obj, debug_label="", debug=False):
"""Copy all custom props from orig_obj to rep_obj (object or pose bone), including nested groups."""
keys = [k for k in orig_obj.keys() if k not in EXCLUDE_PROPS]
if debug and keys:
print(f"[DLM MigCustProps] {debug_label} keys: {keys}")
for key in keys:
_copy_id_prop_recursive(orig_obj, rep_obj, key, debug_label, debug)
def run_mig_cust_props(orig, rep):
"""Custom properties: copy overridden settings (ID props only, incl. nested e.g. Settings/Devices) from orig to rep."""
debug = True
print(f"[DLM MigCustProps] orig={orig.name!r} rep={rep.name!r}")
# Armature object
o_keys = list(orig.keys())
print(f"[DLM MigCustProps] armature orig keys (all): {o_keys}")
_copy_custom_props_from(orig, rep, f"obj:{orig.name}", debug)
# Bones with any id props
bones_with_keys = [(pb.name, list(pb.keys())) for pb in orig.pose.bones if pb.keys()]
print(f"[DLM MigCustProps] bones with id_props: {bones_with_keys}")
for pbone in orig.pose.bones:
if pbone.name not in rep.pose.bones:
continue
rbone = rep.pose.bones[pbone.name]
_copy_custom_props_from(pbone, rbone, f"bone:{pbone.name}", debug)
# After: rep armature and Settings bone if present
print(f"[DLM MigCustProps] rep armature keys after: {list(rep.keys())}")
if "Settings" in rep.pose.bones:
sb = rep.pose.bones["Settings"]
print(f"[DLM MigCustProps] rep bone Settings keys after: {list(sb.keys())}")
if sb.keys():
for k in sb.keys():
v = sb[k]
if _is_id_prop_group(v):
print(f"[DLM MigCustProps] Settings[{k!r}] (group) keys: {list(v.keys())}")
else:
print(f"[DLM MigCustProps] Settings[{k!r}] = {v!r}")
def _retarget_id(ob, orig, rep, orig_to_rep):
"""Return rep, orig_to_rep[ob], or ob so constraint targets point to replacement when appropriate."""
if ob is None:
return None
if ob == orig:
return rep
return orig_to_rep.get(ob, ob)
def _copy_constraint_props(c, nc, orig, rep, orig_to_rep):
"""Copy all copyable RNA properties from c to nc, retargeting object/armature pointers."""
for rna_prop in c.bl_rna.properties:
if rna_prop.is_readonly or rna_prop.identifier in ("name", "type"):
continue
if not hasattr(nc, rna_prop.identifier):
continue
try:
val = getattr(c, rna_prop.identifier)
except Exception:
continue
rna_type = getattr(rna_prop, "type", None)
if rna_type == "POINTER":
setattr(nc, rna_prop.identifier, _retarget_id(val, orig, rep, orig_to_rep))
elif rna_type == "COLLECTION":
# e.g. ArmatureConstraint.targets: ensure count then copy item props (target, subtarget, weight)
try:
dst_coll = getattr(nc, rna_prop.identifier)
src_coll = getattr(c, rna_prop.identifier)
add_fn = getattr(dst_coll, "add", None) or getattr(dst_coll, "new", None)
for i in range(len(src_coll)):
if i >= len(dst_coll) and add_fn:
add_fn()
for i, src_item in enumerate(src_coll):
if i >= len(dst_coll):
break
dst_item = dst_coll[i]
for p in dst_item.bl_rna.properties:
if p.is_readonly or p.identifier == "name":
continue
if not hasattr(dst_item, p.identifier):
continue
try:
v = getattr(src_item, p.identifier)
if getattr(p, "type", None) == "POINTER":
v = _retarget_id(v, orig, rep, orig_to_rep)
setattr(dst_item, p.identifier, v)
except Exception:
pass
except Exception:
pass
else:
try:
setattr(nc, rna_prop.identifier, val)
except Exception:
pass
def run_mig_bone_const(orig, rep, orig_to_rep):
"""Bone constraints: remove stale on rep, copy from orig with full props (targets, etc.) and retarget, trim duplicates."""
other_originals = [o for o in orig_to_rep if o != orig]
for pb in rep.pose.bones:
to_remove = [c for c in pb.constraints if getattr(c, "target", None) in other_originals]
for c in to_remove:
pb.constraints.remove(c)
for pbone in orig.pose.bones:
if pbone.name not in rep.pose.bones:
continue
rbone = rep.pose.bones[pbone.name]
for c in pbone.constraints:
nc = rbone.constraints.new(type=c.type)
nc.name = c.name
_copy_constraint_props(c, nc, orig, rep, orig_to_rep)
for pb in orig.pose.bones:
if pb.name not in rep.pose.bones:
continue
ro, rr = pb.constraints, rep.pose.bones[pb.name].constraints
while len(rr) > len(ro):
rr.remove(rr[-1])
def run_retarg_relatives(orig, rep, rep_descendants, orig_to_rep):
"""Retarget relations: parents, constraint targets, Armature modifiers to rep. Skip objects in orig's hierarchy (linked collection)."""
orig_hierarchy = {orig} | descendants(orig)
candidates = set(rep_descendants)
for ob in bpy.data.objects:
if getattr(ob.parent, "name", None) == orig.name:
candidates.add(ob)
for c in getattr(ob, "constraints", []):
if getattr(c, "target", None) == orig:
candidates.add(ob)
candidates -= orig_hierarchy
for ob in candidates:
if ob.parent == orig:
ob.parent = rep
for c in getattr(ob, "constraints", []):
if getattr(c, "target", None) == orig:
c.target = rep
if ob.type == "MESH" and ob.modifiers:
for m in ob.modifiers:
if getattr(m, "object", None) == orig:
m.object = rep
def _base_body_name_match(ob):
"""True if object looks like the base body mesh (MESH, name has body+base)."""
if ob.type != "MESH":
return False
name_lower = (ob.name + " " + (ob.data.name if ob.data else "")).lower()
return "body" in name_lower and "base" in name_lower
def _objects_in_collection_recursive(coll):
"""Yield all objects in collection and nested collections."""
for ob in coll.objects:
yield ob
for child in coll.children:
yield from _objects_in_collection_recursive(child)
def _find_base_body(armature, descendants_iter, rep_base_name=None):
"""Return the base body mesh: in descendants (armature mod), or in armature's collection(s), matched by name."""
def gather_candidates(ob_iter):
candidates = []
for ob in ob_iter:
if not _base_body_name_match(ob):
continue
if ob.modifiers:
for m in ob.modifiers:
if m.type == "ARMATURE" and m.object == armature:
return ob, candidates
candidates.append(ob)
return None, candidates
found, candidates = gather_candidates(descendants_iter)
if found:
return found
# Fallback: base body may be in same collection as armature but not parented to it (e.g. linked).
if not candidates:
for coll in [collection_containing_armature(armature)] + list(getattr(armature, "users_collection", []) or []):
if not coll:
continue
found, candidates = gather_candidates(_objects_in_collection_recursive(coll))
if found:
return found
if candidates:
break
if not candidates:
return None
if rep_base_name:
base = rep_base_name.rsplit(".", 1)[0] if "." in rep_base_name else rep_base_name
for ob in candidates:
if ob.name == base or ob.name.startswith(base + ".") or (ob.data and ob.data.name == base):
return ob
return candidates[0]
def run_mig_bbody_shapekeys(orig, rep, rep_descendants, context=None):
"""Replacement base body: library override (fully editable when context given), copy shapekey values, then shape-key action."""
orig_descendants = list(descendants(orig))
for ob in list(rep_descendants):
if not _base_body_name_match(ob):
continue
if ob.modifiers:
for m in ob.modifiers:
if m.type == "ARMATURE" and m.object == rep:
break
else:
continue
orig_base = _find_base_body(orig, orig_descendants, rep_base_name=ob.name)
# Debug: base body mesh state before override handling.
_lib = getattr(ob.data, "library", None)
_ol = getattr(ob.data, "override_library", None)
_sys = getattr(_ol, "is_system_override", None) if _ol else None
print(f"[DLM step6] {ob.name} data: linked={_lib is not None}, override={_ol is not None}, is_system_override={_sys}")
# Library override: use hierarchy create (fully editable) when context available, else single-id override.
if getattr(ob, "library", None):
if context:
try:
ob = ob.override_hierarchy_create(
context.scene, context.view_layer, do_fully_editable=True
)
except Exception:
try:
ob.override_create()
except Exception:
pass
else:
try:
ob.override_create()
except Exception:
pass
if getattr(ob.data, "library", None):
try:
ob.data.override_create(remap_local_usages=True)
# Make override user-editable (same as shift-click in data tab).
ol = getattr(ob.data, "override_library", None)
if ol is not None and getattr(ol, "is_system_override", None) is not None:
try:
ol.is_system_override = False
except Exception as e:
print(f"[DLM step6] {ob.name} set is_system_override=False: {e}")
except Exception as e:
print(f"[DLM step6] {ob.name} ob.data.override_create: {e}")
elif getattr(ob.data, "override_library", None):
ol = ob.data.override_library
if getattr(ol, "is_system_override", False):
try:
ol.is_system_override = False
except Exception as e:
print(f"[DLM step6] {ob.name} set is_system_override=False: {e}")
# Debug: state after override handling.
_ol2 = getattr(ob.data, "override_library", None)
_sys2 = getattr(_ol2, "is_system_override", None) if _ol2 else None
print(f"[DLM step6] {ob.name} after: override={_ol2 is not None}, is_system_override={_sys2} (False=editable)")
if ob.data.shape_keys:
# Ensure we can write shape key values: override the Key block if it is linked.
sk = ob.data.shape_keys
if getattr(sk, "library", None):
try:
sk.override_create(remap_local_usages=True)
except Exception as e:
print(f"[DLM step6] {ob.name} shape_keys.override_create: {e}")
# Copy shape key values from original base body to replacement (by matching key name).
if orig_base and orig_base.data.shape_keys:
rep_blocks = ob.data.shape_keys.key_blocks
orig_blocks = orig_base.data.shape_keys.key_blocks
n_copied = 0
for orig_key in orig_blocks:
rep_key = rep_blocks.get(orig_key.name)
if rep_key is not None:
rep_key.value = orig_key.value
n_copied += 1
print(f"[DLM step6] {ob.name} shapekey values: copied {n_copied}/{len(orig_blocks)} from {orig_base.name}")
else:
if not orig_base:
print(f"[DLM step6] {ob.name} no orig base body found for armature {orig.name}")
elif not orig_base.data.shape_keys:
print(f"[DLM step6] {ob.name} orig base body has no shape_keys")
if not ob.data.shape_keys.animation_data:
ob.data.shape_keys.animation_data_create()
sk_ad = ob.data.shape_keys.animation_data
# Prefer action (and slot) from original base body; fallback to name lookup.
orig_sk_ad = None
if orig_base and orig_base.data.shape_keys:
orig_sk_ad = orig_base.data.shape_keys.animation_data
action = None
if orig_sk_ad and getattr(orig_sk_ad, "action", None):
action = orig_sk_ad.action
if action is None:
body_name = ob.name
action = (
bpy.data.actions.get(body_name + "Action")
or bpy.data.actions.get(ob.data.name + "Action")
or bpy.data.actions.get(body_name + "Action.001")
)
if action:
# Copy slot-related props before action so slot is applied (Blender 4.4+).
if orig_sk_ad and hasattr(sk_ad, "last_slot_identifier") and hasattr(orig_sk_ad, "last_slot_identifier") and orig_sk_ad.last_slot_identifier:
sk_ad.last_slot_identifier = orig_sk_ad.last_slot_identifier
sk_ad.action = action
if orig_sk_ad and getattr(orig_sk_ad, "action_slot", None) and getattr(sk_ad, "action_slot", None):
try:
sk_ad.action_slot = orig_sk_ad.action_slot
except Exception:
pass
for prop in ("action_blend_type", "action_extrapolation", "action_influence"):
if orig_sk_ad and hasattr(orig_sk_ad, prop) and hasattr(sk_ad, prop):
try:
setattr(sk_ad, prop, getattr(orig_sk_ad, prop))
except Exception:
pass
def run_full_migration(context):
"""
Run the full 7-step character migration for the single pair (manual or automatic).
Returns (True, message) on success, (False, error_message) on failure.
"""
props = getattr(context.scene, "dynamic_link_manager", None)
use_auto = props and getattr(props, "migrator_mode", False)
orig, rep = (get_pair_automatic(context) if use_auto else get_pair_manual(context))
if not orig or not rep:
return False, "No character pair (set Original/Replacement or enable Automatic)."
if orig == rep:
return False, "Original and replacement must be different armatures."
orig_to_rep = {orig: rep}
rep_descendants = descendants(rep)
try:
run_copy_attr(orig, rep)
run_mig_nla(orig, rep)
run_mig_cust_props(orig, rep)
run_mig_bone_const(orig, rep, orig_to_rep)
run_retarg_relatives(orig, rep, rep_descendants, orig_to_rep)
run_mig_bbody_shapekeys(orig, rep, rep_descendants, context)
except Exception as e:
return False, str(e)
return True, f"Migrated {orig.name}{rep.name}"
@@ -0,0 +1,146 @@
# 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 3 of the License, or
# (at your option) any later version.
"""Tweak tools: add/remove/bake COPY_TRANSFORMS on Rigify arm/leg tweak bones."""
import bpy
# Rigify-style tweak bone names (only those present on armature are used)
ARM_TWEAK_BONES = (
"upper_arm_tweak.L", "upper_arm_tweak.R",
"forearm_tweak.L", "forearm_tweak.R",
"hand_tweak.L", "hand_tweak.R",
)
LEG_TWEAK_BONES = (
"thigh_tweak.L", "thigh_tweak.R",
"shin_tweak.L", "shin_tweak.R",
"foot_tweak.L", "foot_tweak.R",
)
TWEAK_CONSTRAINT_NAME = "Copy from Original"
def get_tweak_bones(armature, limb):
"""Return list of tweak bone names that exist on armature. limb in 'arm', 'leg', 'both'."""
if not armature or armature.type != "ARMATURE" or not armature.pose:
return []
bones = armature.pose.bones
if limb == "arm":
names = ARM_TWEAK_BONES
elif limb == "leg":
names = LEG_TWEAK_BONES
elif limb == "both":
names = ARM_TWEAK_BONES + LEG_TWEAK_BONES
else:
return []
return [n for n in names if n in bones]
def add_tweak_constraints(orig, rep, limb):
"""On rep, add COPY_TRANSFORMS on each tweak bone targeting same bone on orig."""
names = get_tweak_bones(rep, limb)
for name in names:
pb = rep.pose.bones[name]
c = pb.constraints.new(type="COPY_TRANSFORMS")
c.name = TWEAK_CONSTRAINT_NAME
c.target = orig
c.subtarget = name
c.mix_mode = "REPLACE"
def remove_tweak_constraints(orig, rep, limb):
"""On rep, remove COPY_TRANSFORMS that target orig from tweak bones."""
names = get_tweak_bones(rep, limb)
removed = 0
for name in names:
pb = rep.pose.bones[name]
to_remove = [
c for c in pb.constraints
if c.type == "COPY_TRANSFORMS" and getattr(c, "target", None) == orig
]
for c in to_remove:
pb.constraints.remove(c)
removed += 1
return removed
def _frame_range_from_track(rep, track_name):
"""Return (frame_start, frame_end) from rep's NLA track named track_name, or None."""
if not track_name or not rep.animation_data or not rep.animation_data.nla_tracks:
return None
track = rep.animation_data.nla_tracks.get(track_name)
if not track or not track.strips:
return None
start = min(s.frame_start for s in track.strips)
end = max(s.frame_end for s in track.strips)
return (int(start), int(end))
def bake_tweak_constraints(context, orig, rep, limb, track_name, post_clean):
"""
Select rep's tweak bones, run nla.bake, optionally run clean + decimate.
Returns (True, message) or (False, error_message).
"""
names = get_tweak_bones(rep, limb)
if not names:
return False, f"No tweak bones found for {limb} on {rep.name}"
scene = context.scene
frame_range = _frame_range_from_track(rep, track_name) if track_name else None
if not frame_range:
frame_range = (scene.frame_start, scene.frame_end)
frame_start, frame_end = frame_range
# Ensure rep is active and in pose mode
if context.view_layer.objects.active != rep:
context.view_layer.objects.active = rep
if rep.mode != "POSE":
bpy.ops.object.mode_set(mode="POSE")
# Select only tweak bones on rep
bpy.ops.pose.select_all(action="DESELECT")
for name in names:
rep.pose.bones[name].bone.select = True
# Bake
try:
bpy.ops.nla.bake(
frame_start=frame_start,
frame_end=frame_end,
step=1,
only_selected=True,
visual_keying=True,
clear_constraints=True,
clear_parents=True,
use_current_action=True,
clean_curves=False,
bake_types={"POSE"},
channel_types={"LOCATION", "ROTATION"},
)
except Exception as e:
return False, str(e)
if not post_clean:
return True, f"Baked {len(names)} tweak bones ({frame_start}-{frame_end})."
# Post-clean: find an area we can use for action/graph ops
win = context.window
for area in win.screen.areas:
if area.type == "DOPESHEET_EDITOR":
with context.temp_override(window=win, area=area):
try:
bpy.ops.action.clean_keyframes()
except Exception:
pass
break
for area in win.screen.areas:
if area.type == "GRAPH_EDITOR":
with context.temp_override(window=win, area=area):
try:
bpy.ops.graph.decimate(mode="ERROR", error=0.001)
except Exception:
pass
break
return True, f"Baked and cleaned {len(names)} tweak bones ({frame_start}-{frame_end})."
@@ -1,263 +0,0 @@
import bpy
from bpy.types import Panel, PropertyGroup, AddonPreferences, UIList
from bpy.props import IntProperty, StringProperty, BoolProperty, CollectionProperty
# Properties for search paths
class SearchPathItem(PropertyGroup):
path: StringProperty(
name="Search Path",
description="Path to search for missing linked libraries",
subtype='DIR_PATH'
)
# Properties for individual linked datablocks
class LinkedDatablockItem(PropertyGroup):
name: StringProperty(name="Name", description="Name of the linked datablock")
type: StringProperty(name="Type", description="Type of the linked datablock")
# FMT-style UIList for linked libraries
class DYNAMICLINK_UL_library_list(UIList):
def draw_item(self, context, layout, data, item, icon, active_data, active_propname, index):
custom_icon = 'FILE_BLEND'
if self.layout_type in {'DEFAULT', 'COMPACT'}:
# Library name and status (FMT-style layout)
layout.scale_x = 0.4
layout.label(text=item.name)
# Status indicator
layout.scale_x = 0.3
if item.is_missing:
layout.label(text="MISSING", icon='ERROR')
elif item.has_indirect_missing:
layout.label(text="INDIRECT", icon='ERROR')
else:
layout.label(text="OK", icon='FILE_BLEND')
# File path (FMT-style truncated)
layout.scale_x = 0.3
path_text = item.filepath
if len(path_text) > 30:
path_text = "..." + path_text[-27:]
layout.label(text=path_text, icon='FILE_FOLDER')
elif self.layout_type in {'GRID'}:
layout.alignment = 'CENTER'
layout.label(text="", icon=custom_icon)
# Properties for a single linked library file
class LinkedLibraryItem(PropertyGroup):
filepath: StringProperty(name="File Path", description="Path to the linked .blend file")
name: StringProperty(name="Name", description="Name of the linked .blend file")
is_missing: BoolProperty(name="Missing", description="True if the linked file is not found")
has_indirect_missing: BoolProperty(name="Has Indirect Missing", description="True if an indirectly linked blend is missing")
indirect_missing_count: IntProperty(name="Indirect Missing Count", description="Number of indirectly linked blends that are missing")
is_expanded: BoolProperty(name="Expanded", description="Whether this library item is expanded in the UI", default=True)
linked_datablocks: CollectionProperty(type=LinkedDatablockItem, name="Linked Datablocks")
class DynamicLinkManagerProperties(PropertyGroup):
linked_libraries: CollectionProperty(type=LinkedLibraryItem, name="Linked Libraries")
linked_libraries_index: IntProperty(
name="Linked Libraries Index",
description="Index of the selected linked library",
default=0
)
linked_assets_count: IntProperty(
name="Linked Assets Count",
description="Number of linked assets found in scene",
default=0
)
linked_libraries_expanded: BoolProperty(
name="Linked Libraries Expanded",
description="Whether the linked libraries section is expanded",
default=True
)
selected_asset_path: StringProperty(
name="Selected Asset Path",
description="Path to the currently selected asset",
default=""
)
# Addon preferences for search paths
class DynamicLinkManagerPreferences(AddonPreferences):
bl_idname = __package__
search_paths: CollectionProperty(
type=SearchPathItem,
name="Search Paths",
description="Paths to search for missing linked libraries"
)
def draw(self, context):
layout = self.layout
# Search paths section (FMT-style)
box = layout.box()
box.label(text="Default Search Paths for Missing Libraries")
# Add button - right-justified
row = box.row()
row.alignment = 'RIGHT'
row.operator("dlm.add_search_path", text="Add search path", icon='ADD')
# List search paths (FMT-style)
for i, path_item in enumerate(self.search_paths):
row = box.row()
row.prop(path_item, "path", text=f"Search path {i+1}")
# Folder icon for browsing (FMT-style)
row.operator("dlm.browse_search_path", text="", icon='FILE_FOLDER').index = i
# Remove button (FMT-style)
row.operator("dlm.remove_search_path", text="", icon='REMOVE').index = i
class DLM_PT_main_panel(Panel):
bl_space_type = 'VIEW_3D'
bl_region_type = 'UI'
bl_category = 'Dynamic Link Manager'
bl_label = "Dynamic Link Manager"
def draw_header(self, context):
layout = self.layout
layout.operator("preferences.addon_show", text="", icon='PREFERENCES').module = __package__
def draw(self, context):
layout = self.layout
props = context.scene.dynamic_link_manager
# Main scan section (FMT-style)
box = layout.box()
box.label(text="Linked Libraries Analysis")
row = box.row()
row.operator("dlm.scan_linked_assets", text="Scan Linked Assets", icon='FILE_REFRESH')
row.label(text=f"({props.linked_assets_count} libraries)")
# Show more detailed info if we have results
if props.linked_assets_count > 0:
# Linked Libraries section with single dropdown (FMT-style)
row = box.row(align=True)
# Dropdown arrow for the entire section
icon = 'DISCLOSURE_TRI_DOWN' if props.linked_libraries_expanded else 'DISCLOSURE_TRI_RIGHT'
row.prop(props, "linked_libraries_expanded", text="", icon=icon, icon_only=True)
# Section header
row.label(text="Linked Libraries:")
row.label(text=f"({props.linked_assets_count} libraries)")
# Only show library details if section is expanded
if props.linked_libraries_expanded:
# FMT-style compact list view
row = box.row()
row.template_list("DYNAMICLINK_UL_library_list", "", props, "linked_libraries", props, "linked_libraries_index")
# Action buttons below the list (FMT-style)
row = box.row()
row.scale_x = 0.3
if props.linked_libraries and 0 <= props.linked_libraries_index < len(props.linked_libraries):
selected_lib = props.linked_libraries[props.linked_libraries_index]
open_op = row.operator("dlm.open_linked_file", text="Open Selected", icon='FILE_BLEND')
open_op.filepath = selected_lib.filepath
else:
row.operator("dlm.open_linked_file", text="Open Selected", icon='FILE_BLEND')
row.scale_x = 0.7
row.operator("dlm.scan_linked_assets", text="Refresh", icon='FILE_REFRESH')
# Show details of selected item (FMT-style info display)
if props.linked_libraries and 0 <= props.linked_libraries_index < len(props.linked_libraries):
selected_lib = props.linked_libraries[props.linked_libraries_index]
info_box = box.box()
info_box.label(text=f"Selected: {selected_lib.name}")
if selected_lib.is_missing:
info_box.label(text="Status: MISSING", icon='ERROR')
elif selected_lib.has_indirect_missing:
info_box.label(text=f"Status: INDIRECT MISSING ({selected_lib.indirect_missing_count} dependencies)", icon='ERROR')
else:
info_box.label(text="Status: OK", icon='FILE_BLEND')
info_box.label(text=f"Path: {selected_lib.filepath}", icon='FILE_FOLDER')
# Search Paths Management (FMT-style) - integrated into Linked Libraries Analysis
if props.linked_assets_count > 0:
# Get preferences for search paths
prefs = context.preferences.addons.get(__package__)
# Search paths list (FMT-style) - Each path gets its own row with folder icon
if prefs and prefs.preferences.search_paths:
for i, path_item in enumerate(prefs.preferences.search_paths):
row = box.row()
row.prop(path_item, "path", text=f"Search path {i+1}")
# Folder icon for browsing (FMT-style)
row.operator("dlm.browse_search_path", text="", icon='FILE_FOLDER').index = i
# Remove button (FMT-style)
row.operator("dlm.remove_search_path", text="", icon='REMOVE').index = i
# Add button (FMT-style) - Just the + button, right-justified
row = box.row()
row.alignment = 'RIGHT'
row.operator("dlm.add_search_path", text="", icon='ADD')
# Main action button (FMT-style)
missing_count = sum(1 for lib in props.linked_libraries if lib.is_missing)
if missing_count > 0:
row = box.row()
row.operator("dlm.fmt_style_find", text="Find libraries in these folders", icon='VIEWZOOM')
# Asset replacement section (FMT-style)
box = layout.box()
box.label(text="Asset Replacement")
obj = context.active_object
if obj:
box.label(text=f"Selected: {obj.name}")
# Check if object itself is linked
if obj.library:
box.label(text=f"Linked from: {obj.library.filepath}")
row = box.row()
row.operator("dlm.replace_linked_asset", text="Replace Asset")
# Check if object's data is linked
elif obj.data and obj.data.library:
box.label(text=f"Data linked from: {obj.data.library.filepath}")
row = box.row()
row.operator("dlm.replace_linked_asset", text="Replace Asset")
# Check if it's a linked armature
elif obj.type == 'ARMATURE' and obj.data and hasattr(obj.data, 'library') and obj.data.library:
box.label(text=f"Armature linked from: {obj.data.library.filepath}")
row = box.row()
row.operator("dlm.replace_linked_asset", text="Replace Asset")
else:
box.label(text="Not a linked asset")
else:
box.label(text="No object selected")
# Settings section (FMT-style)
box = layout.box()
box.label(text="Settings")
row = box.row()
row.prop(props, "selected_asset_path", text="Asset Path")
def register():
bpy.utils.register_class(SearchPathItem)
bpy.utils.register_class(LinkedDatablockItem)
bpy.utils.register_class(DYNAMICLINK_UL_library_list)
bpy.utils.register_class(LinkedLibraryItem)
bpy.utils.register_class(DynamicLinkManagerProperties)
bpy.utils.register_class(DynamicLinkManagerPreferences)
bpy.utils.register_class(DLM_PT_main_panel)
# Register properties to scene
bpy.types.Scene.dynamic_link_manager = bpy.props.PointerProperty(type=DynamicLinkManagerProperties)
def unregister():
# Unregister properties from scene
del bpy.types.Scene.dynamic_link_manager
bpy.utils.unregister_class(DLM_PT_main_panel)
bpy.utils.unregister_class(DynamicLinkManagerPreferences)
bpy.utils.unregister_class(DynamicLinkManagerProperties)
bpy.utils.unregister_class(LinkedLibraryItem)
bpy.utils.unregister_class(DYNAMICLINK_UL_library_list)
bpy.utils.unregister_class(LinkedDatablockItem)
bpy.utils.unregister_class(SearchPathItem)
@@ -0,0 +1,24 @@
# 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 3 of the License, or
# (at your option) any later version.
from .operators import OPERATOR_CLASSES
from .panels import DLM_PT_main_panel, DLM_UL_library_list
from .preferences import DynamicLinkManagerPreferences
from . import properties
PANEL_CLASSES = [DLM_PT_main_panel, DLM_UL_library_list]
CLASSES = (
properties.SearchPathItem,
properties.LinkedDatablockItem,
properties.LinkedLibraryItem,
properties.DynamicLinkManagerProperties,
DynamicLinkManagerPreferences,
DLM_UL_library_list,
DLM_PT_main_panel,
*OPERATOR_CLASSES,
)
__all__ = ["CLASSES", "OPERATOR_CLASSES", "PANEL_CLASSES", "DynamicLinkManagerPreferences", "properties"]
@@ -0,0 +1,694 @@
# 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 3 of the License, or
# (at your option) any later version.
import bpy
import os
from bpy.types import Operator
from bpy.props import StringProperty, IntProperty
ADDON_NAME = __package__.rsplit(".", 1)[0] if "." in __package__ else __package__
def _prefs(context):
return context.preferences.addons.get(ADDON_NAME)
class DLM_OT_replace_linked_asset(Operator):
bl_idname = "dlm.replace_linked_asset"
bl_label = "Replace Linked Asset"
bl_description = "Open file browser to replace the linked asset with another file"
bl_options = {"REGISTER", "UNDO"}
filepath: StringProperty(name="File Path", description="Path to the new asset file", subtype="FILE_PATH")
def execute(self, context):
obj = context.active_object
if not obj:
self.report({"ERROR"}, "No object selected")
return {"CANCELLED"}
if getattr(obj, "library", None):
self.report({"INFO"}, f"Object '{obj.name}' is linked from: {obj.library.filepath}")
return {"FINISHED"}
if obj.data and getattr(obj.data, "library", None) and obj.data.library:
self.report({"INFO"}, f"Object '{obj.name}' has linked data from: {obj.data.library.filepath}")
return {"FINISHED"}
if obj.type == "ARMATURE" and obj.data and obj.data.name in bpy.data.armatures:
ad = bpy.data.armatures[obj.data.name]
if getattr(ad, "library", None) and ad.library:
self.report({"INFO"}, f"Armature '{obj.name}' data is linked from: {ad.library.filepath}")
return {"FINISHED"}
self.report({"ERROR"}, "Selected object is not a linked asset")
return {"CANCELLED"}
def invoke(self, context, event):
context.window_manager.fileselect_add(self)
return {"RUNNING_MODAL"}
class DLM_OT_scan_linked_assets(Operator):
bl_idname = "dlm.scan_linked_assets"
bl_label = "Scan Linked Libraries"
bl_description = "Scan the current file for linked libraries and list their status"
bl_options = {"REGISTER"}
def execute(self, context):
from ..ops import library
return library.scan_linked_assets(context, self.report)
class DLM_OT_find_libraries_in_folders(Operator):
bl_idname = "dlm.find_libraries_in_folders"
bl_label = "Find Libraries in Folders"
bl_description = "Search addon search paths for missing library blend files"
bl_options = {"REGISTER", "UNDO"}
def execute(self, context):
from ..ops import library
return library.find_libraries_in_folders(context, self.report, ADDON_NAME)
class DLM_OT_open_linked_file(Operator):
bl_idname = "dlm.open_linked_file"
bl_label = "Open Linked File"
bl_description = "Open the selected linked blend file in a new Blender instance"
bl_options = {"REGISTER"}
filepath: StringProperty(name="File Path", default="")
def execute(self, context):
if not self.filepath:
self.report({"ERROR"}, "No file path specified")
return {"CANCELLED"}
try:
bpy.ops.wm.path_open(filepath=self.filepath)
self.report({"INFO"}, f"Opening linked file: {self.filepath}")
except Exception as e:
self.report({"ERROR"}, f"Failed to open linked file: {e}")
return {"CANCELLED"}
return {"FINISHED"}
class DLM_OT_add_search_path(Operator):
bl_idname = "dlm.add_search_path"
bl_label = "Add Search Path"
bl_description = "Add a new folder to the list of search paths for finding libraries"
bl_options = {"REGISTER"}
def execute(self, context):
prefs = _prefs(context)
if prefs:
p = prefs.preferences.search_paths.add()
p.path = "//"
self.report({"INFO"}, f"Added search path: {p.path}")
return {"FINISHED"}
class DLM_OT_remove_search_path(Operator):
bl_idname = "dlm.remove_search_path"
bl_label = "Remove Search Path"
bl_description = "Remove the selected search path from the list"
bl_options = {"REGISTER"}
index: IntProperty(name="Index", default=0)
def execute(self, context):
prefs = _prefs(context)
if prefs and prefs.preferences.search_paths and 0 <= self.index < len(prefs.preferences.search_paths):
prefs.preferences.search_paths.remove(self.index)
self.report({"INFO"}, f"Removed search path at index {self.index}")
return {"FINISHED"}
class DLM_OT_attempt_relink(Operator):
bl_idname = "dlm.attempt_relink"
bl_label = "Attempt Relink"
bl_description = "Try to relink missing libraries using the configured search paths"
bl_options = {"REGISTER"}
def execute(self, context):
from ..ops import library
return library.attempt_relink(context, self.report, ADDON_NAME)
class DLM_OT_browse_search_path(Operator):
bl_idname = "dlm.browse_search_path"
bl_label = "Browse Search Path"
bl_description = "Browse to set the folder for the selected search path"
bl_options = {"REGISTER"}
index: IntProperty(name="Index", default=0)
filepath: StringProperty(name="Search Path", subtype="DIR_PATH")
def execute(self, context):
prefs = _prefs(context)
if prefs and prefs.preferences.search_paths and 0 <= self.index < len(prefs.preferences.search_paths):
prefs.preferences.search_paths[self.index].path = self.filepath
self.report({"INFO"}, f"Updated search path {self.index + 1}: {self.filepath}")
return {"FINISHED"}
def invoke(self, context, event):
prefs = _prefs(context)
if prefs and prefs.preferences.search_paths and 0 <= self.index < len(prefs.preferences.search_paths):
self.filepath = prefs.preferences.search_paths[self.index].path
context.window_manager.fileselect_add(self)
return {"RUNNING_MODAL"}
class DLM_OT_reload_libraries(Operator):
bl_idname = "dlm.reload_libraries"
bl_label = "Reload Libraries"
bl_description = "Reload all linked libraries (or fallback manual reload)"
bl_options = {"REGISTER"}
def execute(self, context):
try:
bpy.ops.outliner.lib_operation(type="RELOAD")
self.report({"INFO"}, "Library reload operation completed")
except Exception:
try:
for lib in bpy.data.libraries:
if lib.filepath and os.path.exists(bpy.path.abspath(lib.filepath)):
lib.reload()
self.report({"INFO"}, "Libraries reloaded manually")
except Exception as e:
self.report({"ERROR"}, f"Failed to reload libraries: {e}")
return {"CANCELLED"}
return {"FINISHED"}
class DLM_OT_make_paths_relative(Operator):
bl_idname = "dlm.make_paths_relative"
bl_label = "Make Paths Relative"
bl_description = "Convert all internal file paths to relative"
bl_options = {"REGISTER"}
def execute(self, context):
try:
bpy.ops.file.make_paths_relative()
self.report({"INFO"}, "All file paths made relative")
except Exception as e:
self.report({"ERROR"}, f"Failed to make paths relative: {e}")
return {"CANCELLED"}
return {"FINISHED"}
class DLM_OT_make_paths_absolute(Operator):
bl_idname = "dlm.make_paths_absolute"
bl_label = "Make Paths Absolute"
bl_description = "Convert all internal file paths to absolute"
bl_options = {"REGISTER"}
def execute(self, context):
try:
bpy.ops.file.make_paths_absolute()
self.report({"INFO"}, "All file paths made absolute")
except Exception as e:
self.report({"ERROR"}, f"Failed to make paths absolute: {e}")
return {"CANCELLED"}
return {"FINISHED"}
class DLM_OT_relocate_single_library(Operator):
bl_idname = "dlm.relocate_single_library"
bl_label = "Relocate Library"
bl_description = "Point the selected library to a new blend file and reload"
bl_options = {"REGISTER", "UNDO"}
target_filepath: StringProperty(name="Current Library Path", default="")
filepath: StringProperty(name="New Library File", subtype="FILE_PATH", default="")
def execute(self, context):
if not self.target_filepath or not self.filepath:
self.report({"ERROR"}, "No target or new file specified")
return {"CANCELLED"}
abs_match = bpy.path.abspath(self.target_filepath) if self.target_filepath else ""
library = None
for lib in bpy.data.libraries:
try:
if lib.filepath and bpy.path.abspath(lib.filepath) == abs_match:
library = lib
break
except Exception:
if lib.filepath == self.target_filepath:
library = lib
break
if not library:
self.report({"ERROR"}, "Could not resolve the selected library")
return {"CANCELLED"}
try:
library.filepath = self.filepath
library.reload()
self.report({"INFO"}, f"Relocated to: {self.filepath}")
except Exception as e:
self.report({"ERROR"}, f"Failed to relocate: {e}")
return {"CANCELLED"}
return {"FINISHED"}
def invoke(self, context, event):
if self.target_filepath:
try:
self.filepath = bpy.path.abspath(self.target_filepath)
except Exception:
self.filepath = self.target_filepath
context.window_manager.fileselect_add(self)
return {"RUNNING_MODAL"}
def _get_migrator_pair(context):
"""Return (orig, rep) from scene props (manual or automatic). (None, None) if invalid."""
from ..ops.migrator import get_pair_manual, get_pair_automatic
props = getattr(context.scene, "dynamic_link_manager", None)
if not props:
return None, None
use_auto = getattr(props, "migrator_mode", False)
orig, rep = get_pair_automatic(context) if use_auto else get_pair_manual(context)
return orig, rep
class DLM_OT_migrator_copy_attributes(Operator):
bl_idname = "dlm.migrator_copy_attributes"
bl_label = "CopyAttr"
bl_description = "Copy object and armature attributes from original to replacement character"
bl_icon = "COPY_ID"
bl_options = {"REGISTER", "UNDO"}
def execute(self, context):
orig, rep = _get_migrator_pair(context)
if not orig or not rep or orig == rep:
self.report({"ERROR"}, "No valid character pair (set Original/Replacement or enable Automatic).")
return {"CANCELLED"}
try:
from ..ops.migrator import run_copy_attr
run_copy_attr(orig, rep)
self.report({"INFO"}, "Copy attributes done.")
return {"FINISHED"}
except Exception as e:
self.report({"ERROR"}, str(e))
return {"CANCELLED"}
class DLM_OT_migrator_migrate_nla(Operator):
bl_idname = "dlm.migrator_migrate_nla"
bl_label = "MigNLA"
bl_description = "Migrate NLA tracks and strips from original to replacement character"
bl_icon = "NLA"
bl_options = {"REGISTER", "UNDO"}
def execute(self, context):
orig, rep = _get_migrator_pair(context)
if not orig or not rep or orig == rep:
self.report({"ERROR"}, "No valid character pair.")
return {"CANCELLED"}
try:
from ..ops.migrator import run_mig_nla
run_mig_nla(orig, rep, report=self.report)
return {"FINISHED"}
except Exception as e:
self.report({"ERROR"}, str(e))
return {"CANCELLED"}
class DLM_OT_migrator_custom_properties(Operator):
bl_idname = "dlm.migrator_custom_properties"
bl_label = "MigCustProps"
bl_description = "Copy custom properties from original to replacement character"
bl_icon = "PROPERTIES"
bl_options = {"REGISTER", "UNDO"}
def execute(self, context):
orig, rep = _get_migrator_pair(context)
if not orig or not rep or orig == rep:
self.report({"ERROR"}, "No valid character pair.")
return {"CANCELLED"}
try:
from ..ops.migrator import run_mig_cust_props
run_mig_cust_props(orig, rep)
self.report({"INFO"}, "Custom properties done.")
return {"FINISHED"}
except Exception as e:
self.report({"ERROR"}, str(e))
return {"CANCELLED"}
class DLM_OT_migrator_bone_constraints(Operator):
bl_idname = "dlm.migrator_bone_constraints"
bl_label = "MigBoneConst"
bl_description = "Migrate bone constraints from original to replacement armature"
bl_icon = "CONSTRAINT_BONE"
bl_options = {"REGISTER", "UNDO"}
def execute(self, context):
orig, rep = _get_migrator_pair(context)
if not orig or not rep or orig == rep:
self.report({"ERROR"}, "No valid character pair.")
return {"CANCELLED"}
try:
from ..ops.migrator import run_mig_bone_const
orig_to_rep = {orig: rep}
run_mig_bone_const(orig, rep, orig_to_rep)
self.report({"INFO"}, "Bone constraints done.")
return {"FINISHED"}
except Exception as e:
self.report({"ERROR"}, str(e))
return {"CANCELLED"}
class DLM_OT_migrator_retarget_relations(Operator):
bl_idname = "dlm.migrator_retarget_relations"
bl_label = "RetargRelatives"
bl_description = "Retarget parent/child and other relations to the replacement character"
bl_icon = "ORIENTATION_PARENT"
bl_options = {"REGISTER", "UNDO"}
def execute(self, context):
orig, rep = _get_migrator_pair(context)
if not orig or not rep or orig == rep:
self.report({"ERROR"}, "No valid character pair.")
return {"CANCELLED"}
try:
from ..ops.migrator import run_retarg_relatives
from ..utils import descendants
rep_descendants = descendants(rep)
orig_to_rep = {orig: rep}
run_retarg_relatives(orig, rep, rep_descendants, orig_to_rep)
self.report({"INFO"}, "Retarget relations done.")
return {"FINISHED"}
except Exception as e:
self.report({"ERROR"}, str(e))
return {"CANCELLED"}
class DLM_OT_migrator_basebody_shapekeys(Operator):
bl_idname = "dlm.migrator_basebody_shapekeys"
bl_label = "MigBBodyShapeKeys"
bl_description = "Migrate base body mesh shape key values from original to replacement"
bl_icon = "SHAPEKEY_DATA"
bl_options = {"REGISTER", "UNDO"}
def execute(self, context):
orig, rep = _get_migrator_pair(context)
if not orig or not rep or orig == rep:
self.report({"ERROR"}, "No valid character pair.")
return {"CANCELLED"}
try:
from ..ops.migrator import run_mig_bbody_shapekeys
from ..utils import descendants
rep_descendants = descendants(rep)
run_mig_bbody_shapekeys(orig, rep, rep_descendants, context)
self.report({"INFO"}, "Migrate BaseBody shapekeys done.")
return {"FINISHED"}
except Exception as e:
self.report({"ERROR"}, str(e))
return {"CANCELLED"}
MIGRATOR_STEP_OPS = (
"dlm.migrator_copy_attributes",
"dlm.migrator_migrate_nla",
"dlm.migrator_custom_properties",
"dlm.migrator_bone_constraints",
"dlm.migrator_retarget_relations",
"dlm.migrator_basebody_shapekeys",
)
class DLM_OT_run_character_migration(Operator):
bl_idname = "dlm.run_character_migration"
bl_label = "Run Character Migration"
bl_description = "Run all six migration steps (CopyAttr, MigNLA, MigCustProps, MigBoneConst, RetargRelatives, MigBBodyShapeKeys) in order"
bl_options = {"REGISTER", "UNDO"}
def execute(self, context):
steps = [
bpy.ops.dlm.migrator_copy_attributes,
bpy.ops.dlm.migrator_migrate_nla,
bpy.ops.dlm.migrator_custom_properties,
bpy.ops.dlm.migrator_bone_constraints,
bpy.ops.dlm.migrator_retarget_relations,
bpy.ops.dlm.migrator_basebody_shapekeys,
]
for i, op in enumerate(steps):
result = op()
if result != {"FINISHED"}:
self.report({"ERROR"}, f"Migration failed at step {i + 1}: {MIGRATOR_STEP_OPS[i]}")
return {"CANCELLED"}
self.report({"INFO"}, "Migration complete.")
return {"FINISHED"}
class DLM_OT_picker_original_character(Operator):
bl_idname = "dlm.picker_original_character"
bl_label = "Pick Original"
bl_description = "Set the original character armature from the active object"
bl_options = {"REGISTER"}
def execute(self, context):
obj = context.active_object
if not obj or obj.type != "ARMATURE":
self.report({"WARNING"}, "Select an armature")
return {"CANCELLED"}
context.scene.dynamic_link_manager.original_character = obj
self.report({"INFO"}, f"Original: {obj.name}")
return {"FINISHED"}
class DLM_OT_picker_replacement_character(Operator):
bl_idname = "dlm.picker_replacement_character"
bl_label = "Pick Replacement"
bl_description = "Set the replacement character armature from the active object"
bl_options = {"REGISTER"}
def execute(self, context):
obj = context.active_object
if not obj or obj.type != "ARMATURE":
self.report({"WARNING"}, "Select an armature")
return {"CANCELLED"}
context.scene.dynamic_link_manager.replacement_character = obj
self.report({"INFO"}, f"Replacement: {obj.name}")
return {"FINISHED"}
def _tweak_poll(context):
orig, rep = _get_migrator_pair(context)
return orig is not None and rep is not None
class DLM_OT_tweak_add_arm(Operator):
bl_idname = "dlm.tweak_add_arm"
bl_label = "Add Arm Tweaks"
bl_description = "Add tweak bone constraints to arm bones on the replacement character"
bl_options = {"REGISTER", "UNDO"}
@classmethod
def poll(cls, context):
return _tweak_poll(context)
def execute(self, context):
orig, rep = _get_migrator_pair(context)
from ..ops import tweak_tools
tweak_tools.add_tweak_constraints(orig, rep, "arm")
self.report({"INFO"}, "Arm tweak constraints added.")
return {"FINISHED"}
class DLM_OT_tweak_remove_arm(Operator):
bl_idname = "dlm.tweak_remove_arm"
bl_label = "Remove Arm Tweaks"
bl_description = "Remove arm tweak constraints from the replacement character"
bl_options = {"REGISTER", "UNDO"}
@classmethod
def poll(cls, context):
return _tweak_poll(context)
def execute(self, context):
orig, rep = _get_migrator_pair(context)
from ..ops import tweak_tools
n = tweak_tools.remove_tweak_constraints(orig, rep, "arm")
self.report({"INFO"}, f"Removed {n} arm tweak constraints.")
return {"FINISHED"}
class DLM_OT_tweak_bake_arm(Operator):
bl_idname = "dlm.tweak_bake_arm"
bl_label = "Bake Arm Tweaks"
bl_description = "Bake arm tweak constraints to keyframes and optionally remove constraints"
bl_options = {"REGISTER", "UNDO"}
@classmethod
def poll(cls, context):
return _tweak_poll(context)
def execute(self, context):
orig, rep = _get_migrator_pair(context)
props = context.scene.dynamic_link_manager
from ..ops import tweak_tools
ok, msg = tweak_tools.bake_tweak_constraints(
context, orig, rep, "arm",
getattr(props, "tweak_nla_track_name", "") or "",
getattr(props, "tweak_bake_post_clean", False),
)
if ok:
self.report({"INFO"}, msg)
return {"FINISHED"}
self.report({"ERROR"}, msg)
return {"CANCELLED"}
class DLM_OT_tweak_add_leg(Operator):
bl_idname = "dlm.tweak_add_leg"
bl_label = "Add Leg Tweaks"
bl_description = "Add tweak bone constraints to leg bones on the replacement character"
bl_options = {"REGISTER", "UNDO"}
@classmethod
def poll(cls, context):
return _tweak_poll(context)
def execute(self, context):
orig, rep = _get_migrator_pair(context)
from ..ops import tweak_tools
tweak_tools.add_tweak_constraints(orig, rep, "leg")
self.report({"INFO"}, "Leg tweak constraints added.")
return {"FINISHED"}
class DLM_OT_tweak_remove_leg(Operator):
bl_idname = "dlm.tweak_remove_leg"
bl_label = "Remove Leg Tweaks"
bl_description = "Remove leg tweak constraints from the replacement character"
bl_options = {"REGISTER", "UNDO"}
@classmethod
def poll(cls, context):
return _tweak_poll(context)
def execute(self, context):
orig, rep = _get_migrator_pair(context)
from ..ops import tweak_tools
n = tweak_tools.remove_tweak_constraints(orig, rep, "leg")
self.report({"INFO"}, f"Removed {n} leg tweak constraints.")
return {"FINISHED"}
class DLM_OT_tweak_bake_leg(Operator):
bl_idname = "dlm.tweak_bake_leg"
bl_label = "Bake Leg Tweaks"
bl_description = "Bake leg tweak constraints to keyframes and optionally remove constraints"
bl_options = {"REGISTER", "UNDO"}
@classmethod
def poll(cls, context):
return _tweak_poll(context)
def execute(self, context):
orig, rep = _get_migrator_pair(context)
props = context.scene.dynamic_link_manager
from ..ops import tweak_tools
ok, msg = tweak_tools.bake_tweak_constraints(
context, orig, rep, "leg",
getattr(props, "tweak_nla_track_name", "") or "",
getattr(props, "tweak_bake_post_clean", False),
)
if ok:
self.report({"INFO"}, msg)
return {"FINISHED"}
self.report({"ERROR"}, msg)
return {"CANCELLED"}
class DLM_OT_tweak_add_both(Operator):
bl_idname = "dlm.tweak_add_both"
bl_label = "Add Arm & Leg Tweaks"
bl_description = "Add tweak bone constraints to both arm and leg bones"
bl_options = {"REGISTER", "UNDO"}
@classmethod
def poll(cls, context):
return _tweak_poll(context)
def execute(self, context):
orig, rep = _get_migrator_pair(context)
from ..ops import tweak_tools
tweak_tools.add_tweak_constraints(orig, rep, "both")
self.report({"INFO"}, "Arm & leg tweak constraints added.")
return {"FINISHED"}
class DLM_OT_tweak_remove_both(Operator):
bl_idname = "dlm.tweak_remove_both"
bl_label = "Remove Arm & Leg Tweaks"
bl_description = "Remove all arm and leg tweak constraints from the replacement character"
bl_options = {"REGISTER", "UNDO"}
@classmethod
def poll(cls, context):
return _tweak_poll(context)
def execute(self, context):
orig, rep = _get_migrator_pair(context)
from ..ops import tweak_tools
n = tweak_tools.remove_tweak_constraints(orig, rep, "both")
self.report({"INFO"}, f"Removed {n} tweak constraints.")
return {"FINISHED"}
class DLM_OT_tweak_bake_both(Operator):
bl_idname = "dlm.tweak_bake_both"
bl_label = "Bake Arm & Leg Tweaks"
bl_description = "Bake all arm and leg tweak constraints to keyframes and optionally remove constraints"
bl_options = {"REGISTER", "UNDO"}
@classmethod
def poll(cls, context):
return _tweak_poll(context)
def execute(self, context):
orig, rep = _get_migrator_pair(context)
props = context.scene.dynamic_link_manager
from ..ops import tweak_tools
ok, msg = tweak_tools.bake_tweak_constraints(
context, orig, rep, "both",
getattr(props, "tweak_nla_track_name", "") or "",
getattr(props, "tweak_bake_post_clean", False),
)
if ok:
self.report({"INFO"}, msg)
return {"FINISHED"}
self.report({"ERROR"}, msg)
return {"CANCELLED"}
OPERATOR_CLASSES = [
DLM_OT_replace_linked_asset,
DLM_OT_scan_linked_assets,
DLM_OT_find_libraries_in_folders,
DLM_OT_open_linked_file,
DLM_OT_add_search_path,
DLM_OT_remove_search_path,
DLM_OT_browse_search_path,
DLM_OT_attempt_relink,
DLM_OT_reload_libraries,
DLM_OT_make_paths_relative,
DLM_OT_make_paths_absolute,
DLM_OT_relocate_single_library,
DLM_OT_run_character_migration,
DLM_OT_picker_original_character,
DLM_OT_picker_replacement_character,
DLM_OT_migrator_copy_attributes,
DLM_OT_migrator_migrate_nla,
DLM_OT_migrator_custom_properties,
DLM_OT_migrator_bone_constraints,
DLM_OT_migrator_retarget_relations,
DLM_OT_migrator_basebody_shapekeys,
DLM_OT_tweak_add_arm,
DLM_OT_tweak_remove_arm,
DLM_OT_tweak_bake_arm,
DLM_OT_tweak_add_leg,
DLM_OT_tweak_remove_leg,
DLM_OT_tweak_bake_leg,
DLM_OT_tweak_add_both,
DLM_OT_tweak_remove_both,
DLM_OT_tweak_bake_both,
]
@@ -0,0 +1,164 @@
# 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 3 of the License, or
# (at your option) any later version.
import bpy
from bpy.types import Panel, UIList
from . import properties
class DLM_UL_library_list(UIList):
def draw_item(self, context, layout, data, item, icon, active_data, active_propname, index):
if self.layout_type in {"DEFAULT", "COMPACT"}:
layout.scale_x = 0.4
layout.label(text=item.name)
layout.scale_x = 0.3
if item.is_missing:
layout.label(text="MISSING", icon="ERROR")
elif item.is_indirect:
layout.label(text="INDIRECT", icon="INFO")
else:
layout.label(text="OK", icon="FILE_BLEND")
layout.scale_x = 0.3
path_text = item.filepath or ""
if path_text.startswith("\\\\"):
parts = path_text.split("\\")
short_path = f"\\\\{parts[2]}\\" if len(parts) >= 3 else "\\\\ (network)"
elif len(path_text) >= 2 and path_text[1] == ":":
short_path = f"{path_text[:2]}\\"
elif path_text.startswith("//"):
short_path = "// (relative)"
else:
short_path = path_text[:15] + "..." if len(path_text) > 15 else path_text
layout.label(text=short_path, icon="FILE_FOLDER")
elif self.layout_type == "GRID":
layout.alignment = "CENTER"
layout.label(text="", icon="FILE_BLEND")
def _get_short_path(filepath):
if not filepath:
return "Unknown"
if filepath.startswith("//"):
return "// (relative)"
if filepath.startswith("\\\\"):
parts = filepath.split("\\")
return f"\\\\{parts[2]}\\" if len(parts) >= 3 else "\\\\ (network)"
if len(filepath) >= 2 and filepath[1] == ":":
return f"{filepath[:2]}\\"
if filepath.startswith("/"):
parts = filepath.split("/")
return f"/{parts[1]}/" if len(parts) >= 2 else "/ (root)"
return "Unknown"
class DLM_PT_main_panel(Panel):
bl_space_type = "VIEW_3D"
bl_region_type = "UI"
bl_category = "Dynamic Link Manager"
bl_label = "Dynamic Link Manager"
def draw_header(self, context):
layout = self.layout
layout.operator("preferences.addon_show", text="", icon="PREFERENCES").module = __package__.rsplit(".", 1)[0]
def draw(self, context):
layout = self.layout
props = context.scene.dynamic_link_manager
# Path management
row = layout.row()
row.operator("dlm.make_paths_relative", text="Make Paths Relative", icon="FILE_PARENT")
row.operator("dlm.make_paths_absolute", text="Make Paths Absolute", icon="FILE_FOLDER")
# CharMig section (placeholder; full UI in implementation step 5)
box = layout.box()
box.label(text="Character Migrator")
row = box.row()
row.prop(props, "migrator_mode", text="Automatic pair discovery")
row = box.row()
row.prop(props, "original_character", text="Original")
row.operator("dlm.picker_original_character", text="", icon="EYEDROPPER")
row = box.row()
row.prop(props, "replacement_character", text="Replacement")
row.operator("dlm.picker_replacement_character", text="", icon="EYEDROPPER")
row = box.row()
row.operator("dlm.run_character_migration", text="Run migration", icon="ARMATURE_DATA")
row = box.row(align=True)
row.operator("dlm.migrator_copy_attributes", text="CopyAttr", icon="COPY_ID")
row.operator("dlm.migrator_migrate_nla", text="MigNLA", icon="NLA")
row.operator("dlm.migrator_custom_properties", text="MigCustProps", icon="PROPERTIES")
row = box.row(align=True)
row.operator("dlm.migrator_bone_constraints", text="MigBoneConst", icon="CONSTRAINT_BONE")
row.operator("dlm.migrator_retarget_relations", text="RetargRelatives", icon="ORIENTATION_PARENT")
row.operator("dlm.migrator_basebody_shapekeys", text="MigBBodyShapeKeys", icon="SHAPEKEY_DATA")
# Tweak Tools
tweak_box = layout.box()
tweak_box.label(text="Tweak Tools", icon="CONSTRAINT")
row = tweak_box.row(align=True)
row.operator("dlm.tweak_add_arm", text="Add Arm", icon="CONSTRAINT_BONE")
row.operator("dlm.tweak_remove_arm", text="Remove Arm", icon="X")
row.operator("dlm.tweak_bake_arm", text="Bake Arm", icon="KEYFRAME")
row = tweak_box.row(align=True)
row.operator("dlm.tweak_add_leg", text="Add Leg", icon="CONSTRAINT_BONE")
row.operator("dlm.tweak_remove_leg", text="Remove Leg", icon="X")
row.operator("dlm.tweak_bake_leg", text="Bake Leg", icon="KEYFRAME")
row = tweak_box.row(align=True)
row.operator("dlm.tweak_add_both", text="Add Both", icon="CONSTRAINT_BONE")
row.operator("dlm.tweak_remove_both", text="Remove Both", icon="X")
row.operator("dlm.tweak_bake_both", text="Bake Both", icon="KEYFRAME")
row = tweak_box.row()
row.prop(props, "tweak_nla_track_name", text="NLA track")
row = tweak_box.row()
row.prop(props, "tweak_bake_post_clean", text="Post-clean after bake")
# Linked Libraries: header row (always), main box only when expanded
missing_count = sum(1 for lib in props.linked_libraries if lib.is_missing)
section_icon = "DISCLOSURE_TRI_DOWN" if props.linked_libraries_section_expanded else "DISCLOSURE_TRI_RIGHT"
row = layout.row(align=True)
row.prop(props, "linked_libraries_section_expanded", text="", icon=section_icon, icon_only=True)
row.label(text="Linked Libraries Analysis")
if missing_count > 0:
row.label(text=f"({props.linked_assets_count} libs, {missing_count} missing)", icon="ERROR")
else:
row.label(text=f"({props.linked_assets_count} libraries)")
if props.linked_libraries_section_expanded:
box = layout.box()
row = box.row()
row.operator("dlm.scan_linked_assets", text="Scan Linked Assets", icon="FILE_REFRESH")
if props.linked_assets_count > 0:
row = box.row()
row.template_list("DLM_UL_library_list", "", props, "linked_libraries", props, "linked_libraries_index")
row = box.row()
row.operator("dlm.reload_libraries", text="Reload Libraries", icon="FILE_REFRESH")
prefs = context.preferences.addons.get(__package__.rsplit(".", 1)[0])
if prefs and prefs.preferences.search_paths:
for i, path_item in enumerate(prefs.preferences.search_paths):
row = box.row()
row.prop(path_item, "path", text=f"Search path {i+1}")
row.operator("dlm.browse_search_path", text="", icon="FILE_FOLDER").index = i
row.operator("dlm.remove_search_path", text="", icon="REMOVE").index = i
row = box.row()
row.alignment = "RIGHT"
row.operator("dlm.add_search_path", text="Add search path", icon="ADD")
if missing_count > 0:
row = box.row()
row.operator("dlm.find_libraries_in_folders", text="Find libraries in these folders", icon="VIEWZOOM")
if props.linked_libraries and 0 <= props.linked_libraries_index < len(props.linked_libraries):
selected_lib = props.linked_libraries[props.linked_libraries_index]
info_box = box.box()
if selected_lib.is_missing:
info_box.alert = True
info_box.label(text=f"Selected: {selected_lib.name}")
if selected_lib.is_missing:
info_box.label(text="Status: MISSING", icon="ERROR")
elif selected_lib.is_indirect:
info_box.label(text="Status: INDIRECT", icon="INFO")
else:
info_box.label(text="Status: OK", icon="FILE_BLEND")
info_box.label(text=f"Path: {selected_lib.filepath}", icon="FILE_FOLDER")
info_box.operator("dlm.open_linked_file", text="Open Blend", icon="FILE_BLEND").filepath = selected_lib.filepath
op = info_box.operator("dlm.relocate_single_library", text="Relocate Library", icon="FILE_FOLDER")
op.target_filepath = selected_lib.filepath
@@ -0,0 +1,32 @@
# 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 3 of the License, or
# (at your option) any later version.
import bpy
from bpy.types import AddonPreferences
from bpy.props import StringProperty, CollectionProperty
from . import properties
class DynamicLinkManagerPreferences(AddonPreferences):
bl_idname = __package__.rsplit(".", 1)[0]
search_paths: CollectionProperty(
type=properties.SearchPathItem,
name="Search Paths",
description="Paths to search for missing linked libraries",
)
def draw(self, context):
layout = self.layout
box = layout.box()
box.label(text="Default Search Paths for Missing Libraries")
row = box.row()
row.alignment = "RIGHT"
row.operator("dlm.add_search_path", text="Add search path", icon="ADD")
for i, path_item in enumerate(self.search_paths):
row = box.row()
row.prop(path_item, "path", text=f"Search path {i+1}")
row.operator("dlm.browse_search_path", text="", icon="FILE_FOLDER").index = i
row.operator("dlm.remove_search_path", text="", icon="REMOVE").index = i
@@ -0,0 +1,73 @@
# 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 3 of the License, or
# (at your option) any later version.
import bpy
from bpy.types import PropertyGroup
from bpy.props import IntProperty, StringProperty, BoolProperty, CollectionProperty, PointerProperty
class SearchPathItem(PropertyGroup):
path: StringProperty(
name="Search Path",
description="Path to search for missing linked libraries",
subtype="DIR_PATH",
)
class LinkedDatablockItem(PropertyGroup):
name: StringProperty(name="Name", description="Name of the linked datablock")
type: StringProperty(name="Type", description="Type of the linked datablock")
class LinkedLibraryItem(PropertyGroup):
filepath: StringProperty(name="File Path", description="Path to the linked .blend file")
name: StringProperty(name="Name", description="Name of the linked .blend file")
is_missing: BoolProperty(name="Missing", description="True if the linked file is not found")
is_indirect: BoolProperty(name="Is Indirect", description="True if this is an indirectly linked library")
is_expanded: BoolProperty(name="Expanded", default=True)
linked_datablocks: CollectionProperty(type=LinkedDatablockItem, name="Linked Datablocks")
class DynamicLinkManagerProperties(PropertyGroup):
linked_libraries: CollectionProperty(type=LinkedLibraryItem, name="Linked Libraries")
linked_libraries_index: IntProperty(name="Linked Libraries Index", default=0)
linked_assets_count: IntProperty(name="Linked Assets Count", default=0)
linked_libraries_section_expanded: BoolProperty(
name="Linked Libraries Analysis Expanded",
description="Show or hide the Linked Libraries Analysis section",
default=False,
)
selected_asset_path: StringProperty(name="Selected Asset Path", default="")
# Character migrator (manual mode)
migrator_mode: BoolProperty(
name="Automatic",
description="Automatic: discover pair by Name_Rigify / Name_Rigify.001. Manual: use fields below",
default=False,
)
original_character: PointerProperty(
name="Original Character",
description="Armature to migrate from",
type=bpy.types.Object,
poll=lambda self, obj: obj and obj.type == "ARMATURE",
)
replacement_character: PointerProperty(
name="Replacement Character",
description="Armature to migrate to",
type=bpy.types.Object,
poll=lambda self, obj: obj and obj.type == "ARMATURE",
)
# Tweak tools (bake frame range and post-clean)
tweak_nla_track_name: StringProperty(
name="NLA Track (bake range)",
description="If set, bake uses this NLA track on the replacement armature for frame range; else scene range",
default="",
)
tweak_bake_post_clean: BoolProperty(
name="Post-clean after bake",
description="Run action clean keyframes and graph decimate (error 0.001) after baking",
default=False,
)
@@ -0,0 +1,39 @@
# 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 3 of the License, or
# (at your option) any later version.
"""Shared helpers for Dynamic Link Manager."""
import bpy
def descendants(armature):
"""Return a set of objects whose parent chain leads to the given armature."""
out = set()
for ob in bpy.data.objects:
p = ob.parent
while p:
if p == armature:
out.add(ob)
break
p = p.parent
return out
def collection_containing_armature(armature):
"""
Return a collection that contains the armature (for linked character context).
Prefers a collection whose name matches the character base (e.g. "Steve" for Steve_Rigify).
"""
if not armature or armature.name not in bpy.data.objects:
return None
colls = getattr(armature, "users_collection", []) or []
if not colls:
return None
name = armature.name
base = name.replace("_Rigify", "").replace(".001", "").rstrip("0123456789.")
for c in colls:
if c.name == base or base in c.name or c.name in name:
return c
return colls[0]
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
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,5 +1,5 @@
{ {
"last_check": "2026-02-12 10:41:20.508861", "last_check": "2026-03-10 16:36:48.530366",
"backup_date": "December-11-2025", "backup_date": "December-11-2025",
"update_ready": false, "update_ready": false,
"ignore": false, "ignore": false,
@@ -1,674 +0,0 @@
GNU GENERAL PUBLIC LICENSE
Version 3, 29 June 2007
Copyright (C) 2007 Free Software Foundation, Inc. <https://fsf.org/>
Everyone is permitted to copy and distribute verbatim copies
of this license document, but changing it is not allowed.
Preamble
The GNU General Public License is a free, copyleft license for
software and other kinds of works.
The licenses for most software and other practical works are designed
to take away your freedom to share and change the works. By contrast,
the GNU General Public License is intended to guarantee your freedom to
share and change all versions of a program--to make sure it remains free
software for all its users. We, the Free Software Foundation, use the
GNU General Public License for most of our software; it applies also to
any other work released this way by its authors. You can apply it to
your programs, too.
When we speak of free software, we are referring to freedom, not
price. Our General Public Licenses are designed to make sure that you
have the freedom to distribute copies of free software (and charge for
them if you wish), that you receive source code or can get it if you
want it, that you can change the software or use pieces of it in new
free programs, and that you know you can do these things.
To protect your rights, we need to prevent others from denying you
these rights or asking you to surrender the rights. Therefore, you have
certain responsibilities if you distribute copies of the software, or if
you modify it: responsibilities to respect the freedom of others.
For example, if you distribute copies of such a program, whether
gratis or for a fee, you must pass on to the recipients the same
freedoms that you received. You must make sure that they, too, receive
or can get the source code. And you must show them these terms so they
know their rights.
Developers that use the GNU GPL protect your rights with two steps:
(1) assert copyright on the software, and (2) offer you this License
giving you legal permission to copy, distribute and/or modify it.
For the developers' and authors' protection, the GPL clearly explains
that there is no warranty for this free software. For both users' and
authors' sake, the GPL requires that modified versions be marked as
changed, so that their problems will not be attributed erroneously to
authors of previous versions.
Some devices are designed to deny users access to install or run
modified versions of the software inside them, although the manufacturer
can do so. This is fundamentally incompatible with the aim of
protecting users' freedom to change the software. The systematic
pattern of such abuse occurs in the area of products for individuals to
use, which is precisely where it is most unacceptable. Therefore, we
have designed this version of the GPL to prohibit the practice for those
products. If such problems arise substantially in other domains, we
stand ready to extend this provision to those domains in future versions
of the GPL, as needed to protect the freedom of users.
Finally, every program is threatened constantly by software patents.
States should not allow patents to restrict development and use of
software on general-purpose computers, but in those that do, we wish to
avoid the special danger that patents applied to a free program could
make it effectively proprietary. To prevent this, the GPL assures that
patents cannot be used to render the program non-free.
The precise terms and conditions for copying, distribution and
modification follow.
TERMS AND CONDITIONS
0. Definitions.
"This License" refers to version 3 of the GNU General Public License.
"Copyright" also means copyright-like laws that apply to other kinds of
works, such as semiconductor masks.
"The Program" refers to any copyrightable work licensed under this
License. Each licensee is addressed as "you". "Licensees" and
"recipients" may be individuals or organizations.
To "modify" a work means to copy from or adapt all or part of the work
in a fashion requiring copyright permission, other than the making of an
exact copy. The resulting work is called a "modified version" of the
earlier work or a work "based on" the earlier work.
A "covered work" means either the unmodified Program or a work based
on the Program.
To "propagate" a work means to do anything with it that, without
permission, would make you directly or secondarily liable for
infringement under applicable copyright law, except executing it on a
computer or modifying a private copy. Propagation includes copying,
distribution (with or without modification), making available to the
public, and in some countries other activities as well.
To "convey" a work means any kind of propagation that enables other
parties to make or receive copies. Mere interaction with a user through
a computer network, with no transfer of a copy, is not conveying.
An interactive user interface displays "Appropriate Legal Notices"
to the extent that it includes a convenient and prominently visible
feature that (1) displays an appropriate copyright notice, and (2)
tells the user that there is no warranty for the work (except to the
extent that warranties are provided), that licensees may convey the
work under this License, and how to view a copy of this License. If
the interface presents a list of user commands or options, such as a
menu, a prominent item in the list meets this criterion.
1. Source Code.
The "source code" for a work means the preferred form of the work
for making modifications to it. "Object code" means any non-source
form of a work.
A "Standard Interface" means an interface that either is an official
standard defined by a recognized standards body, or, in the case of
interfaces specified for a particular programming language, one that
is widely used among developers working in that language.
The "System Libraries" of an executable work include anything, other
than the work as a whole, that (a) is included in the normal form of
packaging a Major Component, but which is not part of that Major
Component, and (b) serves only to enable use of the work with that
Major Component, or to implement a Standard Interface for which an
implementation is available to the public in source code form. A
"Major Component", in this context, means a major essential component
(kernel, window system, and so on) of the specific operating system
(if any) on which the executable work runs, or a compiler used to
produce the work, or an object code interpreter used to run it.
The "Corresponding Source" for a work in object code form means all
the source code needed to generate, install, and (for an executable
work) run the object code and to modify the work, including scripts to
control those activities. However, it does not include the work's
System Libraries, or general-purpose tools or generally available free
programs which are used unmodified in performing those activities but
which are not part of the work. For example, Corresponding Source
includes interface definition files associated with source files for
the work, and the source code for shared libraries and dynamically
linked subprograms that the work is specifically designed to require,
such as by intimate data communication or control flow between those
subprograms and other parts of the work.
The Corresponding Source need not include anything that users
can regenerate automatically from other parts of the Corresponding
Source.
The Corresponding Source for a work in source code form is that
same work.
2. Basic Permissions.
All rights granted under this License are granted for the term of
copyright on the Program, and are irrevocable provided the stated
conditions are met. This License explicitly affirms your unlimited
permission to run the unmodified Program. The output from running a
covered work is covered by this License only if the output, given its
content, constitutes a covered work. This License acknowledges your
rights of fair use or other equivalent, as provided by copyright law.
You may make, run and propagate covered works that you do not
convey, without conditions so long as your license otherwise remains
in force. You may convey covered works to others for the sole purpose
of having them make modifications exclusively for you, or provide you
with facilities for running those works, provided that you comply with
the terms of this License in conveying all material for which you do
not control copyright. Those thus making or running the covered works
for you must do so exclusively on your behalf, under your direction
and control, on terms that prohibit them from making any copies of
your copyrighted material outside their relationship with you.
Conveying under any other circumstances is permitted solely under
the conditions stated below. Sublicensing is not allowed; section 10
makes it unnecessary.
3. Protecting Users' Legal Rights From Anti-Circumvention Law.
No covered work shall be deemed part of an effective technological
measure under any applicable law fulfilling obligations under article
11 of the WIPO copyright treaty adopted on 20 December 1996, or
similar laws prohibiting or restricting circumvention of such
measures.
When you convey a covered work, you waive any legal power to forbid
circumvention of technological measures to the extent such circumvention
is effected by exercising rights under this License with respect to
the covered work, and you disclaim any intention to limit operation or
modification of the work as a means of enforcing, against the work's
users, your or third parties' legal rights to forbid circumvention of
technological measures.
4. Conveying Verbatim Copies.
You may convey verbatim copies of the Program's source code as you
receive it, in any medium, provided that you conspicuously and
appropriately publish on each copy an appropriate copyright notice;
keep intact all notices stating that this License and any
non-permissive terms added in accord with section 7 apply to the code;
keep intact all notices of the absence of any warranty; and give all
recipients a copy of this License along with the Program.
You may charge any price or no price for each copy that you convey,
and you may offer support or warranty protection for a fee.
5. Conveying Modified Source Versions.
You may convey a work based on the Program, or the modifications to
produce it from the Program, in the form of source code under the
terms of section 4, provided that you also meet all of these conditions:
a) The work must carry prominent notices stating that you modified
it, and giving a relevant date.
b) The work must carry prominent notices stating that it is
released under this License and any conditions added under section
7. This requirement modifies the requirement in section 4 to
"keep intact all notices".
c) You must license the entire work, as a whole, under this
License to anyone who comes into possession of a copy. This
License will therefore apply, along with any applicable section 7
additional terms, to the whole of the work, and all its parts,
regardless of how they are packaged. This License gives no
permission to license the work in any other way, but it does not
invalidate such permission if you have separately received it.
d) If the work has interactive user interfaces, each must display
Appropriate Legal Notices; however, if the Program has interactive
interfaces that do not display Appropriate Legal Notices, your
work need not make them do so.
A compilation of a covered work with other separate and independent
works, which are not by their nature extensions of the covered work,
and which are not combined with it such as to form a larger program,
in or on a volume of a storage or distribution medium, is called an
"aggregate" if the compilation and its resulting copyright are not
used to limit the access or legal rights of the compilation's users
beyond what the individual works permit. Inclusion of a covered work
in an aggregate does not cause this License to apply to the other
parts of the aggregate.
6. Conveying Non-Source Forms.
You may convey a covered work in object code form under the terms
of sections 4 and 5, provided that you also convey the
machine-readable Corresponding Source under the terms of this License,
in one of these ways:
a) Convey the object code in, or embodied in, a physical product
(including a physical distribution medium), accompanied by the
Corresponding Source fixed on a durable physical medium
customarily used for software interchange.
b) Convey the object code in, or embodied in, a physical product
(including a physical distribution medium), accompanied by a
written offer, valid for at least three years and valid for as
long as you offer spare parts or customer support for that product
model, to give anyone who possesses the object code either (1) a
copy of the Corresponding Source for all the software in the
product that is covered by this License, on a durable physical
medium customarily used for software interchange, for a price no
more than your reasonable cost of physically performing this
conveying of source, or (2) access to copy the
Corresponding Source from a network server at no charge.
c) Convey individual copies of the object code with a copy of the
written offer to provide the Corresponding Source. This
alternative is allowed only occasionally and noncommercially, and
only if you received the object code with such an offer, in accord
with subsection 6b.
d) Convey the object code by offering access from a designated
place (gratis or for a charge), and offer equivalent access to the
Corresponding Source in the same way through the same place at no
further charge. You need not require recipients to copy the
Corresponding Source along with the object code. If the place to
copy the object code is a network server, the Corresponding Source
may be on a different server (operated by you or a third party)
that supports equivalent copying facilities, provided you maintain
clear directions next to the object code saying where to find the
Corresponding Source. Regardless of what server hosts the
Corresponding Source, you remain obligated to ensure that it is
available for as long as needed to satisfy these requirements.
e) Convey the object code using peer-to-peer transmission, provided
you inform other peers where the object code and Corresponding
Source of the work are being offered to the general public at no
charge under subsection 6d.
A separable portion of the object code, whose source code is excluded
from the Corresponding Source as a System Library, need not be
included in conveying the object code work.
A "User Product" is either (1) a "consumer product", which means any
tangible personal property which is normally used for personal, family,
or household purposes, or (2) anything designed or sold for incorporation
into a dwelling. In determining whether a product is a consumer product,
doubtful cases shall be resolved in favor of coverage. For a particular
product received by a particular user, "normally used" refers to a
typical or common use of that class of product, regardless of the status
of the particular user or of the way in which the particular user
actually uses, or expects or is expected to use, the product. A product
is a consumer product regardless of whether the product has substantial
commercial, industrial or non-consumer uses, unless such uses represent
the only significant mode of use of the product.
"Installation Information" for a User Product means any methods,
procedures, authorization keys, or other information required to install
and execute modified versions of a covered work in that User Product from
a modified version of its Corresponding Source. The information must
suffice to ensure that the continued functioning of the modified object
code is in no case prevented or interfered with solely because
modification has been made.
If you convey an object code work under this section in, or with, or
specifically for use in, a User Product, and the conveying occurs as
part of a transaction in which the right of possession and use of the
User Product is transferred to the recipient in perpetuity or for a
fixed term (regardless of how the transaction is characterized), the
Corresponding Source conveyed under this section must be accompanied
by the Installation Information. But this requirement does not apply
if neither you nor any third party retains the ability to install
modified object code on the User Product (for example, the work has
been installed in ROM).
The requirement to provide Installation Information does not include a
requirement to continue to provide support service, warranty, or updates
for a work that has been modified or installed by the recipient, or for
the User Product in which it has been modified or installed. Access to a
network may be denied when the modification itself materially and
adversely affects the operation of the network or violates the rules and
protocols for communication across the network.
Corresponding Source conveyed, and Installation Information provided,
in accord with this section must be in a format that is publicly
documented (and with an implementation available to the public in
source code form), and must require no special password or key for
unpacking, reading or copying.
7. Additional Terms.
"Additional permissions" are terms that supplement the terms of this
License by making exceptions from one or more of its conditions.
Additional permissions that are applicable to the entire Program shall
be treated as though they were included in this License, to the extent
that they are valid under applicable law. If additional permissions
apply only to part of the Program, that part may be used separately
under those permissions, but the entire Program remains governed by
this License without regard to the additional permissions.
When you convey a copy of a covered work, you may at your option
remove any additional permissions from that copy, or from any part of
it. (Additional permissions may be written to require their own
removal in certain cases when you modify the work.) You may place
additional permissions on material, added by you to a covered work,
for which you have or can give appropriate copyright permission.
Notwithstanding any other provision of this License, for material you
add to a covered work, you may (if authorized by the copyright holders of
that material) supplement the terms of this License with terms:
a) Disclaiming warranty or limiting liability differently from the
terms of sections 15 and 16 of this License; or
b) Requiring preservation of specified reasonable legal notices or
author attributions in that material or in the Appropriate Legal
Notices displayed by works containing it; or
c) Prohibiting misrepresentation of the origin of that material, or
requiring that modified versions of such material be marked in
reasonable ways as different from the original version; or
d) Limiting the use for publicity purposes of names of licensors or
authors of the material; or
e) Declining to grant rights under trademark law for use of some
trade names, trademarks, or service marks; or
f) Requiring indemnification of licensors and authors of that
material by anyone who conveys the material (or modified versions of
it) with contractual assumptions of liability to the recipient, for
any liability that these contractual assumptions directly impose on
those licensors and authors.
All other non-permissive additional terms are considered "further
restrictions" within the meaning of section 10. If the Program as you
received it, or any part of it, contains a notice stating that it is
governed by this License along with a term that is a further
restriction, you may remove that term. If a license document contains
a further restriction but permits relicensing or conveying under this
License, you may add to a covered work material governed by the terms
of that license document, provided that the further restriction does
not survive such relicensing or conveying.
If you add terms to a covered work in accord with this section, you
must place, in the relevant source files, a statement of the
additional terms that apply to those files, or a notice indicating
where to find the applicable terms.
Additional terms, permissive or non-permissive, may be stated in the
form of a separately written license, or stated as exceptions;
the above requirements apply either way.
8. Termination.
You may not propagate or modify a covered work except as expressly
provided under this License. Any attempt otherwise to propagate or
modify it is void, and will automatically terminate your rights under
this License (including any patent licenses granted under the third
paragraph of section 11).
However, if you cease all violation of this License, then your
license from a particular copyright holder is reinstated (a)
provisionally, unless and until the copyright holder explicitly and
finally terminates your license, and (b) permanently, if the copyright
holder fails to notify you of the violation by some reasonable means
prior to 60 days after the cessation.
Moreover, your license from a particular copyright holder is
reinstated permanently if the copyright holder notifies you of the
violation by some reasonable means, this is the first time you have
received notice of violation of this License (for any work) from that
copyright holder, and you cure the violation prior to 30 days after
your receipt of the notice.
Termination of your rights under this section does not terminate the
licenses of parties who have received copies or rights from you under
this License. If your rights have been terminated and not permanently
reinstated, you do not qualify to receive new licenses for the same
material under section 10.
9. Acceptance Not Required for Having Copies.
You are not required to accept this License in order to receive or
run a copy of the Program. Ancillary propagation of a covered work
occurring solely as a consequence of using peer-to-peer transmission
to receive a copy likewise does not require acceptance. However,
nothing other than this License grants you permission to propagate or
modify any covered work. These actions infringe copyright if you do
not accept this License. Therefore, by modifying or propagating a
covered work, you indicate your acceptance of this License to do so.
10. Automatic Licensing of Downstream Recipients.
Each time you convey a covered work, the recipient automatically
receives a license from the original licensors, to run, modify and
propagate that work, subject to this License. You are not responsible
for enforcing compliance by third parties with this License.
An "entity transaction" is a transaction transferring control of an
organization, or substantially all assets of one, or subdividing an
organization, or merging organizations. If propagation of a covered
work results from an entity transaction, each party to that
transaction who receives a copy of the work also receives whatever
licenses to the work the party's predecessor in interest had or could
give under the previous paragraph, plus a right to possession of the
Corresponding Source of the work from the predecessor in interest, if
the predecessor has it or can get it with reasonable efforts.
You may not impose any further restrictions on the exercise of the
rights granted or affirmed under this License. For example, you may
not impose a license fee, royalty, or other charge for exercise of
rights granted under this License, and you may not initiate litigation
(including a cross-claim or counterclaim in a lawsuit) alleging that
any patent claim is infringed by making, using, selling, offering for
sale, or importing the Program or any portion of it.
11. Patents.
A "contributor" is a copyright holder who authorizes use under this
License of the Program or a work on which the Program is based. The
work thus licensed is called the contributor's "contributor version".
A contributor's "essential patent claims" are all patent claims
owned or controlled by the contributor, whether already acquired or
hereafter acquired, that would be infringed by some manner, permitted
by this License, of making, using, or selling its contributor version,
but do not include claims that would be infringed only as a
consequence of further modification of the contributor version. For
purposes of this definition, "control" includes the right to grant
patent sublicenses in a manner consistent with the requirements of
this License.
Each contributor grants you a non-exclusive, worldwide, royalty-free
patent license under the contributor's essential patent claims, to
make, use, sell, offer for sale, import and otherwise run, modify and
propagate the contents of its contributor version.
In the following three paragraphs, a "patent license" is any express
agreement or commitment, however denominated, not to enforce a patent
(such as an express permission to practice a patent or covenant not to
sue for patent infringement). To "grant" such a patent license to a
party means to make such an agreement or commitment not to enforce a
patent against the party.
If you convey a covered work, knowingly relying on a patent license,
and the Corresponding Source of the work is not available for anyone
to copy, free of charge and under the terms of this License, through a
publicly available network server or other readily accessible means,
then you must either (1) cause the Corresponding Source to be so
available, or (2) arrange to deprive yourself of the benefit of the
patent license for this particular work, or (3) arrange, in a manner
consistent with the requirements of this License, to extend the patent
license to downstream recipients. "Knowingly relying" means you have
actual knowledge that, but for the patent license, your conveying the
covered work in a country, or your recipient's use of the covered work
in a country, would infringe one or more identifiable patents in that
country that you have reason to believe are valid.
If, pursuant to or in connection with a single transaction or
arrangement, you convey, or propagate by procuring conveyance of, a
covered work, and grant a patent license to some of the parties
receiving the covered work authorizing them to use, propagate, modify
or convey a specific copy of the covered work, then the patent license
you grant is automatically extended to all recipients of the covered
work and works based on it.
A patent license is "discriminatory" if it does not include within
the scope of its coverage, prohibits the exercise of, or is
conditioned on the non-exercise of one or more of the rights that are
specifically granted under this License. You may not convey a covered
work if you are a party to an arrangement with a third party that is
in the business of distributing software, under which you make payment
to the third party based on the extent of your activity of conveying
the work, and under which the third party grants, to any of the
parties who would receive the covered work from you, a discriminatory
patent license (a) in connection with copies of the covered work
conveyed by you (or copies made from those copies), or (b) primarily
for and in connection with specific products or compilations that
contain the covered work, unless you entered into that arrangement,
or that patent license was granted, prior to 28 March 2007.
Nothing in this License shall be construed as excluding or limiting
any implied license or other defenses to infringement that may
otherwise be available to you under applicable patent law.
12. No Surrender of Others' Freedom.
If conditions are imposed on you (whether by court order, agreement or
otherwise) that contradict the conditions of this License, they do not
excuse you from the conditions of this License. If you cannot convey a
covered work so as to satisfy simultaneously your obligations under this
License and any other pertinent obligations, then as a consequence you may
not convey it at all. For example, if you agree to terms that obligate you
to collect a royalty for further conveying from those to whom you convey
the Program, the only way you could satisfy both those terms and this
License would be to refrain entirely from conveying the Program.
13. Use with the GNU Affero General Public License.
Notwithstanding any other provision of this License, you have
permission to link or combine any covered work with a work licensed
under version 3 of the GNU Affero General Public License into a single
combined work, and to convey the resulting work. The terms of this
License will continue to apply to the part which is the covered work,
but the special requirements of the GNU Affero General Public License,
section 13, concerning interaction through a network will apply to the
combination as such.
14. Revised Versions of this License.
The Free Software Foundation may publish revised and/or new versions of
the GNU General Public License from time to time. Such new versions will
be similar in spirit to the present version, but may differ in detail to
address new problems or concerns.
Each version is given a distinguishing version number. If the
Program specifies that a certain numbered version of the GNU General
Public License "or any later version" applies to it, you have the
option of following the terms and conditions either of that numbered
version or of any later version published by the Free Software
Foundation. If the Program does not specify a version number of the
GNU General Public License, you may choose any version ever published
by the Free Software Foundation.
If the Program specifies that a proxy can decide which future
versions of the GNU General Public License can be used, that proxy's
public statement of acceptance of a version permanently authorizes you
to choose that version for the Program.
Later license versions may give you additional or different
permissions. However, no additional obligations are imposed on any
author or copyright holder as a result of your choosing to follow a
later version.
15. Disclaimer of Warranty.
THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY
APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT
HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY
OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO,
THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM
IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF
ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
16. Limitation of Liability.
IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS
THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY
GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE
USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF
DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD
PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS),
EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF
SUCH DAMAGES.
17. Interpretation of Sections 15 and 16.
If the disclaimer of warranty and limitation of liability provided
above cannot be given local legal effect according to their terms,
reviewing courts shall apply local law that most closely approximates
an absolute waiver of all civil liability in connection with the
Program, unless a warranty or assumption of liability accompanies a
copy of the Program in return for a fee.
END OF TERMS AND CONDITIONS
How to Apply These Terms to Your New Programs
If you develop a new program, and you want it to be of the greatest
possible use to the public, the best way to achieve this is to make it
free software which everyone can redistribute and change under these terms.
To do so, attach the following notices to the program. It is safest
to attach them to the start of each source file to most effectively
state the exclusion of warranty; and each file should have at least
the "copyright" line and a pointer to where the full notice is found.
<one line to give the program's name and a brief idea of what it does.>
Copyright (C) <year> <name of author>
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 3 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, see <https://www.gnu.org/licenses/>.
Also add information on how to contact you by electronic and paper mail.
If the program does terminal interaction, make it output a short
notice like this when it starts in an interactive mode:
<program> Copyright (C) <year> <name of author>
This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
This is free software, and you are welcome to redistribute it
under certain conditions; type `show c' for details.
The hypothetical commands `show w' and `show c' should show the appropriate
parts of the General Public License. Of course, your program's commands
might be different; for a GUI interface, you would use an "about box".
You should also get your employer (if you work as a programmer) or school,
if any, to sign a "copyright disclaimer" for the program, if necessary.
For more information on this, and how to apply and follow the GNU GPL, see
<https://www.gnu.org/licenses/>.
The GNU General Public License does not permit incorporating your program
into proprietary programs. If your program is a subroutine library, you
may consider it more useful to permit linking proprietary applications with
the library. If this is what you want to do, use the GNU Lesser General
Public License instead of this License. But first, please read
<https://www.gnu.org/licenses/why-not-lgpl.html>.
@@ -1,884 +0,0 @@
# CC/iC Blender Tools (Installed in Blender)
An add-on for importing and automatically setting up materials for Character Creator 3, 4 & 5 and iClone 7 & 8 character exports.
Using Blender in the Character Creator pipeline can often feel like hitting a brick wall. Spending potentially hours having to get the import settings correct and setting up the materials often with hundreds of textures.
This add-on aims to reduce that time spent getting characters into Blender down to just a few seconds and make use of as many of the exported textures as possible so that character artists can work in the highest quality possible using Blender.
[Online Documentation](https://soupday.github.io/cc_blender_tools/index.html)
[Reallusion Forum Thread](https://forum.reallusion.com/475005/Blender-Auto-Setup)
Links
=====
[CC4/5 Blender Pipeline Tool (Installed in CC4/5)](https://github.com/soupday/CCiC-Blender-Pipeline-Plugin)
[CC3 Blender Pipeline Tool (Installed in CC3)](https://github.com/soupday/CC3-Blender-Tools-Plugin)
## Installation, Updating, Removal
### To Install
- Download the [latest release](https://github.com/soupday/cc_blender_tools/releases).
- In Blender go to menu **Edit**->**Preferences** then select **Add-ons**.
- Click the **Install** button at the top of the preferences window and navigate to where you downloaded the zip file, select the file and click **Install Add-on**.
- Activate the add-on by ticking the checkbox next to **Edit**->**Preferences** then select **Add-ons**
- The add-ons functionality is available through the **CC/iC Blender Tools** Tab in the tool menu to the right of the main viewport. Press _N_ to show the tools if they are hidden.
### To Remove
- From the menu: **Edit**->**Preferences** then select **Add-ons**
- In the search box search **All** add-ons for **"CC/iC Blender Tools"**
- Deactivate the add-on by unticking the checkbox next to **Edit**->**Preferences** then select **Add-ons**.
- Then click the **Remove** button.
### To Update
- Remove the current version of the add-on by following the remove instructions above.
- Follow the installation instructions, above, to install the new version.
## Changelog
### 2.3.4
- Add / apply physics resets physics to frame 1.
- Reset all dynamics added to cloth physics panel.
- Reset physics sets frame sync to "play all frames".
- Warning on bake physics button when not "play all frames".
- Fixes to:
- Spring rig simulation reset error when baked.
- Remove all physics error.
- Rigidbody colliders positions when character rig is not at origin.
- Physics weight paint error in Blender 5.0.
- ARKit Proxy CSV load error in Blender 5.0.
- Receive Pose/Sequence error in Blender 5.0.
- Material bake in Blender 5.0.
### 2.3.3
- Blender 5.0 update.
- Blender 5.0 API changed bone selection methods and compositor which broke just about everything ...
- Eye shader updates, Limbus region should be more accurate to CC4/5 now.
- Expression drivers for CC3 rig use expression json.
- Materials build for current render engine (Eevee / Cycles).
- Buttons activate on Materials Parameters and DataLink panels to rebuild materials if changed.
- Rigify will fallback to next best face rig if selected in unavailable.
- Widgets for face rig included in Link/Append function.
- Fix to rigidbody collider orientation.
- Import option added in preferences to reset all custom normals on import.
- On some characters, custom split normals cause dark lighting artifacts that can be cured by removing custom normals.
- Camera switch markers generated when sending camera's across DataLink.
### 2.3.2
- Shaders updated for CC5:
- Skin shaders updated to use cavity maps and dual specular blending.
- Eye & Hair shaders updated.
- *Tearline Plus* and *Eye Occlusion Plus* shaders added.
- Wrinkle map system update for MH facial profile.
- Wrinkle map displacement update for CC5 head material.
- Textures packed with compositor for faster wrinkle system setup.
- Facerig updated to use expression json bone data.
- MetaHuman profile facial expression rig added.
- Expression control constraints and limits.
- Max texture pack size option added.
- Datalink use automatic lighting option added.
### 2.3.1
- 1:1 Viseme driver fix.
- Fix to Rigify bone locks being cleared by pose reset.
- Export Rigify motion only fix.
- CC Aligned metarig finger rotation fix.
- Fix to FBX exports not adding correct scale units.
- This was causing Rigify export avatar bones to scale incorrectly in Unity.
- Removed Rigify disable IK stretch option as Rigify IK doesn't work correctly without it.
- Rigify export using CC Base naming aligns bones to original CC rig orientations.
- In particular the jaw bones and eye bones should have a more compatible alignment. i.e. Unity ConvAI.
- Exit DataLink shutdown moved from atexit to Unregister.
### 2.3.0
- Rigify:
- Meta-human like facial expression rig for Extended, Standard and Traditional facial profiles.
- Expression rig supports retargeting, datalink motion transfer and Pose/Sequences to and from CC4/iClone8.
- Options to adjust control colours, and face rig placement and attachment to character.
- Supports ActorCore, ActorScan and ActorBuild characters.
- The expression rig allows for more advanced targeting and transfer, for now:
- ARKit Proxy:
- The ARKit proxy can be used as a target for LiveLink face transfer (e.g. from Face-it add-on)
- The proxy will drive the controls on the expression rig, which in turn drives the expression shape keys and head bones on the character.
- Adjustment parameters can be used to alter expression strength, relax / exaggerate expressions, shift the directional bias towards Left/Right and Up/Down expressions.
- Rotational adjustments for head bone.
- Load CSV function to load face capture recordings in CSV format.
- In particular this also works with the output from [Face Landmark Link](https://github.com/Qaanaaq/Face_Landmark_Link/)
- DataLink:
- Motion Pose/Sequences no longer dependent on Quaternions, preserves bone rotation modes across the the rig.
- Avatar/Prop skin bones identified by iClone ID, for accurate transfer and retargeting.
- Fix to incorrect prop mesh positioning due to bugged exports from iClone.
- Not all prop hierarchies can be exported exactly to Blender, sometimes they must have their transforms or scale reset in iClone.
- Which now allows for Pose/Sequence transfer of Prop animations from Blender back to iClone.
- Import & Transfer of lights and cameras with animations, including pose and sequences.
- Light & Camera sequence can be send back to iClone via Sequence transfer.
- (Must already exist in iClone for this to work)
- Lights can use IES and light cookie textures (Cycles Only)
- Spring Rigs:
- Hair spring bone generation fixes for Blender 4.4.
- Fix to remove and rebuild Rigify spring bone control rigs and simulations.
- Materials:
- Fix to incorrect embedded alpha channels from FBX importer.
- Fix to embedded alpha channel custom UV mappings.
- Emission rework, as multiplier on base color, with expanded strength.
- Reflection surface supported as clearcoat.
- Default (no texture) roughness and specular value fixes for Pbr and Tra shaders.
- General Fixes for Blender versions 3.4 - 4.4
- Wrinkle masks (local library images) packed into blend file.
- Export material fixes when image missing from texture image node.
- Export rigified error fixes.
- Convert to humanoid export json avatar type fix.
- Fix to Rigify export blendshapes on Rigify face rig and No face rig.
### 2.2.5
- Fix to Blender 4.1 material refraction setting.
- Rigify setup defaults rig to zero IK stretch.
- Fix to drivers trying build on OBJ / Morph imports.
- Some Bake streamlining and Baked GLTF export button.
- Datalink remote file transfer, to and from remote CC/iClone.
- Wrinkle Region ALL, driver update fix.
- Scene Tools - Eevee setup button.
- Blender 4.4 action slot fixes.
- Fix to Blender 4.4 proportion edit crash when removing all shape keys.
### 2.2.4
- Rigify face rig fallback to envelope weights if auto weights fail.
- DataLink Import motion will optionally (confirmation dialog) import motion to active character if no matching character.
- Rigify head turn expression driver corrected.
- Fixed collision objects being included in export.
- Fixed displacement strength export.
### 2.2.3
- Wrinkle map region strength controls added.
- Nose crease wrinkle maps added to Mouth_Smile_* expressions.
- Export Bake:
- Fixes and alpha fixes in Blender 4.3
- Iris Brightness adjustments removed when baking.
### 2.2.2
- Teeth and tongue added to bone / expression drivers.
- Meta-rig bone alignment options added the Basic rig panel.
- DataLink pose functions no longer break expression drivers.
### 2.2.1
- Material and Lighting fixes for Blender 4.3.
- When exporting or sending Rigified animations: IK stretch is now disabled in the rig.
- This should help with limb alignment problems on other platforms.
- Rigify Metarig bone rolls are aligned exactly to the CC/iC source bones now.
- This can be disabled in the preferences (or advanced settings) to use the original Metarig bone roll alignments.
- DataLink Send Avatar will ask to overwrite (or cancel transfer) any existing *same* character.
- Generic import option to disable auto-conversion of materials.
- Fix to import hanging when no characters in FBX.
- Rebuild drivers also rebuilds Rigify shape key drivers.
- DataLink transfer Sequence and Pose actions separately labelled "Sequence" and "Pose" no longer just "DataLink" for both.
- Operations that use object or mesh duplicates no longer duplicate the actions on the objects.
### 2.2.0
- Character Management functions - CCiC Create > Character Management:
- Transfer weights supports split body meshes.
- Voxel head diffuse skinning (if installed) button for selected character meshes.
- Clean Empty Data: Removes empty shape keys and empty vertex groups from the character meshes.
- Blend Body Weights: Blends body vertex weights with existing vertex weights on selected objects based on distance from the surface of the body, to correct vertex weighting for clothing items that don't conform correctly to the body, e.g. from Voxel Heat Diffusion weights or from Daz original weights.
- New scene presets added:
- scene view transform and world background strength controls added.
### 2.1.10
- Sets up Rigidbody physics when _appending_ a character from another library blend file via the Append button in the import panel.
- Note: Rigidbody physics does *not* work with linked character _overrides_.
- Export non-standard character fix.
- Support for split meshes when exporting, rebuilding materials, physics & rigging:
- Any mesh separated or duplicated from a source character mesh will be considered part of the character and an extension of that object.
- For split body meshes, the mesh with the head will be used as the source for facial expression drivers and wrinkle map drivers.
- Note: Split *body* meshes will _not_ import back into CC4 as standard CC3+ characters.
- Added buttons to rebuild and to remove just the expression and bone drivers to the Character Build Settings panel.
### 2.1.9
- Maintain operator context for scene operations.
- Align to view distance fix.
### 2.1.8
- Fixes to view 3d shading context in 4.2.
- Imported objects, materials, images and actions detection no longer uses tags.
- Added more checks to skip null material slots.
### 2.1.7
- Sync lights includes Scene IBL from CC/iC visual settings.
- Export to CC3/4 button fixed.
- Added extra checks for AccuRig / ActorScan when unknown humanoids detected.
### 2.1.6
- Import supports multiple file selection.
- To import multiple objects from a folder, press shift + select the FBX files in the importer file selection window.
- Fixes for blender version before 4.0:
- Replace mesh OBJ export.
- Rigging bone layer assignment.
- Property collection clear.
- Rigging widget control scale.
### 2.1.5
- Proportion edit should work on all character types (except Rigified)
- Sync lights and send pose should work in all modes and keeps the active mode.
- Lighting brightness adjust slider.
- Some GameBase detection fixes.
- GameBase skin roughness tweaks.
- Fix for vertex color sampling when json data is missing.
### 2.1.4
- DataLink receive Update / Replace function, to replace whole characters or selected parts.
- Fix to character validation and clean-up.
### 2.1.3
- Motion Set UI errors fixed.
- Eevee-Next SSR Eyes fixes.
- Iris brightness render settings for Eevee & Cycles.
- Bake fixes for Blender 4.2 refractive eyes.
- Export-Bake warnings when not build for Eevee.
- Lighting tweaks.
### 2.1.2
- Blender 4.2 lighting settings fixes and adjustments.
- Eevee-Next Raytracing, shadows and shadow jitter enabled on render settings and lights.
- Blender 4.0+ lighting presets use AgX.
- Eevee & Cycles global material options.
- Control of SSS weights, roughnes power and normals for various material types.
### 2.1.1
- Fix UDIM flattening on proportion editing and sculpt base mesh transfer.
- Lighting sync area correction.
- DataLink plugin version compatibility check.
### 2.1.0
- Motion Sets:
- Action name remapping and retargetting overhaul.
- Motion set functions: Load/Push/Clear/Select/Rename/Delete
- Motion prefix and use fake user option added for all animation retargeting and import.
- Motion set filter and motion set info function.
- NLA Tools:
- NLA Bake functions moved to NLA editor panel.
- Motion set panel in rigging and NLA editor and DataLink.
- NLA strip alignment and sizing utility functions.
- Fixes:
- Duplicating character no longer duplicates actions.
- Store object state checks objects/materials exist.
- Positioning fixes with rigify
### 2.0.9
- DataLink:
- DataLink main loop stability improvements.
- Live sequence back to CC4/iClone takes facial expression bone rotations into account.
- Live Sequence stop button.
- Replace mesh function: Quickly send (non topology changing) mesh alterations back to CC4.
- Update material & texture function: Send selected material data and textures back to CC4.
- Sync lighting recalculations.
- Export:
- Restores armature and object states after export.
- Fix to baking custom Diffuse Map channel.
- Fix to Blender 3.4-3.6 Eevee subsurface color.
- Material Parameter controls disabled for linked characters (unless library override).
### 2.0.8
- Import/Export:
- Fix to Update Unity Project being greyed out after saving as Blend file.
- Fix to export non-standard character.
- Fix to export non-standard ARP rigged character.
- (Blender 4.0+) Fix to reverting object and material name changes on export and other force name changes.
- Generic character/prop import expanded to support USD/USDZ.
- Rigify export now have choice of naming system:
- Metarig names (without Root bone) for exporting animations back into CC/iC.
- CC Base names (with Root bone) for exporting to Unity Auto-setup.
- Rigified characters/motion exports now generate custom HIK profile which can be used to import/convert Rigified motion exports into CC/iC.
- DataLink Rigified characters (optionally) disable tweak bones as they are not compatible with CC/iC animation.
- Scene tools:
- Scene lighting presets overhauled.
- Added function to align any object to view location and orientation (useful for placing lights and cameras).
- Added function to add a camera at current view location and orientation.
- Added function to setup a main face tracking camera centered on character's head.
- Added function to convert current view studio lighting into world lighting node setup.
- Rigify:
- Fix to GameBase detection.
- Fix to AccuRig generation code not being recognized as valid Rigify target.
- Fix to support Mixamorigs with suffix numbers in retargeting.
- Auto-retarget toggle added to automatically retarget any animation on character when using Quick Rigify.
- Shaders:
- Skin, eye and hair shaders updated to use Blender 4.0+ Random Walk (Skin) Subsurface Scattering.
- Displacement map (if present) will be used on skin materials for bump and mesh displacement.
- Cycles subsurface calculations and parameters tweaked.
- Separate cycles modifiers for Blender 3.4-3.6 and 4.0+.
- Eye Sclera color tint added.
- Cycles Tearline shader reworked.
- Character Management
- Character edit function added.
- Character duplicate function added (duplicates character objects and meta-data so can be used and configured independently).
- Character tools (select/rigify/convert/delete/duplicate), also sub-panel in DataLink.
- Convert to accessory fix.
- DataLink:
- Added Receiving prop posing and animation live sequence.
- Added custom prop rig control bones when sending through datalink.
- Added Direct Motion Transfer from CC/iC (automatic motion export->import).
- Added "Go iC" button to send (just props for now) back to iClone.
### 2.0.7
- Attempts to restore datalink when reloading linked blend file.
- All returning datalink operators will attempt to first reconnect if not connected.
- Facial expressions included in datalink send pose and sequence. (But not visemes)
- Currently certain expression bone movements are conflicting with existing bone movements.
- You may wish to avoid the Head_Turn expressions as a consequence.
- Character Proportion editing mode added to CC/iC Create panels.
- Spring bone hair binding will add an armature modifier for the hair object if absent, to allow binding for newly created hair mashes.
- Scale body weights now acts on the normalized existing hair weights.
### 2.0.6
- Restored Rigify retarget limb correction utilities.
- Fix to Blender 4.1 import crash caused by 4.1 removing auto-smoothed normals.
### 2.0.5
- Fix to converting generic objects to props.
- Fix to baking value textures back to CC4 when exporting converted props and humanoids.
- DataLink data send rate synchronization improvements.
- Rigify retarget and NLA bake options to bake to FK/IK/Both.
- Rig FK/IK mode set appropriately, unchanged when baking to 'Both'.
- Quick FK/IK switch button added to rigify mini-panel.
- Send Rigified pose or sequence fix.
- Rigify Jaw alignment changed to -Z.
### 2.0.4
- Linking/Appending:
- Added linking/append functions to auto-link to characters in blend files with full character data and functionality.
- Added connect function to re-build character data for linked/appended characters.
- Added custom properties to armatures/meshes and materials to aid re-connection of character data.
- Rebuilding materials will add this custom data to existing characters.
- Auto-linked/Re-connected characters can use full add-on functionality i.e. rigging, retargeting, exporting, rebuilding materials, etc...
- Rigify:
- DataLink pose retargeting teeth position fix.
- Eye bone and jaw bone alignments corrected.
- Face rig jaw constraints adjusted for less lip deformation.
- Parallax eye shader AO fix.
- Basic materials SSS fixes.
- Importing a bad or incompatible mesh should fail more gracefully.
### 2.0.3
- DataLink:
- Lighting and Camera sync.
- Send Character (Go-CC) back to CC4.
- Facial expressions and Visemes transferred in the pose and animation sequencing.
- Animation sequence now writes to rig and shape-key action tracks directly using low level fast keyframing, all at once at the end. Which is much faster, for both native rig and Rigify rig.
- Sequence rate matching so CC4 doesn't get too far ahead of itself.
- Supports Morph editing and mesh updating for sending back morph OBJ for automatic morph slider creation in CC4.
- Subsurface Recalculations (you may need to reset the preferences in the add-on prefs)
- Export of Hue, Sat, Brightness params to CC4.
- Fixed SSS material detection.
- Fixed ActorBuild generation detection and export.
- HIK and facial profiles copied with character export (if generated by CC4)
- OBJ import/export fix for Blender 4.0.
- OBJ import now supports full materials.
- Fixed T-pose orientation when exporting Rigified animation.
### 2.0.2
- Correction to malformed json texture paths when exporting character from CC4 directly to the root of a drive.
- Disabled image search on FBX importer, should import a little faster now.
### 2.0.1
- VRM import fixes.
- VRM to CC4 export generates HIK profile for auto characterization.
- Rigify fixes:
- Face bone roll axis corrections.
- Tongue bone meta-rig positioning corrections.
- Teeth bone retargeting corrections.
### 2.0.0
- Blender 4.0 support.
- WIP Experimental DataLink added:
- Currently in alpha stages, more a proof of concept at the moment.
- Bake add-on updated and merged into this project.
### 1.6.1
- Object Management:
- Generic material conversion better detects AO maps in Blender 3+
- Transfer vertex weights with posed armature fix.
- Empty transform hierarchy to Prop conversion puts bones in the right correct places.
- Exporting rigified animations with parented armatures now excludes those armatures from export.
### 1.6.0.4
- Fixed bake rigify retarget not assigning action to rig after baking
- Removes facial expression bone drivers on Rigifying (caused cyclic dependencies)
- Re-importing/rebuilding materials on a character will reload any texture images that are being re-used from existing or previous imports, just in case they have been changed on disk.
- Except when the image has been modified by the user and has not yet been saved.
- NLA Bake fix.
- Fix Generic character import.
- Spring rig panels show if character is invalid for spring rigging.
- Expression drivers for bones only apply to CC4 Ext and Std profiles
- Bone drivers for direct visemes Ah and Oh added.
- Viseme bone drivers now excluded when Jaw drivers are disabled.
- Export Rigified motion and Unity T-Pose generation fix.
### 1.6.0
- Rigifying character keeps meta-rig and allows for Re-Rigifying the control rig from the meta-rig.
- Useful for re-aligning bones, re-positioning face rig, etc...
- First draft of (optional) Dual Specular skin shader (Eevee & Cycles) with specular micro details.
- Added build options to generate drivers for Jaw, Eyes and Head bones from facial expression shape keys.
- Added build option to generate drivers for all expression shape keys driven from the body mesh shape keys.
- Which means only the expressions on the body mesh need to be updated/animated.
- Fix to turn off vertex colours in hair materials when hair mesh has blank vertex colour data (i.e. all zero).
- Facial Expression shape key value range expanded to -1.5 - 1.5 (except for eye look shape keys)
- Characters exported with Mouth Open as Morph, now correctly detects the body mesh.
- Fix to support sphere colliders in collisions shapes.
- Some additional lighting arrangements: Authority and Blur Warm.
### 1.5.8.5
- Fixes:
- Fix to empty transforms or deleted objects in export.
- Fix to transfer vertex weights leaving working copies behind.
- Some object management UI corrections.
- Fix to bake path when exporting character converted from generic with materials added after conversion.
- Fix to replace selected bones from hair cards.
- Fix cloth settings error in detect physics.
- Fix CC4 spring bones creation.
- Fix to exports of objects which originally had duplicate object names.
- Fix to import collider parenting crash when using Blender versions before 3.5
- Fix to UI panel in 2.93.
- Fix to collider generation in 2.93
- Fix to collider generation when Rigifying when posed.
### 1.5.8
- Spring Bones:
- Blender spring bone rigid body simulation added for spring bone hair rigs.
- Spring bone simulation controls.
- Hair spring bone chain renaming.
- Bone generation truncate and smoothing parameters.
- Added support for not quad grid poly mesh hair cards, should work with any hair mesh.
- Rigid body colliders for the spring bones that use the collision shapes from character creator.
- Rigify update for spring bone system.
- FK, IK and tweak bones for spring rigs.
- Rigify and spring bone UI updates.
- Baking spring bone simulation and animation into new animations
- Exporting rigified spring bone characters and animations, including the rigid body simulation as animation.
- Cloth Physics overhaul:
- Physics UI update.
- Better mapping of PhysX weight map to blender vertex pin weights.
- Physics presets updates, mass, tension and bending to better simulate the cloth type and work more consistently with external forces.
- Cloth physics preset detection on import.
- UI tools for point cache baking.
- Fixes to weightmap paint mode resetting texture.
- Browse button for painted weightmap, so you can find it.
- Weightmap assignment fix for materials with the same base name.
- Sculpt / Mesh:
- Character geometry transfer to shape-keys function.
- Character Objects:
- Add object to character (from another character) now copies the object into the new character.
- Transfer weight maps now works when posed to effectively parent in place target mesh.
- Other:
- Fix to eye close slider.
### 1.5.7
- Hair bone de-duplication.
- Bones from grease pencil lines or hair card generation now replaces (matching) existing bones.
- Grease Pencil lines generated only from active grease pencil layer, allowing for better organization.
- Added some color space fallbacks when using different color space configurations.
### 1.5.6
- Relative wrinkle strengths for individual wrinkle maps implemented.
- Overall wrinkle strength and curve power slider added the head material parameters.
- Competing wrinkle maps now use additive blending to solve overlap.
- Brow correction added for brow raise + brow compress wrinkles.
- Generated images not yet saves are autosaved for export (so they get included).
- Add custom bone function for hair rigging.
### 1.5.5
- Flow maps added to wrinkle map system.
- Better texture limiting for the head material.
- Fix to export crash when a texture field is missing in the JSON data.
- Corrupted JSON data detection and error report on import/build.
- In some cases resetting the collision shapes in CC4 will fix corrupted JSON data.
### 1.5.4
- Wrinkle Map system implemented.
- Characters with wrinkle maps will setup wrinkle shaders in the head material automatically.
- Preferences for Build Wrinkle Maps.
- OpenColorIO ACES color space support.
- Preferences for ACES color space overrides.
- Optional Texture Packing and Texture limits added to reduce number of textures in imported materals.
- Some systems can have very low texture limits (i.e. only 8 on some OSX systems) this can help import full CC4 characters.
- Preferences for pack and/or limit textures.
- Body sculpting updated:
- All sculpting modes work on a copy of the character.
- Multi-res applied base shape copied back to original character in a way that preserves existing shape keys.
- AO Map added to baking, layers and export.
- Additional strength, definition and mix mode controls added to layer ui.
- Spring Bone Hair Rigging (Cloth Rigging to follow)
- Added initial Hair curve extraction from hair cards.
- Spring bone hair generation from selected hair cards or greased pencil lines on surface.
- Hair card weight binding fine tuning controls.
- Hair cards are weighted individualy to neighboring bones and uniformly across to avoid lateral stretch and behaves more like cloth physics implementations.
- Spring bone generation from grease pencil lines.
- Fixes:
- Some extra transparency material detection.
- Fix to hand & finger bone roll alignment when bind pose has arms and hands at a steep downward angle.
- Fix to partial material name matching errors from ActorCore and AccuRig.
- Export bake socket fix for Blender 3.4+.
- Shapekey locks will be disabled and all shapekeys reset to zero on character export.
### 1.5.3
- Fix to retarget baking in Blender 3.4 not baking pose bones to rigify armature action.
- Fix to Rigify motion export bone root name.
### 1.5.2
- Rigify IK-FK influence controls replicated in Rigging panel.
- Fix to material setup error caused by missing normal map data.
### 1.5.1
- Fix to Generic character export.
- Fix to Generic converted character export.
### 1.5.0
- Rigify export mesh and/or animation overhaul.
- Smoothing groups added to export file dialog options.
- Support for CC4 Plugin, facial expression and viseme data export.
- Fix to legacy hair detection & scalp detection.
- Very slight subsurface added to scalp to prevent the dark/blueish artifacts on skin.
- Fix to bump maps connecting to normal sockets.
- Eye limbus darkness recalculated.
- Initial attempt at exporting Blender 3.3 Curve hair on characters via Alembic export.
### 1.4.9
- Fix to embedded image correction and image filepath comparisons.
- Fix to basic material texture loading.
- Convert to Accessory function added to Object Management.
### 1.4.8
- Adding existing RL material to a new character import will copy the material data.
### 1.4.7
- Match existing materials (for AccuRig imports) button added to Create tab and Rigify info pane.
- Attempts to assign existing materials to the AccuRig import that match the original export to AccurRig.
- For a Blender > AccuRig / ActorCore > Blender round trip workflow.
- Eye occlusion (Eevee) color darkened.
- Hair shader (Eevee) Specular blend added (Base surface Specularity -> Anisotropic Specularity)
### 1.4.6
- Fix for palm control location with less than 5 fingers.
### 1.4.5
- Fix for ActorCore / AccuRig import detection.
### 1.4.4
- Missing bone chains from CC3 rig will hide corresponding control rig bones (e.g. missing fingers)
- Export texture baking no longer duplicates baking on same materials across different objects.
- Export fix to CC3/4 when material name starts with a digit.
- Fix to import/convert of multiple generic GLTF/GLB props or characters.
- Fix to exporting Roughness and Metallic texture strength (should always be 100).
### 1.4.3
- Export bake maps now correctly sets bit depth and no alpha channel. Opacity texture bakes should work now.
- Convert generic character from objects to props added.
- Auto converts generic imports where possible, when using the import character button.
- Texture and material de-duplication (optional)
- ActorCore rigify accessory fix.
- CC/iC Create tab and panels added.
- Physics & Object Management moved to CC/iC Create.
- Multi-res sculpt & bake helper tools panels added.
### 1.4.2
- Separate Iris & Sclera emission colors for parallax eye shader.
- Displacement calculation reworked as it behaves differently in Blender 3.2.1 (was causing triangulation artifacts)
### 1.4.1
- Fixes
- Export T-pose action created correctly when character bind pose is already a T-pose.
- Tongue subsurface scatter parameter fixes in UI.
- Hair material UI corrected in Blender 3.2.1 (caused by renamed icon enums)
- Accessory bones now copy to rigify rig.
### 1.4.0
- Physics JSON changes and additions export.
- Unity T-pose export fix.
- Body Collision Mesh fix.
- Unity export logic fixes.
- Resize weightmap function.
- Increment/decrement paint strenth weightmap buttons.
- Image path writeback fix.
### 1.3.9
- Full Json data generation added.
- Rigify Toe IK Stretch Fix for Blender 3.1+
- Convert to Non-standard function.
- Convert from Generic character to Reallusion material based non-standard character.
- Export baking fixes and default PBR export support added.
### 1.3.8
- UI naming update.
- Repository rename to **cc_blender_tools** (title **CC/iC Blender Tools**), old repo links still apply.
- Initial support for exporting _any_ non-standard characters to CC4.
- Character should be aligned -Y forward, Z up for correct bone translation in CC4.
- Json data constructed on export to try and reconstruct materials using the **CC4 Blender Tools Plugin**.
- Materials must be based on Principled BSDF otherwise only texture name matching is possible.
- non-standard characters rigged with Auto-Rig Pro will (try to) invoke the ARP export operator (if installed)
- ARP export operator cleans up the rig and leaves only the relevent deformation bones.
- Import functions expanded to allow import from FBX, GLTF and VRM for non-standard characters.
- These are **not** considered to be CC/iC characters and have no material parameter, rigging or physics options.
### 1.3.7
- Iris Color and Iris Cloudy Color added.
- Tool bar tab renamed from **CC3** to **CC/iC**
- Some UI button name changes.
### 1.3.6
- Rigify
- Finger roll alignment fixed. All fingers now have exactly the same local bend axis.
- Disables all physics modifiers non-contributing armatures and meshes during retarget baking to speed it up a bit.
- Physics:
- Low poly (1/8th) Collision Body mesh created from decimating a copy of the Body mesh and removing the eyelashes.
- Hair would easily get trapped in the eyelashes and a lower poly collision mesh should speed up the cloth simulation.
- PhysX weight maps normalized, provides a more consistent and controllable simulation across different weight maps.
- Tweaked some cloth simulation parameters.
- Smart Hair meshes in particular should simulate better now.
- Unity:
- Added animation export options (Actions or Strips)
### 1.3.5
- Fix to shape-key action name matching.
### 1.3.4
- Rigify Retargeting:
- GameBase animation retargeting to CC Rigified Rig.
- (Experimental) Mixamo animation retargeting to CC Rigified Rig.
- Facial Expression Shape-key animation retargeting from source animation to CC character.
- Shape-key NLA baking added (optional).
- Materials:
- Diffuse Color, Hue, Saturation & Brightness parameters for Skin and Hair materials.
- Exporting:
- **Export to CC3** button renamed to **Export Morph Target** when editing OBJ imports with ObjKey.
- When **Export** button is disabled: The reason is displayed under the button.
- Export button Key check is overridable in the add-on preferences.
- Other Changes:
- Fix to ActorCore character (ActorCore website download) animation retargeting.
- Basic face rig generated from full face rig and has the same jaw & eye controls and parameters.
- Jaw pivot retargeting fixes.
- Palm bones no longer affect deformation or generate vertex weights.
- Crash fixes importing characters with very short names.
### 1.3.3
- CC/iC/ActorCore animation retargeting to CC Rigified Rig.
- Preview and Bake functions for retargeted animations.
- Arms, Legs, Heel & Height adjustments.
- Source armature and action selector, filtering by Armature name.
- Animation importer, import multiple animations in one go.
- Renames actions and armatures to match file names.
- Optionally removes meshes & materials.
- Bake control rig & retargeted animations to Unity.
- Export Rigified character to Unity.
- Basic face rig drivers for __Eye Look__ blend shapes. (If character has ExPlus blend shapes)
- GameBase to Rigify support.
### 1.3.2
- Face rig Automatic Weight failure detection and (some) auto-correction.
- Support for Voxel Heat Diffuse Skinning add-on for voxel weight mapping of the face rig.
### 1.3.1
- Optional two stage Rigify process to allow modifications to the meta-rig.
- Optional basic face rigging.
- Support for ActorCore character Rigging. (Only with basic face rigging)
- Support for G3 character generation, which includes Toon base characters. (i.e. the old CC3 base)
- Support for rigging G3Plus, G3, ActorCore characters exported from iClone. (iClone exports with no bone name prefix)
- Some Control Widget fixes and scaling.
- Better character Generation check on import for exports without JSON data.
### 1.3.0
- Rigify function added.
- Character objects are bound to the Rigify control rig.
- Vertex weights are remapped to Rigify deformation bones.
### 1.2.2
- Object Management panel
- All round-trip/Export object management functions moved to this panel.
- Including checks and clean up.
- Normalize weights function added.
- Bake/Combine Bump maps into Normal maps function added to Material Parameters panel.
- Highlight shift added to Eevee hair shader.
### 1.2.1
- Some fixes to exporting additional objects with character.
- Added a 'Check Export' function to identify potential problems with the export.
### 1.2.0
- Added initial export to Unity project function.
- Round-trip/Unity export additions and fixes.
- Added baking raw metallic and roughness values into textures, when no textures connected, when exporting.
- Modifiers removed from eye parts when exporting that prevented exporting blend shapes.
- Modifiers applied on FBX exports.
- Armature set to Pose mode on export, otherwise skeleton/bind-pose imports to CC3 incorrectly.
- Some file path fixes when baking new textures with exports.
- Fixed skin micro-smoothness calculation causing smoothness seams between head and body materials.
- Fixed UI updating wrong unmasked micro smoothness parameter.
- Blender 3.0+ subsurface scattering method set to Christensen-Burley, rather than Random-Walk, which does not work well with hair transparencies.
- Color mixers for actor core/color masked materials prevented from generating negative/zero color values which could affect diffuse lighting.
### 1.1.9
- Fixed PBR eye material crash.
### 1.1.8
- Texture Channel Mixer added, primarily for alteration of Actor Core characters with RGB and Color ID masks, but can be used with all CC3 based materials.
- Cycles hair anisotropy reworked.
- Cycles parameter tweaks:
- Brighter iris settings for SSR eyes.
- Hair subsurface reduced (May want to turn it off completely, there can be too many artifacts generated in the hair.)
- Skin subsurface slightly increased.
### 1.1.7
- Cornea material detection crash fix.
### 1.1.6
- Baking add-on compatibility update.
### 1.1.5
- Character and Object operators (**Character Settings** Panel):
- Add object to character, with parenting and armature modifier.
- Convert and add materials to character to use with material parameters and export write back.
- Transfer body vertex weights to object.
- Clean up object and material character data.
- Export back to CC3 will include Texture & Json data for any new objects added with these operators.
- Fix to Subsurface scattering for Blender 3.0 Cycles renders.
- Cycles adjustment settings added to preferences for user fine tuning of subsurface material parameters for Cycles.
### 1.1.4
- Added more support for exporting from characters with embedded textures.
### 1.1.3
- Baking fix for Blender versions 2.83 - 2.91
- (Experimental) Added operators to add new objects to the character data.
- See Character Settings panel with new object selected.
### 1.1.2
- Export accessory button no longer locked to character.
- Some import/export folder logic changed to try and cope with project folder & files being moved.
- Added custom texture node baking on export to CC3, including baking bump maps into normal maps on export.
- If nodes are used to modify (or replace) texture inputs to material shaders, those nodes can be baked into the texture channel on export. This assumes the mesh has a valid UV map.
- Bump maps can be baked into normal channels. Typically CC3 will only allow Normal maps OR bump maps for a material, not both, so an option has been added to combine them into just the normal map.
### 1.1.1
- Fix to crash from multiple character imports from iClone.
- Note: Exporting multiple characters in one Fbx from iClone to Blender is not fully supported.
- Characters should be exported individually.
- If multiple characters are detected then a warning pop-up will be displayed.
### 1.1.0
- Updated export function to generate compatible Fbx file for CC3 re-import with FbxKey and to write back json material parameter and texture information. To be used in tandem with [Blender Importer Plugin for CC3 3.44](https://github.com/soupday/CC3-Blender-Tools-Plugin) for full round-trip character editing in Blender.
- Import/Export Interface simplified.
- If character has Fbxkey then character is setup for editing. (i.e. Shapekeys locked to basis)
- Otherwise (character is posed or has animation) character is setup for rendering.
- Only Fbxkey character can be exported back to CC3.
- Optional Json and Texture write back for exports.
- Optional teeth rotation fix that affects some older generated characters when importing back into CC3.
- Bake on export function added to bake custom material nodes connected to master shader's texture map sockets into textures to include when re-importing back into CC3.
- Additional objects can be selected for exporting with the character, but must be properly parented and weighted with an armature modifier. Otherwise CC3 will ignore them.
- Some property and parameter fixes.
### 1.0.3
- First attempt at a single material parallax eye shader added. Which does not use SSR or transparency and thus can receive full shadows and subsurface scattering in Eevee.
### 1.0.2
- Fixed Eevee subsurface scattering settings:
- Reworked shaders to allow for direct application of subsurface radius to Principled BSDF nodes.
- Only the default values in the subsurface radius socket are used in Eevee rendering.
- As such, Eevee does not support inputs to subsurface radius and so shader and parameter code needed to be re-written to accomodate this.
- Cycles unaffected by this.
- Fixed node group upgrade code that incorrectly renamed existing node groups and did not properly replace old shader/node groups with new ones in existing blend files.
### 1.0.1
- Added render target preferences setting for Cycles and Eevee.
- Added cycles specific shaders for hair, tear-line and eye occlusion.
### 1.0.0
- Moved all shaders over to new shader model.
- Streamlined parameter and shader code to be data driven, rather than hard coded.
- Character, Object and Material parameters now stored independently for each character import.
- Json data parser to automatically set up all shader parameters.
### 0.7.4
- New eye shader model.
### 0.7.3
- New teeth and tongue shader model.
### 0.7.2
- New skin and head shader model.
### 0.7.1
- Back ported the more advanced Eye Occlusion shader from the Unity HDRP setup.
- Added displacement modifiers & parameters to Eye Occlusion and Tearline objects.
- Initial support for ActorCore models type C/D/D+.
### 0.6.3
- Fixed 'Export as accessory' correctly exporting as .obj when character was imported from an .obj file.
(And not exporting as .fbx with the wrong file extension)
### 0.6.2
- Lighting setups set Cycles transparent bounces set to 50 to accomodate Smart Hair mesh density.
- Lighting setups do not delete existing lights or camera, but they will hide them.
- Material setup now properly detects Game Base objects (i.e. Converted to Game Base in CC3 before exporting to Blender).
- Each material now maintains it's own set of parameters.
- Updating material parameters in **linked** mode will change the same parameters on all materials of the same type.
- Updating parameters in **selected** only mode will only change the parameter for that one material.
### 0.5.2
- Applies IOR shader input setting when building materials.
- Exposed some build preferences in the Build Settings panel.
- Enabled SSR and refraction when importing with refractive eyes.
- Auto updater now targets Main branch for current build.
### 0.5.1
- Fixed problem appending duplicate displacment map images.
- Fixed not removing eye displacement modifiers on rebuild.
- Added eye occlusion hardness parameter.
### 0.5.0
- Refractive Eyes:
- Iris refractive transmission with depth control and pupil size parameters.
- Limbus parameters.
- IOR and refractive depth parameters.
- Blood vessel and iris bump normals.
- Option in preferences to generate old eyes instead.
- Skin roughness power parameter added.
### 0.4.3
- Corrected an issue where the opacity maps were ignored in favour of diffuse alpha channels.
- Added opacity parameters for hair, scalp and eyelashes.
- Added roughness and specular parameters for eyelashes.
- Fixed a crash calling the import operator from from script.
- Added auto update scripts.
### 0.4.1
- Full smart hair support.
- Hair and scalp hints expanded to cover the smart hair system and moved to the preferences.
- Parameter changes update only that parameter in the imported or selected objects materials.
- Fake anisotropic highlights add to smart hair shader. (Can disable in the preferences.)
- Fake bump normals can be generated from the diffuse map if there is no normal or bump map present. (Can disable in the preferences.)
- Animation ranges only changed if physics enabled.
- Build settings and material parameters separated into their own interface panels.
- Build settings now applicable by material and the object and material build types as detected by the add-on are exposed and editable so you can fix them if it gets them wrong.
- Material parameters are context sensitive to the currently active object and material.
- Material parameters grouped into sections.
- Detects smart hair material or normal hair material and only shows relevant parameters.
- Option in preferences to gamma correct smart hair colours so they behave more like the colours in CC3.
### 0.3.0
- Fix to hair mesh detection with new smart hair system.
### 0.2.2 Alpha
- When no texture maps are present for an advanced node group, does not generate the node group.
- When exporting morph characters with .fbxkey or .objkey files, the key file is copied along with the export.
- Function added to reset preferences to default values.
- Alpha blend settings and back face culling settings can be applied to materials in the object now.
- Option to apply alpha blend settings to whole object(s) or just active materal.
- Remembers the applied alpha blend settings and re-applies when rebuilding materials.
- Option to pick Scalp Material.
- Only scans once on import for hair object and scalp material, so it can be cleared if it gets it wrong and wont keep putting it back.
- FBX import keeps track of the objects as well as the armature in case the armature is replaced.
- Physics support added:
- Uses the physX weight maps to auto-generate vertex pin weights for cloth/hair physics (Optional)
- Automatically sets up cloth/hair physics modifiers (Optional)
- Physics cloth presets can be applied to the selected object(s) and are remembered with rebuilding materials.
- Weightmaps can be added/removed to the individual materials of the objects.
- Weight map painting added.
- Saving of modified weight maps and Deleting weight map functions added.
Binary file not shown.
Binary file not shown.
@@ -1,297 +0,0 @@
# Copyright (C) 2021 Victor Soupday
# This file is part of CC/iC Blender Tools <https://github.com/soupday/cc_blender_tools>
#
# CC/iC Blender Tools 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 3 of the License, or
# (at your option) any later version.
#
# CC/iC Blender Tools 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 CC/iC Blender Tools. If not, see <https://www.gnu.org/licenses/>.
if "bpy" in locals():
import importlib
importlib.reload(addon_updater_ops)
importlib.reload(preferences)
importlib.reload(vars)
importlib.reload(params)
importlib.reload(utils)
importlib.reload(lib)
importlib.reload(cc)
importlib.reload(jsonutils)
importlib.reload(nodeutils)
importlib.reload(imageutils)
importlib.reload(channel_mixer)
importlib.reload(materials)
importlib.reload(characters)
importlib.reload(hik)
importlib.reload(meshutils)
importlib.reload(modifiers)
importlib.reload(shaders)
importlib.reload(basic)
importlib.reload(physics)
importlib.reload(bake)
importlib.reload(panels)
importlib.reload(properties)
importlib.reload(scene)
importlib.reload(exporter)
importlib.reload(importer)
importlib.reload(geom)
importlib.reload(bones)
importlib.reload(rigidbody)
importlib.reload(springbones)
importlib.reload(drivers)
importlib.reload(wrinkle)
importlib.reload(facerig_data)
importlib.reload(facerig)
importlib.reload(rigify_mapping_data)
importlib.reload(rigging)
importlib.reload(rigutils)
importlib.reload(sculpting)
importlib.reload(hair)
importlib.reload(colorspace)
importlib.reload(world)
importlib.reload(normal)
importlib.reload(link)
importlib.reload(proportion)
importlib.reload(iconutils)
importlib.reload(rlx)
import bpy
from . import addon_updater_ops
from . import preferences
from . import vars
from . import params
from . import utils
from . import lib
from . import cc
from . import jsonutils
from . import nodeutils
from . import imageutils
from . import channel_mixer
from . import materials
from . import characters
from . import hik
from . import meshutils
from . import modifiers
from . import shaders
from . import basic
from . import physics
from . import bake
from . import panels
from . import properties
from . import scene
from . import exporter
from . import importer
from . import geom
from . import bones
from . import rigidbody
from . import springbones
from . import drivers
from . import wrinkle
from . import facerig_data
from . import facerig
from . import rigify_mapping_data
from . import rigging
from . import rigutils
from . import sculpting
from . import hair
from . import colorspace
from . import world
from . import normal
from . import link
from . import proportion
from . import iconutils
from . import rlx
bl_info = {
"name": "CC/iC Tools",
"author": "Victor Soupday",
"version": (2, 3, 4),
"blender": (3, 4, 1),
"category": "Characters",
"location": "3D View > Properties > CC/iC Pipeline",
"description": "Automatic import and material setup of CC3/4-iClone7/8 characters.",
"wiki_url": "https://soupday.github.io/cc_blender_tools/index.html",
"tracker_url": "https://github.com/soupday/cc_blender_tools/issues",
}
vars.set_version_string(bl_info)
classes = (
preferences.CC3ToolsAddonPreferences,
preferences.MATERIAL_UL_weightedmatslots,
channel_mixer.CC3RGBMixer,
channel_mixer.CC3IDMixer,
channel_mixer.CC3MixerSettings,
properties.CCICLinkProps,
properties.CCICBakeCache,
properties.CCICBakeMaterialSettings,
properties.CCICBakeProps,
properties.CC3ActionList,
properties.CC3ArmatureList,
properties.CCIC_UI_MixItem,
properties.CCIC_UI_MixList,
properties.CCICActionOptions,
properties.CC3HeadParameters,
properties.CC3SkinParameters,
properties.CC3EyeParameters,
properties.CC3EyeOcclusionParameters,
properties.CC3TearlineParameters,
properties.CC3TeethParameters,
properties.CC3TongueParameters,
properties.CC3HairParameters,
properties.CC3PBRParameters,
properties.CC3SSSParameters,
properties.CC3BasicParameters,
properties.CC3TextureMapping,
properties.CC3EyeMaterialCache,
properties.CC3EyeOcclusionMaterialCache,
properties.CC3TearlineMaterialCache,
properties.CC3TeethMaterialCache,
properties.CC3TongueMaterialCache,
properties.CC3HairMaterialCache,
properties.CC3HeadMaterialCache,
properties.CC3SkinMaterialCache,
properties.CC3PBRMaterialCache,
properties.CC3SSSMaterialCache,
properties.CCICExpressionData,
properties.CC3ObjectCache,
properties.CCICActionStore,
properties.CC3CharacterCache,
properties.CC3ImportProps,
importer.CC3Import,
importer.CC3ImportAnimations,
exporter.CC3Export,
scene.CC3Scene,
bake.CC3BakeOperator,
rigging.CC3Rigifier,
rigging.CC3RigifierModal,
bake.CCICBakeSettings,
bake.CCICBaker,
bake.CCICJpegify,
springbones.CC3OperatorSpringBones,
physics.CC3OperatorPhysics,
materials.CC3OperatorMaterial,
characters.CC3OperatorCharacter,
characters.CCICWeightTransferBlend,
properties.CC3OperatorProperties,
preferences.CC3OperatorPreferences,
channel_mixer.CC3OperatorChannelMixer,
characters.CC3OperatorTransferCharacterGeometry,
characters.CC3OperatorTransferMeshGeometry,
characters.CCICCharacterRename,
characters.CCICCharacterConvertGeneric,
sculpting.CC3OperatorSculpt,
sculpting.CC3OperatorSculptExport,
hair.CC3OperatorHair,
hair.CC3ExportHair,
link.CCICDataLink,
link.CCICLinkConfirmDialog,
link.CCICLinkTest,
characters.CCICCharacterLink,
proportion.CCICCharacterProportions,
rigutils.CCICMotionSetRename,
rigutils.CCICMotionSetInfo,
rigutils.CCICRigUtils,
rigutils.CCIC_ImportMixBones_UL_List,
rigutils.CCIC_RigMixBones_UL_List,
rigutils.CCICActionImportFunctions,
rigutils.CCICActionImportOptions,
facerig.CCICImportARKitCSV,
panels.ARMATURE_UL_List,
panels.ACTION_UL_List,
panels.ACTION_SET_UL_List,
panels.UNITY_ACTION_UL_List,
# pipeline panels
panels.CC3ToolsPipelineImportPanel,
panels.CC3ToolsPipelineExportPanel,
panels.CC3CharacterSettingsPanel,
panels.CC3MaterialParametersPanel,
panels.CC3RigifyPanel,
panels.CCICBakePanel,
panels.CC3PipelineScenePanel,
# NLA panels
panels.CCICNLASetsPanel,
panels.CCICNLABakePanel,
# create panels
panels.CC3ToolsCreatePanel,
panels.CC3ObjectManagementPanel,
panels.CC3WeightPaintPanel,
panels.CC3ToolsPhysicsPanel,
panels.CC3SpringRigPanel,
panels.CC3ToolsSculptingPanel,
panels.CCICProportionPanel,
panels.CC3HairPanel,
# link panels
panels.CCICDataLinkPanel,
panels.CCICAnimationToolsPanel,
panels.CCICLinkScenePanel,
# control panels
panels.CC3SpringControlPanel,
# test panels
panels.CC3ToolsUtilityPanel,
)
def register():
addon_updater_ops.register(bl_info)
for cls in classes:
bpy.utils.register_class(cls)
iconutils.register()
bpy.types.Scene.CC3ImportProps = bpy.props.PointerProperty(type=properties.CC3ImportProps)
bpy.types.Scene.CCICBakeProps = bpy.props.PointerProperty(type=properties.CCICBakeProps)
bpy.types.Scene.CCICLinkProps = bpy.props.PointerProperty(type=properties.CCICLinkProps)
bpy.types.TOPBAR_MT_file_import.append(importer.menu_func_import)
bpy.types.TOPBAR_MT_file_import.append(importer.menu_func_import_animation)
bpy.types.TOPBAR_MT_file_export.append(exporter.menu_func_export)
if link.disconnect not in bpy.app.handlers.load_pre:
bpy.app.handlers.load_pre.append(link.disconnect)
if link.reconnect not in bpy.app.handlers.load_post:
bpy.app.handlers.load_post.append(link.reconnect)
bpy.app.timers.register(link.reconnect, first_interval=0.5, persistent=False)
def unregister():
link.disconnect()
addon_updater_ops.unregister()
bpy.types.TOPBAR_MT_file_import.remove(importer.menu_func_import)
bpy.types.TOPBAR_MT_file_import.remove(importer.menu_func_import_animation)
bpy.types.TOPBAR_MT_file_export.remove(exporter.menu_func_export)
for cls in classes:
bpy.utils.unregister_class(cls)
iconutils.unregister()
del(bpy.types.Scene.CC3ImportProps)
del(bpy.types.Scene.CCICBakeProps)
del(bpy.types.Scene.CCICLinkProps)
if link.disconnect in bpy.app.handlers.load_pre:
bpy.app.handlers.load_pre.remove(link.disconnect)
if link.reconnect in bpy.app.handlers.load_post:
bpy.app.handlers.load_post.remove(link.reconnect)
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
@@ -1,500 +0,0 @@
# Copyright (C) 2021 Victor Soupday
# This file is part of CC/iC Blender Tools <https://github.com/soupday/cc_blender_tools>
#
# CC/iC Blender Tools 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 3 of the License, or
# (at your option) any later version.
#
# CC/iC Blender Tools 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 CC/iC Blender Tools. If not, see <https://www.gnu.org/licenses/>.
import bpy
import os
from . import materials, nodeutils, imageutils, jsonutils, params, lib, utils, vars
def reset_shader(nodes, links, shader_label, shader_name):
bsdf_id = "(" + str(shader_name) + "_BSDF)"
bsdf_node: bpy.types.Node = None
output_node: bpy.types.Node = None
links.clear()
for n in nodes:
if n.type == "BSDF_PRINCIPLED":
if not bsdf_node:
utils.log_info("Keeping old BSDF: " + n.name)
bsdf_node = n
else:
nodes.remove(n)
elif n.type == "OUTPUT_MATERIAL":
if output_node:
nodes.remove(n)
else:
output_node = n
else:
nodes.remove(n)
if not bsdf_node:
bsdf_node = nodes.new("ShaderNodeBsdfPrincipled")
bsdf_node.name = utils.unique_name(bsdf_id)
bsdf_node.label = shader_label
bsdf_node.width = 240
utils.log_info("Creating new BSDF: " + bsdf_node.name)
if not output_node:
output_node = nodes.new("ShaderNodeOutputMaterial")
bsdf_node.location = (0,0)
output_node.location = (400, 0)
emission_socket = nodeutils.input_socket(bsdf_node, "Emission")
nodeutils.set_node_input_value(bsdf_node, emission_socket, (0,0,0))
if utils.B400():
emission_strength_socket = nodeutils.input_socket(bsdf_node, "Emission Strength")
nodeutils.set_node_input_value(bsdf_node, emission_strength_socket, 0)
# connect the shader to the output
nodeutils.link_nodes(links, bsdf_node, "BSDF", output_node, "Surface")
return bsdf_node
def connect_tearline_material(obj, mat, mat_json, processed_images):
props = vars.props()
chr_cache = props.get_character_cache(obj, mat)
parameters = chr_cache.basic_parameters
obj_cache = chr_cache.get_object_cache(obj)
mat_cache = chr_cache.get_material_cache(mat)
nodes = mat.node_tree.nodes
links = mat.node_tree.links
bsdf_node = reset_shader(nodes, links, "Tearline Shader", "basic_tearline")
base_color_socket = nodeutils.input_socket(bsdf_node, "Base Color")
metallic_socket = nodeutils.input_socket(bsdf_node, "Metallic")
specular_socket = nodeutils.input_socket(bsdf_node, "Specular")
roughness_socket = nodeutils.input_socket(bsdf_node, "Roughness")
alpha_socket = nodeutils.input_socket(bsdf_node, "Alpha")
nodeutils.set_node_input_value(bsdf_node, base_color_socket, (1.0, 1.0, 1.0, 1.0))
nodeutils.set_node_input_value(bsdf_node, metallic_socket, 1.0)
nodeutils.set_node_input_value(bsdf_node, specular_socket, 1.0)
nodeutils.set_node_input_value(bsdf_node, roughness_socket, parameters.tearline_roughness)
nodeutils.set_node_input_value(bsdf_node, alpha_socket, parameters.tearline_alpha)
bsdf_node.name = utils.unique_name("eye_tearline_shader")
materials.set_material_alpha(mat, "BLEND", shadows=False, refraction=True)
def connect_eye_occlusion_material(obj, mat, mat_json, processed_images):
props = vars.props()
chr_cache = props.get_character_cache(obj, mat)
parameters = chr_cache.basic_parameters
obj_cache = chr_cache.get_object_cache(obj)
mat_cache = chr_cache.get_material_cache(mat)
nodes = mat.node_tree.nodes
links = mat.node_tree.links
bsdf_node = reset_shader(nodes, links, "Eye Occlusion Shader", "basic_eye_occlusion")
bsdf_node.name = utils.unique_name("eye_occlusion_shader")
base_color_socket = nodeutils.input_socket(bsdf_node, "Base Color")
metallic_socket = nodeutils.input_socket(bsdf_node, "Metallic")
specular_socket = nodeutils.input_socket(bsdf_node, "Specular")
roughness_socket = nodeutils.input_socket(bsdf_node, "Roughness")
alpha_socket = nodeutils.input_socket(bsdf_node, "Alpha")
nodeutils.set_node_input_value(bsdf_node, base_color_socket, (0,0,0,1))
nodeutils.set_node_input_value(bsdf_node, metallic_socket, 0.0)
nodeutils.set_node_input_value(bsdf_node, specular_socket, 0.0)
nodeutils.set_node_input_value(bsdf_node, roughness_socket, 1.0)
nodeutils.reset_cursor()
# groups
group = lib.get_node_group("eye_occlusion_mask")
occ_node = nodeutils.make_node_group_node(nodes, group, "Eye Occulsion Alpha", "eye_occlusion_mask")
# values
nodeutils.set_node_input_value(occ_node, "Strength", parameters.eye_occlusion)
nodeutils.set_node_input_value(occ_node, "Hardness", parameters.eye_occlusion_power)
# links
nodeutils.link_nodes(links, occ_node, "Alpha", bsdf_node, alpha_socket)
materials.set_material_alpha(mat, "BLEND", shadows=False)
def connect_basic_eye_material(obj, mat, mat_json, processed_images):
props = vars.props()
chr_cache = props.get_character_cache(obj, mat)
parameters = chr_cache.basic_parameters
obj_cache = chr_cache.get_object_cache(obj)
mat_cache = chr_cache.get_material_cache(mat)
nodes = mat.node_tree.nodes
links = mat.node_tree.links
bsdf_node = reset_shader(nodes, links, "Eye Shader", "basic_eye")
base_color_socket = nodeutils.input_socket(bsdf_node, "Base Color")
metallic_socket = nodeutils.input_socket(bsdf_node, "Metallic")
specular_socket = nodeutils.input_socket(bsdf_node, "Specular")
roughness_socket = nodeutils.input_socket(bsdf_node, "Roughness")
alpha_socket = nodeutils.input_socket(bsdf_node, "Alpha")
normal_socket = nodeutils.input_socket(bsdf_node, "Normal")
clearcoat_socket = nodeutils.input_socket(bsdf_node, "Clearcoat")
clearcoat_roughness_socket = nodeutils.input_socket(bsdf_node, "Clearcoat Roughness")
# Base Color
#
nodeutils.reset_cursor()
diffuse_image = find_material_image_basic(mat, "DIFFUSE", mat_json, processed_images)
if diffuse_image is not None:
diffuse_node = nodeutils.make_image_node(nodes, diffuse_image, "(DIFFUSE)")
nodeutils.advance_cursor(1.0)
hsv_node = nodeutils.make_shader_node(nodes, "ShaderNodeHueSaturation", 0.6)
hsv_node.label = "HSV"
hsv_node.name = utils.unique_name("eye_basic_hsv")
nodeutils.set_node_input_value(hsv_node, "Value", parameters.eye_brightness)
# links
nodeutils.link_nodes(links, diffuse_node, "Color", hsv_node, "Color")
nodeutils.link_nodes(links, hsv_node, "Color", bsdf_node, base_color_socket)
# Metallic
#
nodeutils.reset_cursor()
metallic_node = nodeutils.make_value_node(nodes, "Eye Metallic", "eye_metallic", 0.0)
nodeutils.link_nodes(links, metallic_node, "Value", bsdf_node, metallic_socket)
# Specular
#
nodeutils.reset_cursor()
specular_node = nodeutils.make_value_node(nodes, "Eye Specular", "eye_specular", parameters.eye_specular)
nodeutils.link_nodes(links, specular_node, "Value", bsdf_node, specular_socket)
# Roughness
#
nodeutils.reset_cursor()
roughness_node = nodeutils.make_value_node(nodes, "Eye Roughness", "eye_roughness", parameters.eye_roughness)
nodeutils.link_nodes(links, roughness_node, "Value", bsdf_node, roughness_socket)
# Alpha
#
nodeutils.set_node_input_value(bsdf_node, alpha_socket, 1.0)
# Normal
#
nodeutils.reset_cursor()
normal_image = find_material_image_basic(mat, "SCLERANORMAL", mat_json, processed_images)
if normal_image is not None:
strength_node = nodeutils.make_value_node(nodes, "Normal Strength", "eye_normal", parameters.eye_normal)
normal_node = nodeutils.make_image_node(nodes, normal_image, "(SCLERANORMAL)")
nodeutils.advance_cursor()
normalmap_node = nodeutils.make_shader_node(nodes, "ShaderNodeNormalMap", 0.6)
nodeutils.link_nodes(links, strength_node, "Value", normalmap_node, "Strength")
nodeutils.link_nodes(links, normal_node, "Color", normalmap_node, "Color")
nodeutils.link_nodes(links, normalmap_node, "Normal", bsdf_node, normal_socket)
# Clearcoat
#
nodeutils.set_node_input_value(bsdf_node, clearcoat_socket, 1.0)
nodeutils.set_node_input_value(bsdf_node, clearcoat_roughness_socket, 0.15)
materials.set_material_alpha(mat, "OPAQUE")
return
def connect_basic_material(obj, mat, mat_json, processed_images):
props = vars.props()
chr_cache = props.get_character_cache(obj, mat)
parameters = chr_cache.basic_parameters
obj_cache = chr_cache.get_object_cache(obj)
mat_cache = chr_cache.get_material_cache(mat)
nodes = mat.node_tree.nodes
links = mat.node_tree.links
bsdf_node = reset_shader(nodes, links, "Basic Shader", "basic")
base_color_socket = nodeutils.input_socket(bsdf_node, "Base Color")
metallic_socket = nodeutils.input_socket(bsdf_node, "Metallic")
specular_socket = nodeutils.input_socket(bsdf_node, "Specular")
roughness_socket = nodeutils.input_socket(bsdf_node, "Roughness")
alpha_socket = nodeutils.input_socket(bsdf_node, "Alpha")
emission_socket = nodeutils.input_socket(bsdf_node, "Emission")
emission_strength_socket = nodeutils.input_socket(bsdf_node, "Emission Strength")
normal_socket = nodeutils.input_socket(bsdf_node, "Normal")
sss_socket = nodeutils.input_socket(bsdf_node, "Subsurface")
# Base Color
#
nodeutils.reset_cursor()
diffuse_image = find_material_image_basic(mat, "DIFFUSE", mat_json, processed_images)
ao_image = find_material_image_basic(mat, "AO", mat_json, processed_images)
diffuse_node = ao_node = None
if (diffuse_image is not None):
diffuse_node = nodeutils.make_image_node(nodes, diffuse_image, "(DIFFUSE)")
if ao_image is not None:
if mat_cache.is_skin() or mat_cache.is_nails():
prop = "skin_ao"
ao_strength = parameters.skin_ao
elif mat_cache.is_hair():
prop = "hair_ao"
ao_strength = parameters.hair_ao
else:
prop = "default_ao"
ao_strength = parameters.default_ao
fac_node = nodeutils.make_value_node(nodes, "Ambient Occlusion Strength", prop, ao_strength)
ao_node = nodeutils.make_image_node(nodes, ao_image, "ao_tex")
nodeutils.advance_cursor(1.5)
nodeutils.drop_cursor(0.75)
mix_node = nodeutils.make_mixrgb_node(nodes, "MULTIPLY")
mix_node.name = utils.unique_name("AO_Mix")
mix_node.label = "AO Mix"
nodeutils.link_nodes(links, diffuse_node, "Color", mix_node, "Color1")
nodeutils.link_nodes(links, ao_node, "Color", mix_node, "Color2")
nodeutils.link_nodes(links, fac_node, "Value", mix_node, "Fac")
nodeutils.link_nodes(links, mix_node, "Color", bsdf_node, base_color_socket)
else:
nodeutils.link_nodes(links, diffuse_node, "Color", bsdf_node, base_color_socket)
# SSS
#
if mat_cache.is_skin():
nodeutils.set_node_input_value(bsdf_node, sss_socket, 0.25)
else:
nodeutils.set_node_input_value(bsdf_node, sss_socket, 0)
# Metallic
#
nodeutils.reset_cursor()
metallic_image = find_material_image_basic(mat, "METALLIC", mat_json, processed_images)
metallic_node = None
if metallic_image is not None:
metallic_node = nodeutils.make_image_node(nodes, metallic_image, "(METALLIC)")
nodeutils.link_nodes(links, metallic_node, "Color", bsdf_node, metallic_socket)
# Specular
#
nodeutils.reset_cursor()
specular_image = find_material_image_basic(mat, "SPECULAR", mat_json, processed_images)
mask_image = find_material_image_basic(mat, "SPECMASK", mat_json, processed_images)
prop = "none"
spec = 0.5
if mat_cache.is_skin() or mat_cache.is_nails():
prop = "skin_specular"
spec = parameters.skin_specular
elif mat_cache.is_hair():
prop = "hair_specular"
spec = parameters.hair_specular
elif mat_cache.is_scalp() or mat_cache.is_eyelash():
prop = "scalp_specular"
spec = parameters.scalp_specular
elif mat_cache.is_teeth():
prop = "teeth_specular"
spec = parameters.teeth_specular
elif mat_cache.is_tongue():
prop = "tongue_specular"
spec = parameters.tongue_specular
else:
prop = "default_specular"
spec = parameters.default_specular
specular_node = mask_node = mult_node = None
if specular_image is not None:
specular_node = nodeutils.make_image_node(nodes, specular_image, "(SPECULAR)")
nodeutils.link_nodes(links, specular_node, "Color", bsdf_node, specular_socket)
# always make a specular value node for skin or if there is a mask (but no map)
elif prop != "none":
specular_node = nodeutils.make_value_node(nodes, "Specular Strength", prop, spec)
nodeutils.link_nodes(links, specular_node, "Value", bsdf_node, specular_socket)
if mask_image is not None:
mask_node = nodeutils.make_image_node(nodes, mask_image, "(SPECMASK)")
nodeutils.advance_cursor()
mult_node = nodeutils.make_math_node(nodes, "MULTIPLY")
mult_node.name = utils.unique_name("(Specular_Mult)")
mult_node.label = "Apply Specular Mask"
if specular_node.type == "VALUE":
nodeutils.link_nodes(links, specular_node, "Value", mult_node, 0)
else:
nodeutils.link_nodes(links, specular_node, "Color", mult_node, 0)
nodeutils.link_nodes(links, mask_node, "Color", mult_node, 1)
nodeutils.link_nodes(links, mult_node, "Value", bsdf_node, specular_socket)
# Roughness
#
nodeutils.reset_cursor()
roughness_image = find_material_image_basic(mat, "ROUGHNESS", mat_json, processed_images)
roughness_node = None
if roughness_image is not None:
roughness_node = nodeutils.make_image_node(nodes, roughness_image, "(ROUGHNESS)")
if mat_cache.is_skin():
prop = "skin_roughness"
roughness = parameters.skin_roughness
elif mat_cache.is_teeth():
prop = "teeth_roughness"
roughness = parameters.teeth_roughness
elif mat_cache.is_tongue():
prop = "tongue_roughness"
roughness = parameters.tongue_roughness
else:
prop = "none"
roughness = 1
if mat_cache.material_type.startswith("SKIN"):
nodeutils.advance_cursor()
remap_node = nodeutils.make_shader_node(nodes, "ShaderNodeMapRange")
remap_node.name = utils.unique_name(prop)
nodeutils.set_node_input_value(remap_node, "To Min", roughness)
nodeutils.link_nodes(links, roughness_node, "Color", remap_node, "Value")
nodeutils.link_nodes(links, remap_node, "Result", bsdf_node, roughness_socket)
elif mat_cache.material_type.startswith("TEETH") or mat_cache.material_type == "TONGUE":
nodeutils.advance_cursor()
rmult_node = nodeutils.make_math_node(nodes, "MULTIPLY", 1, roughness)
rmult_node.name = utils.unique_name(prop)
rmult_node.label = "Roughness Remap"
nodeutils.link_nodes(links, roughness_node, "Color", rmult_node, 0)
nodeutils.link_nodes(links, rmult_node, "Value", bsdf_node, roughness_socket)
else:
nodeutils.link_nodes(links, roughness_node, "Color", bsdf_node, roughness_socket)
# Emission
#
nodeutils.reset_cursor()
emission_image = find_material_image_basic(mat,"EMISSION", mat_json, processed_images)
emission_node = None
if emission_image is not None:
emission_node = nodeutils.make_image_node(nodes, emission_image, "(EMISSION)")
nodeutils.link_nodes(links, emission_node, "Color", bsdf_node, emission_socket)
emission_strength = jsonutils.get_texture_channel_strength(mat_json, "Glow", 0.0)
nodeutils.set_node_input_value(bsdf_node, emission_strength_socket, emission_strength)
# Alpha
#
nodeutils.reset_cursor()
alpha_image = find_material_image_basic(mat, "ALPHA", mat_json, processed_images)
alpha_node = None
if alpha_image is not None:
alpha_node = nodeutils.make_image_node(nodes, alpha_image, "(ALPHA)")
dir, file = os.path.split(alpha_image.filepath)
if "_diffuse" in file.lower() or "_albedo" in file.lower():
nodeutils.link_nodes(links, alpha_node, "Alpha", bsdf_node, alpha_socket)
else:
nodeutils.link_nodes(links, alpha_node, "Color", bsdf_node, alpha_socket)
elif diffuse_node:
nodeutils.link_nodes(links, diffuse_node, "Alpha", bsdf_node, alpha_socket)
# material alpha blend settings
method = materials.determine_material_alpha(obj_cache, mat_cache, mat_json)
materials.set_material_alpha(mat, method)
# Normal
#
nodeutils.reset_cursor()
normal_strength = jsonutils.get_texture_channel_strength(mat_json, "Normal", 1.0)
normal_image = find_material_image_basic(mat, "NORMAL", mat_json, processed_images)
bump_image = find_material_image_basic(mat,"BUMP", mat_json, processed_images)
normal_node = bump_node = normalmap_node = bumpmap_node = None
if normal_image is not None:
normal_node = nodeutils.make_image_node(nodes, normal_image, "(NORMAL)")
nodeutils.advance_cursor()
normalmap_node = nodeutils.make_shader_node(nodes, "ShaderNodeNormalMap", 0.6)
nodeutils.link_nodes(links, normal_node, "Color", normalmap_node, "Color")
nodeutils.link_nodes(links, normalmap_node, "Normal", bsdf_node, normal_socket)
nodeutils.set_node_input_value(normalmap_node, "Strength", normal_strength)
if bump_image is not None:
if mat_cache.is_hair() or mat_cache.is_eyelash() or mat_cache.is_scalp():
prop = "hair_bump"
bump_strength = parameters.hair_bump
else:
prop = "default_bump"
bump_strength = parameters.default_bump
bump_strength_node = nodeutils.make_value_node(nodes, "Bump Strength", prop, bump_strength / 1000)
bump_node = nodeutils.make_image_node(nodes, bump_image, "(BUMP)")
nodeutils.advance_cursor()
bumpmap_node = nodeutils.make_shader_node(nodes, "ShaderNodeBump", 0.7)
nodeutils.advance_cursor()
nodeutils.link_nodes(links, bump_strength_node, "Value", bumpmap_node, "Distance")
nodeutils.link_nodes(links, bump_node, "Color", bumpmap_node, "Height")
if normal_image is not None:
nodeutils.link_nodes(links, normalmap_node, "Normal", bumpmap_node, "Normal")
nodeutils.link_nodes(links, bumpmap_node, "Normal", bsdf_node, normal_socket)
def find_material_image_basic(mat, tex_type, mat_json, processed_images):
json_id = imageutils.get_image_type_json_id(tex_type)
tex_json = jsonutils.get_texture_info(mat_json, json_id)
return imageutils.find_material_image(mat, tex_type, processed_images, tex_json, mat_json)
def update_basic_material(mat, mat_cache, prop):
props = vars.props()
chr_cache = props.get_character_cache(None, mat)
parameters = chr_cache.basic_parameters
scope = locals()
if mat is not None and mat.node_tree is not None:
nodes = mat.node_tree.nodes
for node in nodes:
for prop_info in params.BASIC_PROPS:
prop_name = prop_info[3]
prop_node = prop_info[2]
if prop_node == "":
prop_node = prop_name
if prop_node in node.name and (prop == "ALL" or prop == prop_name):
prop_dir = prop_info[0]
prop_socket = prop_info[1]
try:
if len(prop_info) > 5:
prop_eval = prop_info[5]
else:
prop_eval = "parameters." + prop_name
prop_value = eval(prop_eval, None, scope)
if prop_dir == "IN":
nodeutils.set_node_input_value(node, prop_socket, prop_value)
elif prop_dir == "OUT":
nodeutils.set_node_output_value(node, prop_socket, prop_value)
except Exception as e:
utils.log_error("update_basic_materials(): Unable to evaluate or set: " + prop_eval, e)
def init_basic_default(chr_cache):
props = vars.props()
parameters = chr_cache.basic_parameters
for prop_info in params.BASIC_PROPS:
prop_name = prop_info[3]
prop_default = prop_info[4]
try:
prop_eval = "parameters." + prop_name + " = " + str(prop_default)
exec(prop_eval, None, locals())
except Exception as e:
utils.log_error("init_basic_default(): Unable to set: " + prop_eval, e)
if chr_cache.is_actor_core():
chr_cache.basic_parameters.default_ao = 0.2
chr_cache.basic_parameters.default_specular = 0.2
File diff suppressed because it is too large Load Diff
-133
View File
@@ -1,133 +0,0 @@
import bpy
from mathutils import Matrix, Vector
from . import bones, utils, vars
def set_pose_bone_world_transform(arm, pose_bone: bpy.types.PoseBone, world_transform: dict, local_transform: dict):
if arm and pose_bone and world_transform and local_transform:
loc = utils.array_to_vector(world_transform["location"]) * 0.01
rot = utils.array_to_quaternion(world_transform["rotation"])
s = utils.array_to_vector(world_transform["scale"])
sca = Vector((utils.sign(s.x), utils.sign(s.y), utils.sign(s.z))) * arm.scale
M = utils.make_transform_matrix(loc, rot, sca)
pose_bone.matrix = M
def set_mesh_world_transform(arm, mesh_obj: bpy.types.Object, world_transform: dict, local_transform: dict):
if arm and mesh_obj and world_transform and local_transform:
loc = utils.array_to_vector(world_transform["location"]) * 0.01
rot = utils.array_to_quaternion(world_transform["rotation"])
s = utils.array_to_vector(world_transform["scale"])
sca = Vector((utils.sign(s.x), utils.sign(s.y), utils.sign(s.z))) * arm.scale
M = utils.make_transform_matrix(loc, rot, sca)
mesh_obj.matrix_world = M
def bone_name_match(rl_name, blender_name):
if rl_name == "_Object_Pivot_Node_":
rl_name = "CC_Base_Pivot"
export_name = bones.rl_export_bone_name(rl_name)
unduplicated_name = utils.strip_name(blender_name)
if rl_name == unduplicated_name or export_name == unduplicated_name:
return True
return False
def deduplicate_name(name, names: dict):
count = names[name] if name in names else 0
names[name] = count + 1
return f"{name}.{count:03d}" if count > 0 else name
def match_id_tree(rl_tree, arm=None,
pose_bone: bpy.types.PoseBone=None,
mesh_obj: bpy.types.Object=None,
parent_bone: bpy.types.PoseBone=None,
id_map=None,
names=None,
pose=False):
"""If supplying an armature, match the bone tree to the armature and return a mapping (by id)
to the armature bones. If no armature (i.e. for rigified avatars), then map the bones (by id)
to unduplicated bone names"""
if arm and not (pose_bone or mesh_obj):
pose_bone = arm.pose.bones[0]
if id_map is None:
id_map = {}
if names is None:
names = {}
name = pose_bone.name if pose_bone else mesh_obj.name if mesh_obj else deduplicate_name(rl_tree["name"], names)
# id_map is a dict of bones by ID, mapping the source skin_bone name to the armature bone or mesh
id_map[rl_tree["id"]] = {
"source": rl_tree["name"],
"name": name,
"mesh": mesh_obj is not None,
}
# id tree
id_tree = {
"name": name,
"id": rl_tree["id"],
"source": rl_tree["name"],
"children": []
}
world_transform_data = rl_tree.get("world_transform", None)
local_transform_data = rl_tree.get("local_transform", None)
if mesh_obj:
id_tree["mesh"] = True
if pose and world_transform_data:
set_mesh_world_transform(arm, mesh_obj, world_transform_data, local_transform_data)
else:
if pose and world_transform_data:
set_pose_bone_world_transform(arm, pose_bone, world_transform_data, local_transform_data)
#utils.log_detail(f"Bone: {bone.name} / Tree: {rl_tree['name']} {rl_tree['id']}")
for child_tree in rl_tree["children"]:
child_name = child_tree["name"]
#utils.log_detail(f"Trying: {child_name}")
if arm:
found = False
if not found and not child_tree["children"]:
for obj in arm.children:
if obj.parent and obj.parent_type == "BONE" and obj.parent_bone == pose_bone.name:
if bone_name_match(child_name, obj.name):
#utils.log_detail(f" - child_mesh: {obj.name} / child_tree: {child_name} - parented to: {obj.parent_bone}")
found = True
child_tree = match_id_tree(child_tree, arm=arm, mesh_obj=obj, parent_bone=pose_bone, id_map=id_map, pose=pose)[0]
if child_tree:
id_tree["children"].append(child_tree)
break
if not found:
for child_bone in pose_bone.children:
if bone_name_match(child_name, child_bone.name):
#utils.log_detail(f" - child_bone: {child_bone.name} / child_tree: {child_name}")
found = True
child_tree = match_id_tree(child_tree, arm=arm, pose_bone=child_bone, id_map=id_map, pose=pose)[0]
if child_tree:
id_tree["children"].append(child_tree)
break
else:
child_tree = match_id_tree(child_tree, id_map=id_map, names=names)[0]
if child_tree:
id_tree["children"].append(child_tree)
return id_tree, id_map
def confirm_bone_order(bones, ids, id_map: dict):
result = True
for id, id_def in id_map.items():
if id not in ids or id_def["source"] not in bones:
utils.log_warn(f"bone {id_def['source']} ({id}) not found in skin bones!")
result = False
if result and len(ids) < len(id_map):
utils.log_info("All bones present, but more bones found in id_tree!")
elif result:
utils.log_info("All bones present!")
return result
def convert_id_tree(arm, id_tree_root):
if not id_tree_root: return None
id_tree, id_map = match_id_tree(id_tree_root, arm=arm)
return id_tree, id_map
@@ -1,674 +0,0 @@
GNU GENERAL PUBLIC LICENSE
Version 3, 29 June 2007
Copyright (C) 2007 Free Software Foundation, Inc. <https://fsf.org/>
Everyone is permitted to copy and distribute verbatim copies
of this license document, but changing it is not allowed.
Preamble
The GNU General Public License is a free, copyleft license for
software and other kinds of works.
The licenses for most software and other practical works are designed
to take away your freedom to share and change the works. By contrast,
the GNU General Public License is intended to guarantee your freedom to
share and change all versions of a program--to make sure it remains free
software for all its users. We, the Free Software Foundation, use the
GNU General Public License for most of our software; it applies also to
any other work released this way by its authors. You can apply it to
your programs, too.
When we speak of free software, we are referring to freedom, not
price. Our General Public Licenses are designed to make sure that you
have the freedom to distribute copies of free software (and charge for
them if you wish), that you receive source code or can get it if you
want it, that you can change the software or use pieces of it in new
free programs, and that you know you can do these things.
To protect your rights, we need to prevent others from denying you
these rights or asking you to surrender the rights. Therefore, you have
certain responsibilities if you distribute copies of the software, or if
you modify it: responsibilities to respect the freedom of others.
For example, if you distribute copies of such a program, whether
gratis or for a fee, you must pass on to the recipients the same
freedoms that you received. You must make sure that they, too, receive
or can get the source code. And you must show them these terms so they
know their rights.
Developers that use the GNU GPL protect your rights with two steps:
(1) assert copyright on the software, and (2) offer you this License
giving you legal permission to copy, distribute and/or modify it.
For the developers' and authors' protection, the GPL clearly explains
that there is no warranty for this free software. For both users' and
authors' sake, the GPL requires that modified versions be marked as
changed, so that their problems will not be attributed erroneously to
authors of previous versions.
Some devices are designed to deny users access to install or run
modified versions of the software inside them, although the manufacturer
can do so. This is fundamentally incompatible with the aim of
protecting users' freedom to change the software. The systematic
pattern of such abuse occurs in the area of products for individuals to
use, which is precisely where it is most unacceptable. Therefore, we
have designed this version of the GPL to prohibit the practice for those
products. If such problems arise substantially in other domains, we
stand ready to extend this provision to those domains in future versions
of the GPL, as needed to protect the freedom of users.
Finally, every program is threatened constantly by software patents.
States should not allow patents to restrict development and use of
software on general-purpose computers, but in those that do, we wish to
avoid the special danger that patents applied to a free program could
make it effectively proprietary. To prevent this, the GPL assures that
patents cannot be used to render the program non-free.
The precise terms and conditions for copying, distribution and
modification follow.
TERMS AND CONDITIONS
0. Definitions.
"This License" refers to version 3 of the GNU General Public License.
"Copyright" also means copyright-like laws that apply to other kinds of
works, such as semiconductor masks.
"The Program" refers to any copyrightable work licensed under this
License. Each licensee is addressed as "you". "Licensees" and
"recipients" may be individuals or organizations.
To "modify" a work means to copy from or adapt all or part of the work
in a fashion requiring copyright permission, other than the making of an
exact copy. The resulting work is called a "modified version" of the
earlier work or a work "based on" the earlier work.
A "covered work" means either the unmodified Program or a work based
on the Program.
To "propagate" a work means to do anything with it that, without
permission, would make you directly or secondarily liable for
infringement under applicable copyright law, except executing it on a
computer or modifying a private copy. Propagation includes copying,
distribution (with or without modification), making available to the
public, and in some countries other activities as well.
To "convey" a work means any kind of propagation that enables other
parties to make or receive copies. Mere interaction with a user through
a computer network, with no transfer of a copy, is not conveying.
An interactive user interface displays "Appropriate Legal Notices"
to the extent that it includes a convenient and prominently visible
feature that (1) displays an appropriate copyright notice, and (2)
tells the user that there is no warranty for the work (except to the
extent that warranties are provided), that licensees may convey the
work under this License, and how to view a copy of this License. If
the interface presents a list of user commands or options, such as a
menu, a prominent item in the list meets this criterion.
1. Source Code.
The "source code" for a work means the preferred form of the work
for making modifications to it. "Object code" means any non-source
form of a work.
A "Standard Interface" means an interface that either is an official
standard defined by a recognized standards body, or, in the case of
interfaces specified for a particular programming language, one that
is widely used among developers working in that language.
The "System Libraries" of an executable work include anything, other
than the work as a whole, that (a) is included in the normal form of
packaging a Major Component, but which is not part of that Major
Component, and (b) serves only to enable use of the work with that
Major Component, or to implement a Standard Interface for which an
implementation is available to the public in source code form. A
"Major Component", in this context, means a major essential component
(kernel, window system, and so on) of the specific operating system
(if any) on which the executable work runs, or a compiler used to
produce the work, or an object code interpreter used to run it.
The "Corresponding Source" for a work in object code form means all
the source code needed to generate, install, and (for an executable
work) run the object code and to modify the work, including scripts to
control those activities. However, it does not include the work's
System Libraries, or general-purpose tools or generally available free
programs which are used unmodified in performing those activities but
which are not part of the work. For example, Corresponding Source
includes interface definition files associated with source files for
the work, and the source code for shared libraries and dynamically
linked subprograms that the work is specifically designed to require,
such as by intimate data communication or control flow between those
subprograms and other parts of the work.
The Corresponding Source need not include anything that users
can regenerate automatically from other parts of the Corresponding
Source.
The Corresponding Source for a work in source code form is that
same work.
2. Basic Permissions.
All rights granted under this License are granted for the term of
copyright on the Program, and are irrevocable provided the stated
conditions are met. This License explicitly affirms your unlimited
permission to run the unmodified Program. The output from running a
covered work is covered by this License only if the output, given its
content, constitutes a covered work. This License acknowledges your
rights of fair use or other equivalent, as provided by copyright law.
You may make, run and propagate covered works that you do not
convey, without conditions so long as your license otherwise remains
in force. You may convey covered works to others for the sole purpose
of having them make modifications exclusively for you, or provide you
with facilities for running those works, provided that you comply with
the terms of this License in conveying all material for which you do
not control copyright. Those thus making or running the covered works
for you must do so exclusively on your behalf, under your direction
and control, on terms that prohibit them from making any copies of
your copyrighted material outside their relationship with you.
Conveying under any other circumstances is permitted solely under
the conditions stated below. Sublicensing is not allowed; section 10
makes it unnecessary.
3. Protecting Users' Legal Rights From Anti-Circumvention Law.
No covered work shall be deemed part of an effective technological
measure under any applicable law fulfilling obligations under article
11 of the WIPO copyright treaty adopted on 20 December 1996, or
similar laws prohibiting or restricting circumvention of such
measures.
When you convey a covered work, you waive any legal power to forbid
circumvention of technological measures to the extent such circumvention
is effected by exercising rights under this License with respect to
the covered work, and you disclaim any intention to limit operation or
modification of the work as a means of enforcing, against the work's
users, your or third parties' legal rights to forbid circumvention of
technological measures.
4. Conveying Verbatim Copies.
You may convey verbatim copies of the Program's source code as you
receive it, in any medium, provided that you conspicuously and
appropriately publish on each copy an appropriate copyright notice;
keep intact all notices stating that this License and any
non-permissive terms added in accord with section 7 apply to the code;
keep intact all notices of the absence of any warranty; and give all
recipients a copy of this License along with the Program.
You may charge any price or no price for each copy that you convey,
and you may offer support or warranty protection for a fee.
5. Conveying Modified Source Versions.
You may convey a work based on the Program, or the modifications to
produce it from the Program, in the form of source code under the
terms of section 4, provided that you also meet all of these conditions:
a) The work must carry prominent notices stating that you modified
it, and giving a relevant date.
b) The work must carry prominent notices stating that it is
released under this License and any conditions added under section
7. This requirement modifies the requirement in section 4 to
"keep intact all notices".
c) You must license the entire work, as a whole, under this
License to anyone who comes into possession of a copy. This
License will therefore apply, along with any applicable section 7
additional terms, to the whole of the work, and all its parts,
regardless of how they are packaged. This License gives no
permission to license the work in any other way, but it does not
invalidate such permission if you have separately received it.
d) If the work has interactive user interfaces, each must display
Appropriate Legal Notices; however, if the Program has interactive
interfaces that do not display Appropriate Legal Notices, your
work need not make them do so.
A compilation of a covered work with other separate and independent
works, which are not by their nature extensions of the covered work,
and which are not combined with it such as to form a larger program,
in or on a volume of a storage or distribution medium, is called an
"aggregate" if the compilation and its resulting copyright are not
used to limit the access or legal rights of the compilation's users
beyond what the individual works permit. Inclusion of a covered work
in an aggregate does not cause this License to apply to the other
parts of the aggregate.
6. Conveying Non-Source Forms.
You may convey a covered work in object code form under the terms
of sections 4 and 5, provided that you also convey the
machine-readable Corresponding Source under the terms of this License,
in one of these ways:
a) Convey the object code in, or embodied in, a physical product
(including a physical distribution medium), accompanied by the
Corresponding Source fixed on a durable physical medium
customarily used for software interchange.
b) Convey the object code in, or embodied in, a physical product
(including a physical distribution medium), accompanied by a
written offer, valid for at least three years and valid for as
long as you offer spare parts or customer support for that product
model, to give anyone who possesses the object code either (1) a
copy of the Corresponding Source for all the software in the
product that is covered by this License, on a durable physical
medium customarily used for software interchange, for a price no
more than your reasonable cost of physically performing this
conveying of source, or (2) access to copy the
Corresponding Source from a network server at no charge.
c) Convey individual copies of the object code with a copy of the
written offer to provide the Corresponding Source. This
alternative is allowed only occasionally and noncommercially, and
only if you received the object code with such an offer, in accord
with subsection 6b.
d) Convey the object code by offering access from a designated
place (gratis or for a charge), and offer equivalent access to the
Corresponding Source in the same way through the same place at no
further charge. You need not require recipients to copy the
Corresponding Source along with the object code. If the place to
copy the object code is a network server, the Corresponding Source
may be on a different server (operated by you or a third party)
that supports equivalent copying facilities, provided you maintain
clear directions next to the object code saying where to find the
Corresponding Source. Regardless of what server hosts the
Corresponding Source, you remain obligated to ensure that it is
available for as long as needed to satisfy these requirements.
e) Convey the object code using peer-to-peer transmission, provided
you inform other peers where the object code and Corresponding
Source of the work are being offered to the general public at no
charge under subsection 6d.
A separable portion of the object code, whose source code is excluded
from the Corresponding Source as a System Library, need not be
included in conveying the object code work.
A "User Product" is either (1) a "consumer product", which means any
tangible personal property which is normally used for personal, family,
or household purposes, or (2) anything designed or sold for incorporation
into a dwelling. In determining whether a product is a consumer product,
doubtful cases shall be resolved in favor of coverage. For a particular
product received by a particular user, "normally used" refers to a
typical or common use of that class of product, regardless of the status
of the particular user or of the way in which the particular user
actually uses, or expects or is expected to use, the product. A product
is a consumer product regardless of whether the product has substantial
commercial, industrial or non-consumer uses, unless such uses represent
the only significant mode of use of the product.
"Installation Information" for a User Product means any methods,
procedures, authorization keys, or other information required to install
and execute modified versions of a covered work in that User Product from
a modified version of its Corresponding Source. The information must
suffice to ensure that the continued functioning of the modified object
code is in no case prevented or interfered with solely because
modification has been made.
If you convey an object code work under this section in, or with, or
specifically for use in, a User Product, and the conveying occurs as
part of a transaction in which the right of possession and use of the
User Product is transferred to the recipient in perpetuity or for a
fixed term (regardless of how the transaction is characterized), the
Corresponding Source conveyed under this section must be accompanied
by the Installation Information. But this requirement does not apply
if neither you nor any third party retains the ability to install
modified object code on the User Product (for example, the work has
been installed in ROM).
The requirement to provide Installation Information does not include a
requirement to continue to provide support service, warranty, or updates
for a work that has been modified or installed by the recipient, or for
the User Product in which it has been modified or installed. Access to a
network may be denied when the modification itself materially and
adversely affects the operation of the network or violates the rules and
protocols for communication across the network.
Corresponding Source conveyed, and Installation Information provided,
in accord with this section must be in a format that is publicly
documented (and with an implementation available to the public in
source code form), and must require no special password or key for
unpacking, reading or copying.
7. Additional Terms.
"Additional permissions" are terms that supplement the terms of this
License by making exceptions from one or more of its conditions.
Additional permissions that are applicable to the entire Program shall
be treated as though they were included in this License, to the extent
that they are valid under applicable law. If additional permissions
apply only to part of the Program, that part may be used separately
under those permissions, but the entire Program remains governed by
this License without regard to the additional permissions.
When you convey a copy of a covered work, you may at your option
remove any additional permissions from that copy, or from any part of
it. (Additional permissions may be written to require their own
removal in certain cases when you modify the work.) You may place
additional permissions on material, added by you to a covered work,
for which you have or can give appropriate copyright permission.
Notwithstanding any other provision of this License, for material you
add to a covered work, you may (if authorized by the copyright holders of
that material) supplement the terms of this License with terms:
a) Disclaiming warranty or limiting liability differently from the
terms of sections 15 and 16 of this License; or
b) Requiring preservation of specified reasonable legal notices or
author attributions in that material or in the Appropriate Legal
Notices displayed by works containing it; or
c) Prohibiting misrepresentation of the origin of that material, or
requiring that modified versions of such material be marked in
reasonable ways as different from the original version; or
d) Limiting the use for publicity purposes of names of licensors or
authors of the material; or
e) Declining to grant rights under trademark law for use of some
trade names, trademarks, or service marks; or
f) Requiring indemnification of licensors and authors of that
material by anyone who conveys the material (or modified versions of
it) with contractual assumptions of liability to the recipient, for
any liability that these contractual assumptions directly impose on
those licensors and authors.
All other non-permissive additional terms are considered "further
restrictions" within the meaning of section 10. If the Program as you
received it, or any part of it, contains a notice stating that it is
governed by this License along with a term that is a further
restriction, you may remove that term. If a license document contains
a further restriction but permits relicensing or conveying under this
License, you may add to a covered work material governed by the terms
of that license document, provided that the further restriction does
not survive such relicensing or conveying.
If you add terms to a covered work in accord with this section, you
must place, in the relevant source files, a statement of the
additional terms that apply to those files, or a notice indicating
where to find the applicable terms.
Additional terms, permissive or non-permissive, may be stated in the
form of a separately written license, or stated as exceptions;
the above requirements apply either way.
8. Termination.
You may not propagate or modify a covered work except as expressly
provided under this License. Any attempt otherwise to propagate or
modify it is void, and will automatically terminate your rights under
this License (including any patent licenses granted under the third
paragraph of section 11).
However, if you cease all violation of this License, then your
license from a particular copyright holder is reinstated (a)
provisionally, unless and until the copyright holder explicitly and
finally terminates your license, and (b) permanently, if the copyright
holder fails to notify you of the violation by some reasonable means
prior to 60 days after the cessation.
Moreover, your license from a particular copyright holder is
reinstated permanently if the copyright holder notifies you of the
violation by some reasonable means, this is the first time you have
received notice of violation of this License (for any work) from that
copyright holder, and you cure the violation prior to 30 days after
your receipt of the notice.
Termination of your rights under this section does not terminate the
licenses of parties who have received copies or rights from you under
this License. If your rights have been terminated and not permanently
reinstated, you do not qualify to receive new licenses for the same
material under section 10.
9. Acceptance Not Required for Having Copies.
You are not required to accept this License in order to receive or
run a copy of the Program. Ancillary propagation of a covered work
occurring solely as a consequence of using peer-to-peer transmission
to receive a copy likewise does not require acceptance. However,
nothing other than this License grants you permission to propagate or
modify any covered work. These actions infringe copyright if you do
not accept this License. Therefore, by modifying or propagating a
covered work, you indicate your acceptance of this License to do so.
10. Automatic Licensing of Downstream Recipients.
Each time you convey a covered work, the recipient automatically
receives a license from the original licensors, to run, modify and
propagate that work, subject to this License. You are not responsible
for enforcing compliance by third parties with this License.
An "entity transaction" is a transaction transferring control of an
organization, or substantially all assets of one, or subdividing an
organization, or merging organizations. If propagation of a covered
work results from an entity transaction, each party to that
transaction who receives a copy of the work also receives whatever
licenses to the work the party's predecessor in interest had or could
give under the previous paragraph, plus a right to possession of the
Corresponding Source of the work from the predecessor in interest, if
the predecessor has it or can get it with reasonable efforts.
You may not impose any further restrictions on the exercise of the
rights granted or affirmed under this License. For example, you may
not impose a license fee, royalty, or other charge for exercise of
rights granted under this License, and you may not initiate litigation
(including a cross-claim or counterclaim in a lawsuit) alleging that
any patent claim is infringed by making, using, selling, offering for
sale, or importing the Program or any portion of it.
11. Patents.
A "contributor" is a copyright holder who authorizes use under this
License of the Program or a work on which the Program is based. The
work thus licensed is called the contributor's "contributor version".
A contributor's "essential patent claims" are all patent claims
owned or controlled by the contributor, whether already acquired or
hereafter acquired, that would be infringed by some manner, permitted
by this License, of making, using, or selling its contributor version,
but do not include claims that would be infringed only as a
consequence of further modification of the contributor version. For
purposes of this definition, "control" includes the right to grant
patent sublicenses in a manner consistent with the requirements of
this License.
Each contributor grants you a non-exclusive, worldwide, royalty-free
patent license under the contributor's essential patent claims, to
make, use, sell, offer for sale, import and otherwise run, modify and
propagate the contents of its contributor version.
In the following three paragraphs, a "patent license" is any express
agreement or commitment, however denominated, not to enforce a patent
(such as an express permission to practice a patent or covenant not to
sue for patent infringement). To "grant" such a patent license to a
party means to make such an agreement or commitment not to enforce a
patent against the party.
If you convey a covered work, knowingly relying on a patent license,
and the Corresponding Source of the work is not available for anyone
to copy, free of charge and under the terms of this License, through a
publicly available network server or other readily accessible means,
then you must either (1) cause the Corresponding Source to be so
available, or (2) arrange to deprive yourself of the benefit of the
patent license for this particular work, or (3) arrange, in a manner
consistent with the requirements of this License, to extend the patent
license to downstream recipients. "Knowingly relying" means you have
actual knowledge that, but for the patent license, your conveying the
covered work in a country, or your recipient's use of the covered work
in a country, would infringe one or more identifiable patents in that
country that you have reason to believe are valid.
If, pursuant to or in connection with a single transaction or
arrangement, you convey, or propagate by procuring conveyance of, a
covered work, and grant a patent license to some of the parties
receiving the covered work authorizing them to use, propagate, modify
or convey a specific copy of the covered work, then the patent license
you grant is automatically extended to all recipients of the covered
work and works based on it.
A patent license is "discriminatory" if it does not include within
the scope of its coverage, prohibits the exercise of, or is
conditioned on the non-exercise of one or more of the rights that are
specifically granted under this License. You may not convey a covered
work if you are a party to an arrangement with a third party that is
in the business of distributing software, under which you make payment
to the third party based on the extent of your activity of conveying
the work, and under which the third party grants, to any of the
parties who would receive the covered work from you, a discriminatory
patent license (a) in connection with copies of the covered work
conveyed by you (or copies made from those copies), or (b) primarily
for and in connection with specific products or compilations that
contain the covered work, unless you entered into that arrangement,
or that patent license was granted, prior to 28 March 2007.
Nothing in this License shall be construed as excluding or limiting
any implied license or other defenses to infringement that may
otherwise be available to you under applicable patent law.
12. No Surrender of Others' Freedom.
If conditions are imposed on you (whether by court order, agreement or
otherwise) that contradict the conditions of this License, they do not
excuse you from the conditions of this License. If you cannot convey a
covered work so as to satisfy simultaneously your obligations under this
License and any other pertinent obligations, then as a consequence you may
not convey it at all. For example, if you agree to terms that obligate you
to collect a royalty for further conveying from those to whom you convey
the Program, the only way you could satisfy both those terms and this
License would be to refrain entirely from conveying the Program.
13. Use with the GNU Affero General Public License.
Notwithstanding any other provision of this License, you have
permission to link or combine any covered work with a work licensed
under version 3 of the GNU Affero General Public License into a single
combined work, and to convey the resulting work. The terms of this
License will continue to apply to the part which is the covered work,
but the special requirements of the GNU Affero General Public License,
section 13, concerning interaction through a network will apply to the
combination as such.
14. Revised Versions of this License.
The Free Software Foundation may publish revised and/or new versions of
the GNU General Public License from time to time. Such new versions will
be similar in spirit to the present version, but may differ in detail to
address new problems or concerns.
Each version is given a distinguishing version number. If the
Program specifies that a certain numbered version of the GNU General
Public License "or any later version" applies to it, you have the
option of following the terms and conditions either of that numbered
version or of any later version published by the Free Software
Foundation. If the Program does not specify a version number of the
GNU General Public License, you may choose any version ever published
by the Free Software Foundation.
If the Program specifies that a proxy can decide which future
versions of the GNU General Public License can be used, that proxy's
public statement of acceptance of a version permanently authorizes you
to choose that version for the Program.
Later license versions may give you additional or different
permissions. However, no additional obligations are imposed on any
author or copyright holder as a result of your choosing to follow a
later version.
15. Disclaimer of Warranty.
THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY
APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT
HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY
OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO,
THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM
IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF
ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
16. Limitation of Liability.
IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS
THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY
GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE
USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF
DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD
PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS),
EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF
SUCH DAMAGES.
17. Interpretation of Sections 15 and 16.
If the disclaimer of warranty and limitation of liability provided
above cannot be given local legal effect according to their terms,
reviewing courts shall apply local law that most closely approximates
an absolute waiver of all civil liability in connection with the
Program, unless a warranty or assumption of liability accompanies a
copy of the Program in return for a fee.
END OF TERMS AND CONDITIONS
How to Apply These Terms to Your New Programs
If you develop a new program, and you want it to be of the greatest
possible use to the public, the best way to achieve this is to make it
free software which everyone can redistribute and change under these terms.
To do so, attach the following notices to the program. It is safest
to attach them to the start of each source file to most effectively
state the exclusion of warranty; and each file should have at least
the "copyright" line and a pointer to where the full notice is found.
<one line to give the program's name and a brief idea of what it does.>
Copyright (C) <year> <name of author>
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 3 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, see <https://www.gnu.org/licenses/>.
Also add information on how to contact you by electronic and paper mail.
If the program does terminal interaction, make it output a short
notice like this when it starts in an interactive mode:
<program> Copyright (C) <year> <name of author>
This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
This is free software, and you are welcome to redistribute it
under certain conditions; type `show c' for details.
The hypothetical commands `show w' and `show c' should show the appropriate
parts of the General Public License. Of course, your program's commands
might be different; for a GUI interface, you would use an "about box".
You should also get your employer (if you work as a programmer) or school,
if any, to sign a "copyright disclaimer" for the program, if necessary.
For more information on this, and how to apply and follow the GNU GPL, see
<https://www.gnu.org/licenses/>.
The GNU General Public License does not permit incorporating your program
into proprietary programs. If your program is a subroutine library, you
may consider it more useful to permit linking proprietary applications with
the library. If this is what you want to do, use the GNU Lesser General
Public License instead of this License. But first, please read
<https://www.gnu.org/licenses/why-not-lgpl.html>.
@@ -1,884 +0,0 @@
# CC/iC Blender Tools (Installed in Blender)
An add-on for importing and automatically setting up materials for Character Creator 3, 4 & 5 and iClone 7 & 8 character exports.
Using Blender in the Character Creator pipeline can often feel like hitting a brick wall. Spending potentially hours having to get the import settings correct and setting up the materials often with hundreds of textures.
This add-on aims to reduce that time spent getting characters into Blender down to just a few seconds and make use of as many of the exported textures as possible so that character artists can work in the highest quality possible using Blender.
[Online Documentation](https://soupday.github.io/cc_blender_tools/index.html)
[Reallusion Forum Thread](https://forum.reallusion.com/475005/Blender-Auto-Setup)
Links
=====
[CC4/5 Blender Pipeline Tool (Installed in CC4/5)](https://github.com/soupday/CCiC-Blender-Pipeline-Plugin)
[CC3 Blender Pipeline Tool (Installed in CC3)](https://github.com/soupday/CC3-Blender-Tools-Plugin)
## Installation, Updating, Removal
### To Install
- Download the [latest release](https://github.com/soupday/cc_blender_tools/releases).
- In Blender go to menu **Edit**->**Preferences** then select **Add-ons**.
- Click the **Install** button at the top of the preferences window and navigate to where you downloaded the zip file, select the file and click **Install Add-on**.
- Activate the add-on by ticking the checkbox next to **Edit**->**Preferences** then select **Add-ons**
- The add-ons functionality is available through the **CC/iC Blender Tools** Tab in the tool menu to the right of the main viewport. Press _N_ to show the tools if they are hidden.
### To Remove
- From the menu: **Edit**->**Preferences** then select **Add-ons**
- In the search box search **All** add-ons for **"CC/iC Blender Tools"**
- Deactivate the add-on by unticking the checkbox next to **Edit**->**Preferences** then select **Add-ons**.
- Then click the **Remove** button.
### To Update
- Remove the current version of the add-on by following the remove instructions above.
- Follow the installation instructions, above, to install the new version.
## Changelog
### 2.3.4
- Add / apply physics resets physics to frame 1.
- Reset all dynamics added to cloth physics panel.
- Reset physics sets frame sync to "play all frames".
- Warning on bake physics button when not "play all frames".
- Fixes to:
- Spring rig simulation reset error when baked.
- Remove all physics error.
- Rigidbody colliders positions when character rig is not at origin.
- Physics weight paint error in Blender 5.0.
- ARKit Proxy CSV load error in Blender 5.0.
- Receive Pose/Sequence error in Blender 5.0.
- Material bake in Blender 5.0.
### 2.3.3
- Blender 5.0 update.
- Blender 5.0 API changed bone selection methods and compositor which broke just about everything ...
- Eye shader updates, Limbus region should be more accurate to CC4/5 now.
- Expression drivers for CC3 rig use expression json.
- Materials build for current render engine (Eevee / Cycles).
- Buttons activate on Materials Parameters and DataLink panels to rebuild materials if changed.
- Rigify will fallback to next best face rig if selected in unavailable.
- Widgets for face rig included in Link/Append function.
- Fix to rigidbody collider orientation.
- Import option added in preferences to reset all custom normals on import.
- On some characters, custom split normals cause dark lighting artifacts that can be cured by removing custom normals.
- Camera switch markers generated when sending camera's across DataLink.
### 2.3.2
- Shaders updated for CC5:
- Skin shaders updated to use cavity maps and dual specular blending.
- Eye & Hair shaders updated.
- *Tearline Plus* and *Eye Occlusion Plus* shaders added.
- Wrinkle map system update for MH facial profile.
- Wrinkle map displacement update for CC5 head material.
- Textures packed with compositor for faster wrinkle system setup.
- Facerig updated to use expression json bone data.
- MetaHuman profile facial expression rig added.
- Expression control constraints and limits.
- Max texture pack size option added.
- Datalink use automatic lighting option added.
### 2.3.1
- 1:1 Viseme driver fix.
- Fix to Rigify bone locks being cleared by pose reset.
- Export Rigify motion only fix.
- CC Aligned metarig finger rotation fix.
- Fix to FBX exports not adding correct scale units.
- This was causing Rigify export avatar bones to scale incorrectly in Unity.
- Removed Rigify disable IK stretch option as Rigify IK doesn't work correctly without it.
- Rigify export using CC Base naming aligns bones to original CC rig orientations.
- In particular the jaw bones and eye bones should have a more compatible alignment. i.e. Unity ConvAI.
- Exit DataLink shutdown moved from atexit to Unregister.
### 2.3.0
- Rigify:
- Meta-human like facial expression rig for Extended, Standard and Traditional facial profiles.
- Expression rig supports retargeting, datalink motion transfer and Pose/Sequences to and from CC4/iClone8.
- Options to adjust control colours, and face rig placement and attachment to character.
- Supports ActorCore, ActorScan and ActorBuild characters.
- The expression rig allows for more advanced targeting and transfer, for now:
- ARKit Proxy:
- The ARKit proxy can be used as a target for LiveLink face transfer (e.g. from Face-it add-on)
- The proxy will drive the controls on the expression rig, which in turn drives the expression shape keys and head bones on the character.
- Adjustment parameters can be used to alter expression strength, relax / exaggerate expressions, shift the directional bias towards Left/Right and Up/Down expressions.
- Rotational adjustments for head bone.
- Load CSV function to load face capture recordings in CSV format.
- In particular this also works with the output from [Face Landmark Link](https://github.com/Qaanaaq/Face_Landmark_Link/)
- DataLink:
- Motion Pose/Sequences no longer dependent on Quaternions, preserves bone rotation modes across the the rig.
- Avatar/Prop skin bones identified by iClone ID, for accurate transfer and retargeting.
- Fix to incorrect prop mesh positioning due to bugged exports from iClone.
- Not all prop hierarchies can be exported exactly to Blender, sometimes they must have their transforms or scale reset in iClone.
- Which now allows for Pose/Sequence transfer of Prop animations from Blender back to iClone.
- Import & Transfer of lights and cameras with animations, including pose and sequences.
- Light & Camera sequence can be send back to iClone via Sequence transfer.
- (Must already exist in iClone for this to work)
- Lights can use IES and light cookie textures (Cycles Only)
- Spring Rigs:
- Hair spring bone generation fixes for Blender 4.4.
- Fix to remove and rebuild Rigify spring bone control rigs and simulations.
- Materials:
- Fix to incorrect embedded alpha channels from FBX importer.
- Fix to embedded alpha channel custom UV mappings.
- Emission rework, as multiplier on base color, with expanded strength.
- Reflection surface supported as clearcoat.
- Default (no texture) roughness and specular value fixes for Pbr and Tra shaders.
- General Fixes for Blender versions 3.4 - 4.4
- Wrinkle masks (local library images) packed into blend file.
- Export material fixes when image missing from texture image node.
- Export rigified error fixes.
- Convert to humanoid export json avatar type fix.
- Fix to Rigify export blendshapes on Rigify face rig and No face rig.
### 2.2.5
- Fix to Blender 4.1 material refraction setting.
- Rigify setup defaults rig to zero IK stretch.
- Fix to drivers trying build on OBJ / Morph imports.
- Some Bake streamlining and Baked GLTF export button.
- Datalink remote file transfer, to and from remote CC/iClone.
- Wrinkle Region ALL, driver update fix.
- Scene Tools - Eevee setup button.
- Blender 4.4 action slot fixes.
- Fix to Blender 4.4 proportion edit crash when removing all shape keys.
### 2.2.4
- Rigify face rig fallback to envelope weights if auto weights fail.
- DataLink Import motion will optionally (confirmation dialog) import motion to active character if no matching character.
- Rigify head turn expression driver corrected.
- Fixed collision objects being included in export.
- Fixed displacement strength export.
### 2.2.3
- Wrinkle map region strength controls added.
- Nose crease wrinkle maps added to Mouth_Smile_* expressions.
- Export Bake:
- Fixes and alpha fixes in Blender 4.3
- Iris Brightness adjustments removed when baking.
### 2.2.2
- Teeth and tongue added to bone / expression drivers.
- Meta-rig bone alignment options added the Basic rig panel.
- DataLink pose functions no longer break expression drivers.
### 2.2.1
- Material and Lighting fixes for Blender 4.3.
- When exporting or sending Rigified animations: IK stretch is now disabled in the rig.
- This should help with limb alignment problems on other platforms.
- Rigify Metarig bone rolls are aligned exactly to the CC/iC source bones now.
- This can be disabled in the preferences (or advanced settings) to use the original Metarig bone roll alignments.
- DataLink Send Avatar will ask to overwrite (or cancel transfer) any existing *same* character.
- Generic import option to disable auto-conversion of materials.
- Fix to import hanging when no characters in FBX.
- Rebuild drivers also rebuilds Rigify shape key drivers.
- DataLink transfer Sequence and Pose actions separately labelled "Sequence" and "Pose" no longer just "DataLink" for both.
- Operations that use object or mesh duplicates no longer duplicate the actions on the objects.
### 2.2.0
- Character Management functions - CCiC Create > Character Management:
- Transfer weights supports split body meshes.
- Voxel head diffuse skinning (if installed) button for selected character meshes.
- Clean Empty Data: Removes empty shape keys and empty vertex groups from the character meshes.
- Blend Body Weights: Blends body vertex weights with existing vertex weights on selected objects based on distance from the surface of the body, to correct vertex weighting for clothing items that don't conform correctly to the body, e.g. from Voxel Heat Diffusion weights or from Daz original weights.
- New scene presets added:
- scene view transform and world background strength controls added.
### 2.1.10
- Sets up Rigidbody physics when _appending_ a character from another library blend file via the Append button in the import panel.
- Note: Rigidbody physics does *not* work with linked character _overrides_.
- Export non-standard character fix.
- Support for split meshes when exporting, rebuilding materials, physics & rigging:
- Any mesh separated or duplicated from a source character mesh will be considered part of the character and an extension of that object.
- For split body meshes, the mesh with the head will be used as the source for facial expression drivers and wrinkle map drivers.
- Note: Split *body* meshes will _not_ import back into CC4 as standard CC3+ characters.
- Added buttons to rebuild and to remove just the expression and bone drivers to the Character Build Settings panel.
### 2.1.9
- Maintain operator context for scene operations.
- Align to view distance fix.
### 2.1.8
- Fixes to view 3d shading context in 4.2.
- Imported objects, materials, images and actions detection no longer uses tags.
- Added more checks to skip null material slots.
### 2.1.7
- Sync lights includes Scene IBL from CC/iC visual settings.
- Export to CC3/4 button fixed.
- Added extra checks for AccuRig / ActorScan when unknown humanoids detected.
### 2.1.6
- Import supports multiple file selection.
- To import multiple objects from a folder, press shift + select the FBX files in the importer file selection window.
- Fixes for blender version before 4.0:
- Replace mesh OBJ export.
- Rigging bone layer assignment.
- Property collection clear.
- Rigging widget control scale.
### 2.1.5
- Proportion edit should work on all character types (except Rigified)
- Sync lights and send pose should work in all modes and keeps the active mode.
- Lighting brightness adjust slider.
- Some GameBase detection fixes.
- GameBase skin roughness tweaks.
- Fix for vertex color sampling when json data is missing.
### 2.1.4
- DataLink receive Update / Replace function, to replace whole characters or selected parts.
- Fix to character validation and clean-up.
### 2.1.3
- Motion Set UI errors fixed.
- Eevee-Next SSR Eyes fixes.
- Iris brightness render settings for Eevee & Cycles.
- Bake fixes for Blender 4.2 refractive eyes.
- Export-Bake warnings when not build for Eevee.
- Lighting tweaks.
### 2.1.2
- Blender 4.2 lighting settings fixes and adjustments.
- Eevee-Next Raytracing, shadows and shadow jitter enabled on render settings and lights.
- Blender 4.0+ lighting presets use AgX.
- Eevee & Cycles global material options.
- Control of SSS weights, roughnes power and normals for various material types.
### 2.1.1
- Fix UDIM flattening on proportion editing and sculpt base mesh transfer.
- Lighting sync area correction.
- DataLink plugin version compatibility check.
### 2.1.0
- Motion Sets:
- Action name remapping and retargetting overhaul.
- Motion set functions: Load/Push/Clear/Select/Rename/Delete
- Motion prefix and use fake user option added for all animation retargeting and import.
- Motion set filter and motion set info function.
- NLA Tools:
- NLA Bake functions moved to NLA editor panel.
- Motion set panel in rigging and NLA editor and DataLink.
- NLA strip alignment and sizing utility functions.
- Fixes:
- Duplicating character no longer duplicates actions.
- Store object state checks objects/materials exist.
- Positioning fixes with rigify
### 2.0.9
- DataLink:
- DataLink main loop stability improvements.
- Live sequence back to CC4/iClone takes facial expression bone rotations into account.
- Live Sequence stop button.
- Replace mesh function: Quickly send (non topology changing) mesh alterations back to CC4.
- Update material & texture function: Send selected material data and textures back to CC4.
- Sync lighting recalculations.
- Export:
- Restores armature and object states after export.
- Fix to baking custom Diffuse Map channel.
- Fix to Blender 3.4-3.6 Eevee subsurface color.
- Material Parameter controls disabled for linked characters (unless library override).
### 2.0.8
- Import/Export:
- Fix to Update Unity Project being greyed out after saving as Blend file.
- Fix to export non-standard character.
- Fix to export non-standard ARP rigged character.
- (Blender 4.0+) Fix to reverting object and material name changes on export and other force name changes.
- Generic character/prop import expanded to support USD/USDZ.
- Rigify export now have choice of naming system:
- Metarig names (without Root bone) for exporting animations back into CC/iC.
- CC Base names (with Root bone) for exporting to Unity Auto-setup.
- Rigified characters/motion exports now generate custom HIK profile which can be used to import/convert Rigified motion exports into CC/iC.
- DataLink Rigified characters (optionally) disable tweak bones as they are not compatible with CC/iC animation.
- Scene tools:
- Scene lighting presets overhauled.
- Added function to align any object to view location and orientation (useful for placing lights and cameras).
- Added function to add a camera at current view location and orientation.
- Added function to setup a main face tracking camera centered on character's head.
- Added function to convert current view studio lighting into world lighting node setup.
- Rigify:
- Fix to GameBase detection.
- Fix to AccuRig generation code not being recognized as valid Rigify target.
- Fix to support Mixamorigs with suffix numbers in retargeting.
- Auto-retarget toggle added to automatically retarget any animation on character when using Quick Rigify.
- Shaders:
- Skin, eye and hair shaders updated to use Blender 4.0+ Random Walk (Skin) Subsurface Scattering.
- Displacement map (if present) will be used on skin materials for bump and mesh displacement.
- Cycles subsurface calculations and parameters tweaked.
- Separate cycles modifiers for Blender 3.4-3.6 and 4.0+.
- Eye Sclera color tint added.
- Cycles Tearline shader reworked.
- Character Management
- Character edit function added.
- Character duplicate function added (duplicates character objects and meta-data so can be used and configured independently).
- Character tools (select/rigify/convert/delete/duplicate), also sub-panel in DataLink.
- Convert to accessory fix.
- DataLink:
- Added Receiving prop posing and animation live sequence.
- Added custom prop rig control bones when sending through datalink.
- Added Direct Motion Transfer from CC/iC (automatic motion export->import).
- Added "Go iC" button to send (just props for now) back to iClone.
### 2.0.7
- Attempts to restore datalink when reloading linked blend file.
- All returning datalink operators will attempt to first reconnect if not connected.
- Facial expressions included in datalink send pose and sequence. (But not visemes)
- Currently certain expression bone movements are conflicting with existing bone movements.
- You may wish to avoid the Head_Turn expressions as a consequence.
- Character Proportion editing mode added to CC/iC Create panels.
- Spring bone hair binding will add an armature modifier for the hair object if absent, to allow binding for newly created hair mashes.
- Scale body weights now acts on the normalized existing hair weights.
### 2.0.6
- Restored Rigify retarget limb correction utilities.
- Fix to Blender 4.1 import crash caused by 4.1 removing auto-smoothed normals.
### 2.0.5
- Fix to converting generic objects to props.
- Fix to baking value textures back to CC4 when exporting converted props and humanoids.
- DataLink data send rate synchronization improvements.
- Rigify retarget and NLA bake options to bake to FK/IK/Both.
- Rig FK/IK mode set appropriately, unchanged when baking to 'Both'.
- Quick FK/IK switch button added to rigify mini-panel.
- Send Rigified pose or sequence fix.
- Rigify Jaw alignment changed to -Z.
### 2.0.4
- Linking/Appending:
- Added linking/append functions to auto-link to characters in blend files with full character data and functionality.
- Added connect function to re-build character data for linked/appended characters.
- Added custom properties to armatures/meshes and materials to aid re-connection of character data.
- Rebuilding materials will add this custom data to existing characters.
- Auto-linked/Re-connected characters can use full add-on functionality i.e. rigging, retargeting, exporting, rebuilding materials, etc...
- Rigify:
- DataLink pose retargeting teeth position fix.
- Eye bone and jaw bone alignments corrected.
- Face rig jaw constraints adjusted for less lip deformation.
- Parallax eye shader AO fix.
- Basic materials SSS fixes.
- Importing a bad or incompatible mesh should fail more gracefully.
### 2.0.3
- DataLink:
- Lighting and Camera sync.
- Send Character (Go-CC) back to CC4.
- Facial expressions and Visemes transferred in the pose and animation sequencing.
- Animation sequence now writes to rig and shape-key action tracks directly using low level fast keyframing, all at once at the end. Which is much faster, for both native rig and Rigify rig.
- Sequence rate matching so CC4 doesn't get too far ahead of itself.
- Supports Morph editing and mesh updating for sending back morph OBJ for automatic morph slider creation in CC4.
- Subsurface Recalculations (you may need to reset the preferences in the add-on prefs)
- Export of Hue, Sat, Brightness params to CC4.
- Fixed SSS material detection.
- Fixed ActorBuild generation detection and export.
- HIK and facial profiles copied with character export (if generated by CC4)
- OBJ import/export fix for Blender 4.0.
- OBJ import now supports full materials.
- Fixed T-pose orientation when exporting Rigified animation.
### 2.0.2
- Correction to malformed json texture paths when exporting character from CC4 directly to the root of a drive.
- Disabled image search on FBX importer, should import a little faster now.
### 2.0.1
- VRM import fixes.
- VRM to CC4 export generates HIK profile for auto characterization.
- Rigify fixes:
- Face bone roll axis corrections.
- Tongue bone meta-rig positioning corrections.
- Teeth bone retargeting corrections.
### 2.0.0
- Blender 4.0 support.
- WIP Experimental DataLink added:
- Currently in alpha stages, more a proof of concept at the moment.
- Bake add-on updated and merged into this project.
### 1.6.1
- Object Management:
- Generic material conversion better detects AO maps in Blender 3+
- Transfer vertex weights with posed armature fix.
- Empty transform hierarchy to Prop conversion puts bones in the right correct places.
- Exporting rigified animations with parented armatures now excludes those armatures from export.
### 1.6.0.4
- Fixed bake rigify retarget not assigning action to rig after baking
- Removes facial expression bone drivers on Rigifying (caused cyclic dependencies)
- Re-importing/rebuilding materials on a character will reload any texture images that are being re-used from existing or previous imports, just in case they have been changed on disk.
- Except when the image has been modified by the user and has not yet been saved.
- NLA Bake fix.
- Fix Generic character import.
- Spring rig panels show if character is invalid for spring rigging.
- Expression drivers for bones only apply to CC4 Ext and Std profiles
- Bone drivers for direct visemes Ah and Oh added.
- Viseme bone drivers now excluded when Jaw drivers are disabled.
- Export Rigified motion and Unity T-Pose generation fix.
### 1.6.0
- Rigifying character keeps meta-rig and allows for Re-Rigifying the control rig from the meta-rig.
- Useful for re-aligning bones, re-positioning face rig, etc...
- First draft of (optional) Dual Specular skin shader (Eevee & Cycles) with specular micro details.
- Added build options to generate drivers for Jaw, Eyes and Head bones from facial expression shape keys.
- Added build option to generate drivers for all expression shape keys driven from the body mesh shape keys.
- Which means only the expressions on the body mesh need to be updated/animated.
- Fix to turn off vertex colours in hair materials when hair mesh has blank vertex colour data (i.e. all zero).
- Facial Expression shape key value range expanded to -1.5 - 1.5 (except for eye look shape keys)
- Characters exported with Mouth Open as Morph, now correctly detects the body mesh.
- Fix to support sphere colliders in collisions shapes.
- Some additional lighting arrangements: Authority and Blur Warm.
### 1.5.8.5
- Fixes:
- Fix to empty transforms or deleted objects in export.
- Fix to transfer vertex weights leaving working copies behind.
- Some object management UI corrections.
- Fix to bake path when exporting character converted from generic with materials added after conversion.
- Fix to replace selected bones from hair cards.
- Fix cloth settings error in detect physics.
- Fix CC4 spring bones creation.
- Fix to exports of objects which originally had duplicate object names.
- Fix to import collider parenting crash when using Blender versions before 3.5
- Fix to UI panel in 2.93.
- Fix to collider generation in 2.93
- Fix to collider generation when Rigifying when posed.
### 1.5.8
- Spring Bones:
- Blender spring bone rigid body simulation added for spring bone hair rigs.
- Spring bone simulation controls.
- Hair spring bone chain renaming.
- Bone generation truncate and smoothing parameters.
- Added support for not quad grid poly mesh hair cards, should work with any hair mesh.
- Rigid body colliders for the spring bones that use the collision shapes from character creator.
- Rigify update for spring bone system.
- FK, IK and tweak bones for spring rigs.
- Rigify and spring bone UI updates.
- Baking spring bone simulation and animation into new animations
- Exporting rigified spring bone characters and animations, including the rigid body simulation as animation.
- Cloth Physics overhaul:
- Physics UI update.
- Better mapping of PhysX weight map to blender vertex pin weights.
- Physics presets updates, mass, tension and bending to better simulate the cloth type and work more consistently with external forces.
- Cloth physics preset detection on import.
- UI tools for point cache baking.
- Fixes to weightmap paint mode resetting texture.
- Browse button for painted weightmap, so you can find it.
- Weightmap assignment fix for materials with the same base name.
- Sculpt / Mesh:
- Character geometry transfer to shape-keys function.
- Character Objects:
- Add object to character (from another character) now copies the object into the new character.
- Transfer weight maps now works when posed to effectively parent in place target mesh.
- Other:
- Fix to eye close slider.
### 1.5.7
- Hair bone de-duplication.
- Bones from grease pencil lines or hair card generation now replaces (matching) existing bones.
- Grease Pencil lines generated only from active grease pencil layer, allowing for better organization.
- Added some color space fallbacks when using different color space configurations.
### 1.5.6
- Relative wrinkle strengths for individual wrinkle maps implemented.
- Overall wrinkle strength and curve power slider added the head material parameters.
- Competing wrinkle maps now use additive blending to solve overlap.
- Brow correction added for brow raise + brow compress wrinkles.
- Generated images not yet saves are autosaved for export (so they get included).
- Add custom bone function for hair rigging.
### 1.5.5
- Flow maps added to wrinkle map system.
- Better texture limiting for the head material.
- Fix to export crash when a texture field is missing in the JSON data.
- Corrupted JSON data detection and error report on import/build.
- In some cases resetting the collision shapes in CC4 will fix corrupted JSON data.
### 1.5.4
- Wrinkle Map system implemented.
- Characters with wrinkle maps will setup wrinkle shaders in the head material automatically.
- Preferences for Build Wrinkle Maps.
- OpenColorIO ACES color space support.
- Preferences for ACES color space overrides.
- Optional Texture Packing and Texture limits added to reduce number of textures in imported materals.
- Some systems can have very low texture limits (i.e. only 8 on some OSX systems) this can help import full CC4 characters.
- Preferences for pack and/or limit textures.
- Body sculpting updated:
- All sculpting modes work on a copy of the character.
- Multi-res applied base shape copied back to original character in a way that preserves existing shape keys.
- AO Map added to baking, layers and export.
- Additional strength, definition and mix mode controls added to layer ui.
- Spring Bone Hair Rigging (Cloth Rigging to follow)
- Added initial Hair curve extraction from hair cards.
- Spring bone hair generation from selected hair cards or greased pencil lines on surface.
- Hair card weight binding fine tuning controls.
- Hair cards are weighted individualy to neighboring bones and uniformly across to avoid lateral stretch and behaves more like cloth physics implementations.
- Spring bone generation from grease pencil lines.
- Fixes:
- Some extra transparency material detection.
- Fix to hand & finger bone roll alignment when bind pose has arms and hands at a steep downward angle.
- Fix to partial material name matching errors from ActorCore and AccuRig.
- Export bake socket fix for Blender 3.4+.
- Shapekey locks will be disabled and all shapekeys reset to zero on character export.
### 1.5.3
- Fix to retarget baking in Blender 3.4 not baking pose bones to rigify armature action.
- Fix to Rigify motion export bone root name.
### 1.5.2
- Rigify IK-FK influence controls replicated in Rigging panel.
- Fix to material setup error caused by missing normal map data.
### 1.5.1
- Fix to Generic character export.
- Fix to Generic converted character export.
### 1.5.0
- Rigify export mesh and/or animation overhaul.
- Smoothing groups added to export file dialog options.
- Support for CC4 Plugin, facial expression and viseme data export.
- Fix to legacy hair detection & scalp detection.
- Very slight subsurface added to scalp to prevent the dark/blueish artifacts on skin.
- Fix to bump maps connecting to normal sockets.
- Eye limbus darkness recalculated.
- Initial attempt at exporting Blender 3.3 Curve hair on characters via Alembic export.
### 1.4.9
- Fix to embedded image correction and image filepath comparisons.
- Fix to basic material texture loading.
- Convert to Accessory function added to Object Management.
### 1.4.8
- Adding existing RL material to a new character import will copy the material data.
### 1.4.7
- Match existing materials (for AccuRig imports) button added to Create tab and Rigify info pane.
- Attempts to assign existing materials to the AccuRig import that match the original export to AccurRig.
- For a Blender > AccuRig / ActorCore > Blender round trip workflow.
- Eye occlusion (Eevee) color darkened.
- Hair shader (Eevee) Specular blend added (Base surface Specularity -> Anisotropic Specularity)
### 1.4.6
- Fix for palm control location with less than 5 fingers.
### 1.4.5
- Fix for ActorCore / AccuRig import detection.
### 1.4.4
- Missing bone chains from CC3 rig will hide corresponding control rig bones (e.g. missing fingers)
- Export texture baking no longer duplicates baking on same materials across different objects.
- Export fix to CC3/4 when material name starts with a digit.
- Fix to import/convert of multiple generic GLTF/GLB props or characters.
- Fix to exporting Roughness and Metallic texture strength (should always be 100).
### 1.4.3
- Export bake maps now correctly sets bit depth and no alpha channel. Opacity texture bakes should work now.
- Convert generic character from objects to props added.
- Auto converts generic imports where possible, when using the import character button.
- Texture and material de-duplication (optional)
- ActorCore rigify accessory fix.
- CC/iC Create tab and panels added.
- Physics & Object Management moved to CC/iC Create.
- Multi-res sculpt & bake helper tools panels added.
### 1.4.2
- Separate Iris & Sclera emission colors for parallax eye shader.
- Displacement calculation reworked as it behaves differently in Blender 3.2.1 (was causing triangulation artifacts)
### 1.4.1
- Fixes
- Export T-pose action created correctly when character bind pose is already a T-pose.
- Tongue subsurface scatter parameter fixes in UI.
- Hair material UI corrected in Blender 3.2.1 (caused by renamed icon enums)
- Accessory bones now copy to rigify rig.
### 1.4.0
- Physics JSON changes and additions export.
- Unity T-pose export fix.
- Body Collision Mesh fix.
- Unity export logic fixes.
- Resize weightmap function.
- Increment/decrement paint strenth weightmap buttons.
- Image path writeback fix.
### 1.3.9
- Full Json data generation added.
- Rigify Toe IK Stretch Fix for Blender 3.1+
- Convert to Non-standard function.
- Convert from Generic character to Reallusion material based non-standard character.
- Export baking fixes and default PBR export support added.
### 1.3.8
- UI naming update.
- Repository rename to **cc_blender_tools** (title **CC/iC Blender Tools**), old repo links still apply.
- Initial support for exporting _any_ non-standard characters to CC4.
- Character should be aligned -Y forward, Z up for correct bone translation in CC4.
- Json data constructed on export to try and reconstruct materials using the **CC4 Blender Tools Plugin**.
- Materials must be based on Principled BSDF otherwise only texture name matching is possible.
- non-standard characters rigged with Auto-Rig Pro will (try to) invoke the ARP export operator (if installed)
- ARP export operator cleans up the rig and leaves only the relevent deformation bones.
- Import functions expanded to allow import from FBX, GLTF and VRM for non-standard characters.
- These are **not** considered to be CC/iC characters and have no material parameter, rigging or physics options.
### 1.3.7
- Iris Color and Iris Cloudy Color added.
- Tool bar tab renamed from **CC3** to **CC/iC**
- Some UI button name changes.
### 1.3.6
- Rigify
- Finger roll alignment fixed. All fingers now have exactly the same local bend axis.
- Disables all physics modifiers non-contributing armatures and meshes during retarget baking to speed it up a bit.
- Physics:
- Low poly (1/8th) Collision Body mesh created from decimating a copy of the Body mesh and removing the eyelashes.
- Hair would easily get trapped in the eyelashes and a lower poly collision mesh should speed up the cloth simulation.
- PhysX weight maps normalized, provides a more consistent and controllable simulation across different weight maps.
- Tweaked some cloth simulation parameters.
- Smart Hair meshes in particular should simulate better now.
- Unity:
- Added animation export options (Actions or Strips)
### 1.3.5
- Fix to shape-key action name matching.
### 1.3.4
- Rigify Retargeting:
- GameBase animation retargeting to CC Rigified Rig.
- (Experimental) Mixamo animation retargeting to CC Rigified Rig.
- Facial Expression Shape-key animation retargeting from source animation to CC character.
- Shape-key NLA baking added (optional).
- Materials:
- Diffuse Color, Hue, Saturation & Brightness parameters for Skin and Hair materials.
- Exporting:
- **Export to CC3** button renamed to **Export Morph Target** when editing OBJ imports with ObjKey.
- When **Export** button is disabled: The reason is displayed under the button.
- Export button Key check is overridable in the add-on preferences.
- Other Changes:
- Fix to ActorCore character (ActorCore website download) animation retargeting.
- Basic face rig generated from full face rig and has the same jaw & eye controls and parameters.
- Jaw pivot retargeting fixes.
- Palm bones no longer affect deformation or generate vertex weights.
- Crash fixes importing characters with very short names.
### 1.3.3
- CC/iC/ActorCore animation retargeting to CC Rigified Rig.
- Preview and Bake functions for retargeted animations.
- Arms, Legs, Heel & Height adjustments.
- Source armature and action selector, filtering by Armature name.
- Animation importer, import multiple animations in one go.
- Renames actions and armatures to match file names.
- Optionally removes meshes & materials.
- Bake control rig & retargeted animations to Unity.
- Export Rigified character to Unity.
- Basic face rig drivers for __Eye Look__ blend shapes. (If character has ExPlus blend shapes)
- GameBase to Rigify support.
### 1.3.2
- Face rig Automatic Weight failure detection and (some) auto-correction.
- Support for Voxel Heat Diffuse Skinning add-on for voxel weight mapping of the face rig.
### 1.3.1
- Optional two stage Rigify process to allow modifications to the meta-rig.
- Optional basic face rigging.
- Support for ActorCore character Rigging. (Only with basic face rigging)
- Support for G3 character generation, which includes Toon base characters. (i.e. the old CC3 base)
- Support for rigging G3Plus, G3, ActorCore characters exported from iClone. (iClone exports with no bone name prefix)
- Some Control Widget fixes and scaling.
- Better character Generation check on import for exports without JSON data.
### 1.3.0
- Rigify function added.
- Character objects are bound to the Rigify control rig.
- Vertex weights are remapped to Rigify deformation bones.
### 1.2.2
- Object Management panel
- All round-trip/Export object management functions moved to this panel.
- Including checks and clean up.
- Normalize weights function added.
- Bake/Combine Bump maps into Normal maps function added to Material Parameters panel.
- Highlight shift added to Eevee hair shader.
### 1.2.1
- Some fixes to exporting additional objects with character.
- Added a 'Check Export' function to identify potential problems with the export.
### 1.2.0
- Added initial export to Unity project function.
- Round-trip/Unity export additions and fixes.
- Added baking raw metallic and roughness values into textures, when no textures connected, when exporting.
- Modifiers removed from eye parts when exporting that prevented exporting blend shapes.
- Modifiers applied on FBX exports.
- Armature set to Pose mode on export, otherwise skeleton/bind-pose imports to CC3 incorrectly.
- Some file path fixes when baking new textures with exports.
- Fixed skin micro-smoothness calculation causing smoothness seams between head and body materials.
- Fixed UI updating wrong unmasked micro smoothness parameter.
- Blender 3.0+ subsurface scattering method set to Christensen-Burley, rather than Random-Walk, which does not work well with hair transparencies.
- Color mixers for actor core/color masked materials prevented from generating negative/zero color values which could affect diffuse lighting.
### 1.1.9
- Fixed PBR eye material crash.
### 1.1.8
- Texture Channel Mixer added, primarily for alteration of Actor Core characters with RGB and Color ID masks, but can be used with all CC3 based materials.
- Cycles hair anisotropy reworked.
- Cycles parameter tweaks:
- Brighter iris settings for SSR eyes.
- Hair subsurface reduced (May want to turn it off completely, there can be too many artifacts generated in the hair.)
- Skin subsurface slightly increased.
### 1.1.7
- Cornea material detection crash fix.
### 1.1.6
- Baking add-on compatibility update.
### 1.1.5
- Character and Object operators (**Character Settings** Panel):
- Add object to character, with parenting and armature modifier.
- Convert and add materials to character to use with material parameters and export write back.
- Transfer body vertex weights to object.
- Clean up object and material character data.
- Export back to CC3 will include Texture & Json data for any new objects added with these operators.
- Fix to Subsurface scattering for Blender 3.0 Cycles renders.
- Cycles adjustment settings added to preferences for user fine tuning of subsurface material parameters for Cycles.
### 1.1.4
- Added more support for exporting from characters with embedded textures.
### 1.1.3
- Baking fix for Blender versions 2.83 - 2.91
- (Experimental) Added operators to add new objects to the character data.
- See Character Settings panel with new object selected.
### 1.1.2
- Export accessory button no longer locked to character.
- Some import/export folder logic changed to try and cope with project folder & files being moved.
- Added custom texture node baking on export to CC3, including baking bump maps into normal maps on export.
- If nodes are used to modify (or replace) texture inputs to material shaders, those nodes can be baked into the texture channel on export. This assumes the mesh has a valid UV map.
- Bump maps can be baked into normal channels. Typically CC3 will only allow Normal maps OR bump maps for a material, not both, so an option has been added to combine them into just the normal map.
### 1.1.1
- Fix to crash from multiple character imports from iClone.
- Note: Exporting multiple characters in one Fbx from iClone to Blender is not fully supported.
- Characters should be exported individually.
- If multiple characters are detected then a warning pop-up will be displayed.
### 1.1.0
- Updated export function to generate compatible Fbx file for CC3 re-import with FbxKey and to write back json material parameter and texture information. To be used in tandem with [Blender Importer Plugin for CC3 3.44](https://github.com/soupday/CC3-Blender-Tools-Plugin) for full round-trip character editing in Blender.
- Import/Export Interface simplified.
- If character has Fbxkey then character is setup for editing. (i.e. Shapekeys locked to basis)
- Otherwise (character is posed or has animation) character is setup for rendering.
- Only Fbxkey character can be exported back to CC3.
- Optional Json and Texture write back for exports.
- Optional teeth rotation fix that affects some older generated characters when importing back into CC3.
- Bake on export function added to bake custom material nodes connected to master shader's texture map sockets into textures to include when re-importing back into CC3.
- Additional objects can be selected for exporting with the character, but must be properly parented and weighted with an armature modifier. Otherwise CC3 will ignore them.
- Some property and parameter fixes.
### 1.0.3
- First attempt at a single material parallax eye shader added. Which does not use SSR or transparency and thus can receive full shadows and subsurface scattering in Eevee.
### 1.0.2
- Fixed Eevee subsurface scattering settings:
- Reworked shaders to allow for direct application of subsurface radius to Principled BSDF nodes.
- Only the default values in the subsurface radius socket are used in Eevee rendering.
- As such, Eevee does not support inputs to subsurface radius and so shader and parameter code needed to be re-written to accomodate this.
- Cycles unaffected by this.
- Fixed node group upgrade code that incorrectly renamed existing node groups and did not properly replace old shader/node groups with new ones in existing blend files.
### 1.0.1
- Added render target preferences setting for Cycles and Eevee.
- Added cycles specific shaders for hair, tear-line and eye occlusion.
### 1.0.0
- Moved all shaders over to new shader model.
- Streamlined parameter and shader code to be data driven, rather than hard coded.
- Character, Object and Material parameters now stored independently for each character import.
- Json data parser to automatically set up all shader parameters.
### 0.7.4
- New eye shader model.
### 0.7.3
- New teeth and tongue shader model.
### 0.7.2
- New skin and head shader model.
### 0.7.1
- Back ported the more advanced Eye Occlusion shader from the Unity HDRP setup.
- Added displacement modifiers & parameters to Eye Occlusion and Tearline objects.
- Initial support for ActorCore models type C/D/D+.
### 0.6.3
- Fixed 'Export as accessory' correctly exporting as .obj when character was imported from an .obj file.
(And not exporting as .fbx with the wrong file extension)
### 0.6.2
- Lighting setups set Cycles transparent bounces set to 50 to accomodate Smart Hair mesh density.
- Lighting setups do not delete existing lights or camera, but they will hide them.
- Material setup now properly detects Game Base objects (i.e. Converted to Game Base in CC3 before exporting to Blender).
- Each material now maintains it's own set of parameters.
- Updating material parameters in **linked** mode will change the same parameters on all materials of the same type.
- Updating parameters in **selected** only mode will only change the parameter for that one material.
### 0.5.2
- Applies IOR shader input setting when building materials.
- Exposed some build preferences in the Build Settings panel.
- Enabled SSR and refraction when importing with refractive eyes.
- Auto updater now targets Main branch for current build.
### 0.5.1
- Fixed problem appending duplicate displacment map images.
- Fixed not removing eye displacement modifiers on rebuild.
- Added eye occlusion hardness parameter.
### 0.5.0
- Refractive Eyes:
- Iris refractive transmission with depth control and pupil size parameters.
- Limbus parameters.
- IOR and refractive depth parameters.
- Blood vessel and iris bump normals.
- Option in preferences to generate old eyes instead.
- Skin roughness power parameter added.
### 0.4.3
- Corrected an issue where the opacity maps were ignored in favour of diffuse alpha channels.
- Added opacity parameters for hair, scalp and eyelashes.
- Added roughness and specular parameters for eyelashes.
- Fixed a crash calling the import operator from from script.
- Added auto update scripts.
### 0.4.1
- Full smart hair support.
- Hair and scalp hints expanded to cover the smart hair system and moved to the preferences.
- Parameter changes update only that parameter in the imported or selected objects materials.
- Fake anisotropic highlights add to smart hair shader. (Can disable in the preferences.)
- Fake bump normals can be generated from the diffuse map if there is no normal or bump map present. (Can disable in the preferences.)
- Animation ranges only changed if physics enabled.
- Build settings and material parameters separated into their own interface panels.
- Build settings now applicable by material and the object and material build types as detected by the add-on are exposed and editable so you can fix them if it gets them wrong.
- Material parameters are context sensitive to the currently active object and material.
- Material parameters grouped into sections.
- Detects smart hair material or normal hair material and only shows relevant parameters.
- Option in preferences to gamma correct smart hair colours so they behave more like the colours in CC3.
### 0.3.0
- Fix to hair mesh detection with new smart hair system.
### 0.2.2 Alpha
- When no texture maps are present for an advanced node group, does not generate the node group.
- When exporting morph characters with .fbxkey or .objkey files, the key file is copied along with the export.
- Function added to reset preferences to default values.
- Alpha blend settings and back face culling settings can be applied to materials in the object now.
- Option to apply alpha blend settings to whole object(s) or just active materal.
- Remembers the applied alpha blend settings and re-applies when rebuilding materials.
- Option to pick Scalp Material.
- Only scans once on import for hair object and scalp material, so it can be cleared if it gets it wrong and wont keep putting it back.
- FBX import keeps track of the objects as well as the armature in case the armature is replaced.
- Physics support added:
- Uses the physX weight maps to auto-generate vertex pin weights for cloth/hair physics (Optional)
- Automatically sets up cloth/hair physics modifiers (Optional)
- Physics cloth presets can be applied to the selected object(s) and are remembered with rebuilding materials.
- Weightmaps can be added/removed to the individual materials of the objects.
- Weight map painting added.
- Saving of modified weight maps and Deleting weight map functions added.
@@ -1,297 +0,0 @@
# Copyright (C) 2021 Victor Soupday
# This file is part of CC/iC Blender Tools <https://github.com/soupday/cc_blender_tools>
#
# CC/iC Blender Tools 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 3 of the License, or
# (at your option) any later version.
#
# CC/iC Blender Tools 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 CC/iC Blender Tools. If not, see <https://www.gnu.org/licenses/>.
if "bpy" in locals():
import importlib
importlib.reload(addon_updater_ops)
importlib.reload(preferences)
importlib.reload(vars)
importlib.reload(params)
importlib.reload(utils)
importlib.reload(lib)
importlib.reload(cc)
importlib.reload(jsonutils)
importlib.reload(nodeutils)
importlib.reload(imageutils)
importlib.reload(channel_mixer)
importlib.reload(materials)
importlib.reload(characters)
importlib.reload(hik)
importlib.reload(meshutils)
importlib.reload(modifiers)
importlib.reload(shaders)
importlib.reload(basic)
importlib.reload(physics)
importlib.reload(bake)
importlib.reload(panels)
importlib.reload(properties)
importlib.reload(scene)
importlib.reload(exporter)
importlib.reload(importer)
importlib.reload(geom)
importlib.reload(bones)
importlib.reload(rigidbody)
importlib.reload(springbones)
importlib.reload(drivers)
importlib.reload(wrinkle)
importlib.reload(facerig_data)
importlib.reload(facerig)
importlib.reload(rigify_mapping_data)
importlib.reload(rigging)
importlib.reload(rigutils)
importlib.reload(sculpting)
importlib.reload(hair)
importlib.reload(colorspace)
importlib.reload(world)
importlib.reload(normal)
importlib.reload(link)
importlib.reload(proportion)
importlib.reload(iconutils)
importlib.reload(rlx)
import bpy
from . import addon_updater_ops
from . import preferences
from . import vars
from . import params
from . import utils
from . import lib
from . import cc
from . import jsonutils
from . import nodeutils
from . import imageutils
from . import channel_mixer
from . import materials
from . import characters
from . import hik
from . import meshutils
from . import modifiers
from . import shaders
from . import basic
from . import physics
from . import bake
from . import panels
from . import properties
from . import scene
from . import exporter
from . import importer
from . import geom
from . import bones
from . import rigidbody
from . import springbones
from . import drivers
from . import wrinkle
from . import facerig_data
from . import facerig
from . import rigify_mapping_data
from . import rigging
from . import rigutils
from . import sculpting
from . import hair
from . import colorspace
from . import world
from . import normal
from . import link
from . import proportion
from . import iconutils
from . import rlx
bl_info = {
"name": "CC/iC Tools",
"author": "Victor Soupday",
"version": (2, 3, 4),
"blender": (3, 4, 1),
"category": "Characters",
"location": "3D View > Properties > CC/iC Pipeline",
"description": "Automatic import and material setup of CC3/4-iClone7/8 characters.",
"wiki_url": "https://soupday.github.io/cc_blender_tools/index.html",
"tracker_url": "https://github.com/soupday/cc_blender_tools/issues",
}
vars.set_version_string(bl_info)
classes = (
preferences.CC3ToolsAddonPreferences,
preferences.MATERIAL_UL_weightedmatslots,
channel_mixer.CC3RGBMixer,
channel_mixer.CC3IDMixer,
channel_mixer.CC3MixerSettings,
properties.CCICLinkProps,
properties.CCICBakeCache,
properties.CCICBakeMaterialSettings,
properties.CCICBakeProps,
properties.CC3ActionList,
properties.CC3ArmatureList,
properties.CCIC_UI_MixItem,
properties.CCIC_UI_MixList,
properties.CCICActionOptions,
properties.CC3HeadParameters,
properties.CC3SkinParameters,
properties.CC3EyeParameters,
properties.CC3EyeOcclusionParameters,
properties.CC3TearlineParameters,
properties.CC3TeethParameters,
properties.CC3TongueParameters,
properties.CC3HairParameters,
properties.CC3PBRParameters,
properties.CC3SSSParameters,
properties.CC3BasicParameters,
properties.CC3TextureMapping,
properties.CC3EyeMaterialCache,
properties.CC3EyeOcclusionMaterialCache,
properties.CC3TearlineMaterialCache,
properties.CC3TeethMaterialCache,
properties.CC3TongueMaterialCache,
properties.CC3HairMaterialCache,
properties.CC3HeadMaterialCache,
properties.CC3SkinMaterialCache,
properties.CC3PBRMaterialCache,
properties.CC3SSSMaterialCache,
properties.CCICExpressionData,
properties.CC3ObjectCache,
properties.CCICActionStore,
properties.CC3CharacterCache,
properties.CC3ImportProps,
importer.CC3Import,
importer.CC3ImportAnimations,
exporter.CC3Export,
scene.CC3Scene,
bake.CC3BakeOperator,
rigging.CC3Rigifier,
rigging.CC3RigifierModal,
bake.CCICBakeSettings,
bake.CCICBaker,
bake.CCICJpegify,
springbones.CC3OperatorSpringBones,
physics.CC3OperatorPhysics,
materials.CC3OperatorMaterial,
characters.CC3OperatorCharacter,
characters.CCICWeightTransferBlend,
properties.CC3OperatorProperties,
preferences.CC3OperatorPreferences,
channel_mixer.CC3OperatorChannelMixer,
characters.CC3OperatorTransferCharacterGeometry,
characters.CC3OperatorTransferMeshGeometry,
characters.CCICCharacterRename,
characters.CCICCharacterConvertGeneric,
sculpting.CC3OperatorSculpt,
sculpting.CC3OperatorSculptExport,
hair.CC3OperatorHair,
hair.CC3ExportHair,
link.CCICDataLink,
link.CCICLinkConfirmDialog,
link.CCICLinkTest,
characters.CCICCharacterLink,
proportion.CCICCharacterProportions,
rigutils.CCICMotionSetRename,
rigutils.CCICMotionSetInfo,
rigutils.CCICRigUtils,
rigutils.CCIC_ImportMixBones_UL_List,
rigutils.CCIC_RigMixBones_UL_List,
rigutils.CCICActionImportFunctions,
rigutils.CCICActionImportOptions,
facerig.CCICImportARKitCSV,
panels.ARMATURE_UL_List,
panels.ACTION_UL_List,
panels.ACTION_SET_UL_List,
panels.UNITY_ACTION_UL_List,
# pipeline panels
panels.CC3ToolsPipelineImportPanel,
panels.CC3ToolsPipelineExportPanel,
panels.CC3CharacterSettingsPanel,
panels.CC3MaterialParametersPanel,
panels.CC3RigifyPanel,
panels.CCICBakePanel,
panels.CC3PipelineScenePanel,
# NLA panels
panels.CCICNLASetsPanel,
panels.CCICNLABakePanel,
# create panels
panels.CC3ToolsCreatePanel,
panels.CC3ObjectManagementPanel,
panels.CC3WeightPaintPanel,
panels.CC3ToolsPhysicsPanel,
panels.CC3SpringRigPanel,
panels.CC3ToolsSculptingPanel,
panels.CCICProportionPanel,
panels.CC3HairPanel,
# link panels
panels.CCICDataLinkPanel,
panels.CCICAnimationToolsPanel,
panels.CCICLinkScenePanel,
# control panels
panels.CC3SpringControlPanel,
# test panels
panels.CC3ToolsUtilityPanel,
)
def register():
addon_updater_ops.register(bl_info)
for cls in classes:
bpy.utils.register_class(cls)
iconutils.register()
bpy.types.Scene.CC3ImportProps = bpy.props.PointerProperty(type=properties.CC3ImportProps)
bpy.types.Scene.CCICBakeProps = bpy.props.PointerProperty(type=properties.CCICBakeProps)
bpy.types.Scene.CCICLinkProps = bpy.props.PointerProperty(type=properties.CCICLinkProps)
bpy.types.TOPBAR_MT_file_import.append(importer.menu_func_import)
bpy.types.TOPBAR_MT_file_import.append(importer.menu_func_import_animation)
bpy.types.TOPBAR_MT_file_export.append(exporter.menu_func_export)
if link.disconnect not in bpy.app.handlers.load_pre:
bpy.app.handlers.load_pre.append(link.disconnect)
if link.reconnect not in bpy.app.handlers.load_post:
bpy.app.handlers.load_post.append(link.reconnect)
bpy.app.timers.register(link.reconnect, first_interval=0.5, persistent=False)
def unregister():
link.disconnect()
addon_updater_ops.unregister()
bpy.types.TOPBAR_MT_file_import.remove(importer.menu_func_import)
bpy.types.TOPBAR_MT_file_import.remove(importer.menu_func_import_animation)
bpy.types.TOPBAR_MT_file_export.remove(exporter.menu_func_export)
for cls in classes:
bpy.utils.unregister_class(cls)
iconutils.unregister()
del(bpy.types.Scene.CC3ImportProps)
del(bpy.types.Scene.CCICBakeProps)
del(bpy.types.Scene.CCICLinkProps)
if link.disconnect in bpy.app.handlers.load_pre:
bpy.app.handlers.load_pre.remove(link.disconnect)
if link.reconnect in bpy.app.handlers.load_post:
bpy.app.handlers.load_post.remove(link.reconnect)
@@ -1,500 +0,0 @@
# Copyright (C) 2021 Victor Soupday
# This file is part of CC/iC Blender Tools <https://github.com/soupday/cc_blender_tools>
#
# CC/iC Blender Tools 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 3 of the License, or
# (at your option) any later version.
#
# CC/iC Blender Tools 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 CC/iC Blender Tools. If not, see <https://www.gnu.org/licenses/>.
import bpy
import os
from . import materials, nodeutils, imageutils, jsonutils, params, lib, utils, vars
def reset_shader(nodes, links, shader_label, shader_name):
bsdf_id = "(" + str(shader_name) + "_BSDF)"
bsdf_node: bpy.types.Node = None
output_node: bpy.types.Node = None
links.clear()
for n in nodes:
if n.type == "BSDF_PRINCIPLED":
if not bsdf_node:
utils.log_info("Keeping old BSDF: " + n.name)
bsdf_node = n
else:
nodes.remove(n)
elif n.type == "OUTPUT_MATERIAL":
if output_node:
nodes.remove(n)
else:
output_node = n
else:
nodes.remove(n)
if not bsdf_node:
bsdf_node = nodes.new("ShaderNodeBsdfPrincipled")
bsdf_node.name = utils.unique_name(bsdf_id)
bsdf_node.label = shader_label
bsdf_node.width = 240
utils.log_info("Creating new BSDF: " + bsdf_node.name)
if not output_node:
output_node = nodes.new("ShaderNodeOutputMaterial")
bsdf_node.location = (0,0)
output_node.location = (400, 0)
emission_socket = nodeutils.input_socket(bsdf_node, "Emission")
nodeutils.set_node_input_value(bsdf_node, emission_socket, (0,0,0))
if utils.B400():
emission_strength_socket = nodeutils.input_socket(bsdf_node, "Emission Strength")
nodeutils.set_node_input_value(bsdf_node, emission_strength_socket, 0)
# connect the shader to the output
nodeutils.link_nodes(links, bsdf_node, "BSDF", output_node, "Surface")
return bsdf_node
def connect_tearline_material(obj, mat, mat_json, processed_images):
props = vars.props()
chr_cache = props.get_character_cache(obj, mat)
parameters = chr_cache.basic_parameters
obj_cache = chr_cache.get_object_cache(obj)
mat_cache = chr_cache.get_material_cache(mat)
nodes = mat.node_tree.nodes
links = mat.node_tree.links
bsdf_node = reset_shader(nodes, links, "Tearline Shader", "basic_tearline")
base_color_socket = nodeutils.input_socket(bsdf_node, "Base Color")
metallic_socket = nodeutils.input_socket(bsdf_node, "Metallic")
specular_socket = nodeutils.input_socket(bsdf_node, "Specular")
roughness_socket = nodeutils.input_socket(bsdf_node, "Roughness")
alpha_socket = nodeutils.input_socket(bsdf_node, "Alpha")
nodeutils.set_node_input_value(bsdf_node, base_color_socket, (1.0, 1.0, 1.0, 1.0))
nodeutils.set_node_input_value(bsdf_node, metallic_socket, 1.0)
nodeutils.set_node_input_value(bsdf_node, specular_socket, 1.0)
nodeutils.set_node_input_value(bsdf_node, roughness_socket, parameters.tearline_roughness)
nodeutils.set_node_input_value(bsdf_node, alpha_socket, parameters.tearline_alpha)
bsdf_node.name = utils.unique_name("eye_tearline_shader")
materials.set_material_alpha(mat, "BLEND", shadows=False, refraction=True)
def connect_eye_occlusion_material(obj, mat, mat_json, processed_images):
props = vars.props()
chr_cache = props.get_character_cache(obj, mat)
parameters = chr_cache.basic_parameters
obj_cache = chr_cache.get_object_cache(obj)
mat_cache = chr_cache.get_material_cache(mat)
nodes = mat.node_tree.nodes
links = mat.node_tree.links
bsdf_node = reset_shader(nodes, links, "Eye Occlusion Shader", "basic_eye_occlusion")
bsdf_node.name = utils.unique_name("eye_occlusion_shader")
base_color_socket = nodeutils.input_socket(bsdf_node, "Base Color")
metallic_socket = nodeutils.input_socket(bsdf_node, "Metallic")
specular_socket = nodeutils.input_socket(bsdf_node, "Specular")
roughness_socket = nodeutils.input_socket(bsdf_node, "Roughness")
alpha_socket = nodeutils.input_socket(bsdf_node, "Alpha")
nodeutils.set_node_input_value(bsdf_node, base_color_socket, (0,0,0,1))
nodeutils.set_node_input_value(bsdf_node, metallic_socket, 0.0)
nodeutils.set_node_input_value(bsdf_node, specular_socket, 0.0)
nodeutils.set_node_input_value(bsdf_node, roughness_socket, 1.0)
nodeutils.reset_cursor()
# groups
group = lib.get_node_group("eye_occlusion_mask")
occ_node = nodeutils.make_node_group_node(nodes, group, "Eye Occulsion Alpha", "eye_occlusion_mask")
# values
nodeutils.set_node_input_value(occ_node, "Strength", parameters.eye_occlusion)
nodeutils.set_node_input_value(occ_node, "Hardness", parameters.eye_occlusion_power)
# links
nodeutils.link_nodes(links, occ_node, "Alpha", bsdf_node, alpha_socket)
materials.set_material_alpha(mat, "BLEND", shadows=False)
def connect_basic_eye_material(obj, mat, mat_json, processed_images):
props = vars.props()
chr_cache = props.get_character_cache(obj, mat)
parameters = chr_cache.basic_parameters
obj_cache = chr_cache.get_object_cache(obj)
mat_cache = chr_cache.get_material_cache(mat)
nodes = mat.node_tree.nodes
links = mat.node_tree.links
bsdf_node = reset_shader(nodes, links, "Eye Shader", "basic_eye")
base_color_socket = nodeutils.input_socket(bsdf_node, "Base Color")
metallic_socket = nodeutils.input_socket(bsdf_node, "Metallic")
specular_socket = nodeutils.input_socket(bsdf_node, "Specular")
roughness_socket = nodeutils.input_socket(bsdf_node, "Roughness")
alpha_socket = nodeutils.input_socket(bsdf_node, "Alpha")
normal_socket = nodeutils.input_socket(bsdf_node, "Normal")
clearcoat_socket = nodeutils.input_socket(bsdf_node, "Clearcoat")
clearcoat_roughness_socket = nodeutils.input_socket(bsdf_node, "Clearcoat Roughness")
# Base Color
#
nodeutils.reset_cursor()
diffuse_image = find_material_image_basic(mat, "DIFFUSE", mat_json, processed_images)
if diffuse_image is not None:
diffuse_node = nodeutils.make_image_node(nodes, diffuse_image, "(DIFFUSE)")
nodeutils.advance_cursor(1.0)
hsv_node = nodeutils.make_shader_node(nodes, "ShaderNodeHueSaturation", 0.6)
hsv_node.label = "HSV"
hsv_node.name = utils.unique_name("eye_basic_hsv")
nodeutils.set_node_input_value(hsv_node, "Value", parameters.eye_brightness)
# links
nodeutils.link_nodes(links, diffuse_node, "Color", hsv_node, "Color")
nodeutils.link_nodes(links, hsv_node, "Color", bsdf_node, base_color_socket)
# Metallic
#
nodeutils.reset_cursor()
metallic_node = nodeutils.make_value_node(nodes, "Eye Metallic", "eye_metallic", 0.0)
nodeutils.link_nodes(links, metallic_node, "Value", bsdf_node, metallic_socket)
# Specular
#
nodeutils.reset_cursor()
specular_node = nodeutils.make_value_node(nodes, "Eye Specular", "eye_specular", parameters.eye_specular)
nodeutils.link_nodes(links, specular_node, "Value", bsdf_node, specular_socket)
# Roughness
#
nodeutils.reset_cursor()
roughness_node = nodeutils.make_value_node(nodes, "Eye Roughness", "eye_roughness", parameters.eye_roughness)
nodeutils.link_nodes(links, roughness_node, "Value", bsdf_node, roughness_socket)
# Alpha
#
nodeutils.set_node_input_value(bsdf_node, alpha_socket, 1.0)
# Normal
#
nodeutils.reset_cursor()
normal_image = find_material_image_basic(mat, "SCLERANORMAL", mat_json, processed_images)
if normal_image is not None:
strength_node = nodeutils.make_value_node(nodes, "Normal Strength", "eye_normal", parameters.eye_normal)
normal_node = nodeutils.make_image_node(nodes, normal_image, "(SCLERANORMAL)")
nodeutils.advance_cursor()
normalmap_node = nodeutils.make_shader_node(nodes, "ShaderNodeNormalMap", 0.6)
nodeutils.link_nodes(links, strength_node, "Value", normalmap_node, "Strength")
nodeutils.link_nodes(links, normal_node, "Color", normalmap_node, "Color")
nodeutils.link_nodes(links, normalmap_node, "Normal", bsdf_node, normal_socket)
# Clearcoat
#
nodeutils.set_node_input_value(bsdf_node, clearcoat_socket, 1.0)
nodeutils.set_node_input_value(bsdf_node, clearcoat_roughness_socket, 0.15)
materials.set_material_alpha(mat, "OPAQUE")
return
def connect_basic_material(obj, mat, mat_json, processed_images):
props = vars.props()
chr_cache = props.get_character_cache(obj, mat)
parameters = chr_cache.basic_parameters
obj_cache = chr_cache.get_object_cache(obj)
mat_cache = chr_cache.get_material_cache(mat)
nodes = mat.node_tree.nodes
links = mat.node_tree.links
bsdf_node = reset_shader(nodes, links, "Basic Shader", "basic")
base_color_socket = nodeutils.input_socket(bsdf_node, "Base Color")
metallic_socket = nodeutils.input_socket(bsdf_node, "Metallic")
specular_socket = nodeutils.input_socket(bsdf_node, "Specular")
roughness_socket = nodeutils.input_socket(bsdf_node, "Roughness")
alpha_socket = nodeutils.input_socket(bsdf_node, "Alpha")
emission_socket = nodeutils.input_socket(bsdf_node, "Emission")
emission_strength_socket = nodeutils.input_socket(bsdf_node, "Emission Strength")
normal_socket = nodeutils.input_socket(bsdf_node, "Normal")
sss_socket = nodeutils.input_socket(bsdf_node, "Subsurface")
# Base Color
#
nodeutils.reset_cursor()
diffuse_image = find_material_image_basic(mat, "DIFFUSE", mat_json, processed_images)
ao_image = find_material_image_basic(mat, "AO", mat_json, processed_images)
diffuse_node = ao_node = None
if (diffuse_image is not None):
diffuse_node = nodeutils.make_image_node(nodes, diffuse_image, "(DIFFUSE)")
if ao_image is not None:
if mat_cache.is_skin() or mat_cache.is_nails():
prop = "skin_ao"
ao_strength = parameters.skin_ao
elif mat_cache.is_hair():
prop = "hair_ao"
ao_strength = parameters.hair_ao
else:
prop = "default_ao"
ao_strength = parameters.default_ao
fac_node = nodeutils.make_value_node(nodes, "Ambient Occlusion Strength", prop, ao_strength)
ao_node = nodeutils.make_image_node(nodes, ao_image, "ao_tex")
nodeutils.advance_cursor(1.5)
nodeutils.drop_cursor(0.75)
mix_node = nodeutils.make_mixrgb_node(nodes, "MULTIPLY")
mix_node.name = utils.unique_name("AO_Mix")
mix_node.label = "AO Mix"
nodeutils.link_nodes(links, diffuse_node, "Color", mix_node, "Color1")
nodeutils.link_nodes(links, ao_node, "Color", mix_node, "Color2")
nodeutils.link_nodes(links, fac_node, "Value", mix_node, "Fac")
nodeutils.link_nodes(links, mix_node, "Color", bsdf_node, base_color_socket)
else:
nodeutils.link_nodes(links, diffuse_node, "Color", bsdf_node, base_color_socket)
# SSS
#
if mat_cache.is_skin():
nodeutils.set_node_input_value(bsdf_node, sss_socket, 0.25)
else:
nodeutils.set_node_input_value(bsdf_node, sss_socket, 0)
# Metallic
#
nodeutils.reset_cursor()
metallic_image = find_material_image_basic(mat, "METALLIC", mat_json, processed_images)
metallic_node = None
if metallic_image is not None:
metallic_node = nodeutils.make_image_node(nodes, metallic_image, "(METALLIC)")
nodeutils.link_nodes(links, metallic_node, "Color", bsdf_node, metallic_socket)
# Specular
#
nodeutils.reset_cursor()
specular_image = find_material_image_basic(mat, "SPECULAR", mat_json, processed_images)
mask_image = find_material_image_basic(mat, "SPECMASK", mat_json, processed_images)
prop = "none"
spec = 0.5
if mat_cache.is_skin() or mat_cache.is_nails():
prop = "skin_specular"
spec = parameters.skin_specular
elif mat_cache.is_hair():
prop = "hair_specular"
spec = parameters.hair_specular
elif mat_cache.is_scalp() or mat_cache.is_eyelash():
prop = "scalp_specular"
spec = parameters.scalp_specular
elif mat_cache.is_teeth():
prop = "teeth_specular"
spec = parameters.teeth_specular
elif mat_cache.is_tongue():
prop = "tongue_specular"
spec = parameters.tongue_specular
else:
prop = "default_specular"
spec = parameters.default_specular
specular_node = mask_node = mult_node = None
if specular_image is not None:
specular_node = nodeutils.make_image_node(nodes, specular_image, "(SPECULAR)")
nodeutils.link_nodes(links, specular_node, "Color", bsdf_node, specular_socket)
# always make a specular value node for skin or if there is a mask (but no map)
elif prop != "none":
specular_node = nodeutils.make_value_node(nodes, "Specular Strength", prop, spec)
nodeutils.link_nodes(links, specular_node, "Value", bsdf_node, specular_socket)
if mask_image is not None:
mask_node = nodeutils.make_image_node(nodes, mask_image, "(SPECMASK)")
nodeutils.advance_cursor()
mult_node = nodeutils.make_math_node(nodes, "MULTIPLY")
mult_node.name = utils.unique_name("(Specular_Mult)")
mult_node.label = "Apply Specular Mask"
if specular_node.type == "VALUE":
nodeutils.link_nodes(links, specular_node, "Value", mult_node, 0)
else:
nodeutils.link_nodes(links, specular_node, "Color", mult_node, 0)
nodeutils.link_nodes(links, mask_node, "Color", mult_node, 1)
nodeutils.link_nodes(links, mult_node, "Value", bsdf_node, specular_socket)
# Roughness
#
nodeutils.reset_cursor()
roughness_image = find_material_image_basic(mat, "ROUGHNESS", mat_json, processed_images)
roughness_node = None
if roughness_image is not None:
roughness_node = nodeutils.make_image_node(nodes, roughness_image, "(ROUGHNESS)")
if mat_cache.is_skin():
prop = "skin_roughness"
roughness = parameters.skin_roughness
elif mat_cache.is_teeth():
prop = "teeth_roughness"
roughness = parameters.teeth_roughness
elif mat_cache.is_tongue():
prop = "tongue_roughness"
roughness = parameters.tongue_roughness
else:
prop = "none"
roughness = 1
if mat_cache.material_type.startswith("SKIN"):
nodeutils.advance_cursor()
remap_node = nodeutils.make_shader_node(nodes, "ShaderNodeMapRange")
remap_node.name = utils.unique_name(prop)
nodeutils.set_node_input_value(remap_node, "To Min", roughness)
nodeutils.link_nodes(links, roughness_node, "Color", remap_node, "Value")
nodeutils.link_nodes(links, remap_node, "Result", bsdf_node, roughness_socket)
elif mat_cache.material_type.startswith("TEETH") or mat_cache.material_type == "TONGUE":
nodeutils.advance_cursor()
rmult_node = nodeutils.make_math_node(nodes, "MULTIPLY", 1, roughness)
rmult_node.name = utils.unique_name(prop)
rmult_node.label = "Roughness Remap"
nodeutils.link_nodes(links, roughness_node, "Color", rmult_node, 0)
nodeutils.link_nodes(links, rmult_node, "Value", bsdf_node, roughness_socket)
else:
nodeutils.link_nodes(links, roughness_node, "Color", bsdf_node, roughness_socket)
# Emission
#
nodeutils.reset_cursor()
emission_image = find_material_image_basic(mat,"EMISSION", mat_json, processed_images)
emission_node = None
if emission_image is not None:
emission_node = nodeutils.make_image_node(nodes, emission_image, "(EMISSION)")
nodeutils.link_nodes(links, emission_node, "Color", bsdf_node, emission_socket)
emission_strength = jsonutils.get_texture_channel_strength(mat_json, "Glow", 0.0)
nodeutils.set_node_input_value(bsdf_node, emission_strength_socket, emission_strength)
# Alpha
#
nodeutils.reset_cursor()
alpha_image = find_material_image_basic(mat, "ALPHA", mat_json, processed_images)
alpha_node = None
if alpha_image is not None:
alpha_node = nodeutils.make_image_node(nodes, alpha_image, "(ALPHA)")
dir, file = os.path.split(alpha_image.filepath)
if "_diffuse" in file.lower() or "_albedo" in file.lower():
nodeutils.link_nodes(links, alpha_node, "Alpha", bsdf_node, alpha_socket)
else:
nodeutils.link_nodes(links, alpha_node, "Color", bsdf_node, alpha_socket)
elif diffuse_node:
nodeutils.link_nodes(links, diffuse_node, "Alpha", bsdf_node, alpha_socket)
# material alpha blend settings
method = materials.determine_material_alpha(obj_cache, mat_cache, mat_json)
materials.set_material_alpha(mat, method)
# Normal
#
nodeutils.reset_cursor()
normal_strength = jsonutils.get_texture_channel_strength(mat_json, "Normal", 1.0)
normal_image = find_material_image_basic(mat, "NORMAL", mat_json, processed_images)
bump_image = find_material_image_basic(mat,"BUMP", mat_json, processed_images)
normal_node = bump_node = normalmap_node = bumpmap_node = None
if normal_image is not None:
normal_node = nodeutils.make_image_node(nodes, normal_image, "(NORMAL)")
nodeutils.advance_cursor()
normalmap_node = nodeutils.make_shader_node(nodes, "ShaderNodeNormalMap", 0.6)
nodeutils.link_nodes(links, normal_node, "Color", normalmap_node, "Color")
nodeutils.link_nodes(links, normalmap_node, "Normal", bsdf_node, normal_socket)
nodeutils.set_node_input_value(normalmap_node, "Strength", normal_strength)
if bump_image is not None:
if mat_cache.is_hair() or mat_cache.is_eyelash() or mat_cache.is_scalp():
prop = "hair_bump"
bump_strength = parameters.hair_bump
else:
prop = "default_bump"
bump_strength = parameters.default_bump
bump_strength_node = nodeutils.make_value_node(nodes, "Bump Strength", prop, bump_strength / 1000)
bump_node = nodeutils.make_image_node(nodes, bump_image, "(BUMP)")
nodeutils.advance_cursor()
bumpmap_node = nodeutils.make_shader_node(nodes, "ShaderNodeBump", 0.7)
nodeutils.advance_cursor()
nodeutils.link_nodes(links, bump_strength_node, "Value", bumpmap_node, "Distance")
nodeutils.link_nodes(links, bump_node, "Color", bumpmap_node, "Height")
if normal_image is not None:
nodeutils.link_nodes(links, normalmap_node, "Normal", bumpmap_node, "Normal")
nodeutils.link_nodes(links, bumpmap_node, "Normal", bsdf_node, normal_socket)
def find_material_image_basic(mat, tex_type, mat_json, processed_images):
json_id = imageutils.get_image_type_json_id(tex_type)
tex_json = jsonutils.get_texture_info(mat_json, json_id)
return imageutils.find_material_image(mat, tex_type, processed_images, tex_json, mat_json)
def update_basic_material(mat, mat_cache, prop):
props = vars.props()
chr_cache = props.get_character_cache(None, mat)
parameters = chr_cache.basic_parameters
scope = locals()
if mat is not None and mat.node_tree is not None:
nodes = mat.node_tree.nodes
for node in nodes:
for prop_info in params.BASIC_PROPS:
prop_name = prop_info[3]
prop_node = prop_info[2]
if prop_node == "":
prop_node = prop_name
if prop_node in node.name and (prop == "ALL" or prop == prop_name):
prop_dir = prop_info[0]
prop_socket = prop_info[1]
try:
if len(prop_info) > 5:
prop_eval = prop_info[5]
else:
prop_eval = "parameters." + prop_name
prop_value = eval(prop_eval, None, scope)
if prop_dir == "IN":
nodeutils.set_node_input_value(node, prop_socket, prop_value)
elif prop_dir == "OUT":
nodeutils.set_node_output_value(node, prop_socket, prop_value)
except Exception as e:
utils.log_error("update_basic_materials(): Unable to evaluate or set: " + prop_eval, e)
def init_basic_default(chr_cache):
props = vars.props()
parameters = chr_cache.basic_parameters
for prop_info in params.BASIC_PROPS:
prop_name = prop_info[3]
prop_default = prop_info[4]
try:
prop_eval = "parameters." + prop_name + " = " + str(prop_default)
exec(prop_eval, None, locals())
except Exception as e:
utils.log_error("init_basic_default(): Unable to set: " + prop_eval, e)
if chr_cache.is_actor_core():
chr_cache.basic_parameters.default_ao = 0.2
chr_cache.basic_parameters.default_specular = 0.2
@@ -1,133 +0,0 @@
import bpy
from mathutils import Matrix, Vector
from . import bones, utils, vars
def set_pose_bone_world_transform(arm, pose_bone: bpy.types.PoseBone, world_transform: dict, local_transform: dict):
if arm and pose_bone and world_transform and local_transform:
loc = utils.array_to_vector(world_transform["location"]) * 0.01
rot = utils.array_to_quaternion(world_transform["rotation"])
s = utils.array_to_vector(world_transform["scale"])
sca = Vector((utils.sign(s.x), utils.sign(s.y), utils.sign(s.z))) * arm.scale
M = utils.make_transform_matrix(loc, rot, sca)
pose_bone.matrix = M
def set_mesh_world_transform(arm, mesh_obj: bpy.types.Object, world_transform: dict, local_transform: dict):
if arm and mesh_obj and world_transform and local_transform:
loc = utils.array_to_vector(world_transform["location"]) * 0.01
rot = utils.array_to_quaternion(world_transform["rotation"])
s = utils.array_to_vector(world_transform["scale"])
sca = Vector((utils.sign(s.x), utils.sign(s.y), utils.sign(s.z))) * arm.scale
M = utils.make_transform_matrix(loc, rot, sca)
mesh_obj.matrix_world = M
def bone_name_match(rl_name, blender_name):
if rl_name == "_Object_Pivot_Node_":
rl_name = "CC_Base_Pivot"
export_name = bones.rl_export_bone_name(rl_name)
unduplicated_name = utils.strip_name(blender_name)
if rl_name == unduplicated_name or export_name == unduplicated_name:
return True
return False
def deduplicate_name(name, names: dict):
count = names[name] if name in names else 0
names[name] = count + 1
return f"{name}.{count:03d}" if count > 0 else name
def match_id_tree(rl_tree, arm=None,
pose_bone: bpy.types.PoseBone=None,
mesh_obj: bpy.types.Object=None,
parent_bone: bpy.types.PoseBone=None,
id_map=None,
names=None,
pose=False):
"""If supplying an armature, match the bone tree to the armature and return a mapping (by id)
to the armature bones. If no armature (i.e. for rigified avatars), then map the bones (by id)
to unduplicated bone names"""
if arm and not (pose_bone or mesh_obj):
pose_bone = arm.pose.bones[0]
if id_map is None:
id_map = {}
if names is None:
names = {}
name = pose_bone.name if pose_bone else mesh_obj.name if mesh_obj else deduplicate_name(rl_tree["name"], names)
# id_map is a dict of bones by ID, mapping the source skin_bone name to the armature bone or mesh
id_map[rl_tree["id"]] = {
"source": rl_tree["name"],
"name": name,
"mesh": mesh_obj is not None,
}
# id tree
id_tree = {
"name": name,
"id": rl_tree["id"],
"source": rl_tree["name"],
"children": []
}
world_transform_data = rl_tree.get("world_transform", None)
local_transform_data = rl_tree.get("local_transform", None)
if mesh_obj:
id_tree["mesh"] = True
if pose and world_transform_data:
set_mesh_world_transform(arm, mesh_obj, world_transform_data, local_transform_data)
else:
if pose and world_transform_data:
set_pose_bone_world_transform(arm, pose_bone, world_transform_data, local_transform_data)
#utils.log_detail(f"Bone: {bone.name} / Tree: {rl_tree['name']} {rl_tree['id']}")
for child_tree in rl_tree["children"]:
child_name = child_tree["name"]
#utils.log_detail(f"Trying: {child_name}")
if arm:
found = False
if not found and not child_tree["children"]:
for obj in arm.children:
if obj.parent and obj.parent_type == "BONE" and obj.parent_bone == pose_bone.name:
if bone_name_match(child_name, obj.name):
#utils.log_detail(f" - child_mesh: {obj.name} / child_tree: {child_name} - parented to: {obj.parent_bone}")
found = True
child_tree = match_id_tree(child_tree, arm=arm, mesh_obj=obj, parent_bone=pose_bone, id_map=id_map, pose=pose)[0]
if child_tree:
id_tree["children"].append(child_tree)
break
if not found:
for child_bone in pose_bone.children:
if bone_name_match(child_name, child_bone.name):
#utils.log_detail(f" - child_bone: {child_bone.name} / child_tree: {child_name}")
found = True
child_tree = match_id_tree(child_tree, arm=arm, pose_bone=child_bone, id_map=id_map, pose=pose)[0]
if child_tree:
id_tree["children"].append(child_tree)
break
else:
child_tree = match_id_tree(child_tree, id_map=id_map, names=names)[0]
if child_tree:
id_tree["children"].append(child_tree)
return id_tree, id_map
def confirm_bone_order(bones, ids, id_map: dict):
result = True
for id, id_def in id_map.items():
if id not in ids or id_def["source"] not in bones:
utils.log_warn(f"bone {id_def['source']} ({id}) not found in skin bones!")
result = False
if result and len(ids) < len(id_map):
utils.log_info("All bones present, but more bones found in id_tree!")
elif result:
utils.log_info("All bones present!")
return result
def convert_id_tree(arm, id_tree_root):
if not id_tree_root: return None
id_tree, id_map = match_id_tree(id_tree_root, arm=arm)
return id_tree, id_map
@@ -1,17 +0,0 @@
{
"last_check": "2026-01-09 16:23:22.382598",
"backup_date": "December-30-2025",
"update_ready": true,
"ignore": false,
"just_restored": false,
"just_updated": false,
"version_text": {
"link": "https://api.github.com/repos/soupday/cc_blender_tools/zipball/refs/tags/2_3_4_p1",
"version": [
2,
3,
4,
1
]
}
}

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