2026-03-11_1
This commit is contained in:
@@ -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\
|
||||||
|
|||||||
@@ -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",
|
||||||
|
|||||||
@@ -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
@@ -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
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
-1
@@ -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]
|
||||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
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
-1
@@ -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,
|
||||||
|
|||||||
Binary file not shown.
@@ -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
@@ -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
|
|
||||||
BIN
Binary file not shown.
@@ -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>.
|
|
||||||
-884
@@ -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.
|
|
||||||
|
|
||||||
BIN
Binary file not shown.
BIN
Binary file not shown.
-297
@@ -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)
|
|
||||||
|
|
||||||
-1714
File diff suppressed because it is too large
Load Diff
-1513
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
|
|
||||||
|
|
||||||
-2006
File diff suppressed because it is too large
Load Diff
@@ -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
|
|
||||||
-17
@@ -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
|
|
||||||
]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user