UI: File Save Incremental Operator #104678
|
@ -590,6 +590,7 @@ def km_window(params):
|
||||||
("wm.open_mainfile", {"type": 'O', "value": 'PRESS', "ctrl": True}, None),
|
("wm.open_mainfile", {"type": 'O', "value": 'PRESS', "ctrl": True}, None),
|
||||||
("wm.save_mainfile", {"type": 'S', "value": 'PRESS', "ctrl": True}, None),
|
("wm.save_mainfile", {"type": 'S', "value": 'PRESS', "ctrl": True}, None),
|
||||||
("wm.save_as_mainfile", {"type": 'S', "value": 'PRESS', "shift": True, "ctrl": True}, None),
|
("wm.save_as_mainfile", {"type": 'S', "value": 'PRESS', "shift": True, "ctrl": True}, None),
|
||||||
|
("wm.save_mainfile", {"type": 'S', "value": 'PRESS', "ctrl": True, "alt": True}, {"properties": [("incremental", True)]}),
|
||||||
("wm.quit_blender", {"type": 'Q', "value": 'PRESS', "ctrl": True}, None),
|
("wm.quit_blender", {"type": 'Q', "value": 'PRESS', "ctrl": True}, None),
|
||||||
|
|
||||||
# Quick menu and toolbar
|
# Quick menu and toolbar
|
||||||
|
@ -4555,7 +4556,7 @@ def km_pose(params):
|
||||||
("armature.layers_show_all", {"type": 'ACCENT_GRAVE', "value": 'PRESS', "ctrl": True}, None),
|
("armature.layers_show_all", {"type": 'ACCENT_GRAVE', "value": 'PRESS', "ctrl": True}, None),
|
||||||
("armature.armature_layers", {"type": 'M', "value": 'PRESS', "shift": True}, None),
|
("armature.armature_layers", {"type": 'M', "value": 'PRESS', "shift": True}, None),
|
||||||
("pose.bone_layers", {"type": 'M', "value": 'PRESS'}, None),
|
("pose.bone_layers", {"type": 'M', "value": 'PRESS'}, None),
|
||||||
("transform.bbone_resize", {"type": 'S', "value": 'PRESS', "ctrl": True, "alt": True}, None),
|
("transform.bbone_resize", {"type": 'S', "value": 'PRESS', "shift": True, "ctrl": True, "alt": True}, None),
|
||||||
("anim.keyframe_insert_menu", {"type": 'I', "value": 'PRESS'}, None),
|
("anim.keyframe_insert_menu", {"type": 'I', "value": 'PRESS'}, None),
|
||||||
("anim.keyframe_delete_v3d", {"type": 'I', "value": 'PRESS', "alt": True}, None),
|
("anim.keyframe_delete_v3d", {"type": 'I', "value": 'PRESS', "alt": True}, None),
|
||||||
("anim.keying_set_active_set", {"type": 'I', "value": 'PRESS', "shift": True, "ctrl": True, "alt": True}, None),
|
("anim.keying_set_active_set", {"type": 'I', "value": 'PRESS', "shift": True, "ctrl": True, "alt": True}, None),
|
||||||
|
@ -5484,7 +5485,7 @@ def km_armature(params):
|
||||||
("armature.bone_layers", {"type": 'M', "value": 'PRESS'}, None),
|
("armature.bone_layers", {"type": 'M', "value": 'PRESS'}, None),
|
||||||
# Special transforms.
|
# Special transforms.
|
||||||
op_tool_optional(
|
op_tool_optional(
|
||||||
("transform.bbone_resize", {"type": 'S', "value": 'PRESS', "ctrl": True, "alt": True}, None),
|
("transform.bbone_resize", {"type": 'S', "value": 'PRESS', "shift": True, "ctrl": True, "alt": True}, None),
|
||||||
(op_tool_cycle, "builtin.bone_size"), params),
|
(op_tool_cycle, "builtin.bone_size"), params),
|
||||||
op_tool_optional(
|
op_tool_optional(
|
||||||
("transform.transform", {"type": 'S', "value": 'PRESS', "alt": True},
|
("transform.transform", {"type": 'S', "value": 'PRESS', "alt": True},
|
||||||
|
|
|
@ -188,6 +188,7 @@ def km_window(params):
|
||||||
("wm.open_mainfile", {"type": 'O', "value": 'PRESS', "ctrl": True}, None),
|
("wm.open_mainfile", {"type": 'O', "value": 'PRESS', "ctrl": True}, None),
|
||||||
("wm.save_mainfile", {"type": 'S', "value": 'PRESS', "ctrl": True}, None),
|
("wm.save_mainfile", {"type": 'S', "value": 'PRESS', "ctrl": True}, None),
|
||||||
("wm.save_as_mainfile", {"type": 'S', "value": 'PRESS', "shift": True, "ctrl": True}, None),
|
("wm.save_as_mainfile", {"type": 'S', "value": 'PRESS', "shift": True, "ctrl": True}, None),
|
||||||
|
("wm.save_mainfile", {"type": 'S', "value": 'PRESS', "ctrl": True, "alt": True}, {"properties": [("incremental", True)]}),
|
||||||
("wm.quit_blender", {"type": 'Q', "value": 'PRESS', "ctrl": True}, None),
|
("wm.quit_blender", {"type": 'Q', "value": 'PRESS', "ctrl": True}, None),
|
||||||
|
|
||||||
# Quick menu and toolbar
|
# Quick menu and toolbar
|
||||||
|
|
|
@ -283,6 +283,10 @@ class TOPBAR_MT_file(Menu):
|
||||||
layout.operator_context = 'EXEC_AREA' if context.blend_data.is_saved else 'INVOKE_AREA'
|
layout.operator_context = 'EXEC_AREA' if context.blend_data.is_saved else 'INVOKE_AREA'
|
||||||
layout.operator("wm.save_mainfile", text="Save", icon='FILE_TICK')
|
layout.operator("wm.save_mainfile", text="Save", icon='FILE_TICK')
|
||||||
|
|
||||||
|
sub = layout.row()
|
||||||
|
sub.enabled = context.blend_data.is_saved
|
||||||
|
sub.operator("wm.save_mainfile", text="Save Incremental").incremental = True
|
||||||
|
|
||||||
layout.operator_context = 'INVOKE_AREA'
|
layout.operator_context = 'INVOKE_AREA'
|
||||||
layout.operator("wm.save_as_mainfile", text="Save As...")
|
layout.operator("wm.save_as_mainfile", text="Save As...")
|
||||||
layout.operator_context = 'INVOKE_AREA'
|
layout.operator_context = 'INVOKE_AREA'
|
||||||
|
|
|
@ -435,6 +435,15 @@ void BLI_str_rstrip(char *str) ATTR_NONNULL(1);
|
||||||
*/
|
*/
|
||||||
int BLI_str_rstrip_float_zero(char *str, char pad) ATTR_NONNULL(1);
|
int BLI_str_rstrip_float_zero(char *str, char pad) ATTR_NONNULL(1);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Strip trailing digits.
|
||||||
|
* ABC123 -> ABC
|
||||||
|
*
|
||||||
|
* \param str:
|
||||||
|
* \return The number of digits stripped.
|
||||||
|
*/
|
||||||
|
int BLI_str_rstrip_digits(char *str) ATTR_NONNULL();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Return index of a string in a string array.
|
* Return index of a string in a string array.
|
||||||
*
|
*
|
||||||
|
|
|
@ -1057,6 +1057,17 @@ int BLI_str_rstrip_float_zero(char *str, const char pad)
|
||||||
return totstrip;
|
return totstrip;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
int BLI_str_rstrip_digits(char *str)
|
||||||
|
{
|
||||||
|
int totstrip = 0;
|
||||||
|
int str_len = strlen(str);
|
||||||
|
while (str_len > 0 && isdigit(str[--str_len])) {
|
||||||
|
str[str_len] = '\0';
|
||||||
|
totstrip++;
|
||||||
|
}
|
||||||
|
return totstrip;
|
||||||
|
}
|
||||||
|
|
||||||
/** \} */
|
/** \} */
|
||||||
|
|
||||||
/* -------------------------------------------------------------------- */
|
/* -------------------------------------------------------------------- */
|
||||||
|
|
|
@ -3257,6 +3257,31 @@ static int wm_save_as_mainfile_exec(bContext *C, wmOperator *op)
|
||||||
return OPERATOR_CANCELLED;
|
return OPERATOR_CANCELLED;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (RNA_boolean_get(op->ptr, "incremental")) {
|
||||||
|
char head[FILE_MAXFILE], tail[FILE_MAXFILE];
|
||||||
|
ushort digits;
|
||||||
|
int num = BLI_path_sequence_decode(filepath, head, sizeof(head), tail, sizeof(tail), &digits);
|
||||||
|
/* Numbers greater than INT_MAX return 0, resulting in always appending "1" to the name. */
|
||||||
|
if (num == 0 && digits == 0) {
|
||||||
|
/* This does nothing if there are no numbers at the end of the head. */
|
||||||
|
BLI_str_rstrip_digits(head);
|
||||||
|
}
|
||||||
|
|
||||||
|
const int tries_limit = 1000;
|
||||||
|
int tries = 0;
|
||||||
|
bool in_use = true;
|
||||||
|
do {
|
||||||
|
num++;
|
||||||
|
tries++;
|
||||||
|
BLI_path_sequence_encode(filepath, sizeof(filepath), head, tail, digits, num);
|
||||||
|
in_use = BLI_exists(filepath);
|
||||||
|
} while (in_use && tries < tries_limit && num < INT_MAX);
|
||||||
|
if (in_use) {
|
||||||
|
BKE_report(op->reports, RPT_ERROR, "Unable to find an available incremented file name");
|
||||||
|
return OPERATOR_CANCELLED;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const int fileflags_orig = G.fileflags;
|
const int fileflags_orig = G.fileflags;
|
||||||
int fileflags = G.fileflags;
|
int fileflags = G.fileflags;
|
||||||
|
|
||||||
|
@ -3391,6 +3416,18 @@ static int wm_save_mainfile_invoke(bContext *C, wmOperator *op, const wmEvent *
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static char *wm_save_mainfile_get_description(bContext * /*C*/,
|
||||||
|
wmOperatorType * /*ot*/,
|
||||||
|
PointerRNA *ptr)
|
||||||
|
{
|
||||||
|
if (RNA_boolean_get(ptr, "incremental")) {
|
||||||
|
return BLI_strdup(
|
||||||
|
TIP_("Save the current Blender file with a numerically incremented name that does not "
|
||||||
|
"overwrite any existing files"));
|
||||||
|
}
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
void WM_OT_save_mainfile(wmOperatorType *ot)
|
void WM_OT_save_mainfile(wmOperatorType *ot)
|
||||||
{
|
{
|
||||||
ot->name = "Save Blender File";
|
ot->name = "Save Blender File";
|
||||||
|
@ -3419,6 +3456,14 @@ void WM_OT_save_mainfile(wmOperatorType *ot)
|
||||||
|
|
||||||
prop = RNA_def_boolean(ot->srna, "exit", false, "Exit", "Exit Blender after saving");
|
prop = RNA_def_boolean(ot->srna, "exit", false, "Exit", "Exit Blender after saving");
|
||||||
RNA_def_property_flag(prop, PropertyFlag(PROP_HIDDEN | PROP_SKIP_SAVE));
|
RNA_def_property_flag(prop, PropertyFlag(PROP_HIDDEN | PROP_SKIP_SAVE));
|
||||||
|
|
||||||
|
prop = RNA_def_boolean(ot->srna,
|
||||||
|
"incremental",
|
||||||
|
false,
|
||||||
|
"Incremental",
|
||||||
|
"Save the current Blender file with a numerically incremented name that "
|
||||||
|
"does not overwrite any existing files");
|
||||||
|
RNA_def_property_flag(prop, PropertyFlag(PROP_HIDDEN | PROP_SKIP_SAVE));
|
||||||
}
|
}
|
||||||
|
|
||||||
/** \} */
|
/** \} */
|
||||||
|
|
Loading…
Reference in New Issue