diff --git a/OP_key_duplicate_send.py b/OP_key_duplicate_send.py index 6be3e62..514c91b 100644 --- a/OP_key_duplicate_send.py +++ b/OP_key_duplicate_send.py @@ -1,5 +1,6 @@ import bpy from bpy.types import Operator +from . import utils def get_layer_list(self, context): @@ -65,26 +66,24 @@ class GPTB_OT_duplicate_send_to_layer(Operator) : ## Remove overlapping frames for f in reversed(to_replace): - target_layer.frames.remove(f) - - # FIXME : need to createa a dedicated function to copy a frame + target_layer.frames.remove(f.frame_number) + ## Copy original frames for f in selected_frames: - target_layer.frames.copy(f) + utils.copy_frame_at(f, target_layer, f.frame_number) + # target_layer.frames.copy(f) # GPv2 sent = len(selected_frames) ## Delete original frames as an option if self.delete_source: for f in reversed(selected_frames): - act_layer.frames.remove(f) + act_layer.frames.remove(f.frame_number) + mess = f'{sent} keys moved' + else: + mess = f'{sent} keys copied' - mess = f'{sent} keys copied' if replaced: mess += f' ({replaced} replaced)' - - mod = context.mode - bpy.ops.gpencil.editmode_toggle() - bpy.ops.object.mode_set(mode=mod) self.report({'INFO'}, mess) return {'FINISHED'} diff --git a/utils.py b/utils.py index b5c75db..f768437 100644 --- a/utils.py +++ b/utils.py @@ -5,6 +5,7 @@ import mathutils import math import subprocess +from time import time from math import sqrt from mathutils import Vector from sys import platform @@ -589,6 +590,106 @@ def copy_stroke_to_frame(s, frame, select=True): ns.points.update() return ns +def bulk_copy_attributes(source_attr, target_attr): + # Get the data as flat numpy array based on attribute type and domain + if source_attr.data_type == 'INT': + data = np.empty(len(source_attr.data), dtype=np.int32) + source_attr.data.foreach_get('value', data) + target_attr.data.foreach_set('value', data) + elif source_attr.data_type == 'INT8': + data = np.empty(len(source_attr.data), dtype=np.int8) + source_attr.data.foreach_get('value', data) + target_attr.data.foreach_set('value', data) + elif source_attr.data_type == 'FLOAT': + data = np.empty(len(source_attr.data), dtype=np.float32) + source_attr.data.foreach_get('value', data) + target_attr.data.foreach_set('value', data) + elif source_attr.data_type == 'FLOAT_VECTOR': + data = np.empty(len(source_attr.data) * 3, dtype=np.float32) + source_attr.data.foreach_get('vector', data) + target_attr.data.foreach_set('vector', data) + elif source_attr.data_type == 'FLOAT_COLOR': + data = np.empty(len(source_attr.data) * 4, dtype=np.float32) + source_attr.data.foreach_get('color', data) + target_attr.data.foreach_set('color', data) + elif source_attr.data_type == 'BOOLEAN': + data = np.empty(len(source_attr.data), dtype=bool) + source_attr.data.foreach_get('value', data) + target_attr.data.foreach_set('value', data) + + ## Filter by domain ? (not needed it seem, dtypes are the same) + ''' + if source_attr.domain == 'POINT': + if source_attr.data_type == 'FLOAT': + data = np.empty(len(source_attr.data), dtype=np.float32) + source_attr.data.foreach_get('value', data) + target_attr.data.foreach_set('value', data) + elif source_attr.data_type == 'FLOAT_VECTOR': + data = np.empty(len(source_attr.data) * 3, dtype=np.float32) + source_attr.data.foreach_get('vector', data) + target_attr.data.foreach_set('vector', data) + elif source_attr.data_type == 'FLOAT_COLOR': + data = np.empty(len(source_attr.data) * 4, dtype=np.float32) + source_attr.data.foreach_get('color', data) + target_attr.data.foreach_set('color', data) + ## No Boolean by defaut on points domain + elif source_attr.domain == 'CURVE': + if source_attr.data_type == 'INT': + data = np.empty(len(source_attr.data), dtype=np.int32) + source_attr.data.foreach_get('value', data) + target_attr.data.foreach_set('value', data) + elif source_attr.data_type == 'INT8': + data = np.empty(len(source_attr.data), dtype=np.int8) + source_attr.data.foreach_get('value', data) + target_attr.data.foreach_set('value', data) + elif source_attr.data_type == 'FLOAT_COLOR': + data = np.empty(len(source_attr.data) * 4, dtype=np.float32) + source_attr.data.foreach_get('color', data) + target_attr.data.foreach_set('color', data) + elif source_attr.data_type == 'BOOLEAN': + data = np.empty(len(source_attr.data), dtype=bool) + source_attr.data.foreach_get('value', data) + target_attr.data.foreach_set('value', data) + ''' + + +def copy_attribute_values(src_dr, dst_dr, source_attr, dest_attr): + ## Zip method to copy one by one + ## One liner + # val_type = {'FLOAT_COLOR': 'color','FLOAT_VECTOR': 'vector'}.get(source_attr.data_type, 'value') + if source_attr.data_type == 'FLOAT_COLOR': + val_type = 'color' + elif source_attr.data_type == 'FLOAT_VECTOR': + val_type = 'vector' + else: + val_type = 'value' + ## /!\ DOESN'T WORK. index is wrong + for src_idx, dest_idx in zip( + range(src_dr.curve_offsets[0].value, src_dr.curve_offsets[-1].value), + range(dst_dr.curve_offsets[0].value, dst_dr.curve_offsets[-1].value)): + setattr(dest_attr.data[dest_idx], val_type, getattr(source_attr.data[src_idx], val_type)) + +def copy_frame_at(source_frame, layer, frame_number): + '''Copy a frame (source_frame) to a layer at given frame_number''' + source_drawing = source_frame.drawing + + frame = layer.frames.new(frame_number) + dr = frame.drawing + dr.add_strokes([len(s.points) for s in source_drawing.strokes]) + for attr_name in source_drawing.attributes.keys(): + source_attr = source_drawing.attributes[attr_name] + if attr_name not in dr.attributes: + dr.attributes.new( + name=attr_name, type=source_attr.data_type, domain=source_attr.domain) + target_attr = dr.attributes[attr_name] + bulk_copy_attributes(source_attr, target_attr) + + ## Broken + # start_time = time() + # copy_attribute_values(source_drawing, dr, source_attr, target_attr) + # end_time = time() + # print(f"copy_attribute_values execution time: {end_time - start_time} seconds") + # ----------------- ### Vector utils 3d # -----------------