Navigation

    • Register
    • Login
    • Search
    1. Home
    2. Cairyn
    3. Posts
    • Profile
    • More
      • Following
      • Followers
      • Topics
      • Posts
      • Best
      • Groups

    Posts made by Cairyn

    RE: 3D Mouse Slider Focus

    @charly CollieMouse rotates around the current selection or the explicit rotation center (the one you set with the INS shortcut). It will not rotate around the mouse pointer (I think... I implemented so many modes over time that I tend to forget the options...). The mouse pointer only exists in 2D space anyway so the actual rotation would happen around a projection of the mouse position into the scene, and if you move the mouse, the rotational center would change... not sure whether that is even a usable mode.

    The mode I'd recommend would be around the explicit center: Position the mouse, press INS (or whatever shortcut you use), and then use the 3D mouse to rotate around that. C4D shows the explicit center as green cross so you always know where it is.

    At the moment there is no R2023 version of CollieMouse anyway, so the point is moot. But you can reach me under cairyn (at) tigress (dot) com if you require specialty development.

    posted in Cinema 4D SDK •
    RE: Custom GUI Linkbox checking for Accepted types?

    @ferdinand Thanks for the reply! Ah, of course, I should have considered that Command is actually the last message I get for that operation. This Message stuff totally got me on the wrong track.

    Naturally, resetting the link and thereby cancelling and reverting the user's drag or pick operation is not as nice as showing a "stop sign" mouse pointer during the operation. Since the ACCEPT clause in a Resource allows setting accepted types, I wonder why there is no API functionality allowing the same. It's not even in the C++ implementation. I suppose people should use external resources anyway, but seeing stuff in the API makes functionality clearer to me at least.

    Regarding PyCapsule, no, I didn't happen across that previously, I just took code from some other thread on this forum to get into the message. I was lazy and looked for PyCapsule in the Cinema 4D API doc, and assumed it undocumented when it wasn't found. Totally my fault, I should have checked the web too. Never program in an unfamiliar scope past midnight! 😹

    Nice code btw.

    posted in Cinema 4D SDK •
    Custom GUI Linkbox checking for Accepted types?

    Hello; (followup to previous Custom GUI question)

    setting up a linkbox in a dialog through a CUSTOMGUI_LINKBOX is easy enough programmatically, and it works fine. However, by default that linkbox accepts any BaseList2D class, object, material, or tag (and more).

    If I were in a Resource, I could use the ACCEPT notation to define the accepted types, but I want to keep the whole micro-plugin in one file, so I am building the GUI through Python code... and the LinkBoxGui class has no Accept method, nor an Accept key for the setup BaseContainer, nor is there anything in the class hierarchy that looks like it.

    On this forum, I found on older post telling me to evaluate the MSG_DESCRIPTION_CHECKDRAGANDDROP message. This apparently needs to be done in the dialog's Message() function, and yes, I do receive this message:

    import c4d
    import ctypes
    import _ctypes
    
    
    PLUGIN_ID = 1000001 # this is a test ID, get your own!
    
    ID_BUTTON_CLOSE = 1001
    ID_BUTTON_CHECK = 1002
    ID_LINKBOX = 1003
    
    
    class LinkDialog(c4d.gui.GeDialog):
    
        def CreateLayout(self):
            self.SetTitle("Link Test")
            self.GroupBorderSpace(10, 10, 10, 10)
    
            if self.GroupBegin(id=0, flags=c4d.BFH_SCALEFIT, cols=1, title="", groupflags=0):
    
                bc = c4d.BaseContainer()
                bc[c4d.LINKBOX_HIDE_ICON] = False
                bc[c4d.LINKBOX_NO_PICKER] = False
                bc[c4d.LINKBOX_LAYERMODE] = False
    
                self.linkControl = self.AddCustomGui(ID_LINKBOX, c4d.CUSTOMGUI_LINKBOX,
                        name="A link", flags = c4d.BFH_SCALEFIT | c4d.BFV_FIT,
                        minw=10, minh=10, customdata=bc)
                self.GroupEnd()
    
            if self.GroupBegin(id=0, flags=c4d.BFH_CENTER, cols=2, title="", groupflags=0):
                self.GroupBorderSpace(0, 20, 0, 0)
                self.AddButton(ID_BUTTON_CLOSE, c4d.BFH_SCALEFIT, name="Close")
                self.AddButton(ID_BUTTON_CHECK, c4d.BFH_SCALEFIT, name="Check")
                self.GroupEnd()
    
            return True
    
        def Command(self, messageId, bc):
            if messageId == ID_BUTTON_CLOSE:
                self.Close()
            if messageId == ID_BUTTON_CHECK:
                obj = self.linkControl.GetLink(c4d.documents.GetActiveDocument())
                if obj == None:
                    print("No object linked!")
                else:
                    print("Name: ", obj.GetName())
                    print("Class: ", type(obj))
                    print("# of direct children: ", len(obj.GetChildren()))
            return True
    
        def Message(self, msg, result):
            if msg.GetId() == c4d.MSG_DESCRIPTION_CHECKDRAGANDDROP: # = numerically 26
                ctrlID = msg.GetLong(c4d.LINKBOX_ACCEPT_MESSAGE_CONTROL_ID)
                print (ctrlID)
                if ctrlID == ID_LINKBOX:
                    print ("Type ID: ", msg[c4d.LINKBOX_ACCEPT_MESSAGE_TYPE]) # 201, not MSG_DESCRIPTION_CHECKDRAGANDDROP !
                    print ("Element: ", msg[c4d.LINKBOX_ACCEPT_MESSAGE_ELEMENT])
                    print ("Element type: ", type(msg[c4d.LINKBOX_ACCEPT_MESSAGE_ELEMENT]))
                    print ("Accept: ", msg[c4d.LINKBOX_ACCEPT_MESSAGE_ACCEPT])
                    # ptr = ctypes.pythonapi.PyCapsule_GetPointer(msg[c4d.LINKBOX_ACCEPT_MESSAGE_ELEMENT], None)
                    obj = self.linkControl.GetLink(c4d.documents.GetActiveDocument())
                    if obj != None:
                        print (obj.GetName())
                        if not isinstance (obj, c4d.BaseObject):
                            print ("Delete dragged object!")
                            self.linkControl.SetLink(None)
                        else:
                            print ("Accepted.")
            return c4d.gui.GeDialog.Message(self, msg, result)
            
            
    class LinkCommandData(c4d.plugins.CommandData):
    
        dialog = None
    
        def Execute(self, doc):
            if self.dialog is None:
                self.dialog = LinkDialog()
            return self.dialog.Open(dlgtype=c4d.DLG_TYPE_ASYNC, pluginid=PLUGIN_ID, defaulth=40, defaultw=200)
    
        def RestoreLayout(self, sec_ref):
            if self.dialog is None:
                self.dialog = LinkDialog()
            return self.dialog.Restore(pluginid=PLUGIN_ID, secret=sec_ref)
    
    
    if __name__ == "__main__":
        c4d.plugins.RegisterCommandPlugin(
                    id=PLUGIN_ID,
                    str="Link custom GUI test",
                    help="Demonstrates Link field usage",
                    info=0,
                    dat=LinkCommandData(),
                    icon=None)
    

    Unfortunately, that post seems to be too old (8 years or so) to be of use, because it says I would receive this message after the value in the control is changed. That is not the case.

    The message appears once when I use the Pick functionality, and many times when I drag an object into the link box. Apparently, I receive this message as long as I hover the mouse over the link field.

    It is unclear under the circumstances which of these message is chronologically the last one (on releasing the mouse button). The object returned by GetLink() on the control is always the old/previous object, not the one I drag into the link field. The dragged object from the message itself, received as "Element", is a PyCapsule (for Python3).

    So, how to proceed? To inspect the dragged object, I need to unwrap it from the PyCapsule; sadly, that class is not documented. I understand that I need to call PyCapsule_GetPointer somehow, and that's where my understanding ends (trying samples from this forum all end in a crash of C4D).
    Then, I am probably(?) required to store a key/value in result (a BaseContainer)... and return False? (Returning False alone does not do anything.)

    And is the evaluation of this method the same for Pick and for Drag activities? Am I even in the correct message?

    posted in Cinema 4D SDK •
    RE: Using AddCustomGui for primitive controls

    @manuel Thanks for the notes, there seems to be a lot that requires additional explanation beyond the API description. I may ask more specific questions later...

    Regarding the DateTime, I got a weird error when I try to show the extended GUI: the clock and calendar fields get duplicated:

    20221124__05__Screenshot_DateTime_04.jpg

    This happens upon calling

    self.dateTimeControl.SetLayoutMode(c4d.LAYOUTMODE_MAXIMIZED)

    or if you like the full script,

    import c4d
    from c4d import gui
    from datetime import datetime
    
    ID_BUTTON_CLOSE = 1001
    ID_DATETIME = 1002
    
    class CustomDialog(c4d.gui.GeDialog):
    
        def __init__(self):
            self.customcontrol = None
    
        def CreateLayout(self):
            self.SetTitle("Custom Control Test")
            self.GroupBorderSpace(10, 10, 10, 10)
    
            if self.GroupBegin(id=0, flags=c4d.BFH_SCALEFIT, cols=1, title="", groupflags=0):
    
                bc = c4d.BaseContainer()
                bc[c4d.DATETIME_TIME_CONTROL] = True
                bc[c4d.DATETIME_DATE_CONTROL] = True
    
                self.dateTimeControl = self.AddCustomGui(ID_DATETIME, c4d.DATETIME_GUI,
                        name="DateTime",
                        flags = c4d.BFH_SCALEFIT | c4d.BFV_FIT,
                        minw=10, minh=10, customdata=bc)
                self.dateTimeControl.SetLayoutMode(c4d.LAYOUTMODE_MAXIMIZED)
                dt = datetime.strptime('16.07.2011 03:37:12',"%d.%m.%Y %H:%M:%S")
                dtd = c4d.DateTimeData()
                dtd.SetDateTime(dt)
                self.dateTimeControl.SetDateTime(dtd)
                self.GroupEnd()
    
            if self.GroupBegin(id=0, flags=c4d.BFH_CENTER, cols=2, title="", groupflags=0):
                self.GroupBorderSpace(0, 20, 0, 0)
                self.AddButton(ID_BUTTON_CLOSE, c4d.BFH_SCALEFIT, name="Close")
                self.GroupEnd()
    
            return True
    
        def Command(self, messageId, bc):
            if messageId == ID_BUTTON_CLOSE:
                self.Close()
            return True
    
    def main():
    
        dlg = CustomDialog()
        dlg.Open(dlgtype=c4d.DLG_TYPE_MODAL_RESIZEABLE)
        dt = dlg.dateTimeControl.GetDateTime().GetDateTime()
        print (dt.year, dt.month, dt.day)
        print (dt.hour, dt.minute, dt.second)
    
    if __name__=='__main__':
        main()
    

    (and no, it doesn't work in a CommandData plugin either...)
    I am still on R23.1 and have no current access to more recent versions, so this may actually be an error that has already been corrected, and it's no longer a valid question...

    posted in Cinema 4D SDK •
    Using AddCustomGui for primitive controls

    Hello;
    using AddCustomGui for complex controls like Quicktab or Linkbox seems easy enough, as you get a specific class derived from BaseCustomGui. However, in the description for GeDialog::AddCustomGui there are numerous "Symbol ID"s listed that do not have a specific class but result in a BaseCustomGui being returned: CUSTOMGUI_REAL, CUSTOMGUI_STATICTEXT, CUSTOMGUI_TIME, etc.

    Mostly they seem to correspond with certain primitives (which already have their own dedicated Add functions, e.g. CUSTOMGUI_REALSLIDER and AddEditSlider), but sometimes there is no separate Add function, as e.g. for CUSTOMGUI_PROGRESSBAR or CUSTOMGUI_MATRIX.

    I have no idea how to work with these.

    For complex controls, I just create the GUI:

    self.customcontrol = self.AddCustomGui(ID_CUSTOM, c4d.CUSTOMGUI_QUICKTAB, 
    name="A quick tab", 
    flags = c4d.BFH_SCALEFIT | c4d.BFV_FIT, minw=120, minh=30, 
    customdata=bc)
    

    and work from there with the returned class. No issues here.

    If I try the same for a primitive control like CUSTOMGUI_REALSLIDER, no GUI appears in the dialog at all (the return value is BaseCustomGui, not None, so the call did not fail at least). I cannot find any documentation on parameters to be set through the passed BaseContainer, if that's the issue.

    You may say "use AddEditSlider" but there is no AddTime or AddProgressBar so I would expect AddCustomGui to work.

    I seem to overlook something important. What do these "Symbol ID"s document, and how do I use them?

    posted in Cinema 4D SDK •
    RE: Look At Camera Tag and text orientation

    (If the hierarchy is not so much of an issue, you could also just set a null as parent of the text. The null will then get the look-at-camera tag so its z-axis points at the camera. The text will be a child of the null, and you turn it by 180° so its z axis points away from the camera in the opposite direction as its parent. I am not totally sure the look-at-camera is always well behaved, though.)

    posted in Cinema 4D SDK •
    RE: SetTime() and document updating

    There are predefined variables available inside the execution context of the Python tag. op is the tag itself (just like doc is the document). So you can just write op[c4d.ID_USERDATA, 2]
    obj = op.GetObject() is the object the tag is on, e.g. the null. It makes sense to place the Python tag appropriately, so you don't have to use SearchObject, which is an "expensive" operation.

    Note that in my sample code I use doc.GetFirstObject() to get the cube, which is of course bad code and you shouldn't do that 😉

    posted in Cinema 4D SDK •
    RE: SetTime() and document updating

    @brucek5

    Well, first of all you wouldn't need Event Notification if the button is on the Python tag itself, instead of the null. For its own controls, the message function already receives all events:

    import c4d
    
    def message(msg_type, data):
        if msg_type == c4d.MSG_DESCRIPTION_COMMAND:
            print (data)
            desc_id = data['id']
            print (desc_id, " received")
    
    def main():
        pass
    

    (to be used in a Python tag with one button)

    This is still called very often but at least doesn't require the whole notification shebang which is listed as private in the documentation, and as long as you use early return methods, it's not bad.
    There may be reasons why you'd want the GUI attached to the null but in this case I would readily put it on the Python tag.

    As far as Python scripts go: Yes, the advantage of a Python tag is that the code is bound to the document/project/file, which is not the case with a script, which resides separately in the script folder, or a full Python plugin, which goes in the plugin folder. There are arguments for both ways to store Python code - the script or plugin is available for all projects (if properly generalized); the plugin even allows setting up a GUI that can be embedded in the layout.

    In your case, I am honestly not sure whether I recommend a script or plugin; I guess I personally would realize it as such (being a script fan 😁 ) but if the code is tightly bound to your project, doing it within the tag is just as fine, as long as you execute the code as reaction to a button (in the message function) and not as part of the scene evaluation (in the main function).

    Setting up your own dialog (or even plugin, for which you'd need a unique plugin ID to be obtained from Maxon) indeed requires a bit more work than the bare user data. Here's a sample script (not plugin) that opens a very simple dialog with two buttons:

    import c4d
    from c4d import gui, utils
    
    ID_EDIT_BUTTONGROUP = 1007
    ID_EDIT_OKBUTTON = 1008
    ID_EDIT_CANCELBUTTON = 1009
    
    class settingsDialog(c4d.gui.GeDialog):
    
        def __init__(self):
            c4d.gui.GeDialog.__init__(self)
            self.exitok = False
    
    
        def CreateLayout(self):
            self.SetTitle("Several text fields")
            if self.GroupBegin(ID_EDIT_BUTTONGROUP, c4d.BFH_CENTER, 2, 0):
                self.AddButton(ID_EDIT_OKBUTTON, c4d.BFH_LEFT, name="OK")
                self.AddButton(ID_EDIT_CANCELBUTTON, c4d.BFH_LEFT, name="Cancel")
            self.GroupEnd()
            return True
    
    
        def Command(self, id, msg):
            if id==ID_EDIT_OKBUTTON:
                print ("OK clicked")
                self.exitok = True
                self.Close()
            if id==ID_EDIT_CANCELBUTTON:
                print ("Cancel clicked")
                self.exitok = False
                self.Close()
            return True
    
    
    def main():
        sDialog = settingsDialog()
        if sDialog.Open(c4d.DLG_TYPE_MODAL_RESIZEABLE):
            print ("Exit:", sDialog.exitok)
    
    
    if __name__=='__main__':
        main()
    

    I do have lessons for script dialogs galore here (section 10):
    https://www.patreon.com/cairyn
    and also simple command plugins that allow you to embed the dialog in the GUI, but that is not really the core of the question. (There are also examples here on the forum if you search for GeDialog, I assume.)

    Back to the original topic:
    I have set up a simple example with a cube falling on a plane through dynamics.
    The Python tag contains a button that, if clicked, executes the scene for frames 0-80 and creates keyframes for rotation and translation of the cube. The tag's code looks like this:

    import c4d
    
    def SetKey (currentTime, obj, parameterDesc, value):
        track = obj.FindCTrack(parameterDesc)
    
        if track == None:
            # create track for this parameter
            track = c4d.CTrack(obj, parameterDesc)
            obj.InsertTrackSorted(track)
        curve = track.GetCurve() # always present
        currentKey = curve.FindKey(currentTime) # returns dictionary "key" (Key), "idx" (Index)
        if currentKey:
            key = currentKey['key'] # key reference to change
            doc.AddUndo(c4d.UNDOTYPE_CHANGE_SMALL, key)
            key.SetValue(curve, value)
            curve.SetKeyDirty()
        else:
            defkey, dub = doc.GetDefaultKey() # new key to insert
            key = defkey.GetClone() # needed?
            key.SetValue(curve, value)
            key.SetTime(curve, currentTime)
            key.SetInterpolation(curve, c4d.CINTERPOLATION_SPLINE)
            curve.InsertKey(key, True)
            doc.AddUndo(c4d.UNDOTYPE_NEW, key)
    
    def message(msg_type, data):
        if msg_type == c4d.MSG_DESCRIPTION_COMMAND:
            print (data) # irrelevant since there is only one button
            fps = doc.GetFps()
            theCube = doc.GetFirstObject()
            for frame in range(81):
                doc.SetTime(c4d.BaseTime(frame, fps))
                doc.ExecutePasses(None, True, True, True, c4d.BUILDFLAGS_INTERNALRENDERER)
                print ("Rot:", theCube[c4d.ID_BASEOBJECT_REL_ROTATION])
                print ("Pos:", theCube[c4d.ID_BASEOBJECT_REL_POSITION])
    
                descX = c4d.DescID(
                        c4d.DescLevel(c4d.ID_BASEOBJECT_REL_ROTATION, c4d.DTYPE_VECTOR, 0),
                        c4d.DescLevel(c4d.VECTOR_X, c4d.DTYPE_REAL, 0)
                )
                descY = c4d.DescID(
                        c4d.DescLevel(c4d.ID_BASEOBJECT_REL_ROTATION, c4d.DTYPE_VECTOR, 0),
                        c4d.DescLevel(c4d.VECTOR_Y, c4d.DTYPE_REAL, 0)
                )
                descZ = c4d.DescID(
                        c4d.DescLevel(c4d.ID_BASEOBJECT_REL_ROTATION, c4d.DTYPE_VECTOR, 0),
                        c4d.DescLevel(c4d.VECTOR_Z, c4d.DTYPE_REAL, 0)
                )
                val = theCube[descX]
                SetKey (doc.GetTime(), theCube, descX, val)
                val = theCube[descY]
                SetKey (doc.GetTime(), theCube, descY, val)
                val = theCube[descZ]
                SetKey (doc.GetTime(), theCube, descZ, val)
                descX = c4d.DescID(
                        c4d.DescLevel(c4d.ID_BASEOBJECT_REL_POSITION, c4d.DTYPE_VECTOR, 0),
                        c4d.DescLevel(c4d.VECTOR_X, c4d.DTYPE_REAL, 0)
                )
                descY = c4d.DescID(
                        c4d.DescLevel(c4d.ID_BASEOBJECT_REL_POSITION, c4d.DTYPE_VECTOR, 0),
                        c4d.DescLevel(c4d.VECTOR_Y, c4d.DTYPE_REAL, 0)
                )
                descZ = c4d.DescID(
                        c4d.DescLevel(c4d.ID_BASEOBJECT_REL_POSITION, c4d.DTYPE_VECTOR, 0),
                        c4d.DescLevel(c4d.VECTOR_Z, c4d.DTYPE_REAL, 0)
                )
                val = theCube[descX]
                SetKey (doc.GetTime(), theCube, descX, val)
                val = theCube[descY]
                SetKey (doc.GetTime(), theCube, descY, val)
                val = theCube[descZ]
                SetKey (doc.GetTime(), theCube, descZ, val)
    
    def main():
        pass
    

    As you see, the scene evaluation is triggered in the aforementioned way by setting the frame and then calling ExecutePasses, which will realize the dynamics. Then the keyframes are generated for the new cube position. You can then switch off dynamics and use only the keyframes. (I am using BUILDFLAGS_INTERNALRENDERER here but I am not sure whether this makes a difference.)

    You can play with that; I suppose without knowing your exact code I can't help much more.

    EDIT: Here's the scene for your convenience:
    Python tag triggers scene evaluation.c4d

    posted in Cinema 4D SDK •
    RE: SetTime() and document updating

    Ah, I see now you wrote "tag based". Since the tag is evaluated within the pass execution, I don't think you can call ExecutePasses() at all (but then, you shouldn't be able to SetTime() either? Are you executing the script as response to a button press, or during the regular execution of the tag?) I am now a bit confused about the logic of it all. You create keyframes, so why is this a tag instead of a regular script that you would only trigger once?

    If you want to create keyframes that reflect a certain time-based state, you could run through the scene in a script (not tag) and ExecutePasses from there, running the script only when necessary. This would essentially be a form of baking your dynamic target values into keyframes.

    posted in Cinema 4D SDK •
    RE: SetTime() and document updating

    That's correct, SetTime() doesn't perform an evaluation of the scene. You need to call BaseDocument.ExecutePasses() to do that.

    posted in Cinema 4D SDK •
    RE: BaseObject.SetModelingAxis(self, m) Not Working

    The Tool settings must set the Axis to Free (under Modeling Axis), so the change of the modeling axis has any visible effect.
    If you do that, you will see that the tag affects the modeling axis... go to Polygon Mode, Axis Mode, move the modeling axis, and it snaps back to the origin.

    The more interesting question would be why you'd want to change the tool setting in a tag? What's the purpose?

    (Also, please include the full scene, not just a screenshot, so the Maxon people don't have to re-type everything...)

    posted in Cinema 4D SDK •
    RE: Get parameter value at given time.

    @danniccs said in Get parameter value at given time.:

    I might not have been clear in my previous post. The parameter is a BaseLink to a mesh, and that parameter is not animated. It should always be a link to the same BaseObject. The mesh itself has animated parameters, and I need to access the mesh object at a specified time t. At the very least, I need to know its position, rotation and scale values at t.

    Okay, you are right, that wasn't totally clear 😇
    But anyway, in my understanding now: you have some undefined things that drive a value that does not have a track and therefore neither a curve nor keys, and you want the value itself.
    In my experience C4D will evaluate such a situation dynamically (unless cached) since there may be any kind of stuff used as driver: Python tags, XPresso tags, dynamics, particles, cloners, etc etc. So, unless you replicate the evaluation in your own code, you cannot access the result without actually allowing C4D to execute the evaluation.

    Maybe Ferdinand will have a better idea once you provide the details he asked for, I am not yet seeing quite what you are going for.

    posted in Cinema 4D SDK •
    RE: Get parameter value at given time.

    @danniccs said in Get parameter value at given time.:

    The parameter I am trying to evaluate is actually a BaseLink to a BaseObject representing a mesh. As such, I cannot use the CCurve::GetValue approach directly on the parameter.

    BaseLink is a discrete parameter, as you cannot interpolate between two links. Nevertheless, you should be able to find out what link is represented by a CCurve "curve", as you still have keys.

    1. Use CCurve.FindKey() to determine the key which is holding the data for the current frame. This would be the key to the left, except if there is none, in which case it's the key to the right.
    2. Use CKey.GetGeData() to read the data. This is not a float or long value - in this case, it's a BaseLink.

    Here is some Python script that reads the currently linked camera from a (selected) stage object:

    import c4d
    from c4d import gui
    
    
    def PrintLinkKey (currentTime, obj, parameterDesc):
        track = obj.FindCTrack(parameterDesc)
    
        if track == None: return
        curve = track.GetCurve()
        cat = track.GetTrackCategory()
        if cat != c4d.CTRACK_CATEGORY_DATA: return
    
        key = None
        currentKey = curve.FindKey(currentTime, c4d.FINDANIM_LEFT)
        if currentKey:
            key = currentKey['key']
        else:
            currentKey = curve.FindKey(currentTime, c4d.FINDANIM_RIGHT)
            if currentKey:
                key = currentKey['key']
                
        if key != None:
            data = key.GetGeData()
            print (data)
    
    
    def main():
        if op == None : return
    
        descCamLink = c4d.DescID(c4d.DescLevel(c4d.STAGEOBJECT_CLINK, c4d.DTYPE_BASELISTLINK, 0))
        PrintLinkKey (doc.GetTime(), op, descCamLink)
    
    
    if __name__=='__main__':
        main()
    

    (Yes, I know you tagged C++ but you can use the same calls and logic - not going to create a full plugin for that...)

    posted in Cinema 4D SDK •
    RE: What is my Clipboardowner for a script?

    Hmm, there seem to be recent changes in that functionality. When I try to copy a newly rendered image from the PictureViewer, the owner actually seems to be set fine in the R23.110:

    >>> c4d.GetC4DClipboardOwner()
    200000244
    

    (I assume that Python cannot do anything that C++ would not support...)

    On the other hand, your other code indeed returns 0 so maybe the functionality wasn't consistent in R23 anyway.

    Thanks, I will go with an ownerid of 0 for now... as there are no callbacks targeting my script, it can't hurt anyway.

    posted in Cinema 4D SDK •
    What is my Clipboardowner for a script?

    Hello;

    I was just looking into the clipboard functions and found that the specialized function CopyBitmapToClipboard requires a CLIPBOARDOWNER.

    I assume this has to do with delay-rendering of clipboard content and the WM_RENDERFORMAT message under Windows - no idea how the clipboard works under Mac... Wasn't actually aware that C4D uses that mechanism at all, but that's how it looks.

    So, if I use CopyBitmapToClipboard in a script or plugin of my own, I wouldn't delay-render the bitmap, so I'd use neither the CLIPBOARDOWNER_BODYPAINT nor the CLIPBOARDOWNER_PICTUREVIEWER constants, which (I assume) would cause the clipboard to send WM_RENDERFORMAT to either window instead of my script. Of course, these are the only constants available, and the function description does not offer anything else.

    When getting a bitmap from the system, GetC4DClipboardOwner returns 0, so I assume I can pass 0 safely too in calls to CopyBitmapToClipboard? Or am I overlooking some larger context here? As I am neither BodyPaint nor the PictureViewer, I would never want to use these constants anyway. But there is no speaking constant for 0, and CopyBitmapToClipboard makes it even a mandatory parameter?

    Can I crash C4D by using one of the two CLIPBOARDOWNER constants in a script (while not implementing a delay-render to clipboard at all which C4D doesn't even offer)?

    GetC4DClipboardOwner allows me to retrieve the clipboard owner, but under what circumstances would I want to know that? If this is only for the delayed retrieval of clipboard content, then this should work fully transparently (?)

    (Edit: Does look better with less formatting now?)

    posted in Cinema 4D SDK •
    RE: How can get the visibility of the every point/edge/face?

    Use any of the functions

    PointObject.GetPointH(self)
    PolygonObject.GetPolygonH(self)
    PolygonObject.GetEdgeH(self)
    
    posted in Cinema 4D SDK •
    RE: Modify rotation of clone child objects

    @will_blizzard said in Modify rotation of clone child objects:

    have no issue rendering to picture viewer with python effector, so not sure what you're referring to

    The error is in the function moData.GetFalloffs(), which you are not using, so you are not affected I guess.

    posted in Cinema 4D SDK •
    RE: Base Object - Attributes : Enabled parameter

    I don't know where it's defined, but the console tells me it's c4d.ID_BASEOBJECT_GENERATOR_FLAG.

    (You are aware that you can drag the label "Enabled" et al. to the Python console to get the access constant? And press Return to get the current value, although that is not shown as constant, at least up to R23...)

    posted in Cinema 4D SDK •
    RE: Copy Paste Point Global Position

    @ymoon

    selgmg is a c4d.Vector, while GetStringFromClipboard expects a string, so Python uses the str function to convert, which results in an unwieldy string like
    Vector(50, 0, 0)
    If you want to put that string back into a vector, you need to parse it - I don't think there is an API function that does that.

    However, you can solve the parsing issue pretty quickly by putting a JSON string to the clipboard instead which represents the three components of the vector:

    import c4d
    import json
    
    def main():
    
        sel_pt = op.GetPointS()
        id_sel_pt = 0
    
        for i in range(op.GetPointCount()):
             if sel_pt.IsSelected(i):
                 id_sel_pt = i
    
        objectmg = op.GetMg()
        selgmg = op.GetPoint(id_sel_pt) * objectmg
        selgmgStr = json.dumps([selgmg.x, selgmg.y, selgmg.z])
        print(selgmg, selgmgStr)
        
        c4d.CopyStringToClipboard(selgmgStr) 
    
    if __name__ == '__main__':
        main()
    

    Then the readback would be just as easy:

    import c4d
    import json
    
    def main():
    
        selgmgStr = c4d.GetStringFromClipboard() 
        selgmgList = json.loads(selgmgStr)
        selgmg = c4d.Vector(selgmgList[0], selgmgList[1], selgmgList[2])
        print(selgmg, selgmgList, selgmgStr)
        
        sel_pt = op.GetPointS()
        id_sel_pt = 0
    
        for i in range(op.GetPointCount()):
             if sel_pt.IsSelected(i):
                 id_sel_pt = i
    
        objectmg = op.GetMg()
        op.SetPoint(id_sel_pt, selgmg * ~objectmg)
        
        op.Message (c4d.MSG_UPDATE)
        c4d.EventAdd()
    
    if __name__ == '__main__':
        main()
    

    Python for C4D scripting:
    https://www.patreon.com/cairyn

    posted in Cinema 4D SDK •
    RE: Retrieving camera view volume

    @user168462

    1. Not that I know of

    2. In a parallel camera, the view frustrum is a rectangular box/"cube". The dimensions depend on the render setting dimensions and the camera's zoom value. For some reason the neutral zoom is 1.024, not 1, so: if your render settings define an image of 1000x500 pixels, and the camera zoom is 1.024, then an object with the dimensions 1000x500 units parallel to the camera's view plane will appear as filling the image.
      (Obviously, in parallel projection the distance of the object does not matter.)
      A zoom of 0.512 will show the object as half size (500x250 pixels, plusminus whatever the antialias settings screw up), a zoom of 2.048 will show the object as double size, etc. As the geometric frustrum is simple, the calculations for the volume (up to the focus distance) are trivial.

    0e8b171d-2ed9-4ff3-a14a-c55925fb05fb-image.png

    Note: I did this on R23, so if the later versions changed the neutral zoom, I wouldn't know.

    posted in Cinema 4D SDK •