# Netlist Formatting Utility (NFU), a LWDAQ Package
#
# Copyright (C) 2005-2021 Kevan Hashemi, Brandeis University
# Copyright (C) 2022-2024 Kevan Hashemi, Open Source Instruments Inc.
#
# Formats netlists produced by Traxmaker or Kicad, producing a human-readable
# list against which we can check a hand-drawn schematic. The NFU package began
# as a non-standard LWDAQ Tool called Format Netlist. Now it provides a format
# command that accepts a file name. If we provide an empty string for the file
# name, the routine will open a file browser. The routine reads the file,
# detects its format, constructs a netlist, and writes the netlist to disk. The
# output file will be named after the folder enclosing the netlist, with suffix
# _NFU and extension ".txt". The output netlist file contains some information
# at the top, before the netlist begins.
#
# This program is free software: you can redistribute it and/or modify it under
# the terms of the GNU General Public License as published by the Free Software
# Foundation, either version 3 of the License, or (at your option) any later
# version.
#
# This program is distributed in the hope that it will be useful, but WITHOUT
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
# FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
# details.
#
# You should have received a copy of the GNU General Public License along with
# this program. If not, see <https://www.gnu.org/licenses/>.

# Version 3.1: Formats KiCad schematic netlists, KiCad PCB netlists, and 
# Traxmaker netlists automatically. For Traxmaker, generates modern drill
# file from obsolete format drill and tool files.

# Version 3.2: When formatting KiCad PCB netlist, omit the "N/C" net.

# Load this package or routines into LWDAQ with "package require EDF".
package provide NFU 3.2

#
# NFU_format reads a netlist file, identifies its type, re-formats it, and
# writes a formatted version to disk. In the case of a Traxmaker netlist, the
# routine also looks for a drill file that it can translate into a more modern
# format by combining with a tool file. The routine returns some information
# about what it did rather than the netlist contents.
#
proc NFU_format {{fn ""}} {
	
	# Get a valid netlist file name.
	if {$fn == ""} {set fn [LWDAQ_get_file_name]}
	if {$fn == ""} {return "none 0 none"}
	if {![file exists $fn]} {error "cannot find \"$fn\""}
	
	# Read the netlist, trim leading and trailing white space.
	set f [open $fn r]
	set netlist [string trim [read $f]]
	close $f

	# Detect the type. A KiCad schematic netlist contains the string "(export".
	if {[regexp {\(export} $netlist]} {
		set netlist_type "kicadsch"
	
	# A KiCad pcb netlist contains the string "P  CODE 00" at the start.
	} elseif {[regexp {^P  CODE 00} $netlist]} {
		set netlist_type "kicadpcb"

	# Otherwise we assume the netlist has been generated by Traxmaker.
	} else {
		set netlist_type "traxmaker"
	}
	
	# Reformat KiCad Schematic netlist. We remove the component library, find
	# the netlist, eliminate verbiage and try to put each net on one line.
	if {$netlist_type == "kicadsh"} {
		set found [regexp {nets(.*)} $netlist match nets]
		if {!$found} {
			error "File contains \"export\" keyword, but not \"(nets\"."
		}
		set nets [split [string trim $nets] \n]
		set netlist ""
		set net ""
		set nodes "0"
		foreach line $nets {
			if {[regexp {[ ]*\(net.*?\(name ([^\n]*)} $line match name]} {
				if {$nodes > 1} {append netlist "$net \n"}
				set name [regsub -all {\)|/|.+?\(|"} $name ""]
				set net "NET: \"$name\" "
				set nodes "0"
			} elseif {[regexp {[ ]+\(node.*?\(ref ([^\)]*)\).+?\(pin ([^\)]*)\)} \
					$line match part pin]} {
				append net "$part-$pin "
				incr nodes
			}
		}
		if {$nodes > 1} {append netlist $net}
	} 
	
	# The KiCad PCB netlist has a line for every pad or via that connects to each net. We
	# want to ignore the lines for vias and other no-connect pads, so we look for lines that
	# contain four elements separated by spaces.
	if {$netlist_type == "kicadpcb"} {
		set nodes [list]
		foreach node [lrange [split $netlist \n] 3 end] {
			if {[llength $node] == 4} {
				set node [regsub {^317} $node ""]
				set node [regsub {^327} $node ""]
				set node [regsub {^/} $node ""]
				set node [regsub {^NET-} $node ""]
				lappend nodes $node
			}
		}
		set nodes [lsort -increasing $nodes]
		set netlist ""
		set netname ""
		foreach node $nodes {
			set first [lindex $node 0]
			if {$first == "N/C"} {continue}
			if {$first != $netname} {
				set netname $first
				append netlist "\n$netname\: [lindex $node 1][lindex $node 2] "
			} else {
				append netlist "[lindex $node 1][lindex $node 2] "
			}
		}
		set netlist [string trim $netlist]
	}
	
	# For Traxmaker netlists, we replace all carriage returns with spaces, then
	# insert carriage returns before parentheses and brackets. We also try to
	# find the tool list and drill file, which we are going to read, interpret,
	# and use to make a modern TXT drill file.
	if {$netlist_type == "traxmaker"} {
	
		set netlist [regsub -all {\n} $netlist " "]
		set netlist [regsub -all {\[} $netlist "\n\["]
		set netlist [regsub -all {\(} $netlist "\n\("]

		set tfn [file root $fn].TOL
		set dfn [file root $fn].TXT
		if {[file exists $tfn] && [file exists $tfn]} {
			set f [open $dfn r]
			set drill [read $f]
			close $f
			set drill [regsub {M48.*?%} $drill ""]
			set f [open $tfn r]
			set tool [read $f]
			close $f
			set tool [regsub {\-.*-\n} $tool ""]
			set newdrill "M48\nINCH\n"
			foreach {tn dia} $tool {
				append newdrill "[set tn]C00.[format %03d $dia]\n"
			}
			append newdrill "%\n"
			append newdrill [string trim $drill]
			set f [open $dfn w]
			puts $f $newdrill
			close $f
		} 
	}

	# Write the new netlist to disk with an extension marking it as the product
	# of our routine. If a previous version of the file exists, overwrite it.
	set ofn [file root $fn]_NFU.txt
	set f [open $ofn w]
	puts $f "INFILE: $fn"
	puts $f "OUTFILE: $ofn"
	puts $f "FORMAT: $netlist_type"
	puts $f "LENGTH: [llength [split $netlist \n]]"
	puts $f $netlist
	close $f
	
	# Return the netlist type and the number of nets.
	return "[file tail $fn] [file tail $ofn] $netlist_type [llength [split $netlist \n]]"
}