velocity based interpolate, multi layers
parent
c16e8a7731
commit
d11d1e0435
|
@ -0,0 +1,4 @@
|
|||
|
||||
from pathlib import Path
|
||||
|
||||
RESOURCES_DIR = Path(__file__).parent /'resources'
|
|
@ -1,6 +1,7 @@
|
|||
from gp_interpolate.interpolate_strokes import (properties,
|
||||
operators,
|
||||
operators_triangle,
|
||||
operators_velocity,
|
||||
debug,
|
||||
bind_points,
|
||||
)
|
||||
|
@ -9,6 +10,7 @@ modules = (
|
|||
properties,
|
||||
operators,
|
||||
operators_triangle,
|
||||
operators_velocity,
|
||||
debug,
|
||||
bind_points,
|
||||
)
|
||||
|
|
|
@ -88,20 +88,21 @@ class GP_OT_interpolate_stroke_base(bpy.types.Operator):
|
|||
return {'FINISHED'}
|
||||
return {'CANCELLED'}
|
||||
|
||||
def get_stroke_to_interpolate(self, context):
|
||||
## Get strokes to interpolate
|
||||
tgt_strokes = [s for s in self.gp.data.layers.active.active_frame.strokes if s.select]
|
||||
# def get_stroke_to_interpolate(self, context):
|
||||
# ## Get strokes to interpolate
|
||||
# #tgt_strokes = [s for s in self.gp.data.layers.active.active_frame.strokes if s.select]
|
||||
# tgt_strokes = [s for l in self.layers for s in l.active_frame.strokes if s.select]
|
||||
|
||||
## If nothing selected in sculpt/paint, Select all before triggering
|
||||
if not tgt_strokes and context.mode in ('SCULPT_GPENCIL', 'PAINT_GPENCIL'):
|
||||
for s in self.gp.data.layers.active.active_frame.strokes:
|
||||
s.select = True
|
||||
tgt_strokes = self.gp.data.layers.active.active_frame.strokes
|
||||
# ## If nothing selected in sculpt/paint, Select all before triggering
|
||||
# if not tgt_strokes and context.mode in ('SCULPT_GPENCIL', 'PAINT_GPENCIL'):
|
||||
# for s in self.gp.data.layers.active.active_frame.strokes:
|
||||
# s.select = True
|
||||
# tgt_strokes = self.gp.data.layers.active.active_frame.strokes
|
||||
|
||||
if tgt_strokes:
|
||||
return tgt_strokes
|
||||
# if tgt_strokes:
|
||||
# return tgt_strokes
|
||||
|
||||
return self.exit(context, status='ERROR', text='No stroke selected!')
|
||||
# return self.exit(context, status='ERROR', text='No stroke selected!')
|
||||
|
||||
|
||||
## Added to operators owns invoke with uper().invoke(context, event)
|
||||
|
@ -128,19 +129,28 @@ class GP_OT_interpolate_stroke_base(bpy.types.Operator):
|
|||
if interp_col := bpy.data.collections.get('interpolation_tool'):
|
||||
bpy.data.collections.remove(interp_col)
|
||||
|
||||
if context.mode != 'EDIT_GPENCIL':
|
||||
self.report({"ERROR"}, "Mode need to be Edit Grease Pencil")
|
||||
return {"CANCELLED"}
|
||||
|
||||
## Change active layer if strokes are selected only on this layer
|
||||
layers = [l for l in self.gp.data.layers
|
||||
if (not l.lock and l.active_frame)
|
||||
self.layers = [l for l in self.gp.data.layers
|
||||
if (not l.lock and l.active_frame and not l.hide)
|
||||
and next((s for s in l.active_frame.strokes if s.select), None)]
|
||||
|
||||
if not layers:
|
||||
return self.exit(context, status='ERROR', text='No stroke selected!')
|
||||
self.strokes = [s for l in self.layers for s in l.active_frame.strokes if s.select]
|
||||
if not self.strokes:
|
||||
self.report({"ERROR"}, "No strokes selected")
|
||||
return {"CANCELLED"}
|
||||
|
||||
#if not self.layers:
|
||||
# return self.exit(context, status='ERROR', text='No stroke selected!')
|
||||
|
||||
elif len(layers) > 1:
|
||||
return self.exit(context, status='ERROR', text='Strokes selected accross multiple layers!')
|
||||
#elif len(layers) > 1:
|
||||
# return self.exit(context, status='ERROR', text='Strokes selected accross multiple layers!')
|
||||
|
||||
## Set active layer
|
||||
self.gp.data.layers.active = layers[0]
|
||||
#self.gp.data.layers.active = layers[0]
|
||||
|
||||
if self.interactive:
|
||||
self.frames_to_jump = following_keys(forward=True, animation=True)
|
||||
|
@ -269,9 +279,10 @@ class GP_OT_interpolate_stroke(GP_OT_interpolate_stroke_base):
|
|||
|
||||
origin = scn.camera.matrix_world.to_translation()
|
||||
|
||||
tgt_strokes = self.get_stroke_to_interpolate(context)
|
||||
if isinstance(tgt_strokes, set):
|
||||
return tgt_strokes
|
||||
strokes = [s for l in self.layers for s in l.active_frame.strokes if s.select]
|
||||
if not strokes:
|
||||
self.report({"ERROR"}, "No strokes selected")
|
||||
return {"CANCELLED"}
|
||||
|
||||
col = self.settings.target_collection
|
||||
if not col:
|
||||
|
@ -350,7 +361,7 @@ class GP_OT_interpolate_stroke(GP_OT_interpolate_stroke_base):
|
|||
dg = bpy.context.evaluated_depsgraph_get()
|
||||
self.strokes_data = []
|
||||
|
||||
for stroke_index, stroke in enumerate(tgt_strokes):
|
||||
for stroke_index, stroke in enumerate(strokes):
|
||||
stroke_data = []
|
||||
for point_index, point in enumerate(stroke.points):
|
||||
point_co_world = self.gp.matrix_world @ point.co
|
||||
|
@ -393,7 +404,7 @@ class GP_OT_interpolate_stroke(GP_OT_interpolate_stroke_base):
|
|||
origin = scn.camera.matrix_world.to_translation()
|
||||
plane_co, plane_no = get_gp_draw_plane(self.gp)
|
||||
bpy.ops.gpencil.select_all(action='DESELECT')
|
||||
bpy.ops.gpencil.paste()
|
||||
bpy.ops.gpencil.paste(type='LAYER')
|
||||
|
||||
if self.settings.method == 'BONE':
|
||||
## Set plane on the bone
|
||||
|
@ -405,10 +416,13 @@ class GP_OT_interpolate_stroke(GP_OT_interpolate_stroke_base):
|
|||
dg = bpy.context.evaluated_depsgraph_get()
|
||||
|
||||
## Get pasted stroke
|
||||
new_strokes = [s for s in self.gp.data.layers.active.active_frame.strokes if s.select]
|
||||
#new_strokes = [s for s in self.gp.data.layers.active.active_frame.strokes if s.select]
|
||||
new_strokes = [s for l in self.layers for s in l.active_frame.strokes if s.select]
|
||||
## Keep reference to all accessible other strokes (in all accessible layer)
|
||||
other_strokes = [s for l in self.gp.data.layers if l.active_frame and not l.lock for s in l.active_frame.strokes if not s.select]
|
||||
|
||||
smooth_level = self.settings.smooth_level
|
||||
|
||||
occluded_points = []
|
||||
for new_stroke, stroke_data in zip(list(new_strokes), list(self.strokes_data)):
|
||||
world_co_3d = []
|
||||
|
@ -416,9 +430,25 @@ class GP_OT_interpolate_stroke(GP_OT_interpolate_stroke_base):
|
|||
eval_ob = object_hit.evaluated_get(dg)
|
||||
tri_b = [eval_ob.matrix_world @ eval_ob.data.vertices[i].co for i in tri_indices]
|
||||
|
||||
new_loc = barycentric_transform(hit_location, *tri_a, *tri_b)
|
||||
new_loc = barycentric_transform(hit_location, *tri_a, *tri_b)
|
||||
world_co_3d.append(new_loc)
|
||||
|
||||
# Smooth points
|
||||
if smooth_level:
|
||||
old_co_3d = [s[1] for s in stroke_data]
|
||||
points_velocity = [b-a for a, b in zip(old_co_3d, world_co_3d)]
|
||||
|
||||
# Average of points
|
||||
for i in range(smooth_level + 1):
|
||||
points_velocity = [
|
||||
(points_velocity[i] + points_velocity[i + 1]) / 2 if i == 0 else
|
||||
(points_velocity[i] + points_velocity[i - 1]) / 2 if i == len(points_velocity) - 1 else
|
||||
(points_velocity[i - 1] + points_velocity[i] + points_velocity[i + 1]) / 3
|
||||
for i in range(len(points_velocity))
|
||||
]
|
||||
|
||||
world_co_3d = [a+b for a, b in zip(old_co_3d, points_velocity)]
|
||||
|
||||
## Reproject on plane
|
||||
new_world_co_3d = [intersect_line_plane(origin, p, plane_co, plane_no) for p in world_co_3d]
|
||||
new_local_co_3d = [co for coord in new_world_co_3d for co in self.gp.matrix_world.inverted() @ coord]
|
||||
|
|
|
@ -78,7 +78,7 @@ class GP_OT_interpolate_stroke(bpy.types.Operator):
|
|||
|
||||
# print('----')
|
||||
|
||||
tgt_strokes = [s for s in gp.data.layers.active.active_frame.strokes if s.select]
|
||||
tgt_strokes = [s for l in self.layers for s in l.active_frame.strokes if s.select]
|
||||
|
||||
## If nothing selected in sculpt/paint, Select all before triggering
|
||||
if not tgt_strokes and context.mode in ('SCULPT_GPENCIL', 'PAINT_GPENCIL'):
|
||||
|
@ -247,7 +247,7 @@ class GP_OT_interpolate_stroke(bpy.types.Operator):
|
|||
origin = scn.camera.matrix_world.to_translation()
|
||||
# origin = np.array(scn.camera.matrix_world.to_translation(), 'float64')
|
||||
plan_co, plane_no = get_gp_draw_plane(gp)
|
||||
bpy.ops.gpencil.paste()
|
||||
bpy.ops.gpencil.paste(type="LAYER")
|
||||
|
||||
if settings.method == 'BONE':
|
||||
bone_plane = plane_on_bone(settings.target_rig.pose.bones.get(settings.target_bone),
|
||||
|
@ -257,10 +257,10 @@ class GP_OT_interpolate_stroke(bpy.types.Operator):
|
|||
|
||||
dg = bpy.context.evaluated_depsgraph_get()
|
||||
matrix_inv = np.array(gp.matrix_world.inverted(), dtype='float64')#.inverted()
|
||||
new_strokes = gp.data.layers.active.active_frame.strokes[-len(strokes_data):]
|
||||
new_strokes = [(l, s) for l in self.layers for s in l.active_frame.strokes if s.select]
|
||||
|
||||
# for new_stroke, stroke_data in zip(new_strokes, strokes_data):
|
||||
for new_stroke, stroke_data in zip(reversed(new_strokes), reversed(strokes_data)):
|
||||
for (layer, new_stroke), stroke_data in zip(reversed(new_strokes), reversed(strokes_data)):
|
||||
world_co_3d = []
|
||||
for stroke, point_co, object_hit, hit_location, tri_a, tri_indices in stroke_data:
|
||||
eval_ob = object_hit.evaluated_get(dg)
|
||||
|
@ -316,7 +316,7 @@ class GP_OT_interpolate_stroke(bpy.types.Operator):
|
|||
if len(sublist) == 1:
|
||||
continue
|
||||
|
||||
ns = gp.data.layers.active.active_frame.strokes.new()
|
||||
ns = layer.active_frame.strokes.new()
|
||||
for elem in ('hardness', 'material_index', 'line_width'):
|
||||
setattr(ns, elem, getattr(new_stroke, elem))
|
||||
|
||||
|
@ -326,7 +326,7 @@ class GP_OT_interpolate_stroke(bpy.types.Operator):
|
|||
setattr(ns.points[i], elem, getattr(new_stroke.points[point_index], elem))
|
||||
|
||||
## Delete original stroke
|
||||
gp.data.layers.active.active_frame.strokes.remove(new_stroke)
|
||||
layer.active_frame.strokes.remove(new_stroke)
|
||||
|
||||
wm.progress_end() # Pgs
|
||||
|
||||
|
|
|
@ -27,10 +27,6 @@ class GP_OT_interpolate_stroke_tri(GP_OT_interpolate_stroke_base):
|
|||
|
||||
origin = scn.camera.matrix_world.to_translation()
|
||||
|
||||
tgt_strokes = self.get_stroke_to_interpolate(context)
|
||||
if isinstance(tgt_strokes, set):
|
||||
return tgt_strokes
|
||||
|
||||
## Prepare context manager
|
||||
attrs = [
|
||||
# (context.view_layer.objects, 'active', self.gp),
|
||||
|
@ -55,7 +51,7 @@ class GP_OT_interpolate_stroke_tri(GP_OT_interpolate_stroke_base):
|
|||
|
||||
self.strokes_data = []
|
||||
|
||||
for stroke in tgt_strokes:
|
||||
for stroke in self.strokes:
|
||||
stroke_data = []
|
||||
for point in stroke.points:
|
||||
point_co_world = self.gp.matrix_world @ point.co
|
||||
|
@ -91,12 +87,13 @@ class GP_OT_interpolate_stroke_tri(GP_OT_interpolate_stroke_base):
|
|||
scn = context.scene
|
||||
origin = scn.camera.matrix_world.to_translation()
|
||||
plane_co, plane_no = get_gp_draw_plane(self.gp)
|
||||
bpy.ops.gpencil.paste()
|
||||
bpy.ops.gpencil.paste(type='LAYER')
|
||||
|
||||
dg = bpy.context.evaluated_depsgraph_get()
|
||||
|
||||
## List of newly pasted strokes (using range)
|
||||
new_strokes = self.gp.data.layers.active.active_frame.strokes[-len(self.strokes_data):]
|
||||
new_strokes = [s for l in self.layers for s in l.active_frame.strokes if s.select]
|
||||
#new_strokes = self.gp.data.layers.active.active_frame.strokes[-len(self.strokes_data):]
|
||||
|
||||
## Get user triangle position at current frame
|
||||
tri_b = []
|
||||
|
|
|
@ -0,0 +1,275 @@
|
|||
import bpy
|
||||
from time import time
|
||||
|
||||
import bpy
|
||||
#from bpy_extras.object_utils import world_to_camera_view
|
||||
from mathutils import Vector
|
||||
from mathutils.kdtree import KDTree
|
||||
from math import tan
|
||||
|
||||
|
||||
from mathutils.geometry import (barycentric_transform,
|
||||
intersect_line_plane)
|
||||
|
||||
from ..utils import (triangle_normal,
|
||||
get_gp_draw_plane, load_datablock)
|
||||
|
||||
from ..constants import RESOURCES_DIR
|
||||
from .operators import GP_OT_interpolate_stroke_base
|
||||
|
||||
|
||||
def world_to_camera_view(scene, obj, coord):
|
||||
"""
|
||||
Returns the camera space coords for a 3d point.
|
||||
(also known as: normalized device coordinates - NDC).
|
||||
|
||||
Where (0, 0) is the bottom left and (1, 1)
|
||||
is the top right of the camera frame.
|
||||
values outside 0-1 are also supported.
|
||||
A negative 'z' value means the point is behind the camera.
|
||||
|
||||
Takes shift-x/y, lens angle and sensor size into account
|
||||
as well as perspective/ortho projections.
|
||||
|
||||
:arg scene: Scene to use for frame size.
|
||||
:type scene: :class:`bpy.types.Scene`
|
||||
:arg obj: Camera object.
|
||||
:type obj: :class:`bpy.types.Object`
|
||||
:arg coord: World space location.
|
||||
:type coord: :class:`mathutils.Vector`
|
||||
:return: a vector where X and Y map to the view plane and
|
||||
Z is the depth on the view axis.
|
||||
:rtype: :class:`mathutils.Vector`
|
||||
"""
|
||||
from mathutils import Vector
|
||||
|
||||
co_local = obj.matrix_world.normalized().inverted() @ coord
|
||||
z = -co_local.z
|
||||
|
||||
camera = obj.data
|
||||
frame = [v for v in camera.view_frame(scene=scene)[:3]]
|
||||
if camera.type != 'ORTHO':
|
||||
if z == 0.0:
|
||||
return Vector((0.5, 0.5, 0.0))
|
||||
else:
|
||||
frame = [-(v / (v.z / z)) for v in frame]
|
||||
|
||||
min_x, max_x = frame[2].x, frame[1].x
|
||||
min_y, max_y = frame[1].y, frame[0].y
|
||||
|
||||
x = (co_local.x - min_x) / (max_x - min_x)
|
||||
y = (co_local.y - min_y) / (max_y - min_y)
|
||||
|
||||
return Vector((x, y, z))
|
||||
|
||||
def camera_view_to_world(scene, obj, coord):
|
||||
"""Reverse function of world_to_camera_view"""
|
||||
|
||||
frame = [obj.matrix_world @ co for co in obj.data.view_frame(scene=scene)]
|
||||
|
||||
x, y, z = coord
|
||||
|
||||
right_interp = frame[1] + y * (frame[0] - frame[1])
|
||||
# Interpolate along x-axis (left side)
|
||||
left_interp = frame[2] + y * (frame[3] - frame[2])
|
||||
|
||||
# Interpolate along y-axis
|
||||
return Vector(left_interp + x * (right_interp - left_interp))
|
||||
|
||||
|
||||
class GP_OT_interpolate_stroke_velocity(GP_OT_interpolate_stroke_base):
|
||||
bl_idname = "gp.interpolate_stroke_velocity"
|
||||
bl_label = "Interpolate Stroke"
|
||||
bl_description = 'Interpolate Stroke based on velocity'
|
||||
bl_options = {'REGISTER', 'UNDO'}
|
||||
|
||||
def invoke(self, context, event):
|
||||
if state := super().invoke(context, event):
|
||||
return state
|
||||
|
||||
scn = bpy.context.scene
|
||||
settings = context.scene.gp_interpo_settings
|
||||
col = settings.target_collection
|
||||
|
||||
## Prepare context manager
|
||||
attrs = [
|
||||
# (context.view_layer.objects, 'active', self.gp),
|
||||
(context.tool_settings, 'use_keyframe_insert_auto', True),
|
||||
# (bpy.context.scene.render, 'simplify_subdivision', 0),
|
||||
]
|
||||
self.apply_and_store(attrs)
|
||||
|
||||
velocity_mesh = bpy.data.meshes.new('interpolate_velocity')
|
||||
velocity_ob = bpy.data.objects.new('interpolate_velocity', velocity_mesh)
|
||||
|
||||
self.velocity_node_group = load_datablock(RESOURCES_DIR/'nodes.blend', 'Velocity Grid', type='node_groups', link=False)
|
||||
|
||||
instance_col_mod = velocity_ob.modifiers.new('IngestCollection', 'NODES')
|
||||
ingest_node_group = load_datablock(RESOURCES_DIR/'nodes.blend', 'Ingest Collection', type='node_groups', link=False)
|
||||
instance_col_mod.node_group = ingest_node_group
|
||||
instance_col_mod["Socket_2"] = col
|
||||
|
||||
scn.collection.objects.link(velocity_ob)
|
||||
|
||||
# Apply instance collection modifier
|
||||
dg = bpy.context.evaluated_depsgraph_get()
|
||||
eval_ob = velocity_ob.evaluated_get(dg)
|
||||
eval_data = eval_ob.data.copy()
|
||||
|
||||
velocity_ob.modifiers.remove(instance_col_mod)
|
||||
velocity_ob.data = eval_data
|
||||
|
||||
bpy.data.node_groups.remove(ingest_node_group)
|
||||
|
||||
self.velocity_ob = velocity_ob
|
||||
|
||||
if self.debug:
|
||||
self.scan_time = time()-self.start
|
||||
print(f'Scan time {self.scan_time:.4f}s')
|
||||
|
||||
# Baking Camera
|
||||
self.camera = scn.camera.copy()
|
||||
self.camera.data = self.camera.data.copy()
|
||||
self.camera.animation_data_clear()
|
||||
self.camera.data.animation_data_clear()
|
||||
|
||||
cam_mat = self.camera.matrix_world.copy()
|
||||
self.camera.animation_data_clear()
|
||||
self.camera.parent = None
|
||||
self.camera.matrix_world = cam_mat
|
||||
|
||||
# Store curent gp matrix
|
||||
self.gp_matrix = self.gp.matrix_world.copy()
|
||||
|
||||
# Ensure whole stroke are selected before copy
|
||||
|
||||
bpy.ops.gpencil.select_linked()
|
||||
# Copy stroke selection
|
||||
bpy.ops.gpencil.copy()
|
||||
|
||||
# Jump frame and paste
|
||||
# if self.report_progress:
|
||||
# context.window_manager.progress_begin(self.frames_to_jump[0], self.frames_to_jump[-1]) # Pgs
|
||||
# context.area.header_text_set('Starting interpolation | Esc: Cancel')
|
||||
|
||||
context.window_manager.modal_handler_add(self)
|
||||
return {'RUNNING_MODAL'}
|
||||
|
||||
def interpolate_frame(self, context):
|
||||
scn = context.scene
|
||||
cam = scn.camera
|
||||
#dg = bpy.context.evaluated_depsgraph_get()
|
||||
col = self.settings.target_collection
|
||||
smooth_level = self.settings.smooth_level
|
||||
|
||||
origin = scn.camera.matrix_world.to_translation()
|
||||
plane_co, plane_no = get_gp_draw_plane(self.gp)
|
||||
|
||||
#print("interpolate_frame")
|
||||
|
||||
print(self.gp_matrix)
|
||||
print(self.gp.matrix_world)
|
||||
|
||||
velocity_ob = self.velocity_ob
|
||||
|
||||
velocity_ob.hide_set(True)
|
||||
|
||||
|
||||
grid_velocity_mod = velocity_ob.modifiers.new('VelocityGrid', 'NODES')
|
||||
grid_velocity_mod.node_group = self.velocity_node_group
|
||||
grid_velocity_mod["Socket_2"] = col
|
||||
grid_velocity_mod["Socket_4"] = self.camera
|
||||
grid_velocity_mod["Socket_5"] = self.camera.data.angle
|
||||
grid_velocity_mod["Socket_6"] = self.camera.data.shift_x
|
||||
grid_velocity_mod["Socket_7"] = self.camera.data.shift_y
|
||||
|
||||
#raise Exception()
|
||||
|
||||
# Apply velocity grid modifier
|
||||
dg = bpy.context.evaluated_depsgraph_get()
|
||||
eval_ob = velocity_ob.evaluated_get(dg)
|
||||
#eval_data = eval_ob.data.copy()
|
||||
grid_ob = bpy.data.objects.new('Velocity Grid Object', eval_ob.data.copy())
|
||||
|
||||
#copy_ob = velocity_ob.copy()
|
||||
#scn.collection.objects.link(copy_ob)
|
||||
|
||||
velocity_ob.modifiers.remove(grid_velocity_mod)
|
||||
#velocity_ob.data = eval_data
|
||||
|
||||
|
||||
#Create kd tree for finding nearest points
|
||||
kd = KDTree(len(grid_ob.data.vertices))
|
||||
|
||||
points = [0, 0, 0] * len(grid_ob.data.vertices)
|
||||
grid_ob.data.vertices.foreach_get('co', points)
|
||||
|
||||
for i in range(0, len(points), 3):
|
||||
kd.insert(points[i:i+3], int(i/3))
|
||||
|
||||
kd.balance()
|
||||
|
||||
#nb_strokes = len(self.gp.data.layers.active.active_frame.strokes)
|
||||
bpy.ops.gpencil.paste(type='LAYER')
|
||||
|
||||
## List of newly pasted strokes (using range)
|
||||
new_strokes = [s for l in self.layers for s in l.active_frame.strokes if s.select]
|
||||
#new_strokes = self.gp.data.layers.active.active_frame.strokes[-nb_strokes:]
|
||||
|
||||
velocity_attr = grid_ob.data.attributes["velocity"].data
|
||||
|
||||
for stroke in new_strokes:
|
||||
points = [0, 0, 0] * len(stroke.points)
|
||||
stroke.points.foreach_get('co', points)
|
||||
|
||||
points_2d = [world_to_camera_view(scn, self.camera, self.gp_matrix @ Vector(points[i:i+3])) for i in range(0, len(points), 3)]
|
||||
points_2d = [Vector((p.x, p.y, 0)) for p in points_2d] # Remove Z component
|
||||
|
||||
points_velocity = [velocity_attr[kd.find(p)[1]].vector for p in points_2d]
|
||||
|
||||
if smooth_level:
|
||||
# Average of points
|
||||
for i in range(smooth_level + 1):
|
||||
points_velocity = [
|
||||
(points_velocity[i] + points_velocity[i + 1]) / 2 if i == 0 else
|
||||
(points_velocity[i] + points_velocity[i - 1]) / 2 if i == len(points_velocity) - 1 else
|
||||
(points_velocity[i - 1] + points_velocity[i] + points_velocity[i + 1]) / 3
|
||||
for i in range(len(points_velocity))
|
||||
]
|
||||
|
||||
new_points_3d = [camera_view_to_world(scn, cam, p+vel) for p, vel in zip(points_2d, points_velocity)]
|
||||
|
||||
## Reproject on plane
|
||||
new_points_3d = [intersect_line_plane(origin, p, plane_co, plane_no) for p in new_points_3d]
|
||||
|
||||
stroke.points.foreach_set('co', [v for p in new_points_3d for v in self.gp.matrix_world.inverted() @p])
|
||||
|
||||
#stroke.points.foreach_set('co', [v for p in points_2d for v in self.gp.matrix_world.inverted() @p])
|
||||
stroke.points.update()
|
||||
|
||||
#velocity_ob.modifiers.remove(grid_velocity_mod)
|
||||
|
||||
bpy.data.meshes.remove(grid_ob.data)
|
||||
|
||||
def exit(self, context, status='INFO', text=None, cancelled=False):
|
||||
out = super().exit(context, status='INFO', text=None, cancelled=False)
|
||||
|
||||
bpy.data.node_groups.remove(self.velocity_node_group)
|
||||
bpy.data.meshes.remove(self.velocity_ob.data)
|
||||
bpy.data.cameras.remove(self.camera.data)
|
||||
|
||||
return out
|
||||
|
||||
|
||||
classes = (
|
||||
GP_OT_interpolate_stroke_velocity,
|
||||
)
|
||||
|
||||
def register():
|
||||
for c in classes:
|
||||
bpy.utils.register_class(c)
|
||||
|
||||
|
||||
def unregister():
|
||||
for c in reversed(classes):
|
||||
bpy.utils.unregister_class(c)
|
|
@ -21,6 +21,7 @@ class GP_PG_interpolate_settings(PropertyGroup):
|
|||
('OBJECT', 'Object Geometry', 'Same as Geometry mode, but target only a specific object, even if occluded (ignore all the others)', 1),
|
||||
('BONE', 'Bone', 'Pick an armature bone and follow it', 2),
|
||||
('TRI', 'Triangle', 'Interpolate based on triangle traced manually over geometry', 3),
|
||||
('VELOCITY', 'Velocity', 'Interpolate based on velocity, works well for point outside geometry', 4)
|
||||
),
|
||||
default='GEOMETRY',
|
||||
description='Select method for interpolating strokes'
|
||||
|
@ -83,6 +84,11 @@ class GP_PG_interpolate_settings(PropertyGroup):
|
|||
default=True,
|
||||
description='Apply rotation of the bone') # Bone
|
||||
|
||||
#selection: EnumProperty(default='SELECTED', items=[("SELECTED", "Selected", ""), ("ALL", "All", "")],
|
||||
# description="Stroke to interpolate")
|
||||
|
||||
smooth_level: IntProperty(default=2, min=0, max=10, name='Smooth Level')
|
||||
|
||||
classes = (
|
||||
GP_PG_interpolate_settings,
|
||||
)
|
||||
|
|
Binary file not shown.
15
ui.py
15
ui.py
|
@ -36,7 +36,13 @@ class GP_PT_interpolate(bpy.types.Panel):
|
|||
row.scale_y = 1.2
|
||||
direction_button_row = row.row(align=True)
|
||||
direction_button_row.scale_x = 3
|
||||
ops_id = "gp.interpolate_stroke_tri" if settings.method == 'TRI' else "gp.interpolate_stroke"
|
||||
ops_id = "gp.interpolate_stroke"
|
||||
if settings.method == 'TRI':
|
||||
ops_id = "gp.interpolate_stroke_tri"
|
||||
elif settings.method == 'VELOCITY':
|
||||
ops_id = "gp.interpolate_stroke_velocity"
|
||||
|
||||
|
||||
direction_button_row.operator(ops_id, text=prev_text, icon=prev_icon).next = False
|
||||
direction_button_row.operator(ops_id, text=next_text, icon=next_icon).next = True
|
||||
|
||||
|
@ -59,10 +65,17 @@ class GP_PT_interpolate(bpy.types.Panel):
|
|||
elif settings.method == 'GEOMETRY':
|
||||
col.prop(settings, 'search_range')
|
||||
col.prop(settings, 'remove_occluded')
|
||||
col.prop(settings, 'smooth_level', text='Smooth')
|
||||
|
||||
elif settings.method == 'OBJECT':
|
||||
col.prop(settings, 'search_range')
|
||||
col.prop(settings, 'target_object', text='Object')
|
||||
col.prop(settings, 'smooth_level', text='Smooth')
|
||||
|
||||
elif settings.method == 'VELOCITY':
|
||||
col.prop(settings, 'target_collection', text='Collection')
|
||||
col.prop(settings, 'target_object', text='Object')
|
||||
col.prop(settings, 'smooth_level', text='Smooth')
|
||||
|
||||
col.separator()
|
||||
col = layout.column(align=True)
|
||||
|
|
60
utils.py
60
utils.py
|
@ -2,6 +2,9 @@ import bpy
|
|||
import math
|
||||
|
||||
import numpy as np
|
||||
import fnmatch
|
||||
import os
|
||||
from pathlib import Path
|
||||
|
||||
from math import tan
|
||||
from mathutils import Vector, Matrix
|
||||
|
@ -35,6 +38,63 @@ class attr_set():
|
|||
|
||||
# --- Vector
|
||||
|
||||
def load_datablock(filepath, *names, type='objects', link=True, expr=None, assets_only=False,
|
||||
relative_to=None):
|
||||
"""link or append elements from another blender scene
|
||||
|
||||
Args:
|
||||
filepath (str): filepath of the scene to import objects from
|
||||
names (list[str]): names of datablocks to import.
|
||||
type (str, optional): type of data to import.
|
||||
Defaults to 'objects'.
|
||||
link (bool, optional): true if we want to import as link, else append.
|
||||
Defaults to True.
|
||||
expr (str, optional): pattern of names to import.
|
||||
Defaults to None.
|
||||
assets_only (bool, optional): If true, import only data-blocks marked as assets.
|
||||
Defaults to False.
|
||||
relative_to (str|Path|bool, optionnal): If str or Path and link make path relative to it
|
||||
if False make path absolute, if None use preferences
|
||||
Defaults to None.
|
||||
|
||||
Returns:
|
||||
list|bpy.types.Object: datablocks imported
|
||||
"""
|
||||
|
||||
# convert names from tuple to list to get the correct datablock type (blender tricks)
|
||||
names = list(names)
|
||||
|
||||
if isinstance(expr, str):
|
||||
pattern = expr
|
||||
expr = lambda x: fnmatch(x, pattern)
|
||||
|
||||
with bpy.data.libraries.load(str(filepath), link=link, assets_only=assets_only) as (data_from, data_to):
|
||||
datablocks = getattr(data_from, type)
|
||||
if expr:
|
||||
names += [i for i in datablocks if expr(i)]
|
||||
elif not names:
|
||||
names = datablocks
|
||||
|
||||
setattr(data_to, type, names)
|
||||
|
||||
datablocks = getattr(data_to, type)
|
||||
|
||||
if link and datablocks:
|
||||
lib = datablocks[0].library
|
||||
lib_path = os.path.abspath(bpy.path.abspath(lib.filepath))
|
||||
|
||||
if relative_to is False:
|
||||
lib.filepath = lib_path
|
||||
elif isinstance(relative_to, (str, Path)):
|
||||
lib.filepath = bpy.path.relpath(lib_path, start=str(relative_to))
|
||||
|
||||
if len(names) > 1:
|
||||
return datablocks
|
||||
|
||||
if datablocks:
|
||||
return datablocks[0]
|
||||
|
||||
|
||||
def triangle_normal(p1, p2, p3):
|
||||
"""
|
||||
Calculate the normal of a triangle given its three vertices.
|
||||
|
|
Loading…
Reference in New Issue