Get pressed keys in ObjectData plugin
-
Hello @ferdinand,
I'm so grateful that you're looking into this and providing me with so much information. Step by step I'm getting a grasp on how to work with C4D. Also thank you so much for pointing out bad hacks since those prevent me from getting bad ideas as well as help me understand why they are bad. This is so valuable if I ever want to be able to make things completely on my own.
What I actually want to do
My
ObjectData
plugin shows a number of Nulls which are ordered sequentially. All of them should show handles to control an internal value for orientation and size. Think of the controls on a spline point when working with the tangents:Let's say I have five Nulls. I want each Null to have a handle like a spline point tangent, except for the first and last Nulls, those should only show a "single sided tangent handle". This looks something like this:
Nulls 0 and 4 therefore have a single handle, while Nulls 1 to 3 each have two handles, one for either side of the imaginary tangent.
When hovering over a handle I want to highlight it along with the neighboring handle if there is one:
Signalling to the user that he's going to manipulate both of these handles at the same time.
And this is where my requirement comes in: When holding Ctrl (or Shift, haven't decided yet) only the hovered handle should be highlighted (and later on also manipulated) while the neighboring should not, so as to indicate that manipulating this handle will not affect the neighboring handle.
It will then behave similiar to a tangent handle on a spline point when holding Shift.
Also let me state that simply using a Spline is not an option due to other features I'd like to provide. Like better spline handles because dear lord I despise those tiny lines and points on the default Spline handles. But that's a story for another time.
So there we go, this is my goal.
Using a
SceneHookData
From what I understand (and I have to admit I haven't read much into it yet) this will allow me to catch those keyboard inputs. So my next attempt will be to do just that. However I'm not too fond of performing the draw in there as you suggested. Drawing the handles should still be the responsibility of the
ObjectData
itself.If this was C# I'd implement a simple observer pattern so the
SceneHookData
provides a list to which any object implementing a certain interface may subscribe, and whenever one of those keys is pressed theSceneHookData
will call a function on those objects to let them know. I'm not sure if I'm fluent enough in C++ to attempt this just yet but that's my preferred way as of know. You know, something like this:// Pseudo-code interface IInputObserver { void InputChanged(InputChangedEventArgs e); } class InputObserverSceneHook : SceneHookData { List<IInputObserver> Observers; override Bool KeyboardInput () { foreach (IInputObserver observer in this.Observers) { observer.InputChanged(...); } } } class MyObjectDataPlugin : ObjectData, IInputObserver { void Init() { InputObserverSceneHook sceneHook = getInputObserverSceneHook(); if (sceneHook != null) { sceneHook.Observers.Add(this); } } }
I will update this thread once I get around to do it. Unless you're stopping me in my tracks.
PS
Just a little personal rant so feel free to skip.
The fact that there's not a clean way to do this makes me think that nobody asked for it yet which makes me think that I'm looking for something that noone else is looking for which makes me think that I'm doing something wrong. Which is rather frustrating. Even more so because I wish this wasn't such an issue. My main job (which is not related to C4D) includes designing business processes which make the lifes of users easier and to achieve that I have to think outside the box and come up with new ways to save inputs and "mouse mileage". So I'm really stubborn when it comes to realizing my ideas because once I got a clear view ahead of me I hate making compromises. I know that having this option to use Ctrl will make the user super happy so I'm having trouble accepting that programming this is not intended and that I should look for another approach. But if that's how it's gotta be that's how it's gotta be.
Best regards,
CJ
-
Hey @CJtheTiger,
just as an FYI, I have not overlooked your answer here, I just had a crazy week. This is the next thing on my desk
Cheers and happy coding,
Ferdinand -
Hey @CJtheTiger ,
Please excuse the long waiting time.
- As indicated earlier by me, doing what you want to do, is somewhat possible when overwriting the custom handle handling methods of
ObjectData
instead of using the automated ones. - There are, however, still cases where this does not work 100%.
- For example, when a user clicks a handle, and then presses CTRL to break it, and then releases the mouse button while moving away from the handle also keeping CTRL pressed, the CTRL state will linger even the user now releases the CTRL button. The reason is that in that case there is no event where we can rest our state the handle handling is nor running anymore.
- For most cases this will work well enough, but that last inch is probably hard to conquer. But it might be possible when one is stubborn enough. Tying the color of scene elements to a keyboard state is still not really intended in an
ObjectData
plugin.
- Regarding
SceneHookData
. What I was suggesting works the other way around, instead of objects registering as observers to an observable SceneHook to get informed about key press events, I was proposing that aSceneHookData
collects objects of typeT
in a scene to conduct all drawing operations for them. The objects are not drawing themselves, but the scene hook is. But since you have tangible events since you want to move handles, you do not have to go that crazy route. The whole idea was here that when you are "draw-event-starved" in yourObjectData
hook, you simply set up shop in another hook where you are not (SceneHookData
) and do your drawing from there. - I get what you mean, when I look at your pseudo-code I can hear .NET shouting that it wants its paradigms and code styling back, so I can imagine the kind of code you are used to. This is of course another world and classic API Cinema 4D code is a different beast and Maxon's approach to C++ in general is special. I personally like simple old-school code where you do not have three million observables, delegates, MVP/MVC layers, and ofc, machine learning, to toggle a button from on to off. Our maxon API is such more modern API and uses meta-programming and other fancy things, there we have for example concepts like observables, managed memory, and graceful error handling. But that API currently focuses on backend logic and not stuff like the GUI core or the plugin hooks.
Find my take on the problem below.
Cheers,
FerdinandFile: example.ohandlenull.zip - This is the full project, you just have to link it in a solution and you can compile it.
Result:
When the tangents turn orange, I am pressing CTRL to break them.
- As indicated earlier by me, doing what you want to do, is somewhat possible when overwriting the custom handle handling methods of
-
Hi @ferdinand,
you're simply a wizard. I'll try this code out when I get home. From looking at your code
DetectHandle
is going to be the key to success for me. It already gets the qualifier which I think is what gets me there. Why didn't I find this function earlier?Regarding
SceneHookData
The thing I don't like about this approach is that this would only work for my specific plugin. For the SceneHook to draw the handles of my
ObjectData
it would need to know how they should be drawn. Other plugins might also want to know about Ctrl, but we can't expect the SceneHook to know how to draw them. Except (and this is my .NET brain speaking) the ObjectData injects its drawing routine into the SceneHook so we can break this dependency. Unless of course your suggested approach already covers that in some way I'm not aware of.Offtopic: .NET
Yeah .NET is a completely different approach. That's why working with C4D is so much fun to me, it's a whole new world to stumble around in. What even is this
for (auto i: {ID_OHANDLENULL_TANGENT_LEFT, ID_OHANDLENULL_TANGENT_RIGHT})
loop? I mean I can figure out what it does but I've never seen syntax like this. Finding all of this out is just a jolly good time. In my main job I develop scalable enterprise application interfaces based on .NET, Azure and REST but at some point it's getting stale. So doing this here in my own time is just awesome.Thank you so much and have a nice day!
Best regards,
Daniel -
this might help
def IsDownCTRL(self): # the user hold down CTRL key BC = c4d.BaseContainer() if c4d.gui.GetInputState(c4d.BFM_INPUT_KEYBOARD, c4d.BFM_INPUT_CHANNEL, BC): return BC[c4d.BFM_INPUT_QUALIFIER] == c4d.QUALIFIER_CTRL return False def IsDownSHIFT(self): # the user hold down SHIFT key BC = c4d.BaseContainer() if c4d.gui.GetInputState(c4d.BFM_INPUT_KEYBOARD, c4d.BFM_INPUT_CHANNEL, BC): return BC[c4d.BFM_INPUT_QUALIFIER] == c4d.QUALIFIER_SHIFT return False def IsDownALT(self): # the user hold down ALT key BC = c4d.BaseContainer() if c4d.gui.GetInputState(c4d.BFM_INPUT_KEYBOARD, c4d.BFM_INPUT_CHANNEL, BC): return BC[c4d.BFM_INPUT] == c4d.QUALIFIER_ALT return False
-
def IsDownALT(self):
# the user hold down ALT key BC = c4d.BaseContainer() if c4d.gui.GetInputState(c4d.BFM_INPUT_KEYBOARD, c4d.BFM_INPUT_CHANNEL, BC): return BC[c4d.BFM_INPUT_QUALIFIER] == c4d.QUALIFIER_ALT return False
-
This post is deleted! -
I'd like to follow this post up with a closing response.
@ferdinand once again delivered the key to success: The
DetectHandle
function.virtual Int32 DetectHandle(BaseObject* op, BaseDraw* bd, Int32 x, Int32 y, QUALIFIER qualifier);
Since it gets passed the
QUALIFIER
I simply store it in a private class variable and access it inDraw
andMoveHandle
. As @ferdinand said this comes with the drawback that releasing a key while moving the handle will not update this variable in time forDraw
andMoveHandle
. But this is fine for me. Just choose which key to use before you click you silly user. >:) -
Hey @CJtheTiger ,
it is great to hear that you found your solution and always appreciated when users share their outcome as it will make threads more "complete" and helpful for future readers.
As a minor detail: As I see that you were struggling with the video player, the trick is to put down
local-player
as the display label of a URL. So, when you have uploaded an mp4, avi, webm or mov file and you have then the following text in the editor:[1688150981413-e5f8e80f-803a-4eb0-8226-37791f9f1d45-cinema_4d_7muveow2my.mp4](https://developers.maxon.net/forumhttps://developers.maxon.net/forumhttps://developers.maxon.net/forum/assets/uploads/files/1688150981413-e5f8e80f-803a-4eb0-8226-37791f9f1d45-cinema_4d_7muveow2my.mp4)
You must then edit it so that the displayed text of that link becomes
local-player
:[local-player](https://developers.maxon.net/forumhttps://developers.maxon.net/forumhttps://developers.maxon.net/forum/assets/uploads/files/1688150981413-e5f8e80f-803a-4eb0-8226-37791f9f1d45-cinema_4d_7muveow2my.mp4)
It will then render like this:
local-playerI will hopefully have time in the coming months to have a bit more user friendly solution and also lift the file size limit over eight megabytes. But that will require me touching the server. This is just a quick and dirty solution for everyone who is tired of creating animated GIFs in the meantime
Cheers,
Ferdinand -
-
Hello @CJtheTiger ,
without further questions or postings, we will consider this topic as solved by Friday, the 11th of august 2023 and flag it accordingly.
Thank you for your understanding,
Maxon SDK Group