Help with creating Last Tool shortcut

Repeat Last and Repeat History do what I want, but too much:
1. Get last tool
2. Use last tool exactly the same way (mouse movement etc)

Difference is History gives you a list of previous operations to choose from.

All I want is the last tool, not the performance afterwards. I can have a single key that will give me the last tool used. It is a great thing. It saves my left arm from repeating a zillion multikey shortcuts, and my right arm from repeated mouse movement back and forth through toolbars, pie menus, and quick favorites.

Having trouble finding where the magic happens. The part that says, “First, get the last tool. Second, do a bunch of mousey garbage you don’t want 90% of the time.”

I could either create a new thing called Last Tool or modify Repeat Last. At this point, I don’t even care about the stuff that happens after giving me the previous tool. I use previous tool way more often than previous tool + identical performance. I have no problem sacrificing that for the short term while a more elegant solution is created.

After a few hours of digging, this is where I am:
screen_ops.c — “blender-git\blender\source\blender\editors\screen”

Line 3409 for Repeat Last:
WM_operator_repeat_interactive(C, lastop);

Line 3470 for Repeat History:
WM_operator_repeat(C, op);

These lead to:
wm_event_system.c — “blender-git\blender\source\blender\windowmanager\intern”

Lines 1050-1057:
int WM_operator_repeat(bContext *C, wmOperator *op)
{
	return wm_operator_exec(C, op, true, true, true);
}
int WM_operator_repeat_interactive(bContext *C, wmOperator *op)
{
	return wm_operator_exec(C, op, true, false, true);
}

Lines 952-1004:
static int wm_operator_exec(
		bContext *C, wmOperator *op,
		const bool repeat, const bool use_repeat_op_flag, const bool store)
{
	wmWindowManager *wm = CTX_wm_manager(C);
	int retval = OPERATOR_CANCELLED;

	CTX_wm_operator_poll_msg_set(C, NULL);

	if (op == NULL || op->type == NULL)
		return retval;

	if (0 == WM_operator_poll(C, op->type))
		return retval;

	if (op->type->exec) {
		if (op->type->flag & OPTYPE_UNDO) {
			wm->op_undo_depth++;
		}

		if (repeat && use_repeat_op_flag) {
			op->flag |= OP_IS_REPEAT;
		}
		retval = op->type->exec(C, op);
		OPERATOR_RETVAL_CHECK(retval);
		if (repeat && use_repeat_op_flag) {
			op->flag &= ~OP_IS_REPEAT;
		}

		if (op->type->flag & OPTYPE_UNDO && CTX_wm_manager(C) == wm) {
			wm->op_undo_depth--;
		}
	}

	/* XXX Disabled the repeat check to address part 2 of #31840.
		*     Carefully checked all calls to wm_operator_exec and WM_operator_repeat, don't see any reason
		*     why this was needed, but worth to note it in case something turns bad. (mont29) */
	if (retval & (OPERATOR_FINISHED | OPERATOR_CANCELLED) /* && repeat == 0 */)
		wm_operator_reports(C, op, retval, false);

	if (retval & OPERATOR_FINISHED) {
		wm_operator_finished(C, op, repeat, store && wm->op_undo_depth == 0);
	}
	else if (repeat == 0) {
		/* warning: modal from exec is bad practice, but avoid crashing. */
		if (retval & (OPERATOR_FINISHED | OPERATOR_CANCELLED)) {
			WM_operator_free(op);
		}
	}

	return retval | OPERATOR_HANDLED;

}

Where to go from here? I’ve looked a bit at a bunch of similar-ish scripts like wm_event_system.h and wm_event_types.h. Going to go through the #include section(s) again, but I’m already helpless enough.

I don’t even know what’s going on here, but can kinda follow where things lead to. Any help is greatly appreciated.

My guess? Operators have the post-tool mouse movement built into them. Which probably makes a solution for just the tool, without the mouse movement, pretty annoying. Hoping that I’m wrong.

2 Likes

You’ll probably want to call something like this:

WM_operator_name_call_ptr(C, op->type, WM_OP_INVOKE_DEFAULT, NULL);

Where you invoke rather than execute the operator, and don’t pass the operator itself but just its type to create a new one.

1 Like

Thanks so much, it is working. Having trouble filtering out unwanted operators like move, scale, rotate, deselect.

static int repeat_tool_exec(bContext *C, wmOperator *UNUSED(op)) {
wmWindowManager *wm = CTX_wm_manager(C);
wmOperator *lastop = wm->operators.last;

/* Seek last registered operator */
while (lastop) {
	if (lastop->type->flag & OPTYPE_REGISTER) {
		if (STRINGIFY(lastop->idname) == "TRANSFORM_OT_translate") {
			printf("\n");
			printf("TRANSFORM_OT_translate is the same as ");
			printf(lastop->idname);
			lastop = lastop->prev;
		}
		else {
			printf("\n");
			printf("TRANSFORM_OT_translate is NOT the same as ");
			printf(lastop->idname);
			break;
		}
	}
	else {
		lastop = lastop->prev;
	}
}

if (lastop) {
	WM_operator_free_all_after(wm, lastop);
	WM_operator_repeat_tool(C, lastop);}

return OPERATOR_CANCELLED;
}

Here is what console gives me:

TRANSFORM_OT_translate is NOT the same as MESH_OT_extrude_vertices_move
TRANSFORM_OT_translate is NOT the same as TRANSFORM_OT_translate

Pretty sure I should be creating a string, setting it to lastop->idname, and comparing that.

string idName = STRINGIFY(lastop->idname);
if (idName != "TRANSFORM_OT_translate") {
    break;
}

Problem is, I can’t use string or I get undeclared identifier errors on build. Tried all kinds of stuff with #include and “using namespace std” but nothing gives. If I include STR_String.h other things throw errors instead.

How do I use strings? I feel like I’m losing my mind or something.

Use STREQ(lastop->idname, "TRANSFORM_OT_translate") instead.

1 Like

Would it be possible to get similar functionality just using Python ?

I dunno but based on what I’ve seen as far as addons I have to figure it could. I have this working but I’m going to revisit it to see what I can do for a history version of it. I have it where it will show a list of previous tools, but it won’t filter it for uniques. If you use the same tool a few times, it will just be a list of one tool over and over.

Haven’t done anything with it since back almost a year ago so it will be interesting to see what if anyting is different now.

1 Like

It would be nice to have a tool that works more along the lines of Maya’s “Repeat Last”. The one in Blender feels somewhat awkward to me in that regard. Mostly because it, as you said, picks up things that you don’t always want to repeat, and it seems to be thrown off easily by other actions.

An alternative Repeat-Last would be nice, one that could exist alongside the current one.
Or maybe Repeat-Last could get a feature that lets the user filter out certain types of actions.

Maybe i missing something and this functionality already exists under a different name ?

Yeah that’s exactly what I was going for, and it works the same as Maya far as I can tell.

WM_operator_name_call_ptr(C, op->type, WM_OP_INVOKE_DEFAULT, NULL);

Is the key part of making it work, doing INVOKE does exactly what I was wanting. That and adding filters for things you don’t want to repeat. The History version of repeat in Blender, where it will show you a list of previous tools is the trickier one I never got quite like I want. But what I got going for that was functional at least.

Gonna go back to this and a few other things I’ve done once I’m done messing around with select-through.

And no, unless somethings changed, or I’ve missed it as well, this Maya type of repeat doesn’t exist in Blender.

1 Like

I wonder if there is a “Feature Requests” thing somewhere where people can vote up feature requests for Blender. I know i would vote “More Industry-Standard like Repeat Last Tool” through the roof :wink:

You can find that over at right click select.

1 Like

Did you end up getting anywhere with this ?

I keep trying to get into Blender but the fact that this is missing ( a pretty standard function in 3D applications for years ), really keeps repelling me - since i used to rely a lot on that functionality in Maya.

I looked into somehow making it work using Python, but no luck so far.
Theoretically it shouldn’t be that hard, but i tried several things and am running out of good approaches.

I’m actually surprised this isn’t more of an issue for people.
Or maybe most Blender users don’t know this functionality because it was never really in Blender.

And what you don’t know, you can’t miss.

I haven’t done anything with it since Brecht showed me how to make it work, and it still does. I have the history version as well that will give you a list of previous actions, but it isn’t very useful. At the moment it adds redundant entries rather than only unique ones, so you’d most likely just have a list of the same operation over and over with no reason for it to ever be used over the normal repeat.

Here’s a diff if you want to try it out, you’ll need to compile Blender, let me know if you need help
[DIFF] 2931 Repeat Tool

Just add a new entry in the keymap under 3dView Global for SCREEN_OT_repeat_tool

It will filter out delete, select all, mode changes, and common stuff like move and rotate. The stuff it doesn’t filter it will print to console, so if there’s something else you don’t want it to do, or if it’s filtering something that you do want, go over to screen_ops.c and add/remove it under static int repeat_tool_exec

There’s more things to add to the filter I’m sure, but it’s subjective. I’ll start using it more regularly and see what else I want it to not repeat. As far as python, I’m fine with compiling Blender until something similar is officially added, because I don’t have a whole lot of time to spend learning new tricks right now. I’m all ears for anything you’re interested in changing though, so let me know because I might want to do the same but hadn’t thought about it yet.

1 Like

Interesting - Many thanks for sharing the Diff !

I’m kind of the opposite when it comes to compiling Blender, i’d like to avoid it as much as possible.

Because the last time i tried, it ended up using around 30GB just from dependencies and build files.

And i’m not very comfortable around it’s sourcecode, it seems a little hard to navigate and make sense of in a lot of places.
But maybe it’s unavoidable for this.

From what i could gather so far from the Python side of things, it looks like the tool and operator system is somewhat fragmented and doesn’t go through a common path that would make these things simple to do. At least not in the way i would like them to work ( which is basically just exactly what Maya does ).

I don’t really know enough about the inner workings of it all though, so maybe i’m wrong and there is some good and robust way to approach the whole thing from Python.