Is it possible to reuse blendshape deltas in Maya?

I have a progressive jaw open blendshape: One rest value, then 25, 50, 75 and 100%. We were given a note that the jaw should open farther. So I want to create a setup that takes any sculpts done to the 100% head, and applies a percentage to the 25/50/75 heads live as the user sculpts. The other (annoying) requirement is that, with all deformers turned off, we will be left with the shapes we started with (other tools depend on this behavior)

I’ve already done something that works, and our shape sculptors love it… but I don’t because the solution I came up with is just annoyingly clunky, requires 2 blendshape nodes per head, and I want to simplify it so I can apply this technique on top of other setups to make something really cool.

So, lets think about how I should be able to accomplish this…
A blendshape node must figure out an offset vector per vertex per shape somewhere, because you take the shape value, multiply that by its vectors, add the per-shape vectors together, and use that final sum to displace the vertices of the mesh. That’s the definition of a blendshape, right? Of course, there’s pruning and some other optimizations, but but you still have to have those offsets somewhere.

So if those deltas are exposed as plugs on the blendshape node, I should be able to connect the deltas from one blendshape node directly into another so long as the meshes they’re deforming share topology.

So, as far as I can tell, they do expose that data as a pair of plugs. Under
blendshape.inputTarget[*].inputTargetGroup[*].inputTargetItem[6000]
you have inputPointsTarget and inputComponentsTarget which hold the deltas and components per shape. I’ve been reading data from these plugs for a while, and they give me exactly what I’m expecting.

Now for the disappointing part. I try to plug that data into a different blendshape node, and I get all sorts of unexpected behavior. It works some times, snaps back to rest at others.

So, after all that background information… Does anybody out there know if there’s some way to directly share deltas? Also, does anybody know what the inputRelativePointsTarget and inputRelativeComponentsTarget are?

I haven’t dealt with blend shapes, however the following might lead to some insight:

Heh, I know that I ask some way out there questions. Unfortunately not relevant as far as I can see. That seems to just point to which index to read/connect, not how to connect.
Though, that IS a very strange design choice, and I’m glad I know about it. Maybe I’ll get some magic insight if I think about it more.

Much appreciated

Hey there, why don`t you just recalculate them somehow?.. and “rebake them”… you can substract the 100% pose… from the final one… and then trigger that however much you want along the way… and rebake each of them…

The connection needs to remain live. To be fair, I only gave one example of what I’m trying. Here’s another

I want to have the ability to “freeze” a combination of blendshapes.
So say I’ve got two primary shapes jawOpen and smile; and one fixit shape jawOpen_smile.
So when both of the primaries are on, the fixit also turns on.

Now say I’m an artist, and I get a note that the smile shape needs changed, but the jawOpen_smile combination is perfect just the way it is. That means I have to take whatever changes I make to my smile shape and subtract them from jawOpen_smile. And I can do this as a post-process, but without a connection like I’m looking for, I don’t think I can do it so the changes are calculated live.

This also has to work with higher depth fixits: eg. A depth-5 fixit shape can have up to 30 shapes leading up to it.

Hmm… can`t you disconnect them temporary… run the script… propagate the changes, then reconnect, or you want to use as a sort of corrective blendshape… without baking?.. and for that combination… you can use some lerps… or remap nodes i guess… create the logic so that when both of those are on… the fixit also turns on… thats how im doing stuff as well. And I guess this would work for the first thing as well… anyway not sure I understand the whole situation :))

OK, lets break this down a bit.
Imagine I’ve got mesh that is a cube with a bunch of subdivisions, and 2 blendshapes: shapeA, and shapeB. Doesn’t matter what those individual shapes look like. What does matter is that when the weights for both shapes are at 100%, it turns my cube into a perfect sphere.

Now imagine I make a mesh that looks like shapeA (call it sculptA), and connect it to the blendshape node. So now, if the shapeA weight is at 100% and I move some points on sculptA, then my mesh will also move (this is what I mean by a live connection)

However, now that I’ve done some sculpting on sculptA, when I set both weights to 100%, my mesh won’t be a perfect sphere any more, and that’s not what I want.

What I’m looking for is a simple setup that will automatically update shapeB as I’m sculping on sculptA so that my mesh will always turn into a perfect sphere…


Now for the annoying part: It has to handle lots of shapes (instead of just 2 above, I could have 30 or more)
Also, I don’t want to break the user’s expectations: The user should be able to connect and disconnect meshes from the blendshape node as normal (except the shape that’s auto-compensating, of course)

so let me get this right… so what you are sayinnggg is that the …script… will automatically calculate the delta of the other blendshapes in the system… based on the 100% position compensating for what you are doing to the one you are sculpting…

so if …
A + B + C = 100…
and you go and add 30 to C…
then it should automatically substract some numbers from A and B… so the final result is still 100?..
or based on some weights?.. like having equal weights divided amongs the other blendshapes not being sculpted would subtract 15 to A and 15 to B… … or if A has 100% weight… it would subtract the 30 back to A… and nothing to B?..

OR…

you want to use a 3rd blendshape that does the compensation… for all the other blendshapes in the system?

eitherway :)) I don`t think there are any nodes that do this out of the box so you have 2 options… you either write a script…that calculates and compensates somehow at the end of the sculpt… or you write a mll deformer that keeps track while you sculpt and compensates on the fly… and to maintain the users expectations… you could probably write a script job or something that sniffs for changes… maybe ;)) either way its doable…

hopefully I understood… haha :grin:

This.


Ok. So now you have the idea of what I’m looking for. Go re-read my initial post with that in mind.
That data does exist on those “6000” plugs in the blendshape node. I can access it via the API.

Why the heck can’t I connect it? That’s my main question.

Well… how are you trying to connect it… post a video or something… or an example file… so we can take a look exactly at your setup…
probably the data from the output is not compatible?.. anyway post an explicit example… then I can take a look…:)) too much speculating

Fair enough

# Build 4 spheres
sphA1 = cmds.polySphere(name="sphA1", constructionHistory=False)[0]
sphA2 = cmds.polySphere(name="sphA2", constructionHistory=False)[0]
sphB1 = cmds.polySphere(name="sphB1", constructionHistory=False)[0]
sphB2 = cmds.polySphere(name="sphB2", constructionHistory=False)[0]

cmds.xform(sphA1, translation=(5, 0, 0))
cmds.xform(sphB1, translation=(10, 0, 0))

# set up the blendshapes
bsA = cmds.blendShape(sphA2, sphA1)[0]
bsB = cmds.blendShape(sphB2, sphB1)[0]
cmds.delete(sphB2)
# Don't delete A2,  we're sculpting on that

# Build the format string
cnxFmt = '{0}.inputTarget[0].inputTargetGroup[0].inputTargetItem[6000].{1}'

# make the connections
for tar in ['inputPointsTarget', 'inputComponentsTarget']:
    cmds.connectAttr(cnxFmt.format(bsA, tar), cnxFmt.format(bsB, tar), force=True)

cmds.blendShape(bsA, edit=True, weight=(0, 1.0))
cmds.blendShape(bsB, edit=True, weight=(0, 1.0))

Run that. Sculpt on sphA2 (at the origin), see that A1 updates. See that B1 doesn’t.
Change the weight on B1’s blendshape, see how it snaps. Try sculpting on A2 again.
Join me in confusion.

alright ill take a look… :)) then join you in confusion haha

Ok, :)) so this clearly doesn`t work… it crashed Maya every single time :)) on the second attempt to sculpt… My guess is that its related to the way information propagates… all that dependency graph…dirty…evaluation…

So what happens from what I can see… is that you sculpt …the first one which is connected normally updates… normally… but the second one doesnt cause there is not a direct connection to that... so maya runs the first cycle updates the normal blendshape... then when you change the weights of the second blendshape... thats when that deformer checks to see if its input has changed and updates... but then if you go back to the first... it creates this whole dependancy feedback loop probably and thats why maya crashes.... so doing it like this cant work because it breaks the laws of physics… :)) or at least the laws of mayas DG…and I did also try to switch between parallel serial and dg… same thing…

so no you cant reuse deltas like this... cause its unstable... you have to go about it another way.. From a memory / dg standpoint ..... the data has to be stored somewhere until it rereads what changed and re-updates in those ~0.003 ms between screen updates... so like I said initialy.. you need a 3rd custom node / deformer that does the checking and updating if you want to keep the realtime response...or at least thats how I would do it... and if you dont need it realtime…then you can just write a quick and dirty python script that updates when you run it… but reusing that output like in your example… as far as I can tell… does not work… if you do find a way to do it… :)) drop a line here…cause i’m curious as well if there is a workaround…

There’s no cycles in the graph, so if Maya is cycling without cycled connections, that’s a HUGE issue, which makes me think that’s not the case.
And I’m getting no crashes, just confusing behavior. I’m on 2019.2. Which Maya are you using?

My best guess is that they didn’t set up the equivalent of attributeAffects() for .inputPointsTarget and .inputComponentsTarget to .output in the c++ code. That may explain the popping when changing a weight (which does have the attributeAffects() defined) finally forces the node to update.

To be fair, I get that it’s not possible, but I guess I’m getting more hung up on WHY it’s not possible when there’s no obvious reason why not. Also, if they don’t expect you to connect to a plug, then why make it connectable in the first place?

if its just a matter of attributeAffects then why does it crash ( maya 2018 )… What happens on your side after you update B for the first time? Can you still sculpt and it just updates when you change the weight?.. cause thats not what I’m seeing on my side…

If I try to move the vertices on the live obj… instead of sculpting… I get a 2 3 second hang… before even A updates… so there is something squirly even before triggering the update with the weights.

And yes its not a “cycled connections” issue… the one you can turn cycles off…

I just tried in Maya 2018 as well. It does the exact same thing as in 2019. No crashing, just bad behavior.
Also, I never move points on sphB1 (farthest from origin), I only change the weight on blendShape2 that’s connected to it.
I only sculpt/move vertices on sphA2 (the one at the origin), and whether I sculpt or move points, I get constant updates on sphA1. No lag whatsoever.

did some more digging, not sure why yours doesnt crash... but mine works if I just connect the whole tree... any other than this... it explodes... blendShape1.inputTarget[0].inputTargetGroup[0] to blendShape2.inputTarget[0].inputTargetGroup[0] then it doesnt crash… but it updates only if I hit the edit button on the blendshape… it might need to store them first? anyway doing it like this… why not just pass the first worldmesh :)) directly in the second deformer =)) cause honestly i don`t think it passes stuff directly anyway… since you can delete the live object and the changes remain… sphA2Shape.worldMesh[0] to blendShape2.inputTarget[0].inputTargetGroup[0].inputTargetItem[6000].inputGeomTarget -> this way it does update them both at the same time :)) but its not what you asked… so…

I dont know.. we are just speculating about the reasons...I would need to see the deformers code to understand :) It might not be propagating... it might store the information someplace else... and thats not updating without a trigger...it might need more plugs to work...... who knows... The fact that mine crashes makes me believe it cant work with just inputTargetItem[6000]… maybe it throws an error on your side but somehow it manages to blow over it… without the code we are trying stuff on top of a blackbox hoping it aligns with our intuition :))
Personally i would just write a second deformer to handle the updates.