def set_bone_groups(): if do_mch: bg = Get.bone_group(rig, "BBone FK") if not bg: bg = New.bone_group(rig, "BBone FK", True) bone_mch.bone_group = bg if do_start_end: bg = Get.bone_group(rig, "BBone Stretch") if not bg: bg = New.bone_group(rig, "BBone Stretch", True) bone_start.bone_group = bone_end.bone_group = bg for name in self.center_bones[rig]: rig.pose.bones[name].bone_group = bg if do_in_out: bg = Get.bone_group(rig, "BBone Curve") if not bg: bg = New.bone_group(rig, "BBone Curve", True) bone_in.bone_group = bone_out.bone_group = bg if self.hide_bones.get(rig): bg = Get.bone_group(rig, "BBone Stretch [Hidden]") if not bg: bg = New.bone_group(rig, "BBone Stretch [Hidden]", 'THEME20') for name in self.hide_bones[rig]: rig.pose.bones[name].bone_group = bg
class stretch: head = New.bone(context, rig, name="IK-Stretch-" + fk.head.name) tail = New.bone(context, rig, name="IK-Stretch-" + fk.tail.name) bone = New.bone(context, rig, name="IK-Stretch-" + fk.bone.name)
def add_driver(pbone, path, transform_type, name="var", **kargs): return New.driver(bone, path, target=pbone, transform_type=transform_type, name=name, **kargs)
def execute(self, context): # active = Get.active(context) mode = context.mode # pose = list() for rig in context.selected_objects: if (not Is.armature(rig)) or (rig.data.get('rig_id') is None): continue meta = New.object(context, name="metarig", data=rig.data.copy()) meta.data.animation_data_clear() metafy_rigify(context, meta) # pose.append(meta, rig) else: if context.mode != mode: bpy.ops.object.mode_set(mode=mode) # if mode == 'POSE': # Set.mode(context, 'OBJECT') # for (meta, rig) in pose: # Set.select(rig, True) # Set.select(meta, False) # if meta == active: # Set.active(context, rig) # if mode == 'POSE': # Set.mode(context, 'POSE') return {'FINISHED'}
def execute(self, context): if self.bones: bones = eval(self.bones) else: bones = Get.selected_pose_bones(context) if not bones: return {'CANCELLED'} wgts = readWidgets() if self.widget: widget = wgts[self.widget] else: widget = wgts[context.scene.widget_list] for bone in bones: if bone.id_data.proxy: continue slide = list(self.slide) rotate = list(self.rotate) if self.mirror: mirror = findMirrorObject(bone) if mirror and mirror in bones and bone.name.endswith( ('L', 'l')): slide[0] *= -1 rotate[1] *= -1 rotate[2] *= -1 createWidget( context, bone, widget, self.relative_size, self.global_size, [*self.scale], slide, rotate, get_collection(context), ) utils.update(context) pr = utils.prefs(__addon__).bone_widgets # pr = prefs.prefs().bone_widgets if pr.keep_settings: pr.slide = self.slide pr.rotate = self.rotate pr.relative_size = self.relative_size pr.global_size = self.global_size pr.scale = self.scale pr.mirror = self.mirror # Create new object then delete it. # When creating multiple widgets, if the last one tries to enter edit # mode before a particular update occurs, Blender will crash. # If the last object created (I.E. this empty) is not the widget, # Blender can enter edit mode without crash Get.objects(context, link=True).unlink(New.object(context)) return {'FINISHED'}
def empty(context, name="Empty", type='PLAIN_AXES', size=1.0, link=True): "Create a new empty object" empty = New.object(context, name, None, link) Set.empty_type(empty, type) Set.empty_size(empty, size) return empty
def camera(context, name="Camera", size=1.0, link=True): "Create a new camera object" data = bpy.data.cameras.new(name) obj = New.object(context, name=name, data=data, link=link) data.display_size = size return obj
def armature(context, name="Armature", display_type=None, link=True): "Create and return a rig object" data = bpy.data.armatures.new(name) # armature.show_in_front = True if display_type is not None: Set.armature_display_type(data, display_type) return New.object(context, name, data, link)
def execute(self, context): area = context.area view = area.spaces.active reg = view.region_3d # Create new camera object, add to scene, and select it camo = New.camera(context, name="Viewport", size=0.5) cam = camo.data Set.active(context, camo) # Set.active_select(camo) # Set Lock viewport to the camera view.camera = camo view.use_local_camera = True view.lock_camera = True # Send camera object to the current viewport rotation/location Set.matrix(camo, reg.view_matrix.inverted()) # Switch to camera view if reg.view_perspective != 'CAMERA': bpy.ops.view3d.view_camera() # run op to prevent changing the (actual) viewport's position # reg.view_perspective = 'CAMERA' # Mirror viewport properties to the camera cam.lens = view.lens / 2 cam.clip_start = view.clip_start cam.clip_end = view.clip_end # Re-center the camera view window if it was changed bpy.ops.view3d.view_center_camera() # reg.view_camera_offset = [0.0, 0.0] # reg.view_camera_zoom = 28 # Setup Deph of Field cam.dof.use_dof = True # cam.dof.aperture_fstop = 0.5 # Blur amount # cam.dof.aperture_fstop = 2.8 # cam.dof.focus_distance = reg.view_distance bpy.ops.ui.eyedropper_depth('INVOKE_DEFAULT') return {'FINISHED'}
def curve(context, name="Spline IK", link=True): "Create a curve object" # Create curve data = bpy.data.curves.new(name, 'CURVE') # Setup curve's display settings data.dimensions = '3D' data.fill_mode = 'FULL' data.bevel_depth = 0.01 curve = New.object(context, name, data, link) if hasattr(curve, 'display_type'): # 2.8 curve.display_type = 'WIRE' elif hasattr(curve, 'draw_type'): # 2.7 curve.draw_type = 'WIRE' return curve
def bone_group(bone, group, color=None): """ Assign bone to group\\ if group is text, find the group or create it first """ if Is.string(group): bgs = bone.id_data.pose.bone_groups if group in bgs: group = bgs[group] else: from zpy import New group = New.bone_group(bone, name=group) if Is.string(color): group.color_set = color # DEFAULT = regular bone color (not unique) # THEME01 - THEME15 = Builtin color palettes # THEME16 - THEME20 = Black for user assigned templates # CUSTOM = manually set elif color is False: group.color_set = 'DEFAULT' elif color is True: from random import randrange as random rand = f"{random(1, 15):02}" group.color_set = f'THEME{rand}' elif color: group.color_set = 'CUSTOM' gc = group.colors if not Is.iterable(color): if Is.digit(color): color = [[color] * 3] * 3 else: color = [color] * 3 (gc.normal, gc.select, gc.active) = color bone.bone_group = group return group
def execute(self, context): an = context.space.grease_pencil # context.space.grease_pencil = ob = New.object(context, name=an.name, data=an) ob.location.z = 0.5 ob.rotation_mode = 'XYZ' ob.rotation_euler.x = 1.5707963705062866 # Face front ob.scale = (0.001, ) * 3 an.stroke_thickness_space = 'SCREENSPACE' # Send the color from annotations to the grease pencil object for layer in an.layers: layer.tint_color = layer.channel_color layer.tint_factor = 1 """ In addition to not "currently" knowing where individual stroke colors are Setting them will take longer and override any manual colors I may use. Setting the tint color (which annotations can only used) is faster and easier """ return {'FINISHED'}
def manual(context, src, path, **kargs): "Insert a keyframe manually. Sub is for sub targets, like constraints" # kargs: # sub=None, index=-1, frame=None, value=None, # group=None, insert_key=None, action=None, context=None insert_key = kargs.get('insert_key', None) sub = kargs.get('sub', None) index = kargs.get('index', -1) frame = kargs.get('frame', None) value = kargs.get('value', None) group = kargs.get('group', None) action = kargs.get('action', None) results = kargs.get('results', list()) options = kargs.get('options', set()) delete = kargs.get('delete', False) keyframe_type = kargs.get('type', context.scene.tool_settings.keyframe_type) if not keyframe.poll_insert(context, insert_key, src=src): return results # if frame is None: # frame = context.scene.frame_current # if group is None: # group = keyframe.group_name(src) # src.keyframe_insert(path, index=index, frame=frame, group=group) # return if group is None: group = keyframe.group_name(src) obj = src.id_data anim = obj.animation_data_create() if action is None: key_in_action = True action = anim.action if action is None: action = anim.action = bpy.data.actions.new("Action") else: # When using a specified action, don't insert # keyframes later to determine the property's value key_in_action = False if frame is None: frame = context.scene.frame_current_final strip = Get.active_strip(anim) if anim.use_tweak_mode else None if strip: if not (strip.frame_start <= frame <= strip.frame_end): # frame outside of the strip's bounds key_in_action = False tweak_frame = Get.frame_to_strip(context, anim, frame=frame) else: tweak_frame = frame if Is.posebone(src) and not path.startswith('pose.bones'): keypath = utils.string('pose.bones[\"', src.name, '\"]', '' if path.startswith('[') else '.', path) elif not Is.object(src) and hasattr(src, 'path_from_id'): keypath = src.path_from_id(path) else: keypath = path # Find the value(s) to insert keyframes for if hasattr(sub, path): base = getattr(sub, path) elif hasattr(src, path): base = getattr(src, path) else: base = eval(f'{obj!r}.{keypath}') if value is None: prop = base else: if Is.iterable(base) and not Is.iterable(value): prop = [value for i in base] elif not Is.iterable(base) and Is.iterable(value): prop = value[(index, 0)[index == -1]] else: prop = value if (not Is.iterable(prop)): if index != -1: index = -1 if (not Is.iterable(prop)): props = [(index, prop)] else: props = [(index, prop[index])] elif (index == -1): props = list(enumerate(prop)) else: props = [(index, prop[index])] def save_quats(): quats = [0, 0, 0, 0] for index in range(4): fc = Get.fcurve(action, keypath, index=index) if fc: quats[index] = len(fc.keyframe_points) return quats def restore_quats(skip): nonlocal quats for index in range(4): if index == skip: continue fc = Get.fcurve(action, keypath, index=index) if fc and len(fc.keyframe_points) > quats[index]: for key in fc.keyframe_points: if key.co.x == tweak_frame: fc.keyframe_points.remove(key) break else: # Shouldn't happen but backup in case fail to find key key_args = dict(index=index, frame=frame, group=group) if sub is None: src.keyframe_delete(path, **key_args) else: sub.keyframe_delete(path, **key_args) if path.endswith('rotation_quaternion') and ( (strip and strip.blend_type == 'COMBINE') or (not strip and anim.action_blend_type == 'COMBINE')): # Combine forces keyframe insertion on all 4 channels, so reset them quats = save_quats() else: quats = None # Create curve(s) (if needed) and keyframe(s) for (i, v) in props: fc = Get.fcurve(action, keypath, i) new_fc = not bool(fc) if new_fc: fc = New.fcurve(action, keypath, index=i, group=group) if fc.lock: # Internal ops don't allow keyframing locked channels, so :p results.append((fc, None)) continue if delete: results.append((fc, src.keyframe_delete(path, frame=frame))) continue count = len(fc.keyframe_points) if (value is None) and key_in_action: key_args = dict(index=i, frame=frame, group=group, options=options) if sub is None: src.keyframe_insert(path, **key_args) else: sub.keyframe_insert(path, **key_args) v = fc.evaluate(tweak_frame) key = fc.keyframe_points.insert(tweak_frame, v, options={'FAST'}) # options=set({'REPLACE', 'NEEDED', 'FAST'}) # src.keyframe_insert(path, index=i, frame=frame, group=group) if quats: restore_quats(skip=i) quats[i] = len(fc.keyframe_points) # Update key count for current index, to not remove it later # Update keyframe to use default preferences values edit = utils.prefs().edit key.handle_left_type = key.handle_right_type = \ edit.keyframe_new_handle_type if new_fc: # When inserting keyframes, only change their interpolation type if the fcurve is new key.interpolation = edit.keyframe_new_interpolation_type if len(fc.keyframe_points) > count: # New keyframe was added key.type = keyframe_type results.append((fc, key)) if kargs.get('index', -1) == -1: return results else: return results[0]
def mesh(context, name='Mesh', type='PLANE', link=True, **kargs): "Create a box-mesh object" import bmesh from mathutils import Matrix # from bpy_extras.object_utils import AddObjectHelper width = kargs.get('width', 1.0) height = kargs.get('height', 1.0) depth = kargs.get('depth', 1.0) loc = kargs.get('loc', None) rot = kargs.get('rot', None) def gen_mesh(): """ This function takes inputs and returns vertex and face arrays. no actual mesh data creation is done here. """ if type == 'CUBE': left = -1.0 right = +1.0 top = +1.0 bottom = -1.0 front = -1.0 back = +1.0 verts = [ (right, front, top), # right-front-top (right, front, bottom), # right-front-bottom (left, front, bottom), # left-front-bottom (left, front, top), # left-front-top (right, back, top), # right-back-top (right, back, bottom), # right-back-bottom (left, back, bottom), # left-back-bottom (left, back, top), # left-back-top ] faces = [ (3, 2, 1, 0), (5, 6, 7, 4), (1, 5, 4, 0), (2, 6, 5, 1), (3, 7, 6, 2), (7, 3, 0, 4), # (3, 2, 1, 0), # # (4, 7, 6, 5), # # (0, 4, 5, 1), # # (1, 5, 6, 2), # # (2, 6, 7, 3), # # (4, 0, 3, 7), ] elif type == 'PLANE': left = -1.0 right = +1.0 top = +0.0 bottom = -0.0 front = -1.0 back = +1.0 # Plane verts = [ (left, back, bottom), # left-back-bottom (left, front, bottom), # left-front-bottom (right, front, bottom), # right-front-bottom (right, back, bottom), # right-back-bottom ] faces = [ (0, 1, 2, 3), # top ] elif type == 'POINT': verts = [(0, 0, 0)] faces = [] else: # null mesh verts = [] faces = [] # apply size if loc: for (dist, axis) in zip(loc, (0, 1, 2)): for i, v in enumerate(verts): verts[i] = list(verts[i]) verts[i][axis] += dist for i, v in enumerate(verts): verts[i] = v[0] * width, v[1] * depth, v[2] * height return (verts, faces) verts_loc, faces = gen_mesh() mesh = bpy.data.meshes.new(name) bm = bmesh.new() for v_co in verts_loc: bm.verts.new(v_co) # if loc: # bm.transform(Matrix().Translation(loc)) if rot: from zpy import utils bm.transform(utils.rotate_matrix(Matrix(), rot)) bm.verts.ensure_lookup_table() for f_idx in faces: bm.faces.new([bm.verts[i] for i in f_idx]) bm.to_mesh(mesh) mesh.update() # # add the mesh as an object into the scene with this utility module # from bpy_extras import object_utils # object_utils.object_data_add(context, mesh, operator=self) return New.object(context, name, mesh, link)
def edit_func(self, context, bone): rig = Get.rig(context, bone.id_data) ebones = rig.data.edit_bones (do_mch, do_start_end, do_in_out) = self.do() def get_disconnected_parent(bone, first_loop=True): if ((bone is None) or (not bone.parent)): if first_loop: return else: return bone elif Is.connected(bone): # Keep going up the chain until it finds a disconnected bone return get_disconnected_parent(bone.parent, False) else: return bone.parent def reset(bone, edit_bone): attributes = [ 'head', 'head_radius', 'tail', 'tail_radius', 'roll', 'matrix', 'layers', 'bbone_x', 'bbone_z', ] for atr in attributes: if hasattr(bone, atr): setattr(bone, atr, getattr(edit_bone, atr)) def edit(ebone, bbone_xz=1.0): reset(ebone, bone) ebone.bbone_x *= bbone_xz ebone.bbone_z *= bbone_xz ebone.use_deform = False ebone.inherit_scale = 'NONE' ebone.hide = True def edit_mch(ebone): edit(ebone, 1.25) ebone.parent = bone.parent ebone.inherit_scale = bone.inherit_scale # ebone.use_connect = bone.use_connect # bone.use_connect = False # bone.parent = ebone # for cbone in bone.children: # cbone.parent = ebone def edit_start(ebone): edit(ebone, 2.5) if do_mch: ebone.parent = bone_mch else: if Is.connected(bone): ebone.parent = ebones.get( get_name(bone.parent, 'bbone_end')) if ebone.parent: self.hide_bones[rig].append(ebone.name) ebone.hide = True else: ebone.parent = bone.parent if not do_in_out: cbone = ebones.get(get_name(bone, 'bbone_in')) if cbone: cbone.parent = ebone for cbone in bone.children_recursive: if (bone.head != cbone.tail): continue cbone_end = ebones.get(get_name(cbone, 'bbone_end')) if cbone_end: cbone_end.parent = ebone self.hide_bones[rig].append(cbone_end.name) cbone_end.hide = True ebone.tail = utils.lerp(ebone.head, ebone.tail, 0.1) def edit_head(ebone): edit(ebone, 0.5) ebone.parent = bone_start ebone.tail = utils.lerp(ebone.head, ebone.tail, 0.1) ebone.translate(ebone.head - ebone.tail) bone.bbone_custom_handle_start = ebone self.hide_bones[rig].append(ebone.name) def edit_end(ebone): edit(ebone, 2.5) if do_mch: ebone.parent = bone_mch else: for tbone in bone.parent_recursive: if (tbone.head != bone.tail): continue tobone_name = get_name(tbone, 'bbone_start') tobone = ebones.get(tobone_name) if tobone or ((tbone, rig) in self.selected): self.hide_bones[rig].append(ebone.name) ebone.hide = True if tobone: ebone.parent = tobone else: self.delayed_parenting.append( ebones, ebone.name, tobone_name) else: ebone.parent = tbone break else: ebone.parent = get_disconnected_parent(bone) if not do_in_out: cbone = ebones.get(get_name(bone, 'bbone_out')) if cbone: cbone.parent = ebone for cbone in bone.children: if Is.connected(cbone): cbone_start = ebones.get(get_name(cbone, 'bbone_start')) if cbone_start: cbone_start.parent = ebone self.hide_bones[rig].append(cbone_start.name) cbone_start.hide = True ebone.head = utils.lerp(ebone.head, ebone.tail, 0.9) ebone.translate(ebone.tail - ebone.head) bone.bbone_custom_handle_end = ebone def edit_in(ebone): edit(ebone, 2.0) if do_start_end: ebone.parent = bone_start else: ebone.parent = ebones.get(get_name(bone, 'bbone_start'), bone) (head, tail) = (ebone.head.copy(), ebone.tail.copy()) ebone.head = utils.lerp(head, tail, 0.1) ebone.tail = utils.lerp(head, tail, 0.2) def edit_out(ebone): edit(ebone, 2.0) if do_start_end: ebone.parent = bone_end else: ebone.parent = ebones.get(get_name(bone, 'bbone_end'), bone) (head, tail) = (ebone.head.copy(), ebone.tail.copy()) ebone.tail = utils.lerp(head, tail, 0.8) ebone.head = utils.lerp(head, tail, 0.9) ebone.align_roll(-bone.z_axis) # This bone is reversed, so the the roll needs to be flipped if (do_in_out and (not (do_mch or do_start_end)) and (bone.bbone_segments < 2)): # parenting to the bone will cause dependency loop, with drivers # if the bone isn't using bbones if not (ebones.get(get_name(bone, 'bbone_start'), ebones.get(get_name(bone, 'bbone_end')))): if self.warning: self.warning = ( f"{bone.name} does not have Bendy Bone Segments;" " this will cause a dependency cycle-loop with its drivers/controllers" ) else: self.warning = ( f"{self.warnings + 1} bones don't have Bendy Bone Segments;" " this will cause a dependency cycle-loop with their drivers/controllers" ) self.warnings += 1 if do_start_end: bone.bbone_handle_type_start = bone.bbone_handle_type_end = 'ABSOLUTE' args = dict(context=context, armature=rig, overwrite=True) if do_mch: bone_mch = New.bone(**args, name=get_name(bone, 'bbone'), edit=edit_mch) if do_start_end: bone_start = New.bone(**args, name=get_name(bone, 'bbone_start'), edit=edit_start) bone_end = New.bone(**args, name=get_name(bone, 'bbone_end'), edit=edit_end) bone_head = New.bone(**args, name=get_name(bone, 'bbone_head'), edit=edit_head) if do_in_out: bone_in = New.bone(**args, name=get_name(bone, 'bbone_in'), edit=edit_in) bone_out = New.bone(**args, name=get_name(bone, 'bbone_out'), edit=edit_out)
def edit_mirror_center(self, context): def get_bones(rig, bbone): ebones = rig.data.edit_bones ebone = ebones.get(get_name(bone, bbone)) mebone = Get.mirror_bone(ebone) return (ebone, mebone) found = [] for (bone, rig) in self.selected: if not (rig.pose.use_mirror_x or rig.data.use_mirror_x): continue mbone = Get.mirror_bone(bone) if mbone in found: continue else: found.append(bone) (ebone, mebone) = get_bones(rig, 'bbone_start') if not (ebone and mebone): continue if (ebone.parent == mebone.parent): # Connect heads if Is.connected(bone): # The parent will already handle the symmetry continue parent = ebone.parent else: (ebone, mebone) = get_bones(rig, 'bbone_end') if not (ebone and mebone): continue # Find a mutual parent between the two bones parent = [ *(x for x in ebone.parent_recursive if x in mebone.parent_recursive), None ][0] distance = abs(sum(ebone.head) - sum(mebone.head)) / 2 margin = utils.lerp(bone.bone.length, mbone.bone.length, 0.5) / bone.bone.bbone_segments if distance >= margin: # Bones too far apart continue (prefix, replace, suffix, number) = utils.flip_name(bone.name, only_split=True) center_name = prefix + suffix + number center = New.bone(context, rig, name=center_name, overwrite=True) attributes = [ 'head', 'head_radius', 'tail', 'tail_radius', 'roll', 'matrix', 'layers', 'bbone_x', 'bbone_z', ] for atr in attributes: if hasattr(center, atr): setattr( center, atr, utils.lerp(getattr(ebone, atr), getattr(mebone, atr), 0.5)) center.use_deform = False center.inherit_scale = 'NONE' center.parent = parent center.hide = True ebone.parent = mebone.parent = center self.hide_bones[rig].extend((ebone.name, mebone.name)) self.center_bones[rig].append(center.name)
class ik: head = New.bone(context, rig, name="IK-" + fk.head.name) tail = New.bone(context, rig, name="IK-" + fk.tail.name) bone = New.bone(context, rig, name="IK-" + fk.bone.name)
def execute_new(self, context): """ Either made this originally then scrapped for the simpler version above, or was making this then got distracted and never came back to it """ def sync(owner, target, *attribs): for attrib in attribs: try: setattr(target, attrib, getattr(owner, attrib)) except: print("Can't write", attrib, "in", target) gp = bpy.data.grease_pencils.new(name=an.name) New.object(context, name=an.name, data=gp) for alayer in an.layers: glayer = gp.layers.new(alayer.info) for aframe in alayer.frames: gframe = glayer.frames.new(aframe.frame_number) for astroke in aframe.strokes: gstroke = gframe.strokes.new() gstroke.points.add(len(astroke.points)) for (index, gpoint) in enumerate(gstroke.points): apoint = astroke.points[index] sync(apoint, gpoint, 'co', 'pressure', 'select', 'strength', 'uv_factor', 'uv_rotation') sync( astroke, gstroke, 'draw_cyclic', 'end_cap_mode', 'gradient_factor', 'gradient_shape', 'line_width', 'material_index', 'select', 'start_cap_mode', # 'groups', 'triangles' ) gstroke.display_mode = '3DSPACE' sync(aframe, gframe, 'frame_number', 'select') sync( alayer, glayer, # 'active_frame', 'annotation_hide', 'annotation_onion_after_color', 'annotation_onion_before_color', 'annotation_onion_after_range', 'annotation_onion_before_range', 'blend_mode', 'channel_color', 'color', 'hide', 'line_change', 'lock', 'lock_frame', 'lock_material', 'mask_layer', 'matrix_inverse', 'opacity', 'parent', 'parent_bone', 'parent_type', 'pass_index', 'select', 'show_in_front', 'show_points', 'thickness', 'tint_color', 'tint_factor', 'use_annotation_onion_skinning', 'use_onion_skinning', 'use_solo_mode', 'viewlayer_render') return {'FINISHED'}
def update_pose(self, context): # for region in context.area.regions: # if region.type == 'WINDOW': # break # else: # return self.cancel(context) region = context.region rv3d = context.space_data.region_3d gp = context.annotation_data stroke = gp.layers.active.frames[0].strokes[0] for chain in Get.sorted_chains(context.selected_pose_bones): bone_chain = list() for bone in reversed(chain): bone_chain.insert(0, bone) if bone == chain[0]: # Do location pass # continue # or break; should do the same else: pass while bone.parent not in chain: # Do unselected in betweens bone = bone.parent if not Is.visible(context, bone): # Don't rotate hidden bones continue bone_chain.insert(0, bone) bcount = len(bone_chain) - 1 gcount = len(stroke.points) - 1 # if bcount: # while gcount > bcount * 3: # # Split point count in half # index = 0 # while index < len(stroke.points) - 1: # stroke.points.pop(index=index + 1) # index += 1 # print(bcount, gcount, '\t', index, len(stroke.points)) # gcount = len(stroke.points) - 1 bone_mats = list() con_tmp = list() index = 0 for bone in bone_chain: if index > bcount: index = bcount point_index = utils.scale_range(index, 0, bcount, 0, gcount) point = stroke.points[int(point_index)] if index == 0: if not (bone.parent): bone = bone_chain[0] point = stroke.points[0] to_2d = location_3d_to_region_2d( region, rv3d, point.co) # get 2d space of stroke if to_2d: to_3d = region_2d_to_location_3d( region, rv3d, to_2d, bone.head) # keep depth of bone empty = New.object(context, bone.name) empty.empty_display_size = 0.25 empty.location = to_3d con = bone.constraints.new('COPY_LOCATION') con.target = empty con_tmp.append((bone, con, empty)) if bcount == 0: point = stroke.points[-1] else: # index += 1 point_index = utils.scale_range( 0.5, 0, bcount, 0, gcount) point = stroke.points[int(point_index)] to_2d = location_3d_to_region_2d( region, rv3d, point.co) # get 2d space of stroke if to_2d: to_3d = region_2d_to_location_3d( region, rv3d, to_2d, bone.tail) # keep depth of bone empty = New.object(context, bone.name) empty.empty_display_size = 0.1 empty.location = to_3d con = bone.constraints.new('DAMPED_TRACK') con.target = empty con_tmp.append((bone, con, empty)) index += 1 utils.update(context) for (bone, con, empty) in reversed(con_tmp): mat = Get.matrix_constraints(context, bone) # mat = Get.matrix(bone) bone_mats.append((bone, mat)) bone.constraints.remove(con) Get.objects(context, link=True).unlink(empty) for (bone, mat) in bone_mats: Set.matrix(bone, mat) keyframe.keyingset(context, selected=[bone], skip_bones=True) self.remove_annotation(context) return {'FINISHED'}