UNSOLVED Does XRef or XRef simple are now accessible with Python?

Hi all,

After some research on plugincafe, I found many old threads saying that XRef is not scriptable with Python. They are all from 2018, so, maybe in between the situation has changed and is it possible now to:

• Change the c4d.ID_CA_XREF_FILE file referenced on the fly with Python
• Change the Pivot vectors c4d.ID_CA_XREF_PIVOT_POS, c4d. c4d.ID_CA_XREF_PIVOT_ROT and c4d.ID_CA_XREF_PIVOT_SCL
• Force a reload with c4d.ID_CA_XREF_REFRESH if the file referenced has changed?

I do know that's a lot of question, but prior investigated and spending time on this subject I would prefer to be sure that is possible. @ferdinand, your inputs on this are more than welcome!

Thanks a lot,
Cheers,

Christophe

Hey @mocoloco,

Thank you for reaching out to us. I am not quite sure where you found the information that Oxref is 'not scriptable', but that information does not seem quite right to me. You just have to be a bit more verbose when setting parameters. Find an example below.

Cheers,
Ferdinand

Result:
Screenshot 2022-12-20 at 13.24.42.png

Code:

"""Provides an example for setting Xref object parameters.
"""
import c4d

doc: c4d.documents.BaseDocument # The active document.

def main() -> None:
    """
    """
    xref: c4d.BaseObject = c4d.BaseObject(c4d.Oxref)
    if not xref:
        raise MemoryError("Could not allocate Xref object.")
    
    # Add the Xref to the document and define the url for the 'Pillow Decorative' object asset.
    doc.InsertObject(xref)
    assetUrl: str = "asset:///file_0de9c0c9d02f0d77~.c4d"

    # The sort of trick is here, that you must be a bit more verbose with setting parameters,
    # and and cannot use the high level __setitem__, e.g., `node[myId]` access. Instead, you must
    # use the lower level C4DAtom.Get/SetParameter access and simulate a user interaction. The Xref
    # object is a bit special :D
    
    # The DescID for the things you want to write, Get/SetParameter only accepts fully formed DescID.
    fileId: c4d.DescID = c4d.DescID(c4d.DescLevel(c4d.ID_CA_XREF_FILE, c4d.DTYPE_FILENAME, 0))
    posId: c4d.DescID = c4d.DescID(c4d.DescLevel(c4d.ID_CA_XREF_PIVOT_POS, c4d.DTYPE_VECTOR, 0))
    rotId: c4d.DescID = c4d.DescID(c4d.DescLevel(c4d.ID_CA_XREF_PIVOT_ROT, c4d.DTYPE_VECTOR, 0))

    # Set the Xref to the asset and write some pivot data.
    xref.SetParameter(fileId, assetUrl, c4d.DESCFLAGS_SET_USERINTERACTION)
    xref.SetParameter(posId, c4d.Vector(0, 100, 0), c4d.DESCFLAGS_SET_USERINTERACTION)
    xref.SetParameter(rotId, c4d.Vector(c4d.utils.DegToRad(30)), c4d.DESCFLAGS_SET_USERINTERACTION)

    c4d.EventAdd()

    # Clicking the button is not necessary the xref is already correctly setup after the event.

if __name__ == "__main__":
    main()

Hello @ferdinand,

Thanks a lot for your reactivity. Like said is was through old post here from 2018 and nothing more relevant to go through it.

Does the FileName have to start with asset::///?

I'm on MacOSX and when I'm writing the following below the file parameter in XRef remains blank without raising any error on console. Even if it seems I understood the DescID formatting.

xRefFile = "Assets/default_xref.c4d"

xRefPivot = baseDoc.SearchObject('mPivot')
     if xRefPivot is not None:
          print ("mPivot found")
          if (baseDoc.SearchObject('mXRef')) is None:
               mXRefObj = c4d.BaseObject(c4d.Oxref) #c4d.BaseObject(c4d.Oxrefsimple) # R2023 only
               mXRefObj.InsertUnder(xRefPivot) 
               mXRefObj.SetName("mXRef")
        
          mXRefObj = baseDoc.SearchObject('mXRef')
          # Assign paramters on mXRef
          print(f"File: { XRefFile }" )
          descID = c4d.DescID(c4d.DescLevel(c4d.ID_CA_XREF_FILE, c4d.DTYPE_FILENAME, 0))
          mXRefObj.SetParameter(descID, XRefFile, c4d.DESCFLAGS_SET_PARAM_SET)
          print('Model successfully imported')
          c4d.EventAdd()
      else:
          print("Model can't be imported")

The only difference was about c4d.DESCFLAGS_SET_PARAM_SET while you are using c4d.DESCFLAGS_SET_USERINTERACTION. But in that case, could we consider this as a User interaction?

Thanks

Indeed, the problem was caused by using c4d.DESCFLAGS_SET_PARAM_SET instead of c4d.DESCFLAGS_SET_USERINTERACTION.

After reading the SDK documentation again, as far as I understand the XRef Filename is considered as a User Interactive Element. That way, it seems mandatory to have c4d.DESCFLAGS_SET_USERINTERACTION to set the parameter. I assume that's the case for all the parameter accessible by the user in a Ge.

Thanks for the example @ferdinand, it helped me to point this out!

One more question; I'm continuing to explore XRef and I can't really find if there returned value or flag by XRef when the object is correctly loaded.
Is there such a thing, or do I need to check if the Xref object have childs which means somehow that the load happened correctly?

Cheers,
Christophe

Hi @mocoloco,

Does the FileName have to start with asset::///?

No, I only used an asset here so that example runs on any machine (and does not require a specific file on a local volume).

The string assetUrl is deliberately named this way, because we effectively define a maxon.Url here. The parameter ID_CA_XREF_FILE is of type Filename. The type does not explicitly exist in the Python API and is instead represented by str. Internally, all Filename are however converted to and represented as maxon::Url. Filename is just a classic API thin wrapper for maxon::Url.

asset:/// is the scheme of this URL and maxon::Url supports many schemes; asset and file are just two of them. To define a URL in the file scheme you can either do it implicitly, provide no scheme, or be explicit.

assetUrl: str = r"E:/cube.c4d" # Implicitly in the file scheme
assetUrl: str = r"file:///E:/cube.c4d"

it seems mandatory to have c4d.DESCFLAGS_SET_USERINTERACTION to set the parameter. I assume that's the case for all the parameters accessible by the user in a Ge.

I am not sure what you meant with 'Ge', but in general that is not the case. In most cases you can use GeListNode.__getitem__ and GeListNode.__setitem__ to access parameters. For more complex parameter access you sometimes need C4DAtom.Get/SetParameter, usually with the flags DESCFLAGS_GET_NONE and DESCFLAGS_SET_NONE. It is only in very rare cases that you have to simulate a user interaction with DESCFLAGS_SET_USERINTERACTION. This happens when the internal data of a node is much more complex than its parameters make it look like and the node must manage the data on each parameter change. Oxref is an example and also the new Orscamera, i.e., the new standard camera.

One more question; I'm continuing to explore XRef and I can't really find if there returned value or flag by XRef when the object is correctly loaded.

I do not think so, that would be very uncharacteristic for the classic API. Message streams are there sealed, i.e., not accessible for outside observers. Sometimes nodes write a state into a hidden parameter, but looking at the Xref description I cannot see such parameter.

Where would that be relevant for you? Once you have passed the EventAdd() in my script, the object should be finalized. You could technically also send xref.Message(c4d.MSG_CHANGE) to the atom, but this is a bit pointless since you do not get a response for this message and the node is updating anyways.

Cheers,
Ferdinand

Good morning @ferdinand,

Thanks a lot for all the explanation, I really appreciate, it clarify a lot of stuff and fill the gap. I consider this thread Solved. Thanks.

"Where would that be relevant for you? Once you have passed the EventAdd() in my script, the object should be finalized"

Should, that's why I asked. Maybe the XRef plugin raise an error or a dialog box saying that something wrong happened. I also had a look on the XRef description, and thought at first that LOADED parameter what was I was looking, but it is not.

PS: I used 'Ge' to design all the user interactive elements such as Sliders, Input for value vector etc. It was maybe a wrong designation, sorry.

Cheer,
Christophe

Hey @mocoloco,

Should, that's why I asked.

Yeah, that is not the style of the classic API, there are only little error messages and things like that. What you can also do, is use the return value of SetParameter, a boolean. It will be False when setting the parameter failed.

I used 'Ge' to design all [...]

Eh, I understand, you mean from GeDialog, or what? I think it just stands for GenericDialog, it also pops up in thing like GeListNode or GeClipMap. There are some really old entities in the classic API and naming conventions were different then 😉 Ge is at least not a name for interface gadgets in our APIs.

Cheers,
Ferdinand