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)