#!/bin/sh # the next line restarts using emcsh \ exec $LINUXCNC_EMCSH "$0" "$@" ############################################################### # Description: emccalib.tcl # A Tcl/Tk script that interfaces with LinuxCNC # through GUIs. It configures ini tuning variables # on the fly and saves if desired. # # Author: Ray Henry # License: GPL Version 2 # # Copyright (c) 2003-2009 All rights reserved. ############################################################### # EMC parameter setting application # Needs emcsh to run # Saves changes to ini file if requested ############################################################### # FIXME allow multiple reads of ini var. # this is a one way system. It finds ini referenced variable in hal files # and builds widgets to display and manipulate their values # it does not and will not sort on hal components. source [file join [file dirname [info script]] .. linuxcnc.tcl] eval emc_init $argv source [file join $::env(HALLIB_DIR) hal_procs_lib.tcl] package require BWidget # add a few default characteristics to the display foreach class { Button Checkbutton Entry Label Listbox Menu Menubutton \ Message Radiobutton Scale } { option add *$class.borderWidth 1 100 } set ::EC(numjoints) [emc_ini "JOINTS" "KINS"] set ::EC(numspindles) 10 ;# currently 8 spindles allowed (0..7) ;# but legacy configs may have [SPINDLE_9] ;# so allow up to 10 (0..9) #-------------------------------------------------------------- # Tuning stanzas set ::EC(stanzas) [list TUNE JOINT_ AXIS_ SPINDLE_] # Specify a stanza for tuning with: # ::EC(stanza_name,howmany) == number of allowed names # ::EC(stanza_name,suffixes) == list of allowed suffixes # # Special case: howmany==1 and items=="", no allowed suffixes # # Examples: # item==JOINT_ specifies name/value pairs like: # [JOINT_n]name=value # where n is a suffix in {0 1 ...} # # item==TUNE using howmany==1 and items=="" specifies # name/value pairs with no suffix like: # [TUNE]name=value set ::EC(TUNE,howmany) 1 set ::EC(TUNE,suffixes) "" set ::EC(JOINT_,howmany) $::EC(numjoints) for {set i 0} {$i < $::EC(JOINT_,howmany)} {incr i} { lappend ::EC(JOINT_,suffixes) $i } set ::EC(SPINDLE_,howmany) $::EC(numspindles) for {set i 0} {$i < $::EC(SPINDLE_,howmany)} {incr i} { lappend ::EC(SPINDLE_,suffixes) $i } set ::EC(AXIS_,suffixes) {X Y Z A B C U V W} set ::EC(AXIS_,howmany) [llength $::EC(AXIS_,suffixes)] #-------------------------------------------------------------- # Find the name of the ini file used for this run. set ::EC(thisinifile) "$::EMC_INIFILE" set ::EC(thisconfigdir) [file dirname $::EC(thisinifile)] #----------start toplevel---------- # # trap mouse click on window manager delete # might add a test for up to date ini before closing wm protocol . WM_DELETE_WINDOW askKill proc askKill {} { destroy . exit } wm title . [msgcat::mc "LinuxCNC Calibration"] set ::EC(main) [frame .main ] set ::EC(nbook) [NoteBook .main.top] pack $::EC(main) -side left -expand yes -fill both \ -padx 18 -pady 18 -anchor n pack $::EC(nbook) -side top -fill both -expand yes set terminate [frame $::EC(main).bot] pack $terminate -side top -fill x button $terminate.save -text [msgcat::mc "Save To File"] -command {saveIni tune} button $terminate.quit -text [msgcat::mc "Quit"] -command {saveIni quit} pack $terminate.save $terminate.quit \ -pady 4 -side left -fill both -expand yes # remove slider # slider process is used for several widgets proc sSlide {f a b} { $f.sc set $a $b } # Build menu # fixme clean up the underlines so they are unique under each set set menubar [menu $::EC(nbook).menubar -tearoff 0] #. configure -menu $menubar set filemenu [menu $menubar.file -tearoff 0] $menubar add cascade -label [msgcat::mc "File"] \ -menu $filemenu $filemenu add command -label [msgcat::mc "Refresh"] \ -command {refreshHAL} $filemenu add command -label [msgcat::mc "Save"] \ -command {saveHAL save} # Make a text widget to hold the ini file set ::EC(initext) [text $::EC(nbook).initext] set ::EC(haltext) [text $::EC(nbook).haltext] $::EC(initext) config -state normal $::EC(initext) delete 1.0 end set programin [open $::EC(thisinifile)] $::EC(initext) insert end [read $programin] close $programin # setup array with section names and line numbers set ::EC(sectionlist) {} scan [$::EC(initext) index end] %d nl set inilastline $nl for {set i 1} {$i < $nl} {incr i} { if { [$::EC(initext) get $i.0] == "\[" } { set inisectionname [$::EC(initext) get $i.1 $i.end] set tab " " set spc " " set inisectionname [string trimright $inisectionname "$spc$tab]" ] set ::EC(sectionline,$inisectionname) $i lappend ::EC(sectionlist) $inisectionname } } set ::EC(sectionline,END_OF_INI_FILE) [expr $nl-1] lappend ::EC(sectionlist) END_OF_INI_FILE # Find the HALFILE names between HAL and the next section set startline $::EC(sectionline,HAL) set endline $::EC(sectionline,[lindex $::EC(sectionlist) \ [expr [lsearch -exact $::EC(sectionlist) HAL] + 1 ]]) set ::EC(halfilelist) "" for {set i $startline} {$i < $endline} {incr i} { set thisstring [string trim [$::EC(initext) get $i.0 $i.end]] if { ([string first "HALFILE" "$thisstring"] == 0) || ([string first "POSTGUI_HALFILE" "$thisstring"] == 0) } { set arglist [lindex [split $thisstring =] 1] set thishalname [lindex $arglist 0] lappend ::EC(halfilelist) [find_file_in_hallib_path $thishalname $::EC(thisinifile)] } } proc find_ini_refs {stanza} { # find the ini references in this hal and build widgets scan [$::EC(haltext) index end] %d nl for {set i 1} { $i < $nl } {incr i} { set tmpstring [string trim [$::EC(haltext) get $i.0 $i.end]] if {[string match *${stanza}* $tmpstring] && ![string match *#* $tmpstring]} { set halcommand [split $tmpstring " \t"] if { [lindex $halcommand 0] == "setp" || [lindex $halcommand 1] == "\=" } { if { [lindex $halcommand 1] == "\=" } { set tmpstring "setp [lindex $halcommand 0] [lindex $halcommand 2]" } # tunable item is the setp value (3rd token); ignore ini refs # that only appear inside the pin name (issue #4165) set valuestring [lindex $tmpstring 2] for {set sfx 0} {$sfx < $::EC($stanza,howmany)} {incr sfx} { set tabno $::EC($stanza,$sfx,tabno) set itag [lindex $::EC($stanza,suffixes) $sfx] if {[string match *${stanza}${itag}* $valuestring]} { # this is a hal file search ordered loop set thisininame [lindex [split $valuestring "\]" ] end ] set thisininame [string map {( "" ) ""} $thisininame] set lowername "[string tolower $thisininame]" set thishalcommand [lindex $tmpstring 1] set tmpval [string trim [hal getp [halcmdSubstitute $thishalcommand]]] set ::EC(value,$tabno,$lowername) $tmpval set ::EC(value,$tabno,$lowername,next) $tmpval if { [catch { set thisname [label $::EC(tab,$tabno).label-$itag-$lowername \ -text "$thisininame: " -width 20 -anchor e] } msg ] } { #puts msg=$msg } else { set thisval [label $::EC(tab,$tabno).entry-$lowername -width 8 \ -textvariable ::EC(value,$tabno,$lowername) -anchor e -foreg firebrick4 ] set thisentry [entry $::EC(tab,$tabno).next-$lowername -width 8 \ -width 12 -textvariable ::EC(value,$tabno,$lowername,next) ] grid $thisname $thisval x $thisentry } lappend ::EC(ininamearray,$tabno) $lowername lappend ::EC(commandarray,$tabno) $thishalcommand break } } } } } } ;# find_ini_refs proc makeIniTune {} { set tabno 0 foreach stanza $::EC(stanzas) { for {set sfx 0} {$sfx < $::EC($stanza,howmany)} {incr sfx} { set ::EC($stanza,$sfx,tabno) $tabno set ::EC($tabno,stanza) $stanza ;# cross reference tabno-->stanza set itag [lindex $::EC($stanza,suffixes) $sfx] if {($::EC($stanza,howmany) == 1) && ($::EC($stanza,suffixes)=="")} { set tablabel ${stanza} } else { set tablabel ${stanza}$itag } set ::EC(tab,$tabno) [$::EC(nbook) insert [expr $tabno+3] page$tabno \ -text $tablabel -raisecmd "selectTab $tabno" ] set col0 [label $::EC(tab,$tabno).collabel0 -text [msgcat::mc "INI Name"]] set col1 [label $::EC(tab,$tabno).collabel1 -text [msgcat::mc "HAL's Value"]] set col2 [label $::EC(tab,$tabno).space -text " "] set col3 [label $::EC(tab,$tabno).collabel3 -text [msgcat::mc "Next Value"]] grid $col0 $col1 $col2 $col3 -ipady 5 grid rowconfigure $::EC(tab,$tabno) 9999 -weight 1 incr tabno } } foreach fname $::EC(halfilelist) { $::EC(haltext) config -state normal $::EC(haltext) delete 1.0 end if {[catch {open $fname} programin]} { continue } else { $::EC(haltext) insert end [read $programin] catch {close $programin} } foreach stanza $::EC(stanzas) { find_ini_refs $stanza } } if {[array names ::EC ininamearray,*] == ""} { incompatible_ini_file return } # build the buttons to control variables. foreach stanza $::EC(stanzas) { for {set sfx 0} {$sfx < $::EC($stanza,howmany)} {incr sfx} { set itag [lindex $::EC($stanza,suffixes) $sfx] set tabno $::EC($stanza,$sfx,tabno) if ![info exists ::EC(startpagenum)] { set ::EC(startpagenum) $tabno } if ![info exists ::EC(ininamearray,$tabno)] { $::EC(nbook) delete page$tabno if {$tabno == $::EC(startpagenum)} { unset ::EC(startpagenum) } continue } set bframe [frame $::EC(tab,$tabno).buttons] grid configure $bframe -columnspan 4 -sticky nsew set tmptest [button $bframe.test -text [msgcat::mc "Test"] \ -command {iniTuneButtonpress test}] set tmpok [button $bframe.ok -text [msgcat::mc "OK"] \ -command {iniTuneButtonpress ok} -state disabled ] set tmpcancel [button $bframe.cancel -text [msgcat::mc "Cancel"] \ -command {iniTuneButtonpress cancel} -state disabled ] set tmprefresh [button $bframe.refresh -text [msgcat::mc "Refresh"] \ -command {iniTuneButtonpress refresh}] pack $tmpok $tmptest $tmpcancel $tmprefresh -side left -fill both -expand yes -pady 5 } } } ;# makeIniTune proc incompatible_ini_file {} { set progname [file tail $::argv0] set fname [file tail $::EMC_INIFILE] set answer [tk_messageBox \ -title "Ini file not compatible with $progname" \ -message [msgcat::mc "\ No Tuneable items in ini file:\n\ $fname\n\n\ Sections supported are:\n\ \[JOINT_N\]name=value\n\ \[AXIS_L\]name=value\n\ \[SPINDLE_S\]name=value\n\ \[TUNE\]name=value\n\n\ N is a joint number\n\ L is an axis letter\n\ S is a spindle number\n\n\ A Halfile must include a setp\n\ for a supported section item\n\n\ Inifile example:\n\ \[JOINT_0\]\n\ PGAIN=1\n\ ...\n\ \[TUNE\]\n\ SPEED=100\n\ ...\n\n\ Halfile example:\n\ setp pid.0.Pgain \[JOINT_0\]PGAIN\n\ setp mux2.0.in1 \[TUNE\]SPEED\n\ ...\n\n\ \n\ " ] \ -type ok \ -icon info] exit 1 } ;# incompatible_ini_file proc selectTab {which} { set ::curtab $which ;# current tab number } proc iniTuneButtonpress {which} { set labelname ".main.top.fpage$::curtab.next" set basename ".main.top.fpage$::curtab.buttons" # which can be (test, ok, cancel) switch -- $which { test { $basename.test configure -state disabled $basename.ok configure -state normal $basename.cancel configure -state normal set entrystate "disabled" } cancel { $basename.test configure -state normal $basename.ok configure -state disabled $basename.cancel configure -state disabled set entrystate "normal" } ok { $basename.test configure -state normal $basename.ok configure -state disabled $basename.cancel configure -state disabled set entrystate "normal" } refresh { $basename.test configure -state normal $basename.ok configure -state disabled $basename.cancel configure -state disabled set entrystate "normal" } default { } } foreach name $::EC(ininamearray,$::curtab) { $labelname-$name configure -state $entrystate } changeIni $which $::EC($::curtab,stanza) } ;# iniTuneButtonpress proc changeIni {how stanza} { switch -- $how { cancel {-} test { set varnames $::EC(ininamearray,$::curtab) set varcommands $::EC(commandarray,$::curtab) set maxvarnum [llength $varnames] set swapvars $varnames # handle each variable and values inside loop for {set listnum 0} {$listnum < $maxvarnum} {incr listnum} { set vname [lindex $varnames $listnum] set var ::EC(value,$::curtab,$vname) set varnext ::EC(value,$::curtab,$vname,next) # remove this item from the "to be swapped" list set thisvar [lindex $swapvars 0] set swapvars [lrange $swapvars 1 end] # get the values set oldval [set $var] set newval [set $varnext] # get the parameter name string set tmpcmd [lindex $varcommands $listnum] # set new parameter value in hal set thisret [hal setp [halcmdSubstitute $tmpcmd] $newval] # see if we need to swap the new and old control values if {[lsearch -exact $swapvars $thisvar] < 0} { # set the current value for display set $var $newval # set the tmp value as next in display set $varnext $oldval } } } ok { set varnames $::EC(ininamearray,$::curtab) set varcommands $::EC(commandarray,$::curtab) set maxvarnum [llength $varnames] # handle each variable and values inside loop for {set listnum 0} {$listnum < $maxvarnum} {incr listnum} { set vname [lindex $varnames $listnum] set var ::EC(value,$::curtab,$vname) set varnext ::EC(value,$::curtab,$vname,next) # get the values set oldval [set $var] # set the tmp value as next in display set $varnext $oldval } } refresh { set varnames $::EC(ininamearray,$::curtab) set varcommands $::EC(commandarray,$::curtab) set maxvarnum [llength $varnames] # handle each variable and values inside loop for {set listnum 0} {$listnum < $maxvarnum} {incr listnum} { set vname [lindex $varnames $listnum] set var ::EC(value,$::curtab,$vname) set varnext ::EC(value,$::curtab,$vname,next) # get the parameter name string set tmpcmd [lindex $varcommands $listnum] # get parameter value from hal set val [string trim [hal getp [halcmdSubstitute $tmpcmd]]] set $var $val set $varnext $val } } quit { puts stderr "Unexpected changeIni quit" # was never called, removed deadcode here } default {} } } ;# changeIni proc saveIni {which} { if {![file writable $::EC(thisinifile)]} { tk_messageBox -type ok -message [msgcat::mc "Not permitted to save here.\n\n \ You need to copy a configuration to your home directory and work there."] } switch -- $which { tune { update_initext $::EC($::curtab,stanza) saveFile $::EC(thisinifile) $::EC(initext) } quit {destroy .; exit} } } ;# saveIni proc update_initext {stanza} { set anames [array names ::EC ininamearray,*] foreach stanza $::EC(stanzas) { for {set sfx 0} {$sfx < $::EC($stanza,howmany)} {incr sfx} { set itag [lindex $::EC($stanza,suffixes) $sfx] set tabno $::EC($stanza,$sfx,tabno) if {[lsearch $anames ininamearray,$tabno] < 0} continue set varnames $::EC(ininamearray,$tabno) set upvarnames [string toupper $varnames] set varcommands $::EC(commandarray,$tabno) set maxvarnum [llength $varnames] set sectname "${stanza}$itag" # get the name of the next ini section set endsect [lindex $::EC(sectionlist) \ [expr [lsearch -exact $::EC(sectionlist) $sectname] + 1 ]] set sectnum "[set ::EC(sectionline,$sectname)]" set nextsectnum "[set ::EC(sectionline,$endsect)]" $::EC(initext) configure -state normal for {set ind $sectnum} {$ind < $nextsectnum} {incr ind} { switch -- [$::EC(initext) get $ind.0] { "#" {} default { set tmpstr [$::EC(initext) get $ind.0 $ind.end] set tmpvar [lindex [split $tmpstr "="] 0] set tmpvar [string toupper [string trim $tmpvar]] set tmpindx [lsearch $upvarnames $tmpvar] if {$tmpindx != -1} { set cmd [lindex $varcommands $tmpindx] $::EC(initext) mark set insert $ind.0 set newval [string trim [hal getp [halcmdSubstitute $cmd]]] set tmptest [string first "e" $newval] if {[string first "e" $newval] > 1} { set newval [format %f $newval] } regsub {(^.*=[ \t]*)[^ \t]*(.*)} $tmpstr "\\1$newval\\2" newvar $::EC(initext) delete insert "insert lineend" $::EC(initext) insert insert $newvar } } } } $::EC(initext) configure -state disabled } } } ;# update_initext proc halcmdSubstitute {s} { set pat {\[([^]]+)\](\([^)]+\)|[^() \r\n\t]+)} set pos 0 set result {} while {$pos < [string length $s] && [regexp -start $pos -indices $pat $s indx]} { set start [lindex $indx 0] set end [lindex $indx 1] append result [string range $s $pos [expr $start-1]] set query [string range $s $start $end] regexp $pat $query - section var set var [regsub {\((.*)\)} $var {\1}] append result [emc_ini $var $section] set pos [expr $end+1] } append result [string range $s $pos end] return $result } ;# halcmdSubstitute proc saveFile {filename contents} { catch {file copy -force $filename $filename.bak} if { [catch {open $filename w} fileout] } { puts stdout [msgcat::mc "can't save %s" $halconfFilename($name)] return } if {-1 != [string first ".ini.expanded" $filename]} { set fname [file tail $filename] set answer [tk_messageBox \ -title "Expanded File" \ -message [msgcat::mc "\ Save to: <$fname>\n\n\ Expanded file for \#INCLUDEs\n\n\ \#INCLUDE files (*.inc)\n\ must be edited separately\n\ \n\ " ] \ -type ok \ -icon info] } switch -- [string index $contents 0] { "/" { # a filename puts "I got a filename" } "." { # "." means this is a widget name so get contents set lines [$contents count -lines 1.0 end] # was: puts $fileout [$contents get 1.0 end] # avoid writing empty lines at end of file: incr lines -1 set last ${lines}.end puts $fileout [$contents get 1.0 $last] catch {close $fileout} } default { # assumes that contents is plain text so stuff it away puts $fileout $contents catch {close $fileout} } } } ;# saveFile makeIniTune $::EC(nbook) raise page$::EC(startpagenum)