initial code for follow curve
parent
4622aa4520
commit
336d1b264c
|
@ -0,0 +1,68 @@
|
||||||
|
import bpy
|
||||||
|
from mathutils import Vector
|
||||||
|
from . import utils
|
||||||
|
|
||||||
|
|
||||||
|
class GPTB_OT_create_follow_path_curve(bpy.types.Operator):
|
||||||
|
bl_idname = "object.create_follow_path_curve"
|
||||||
|
bl_label = "Create Follow Path Curve"
|
||||||
|
bl_description = "Create curve and add follow path constraint\
|
||||||
|
\n(remove location offset from object if any)"
|
||||||
|
bl_options = {"REGISTER", "UNDO"}
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def poll(cls, context):
|
||||||
|
return context.object
|
||||||
|
|
||||||
|
def execute(self, context):
|
||||||
|
ob = context.object
|
||||||
|
# settings = context.scene.anim_cycle_settings
|
||||||
|
bpy.ops.object.mode_set(mode='OBJECT', toggle=False)
|
||||||
|
|
||||||
|
## For bones
|
||||||
|
# root_name = fn.get_root_name(context=context)
|
||||||
|
# root = ob.pose.bones.get(root_name)
|
||||||
|
# if not root:
|
||||||
|
# self.report({'ERROR'}, f'posebone {root_name} not found in armature {ob.name} check addon preferences to change name')
|
||||||
|
# return {"CANCELLED"}
|
||||||
|
|
||||||
|
## create curve at bone position
|
||||||
|
# loc = ob.matrix_world @ root.matrix.to_translation()
|
||||||
|
# root_axis_vec = fn.get_direction_vector_from_enum(settings.forward_axis)
|
||||||
|
## get real world direction of the root
|
||||||
|
# world_forward = (root.matrix @ root_axis_vec) - root.matrix.to_translation()
|
||||||
|
|
||||||
|
loc = ob.matrix_world.to_translation()
|
||||||
|
|
||||||
|
## X global
|
||||||
|
# TODO: Set direction orientation in view space (UP, LEFT, RIGHT, DOWN)
|
||||||
|
direction = Vector((1,0,0))
|
||||||
|
curve = utils.create_curve(location=loc,
|
||||||
|
direction=direction.normalized() * 2,
|
||||||
|
name='curve_path',
|
||||||
|
context=context)
|
||||||
|
|
||||||
|
utils.create_follow_path_constraint(ob, curve)
|
||||||
|
|
||||||
|
## reset location to remove offset
|
||||||
|
ob.location = (0,0,0)
|
||||||
|
# ob.keyframe_insert('location')
|
||||||
|
ob.rotation_euler = (0,0,0)
|
||||||
|
# ob.keyframe_insert('rotation_euler')
|
||||||
|
|
||||||
|
# refresh evaluation so constraint shows up correctly
|
||||||
|
bpy.context.scene.frame_set(bpy.context.scene.frame_current)
|
||||||
|
return {"FINISHED"}
|
||||||
|
|
||||||
|
|
||||||
|
classes = (
|
||||||
|
GPTB_OT_create_follow_path_curve,
|
||||||
|
)
|
||||||
|
|
||||||
|
def register():
|
||||||
|
for cls in classes:
|
||||||
|
bpy.utils.register_class(cls)
|
||||||
|
|
||||||
|
def unregister():
|
||||||
|
for cls in reversed(classes):
|
||||||
|
bpy.utils.unregister_class(cls)
|
|
@ -222,6 +222,10 @@ class GPTB_PT_anim_manager(Panel):
|
||||||
row.operator('gp.toggle_hide_gp_modifier', text = 'ON').show = True
|
row.operator('gp.toggle_hide_gp_modifier', text = 'ON').show = True
|
||||||
row.operator('gp.toggle_hide_gp_modifier', text = 'OFF').show = False
|
row.operator('gp.toggle_hide_gp_modifier', text = 'OFF').show = False
|
||||||
|
|
||||||
|
## Follow curve path
|
||||||
|
row = col.row(align=True)
|
||||||
|
row.operator('object.create_follow_path_curve', text='Create Curve', icon='CURVE_BEZCURVE')
|
||||||
|
|
||||||
## This can go in an extra category...
|
## This can go in an extra category...
|
||||||
col = layout.column()
|
col = layout.column()
|
||||||
col.use_property_split = False
|
col.use_property_split = False
|
||||||
|
|
|
@ -4,7 +4,7 @@ bl_info = {
|
||||||
"name": "GP toolbox",
|
"name": "GP toolbox",
|
||||||
"description": "Tool set for Grease Pencil in animation production",
|
"description": "Tool set for Grease Pencil in animation production",
|
||||||
"author": "Samuel Bernou, Christophe Seux",
|
"author": "Samuel Bernou, Christophe Seux",
|
||||||
"version": (2, 0, 11),
|
"version": (2, 1, 0),
|
||||||
"blender": (3, 0, 0),
|
"blender": (3, 0, 0),
|
||||||
"location": "Sidebar (N menu) > Gpencil > Toolbox / Gpencil properties",
|
"location": "Sidebar (N menu) > Gpencil > Toolbox / Gpencil properties",
|
||||||
"warning": "",
|
"warning": "",
|
||||||
|
@ -45,6 +45,7 @@ from . import OP_material_picker
|
||||||
from . import OP_git_update
|
from . import OP_git_update
|
||||||
from . import OP_layer_namespace
|
from . import OP_layer_namespace
|
||||||
from . import OP_pseudo_tint
|
from . import OP_pseudo_tint
|
||||||
|
from . import OP_follow_curve
|
||||||
# from . import OP_eraser_brush
|
# from . import OP_eraser_brush
|
||||||
# from . import TOOL_eraser_brush
|
# from . import TOOL_eraser_brush
|
||||||
from . import handler_draw_cam
|
from . import handler_draw_cam
|
||||||
|
@ -658,8 +659,8 @@ def set_namespace_env(name_env, prop_group):
|
||||||
if tag_list:
|
if tag_list:
|
||||||
tag_list = tag_list.strip(',').split(',')
|
tag_list = tag_list.strip(',').split(',')
|
||||||
current_pfix = [n.tag for n in prop_group.namespaces if n.tag]
|
current_pfix = [n.tag for n in prop_group.namespaces if n.tag]
|
||||||
for n in prop_group.namespaces:
|
# for n in prop_group.namespaces:
|
||||||
print(n.tag, n.name)
|
# print(n.tag, n.name)
|
||||||
for p in tag_list:
|
for p in tag_list:
|
||||||
tag = p.split(':')[0].strip()
|
tag = p.split(':')[0].strip()
|
||||||
name = '' if not ':' in p else p.split(':')[1].strip()
|
name = '' if not ':' in p else p.split(':')[1].strip()
|
||||||
|
@ -789,6 +790,7 @@ addon_modules = (
|
||||||
OP_git_update,
|
OP_git_update,
|
||||||
OP_layer_picker,
|
OP_layer_picker,
|
||||||
OP_layer_nav,
|
OP_layer_nav,
|
||||||
|
OP_follow_curve,
|
||||||
# OP_eraser_brush,
|
# OP_eraser_brush,
|
||||||
# TOOL_eraser_brush, # experimental eraser brush
|
# TOOL_eraser_brush, # experimental eraser brush
|
||||||
handler_draw_cam,
|
handler_draw_cam,
|
||||||
|
|
145
utils.py
145
utils.py
|
@ -956,3 +956,148 @@ def iterate_selector(zone, attr, state, info_attr = None, active_access='active'
|
||||||
info = getattr(active_item, info_attr)
|
info = getattr(active_item, info_attr)
|
||||||
|
|
||||||
return info, bottom
|
return info, bottom
|
||||||
|
|
||||||
|
# -----------------
|
||||||
|
### Curve handle
|
||||||
|
# -----------------
|
||||||
|
|
||||||
|
def create_curve(location=(0,0,0), direction=(1,0,0), name='curve_path', enter_edit=True, context=None):
|
||||||
|
'''Create curve at provided location and direction vector'''
|
||||||
|
|
||||||
|
## option to create nurbs instaed of bezier ?
|
||||||
|
|
||||||
|
context = context or bpy.context
|
||||||
|
|
||||||
|
## using ops (dirty)
|
||||||
|
# bpy.ops.curve.primitive_bezier_curve_add(radius=1, enter_editmode=enter_edit, align='WORLD', location=location, scale=(1, 1, 1))
|
||||||
|
# curve = context.object
|
||||||
|
# curve.name = 'curve_path'
|
||||||
|
# # fast straighten
|
||||||
|
# bpy.ops.curve.handle_type_set(type='VECTOR')
|
||||||
|
# bpy.ops.curve.handle_type_set(type='ALIGNED')
|
||||||
|
# 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)
|
||||||
|
|
||||||
|
## using data
|
||||||
|
curve_data = bpy.data.curves.new(name, 'CURVE') # ('CURVE', 'SURFACE', 'FONT')
|
||||||
|
curve_data.dimensions = '3D'
|
||||||
|
curve_data.use_path = True
|
||||||
|
|
||||||
|
curve = bpy.data.objects.new(name, curve_data)
|
||||||
|
spl = curve_data.splines.new('BEZIER') # ('POLY', 'BEZIER', 'NURBS')
|
||||||
|
spl.bezier_points.add(1) # One point already exists
|
||||||
|
for i in range(2):
|
||||||
|
spl.bezier_points[i].handle_left_type = 'VECTOR' # ('FREE', 'VECTOR', 'ALIGNED', 'AUTO')
|
||||||
|
spl.bezier_points[i].handle_right_type = 'VECTOR'
|
||||||
|
spl.bezier_points[1].co = direction
|
||||||
|
|
||||||
|
# Back to aligned mode
|
||||||
|
for i in range(2):
|
||||||
|
spl.bezier_points[i].handle_right_type = spl.bezier_points[i].handle_left_type = 'ALIGNED'
|
||||||
|
|
||||||
|
# Select second point
|
||||||
|
spl.bezier_points[1].select_control_point = True
|
||||||
|
spl.bezier_points[1].select_left_handle = True
|
||||||
|
spl.bezier_points[1].select_right_handle = True
|
||||||
|
|
||||||
|
# link
|
||||||
|
context.scene.collection.objects.link(curve)
|
||||||
|
|
||||||
|
# curve object settings
|
||||||
|
curve.location = location
|
||||||
|
curve.show_in_front = True
|
||||||
|
|
||||||
|
# enter edit
|
||||||
|
if enter_edit and context.mode == 'OBJECT':
|
||||||
|
curve.select_set(True)
|
||||||
|
context.view_layer.objects.active = curve
|
||||||
|
bpy.ops.object.mode_set(mode='EDIT', toggle=False) # EDIT_CURVE
|
||||||
|
|
||||||
|
## set viewport overlay visibility for better view
|
||||||
|
if context.space_data.type == 'VIEW_3D':
|
||||||
|
context.space_data.overlay.show_curve_normals = True
|
||||||
|
context.space_data.overlay.normals_length = 0.2
|
||||||
|
|
||||||
|
return curve
|
||||||
|
|
||||||
|
def get_direction_vector_from_enum(string) -> Vector:
|
||||||
|
orient_vectors = {
|
||||||
|
'FORWARD_X' : Vector((1,0,0)),
|
||||||
|
'FORWARD_Y' : Vector((0,1,0)),
|
||||||
|
'FORWARD_Z' : Vector((0,0,1)),
|
||||||
|
'TRACK_NEGATIVE_X' : Vector((-1,0,0)),
|
||||||
|
'TRACK_NEGATIVE_Y' : Vector((0,-1,0)),
|
||||||
|
'TRACK_NEGATIVE_Z' : Vector((0,0,-1))
|
||||||
|
}
|
||||||
|
return orient_vectors[string]
|
||||||
|
|
||||||
|
def orentation_track_from_vector(input_vector) -> str:
|
||||||
|
'''return closest world track orientation name from passed vector direction'''
|
||||||
|
orient_vectors = {
|
||||||
|
'FORWARD_X' : Vector((1,0,0)),
|
||||||
|
'FORWARD_Y' : Vector((0,1,0)),
|
||||||
|
'FORWARD_Z' : Vector((0,0,1)),
|
||||||
|
'TRACK_NEGATIVE_X' : Vector((-1,0,0)),
|
||||||
|
'TRACK_NEGATIVE_Y' : Vector((0,-1,0)),
|
||||||
|
'TRACK_NEGATIVE_Z' : Vector((0,0,-1))
|
||||||
|
}
|
||||||
|
|
||||||
|
orient = None
|
||||||
|
min_angle = 10000
|
||||||
|
for track, v in orient_vectors.items():
|
||||||
|
angle = input_vector.angle(v)
|
||||||
|
if angle < min_angle:
|
||||||
|
min_angle = angle
|
||||||
|
orient = track
|
||||||
|
|
||||||
|
return orient
|
||||||
|
|
||||||
|
def create_follow_path_constraint(ob, curve, follow_curve=False):
|
||||||
|
'''return create constraint'''
|
||||||
|
# # Clear bone follow path constraint
|
||||||
|
exiting_fp_constraints = [c for c in ob.constraints if c.type == 'FOLLOW_PATH']
|
||||||
|
for c in exiting_fp_constraints:
|
||||||
|
ob.constraints.remove(c)
|
||||||
|
|
||||||
|
# loc = ob.matrix_world @ ob.matrix.to_translation()
|
||||||
|
if ob.location != (0,0,0):
|
||||||
|
old_loc = ob.location
|
||||||
|
ob.location = (0,0,0)
|
||||||
|
print(f'ob moved from {old_loc} to (0,0,0) to counter follow curve offset')
|
||||||
|
|
||||||
|
const = ob.constraints.new('FOLLOW_PATH')
|
||||||
|
const.target = curve
|
||||||
|
if follow_curve:
|
||||||
|
const.use_curve_follow = True
|
||||||
|
return const
|
||||||
|
|
||||||
|
## on_bones:
|
||||||
|
# prefs = get_addon_prefs()
|
||||||
|
# root_name = prefs.tgt_bone
|
||||||
|
# root = ob.pose.bones.get(root_name)
|
||||||
|
|
||||||
|
# if not root:
|
||||||
|
# return ('ERROR', f'posebone {root_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 == ('world', '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 follow curve offset')
|
||||||
|
|
||||||
|
# const = root.constraints.new('FOLLOW_PATH')
|
||||||
|
# const.target = curve
|
||||||
|
# # axis only in this case, should be in addon to prefs
|
||||||
|
|
||||||
|
# ## determine which axis to use... maybe found orientation in world space from matrix_basis ?
|
||||||
|
# root_world_base_direction = root.bone.matrix_local @ get_direction_vector_from_enum(bpy.context.scene.anim_cycle_settings.forward_axis)
|
||||||
|
# const.forward_axis = orentation_track_from_vector(root_world_base_direction) # 'TRACK_NEGATIVE_Y' # bpy.context.scene.anim_cycle_settings.forward_axis # 'FORWARD_X'
|
||||||
|
# print('const.forward_axis: ', const.forward_axis)
|
||||||
|
# const.use_curve_follow = True
|
||||||
|
# return curve, const
|
Loading…
Reference in New Issue