Navigation

    • Register
    • Login
    • Search
    1. Home
    2. kvb
    K

    kvb

    @kvb

    0
    Reputation
    19
    Posts
    30
    Profile views
    0
    Followers
    0
    Following
    Joined Last Online

    • Profile
    • More
      • Following
      • Followers
      • Topics
      • Posts
      • Best
      • Groups
    kvb Follow

    Best posts made by kvb

    This user does not have any upvoted posts yet.

    Latest posts made by kvb

    RE: GeDialog Timer Always Printing to Console?

    Well, that was a fast solution... couldn't it have emerged BEFORE I hit submit?!? Haha, the solution was easy... I was returning True on the Timer function. Took a look at the python memory viewer example and noticed they don't return a value on it. Removed the return from my Timer() function and all is working as expected.

    def Timer(self, msg):
        print("Timer")
    

    That's it:)

    posted in Cinema 4D SDK •
    GeDialog Timer Always Printing to Console?

    Hey Folks,
    Getting some odd behavior from a GeDialog's Timer and I've been troubleshooting this all day with no success. I thought it was due to a lot of different things, including some python libraries I had been using, but when I boiled it down to the most basic example and tried testing in a c4d without those libraries installed, I still get this very odd behavior of "(null)" being printed to the console over and over again if my Timer is active.

    If I uncomment the print("Timer") line it just prints "(null)Timer". The "(null)" calls always show up on the same line, unless I print from Timer(), then the print statement adds a new line break.

    Issues are the same in 2023 and S26, whether I have the 3rd party python libraries installed or not. The following example is pure vanilla. No libs, just a boilerplate dialog that runs a timer and has 4 static text fields.

    Thanks for your help!

    import c4d
    from c4d import gui
    
    PLUGIN_ID = 1234567  # Replace with your unique plugin ID from PluginCafe
    
    class MyDialog(gui.GeDialog):
    
        ID_GROUP1 = 1000
        ID_GROUP2 = 2000
        ID_STATIC_TEXT1 = 1001
        ID_STATIC_TEXT2 = 1002
        ID_STATIC_TEXT3 = 2001
        ID_STATIC_TEXT4 = 2002
    
        def CreateLayout(self):
            self.SetTitle("My Custom GeDialog")
    
            # Group 1
            if self.GroupBegin(self.ID_GROUP1, c4d.BFH_SCALEFIT, 2, 1, "Group 1"):
                self.AddStaticText(self.ID_STATIC_TEXT1, c4d.BFH_LEFT, name="Static Text 1")
                self.AddStaticText(self.ID_STATIC_TEXT2, c4d.BFH_LEFT, name="Static Text 2")
            self.GroupEnd()
    
            # Group 2
            if self.GroupBegin(self.ID_GROUP2, c4d.BFH_SCALEFIT, 2, 1, "Group 2"):
                self.AddStaticText(self.ID_STATIC_TEXT3, c4d.BFH_LEFT, name="Static Text 3")
                self.AddStaticText(self.ID_STATIC_TEXT4, c4d.BFH_LEFT, name="Static Text 4")
            self.GroupEnd()
    
            return True
        
        def Timer(self, msg):
            print("Timer")
            return True
        
        def InitValues(self):
            self.SetTimer(100)
            print("InitValues")
            return True
    
    
    class MyCommandData(c4d.plugins.CommandData):
    
        dialog = None
    
        def Execute(self, doc):
            if self.dialog is None:
                self.dialog = MyDialog()
    
            return self.dialog.Open(dlgtype=c4d.DLG_TYPE_ASYNC, pluginid=PLUGIN_ID, defaultw=300, defaulth=150)
    
        def RestoreLayout(self, sec_ref):
            if self.dialog is None:
                self.dialog = MyDialog()
    
            return self.dialog.Restore(pluginid=PLUGIN_ID, secret=sec_ref)
    
    
    if __name__ == "__main__":
        c4d.plugins.RegisterCommandPlugin(PLUGIN_ID, "My Custom GeDialog", 0, None, "A custom GeDialog example", MyCommandData())
    
    

    Timer-no-prints.jpg Timer-with-print.jpg

    posted in Cinema 4D SDK •
    RE: Vertex Maps on Generators

    Apologies, I thought my mention of the Voronoi Fracture's ability to generate vertex map tags (itself being a generator that deals with variable geometry) would be clear enough, but I suppose a casual mention doesn't suffice for an app this complex, haha!

    Yeah, it's really all about overcoming that immutable nature of the vertex map tag. I'm generating them on parametric objects and I need them to be able to adapt to changing point counts. Just like they're able to do on editable polygon objects. Which means I have to create a new tag each time the host object's geometry changes and update anywhere else it's linked. That's where TransferGoal() comes in.

    Where my function was failing was a result of TransferGoal() not only updating the BaseLinks, but also the variables within my code. So in my function, once TransferGoal()is called, both vmap and replacement_tag variables end up referencing the same tag (the new one), leaving me without a variable filled with the old tag to delete.

    Passing True as the second argument left my variables alone and only updated the links, essentially making it work perfectly. So passing True or False to TransferGoal() didn't make any difference as far as the BaseLinks were concerned. Those always updated correctly. If I had to guess I would say it has to do with the memory addresses of the objects being transferred... which is just more reason to avoid ignoring that little instruction in the sdk;) I'll trust the devs on matters of memory handling lol.

    Turns out it's quite easy to work around. Since I insert the new tag directly after the old tag I can simply use GetPred() to find the old tag and successfully delete it.

    As far as your question as to whether I would consider this desirable or expected behavior, I would say no. I would much prefer that my in-code variables remain as they were before calling TransferGoal, because as it currently is I'm left with two distinct variables that hold the same object. In other words, I'm left with redundancy and it ends up preventing me from doing more to the old BaseList2D object. In my case, it happens to be relatively easy to re-find and re-reference that BaseList2D object, but that might not always be the case.

    But yeah, now I have a working skeleton function (that doesn't break the rules lol). Just gotta bulk it up with appropriate checks and I should be good to go!

    Thanks for your help @zipit!

    Cheers!
    Kevin

    posted in Cinema 4D SDK •
    RE: Vertex Maps on Generators

    Thanks @zipit!
    While being diligent in my response I actually started to notice a pattern to what I was seeing with my "recreate the tag" workaround. I believe it's related to python's "call by assignment" nature coupled with how TransferGoal() works.
    When I would call TransferGoal() how the sdk instructs me to do (always passing False for the second argument) it would result in the variable that holds the old vertex map tag updating to now hold the newly created tag (since it is now in the baselink from which the old tag was referenced). If I pass True to that second argument, my old tag variable continues to reference the old tag and everything works as expected.

    The question now is, what's the reasoning behind the sdk marking that argument as private and instructing us to always set it to False?

    https://developers.maxon.net/docs/Cinema4DPythonSDK/html/modules/c4d/C4DAtom/GeListNode/BaseList2D/index.html?highlight=transfergoal#BaseList2D.TransferGoal

    Cheers!
    Kevin

    posted in Cinema 4D SDK •
    Vertex Maps on Generators

    I'm pretty sure I know the answer to this, but I just need to ask to make sure I'm not missing something.

    I'm trying to do something similar to how the Voronoi Fracture can generate selections and vertex maps. Selections have been going swell, its these pesky vertex maps! My plugin happens to be a tag, not a generator... not sure if that distinction matters in this context.

    I don't know how c4d updates vertex maps when their host object's geometry changes, but it certainly doesn't happen when that host object is parametric. Once the geometry changes a replacement tag needs to be generated and the old vertex map's goals transferred to it. While I've been able to get this much of it working, once I attempt to delete the original tag it all falls apart. Even if I could get this working, I still don't know where I could safely call the following function in a NodeData plugin:

    def RefreshVertexMapTag(vmap, obj, point_cnt):
        # Make a brand new vertex map tag
        replacement_tag = obj.MakeVariableTag(c4d.Tvertexmap, point_count, vmap)
        # Disable the tag so that c4d doesn't try to update it
        replacement_tag[c4d.EXPRESSION_ENABLE] = False
        # transfer all goals to the new tag
        vmap.TransferGoal(replacement_tag, False)
        # Remove the original tag
        vmap.Remove()
    

    This very simplified function is based on a workaround posted by Maxime last year, simply to illustrate what it would entail to expose a vertex map on a generator who's data is of a variable size, noting that it is not suitable for production because it does some cross-threaded things that could crash c4d.

    I've even tried updating the byte sequence manually using GetAllLowlevelDataW(), but since that's dealing with a memoryview it naturally doesn't work.

    So, I've finally resigned to giving up on this effort, but not without at least asking if it's possible. Can a vertex map be updated to respond to the changing geometry of a parametric object/generator in a way that is safe to use in a NodeData plugin?

    To recap:

    • SetAllHighlevelData(): throws an error when the passed data's size differs

    • GetAllLowlevelDataW(): basically same result as above, except quietly (no error, just never applies any of the changes to the byte sequence)

    • Fully replace tag: Works until you try to delete the old tag, then none of it works... but even if it did it's prone to crashing c4d.

    Thanks in advance for either confirming the bad news... or for swooping in to save the day (if that's possible!)

    Cheers!
    Kevin

    posted in Cinema 4D SDK •
    RE: MoData.GetData()?

    Oh, man... I thought I hit reply on this days ago! Sorry!

    So, MoData tag container didn't work, that's not where the MoData is stored. Well, there seems to be MoData there, just always empty... but I think that's because the mograph stuff uses the messaging system so heavily that just grabbing a basecontainer doesn't cut it.

    @zipit said in MoData.GetData()?:

    Hi,

    I was a bit overworked when I wrote this test, which is why I did made a little booboo in the code above with rather dire consequences. I have fixed this now and the timings are now correct (aside from the general diceyness of the whole test). But the general consensus is the same. BaseContainer is not slow and a good choice when we need key, value pairs. One should also keep in mind, that I did employ simplifications for list both in the insert and delete case, if we want to truly delete and insert arbitrary elements from/to a list, this type is terrible, as it has to rebuild all data each time.

    Cheers,
    zipit

    Thanks for revising this. Even if the basecontainer times technically look worse by comparison, in context I can still see it's no slouch! I've moved forward with this approach and so far I'm getting great performance with breaking down and stashing away the array's data into basecontainers. Still wondering if it wouldn't be better to go with a Read/Write/CopyTo implementation, but I honestly don't see a need here. It's plenty fast enough and doesn't even need to be accessed often.

    @m_magalhaes said in MoData.GetData()?:

    hi,

    it's a bit hard to tell as i still don't understood what you were trying to achieve.
    Do you want to save the mograph data and use them in c4d as a kind of library, or do you want to export them to a 3rd Party software ?

    Cheers,
    Manuel

    I need mograph cache tag functionality without the mograph cache tag. So I need to store MoData and be able to have it persist through file saves/loads, copying to a new document/rendering, duplication, etc. Given the potential size of the data in question (especially once you add animation into the mix) I want to make sure I'm doing this in the most efficient manner possible.
    Given that all the datatypes that contain the MoData aren't directly supported by the normal storage methods in c4d (basecontainers and hyperfiles), I was concerned that breaking all the data into its individual components to store it away wouldn't be efficient. I'm seeing now that I had little to be concerned about. Performance is great so far!

    I think I've even cleared my final hurdle, which was creating an array of MoDatas. My problem there was trying to use a BaseArray when what I needed was a PointerArray. Since PointerArray's take ownership of the pointed object, freeing the allocated MoDatas is as clean and simple as calling Reset() on the Pointer Array:) Injecting my MoData into the effector pipeline is equally simple by doing a CopyTo() or MergeData()... currently I'm doing that in ModifyPoints, but maybe I should be doing it in InitPoints? I'll figure that part out, but right now I'm just happy to have it all working!

    Thank you again zipit and Manuel!

    posted in Cinema 4D SDK •
    RE: MoData.GetData()?

    @m_magalhaes said in MoData.GetData()?:

    hi,

    thanks a lot @zipit for your time.

    About the BaseContainer functions, on C++ that's the same story. Those functions are/were used internally to send data to other part of Cinema 4D (like dynamics for example)

    All will be marked as private as it's kind of useless for 3rd party developers.

    Cheers,
    Manuel.

    My god, did I really just space out on what private means... ignore me, I'm an idiot lol. That is a shame though, because those functions sound like they do exactly what I'm trying to do. Neither the Modata datatype nor the data arrays themselves are supported directly by either basecontainer or hyperfile. It seems there are really only two options then:

    • Break them down into their individual elements and build them back up. Or deal with Get/SetMemory(), which I'd rather avoid... unless that's, I don't know, a good idea maybe?

    • Store away a clone of the mograph generator, but that's gonna have a bunch of extraneous data that I don't need, which I'd like to avoid... unless... the MoData tag itself? Yes, that might work! How could I forget that the MoData is stored on a hidden tag designed specifically for that purpose?!? That fact literally inspired my plugin's design!

    Apparently my brain does not have a perfect hash function and suffers from a terribly high load factor, haha!

    Thanks!
    Kevin

    posted in Cinema 4D SDK •
    RE: MoData.GetData()?

    Wow... well it's hard to argue with those results! Looking back at the posts that led me down this rabbit hole it seems the info may have been outdated and/or was based on casual assumptions.
    Looking again at the research that followed those finds I can see how I may have fallen into the trap of looking to confirm those results instead of actually expanding my understanding. Thanks for the incredibly enlightening post!

    As far as what I'm trying to do, let's just say I need mograph cache functionality but can't rely on the mograph cache tag itself;) Luckily I'll no longer have the bottleneck of python... and nothing wrong with considering the path of least resistance (will likely be my initial test case, actually).

    Thanks again!
    Kevin

    posted in Cinema 4D SDK •
    RE: MoData.GetData()?

    @zipit said in MoData.GetData()?:

    Hi,

    the correct method to access the individual arrays of a MoData object would be GetArray. Assuming that is what you are trying to do. You will also have to specify the array type you want to access, MODATA_MATRIX for the particle matrices for example.

    If you want to keep a copy of a MoGraph state, I would clone the generator in Python (unfortunately we cannot instantiate MoData in Python directly). You could also do this manually, but you should keep in mind that most particle data is presented as mutable objects, so you would have to explicitly clone them or otherwise you will just reference them.

    import c4d
    
    def main():
        """
        """
        # Get the MoData for the selected BaseObject, if there is either
        # no selection or it does not yield some MoData, get out.
        if not isinstance(op, c4d.BaseObject):
            return
    
        modata = c4d.modules.mograph.GeGetMoData(op)
        if modata is None:
            return
    
        # Iterate over the matrices of our MoData. If you want other data,
        # e.g. colors, weights, etc., you will have to iterate over these
        # by using their respective symbol. Passing NOTOK will yield no data,
        # I am not sure why MAXON did make this the method's default argument.
        for matrix in modata.GetArray(c4d.MODATA_MATRIX):
            print matrix
    
        # We could cache the array's of our MoData individually, but an easier
        # approach in Python would be just to clone the generator which hosts
        # the MoData.
    
        # This would be the object op in this example.
        generator = modata.GetGenerator()
        # Clone that object.
        cache = generator.GetClone()
        # So that we can later on access its data.
        cached_data = c4d.modules.mograph.GeGetMoData(cache)
        print cached_data
    
    if __name__=='__main__':
        main()
    

    On a side note: I have not done any extensive tests on the performance of BaseContainer, but they are just integer key hash maps that allow for the dynamic typing of their values. And hash maps are very efficient for larger data sets, especially when it comes to access, which is probably why MAXON used them as a basis for basically everything in Cinema.

    Cheers,
    zipit

    Thanks @zipit, I'm aware of the usual way of getting and setting mograph data. Maxon's gone out of their way to consolidate these arrays in manageable number of basecontainers and I want to take advantage of that.

    I'm not sure about storing an entire clone of the generator itself would be the most efficient method, and I'm trying to be as efficient as possible, but I don't know, to be honest. If I can get it down to as simple a method of storing away just the modata, then I'll be in good shape. But it's certainly more concise than breaking down the data into it's component parts and trying to stash it away in a basecontainer or hyperfile. Certainly worth a look-see:)

    Regarding basecontainer efficiency, I've actually been hearing the opposite in a few posts, that it wasn't suited for large data sets. Now, this may have been expressed in a general sense as, without a perfect hash function, they can get very slow when the entry count gets very large. In the end it depends on the hash function. Wikipedia describes a few different perfect hash approaches, so Maxon is probably employing one of those. I say all that as a simple superuser with an internet connection and a penchant for getting in over my head, not a developer... that's about as deep as my knowledge on hash mapping goes haha! But ultimately, I don't want to rely on a possibility of reading/writing hundreds of thousands of individual basecontainer entries.

    @m_magalhaes said in MoData.GetData()?:

    hi,

    After asking the dev, this is used internally and should be marked as private. Same goes for all modata functions that return a BaseContainer.
    (GetDataIndexInstance, GetDataInstance)

    Cheers,
    Manuel

    Thanks, Manuel. Private, eh? Which would mean I need access to the SetData() function of Modata, which isn't available in python. This just so happens to have become a C++ plugin, which has access to that function. Would I be correct in assuming that I can store these containers in my own basecontainer, then retrieve them later by passing them into a modata?

    Thanks!
    Kevin

    posted in Cinema 4D SDK •
    MoData.GetData()?

    MoData.GetData(self, id=NOTOK)
    Get a copy of the array’s container.

    Cool... how do I use the basecontainer? I can't seem to access any data from it? My goal is to store snapshots of mograph clone arrays for later use. I'm not even sure if this is the correct approach, but it seems convenient to to be able to just save a single basecontainer instead of a series of arrays that might be cumbersome to try and stash away.

    I know that using baseconainers to store/retrieve large datasets isn't very efficient (i.e. don't use it like it's an array). I intend to use Read/Write/CopyTo, since I might be attempting to store entire mograph animations, and I gather writing successive frames as single basecontainers to a hyperfile is better than saving each one as a subcontainer in a basecontainer.

    Thanks in advance!
    Kevin

    posted in Cinema 4D SDK •