Notifications
Clear all

[Closed] SkinOps undo solutions?

I’ve noticed that despite user complaints going back to at least 2008, there are still a bunch of SkinOps functions that are ignored by Max’s undo context. Currently working with something that involves setting vertex envelope weights, and I’m getting tired of having to constantly reload the scene to get back to the previous state.

I’ve already played around in the past with using callbacks to get around having to undo or redo multiple times for certain actions (thanks for the help with that, Denis), but that sort of thing seems unlikely to help with something that bypasses the undo context entirely. I’m curious what kinds of solutions or workarounds everyone else uses for dealing with this type of issue.

15 Replies
1 Reply
(@denist)
Joined: 1 year ago

Posts: 0

it’s more correct to say – many methods ignore undo content.

bakeSelectedVerts is the fastest undoable skinops method

1 Reply
(@malkalypse)
Joined: 1 year ago

Posts: 0

That’s good to know, but how can that help with this problem?

it's more correct to say - many methods ignore undo content.

But is there a way around that?

As an example of a workaround, here’s a script I made to set the weight of all selected vertices to 1.0 for their closest respective bones. The first version will not undo, the second one will. It’s not exactly an elegant solution, but it works for now.

fn vertsToClosestBones1 = with undo on (
 	local skinMesh = $
 	local skinMod = modPanel.getCurrentObject()
 	local numBones = skinOps.GetNumberBones skinMod
 	local numVerts = skinOps.getNumberVertices skinMod
 
 	local skinBones = #(); for b = 1 to numBones do
 		for o in objects where o.name == (skinOps.getBoneName skinMod b 0) do append skinBones o
 
 	selVerts = for v = 1 to numVerts where (skinops.isVertexSelected skinMod v) == 1 collect v
 
 	boneVertArray = #()
 	for v in selVerts do (
 		local vPos = getVert skinMesh.mesh v
 
 		local closestBone, closestDist = 1e9
 		for b in skinBones do (
 			bMid = b.center
 			d = distance bMid vPos
 			if d < closestDist do (closestBone = b; closestDist = d)
 		)
 
 		for b = 1 to numBones where (skinOps.getBoneName skinMod b 0) == closestBone.name do
 			skinOps.replaceVertexWeights skinMod v b 1
 	) -- end v loop
 
 	OK
 )
 
 fn vertsToClosestBones2 = with undo on (
 	local skinMesh = $
 	local skinMod = modPanel.getCurrentObject()
 	local numBones = skinOps.GetNumberBones skinMod
 	local numVerts = skinOps.getNumberVertices skinMod
 
 	local skinBones = #(); for b = 1 to numBones do
 		for o in objects where o.name == (skinOps.getBoneName skinMod b 0) do append skinBones o
 
 	selVerts = for v = 1 to numVerts where (skinops.isVertexSelected skinMod v) == 1 collect v
 
 	boneVertArray = #()
 	for v in selVerts do (
 		local vPos = getVert skinMesh.mesh v
 
 		local closestBone, closestDist = 1e9
 		for b in skinBones do (
 			bMid = b.center
 			d = distance bMid vPos
 			if d < closestDist do (closestBone = b; closestDist = d)
 		)
 
 		for b = 1 to numBones where (skinOps.getBoneName skinMod b 0) == closestBone.name do (
 			if boneVertArray[b] == undefined then
 				boneVertArray[b] = #(v)
 			else
 				append boneVertArray[b] v
 		)
 	) -- end v loop
 
 	for b = 1 to boneVertArray.count do (
 		skinOps.SelectBone skinMod b
 		skinOps.selectVertices skinMod boneVertArray[b]
 		skinOps.setWeight skinMod 1
 	)
 
 	OK
 )

Obviously, that solution works for this particular function, but would be much trickier to use in anything that involves blending vertex weights between multiple bones. Am I stuck finding messy workarounds on a case by case basis, or is there something I can do to make the skinOps actions show up in the undo stack?

3 Replies
(@denist)
Joined: 1 year ago

Posts: 0

you probably didn’t get what i told about bakeselectedverts.
i said – do bakeselectedverts before any not undoable operation, it will make whole action undoable.

(@mattrennie)
Joined: 1 year ago

Posts: 0

Cunning! I’m going to have to go and add this to loads of my tools now! Thanks Dennis!

(@malkalypse)
Joined: 1 year ago

Posts: 0

You didn’t actually say that (well now you did), though in hindsight I can now see what you were implying. Anyway, it works, so thanks!

In one of the tools I use it has a smooth skin function that doesn’t get tracked by undos so I added my own fake undo method which stores the skin info every time i smooth so i can apply it back in the case that I do not like it. It is not pretty and it is not the best solution, but it does the job.

I assume that system doesn’t work with multiple steps of undo and redo?

Ohh Nice Denis , much thx! Was this something you just stumbled upon one day?

1 Reply
(@denist)
Joined: 1 year ago

Posts: 0

it’s pretty common solution for many not undoable operations in max. the solution is to find something undoable and run it with not-undoable…
for example: meshop.setmapvert is not undoable and you want to paint map verts. so on startstroke do converttomesh. it makes your paint undoable.

Huh good to know!

Hm. I’ve noticed that this seems to have somewhat inconsistent results for me, so I’m thinking I may not be using it properly.

If I have a function that contains several skinOps methods, and I want to make that function undoable, do I need to include the undoable action (e.g. bakeselectedverts) before each skinOps method, or do I only need it once at the start of the function?

I was using it only once at the start of the function, and for a while it seemed to be working. But then with another function it didn’t work, and when I examined the first one more closely I noticed that sometimes it would not work at all, and other times would actually undo one step PAST the start of the function!

In testing things out further, I also noticed that if I set the function in a labelled undo context, I don’t seem to be getting that label on the undo.

In short, I’m a bit confused. Do you have an example of a working use of this solution that you can share?

try(destroydialog skinTest) catch()
rollout skinTest "Skin Tests" width:200
(
	button make_bt "Make" width:192 align:#right offset:[8,0]
	button test_bt "Test" width:192 align:#right offset:[8,4]
	
	checkbox use_undo "Undoable" offset:[0,4]
	
	button random_bt "Ramdomize Weights" width:192 align:#right offset:[8,4]

	button set_bt "Set Weights" width:192 align:#right offset:[8,4]
	button clear_bt "Clear Weights" width:192 align:#right offset:[8,0]

	button unnorm_on_bt "Normalize ON" width:192 align:#right offset:[8,4]
	button unnorm_off_bt "Normalize OFF" width:192 align:#right offset:[8,0]

	local obj, sk, b0, b1
	
	fn getSkinInfo sk action:"test" = 
	(
		redrawviews()
		num = skinops.getnumbervertices sk
		format "
%
" action
		for v=1 to num do
		(
			bb = #()
			ww = #()
			for b=1 to skinops.getvertexweightcount sk v do
			(
				id = skinops.getvertexweightboneid sk v b
				append bb id
				append ww (skinops.getvertexweight sk v id)
			)	
			format "	%:	normalized:%	id:%	weight:%
" v (skinops.isunnormalizevertex sk v != 1) bb ww
		)
	)	
	
	fn decorateUndo sk = 
	(
		skinops.selectvertices sk #{1..num}
		skinops.bakeselectedverts sk 
	)

	on test_bt pressed do
	(		
		getSkinInfo sk action:"test >>"
	)
	on make_bt pressed do undo "Make-SK" on
	(
		delete objects
		obj = converttopoly (box name:#skobj segments:1)
		sk = Skin()
		
		b0 = dummy name:#skbone1
		b1 = dummy name:#skbone2
		
		addmodifier obj sk
		select obj
		max modify mode
		modpanel.setcurrentobject sk
		skinops.addbone sk b0 0
		skinops.addbone sk b1 1

		getSkinInfo sk action:"make >>"
	)
	on random_bt pressed do undo "Random-SK" on
	(
		num = skinops.getnumbervertices sk
		if use_undo.state do decorateUndo sk
		
		for v=1 to num do skinops.setvertexweights sk v #(1,2) #(random 0.0 1.0, random 0.0 1.0)
		getSkinInfo sk action:"set >>"
	)
	on set_bt pressed do undo "Set-SK" on
	(
		num = skinops.getnumbervertices sk
		if use_undo.state do decorateUndo sk
		
		for v=1 to num do skinops.replacevertexweights sk v #(1,2) #(0.5,0.5)
		getSkinInfo sk action:"set >>"
	)
	on clear_bt pressed do undo "Clear-SK" on
	(
		num = skinops.getnumbervertices sk
		if use_undo.state do decorateUndo sk
		
		for v=1 to num do skinops.replacevertexweights sk v #(1,2) #(0,0)
		getSkinInfo sk action:"clear >>"
	)
	on unnorm_on_bt pressed do undo "NormOFF-SK" on
	(
		num = skinops.getnumbervertices sk
		if use_undo.state do decorateUndo sk
		
		for v=1 to num do skinops.unnormalizevertex sk v off
		getSkinInfo sk action:"unnorm on >>"
	)
	on unnorm_off_bt pressed do undo "NormON-SK" on
	(
		num = skinops.getnumbervertices sk
		if use_undo.state do decorateUndo sk
		
		for v=1 to num do skinops.unnormalizevertex sk v on
		getSkinInfo sk action:"unnorm off >>"
	)
		
)
createdialog skinTest