Files
linuxcncAllWasm/tcl/bin/emccalib.tcl
2026-08-20 06:17:39 +08:00

569 lines
21 KiB
Tcl
Executable File

#!/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)