RenderOptions created from a dictionary don't set correct Render Mode

Started by Catalin Moldovan, November 26, 2019, 01:45:25 AM

Previous topic - Next topic

0 Members and 1 Guest are viewing this topic.

Catalin Moldovan

Hi, while trying to set some render options that I read from configuration files I've noticed some strange behavior.
Even if I set the render_mode key with proper values (0 - for advanced rendering, 1 - for max time rendering and 2 - for max samples rendering) the rendering mode is not set to what I explicitly ask all the time.
For example:
- if I set progressive_max_samples to a non zero value and render_mode to 2 it properly sets the rendering mode
- if I set progressive_max_samples to a non zero value and render_mode to 2 but I also have set progressive_max_time to a non zero value, it will set the rendering mode to 1 even though I explicitly set the rendering mode to 2
- if I set progressive_max_samples to a non zero value and render_mode to 2 but I also have set progressive_max_time to a non zero value and advanced_samples to a non zero value, it will set the rendering mode to 0 even though I explicitly set the rendering mode to 2

I think this is a bug because if I explicitly ask for a rendering mode, even though I pass more settings than required for that particular render mode, that render mode should be set.
I've noticed that if I use  the RenderOptions methods for setting the render mode (setAdvancedRendering, setMaxSamplesRendering, setMaxTimeRendering) the render mode is properly set all the time because these methods zero out unneeded parameters. Unfortunately for my use case I need this to work from a dictionary when I create the RenderOptions object.
I kindly ask you to investigate this issue.
Thank you!

Niko Planke

Hi Catalin Moldovan,

Using the dictionary directly requires  significantly more care.
I would always recommend to use the RenderOptions class.

Since KeyShot 9.0 you have the option to  change the render-mode using, setRenderEngine() to change the render mode, which should make that workflow more valid for you.

If you start out with a dict,you can always convert a dict to a Renderoption class using lux.RenderOptions(dict=<your dict>)

If the above does not work as a solution i would be very interested in knowing your exact use case.

Also feel free to provide the Dictionary you are using for us to check.
It is here important that the "__VERSION" value is set correctly, Since that may impact how the render options are read.

I hope this helps.



Catalin Moldovan

Hi Niko,

I may have not been clear enough. I do create the RenderOptions object from a dictionary renderOptions = lux.RenderOptions(renderOptionsDict)
and then I'm calling lux.renderImage(str(renderPath), output.Width, output.Height, renderOptions)
This is all properly done as far as the API is concerned. I also set the version value correctly because I get that value from the default render options by calling lux.getRenderOptions(True) and provide that version back to my dictionary I use to create the RenderOptions object.
Here's how the dictionary looks like before creating the RenderOptions object [<string>:359 - debugLog()] Preset render options: {'add_to_queue': True, 'advanced_samples': 250, 'engine_anti_aliasing': 4, 'engine_caustics_quality': 2, 'engine_dof_quality': 3, 'engine_global_illumination': 5, 'engine_global_illumination_cache': False, 'engine_indirect_bounces': 4, 'engine_pixel_blur': 1, 'engine_ray_bounces': 20, 'engine_shadow_quality': 5, 'engine_sharp_shadows': False, 'engine_sharper_texture_filtering': True, 'engine_threads': 8, 'output_alpha_channel': True, 'progressive_max_samples': 25, 'progressive_max_time': 10, 'render_mode': 2, '__VERSION': 4} and here is the dictionary obtained from the created RenderObject retrieved using RenderOptions.getDict [<string>:359 - debugLog()] Render options dict: {'__VERSION': 4, 'add_to_queue': True, 'advanced_samples': 250, 'engine_anti_aliasing': 4, 'engine_caustics_quality': 2, 'engine_dof_quality': 3, 'engine_global_illumination': 5, 'engine_global_illumination_cache': False, 'engine_indirect_bounces': 4, 'engine_pixel_blur': 1, 'engine_ray_bounces': 20, 'engine_shadow_quality': 5, 'engine_sharp_shadows': False, 'engine_sharper_texture_filtering': True, 'engine_threads': 8, 'output_alpha_channel': True, 'progressive_max_samples': 25, 'progressive_max_time': 10, 'render_mode': 2} it's exactly the same, meaning RenderOptions object is properly created.
Notice how the render_mode is set to 2 but look at the attached picture and you'll see that in the render queue the render mode is set to "Custom Control" which from my investigation is render_mode 0.

Morten Kristensen

Hi Catalin,

First of all, which version of KeyShot are you using?
Second, why do you need to work with the dictionary? The dictionary should only be used to save/load render options but must always be manipulated via the lux.RenderOptions class. Thus you don't have to manipulate it outside of the class.

It's okay to not have all possible render options in the dictionary because when KeyShot applies them, it will only apply those it finds, i.e. it will only overwrite render options of the current scene's context that are specified in the dictionary. That's why it's safest to have them all present.

Note that only the version is checked and certain outdated keys converted when you pass the dictionary to lux.RenderOptions. Nothing else. That's why it's important that the values are correctly set in the first place.

Render mode 0 means "advanced control", 1 means "maximum time", and 2 means "maximum samples".

If you call lux.RenderOptions.setAdvancedRendering(samples), it will set/change the following values in the internal dictionary:

{"advanced_samples": samples,
"progressive_max_samples": 0,
"progressive_max_time": 0.0,
"render_mode": 0}


lux.RenderOptions.setMaxSamplesRendering(samples):

{"advanced_samples": 0,
"progressive_max_samples": samples,
"progressive_max_time": 0.0,
"render_mode": 2}


lux.RenderOptions.setMaxTimeRendering(time):

{"advanced_samples": 0,
"progressive_max_samples": 0,
"progressive_max_time": time,
"render_mode": 1}


The values yo uspecified in your dictionary are the following wrt. rendering mode:

{"advanced_samples": 250,
"progressive_max_samples": 25,
"progressive_max_time": 10,
"render_mode": 2}

And that won't be recognized correctly when applied inside KeyShot, i.e. it is valid but it doesn't do what you intend.
What you end up with inside KeyShot is instead:

{"advanced_samples": 250,
"progressive_max_samples": 0,
"progressive_max_time": 0.0,
"render_mode": 2}

Which means maximum samples rendering with zero samples since the "render_mode" is first interpreted and the order of evaluation means that "advanced-samples" is evaluated after "progressive_max_samples" and "progressive_max_time".

Ultimately, I recommend only using the lux.RenderOptions to manipulate the render options. If you do it manually, you have to set the values like I explained above and not set all of them like was done in your supplied dictionary.

I hope that helps.
Thanks!

Catalin Moldovan

Hi Morten,
thanks for your reply, I'm using Keyshot 9.0
To explain the context, I'm using various yaml files (or for the sake of argument I could use json files or even directly python dictionaries) to set variants of RenderOptions with different levels of details and rendering modes. The parameters from those files map 1:1 with the keys that the RenderOptions expects in the dictionary. Even though I could interpret each parameter from the file and then call corresponding RenderOptions methods (e.g RenderOptions.setCausticsQuality or RenderOptions.setIndirectBounces, etc.), I will end with a lot of code (a lot of if statements) to do just that. Instead if I create a dictionary with all the parameters and create the render options from that dictionary, that eliminates a lot of unnecessary code. I would expect from the API, if it already allows construction from a dictionary, to validate its input and construct a proper object without me needing to call all the methods from the class to properly set the parameters which are already present in the dictionary. If that's not the case I would expect the API to specify that one should not manually modify the dictionary.

Getting back to your response, I'm seeing a different behavior than the one you're describing
QuoteWhat you end up with inside KeyShot is instead: {"advanced_samples": 250,
"progressive_max_samples": 0,
"progressive_max_time": 0.0,
"render_mode": 2}
The values that I get back from the RenderOptions object after constructing it are exactly the same as in the dictionary I used to construct it. But even though the RenderOptions have "render_mode" set to 2, Keyshot renders with "render_mode" 0 ("advanced control"). Please check the picture again. I still cannot understand how I could end up with "progressive_max_samples: 0" from "progressive_max_samples: 25" when "rendering mode: 2" ("maximum samples") needs exactly that parameter. I still believe this might be a bug and should be investigated, render mode should not change depending on other keys in dictionary if it is explicitly set and if, as you say, takes precedence.
Thanks for you help!

Morten Kristensen

Hi Catalin,

I understand what you mean. If you don't use the interface properly then you have to know how the values and their relationships work, which can easily change in future releases. That's why it's not a good idea, and the doc for getDict() says:
Quote
Get specified options as a dictionary. This can be useful to persist options and then supply to a lux.RenderOptions() constructor when needed to render something. Be wary of the version specified in the dictionary, key values will be converted to the latest version automatically.

Maybe it's not clear enough but the idea is that it's not recommended to use for anything else than persisting and putting back into lux.RenderOptions(dict=..).

Yes, it's the same dictionary. I said that it doesn't change it, only if you pass an outdated dictionary with an outdated __VERSION field.

The point is that you cannot set all values because it confuses KeyShot. That's what you have to do if you want to do it manually. If you want to do max samples rendering then only set "progressive_max_samples" and "render_mode"=2, "advanced_samples"=0, "progressive_max_time"=0.0.

For a future release, I will add an automatic conversion in lux.RenderOptions(dict=..) so that it puts zeros the right places depending on the value of "render_mode". And verifying the existence and value of the required companion field, like "advanced_samples".

Thanks.

Catalin Moldovan

Thanks Morten, I understand your point of view. The check for future release would be a welcome addition.
Best regards!

Morten Kristensen