SOLVED Ensuring only 1 specific pyTag in a document?

Hi, I've got a prototype/pythonTag running in py2 & py3, where all the basics are just working fine. The problem is that python2 crashes when I try to avoid multiple "MSG" pyTags in one scene/doc...

I can only have 1 tag per scene since the pyTag adjusts the timeLine where needed. Multiple tags would fight each other..

I thought of a workaround involving 2 items set to the document container (op & op.GetObject()).
The basic principle is that I know what/where to delete if a new tag is copied/inserted by the user. (The pyTag itself is set to No_Delete & No_DragNDrop)

As said, this works fine in py3 (R23/24), but I don't understand why this isn't working in py2 - for I can just read the doc.GetDataInstance & ..GetData(..) etc.

Any clues on what's causing the crash in py2?
(and as a sub-question - is there a smarter way to ensure 1 specific tag per scene?..)

See the check_single_pytag() func below. Tia, Jochem

def set_doc_bc(uniqueId):
    doc = op.GetDocument()
    docBC = doc.GetDataInstance()
    subBc = c4d.BaseContainer()
    subBc[123] = op
    subBc[124] = op.GetObject()
    docBC.SetContainer(uniqueId, subBc)
    doc.SetData(docBC)


def check_single_pytag(): # check pyTag(s) - for there can be only one..
    doc = op.GetDocument()
    docBC = doc.GetDataInstance()
    uniqueId = 12345 # not this :)

    def new_null(): # small local func to set pyTag to a new null..
        nully = c4d.BaseObject(c4d.Onull)
        nully.InsertBefore(op.GetObject())
        nully.SetName("MSG")
        nully[c4d.NULLOBJECT_DISPLAY] = 14 # none
        nully.InsertTag(op)
        op[c4d.ID_USERDATA,52] = nully
        op.Message(c4d.MSG_UPDATE)

    try:
        docBcTag = doc.GetDataInstance()[uniqueId].GetData(123)
        if docBcTag == op: pass # original tag, so skip
        elif not docBcTag.GetDocument(): # orginal tag deleted - set a new one
            new_null()
            set_doc_bc(uniqueId)
        else:
            # UD52 = linkfield on pyTag with op.GetObject()
            if op[c4d.ID_USERDATA,52] == None or op.GetObject() == doc.GetDataInstance()[uniqueId].GetData(124):
                op.Remove() # 2nd tag on original pyNull - remove
                return False
            else: # new/copied tag - remove all..
                op.GetObject().Remove()
                return False
    except AttributeError: # no tag in doc yet..
        new_null()
        set_doc_bc(uniqueId)

    return True

Hi @ferdinand, Learned a couple of new tricks, almost had a good solution, but in the end it still wasn't 100% safe.. So I decided to just disable the pyTags in such a way they never would be able to initialize again. Not the nicest solution, but anyhow.. Thx for your input - consider the topic closed.

Hello @jochemdk,

thank you for reaching out to us. While we do understand your question, it is extremely hard to answer for us, since you do not share the full code (your Python Programming Tag code is missing at least its main function).

What we can say immediately though, is that when you invoke check_single_pytag() from main(), then you are violating the threading restrictions of Cinema 4D, because you do modify the scene graph within check_single_pytag(). The only valid place inside a Python Programming Tag to invoke your check_single_pytag() would be message() for messages which are send from the main thread (almost all of them are).

So, we would ask you to provide an executable example which has been reduced to a form which does reproduce the problem but does not contain too much other stuff either.

Also: When one has maneuvered one's code into a corner where a scripting object does cause crashes, one can replace the sys.stdout provided by our Python (which itself is not Python's native stdout) with a file handler, so that one can read out stuff even when Cinema has crashed. The topic has been discussed in the thread Debug Python related CRASH before. The example does only close the file handler when the handler is being deallocated, which can cause unreadable files when Cinema 4D does crash. So, you would have to open and close the file on each write operation when facing that problem.

Cheers,
Ferdinand

Hi @ferdinand, Indeed the above code is checked via the main function of 1 or 2 python tags..
I’m aware of things you’re not supposed to do with a pythonTag, but sometimes it just works.
I’ve been testing for over a week now with the above code in py3 & literally not a single problem 🙂

Ok, so I understand you’re not supposed to give me advise on things I shouldn’t do, but perhaps you can point me in the right direction..
I use the message function all the time for user data buttons, but how would I proceed to “catch a python tag insert” from a message (id)?

Tia, Jochem

Hello @jochemdk,

using message in Python Tag to react to button clicks is okay, it is in fact one of its major purposes. Furthermore, you can do everything you want in message when it is on the main thread, as this will lift all threading restrictions from your code. These threading restrictions also do not work in a determined consequence fashion. Yes, we are aware that you can often wiggle your way around them. The threading restrictions are there, so that other code can operate on the assumption object pointers will be valid while your code is being executed in parallel. When you are lucky, there might be now code running parallel to your code which does rely on that specific patch of memory which is being modified by your scene graph modifications and everything will therefore run fine. But just changing the order of two objects in the Object Manger could all change that in bring down Cinema 4D burning. Which is why we cannot provide 'hacks' around it since it is impossible to anticipate in which context your code will run.

There are no messages in NodeData.Message (and by extension in message in a Python Tag) you could listen for to detect a Python Tag being inserted into the document your Python Tag is part of. NodeData.Message only does receive messages about the C4DAtom which is representing that NodeData, e.g., some object, material, shader, etc. in one of the mangers. Inserting a node, e.g., a Python Tag, will however add an event which you can detect as EVMSG_CHANGE in a core message method, e.g., GeDialog.CoreMessage. This event will be raised for every scene modification, i.e., will be fired very often and you then still must detect yourself that a Python Tag has been added (and that not just some other event did occur).

So, if you want to do some constant scene modification while tracking the state of the scene, a GeDialog, GeUserArea or MessageData would be the best bets in Python. A SceneHookData might even be better, depending on what you are trying to do, but that interface is only available in C++. You could technically also piggy-back onto some frequently fired messages in message in your Python Tag, I have done that myself in the past, and it can work reasonably well to do something constantly on the main thread. The problem is: A. Tags do not lend themselves well for this hack, since their message can go dormant for seconds when the tag is not selected. B. Monitoring a scene graph and shuffling stuff around can be become quickly time-consuming to do on larger scenes. So, you will need optimization to not constantly block the main thread. Which will be hard to do in a Python Tag.

Cheers,
Ferdinand

Hi @ferdinand, thx for your reply. Think I've got some more investigating to do... I've been looking for message-function examples, but there aren't that many (or I use the wrong search terms:).

If anyone has some examples to get me in the right direction, it would be very much appreciated. Otherwise, just close the topic & I'll have to study harder:) Thx, J

Hi @jochemdk,

what do you mean by 'message-function examples'? Examples for Python scripting objects that support the message function? There are no official examples since all the scripting objects are just front-ends for the respective NodeData types, e.g., message() is just a front-end for NodeData.Message. So, we just treat them as documented by the examples for these interfaces.

NodeData.Message has quite a few examples in our GitHub examples. There is also a bit longer example for the message function in a Python Programming Tag in our Praxiswochen Repository. I used the function there in a very conservative manner though; to react to button clicks and clamp values. I am not aware of any examples provided by us where a tag (be it a Python Programming Tag or a 'proper' TagData) does modify the scene graph by inserting nodes. While this is technically possible, we probably will never do this, since there are better options as lined out in my previous posting. There is however this thread where I did abuse c4d.MSG_GETREALCAMERADATA to automatically build the user data interface of a Python Generator Object. This example does not show how to modify the scene graph from inside message(), but it demonstrates the idea of piggybacking which is rather simple. You just pick a frequently broadcasted message and use it in a different context.

And to be clear here: A tag is the wrong interface for what you are trying to do. You should use a dialog.

Cheers,
Ferdinand

Thx @ferdinand for the input, I'll look at it as soon as I can!

Hi @ferdinand, Learned a couple of new tricks, almost had a good solution, but in the end it still wasn't 100% safe.. So I decided to just disable the pyTags in such a way they never would be able to initialize again. Not the nicest solution, but anyhow.. Thx for your input - consider the topic closed.

Okay, thanks for the heads up 🙂