Why do I have to hit Undo twice after performing SendModelingCommand 'Weld'?
-
Hi guys,
so as the title suggests I observed some strange behaviour of the SMC Weld command in conjunction with
BaseObject.GetModelingAxis()
.My test setup is as follows:
I have a simple plane objects made editable. Then I select some vertices I want to weld together. I then move the Modeling Axis to the random position and execute the script down below.The first question I have is why can't I weld the points directly together at the position vector
BaseObject.GetModelingAxis()
provides me with?To circumvent this issue I came up with the workaround to first weld the point selection to its 'center' while afterwards move that point to the position of
BaseObject.GetModelingAxis()
.The second question possibly arises from this workaround because while it works it seems that this, way the undo system gets screwed up. To undo the weld operation I have to hit undo twice. Why is that?
I hope someone can help me here and I could explain my problem well enough. If not just let me know.
Cheers,
Sebastianfrom typing import Optional, Generator, Union import c4d doc: c4d.documents.BaseDocument # The active document op: Optional[c4d.BaseObject] # The active object, None if unselected def is_mesh(node: c4d.BaseObject) -> bool: """Return whether *node* is of type *c4d.PointObject*.""" return isinstance(node, c4d.PointObject) def iter_selected_points(obj: c4d.BaseObject) -> Generator: """Yield selected points of *obj* along with their index.""" points = obj.GetAllPoints() baseselect = obj.GetPointS() sel = baseselect.GetAll(obj.GetPointCount()) for (index, selected), point in zip(enumerate(sel), points): if not selected: continue yield index, point def smc_weld(obj: c4d.PointObject, doc: c4d.documents.BaseDocument) -> bool: """Perform modeling comannd 'Weld' for selected points of *obj*.""" settings = c4d.BaseContainer() # Explicitly say we will not use point, so the center will be used settings[c4d.MDATA_WELD_TOPOINT] = False result = c4d.utils.SendModelingCommand( command=c4d.ID_MODELING_WELD_TOOL, list=[obj], mode=c4d.MODELINGCOMMANDMODE_POINTSELECTION, bc=settings, doc=doc ) return result def smc_optimize(obj: c4d.PointObject, doc: c4d.documents.BaseDocument, tolerance: float = 0.001) -> bool: """Perform modeling comannd 'Optimze' for all points of *obj*.""" settings = c4d.BaseContainer() settings[c4d.MDATA_OPTIMIZE_TOLERANCE] = tolerance settings[c4d.MDATA_OPTIMIZE_POINTS] = True settings[c4d.MDATA_OPTIMIZE_POLYGONS] = True settings[c4d.MDATA_OPTIMIZE_UNUSEDPOINTS] = True result = c4d.utils.SendModelingCommand( command=c4d.MCOMMAND_OPTIMIZE, list=[obj], mode=c4d.MODELINGCOMMANDMODE_ALL, bc=settings, doc=doc ) return result def main() -> None: # Called when the plugin is selected by the user. Similar to CommandData.Execute. c4d.StopAllThreads() doc = c4d.documents.GetActiveDocument() doc.StartUndo() obj = doc.GetActiveObject() if not obj: return doc.AddUndo(c4d.UNDOTYPE_CHANGE, obj) position = obj.GetModelingAxis(doc).off if not smc_weld(obj, doc): msg = f"Could not execute Modeling Command 'Weld'." raise RuntimeError(msg) index = list(dict(iter_selected_points(obj)).keys())[0] obj.SetPoint(index, position) obj.Message(c4d.MSG_UPDATE) if not smc_optimize(obj, doc): msg = f"Could not execute Modeling Command 'Optimize'." raise RuntimeError(msg) doc.EndUndo() c4d.EventAdd() def state(): # Defines the state of the command in a menu. Similar to CommandData.GetState. doc = c4d.documents.GetActiveDocument() obj = doc.GetActiveObject() if not obj: return False return c4d.CMD_ENABLED if __name__ == '__main__': main()
-
Hey @HerrMay,
Thank you for reaching out to us. While I understand that it can be hard to follow this rule when you are in the middle of things, I must point out that you should limit the scope (i.e., number of questions) of your topics as they can become otherwise hard to answer.
- Most of your setup here is irrelevant for the problem at hand, the sole culprit seems to be the weld command.
- The weld command seems to be bugged (tested in S26.1 and 2024.1.0):
- Some of its parameters behave oddly (e.g.,
MDATA_WELD_POINT
). - The command seems to have problems with undos in multiple contexts, ranging from adding multiple undos, to completely breaking the undo stack.
- As always it is a bit intransparent which of the tool/command parameters are still supported by the new modeling kernel.
- Some of its parameters behave oddly (e.g.,
Given that the problem does exist in S26 and 2024 (and probably everything in between) and only for the weld tool, indicates that this problem likely originates in the modeling kernel and is not caused by the Python layer and its GIL. I have forwarded the problem to one of our modeling developers to have a look.
But please understand that we cannot fix things for S26 retroactively. To answer a few of your questions:
- The tool does offer more control where to weld to, one is
MDATA_WELD_POINTINDEX
and the other isMDATA_WELD_POINT
, although the latter is behaving weirdly. - The undo problems are likely a bug.
What can I do?
I am afraid, not much from the Python side. The only thing I see here is a brute force approach. Just copy your weld object to a temporary document, do all computations there, and then copy the result back (wrapped in one undo). But you then of course run in the usual dependencies problem: Which entities must be copied into the temporary document? Are there dependencies such as deformers and fields; which when omitted will change the result?
When you want to know more about operating in temporary documents with SMC, you should have a look at our smc_extrude_s26.py example.
I will update this thread once we have conclude what is up with the weld tool (there is also a chance that you and I are simply misusing the tool, that there is magic order of operations/arguments one must follow).
Code:
from typing import Optional import c4d doc: c4d.documents.BaseDocument # The active document op: Optional[c4d.BaseObject] # The active object, None if unselected def main() -> None: """ """ if not op: return bc: c4d.BaseContainer = c4d.BaseContainer() # When set, use the point index to weld to. bc[c4d.MDATA_WELD_TOPOINT] = True # The point to weld to (does not have to be a selected point). bc[c4d.MDATA_WELD_POINTINDEX] = 0 bc[c4d.MDATA_WELD_OBJECTINDEX] = op # ??? bc[c4d.MDATA_WELD_POINT] = c4d.Vector(0) # Behaves weirdly. if not c4d.utils.SendModelingCommand( c4d.ID_MODELING_WELD_TOOL, [op], c4d.MODELINGCOMMANDMODE_POINTSELECTION, bc, doc): raise RuntimeError() c4d.EventAdd() if __name__ == '__main__': main()
-
Hi @ferdinand,
ah okay I see. Some part of me already suspected it could be a bug. Thanks for taking the time to look into it.
Regarding the scope of question. You are absolutely right. And while I understand that it can make answering branching questions hard, it is sometimes not that easy to split things up. But all good, no offense taken.
One last thing, I know you guys are not here to develop solutions or come up with algorithms. Still, I was wondering if a possible solution could be to move the selected points to a custom vector position and afterwards simply send an "Optimize" modeling command?
Cheers,
Sebastian