I'd like to add to this and ask whether the IDs of the preferences change between releases, assuming that they don't change their meaning/effects.
 Offline
Offline
Posts
- 
RE: Setting Preferences via script
- 
C4D Prototype To Plugin Converter -> Where did it go?I found this post: 
 https://developers.maxon.net/forum/topic/10699/14153_c4d-prototype-to-plugin-converter
 which talks about a tool to convert Python scripts to plugins, which sounds wonderful.Unfortunately the therein mentioned repository is not available anymore (not even archived, it's just gone), even though the GitHub account of the author still exists. I'm not trying to call him out or anything, I'm just interested in what happened to it and if there's a chance to get a hold of it, especially since it was commissioned by Cineversity. 
- 
RE: Reset Tool in Interaction TagHi @ferdinand, Thank you for another great response. Yes, my question is indeed about how to reset the tool to the state it had before my script tinkered with it. I was hoping there is an easy way to do this judging by the doc paragraph I quoted, but it sounds like there isn't. I'll try and be clearer about my question in the future. 
 Your two suggestions both seem fine, so thank you very much for those! Being responsible with what we know is part of what makes this job fun.  
 This here: I'm also not sure whether this shared-axis-behavior is intentional? was aimed towards the behavior and intention of the Interaction Tag, not towards the default value of the tool flag. Let me explain: From my understanding one of the purposes of the Interaction Tag is to manipulate the object Proxy through the object Controller which holds the Interaction Tag. Since the tag effectively prevents the Controller from being manipulated and instead directs all of this to Proxy I believe that the origin of the manipulation matrix should not be in the middle of Proxy and Controller (which it is as you can see in my previous gif) but directly on the object axis of Proxy instead. But if this should be a support ticket instead of a forum discussion I can go ahead and do that of course, I just figured you might know of a good reason why the way it is may actually be intended. Cheers, 
 Daniel
- 
RE: How to properly load async data into C++ generator plugin?Hi guys, I might be completely off here but let me throw this into the room: - These two lines in Messageof theSceneHookDataof @ferdinand 's pseudo code will block the current thread:
 while (AssetIsLoading(url)) continue;- This means that Messagein theSceneHookDatawill only ever finish once the asset has in fact finished loading.
- This means that GVOof the callingObjectDatawill also block until it has finished loading.
 Let's just assume the worst case where a user creates a large number of ObjectDatas and sets all their URLs at once to different values, and loading and processing a single URL would take multiple seconds. Would this cause issues for C4D since so many threads would be blocking?Please correct me if I misunderstood something. Cheers, 
 DanielEDIT 
 Adding to @Havremunken 's response: Threads like this one really help a ton for building up that basic understanding of how C4D works, and @ferdinand provides in-depth answers explaining everything we need to know. I can't thank this man enough.
- These two lines in 
- 
Reset Tool in Interaction TagHello coders, I was playing around with the Interaction Tag. I'll call the object holding the Interaction Tag the Controller and the proxy object the Proxy. Here's what I want to do: - When the user drags the Controller using the left mouse button the Proxy should be moved.
- When the user drags the Controller using the right mouse button the Proxy should be scaled.
 So first I enable Use Right Mouse Button in the Output tab, then in the Scripting tab in the Python script in mouseDown()I check for the right mouse button and eventually set the relevant tool:def mouseDown(): bc = c4d.BaseContainer() c4d.gui.GetInputState(c4d.BFM_INPUT_MOUSE, c4d.BFM_INPUT_MOUSERIGHT, bc) right_mouse_down = bc[c4d.BFM_INPUT_VALUE] == True if right_mouse_down: doc.SetAction(200000089) # Scale else: doc.SetAction(200000088) # MoveSo far so good. But when right-click-dragging the Proxy will be scaled using the shared axis of the Proxy and the Controller: 
  
 This already seemed weird but I tried to work around it because obviously I want to scale the object on its own axis. So I added this line to the script to enable the Per-Object Transform of the scale tool after activating it:tool_data = doc.GetActiveToolData() tool_data[c4d.MDATA_AXIS_LOCALMANIPULATION] = True Works fine. However being a good little developer I want to reset the tool to its original state after we're done. The C4D help states for the function mouseUp() of the Interaction Tag:mouseUp 
 This is called when the user releases the mouse button and signals the end of the user interaction. You can reset tools or kill off any memory-hungry objects that have been allocated in mouseDown or mouseDrag at this point.But how do I reset the tool? I do not want to just set the flag to Falsesince the user might've set it toTruedeliberately regardless of my Interaction Tag so it should regain this expected state.
 DemoHere's a simple demo scene (2024.2.0) on my personal OneDrive: 
 https://1drv.ms/u/s!At78FKXjEGEomLQx70lV4DUgjN5dbw?e=iwNum5To see the behavior: - Activate the Scale tool.
- Set the flag Per-Object-Transform to True.
- Activate another tool (for example move or rotate).
- Make sure no object is selected, then right-click-drag the sphere which will scale the cube.
- After releasing the right mouse button, activate the Scale tool and check the flag Per-Object-Transform. It is now unset.
 
 Intentional Shared-Axis-Behavior?I'm also not sure whether this shared-axis-behavior is intentional? Because if you're trying to manipulate a proxy using this tag you most likely want to do it while completely disregarding the axis of the object holding the tag. Correct me if I'm wrong. Cheers, 
 Daniel
- 
RE: How to properly load async data into C++ generator plugin?Good morning @Havremunken, Is there a particular message I am listening for? Or is there another way to be notified of a parameter change outside of GetVirtualObjects? In order to listen to parameter changes you can implement the Messagefunction in your plugin and listen for theMSG_DESCRIPTION_POSTSETPARAMETERmessage. Cast the incomingdataargument toDescriptionPostSetValueto extract the ID of the changed parameter and get the value through thenodeargument.Sample code taken from here and adjusted slightly: Bool OMyPlugin::Message(GeListNode* node, Int32 type, void* data) { BaseContainer* bc; ... if (type == MSG_DESCRIPTION_POSTSETPARAMETER) { // Get message data DescriptionPostSetValue* dparm = (DescriptionPostSetValue*)data; Int32 parameterId = (*(dparm->descid))[0].id; bc = static_cast<BaseObject*>(node)->GetDataInstance(); switch (parameterId) { case FILE_PATH: // Check for the parameter ID of the file path parameter. // Load the file via bc->GetString(FILE_PATH). ApplicationOutput(bc->GetString(FILE_PATH)); break; } } ... }This way you could also initiate the cancellation of the current loading process in case the file path is being changed while a file is already being loaded. Alternatively you could lock the parameter in the description using GetDEnablingwhile the file is being loaded.From a UX point of view I recommend indicating the current processing status to the user so the user knows whether a file is loading or if the loading has finished/failed. And a personal flavor hint: I'm used to providing control buttons (Load/Start/Import/... and Cancel) for potential long running operations. This way it is clear and intuitive to the user how to use your object. Cheers, 
 Daniel
- 
RE: 3D -> 2D and back with perspectiveGood morning Ilia, Thanks for your detailed response even though this is out of support scope! 
 Let me explain what I meant by this: involve more computations than I'd like it to Using ProjectPointOnLine()the calculations by the script are as follows:- Transform 3D points to 2D screen space using WS().
- Calculate my desired new point in screen space.
- Transform the new point to 3D world space by projecting the new point onto the 3D line using ProjectPointOnLine().
- Transform the new point back from 3D world space to 2D screen space using WS().
- Do more things using these correct 2D coordinates of the new point.
 The issue of this topic is addressed by step 3 but to further work properly with the point in 2D space I also need step 4. I in fact do not only need the 2D coordinates but the z component as well. I feel like there must be a mathematical formula to get the correct Z value without transforming all three vector components back and forth in steps 3 and 4. So the issue I have with the "hacky" solution is purely to reduce the amount of performed calculations to a bare minimum which to be completely honest is just a means to satisfy this itch of mine to optimize as much as possible. It's part of what makes programming fun to me.  Also it would really be interesting to grasp the mathematical concept behind this. As someone who taught himself 3D programming by reading a bunch of game dev tutorials 15 years ago this is super interesting. But enough rambling. 
 Regarding your question: Why can't you first do your computations in screen space and only then use the ProjectPointOnLine() function? In my example the script I'm currently working on takes the new point as a base and creates some more points around that which are not on the line so I can't use ProjectPointOnLine()for those. For that the new point must have the correct coordinates already so I can derive the other new points correctly, otherwise their position in the world will be skewed as well.
 The frustum concept is something I'll have to take some time to fully understand how I can utilize that. Thanks for the hint. Have a nice weekend, 
 Daniel
- Transform 3D points to 2D screen space using 
- 
RE: 3D -> 2D and back with perspectiveHacky SolutionI stumbled across BaseView.ProjectPointOnLine. This takes a line in 3D space and tries to project "a given mouse coordinate" onto it. Don't know why they'd specifically point out that this is for mouse coordinates when it seems to also work for any camera screen coordinates.So here's what I did: - Calculate the screen position of the desired point new_point_s.
- Call BaseView.ProjectPointOnLine. Use the world position ofStartandTargetfor the line andnew_point_sfor the point to project onto the line like this:
 new_point_w = bd.ProjectPointOnLine(start_pos_w, target_pos_w - start_pos_w, new_point_s.x, new_point_s.y)While this technically works it does involve more computations than I'd like it to because it does include all of this overhead of projecting that screen position onto the line which I don't believe to be the best solution to this. Imagine I actually want to work with the point in screen space before translating it back to world space, I'd have to do another round-trip of WS-ing (to actually get the correctnew_point_swith the correct z component) and finallySW-ing that back to world again. Yikes.ProperI stumbled across this document which I think talks about what I need: 
 https://www.comp.nus.edu.sg/~lowkl/publications/lowk_persp_interp_techrep.pdf
 And while I don't expect you to read through those two pages what is important is (12) on page 2 which I think should translate to this:new_z = 1.0 / ( (1.0 / start_pos_w.z) + length_s_flat_delta * ( (1.0 - target_pos_w.z) - (1.0 / start_pos_w.z) ) )However this does not produce the correct result. Isn't this document talking about what I need? Cheers, 
 Daniel
- Calculate the screen position of the desired point 
- 
3D -> 2D and back with perspectiveHello coders, since this is a question about algorithms I figured I'd post this here. Also this is mostly me hoping that someone will drop some wisdom on me in terms of 3D programming. ProblemSo I got two different positions in 3D space, StartandTarget. When I look at them in the C4D viewport I need to calculate a new point which is between the two points, but 30 pixels before the target.Take a look at this example where the distance in pixels on the screen is 298, so I need a new point which is 268 pixels away from Start(298px - 30px), blue X marks the desired spot.
  
 However this point must exist in world space.So my thought process was this: - Get screen coordinates of both points using BaseView.WS.
- Calculate how far away the new point is from Startin percent relative to the screen distance toTarget.
- Create a new point in screen space using this calculated ratio by doing Start + Direction * Distance * Ratio.
- Transform to world coordinates using BaseView.SW.
 The new point will be in the correct screen coordinates, so x and y are fine. However z is messed up which I can only assume is because of the camera perspective. 
  
 Note in the following demo how both the target and the new point are always the same distance apart from each other in Perspective View while the z coordinate in Top View prevents the point from being on the desired pink line between the two points:
  
 This gets more apparent when moving the target closer to the camera.Using a parallel camera (instead of perspective) of course does not have this issue. 
  
 See how it's always nicely on the pink line?And that's where I'm stuck. How do I fix this? Can anyone give me a hint? Here's my code and a demo scene. import c4d import math doc: c4d.documents.BaseDocument # The document the object `op` is contained in. op: c4d.BaseObject # The Python generator object holding this code. hh: "PyCapsule" # A HierarchyHelp object, only defined when main is executed. def main() -> c4d.BaseObject: bd = doc.GetRenderBaseDraw() if bd is None: return c4d.BaseObject(c4d.Onull) # Note for variable names postix: # > _w = World # > _s = Screen # > _s_flat = Screen without z coordinate # World positions of start and target. start_pos_w = doc.SearchObject('Start').GetAbsPos() target_pos_w = doc.SearchObject('Target').GetAbsPos() # The distance in pixels the new point should be away from point 2. distance_from_target = 30.0 # Calculate the screen coordinates of the points. start_pos_s = bd.WS(start_pos_w) target_pos_s = bd.WS(target_pos_w) # Calling WS creates a z coordinate to describe the distance of the # point to the camera. To correctly calculate the 2D distance the # z axis must be ignored. start_pos_s_flat = c4d.Vector(start_pos_s.x, start_pos_s.y, 0) target_pos_s_flat = c4d.Vector(target_pos_s.x, target_pos_s.y, 0) # Get the direction and distance of both points in flat screen space. direction_s_flat = (target_pos_s_flat - start_pos_s_flat).GetNormalized() length_s_flat = (target_pos_s_flat - start_pos_s_flat).GetLength() # Calculate the position of the new point in screen space with respect # to the z coordinate: # 1. Calculate the ratio how far away the new point is from the # target in percent. # 1.a Subtract the distance_from_target from the flat length. length_s_flat_delta = length_s_flat - distance_from_target # 1.b Divide delta by the flat length to get the ratio. ratio = length_s_flat_delta / length_s_flat # 2. Calculate the new point in "deep" screen space. # 2.a Get direction and length of the screen space positions. direction_s = (target_pos_s - start_pos_s).GetNormalized() length_s = (target_pos_s - start_pos_s).GetLength() # 2.b Use the ratio to calculate the new point in "deep" screen space. new_point_s = start_pos_s + (direction_s * length_s * ratio) # Transform the new point to world coordinates. new_point_w = bd.SW(new_point_s) # Create some output to visualize the result. points = [start_pos_w, new_point_w, target_pos_w] poly_obj = c4d.BaseObject(c4d.Opolygon) poly_obj.ResizeObject(len(points), 0) for i, point in enumerate(points): poly_obj.SetPoint(i, point) return poly_objLink to demo scene (2024.2.0) on my OneDrive: 
 https://1drv.ms/u/s!At78FKXjEGEomLMOaoZcJaA2TSwblg?e=8wfev1Cheers, 
 Daniel
- Get screen coordinates of both points using 
- 
RE: Object Plugin - Generator Objects - Hiding the child objects in Editor ViewHi @ThomasB, ~~these generators are (probably) plugins of type ObjectData. These plugins implement theGetVirtualObjectsmethod which returns what will be drawn on the screen, and only this will be drawn with complete disregard of the children. They do use their children as inputs to generate the result which you then see but the children themselves are not returned byGetVirtualObjects.See also the documentation on ObjectData.~~Seems I was wrong about this, sorry. Maybe this plays together with TouchDependenceListbut I'm not sure so I'll just keep my mouth shut now.Cheers, 
 Daniel
- 
RE: Controlling drag&drop using MSG_DESCRIPTION_CHECKDRAGANDDROPHi @m_adam, thanks a lot, that did the trick. To close this topic off here's a snippet to allow other objects but not the current object to be dropped in there: def Message(self, node: GeListNode, type: int, data: object) -> bool: if type == c4d.MSG_DESCRIPTION_CHECKDRAGANDDROP: relevant_id = c4d.DescID(c4d.YOUR_PARAM) # Use the ID of the parameter of your object that you want to check. current_id: c4d.DescID = data['id'] if relevant_id.IsPartOf(current_id)[0]: dragged_element = data["element"] is_same_object = node == dragged_element data['result'] = not is_same_object return True return TrueCheers, 
 Daniel
- 
RE: Python Linting in VSCode?Hi @m_adam, I coincidentally followed the steps on there when I installed the VS Code extension way back. It's pretty straight forward. PyLint in VS Code immediately ramped up my CPU usage so I got rid of that extension. But maybe there's a trick that someone else knows about that could help me out. I'm always open for suggestions and alternatives. What IDE are you using if I may ask? Did you setup anything special to make life easier? Best regards, 
 Daniel
- 
RE: How to create python plugin in 2024?Hello @BretBays, for me it's a matter of looking through the documentation and the samples on GitHub (I can especially recommend the Rounded Tube). I create all the .h and .res files for my plugins by hand. The C++ documentation is definitely more in-depth especially when it comes to Description Resources and there's generally a lot in there you can just carry over to Python. Still there's a lot to just kind of figure out as you go and a lot of digging through the forums. To be completely honest I feel like there should be a guide to get started since there's a lot you can mess up while getting not very descriptive errors especially with the .res files. I'm interested to see what the others say, maybe I'm just making a mountain out of a molehill. Cheers, 
 Daniel
- 
RE: Controlling drag&drop using MSG_DESCRIPTION_CHECKDRAGANDDROPHello @spedler, Thanks, this totally works. However this refuses to take any of my plugin objects, while in the end I only want to refuse the current plugin object; I still want to be able to drag other instances of my plugin object in there. So in the end I'd still like to solve this using advanced logic in the MSG_DESCRIPTION_CHECKDRAGANDDROPmessage.Best regards, 
 Daniel
- 
Controlling drag&drop using MSG_DESCRIPTION_CHECKDRAGANDDROPHello coders. Me again. In a Python ObjectDataplugin I added aLINKparameter. I want allBaseObjects except for the plugin object itself to be droppable in here. So I put this in my .res file:LINK OMYOBJECT_LINK { ACCEPT { Obase; } }According to the documentation of MSG_DESCRIPTION_CHECKDRAGANDDROPI should listen to this message and if I want the drop to not be acceptable I should returnFalse. So this is in my code:def Message(self, node: GeListNode, type: int, data: object) -> bool: if type == c4d.MSG_DESCRIPTION_CHECKDRAGANDDROP: return False return TrueJust to try it out I always return False. And that's where my issue is: I can still drop any object in there. It is limited toBaseObjects so theACCEPT { Obase; }seems to work but returningFalseseems to not prevent the user from dropping something in the link.What am I doing wrong? I tried removing ACCEPT { Obase; }from the .res file to no effect.As an addition: How do I control what the picker of a LINKis allowed to pick? Since it's technically not a drag&drop action it should be something other thanMSG_DESCRIPTION_CHECKDRAGANDDROPright?
- 
Python Linting in VSCode?Hello coders. Are you linting your Python code? My IDE is Visual Studio Code and I tried PyLint but got immediately shot down when it didn't recognize the values I declared in the .h files but tried to reference them in the python code. 
  
 I can't imagine putting# pylint: disable=no-memberanywhere I'm using these just to get around this rule for these values.From what I understand I also can't get around this with Stubbs? It also seems to simply use wrong metadata for its linting, like when calling c4d.utils.DegToRadit says that the function has no return while it very clearly has:
  
 Does not make any sense to me.Oh, and on top of that it's super slow to recognize any changes. I'm used to near instant feedback from C# development using Visual Studio. This should be possible for Python as well right? Are there any linters or settings you can recommend? Best regards, 
 Daniel
- 
Include same container multiple times in .resHello coders. I'd like to include the Oprimitiveaxiscontainer multiple times in the .res file of myObjectDataplugin where the first inclusion should be called the same as the default Orientation while the second inclusion should be called something else like Alternative Orientation. To access both values in my plugin code they of course need separate IDs as well.For the first inlcusion I can simply do this in the .res file: CONTAINER omyobject { NAME Omyobject; INCLUDE Obase; INCLUDE Oprimitiveaxis; }and access it via: orientation = op[c4d.PRIM_AXIS]What would I need to do to include this a second and possibly even more times? Is this even possible? In my head I'd have to define an alternative name and ID right with the INCLUDEstatement. Or do I simply have copy and paste theCYCLEmyself?Best regards, 
 Daniel
- 
RE: Python: Localized Plugin NameIt seems like I found the working combination. The STRINGTABLEin res\strings_en-US\c4d_strings.str must not be named. In my previous response I pasted the contents which as you can see started withSTRINGTABLE omyobjectwhereas it should simply beSTRINGTABLEwithout a name.- I guess this str-file cannot contain named STRINGTABLEs. My first suspicion was that the error was caused by trying to define twoSTRINGTABLEs with the same name (since I had anotherSTRINGTABLEwith that name in the description folder) but even choosing another name in the c4d_strings.str resulted in the same error. Only when removing the name did I get rid of the error.
 I added the plugin ID to both the res\c4d_symbols.h and the res\description\omyobject.h as an enum member called Omyobject. - Having the ID in res\c4d_symbols.h allows for having a value outside of description resources (i.e. dialogs) which I required for the plugin name, as @m_adam already explained.
- Having the ID in res\description\omyobject.h allows for description resources to access an accordingly named value in the STRINGTABLEin res\strings_xx-XX\description\omyobject.str. It also definesc4d.Omyobjectwhich I'm now using inc4d.plugins.RegisterObjectPluginto access the plugin name in res\strings_xx-XX\c4d_strings.str.- Note that this requires the plugin ID to be defined in two places (res\c4d_symbols.h and res\description\omyobject.h) which decreases maintainability.
- Also note that I'm using the plugin ID from the description header to get a value from a non-description STRINGTABLEwhich sure enough does work but is probably not the cleanest solution.
 
 Does this sort of sound right? Is this the intended way? 
- I guess this str-file cannot contain named 
- 
RE: Python: Localized Plugin NameThank you for your response @m_adam. Unfortunately I'm still struggling even though I think I did what you suggested. I'm getting this error: Error reading resource file '...\res\strings_en-US\c4d_strings.str' 
 Line 2The res\strings_en-US\c4d_strings.str has these contents: STRINGTABLE omyobject { Omyobject "My Object"; }Removed link to my repo since it's no longer relevant and will be outdated soon. 
- 
Python: Localized Plugin NameI've read quite a few posts in the forum on how to use GeLoadStringto get a localized string. I'm trying to do this for the name of an object generator plugin written in Python yet I'm always gettingStrNotFoundwhen callingGeLoadStringduringRegisterObjectPlugin.Let me show you what I got. Keep in mind that I excluded stuff that's irrelevant to this topic. This is my folder structure: - res
- description
- omyobject.h
- omyobject.res
 
- strings_en-US
- description
- omyobject.str
 
 
- description
- strings_de-DE
- description
- omyobject.str
 
 
- description
- c4d_symbols.h
 
- description
- Omyobject.pyp
 In res/description/omyobject.h is this: #ifndef _omyobject_H_ #define _omyobject_H_ enum { Omyobject = <mypluginid> } #endif...where <mypluginid>is my plugin ID.In res/strings_**-**/description/omyobject.str is this: STRINGTABLE omyobject { Omyobject "My Object"; }And finally in Omyobject.pyp I'm trying to register to plugin using this code: c4d.plugins.RegisterObjectPlugin(id=c4d.Omyobject, str=c4d.plugins.GeLoadString(c4d.Omyobject), g=Omyobject, description="omyobject", icon=bmp, info=c4d.OBJECT_GENERATOR|c4d.PLUGINFLAG_HIDEPLUGINMENU)str=c4d.plugins.GeLoadString(c4d.Omyobject)returnsStrNotFoundand I don't know why.What is the intended way to do this? 
- res