Well, you already sketched to steps quite right. In the code below all the points of a Grease Pencil stroke are moved following this approach:
- Convert the point to world space, by applying layer and object transforms (location, rotation, scale).
- Convert the world coordinates to 2D space.
- Move the point in relation to the 2D camera view. (In this example the points are moved to the right with an offset of 30% of the view frame width.)
- Convert these 'moved' 2D coordinates back to 3D space.
- And convert the 'moved' 3D coordinates to the stroke's local space, by applying the inverted layer and object transforms.
So, in short, a lot of converting back and forth to get what you want!
import bpy
import mathutils
from bpy_extras.view3d_utils import location_3d_to_region_2d, region_2d_to_location_3d
# Find first 3D view in active screen
# From: https://blender.stackexchange.com/questions/6377/coordinates-of-corners-of-camera-view-border
def view3d_find():
for area in bpy.context.window.screen.areas:
if area.type == 'VIEW_3D':
v3d = area.spaces[0]
rv3d = v3d.region_3d
for region in area.regions:
if region.type == 'WINDOW':
return region, rv3d
return None, None
# Get 3D view region, for converting 3D coordinates to 2D
region, rv3d = view3d_find()
# Get camera view frame in 2D coordinates
# From: https://blender.stackexchange.com/questions/6377/coordinates-of-corners-of-camera-view-border
camera = bpy.context.scene.camera
view_frame = camera.data.view_frame(scene=bpy.context.scene)
view_frame = [camera.matrix_world @ v for v in view_frame]
view_frame_2d = [location_3d_to_region_2d(region, rv3d, v) for v in view_frame]
# Get x min and max of view frame
view_frame_min_x = view_frame_2d[2][0]
view_frame_max_x = view_frame_2d[0][0]
# For brevity, we assume the active object is a grease pencil object
obj = bpy.context.active_object
# Get active layer
layer = obj.data.layers.active
# For this example, get first stroke in layer (we assume there is one)
stroke = layer.active_frame.strokes[0]
# Move all points in 2D space
for point in stroke.points:
# Get world position of point, by applying
# layer transform and GP object transform.
# Note: the order here matters.
world_co = obj.matrix_world @ layer.matrix_layer @ point.co
# Convert world coordinates to 2D space
co_2d = location_3d_to_region_2d(region, rv3d, world_co)
# As an example, move the point to the right
# with an offset of 30% of the view frame width.
delta_x = (view_frame_max_x - view_frame_min_x) * 0.30
co_2d += mathutils.Vector((delta_x, 0))
# Convert 2D coordinates back to 3D space,
# using the original world location as depth location.
# See: https://docs.blender.org/api/current/bpy_extras.view3d_utils.html#bpy_extras.view3d_utils.region_2d_to_location_3d
co_3d = region_2d_to_location_3d(region, rv3d, co_2d, world_co)
# Convert this world location to stroke local space, by applying
# the inversed transforms of GP layer and object.
# Note: the order here matters.
local_co = layer.matrix_inverse_layer @ obj.matrix_world.inverted() @ co_3d
# Assign to point
point.co = local_co