Hi sadly it is not possible for the moment, I will talk to the responsible team to see if that's possible.
Cheers,
Maxime.
Hi sadly it is not possible for the moment, I will talk to the responsible team to see if that's possible.
Cheers,
Maxime.
Hi @Dunhou there is the method GraphModelInterface.GetModificationStamp which return you a TimeStamp of the last change (aka the last committed transaction).
Then with this stamp you can use GraphModelInterface.GetModificationsSince that will return you which GraphNode (so port since ports are node) are modified.
With that's said currently GetModificationsSince only accept list and not directly a callback, if you want to use a callback you need to use _GetModificationsSinceComplex.
Find bellow a code example
import c4d
import maxon
def main():
# Retrieve the selected baseMaterial
mat = c4d.BaseMaterial(c4d.Mmaterial)
if mat is None:
raise ValueError("Cannot create a BaseMaterial")
# Retrieve the reference of the material as a node Material.
nodeMaterial = mat.GetNodeMaterialReference()
if nodeMaterial is None:
raise ValueError("Cannot retrieve nodeMaterial reference")
# Define the ID of standard material node space and print a warning when the active node space
# is not the the standard material node space.
nodeSpaceId = maxon.Id("net.maxon.nodespace.standard")
if nodeSpaceId != c4d.GetActiveNodeSpaceId():
print (f"Warning: Active node space is not: {nodeSpaceId}")
# Add a graph for the standard node space.
addedGraph = nodeMaterial.CreateDefaultGraph(nodeSpaceId)
if addedGraph is None:
raise ValueError("Cannot add a graph node for this node space")
# Retrieve the Nimbus reference for a specific node space from which we
# will retrieve the graph. One could also use 'addedGraph' defined above.
nimbusRef = mat.GetNimbusRef(nodeSpaceId)
if nimbusRef is None:
raise ValueError("Cannot retrieve the nimbus ref for that node space")
# Retrieve the graph corresponding to that node space.
graph = nimbusRef.GetGraph()
if graph is None:
raise ValueError("Cannot retrieve the graph of this nimbus ref")
# Retrieve the end node of this graph
endNodePath = nimbusRef.GetPath(maxon.NIMBUS_PATH.MATERIALENDNODE)
endNode = graph.GetNode(endNodePath)
if endNode is None:
raise ValueError("Cannot retrieve the end-node of this graph")
# Retrieve the predecessors. Function have been moved in R26.
predecessor = list()
maxon.GraphModelHelper.GetDirectPredecessors(endNode, maxon.NODE_KIND.NODE, predecessor)
bsdfNode = predecessor[0]
if bsdfNode is None:
raise ValueError("Cannot retrieve the node connected to end-node")
# Retrieve the outputs list of the BDSF node
if bsdfNode is None and not bsdfNode.IsValid() :
raise ValueError("Cannot retrieve the inputs list of the bsdfNode node")
bsdfNodeInputs = bsdfNode.GetInputs()
colordePort = bsdfNodeInputs.FindChild("color")
if colordePort is None:
return
stamp = graph.GetModificationStamp()
with graph.BeginTransaction() as transaction:
# Define the value of the Color's port.
colordePort.SetPortValue(maxon.ColorA(1, 0, 0, 1))
transaction.Commit()
# Get the change via a list
modifieds = []
graph.GetModificationsSince(stamp, modifieds, True)
for node, flag in modifieds:
print(flag, node)
# Get the change via a callback, due to a bug that is going to be fixed, you can call GetModificationsSince with a callback
def callback(node: maxon.GraphNode, flag: maxon.GraphModelInterface.MODIFIED):
print(flag, node)
return True
graph._GetModificationsSinceComplex(stamp, callback, True)
doc.InsertMaterial(mat)
c4d.EventAdd()
if __name__ == "__main__":
main()
Sadly there is a missing bit in Python, since in C++ we have the concept of obverser, which allow for a lambda to be executed when a particular event occurs. Sadly observer are not yet implemented in Python, it's on the to-do list but for the moment it's not there. And in C++ there is the observer ObservableTransactionCommitted, which let you react to a graph transaction commit. So for the moment your best bet is to monitor for EVMSG_CHANGE or use a Timer to monitor the timestamp and check if it changed.
Cheers,
Maxime.
Hi @BigRoy, what is described in the post you find is still the actual way. The only change that we did is that now BaseList2D (so any objects, tags, material) can be hashed and this hash is based on the MAXON_CREATOR_ID.
Regarding new document, sadly there is no way to hook into that and the best way would be to have a MessageData, list all doc by calling c4d.documents.GetFirstDocument and then iterating them by calling BaseList2D.GetNext(). Since a document is a BaseList2D you can hash them and therefor know if there is a new document.
With that's said I will have a look into a limited support of SceneHook to it since it's a reasonable request that is often coming. But no promise.
Cheers,
Maxime.
Hey BigRoy, sadly it's still impossible, I will start the discussion again, thanks for the reminder.
Cheers,
Maxime.
Hi @kitbash_ave just to let you know that this is not a bug but a on purpose change, since you are free to install multiple version in different location of the same version. Therefor the hash represent the installation path, while with the previous way (aka just the version number) you then have some conflict.
If you want to know the version, now you have to read the DisplayVersion key under this entry.
Cheers,
Maxime.
Hi just to let you know that Ferdinand is in vacation, he will look into your topic once you are back.
Cheers,
Maxime.
Hello @Hohlucha,
Welcome to the Maxon developers forum and its community, it is great to have you with us!
Before creating your next postings, we would recommend making yourself accustomed with our forum and support procedures. You did not do anything wrong, we point all new users to these rules.
It is strongly recommended to read the first two topics carefully, especially the section Support Procedures: Asking Questions.
This is a development forum and your question does not seem to be development related. Please read our forum guidelines lined out above. I have moved this topic into General Talk for now. When this is indeed an end user question we must ask you to use our Support Center, the developer forum is not the right place for end user questions. When this is a development question, then please line out the current code you have and provide a meaningful problem description.
Cheers,
Maxime
Hi just to let you know that the bug has been fixed, this will be available in one of the next releases. I will post on this topic once the fix is available.
Cheers,
Maxime.
Hi @Dunhou thanks for contacting us, after investigating it's seems there is a bug that freeze the dirty count once you had an object hierarchy, and delete the complete object hierarchy. Then after this operation the dirty count stop to work. This also impact object with children, their dirty count stop at 1.
Reloading Python make it works, because since few version, it create a new documents and refresh everything under the hood and its just a "workaround".
So thanks a lot for reporting it to us, I've created an internal bug for it, let's see what's the outcome of it.
Cheers,
Maxime.
Hi @ceen, the only option available natively in Cinema 4D is the Align Normal but this change the normal, sadly there is no way to only select the inverted normal polygons.
With that's said please for any feature request, contact the Cinema 4D Support Center.
Cheers,
Maxime.
Hi sorry for not answering this topic earlier, I finally found some time to investigate and sadly there is nothing you can do in your side, this memory leak is coming from the Illustrator Importer and can be big, since all the points and tangents of the file are leaking. A fix will be available in the next update.
Thanks for the report !
Cheers,
Maxime.
Hi @lasselauch it depends of the version you use, if its less than R23 which is bound to Python 2.7, you should use the method GetCoreMessageParamOld
but if you use a newer version (which I hope) you should use GetCoreMessageParam23. You can find this information in the Python 3 Migration - Manual.
Cheers,
Maxime.
Hi if you want to define which node is soled you can use the method NimbusInterface.SetPath.
The nimbus ref can be retrieved by calling BaseList2D.GetNimbusRef on the owner of the graph (most likely the material or the scene node capsule).
Then the Path Node can be retrieved from any GraphNode with the method GraphNode.GetPath.
You may find this topic interesting Unsolo a Node?
Cheers,
Maxime.
Hi can you add a bit more context (when and where do you call this CallButton) and if possible a script and the exact error message you have?
Cheers,
Maxime.
Hi @kangddan sadly there is no real workaround except to re-implement what the button does.
Find bellow a script that show the logic behind each button. Note that HandleCreate, is not a 100% match since some internal functions and helper classes are used which hare not exposed so I had to re-use what was exposed. This mean you may find some behavior change, especially in how the null are rotated along the spline.
import c4d
doc: c4d.documents.BaseDocument # The currently active document.
op: c4d.BaseObject | None # The primary selected object in `doc`. Can be `None`.
IKHANDLE_ELEMENT = 10
IKHANDLE_INDEX = 0
IKHANDLE_LINK = 1
IKHANDLE_TWIST = 2
IKHANDLE_LENGTH = 3
IKHANDLE_OFFSET = 5
def HandleAdd(tag):
bc = tag.GetDataInstance()
cnt = bc.GetInt32(c4d.ID_CA_IKSPLINE_HANDLE_COUNT)
if cnt + 1 > 999:
return
bc.SetInt32(c4d.ID_CA_IK_TAG_END_COUNT, cnt + 1)
baseId = c4d.ID_CA_IK_TAG_END_COUNT + 1 + cnt * IKHANDLE_ELEMENT
bc.SetInt32(baseId, cnt)
bc.SetData(baseId + IKHANDLE_LINK, None)
bc.SetBool(baseId + IKHANDLE_TWIST, True)
bc.SetFloat(baseId + IKHANDLE_LENGTH, 10.0)
bc.SetBool(baseId + IKHANDLE_OFFSET, False)
tag.SetDirty(c4d.DIRTYFLAGS_DATA)
doc.AddUndo(c4d.UNDOTYPE_CHANGE, tag)
def HandleRemove(tag):
bc = tag.GetDataInstance()
newCnt = bc.GetInt32(c4d.ID_CA_IKSPLINE_HANDLE_COUNT) - 1
if newCnt < 0:
return
bc.SetInt32(c4d.ID_CA_IK_TAG_END_COUNT, newCnt)
if newCnt < 999:
baseId = c4d.ID_CA_IK_TAG_END_COUNT + 1 + newCnt * IKHANDLE_ELEMENT
bc.RemoveData(baseId + IKHANDLE_ELEMENT)
bc.RemoveData(baseId + IKHANDLE_ELEMENT + IKHANDLE_LINK)
bc.RemoveData(baseId + IKHANDLE_ELEMENT + IKHANDLE_TWIST)
bc.RemoveData(baseId + IKHANDLE_ELEMENT + IKHANDLE_LENGTH)
bc.RemoveData(baseId + IKHANDLE_ELEMENT + IKHANDLE_OFFSET)
bc.RemoveData(baseId + IKHANDLE_ELEMENT + IKHANDLE_LINK)
tag.SetDirty(c4d.DIRTYFLAGS_DATA)
doc.AddUndo(c4d.UNDOTYPE_CHANGE, tag)
def HandleCreate(tag):
doc = tag.GetDocument()
if doc is None:
return
bc = tag.GetDataInstance()
handleCnt = bc.GetInt32(c4d.ID_CA_IKSPLINE_HANDLE_COUNT)
for i in range(handleCnt):
hLink = bc.GetObjectLink(c4d.ID_CA_IKSPLINE_HANDLE_COUNT + i * IKHANDLE_ELEMENT + IKHANDLE_TWIST, doc)
if hLink is None:
break
if i >= handleCnt:
return
pSpline = bc.GetLink(c4d.ID_CA_IKSPLINE_TAG_SPLINE, doc, c4d.Ospline)
if pSpline is None:
return
pCnt = pSpline.GetPointCount()
pindex = bc.GetInt32(c4d.ID_CA_IKSPLINE_HANDLE_COUNT + i * IKHANDLE_ELEMENT + IKHANDLE_LINK)
if pindex < 0 or pindex >= pCnt:
return
null = c4d.BaseObject(c4d.Onull)
if null is None:
return
null.SetName(f"{null.GetName()}.{pindex}")
doc.InsertObject(null)
doc.AddUndo(c4d.UNDOTYPE_NEWOBJ, null)
doc.AddUndo(c4d.UNDOTYPE_CHANGE, tag.GetObject())
doc.AddUndo(c4d.UNDOTYPE_CHANGE, pSpline)
bc.SetLink(c4d.ID_CA_IKSPLINE_HANDLE_COUNT + i * IKHANDLE_ELEMENT + IKHANDLE_TWIST, null)
haveTangents = pSpline.GetInterpolationType() == c4d.SPLINETYPE_BEZIER
if (haveTangents):
v1Length = pSpline.GetTangent(pindex)["vr"].GetLength()
v2Length = pSpline.GetTangent(pindex)["vl"].GetLength()
bc.SetFloat(c4d.ID_CA_IKSPLINE_HANDLE_COUNT + i * IKHANDLE_ELEMENT + 1 + IKHANDLE_LENGTH, max(v1Length, v2Length))
# This is a simplified part here since internally we have some helper that are not exposed, but the code bellow should do appromitavely the same thing
sh = c4d.utils.SplineHelp()
upVector: c4d.Vector
alignValue = bc.GetInt32(c4d.ID_CA_IKSPLINE_TAG_ALIGN_AXIS)
if alignValue == c4d.ID_CA_IKSPLINE_TAG_ALIGN_X:
upVector = c4d.Vector(1.0, 0.0, 0.0)
if alignValue == c4d.ID_CA_IKSPLINE_TAG_ALIGN_Y:
upVector = c4d.Vector(0.0, 1.0, 1.0)
if alignValue == c4d.ID_CA_IKSPLINE_TAG_ALIGN_Z:
upVector = c4d.Vector(0.0, 0.0, 1.0)
sh.InitSplineWithUpVector(pSpline, upVector)
m = sh.GetVertexMatrix(sh.SplineToLineIndex(pindex))
null.SetMg(m)
c4d.EventAdd()
def main() -> None:
doc.StartUndo()
ikSpline = op.GetTag(c4d.Tcaikspline)
HandleAdd(ikSpline)
HandleCreate(ikSpline)
# HandleRemove(ikSpline)
doc.EndUndo()
c4d.EventAdd()
if __name__ == '__main__':
main()
Cheers,
Maxime.
Hi @danniccs just to let you know that your topic, was not forgotten.
I doubt I will have the time to really look into it, but the correct answers to that is that you should provide an maxon::Url for the filename. There is the RamDiskInterface that have the duty to represent a File System but in the memory. So the idea would be to load your picture data (e.g. your TIFF) in a RamDisk and use this ramdisk within the BITMAPSHADER_FILENAME. But in any case it will need to have the same data structure than the supported picture file format from Cinema 4D.
I will try to come with an example next week.
Cheers,
Maxime.
Hi gheyret, there is no real good workaround about, it GetCommandHelp is just an helper around a CoreMessage.
So technically speaking you can do this
msg = c4d.BaseContainer(c4d.COREMSG_CINEMA_GETCOMMANDHELP)
msg.SetInt32(c4d.COREMSG_CINEMA_GETCOMMANDHELP, 1034012)
help = c4d.SendCoreMessage(c4d.COREMSG_CINEMA, msg, 0)
print(help)
While it will work, sending CoreMessage outside of the Main Thread can have side effect, and can mess-up some internal Message handling, so do it at your own risk.
Cheers,
Maxime.
Hi @gheyret it depends of your context what is "your plugin" is it a CommandData? An ObjectData?
If you want from your command to know the current active context, this is not possible.
The only way so far is to create a command and then in your Message implementation listen to the MSG_COMMANDINFORMATION which is sent by Cinema 4D to your command plugin to retrieve the active context. Then the various data that you need to fill has been explained in Restricting a Command to a GeDialog, this is in C++ but it should be the same things, except that in Python you receive a dictionary. Finally even if this is not really your initial question but if you want to retrieve this information for any commands it is not possible in Python since you can't send MSG_COMMANDINFORMATION to an arbitrary command in Python.
So if you want different behavior then you need multiple commands each one restricted to different manager. Then you need to iterate hover all your commands and find the one that enabled or disabled with c4d.IsCommandEnabled, this way you can guess if a context is active.
Cheers,
Maxime.
Hi another way of doing it is to name a 32x32 TIF icon like the .py file and place it along the script.
There is an example in github script_custom_icon_r13.py script and it's icon script_custom_icon_r13.tif.
Cheers,
Maxime.