import bpy, re from . import fn ## step 1 : Create the curve and/or follow path constraint, snap to ground def create_follow_path_constraint(ob, curve): pref = fn.get_addon_prefs() bone_name = pref.tgt_bone root = ob.pose.bones.get(bone_name) if not root: return ('ERROR', f'posebone {bone_name} not found in armature {ob.name} check addon preferences to change name') # Clear bone follow path constraint exiting_fp_constraints = [c for c in root.constraints if c.type == 'FOLLOW_PATH'] for c in exiting_fp_constraints: root.constraints.remove(c) # loc = ob.matrix_world @ root.matrix.to_translation() if root.name == 'root' and root.location != (0,0,0): old_loc = root.location root.location = (0,0,0) print(f'root moved from {old_loc} to (0,0,0) to counter curve offset') const = root.constraints.new('FOLLOW_PATH') const.target = curve # axis only in this case, should be in addon to prefs const.forward_axis = 'FORWARD_X' const.use_curve_follow = True return curve, const def snap_curve(): obj = bpy.context.object curve = const = None if obj.type == 'ARMATURE': curve, const = fn.get_follow_curve_from_armature(obj) to_follow = bpy.context.scene.anim_cycle_settings.path_to_follow if not curve and not to_follow: return ('ERROR', f'No curve pointed by "Path" filed') # get curve from field if not curve: curve, const = create_follow_path_constraint(obj, to_follow) if not curve: return (curve, const) # those are error message # if obj.type == 'CURVE': # return ('ERROR', f'Select the armature related to curve {obj.name}') # else: # return ('ERROR', 'Not an armature object') gnd = fn.get_gnd() if not gnd: return # if it's on a snap curve, fetch original if '_snap' in curve.name: org_name = re.sub(r'_snap\.?\d{0,3}$', '', curve.name) org_curve = bpy.context.scene.objects.get(org_name) if org_curve: const.target = org_curve # delete old snap bpy.data.objects.remove(curve) # assign old curve as main one curve = org_curve nc = curve.copy() name = re.sub(r'\.\d{3}$', '', curve.name) + '_snap' const.target = nc nc.name = name nc.data = curve.data.copy() nc.data.name = name + '_data' curve.users_collection[0].objects.link(nc) ## If object mode is Curve subdivide it (TODO if nurbs needs conversion) #-# subdivide the curve (if curve is not nurbs) # bpy.ops.object.mode_set(mode='EDIT') # bpy.ops.curve.select_all(action='SELECT') # bpy.ops.curve.subdivide(number_cuts=4) # bpy.ops.object.mode_set(mode='OBJECT') # shrinkwrap or cast on ground mod = nc.modifiers.new('Shrinkwrap', 'SHRINKWRAP') mod.wrap_method = 'TARGET_PROJECT' mod.target = gnd # Apply and decimate bpy.ops.object.modifier_apply({'object': nc}, modifier="Shrinkwrap", report=False) bpy.context.scene.anim_cycle_settings.path_to_follow = nc # return 0, nc class UAC_OT_create_curve_path(bpy.types.Operator): bl_idname = "anim.create_curve_path" bl_label = "Create Curve" bl_description = "Create curve and add follow path constraint" bl_options = {"REGISTER", "UNDO"} @classmethod def poll(cls, context): return context.object and context.object.type == 'ARMATURE' def execute(self, context): # use root (or other specified bone) to find where to put the curve pref = fn.get_addon_prefs() ob = context.object bpy.ops.object.mode_set(mode='OBJECT', toggle=False) bone_name = pref.tgt_bone root = ob.pose.bones.get(bone_name) if not root: self.report({'ERROR'}, f'posebone {bone_name} not found in armature {ob.name} check addon preferences to change name') return {"CANCELLED"} loc = ob.matrix_world @ root.matrix.to_translation() # TODO propose nurbs instead of curve # TODO mode elegantly create the curve using data... bpy.ops.curve.primitive_bezier_curve_add(radius=1, enter_editmode=True, align='WORLD', location=loc, scale=(1, 1, 1)) # fast straighten print('context.object: ', context.object.name) curve = context.object curve.name = 'curve_path' curve.show_in_front = True bpy.ops.curve.handle_type_set(type='VECTOR') bpy.ops.curve.handle_type_set(type='ALIGNED') # offset to have start bpy.ops.transform.translate(value=(1, 0, 0), orient_type='LOCAL', orient_matrix=((1, 0, 0), (0, 1, 0), (0, 0, 1)), orient_matrix_type='LOCAL', constraint_axis=(True, False, False), mirror=True, use_proportional_edit=False) context.space_data.overlay.show_curve_normals = True context.space_data.overlay.normals_length = 0.2 context.scene.anim_cycle_settings.path_to_follow = curve ## back to objct mode ? # bpy.ops.object.mode_set(mode='OBJECT', toggle=False) ## TODO replace this part with >> create_follow_path_constraint(ob, curve) if root.name == 'root' and root.location != (0,0,0): old_loc = root.location root.location = (0,0,0) print(f'root moved from {old_loc} to (0,0,0) to counter curve offset') # make follow path constraint const = root.constraints.new('FOLLOW_PATH') const.target = curve # axis only in this case, should be in addon to prefs const.forward_axis = 'FORWARD_X' # 'TRACK_NEGATIVE_Y' const.use_curve_follow = True return {"FINISHED"} class UAC_OT_create_follow_path(bpy.types.Operator): bl_idname = "anim.create_follow_path" bl_label = "Create follow path constraint" bl_description = "Create follow path targeting curve in field" bl_options = {"REGISTER", "UNDO"} @classmethod def poll(cls, context): return context.object and context.object.type == 'ARMATURE' def execute(self, context): ob = context.object curve = context.scene.anim_cycle_settings.path_to_follow create_follow_path_constraint(ob, curve) return {"FINISHED"} class UAC_OT_snap_curve_to_ground(bpy.types.Operator): bl_idname = "anim.snap_curve_to_ground" bl_label = "snap_curve_to_ground" bl_description = "snap curve" bl_options = {"REGISTER", "UNDO"} @classmethod def poll(cls, context): return context.object and context.object.type == 'ARMATURE' def execute(self, context): err = snap_curve() if err: self.report({err[0]}, err[1]) if err[0] == 'ERROR': return {"CANCELLED"} return {"FINISHED"} classes=( UAC_OT_create_curve_path, UAC_OT_create_follow_path, UAC_OT_snap_curve_to_ground, ) def register(): for cls in classes: bpy.utils.register_class(cls) def unregister(): for cls in reversed(classes): bpy.utils.unregister_class(cls) # if __name__ == "__main__": # register()