gp_interpolate/interpolate_strokes/operators_velocity.py

269 lines
9.7 KiB
Python

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
if not self.settings.target_object and not self.settings.target_collection:
self.report({"ERROR"}, "No collection of object specified")
return {"CANCELLED"}
scn = bpy.context.scene
settings = context.scene.gp_interpo_settings
## 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_3"] = settings.target_object
instance_col_mod["Socket_2"] = settings.target_collection
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()
target_object = self.settings.target_object
target_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)
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_8"] = target_object
grid_velocity_mod["Socket_2"] = target_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
# 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()
# copy_ob.data = copy_ob.data.copy()
# scn.collection.objects.link(copy_ob)
velocity_ob.modifiers.remove(grid_velocity_mod)
#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()
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]
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])
#new_points_2d = [ p+vel for p, vel in zip(points_2d, points_velocity)]
#stroke.points.foreach_set('co', [v for p in new_points_2d for v in self.gp.matrix_world.inverted() @p])
stroke.points.update()
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)