Teamviewer Compatibility Issue

Update: this has been fixed in VFB+ v2.7.

Based on reports from several users, it seems there is a compatibility issue between new versions of Teamviewer and VFB+ on at least some releases of 3dsmax/Windows.

The symptom of this issue is that the VFB+ overlay will suddenly disappear after VFB window being minimized, resized or closed until 3dsmax is restarted. This is caused by a collision with Teamviewer’s ‘QuickConnect’ button. This is a button with a double-edged arrow that is automatically installed on the title bar of every window in the OS.

Workaround: Either disable the QuickConnect button, or exlude 3dsmax.exe from the list of applications for which it is installed. The following image shows exactly how to do this from the Teamviewer options dialog:
How to disable the QuickConnect button for 3dsmax

I will work towards fixing the issue permanently in the next release of VFB+.

Update: this has been fixed in VFB+ v2.7.

VFB+ 2.6 Preview: Depth of Field

A small teaser video for the upcoming version of VFB+. The biggest new feature in this release is interactive 2D depth of field, right in the frame buffer!

If you don’t mind me saying, this is a rather high quality implementation of DOF, which greatly minimizes the intensity leakage or depth discontinuity artifacts typical to more naive algorithms.

I don’t yet have a date for availability, but I estimate it will be a few weeks.

VFB+ v2.51

VFB+ v2.51 is now available for download.
This latest update fixes a few bugs reported by our users.

Specifically:

  • Deleting all images and then rendering would cause an exception.
  • Fixed batch CC UI quirks.
  • VFB+ could fail to initialize when trying to load a corrupted .VFB cache file.
  • [3dsmax 2013 & above] 3dsmax would crash when using render element names with more than 32 characters.
  • [3dsmax 2013 & above] Images loaded from a previous session of 3dsmax had garbled render element names.
  • [3dsmax 2013 & above] The batch CC feature was broken.
  • [3dsmax 2013 & above] Loading an image into VFB+ using MAXScript was broken.

Thanks again to all those who take the time to send me bug reports and feature requests!

Download VFB+ v2.51

VFB+ v2.5 Now Available

A new version of VFB+ is now available for download!

In this version, the main new feature is a noise reduction filter suited for high frequency noise often found in CG renderings. This filter aims to reduce fine grain, while keeping textural details as sharp as possible. For previews (and often for final renders), this lets you get away with higher levels of noise in your original rendering, while still saving a smooth image out of the frame buffer.

In addition, many bugs have been fixed. See the change log for complete details.

As always, this upgrade is free for all existing users.
Thanks to everyone for sending me continuous feedback, bug reports and feature requests, and for making VFB+ a part of your workflow!

VFB+ v2.45 Available for Download

An updated version of VFB+ has been released!
Version 2.45 supports 3dsmax 2015, as well as support for layered PSD export; VFB+ now allows you to export your image as a PSD, with each element of the image in a separate layer, and Stamper data in a separate layer as well.

Download here or see the change log for a complete rundown of new features and bug fixes.

VFB+ v2.1 Released

Based extensively on user feedback from v2.0, I am proud to release the first update to VFB+2!

A few nifty features have been added, such as being able to capture images from Render To Texture, or automatically saving the image from VFB+, allowing you for example, to render sequences locally and have VFB+ color correction or stamping already applied.

The core image processing algorithms have also been greatly optimized, resolving a problem in which a very large render could cause the UI to stutter.

A lot of options have been added to the preferences and more hotkeys have been added, giving each user the fine-grained control needed to suit VFB+ to the intricate inner-workings of their specific pipeline.

Check out the Change Log for a complete list.

A big thanks to all the users who have already made VFB+ a part of their workflow!

Dotnet Tips for the Lazy Coder

Let’s face it, Dotnetting in Maxscript is one of the less inviting things to get into. It seems half your code is just namespace strings, you’re never sure if you should be using a DotNetClass, DotNetObject or DotNetControl, and the garbage collector is suddenly your worst enemy.

Below are a few small tips that should make your initial venture into Dotnet a bit less taxing on your keyboard. Some might seem obvious and trivial, but I think they are worth mentioning.

Note: This is in no way an introduction to DotNet, I highly recommend the articles by Paul Neale (PEN Productions) and Pete Addington (LoneRobot) for that purpose.

Omit the “System.Windows.Forms.” Prefix


If you’re starting out with Dotnet interfaces in Maxscript, you’ve probably typed these three words out more times than you’d care to acknowledge.
Well, good news then. You do not need to type them at all. The gracious developers at Autodesk have hardcoded this namespace (and sadly, only this namespace) to be presearched for a match when trying to resolve the type string.

What this means is that you can just omit the prefix for all types in the System.Windows.Forms namespace. Thats right:

DotNetObject "System.Windows.Forms.Button"

is exactly the same as

DotNetObject "Button"

Cache types inside loops


Even better than omitting part of the type string, is omitting all of it.
Whenever you are instantiating many DotNetObjects it is prudent to cache the type altogether.

Let’s look at a contrived example:

for i = 1 to 10000 collect dotNetObject "System.Drawing.Point" i i

--Execution time: 1015 ms
--Memory usage: 1280 KB

Now, how could we optimize this?
Well, there’s certainly no reason to allocate 10000 type strings. Let’s put the string into a variable outside of the loop:

ptStr = "System.Drawing.Point"
for i = 1 to 10000 collect dotNetObject ptStr i i

--Execution time: 990 ms
--Memory usage: 720 KB

This helped our memory usage a bit, but execution time is left almost unphased. This is because most of the time is spent looking up the correct object type to instantiate based on the type string.

The solution to this is to look up the type only once. This is done by creating a DotNetClass variable which hold the type information and inputting this variable in place of a type string into the DotNetObject constructor.
Now let’s see what happens when we precache the type:

ptCls = dotNetClass "System.Drawing.Point"
for i = 1 to 10000 collect dotNetObject ptCls i i

--Execution time: 93 ms
--Memory usage: 720 KB

A tenfold improvement! Not only are we saving MAXScript from allocating 10000 strings, we’re also saving it 10000 type checks.

Object, Class or Control?


This is an often confusing topic when starting out with Dotnet in MAXScript, though it’s really more simple than it sounds. I will not get into the differences between classes and objects here, you should really read the articles mentioned at the top of this post if you are not sure what the difference is. In a sentence, a DotNetClass represents a class, or a type, while a DotNetObject represents an instance of that type.

Use a DotNetClass whenever you want to access a static method or property of an object. e.g:

(dotNetClass "System.Environment").newLine --Access a static property
(dotNetClass "System.GC").Collect() --Execute a static method

Use a DotNetObject when you need to instantiate (construct) an object. If you need to add a control to a Dotnet form, you must create it using a DotNetObject. e.g.:

myButton = dotNetObject "Button"
myForm.Controls.Add myButton

The DotNetControl is a MAXScript wrapper around a Dotnet object which inherits from type System.Windows.Forms.Control. Use a DotNetControl only when you want to embed a Dotnet control inside a MAXScript rollout.
This wrapper enables you to use MAXScript style event handler syntax on the control. e.g.:

rollout myRollout "My Rollout"
(
    dotNetControl myBtn "Button"

    on myBtn click sender args do print sender
)

Enumerations


Enumeration values, for all syntax purposes, can be treated as static fields of a class. They can be accessed in one of two ways. Either using a type string:

myGfxObject.SmoothingMode = (dotNetClass "System.Drawing.Drawing2D.SmoothingMode").AntiAlias

Or simply using the property which we want to assign:

myGfxObject.SmoothingMode = myGfxObject.SmoothingMode.AntiAlias

This works because the property SmoothingMode is already of type System.Drawing.Drawing2D.SmoothingMode, and thus has a member AntiAlias.
Note that the fact that both the property we are assigning and the enumeration type have the same name is a coincidence and is not always the case.

Using the former syntax might be more readable in cases where the type of the enumeration is not immediately evident from the name of the property. Using the latter syntax is faster, more performant (only within the context of a large loop, it would never be noticable as a single call), and usually involves less typing.

Another way is to precache the enumeration type in your script:

smoothingMode = dotNetClass "System.Drawing.Drawing2D.SmoothingMode"
myGfxObject.SmoothingMode = smoothingMode.AntiAlias

This had the benefits of both methods.

Enumeration values can be combined using the dotNet.CombineEnums methods:

myButton.Anchor = dotNet.CombineEnums myButton.Anchor.Left myButton.Anchor.Right

This is equivalent to the c# bitwise or operator (|), and essentially means to use both values – in this case, anchor the control to both left side and right side of the parent control.
Note that not all enumerations can be combined in this way.

Use Factory Functions for Repetitive Controls


If you are initializing many controls of the same type, your code can get unnecessarily long. Consider the following:

btn1 = dotNetObject "Button"
btn1.Anchor = dotNet.combineEnums btn1.Anchor.Bottom btn1.Anchor.Right
btn1.Width = 50
btn1.Height = 25
btn1.Text = "Button 1"

btn2 = dotNetObject "Button"
btn2.Anchor = dotNet.combineEnums btn2.Anchor.Bottom btn2.Anchor.Right
btn2.Width = 80
btn2.Height = 25
btn2.Text = "Button 2"

...

btn10 = ...

This results in a massive ugly code block that is uncomfortable to revise. In place of this code we could create a function which returns a button, as such:

fn buttonFactory text width height =
(
    local btn = dotNetObject "Button"
    btn.Anchor = dotNet.combineEnums btn.Anchor.Left btn.Anchor.Right
    btn.Text = text
    btn.Width = width
    btn.Height = height
    btn
)

btn1 = buttonFactory "Button 1" 75 25
btn2 = buttonFactory "Button 2" 50 25

Of course, this function could be made as specific or as general as needed; Only add the properties which are not common to all controls as parameters. Similarly, it could even be turned into a general purpose control factory, accepting a type string, and as many parameters as needed.

fn controlFactory typeString params =
(
    --construct the control according to type string:
    local ctrl = dotNetObject typeString

    --initialize any common properties here:
    ctrl.Anchor = dotNet.combineEnums ctrl.Anchor.Bottom ctrl.Anchor.Right

    --initialize specified parameters:
    for p in params do setProperty ctrl p[1] p[2]

    --return the new control:
    ctrl
)

btn1 = controlFactory "Button" #(#(#width, 50), #(#height, 25), #(#text, "Button1"))
chk1 = controlFactory "CheckBox" #(#(#width, 80), #(#height, 20), #(#text, "CheckBox1"), #(#checked, true))