I'd like to get the coordinates of my selected Grease Pencil points and move them in relation to the 2D Camera View Plane coordinates (as you do when you use the View Transform Orientation).

View Orientation

For example, if the scene's render resolution is square and the camera view is 1080x1080, the top left corner of the Camera View Plane would be 0,0 and the bottom right corner would be 1080,1080, making this line's top point 494,118 and the bottom point 494,954 Grease Pencil Points

I found this post, Coordinates of corners of camera view border, which shows how to get the camera's view relative to the area. How could I (& should I) take a similar approach, which seems to be:

Get the camera frame in pixels

  • getting the View Frame's 4 points for the cameras frame
  • convert to world space
  • convert to pixel space with location_3d_to_region_2d

Get the Grease Pencil points in pixels:

  • convert to world space based on Grease Pencil object's matrix?
  • convert to pixel space with location_3d_to_region_2d?
  • move the points in pixels based on the Camera View coordinates?
  • convert back to the Grease Pencil's object space?

Any guidance on how to go about this would be most appreciated. Thank you!


1 Answer 1


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
  • $\begingroup$ This is SOOOOOO very helpful and brilliant. Thank you so much Sietse for taking the time!!!!! $\endgroup$ Jan 19, 2023 at 19:01

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .