Your browser does not seem to support JavaScript. As a result, your viewing experience will be diminished, and you have been placed in read-only mode.
Please download a browser that supports JavaScript, or enable it if it's disabled (i.e. NoScript).
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.
TransferGoal()
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.
BaseLinks
vmap
replacement_tag
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.
True
False
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.
GetPred()
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.
TransferGoal
BaseList2D
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
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
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!)
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
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.
BaseContainer
list
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
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!
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.
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
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
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
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.
MoData
GetArray
MODATA_MATRIX
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.
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.
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
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)
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?
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
@m_magalhaes said in Identify Critical Stop Please?:
hello, For your next threads, please help us keeping things organised and clean. I know it's not your priority but it really simplify our work here. Q&A New Functionality. About the error it's related to FileName. But we need more information to help more. Be careful about the other thread, it may happen that we add stuff and the line will not refer to the correct DataType anymore. Cheers, Manuel
hello,
For your next threads, please help us keeping things organised and clean. I know it's not your priority but it really simplify our work here.
About the error it's related to FileName. But we need more information to help more. Be careful about the other thread, it may happen that we add stuff and the line will not refer to the correct DataType anymore.
Dammit! No wonder my code keeps breaking... I can't even follow simple forum instructions lol! But hey, at least I got it down to one link. Haha, progress!
Yes, Filename, that actually makes perfect sense, so I'm pretty confident I've conquered this error. You're right that I shouldn't rely too strongly on those line numbers. Hell, the next bug fix could make that info obsolete, let alone a reference from years ago. But sometimes you can glean value from outdated info. Maybe in the future line 277 doesn't refer to Filename, but if that error pops up and all my Filename calls look to be in good order I might be able to make some guesses that increase my luck in identifying the issue faster. For instance, I might look at string or static text parameters first, thinking that like datatypes may have been grouped together in the source code. If 329 doesn't refer to baselink in the future I might start looking at in/exclude boxes or shader links first for the same reason. Maybe the list of datatypes in the description manual in the sdk is ordered how they are in ge_container.h and I can use that to choose my next lead. Shot in the dark type stuff, for sure, but maybe gets me generally in the right direction.
You know what, this may have to do with me tracking down a weird behavior where Init() seemed to be the cause of every parameter invoking a MSG_DESCRIPTION_POSTSETPARAMETER message on any parameter change... but I have both debugging AND pandemic fog on my brain, so I don't even know exactly which thing I was doing wrong that may have been the culprit, so there's no point getting into all that... doesn't help that a VS Code update seems to like breaking my local git repo's randomly, so that makes reviewing previous code fun. In any case, the error hasn't popped up at all after ensuring all my base container calls were proper and there were no missing parameter initializations. Not that it was a readily reproduce-able error, just one that would pop up sometimes after a heavy testing session... so I had to do a lot of those to ensure it's really gone.
But it does raise a question. Since the Filename object doesn't exist in python, what's the best way to initialize it? Docs say it's a string, but I saw something in a forum post that someone was initializing their filename parameter directly through the base container. I figured this had to do with how the python wrapper is internally converting the path to Filename object or something, so that's what I've been doing, but that WAS from 2013. So which is the proper way to initialize it?
self.InitAttr(node, str, c4d.ID_FILENAME_PARAMETER)
or
data.SetFilename(c4d.ID_FILENAME_PARAMETER, "")
And even if it's the former, do I set its default value through the basecontainer or in the normal way like so:
node[c4d.ID_FILENAME_PARAMETER] = ""
@r_gigante said in Identify Critical Stop Please?:
Hi @kvb, with regard to the scope and purpose of the CriticalStop I warmly recommend you to have a look at: FAQ CriticalStop() Debug and Output Functions These pages properly explain how the CriticalStop is used in Cinema 4D, how it can be used in plugins and what to expect when it's hit. In you case, as already pointed out by @m_magalhaes , it's likely you're using a GeData::GetFilename() with a GeData whose type was not a Filename. Please recheck your code and if you don't spot the issue, provide us with an example or snippet to reproduce it. Cheers, R
Hi @kvb, with regard to the scope and purpose of the CriticalStop I warmly recommend you to have a look at:
These pages properly explain how the CriticalStop is used in Cinema 4D, how it can be used in plugins and what to expect when it's hit.
In you case, as already pointed out by @m_magalhaes , it's likely you're using a GeData::GetFilename() with a GeData whose type was not a Filename. Please recheck your code and if you don't spot the issue, provide us with an example or snippet to reproduce it.
GeData::GetFilename()
GeData
Cheers, R
Thanks for the links. They'll come in handy on my next project, which will be in c++ (and I will be abusing the hell out of all the debugging options!).
Could it have been caused by the filename returning None? Is that even a thing or just something I noticed while I was missing any initialization on my Filename parameter? I can't see it being anything else... unless an empty string could trigger that error?
Thanks all! Kevin
Hey, Yeah, it is a bit vague, but that's because the only people who could possibly answer would know exactly what that is without any context behind it. It is in the crash report, btw, not the console, and other than a timestamp, this is the complete error code. Some examples of others encountering similar problems: https://plugincafe.maxon.net/topic/9850/13264_cinema-crashed-when-selecting-gradient https://plugincafe.maxon.net/topic/10339/13832_debugger-critical-stop-
Yes, I'm properly initializing my attributes with the proper datatypes and getting/setting them in the appropriate fashion... though I may be missing one somewhere. I'll have to continue to scour my code... but knowing what this particular critical stop is referring to (like those other threads I linked) would be very helpful.
Thanks!