[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.
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?
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.
Cunning! I’m going to have to go and add this to loads of my tools now! Thanks Dennis!
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?
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.
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