2026-02-16

This commit is contained in:
2026-03-17 15:25:32 -06:00
parent d5dd373de0
commit 60100fbab2
560 changed files with 33397 additions and 20776 deletions
@@ -1,5 +1,5 @@
{
"last_check": "2025-12-29 12:28:02.952580",
"last_check": "2026-02-12 10:41:20.508861",
"backup_date": "December-11-2025",
"update_ready": false,
"ignore": false,
+3 -1
View File
@@ -18,6 +18,7 @@
# ***** END GPL LICENCE BLOCK *****
import bpy
from . import Tools
class GizmoSizeUp(bpy.types.Operator):
"""Share keyframes between all the selected objects and bones"""
@@ -144,7 +145,8 @@ class SwitchBoneCollectionsVisibility(bpy.types.Operator):
start = 'pose.bones["'
end = '"]'
#get all the animated bones from the fcurves
for fcu in obj.animation_data.action.fcurves:
fcurves = Tools.get_fcurves(obj, obj.animation_data.action)
for fcu in fcurves:
start_index = fcu.data_path.find(start)
end_index = fcu.data_path.find(end)
#if it's not a posebone fcurve then skip
File diff suppressed because it is too large Load Diff
+289 -189
View File
@@ -8,6 +8,7 @@ from mathutils import Vector
# import math
from math import pi
from . import emp
from . import TempCtrls
# import time
from bpy.app.handlers import persistent
from bpy_extras import anim_utils
@@ -68,17 +69,21 @@ def draw_text_callback_px(self, context):
font_id = 0 # XXX, need to find out how best to get this.
#fade in
factor = 0.5
if not self.fade_out_start:
alpha = (self.timer.time_duration / self.fade_duration) * factor if not self.no_timer else factor
else:
timer = self.timer.time_duration - self.fade_out_start
alpha = (1 * factor) - ((timer / self.fade_duration ) * factor)
try:
if not self.fade_out_start:
alpha = (self.timer.time_duration / self.fade_duration) * factor if not self.no_timer else factor
else:
timer = self.timer.time_duration - self.fade_out_start
alpha = (1 * factor) - ((timer / self.fade_duration ) * factor)
except ReferenceError:
return
as_height = asset_shelf_height(context)
# draw some text
blf.position(font_id, 70, 45 + as_height, 0)
blf.position(font_id, 70, 50 + as_height, 0)
blf.size(font_id, self.size)
blf.color(0, 1, 1, 0, alpha)
blf.draw(font_id, self.text)
class Markers_Retimer(bpy.types.Operator):
@@ -188,8 +193,8 @@ class Markers_Retimer(bpy.types.Operator):
if strip.mute:
continue
# actions.add(strip.action)
all_fcurves.add(get_fcurves_channelbag(obj, strip.action))
fcurves = get_fcurves_channelbag(obj, obj.animation_data.action)
all_fcurves.add(get_fcurves(obj, strip.action))
fcurves = get_fcurves(obj, obj.animation_data.action)
if fcurves not in all_fcurves:
all_fcurves.add(fcurves)
# actions.add(obj.animation_data.action)
@@ -352,19 +357,23 @@ class Markers_Retimer(bpy.types.Operator):
self.report({'ERROR'}, str(e) + '. Quitting RetimerMarkers')
return {'CANCELLED'}
def init_frame_start_marker(self, scene, name = "Frame_Start"):
def init_frame_start_marker(self, scene, name = "Frame Start"):
'''Create the start frame marker'''
self.frame_start_marker = scene.timeline_markers.new(name, frame = self.frame_start)
self.frame_start_marker[ "Frame_Start"] = True
self.frame_start_marker.select = False
self.frame_start_marker_name = name
if name in scene.timeline_markers:
self.frame_start_marker = scene.timeline_markers[name]
else:
self.frame_start_marker = scene.timeline_markers.new(name, frame = self.frame_start)
self.frame_start_marker.select = False
def init_frame_end_marker(self, scene, name = "Frame_End"):
def init_frame_end_marker(self, scene, name = "Frame End"):
'''Create the start frame marker'''
self.frame_end_marker = scene.timeline_markers.new(name, frame = self.frame_end)
self.frame_end_marker["Frame_End"] = True
self.frame_end_marker.select = False
self.frame_end_marker_name = name
if name in scene.timeline_markers:
self.frame_end_marker = scene.timeline_markers[name]
else:
self.frame_end_marker = scene.timeline_markers.new(name, frame = self.frame_end)
self.frame_end_marker.select = False
def check_removed_markers(self, scene):
#check if markers are gone in case of undo or if removed
@@ -507,7 +516,7 @@ class Markers_BakeRange(bpy.types.Operator):
atb.bake_frame_range = False
return {'CANCELLED'}
scene.animtoolbox.bake_frame_range = True
atb.bake_frame_range = True
self.frame_start = atb.bake_frame_start
self.frame_end = atb.bake_frame_end
@@ -640,15 +649,23 @@ def update_cursor_matrix(context):
item_matrix = item.id_data.matrix_world @ item.matrix
else:
item_matrix = item.matrix_world
#apply the new cursor position based on previous cursor
relative_matrix = Matrix(item['relative_cursor'])
cursor.matrix = item_matrix @ relative_matrix.inverted()
if bpy.app.version < (5, 0, 0):
relative_matrix = Matrix(item['relative_cursor'])
else:
# Since Blender 5 I need to reshape the array into a matrix using numpy
matrix_4x4 = np.array(item['relative_cursor']).reshape(4, 4).T
relative_matrix = Matrix(matrix_4x4)
cursor.matrix = item_matrix @ relative_matrix.inverted()
cursor_matrix = cursor.matrix
def assign_relative_matrix(context, items):
def assign_relative_matrix(context, items, prop = 'relative_cursor'):
'''get the cursor relative matrices either from object or bones'''
global last_matrices
if 'last_matrices' not in globals():
last_matrices = []
cursor_matrix = context.scene.cursor.matrix
matrix_avg = get_matrix_avg(items)
@@ -664,9 +681,9 @@ def assign_relative_matrix(context, items):
item_matrix = matrix_avg if len(items) > 1 else item_matrix
matrix_dist = cursor_matrix.inverted() @ item_matrix
item['relative_cursor'] = matrix_dist
item[prop] = matrix_dist
return last_matrices
return matrix_dist
def assign_relative_cursor_prop(context):
'''Updating the matrix property of the cursor distance on the bone'''
@@ -762,6 +779,7 @@ class Keyframe_Offset(bpy.types.Operator):
def invoke(self, context, event):
self.mouse_x = event.mouse_x
self.initial_offset = context.scene.animtoolbox.keyframes_offset
# self.mouse_y = event.mouse_y
context.window_manager.modal_handler_add(self)
return {'RUNNING_MODAL'}
@@ -779,8 +797,11 @@ class Keyframe_Offset(bpy.types.Operator):
self.mouse_x = event.mouse_x
scene.animtoolbox.keyframes_offset += value*0.01
if event.type in {'ESC'} or event.value == 'RELEASE': # Cancel
if event.type in {'ESC', 'RIGHTMOUSE'} and event.value == 'RELEASE': # Cancel
scene.animtoolbox.keyframes_offset = self.initial_offset
return {'CANCELLED'}
if event.value == 'RELEASE' and event.type == 'LEFTMOUSE':
self.execute(context)
return {'FINISHED'}
return {'RUNNING_MODAL'}
@@ -838,7 +859,7 @@ def remove_offset_property(context):
def add_offset(obj, offset, prev_offset, bone_id = None):
fcurves = get_fcurves_channelbag(obj, obj.animation_data.action)
fcurves = get_fcurves(obj, obj.animation_data.action)
for fcu in fcurves:
if bone_id is not None:
@@ -876,83 +897,71 @@ def add_offset(obj, offset, prev_offset, bone_id = None):
fcu.keyframe_points.foreach_set('handle_left', updated_left_handle)
fcu.keyframe_points.foreach_set('handle_right', updated_right_handle)
fcu.update()
def add_keyframe(bone):
def get_locked_fcus(obj, locked_fcus = None):
# Locked fcus might have been already created, if it's not None it was already not checked
if locked_fcus is not None:
return locked_fcus
locked_fcus = set()
if not obj.animation_data:
return locked_fcus
if not obj.animation_data.action:
return locked_fcus
fcurves = get_fcurves(obj, obj.animation_data.action)
locked_fcus = {(fcu.data_path, fcu.array_index) for fcu in fcurves if fcu.lock}
return locked_fcus
def add_keyframe(bone, locked_fcus = None):
obj = bone.id_data
if bone.rotation_mode == 'QUATERNION':
rotation = 'rotation_quaternion'
elif bone.rotation_mode == 'AXIS_ANGLE':
rotation = 'rotation_axis_angle'
else:
rotation = 'rotation_euler'
rotation = 'rotation_euler' if len(bone.rotation_mode) == 3 else 'rotation_' + bone.rotation_mode.lower()
# rotation = 'rotation_quaternion' if bone.rotation_mode == 'QUATERNION' else 'rotation_euler'
transforms = ['location', rotation, 'scale']
path = bone.path_from_id() if obj.mode == 'POSE' else ''
groups = dict()
#find fcurves that are locked to exclude them
excluded_fcus = {}
if obj.animation_data:
if obj.animation_data.action:
fcurves = get_fcurves_channelbag(obj, obj.animation_data.action)
for fcu in fcurves:
if path not in fcu.data_path or not fcu.lock:
continue
groups.update({(fcu.data_path, fcu.array_index ): fcu.group.name})
if fcu.data_path in excluded_fcus:
excluded_fcus[fcu.data_path].append(fcu.array_index)
else:
excluded_fcus.update({fcu.data_path : [fcu.array_index]})
path = bone.path_from_id() + '.' if obj.mode == 'POSE' else ''
locked_fcus = get_locked_fcus(obj, locked_fcus)
#insert keyframe only to channels that are not excluded
for transform in transforms:
#path = bone.path_from_id() + '.' + transform
path = bone.path_from_id() + '.' + transform if obj.mode == 'POSE' else transform
# if path not in excluded_fcus:
# bone.keyframe_insert(transform)
# continue
data_path = path + transform
length = len(getattr(bone, transform))
for i in range(length):
if path in excluded_fcus:
if i in excluded_fcus[path]:
continue
if (path, i) in groups:
group_name = groups[(path, i)]
else:
group_name = bone.name
bone.keyframe_insert(transform, index = i, group = group_name, frame = bpy.context.scene.frame_current_final)
def get_fcu_inbetweens(bone, frame, fcu_inbetweens):
for i in range(length):
# Check if this channel being filtered
if TempCtrls.filter_transform(transform, i):
continue
# Skip if the fcurve was locked
if (data_path, i) in locked_fcus:
continue
bone.keyframe_insert(transform, index = i, group = bone.name, frame = bpy.context.scene.frame_current_final)
def get_fcu_inbetweens(bone, frame, fcu_inbetweens, inbetweens):
obj = bone.id_data
if bone.rotation_mode == 'QUATERNION':
rotation = 'rotation_quaternion'
elif bone.rotation_mode == 'AXIS_ANGLE':
rotation = 'rotation_axis_angle'
else:
rotation = 'rotation_euler'
rot_channel = rot_mode_to_channel(bone.rotation_mode)
# rotation = 'rotation_quaternion' if bone.rotation_mode == 'QUATERNION' else 'rotation_euler'
transforms = ['location', rotation, 'scale']
# fcurves = obj.animation_data.action.fcurves
fcurves = get_fcurves_channelbag(obj, obj.animation_data.action)
transforms = ['location', rot_channel, 'scale']
fcurves = get_fcurves(obj, obj.animation_data.action)
#insert keyframe only to channels that are not excluded
location, rotation, scale = bone.matrix_basis.decompose()
for transform in transforms:
path = bone.path_from_id() + '.' + transform if obj.mode == 'POSE' else transform
length = len(getattr(bone, transform))
for i in range(length):
fcu = fcurves.find(data_path = path, index = i)
if not fcu:
continue
if fcu.lock:
continue
if not inbetweens:
# If there is no inbetweens then we just want to store the fcurves for auto handles
fcu_inbetweens[fcu] = None
continue
if 'rotation' in transform:
if transform == 'rotation_euler':
coords = bone.matrix_basis.to_euler(bone.rotation_mode)
@@ -960,7 +969,7 @@ def get_fcu_inbetweens(bone, frame, fcu_inbetweens):
coords = rotation
else:
coords = locals()[transform]
if fcu in fcu_inbetweens:
fcu_inbetweens[fcu].update({frame : coords[i]})
else:
@@ -987,8 +996,10 @@ def add_inbetween_key(self, context):
#strip = anim_data.nla_tracks[obj.als.layer_index].strips[0]
#frame = round(bake_ops.frame_evaluation(context.scene.frame_current, strip), 3)
frame = context.scene.frame_current
paths = [bone.path_from_id() for bone in context.selected_pose_bones]
fcurves = get_fcurves_channelbag(obj, anim_data.action)
if obj.mode == 'POSE':
paths = [bone.path_from_id() for bone in context.selected_pose_bones]
fcurves = get_fcurves(obj, anim_data.action)
for fcu in fcurves:
#filter selected bones
if obj.mode == 'POSE': #apply only to selected bones
@@ -1059,10 +1070,8 @@ def add_inbetween_worldmatrix(self, context):
if action is None:
self['inbetween_worldmatrix'] = 0.0
return
# frame = round(context.scene.frame_current, 2)
if not self.inbetween_worldmatrix:
# scene.frame_set(frame)
return
if obj.mode == 'POSE' and not context.selected_pose_bones:
@@ -1073,37 +1082,38 @@ def add_inbetween_worldmatrix(self, context):
def get_matrix_other(self, context):
'''Get the matrix from the other frame, either previous or the next'''
#get all the list of the frames from the different channels for each bone
#rest of the code is only for posebones
#creating a new dict to avoid frame_set at the same frame multiple times. It will get the matrix of multiple bones from each frame
#This is done just to avoid extra scene evaluation using frame_set
#adding the bone to the new flipped dict key using the frame number
scene = context.scene
prev_frame_bones = dict()
next_frame_bones = dict()
for item, frames in self.items_frames.items():
#get the next and previous frames
next_frames = [frame for frame in frames if frame > self.frame]
if next_frames:
next_frame = next_frames[0]
next_frame = next_frames[0]
#the bones that are on the next frame
if next_frame in next_frame_bones:
next_frame_bones[next_frame].append(item)
else:
next_frame_bones.update({next_frame : [item]})
prev_frames = [frame for frame in frames if frame < self.frame]
if prev_frames:
prev_frame = prev_frames[-1]
if not prev_frame and not next_frame:
prev_frame = prev_frames[-1]
if prev_frame in prev_frame_bones:
prev_frame_bones[prev_frame].append(item)
else:
prev_frame_bones.update({prev_frame : [item]})
if not prev_frames and not next_frames:
continue
#get all the list of the frames from the different channels for each bone
#rest of the code is only for posebones
#creating a new dict to avoid frame_set at the same frame multiple times. It will get the matrix of multiple bones from each frame
#This is done just to avoid extra scene evaluation using frame_set
#adding the bone to the new flipped dict key using the frame number
prev_frame_bones = dict()
if prev_frame in prev_frame_bones:
prev_frame_bones[prev_frame].append(item)
else:
prev_frame_bones.update({prev_frame : [item]})
#the bones that are on the next frame
next_frame_bones = dict()
if next_frame in next_frame_bones:
next_frame_bones[next_frame].append(item)
else:
next_frame_bones.update({next_frame : [item]})
#if item.id_data.mode == 'POSE':
@@ -1154,7 +1164,7 @@ class InbetweenWorldMatrix(bpy.types.Operator):
bone = None
fcurves = get_fcurves_channelbag(obj, action)
fcurves = get_fcurves(obj, action)
for fcu in fcurves:
#filter selected bones
if obj.mode == 'POSE': #apply only to selected bones
@@ -1188,7 +1198,6 @@ class InbetweenWorldMatrix(bpy.types.Operator):
self.items_frames.update({bone : frames})
get_matrix_other(self, context)
context.window_manager.modal_handler_add(self)
return {'RUNNING_MODAL'}
@@ -1205,6 +1214,20 @@ class InbetweenWorldMatrix(bpy.types.Operator):
#modal is being cancelled because of undo issue with the modal running through the property
return {'FINISHED'}
if event.type in {'ESC', 'RIGHTMOUSE'} and event.value == 'RELEASE': # Cancel
# Revert to the original matrix
for bone, matrix in self.items_matrix_org.items():
if bone.id_data.mode == 'POSE':
bone.matrix = matrix
else:
bone.matrix_world = matrix
# if scene.tool_settings.use_keyframe_insert_auto:
# add_keyframe(bone)
context.window_manager.atb_ui['is_dragging'] = False
self.atb['inbetween_worldmatrix'] = 0
return {'CANCELLED'}
if event.value == 'RELEASE': # Stop the modal on next frame. Don't block the event since we want to exit the field dragging
self.stop = True
@@ -1227,8 +1250,8 @@ class InbetweenWorldMatrix(bpy.types.Operator):
else:
bone.matrix_world = matrix
if scene.tool_settings.use_keyframe_insert_auto:
add_keyframe(bone)
# if scene.tool_settings.use_keyframe_insert_auto:
# add_keyframe(bone)
return {'PASS_THROUGH'}
@@ -1296,18 +1319,19 @@ class BlendToMirroModal(bpy.types.Operator):
if self.stop:
context.window_manager.atb_ui['is_dragging'] = False
for bone in context.selected_pose_bones:
if scene.tool_settings.use_keyframe_insert_auto:
if scene.tool_settings.use_keyframe_insert_auto and self.finish == {'FINISHED'}:
add_keyframe(bone)
if 'matrix' in bone:
del bone['matrix']
self.ui['blend_mirror'] = 0
redraw_areas(['PROPERTIES'])
#modal is being cancelled because of undo issue with the modal running through the property
return {'FINISHED'}
return self.finish
if event.value == 'RELEASE': # Stop the modal on next frame. Don't block the event since we want to exit the field dragging
self.stop = True
self.finish = {'CANCELLED'} if event.type in {'ESC', 'RIGHTMOUSE'} else {'FINISHED'}
for bone in context.selected_pose_bones:
mirror_bone = find_mirror_bone(bone)
if mirror_bone is None:
@@ -1315,7 +1339,10 @@ class BlendToMirroModal(bpy.types.Operator):
mirror_bone_matrix = mirror_bone.matrix_basis.copy()
mirror_plane = Matrix.Scale(-1, 4, (1, 0, 0))
mirrored_matrix = mirror_plane @ mirror_bone_matrix @ mirror_plane
matrix = Matrix(bone['matrix']).lerp(mirrored_matrix, self.ui.blend_mirror)
bone_matrix = bone['matrix'] if bpy.app.version < (5, 0, 0) else np.array(bone['matrix']).reshape(4, 4).T
matrix = Matrix(bone_matrix).lerp(mirrored_matrix, self.ui.blend_mirror)
matrix = filter_matrix_properties(context, bone.matrix_basis, matrix)
bone.matrix_basis = matrix
@@ -1357,7 +1384,7 @@ class SelectKeyframesOffset(bpy.types.Operator):
if 'keyframes_offset' in bone:
#check if there are bones with an offset
posemode = True
obj.data.bones[bone.name].select = True
TempCtrls.posebone_select(bone, True)
if posemode and obj.mode != 'POSE':
context.view_layer.objects.active = obj
bpy.ops.object.mode_set(mode = 'POSE')
@@ -1365,6 +1392,25 @@ class SelectKeyframesOffset(bpy.types.Operator):
return {'FINISHED'}
def get_frame_range(context, obj):
def selected_frame_range(frame_range):
smartframes = set()
if obj.mode == 'POSE':
bones_paths = [bone.path_from_id() for bone in context.selected_pose_bones if bone.name in obj.pose.bones]
fcurves = get_fcurves(obj, obj.animation_data.action)
for fcu in fcurves:
path = fcu.data_path.split('"].')[0] + '"]'
if obj.mode == 'POSE' and path not in bones_paths:
continue
for keyframe in fcu.keyframe_points:
smartframes.add(round(keyframe.co[0], 2))
if keyframe.select_control_point:
frame_range.append(round(keyframe.co[0], 2))
return smartframes
#get the frame range
frame_range = []
inbetweens = []
@@ -1377,27 +1423,21 @@ def get_frame_range(context, obj):
if atb.bake_frame_start > atb.bake_frame_end:
atb.bake_frame_start = atb.bake_frame_end
frame_range = list(range(atb.bake_frame_start, atb.bake_frame_end+1))
elif atb.range_type == 'SELECTED':
if obj.mode == 'POSE':
bones_paths = [bone.path_from_id() for bone in context.selected_pose_bones]
if obj.animation_data is None:
return frame_range
if obj.animation_data.action is None:
return frame_range
smartframes = set()
elif obj.animation_data is None:
return frame_range, inbetweens
elif obj.animation_data.action is None:
return frame_range, inbetweens
elif atb.range_type == 'SELECTED_RANGE':
# Create a list of a keyframes between the first selected keyframes and the last selected one
selected_frame_range(frame_range)
if frame_range:
frame_range = list(range(int(min(frame_range)), int(max(frame_range))+1))
fcurves = get_fcurves_channelbag(obj, obj.animation_data.action)
for fcu in fcurves:
path = fcu.data_path.split('"].')[0] + '"]'
if obj.mode == 'POSE' and path not in bones_paths:
continue
for keyframe in fcu.keyframe_points:
smartframes.add(round(keyframe.co[0], 2))
if keyframe.select_control_point:
frame_range.append(round(keyframe.co[0], 2))
elif atb.range_type == 'SELECTED':
smartframes = selected_frame_range(frame_range)
smartframes = sorted(smartframes)
frame_range = sorted(set(frame_range))
@@ -1422,6 +1462,7 @@ def get_frame_range(context, obj):
def notification_invoke(self, context, text = 'Copy Matrix', size = 20.0):
'''get ready and lauch the notification'''
wm = context.window_manager
self.timer = wm.event_timer_add(0.1, window=context.window)
self.no_timer = False
@@ -1432,16 +1473,21 @@ def notification_invoke(self, context, text = 'Copy Matrix', size = 20.0):
self.fade_out_start = 0
self.draw_handle = bpy.types.SpaceView3D.draw_handler_add(draw_text_callback_px, (self, context), 'WINDOW', 'POST_PIXEL')
redraw_areas(['VIEW_3D'])
def update_notification(self, context, event, esc = False, fade_out = True):
'''notify with a modal operator that the matrix was being copied'''
if not hasattr(self, 'draw_handle'):
return {'FINISHED'}
#Quit from the notification
if event.type in {'ESC'} or esc:
bpy.types.SpaceView3D.draw_handler_remove(self.draw_handle, 'WINDOW')
context.area.tag_redraw()
if context.area is not None:
context.area.tag_redraw()
elif hasattr(self, 'area'):
self.area.tag_redraw()
context.window_manager.event_timer_remove(self.timer)
del self.draw_handle
return {'FINISHED'}
@@ -1470,6 +1516,14 @@ def update_notification(self, context, event, esc = False, fade_out = True):
return {'PASS_THROUGH'}
def compatible_rotation(bone, bones_prevrot):
'''Make sure the bone is not flipping and is compatible with previous frame rotation'''
rotation_channel = rot_mode_to_channel(bone.rotation_mode)
if bone in bones_prevrot:
rot = getattr(bone, rotation_channel)
rot.make_compatible(bones_prevrot[bone])
bones_prevrot[bone] = getattr(bone, rotation_channel).copy()
class CopyMatrix(bpy.types.Operator):
"""Copy the matrix of the selection and store it"""
bl_idname = "anim.copy_matrix"
@@ -1546,6 +1600,7 @@ class PasteMatrix(bpy.types.Operator):
frame_current_final = scene.frame_current_final
bones_matrices = dict()
bones_prevrot = dict()
constrained = set()
#constraint evaluation is
# con.target.matrix_world.copy() @ con.inverse_matrix @ basis
@@ -1564,6 +1619,11 @@ class PasteMatrix(bpy.types.Operator):
for obj in context.selected_objects:
frame_range, inbetweens = get_frame_range(context, obj)
fcu_inbetweens = dict()
# Checking for locked fcurves before iterating
locked_fcus = get_locked_fcus(obj)
for frame in sorted(frame_range + inbetweens):
scene.frame_set(int(frame), subframe = frame % 1)
if obj.mode == 'POSE':
@@ -1588,7 +1648,7 @@ class PasteMatrix(bpy.types.Operator):
#adding the keyframes
for bone in bones_matrices.keys():
paste_keyframes_get_inbetweens(scene, bone, inbetweens, frame, frame_range, fcu_inbetweens)
paste_keyframes_get_inbetweens(scene, bone, inbetweens, frame, frame_range, fcu_inbetweens, locked_fcus, bones_prevrot)
else:
target = obj
@@ -1603,10 +1663,13 @@ class PasteMatrix(bpy.types.Operator):
matrix_copied = reverse_constraint_offset(target.matrix_world, matrix_copied)
target.matrix_world = filter_matrix_properties(context, target.matrix_world, matrix_copied)
paste_keyframes_get_inbetweens(scene, target, inbetweens, frame, frame_range, fcu_inbetweens)
paste_keyframes_get_inbetweens(scene, target, inbetweens, frame, frame_range, fcu_inbetweens, locked_fcus, bones_prevrot)
if inbetweens and len(frame_range) > 1:
add_interpolations(fcu_inbetweens.keys(), fcu_inbetweens, frames = frame_range[:-1])
if len(frame_range) > 1:
if inbetweens:
add_interpolations(fcu_inbetweens.keys(), fcu_inbetweens, frames = frame_range[:-1])
else:
set_auto_handles(fcu_inbetweens.keys(), frame_range)
if context.scene.frame_current_final != frame_current_final:
scene.frame_current = int(frame_current_final)
@@ -1678,7 +1741,8 @@ class CopyRelativeMatrix(bpy.types.Operator):
if obj.type == 'ARMATURE':
#Get the distance from the selected bones
for bone_relative in obj.pose.bones:
if not bone_relative.bone.select:
selected = bone_relative.bone.select if bpy.app.version < (5, 0, 0) else bone_relative.select
if not selected:
continue
if bone_relative == source_active:
continue
@@ -1704,14 +1768,18 @@ class CopyRelativeMatrix(bpy.types.Operator):
return {'FINISHED'}
def paste_keyframes_get_inbetweens(scene, target, inbetweens, frame, frame_range, fcu_inbetweens):
def paste_keyframes_get_inbetweens(scene, target, inbetweens, frame, frame_range, fcu_inbetweens, locked_fcus, bones_prevrot):
#Make sure the rotation is compatible with the previous rotation
compatible_rotation(target, bones_prevrot)
#if autokey is turned on then add a keyframe
if (scene.tool_settings.use_keyframe_insert_auto or scene.animtoolbox.range_type != 'CURRENT') and 'target' in locals():
if frame not in inbetweens:
add_keyframe(target)
add_keyframe(target, locked_fcus)
#store the inbetween values
elif len(frame_range) > 1:
fcu_inbetweens = get_fcu_inbetweens(target, frame, fcu_inbetweens)
if len(frame_range) > 1:
fcu_inbetweens = get_fcu_inbetweens(target, frame, fcu_inbetweens, inbetweens)
def reverse_bone_constraints(context, bone, matrix_copied):
@@ -1865,6 +1933,8 @@ class PasteRelativeMatrix(bpy.types.Operator):
source_bone = None
source_obj = None
bones_matrices = dict()
# Store previous rotations to be compared and avoid flips
bones_prevrot = dict()
constrained = set()
#get the source matrix
if 'source_rig_name' in globals():
@@ -1890,6 +1960,8 @@ class PasteRelativeMatrix(bpy.types.Operator):
frame_range, inbetweens = get_frame_range(context, obj)
fcu_inbetweens = dict()
# Checking for locked fcurves before iterating
locked_fcus = get_locked_fcus(obj)
for frame in sorted(frame_range+inbetweens):
scene.frame_set(int(frame))
@@ -1925,7 +1997,7 @@ class PasteRelativeMatrix(bpy.types.Operator):
paste_bones_matrices(bones_matrices, constrained)
for bone in bones_matrices.keys():
paste_keyframes_get_inbetweens(scene, bone, inbetweens, frame, frame_range, fcu_inbetweens)
paste_keyframes_get_inbetweens(scene, bone, inbetweens, frame, frame_range, fcu_inbetweens, locked_fcus, bones_prevrot)
else:
target = obj
@@ -1950,11 +2022,14 @@ class PasteRelativeMatrix(bpy.types.Operator):
matrix_new = reverse_constraint_offset(target.matrix_world, matrix_new)
target.matrix_world = filter_matrix_properties(context, target.matrix_world, matrix_new)
paste_keyframes_get_inbetweens(scene, target, inbetweens, frame, frame_range, fcu_inbetweens)
if inbetweens and len(frame_range) > 1:
add_interpolations(fcu_inbetweens.keys(), fcu_inbetweens, frames = frame_range[:-1])
paste_keyframes_get_inbetweens(scene, target, inbetweens, frame, frame_range, fcu_inbetweens, locked_fcus, bones_prevrot)
if len(frame_range) > 1:
if inbetweens:
add_interpolations(fcu_inbetweens.keys(), fcu_inbetweens, frames = frame_range[:-1])
else:
set_auto_handles(fcu_inbetweens.keys(), frame_range)
#return to the original frame in case of using frame range
if context.scene.frame_current != frame_current:
scene.frame_current = frame_current
@@ -2005,8 +2080,7 @@ def sharekeys_add_missing_fcurves(obj, attr_index, fcurves):
action = obj.id_data.animation_data.action
#Get the container, either action of channelbag because of adding groups
fcurves_container = get_fcurves_container(obj, action)
channelbag = get_channelbag(obj.id_data, action)
if 'rotation' in attr:
#converting the rotation depending on the rotation mode
mode = 'euler' if len(obj.rotation_mode) == 3 else obj.rotation_mode.lower()
@@ -2019,14 +2093,14 @@ def sharekeys_add_missing_fcurves(obj, attr_index, fcurves):
continue
elif rot != 'rotation_euler' and (path, 3) not in datapaths_arrays:
#adding an extra curve for quaternion or axis_angle
extra_fcu = fcurves_container.fcurves.new(data_path = path, index = 3, action_group = group)
extra_fcu = channelbag.fcurves.new(data_path = path, index = 3, action_group = group)
fcurves.append(extra_fcu)
datapaths_arrays.add((extra_fcu.data_path, extra_fcu.array_index))
if (path, index) in datapaths_arrays:
continue
fcu = fcurves_container.fcurves.new(data_path = path, index = index)
fcu = channelbag.fcurves.new(data_path = path, index = index)
add_group_to_fcurve(obj.id_data, fcu, group)
fcurves.append(fcu)
datapaths_arrays.add((fcu.data_path, fcu.array_index))
@@ -2102,7 +2176,7 @@ class ShareKeys(bpy.types.Operator):
def get_fcurves_set(self, obj_actions):
fcurves = set()
for obj, action in obj_actions.items():
fcurves = fcurves.union(set(get_fcurves_channelbag(obj, action)))
fcurves = fcurves.union(set(get_fcurves(obj, action)))
return fcurves
def add_get_action(self, obj):
@@ -2141,7 +2215,7 @@ class ShareKeys(bpy.types.Operator):
continue
#if there is no animation data or an action then create it
action = self.add_get_action(obj)
fcurves = get_fcurves_channelbag(obj, action)
fcurves = get_fcurves(obj, action)
frames, attr_index_obj = get_fcurves_frames([obj], fcurves, all_fcurves, frames, attr_index_obj)
if obj.data.animation_data is None:
@@ -2150,7 +2224,7 @@ class ShareKeys(bpy.types.Operator):
continue
#add the type of object if the data is also animated
actions_data_types.add(obj.type)
fcurves = get_fcurves_channelbag(obj.data, obj.data.animation_data.action)
fcurves = get_fcurves(obj.data, obj.data.animation_data.action)
frames, attr_index_obj = get_fcurves_frames([obj], fcurves, all_fcurves, frames, attr_index_obj)
#Getting all the available transforms and array index
@@ -2333,6 +2407,15 @@ def add_inbetweens(smartframes):
# all_frames = sorted(self.frames + self.inbetweens)
return inbetweens
def set_auto_handles(fcurves, frame_range):
for fcu in fcurves:
for keyframe in fcu.keyframe_points:
if keyframe.co[0] in frame_range:
keyframe.handle_right_type = 'AUTO'
keyframe.handle_left_type = 'AUTO'
fcu.update()
def add_interpolations(fcurves, fcu_inbetweens, frames = None):
inbetweens = sorted(set([frame for inbetweens in fcu_inbetweens.values() for frame in inbetweens.keys()]))
@@ -2419,7 +2502,7 @@ class FindRotationMode(bpy.types.Operator):
rot_mode = posebone.rotation_mode
obj = posebone.id_data
# fcurves = obj.animation_data.action.fcurves
fcurves = get_fcurves_channelbag(obj, obj.animation_data.action)
fcurves = get_fcurves(obj, obj.animation_data.action)
new_rot_eulers = []
#get the values from the original rotation
@@ -2533,7 +2616,7 @@ class ConvertRotationMode(bpy.types.Operator):
def switch_rot_mode_keyframes(self, obj, posebone):
# Switching any rotation mode keyframes to the new rotation mode value using to_rot_mode_index
bone_path = posebone.path_from_id() + '.' if type(posebone) == bpy.types.PoseBone else ''
fcurves = get_fcurves_channelbag(obj, obj.animation_data.action)
fcurves = get_fcurves(obj, obj.animation_data.action)
fcu_rotation_mode = fcurves.find(data_path = bone_path + 'rotation_mode', index = 0)
if fcu_rotation_mode:
for keyframe in fcu_rotation_mode.keyframe_points:
@@ -2556,7 +2639,7 @@ class ConvertRotationMode(bpy.types.Operator):
obj = posebone.id_data
# fcurves = obj.animation_data.action.fcurves
action = obj.animation_data.action
fcurves = get_fcurves_channelbag(obj, action)
fcurves = get_fcurves(obj, action)
fcu_inbetweens = dict()
fcu_keyframes = dict()
#get the transform from the original rot mode
@@ -2858,6 +2941,10 @@ def get_obj_slot(obj, action):
if obj in slot.users():
return slot
# If no slot with the users was found then get the next available slot
if obj.animation_data.action_suitable_slots:
return next(iter(obj.animation_data.action_suitable_slots))
return None
def get_all_fcurves(action):
@@ -2868,21 +2955,48 @@ def get_all_fcurves(action):
for strip in layer.strips:
for channelbag in strip.channelbags:
yield from channelbag.fcurves
def get_fcurves(obj, action: bpy.types.Action):
if hasattr(action, 'layers'):
channelbag = get_channelbag(obj, action)
return channelbag.fcurves
# action.fcurves not available anymore from Blender 5.0
if hasattr(action, 'fcurves'):
return action.fcurves
def get_fcurves_channelbag(obj, action: bpy.types.Action):
return []
def get_channelbag(obj, action: bpy.types.Action):
'''Getting the container of the fcurves, either the action or channelbag
Using this when adding a new group to the action'''
if hasattr(action, 'layers'):
slot = get_obj_slot(obj, action)
if not slot:
return action.fcurves
channelbag = anim_utils.action_get_channelbag_for_slot(action, slot)
if channelbag:
return channelbag.fcurves
return action.fcurves
channelbag = None
if slot:
channelbag = anim_utils.action_get_channelbag_for_slot(action, slot)
else:
# If a signed slot was not found then add a new one
slot = add_action_slot(obj, action)
obj.animation_data.action_slot = slot
channelbag = anim_utils.action_get_channelbag_for_slot(action, slot)
if channelbag is None:
# action_ensure_channelbag_for_slot works only from Blender 5
if hasattr(anim_utils, 'action_ensure_channelbag_for_slot'):
channelbag = anim_utils.action_ensure_channelbag_for_slot(action, slot)
else:
channelbag = add_channelbag(obj, action)
return channelbag
else:
return action
def add_channelbag(obj, action):
'''Old might need to remove'''
if not hasattr(action, 'layers'):
return
slot = get_obj_slot(obj, action)
@@ -2904,26 +3018,12 @@ def add_channelbag(obj, action):
return channelbag
def get_fcurves_container(obj: bpy.types.Object, action: bpy.types.Action):
'''Getting the container of the fcurves, either the action or channelbag
Using this when adding a new group to the action'''
if hasattr(action, 'layers'):
slot = get_obj_slot(obj, action)
if not slot:
return action
channelbag = anim_utils.action_get_channelbag_for_slot(action, slot)
return channelbag
else:
return action
def add_group_to_fcurve(obj, fcu, groupname):
'''Add an fcurve group based on the fcurve container, either action or channelbag'''
action = fcu.id_data
#get the container which is either a channelbag or a group
fcu_container = get_fcurves_container(obj, action)
fcu_container = get_channelbag(obj, action)
group = fcu_container.groups.get(groupname)
if group is None:
group = fcu_container.groups.new(groupname)
+46 -5
View File
@@ -20,7 +20,7 @@
bl_info = {
"name": "AnimToolBox",
"author": "Tal Hershkovich",
"version" : (0, 0, 8),
"version" : (0, 2, 3),
"blender" : (3, 2, 0),
"location": "View3D - Properties - Animation Panel",
"description": "A set of animation tools",
@@ -119,6 +119,11 @@ class TempCtrlsSceneSettings(bpy.types.PropertyGroup):
add_ik_ctrl: bpy.props.BoolProperty(name = 'Add an Extra IK Ctrl Bone', description = "Adds an extra bone ctrl as the ik ctrl", default = False, update = TempCtrls.add_ik_prop)
pole_target: bpy.props.BoolProperty(name = 'Add Pole Target', description = "Adding Pole Target to the IK Chain", default = True, update = TempCtrls.pole_prop)
pole_offset: bpy.props.FloatProperty(name="Offset", description="Offset the bone in the axis direction", default=1.0, update = TempCtrls.pole_offset)
pole_axis: bpy.props.EnumProperty(name = 'Axis Direction', description="Which direction should the pole bone point", default = 4, update = TempCtrls.update_axis_prop,
items = [('+X', '+X','Target Bone the +X Axis', 0), ('+Y', '+Y', 'Target Bone the +Y Axis', 1),
('-X', '-X','Target Bone the -X Axis', 2), ('-Y', '-Y', 'Target Bone the -Y Axis', 3),
('AUTO', 'Auto','Select pole bone direction based on the current pose', 4)])
child: bpy.props.BoolProperty(name = 'Add extra child Ctrls', description = "Add an child control for an overlay control", default = False, update = TempCtrls.child_prop)
orientation: bpy.props.BoolProperty(name = 'Use World Space Orientation', description = "Orient the bones to world space instead of to the original bones", default = True)
@@ -252,8 +257,9 @@ class AnimToolBoxGlobalSettings(bpy.types.PropertyGroup):
#Copy/Pase Matrix
range_type: bpy.props.EnumProperty(name = 'Paste to Frames', description="Paste to current frame or a range of frames.", update = Tools.bake_range_type,
items = [('CURRENT', 'Current Frame','Paste Matrix to only current frame', 0),
('SELECTED', 'Selected Keyframe','Paste Matrix to only selected keyframes', 1),
('RANGE', 'Frame Range','Paste Matrix to a Frame Range', 2)])
('SELECTED', 'Selected Keyframes','Paste Matrix only to the selected keyframes', 1),
('RANGE', 'Frame Range','Paste Matrix to a Custom Frame Range', 2),
('SELECTED_RANGE', 'Selected Keyframes Range','Paste Matrix to every frame within a selected keyframe range', 3)])
bake_frame_start: bpy.props.IntProperty(name = "Bake Frame Start", description = "Define the start frame to paste the matrix", min = 0, update = Tools.bake_frame_start_limit)
bake_frame_end: bpy.props.IntProperty(name = "Bake Frame End", description = "Define the end frame to paste the matrix", min = 0, update = Tools.bake_frame_end_limit)
@@ -298,6 +304,11 @@ class AnimToolBoxPreferences(bpy.types.AddonPreferences):
mp_handle_selection_color: bpy.props.FloatVectorProperty(name="Handles Selection", subtype='COLOR', default=(0.8, 0.65, 0.6, 0.8), size=4, min=0.0, max=1.0, description="Handles selection color")
mp_key_selection_color: bpy.props.FloatVectorProperty(name="Keyframe Selection", subtype='COLOR', default=(0.8, 0.8, 0.6, 0.8), size=4, min=0.0, max=1.0, description="Keyframe selection color")
# Motion Path brush colors
mp_brush_disabled: bpy.props.FloatVectorProperty(name="Disabled", subtype='COLOR', default=(0.7, 0.7, 0.9, 0.75), size=4, min=0.0, max=1.0, description="Brush is disabled and have no influence")
mp_brush_hover: bpy.props.FloatVectorProperty(name="Hover", subtype='COLOR', default=(0.8, 0.7, 0.2, 0.8), size=4, min=0.0, max=1.0, description="Brush is hovering and can be activated")
mp_brush_active: bpy.props.FloatVectorProperty(name="Active", subtype='COLOR', default=(1.0, 1.0, 1.0, 0.9), size=4, min=0.0, max=1.0, description="Brush is activated")
# addon updater preferences from `__init__`, be sure to copy all of them
auto_check_update: bpy.props.BoolProperty(
name = "Auto-check for Update",
@@ -371,6 +382,12 @@ class AnimToolBoxPreferences(bpy.types.AddonPreferences):
row.prop(self, 'mp_hover_color')
row.prop(self, 'mp_key_selection_color')
row.prop(self, 'mp_handle_selection_color')
col = box.row()
col.label(text = 'Brush Colors')
row = box.row()
row.prop(self, 'mp_brush_disabled')
row.prop(self, 'mp_brush_hover')
row.prop(self, 'mp_brush_active')
layout.separator()
col = layout.column()
@@ -392,6 +409,8 @@ def loadanimtoolbox_pre(self, context):
if 'mp_dh' in dns:
bpy.types.SpaceView3D.draw_handler_remove(dns['mp_dh'], 'WINDOW')
bpy.app.driver_namespace.pop('mp_dh')
emp.remove_draw_circle()
bpy.context.scene.emp.selected_keyframes = '{}'
if 'markers_retimer_dh' in dns:
@@ -401,20 +420,38 @@ def loadanimtoolbox_pre(self, context):
#remove the motion path app handler if it's still inside
if emp.mp_value_update in bpy.app.handlers.depsgraph_update_post:
bpy.app.handlers.depsgraph_update_post.remove(emp.mp_value_update)
if emp.mp_frame_change in bpy.app.handlers.frame_change_post:
bpy.app.handlers.frame_change_post.remove(emp.mp_frame_change)
if emp.mp_undo_update in bpy.app.handlers.undo_pre:
bpy.app.handlers.undo_pre.remove(emp.mp_undo_update)
@persistent
def loadanimtoolbox_post(self, context):
scene = bpy.context.scene
def remove_markers(prop, marker_type, names):
'''Remove Frame Range or Bake Range Markers if they were turned on'''
if not getattr(prop, marker_type):
return
setattr(prop, marker_type, False)
for marker_name in names:
if marker_name in scene.timeline_markers:
scene.timeline_markers.remove(scene.timeline_markers[marker_name])
dns = bpy.app.driver_namespace
if scene.animtoolbox.isolate_pose_mode:
if Display.isolate_pose_mode not in bpy.app.handlers.depsgraph_update_pre:
bpy.app.handlers.depsgraph_update_pre.append(Display.isolate_pose_mode)
if scene.emp.motion_path:
# In case motion path was active in the last save, make sure atb mp name property
# Is not included inside the bones, for a proper cleanup
for obj in bpy.context.view_layer.objects:
if "atb_mp_name" in obj:
del obj["atb_mp_name"]
if obj.type == 'ARMATURE':
for pbone in obj.pose.bones:
if "atb_mp_name" in pbone:
del pbone["atb_mp_name"]
scene.emp.motion_path = False
bpy.context.workspace.status_text_set(None)
if 'mp_dh' in dns:
@@ -424,6 +461,10 @@ def loadanimtoolbox_post(self, context):
# Because it is used for undo
bpy.context.scene.emp.selected_keyframes = '{}'
remove_markers(scene.animtoolbox, 'marker_frame_range', {'Frame Start', 'Frame End'})
remove_markers(scene.animtoolbox, 'bake_frame_range', {'Bake Start', 'Bake End'})
remove_markers(scene.emp, 'marker_frame_range', {'MotionPath Start', 'MotionPath End'})
Tools.selection_order(self, context)
classes = (TempCtrlsItems, TempCtrlsOrgIds, TempCtrlsObjectSetups, TempCtrlsSceneSettings,TempCtrlsBoneSettings, MultikeyProperties,
@@ -1,6 +1,6 @@
{
"last_check": "2025-09-23 10:58:29.201165",
"backup_date": "September-23-2025",
"last_check": "2026-02-09 15:54:54.419194",
"backup_date": "February-9-2026",
"update_ready": false,
"ignore": false,
"just_restored": false,
File diff suppressed because it is too large Load Diff
@@ -1803,7 +1803,7 @@ def reorder_bones_matrices(bones_matrices, constrained):
return re_bones_matrices
def paste_bone_matrix(bone, matrix_copied, constrained, x_filter = True):
def paste_bone_matrix(bone, matrix_copied, constrained, bones = {}, x_filter = True):
#running again separatly in case the bones are in a hierarchy and influencing each other
# for bone, matrix_copied in bones_matrices.items():
# Determine whether to use bone.matrix or bone.matrix_world
@@ -1818,10 +1818,14 @@ def paste_bone_matrix(bone, matrix_copied, constrained, x_filter = True):
if x_filter : matrix_copied = filter_matrix_properties(context, getattr(bone, matrix_attr), matrix_copied)
# bone.matrix = bone.id_data.matrix_world.inverted() @ matrix_copied
setattr(bone, matrix_attr, matrix_copied) # bone.id_data.matrix_world.inverted() @
children = set(bone.children_recursive).intersection(bones)
if children or bone in constrained:
# print(f'found children {[child.name for child in children]} in bone {bone.name}' )
context.view_layer.update()
#Check if the bone has constrainsts on it that need extra iteration
if bone not in constrained:
# filter_matrix_properties(context, bone.matrix, matrix_copied)
return
context.view_layer.update()
matrix_copied = reverse_bone_constraints(context, bone, matrix_copied)
if x_filter : matrix_copied = filter_matrix_properties(context, bone.matrix, matrix_copied)
@@ -1832,8 +1836,12 @@ def paste_bone_matrix(bone, matrix_copied, constrained, x_filter = True):
def paste_bones_matrices(bones_matrices, constrained, x_filter = True):
#running again separatly in case the bones are in a hierarchy and influencing each other
pasted_bones = set()
for bone, matrix_copied in bones_matrices.items():
paste_bone_matrix(bone, matrix_copied, constrained, x_filter)
#Get the rest of the bones to check if they are children of the current bone
bones = set(bones_matrices.keys()).difference(pasted_bones)
paste_bone_matrix(bone, matrix_copied, constrained, bones, x_filter)
pasted_bones.add(bone)
class PasteRelativeMatrix(bpy.types.Operator):
"""paste the relative matrix of the selection"""
@@ -2522,7 +2530,15 @@ class ConvertRotationMode(bpy.types.Operator):
# @classmethod
# def poll(cls, context):
# return context.object.type == 'ARMATURE'
def switch_rot_mode_keyframes(self, obj, posebone):
# Switching any rotation mode keyframes to the new rotation mode value using to_rot_mode_index
bone_path = posebone.path_from_id() + '.' if type(posebone) == bpy.types.PoseBone else ''
fcurves = get_fcurves_channelbag(obj, obj.animation_data.action)
fcu_rotation_mode = fcurves.find(data_path = bone_path + 'rotation_mode', index = 0)
if fcu_rotation_mode:
for keyframe in fcu_rotation_mode.keyframe_points:
keyframe.co[1] = self.to_rot_mode_index
def execute(self, context):
scene = context.scene
selected_bones = context.selected_pose_bones
@@ -2530,7 +2546,8 @@ class ConvertRotationMode(bpy.types.Operator):
to_rot_mode = scene.animtoolbox.rotation_mode
to_rot_mode_fcu = rot_mode_to_channel(to_rot_mode)
#Getting the index of the rotation mode we want to convert to
self.to_rot_mode_index = list(scene.animtoolbox.bl_rna.properties['rotation_mode'].enum_items.keys()).index(to_rot_mode)
#get the keyframes from the bones
for posebone in selected_bones:
@@ -2548,16 +2565,21 @@ class ConvertRotationMode(bpy.types.Operator):
keyframes = emp.get_bone_keyframes(posebone, transform)
#get all interpolations and handle types of the keyframes
handle_types = emp.get_bone_keyframes(posebone, transform, property = 'interpolation')
interpolations = handle_types[::3]
handle_left_type = handle_types[1::3]
handle_right_type = handle_types[2::3]
# rotation_mode_keyframes = emp.get_bone_keyframes(posebone, 'rotation_mode')
self.switch_rot_mode_keyframes(obj, posebone)
inbetweens = []
smartframes = sorted(set(map(lambda x: round(x, 2), keyframes[::2])))
inbetweens = add_inbetweens(smartframes)
all_frames = sorted(smartframes + inbetweens)
#get all interpolations and handle types of the keyframes
if len(smartframes) > 1:
handle_types = emp.get_bone_keyframes(posebone, transform, property = 'interpolation')
interpolations = handle_types[::3]
handle_left_type = handle_types[1::3]
handle_right_type = handle_types[2::3]
inbetweens = add_inbetweens(smartframes)
all_frames = sorted(smartframes + inbetweens)
new_path = posebone.path_from_id() + '.' + to_rot_mode_fcu
#define array length
@@ -2632,13 +2654,15 @@ class ConvertRotationMode(bpy.types.Operator):
keyframe = new_fcu.keyframe_points[-1]
keyframe.co = (frame, fcu_keyframes[new_fcu][frame])
keyframe.interpolation = interpolations[frame_index]
keyframe.handle_left_type = handle_left_type[frame_index]
keyframe.handle_right_type = handle_right_type[frame_index]
if inbetweens:
keyframe.interpolation = interpolations[frame_index]
keyframe.handle_left_type = handle_left_type[frame_index]
keyframe.handle_right_type = handle_right_type[frame_index]
new_fcu.update()
add_interpolations(new_fcurves, fcu_inbetweens)
if inbetweens:
add_interpolations(new_fcurves, fcu_inbetweens)
posebone.rotation_mode = to_rot_mode
@@ -20,7 +20,7 @@
bl_info = {
"name": "AnimToolBox",
"author": "Tal Hershkovich",
"version" : (0, 0, 7, 3),
"version" : (0, 0, 8),
"blender" : (3, 2, 0),
"location": "View3D - Properties - Animation Panel",
"description": "A set of animation tools",
@@ -133,15 +133,16 @@ class TempCtrlsBoneSettings(bpy.types.PropertyGroup):
setup: bpy.props.EnumProperty(name = 'Setup Type', description="Describes what kind of setup the bone is part of", override = {'LIBRARY_OVERRIDABLE'},
items = [('NONE', 'No Setup','No Setup Applied', 0),
('WORLDSPACE', 'World Space Ctrl','World Space Ctrl setup', 1),
('TEMPFK', 'Temporary FK setup','Temporary FK chain setup', 2),
('TEMPFK_FLIP', 'Temporary flipped FK setup','Temporary flipped FK chain setup', 3),
('TEMPIK', 'Temporary IK setup','Temporary IK setup', 4),
('POLE', 'Temporary IK Pole setup','Temporary IK Pole', 5),
('PARENTCTRL', 'Parent Ctrl from cursor setup','Parent Ctrl from cursor setup', 6),
('ROOT', 'Root', 'Root Ctrl for all the setups', 7),
('EMPTY', 'Root', 'Root Ctrl for all the setups', 8),
('TRACK_TO', 'Track To','World Space Track to Ctrl setup', 9),
('TRACK_TO_EMPTY', 'Track To Empty','World Space Track to Empty Ctrl setup', 10)])
('WORLDSPACE_CURSOR', 'World Space Cursor Ctrl','World Space Cursor pivot', 2),
('TEMPFK', 'Temporary FK setup','Temporary FK chain setup', 3),
('TEMPFK_FLIP', 'Temporary flipped FK setup','Temporary flipped FK chain setup', 4),
('TEMPIK', 'Temporary IK setup','Temporary IK setup', 5),
('POLE', 'Temporary IK Pole setup','Temporary IK Pole', 6),
('PARENTCTRL', 'Parent Ctrl from cursor setup','Parent Ctrl from cursor setup', 7),
('ROOT', 'Root', 'Root Ctrl for all the setups', 8),
('EMPTY', 'Root', 'Root Ctrl for all the setups', 9),
('TRACK_TO', 'Track To','World Space Track to Ctrl setup', 10),
('TRACK_TO_EMPTY', 'Track To Empty','World Space Track to Empty Ctrl setup', 11)])
#using org mostly to decide if it needs a custom shape
org: bpy.props.EnumProperty(name = 'Org Type', description="Describes what if the function of the bone", override = {'LIBRARY_OVERRIDABLE'},
@@ -152,21 +153,29 @@ class TempCtrlsBoneSettings(bpy.types.PropertyGroup):
shape: bpy.props.BoolProperty(name = "Apply shape", description = "Mark if the bone needs a shape", default = False, override = {'LIBRARY_OVERRIDABLE'})
class TempCtrlsOrgIds(bpy.types.PropertyGroup):
# A collection of the org ids used in each setup. Org ID is the direct connection
# between original bones and ctrls
pass
class TempCtrlsObjectSetups(bpy.types.PropertyGroup):
#located at obj.animtoolbox.ctrl_setups
#name: using string of the id
setup: bpy.props.EnumProperty(name = 'Setup Type', description="Describes what kind of setup the bone is part of",
items = [('NONE', 'No Setup','No Setup Applied', 0),
('WORLDSPACE', 'World Space Ctrl','World Space Ctrl setup', 1),
('TEMPFK', 'Temporary FK setup','Temporary FK chain setup', 2),
('TEMPFK_FLIP', 'Temporary flipped FK setup','Temporary flipped FK chain setup', 3),
('TEMPIK', 'Temporary IK setup','Temporary IK setup', 4),
('PARENTCTRL', 'Parent Ctrl from cursor setup','Parent Ctrl from cursor setup', 5),
('ROOT', 'Root', 'Root Ctrl for all the setups', 6),
('EMPTY', 'Root', 'Root Ctrl for all the setups', 8),
('TRACK_TO', 'Track To','World Space Track to Ctrl setup', 9),
('TRACK_TO_EMPTY', 'Track To Empty','World Space Track to Empty Ctrl setup', 10),
])
('WORLDSPACE_CURSOR', 'World Space Cursor Ctrl','World Space Cursor pivot', 2),
('TEMPFK', 'Temporary FK setup','Temporary FK chain setup', 3),
('TEMPFK_FLIP', 'Temporary flipped FK setup','Temporary flipped FK chain setup', 4),
('TEMPIK', 'Temporary IK setup','Temporary IK setup', 5),
('POLE', 'Temporary IK Pole setup','Temporary IK Pole', 6),
('PARENTCTRL', 'Parent Ctrl from cursor setup','Parent Ctrl from cursor setup', 7),
('ROOT', 'Root', 'Root Ctrl for all the setups', 8),
('EMPTY', 'Root', 'Root Ctrl for all the setups', 9),
('TRACK_TO', 'Track To','World Space Track to Ctrl setup', 10),
('TRACK_TO_EMPTY', 'Track To Empty','World Space Track to Empty Ctrl setup', 11)])
org_ids: bpy.props.CollectionProperty(type = TempCtrlsOrgIds)
class MultikeyProperties(bpy.types.PropertyGroup):
@@ -238,37 +247,6 @@ class AnimToolBoxGlobalSettings(bpy.types.PropertyGroup):
#Blendings
inbetweener : bpy.props.FloatProperty(name='Inbetween Keyframe', description="Adds an inbetween Keyframe between the Layer's neighbor keyframes", soft_min = -1, soft_max = 1, default=0.0, options = set(), update = Tools.add_inbetween_key, override = {'LIBRARY_OVERRIDABLE'})
motion_path: bpy.props.BoolProperty(name = "Motion Path", description = "Flag when Motion Path is on", default = False, override = {'LIBRARY_OVERRIDABLE'})
mp_settings: bpy.props.BoolProperty(name = "Motion Path Settings", description = "Open the settings Menu", default = False, override = {'LIBRARY_OVERRIDABLE'})
mp_keyframe_scale: bpy.props.FloatProperty(name = "Scale Selecgted Keyframes Bounding Box", description = "Change the scale of the bounding box around the selected keyframes ", default = 0.1, step = 0.1, precision = 3)
mp_color_before: bpy.props.FloatVectorProperty(name="Motion Path Before Color", subtype='COLOR', default=(1.0, 0.0, 0.0), min=0.0, max=1.0, description="Motion path color before the current frame")
mp_color_after: bpy.props.FloatVectorProperty(name="Motion Path After Color", subtype='COLOR', default=(0.0, 1.0, 0.0), min=0.0, max=1.0, description="Motion path color before the current frame")
mp_infront: bpy.props.BoolProperty(name = "Motion Path In Front", description = "Display motion path in front of all the objects", default = True, override = {'LIBRARY_OVERRIDABLE'})
mp_points: bpy.props.BoolProperty(name = "Motion Path Points", description = "Display motion path points", default = True, override = {'LIBRARY_OVERRIDABLE'})
mp_lines: bpy.props.BoolProperty(name = "Motion Path Lines", description = "Display motion path lines", default = True, override = {'LIBRARY_OVERRIDABLE'})
mp_handles: bpy.props.BoolProperty(name = "Motion Path Handles", description = "Display motion path handles on keyframe selection", default = True, override = {'LIBRARY_OVERRIDABLE'})
mp_display_frames: bpy.props.BoolProperty(name = "Frame Numbers", description = "Display frame numbers on all the keyframes", default = False, override = {'LIBRARY_OVERRIDABLE'})
mp_handle_types: bpy.props.EnumProperty(name = 'Set Keyframe Handle Type', description="Set handle type for selected keyframes", default = 'AUTO', update = emp.update_handle_type_prop,
items = [('FREE', 'Free', 'Free', 'HANDLE_FREE', 0),
('ALIGNED','Aligned', 'Aligned', 'HANDLE_ALIGNED', 1),
('VECTOR', 'Vector', 'Vector', 'HANDLE_VECTOR', 2),
('AUTO','Automatic', 'Automatic', 'HANDLE_AUTO', 3),
('AUTO_CLAMPED','Auto Clamped', 'Auto Clamped', 'HANDLE_AUTOCLAMPED', 4)])
mp_interpolation: bpy.props.EnumProperty(name = 'Set Interpolation', description="Set Keyframe Interpolation", default = 'BEZIER', update = emp.update_interpolation_prop,
items = [('BEZIER', 'Bezier', 'Bezier', 'IPO_BEZIER', 0),
('LINEAR','Linear', 'Linear', 'IPO_LINEAR', 1),
('CONSTANT', 'Constant', 'Constant', 'IPO_CONSTANT', 2)])
mp_frame_range: bpy.props.EnumProperty(name = 'Frame Range', description="Type of Frame Range", default = 'SCENE', #update = emp.mp_frame_range_update,
items = [('KEYS_ALL', 'All_Keys','Use the Scene Frame Length for the Range', 0),
('SCENE', 'Scene','Use the Scene Frame Length for the Range', 1),
('MANUAL', 'Manual','Custom Frame range using numerical input or the markers frame ranger', 2),
('AROUND', 'Around Frames','Show only around the current frame', 3)])
mp_before: bpy.props.IntProperty(name = "Before", description = "Show the frames Before the current frame", min = 0, default = 10)
mp_after: bpy.props.IntProperty(name = "After", description = "Show the frames After the current frame", min = 0, default = 10)
selected_keyframes: bpy.props.StringProperty(name="Selected Keyframes", description="Serialized representation of selected keyframes")
gizmo_size: bpy.props.IntProperty(name = "Add to Gizmo Size", description = "Addition to Gizmo Size", max = 100, min = -100, default = 10)
#Copy/Pase Matrix
@@ -312,6 +290,13 @@ class AnimToolBoxPreferences(bpy.types.AddonPreferences):
#Editable motion path
keyframes_range: bpy.props.IntProperty(name = "Keyframe Range", description = "The range of distance from the keyframes while hovering over them", min = 5, max = 100, default = 15)
mp_pref: bpy.props.BoolProperty(name = "Editable Motion Path Colors Theme", description = "Set the Color them of editable motion path visualization", default = False)
mp_keyframe_color: bpy.props.FloatVectorProperty(name="Keyframes", subtype='COLOR', default=(1.0, 1.0, 0.0, 1.0), size=4, min=0.0, max=1.0, description="Handles selection color")
mp_handle_color: bpy.props.FloatVectorProperty(name="Handles", subtype='COLOR', default=(1.0, 0.8, 0.2, 1.0), size=4, min=0.0, max=1.0, description="Handles selection color")
mp_remove_color: bpy.props.FloatVectorProperty(name="Remove Keyframes", subtype='COLOR', default=(0.0, 0.5, 1.0, 1.0), size=4, min=0.0, max=1.0, description="Keyframe color displayed before removing")
mp_hover_color: bpy.props.FloatVectorProperty(name="Hover", subtype='COLOR', default=(1.0, 0.4, 0.2, 1.0), size=4, min=0.0, max=1.0, description="Color during Hovering")
mp_handle_selection_color: bpy.props.FloatVectorProperty(name="Handles Selection", subtype='COLOR', default=(0.8, 0.65, 0.6, 0.8), size=4, min=0.0, max=1.0, description="Handles selection color")
mp_key_selection_color: bpy.props.FloatVectorProperty(name="Keyframe Selection", subtype='COLOR', default=(0.8, 0.8, 0.6, 0.8), size=4, min=0.0, max=1.0, description="Keyframe selection color")
# addon updater preferences from `__init__`, be sure to copy all of them
auto_check_update: bpy.props.BoolProperty(
@@ -371,6 +356,22 @@ class AnimToolBoxPreferences(bpy.types.AddonPreferences):
row.prop(self, 'clear_setup')
row.prop(self, 'in_front')
layout.separator()
box = layout.box()
col = box.column()
col.prop(self, 'mp_pref', icon = 'DOWNARROW_HLT', text = 'Editable Motion Path Preferences')
if self.mp_pref:
col.prop(self, 'keyframes_range', text = 'Keyframe Distance Range')
col.label(text = 'Colors Theme')
row = box.row()
row.prop(self, 'mp_keyframe_color')
row.prop(self, 'mp_handle_color')
row.prop(self, 'mp_remove_color')
row = box.row()
row.prop(self, 'mp_hover_color')
row.prop(self, 'mp_key_selection_color')
row.prop(self, 'mp_handle_selection_color')
layout.separator()
col = layout.column()
col.label(text = 'Include Extras: ')
@@ -385,12 +386,14 @@ def loadanimtoolbox_pre(self, context):
if scene.animtoolbox.bake_frame_range:
scene.animtoolbox.bake_frame_range = False
if scene.animtoolbox.motion_path:
scene.animtoolbox.motion_path = False
if scene.emp.motion_path:
scene.emp.motion_path = False
bpy.context.workspace.status_text_set(None)
if 'mp_dh' in dns:
bpy.types.SpaceView3D.draw_handler_remove(dns['mp_dh'], 'WINDOW')
bpy.app.driver_namespace.pop('mp_dh')
bpy.context.scene.emp.selected_keyframes = '{}'
if 'markers_retimer_dh' in dns:
bpy.types.SpaceView3D.draw_handler_remove(dns['markers_retimer_dh'], 'WINDOW')
bpy.app.driver_namespace.pop('markers_retimer_dh')
@@ -411,16 +414,20 @@ def loadanimtoolbox_post(self, context):
if Display.isolate_pose_mode not in bpy.app.handlers.depsgraph_update_pre:
bpy.app.handlers.depsgraph_update_pre.append(Display.isolate_pose_mode)
if scene.animtoolbox.motion_path:
scene.animtoolbox.motion_path = False
if scene.emp.motion_path:
scene.emp.motion_path = False
bpy.context.workspace.status_text_set(None)
if 'mp_dh' in dns:
bpy.types.SpaceView3D.draw_handler_remove(dns['mp_dh'], 'WINDOW')
bpy.app.driver_namespace.pop('mp_dh')
# Reset keyframe selection for motion paths it is not used in window manager
# Because it is used for undo
bpy.context.scene.emp.selected_keyframes = '{}'
Tools.selection_order(self, context)
classes = (TempCtrlsItems, TempCtrlsObjectSetups, TempCtrlsSceneSettings,TempCtrlsBoneSettings, MultikeyProperties, IsolatedRigs,
AnimToolBoxObjectSettings, AnimToolBoxUILayout, AnimToolBoxGlobalSettings) + ui.classes
classes = (TempCtrlsItems, TempCtrlsOrgIds, TempCtrlsObjectSetups, TempCtrlsSceneSettings,TempCtrlsBoneSettings, MultikeyProperties,
IsolatedRigs, AnimToolBoxObjectSettings, AnimToolBoxUILayout, AnimToolBoxGlobalSettings) + ui.classes
addon_keymaps = []
@@ -1,16 +1,16 @@
{
"last_check": "2025-09-23 10:57:14.918240",
"backup_date": "June-19-2025",
"last_check": "2026-02-09 15:54:54.419194",
"backup_date": "September-23-2025",
"update_ready": true,
"ignore": false,
"just_restored": false,
"just_updated": false,
"version_text": {
"link": "https://gitlab.com/api/v4/projects/45739913/repository/archive.zip?sha=cb445f00491a54eb763f0d8e72eaae3e44e7d0ba",
"link": "https://gitlab.com/api/v4/projects/45739913/repository/archive.zip?sha=7a9ad24a463bf1c8ae095ec853409979344e0738",
"version": [
0,
0,
8
2,
3
]
}
}
File diff suppressed because it is too large Load Diff
@@ -46,7 +46,7 @@ def attr_default(obj, fcu_key):
if attr not in obj.data.shape_keys.key_blocks:
return [0]
shapekey = obj.data.shape_keys.key_blocks[attr]
return 0 if shapekey.slider_min <= 0 else shapekey.slider_min
return [0] if shapekey.slider_min <= 0 else shapekey.slider_min
#in case of transforms in object mode
else:# fcu_key[0] in transform_types:
source = obj
@@ -273,6 +273,9 @@ def random_value(self, context):
for key in fcu.keyframe_points:
add_value(key, value * random.uniform(-threshold, threshold))
fcu.update()
self['randomness'] = 0.1
def evaluate_combine(data_path, added_array, eval_array, array_default, influence):
@@ -293,23 +296,17 @@ def evaluate_array(fcurves, fcu_path, frame, array_default = [0, 0, 0]):
'''Create an array from all the indexes'''
array_len = len(array_default)
fcu_array = []
#assigning the default array in case
fcu_array = array_default.copy()
#get the missing arrays in case quaternion is not complete
missing_arrays = []
for i in range(array_len):
fcu = fcurves.find(fcu_path, index = i)
if fcu is None:
missing_arrays.append(i)
continue
fcu_array[i] = fcu.evaluate(frame)
fcu_array.append(fcu.evaluate(frame))
#In case it's a quaternion and missing attributes, then adding from default value
if fcu_array and array_len == 4 and missing_arrays:
for i in missing_arrays:
fcu_array.insert(i, array_default[i])
if not len(fcu_array):
if (fcu_array == array_default).all():
return None
return np.array(fcu_array)
@@ -325,7 +322,7 @@ def evaluate_layers(context, obj, anim_data, fcu, array_default):
blend_types = {'ADD' : '+', 'SUBTRACT' : '-', 'MULTIPLY' : '*'}
fcu_path = fcu.data_path
eval_array = array_default
eval_array = array_default.copy()
for track in nla_tracks:
if track.mute:
@@ -373,6 +370,7 @@ def evaluate_layers(context, obj, anim_data, fcu, array_default):
tweak_mode = anim_data.use_tweak_mode
if tweak_mode:
anim_data.use_tweak_mode = False
action = anim_data.action
if action:
influence = anim_data.action_influence
@@ -425,6 +423,7 @@ def evaluate_value(self, context):
for fcu in fcurves:
if fcu in fcu_paths:
continue
current_value = None
if Tools.filter_properties(context.scene.animtoolbox, fcu):
continue
if obj.mode == 'POSE':
@@ -447,10 +446,11 @@ def evaluate_value(self, context):
else:
transform = fcu.data_path
current_value = getattr(obj, transform)
#In case it was completly filtered out and not current value available
if not current_value:
continue
array_default = np.array(attr_default(obj, (fcu.data_path, fcu.array_index)))
# array_default = np.array([attr_default(obj, (fcu.data_path, i)) for i in range(4)
# if fcurves.find(fcu.data_path, index = i) is not None])
eval_array = evaluate_layers(context, obj, anim_data, fcu, array_default)
if eval_array is None:
fcurves = Tools.get_fcurves_channelbag(obj, action)
@@ -63,6 +63,7 @@ class ANIMTOOLBOX_MT_Temp_Ctrls(bpy.types.Menu):
layout = self.layout
custom_icons = preview_collections["main"]
layout.operator('anim.bake_to_ctrl', text="World Space Ctrls", icon = 'WORLD')
# layout.operator('anim.worldspace_cursor', text="World Space Cursor Ctrls", icon ='ORIENTATION_CURSOR')
layout.operator('anim.bake_to_temp_fk', text="Temp FK Ctrls", icon = 'BONE_DATA')
layout.operator('anim.bake_to_temp_ik', text="Temp IK Ctrls", icon = 'CON_KINEMATIC')
layout.separator()
@@ -163,7 +164,7 @@ class ANIMTOOLBOX_MT_operators(bpy.types.Menu):
layout.separator(factor = 0.5)
layout.operator('anim.switch_collections_visibility', icon = 'COLLECTION_COLOR_06', text = 'Animated Collections Visibilty')
layout.operator('anim.isolate_pose_mode', icon_value = custom_icons["isolate"].icon_id, depress = scene.animtoolbox.isolate_pose_mode)
layout.operator('object.motion_path_operator', text = 'Editable Motion Path', depress = scene.animtoolbox.motion_path, icon_value = custom_icons["mt"].icon_id)
layout.operator('object.motion_path_operator', text = 'Editable Motion Path', depress = scene.emp.motion_path, icon_value = custom_icons["mt"].icon_id)
layout.separator()
layout.prop(context.preferences.addons[__package__].preferences, 'quick_menu', text = 'Use Quick Icons Menu')
# layout.prop(context.window_manager.atb_ui, 'quick_menu', text = 'Use Quick Icons Menu')
@@ -257,7 +258,10 @@ class TEMPCTRLS_PT_Panel(ANIMTOOLBOX_PT_Panel, bpy.types.Panel):
layout = self.layout
box = layout.box()
col = box.column()
col.operator('anim.bake_to_ctrl', text="WorldSpace Ctrls", icon ='WORLD') #
row = col.row()
row.operator('anim.bake_to_ctrl', text="WorldSpace Ctrls", icon ='WORLD') #
row.operator('anim.add_empty_ctrl', text="", icon ='EMPTY_AXIS')
# col.operator('anim.worldspace_cursor', text="WorldSpace Cursor Ctrls", icon ='ORIENTATION_CURSOR')
col.operator('anim.bake_to_temp_fk', text="Temp FK Ctrls", icon = 'BONE_DATA')
col.operator('anim.bake_to_temp_ik', text="Temp IK Ctrls", icon = 'CON_KINEMATIC')
@@ -540,44 +544,65 @@ class ANIMTOOLBOX_PT_Display(ANIMTOOLBOX_PT_Panel, bpy.types.Panel):
box = layout.box()
row = box.row()
row.operator('object.motion_path_operator', text = 'Editable Motion Path', depress = scene.animtoolbox.motion_path, icon_value = custom_icons["mt"].icon_id)
row.prop(scene.animtoolbox, 'mp_settings', text = '', icon = 'SETTINGS')
if scene.animtoolbox.mp_settings:
row = box.row()
row.prop(scene.animtoolbox, 'mp_color_before', text = '')
row.prop(scene.animtoolbox, 'mp_color_after', text = '')
row = box.row()
row.prop(scene.animtoolbox, 'mp_keyframe_scale', text = 'Scale Keyframes Box', icon = 'CUBE')
row = box.row()
row.prop(scene.animtoolbox, 'mp_points', text = 'Points')
row.prop(scene.animtoolbox, 'mp_lines', text = 'Lines')
row = box.row()
row.prop(scene.animtoolbox, 'mp_handles', text = 'Handles')
row.prop(scene.animtoolbox, 'mp_infront', text = 'In Front')
row = box.row()
row.prop(scene.animtoolbox, 'mp_display_frames', text = 'Display Keyframe Numbers')
row = box.row()
row.prop(context.preferences.addons[__package__].preferences, 'keyframes_range', text = 'Keyframe Distance Range')
if context.object is None:
return
row.operator('object.motion_path_operator', text = 'Editable Motion Path', depress = scene.emp.motion_path, icon_value = custom_icons["mt"].icon_id)
emp = scene.emp
row.prop(scene.emp, 'settings', text = '', icon = 'SETTINGS')
if scene.emp.settings:
split = box.split(factor = 0.4)
split.label(text ='Frame Range')
# row.prop(scene.animtoolbox, 'mp_frame_range', text = '')
split.prop(context.scene.animtoolbox, 'mp_frame_range', text = '')
if context.scene.animtoolbox.mp_frame_range == 'MANUAL':
split.prop(emp, 'frame_range', text = '')
if emp.frame_range == 'MANUAL':
row = box.row()
row.prop(context.scene.animtoolbox, 'bake_frame_start', text = 'Start')
row.prop(context.scene.animtoolbox, 'bake_frame_end', text = 'End')
row.operator("anim.markers_bakerange", icon = 'MARKER', text ='', depress = scene.animtoolbox.bake_frame_range)
elif context.scene.animtoolbox.mp_frame_range == 'AROUND':
row = box.row()
row.prop(context.scene.animtoolbox, 'mp_before', text = 'Before')
row.prop(context.scene.animtoolbox, 'mp_after', text = 'After')
row.prop(emp, 'frame_start', text = 'Start')
row.prop(emp, 'frame_end', text = 'End')
# row.operator("anim.markers_bakerange", icon = 'MARKER', text ='', depress = scene.animtoolbox.bake_frame_range)
elif emp.frame_range == 'AROUND':
row = box.row()
row.prop(emp, 'before', text = 'Before')
row.prop(emp, 'after', text = 'After')
box.separator(factor = 0.1)
col = box.column()
col.prop(emp, 'display_size', icon = 'DOWNARROW_HLT')
if emp.display_size:
col.prop(emp, 'thickness', text = 'Line Thickness')
col.prop(emp, 'keyframe_size', text = 'Keyframes Size')
col.prop(emp, 'frame_size', text = 'Frames Size')
box.separator(factor = 0.1)
row = box.row()
row.label(text = 'Visualization Type')
row.prop(emp, 'vis_type', text = '')
if emp.vis_type == 'VELOCITY':
row = box.row()
row.prop(emp, 'velocity_factor', text = '')
row.prop(emp, 'clamp_min', text = '')
row.prop(emp, 'clamp_max', text = '')
row = box.row()
row.prop(emp, 'color_before', text = '')
row.prop(emp, 'color_after', text = '')
row = box.row()
row.prop(emp, 'points', text = 'Points')
row.prop(emp, 'lines', text = 'Lines')
row = box.row()
row.prop(emp, 'handles', text = 'Handles')
row.prop(emp, 'infront', text = 'In Front')
row = box.row()
row.prop(emp, 'display_frames', text = 'Display Keyframe Numbers')
row.separator()
row = box.row()
row.operator('anim.go_to_keyframe')
# row = box.row()
# row.prop(context.preferences.addons[__package__].preferences, 'keyframes_range', text = 'Keyframe Distance Range')
# if context.object is None:
# return
# row.operator("anim.markers_bakerange", icon = 'MARKER', text ='', depress = scene.animtoolbox.bake_frame_range)
class RIGGERTOOLBOX_PT_Panel(ANIMTOOLBOX_PT_Panel, bpy.types.Panel):
bl_label = "Rigger Toolbox"
@@ -697,4 +722,4 @@ def draw_menu(self, context):
layout.operator('anim.isolate_pose_mode', text = '', depress = scene.animtoolbox.isolate_pose_mode, icon_value = custom_icons["isolate"].icon_id)
layout.separator()
layout.operator('object.motion_path_operator', text = '', depress = scene.animtoolbox.motion_path, icon_value = custom_icons["mt"].icon_id)
layout.operator('object.motion_path_operator', text = '', depress = scene.emp.motion_path, icon_value = custom_icons["mt"].icon_id)
File diff suppressed because it is too large Load Diff
+11 -9
View File
@@ -124,7 +124,9 @@ def add_value(key, value):
apply_handles(key, handle_r, handle_l)
#calculate the difference between current value and the fcurve value
def add_diff(fcurves, path, current_value, eval_array):
def add_diff(fcurves, path, current_value, eval_array):
if eval_array is None:
return
array_value = current_value - eval_array
if not any(array_value):
return
@@ -160,7 +162,7 @@ class ScaleValuesOp(bpy.types.Operator):
for obj in context.selected_objects:
if obj.animation_data.action is None:
continue
fcurves = Tools.get_fcurves_channelbag(obj, obj.animation_data.action)
fcurves = Tools.get_fcurves(obj, obj.animation_data.action)
for fcu in fcurves:
if obj.mode == 'POSE':
if selected_bones_filter(obj, fcu.data_path):
@@ -255,7 +257,7 @@ def random_value(self, context):
for obj in context.selected_objects:
if obj.animation_data.action is None:
continue
fcurves = Tools.get_fcurves_channelbag(obj, obj.animation_data.action)
fcurves = Tools.get_fcurves(obj, obj.animation_data.action)
for fcu in fcurves:
if obj.mode == 'POSE':
if selected_bones_filter(obj, fcu.data_path):
@@ -306,8 +308,8 @@ def evaluate_array(fcurves, fcu_path, frame, array_default = [0, 0, 0]):
continue
fcu_array[i] = fcu.evaluate(frame)
if (fcu_array == array_default).all():
return None
# if (fcu_array == array_default).all():
# return None
return np.array(fcu_array)
def evaluate_layers(context, obj, anim_data, fcu, array_default):
@@ -363,7 +365,7 @@ def evaluate_layers(context, obj, anim_data, fcu, array_default):
frame_eval = last_frame - (frame_eval - strip.frame_start)
offset = (strip.frame_start * 1/strip.scale - strip.action_frame_start) * strip.scale
frame_eval = strip.frame_start * 1/strip.scale + (frame_eval - strip.frame_start) * 1/strip.scale - offset * 1/strip.scale
fcurves = Tools.get_fcurves_channelbag(obj, action)
fcurves = Tools.get_fcurves(obj, action)
eval_array = evaluate_blend_type(fcurves, eval_array, fcu_path, frame_eval, influence, array_default, blend_type, blend_types)
#Adding an extra layer from the action outside and on top of the nla
@@ -376,7 +378,7 @@ def evaluate_layers(context, obj, anim_data, fcu, array_default):
influence = anim_data.action_influence
blend_type = anim_data.action_blend_type
fcurves = Tools.get_fcurves_channelbag(obj, action)
fcurves = Tools.get_fcurves(obj, action)
eval_array = evaluate_blend_type(fcurves, eval_array, fcu_path, frame, influence, array_default, blend_type, blend_types)
anim_data.use_tweak_mode = tweak_mode
@@ -419,7 +421,7 @@ def evaluate_value(self, context):
if obj.mode == 'POSE':
bonelist = context.selected_pose_bones if ui.multikey.selectedbones else obj.pose.bones
fcurves = Tools.get_fcurves_channelbag(obj, action)
fcurves = Tools.get_fcurves(obj, action)
for fcu in fcurves:
if fcu in fcu_paths:
continue
@@ -453,7 +455,7 @@ def evaluate_value(self, context):
array_default = np.array(attr_default(obj, (fcu.data_path, fcu.array_index)))
eval_array = evaluate_layers(context, obj, anim_data, fcu, array_default)
if eval_array is None:
fcurves = Tools.get_fcurves_channelbag(obj, action)
# fcurves = Tools.get_fcurves(obj, action)
eval_array = evaluate_array(fcurves, fcu.data_path, context.scene.frame_current, array_default)
#calculate the difference between current value and the fcurve value
+27 -2
View File
@@ -558,7 +558,7 @@ class ANIMTOOLBOX_PT_Display(ANIMTOOLBOX_PT_Panel, bpy.types.Panel):
row = box.row()
row.prop(emp, 'frame_start', text = 'Start')
row.prop(emp, 'frame_end', text = 'End')
# row.operator("anim.markers_bakerange", icon = 'MARKER', text ='', depress = scene.animtoolbox.bake_frame_range)
row.operator("anim.emp_markers_range", icon = 'MARKER', text ='', depress = scene.emp.marker_frame_range)
elif emp.frame_range == 'AROUND':
row = box.row()
@@ -596,8 +596,33 @@ class ANIMTOOLBOX_PT_Display(ANIMTOOLBOX_PT_Panel, bpy.types.Panel):
row = box.row()
row.prop(emp, 'display_frames', text = 'Display Keyframe Numbers')
row.separator()
box = layout.box()
split = box.split(factor = 0.35)
split.label(text = 'Channels')
split.prop(emp, 'channels', text ='')
row = box.row()
row.operator('anim.go_to_keyframe')
if bpy.app.version >= (4, 2, 0):
row.prop(emp, 'camera_space', icon = 'CON_CAMERASOLVER')
#row = box.row()
row.prop(emp, 'cursor_offset', icon = 'PIVOT_CURSOR')
box.separator(factor = 0.05)
row = box.row()
row.operator('anim.go_to_keyframe', icon = 'NEXT_KEYFRAME')
box.separator(factor = 0.05)
# split = box.split(factor = 0.75)
row = box.row()
row.prop(emp, 'select_circle', text = 'Selection Brush', icon = 'MESH_CIRCLE')
row = box.row()
prop_edit_icon = 'PROP_ON' if emp.prop_edit else 'PROP_OFF'
row.prop(emp, 'prop_edit', text = 'Proportional Editing', icon = prop_edit_icon)
row.prop(emp, 'smooth', text = 'Smooth', icon = 'MOD_SMOOTH')
row = box.row()
if emp.prop_edit:
row.prop(emp, 'prop_edit_falloff', text = '')
row.prop(emp, 'radius', text = '')
elif emp.smooth:
row.prop(emp, 'smooth_strength', text = '')
row.prop(emp, 'radius', text = '')
# row = box.row()
# row.prop(context.preferences.addons[__package__].preferences, 'keyframes_range', text = 'Keyframe Distance Range')
# if context.object is None:
Binary file not shown.
@@ -1,17 +1,17 @@
{
"last_check": "2025-12-30 14:52:43.839129",
"backup_date": "December-15-2025",
"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_p0",
"link": "https://api.github.com/repos/soupday/cc_blender_tools/zipball/refs/tags/2_3_4_p1",
"version": [
2,
3,
4,
0
1
]
}
}
@@ -1,6 +1,6 @@
{
"last_check": "2025-12-30 14:52:43.839129",
"backup_date": "December-30-2025",
"last_check": "2026-01-09 16:23:22.382598",
"backup_date": "January-9-2026",
"update_ready": false,
"ignore": false,
"just_restored": false,
@@ -1720,12 +1720,15 @@ class CC3Import(bpy.types.Operator):
elif self.param == "BUILD" or self.param == "BUILD_REBUILD":
chr_cache = props.get_context_character_cache(context)
if chr_cache:
bm = props.build_mode
props.build_mode = "IMPORTED"
mode_selection = utils.store_mode_selection_state()
utils.object_mode()
self.build_materials(context)
self.build_drivers(context)
self.do_import_report(context, stage = 1)
utils.restore_mode_selection_state(mode_selection)
props.build_mode = bm
elif self.param == "BUILD_DRIVERS":
chr_cache = props.get_context_character_cache(context)
@@ -1764,32 +1767,41 @@ class CC3Import(bpy.types.Operator):
if chr_cache:
utils.object_mode()
if chr_cache.get_render_target() != "EEVEE":
bm = props.build_mode
props.build_mode = "IMPORTED"
prefs.refractive_eyes = "PARALLAX"
utils.log_info("Character is currently build for Cycles Rendering.")
utils.log_info("Rebuilding Character for Eevee Rendering...")
self.build_materials(context, render_target="EEVEE")
self.build_drivers(context)
props.build_mode = bm
elif self.param == "REBUILD_BAKE":
chr_cache = props.get_context_character_cache(context)
if chr_cache:
utils.object_mode()
props.wrinkle_mode = False
bm = props.build_mode
props.build_mode = "IMPORTED"
prefs.refractive_eyes = "PARALLAX"
utils.log_info("Rebuilding Character for Eevee Bake...")
self.build_materials(context, render_target="EEVEE")
self.build_drivers(context)
props.build_mode = bm
elif self.param == "REBUILD_CYCLES":
chr_cache = props.get_context_character_cache(context)
if chr_cache:
utils.object_mode()
prefs.refractive_eyes = "SSR"
if chr_cache.get_render_target() != "CYCLES":
bm = props.build_mode
props.build_mode = "IMPORTED"
prefs.refractive_eyes = "SSR"
utils.log_info("Character is currently build for Eevee Rendering.")
utils.log_info("Rebuilding Character for Cycles Rendering...")
self.build_materials(context, render_target="CYCLES")
self.build_drivers(context)
props.build_mode = bm
return {"FINISHED"}
@@ -3869,7 +3869,9 @@ class LinkService():
# generate new action set data
set_id, set_generation = rigutils.generate_motion_set(actor_rig, motion_id, LINK_DATA.motion_prefix)
remove_actions = []
action_pairs = []
if actor_rig:
if actor.get_type() == "PROP":
# if it's a prop retarget the animation (or copy the rest pose):
# props have no bind pose so the rest pose is the first frame of
@@ -3888,6 +3890,7 @@ class LinkService():
rigutils.copy_rest_pose(motion_rig, actor_rig)
utils.safe_set_action(actor_rig, motion_rig_action)
rigutils.update_prop_rig(actor_rig)
else: # Avatar
if chr_cache.rigified:
update_link_status(f"Retargeting Motion...")
@@ -3897,11 +3900,14 @@ class LinkService():
rigutils.set_armature_action_name(armature_action, actor_rig_id, motion_id, LINK_DATA.motion_prefix)
remove_actions.append(motion_rig_action)
else:
actor_rig_action = utils.safe_get_action(actor_rig)
rigutils.add_motion_set_data(motion_rig_action, set_id, set_generation, rl_arm_id=rl_arm_id)
rigutils.set_armature_action_name(motion_rig_action, actor_rig_id, motion_id, LINK_DATA.motion_prefix)
motion_rig_action.use_fake_user = LINK_DATA.use_fake_user
utils.safe_set_action(actor_rig, motion_rig_action)
action_pairs.append((actor_rig_action, motion_rig_action))
rigutils.update_avatar_rig(actor_rig)
# assign motion object shape key actions:
key_actions = rigutils.apply_source_key_actions(actor_rig,
source_actions, copy=True,
@@ -3909,11 +3915,12 @@ class LinkService():
motion_prefix=LINK_DATA.motion_prefix,
all_matching=True,
set_id=set_id, set_generation=set_generation)
for action in key_actions.values():
actions = [ p[0] for p in key_actions.values() ]
for action in actions:
action.use_fake_user = LINK_DATA.use_fake_user
# remove unused motion key actions
for obj_action in source_actions["keys"].values():
if obj_action not in key_actions.values():
if obj_action not in actions:
remove_actions.append(obj_action)
# delete imported motion rig and objects
for obj in motion_objects:
@@ -21,7 +21,7 @@ import bpy
from . import imageutils, jsonutils, nodeutils, utils, params, vars
def detect_skin_material(mat):
def detect_skin_material_name(mat):
name = mat.name.lower()
if "std_skin_" in name or "ga_skin_" in name:
return True
@@ -67,7 +67,7 @@ def detect_key_words(hints, text):
return "False"
def detect_scalp_material(mat):
def detect_scalp_material_name(mat):
prefs = vars.prefs()
material_name = mat.name.lower()
hints = prefs.hair_scalp_hint.split(",")
@@ -79,14 +79,14 @@ def detect_scalp_material(mat):
return detect
def detect_eyelash_material(mat):
def detect_eyelash_material_name(mat):
name = mat.name.lower()
if "std_eyelash" in name or "ga_eyelash" in name:
return True
return False
def detect_teeth_material(mat):
def detect_teeth_material_name(mat):
name = mat.name.lower()
if "std_upper_teeth" in name:
return True
@@ -95,21 +95,21 @@ def detect_teeth_material(mat):
return False
def detect_tongue_material(mat):
def detect_tongue_material_name(mat):
name = mat.name.lower()
if "std_tongue" in name or "ga_tongue" in name:
return True
return False
def detect_nails_material(mat):
def detect_nails_material_name(mat):
name = mat.name.lower()
if "std_nails" in name or "ga_nails" in name:
return True
return False
def detect_body_object(chr_cache, obj):
def detect_body_object_name(obj):
name = obj.name.lower()
if "base_body" in name or "game_body" in name:
return True
@@ -282,16 +282,16 @@ def detect_materials_by_name(chr_cache, obj, mat):
if detect_hair_object(obj, tex_dirs, chr_cache.get_import_dir()) == "True":
object_type = "HAIR"
if detect_scalp_material(mat) == "True":
if detect_scalp_material_name(mat) == "True":
material_type = "SCALP"
elif detect_hair_material(obj, mat, tex_dirs, chr_cache.get_import_dir()) == "Deny":
material_type = "DEFAULT"
else:
material_type = "HAIR"
elif detect_body_object(chr_cache, obj):
elif detect_body_object_name(obj):
object_type = "BODY"
if detect_skin_material(mat):
if detect_skin_material_name(mat):
if "head" in mat_name:
material_type = "SKIN_HEAD"
elif "body" in mat_name:
@@ -300,9 +300,9 @@ def detect_materials_by_name(chr_cache, obj, mat):
material_type = "SKIN_ARM"
elif "leg" in mat_name:
material_type = "SKIN_LEG"
elif detect_nails_material(mat):
elif detect_nails_material_name(mat):
material_type = "NAILS"
elif detect_eyelash_material(mat):
elif detect_eyelash_material_name(mat):
material_type = "EYELASH"
elif detect_cornea_material(mat):
@@ -333,14 +333,14 @@ def detect_materials_by_name(chr_cache, obj, mat):
else:
material_type = "TEARLINE_RIGHT"
elif detect_teeth_material(mat):
elif detect_teeth_material_name(mat):
object_type = "TEETH"
if detect_material_side(mat, "UPPER"):
material_type = "TEETH_UPPER"
else:
material_type = "TEETH_LOWER"
elif detect_tongue_material(mat):
elif detect_tongue_material_name(mat):
object_type = "TONGUE"
material_type = "TONGUE"
@@ -368,12 +368,12 @@ def detect_materials_from_json(chr_cache, obj, mat, obj_json, mat_json):
object_type = "HAIR"
if detect_hair_material(obj, mat, tex_dirs, chr_cache.get_import_dir(), mat_json) == "True":
material_type = "HAIR"
elif detect_scalp_material(mat) == "True":
elif detect_scalp_material_name(mat) == "True":
material_type = "SCALP"
else:
material_type = "DEFAULT"
elif detect_eyelash_material(mat):
elif detect_eyelash_material_name(mat):
object_type = "BODY"
material_type = "EYELASH"
@@ -402,7 +402,7 @@ def detect_materials_from_json(chr_cache, obj, mat, obj_json, mat_json):
material_type = "SKIN_ARM"
elif "leg" in mat_name:
material_type = "SKIN_LEG"
elif detect_nails_material(mat):
elif detect_nails_material_name(mat):
material_type = "NAILS"
elif shader == "RLHead":
@@ -459,7 +459,12 @@ def detect_materials_from_json(chr_cache, obj, mat, obj_json, mat_json):
object_type = "DEFAULT"
material_type = "DEFAULT"
utils.log_info(f"Material: {mat_name} detected from Json data as: {material_type}")
# final check for body
if object_type == "DEFAULT" and material_type == "DEFAULT":
if detect_body_object_name(obj) and detect_skin_material_name(mat):
object_type = "BODY"
utils.log_info(f"Material: {obj.name}/{mat_name} detected from Json data as: {object_type}/{material_type}")
return object_type, material_type
@@ -1364,9 +1364,9 @@ SHADER_MATRIX = [
# modifier properties:
# [prop_name, material_type, modifier_type, modifier_id, expression]
"modifiers": [
[ "eye_iris_depth", "EYE_RIGHT", "DISPLACE", "Eye_Displace_R", "mod.strength = 1.5 * parameters.eye_iris_depth"],
[ "eye_iris_depth", "EYE_RIGHT", "DISPLACE", "Eye_Displace_R", "mod.strength = 1.0 * parameters.eye_iris_depth"],
[ "eye_pupil_scale", "EYE_RIGHT", "UV_WARP", "Eye_UV_Warp_R", "mod.scale = (1.0 / parameters.eye_pupil_scale, 1.0 / parameters.eye_pupil_scale)" ],
[ "eye_iris_depth", "EYE_LEFT", "DISPLACE", "Eye_Displace_L", "mod.strength = 1.5 * parameters.eye_iris_depth"],
[ "eye_iris_depth", "EYE_LEFT", "DISPLACE", "Eye_Displace_L", "mod.strength = 1.0 * parameters.eye_iris_depth"],
[ "eye_pupil_scale", "EYE_LEFT", "UV_WARP", "Eye_UV_Warp_L", "mod.scale = (1.0 / parameters.eye_pupil_scale, 1.0 / parameters.eye_pupil_scale)" ],
],
# material setting properties:
@@ -1391,7 +1391,7 @@ SHADER_MATRIX = [
"mapping": [
["DIFFUSE", "Sclera Scale", "", "eye_sclera_scale"],
["DIFFUSE", "Iris Radius", "", "eye_iris_radius"],
["DIFFUSE", "Pupil Scale", "", "eye_pupil_scale"],
["DIFFUSE", "Pupil Scale", "func_set_parallax_pupil_scale", "eye_pupil_scale"],
["DIFFUSE", "Depth", "func_set_parallax_iris_depth", "eye_iris_depth"],
["DIFFUSE", "IOR", "", "eye_ior"],
["SCLERA", "Invert", "func_eye_invert", "eye_is_left_eye"],
@@ -552,7 +552,7 @@ class CC3ToolsAddonPreferences(bpy.types.AddonPreferences):
("LOCAL","Local Machine","Connect to a DataLink server running on the local machine"),
("REMOTE","Remote Host","Connect to a DataLink server running on a remote machine"),
], default="LOCAL", name = "DataLink Target")
datalink_auto_lighting: bpy.props.BoolProperty(default=True,
datalink_auto_lighting: bpy.props.BoolProperty(default=False,
description="Use automatic lighting from CC/iC Go-B")
@@ -2778,7 +2778,7 @@ def full_retarget_source_rig_action(op, chr_cache, src_rig=None, src_action=None
rigutils.add_motion_set_data(armature_action, set_id, set_generation, rl_arm_id=rl_arm_id)
armature_action.use_fake_user = props.rigify_retarget_use_fake_user if use_ui_options else True
utils.log_info(f"Renaming armature action to: {armature_action.name}")
for obj_id, key_action in key_actions.items():
for obj_id, (key_action, old_key_action) in key_actions.items():
rigutils.set_key_action_name(key_action, rig_id, motion_id, obj_id, motion_prefix)
rigutils.add_motion_set_data(key_action, set_id, set_generation, obj_id=obj_id)
utils.log_info(f"Renaming key action ({obj_id}) to: {key_action.name}")
@@ -428,6 +428,7 @@ def apply_source_key_actions(dst_rig, source_actions, all_matching=False, copy=F
if obj.type == "MESH":
if utils.object_has_shape_keys(obj):
obj_id = get_action_obj_id(obj)
old_action = utils.safe_get_action(obj.data.shape_keys)
if (obj_id in source_actions["keys"] and
obj_has_action_shape_keys(obj, source_actions["keys"][obj_id])):
action = source_actions["keys"][obj_id]
@@ -440,7 +441,7 @@ def apply_source_key_actions(dst_rig, source_actions, all_matching=False, copy=F
utils.log_info(f" - Applying action: {action.name} to {obj_id}")
utils.safe_set_action(obj.data.shape_keys, action)
obj_used.append(obj)
key_actions[obj_id] = action
key_actions[obj_id] = (action, old_action)
else:
utils.safe_set_action(obj.data.shape_keys, None)
@@ -452,6 +453,7 @@ def apply_source_key_actions(dst_rig, source_actions, all_matching=False, copy=F
if filter and obj not in filter: continue
if obj not in obj_used and utils.object_has_shape_keys(obj):
obj_id = get_action_obj_id(obj)
old_action = utils.safe_get_action(obj.data.shape_keys)
if body_action:
if obj_has_action_shape_keys(obj, body_action):
action = body_action
@@ -464,7 +466,7 @@ def apply_source_key_actions(dst_rig, source_actions, all_matching=False, copy=F
utils.log_info(f" - Applying action: {action.name} to {obj_id}")
utils.safe_set_action(obj.data.shape_keys, action)
obj_used.append(obj)
key_actions[obj_id] = action
key_actions[obj_id] = (action, old_action)
return key_actions
+58 -13
View File
@@ -210,13 +210,13 @@ def import_rlx_light(data: BinaryData, data_folder):
cutoff_distance_cache = frame_cache(num_frames)
spot_blend_cache = frame_cache(num_frames)
spot_size_cache = frame_cache(num_frames)
visible_cache = frame_cache(num_frames)
render_cache = frame_cache(num_frames)
frame = 0
start = None
while not frames.eof():
frame += 1
time = frames.time()
frame = frames.int()
frame = frames.int() + 1
if start is None:
start = frame
active = frames.bool()
@@ -230,8 +230,6 @@ def import_rlx_light(data: BinaryData, data_folder):
falloff = frames.float() / 100
attenuation = frames.float() / 100
darkness = frames.float()
if not active:
multiplier = 0.0
cutoff_distance = range
store_frame(light, loc_cache, frame, start, loc)
store_frame(light, rot_cache, frame, start, rot)
@@ -254,6 +252,9 @@ def import_rlx_light(data: BinaryData, data_folder):
elif light_type == "POINT":
energy = ENERGY_SCALE * multiplier
store_frame(light, energy_cache, frame, start, energy)
store_frame(light, visible_cache, frame, start, 0.0 if active else 1.0)
store_frame(light, render_cache, frame, start, 0.0 if active else 1.0)
ob_action, light_action, ob_slot, light_slot = prep_rlx_actions(light, name, "Export",
reuse_existing=False,
@@ -261,6 +262,8 @@ def import_rlx_light(data: BinaryData, data_folder):
add_cache_fcurves(ob_action, light.path_from_id("location"), loc_cache, num_frames, "Location", slot=ob_slot)
add_cache_rotation_fcurves(light, ob_action, rot_cache, num_frames, slot=ob_slot)
add_cache_fcurves(ob_action, light.path_from_id("scale"), sca_cache, num_frames, "Scale", slot=ob_slot)
add_cache_fcurves(light_action, light.path_from_id("hide_viewport"), visible_cache, num_frames, "Hide Viewport", slot=ob_slot, interpolation="CONSTANT")
add_cache_fcurves(light_action, light.path_from_id("hide_render"), render_cache, num_frames, "Hide Render", slot=ob_slot, interpolation="CONSTANT")
add_cache_fcurves(light_action, light.data.path_from_id("color"), color_cache, num_frames, "Color", slot=light_slot)
add_cache_fcurves(light_action, light.data.path_from_id("energy"), energy_cache, num_frames, "Energy", slot=light_slot)
add_cache_fcurves(light_action, light.data.path_from_id("cutoff_distance"), cutoff_distance_cache, num_frames, "Cutoff Distance", slot=light_slot)
@@ -298,12 +301,10 @@ def import_rlx_camera(data: BinaryData, data_folder):
f_stop_cache = frame_cache(num_frames)
active_cache = []
frame = 0
start = None
while not frames.eof():
frame += 1
time = frames.time()
frame = frames.int()
frame = frames.int() + 1
if start is None:
start = frame
loc = frames.vector() / 100
@@ -427,7 +428,7 @@ def add_cache_rotation_fcurves(obj, action: bpy.types.Action, cache, num_frames,
add_cache_fcurves(action, data_path, cache, num_frames, group_name=group_name, slot=slot)
def add_cache_fcurves(action: bpy.types.Action, data_path, cache, num_frames, group_name=None, slot=None):
def add_cache_fcurves(action: bpy.types.Action, data_path, cache, num_frames, group_name=None, slot=None, interpolation="LINEAR"):
channels = utils.get_action_channels(action, slot)
num_curves = len(cache)
if channels:
@@ -436,9 +437,51 @@ def add_cache_fcurves(action: bpy.types.Action, data_path, cache, num_frames, gr
channels.groups.new(group_name)
for i in range(0, num_curves):
fcurve = channels.fcurves.new(data_path, index=i)
fcurve.auto_smoothing = "NONE"
fcurve.group = channels.groups[group_name]
fcurve.keyframe_points.add(num_frames)
fcurve.keyframe_points.foreach_set('co', cache[i])
reduced = reduce_cache(cache[i], interpolation)
num_reduced = int(len(reduced) / 2)
fcurve.keyframe_points.add(num_reduced)
fcurve.keyframe_points.foreach_set('co', reduced)
if interpolation != "BEZIER":
for keyframe in fcurve.keyframe_points:
keyframe.interpolation = interpolation
else:
L = len(fcurve.keyframe_points)
for i, keyframe in enumerate(fcurve.keyframe_points):
prev = fcurve.keyframe_points[i-1] if i > 0 else keyframe
next = fcurve.keyframe_points[i+1] if i < L-1 else keyframe
keyframe.handle_left_type = "AUTO"
keyframe.handle_left[0] = keyframe.co.x - 0.5
keyframe.handle_left[1] = (keyframe.co.y + prev.co.y) * 0.5
keyframe.handle_right_type = "AUTO"
keyframe.handle_right[0] = keyframe.co.x + 0.5
keyframe.handle_right[1] = (keyframe.co.y + next.co.y) * 0.5
def reduce_cache(cache, interpolation):
if not cache or len(cache) <= 4:
return cache
reduced = []
L = len(cache)
Lm2 = L - 2
# add first frame
reduced.append(cache[0])
reduced.append(cache[1])
use_next = interpolation != "CONSTANT"
for i in range(2, L, 2):
frame = cache[i]
value = cache[i+1]
value_last = cache[i-1]
value_next = cache[i+3] if i < Lm2 else value
last_changed = abs(value - value_last) > 0.0001
next_changed = abs(value - value_next) > 0.0001
if last_changed or (use_next and next_changed):
reduced.append(frame)
reduced.append(value)
# add last frame
#reduced.append(cache[-2])
#reduced.append(cache[-1])
return reduced
def add_camera_markers(camera, cache, num_frames, start):
@@ -567,8 +610,10 @@ def decode_rlx_light(light_data, light: bpy.types.Object=None, container=None):
light.data.contact_shadow_distance = 0.1
light.data.contact_shadow_bias = 0.03
light.data.contact_shadow_thickness = 0.001
if not active:
utils.hide(light)
light.hide_viewport = not active
light.hide_render = not active
return light
@@ -2641,7 +2641,7 @@ def cycles_setup(context):
pass
try:
context.scene.cycles.preview_denoiser = 'OPTIX'
context.scene.cycles.preview_denoiser = 'OPENIMAGEDENOISE'
context.scene.cycles.denoiser = 'OPTIX'
except:
pass
@@ -67,7 +67,12 @@ def apply_multi_res_shape(body):
mod = modifiers.get_object_modifier(body, modifiers.MOD_MULTIRES, modifiers.MOD_MULTIRES_NAME)
if mod and utils.set_only_active_object(body):
utils.log_info("Applying base shape")
bpy.ops.object.multires_base_apply(modifier=mod.name)
if utils.B500():
# apply_heuristic=True applies base assuming it will be used subdivided
# apply_heuristic=False applies base to level 0
bpy.ops.object.multires_base_apply(modifier=mod.name, apply_heuristic=False)
else:
bpy.ops.object.multires_base_apply(modifier=mod.name)
def displacement_map_func(value):
@@ -209,20 +214,22 @@ def do_multires_bake(context, chr_cache, multires_mesh, layer_target, apply_shap
utils.log_recess()
utils.log_info("Baking complete!")
utils.delete_mesh_object(norm_body)
if layer_target == LAYER_TARGET_SCULPT and apply_shape and source_body:
utils.log_info("Transfering sculpt base shape to source body...")
utils.unhide(multires_mesh)
utils.unhide(source_body)
copy_base_shape(multires_mesh, source_body, layer_target, True)
if norm_body and source_body:
copy_base_shape(norm_body, source_body, layer_target, True)
# if there is a detail sculpt body, update that with the new base shape too
detail_body = chr_cache.get_detail_body(context_object=source_body)
if detail_body:
copy_base_shape(multires_mesh, detail_body, layer_target, True)
# if there is a detail sculpt body, update that with the new base shape too
detail_body = chr_cache.get_detail_body(context_object=source_body)
if detail_body:
# the base shape has only been applied to the norm_body so far ...
copy_base_shape(norm_body, detail_body, layer_target, True)
utils.delete_mesh_object(norm_body)
# restore render engine
bake.post_bake(context, bake_state)
@@ -607,10 +607,13 @@ def func_export_eye_depth(cc, depth):
return (depth) * 3.0
def func_set_eye_depth(cc, depth):
return depth * 1.5
return depth * 1.0
def func_set_parallax_iris_depth(cc, depth):
return depth * 1.5 + 0.1
return depth * 1.0
def func_set_parallax_pupil_scale(cc, scale):
return scale * 0.6666
def func_index_f0(cc, v: list):
return v[0]
@@ -1499,6 +1499,10 @@ def hide_tree(obj, hide=True, render=False):
except: ...
def show(obj: bpy.types.Object, show=True, render=False):
hide(obj, hide=not show, render=render)
def hide(obj: bpy.types.Object, hide=True, render=False):
try:
obj.hide_set(hide)
@@ -0,0 +1,3 @@
# Don't index SpecStory auto-save files, but allow explicit context inclusion via @ references
.specstory/**
@@ -0,0 +1,23 @@
# AssignedTask
AssignedTask is a task as it is received by the Worker.
## Properties
Name | Type | Description | Notes
------------ | ------------- | ------------- | -------------
**uuid** | **str** | |
**job** | **str** | |
**name** | **str** | |
**status** | [**TaskStatus**](TaskStatus.md) | |
**priority** | **int** | |
**job_priority** | **int** | |
**job_type** | **str** | |
**task_type** | **str** | |
**commands** | [**[Command]**](Command.md) | |
**steps_completed** | **int** | |
**steps_total** | **int** | |
**any string name** | **bool, date, datetime, dict, float, int, list, str, none_type** | any string name can be used but the value must be the correct type | [optional]
[[Back to Model list]](../README.md#documentation-for-models) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to README]](../README.md)
@@ -0,0 +1,13 @@
# AssignedWorker
## Properties
Name | Type | Description | Notes
------------ | ------------- | ------------- | -------------
**name** | **str** | |
**uuid** | **str** | |
**any string name** | **bool, date, datetime, dict, float, int, list, str, none_type** | any string name can be used but the value must be the correct type | [optional]
[[Back to Model list]](../README.md#documentation-for-models) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to README]](../README.md)
@@ -0,0 +1,25 @@
# AvailableJobSetting
Single setting of a Job types.
## Properties
Name | Type | Description | Notes
------------ | ------------- | ------------- | -------------
**key** | **str** | Identifier for the setting, must be unique within the job type. |
**type** | [**AvailableJobSettingType**](AvailableJobSettingType.md) | |
**subtype** | [**AvailableJobSettingSubtype**](AvailableJobSettingSubtype.md) | | [optional]
**choices** | **[str]** | When given, limit the valid values to these choices. Only usable with string type. | [optional]
**propargs** | **{str: (bool, date, datetime, dict, float, int, list, str, none_type)}** | Any extra arguments to the bpy.props.SomeProperty() call used to create this property. | [optional]
**description** | **bool, date, datetime, dict, float, int, list, str, none_type** | The description/tooltip shown in the user interface. | [optional]
**label** | **bool, date, datetime, dict, float, int, list, str, none_type** | Label for displaying this setting. If not specified, the key is used to generate a reasonable label. | [optional]
**default** | **bool, date, datetime, dict, float, int, list, str, none_type** | The default value shown to the user when determining this setting. | [optional]
**eval** | **str** | Python expression to be evaluated in order to determine the default value for this setting. | [optional]
**eval_info** | [**AvailableJobSettingEvalInfo**](AvailableJobSettingEvalInfo.md) | | [optional]
**visible** | [**AvailableJobSettingVisibility**](AvailableJobSettingVisibility.md) | | [optional]
**required** | **bool** | Whether to immediately reject a job definition, of this type, without this particular setting. | [optional] if omitted the server will use the default value of False
**editable** | **bool** | Whether to allow editing this setting after the job has been submitted. Would imply deleting all existing tasks for this job, and recompiling it. | [optional] if omitted the server will use the default value of False
**any string name** | **bool, date, datetime, dict, float, int, list, str, none_type** | any string name can be used but the value must be the correct type | [optional]
[[Back to Model list]](../README.md#documentation-for-models) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to README]](../README.md)
@@ -0,0 +1,14 @@
# AvailableJobSettingEvalInfo
Meta-data for the 'eval' expression.
## Properties
Name | Type | Description | Notes
------------ | ------------- | ------------- | -------------
**show_link_button** | **bool** | Enables the &#39;eval on submit&#39; toggle button behavior for this setting. A toggle button will be shown in Blender&#39;s submission interface. When toggled on, the &#x60;eval&#x60; expression will determine the setting&#39;s value. Manually editing the setting is then no longer possible, and instead of an input field, the &#39;description&#39; string is shown. An example use is the to-be-rendered frame range, which by default automatically follows the scene range, but can be overridden manually when desired. | defaults to False
**description** | **str** | Description of what the &#39;eval&#39; expression is doing. It is also used as placeholder text to show when the manual input field is hidden (because eval-on-submit has been toggled on by the user). | defaults to ""
**any string name** | **bool, date, datetime, dict, float, int, list, str, none_type** | any string name can be used but the value must be the correct type | [optional]
[[Back to Model list]](../README.md#documentation-for-models) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to README]](../README.md)
@@ -0,0 +1,12 @@
# AvailableJobSettingSubtype
Sub-type of the job setting. Currently only available for string types. `HASHED_FILE_PATH` is a directory path + `\"/######\"` appended.
## Properties
Name | Type | Description | Notes
------------ | ------------- | ------------- | -------------
**value** | **str** | Sub-type of the job setting. Currently only available for string types. &#x60;HASHED_FILE_PATH&#x60; is a directory path + &#x60;\&quot;/######\&quot;&#x60; appended. | must be one of ["file_path", "dir_path", "file_name", "hashed_file_path", ]
[[Back to Model list]](../README.md#documentation-for-models) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to README]](../README.md)
@@ -0,0 +1,12 @@
# AvailableJobSettingType
Type of job setting, must be usable as IDProperty type in Blender. No nested structures (arrays, dictionaries) are supported.
## Properties
Name | Type | Description | Notes
------------ | ------------- | ------------- | -------------
**value** | **str** | Type of job setting, must be usable as IDProperty type in Blender. No nested structures (arrays, dictionaries) are supported. | must be one of ["string", "int32", "float", "bool", ]
[[Back to Model list]](../README.md#documentation-for-models) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to README]](../README.md)
@@ -0,0 +1,12 @@
# AvailableJobSettingVisibility
When to show this setting. `visible`: always show. `submission`: only show in the UI of a job submitter (like a Blender add-on). `web`: only show in the web interface for management, but not when submitting the job. `hidden`: never show; only available to the job compiler script as internal setting.
## Properties
Name | Type | Description | Notes
------------ | ------------- | ------------- | -------------
**value** | **str** | When to show this setting. &#x60;visible&#x60;: always show. &#x60;submission&#x60;: only show in the UI of a job submitter (like a Blender add-on). &#x60;web&#x60;: only show in the web interface for management, but not when submitting the job. &#x60;hidden&#x60;: never show; only available to the job compiler script as internal setting. | defaults to "visible", must be one of ["visible", "hidden", "submission", "web", ]
[[Back to Model list]](../README.md#documentation-for-models) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to README]](../README.md)
@@ -0,0 +1,17 @@
# AvailableJobType
Job type supported by this Manager, and its parameters.
## Properties
Name | Type | Description | Notes
------------ | ------------- | ------------- | -------------
**name** | **str** | |
**label** | **str** | |
**settings** | [**[AvailableJobSetting]**](AvailableJobSetting.md) | |
**etag** | **str** | Hash of the job type. If the job settings or the label change, this etag will change. This is used on job submission to ensure that the submitted job settings are up to date. |
**description** | **str** | The description/tooltip shown in the user interface. | [optional]
**any string name** | **bool, date, datetime, dict, float, int, list, str, none_type** | any string name can be used but the value must be the correct type | [optional]
[[Back to Model list]](../README.md#documentation-for-models) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to README]](../README.md)
@@ -0,0 +1,13 @@
# AvailableJobTypes
List of job types supported by this Manager.
## Properties
Name | Type | Description | Notes
------------ | ------------- | ------------- | -------------
**job_types** | [**[AvailableJobType]**](AvailableJobType.md) | |
**any string name** | **bool, date, datetime, dict, float, int, list, str, none_type** | any string name can be used but the value must be the correct type | [optional]
[[Back to Model list]](../README.md#documentation-for-models) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to README]](../README.md)
@@ -0,0 +1,16 @@
# BlenderPathCheckResult
## Properties
Name | Type | Description | Notes
------------ | ------------- | ------------- | -------------
**input** | **str** | The input that was given to find this Blender. |
**path** | **str** | The path that was found. |
**source** | [**BlenderPathSource**](BlenderPathSource.md) | |
**is_usable** | **bool** | Whether the path is usable or not. |
**cause** | **str** | Description of why this path is (not) usable. |
**any string name** | **bool, date, datetime, dict, float, int, list, str, none_type** | any string name can be used but the value must be the correct type | [optional]
[[Back to Model list]](../README.md#documentation-for-models) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to README]](../README.md)
@@ -0,0 +1,11 @@
# BlenderPathFindResult
## Properties
Name | Type | Description | Notes
------------ | ------------- | ------------- | -------------
**value** | [**[BlenderPathCheckResult]**](BlenderPathCheckResult.md) | |
[[Back to Model list]](../README.md#documentation-for-models) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to README]](../README.md)
@@ -0,0 +1,11 @@
# BlenderPathSource
## Properties
Name | Type | Description | Notes
------------ | ------------- | ------------- | -------------
**value** | **str** | | must be one of ["file_association", "path_envvar", "system_location", "input_path", "default", ]
[[Back to Model list]](../README.md#documentation-for-models) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to README]](../README.md)
@@ -0,0 +1,15 @@
# Command
Command represents a single command to execute by the Worker.
## Properties
Name | Type | Description | Notes
------------ | ------------- | ------------- | -------------
**name** | **str** | |
**parameters** | **{str: (bool, date, datetime, dict, float, int, list, str, none_type)}** | |
**total_step_count** | **int** | Number of steps this command executes. This has to be implemented in the command&#39;s implementation on the Worker (to recognise what a \&quot;step\&quot; is), as well as given in the authoring code of the job type JavaScript script (to indicate how many steps the command invocation will perform). If not given, or set to 0, the command is not expected to send any step progress. In this case, the Worker will send a step update at completion of the command. |
**any string name** | **bool, date, datetime, dict, float, int, list, str, none_type** | any string name can be used but the value must be the correct type | [optional]
[[Back to Model list]](../README.md#documentation-for-models) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to README]](../README.md)
@@ -0,0 +1,14 @@
# Error
Generic error response.
## Properties
Name | Type | Description | Notes
------------ | ------------- | ------------- | -------------
**code** | **int** | HTTP status code of this response. Is included in the payload so that a single object represents all error information. Code 503 is used when the database is busy. The HTTP response will contain a &#39;Retry-After&#39; HTTP header that indicates after which time the request can be retried. Following the header is not mandatory, and it&#39;s up to the client to do something reasonable like exponential backoff. |
**message** | **str** | |
**any string name** | **bool, date, datetime, dict, float, int, list, str, none_type** | any string name can be used but the value must be the correct type | [optional]
[[Back to Model list]](../README.md#documentation-for-models) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to README]](../README.md)
@@ -0,0 +1,11 @@
# EventFarmStatus
## Properties
Name | Type | Description | Notes
------------ | ------------- | ------------- | -------------
**value** | **FarmStatusReport** | |
[[Back to Model list]](../README.md#documentation-for-models) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to README]](../README.md)
@@ -0,0 +1,24 @@
# EventJobUpdate
Subset of a Job, sent over SocketIO/MQTT when a job changes. For new jobs, `previous_status` will be excluded.
## Properties
Name | Type | Description | Notes
------------ | ------------- | ------------- | -------------
**id** | **str** | UUID of the Job |
**updated** | **datetime** | Timestamp of last update |
**status** | [**JobStatus**](JobStatus.md) | |
**type** | **str** | |
**refresh_tasks** | **bool** | Indicates that the client should refresh all the job&#39;s tasks. This is sent for mass updates, where updating each individual task would generate too many updates to be practical. |
**steps_completed** | **int** | |
**steps_total** | **int** | |
**priority** | **int** | | defaults to 50
**name** | **str** | Name of the job | [optional]
**previous_status** | [**JobStatus**](JobStatus.md) | | [optional]
**delete_requested_at** | **datetime** | If job deletion was requested, this is the timestamp at which that request was stored on Flamenco Manager. | [optional]
**was_deleted** | **bool** | When a job was just deleted, this is set to &#x60;true&#x60;. If this is specified, only the &#39;id&#39; field is set, the rest will be empty. | [optional]
**any string name** | **bool, date, datetime, dict, float, int, list, str, none_type** | any string name can be used but the value must be the correct type | [optional]
[[Back to Model list]](../README.md#documentation-for-models) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to README]](../README.md)
@@ -0,0 +1,14 @@
# EventLastRenderedUpdate
Indicator that the last-rendered image of this job was updated.
## Properties
Name | Type | Description | Notes
------------ | ------------- | ------------- | -------------
**job_id** | **str** | |
**thumbnail** | [**JobLastRenderedImageInfo**](JobLastRenderedImageInfo.md) | |
**any string name** | **bool, date, datetime, dict, float, int, list, str, none_type** | any string name can be used but the value must be the correct type | [optional]
[[Back to Model list]](../README.md#documentation-for-models) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to README]](../README.md)
@@ -0,0 +1,13 @@
# EventLifeCycle
Flamenco life-cycle event, for things like shutting down, starting up, etc.
## Properties
Name | Type | Description | Notes
------------ | ------------- | ------------- | -------------
**type** | [**LifeCycleEventType**](LifeCycleEventType.md) | |
**any string name** | **bool, date, datetime, dict, float, int, list, str, none_type** | any string name can be used but the value must be the correct type | [optional]
[[Back to Model list]](../README.md#documentation-for-models) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to README]](../README.md)
@@ -0,0 +1,14 @@
# EventTaskLogUpdate
Task log chunk, sent to a MQTT topic/SocketIO room dedicated to a single task, to avoid sending too many updates.
## Properties
Name | Type | Description | Notes
------------ | ------------- | ------------- | -------------
**task_id** | **str** | UUID of the Task |
**log** | **str** | Chunk of the task log. May contain multiple lines of text. |
**any string name** | **bool, date, datetime, dict, float, int, list, str, none_type** | any string name can be used but the value must be the correct type | [optional]
[[Back to Model list]](../README.md#documentation-for-models) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to README]](../README.md)
@@ -0,0 +1,22 @@
# EventTaskUpdate
Subset of a Task, sent over SocketIO/MQTT when a task changes. For new tasks, `previous_status` will be excluded.
## Properties
Name | Type | Description | Notes
------------ | ------------- | ------------- | -------------
**id** | **str** | UUID of the Task |
**job_id** | **str** | |
**name** | **str** | Name of the task |
**updated** | **datetime** | Timestamp of last update |
**status** | [**TaskStatus**](TaskStatus.md) | |
**activity** | **str** | |
**steps_completed** | **int** | |
**steps_total** | **int** | |
**worker** | [**AssignedWorker**](AssignedWorker.md) | | [optional]
**previous_status** | [**TaskStatus**](TaskStatus.md) | | [optional]
**any string name** | **bool, date, datetime, dict, float, int, list, str, none_type** | any string name can be used but the value must be the correct type | [optional]
[[Back to Model list]](../README.md#documentation-for-models) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to README]](../README.md)
@@ -0,0 +1,14 @@
# EventWorkerTagUpdate
Worker Tag, sent over SocketIO/MQTT when it changes.
## Properties
Name | Type | Description | Notes
------------ | ------------- | ------------- | -------------
**tag** | [**WorkerTag**](WorkerTag.md) | |
**was_deleted** | **bool** | When a tag was just deleted, this is set to &#x60;true&#x60;. | [optional]
**any string name** | **bool, date, datetime, dict, float, int, list, str, none_type** | any string name can be used but the value must be the correct type | [optional]
[[Back to Model list]](../README.md#documentation-for-models) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to README]](../README.md)
@@ -0,0 +1,22 @@
# EventWorkerUpdate
Subset of a Worker, sent over SocketIO/MQTT when a worker changes.
## Properties
Name | Type | Description | Notes
------------ | ------------- | ------------- | -------------
**id** | **str** | UUID of the Worker |
**name** | **str** | Name of the worker |
**updated** | **datetime** | Timestamp of last update |
**status** | [**WorkerStatus**](WorkerStatus.md) | |
**version** | **str** | |
**can_restart** | **bool** | Whether this Worker can auto-restart. |
**last_seen** | **datetime** | Last time this worker was seen by the Manager. | [optional]
**previous_status** | [**WorkerStatus**](WorkerStatus.md) | | [optional]
**status_change** | [**WorkerStatusChangeRequest**](WorkerStatusChangeRequest.md) | | [optional]
**deleted_at** | **datetime** | This is only set when the worker was deleted. | [optional]
**any string name** | **bool, date, datetime, dict, float, int, list, str, none_type** | any string name can be used but the value must be the correct type | [optional]
[[Back to Model list]](../README.md#documentation-for-models) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to README]](../README.md)
@@ -0,0 +1,11 @@
# FarmStatus
## Properties
Name | Type | Description | Notes
------------ | ------------- | ------------- | -------------
**value** | **str** | | must be one of ["active", "idle", "waiting", "asleep", "inoperative", "unknown", "starting", ]
[[Back to Model list]](../README.md#documentation-for-models) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to README]](../README.md)
@@ -0,0 +1,12 @@
# FarmStatusReport
## Properties
Name | Type | Description | Notes
------------ | ------------- | ------------- | -------------
**status** | [**FarmStatus**](FarmStatus.md) | |
**any string name** | **bool, date, datetime, dict, float, int, list, str, none_type** | any string name can be used but the value must be the correct type | [optional]
[[Back to Model list]](../README.md#documentation-for-models) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to README]](../README.md)
@@ -0,0 +1,15 @@
# FlamencoVersion
## Properties
Name | Type | Description | Notes
------------ | ------------- | ------------- | -------------
**version** | **str** | Version of this Manager, meant for human consumption. For release builds it is the same as &#x60;shortversion&#x60;, for other builds it also includes the &#x60;git&#x60; version info. |
**shortversion** | **str** | |
**name** | **str** | |
**git** | **str** | |
**any string name** | **bool, date, datetime, dict, float, int, list, str, none_type** | any string name can be used but the value must be the correct type | [optional]
[[Back to Model list]](../README.md#documentation-for-models) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to README]](../README.md)
@@ -0,0 +1,29 @@
# Job
## Properties
Name | Type | Description | Notes
------------ | ------------- | ------------- | -------------
**name** | **str** | |
**type** | **str** | |
**submitter_platform** | **str** | Operating system of the submitter. This is used to recognise two-way variables. This should be a lower-case version of the platform, like \&quot;linux\&quot;, \&quot;windows\&quot;, \&quot;darwin\&quot;, \&quot;openbsd\&quot;, etc. Should be ompatible with Go&#39;s &#x60;runtime.GOOS&#x60;; run &#x60;go tool dist list&#x60; to get a list of possible platforms. As a special case, the platform \&quot;manager\&quot; can be given, which will be interpreted as \&quot;the Manager&#39;s platform\&quot;. This is mostly to make test/debug scripts easier, as they can use a static document on all platforms. |
**id** | **str** | UUID of the Job |
**created** | **datetime** | Creation timestamp |
**updated** | **datetime** | Timestamp of last update. |
**status** | [**JobStatus**](JobStatus.md) | |
**activity** | **str** | Description of the last activity on this job. |
**steps_completed** | **int** | |
**steps_total** | **int** | |
**priority** | **int** | | defaults to 50
**type_etag** | **str** | Hash of the job type, copied from the &#x60;AvailableJobType.etag&#x60; property of the job type. The job will be rejected if this field doesn&#39;t match the actual job type on the Manager. This prevents job submission with old settings, after the job compiler script has been updated. If this field is omitted, the check is bypassed. | [optional]
**settings** | [**JobSettings**](JobSettings.md) | | [optional]
**metadata** | [**JobMetadata**](JobMetadata.md) | | [optional]
**storage** | [**JobStorageInfo**](JobStorageInfo.md) | | [optional]
**worker_tag** | **str** | Worker tag that should execute this job. When a tag ID is given, only Workers in that tag will be scheduled to work on it. If empty or omitted, all workers can work on this job. | [optional]
**initial_status** | [**JobStatus**](JobStatus.md) | | [optional]
**delete_requested_at** | **datetime** | If job deletion was requested, this is the timestamp at which that request was stored on Flamenco Manager. | [optional]
**any string name** | **bool, date, datetime, dict, float, int, list, str, none_type** | any string name can be used but the value must be the correct type | [optional]
[[Back to Model list]](../README.md#documentation-for-models) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to README]](../README.md)
@@ -0,0 +1,19 @@
# JobAllOf
## Properties
Name | Type | Description | Notes
------------ | ------------- | ------------- | -------------
**id** | **str** | UUID of the Job |
**created** | **datetime** | Creation timestamp |
**updated** | **datetime** | Timestamp of last update. |
**status** | [**JobStatus**](JobStatus.md) | |
**activity** | **str** | Description of the last activity on this job. |
**steps_completed** | **int** | |
**steps_total** | **int** | |
**delete_requested_at** | **datetime** | If job deletion was requested, this is the timestamp at which that request was stored on Flamenco Manager. | [optional]
**any string name** | **bool, date, datetime, dict, float, int, list, str, none_type** | any string name can be used but the value must be the correct type | [optional]
[[Back to Model list]](../README.md#documentation-for-models) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to README]](../README.md)
@@ -0,0 +1,12 @@
# JobBlocklist
List of workers that are not allowed certain task types on a specific job.
## Properties
Name | Type | Description | Notes
------------ | ------------- | ------------- | -------------
**value** | [**[JobBlocklistEntry]**](JobBlocklistEntry.md) | List of workers that are not allowed certain task types on a specific job. |
[[Back to Model list]](../README.md#documentation-for-models) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to README]](../README.md)
@@ -0,0 +1,14 @@
# JobBlocklistEntry
## Properties
Name | Type | Description | Notes
------------ | ------------- | ------------- | -------------
**worker_id** | **str** | |
**task_type** | **str** | |
**worker_name** | **str** | | [optional]
**any string name** | **bool, date, datetime, dict, float, int, list, str, none_type** | any string name can be used but the value must be the correct type | [optional]
[[Back to Model list]](../README.md#documentation-for-models) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to README]](../README.md)
@@ -0,0 +1,13 @@
# JobDeletionInfo
Info about what will be deleted when this job is deleted.
## Properties
Name | Type | Description | Notes
------------ | ------------- | ------------- | -------------
**shaman_checkout** | **bool** | Whether the Shaman checkout directory will be removed along with the job. |
**any string name** | **bool, date, datetime, dict, float, int, list, str, none_type** | any string name can be used but the value must be the correct type | [optional]
[[Back to Model list]](../README.md#documentation-for-models) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to README]](../README.md)
@@ -0,0 +1,14 @@
# JobLastRenderedImageInfo
Enough information for a client to piece together different strings to form a host-relative URL to the last-rendered image. To construct the URL, concatenate \"{base}/{one of the suffixes}\".
## Properties
Name | Type | Description | Notes
------------ | ------------- | ------------- | -------------
**base** | **str** | |
**suffixes** | **[str]** | |
**any string name** | **bool, date, datetime, dict, float, int, list, str, none_type** | any string name can be used but the value must be the correct type | [optional]
[[Back to Model list]](../README.md#documentation-for-models) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to README]](../README.md)
@@ -0,0 +1,13 @@
# JobMassDeletionSelection
Parameters to describe which jobs should be deleted.
## Properties
Name | Type | Description | Notes
------------ | ------------- | ------------- | -------------
**last_updated_max** | **datetime** | All jobs that were last updated before or on this timestamp will be deleted. | [optional]
**any string name** | **bool, date, datetime, dict, float, int, list, str, none_type** | any string name can be used but the value must be the correct type | [optional]
[[Back to Model list]](../README.md#documentation-for-models) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to README]](../README.md)
@@ -0,0 +1,12 @@
# JobMetadata
Arbitrary metadata strings. More complex structures can be modeled by using `a.b.c` notation for the key.
## Properties
Name | Type | Description | Notes
------------ | ------------- | ------------- | -------------
**any string name** | **str** | any string name can be used but the value must be the correct type | [optional]
[[Back to Model list]](../README.md#documentation-for-models) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to README]](../README.md)
@@ -0,0 +1,12 @@
# JobPriorityChange
## Properties
Name | Type | Description | Notes
------------ | ------------- | ------------- | -------------
**priority** | **int** | |
**any string name** | **bool, date, datetime, dict, float, int, list, str, none_type** | any string name can be used but the value must be the correct type | [optional]
[[Back to Model list]](../README.md#documentation-for-models) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to README]](../README.md)
@@ -0,0 +1,11 @@
# JobSettings
## Properties
Name | Type | Description | Notes
------------ | ------------- | ------------- | -------------
**any string name** | **bool, date, datetime, dict, float, int, list, str, none_type** | any string name can be used but the value must be the correct type | [optional]
[[Back to Model list]](../README.md#documentation-for-models) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to README]](../README.md)
@@ -0,0 +1,11 @@
# JobStatus
## Properties
Name | Type | Description | Notes
------------ | ------------- | ------------- | -------------
**value** | **str** | | must be one of ["active", "canceled", "completed", "failed", "paused", "pause-requested", "queued", "cancel-requested", "requeueing", "under-construction", ]
[[Back to Model list]](../README.md#documentation-for-models) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to README]](../README.md)
@@ -0,0 +1,13 @@
# JobStatusChange
## Properties
Name | Type | Description | Notes
------------ | ------------- | ------------- | -------------
**status** | [**JobStatus**](JobStatus.md) | |
**reason** | **str** | The reason for this status change. |
**any string name** | **bool, date, datetime, dict, float, int, list, str, none_type** | any string name can be used but the value must be the correct type | [optional]
[[Back to Model list]](../README.md#documentation-for-models) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to README]](../README.md)
@@ -0,0 +1,13 @@
# JobStorageInfo
Storage info of a job, which Flamenco can use to remove job-related files when necessary.
## Properties
Name | Type | Description | Notes
------------ | ------------- | ------------- | -------------
**shaman_checkout_id** | **str** | &#39;Checkout ID&#39; used when creating the Shaman checkout for this job. Aids in removing the checkout directory when the job is removed from Flamenco. | [optional]
**any string name** | **bool, date, datetime, dict, float, int, list, str, none_type** | any string name can be used but the value must be the correct type | [optional]
[[Back to Model list]](../README.md#documentation-for-models) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to README]](../README.md)
@@ -0,0 +1,14 @@
# JobTagChange
New tag to assign to a job. Can be empty to remove the tag (and thus make the job available to all workers).
## Properties
Name | Type | Description | Notes
------------ | ------------- | ------------- | -------------
**id** | **str** | UUID of the tag. If this is given, &#39;name&#39; should not be given. | [optional]
**name** | **str** | Name of the tag. If this is given, &#39;id&#39; should not be given. | [optional]
**any string name** | **bool, date, datetime, dict, float, int, list, str, none_type** | any string name can be used but the value must be the correct type | [optional]
[[Back to Model list]](../README.md#documentation-for-models) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to README]](../README.md)
@@ -0,0 +1,13 @@
# JobTasksSummary
Simplified list of tasks of a job. Contains all tasks, but not all info of each task.
## Properties
Name | Type | Description | Notes
------------ | ------------- | ------------- | -------------
**tasks** | [**[TaskSummary]**](TaskSummary.md) | | [optional]
**any string name** | **bool, date, datetime, dict, float, int, list, str, none_type** | any string name can be used but the value must be the correct type | [optional]
[[Back to Model list]](../README.md#documentation-for-models) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to README]](../README.md)
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,17 @@
# JobsQuery
## Properties
Name | Type | Description | Notes
------------ | ------------- | ------------- | -------------
**offset** | **int** | | [optional]
**limit** | **int** | | [optional]
**order_by** | **[str]** | | [optional]
**status_in** | [**[JobStatus]**](JobStatus.md) | Return only jobs with a status in this array. | [optional]
**metadata** | **{str: (str,)}** | Filter by metadata, using &#x60;LIKE&#x60; notation. | [optional]
**settings** | **{str: (bool, date, datetime, dict, float, int, list, str, none_type)}** | Filter by job settings, using &#x60;LIKE&#x60; notation. | [optional]
**any string name** | **bool, date, datetime, dict, float, int, list, str, none_type** | any string name can be used but the value must be the correct type | [optional]
[[Back to Model list]](../README.md#documentation-for-models) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to README]](../README.md)
@@ -0,0 +1,12 @@
# JobsQueryResult
## Properties
Name | Type | Description | Notes
------------ | ------------- | ------------- | -------------
**jobs** | [**[Job]**](Job.md) | |
**any string name** | **bool, date, datetime, dict, float, int, list, str, none_type** | any string name can be used but the value must be the correct type | [optional]
[[Back to Model list]](../README.md#documentation-for-models) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to README]](../README.md)
@@ -0,0 +1,11 @@
# LifeCycleEventType
## Properties
Name | Type | Description | Notes
------------ | ------------- | ------------- | -------------
**value** | **str** | | must be one of ["manager-startup", "manager-shutdown", ]
[[Back to Model list]](../README.md#documentation-for-models) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to README]](../README.md)
@@ -0,0 +1,14 @@
# ManagerConfiguration
## Properties
Name | Type | Description | Notes
------------ | ------------- | ------------- | -------------
**storage_location** | **str** | Directory used for job file storage. |
**shaman_enabled** | **bool** | Whether the Shaman file transfer API is available. |
**is_first_run** | **bool** | Whether this is considered the first time the Manager runs. This is determined by a few factors, like a non-existent configuration file or certain settings being empty while they shouldn&#39;t be. |
**any string name** | **bool, date, datetime, dict, float, int, list, str, none_type** | any string name can be used but the value must be the correct type | [optional]
[[Back to Model list]](../README.md#documentation-for-models) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to README]](../README.md)
@@ -0,0 +1,13 @@
# ManagerVariable
## Properties
Name | Type | Description | Notes
------------ | ------------- | ------------- | -------------
**value** | **str** | |
**is_twoway** | **bool** | One-way variables are the most common one, and are simple replacement from &#x60;{name}&#x60; to their value, which happens when a Task is given to a Worker. Two-way variables are also replaced when submitting a job, where the platform-specific value is replaced by &#x60;{name}&#x60;. |
**any string name** | **bool, date, datetime, dict, float, int, list, str, none_type** | any string name can be used but the value must be the correct type | [optional]
[[Back to Model list]](../README.md#documentation-for-models) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to README]](../README.md)
@@ -0,0 +1,11 @@
# ManagerVariableAudience
## Properties
Name | Type | Description | Notes
------------ | ------------- | ------------- | -------------
**value** | **str** | | must be one of ["workers", "users", ]
[[Back to Model list]](../README.md#documentation-for-models) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to README]](../README.md)
@@ -0,0 +1,12 @@
# ManagerVariables
Mapping from variable name to its properties.
## Properties
Name | Type | Description | Notes
------------ | ------------- | ------------- | -------------
**any string name** | [**ManagerVariable**](ManagerVariable.md) | any string name can be used but the value must be the correct type | [optional]
[[Back to Model list]](../README.md#documentation-for-models) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to README]](../README.md)
@@ -0,0 +1,15 @@
# MayKeepRunning
Indicates whether the worker may keep running the task.
## Properties
Name | Type | Description | Notes
------------ | ------------- | ------------- | -------------
**may_keep_running** | **bool** | |
**reason** | **str** | |
**status_change_requested** | **bool** | Indicates that a status change requested for the worker. It should use the &#x60;workerState&#x60; operation to determine which state to go to next. If this is &#x60;true&#x60;, &#x60;mayKeepRunning&#x60; MUST be &#x60;false&#x60;. |
**any string name** | **bool, date, datetime, dict, float, int, list, str, none_type** | any string name can be used but the value must be the correct type | [optional]
[[Back to Model list]](../README.md#documentation-for-models) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to README]](../README.md)
@@ -0,0 +1,744 @@
# flamenco.manager.MetaApi
All URIs are relative to *http://localhost*
Method | HTTP request | Description
------------- | ------------- | -------------
[**check_blender_exe_path**](MetaApi.md#check_blender_exe_path) | **POST** /api/v3/configuration/check/blender | Validate a CLI command for use as way to start Blender
[**check_shared_storage_path**](MetaApi.md#check_shared_storage_path) | **POST** /api/v3/configuration/check/shared-storage | Validate a path for use as shared storage.
[**find_blender_exe_path**](MetaApi.md#find_blender_exe_path) | **GET** /api/v3/configuration/check/blender | Find one or more CLI commands for use as way to start Blender
[**get_configuration**](MetaApi.md#get_configuration) | **GET** /api/v3/configuration | Get the configuration of this Manager.
[**get_configuration_file**](MetaApi.md#get_configuration_file) | **GET** /api/v3/configuration/file | Retrieve the configuration of Flamenco Manager.
[**get_farm_status**](MetaApi.md#get_farm_status) | **GET** /api/v3/status | Get the status of this Flamenco farm.
[**get_shared_storage**](MetaApi.md#get_shared_storage) | **GET** /api/v3/configuration/shared-storage/{audience}/{platform} | Get the shared storage location of this Manager, adjusted for the given audience and platform.
[**get_variables**](MetaApi.md#get_variables) | **GET** /api/v3/configuration/variables/{audience}/{platform} | Get the variables of this Manager. Used by the Blender add-on to recognise two-way variables, and for the web interface to do variable replacement based on the browser&#39;s platform.
[**get_version**](MetaApi.md#get_version) | **GET** /api/v3/version | Get the Flamenco version of this Manager
[**save_setup_assistant_config**](MetaApi.md#save_setup_assistant_config) | **POST** /api/v3/configuration/setup-assistant | Update the Manager&#39;s configuration, and restart it in fully functional mode.
[**update_configuration_file**](MetaApi.md#update_configuration_file) | **PUT** /api/v3/configuration/file | Overwrites the configuration file. It does not actively reload the new configuration.
# **check_blender_exe_path**
> BlenderPathCheckResult check_blender_exe_path()
Validate a CLI command for use as way to start Blender
### Example
```python
import time
import flamenco.manager
from flamenco.manager.api import meta_api
from flamenco.manager.model.error import Error
from flamenco.manager.model.path_check_input import PathCheckInput
from flamenco.manager.model.blender_path_check_result import BlenderPathCheckResult
from pprint import pprint
# Defining the host is optional and defaults to http://localhost
# See configuration.py for a list of all supported configuration parameters.
configuration = flamenco.manager.Configuration(
host = "http://localhost"
)
# Enter a context with an instance of the API client
with flamenco.manager.ApiClient() as api_client:
# Create an instance of the API class
api_instance = meta_api.MetaApi(api_client)
path_check_input = PathCheckInput(
path="path_example",
) # PathCheckInput | Command or executable path to check (optional)
# example passing only required values which don't have defaults set
# and optional values
try:
# Validate a CLI command for use as way to start Blender
api_response = api_instance.check_blender_exe_path(path_check_input=path_check_input)
pprint(api_response)
except flamenco.manager.ApiException as e:
print("Exception when calling MetaApi->check_blender_exe_path: %s\n" % e)
```
### Parameters
Name | Type | Description | Notes
------------- | ------------- | ------------- | -------------
**path_check_input** | [**PathCheckInput**](PathCheckInput.md)| Command or executable path to check | [optional]
### Return type
[**BlenderPathCheckResult**](BlenderPathCheckResult.md)
### Authorization
No authorization required
### HTTP request headers
- **Content-Type**: application/json
- **Accept**: application/json
### HTTP response details
| Status code | Description | Response headers |
|-------------|-------------|------------------|
**200** | Normal response, path check went fine. | - |
**0** | Something went wrong. | - |
[[Back to top]](#) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to Model list]](../README.md#documentation-for-models) [[Back to README]](../README.md)
# **check_shared_storage_path**
> PathCheckResult check_shared_storage_path()
Validate a path for use as shared storage.
### Example
```python
import time
import flamenco.manager
from flamenco.manager.api import meta_api
from flamenco.manager.model.error import Error
from flamenco.manager.model.path_check_result import PathCheckResult
from flamenco.manager.model.path_check_input import PathCheckInput
from pprint import pprint
# Defining the host is optional and defaults to http://localhost
# See configuration.py for a list of all supported configuration parameters.
configuration = flamenco.manager.Configuration(
host = "http://localhost"
)
# Enter a context with an instance of the API client
with flamenco.manager.ApiClient() as api_client:
# Create an instance of the API class
api_instance = meta_api.MetaApi(api_client)
path_check_input = PathCheckInput(
path="path_example",
) # PathCheckInput | Path to check (optional)
# example passing only required values which don't have defaults set
# and optional values
try:
# Validate a path for use as shared storage.
api_response = api_instance.check_shared_storage_path(path_check_input=path_check_input)
pprint(api_response)
except flamenco.manager.ApiException as e:
print("Exception when calling MetaApi->check_shared_storage_path: %s\n" % e)
```
### Parameters
Name | Type | Description | Notes
------------- | ------------- | ------------- | -------------
**path_check_input** | [**PathCheckInput**](PathCheckInput.md)| Path to check | [optional]
### Return type
[**PathCheckResult**](PathCheckResult.md)
### Authorization
No authorization required
### HTTP request headers
- **Content-Type**: application/json
- **Accept**: application/json
### HTTP response details
| Status code | Description | Response headers |
|-------------|-------------|------------------|
**200** | Normal response, path check went fine. | - |
**0** | Something went wrong. | - |
[[Back to top]](#) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to Model list]](../README.md#documentation-for-models) [[Back to README]](../README.md)
# **find_blender_exe_path**
> BlenderPathFindResult find_blender_exe_path()
Find one or more CLI commands for use as way to start Blender
### Example
```python
import time
import flamenco.manager
from flamenco.manager.api import meta_api
from flamenco.manager.model.error import Error
from flamenco.manager.model.blender_path_find_result import BlenderPathFindResult
from pprint import pprint
# Defining the host is optional and defaults to http://localhost
# See configuration.py for a list of all supported configuration parameters.
configuration = flamenco.manager.Configuration(
host = "http://localhost"
)
# Enter a context with an instance of the API client
with flamenco.manager.ApiClient() as api_client:
# Create an instance of the API class
api_instance = meta_api.MetaApi(api_client)
# example, this endpoint has no required or optional parameters
try:
# Find one or more CLI commands for use as way to start Blender
api_response = api_instance.find_blender_exe_path()
pprint(api_response)
except flamenco.manager.ApiException as e:
print("Exception when calling MetaApi->find_blender_exe_path: %s\n" % e)
```
### Parameters
This endpoint does not need any parameter.
### Return type
[**BlenderPathFindResult**](BlenderPathFindResult.md)
### Authorization
No authorization required
### HTTP request headers
- **Content-Type**: Not defined
- **Accept**: application/json
### HTTP response details
| Status code | Description | Response headers |
|-------------|-------------|------------------|
**200** | Found locations of Blender. | - |
**0** | Something went wrong. | - |
[[Back to top]](#) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to Model list]](../README.md#documentation-for-models) [[Back to README]](../README.md)
# **get_configuration**
> ManagerConfiguration get_configuration()
Get the configuration of this Manager.
### Example
```python
import time
import flamenco.manager
from flamenco.manager.api import meta_api
from flamenco.manager.model.manager_configuration import ManagerConfiguration
from pprint import pprint
# Defining the host is optional and defaults to http://localhost
# See configuration.py for a list of all supported configuration parameters.
configuration = flamenco.manager.Configuration(
host = "http://localhost"
)
# Enter a context with an instance of the API client
with flamenco.manager.ApiClient() as api_client:
# Create an instance of the API class
api_instance = meta_api.MetaApi(api_client)
# example, this endpoint has no required or optional parameters
try:
# Get the configuration of this Manager.
api_response = api_instance.get_configuration()
pprint(api_response)
except flamenco.manager.ApiException as e:
print("Exception when calling MetaApi->get_configuration: %s\n" % e)
```
### Parameters
This endpoint does not need any parameter.
### Return type
[**ManagerConfiguration**](ManagerConfiguration.md)
### Authorization
No authorization required
### HTTP request headers
- **Content-Type**: Not defined
- **Accept**: application/json
### HTTP response details
| Status code | Description | Response headers |
|-------------|-------------|------------------|
**200** | normal response | - |
[[Back to top]](#) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to Model list]](../README.md#documentation-for-models) [[Back to README]](../README.md)
# **get_configuration_file**
> {str: (bool, date, datetime, dict, float, int, list, str, none_type)} get_configuration_file()
Retrieve the configuration of Flamenco Manager.
### Example
```python
import time
import flamenco.manager
from flamenco.manager.api import meta_api
from pprint import pprint
# Defining the host is optional and defaults to http://localhost
# See configuration.py for a list of all supported configuration parameters.
configuration = flamenco.manager.Configuration(
host = "http://localhost"
)
# Enter a context with an instance of the API client
with flamenco.manager.ApiClient() as api_client:
# Create an instance of the API class
api_instance = meta_api.MetaApi(api_client)
# example, this endpoint has no required or optional parameters
try:
# Retrieve the configuration of Flamenco Manager.
api_response = api_instance.get_configuration_file()
pprint(api_response)
except flamenco.manager.ApiException as e:
print("Exception when calling MetaApi->get_configuration_file: %s\n" % e)
```
### Parameters
This endpoint does not need any parameter.
### Return type
**{str: (bool, date, datetime, dict, float, int, list, str, none_type)}**
### Authorization
No authorization required
### HTTP request headers
- **Content-Type**: Not defined
- **Accept**: application/json, application/yaml
### HTTP response details
| Status code | Description | Response headers |
|-------------|-------------|------------------|
**200** | Normal response. | - |
[[Back to top]](#) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to Model list]](../README.md#documentation-for-models) [[Back to README]](../README.md)
# **get_farm_status**
> FarmStatusReport get_farm_status()
Get the status of this Flamenco farm.
### Example
```python
import time
import flamenco.manager
from flamenco.manager.api import meta_api
from flamenco.manager.model.farm_status_report import FarmStatusReport
from pprint import pprint
# Defining the host is optional and defaults to http://localhost
# See configuration.py for a list of all supported configuration parameters.
configuration = flamenco.manager.Configuration(
host = "http://localhost"
)
# Enter a context with an instance of the API client
with flamenco.manager.ApiClient() as api_client:
# Create an instance of the API class
api_instance = meta_api.MetaApi(api_client)
# example, this endpoint has no required or optional parameters
try:
# Get the status of this Flamenco farm.
api_response = api_instance.get_farm_status()
pprint(api_response)
except flamenco.manager.ApiException as e:
print("Exception when calling MetaApi->get_farm_status: %s\n" % e)
```
### Parameters
This endpoint does not need any parameter.
### Return type
[**FarmStatusReport**](FarmStatusReport.md)
### Authorization
No authorization required
### HTTP request headers
- **Content-Type**: Not defined
- **Accept**: application/json
### HTTP response details
| Status code | Description | Response headers |
|-------------|-------------|------------------|
**200** | normal response | - |
[[Back to top]](#) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to Model list]](../README.md#documentation-for-models) [[Back to README]](../README.md)
# **get_shared_storage**
> SharedStorageLocation get_shared_storage(audience, platform)
Get the shared storage location of this Manager, adjusted for the given audience and platform.
### Example
```python
import time
import flamenco.manager
from flamenco.manager.api import meta_api
from flamenco.manager.model.manager_variable_audience import ManagerVariableAudience
from flamenco.manager.model.shared_storage_location import SharedStorageLocation
from pprint import pprint
# Defining the host is optional and defaults to http://localhost
# See configuration.py for a list of all supported configuration parameters.
configuration = flamenco.manager.Configuration(
host = "http://localhost"
)
# Enter a context with an instance of the API client
with flamenco.manager.ApiClient() as api_client:
# Create an instance of the API class
api_instance = meta_api.MetaApi(api_client)
audience = ManagerVariableAudience("workers") # ManagerVariableAudience |
platform = "platform_example" # str |
# example passing only required values which don't have defaults set
try:
# Get the shared storage location of this Manager, adjusted for the given audience and platform.
api_response = api_instance.get_shared_storage(audience, platform)
pprint(api_response)
except flamenco.manager.ApiException as e:
print("Exception when calling MetaApi->get_shared_storage: %s\n" % e)
```
### Parameters
Name | Type | Description | Notes
------------- | ------------- | ------------- | -------------
**audience** | **ManagerVariableAudience**| |
**platform** | **str**| |
### Return type
[**SharedStorageLocation**](SharedStorageLocation.md)
### Authorization
No authorization required
### HTTP request headers
- **Content-Type**: Not defined
- **Accept**: application/json
### HTTP response details
| Status code | Description | Response headers |
|-------------|-------------|------------------|
**200** | Normal response. | - |
[[Back to top]](#) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to Model list]](../README.md#documentation-for-models) [[Back to README]](../README.md)
# **get_variables**
> ManagerVariables get_variables(audience, platform)
Get the variables of this Manager. Used by the Blender add-on to recognise two-way variables, and for the web interface to do variable replacement based on the browser's platform.
### Example
```python
import time
import flamenco.manager
from flamenco.manager.api import meta_api
from flamenco.manager.model.manager_variable_audience import ManagerVariableAudience
from flamenco.manager.model.manager_variables import ManagerVariables
from pprint import pprint
# Defining the host is optional and defaults to http://localhost
# See configuration.py for a list of all supported configuration parameters.
configuration = flamenco.manager.Configuration(
host = "http://localhost"
)
# Enter a context with an instance of the API client
with flamenco.manager.ApiClient() as api_client:
# Create an instance of the API class
api_instance = meta_api.MetaApi(api_client)
audience = ManagerVariableAudience("workers") # ManagerVariableAudience |
platform = "platform_example" # str |
# example passing only required values which don't have defaults set
try:
# Get the variables of this Manager. Used by the Blender add-on to recognise two-way variables, and for the web interface to do variable replacement based on the browser's platform.
api_response = api_instance.get_variables(audience, platform)
pprint(api_response)
except flamenco.manager.ApiException as e:
print("Exception when calling MetaApi->get_variables: %s\n" % e)
```
### Parameters
Name | Type | Description | Notes
------------- | ------------- | ------------- | -------------
**audience** | **ManagerVariableAudience**| |
**platform** | **str**| |
### Return type
[**ManagerVariables**](ManagerVariables.md)
### Authorization
No authorization required
### HTTP request headers
- **Content-Type**: Not defined
- **Accept**: application/json
### HTTP response details
| Status code | Description | Response headers |
|-------------|-------------|------------------|
**200** | Normal response. | - |
[[Back to top]](#) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to Model list]](../README.md#documentation-for-models) [[Back to README]](../README.md)
# **get_version**
> FlamencoVersion get_version()
Get the Flamenco version of this Manager
### Example
```python
import time
import flamenco.manager
from flamenco.manager.api import meta_api
from flamenco.manager.model.flamenco_version import FlamencoVersion
from pprint import pprint
# Defining the host is optional and defaults to http://localhost
# See configuration.py for a list of all supported configuration parameters.
configuration = flamenco.manager.Configuration(
host = "http://localhost"
)
# Enter a context with an instance of the API client
with flamenco.manager.ApiClient() as api_client:
# Create an instance of the API class
api_instance = meta_api.MetaApi(api_client)
# example, this endpoint has no required or optional parameters
try:
# Get the Flamenco version of this Manager
api_response = api_instance.get_version()
pprint(api_response)
except flamenco.manager.ApiException as e:
print("Exception when calling MetaApi->get_version: %s\n" % e)
```
### Parameters
This endpoint does not need any parameter.
### Return type
[**FlamencoVersion**](FlamencoVersion.md)
### Authorization
No authorization required
### HTTP request headers
- **Content-Type**: Not defined
- **Accept**: application/json
### HTTP response details
| Status code | Description | Response headers |
|-------------|-------------|------------------|
**200** | normal response | - |
[[Back to top]](#) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to Model list]](../README.md#documentation-for-models) [[Back to README]](../README.md)
# **save_setup_assistant_config**
> save_setup_assistant_config()
Update the Manager's configuration, and restart it in fully functional mode.
### Example
```python
import time
import flamenco.manager
from flamenco.manager.api import meta_api
from flamenco.manager.model.error import Error
from flamenco.manager.model.setup_assistant_config import SetupAssistantConfig
from pprint import pprint
# Defining the host is optional and defaults to http://localhost
# See configuration.py for a list of all supported configuration parameters.
configuration = flamenco.manager.Configuration(
host = "http://localhost"
)
# Enter a context with an instance of the API client
with flamenco.manager.ApiClient() as api_client:
# Create an instance of the API class
api_instance = meta_api.MetaApi(api_client)
setup_assistant_config = SetupAssistantConfig(
storage_location="storage_location_example",
blender_executable=BlenderPathCheckResult(
input="input_example",
path="path_example",
source=BlenderPathSource("file_association"),
is_usable=True,
cause="cause_example",
),
) # SetupAssistantConfig | Configuration to save. (optional)
# example passing only required values which don't have defaults set
# and optional values
try:
# Update the Manager's configuration, and restart it in fully functional mode.
api_instance.save_setup_assistant_config(setup_assistant_config=setup_assistant_config)
except flamenco.manager.ApiException as e:
print("Exception when calling MetaApi->save_setup_assistant_config: %s\n" % e)
```
### Parameters
Name | Type | Description | Notes
------------- | ------------- | ------------- | -------------
**setup_assistant_config** | [**SetupAssistantConfig**](SetupAssistantConfig.md)| Configuration to save. | [optional]
### Return type
void (empty response body)
### Authorization
No authorization required
### HTTP request headers
- **Content-Type**: application/json
- **Accept**: application/json
### HTTP response details
| Status code | Description | Response headers |
|-------------|-------------|------------------|
**204** | Normal response. The webapp should do a full refresh at this point. | - |
**0** | Something went wrong. | - |
[[Back to top]](#) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to Model list]](../README.md#documentation-for-models) [[Back to README]](../README.md)
# **update_configuration_file**
> update_configuration_file(body)
Overwrites the configuration file. It does not actively reload the new configuration.
### Example
```python
import time
import flamenco.manager
from flamenco.manager.api import meta_api
from flamenco.manager.model.error import Error
from pprint import pprint
# Defining the host is optional and defaults to http://localhost
# See configuration.py for a list of all supported configuration parameters.
configuration = flamenco.manager.Configuration(
host = "http://localhost"
)
# Enter a context with an instance of the API client
with flamenco.manager.ApiClient() as api_client:
# Create an instance of the API class
api_instance = meta_api.MetaApi(api_client)
body = {} # {str: (bool, date, datetime, dict, float, int, list, str, none_type)} | The configuration values as a JSON
# example passing only required values which don't have defaults set
try:
# Overwrites the configuration file. It does not actively reload the new configuration.
api_instance.update_configuration_file(body)
except flamenco.manager.ApiException as e:
print("Exception when calling MetaApi->update_configuration_file: %s\n" % e)
```
### Parameters
Name | Type | Description | Notes
------------- | ------------- | ------------- | -------------
**body** | **{str: (bool, date, datetime, dict, float, int, list, str, none_type)}**| The configuration values as a JSON |
### Return type
void (empty response body)
### Authorization
No authorization required
### HTTP request headers
- **Content-Type**: application/json
- **Accept**: application/json
### HTTP response details
| Status code | Description | Response headers |
|-------------|-------------|------------------|
**204** | The configuration was successfully stored to flamenco-manager.yaml | - |
**0** | Unable to save the configuration file | - |
[[Back to top]](#) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to Model list]](../README.md#documentation-for-models) [[Back to README]](../README.md)
@@ -0,0 +1,12 @@
# PathCheckInput
## Properties
Name | Type | Description | Notes
------------ | ------------- | ------------- | -------------
**path** | **str** | |
**any string name** | **bool, date, datetime, dict, float, int, list, str, none_type** | any string name can be used but the value must be the correct type | [optional]
[[Back to Model list]](../README.md#documentation-for-models) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to README]](../README.md)
@@ -0,0 +1,14 @@
# PathCheckResult
## Properties
Name | Type | Description | Notes
------------ | ------------- | ------------- | -------------
**path** | **str** | The path that was checked. |
**is_usable** | **bool** | Whether the path is usable or not. |
**cause** | **str** | Description of why this path is (not) usable. |
**any string name** | **bool, date, datetime, dict, float, int, list, str, none_type** | any string name can be used but the value must be the correct type | [optional]
[[Back to Model list]](../README.md#documentation-for-models) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to README]](../README.md)
@@ -0,0 +1,18 @@
# RegisteredWorker
## Properties
Name | Type | Description | Notes
------------ | ------------- | ------------- | -------------
**uuid** | **str** | |
**name** | **str** | |
**address** | **str** | |
**status** | [**WorkerStatus**](WorkerStatus.md) | |
**platform** | **str** | |
**software** | **str** | |
**supported_task_types** | **[str]** | |
**any string name** | **bool, date, datetime, dict, float, int, list, str, none_type** | any string name can be used but the value must be the correct type | [optional]
[[Back to Model list]](../README.md#documentation-for-models) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to README]](../README.md)
@@ -0,0 +1,12 @@
# SecurityError
## Properties
Name | Type | Description | Notes
------------ | ------------- | ------------- | -------------
**message** | **str** | |
**any string name** | **bool, date, datetime, dict, float, int, list, str, none_type** | any string name can be used but the value must be the correct type | [optional]
[[Back to Model list]](../README.md#documentation-for-models) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to README]](../README.md)
@@ -0,0 +1,14 @@
# SetupAssistantConfig
Configuration obtained from the Setup Assistant.
## Properties
Name | Type | Description | Notes
------------ | ------------- | ------------- | -------------
**storage_location** | **str** | Directory used for job file storage. |
**blender_executable** | [**BlenderPathCheckResult**](BlenderPathCheckResult.md) | |
**any string name** | **bool, date, datetime, dict, float, int, list, str, none_type** | any string name can be used but the value must be the correct type | [optional]
[[Back to Model list]](../README.md#documentation-for-models) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to README]](../README.md)
@@ -0,0 +1,320 @@
# flamenco.manager.ShamanApi
All URIs are relative to *http://localhost*
Method | HTTP request | Description
------------- | ------------- | -------------
[**shaman_checkout**](ShamanApi.md#shaman_checkout) | **POST** /api/v3/shaman/checkout/create | Create a directory, and symlink the required files into it. The files must all have been uploaded to Shaman before calling this endpoint.
[**shaman_checkout_requirements**](ShamanApi.md#shaman_checkout_requirements) | **POST** /api/v3/shaman/checkout/requirements | Checks a Shaman Requirements file, and reports which files are unknown.
[**shaman_file_store**](ShamanApi.md#shaman_file_store) | **POST** /api/v3/shaman/files/{checksum}/{filesize} | Store a new file on the Shaman server. Note that the Shaman server can forcibly close the HTTP connection when another client finishes uploading the exact same file, to prevent double uploads. The file&#39;s contents should be sent in the request body.
[**shaman_file_store_check**](ShamanApi.md#shaman_file_store_check) | **GET** /api/v3/shaman/files/{checksum}/{filesize} | Check the status of a file on the Shaman server.
# **shaman_checkout**
> ShamanCheckoutResult shaman_checkout(shaman_checkout)
Create a directory, and symlink the required files into it. The files must all have been uploaded to Shaman before calling this endpoint.
### Example
```python
import time
import flamenco.manager
from flamenco.manager.api import shaman_api
from flamenco.manager.model.error import Error
from flamenco.manager.model.shaman_checkout import ShamanCheckout
from flamenco.manager.model.shaman_checkout_result import ShamanCheckoutResult
from pprint import pprint
# Defining the host is optional and defaults to http://localhost
# See configuration.py for a list of all supported configuration parameters.
configuration = flamenco.manager.Configuration(
host = "http://localhost"
)
# Enter a context with an instance of the API client
with flamenco.manager.ApiClient() as api_client:
# Create an instance of the API class
api_instance = shaman_api.ShamanApi(api_client)
shaman_checkout = ShamanCheckout(
files=[
ShamanFileSpec(
sha="sha_example",
size=1,
path="path_example",
),
],
checkout_path="checkout_path_example",
) # ShamanCheckout | Set of files to check out.
# example passing only required values which don't have defaults set
try:
# Create a directory, and symlink the required files into it. The files must all have been uploaded to Shaman before calling this endpoint.
api_response = api_instance.shaman_checkout(shaman_checkout)
pprint(api_response)
except flamenco.manager.ApiException as e:
print("Exception when calling ShamanApi->shaman_checkout: %s\n" % e)
```
### Parameters
Name | Type | Description | Notes
------------- | ------------- | ------------- | -------------
**shaman_checkout** | [**ShamanCheckout**](ShamanCheckout.md)| Set of files to check out. |
### Return type
[**ShamanCheckoutResult**](ShamanCheckoutResult.md)
### Authorization
No authorization required
### HTTP request headers
- **Content-Type**: application/json
- **Accept**: application/json
### HTTP response details
| Status code | Description | Response headers |
|-------------|-------------|------------------|
**200** | Checkout was created successfully. | - |
**424** | There were files missing. Use &#x60;shamanCheckoutRequirements&#x60; to figure out which ones. | - |
**409** | Checkout already exists. | - |
**0** | unexpected error | - |
[[Back to top]](#) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to Model list]](../README.md#documentation-for-models) [[Back to README]](../README.md)
# **shaman_checkout_requirements**
> ShamanRequirementsResponse shaman_checkout_requirements(shaman_requirements_request)
Checks a Shaman Requirements file, and reports which files are unknown.
### Example
```python
import time
import flamenco.manager
from flamenco.manager.api import shaman_api
from flamenco.manager.model.shaman_requirements_request import ShamanRequirementsRequest
from flamenco.manager.model.error import Error
from flamenco.manager.model.shaman_requirements_response import ShamanRequirementsResponse
from pprint import pprint
# Defining the host is optional and defaults to http://localhost
# See configuration.py for a list of all supported configuration parameters.
configuration = flamenco.manager.Configuration(
host = "http://localhost"
)
# Enter a context with an instance of the API client
with flamenco.manager.ApiClient() as api_client:
# Create an instance of the API class
api_instance = shaman_api.ShamanApi(api_client)
shaman_requirements_request = ShamanRequirementsRequest(
files=[
ShamanFileSpec(
sha="sha_example",
size=1,
path="path_example",
),
],
) # ShamanRequirementsRequest | Set of files to check
# example passing only required values which don't have defaults set
try:
# Checks a Shaman Requirements file, and reports which files are unknown.
api_response = api_instance.shaman_checkout_requirements(shaman_requirements_request)
pprint(api_response)
except flamenco.manager.ApiException as e:
print("Exception when calling ShamanApi->shaman_checkout_requirements: %s\n" % e)
```
### Parameters
Name | Type | Description | Notes
------------- | ------------- | ------------- | -------------
**shaman_requirements_request** | [**ShamanRequirementsRequest**](ShamanRequirementsRequest.md)| Set of files to check |
### Return type
[**ShamanRequirementsResponse**](ShamanRequirementsResponse.md)
### Authorization
No authorization required
### HTTP request headers
- **Content-Type**: application/json
- **Accept**: application/json
### HTTP response details
| Status code | Description | Response headers |
|-------------|-------------|------------------|
**200** | Subset of the posted requirements, indicating the unknown files. | - |
**0** | unexpected error | - |
[[Back to top]](#) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to Model list]](../README.md#documentation-for-models) [[Back to README]](../README.md)
# **shaman_file_store**
> shaman_file_store(checksum, filesize, body)
Store a new file on the Shaman server. Note that the Shaman server can forcibly close the HTTP connection when another client finishes uploading the exact same file, to prevent double uploads. The file's contents should be sent in the request body.
### Example
```python
import time
import flamenco.manager
from flamenco.manager.api import shaman_api
from flamenco.manager.model.error import Error
from pprint import pprint
# Defining the host is optional and defaults to http://localhost
# See configuration.py for a list of all supported configuration parameters.
configuration = flamenco.manager.Configuration(
host = "http://localhost"
)
# Enter a context with an instance of the API client
with flamenco.manager.ApiClient() as api_client:
# Create an instance of the API class
api_instance = shaman_api.ShamanApi(api_client)
checksum = "checksum_example" # str | SHA256 checksum of the file.
filesize = 1 # int | Size of the file in bytes.
body = open('/path/to/file', 'rb') # file_type | Contents of the file
x_shaman_can_defer_upload = True # bool | The client indicates that it can defer uploading this file. The \"208\" response will not only be returned when the file is already fully known to the Shaman server, but also when someone else is currently uploading this file. (optional)
x_shaman_original_filename = "X-Shaman-Original-Filename_example" # str | The original filename. If sent along with the request, it will be included in the server logs, which can aid in debugging. MUST either be ASCII or encoded using RFC 2047 (aka MIME encoding). In the latter case the encoding MUST be UTF-8. (optional)
# example passing only required values which don't have defaults set
try:
# Store a new file on the Shaman server. Note that the Shaman server can forcibly close the HTTP connection when another client finishes uploading the exact same file, to prevent double uploads. The file's contents should be sent in the request body.
api_instance.shaman_file_store(checksum, filesize, body)
except flamenco.manager.ApiException as e:
print("Exception when calling ShamanApi->shaman_file_store: %s\n" % e)
# example passing only required values which don't have defaults set
# and optional values
try:
# Store a new file on the Shaman server. Note that the Shaman server can forcibly close the HTTP connection when another client finishes uploading the exact same file, to prevent double uploads. The file's contents should be sent in the request body.
api_instance.shaman_file_store(checksum, filesize, body, x_shaman_can_defer_upload=x_shaman_can_defer_upload, x_shaman_original_filename=x_shaman_original_filename)
except flamenco.manager.ApiException as e:
print("Exception when calling ShamanApi->shaman_file_store: %s\n" % e)
```
### Parameters
Name | Type | Description | Notes
------------- | ------------- | ------------- | -------------
**checksum** | **str**| SHA256 checksum of the file. |
**filesize** | **int**| Size of the file in bytes. |
**body** | **file_type**| Contents of the file |
**x_shaman_can_defer_upload** | **bool**| The client indicates that it can defer uploading this file. The \&quot;208\&quot; response will not only be returned when the file is already fully known to the Shaman server, but also when someone else is currently uploading this file. | [optional]
**x_shaman_original_filename** | **str**| The original filename. If sent along with the request, it will be included in the server logs, which can aid in debugging. MUST either be ASCII or encoded using RFC 2047 (aka MIME encoding). In the latter case the encoding MUST be UTF-8. | [optional]
### Return type
void (empty response body)
### Authorization
No authorization required
### HTTP request headers
- **Content-Type**: application/octet-stream
- **Accept**: application/json
### HTTP response details
| Status code | Description | Response headers |
|-------------|-------------|------------------|
**204** | The file was accepted. | - |
**208** | The file was already known to the server. | - |
**417** | There was a mismatch between the request parameters and the actual file size or checksum of the uploaded file. | - |
**425** | Client should defer uploading this file. The file is currently in the process of being uploaded by someone else, and &#x60;X-Shaman-Can-Defer-Upload: true&#x60; was sent in the request. | - |
**0** | unexpected error | - |
[[Back to top]](#) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to Model list]](../README.md#documentation-for-models) [[Back to README]](../README.md)
# **shaman_file_store_check**
> ShamanSingleFileStatus shaman_file_store_check(checksum, filesize)
Check the status of a file on the Shaman server.
### Example
```python
import time
import flamenco.manager
from flamenco.manager.api import shaman_api
from flamenco.manager.model.error import Error
from flamenco.manager.model.shaman_single_file_status import ShamanSingleFileStatus
from pprint import pprint
# Defining the host is optional and defaults to http://localhost
# See configuration.py for a list of all supported configuration parameters.
configuration = flamenco.manager.Configuration(
host = "http://localhost"
)
# Enter a context with an instance of the API client
with flamenco.manager.ApiClient() as api_client:
# Create an instance of the API class
api_instance = shaman_api.ShamanApi(api_client)
checksum = "checksum_example" # str | SHA256 checksum of the file.
filesize = 1 # int | Size of the file in bytes.
# example passing only required values which don't have defaults set
try:
# Check the status of a file on the Shaman server.
api_response = api_instance.shaman_file_store_check(checksum, filesize)
pprint(api_response)
except flamenco.manager.ApiException as e:
print("Exception when calling ShamanApi->shaman_file_store_check: %s\n" % e)
```
### Parameters
Name | Type | Description | Notes
------------- | ------------- | ------------- | -------------
**checksum** | **str**| SHA256 checksum of the file. |
**filesize** | **int**| Size of the file in bytes. |
### Return type
[**ShamanSingleFileStatus**](ShamanSingleFileStatus.md)
### Authorization
No authorization required
### HTTP request headers
- **Content-Type**: Not defined
- **Accept**: application/json
### HTTP response details
| Status code | Description | Response headers |
|-------------|-------------|------------------|
**200** | Normal response. | - |
**0** | unexpected error | - |
[[Back to top]](#) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to Model list]](../README.md#documentation-for-models) [[Back to README]](../README.md)
@@ -0,0 +1,14 @@
# ShamanCheckout
Set of files with their SHA256 checksum, size in bytes, and desired location in the checkout directory.
## Properties
Name | Type | Description | Notes
------------ | ------------- | ------------- | -------------
**files** | [**[ShamanFileSpec]**](ShamanFileSpec.md) | |
**checkout_path** | **str** | Path where the Manager should create this checkout. It is relative to the Shaman checkout path as configured on the Manager. In older versions of the Shaman this was just the \&quot;checkout ID\&quot;, but in this version it can be a path like &#x60;project-slug/scene-name/unique-ID&#x60;. |
**any string name** | **bool, date, datetime, dict, float, int, list, str, none_type** | any string name can be used but the value must be the correct type | [optional]
[[Back to Model list]](../README.md#documentation-for-models) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to README]](../README.md)
@@ -0,0 +1,13 @@
# ShamanCheckoutResult
The result of a Shaman checkout.
## Properties
Name | Type | Description | Notes
------------ | ------------- | ------------- | -------------
**checkout_path** | **str** | Path where the Manager created this checkout. This can be different than what was requested, as the Manager will ensure a unique directory. The path is relative to the Shaman checkout path as configured on the Manager. |
**any string name** | **bool, date, datetime, dict, float, int, list, str, none_type** | any string name can be used but the value must be the correct type | [optional]
[[Back to Model list]](../README.md#documentation-for-models) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to README]](../README.md)
@@ -0,0 +1,15 @@
# ShamanFileSpec
Specification of a file in the Shaman storage.
## Properties
Name | Type | Description | Notes
------------ | ------------- | ------------- | -------------
**sha** | **str** | SHA256 checksum of the file |
**size** | **int** | File size in bytes |
**path** | **str** | Location of the file in the checkout |
**any string name** | **bool, date, datetime, dict, float, int, list, str, none_type** | any string name can be used but the value must be the correct type | [optional]
[[Back to Model list]](../README.md#documentation-for-models) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to README]](../README.md)
@@ -0,0 +1,16 @@
# ShamanFileSpecWithStatus
Specification of a file, which could be in the Shaman storage, or not, depending on its status.
## Properties
Name | Type | Description | Notes
------------ | ------------- | ------------- | -------------
**sha** | **str** | SHA256 checksum of the file |
**size** | **int** | File size in bytes |
**path** | **str** | Location of the file in the checkout |
**status** | [**ShamanFileStatus**](ShamanFileStatus.md) | |
**any string name** | **bool, date, datetime, dict, float, int, list, str, none_type** | any string name can be used but the value must be the correct type | [optional]
[[Back to Model list]](../README.md#documentation-for-models) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to README]](../README.md)
@@ -0,0 +1,11 @@
# ShamanFileStatus
## Properties
Name | Type | Description | Notes
------------ | ------------- | ------------- | -------------
**value** | **str** | | must be one of ["unknown", "uploading", "stored", ]
[[Back to Model list]](../README.md#documentation-for-models) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to README]](../README.md)
@@ -0,0 +1,13 @@
# ShamanRequirementsRequest
Set of files with their SHA256 checksum and size in bytes.
## Properties
Name | Type | Description | Notes
------------ | ------------- | ------------- | -------------
**files** | [**[ShamanFileSpec]**](ShamanFileSpec.md) | |
**any string name** | **bool, date, datetime, dict, float, int, list, str, none_type** | any string name can be used but the value must be the correct type | [optional]
[[Back to Model list]](../README.md#documentation-for-models) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to README]](../README.md)
@@ -0,0 +1,13 @@
# ShamanRequirementsResponse
The files from a requirements request, with their status on the Shaman server. Files that are known to Shaman are excluded from the response.
## Properties
Name | Type | Description | Notes
------------ | ------------- | ------------- | -------------
**files** | [**[ShamanFileSpecWithStatus]**](ShamanFileSpecWithStatus.md) | |
**any string name** | **bool, date, datetime, dict, float, int, list, str, none_type** | any string name can be used but the value must be the correct type | [optional]
[[Back to Model list]](../README.md#documentation-for-models) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to README]](../README.md)
@@ -0,0 +1,13 @@
# ShamanSingleFileStatus
Status of a file in the Shaman storage.
## Properties
Name | Type | Description | Notes
------------ | ------------- | ------------- | -------------
**status** | [**ShamanFileStatus**](ShamanFileStatus.md) | |
**any string name** | **bool, date, datetime, dict, float, int, list, str, none_type** | any string name can be used but the value must be the correct type | [optional]
[[Back to Model list]](../README.md#documentation-for-models) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to README]](../README.md)
@@ -0,0 +1,16 @@
# SharedStorageLocation
Location of the shared storage, adjusted for a specific audience & platform. This uses two-way variables to adjust the shared storage path from the Manager's configuration.
## Properties
Name | Type | Description | Notes
------------ | ------------- | ------------- | -------------
**location** | **str** | |
**audience** | [**ManagerVariableAudience**](ManagerVariableAudience.md) | |
**platform** | **str** | |
**shaman_enabled** | **bool** | Whether the Shaman file transfer API is available. |
**any string name** | **bool, date, datetime, dict, float, int, list, str, none_type** | any string name can be used but the value must be the correct type | [optional]
[[Back to Model list]](../README.md#documentation-for-models) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to README]](../README.md)
@@ -0,0 +1,15 @@
# SocketIOSubscription
Send by SocketIO clients as `/subscription` event type, to manage their subscription to job updates. Clients always get job updates, but for task updates or task logs they need to explicitly subscribe. For simplicity, clients can only subscribe to one job (to get task updates for that job) and one task's log at a time. This is not used by MQTT, as with that protocol the subscriptions are managed by the MQTT broker, not Flamenco.
## Properties
Name | Type | Description | Notes
------------ | ------------- | ------------- | -------------
**op** | [**SocketIOSubscriptionOperation**](SocketIOSubscriptionOperation.md) | |
**type** | [**SocketIOSubscriptionType**](SocketIOSubscriptionType.md) | |
**uuid** | **str** | UUID of the thing to subscribe to / unsubscribe from. | [optional]
**any string name** | **bool, date, datetime, dict, float, int, list, str, none_type** | any string name can be used but the value must be the correct type | [optional]
[[Back to Model list]](../README.md#documentation-for-models) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to README]](../README.md)

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