Retrieve the current Unit and listen to changes
-
Hello, I need a way to retrieve not only the current unit of the editor and the world matrices, but also to get notified in my plugin, when the user changes the unit in the settings window, because then I will also need to re-calculate the data generated by my plugin to fit the new unit. Is there a way to subscribe to those type of changes, or would I have to write a more complex system to always stay up-to-date with the latest unit settings?
The default unit of the editor should be centimeters I believe, but most use cases of my plugin would probably be millimeters, or even nanometers sometimes.Thanks for any help!
-
Hi @Cankar001 you do not have to do anything as long as you define your parameter as a unit then it will respect the one in Cinema 4D and automatically adapt.
If your plugin is a GeDialog, then you need to call SetMeter and if you are working with description you need to set DESC_UNIT to DESC_UNIT_METER. While the symbol indicate meter, they will respect the settings defined in the document and adapt accordingly.
If you do not want to use the default system, then you will have to do everything yourself, and track yourself the change. But here it depend too much on the type of your plugin to guide you, so please tell us a bit more about it, and most important what is your UI.
Cheers,
Maxime. -
Hi Maxime, I don't have any UI in my plugin, as the control flow is controlled pureley over a websocket. The plugin calculates some data, when the command is received over the websocket and displays some debug ui in the rendering viewport (basic line rendering), but that's about it so I don't have any plugin setting or UI panel. Is it possible to get notified by a core message about the unit change?
-
Hi @Cankar001 there is no such message, so you have to track it yourself.
The best way would be to use a MessageData MessageData to either
create a timer that will periodically (maybe each second) check the value stored in the document or react to EVMSG_CHANGE and also check the value stored in the document.Cheers,
Maxime. -
@m_adam And how exactly would I retrieve and change the unit itself? I tried it this way:
Bool UnitChangeDetector::CoreMessage(Int32 id, const BaseContainer& bc) { if (id == EVMSG_CHANGE) { BaseDocument* doc = GetActiveDocument(); if (!doc) { ApplicationOutput("Failed to get active document!"); return true; } BaseContainer data = doc->GetData(DOCUMENTSETTINGS::GENERAL); data.SetInt32(DOCUMENT_DOCUNIT, (Int32)DOCUMENT_UNIT::KM); doc->SetData(DOCUMENTSETTINGS::GENERAL, data); } return true; }
(Only for testing of course, the final version would not look like this)
But when I check the settings in Edit > Program Settings > Units > Display it still says Centimeters instead of the expected kilometers.
-
Hi what you describe (Edit > Program Settings > Units > Display) is only the display of the unit in the whole Cinema 4D interface. Not the actual unit of the document which can be found by pressing CTRL+D -> Project -> Scale and which is actually changed by the code you provided.
So do you really want to change/listen to the Display unit and not the actual unit?
Cheers,
Maxime. -
@m_adam Thanks for the clarification! What I am trying to achieve is that the whole units for the user get changed. When I change the display setting manually, all the units in the UI also change and the values get re-calculated for the selected unit. My high level goal is, that my plugin also should do these re-calculations, once the user changes the unit in the settings, because right now my plugin would show the wrong values, when the unit gets changed from the default unit. So yes I would need to listen to the display unit I think, because the calculations should be done when the user wanted the units to change
-
Hey @Cankar001 I frankly do not understand what you are doing and why you would need that. Since you told me previously that you do not have any UI in your plugin but you told us "Would show wrong values", so which values are you talking about?
Matrices, Vectors, float in Cinema 4D are unit less and will adapt properly according to the document unit and not the displayed unit. Display unit are more or less just noise and does not indicate anything regarding the actual size of an object. For more information about Display unit vs document unit please read How Do I Change the Units Used By Cinema 4D To Something Other Than Centimeters? .So if you want more help then you will need to share with us some code to demonstrate the issue since we do not really understand the reasoning of why you will want to do that (not saying you do not have a valid reason for doing it, but we do not understand it). If it's private code you can send it to us at [email protected].
With that's said here is how you retrieve and set the preference:BasePlugin* bp = FindPlugin(PREFS_UNITS, cinema::PLUGINTYPE::PREFS); if (bp == nullptr) return; /* Possible Values PREF_UNITS_BASIC_KM = 1, PREF_UNITS_BASIC_M = 2, PREF_UNITS_BASIC_CM = 3, PREF_UNITS_BASIC_MM = 4, PREF_UNITS_BASIC_MICRO = 5, PREF_UNITS_BASIC_NM = 6, PREF_UNITS_BASIC_MILE = 7, PREF_UNITS_BASIC_YARD = 8, PREF_UNITS_BASIC_FOOT = 9, PREF_UNITS_BASIC_INCH = 10, */ GeData unitValue; bp->GetParameter(CreateDescID(PREF_UNITS_BASIC), unitValue, cinema::DESCFLAGS_GET::NONE); Int32 uValue = unitValue.GetInt32(); bp->SetParameter(CreateDescID(PREF_UNITS_BASIC), PREF_UNITS_BASIC_KM, cinema::DESCFLAGS_SET::NONE);
Again there is no such event so it will be on you to have a timer that check if the value changed or not.
Cheers,
Maxime. -
@m_adam Thanks for your quick reply! I wrote a more detailed mail to the mail address you provided
-
Hello @Cankar001,
I am going to answer here in the forum, as there are no confidential aspects here. As Maxime pointed out in his last posting, Cinema 4D is unitless. Find below a video drives home that point.
- When you want to draw unit values into the viewport, you will have to do some manual conversion. You would have to get the values from both the preferences and document settings and write a custom function which accordingly scales the raw API value and applies a unit abbreviation.
- When you want to get informed about either the system unit (the value in the prefs) or the document scale having changed, you would have to listen for
EVMSG_CHANGE
as Maxime explained and check the values against a value you have cached. I would not recommend using a timer (instead ofEVMSG_CHANGE
), unless you set it to a high value like 1000 ms.
Cheers,
Ferdinand -
Hi @ferdinand,
thank you very much for your detailed answer! It helped me a lot understanding the unit system better, I have now written 2 functions, that retrieve the current set unit of the document, which are then used to A) display the current unit in the viewport and B) to scale the distances based on the unit scale:static maxon::String GetCurrentSelectedUnit(const BaseDocument* document) { if (!document) return ""_s; const UnitScaleData* unitScale = document->GetDataInstanceRef().GetCustomDataType<UnitScaleData>(DOCUMENT_DOCUNIT); return unitScale->ToUnitString(); } static Float GetCurrentUnitScale(const BaseDocument* document) { if (!document) { ApplicationOutput("Failed to retrieve the current document."); return 1.0f; } const UnitScaleData* unitScale = document->GetDataInstanceRef().GetCustomDataType<UnitScaleData>(DOCUMENT_DOCUNIT); if (unitScale) { Float currentScale = 1.0f; DOCUMENT_UNIT currentUnit = DOCUMENT_UNIT::UNDEFINED; if (!unitScale->GetUnitScale(currentScale, currentUnit)) { ApplicationOutput("Failed to retrieve the current unit multiplier."); return 1.0f; } if (currentUnit == DOCUMENT_UNIT::UNDEFINED) { ApplicationOutput("Failed to extract the current set unit."); return 1.0f; } return currentScale; } ApplicationOutput("Failed to retrieve the current unit scale."); return 1.0f; }
Those two functions seem to behave as expected now and the text changes, when the project scale is changed and also when changing the scalar value, the distance gets scaled properly. Thank you a lot for the video as well, it helped a lot to see the actual values change live
-
Hey @Cankar001,
Good to hear that you found your solution! One minor thing - you should avoid
ApplicationOutput
in production code, as it leads to console spam which we want to avoid in Cinema 4D. Using it in test code is fine. See Debug and Output Functions for alternatives.An even better way to do what you did in your code would be to use error handling. E.g., your code could look like this:
// Your original function, I turned this into a function using our error system, indicated by the // Result<T> return type. static maxon::Result<maxon::Float> GetCurrentUnitScale(const BaseDocument* const document) { // The error scope handler for this function, i.e., all error returns exit through this handler. iferr_scope; // When there is no document, we return an error. When printed, this will then print the error // message and the source code location, e.g., myfile.cpp:123. if (!document) return maxon::NullptrError(MAXON_SOURCE_LOCATION, "Invalid document pointer."_s); // Your code goes here. // ... return 1.0f; } // A function calling this function which does use error handling itself. static maxon::Result<void> Foo(const BaseDocument* const document) { iferr_scope; // Call the function and return, i.e., propagate the error upwards when there is one. const maxon::Float unitScale = GetCurrentUnitScale(document) iferr_return; // voids in the Result<T> return type are returned with maxon::OK. return maxon::OK; } // A function calling this function which does not use error handling itself, i.e., the error // must terminate here. static bool Bar(const BaseDocument* const document) { // Here we use a manual scope handler to terminate the error chain. You have often to do this in // Cinema API (e.g., ObjectData::GetVirtualObjects), as methods there are not error handled // opposed to the Maxon API. iferr_scope_handler { // Print a debug message with the passed in error #err and the name of this function. And // force a debugger to halt when some condition is met. WarningOutput("@ failed with error: @"_s, MAXON_FUNCTIONNAME, err); if (someErrorCondition) DebugStop(); return false; }; // Call the function (we still have to handle the error with an iferr_return), and then let it // terminate in our manual scope handler. const maxon::Float unitScale = GetCurrentUnitScale(document) iferr_return; return true; }
Cheers,
Ferdinand