Geometry Node Merge Geografts - Geometry change doesn't update nodes

Issue #2005 resolved
GeneralProtectionFault created an issue

So I think this feature is really close. It’s pretty sweet not having to reimport anytime you forget a morph 😁 .

Anyhoo, steps!

Diffeo: 1.7.4.2079

Blender: 4.1

  • Easy Import G9 figure
  • Select GP & Figure
  • Merge Geografts w/ Geometry Nodes checked
  • Select the character mesh, go into Edit mode, select Snap to Symmetry from the Mesh menu

You’ll see that there is now a crease along the geograft.

*** However ***

I think all that is happening here is that the nodes don’t receive the update from the change in geometry.

If you snap to symmetry first, then merge geografts, it’s fine.

Also, snap to symmetry causes very small movements under the circumstances. If I do the above, then go into the geometry nodes, unplug the merge by distance connector, and manually change it from .0001 => .001, then it looks fine again--pretty sure just because the vertices have moved very slightly so they are still within .001 despite that snap to symmetry.

Comments (36)

  1. Alessandro Padovani

    You may try to snap to symmetry the geograft too, I mean the source of the geonode. Then I’m not sure why you need it, if the daz figure is not symmetrical then probably the PA did it on purpose, so to get the same as daz studio you should keep the mesh.

  2. GeneralProtectionFault reporter

    Yeah, and I might just be misunderstanding what the nodes are meant to be doing. I interpreted it as “modifying” the base mesh to incorporate the graft, like a dynamic modifier and that was what allowed the morphs messing w/ the base mesh-so thinking like it would “follow” what was done to the base mesh. Maybe that’s not accurate.

    The main thing that made me question if it was intended behavior or not was the fact that it worked fine if I did the snap to symmetry first--it only causes a problem if I snap to symmetry after the merge.

    It’s not a really big deal now that I know that, but I wasn’t sure if it was behaving as expected. If this is a surprise to no one, then my apologies, we can ditch it 😋 .

    P.S. I agree I’d generally not symmeterize the figure, it doesn’t help realism. The main reason I do it is I like to use strand/Blender hair, and I create a scalp mesh based on the figure, and I was just a bit OCD about selecting the same stuff to create that mesh (select mirror is janky if the mesh isn’t symmetrical).

  3. Thomas Larsson repo owner

    Yes, that is probably what happens, and I don’t think the issue is limited to HD meshes. Merging with geometry nodes was requested by somebody (Midnight Arrow probably) and he showed how to do the implementation. Personally I avoid using geonodes altogether, and I think it might have been a mistake to add it.

    Here is how geograft merging works: The geograft contains information about which vertices to merge and which base mesh vertices to delete. In a first step, every geograft vertex on the boundary is moved to the location of the corresponding base vertex, both with geonode and ordinary merging. Then vertices are merged by a very small distance. This works because the vertices to be merged are now at exactly the same location. However, if you edit the base mesh afterwards, the vertices move apart and no longer merge. This is of course not problem with ordinary merging, where the mesh is merged when you enter edit mode.

    I recently added a new option to the geograft tool: Keep Original Mesh. It creates three subcollections which can be included in the scene depending on the situation.

    1. The original mesh. Use for the dressed version of the character which can load missing morphs.
    2. The merged mesh. For nude version of the character.
    3. The unmerged geografts. Can be deleted.

  4. GeneralProtectionFault reporter

    Thanks Thomas. So personally, I don’t think geonodes was a mistake, I think it’s just not a mature feature, either here or in Blender. I could be wrong, certainly, but I think it’s the seed of a workflow where you have an almost seamless non-destructive workflow. I see you’ve done some stuff on the shell plugin, still not familiar with it though--but having a simple way to turn shells on or off (might be this is what the drivers bit is intended to do). Maybe in the future, displacement could be done after a multi-res modifier for HD expressions and not lose as much performance, but anyway, getting side-tracked by dreaming LOL.

    *** Side Note *** – Even without the actual modifier, I think the “Mask” vertex group you create is very useful as a beginning to create a “scalp” for something like golden growler when converting that to Blender’s hair curves. (though it ends up sometimes needing a bit more than that selection, you get the idea)

    So back to the main point, this issue here:

    https://projects.blender.org/blender/blender/issues/87006

    That looks similar to me, so I’m wondering that it might be a simple question of Blender not “updating.”

    The way I’m imagining it, the whole point of the geometry nodes is (in theory) not having a need to keep a copy of the mesh, because you always have the original mesh if the modifier(s) are off. The fact that the merge function still works perfectly if I mess with the geometry first, I think demonstrates that to be true--it just doesn’t “recalculate” after a change in Edit mode, apparently.

    I was trying a few commands from that issue and didn’t get it to do anything (in 4.1 anyhoo) just yet, but in deference to what I just said, I did find that deleting the Geometry Nodes modifier, then reselecting the graft & mesh, clicking “Merge Geografts” again works just fine 🙂 .

    So, I suppose the question is if this is just the way it should be done based on how Blender is designed, or if there’s an enhancement that makes sense.

    For example, I could see a “ReMerge Geografts” making sense, but just the existence doesn’t make it clear to the user when it should be used, and they’d still probably have to select/unhide the graft. One possible edge case catch might be to check if the Geografts modifier already exists.

    Anyhoo, if the most sensible thing is to do what I did and just re-do it, I’d be glad to write up a contribution to the wiki/blog/etc…

  5. Thomas Larsson repo owner

    Moving boundary vertices into position is done by the script and not by the node tree, so I don’t see how a node tree update could fix that. Anyway, I’m not really comfortable with geonodes; I could translate Midnight’s node tree into a script that generates the nodes, but I wouldn’t have come up with this specific node tree myself.

    Having two separate meshes, but with identical materials, has several benefits: clothes fit better, missing morphs can be loaded, and shell clothes can be added to the original mesh (if you make some materials unique). It is true that it leads to an extra mesh, but render time is not affected because one of the meshes should always be excluded from the scene.

    If you want to write something, you could upload it somewhere and I could put it in the wiki or on the blog.

  6. GeneralProtectionFault reporter

    Oh, snap--I see now. So actually only part of the merge is done in the geometry nodes. And yeah, everything is uncomfortable until you get used to it 😅 , definitely not comfortable with these either, but trying to change that LOL.

    So, I think the snap could absolutely be done in the nodes. I don’t think Set Position was an option when they first appeared:

    So if I plunk that in there, that’s definitely setting the position of the geograft edge. I’m just having trouble figuring out how to feed it the right position. I tried another Capture Attribute set to Vector & Point, but it just sets to 0--definitely affecting the correct vertices, though. Might be the order of the nodes is important or might try creating a vertex group on the to-be-deleted vertices.

    Anyway, I’d like to take a little time to look at that and see. Either way I’ll write a little something up, but just like to investigate this a bit first. It’d be a bummer to write up a workaround and then figure out it’s not necessary 😂

  7. Thomas Larsson repo owner

    I don’t understand how that could work. How do we know how to pair verts on the geograft edge with verts on the body. The geograft mesh has two custom collection properties, DazGraftGroup and DazMaskGroup, which specify the pairing (pairs of body and geograft verts) and the body verts to be deleted. That information must be used somehow. Often the pairs are the closest ones on the boundary, but we cannot rely on that because it is not always true.

    Incidentally, the Finalize Meshes button removes these custom properties, so merging cannot be done afterwards.

  8. GeneralProtectionFault reporter

    I see what you’re getting at Thomas. Basically, even if I got the snap to work via nodes in my example, it would only be working coincidentally because your code has already snapped the graft vertices to their “partners” in the base mesh, and are therefore already closest.

    So, what I’m thinking is if custom attributes (blender.stackexchange.com/questions/266991/manually-setting-custom-attributes-per-edge) can do the trick. I’m not sure if this is apples to apples, but there’s a named attribute node, I’m thinking maybe the vertices store their “partner” index as an attribute in the coding portion, and then the geo nodes affect by “equals” or something like that, based on their index. That would be the first action in the node tree, and the rest/merge by distance are based on the vertex groups… I think lol.

    Not sure of all of that due to my near-nothing experience with geo nodes 🤣, but that’s generally what I’m thinking as far as trying it out.

  9. Alessandro Padovani

    If it helps, in the daz tutorial to make geografts, it is stated that the merge area of the geograft must be copied from the base mesh. And that’s how daz studio itself merges polygons, by those that are perfectly overlapping. Thus it should not be needed to “merge by distance“, unless to avoid precision errors, or possibly some offset introduced when we enter the geometry editor for HD meshes, but ideally there shouldn’t be any.

    If a morph is applied to the base mesh it must be transferred to the geograft to keep the joint area, apart rigid maps if any.

    https://www.youtube.com/watch?v=Zm-QCFKowCw

    p.s. Then personally I don’t use geonodes for figures, I much prefer the “merge geometry“ method.

  10. Thomas Larsson repo owner

    Early versions of the merge tool didn’t move vertices, I think, but just removed doubles with a somewhat wider margin. Vertex moving was introduced when I encountered some example where this procedure didn’t work. The merge geograft tool also disables subsurf modifiers (or move them after the geometry nodes modifier), to make sure that both verts in a pair remain at the same location.

  11. GeneralProtectionFault reporter

    Just want to put an update here (too busy today to dive in further) for information purposes. So the custom attribute seems to work, I think anyhoo--as far as preserving the relationship, as follows:

    # Add custom attribute which will store the vertex to be paired, and accessible via geometry node
    # If this is the graft...
    attribute = aob.data.attributes.new("paired_body_vertex", type="INT", domain="POINT")
    
    # Create a dictionary, default all to -1, but values will be populated with the paired body vertex
    # To set in foreach_set method, needs to be the same length as the # of vertices
    paired_vert_list = dict()
    for idx, v in enumerate(aob.data.vertices):
        paired_vert_list[idx] = -1
    
    for pair in aob.data.DazGraftGroup:
        aob.data.vertices[pair.a].select = True
        if pair.b in assoc.keys():
            # Set value to be added as attribute
            paired_vert_list[pair.a] = pair.b
    
            aedge[pair.a] = True
            cvn = assoc[pair.b]
            ctx_obj.data.vertices[cvn].select = True
            cedge[pair.b] = True
    
    # Set the attribute values for all the vertices
    attribute.data.foreach_set("value", list(paired_vert_list.values()))
    

    That’s just me injecting it into Thomas' code. I’m not sure if that works or I have it right (the vertices might get updated after that code and break it, but the principle should work).

    Then, after merging the geografts, we can see it reflected in the spreadsheet:

    Ironically, the trickier part (to me at least) is the node setup. We can definitely get those values with the named attribute:

    …and we can use the Evaluate At Index node to get the vertices from the body mesh--but the ugly part is shoe-horning that into the graft mesh vertices/data. It’s easy to get the affected vertices on the mesh, and the positions (to snap to) but then your context is that mesh.

    Essentially, I think what’s needed is getting a “list” of positions the same length as the graft vertices (positions), with the base mesh positions substituted into that list. That’s what I’m not sure is doable in the nodes.

    Anyhoo, gonna look into it some more, I know you guys aren’t geo node fan boys 😂 , so don’t feel obligated to run with this. I just wanted to document it.

  12. GeneralProtectionFault reporter

    Ok, combined with the attribute code above, this works from me testing it out. I’ve included some explanations as to what the nodes are doing:

    So I haven’t gotten this into code yet, this is from adding the attribute, merging as currently implemented, then doing the nodes in Blender to test it. I set it up thus, and edited some mesh vertices around the graft and it followed along.

    The essential point is the meshes need to be joined first--because we can’t conditionally act on only certain vertices from different parts of different meshes, which is to say, I don’t see a way 😋 .

    I don’t yet suggest this be implemented, because I’m not yet sure how “matryoshka” this is--if it will work with multiple/nested geografts. The above solution does also depend on the body mesh being “first,” because it’s just mapping to the original vertex, which is unchanged in the body. I don’t think that’s a limitation, because the original index could be stored as an attribute too--it’d just be more complicated.

    In any case, it’ll take some time, but I think it’s certainly possible/proof of concept here.

    EDIT: The Geograft Edge and the Capture Attribute it’s connected to can be removed--forgot.

  13. Alessandro Padovani

    My own personal opinion.

    I rather believe that is a “proof“ why geonodes are not good for figures. Seriously, are we going down this path and have gazillions of complex nodes and relations to handle each figure in the scene ? How do we expect blender can work fine that way ? Again, blender is not daz studio, we should not try and convert blender into a clone of daz studio, it is not. We bake from daz what we need, then work in blender, including geografts.

  14. GeneralProtectionFault reporter

    Ok it works 😁 . Attached the 2 files that were modified.

    Caveats, etc…:

    • Forgive me, I renamed a few variables just to help me track what I needed to when reading the code.
    • I’m not sure what the stype argument does when adding a geometry node, but it doesn’t seem critical. I get Missing NodeSize for some that I added. I assume it’s socket type and something to do w/ that, but anyhoo…
    • I still haven’t tested to see if this breaks when doing geografts on top of others, but I wanted to establish that this works, and from what I can tell, accomplishes a more true non-destructive merge.

    This is just the code now doing the node setup I outlined above, minus the one I forgot was no longer needed (mentioned in previous post).

  15. GeneralProtectionFault reporter

    @Allesandro

    The node setup is not really more complicated than the first setup, it’s just a different approach. It’s not any more complicated than what the existing code does in Python. The geo nodes are essentially just visual scripting. In fact, this node setup is actually simpler, as you don’t even need the geograft edge.

    I don’t think it’s going to affect Blender performance either. Geometry nodes are also modifiers, just like multires. I don’t know this stuff down at like C++ level, but I imagine a multires modifier is not heavier than a mesh that actually has all the vertices--or at least you can shut it off while you’re working, go a lot faster, and then re-enable for rendering.

    It also has nothing to do with keeping parity with DAZ. If anything I’m all for diverging from DAZ, it’s frankly pretty crappy software that has value from the inertia of what’s been done for character modeling, and the artists that have made stuff for it. Anyway, the idea is, first of all, that geometry nodes, while not as easy to pick up, are powerful. Non-destructive changes give you the ability to customize things more, swap things around, etc…

    That said, I’m not telling you guys what to do, I’m doing the work myself and offering it up. This topic has come up before, and if y’all don’t want to, y’all don’t have to 😋 . I’m just showing that the reason for that is not a limitation on it being doable.

  16. GeneralProtectionFault reporter

    Ok the v2 files are an upgrade. I think it’s closer to seriously considering now.

    I pulled updates up to 1.7.4.2086, so any changes up to that point should be in there.

    So, here are the essentials of the code updates:

    • “original_vertex” attribute is added on mesh import (needed to keep track of key indices for when geometry is deleted)
    • Node setup updated
    • Now, if you merge 1 geograft, then another, the modifiers do not have the same name (appended the graft name). This is needed because otherwise it doesn’t work right and tries to do everything to the same modifier.
    • Updated the “paired body vertex” (custom) attribute to also append the graft name, in order to distinguish the paired vertices of different grafts.

    So, with this setup, you can merge 1 graft with geonodes, then another, and both work together or independently. You can swap the geograft modifiers around and it works fine (obviously not above the armature 😋 ). I think the utility here is the modular aspect. Like if you have wings, tails, genitails…whatever… You can toggle the modifier(s) on or off as you need on the same model.

    My test case was a G9 w/ Breastacular & GP.

    So, the only thing I haven’t tested yet is “grafts of grafts.” I don’t immediately see why it would make a difference, but definitely something test-worthy.

    I also tested merging 2 at the same time, works too, existing code seemed to swallow that just fine.

    @Thomas let me know if you see issues with the approach at all. If you would like to implement it (I’ll also see if I can find a graft for a graft to test), let me know, and I’ll make a more polished image of the node setup with explanations so it’s not a black box for anyone interested. Otherwise, I’ll fork 😜

  17. Thomas Larsson repo owner

    I haven’t followed your progress, but it doesn’t seem to work for me. I downloaded you files, but editing the vertex locations still breaks the mesh. Steps to reproduce:

    1. Merge geografts with geonodes.
    2. Move vertices in edit mode.
    3. There is a gap in object mode.

  18. GeneralProtectionFault reporter

    Hmm, weird. So I assume you merged, unhid the graft, then edited a vertex on the graft, right? Should be fine 🤔.

    Just sanity checks, is this also Blender 4.1 (though I don’t imagine that’ll matter if it didn’t throw an exception…), and do you see the new nodes (sorry it’s such a mess, looking into placement in code😋), and if you merge 2 grafts, do you get a separate modifier for each?

    I’ll take a peek after work either way.

    EDIT: Sorry Thomas, I see. I think in the process of getting it to “work” regardless of stacking, it must have excluded the set position on the edge. I’ll look later & update.

  19. Thomas Larsson repo owner

    No, I moved vertices on the merged mesh, with falloff so most vertices in the geograft area were affected.

  20. GeneralProtectionFault reporter

    So this is just a progress update. I think I mostly have a solution, excepting one thing I’m having trouble figuring out vis-a-vis Blender’s wonky API.

    (v3 files attached) The geometry.py file is not important for this because that only added the original vertex # as an attribute, and not using that now.

    So, I think it is possible to track the “original” index in nodes when deleting geometry (via nodes), but it’s pretty unpleasant 😂 . I think I have a better/simpler approach.

    So this (as before) adds each graft as a separate geonode modifier that can be toggled, of course. The difference here is none of the geometry is deleted (as this is what makes it a pain and changes the vertex indices) until the end. It will, for a given figure, merge whatever geografts, then add another geonode modifier that has the delete masks for all the grafts on that figure. This “END” node, as I’ve labeled it to be clear, deletes all the masks in one step, and does a single merge by distance.


    The one issue I’m having is toggling the modifier to use an attribute/filling that in. I’m using the same essential code that you (Thomas) used to do it on the graft modifiers themselves, but for some reason it doesn’t set properly. So, currently, to see the full results, the end node’s attributes just need to be clicked, etc… like so (the masks are already created from the existing code, it’s just a matter of selecting them):

    Obviously that’s terrible and not a solution 😜 , but again, just to demonstrate the proposed workflow until I can figure out why that won’t update (running the same code from the Python console works just fine 😑 ).

    If someone were to merge on graft at a time, or both, this end node needs recreating. So, the code checks for it, wipes/recreates it every merge. This is both because it needs to come after the graft modifiers, and because if it’s done 1 at a time, it needs to look for the existing graft modifier and the one being merged. This is working from the testing I’ve done so far.

    Still haven’t tested on grafts of grafts. I will as soon as I can figure out that annoying API confusion ^^^. Hopefully that’d translate just fine and it’d be the same thing, just treating the “target” graft as the “body” in that case.

    ** A general thing to note **

    I think this was the case even before I touched anything, but I don’t think it works to edit the graft vertices directly on the body mesh after the geonode merge--because the nodes are “calculating” those vertices based on the graft object. You could, however, simply edit the graft object and those changes would be reflected in the “joined” mesh. However, editing nearby body vertices, the mesh “snapping” should follow now.

    An enhancement to this, if working, etc….could be an option to use a sample nearest node--so that such an edit could use a degree of falloff and make the edit smooth, and not just jarring the 1 edge out of place.

    One thing I’m not at all sure about is rigging. I imagine shape keys would be ok, but if a graft had its own rig, then I think the rig would work if it remained separate--on the graft itself, just like the editing. I think this could be good in some cases and bad in others. A good example might be you wish to add wings, they have their own rig, but you don’t want to have to muck with the weights if joining the rigs together. Anyway, that’s fluff on top.

  21. GeneralProtectionFault reporter

    Ok dash it, I think I have it now, v4 files attached. When I created the modifier, I set the node group afterwards and that somehow mattered…meh.

    Pulled up to 1.7.4.2089

    So, I think there are surely still situations where you would want to use geonodes and where you would not, but I think this is an enhancement.

    The original intent is there, moving verts won’t create a gap now. Potentially this could be improved further as I alluded to with the sample nearest node. Also as mentioned, if it’s desired to test by moving verts ON the graft area, it should work by doing so on the graft object itself.

    I’m not sure if it’d be better not to hide them, but then you still have overlap. Anyhoo, not important.

    I haven’t tested rigging & posing, but I don’t think it would be different than before w/ geonodes. I did do the test on the futa + roasty as was demonstrated on the blog. Grafts on grafts worked as far as I could see.

    An advantage here is there’s no need to be concerned with the order of merging the grafts, nested or otherwise. Any can be turned on or off, removed, etc…. without reloading the figure. The “END” modifier that does the delete just has to be after any of the grafts, and it always will be unless deliberately moved.

    I also finally grasped how the node creation was done and that the “cols” are literal and just placement/organizational. That’s why I updated the tree.py file--to add sizes for the nodes I added which were not in the list. And now that I understand the column piece, the nodes aren’t a horrible mass of spaghetti now 😋 . Deleting geometry would still break it, but I doubt that’s a likely cause for complaint 🙂 .

    Anyhoo, this is where I was trying to get it.

    @Thomas just let me know whenever you feel like it if you’d like to implement it. If so, I’ll provide a little write-up of the info I’ve mentioned here with some screenshots and such.

  22. Alessandro Padovani

    Again personally I’m not interested in geonodes for figures, but I believe this is a nice enhancement for the current code, useful for people who want to use geonodes. Just wanted to say thank you so much for contributing here.

  23. GeneralProtectionFault reporter

    @Allesandro

    Glad to do it 😁 . You guys do a lot of work, far more than DAZ does on its pitiful bridges LOL, so if I can, I’m glad to add to it.

    I’ll just wait and make sure Thomas agrees 😋 .

    One thing I forgot to mention is that bit Thomas mentioned regarding Finalize Meshes deleting these attributes.

    I’m not sure if that removes custom attributes as well, but that is a consideration either way, I think. I don’t know if that warrants a “using Geonodes” checkbox or something to exclude the mask, edge (though with the update I don’t use the edge) & any custom attribute that starts with “paired_body_vert”…or if it should just be avoided entirely (warning) when using geonodes. I think that would have been a thing before as well, but I didn’t test that.

  24. Thomas Larsson repo owner

    I had forgotten about this issue, but now I have tested it and it seems to work fine. In particular destructive merging still works, which is what I really care about myself. Not that I understand what the code is doing, but I haven’t worked with custom attributes before, so there is something to learn.

    I changed back some of the variable names that you changed. They are admittedly not very clear, but they make sense to me.

  25. GeneralProtectionFault reporter

    Nice! Ok providing some documentation here. I’m attaching an .odt / document going over what I think is the essential info. Obviously, feel free to ammend if needed. There’s an image in there that is acting up, so I’ll attach that separately as well (KinModifiers1.png). Just a snapshot of the modifier stack after applying Breastacular & GP.

    Also, here are explanations of what the nodes are doing:

    One little weird side note… I noticed when testing w/ a G9 character, the vert count after destructive merge (same grafts) was 42038--then if I merge by distance (under Mesh => Cleanup), I end up w/ 40877.

    However, the nodes (which include the merge by distance, I end up with 40868. Not important, but weird, mentioning for posterity 😜

  26. Thomas Larsson repo owner

    Maybe the merge distance is too large. If the geograft vertices are very dense, the remove duplicates operator may accidentally merge vertices inside the graft. The plugin sets the merge distance to 0.1 mm by default, so it depends on the unit scale. In the last commit I extracted the distance as a modifier parameter so it can be easily tweaked.

  27. GeneralProtectionFault reporter

    Yeah that’s a good idea. Welp, I’ll put it to resolved. If I read it right, I think this change will help Calibrator with what he was asking for in issue 1964. Maybe that was already done, I’m not sure, but thinking of each graft being a separate node group/modifier and the hijacking he mentioned there.

    Anyhoo, thanks again. I know y’all don’t use this stuff, but I think it’s a good idea to kinda keep the lanes open. Down the road, it seems like Blender’s going to be more geo-node-ish 😋 . From this: https://blender.community/c/rightclickselect/79gG/?sorting=hot … seems vertex groups suck at the moment, but maybe a few years down the road there’ll be more focus on the nodes & attributes.

  28. Log in to comment