@ferdinand
WTF is wrong with me? It should work now.
Posts
-
RE: Thread safety when handling CTrack in TagData.Message() on button click
-
RE: Thread safety when handling CTrack in TagData.Message() on button click
Oh sorry, I missed that.
Is Google Drive ok?(which I assume is what you mean with 'running (an) animation').
By "running during animation," I meant clicking the button while the animation is playing.
-
Thread safety when handling CTrack in TagData.Message() on button click
Hi,
I have a small plugin example of a Tag Plugin and I want to know if I handle the threading correctly, because in my production version , I encountered some freezes, espacially during a running animation or when Redhift Render View was running.Download Plugin:
test_tag_plugin.zipQuick explanation:
- tag plugin with a button and a link field
- button click has to load a sound
- When the button is clicked, I want to alternate between loading a sound into the track specified in the link field (In the official release, I calculate a sound, save it to the hard drive, and load it into the track.)
- If no track exists, one must be created.
- I handle this within the
MessageMethod as usual - and check using
c4d.threading.GeIsMainThread().
Is this the right approach, or are there other factors to consider?
Should I perform the check inside theadd_soundmethod instead,
or would it be better to check usingc4d.GeIsMainThreadAndNoDrawThread?Please bear in mind that I threw this together quickly; you shouldn't expect perfection in the code—such as error handling—as my only concern right now is thread safety.
Code:
""" Simple CTrack Sound Loader """ import c4d import os from c4d import bitmaps, plugins # be sure to use a unique ID obtained from www.plugincafe.com PLUGIN_ID = 123456789 PY_ADD_AUDIO = 1000 PY_TRACK = 1001 class TestTagPlugin(plugins.TagData): def __init__(self): self.current_sound: str | None = None def Init(self, node, isCloneInit): if not isCloneInit: self.InitAttr(node, c4d.BaseList2D, c4d.DescID(PY_TRACK)) node[PY_TRACK] = None return True def Execute(self, tag, doc, op, bt, priority, flags): return c4d.EXECUTIONRESULT_OK @staticmethod def check_track(node, sound_path): """ Checks if there is a track in the audiotrack slot, if not it creates one and puts the sound into this track Has to be called from the MainThread :param node: the tag instance :param sound_path: str - The path to the sound file """ track = node[PY_TRACK] if not track or not track.GetType() == c4d.CTsound: # create special Audio Track obj: c4d.BaseObject = node.GetObject() track = c4d.CTrack(obj, c4d.DescID(c4d.DescLevel(c4d.CTsound, c4d.CTsound, 0))) obj.InsertTrackSorted(track) node[PY_TRACK] = track track.SetName("Audio_Track") node[PY_TRACK][c4d.CID_SOUND_NAME] = "" node[PY_TRACK][c4d.CID_SOUND_NAME] = sound_path def msg_add_audio(self, node) -> bool: """ Loads the audio into the track :param node: the tag instance :return: bool """ was_animation_running = False if c4d.CheckIsRunning(c4d.CHECKISRUNNING_ANIMATIONRUNNING): # save animation state and stop animation was_animation_running = True c4d.CallCommand(12412) if self.current_sound == "base_1.wav": self.current_sound = "base_2.wav" else: self.current_sound = "base_1.wav" path = os.path.join(sample_path, self.current_sound) if not os.path.exists(path): return False self.check_track(node, path) if was_animation_running: # restart stopped Animation c4d.CallCommand(12412) return True def Message(self, node: c4d.GeListNode, type: int, data: object) -> bool: if type == c4d.MSG_DESCRIPTION_COMMAND: if data["id"][0].id == PY_ADD_AUDIO: if c4d.threading.GeIsMainThread(): self.msg_add_audio(node) return True return True # main if __name__ == "__main__": bmp = bitmaps.BaseBitmap() p_dir, file = os.path.split(__file__) fn = os.path.join(p_dir, "res", "icon.tif") bmp.InitWith(fn) sample_path = os.path.join(p_dir, "res", "samples") c4d.plugins.RegisterTagPlugin(id=PLUGIN_ID, str="test_tag_plugin", info=c4d.TAG_EXPRESSION | c4d.TAG_VISIBLE, description="test_tag_plugin", g=TestTagPlugin, icon=bmp)An additional question regarding the tag:
I’m also curious about a scenario where I launch aGeDialogfrom the tag via a button and pass the tag to it and toggle the sound from within the dialog as well. Would usingc4d.SpecialEventAdd()keep me on the safe side—for instance. So if the dialog runs asynchronously, stores the tag as a member variable,
retrieves the class instance viaGetNodeData, and then calls theadd_soundfunction?
Is that a viable approach? It works in the official plugin.Thanks in advance.
T.B. -
RE: Best way to hide a child and get best perfomance
@Tpaxep
besides that I have a code which works for your scenario.def GetVirtualObjects(self, op, hh): profile_orig = op.GetDown() if profile_orig is None: return None path_orig = profile_orig.GetNext() if path_orig is None: return None # you need to make clones of the children, don't use the orig # first make clones and then use the GACHC Methode below profile = profile_orig.GetClone() path = path_orig.GetClone() dirty = op.CheckCache(hh) or op.IsDirty(c4d.DIRTYFLAGS_ALL) child_dirty = op.GetAndCheckHierarchyClone(hh, profile_orig, c4d.HIERARCHYCLONEFLAGS_ASSPLINE, True) if not any([dirty, child_dirty["dirty"]]): return child_dirty["clone"] print("GVO Executed") # calculation of your geometry sweep = c4d.BaseObject(c4d.Osweep) tag = c4d.BaseTag(c4d.Tphong) tag[c4d.PHONGTAG_PHONG_USEEDGES] = False sweep.InsertTag(tag) path.InsertUnder(sweep) profile.InsertUnder(sweep) return sweepyou can also track dirty manually:
def GetVirtualObjects(self, op, hh): profile_orig = op.GetDown() if profile_orig is None: return None path_orig = profile_orig.GetNext() if path_orig is None: return None profile = profile_orig.GetClone() path = path_orig.GetClone() dirty = op.CheckCache(hh) or op.IsDirty(c4d.DIRTYFLAGS_ALL) #Manually track dirty of the childs (Example) child_dirty = False for child in op.GetChildren(): child_dirty = child.IsDirty(c4d.DIRTYFLAGS_DATA | c4d.DIRTYFLAGS_MATRIX) if child_dirty: break # After Dirty Touch the childs profile_orig.Touch() path_orig.Touch() if not any([dirty, child_dirty]): return op.GetCache(hh) print("GVO Executed") # calculation of your geometry sweep = c4d.BaseObject(c4d.Osweep) tag = c4d.BaseTag(c4d.Tphong) tag[c4d.PHONGTAG_PHONG_USEEDGES] = False sweep.InsertTag(tag) path.InsertUnder(sweep) profile.InsertUnder(sweep) return sweep -
RE: Educational Licenses
So when I catch it in the Message method ,with c4d.C4DPL_STARTACTIVITY
how do I deactivate the plugin in case the license check went wrong? -
RE: Maxon API for Python
@ferdinand
Thank you for your detailed explanation, it shed some light on the whole matter for me. -
Maxon API for Python
Apologies in advance if this question is out of scope.
Is the Maxon API already fully usable for Python, or can it completely replace the classic API? I find it very difficult to understand right now. Or is it more intended for initializing data types and using it in conjunction with the classic API?
And if not, when will it finally be fully usable for Python? -
RE: TempUVHandle always None! Why?
@i_mazlov
Thank you very much for the hint with the texture view and for the helpful links.
The second thread I have already read, but was not able to find the solution so far. I study these examples. Thank you. -
TempUVHandle always None! Why?
Hi,
I am not able to get the TempUVHandle from an active UVWTag in R21.
It returns always "None".
In the following script I created a simple plane in a TempDoc and took the cache and inserted it into the TempDoc. It already has a UVW-Tag, then I have set the object active and so the UVWTag....My Code:
import c4d def main(): temp = c4d.documents.BaseDocument() plane = c4d.BaseObject(c4d.Oplane) temp.InsertObject(plane) temp.ExecutePasses(bt=None, animation=False, expressions=False, caches=True, flags=c4d.BUILDFLAGS_NONE) plane_cache = plane.GetCache().GetClone() if plane_cache is None: raise Exception("no cache") temp.InsertObject(plane_cache) uvw_tag = plane_cache.GetTag(c4d.Tuvw) temp.SetActiveTag(uvw_tag) temp.SetActiveObject(plane_cache) handle = c4d.modules.bodypaint.GetActiveUVSet(temp, c4d.GETACTIVEUVSET_UVWS) print(handle) # Execute main() if __name__=='__main__': main() -
RE: Dynamically adding parameters in tag plugin - description is not refreshing
@ferdinand
Thanks a lot Ferdinand for your time and effort. It is always admirable how carefully and thoroughly you answer many questions.
At first it was often difficult to understand and follow your code examples...now it is a little easier.
Thanks for that.I found out that the following method also does the job:
c4d.SendCoreMessage(c4d.COREMSG_CINEMA, c4d.BaseContainer(c4d.COREMSG_CINEMA_FORCE_AM_UPDATE), 0)Sorry for the second question about why this
buttonDesc[c4d.DESC_FITH] = True buttonDesc[c4d.DESC_SCALEH] = Truein the GetDDescription method are not working. I thought this is a follow-up question.
I will open another topic for that. -
Dynamically adding parameters in tag plugin - description is not refreshing
Hi, in my object plugin it worked fine but in my tag plugin the description cannot be updated after clicking a button which in this example should add a new button.
so when I click the button it adds a new id to the member variable self.id_list, and then I iter through this list in my GetDDescription() method
It just shows the added Button if I click on the Object which holds the tag and select again the tag.
Do I have to send a message? I read the GitHubExample but it is a example with an ObjectPlugin.import c4d from c4d import bitmaps, gui, plugins # Just a test ID PLUGIN_ID = 1110101 PY_ADD_TRACK = 10000 #The Plugin Class class Audioworkstation(plugins.TagData): def __init__(self): self.id_list = [] def Init(self, node): return True def GetDDescription(self, op, description, flags): if not description.LoadDescription(op.GetType()): return False singleID = description.GetSingleDescID() # id_counter = 1 for desc_id in reversed(self.id_list): measure_object_hide = c4d.DescID(c4d.DescLevel(desc_id + 2, c4d.DTYPE_BUTTON, op.GetType())) if not singleID or measure_object_hide.IsPartOf(singleID)[0]: bc_measure_object_hide = c4d.GetCustomDataTypeDefault(c4d.DTYPE_BUTTON) bc_measure_object_hide[c4d.DESC_NAME] = "DELETE" bc_measure_object_hide[c4d.DESC_CUSTOMGUI] = c4d.CUSTOMGUI_BUTTON # id_counter += 1 if not description.SetParameter(measure_object_hide, bc_measure_object_hide, c4d.DescID(c4d.DescLevel( c4d.ID_TAGPROPERTIES))): return False return True, flags | c4d.DESCFLAGS_DESC_LOADED def Execute(self, tag, doc, op, bt, priority, flags): return c4d.EXECUTIONRESULT_OK def Message(self, node, type, data): if type == c4d.MSG_DESCRIPTION_COMMAND: if data["id"][0].id == PY_ADD_TRACK: last_id = None if self.id_list: last_id = self.id_list[-1] new_id = last_id + 100 self.id_list.append(new_id) else: new_id = 10100 self.id_list.append(new_id) node.Message(c4d.MSG_CHANGE) return True # main if __name__ == "__main__": bmp = bitmaps.BaseBitmap() dir, file = os.path.split(__file__) fn = os.path.join(dir, "res", "icon.tif") bmp.InitWith(fn) c4d.plugins.RegisterTagPlugin(id=PLUGIN_ID, str="C4D-Audioworkstation", info=c4d.TAG_EXPRESSION | c4d.TAG_VISIBLE, description="audioworkstation", g=Audioworkstation, icon=bmp)
edit by @ferdinand:
@ThomasB said:
I have also problems to scale the button so that it fits the attribute manager:
so this doesn't work in the GetDDescription Example above:
bc_measure_object_hide[c4d.DESC_SCALEH] = True bc_measure_object_hide[c4d.DESC_FITH] = True -
RE: 2024.4.0 crashes when setting key-values
@ferdinand
this is really odd, because I have a Button in my plugin which simply creates keyframes for specific description parameters of the plugin, similar as it is in the PoseMorph Tag when you create keyframes for all sliders at once. And here it works without crashing:
Maybe this helps you.
The code of writing the keyframes is pretty the same, the only difference is that I set here keyframes for the description Id's instead of the user-datas...hmthis is just a snippet:
PY_STRENGTH_A_MANUAL = 10051 PY_STRENGTH_E_MANUAL = 10052 PY_STRENGTH_I_MANUAL = 10053 PY_STRENGTH_O_MANUAL = 10054 PY_STRENGTH_U_MANUAL = 10055 PY_STRENGTH_F_MANUAL = 10056 PY_STRENGTH_W_MANUAL = 10057 PY_STRENGTH_SH_MANUAL = 10058 PY_STRENGTH_OTHERS_MANUAL = 10059 PY_STRENGTH_CLOSED_MANUAL = 10060 id_list = [PY_STRENGTH_A_MANUAL, PY_STRENGTH_E_MANUAL, PY_STRENGTH_I_MANUAL, PY_STRENGTH_O_MANUAL, PY_STRENGTH_U_MANUAL, PY_STRENGTH_F_MANUAL, PY_STRENGTH_W_MANUAL, PY_STRENGTH_SH_MANUAL, PY_STRENGTH_OTHERS_MANUAL, PY_STRENGTH_CLOSED_MANUAL ] #========Plugin==================== def Message(self, node, type, data): if data["id"][0].id == PY_RECORD_MANUAL: time = node.GetObject().GetDocument().GetTime() node.GetDocument().AddUndo(c4d.UNDOTYPE_CHANGE, node) if c4d.threading.GeIsMainThread(): for ide in id_list: track = node.FindCTrack(ide) if not track: track = c4d.CTrack(node, ide) node.InsertTrackSorted(track) curve = track.GetCurve(c4d.CCURVE_CURVE, True) keydata = curve.AddKey(time) if keydata is None: raise RuntimeError("Could not add key frame.") key = keydata["key"] key.SetValue(curve, node[ide]) key[c4d.ID_CKEY_PRESET] = 2 # c4d.ID_CKEY_PRESET_AUTO_OVERSHOOTWEIGHTED c4d.EventAdd() -
2024.4.0 crashes when setting key-values
Hello,
I'm just a bit stumped. In previous versions of C4D this approach, writing keys worked perfectly.
Normally the routine is executed by a plugin in the message method, but I was able to change the example into a script and that's where the error occurs.Here I have the script and the required scene with the object.
bake_test.zipThe script should simply write keys with values into the user data, usually the user datas are animated and they are a lot more, so in the plugin it has to filter the right ones
After detailed debugging, I'm sure the error happens in the third last line with key.SetValue(curve, value)import c4d doc: c4d.documents.BaseDocument # The currently active document. op: c4d.BaseObject | None # The primary selected opect in `doc`. Can be `None`. def bake(): strength_list = { "AA": 1, "EE": 1, "IY": 1, "OW": 1, "UW": 1, "F": 1, "W": 1, "SH": 1, "T": 1, "M": 1 } after_list = [] # ================================================= user_list = ["AA", "EE", "IY", "OW", "UW", "F", "W", "SH", "T", "M"] parameter_list = [] for ide, bc in op.GetUserDataContainer(): if bc[c4d.DESC_NAME] in user_list: parameter_list.append(ide) after_list.append(bc[c4d.DESC_NAME]) if not parameter_list: # if no userdatas added c4d.gui.MessageDialog("Missing User Datas!\n\nClick 'Add User-Data'") return True doc.AddUndo(c4d.UNDOTYPE_CHANGE, op) c4d.StatusSetSpin() # start = node[PY_BAKE_START] # end = node[PY_BAKE_END] for frame in range(0, 90): time = c4d.BaseTime(frame, doc.GetFps()) doc.SetTime(time) doc.ExecutePasses(None, True, True, True, c4d.BUILDFLAGS_NONE) # False True False for ide in parameter_list: track = op.FindCTrack(ide) if track is None: track = c4d.CTrack(op, ide) op.InsertTrackSorted(track) curve = track.GetCurve() keyData = curve.AddKey(time) if keyData is None: raise RuntimeError("Could not add key frame.") key = keyData["key"] key.SetValue(curve, op[ide]) key[c4d.ID_CKEY_PRESET] = 2 # c4d.ID_CKEY_PRESET_AUTO_OVERSHOOTWEIGHTED c4d.StatusClear() def main() -> None: """Called by Cinema 4D when the script is being executed. """ bake() if __name__ == '__main__': main()Cheers
-
Snapping to a specific position of an object plugin's object chain
An Object plugin returns an object chain with certain objects.
But now I need a point at a very specific position of the object where other objects created in Cinema 4D can snap to it.I have or rather wanted to solve this by creating an internal polygon object that is placed in the object chain with a point, for example. I then made the point visually visible using the Draw method
ObjectData.Draw(self, op, drawpass, bd, bh)However, the snapping does not work properly. If you're too close, nothing will snap. If the snapping radius is too small, nothing works.
Am I completely on the wrong track or should I approach the whole thing differently? Sometimes there's just a lack of new things, and I can't always find everything in the Github examples...sometimes I have to laugh at myself, how stupid I want to start a spedific approach....Regards
-
RE: How do I get the cache of a cloner in an ObjectData Plugin
@ferdinand
oh man thanks I forgot thisBaseDocument.ExecutePasses(bt=None, animation=False, expressions=False, caches=True, flags=c4d.BUILDFLAGS_NONE)I tried that before but it seems I have set the caches parameter wrong.
Thank you very much.... -
How do I get the cache of a cloner in an ObjectData Plugin
Hi, sorry for this as always dumb question.
I was trying to get the Cache of a cloner object in the GetVirtualObjects(self, op, hh) method.
First I tried it in the python generator, there was already a cloner in the scene and in the python generator I returned the cache so to say, but this approach doesn´t work out in an object plugin.
Even if I create a temp_doc. The returned cache is always None.
But converting the cloner with a ModelingCommand is not the best way, if the cloner produces for instance a million clones.This method works in a python generator when the clone is already in the scene
cloner = op.GetDown() return cloner.GetCache().GetClone()But creating a cloner and returning its cache doesn`t work.
cloner = c4d.BaseObject(1018544) c4d.BaseObject(c4d.Ocube).InsertUnder(cloner) return cloner.GetCache().GetClone()Is there a way to get the cache in a plugin?
Sweep, Extrude, other Generators, Deformers and parametric objects work perfectly. -
RE: Boole Object causes version 2024 to freeze
@ferdinand
Hello Ferdinand,
Thank you very much first of all.
yes, you're right, I worked extremely sloppily with the SMC method, of course I'll take the threading into account and also work with a Temp Document.Regarding the problem itself, I can only say that reinstalling CINEMA 4D solved our problem.
Cheers
-
Boole Object causes version 2024 to freeze
Hi,
for demonstration purposes, I have an extremely stripped down version (1200 LOC) of my plugin (6000 LOC) here.
Unfortunately it was not possible to insert the whole Pyp file here because the post can only be a maximum of 32767 characters long. That's why I only have sent the classes and functions that are not so important are without code.- Condition: the window bars must be activated. Error with Boole happens in the method
def bar_cutter(op, obj, bars, fixed=False)
Description:
Of course, it is not technically perfect and in this version it is more of a prototype. I was able to narrow down the error to the Bars_Cutter() function. That's why I only included the bar_cutter method.
- In this function I read specific points from the created frame and create a spline, which I throw into an Extrude and then convert to a polygon object. this extrude is used to cut the window bars.
- I then create a Boole object and throw the previously created bars together with the extrude into the Boole object.
Normally I also create a polygon object from the Boole object with SendModelingCommand() but to prove that the error comes from the Boole, I insert the Boole object directly under the window frames.
Problem:
The problem now is this. If you reduce the arch height of this arched window, the polygons will now intersect. The following usually happens (R23-2023). The bars then simply disappear. And the glass is no longer displayed correctly either. That's actually OK. The user immediately knows that something is intersected. (Watch Video)
And in version 23-2023.2 this works, as can be seen in the video.
But in version 2024 when the polygons intersect, Cinema 4d freezes.I have a short video to watch here.
-
RE: Object Plugin - Generator Objects - Hiding the child objects in Editor View
@m_adam
Thank you very much Maxime.
But there is one more thing I would like to know.
Why don't the highlighted links work? I am always referred to this site.
-
RE: Python: Detect Plugins
@m_adam
Sorry, little question, what does the python extenstion "pype" mean?