import bpy import mathutils from mathutils import Matrix, Vector from mathutils.geometry import intersect_line_plane from math import pi import numpy as np from time import time from .utils import (location_to_region, region_to_location) ## DISABLED (in init, also in menu append, see register below) """ ## Do not work on multiple object def batch_flat_reproject(obj, proj_type='VIEW', all_strokes=True, restore_frame=False): '''Reproject :all_stroke: affect hidden, locked layers ''' if restore_frame: oframe = bpy.context.scene.frame_current omode = bpy.context.mode # frame_list = [ f.frame_number for l in obj.data.layers for f in l.frames if len(f.drawing.strokes)] # frame_list = list(set(frame_list)) # frame_list.sort() # for fnum in frame_list: # bpy.context.scene.frame_current = fnum t0 = time() scn = bpy.context.scene laynum = len(obj.data.layers) for i, l in enumerate(obj.data.layers): ## \x1b[2K\r ? fnum = len(l.frames) zf = len(str(fnum)) for j, f in enumerate(reversed(l.frames)): # whynot... print(f'{obj.name} : {i+1}/{laynum} : {l.name} : {str(j+1).zfill(zf)}/{fnum}{" "*30}', end='\r') scn.frame_set(f.frame_number) # more chance to update the matrix bpy.context.view_layer.update() # update the matrix ? bpy.context.scene.camera.location = bpy.context.scene.camera.location scn.frame_current = f.frame_number for s in f.drawing.strokes: for p in s.points: p.position = obj.matrix_world.inverted() @ region_to_location(location_to_region(obj.matrix_world @ p.position), scn.cursor.location) if restore_frame: bpy.context.scene.frame_current = oframe print(' '*50,end='\x1b[1K\r') # clear the line print(f'{obj.name} ok ({time()-t0:.2f})') """ """ def batch_flat_reproject(obj): '''Reproject all strokes on 3D cursor for all existing frame of passed GP object''' scn = bpy.context.scene cam = scn.camera for l in obj.data.layers: for f in l.frames: scn.frame_set(f.frame_number) cam_mat = cam.matrix_local.copy() origin = cam.matrix_world.to_translation() mat_inv = obj.matrix_world.inverted() plane_no = Vector((0,0,1)) plane_no.rotate(cam_mat) plane_co = scn.cursor.location for s in f.drawing.strokes: points_co = [obj.matrix_world @ p.position for p in s.points] points_co = [mat_inv @ intersect_line_plane(origin, p, plane_co, plane_no) for p in points_co] points_co = [co for vector in points_co for co in vector] s.points.foreach_set('co', points_co) s.points.add(1) # update s.points.pop() # update #for p in s.points: # loc_2d = location_to_region(obj.matrix_world @ p.position) # p.position = obj.matrix_world.inverted() @ region_to_location(loc_2d, scn.cursor.location) """ def batch_flat_reproject(obj): '''Reproject strokes of passed GP object on 3D cursor full scene range''' scn = bpy.context.scene cam = scn.camera for i in range(scn.frame_start, scn.frame_end + 1): scn.frame_set(i) cam_mat = cam.matrix_local.copy() origin = cam.matrix_world.to_translation() mat_inv = obj.matrix_world.inverted() plane_no = Vector((0,0,1)) plane_no.rotate(cam_mat) plane_co = scn.cursor.location for l in obj.data.layers: f = l.current_frame() if not f: # No active frame continue if f.frame_number != scn.frame_current: f = l.frames.copy(f) # duplicate content of the previous frame for s in f.drawing.strokes: points_co = [obj.matrix_world @ p.position for p in s.points] points_co = [mat_inv @ intersect_line_plane(origin, p, plane_co, plane_no) for p in points_co] points_co = [co for vector in points_co for co in vector] s.points.foreach_set('co', points_co) s.points.add(1) # update s.points.pop() # update class GPTB_OT_batch_flat_reproject(bpy.types.Operator): bl_idname = "gp.batch_flat_reproject" bl_label = "Flat Reproject Selected On cursor" bl_description = "Reproject all frames of all selected gp object on cursor" bl_options = {"REGISTER"} @classmethod def poll(cls, context): return context.object and context.object.type == 'GREASEPENCIL' def execute(self, context): for o in context.selected_objects: if o.type != 'GREASEPENCIL' or not o.select_get(): continue batch_flat_reproject(o) return {"FINISHED"} ### -- MENU ENTRY -- def flat_reproject_clean_menu(self, context): if context.mode == 'EDIT_GREASE_PENCIL': self.layout.operator_context = 'INVOKE_REGION_WIN' # needed for popup (also works with 'INVOKE_DEFAULT') self.layout.operator('gp.batch_flat_reproject', icon='KEYTYPE_JITTER_VEC') def flat_reproject_context_menu(self, context): if context.mode == 'EDIT_GREASE_PENCIL' and context.scene.tool_settings.gpencil_selectmode_edit == 'STROKE': self.layout.operator_context = 'INVOKE_REGION_WIN' # needed for popup self.layout.operator('gp.batch_flat_reproject', icon='KEYTYPE_JITTER_VEC') classes = ( GPTB_OT_batch_flat_reproject, ) def register(): for cl in classes: bpy.utils.register_class(cl) # bpy.types.VIEW3D_MT_grease_pencil_edit_context_menu.append(flat_reproject_context_menu) # bpy.types.GPENCIL_MT_cleanup.append(flat_reproject_clean_menu) def unregister(): # bpy.types.GPENCIL_MT_cleanup.remove(flat_reproject_clean_menu) # bpy.types.VIEW3D_MT_grease_pencil_edit_context_menu.remove(flat_reproject_context_menu) for cl in reversed(classes): bpy.utils.unregister_class(cl)