Can I get keyboard input mode?
-
Hey community.
I wonder if we can get the
text input
event in Cinema like this. That is to say, when pressing the keyboard, it is the action of inputting text.
Another corresponding state is the shortcut key state, such as pressing C to collapse a certain object.
How can I get this state via Python or in C++?
Cheers~
DunHou -
Hey @Dunhou,
Thank you for reaching out to us. I think I do not have to tell you that there is
c4d.gui.GetInputEvent
and.GetInputState
. The event variant literally grabs the last item from the input event queue and the state variant just polls for the current state again. Often they are the same, but in some scenarios Cinema 4D injects events which are then not visible as a state.When you have for example a tool which the user (or your plugin) set to have the shortcut "CTRL+P". Then, when the tool is invoked, you can usually still poll for
CTRL
orC
being pressed withGetInputState
because the user is likely still pressing these keys when the tool is activated. ButGetInputEvent
will likely not returnC
with the modifierCTRL
being pressed, because that event likely has already been consumed.If an event has been consumed, is determined by returning either
False
(not consumed) orTrue
(consumed) in the implementing message method suchGeDialog.Message
. Usually, events are consumed once they are handled by their rightful owner (but developers can make mistakes or deliberately ignore this).Options
All GUI is sealed in the Cinema 4D API, i.e., we intentionally do not expose things like the object manager dialog. You therefore cannot send it messages or hook into its message stream. What you can technically do, is have a plugin or dialog which registers a timer and then regularly polls the keyboard. But that would neither tell you where one semantically connected input event starts and ends (e.g., the user is typing in a renaming field) nor who the owner is (e.g., the object manager). When you own a dialog, there is also
BFM_RENAMEWINDOW
but that won't help you much here.This is not any different in C++, as all this is an intentional design choice (to not expose GUI entities). What you would need is a core message which is emitted on renaming events but something like this does not exist at the moment.
What you can technically also do, at least for scene elements, is hook into their message stream via BaseList2D.AddEventNotification etc., so that you get called when they receive messages, including a
MSG_DESCRIPTION_POSTSETPARAMETER
message when their name has been written. The issue with this is that you will only receive the messages for the exact scene element your registered for. And these methods are also for a reason internal, as you can quite easily crash Cinema 4D with them. But you will find some examples on the forum. Maybee you could just hook into the stream of the currently selected object or something like that. But you should REALLY avoid registering callbacks for countless scene elements, as this will make Cinema 4D very slow.Bottomline
Cinema 4D's event system is VERY coarse, internally we are talking about this for a while and we might change this at some point. But right now you have to effectively track yourself what is happening in a scene (which is not very performant when every plugin does that and why we are inclined to change it at least to some degree).
The easiest solution at the moment to track name changes is unfortunately to hook into
EVMSG_CHANGE
and build the data yourself.Cheers,
FerdinandResult
Code
"""Demonstrates how to hash all objects in a scene to stores their names overt that hash, to then later detect name changes with that. Just create a scene, run this script, rename an object, and then run it again. This just scans for objects but it would be easy to extend to other scene elements. """ import c4d import mxutils doc: c4d.documents.BaseDocument # The currently active document. # An attribute to inject data under into the c4d module. I am just doing this here so that a script # can have a persistent state and remembers what it did on the last execution. PLEASE DO NOT DO THIS # IN PRODUCTION CODE! NAME_STORAGE: str = "my_name_storage" def main() -> None: """Called by Cinema 4D when the script is being executed. """ # You would either bind this code to EVMSG_CHANGE messages or better (because not as wasteful) # to a specific event that makes sense in the context of your plugin. E.g., the user clicking the # button X in your plugin. # Get the dictionary we monkey patch into the c4d module. Please REALLY do not do this in # production code, and instead use your own modules to store data! If you ignore this, it is # bound to lead to serious problems sooner or later! data: dict = getattr(c4d, NAME_STORAGE, {}) if not data: print("No previous data found. Initializing new storage.") else: print("Found previous data:") for key, value in data.items(): print(f" {key}: {value}") # Scan the scene for all objects. obj: c4d.BaseList2D noUpdates: bool = True for obj in mxutils.IterateTree(doc.GetFirstObject(), True, True, True): # Hash the object into an integer. This is reasonably collision resistant and possible since # Cinema 4D 2023.2 (before there was not C4DAtom.__hash__). hid: int = hash(obj) name: str = obj.GetName() # We found a new object. if hid not in data: print(f"Found new object: {name} (hash: {hid})") data[hid] = name noUpdates = False # We found an already previously seen object AND its name changed. elif hid in data and data[hid] != name: print(f"Object renamed: {data[hid]} -> {name} (hash: {hid})") data[hid] = name noUpdates = False if noUpdates: print("No changes detected.") # Write the data back into the c4d module. print("Storing data for next execution.") setattr(c4d, NAME_STORAGE, data) if __name__ == '__main__': main()
-
Hey @ferdinand , thanks for your information, Iterate graph is one of the solutions to listen to name change, but what I want is the "state" of any text input.
As I had posted on the beta forums (Typing software issue with Cinema 4D.), if someone used non-Latin languages, like Chinese for example, If you use a typing software like IME( Mircosoft Pinyin ), it should ship an English input. When we want to change something like an object name, it will set the typing to English mode. It is very annoying when you have tons of objects to rename one by one.
So I wonder if I can get the "typing mode" so I can set the IME always in Chinese mode when I input a text, and keep in English when not typing text to use shortcuts.
I am a very stupid beginner in C++, but you know what I mean. I try to use
GetGUIThreadInfo
andGUI_CARETBLINKING
to get the text cursor, but it seems not work.Hope it is clear.
Cheers~
DunHou -
Hey @Dunhou,
I have edited your posting. It is fine to mention the beta forum, but we would rather not see its name written here.
When you are talking about 'C++', I assume you are talking here about the Windows SDK/API, as for example
GetGUIThreadInfo
. I would doubt that you get very far with that here. Cinema 4D is very OS agnostic and we have our whole GUI decoupled from the OS. The little rename window in our tree view control is not an OS rename dialog. I am not even sure if this is something with anHWND
handle which you could address so that you could intercept keyboard events to it, or if this is just a virtual window within our API.For this IMM thing to work, we would have to use the IME API in Cinema 4D, so that our text edit gadgets support it. I also do not quite understand what you are trying to do. The missing piece is here probably that our text edit gadget does not send its current content to the IME API (at a glance, via
ImmSetCompositionStringW
as shown here). And as a third party, you cannot really fix that. Because you (a) do not own the implementation and you (b) are not informed about an edit event, so you cannot just write a plugin which sends the object name to the IME API.Cheers,
Ferdinand