Problem with Xref script

On 22/11/2017 at 05:51, xxxxxxxx wrote:

i currently try to write a script that takes all top-level hierarchy elements and converts them into Xrefs, just like the 'Convert the selection to Xref', when right-clicking an object.
I found out that the 'Convert selection to Xref' option is a plugin that if used in code, also opens up the save dialog.

Is there a way to achieve the same functionality without opening the user dialog? Or is it possible to interact with the save dialog so that the user does not need to click anything during the process ?

In my current script, i do not use the Pluging. My current script copies all objects into seperate c4d scenes and saves them, and creates Xrefs for all of them. Unfortunetaly, i did not consider the materials since i wrote the script using simple cubes.. So this script does not work as intended.

Here it is:
import os
import c4d
from c4d import gui
from c4d import documents
from c4d import storage
from c4d import plugins

#An iterator, taking the first top level element of a hierarchy and iterating as long as elements are left
class ItemIterator:
    def __init__(self, firstObject) :
        self.firstObject = firstObject
        self.currentObject = firstObject
    def __iter__(self) :
        return self
    def next(self) :
        if self.currentObject == None:
            raise StopIteration
            toReturn = self.currentObject
            self.currentObject = self.currentObject.GetNext()
            return toReturn

#Identifiers for the layout

#A class containing the GUI of the save dialog
class SaveDialog(gui.GeDialog) :
    #Creates the Layout of the GUI when invoked
    def CreateLayout(self) :
        self.SetTitle("Conversion to XRef")
        self.AddStaticText(TEXT_FIRST_LINE, c4d.BFH_CENTER, name = "All top level objects will be saved into separate scenes.\n")
        self.AddStaticText(TEXT_SECOND_LINE, c4d.BFH_CENTER, name = "A new scene containing all these objects as XRefs will be created\n")
        self.GroupBegin(BUTTON_GROUP, c4d.BFH_CENTER, 2, 1)
        self.AddButton(BUTTON_CONVERT, c4d.BFH_SCALE, name = "Convert")
        self.AddButton(BUTTON_CANCEL, c4d.BFH_SCALE, name = "Cancel")
        self.ok = False
        return True
    #Invokes the suitable function for the cancel and the convert button of the layout.
    def Command(self, id, msg) :
        if id == BUTTON_CANCEL:
        if id == BUTTON_CONVERT:
        return True

def convert() :
    activeDocument = documents.GetActiveDocument()
    documentPath = activeDocument[c4d.DOCUMENT_PATH]
    #Check whether the current scene is saved, so that a relative path can be used for the xrefs and the new scene.
    if len(documentPath) == 0:
        gui.MessageDialog("Please save the actual project, so that the xrefs are saved to the appropriate position")
    #Generate the path for the xrefs, and check whether a folder already exists. If so tell the user to move/delete.
    #This way no merging of data has to be performed.
    saveDirectory = os.path.join(documentPath, "xrefs")
    if not os.path.exists(saveDirectory) :
        gui.MessageDialog("There is already an 'xref' folder in existence. Please delete or move it.")
    firstObject = activeDocument.GetFirstObject()
    itemIterator = ItemIterator(firstObject)
    finalXrefDocument = c4d.documents.BaseDocument()

counter = 0
    generatedDocuments = []
    for item in itemIterator:
        #Create new scene and insert a copy of the item into it.
        print counter
        for material in activeDocument.GetMaterials() :
        documentName = generatedDocuments[counter].GetDocumentName() + ".c4d"
        itemClone = item.GetClone()
        #Generate the complete part to where to save the scene.
        completePath = os.path.join(saveDirectory, documentName)
        c4d.documents.SaveDocument(generatedDocuments[counter], completePath, c4d.SAVEDOCUMENTFLAGS_DONTADDTORECENTLIST, c4d.FORMAT_C4DEXPORT)
        #Construct the xref in the final scene.
        op = c4d.BaseObject(c4d.Oxref)
        #Set the name and the reference file of the xref.
        op.SetParameter(c4d.ID_CA_XREF_FILE, completePath, c4d.DESCFLAGS_SET_USERINTERACTION)
        counter = counter + 1
    #Save the scene containing the xrefs.
    convertedScenePath =  os.path.join(documentPath, "ConvertedScene.c4d")
    documents.SaveDocument(finalXrefDocument, convertedScenePath, c4d.SAVEDOCUMENTFLAGS_0,  c4d.FORMAT_C4DEXPORT) 
    #Working, but stupid, fix.
    #Iterate through the new scene.
    newActiveDocument = documents.GetActiveDocument()
    newFirstObject = newActiveDocument.GetFirstObject()
    newItemIterator = ItemIterator(newFirstObject)
    #Rename the elements and Undo the Change afterwards, so they become editable.
    for newItem in newItemIterator:
        newActiveDocument.AddUndo(c4d.UNDOTYPE_CHANGE, newItem)
    gui.MessageDialog("Conversion successful.")
if __name__=='__main__':
    #Open up the save dialog.
    saveDialog = SaveDialog()
    saveDialog.Open(c4d.DLG_TYPE_MODAL, defaultw = 300)

Any help would be appreciated!

On 23/11/2017 at 02:35, xxxxxxxx wrote:

I solved the problem with the textures by now.
There are two problems remaining:
1. In the complete scene with all the xrefs, all materials from all external scenes are gathered, resulting in some materials that are the same, but only differently named. Is there an easy way to merge them together while apprporiately renaming all objects referring to them?
2. All elements in the new scene are not editable, everything is greyed out. How do i change this ?

On 23/11/2017 at 07:28, xxxxxxxx wrote:

Hello and welcome here ! 
About your first ask you can compare two materials together using  BaseMaterial.Compare, so you can iterate all yours materials, then check if one is similar to another one. If yes replace/merge those assigment Example for get since assigment is jsut an c4d.InExcludeData

About 2, I Don't know I guess it's a special bit, to remove but don't know which one.

On 23/11/2017 at 08:17, xxxxxxxx wrote:

Thanks for the link, this way i think i am able to solve the material problem.

To my second problem, i can update this. I am able to enable the reload option of the xref by renaming the xref, followed by an undo of the rename action. Kind of a dirty fix, but it does the job.

The problem now is, that if i edit the external xref and hit reload in the master scene, nothing happens. I first have to check the generator option on the xref, and then do the reload to get the changes into the master scene. Afterwards, the generator option can be turned off, and everything works fine for further reloads.

Any idea ?

And thanks for the welcome :)

On 23/11/2017 at 11:21, xxxxxxxx wrote:


welcome to the Plugin Café forums :slightly_smiling_face:

Regarding use of 'Convert selection to Xref' commands: Using commands via CallCommand() is basically identical to the user calling the command from GUI. There's unfortunately no way to avoid requesters popping up in this way.

So your approach to re-implement the behavior is correct.

On question 1:
There is a command "Remove Duplicate Materials" which would normally achieve this for you. But with Xref'ed materials this unfortunately does not work. So once more you'd have to come up with a custom option.

On question 2:
This is a matter of the options of the Xref (as user click "Options..." on Xref's Object Properties tab, there under "Modify"). I'm still looking into it, if it is possible to set these options via code. I will get back to you on this, no promises made, though.

On 24/11/2017 at 01:22, xxxxxxxx wrote:

Regarding removing the materials: What does the Compare function on Materials actually compare ? I have two materials which only differ in their name, but the compare functions outputs false. I am i doing something wrong? :

(Update, i now do the comparison based on the suffix of the Material, since same materials will have the same suffix: objectA::Material, objectB::Material). Nonetheless i hope that i can find a clean solution for it.

removeList = []
    materialList = finalXrefDocument.GetMaterials()
    for i in range(len(materialList)) :
        for j in range(i + 1, len(materialList)) :
            if materialList .Compare(materialList[j]) :
_                ObjLink = materialList[j][c4d.ID_MATERIALASSIGNMENTS]_
_                print "ObjLink"_
_                objectCount = ObjLink.GetObjectCount()_
_                for k in xrange(objectCount) :_
_                     tag = ObjLink.ObjectFromIndex(finalXrefDocument,k)_
_                     tag[c4d.TEXTURETAG_MATERIAL] = materialList ___
_ _                removeList.append(materialList[j])__
_ _    __
_ _    for itemToRemove in removeList:__
_ _        itemToRemove.Remove()__
_ _        c4d.EventAdd()__
_ Another issue: To actually use the Xref properly, i found out that i have to check and uncheck the generator option one time on the xref, and hit the modify parameters in the xrefs option. Afterwards i can edit the external scene, and the "reload" button is working properly, and i can also edit the actual objects in the master scene. It would therefore be useful if could perform this programatically._
_ Also, is there are some things i have to do during the process so a correct new document is generated. I would be nice to know if they are actually needed, or if i am doing something wrong/too complex:_
_ 1. after copying the object into a new document, i have to get the materials needed in the object, clone them and link them again correctly_
_ 2. if an instance object is used, i have to set the reference object of the instance object again in the document_
_ Thanks for any assistance!_

On 24/11/2017 at 08:35, xxxxxxxx wrote:

So all questions are sorted, except for the "Modify Parameters" Option of the Xrefs, which i still want to set programatically.

On 28/11/2017 at 09:10, xxxxxxxx wrote:


sorry, it took it a bit longer.
The material comparison is actually comparing the materials in depth, among other things the BaseContainer content (aka parameters). Unfortunately these "other things" cause it to fail for Xref'ed materials.

For the last question I have no good news. There's no way to change the options of an Xref via the public APIs.