Sunday, 6 January 2019

Viewport Text

This is less of a snippet and more a full-blown script...

Allows you to display viewport text for:
  • object name and type
  • edge length
  • face area and texels
Text is displayed on the object or at the corner of the viewport, according to preference.

A UI is provided to configure the viewport settings, and may be closed once the viewport text is activated.





global ViewportText_EdgeEnabled
global ViewportText_EdgeEnabled
global ViewportText_EdgeCount
global ViewportText_EdgeTotal
global ViewportText_EdgeAverage

global ViewportText_ObjectEachEnabled
global ViewportText_ObjectName
global ViewportText_ObjectType
global ViewportText_ObjectColorBy

global ViewportText_ObjectSummaryEnabled
global ViewportText_ObjectSummaryName
global ViewportText_ObjectSummaryType

global ViewportText_FaceCount
global ViewportText_FaceArea
global ViewportText_FaceTexels

global ViewportText_TexelsPerMeter
global ViewportText_Position
global ViewportText_Enabled

global ViewportText_VerticalOffset
global GW_displayViewportText
global ViewportText_ObjectColorByColors = #("Always Yellow", "WireColor", "SuperClass", "Geometry SubClass") 


if ViewportText_Enabled == undefined then
(
    
    ViewportText_ObjectEachEnabled = True
    ViewportText_ObjectName = True
    ViewportText_ObjectType = True
    ViewportText_ObjectColorBy = "Geometry SubClass"

    ViewportText_ObjectSummaryEnabled = True
    ViewportText_ObjectSummaryName = True
    ViewportText_ObjectSummaryType = True
    
    ViewportText_EdgeCount = true
    ViewportText_EdgeTotal = true
    ViewportText_EdgeAverage = true
    
    ViewportText_FaceCount = True
    ViewportText_FaceArea = True
    ViewportText_FaceTexels = True
    
    ViewportText_VerticalOffset = 0
    ViewportText_TexelsPerMeter = 1024
    ViewportText_Position = "Bottom-left"
    ViewportText_Enabled = True

)

unregisterRedrawViewsCallback GW_displayViewportText

----------------------
-- Helper Funcitons
----------------------

fn round_to val n =
(
local mult = 10.0 ^ n
(floor ((val * mult) + 0.5)) / mult
)

fn systemAreaToMetersSquared systemUnitsArea =
(
    systemUnitsAreaInMeterSquare = case units.SystemType of
    (
        #Inches: 1550.0
        #Feet: 10.7639
        #Miles: 0.000000386102
        #Millimeters: 1000000.0
        #Centimeters: 10000.0
        #Meters: 1.0
        #Kilometers: 0.000001
    )
    
    meterssquared = systemUnitsArea / systemUnitsAreaInMeterSquare
    meterssquared *= (units.SystemScale ^ 2) 
    return round_to meterssquared 2
)

fn systemAreaToTexelArea systemUnitsArea =
(
    meterssquared = systemAreaToMetersSquared systemUnitsArea
    onesidetexels = (((meterssquared ^ 0.5) * ViewportText_TexelsPerMeter) as integer) as string
    
    return (onesidetexels + "x" + onesidetexels + "px")
)

fn colorFromSuperClass obj =
(
    cyan = color 0 255 255
    return case (superclassof obj) of
    (
        GeometryClass: white
        Helper: green
        Shape: cyan
        Camera: orange
        Light: yellow
        System: red
        SpaceWarpObject: red
        UndefinedClass: red
        default: red
    )
)

fn colorFromGeometryClass obj =
(
    cyan = color 0 255 255
    if superclassof obj != GeometryClass then return white
    return case (classof obj) of
    (
        Editable_Poly: green
        PolyMeshObject: green
        Editable_Mesh: yellow
        Box: cyan
        Plane: cyan
        cone: cyan
        geosphere: cyan
        sphere: cyan
        cylinder:cyan
        torus: cyan
        pyramid: cyan
        teapot: cyan
        default: if canConvertTo o Editable_Poly then orange else red
    )
)


----------------------
-- Viewport Draw
----------------------

fn drawText str textcolor worldpos textposition =
(
    extents = gw.getTextExtent str
    if textposition == "Selection" then
    (
        screenpos = gw.wTransPoint worldpos
        screenpos = [screenpos.x, gw.getWinSizeY()-screenpos.y,0]
        
        if ViewportText_VerticalOffset > 15 or ViewportText_VerticalOffset < -15 then
        (
            gw.setColor #line textcolor
            endoffset = if ViewportText_VerticalOffset > 0 then -10 else 5
            gw.hPolyline #(screenpos, screenpos + [0,ViewportText_VerticalOffset + endoffset,0]) false
        )
        
        screenpos = [screenpos.x - (extents.x / 2.0), screenpos.y - (extents.y / 2.0) + ViewportText_VerticalOffset, 0]
        gw.htext screenpos str color:textcolor
        
        
    )
    else
    (
        case textposition of
        (
            "Bottom-left": pos = [50, 20, 0]
            "Bottom-Right": pos = [gw.getWinSizeX()-extents.x, 20, 0]
            "Top-Right": pos = [gw.getWinSizeX()-extents.x, gw.getWinSizeY()-extents.y, 0]
        )
        gw.htext pos str color:textcolor
    )
  gw.enlargeUpdateRect #whole  
)


fn DisplayEdgeMeasure =
(
    str = ""
    worldpos = [0,0,0]
    
    obj = selection[1]
    op = if classof obj == editable_poly or classof obj == polymeshobject then polyop else meshop
    
    edges = getedgeselection obj as array
    if edges.count == 0 then return false
    edgelengths = #()
    midpoints = #()

    for i = 1 to edges.count do
    (
        verts = op.getvertsusingedge obj edges[i] as array
        v1 = op.getvert obj verts[1]
        v2 = op.getvert obj verts[2]
        edgelengths[i] = distance v1 v2
        midpoints[i] = (v1 + v2) / 2.0
    )

    totallength = 0
    for edlen in edgelengths do totallength += edlen
        
    midpoint = 0
    for m in midpoints do midpoint += m
    midpoint = midpoint / (midpoints.count as float)
    worldpos = midpoint

    --format "edges: %, total: %, average: %\n" edges.count totallength (totallength / edges.count)
    if ViewportText_EdgeCount then 
    (
        str += "edges: " + edges.count as string + "     "
    )
    if ViewportText_EdgeTotal then
    (
        str += "total: " + units.formatValue totallength + "    "
    )
    if ViewportText_EdgeAverage then 
    (
        str += "average: " + units.formatValue (totallength / (edges.count as float)) + "  "
    )
    
    drawText str white worldpos ViewportText_Position
)

fn DisplayFaceMeasure =
(
    str = ""
    
    worldpos = [0,0,0]
    obj = selection[1]
    op = if classof obj == editable_poly or classof obj == polymeshobject then polyop else meshop
    
    faces = (getfaceselection obj) as array
    if faces.count == 0 then return false
    faceareas = #()
    midpoints = #() 

    for i = 1 to faces.count do
    (
        faceareas[i] = op.getfacearea obj faces[i]
        midpoints[i] = op.getfacecenter obj faces[i]
    )

    totalarea = 0
    for a in faceareas do totalarea += a
    
        
    midpoint = [0,0,0]
    for m in midpoints do midpoint += m
    midpoint = midpoint / (midpoints.count as float)
    worldpos = midpoint
--     print "--"
--     print (midpoints as array)
--     print worldpos
    
    if ViewportText_FaceCount then 
    (
        str += "faces: " + faces.count as string + "     "
    )
    if ViewportText_FaceArea then
    (
        str += "area: " + ((systemAreaToMetersSquared totalarea) as string) + "msq    "
    )
    if ViewportText_FaceTexels then
    (
        str += "texels: " + systemAreaToTexelArea totalarea + "    "
    )
    drawText str white worldpos ViewportText_Position
)


fn DisplayObjects =
(
    worldpos = [0,0,0]
    sel = getcurrentselection()
    
    matchingclasses = true
    (
        for o in sel do
        (
            if classof o != classof sel[1] then matchingclasses = false
        )
    )
    matchingsuperclasses = true
    for o in sel do (if superclassof o != superclassof sel[1] then matchingsuperclasses = false; exit)

    if ViewportText_ObjectEachEnabled then
    (
        if sel.count < 1000 then -- too many objects would be very cluttered on screen and hurt perf
        (            
            for obj in sel do
            (
                str = ""
                if ViewportText_ObjectName then str += obj.name + "    "
                if ViewportText_ObjectType then str += ((classof obj) as string) + "    "
                case ViewportText_ObjectColorBy of
                (
                    "Always Yellow": textcolor = yellow
                    "WireColor": try (textcolor = obj.wirecolor) catch (textcolor = yellow)
                    "SuperClass": textcolor = colorFromSuperClass obj
                    "Geometry SubClass": textcolor = colorFromGeometryClass obj
                )
                if textcolor == undefined then (print obj.name; print obj.wirecolor)
                
                worldpos = obj.pos
                drawText str textcolor worldpos "Selection"
            )
        )
    )

    if ViewportText_ObjectSummaryEnabled then
    (
        str = ""
        if ViewportText_ObjectSummaryName then
        (
            if sel.count == 1 then str += sel[1].name + "    "
                else str += sel.count as string + " objects    "
        )
        
        if ViewportText_ObjectSummaryType then
        (
            if matchingclasses then str += (classof sel[1]) as string + " (" + sel.count as string + ")    "
                else str += "various types (" + sel.count as string + ")    "
        )
        
        pos = ViewportText_Position
        -- prevent summary and each from overlapping
        if ViewportText_ObjectEachEnabled and ViewportText_Position == "Selection" then
        (
            pos = "Bottom-left"
        )
        
        worldpos = [0,0,0]
        for o in sel do worldpos += o.pos
        worldpos = worldpos / (sel.count as float)
        
        drawText str yellow worldpos pos
    )    
)


fn GW_displayViewportText =
(
    
    gw.setTransform (matrix3 1)
    
    -- OBJECTS
    if subobjectlevel == 0 or subobjectlevel == undefined and selection.count > 0 then
    (
        DisplayObjects()
    )
    -- EDGES
    if (subobjectlevel == 2 or subobjectlevel == 3) and selection.count == 1 then
    (
        DisplayEdgeMeasure()
    )
    -- FACES
    if (subobjectlevel == 4 or subobjectlevel == 5) and selection.count == 1 then
    (
        DisplayFaceMeasure()
    )
)


----------------------
--            UI
----------------------

try (destroydialog rol_ViewportText) catch()

rollout rol_ViewportText "Viewport Text"
(
    group "Per Object"
    (
        checkbox chk_objectname "Object Name"
        checkbox chk_objecttype "Object Type"
        radiobuttons rdo_colorby "Color By" labels:ViewportText_ObjectColorByColors
        checkbox chk_objecteach "Enabled"
    )
    group "Object Summary"
    (
        checkbox chk_objectsummaryname "Object Name / Count"
        checkbox chk_objectsummarytype "Object Type + Count"
        checkbox chk_objectsummaryenabled "Enabled"
    )
    group "Edge"
    (
        checkbox chk_edgecount "Edge Count"
        checkbox chk_edgetotal "Edge Total"
        checkbox chk_edgeaverage "Edge Average"
    )
    group "Face"
    (
        checkbox chk_facecount "Face Count"
        checkbox chk_facearea "Surface Area"
        checkbox chk_facetexels "Texel Area"
    )
    
    spinner spn_texelsPerMeter "Texels per Meter  " range:[128, 4096, 1024] scale:128 type:#integer offset:[46,4] width:110
    label lbl_pos "Position" across:2 width:80 offset:[8,2]
    dropdownlist ddl_position "" items:#("Selection", "Bottom-left", "Bottom-Right", "Top-Right") width:90 offset:[-20,0]
    spinner spn_verticalOffset "Vertical Offset      " range:[-50,50,0] scale:5 type:#integer offset:[46,-3] width:110
    checkbutton btn_enable "Enable" width:100 offset:[0,-2]
    
    fn updatePrefs =
    (
        ViewportText_EdgeCount = chk_edgecount.checked
        ViewportText_EdgeTotal = chk_edgetotal.checked
        ViewportText_EdgeAverage = chk_edgeaverage.checked
        
        ViewportText_ObjectEachEnabled = chk_objecteach.checked
        ViewportText_ObjectName = chk_objectname.checked
        ViewportText_ObjectType = chk_objecttype.checked
        ViewportText_ObjectColorBy = ViewportText_ObjectColorByColors[rdo_colorby.state]
        
        ViewportText_ObjectSummaryEnabled = chk_objectsummaryenabled.checked
        ViewportText_ObjectSummaryName = chk_objectsummaryname.checked
        ViewportText_ObjectSummaryType = chk_objectsummarytype.checked
        
        ViewportText_FaceCount = chk_facecount.checked
        ViewportText_FaceArea = chk_facearea.checked
        ViewportText_FaceTexels = chk_facetexels.checked
        
        ViewportText_TexelsPerMeter = spn_texelsPerMeter.value
        ViewportText_Position = ddl_position.selected
        ViewportText_VerticalOffset = spn_verticalOffset.value
        ViewportText_Enabled = btn_enable.checked
        
        
        btn_enable.text = if ViewportText_Enabled then "Active" else "Click to Activate"
        unregisterRedrawViewsCallback GW_displayViewportText
        if ViewportText_Enabled then registerRedrawViewsCallback GW_displayViewportText 
        redrawViews()     
    )
    
    on rol_ViewportText open do
    (
        chk_edgecount.checked = ViewportText_EdgeCount
        chk_edgetotal.checked = ViewportText_EdgeTotal
        chk_edgeaverage.checked = ViewportText_EdgeAverage
        
        chk_objecteach.checked = ViewportText_ObjectEachEnabled
        chk_objectname.checked = ViewportText_ObjectSummaryName
        chk_objecttype.checked = ViewportText_ObjectType
        rdo_colorby.state = finditem ViewportText_ObjectColorByColors ViewportText_ObjectColorBy
        
        chk_objectsummaryname.checked = ViewportText_ObjectSummaryEnabled
        chk_objectsummarytype.checked = ViewportText_ObjectSummaryType
        chk_objectsummaryenabled.checked = ViewportText_ObjectSummaryEnabled
        
        chk_facecount.checked = ViewportText_FaceCount
        chk_facearea.checked = ViewportText_FaceArea
        chk_facetexels.checked = ViewportText_FaceTexels
        
        spn_texelsPerMeter.value = ViewportText_TexelsPerMeter
        ddl_position.selection = finditem ddl_position.items ViewportText_Position
        spn_verticalOffset.value = ViewportText_VerticalOffset
        btn_enable.checked = ViewportText_Enabled
        
        updatePrefs()
    )
    
    on chk_edgecount changed val do updatePrefs()
    on chk_edgetotal changed val do updatePrefs()
    on chk_edgeaverage changed val do updatePrefs()
    
    on chk_objectname changed val do updatePrefs()
    on chk_objecttype changed val do updatePrefs()
    on rdo_colorby changed val do updatePrefs()
    on chk_objecteach changed val do updatePrefs()
    
    on chk_objectsummaryname changed val do updatePrefs()
    on chk_objectsummarytype changed val do updatePrefs()
    on chk_objectsummaryenabled changed val do updatePrefs()
    
    on chk_facecount changed val do updatePrefs()
    on chk_facearea changed val do updatePrefs()
    on chk_facetexels changed val do updatePrefs()
    on spn_texelsPerMeter changed val do updatePrefs()
    
    on btn_enable changed val do updatePrefs()
    on ddl_position selected val do updatePrefs()
    on spn_verticalOffset changed val do updatePrefs()
)

createdialog rol_ViewportText