func (nv *NetView) ViewConfig() {
	vs := nv.Scene()
	if nv.Net == nil || nv.Net.NLayers() == 0 {
		vs.DeleteChildren(true)
		vs.Meshes = nil
		return
	}
	if len(vs.Lights) == 0 {
		nv.ViewDefaults()
	}
	vs.BgColor = gi.Prefs.Colors.Background  
	nlay := nv.Net.NLayers()
	laysGp, err := vs.ChildByNameTry("Layers", 0)
	if err != nil {
		laysGp = gi3d.AddNewGroup(vs, vs, "Layers")
	}
	layConfig := kit.TypeAndNameList{}
	for li := 0; li < nlay; li++ {
		lay := nv.Net.Layer(li)
		lmesh := vs.MeshByName(lay.Name())
		if lmesh == nil {
			AddNewLayMesh(vs, nv, lay)
		} else {
			lmesh.(*LayMesh).Lay = lay  
		}
		layConfig.Add(gi3d.KiT_Group, lay.Name())
	}
	gpConfig := kit.TypeAndNameList{}
	gpConfig.Add(KiT_LayObj, "layer")
	gpConfig.Add(KiT_LayName, "name")
	_, updt := laysGp.ConfigChildren(layConfig)
	nmin, nmax := nv.Net.Bounds()
	nsz := nmax.Sub(nmin).Sub(mat32.Vec3{1, 1, 0}).Max(mat32.Vec3{1, 1, 1})
	nsc := mat32.Vec3{1.0 / nsz.X, 1.0 / nsz.Y, 1.0 / nsz.Z}
	szc := mat32.Max(nsc.X, nsc.Y)
	poff := mat32.NewVec3Scalar(0.5)
	poff.Y = -0.5
	for li, lgi := range *laysGp.Children() {
		ly := nv.Net.Layer(li)
		lg := lgi.(*gi3d.Group)
		lg.ConfigChildren(gpConfig)  
		lp := ly.Pos()
		lp.Y = -lp.Y  
		lp = lp.Sub(nmin).Mul(nsc).Sub(poff)
		rp := ly.RelPos()
		lg.Pose.Pos.Set(lp.X, lp.Z, lp.Y)
		lg.Pose.Scale.Set(nsc.X*rp.Scale, szc, nsc.Y*rp.Scale)
		lo := lg.Child(0).(*LayObj)
		lo.Defaults()
		lo.LayName = ly.Name()
		lo.NetView = nv
		lo.SetMeshName(vs, ly.Name())
		lo.Mat.Color.SetUInt8(255, 100, 255, 128)
		lo.Mat.Specular.SetUInt8(128, 128, 128, 255)
		lo.Mat.CullBack = true
		lo.Mat.CullFront = false
		txt := lg.Child(1).(*LayName)
		txt.Defaults(vs)
		txt.NetView = nv
		txt.SetText(vs, ly.Name())
		txt.Pose.Scale = mat32.NewVec3Scalar(nv.Params.LayNmSize).Div(lg.Pose.Scale)
		txt.SetProp("text-align", gist.AlignLeft)
		txt.SetProp("text-vertical-align", gist.AlignTop)
	}
	vs.InitMeshes()
	laysGp.UpdateEnd(updt)
}
ViewConfig configures the 3D view
package netview
import (
	"fmt"
	"log"
	"strings"
	"sync"
	"github.com/emer/emergent/emer"
	"github.com/emer/etable/minmax"
	"github.com/goki/gi/gi"
	"github.com/goki/gi/gi3d"
	"github.com/goki/gi/gist"
	"github.com/goki/gi/giv"
	"github.com/goki/gi/oswin/key"
	"github.com/goki/gi/units"
	"github.com/goki/ki/ki"
	"github.com/goki/ki/kit"
	"github.com/goki/mat32"
)
type NetView struct {
	gi.Layout
	Net          emer.Network          `desc:"the network that we're viewing"`
	Var          string                `desc:"current variable that we're viewing"`
	Vars         []string              `desc:"the list of variables to view"`
	VarParams    map[string]*VarParams `desc:"parameters for the list of variables to view"`
	CurVarParams *VarParams            `json:"-" xml:"-" view:"-" desc:"current var params -- only valid during Update of display"`
	Params       Params                `desc:"parameters controlling how the view is rendered"`
	ColorMap     *giv.ColorMap         `desc:"color map for mapping values to colors -- set by name in Params"`
	RecNo        int                   `desc:"record number to display -- use -1 to always track latest, otherwise in range [0..Data.Ring.Len-1]"`
	LastCtrs     string                `desc:"last non-empty counters string provided -- re-used if no new one"`
	Data         NetData               `desc:"contains all the network data with history"`
	DataMu       sync.RWMutex          `view:"-" copy:"-" json:"-" xml:"-" desc:"mutex on data access"`
}
var KiT_NetView = kit.Types.AddType(&NetView{}, NetViewProps)
func AddNewNetView(parent ki.Ki, name string) *NetView {
	return parent.AddNewChild(KiT_NetView, name).(*NetView)
}
func (nv *NetView) Defaults() {
	nv.Params.NetView = nv
	nv.Params.Defaults()
	nv.ColorMap = giv.AvailColorMaps[string(nv.Params.ColorMap)]
	nv.RecNo = -1
}
func (nv *NetView) SetNet(net emer.Network) {
	nv.Defaults()
	nv.Net = net
	nv.DataMu.Lock()
	nv.Data.Init(nv.Net, nv.Params.MaxRecs)
	nv.DataMu.Unlock()
	nv.Config()
}
func (nv *NetView) SetVar(vr string) {
	nv.DataMu.Lock()
	nv.Var = vr
	nv.VarsUpdate()
	nv.VarScaleUpdate(nv.Var)
	nv.DataMu.Unlock()
	nv.Update()
}
func (nv *NetView) SetMaxRecs(max int) {
	nv.Params.MaxRecs = max
	nv.Data.Init(nv.Net, nv.Params.MaxRecs)
}
func (nv *NetView) HasLayers() bool {
	if nv.Net == nil || nv.Net.NLayers() == 0 {
		return false
	}
	return true
}
func (nv *NetView) Record(counters string) {
	nv.DataMu.Lock()
	defer nv.DataMu.Unlock()
	if counters != "" {
		nv.LastCtrs = counters
	}
	nv.Data.PrjnType = nv.Params.PrjnType
	nv.Data.Record(nv.LastCtrs)
	nv.RecTrackLatest()  
}
func (nv *NetView) GoUpdate() {
	if !nv.IsVisible() || !nv.HasLayers() {
		return
	}
	mvp := nv.ViewportSafe()
	if mvp.IsUpdatingNode() {
		return
	}
	mvp.BlockUpdates()
	vs := nv.Scene()
	updt := vs.UpdateStart()
	nv.UpdateImpl()
	mvp.UnblockUpdates()
	vs.UpdateEnd(updt)
}
func (nv *NetView) Update() {
	if !nv.IsVisible() || !nv.HasLayers() {
		return
	}
	vs := nv.Scene()
	updt := vs.UpdateStart()
	nv.UpdateImpl()
	vs.UpdateEnd(updt)
}
func (nv *NetView) UpdateImpl() {
	nv.DataMu.Lock()
	vp, ok := nv.VarParams[nv.Var]
	if !ok {
		nv.DataMu.Unlock()
		log.Printf("NetView: %v variable: %v not found\n", nv.Nm, nv.Var)
		return
	}
	nv.CurVarParams = vp
	if !vp.Range.FixMin || !vp.Range.FixMax {
		needUpdt := false
		min, max, ok := nv.Data.VarRange(nv.Var)
		if ok {
			vp.MinMax.Set(min, max)
			if !vp.Range.FixMin {
				nmin := float32(minmax.NiceRoundNumber(float64(min), true))  
				if vp.Range.Min != nmin {
					vp.Range.Min = nmin
					needUpdt = true
				}
			}
			if !vp.Range.FixMax {
				nmax := float32(minmax.NiceRoundNumber(float64(max), false))  
				if vp.Range.Max != nmax {
					vp.Range.Max = nmax
					needUpdt = true
				}
			}
			if vp.ZeroCtr && !vp.Range.FixMin && !vp.Range.FixMax {
				bmax := mat32.Max(mat32.Abs(vp.Range.Max), mat32.Abs(vp.Range.Min))
				if !needUpdt {
					if vp.Range.Max != bmax || vp.Range.Min != -bmax {
						needUpdt = true
					}
				}
				vp.Range.Max = bmax
				vp.Range.Min = -bmax
			}
			if needUpdt {
				nv.VarScaleUpdate(nv.Var)
			}
		}
	}
	vs := nv.Scene()
	laysGp, err := vs.ChildByNameTry("Layers", 0)
	if err != nil || laysGp.NumChildren() != nv.Net.NLayers() {
		nv.Config()
	}
	nv.SetCounters(nv.Data.CounterRec(nv.RecNo))
	nv.UpdateRecNo()
	nv.DataMu.Unlock()
	vs.UpdateMeshes()
}
func (nv *NetView) Config() {
	nv.Lay = gi.LayoutVert
	if nv.Params.UnitSize == 0 {
		nv.Defaults()
	}
	cmap, ok := giv.AvailColorMaps[string(nv.Params.ColorMap)]
	if ok {
		nv.ColorMap = cmap
	} else {
		log.Printf("NetView: %v  ColorMap named: %v not found in AvailColorMaps\n", nv.Nm, nv.Params.ColorMap)
	}
	nv.SetProp("spacing", gi.StdDialogVSpaceUnits)
	config := kit.TypeAndNameList{}
	config.Add(gi.KiT_ToolBar, "tbar")
	config.Add(gi.KiT_Layout, "net")
	config.Add(gi.KiT_Label, "counters")
	config.Add(gi.KiT_ToolBar, "vbar")
	mods, updt := nv.ConfigChildren(config)
	if !mods {
		updt = nv.UpdateStart()
	}
	nlay := nv.NetLay()
	nlay.Lay = gi.LayoutHoriz
	nlay.SetProp("max-width", -1)
	nlay.SetProp("max-height", -1)
	nlay.SetProp("spacing", gi.StdDialogVSpaceUnits)
	vncfg := kit.TypeAndNameList{}
	vncfg.Add(gi.KiT_Frame, "vars")
	vncfg.Add(gi3d.KiT_Scene, "scene")
	nlay.ConfigChildren(vncfg)  
	nv.VarsConfig()
	nv.ViewConfig()
	nv.ToolbarConfig()
	nv.ViewbarConfig()
	ctrs := nv.Counters()
	ctrs.Redrawable = true
	ctrs.SetText("Counters: ")
	nv.DataMu.Lock()
	nv.Data.Init(nv.Net, nv.Params.MaxRecs)
	nv.DataMu.Unlock()
	nv.UpdateEnd(updt)
}
func (nv *NetView) IsConfiged() bool {
	if len(nv.Kids) == 0 {
		return false
	}
	nl := nv.NetLay()
	if len(nl.Kids) == 0 {
		return false
	}
	return true
}
func (nv *NetView) Toolbar() *gi.ToolBar {
	return nv.ChildByName("tbar", 0).(*gi.ToolBar)
}
func (nv *NetView) NetLay() *gi.Layout {
	return nv.ChildByName("net", 1).(*gi.Layout)
}
func (nv *NetView) Counters() *gi.Label {
	return nv.ChildByName("counters", 2).(*gi.Label)
}
func (nv *NetView) Viewbar() *gi.ToolBar {
	return nv.ChildByName("vbar", 3).(*gi.ToolBar)
}
func (nv *NetView) Scene() *gi3d.Scene {
	return nv.NetLay().ChildByName("scene", 1).(*gi3d.Scene)
}
func (nv *NetView) VarsLay() *gi.Frame {
	return nv.NetLay().ChildByName("vars", 0).(*gi.Frame)
}
func (nv *NetView) SetCounters(ctrs string) {
	ct := nv.Counters()
	if ct.Text != ctrs {
		ct.SetText(ctrs)
	}
}
func (nv *NetView) UpdateRecNo() {
	vbar := nv.Viewbar()
	rlbl := vbar.ChildByName("rec", 10).(*gi.Label)
	rlbl.SetText(fmt.Sprintf("%4d ", nv.RecNo))
}
func (nv *NetView) RecFullBkwd() bool {
	if nv.RecNo == 0 {
		return false
	}
	nv.RecNo = 0
	return true
}
func (nv *NetView) RecFastBkwd() bool {
	if nv.RecNo == 0 {
		return false
	}
	if nv.RecNo < 0 {
		nv.RecNo = nv.Data.Ring.Len - nv.Params.NFastSteps
	} else {
		nv.RecNo -= nv.Params.NFastSteps
	}
	if nv.RecNo < 0 {
		nv.RecNo = 0
	}
	return true
}
func (nv *NetView) RecBkwd() bool {
	if nv.RecNo == 0 {
		return false
	}
	if nv.RecNo < 0 {
		nv.RecNo = nv.Data.Ring.Len - 1
	} else {
		nv.RecNo -= 1
	}
	if nv.RecNo < 0 {
		nv.RecNo = 0
	}
	return true
}
func (nv *NetView) RecFwd() bool {
	if nv.RecNo >= nv.Data.Ring.Len-1 {
		nv.RecNo = nv.Data.Ring.Len - 1
		return false
	}
	if nv.RecNo < 0 {
		return false
	}
	nv.RecNo += 1
	if nv.RecNo >= nv.Data.Ring.Len-1 {
		nv.RecNo = nv.Data.Ring.Len - 1
	}
	return true
}
func (nv *NetView) RecFastFwd() bool {
	if nv.RecNo >= nv.Data.Ring.Len-1 {
		nv.RecNo = nv.Data.Ring.Len - 1
		return false
	}
	if nv.RecNo < 0 {
		return false
	}
	nv.RecNo += nv.Params.NFastSteps
	if nv.RecNo >= nv.Data.Ring.Len-1 {
		nv.RecNo = nv.Data.Ring.Len - 1
	}
	return true
}
func (nv *NetView) RecTrackLatest() bool {
	if nv.RecNo == -1 {
		return false
	}
	nv.RecNo = -1
	return true
}
func NetVarsList(net emer.Network, layEven bool) []string {
	if net == nil || net.NLayers() == 0 {
		return nil
	}
	unvars := net.UnitVarNames()
	prjnvars := net.SynVarNames()
	ulen := len(unvars)
	if layEven && ulen%2 != 0 {  
		ulen++
	}
	tlen := ulen + 2*len(prjnvars)
	nvars := make([]string, tlen)
	copy(nvars, unvars)
	st := ulen
	for pi := 0; pi < len(prjnvars); pi++ {
		nvars[st+2*pi] = "r." + prjnvars[pi]
		nvars[st+2*pi+1] = "s." + prjnvars[pi]
	}
	return nvars
}
func (nv *NetView) VarsListUpdate() {
	nvars := NetVarsList(nv.Net, true)  
	if len(nvars) == len(nv.Vars) {
		return
	}
	nv.Vars = nvars
	nv.VarParams = make(map[string]*VarParams, len(nv.Vars))
	unprops := nv.Net.UnitVarProps()
	prjnprops := nv.Net.SynVarProps()
	for _, nm := range nv.Vars {
		vp := &VarParams{Var: nm}
		vp.Defaults()
		var vtag string
		if strings.HasPrefix(nm, "r.") || strings.HasPrefix(nm, "s.") {
			vtag = prjnprops[nm[2:]]
		} else {
			vtag = unprops[nm]
		}
		if vtag != "" {
			vp.SetProps(vtag)
		}
		nv.VarParams[nm] = vp
	}
}
func (nv *NetView) VarsUpdate() {
	vl := nv.VarsLay()
	updt := vl.UpdateStart()
	for _, vbi := range *vl.Children() {
		vb := vbi.(*gi.Action)
		if vb.Text == nv.Var {
			vb.SetSelected()
		} else {
			vb.ClearSelected()
		}
	}
	tbar := nv.Toolbar()
	cmap := tbar.ChildByName("cmap", 5).(*giv.ColorMapView)
	cmap.Map = nv.ColorMap
	cmap.UpdateSig()
	vl.UpdateEnd(updt)
}
func (nv *NetView) VarScaleUpdate(varNm string) bool {
	vp := nv.VarParams[varNm]
	tbar := nv.Toolbar()
	mncb := tbar.ChildByName("mncb", 4).(*gi.CheckBox)
	mnsb := tbar.ChildByName("mnsb", 5).(*gi.SpinBox)
	mxcb := tbar.ChildByName("mxcb", 6).(*gi.CheckBox)
	mxsb := tbar.ChildByName("mxsb", 7).(*gi.SpinBox)
	zccb := tbar.ChildByName("zccb", 8).(*gi.CheckBox)
	mod := false
	updt := false
	if mncb.IsChecked() != vp.Range.FixMin {
		updt = tbar.UpdateStart()
		mod = true
		mncb.SetChecked(vp.Range.FixMin)
	}
	if mxcb.IsChecked() != vp.Range.FixMax {
		if !mod {
			updt = tbar.UpdateStart()
			mod = true
		}
		mxcb.SetChecked(vp.Range.FixMax)
	}
	mnv := float32(vp.Range.Min)
	if mnsb.Value != mnv {
		if !mod {
			updt = tbar.UpdateStart()
			mod = true
		}
		mnsb.SetValue(mnv)
	}
	mxv := float32(vp.Range.Max)
	if mxsb.Value != mxv {
		if !mod {
			updt = tbar.UpdateStart()
			mod = true
		}
		mxsb.SetValue(mxv)
	}
	if zccb.IsChecked() != vp.ZeroCtr {
		if !mod {
			updt = tbar.UpdateStart()
			mod = true
		}
		zccb.SetChecked(vp.ZeroCtr)
	}
	tbar.UpdateEnd(updt)
	return mod
}
func (nv *NetView) VarsConfig() {
	vl := nv.VarsLay()
	vl.SetReRenderAnchor()
	vl.Lay = gi.LayoutGrid
	vl.SetProp("columns", 2)
	vl.SetProp("spacing", 0)
	vl.SetProp("vertical-align", gist.AlignTop)
	nv.VarsListUpdate()
	if len(nv.Vars) == 0 {
		vl.DeleteChildren(true)
		return
	}
	config := kit.TypeAndNameList{}
	for _, vn := range nv.Vars {
		config.Add(gi.KiT_Action, vn)
	}
	mods, updt := vl.ConfigChildren(config)
	if !mods {
		updt = vl.UpdateStart()
	}
	for i, vbi := range *vl.Children() {
		vb := vbi.(*gi.Action)
		vb.SetProp("margin", 0)
		vb.SetProp("max-width", -1)
		vn := nv.Vars[i]
		vb.SetText(vn)
		if vn == nv.Var {
			vb.SetSelected()
		} else {
			vb.ClearSelected()
		}
		vb.ActionSig.Connect(nv.This(), func(recv, send ki.Ki, sig int64, data interface{}) {
			nvv := recv.Embed(KiT_NetView).(*NetView)
			vbv := send.(*gi.Action)
			nvv.SetVar(vbv.Text)
		})
	}
	vl.UpdateEnd(updt)
}