diff --git a/utils.py b/utils.py index f768437..c74adb1 100644 --- a/utils.py +++ b/utils.py @@ -590,8 +590,9 @@ 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 +"""## Works, but do not copy all attributes type (probably ok for GP though) +def bulk_frame_copy_attributes(source_attr, target_attr): + '''Get and apply data as flat numpy array based on attribute type''' if source_attr.data_type == 'INT': data = np.empty(len(source_attr.data), dtype=np.int32) source_attr.data.foreach_get('value', data) @@ -617,62 +618,105 @@ def bulk_copy_attributes(source_attr, target_attr): 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) - ''' +## works in slowmotion (keep as reference for testing) +# def copy_attribute_values(src_dr, dst_dr, source_attr, dest_attr, data_size): +# ## Zip method to copy one by one +# val_type = {'FLOAT_COLOR': 'color','FLOAT_VECTOR': 'vector'}.get(source_attr.data_type, 'value') +# src_start = src_dr.curve_offsets[0].value +# src_end = src_start + data_size +# dst_start = dst_dr.curve_offsets[0].value +# dst_end = dst_start + data_size +# for src_idx, dest_idx in zip(range(src_start, src_end),range(dst_start, dst_end)): +# setattr(dest_attr.data[dest_idx], val_type, getattr(source_attr.data[src_idx], val_type)) +""" + +attribute_value_string = { + 'FLOAT': "value", + 'INT': "value", + 'FLOAT_VECTOR': "vector", + 'FLOAT_COLOR': "color", + 'BYTE_COLOR': "color", + 'STRING': "value", + 'BOOLEAN': "value", + 'FLOAT2': "value", + 'INT8': "value", + 'INT32_2D': "value", + 'QUATERNION': "value", + 'FLOAT4X4': "value", +} + +attribute_value_dtype = { + 'FLOAT': np.float32, + 'INT': np.dtype('int'), + 'FLOAT_VECTOR': np.float32, + 'FLOAT_COLOR': np.float32, + 'BYTE_COLOR': np.int8, + 'STRING': np.dtype('str'), + 'BOOLEAN': np.dtype('bool'), + 'FLOAT2': np.float32, + 'INT8': np.int8, + 'INT32_2D': np.dtype('int'), + 'QUATERNION': np.float32, + 'FLOAT4X4': np.float32, +} + +attribute_value_shape = { + 'FLOAT': (), + 'INT': (), + 'FLOAT_VECTOR': (3,), + 'FLOAT_COLOR': (4,), + 'BYTE_COLOR': (4,), + 'STRING': (), + 'BOOLEAN': (), + 'FLOAT2':(2,), + 'INT8': (), + 'INT32_2D': (2,), + 'QUATERNION': (4,), + 'FLOAT4X4': (4,4), +} -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 bulk_copy_attributes(source_attr, target_attr): + '''Get and apply data as flat numpy array based on attribute type''' + value_string = attribute_value_string[source_attr.data_type] + dtype = attribute_value_dtype[source_attr.data_type] + shape = attribute_value_shape[source_attr.data_type] + + domain_size = len(source_attr.data) + ## Need to pass attributes to get domain size + # domain_size = attributes.domain_size(source_attr.domain) + + # start = time() + data = np.empty((domain_size, *shape), dtype=dtype).ravel() + source_attr.data.foreach_get(value_string, data) + target_attr.data.foreach_set(value_string, data) + # end = time() + # np_empty = end - start + + ## np.prod (works, supposedly faster but tested slower) + # data = np.empty(int(domain_size * np.prod(shape)), dtype=dtype) + # source_attr.data.foreach_get(value_string, data) + # target_attr.data.foreach_set(value_string, data) + + + ## np.zeros (works, sometimes faster on big set of attributes) + # start = time() + # data = np.zeros((domain_size, *shape), dtype=dtype) + # source_attr.data.foreach_get(value_string, np.ravel(data)) + # target_attr.data.foreach_set(value_string, np.ravel(data)) + # end = time() + # np_zero = end - start + + # print('np EMPTY faster' if np_empty < np_zero else 'np ZERO faster', source_attr.domain, source_attr.data_type, domain_size) + # print('np_zero', np_zero) + # print('np_empty', np_empty) + # print() 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_copy_start = time() # time_dbg frame = layer.frames.new(frame_number) dr = frame.drawing dr.add_strokes([len(s.points) for s in source_drawing.strokes]) @@ -682,13 +726,18 @@ def copy_frame_at(source_frame, layer, frame_number): 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") + # start_time = time() # time_dbg-per-attrib + + # bulk_frame_copy_attributes(source_attr, target_attr) # only some attributes + bulk_copy_attributes(source_attr, target_attr) + # copy_attribute_values(source_drawing, dr, source_attr, target_attr, source_drawing.attributes.domain_size(source_attr.domain)) # super slow + + # end_time = time() # time_dbg-per-attrib + # print(f"copy_attribute '{attr_name}' execution time: {end_time - start_time} seconds") # time_dbg-per-attrib + + # frame_copy_end = time() # time_dbg + # print(f"frame copy execution time: {frame_copy_end - frame_copy_start} seconds") # time_dbg # ----------------- ### Vector utils 3d