269 lines
9.7 KiB
Python
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)
|