Analyzing Viewport Lighting [3DS Max 2014]


#11

sorry for the delay.
i used the svg script from the reference as it is the same method to get the colors like in your first script and it is better to show in a rendering.
It does not have to be 100% precise and the middle of the face is perfect for my case. But as you can see the values differ very much in the script rendering.
Perhaps its just the factor of ambient lighting which needs to be added I look around how this might work.
Why does the lighting depend on the vewing angle? A very matt surface without reflections should not change depending on perspective if I imagine a real world scenario.


thank alot.


#12

The SVG renderer does not take into account the light multiplier, but I added it to the script. I suppose you are not using a multiplier of 1.0, and so the render comes out different. The first script should work though.

You can modify the SVG code by multiplying the resulting color by the light multiplier.

Note that the script does also not take into account the ambient light, but it wouldn’t be hard to add it.

Regarding the viewing and light angles, I meant if you are using specular and glossiness values other than 0.0. If you only use the diffuse value, the color shouldn’t change much, perhaps you would see a very soft gradient. You could also turn off affect specular in the light parameters just to be sure it is not affecting the material.

What values do you get with the first script for that same example? They should be lighter than the SVG image.

As I mentioned earlier, it would be useful a sample scene, so I can reproduce the results you get, because in my test, it seems to
work well.


#13

the multiplier is 1.0, the values I get from script one are wrong…
here is my testfile https://app.box.com/s/md4y9kzikt0p60ypc5ivrud5uhnmok0h


#14

Your scene is gamma corrected so you should correct the output colors as well.
The formula for gamma correction is:

color = 255 * (color/255.0)^(1/2.2)

#15


#16

Besides Gamma correction, your scene also has Exposure Control, so you must use the same exposure algorithm to correct the output colors.


#17

Considering the amount of variables you need to handle, based on your scene, I think it would be faster to just use the viewports images to get the colors instead of building a pseudo render engine. Not the best approach, but see if this code helps you.

You need to select 2 viewports (for front and back faces) as you described, and then enable it.

Keep in mind that any other visible stuff in the scene can alter the colors, so you better hide everything you can before getting the image. You could add some code to hide things like grid, lights, cameras, etc. to the code if you want.

Hope it helps.


(

    try destroydialog ::RO_DISPLAY_FACES_COLORS catch()

    rollout RO_DISPLAY_FACES_COLORS "Faces Colors" width:172 height:124
    (
        button       bt_vp1 "Set View 1" pos:[ 8,8] width:72 height:32
        button       bt_vp2 "Set View 2" pos:[88,8] width:72 height:32
        
        radioButtons rdo1 "Display:" pos:[8,48] labels:#("RGB","H","S","B") columns:4
        checkbutton  btn1 "Enable"   pos:[8,88] width:154 height:28
        timer        clock interval:20 active:false

        global GW_DisplayFacesColors

        local GetFaceVerts = polyop.getfaceverts
        local GetfaceNormal = polyop.getfaceNormal
        local GetFaceCenter = polyop.getFaceCenter
        local GetVert = polyop.getvert
        local node
        local vp_front = undefined
        local vp_back  = undefined
        local data_front = #()
        local data_back  = #()
        local ViewPanelHwnd = 0
        
        fn GetViewportsPanelHWND =
        (
            for j in (windows.getchildrenhwnd #max) where j[4] == "ViewPanel" do exit with j[1]
        )

        fn CalculateFacesColors =
        (
            dib = gw.getViewportDib()

            viewTM = viewport.getTM()
            viewTM.row4 = [0,0,0]

            gw.setTransform (matrix3 1)

            result = for f = 1 to node.numfaces collect
            (
                faceNormal = normalize (GetfaceNormal node f)

                if (faceNormal*viewTM).z > 0 then
                (
                    faceCenter = GetFaceCenter node f
                    fverts = GetFaceVerts node f

                    verts = for j in fverts collect (faceCenter + (GetVert node j)) / 2.0
                    append verts faceCenter
                    numverts = verts.count

                    faceColor = black

                    for j in verts do
                    (
                        px = gw.transPoint j
                        if px.x > 1 and px.y < dib.width do
                        (
                            pcolor = (getpixels dib [px[1], px[2]] 1)[1]
                            if pcolor != undefined then faceColor += pcolor else numverts -= 1
                        )
                    )
                    
                    faceColor.r = faceColor.r/numverts
                    faceColor.g = faceColor.g/numverts
                    faceColor.b = faceColor.b/numverts
                    
                    if iDisplayGamma.colorCorrectionMode == #gamma do
                    (
                        faceColor.r = 255 * (faceColor.r / 255.0)^(1/IDisplayGamma.gamma)
                        faceColor.g = 255 * (faceColor.g / 255.0)^(1/IDisplayGamma.gamma)
                        faceColor.b = 255 * (faceColor.b / 255.0)^(1/IDisplayGamma.gamma)
                    )
                    
                    faceColor.r = int faceColor.r
                    faceColor.g = int faceColor.g
                    faceColor.b = int faceColor.b
                    
                    #(faceCenter, faceColor)
                )else(
                    dontcollect
                )
            )

            free dib

            return result
        )

        on clock tick do
        (
            if viewport.numViews < vp_front or viewport.numViews < vp_back then
            (
                data_front = CalculateFacesColors()
                data_back = #()
            )else(
                activeVP = viewport.activeViewport
                windows.sendmessage ViewPanelHwnd 11 0 0    -- redraw off
                
                viewport.ActiveViewportEx vp_front
                data_front = CalculateFacesColors()
                
                viewport.ActiveViewportEx vp_back
                data_back = CalculateFacesColors()
                
                viewport.ActiveViewportEx activeVP
                windows.sendmessage ViewPanelHwnd 11 1 0    -- redraw on
            )
        )

        fn GW_DisplayFacesColors =
        (
            for j in data_front do
            (
                val = case rdo1.state of
                (
                    1: j[2] as point3
                    2: int j[2].h
                    3: int j[2].s
                    4: int j[2].v
                )
                gw.text j[1] (val as string) color:blue
            )
            
            for j in data_back do
            (
                val = case rdo1.state of
                (
                    1: j[2] as point3
                    2: int j[2].h
                    3: int j[2].s
                    4: int j[2].v
                )
                gw.text j[1] (val as string) color:red
            )
            gw.enlargeUpdateRect #whole
        )

        on RO_DISPLAY_FACES_COLORS open do
        (
            unregisterRedrawViewsCallback GW_DisplayFacesColors
            completeredraw()
        )

        on RO_DISPLAY_FACES_COLORS close do
        (
            unregisterRedrawViewsCallback GW_DisplayFacesColors
            completeredraw()
        )

        on btn1 changed arg do
        (
            if vp_front == undefined or vp_back == undefined do return messagebox "Select View 1 and 2"
            
            unregisterRedrawViewsCallback GW_DisplayFacesColors
            if arg then
            (
                if selection.count == 1 and iskindof selection[1] editable_poly then
                (
                    ViewPanelHwnd = GetViewportsPanelHWND()
                    node = selection[1]
                    clock.tick()
                    clock.active = true
                    registerRedrawViewsCallback GW_DisplayFacesColors
                )else(
                    node = undefined
                    btn1.checked = false
                    clock.active = false
                    messagebox "Select an object"
                )
            )else(
                clock.active = false
            )
            completeredraw()
        )

        on rdo1 changed arg do completeredraw()
        
        on bt_vp1 pressed do
        (
            vp_front = viewport.activeViewport
            bt_vp1.text = vp_front as string
        )
        
        on bt_vp2 pressed do
        (
            vp_back = viewport.activeViewport
            bt_vp2.text = vp_back as string
        )

    )

    createdialog RO_DISPLAY_FACES_COLORS
)


#18

wow Thank you thats much more than i expected.
It is almost done. Now the last problem is that the User interface is flickering and partly unusable when the activeViewport changed all the time mhh.
I tried to find the exposure control algorithm but i had no luck so far. Do you have any Plan on where to look for it?
What all of this is for : The Project am on is about transferring a virtual light situation onto a real object by measuring-> setting the light on each face.


#19

If the screen flickers then the WM_SETREDRAW message mightnot be working on your system. On my end I see no flickering in any Max version I could test, and I can navigate the viewports normally.

I tested it on Windows 7, perhaps you are using Windows 10?You’ll need to find the correct message in order to set the redraw on/off.

Other than that, the script is like 90% done if you wouldlike to avoid any “hack” using messages, you could use the C# API, which allows you to get the BID of a non-active viewport.

Regarding the Exposure algorithm, I would try the SDKexamples, but I think it is not there. Perhaps you should open a different thread about the Logarithmic Exposure Control and how to do it in MXS? It seems it is a custom algorithm so is not going to be so easy to re-build. The Brightness and Contrast parameters could be just standard, but I don’t know how the other values scale the colors.


#20

I also use win 7 with the following .net versions installed :


They flickering is there I can see the yellow border of the active viewport switch between the viewports. I cannot really interact with the toolbar on the bottom right to turn around or so.
which .net version do you use?


#21

I don’t know what can cause the flickering on your end, but I don’t think is .Net related. The script just sends a standard Windows message. Perhaps is video card or driver related, I can’t tell.
You could try with windows.postMessage() instead of sendmessage().
Or try sending just the messages to enable/disable the redraw and see if they work.


#22

you can really use the orbit tool while running the script?

I also tried to send the same message to ViewportControlPanel but then i couldnt even select the orbit tool anymore…
My earlier script has the same problem that I have to select the object to start the render to texture script.
If I only could find a way to do it in the background or to render to texture without selecting the object everything would work out.
The new “All prepared” option in the render to texture rollout was a hope but I dont know how to select in the script.


clearListener()
global nodeName = "right_perfect"
global DotNetForm
-- Check variable and close window if it exists
try DotNetForm.close() catch()
(    
    unregisterRedrawViewsCallback printTime
    global obj = getnodebyname nodeName
    -- Create a form
    global DotNetForm = DotNetObject "System.Windows.Forms.Form"
    DotNetForm.Size = (DotNetObject "Drawing.Size" 200 80)
    
    global ViewPanelHwnd = 0
    fn GetViewportsPanelHWND = ( for j in (windows.getchildrenhwnd #max) where j[4] == "ViewPanel" do exit with j[1] )
    ViewPanelHwnd = GetViewportsPanelHWND()
    
    global ViewPanelHwnd2 = 0
    fn GetViewportsPanelHWND2 = ( for j in (windows.getchildrenhwnd #max) where j[4] == "RollupPanel" do exit with j[1] )
    ViewPanelHwnd2 = GetViewportsPanelHWND2()
    
    -- create bakemap to use later
    global monitor = bitmap 14 14 color:green
    display monitor
    -- set render environment background to black
    execute "backgroundcolor = color 0 0 0"
    if (obj != undefined AND obj.name == nodeName) then (        
        local bakeMap = diffusemap()
        bakeMap.outputSzX = 16
        bakeMap.outputSzY = 16
        bakeMap.fileType = ".png"
        bakeMap.filterOn = false
        bakeMap.shadowsOn = on
        bakeMap.lightingon = on
        bakeMap.targetMapSlotName = "Diffuse"
        bakeMap.enabled = true
        obj.INodeBakeProperties.addBakeElement bakeMap
        obj.INodeBakeProperties.bakeEnabled = on
        obj.INodeBakeProperties.flags = 1
        obj.INodeBakeProperties.bakeChannel = 1
         obj.INodeBakeProperties.nDilations = 0
    )
    local myBitmap = render rendertype:#bakeSelected vfb:off progressBar:false outputSize:[16,16]
    
    fn renderFrame = (
        if (obj != undefined AND obj.name == nodeName) then (
            faceValues = #()
            selectedbefore = getCurrentSelection()
            
            windows.sendmessage ViewPanelHwnd 11 0 0    -- redraw off
            windows.sendmessage ViewPanelHwnd2 11 0 0    -- redraw off
            select obj
            myBitmap = render rendertype:#bakeSelected vfb:off progressBar:false outputSize:[16,16]

            if (selectedbefore != undefined) then ( select selectedbefore )
            windows.sendmessage ViewPanelHwnd 11 1 0    -- redraw on
            windows.sendmessage ViewPanelHwnd2 11 1 0    -- redraw off
            
            -- this is the first row which has only 6 fields
            thisRow = getPixels myBitmap [1,2] 6
            for color in thisRow do ( append faceValues color.r )
            for row=3 to 16 do (
            thisRow = getPixels myBitmap [1,row] 14 
                for color in thisRow do (
                    append faceValues color.r
                )    
            )
            for y = 1 to 14 do (
                for x = 1 to 14 do (
                    index = (y*x)
                    if index <= 188 then (
                        colorValue = faceValues[index] 
                        thisColor = color colorValue colorValue colorValue
                        -- append colors thisColor
                        colors = #(thisColor)
                        setPixels monitor [(x-1),(y-1)] colors
                    )
                )
            )
            close myBitmap
            -- copy myBitmap monitor
            display monitor 
            -- return faceValues
        )
    )
    
    local theTimer = dotNetObject "System.Windows.Forms.Timer"
    fn printTime = (
        values = renderFrame()
    )
    
    fn stopEverything = (
   unregisterRedrawViewsCallback printTime        
    )
    fn exitProgram = (
   unregisterRedrawViewsCallback printTime
        if (obj != undefined AND obj.name == nodeName) then (
              obj.iNodeBakeProperties.removeAllBakeElements()
        )
        free myBitmap
        undisplay monitor
    )

    -- Create a button
    local DotNetButton = DotNetObject "System.Windows.Forms.Button"
    DotNetButton.Text = "start"
    DotNetButton.Location = (DotNetObject "Drawing.Point" 10 10)

    -- Create another button
    local DotNetButton2 = DotNetObject "System.Windows.Forms.Button"
    DotNetButton2.Text = "stop"
    DotNetButton2.Location = (DotNetObject "Drawing.Point" 100 10)

    -- The "Click" event calls this function (see below)
    fn OnButtonClick s e =(
        if (s.Text == "start") then (
            registerRedrawViewsCallback printTime
        ) else (
            unregisterRedrawViewsCallback printTime
            stopEverything()
        )
    )

    -- Setup an event handler for both buttons
    dotNet.addEventHandler DotNetButton "Click" OnButtonClick
    dotNet.addEventHandler DotNetButton2 "Click" OnButtonClick
    dotNet.addEventHandler DotNetForm "Closing" exitProgram
   
    -- Add the buttons to the form
    DotNetForm.Controls.AddRange #(DotNetButton, DotNetButton2)
    DotNetForm.TopMost = true
    
    -- Show the form   
    DotNetForm.Show()
)

this somehow works but If i add or remove a light while it is running it is crashing .
when adding a light it first crashes if I leave the mouse button so while dragging the light into the scene everything still works even with two lights but as soon as i move my finger up (on mouse up event) I get an application error and max crashes


#23

I can use Max normally including any viewport navigation command, add/remove lights, modify the box, add/remove faces etc. and everything updates in real time, with no flickering, not even in the controls. Additionally, I can animate and play the animation while the tools is enabled and the values updates while playing.

The CPU usage keeps at 0 and the FPS does not slow down, which is surprising for a script that captures 100 images per second. I would expect at least a little drop in performance, but nothing changes on my end.

Using the render() function does cast a lot of notifications so I don’t think it is suitable for this task,

Using the Max .Net API should do it better. Here is all you need to implement it:


(
    gbl = (dotnetclass "Autodesk.Max.GlobalInterface").Instance
    ip  = gbl.COREInterface
    
    viewHwnd = dotnetobject "system.intptr" ip.ActiveViewExp.Hwnd
    viewExp = ip.GetViewExp viewHwnd
    GraphicsWindow = viewExp.Gw
    
    dib = GraphicsWindow.DIB
)

Grab each viewport hwnd and the just get the bitmap of them and find the pixels values. From my last script you can remove all the stuff to switch the active viewport and the redraw messages. With this, you should see no flickering at all. Don’t forget to dispose the returning bitmap once you don’t need it.


#24

cool thanks,
Ufff, I dont really know how to implement the code, did you write it or did you find it somewhere?
I tried to look at the dib data from the code like this but only got black screen:


gbl = (dotnetclass "Autodesk.Max.GlobalInterface").Instance
ip  = gbl.COREInterface

viewHwnd = dotnetobject "system.intptr" ip.ActiveViewExp.Hwnd
viewExp = ip.GetViewExp viewHwnd
GraphicsWindow = viewExp.Gw

dib = GraphicsWindow.DIB

testbmp = bitmap 1920 1024 
copy dib testbmp 
display testbmp


#25

I wrote the code. The bitmap you get is a .Net bitmap so it won’t display with the MXS display() function. You can either copy it to the clipboard (using .Net) and then getting the clipboard image from MXS and display it or use a .Net control to see it.


(
    gbl = (dotnetclass "Autodesk.Max.GlobalInterface").Instance
    ip  = gbl.COREInterface

    viewHwnd = dotnetobject "system.intptr" ip.ActiveViewExp.Hwnd
    viewExp = ip.GetViewExp viewHwnd
    GraphicsWindow = viewExp.Gw

    dib = GraphicsWindow.DIB
    
    rollout RO_VIEWER ""
    (
        dotNetControl dnc_img "PictureBox" pos:[8,8] width:dib.width height:dib.height
        
        on RO_VIEWER open do dnc_img.image = dib
    )
    
    createdialog RO_VIEWER width:(dib.width+16) height:(dib.height+16)
)


#26

okay I did the following to see all properties of gbl.COREInterface


local props = getPropNames gbl:COREInterface
print props 

but there is nothing else than ActiveViewExp in the list
I dont get how to grab a Viewport handle mh, if I find it I will report it here
___ EDIT
I can somehow get the top view with the following, but I have no idea how to get the other views.
Do you know how to do it?


gbl = (dotnetclass "Autodesk.Max.GlobalInterface").Instance
ip  = gbl.COREInterface    
viewHwnd = dotnetobject "system.intptr" ip.MAXHWnd  -- this here returns the top view, I dont know why.


#27

link


#28

Thanks! I could somehow manage to get them like this now by try and error


clearListener()
gbl = (dotnetclass "Autodesk.Max.GlobalInterface").Instance
ip  = gbl.COREInterface
fn GetLeftCamHWND = ( for j in (windows.getchildrenhwnd #max) where j[5] == "camera_left" do exit with j[1] )
fn GetRightCamHWND = ( for j in (windows.getchildrenhwnd #max) where j[5] == "camera_right" do exit with j[1] )
camLeft = GetLeftCamHWND()
camRight = GetRightCamHWND()

viewHwnd = dotnetobject "system.intptr" camLeft    
viewExp = ip.GetViewExp viewHwnd
GraphicsWindow = viewExp.Gw

dib = GraphicsWindow.DIB
rollout RO_VIEWER ""
(
    dotNetControl dnc_img "PictureBox" pos:[8,8] width:dib.width height:dib.height

    on RO_VIEWER open do dnc_img.image = dib
)
createdialog RO_VIEWER width:(dib.width+16) height:(dib.height+16)


#29

Unfortunately not all SDK is implemented in the .Net API, although it gets better in later Max versions. But in Max 2014 many things do not work.

There are several methods and properties implemented in different interfaces, but I couldn’t get most of them to work.

GetCOREInterface7()->getViewport()

Was deprecated in Max 2013 and getViewExp() doesn’t seem to work in Max 2014.

Here is something I could get to work if you want to stick to the SDK, but there might be other ways.


(

    gbl = (dotnetclass "Autodesk.Max.GlobalInterface").Instance
    ip  = gbl.COREInterface
    
    fn GetCameraViewExp name: =
    (
        viewPanel = gbl.ViewPanelManager
        activePanel = viewPanel.ActiveViewPanel
        numViewports = activePanel.NumberOfViewports
        
        for j = 1 to numViewports do
        (
            ViewExp = activePanel.GetViewExpByIndex (j-1)
            ViewType = ViewExp.ViewType.ToString()
            if ViewType == "Camera" do
            (
                if ViewExp.ViewCamera.Name == name do return ViewExp
            )
        )
        return undefined
    )
    
    GetCameraViewExp name:"camera_left"
    
)


#30

Here is a working code using the .net API. You still need to debug and improve it.

The Logarithmic Exposure Control seems to work consistently in viewport and render, but not all the others, so this solution is a partial workaround for what you need. If you switch to another Exposure algorithm, it might not give the values you need.

A better solution would be to implement the Exposure algorithm in the code, or perhaps you can adjust the exposure externally and use something different. After all, it is just a color curve.


(
    global GW_DisplayFacesColors
    local data_front = #()
    local data_back  = #()
    local data_type  = 1
    local showSamples = false

    fn GW_DisplayFacesColors =
    (
        for j in data_front do
        (
            val = case data_type of
            (
                1: j[2] as point3
                2: int j[2].h
                3: int j[2].s
                4: int j[2].v
            )
            gw.text j[1] (val as string) color:red
            if showSamples do for k in j[3] do gw.Marker k #asterisk color:red
        )

        for j in data_back do
        (
            val = case data_type of
            (
                1: j[2] as point3
                2: int j[2].h
                3: int j[2].s
                4: int j[2].v
            )
            gw.text j[1] (val as string) color:black
            if showSamples do for k in j[3] do gw.Marker k #asterisk color:black
        )
        gw.enlargeUpdateRect #whole
    )
    
    
    try destroydialog ::RO_DISPLAY_FACES_COLORS catch()

    rollout RO_DISPLAY_FACES_COLORS "Faces Colors" width:172 height:172
    (
        button       bt_vp1          "Set Font View"      pos:[ 8, 8] width:72 height:32
        button       bt_vp2          "Set Back View"      pos:[88, 8] width:72 height:32
        spinner      sp_sampleDist   "Samples Distance"   pos:[ 8,48] range:[0,100,50] fieldwidth:56
        checkbox     chk_showSamples "Show Smaple Points" pos:[ 8,72]

        radioButtons rdo1 "Display:" pos:[8, 96] labels:#("RGB","H","S","B") columns:4
        checkbutton  btn1 "Enable"   pos:[8,136] width:154 height:28
        timer        clock interval:20 active:false

        local GetFaceVerts = polyop.getfaceverts
        local GetfaceNormal = polyop.getfaceNormal
        local GetFaceCenter = polyop.getFaceCenter
        local GetVert = polyop.getvert
        
        local node
        local vp_front = undefined
        local vp_back  = undefined
        local gw_front = undefined
        local gw_back  = undefined

        local gbl = (dotnetclass "Autodesk.Max.GlobalInterface").Instance
        local ip  = gbl.COREInterface
        
        local identityTM = gbl.IdentityTM.Create()
        
        fn ConvertMatrix tm =
        (
            r1 = tm.GetRow 0
            r2 = tm.GetRow 1
            r3 = tm.GetRow 2
            
            return (matrix3 [r1.x, r1.y, r1.z] [r2.x, r2.y, r2.z] [r3.x, r3.y, r3.z] [0,0,0])
        )
        
        fn CalculateFacesColors viewGW viewTM =
        (
            dib = viewGW.DIB
            
            tm = viewTM.AffineTM
            viewTM = ConvertMatrix tm
            viewGW.transform = identityTM
            
            result = for f = 1 to node.numfaces collect
            (
                faceNormal = normalize (GetfaceNormal node f)

                if (faceNormal*viewTM).z > 0 then
                (
                    faceCenter = GetFaceCenter node f
                    fverts = GetFaceVerts node f

                    numverts = fverts.count
                    faceColor = black
                    
                    samplePoints = #()

                    for j in fverts do
                    (
                        d = distance faceCenter (GetVert node j)
                        n = normalize (faceCenter - (GetVert node j))
                        
                        d = sp_sampleDist.value * d / 100.0
                            
                        pt = faceCenter + (d*n)
                        pt = gbl.Point3.Create pt.x pt.y pt.z
                            
                        px = gbl.Point3.Create 0 0 0
                        viewGW.TransPoint pt px
                        
                        if px.x > 0 and px.x < dib.width and px.y > 0 and px.y < dib.height do
                        (
                            pcolor = dib.GetPixel px.x px.y
                            if pcolor != undefined then
                            (
                                faceColor.r += pcolor.r
                                faceColor.g += pcolor.g
                                faceColor.b += pcolor.b
                            )else(
                                numverts -= 1
                            )
                        )
                        
                        append samplePoints [pt.x, pt.y, pt.z]
                        
                        px.Dispose()
                        pt.Dispose()
                    )

                    faceColor.r = int (faceColor.r/numverts)
                    faceColor.g = int (faceColor.g/numverts)
                    faceColor.b = int (faceColor.b/numverts)
                    
                    #(faceCenter, faceColor, samplePoints)
                )else(
                    dontcollect
                )
            )
            
            tm.Dispose()
            dib.Dispose()
            
            return result
        )
        
        fn GetDisplayData =
        (
            data_front = CalculateFacesColors gw_front vp_front 
            data_back  = CalculateFacesColors gw_back vp_back
        )
        
        fn GetViewExp =
        (
            viewHwnd = dotnetobject "system.intptr" ip.ActiveViewExp.Hwnd
            return ip.GetViewExp viewHwnd
        )

        on clock tick do
        (
            GetDisplayData()
        )

        on RO_DISPLAY_FACES_COLORS close do
        (
            unregisterRedrawViewsCallback GW_DisplayFacesColors
            completeredraw()
        )

        on btn1 changed arg do
        (
            if vp_front == undefined or vp_back == undefined do
            (
                btn1.checked = false
                return messagebox "Select Front and Back Views"
            )

            unregisterRedrawViewsCallback GW_DisplayFacesColors
            if arg then
            (
                if selection.count == 1 and iskindof selection[1] editable_poly then
                (
                    node = selection[1]
                    clock.tick()
                    clock.active = true
                    registerRedrawViewsCallback GW_DisplayFacesColors
                )else(
                    node = undefined
                    btn1.checked = false
                    clock.active = false
                    messagebox "Select an object"
                )
            )else(
                clock.active = false
            )
            completeredraw()
        )

        on rdo1 changed arg do
        (
            data_type = arg
            completeredraw()
        )

        on bt_vp1 pressed do
        (
            vp_front = GetViewExp()
            gw_front = vp_front.Gw
            bt_vp1.text = viewport.activeViewport as string
        )

        on bt_vp2 pressed do
        (
            vp_back = GetViewExp()
            gw_back = vp_back.Gw
            bt_vp2.text = viewport.activeViewport as string
        )
        
        on chk_showSamples changed arg do
        (
            showSamples = arg
            GetDisplayData()
            completeredraw()
        )
        
        on sp_sampleDist changed arg do
        (
            GetDisplayData()
            completeredraw()
        )

    )

    createdialog RO_DISPLAY_FACES_COLORS
)