diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000..0b5e10f --- /dev/null +++ b/.gitattributes @@ -0,0 +1,29 @@ +# +# see https://help.github.com/articles/dealing-with-line-endings/ +# + +# Set the default behavior, in case people don't have core.autocrlf set. +* text=auto + +# Explicitly declare text files you want to always be normalized and converted +# to Unix line endings on checkout. +*.xml text eol=lf +*.java text eol=lf +*.bat text eol=lf +*.sh text eol=lf +*.iml text eol=lf +*.txt text eol=lf +*.ini text eol=lf +*.rules text eol=lf + +# KiCad files +*.dsn text eol=lf +*.kicad_pcb text eol=lf +*.net text eol=lf +*.pro text eol=lf +*.sch text eol=lf + +# Denote all files that are truly binary and should not be modified. +*.png binary +*.jpg binary + diff --git a/src/main/java/com/rusefi/pcb/ChangesModel.java b/src/main/java/com/rusefi/pcb/ChangesModel.java new file mode 100644 index 0000000..c6f12fc --- /dev/null +++ b/src/main/java/com/rusefi/pcb/ChangesModel.java @@ -0,0 +1,105 @@ +package com.rusefi.pcb; + +import com.rusefi.util.FileUtils; + +import java.io.File; +import java.io.IOException; +import java.util.*; + +import static com.rusefi.pcb.PcbMergeTool.log; + +/** + * 1/19/14 + * (c) Andrey Belomutskiy + */ +public class ChangesModel { + private static final ChangesModel instance = new ChangesModel(); + private static final String REMOVE = "remove"; + private static final String ADD = "add"; + private static final String MOVE = "move_module"; + private static final String OPTIMIZE = "optimize"; + private static final String COPY = "copy"; + private static final String MERGE_NET = "merge_net"; + + public final Set DEL_REQUESTS = new TreeSet(String.CASE_INSENSITIVE_ORDER); + public final List ADD_REQUESTS = new ArrayList(); + public final List MOVE_REQUESTS = new ArrayList(); + + public final List OPTIMIZE_REQUESTS = new ArrayList(); + public final List COPY_REQUESTS = new ArrayList(); + /** + * Old net name > New net name + */ + public final Map NET_MERGE_REQUESTS = new TreeMap(String.CASE_INSENSITIVE_ORDER); + + public static ChangesModel getInstance() { + return instance; + } + + public static void readConfiguration(String fileName) throws IOException { + if (!new File(fileName).isFile()) { + log("No such file: " + fileName); + return; + } + log("Reading commands from " + fileName); + List a = FileUtils.readFileToList(fileName); + + getInstance().read(a); + } + + public void read(List lines) { + int lineIndex = 0; + for (String line : lines) { + lineIndex++; + line = line.trim(); + if (line.isEmpty()) + continue; + if (line.startsWith("#")) + continue; // this line is a comment + + if (line.toLowerCase().startsWith(REMOVE)) { + DEL_REQUESTS.add(line.substring(REMOVE.length()).trim()); + continue; + } else if (line.toLowerCase().startsWith(ADD)) { + addAddRequest(line.substring(ADD.length()).trim()); + continue; + } else if (line.toLowerCase().startsWith(MOVE)) { + addMoveRequest(line.substring(MOVE.length()).trim()); + continue; + } else if (line.toLowerCase().startsWith(OPTIMIZE)) { + OPTIMIZE_REQUESTS.add(TwoFileRequest.parseTwoFile(line.substring(OPTIMIZE.length()).trim(), lineIndex)); + continue; + } else if (line.toLowerCase().startsWith(COPY)) { + COPY_REQUESTS.add(TwoFileRequest.parseTwoFile(line.substring(COPY.length()).trim(), lineIndex)); + continue; + } else if (line.toLowerCase().startsWith(MERGE_NET)) { + TwoFileRequest req = TwoFileRequest.parseTwoFile(line.substring(MERGE_NET.length()).trim(), lineIndex); + NET_MERGE_REQUESTS.put(req.input, req.output); + log("Net " + req.input + " to be merged into " + req.output); + continue; + } + + System.err.println("ChangesModel: Ignoring invalid line: " + line); + + } + log("Got " + DEL_REQUESTS.size() + " remove request(s)"); + log("Got " + ADD_REQUESTS.size() + " add request(s)"); + log("Got " + OPTIMIZE_REQUESTS.size() + " optimize request(s)"); + log("Got " + NET_MERGE_REQUESTS.size() + " merge net request(s)"); + } + + private void addMoveRequest(String request) { + MOVE_REQUESTS.add(NameAndOffset.parseNameAndOffset(request)); + } + + private void addAddRequest(String request) { + ADD_REQUESTS.add(NameAndOffset.parseNameAndOffset(request)); + } + + public void clear() { + DEL_REQUESTS.clear(); + ADD_REQUESTS.clear(); + OPTIMIZE_REQUESTS.clear(); + MOVE_REQUESTS.clear(); + } +} diff --git a/src/main/java/com/rusefi/pcb/NameAndOffset.java b/src/main/java/com/rusefi/pcb/NameAndOffset.java new file mode 100644 index 0000000..d99d279 --- /dev/null +++ b/src/main/java/com/rusefi/pcb/NameAndOffset.java @@ -0,0 +1,36 @@ +package com.rusefi.pcb; + +/** + * @author Andrey Belomutskiy + * 1/24/14 + */ +public class NameAndOffset { + private final String name; + public final double x; + public final double y; + + public NameAndOffset(String name, double x, double y) { + this.name = name; + this.x = x; + this.y = y; + } + + public static NameAndOffset parseNameAndOffset(String request) { + String[] tokens = request.split(" "); + NameAndOffset result; + if (tokens.length == 1) { + result = new NameAndOffset(tokens[0], 0, 0); + } else if (tokens.length == 3) { + double x = Double.parseDouble(tokens[1]); + double y = Double.parseDouble(tokens[2]); + result = new NameAndOffset(tokens[0], x, y); + } else { + throw new IllegalArgumentException("Invalid: " + request); + } + return result; + } + + public String getName() { + return name; + } +} diff --git a/src/main/java/com/rusefi/pcb/PcbCopyTool.java b/src/main/java/com/rusefi/pcb/PcbCopyTool.java new file mode 100644 index 0000000..5657b27 --- /dev/null +++ b/src/main/java/com/rusefi/pcb/PcbCopyTool.java @@ -0,0 +1,26 @@ +package com.rusefi.pcb; + +import com.rusefi.pcb.nodes.PcbNode; + +import java.io.IOException; + +/** + * @author Andrey Belomutskiy + * 1/24/14 + */ +public class PcbCopyTool { + public static void main(String[] args) throws IOException { + if (args.length != 2) { + System.out.println("Two parameters expected: SOURCE DESTINATION"); + return; + } + + ChangesModel.readConfiguration("pcb_merge_changes.txt"); + + String input = args[0]; + String output = args[1]; + + PcbNode.copy(input, output); + } + +} diff --git a/src/main/java/com/rusefi/pcb/PcbMergeTool.java b/src/main/java/com/rusefi/pcb/PcbMergeTool.java new file mode 100644 index 0000000..b5a04a8 --- /dev/null +++ b/src/main/java/com/rusefi/pcb/PcbMergeTool.java @@ -0,0 +1,256 @@ +package com.rusefi.pcb; + +import com.rusefi.pcb.nodes.*; + +import java.io.IOException; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +/** + * (c) Andrey Belomutskiy + * 12/16/13. + */ +public class PcbMergeTool { + private static Networks networks = new Networks(); + + public static void main(String[] args) throws IOException { + if (args.length != 3) { + System.out.println("Three parameters expected: SOURCE_PCB_FILENAME DESTINATION_PCB_FILENAME CHANGES_LIST_FILENAME"); + return; + } + String sourcePcb = args[0]; + String destination = args[1]; + String changes = args[2]; + + ChangesModel.readConfiguration(changes); + + log("Running COPY commands"); + for (TwoFileRequest or : ChangesModel.getInstance().COPY_REQUESTS) + PcbNode.copy(or.input, or.output); + + log("Running OPTIMIZE commands"); + for (TwoFileRequest or : ChangesModel.getInstance().OPTIMIZE_REQUESTS) + RemoveUnneededTraces.optimize(or.input, or.output); + + PcbNode destNode = PcbNode.readFromFile(sourcePcb); + + + for (PcbNode net : destNode.iterate("net")) { + String netName = net.getChild(1); // todo: nicer method? + if (!Networks.isLocalNetwork(netName)) + networks.registerNetworkIfPcbSpecific(netName); + } + + log("Running ADD commands"); + for (NameAndOffset addRequest : ChangesModel.getInstance().ADD_REQUESTS) { + PcbNode node = PcbMoveTool.readAndMove(addRequest.getName(), addRequest.x, addRequest.y); + + mergePcb(destNode, node); + } + + log("Running MOVE commands"); + for (NameAndOffset moveRequest : ChangesModel.getInstance().MOVE_REQUESTS) { + String moduleName = moveRequest.getName(); + ModuleNode module = findModuleByName(destNode, moduleName); + if (module == null) { + log("Module not found: " + moduleName); + continue; + } + + PointNode at = module.at; + at.setLocation(at.x + moveRequest.x, at.y + moveRequest.y); + } + + removeNodes(destNode); + + destNode.write(destination); + + RemoveUnneededTraces.optimize(destination, destination); + } + + private static ModuleNode findModuleByName(PcbNode destNode, String moduleName) { + for (PcbNode node : destNode.iterate("module")) { + ModuleNode mn = (ModuleNode) node; + if (moduleName.toLowerCase().equals(mn.getReference().toLowerCase())) + return mn; + } + return null; + } + + private static void mergePcb(PcbNode destNode, PcbNode source) throws IOException { + /** + * original local net name > new net name in combined PCB + */ + Map netNameMapping = new HashMap<>(); + /** + * original local net ID (as string) > new net ID + */ + Map netIdMapping = new HashMap<>(); + + for (PcbNode net : source.iterate("net")) { + String netId = net.getChild(0); + String netName = net.getChild(1); // todo: nicer method? + String newName = networks.registerNetworkIfPcbSpecific(netName); + netNameMapping.put(netName, newName); + netIdMapping.put(netId, networks.getId(newName)); + } + + List zones = source.iterate("zone"); + log("Processing " + zones.size() + " zone(s)"); + for (PcbNode z : zones) { + ZoneNode zone = (ZoneNode) z; + if (zone.getLayerNode().isSilkcreenLayer()) + destNode.addChild(zone); + } + + List arcs = source.iterate("gr_arc"); + log("Processing " + arcs.size() + " arc(s)"); + for (PcbNode arc : arcs) + destNode.addChild(arc); + + + List lines = source.iterate("gr_line"); + log("Processing " + lines.size() + " line(s)"); + for (PcbNode l : lines) { + GrLineNode line = (GrLineNode) l; + if (line.layerNode.isSilkcreenLayer()) + destNode.addChild(line); + } + + + List labels = source.iterate("gr_text"); + log("Processing " + labels.size() + " label(s)"); + for (PcbNode label : labels) { + destNode.addChild(label); + } + + List modules = source.iterate("module"); + log("Processing " + modules.size() + " module(s)"); + for (PcbNode module : modules) { + for (PcbNode pad : module.iterate("pad")) { + if (!pad.hasChild("net")) + continue; + fixNetId(netIdMapping, netNameMapping, pad); +// PcbNode net = pad.find("net"); +// String localName = netNameMapping.get(net.getChild(1)); +// net.setString(1, localName); +// net.setInt(0, networks.getId(localName)); + } + destNode.addChild(module); + } + + List segments = source.iterate("segment"); + log("Processing " + segments.size() + " segments"); + for (PcbNode segment : segments) { +// if (!segment.hasChild("net")) +// continue; + fixNetId(netIdMapping, netNameMapping, segment); + + destNode.addChild(segment); + } + + List vias = source.iterate("via"); + log("Processing " + vias.size() + " vias"); + for (PcbNode via : vias) { + fixNetId(netIdMapping, netNameMapping, via); + + destNode.addChild(via); + } + +// for (PcbNode zone : source.iterate("zone")) { +// fixNetId(netIdMapping, zone); +// destNode.addChild(zone); +// } + } + + public static void removeNodes(PcbNode source) { + for (PcbNode module : source.iterate("module")) { + if (shouldRemove((ModuleNode) module)) + source.removeChild(module); + } + } + + private static boolean shouldRemove(ModuleNode module) { + for (PcbNode fp_text : module.iterate("fp_text")) { + if ("reference".equals(fp_text.getChild(0))) { + String name = fp_text.getChild(1); + if (ChangesModel.getInstance().DEL_REQUESTS.contains(name)) + return true; + } + } + return false; + } + + private static void fixNetId(Map netIdMapping, Map netNameMapping, PcbNode node) { + NetNode net = (NetNode) node.find("net"); + String originalId = net.id; + Integer currentNetId = netIdMapping.get(originalId); + String globalName = networks.nameById.get(currentNetId); +// String newName = netNameMapping.get(originalName); +// if (newName == null) +// throw new NullPointerException("?"); + + if (ChangesModel.getInstance().NET_MERGE_REQUESTS.containsKey(globalName)) { + String newName = ChangesModel.getInstance().NET_MERGE_REQUESTS.get(globalName); + log("Will merge " + globalName + " into " + newName + ". ID was " + currentNetId); + currentNetId = networks.networks.get(newName); + if (currentNetId == null) + throw new NullPointerException("Cannot find net: " + newName); + log("New ID: " + currentNetId); + globalName = newName; + } + net.setInt(0, currentNetId); + if (net.getName() != null) + net.setString(1, globalName); + } + + private static class Networks { + /** + * Net name > Net Id + */ + private Map networks = new HashMap<>(); + private Map nameById = new HashMap<>(); + + public String registerNetworkIfPcbSpecific(String name) { + if (isLocalNetwork(name)) { + String newName = "F-0000" + networks.size(); + log("Board-specific net: " + name + " would be " + newName); + + registerNet(newName); + int newId = networks.get(newName); + log(newName + " is " + newId); + return newName; + } else { + if (networks.containsKey(name)) { + log("Existing global net: " + name); + return name; + } + + log("New global net: " + name); + registerNet(name); + return name; + } + } + + private static boolean isLocalNetwork(String name) { + return name.startsWith("N-00"); + } + + private void registerNet(String name) { + networks.put(name, networks.size()); + nameById.put(networks.get(name), name); + } + + public int getId(String localName) { + Integer value = networks.get(localName); + if (value == null) + throw new NullPointerException("No id for " + localName); + return value; + } + } + + public static void log(String s) { + System.out.println(s); + } +} diff --git a/src/main/java/com/rusefi/pcb/PcbMoveTool.java b/src/main/java/com/rusefi/pcb/PcbMoveTool.java new file mode 100644 index 0000000..6779052 --- /dev/null +++ b/src/main/java/com/rusefi/pcb/PcbMoveTool.java @@ -0,0 +1,139 @@ +package com.rusefi.pcb; + +import com.rusefi.pcb.nodes.PcbNode; + +import java.io.IOException; +import java.util.List; + +/** + * (c) Andrey Belomutskiy + * 12/8/13 + */ +public class PcbMoveTool { + public static void main(String[] args) throws IOException { + if (args.length != 4) { + System.out.println("Four parameters expected: SRC_FILENAME DST_FILENAME X Y"); + return; + } + String srcFileName = args[0]; + String dstFileName = args[1]; + double x = Double.parseDouble(args[2]); + double y = Double.parseDouble(args[3]); + + PcbNode node = readAndMove(srcFileName, x, y); + node.write(dstFileName); + } + + public static PcbNode readAndMove(String fileName, double x, double y) throws IOException { + PcbNode node = PcbNode.readFromFile(fileName); + + move(node, x, y); + return node; + } + + public static void move(PcbNode pcbNode, double dx, double dy) { + System.out.println("Moving " + pcbNode + ": dx=" +dx + " dy=" + dy); + + List dimensions = pcbNode.iterate("dimension"); + System.out.println("Moving " + dimensions.size() + " dimension"); + for (PcbNode dimension : dimensions) { + moveAt(dx, dy, dimension.find("gr_text")); + movePts(dx, dy, dimension.find("feature1")); + movePts(dx, dy, dimension.find("feature2")); + movePts(dx, dy, dimension.find("crossbar")); + movePts(dx, dy, dimension.find("arrow1a")); + movePts(dx, dy, dimension.find("arrow1b")); + movePts(dx, dy, dimension.find("arrow2a")); + movePts(dx, dy, dimension.find("arrow2b")); + } + + List gr_lines = pcbNode.iterate("gr_line"); + System.out.println("Moving " + gr_lines.size() + " gr_lines"); + for (PcbNode gr_line : gr_lines) + moveStartEnd(dx, dy, gr_line); + + List gr_arcs = pcbNode.iterate("gr_arc"); + System.out.println("Moving " + gr_arcs.size() + " gr_arcs"); + for (PcbNode gr_arc : gr_arcs) { + PcbNode start = gr_arc.find("start"); + moveCoordinatesInFirstChildren(dx, dy, start); + + PcbNode end = gr_arc.find("end"); + moveCoordinatesInFirstChildren(dx, dy, end); + } + + List gr_circles = pcbNode.iterate("gr_circle"); + System.out.println("Moving " + gr_circles.size() + " gr_circles"); + for (PcbNode gr_circle : gr_circles) { + PcbNode start = gr_circle.find("center"); + moveCoordinatesInFirstChildren(dx, dy, start); + + PcbNode end = gr_circle.find("end"); + moveCoordinatesInFirstChildren(dx, dy, end); + } + + List gr_texts = pcbNode.iterate("gr_text"); + System.out.println("Moving " + gr_texts.size() + " gr_texts"); + for (PcbNode gr_text : gr_texts) + moveAt(dx, dy, gr_text); + + List zones = pcbNode.iterate("zone"); + System.out.println("Moving " + zones.size() + " zones"); + for (PcbNode zone : zones) { + List filledPolygons = zone.iterate("filled_polygon"); + for (PcbNode filledPolygon : filledPolygons) + movePts(dx, dy, filledPolygon); + List polygons = zone.iterate("polygon"); + for (PcbNode polygon : polygons) + movePts(dx, dy, polygon); + } + + + List segments = pcbNode.iterate("segment"); + System.out.println("Moving " + segments.size() + " segments"); + for (PcbNode segment : segments) + moveStartEnd(dx, dy, segment); + + List vias = pcbNode.iterate("via"); + System.out.println("Moving " + vias.size() + " vias"); + for (PcbNode via : vias) + moveAt(dx, dy, via); + + + List modules = pcbNode.iterate("module"); + System.out.println("Moving " + modules.size() + " modules"); + for (PcbNode module : modules) + moveAt(dx, dy, module); + } + + public static void movePts(double dx, double dy, PcbNode polygon) { + PcbNode pts = polygon.find("pts"); + + for (PcbNode point : pts.nodes()) + moveCoordinatesInFirstChildren(dx, dy, point); + } + + public static void moveStartEnd(double dx, double dy, PcbNode segment) { + PcbNode start = segment.find("start"); + moveCoordinatesInFirstChildren(dx, dy, start); + + PcbNode end = segment.find("end"); + moveCoordinatesInFirstChildren(dx, dy, end); + } + + public static void moveAt(double dx, double dy, PcbNode module) { + PcbNode at = module.find("at"); + moveCoordinatesInFirstChildren(dx, dy, at); + } + + public static void moveCoordinatesInFirstChildren(double dx, double dy, PcbNode at) { + moveCoordinates(dx, dy, at, 0); + } + + private static void moveCoordinates(double dx, double dy, PcbNode at, int index) { + double x = at.asDouble(index); + double y = at.asDouble(index + 1); + at.setDouble(index, x + dx); + at.setDouble(index + 1, y + dy); + } +} diff --git a/src/main/java/com/rusefi/pcb/RemoveUnneededTraces.java b/src/main/java/com/rusefi/pcb/RemoveUnneededTraces.java new file mode 100644 index 0000000..d110d4e --- /dev/null +++ b/src/main/java/com/rusefi/pcb/RemoveUnneededTraces.java @@ -0,0 +1,138 @@ +package com.rusefi.pcb; + +import com.rusefi.pcb.nodes.*; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.HashSet; +import java.util.List; +import java.util.Set; + +/** + * @author Andrey Belomutskiy + * 1/21/14 + */ +public class RemoveUnneededTraces { + private final static Set alreadyRemoved = new HashSet(); + + public static void main(String[] args) throws IOException { + if (args.length != 2) { + System.out.println("two parameters expected: INPUT_FILE OUTPUT_FILE"); + return; + } + String input = args[0]; + String output = args[1]; + + optimize(input, output); + } + + public static void optimize(String input, String output) throws IOException { + PcbNode destNode = PcbNode.readFromFile(input); + + + while (removeUnusedSegments(destNode) || removeUnusedVias(destNode)) { + System.out.println("Still removing..."); + } + + destNode.write(output); + } + + private static boolean removeUnusedVias(PcbNode destNode) { + List unused = findUnusedVias(destNode); + for (ViaNode via : unused) { + System.out.println("Removing via: " + via); + boolean removed = destNode.removeChild(via); + if (!removed) + throw new IllegalStateException("not removed: " + removed); + + } + return !unused.isEmpty(); + } + + private static List findUnusedVias(PcbNode destNode) { + List result = new ArrayList(); + + List stuff = destNode.iterate("segment"); +// stuff.addAll(destNode.iterate("segment")); + + for (PcbNode n : destNode.iterate("via")) { + ViaNode via = (ViaNode) n; + + int count = 0; + + for (PcbNode segment : stuff) + if (segment.isConnected(via.location)) + count++; + + if (via.netId == NetNode.GND_NET_ID) { + if (count == 0) + result.add(via); + } else { + if (count < 2) + result.add(via); + } + } + return result; + } + + private static boolean removeUnusedSegments(PcbNode destNode) { + List stuff = new ArrayList(destNode.iterate("module")); + stuff.addAll(destNode.iterate("via")); + + + Object o = destNode.iterate("segment"); + List segments = (List) o; + System.out.println(segments.size() + " segment(s)"); + + List unused = findUnusedSegments(segments, stuff); + for (SegmentNode segment : unused) { + boolean removed = destNode.removeChild(segment); + if (!removed) + throw new IllegalStateException(); + String netName = segment.net.id; + if (!alreadyRemoved.contains(netName)) { + alreadyRemoved.add(netName); + System.out.println("Unused segment in network " + netName + ": " + segment); + } + } + return !unused.isEmpty(); + } + + private static List findUnusedSegments(List segments, List modules) { + List unused = new ArrayList(); + for (SegmentNode segment : segments) { + if (isUnused(segments, segment, modules)) { +// System.out.println("Unused on " + segment.net.id + ": " + segment); + unused.add(segment); + } + } + return unused; + } + + public static boolean isUnused(List segments, SegmentNode segment, List modules) { + PointNode start = segment.start; + PointNode end = segment.end; + if (isConnected(start, segments, segment) == null && isConnected(start, modules, null) == null) { + System.out.println("Not connected start: " + segment); + return true; + } + + PcbNode endModule = isConnected(end, modules, null); + if (isConnected(end, segments, segment) == null && endModule == null) { + System.out.println("Not connected end: " + segment); + return true; + } + return false; + } + + private static PcbNode isConnected(PointNode point, List elements, SegmentNode parent) { + for (PcbNode segmentNode : elements) { + if (segmentNode == parent) + continue; + + if (segmentNode.isConnected(point)) + return segmentNode; + } + return null; + } +} diff --git a/src/main/java/com/rusefi/pcb/TwoFileRequest.java b/src/main/java/com/rusefi/pcb/TwoFileRequest.java new file mode 100644 index 0000000..a87b06e --- /dev/null +++ b/src/main/java/com/rusefi/pcb/TwoFileRequest.java @@ -0,0 +1,23 @@ +package com.rusefi.pcb; + +/** + * @author Andrey Belomutskiy + * 1/24/14 + */ +public class TwoFileRequest { + public final String input; + public final String output; + + public TwoFileRequest(String input, String output) { + this.input = input; + this.output = output; + } + + static TwoFileRequest parseTwoFile(String request, int lineIndex) { + String[] tokens = request.split(" "); + if (tokens.length != 2) + throw new IllegalArgumentException("Unexpected token count in [" + request + "] @" + lineIndex); + + return new TwoFileRequest(tokens[0], tokens[1]); + } +} diff --git a/src/main/java/com/rusefi/pcb/nodes/AddNetNode.java b/src/main/java/com/rusefi/pcb/nodes/AddNetNode.java new file mode 100644 index 0000000..571168c --- /dev/null +++ b/src/main/java/com/rusefi/pcb/nodes/AddNetNode.java @@ -0,0 +1,15 @@ +package com.rusefi.pcb.nodes; + +import com.rusefi.pcb.nodes.PcbNode; + +import java.util.List; + +/** + * (c) Andrey Belomutskiy + * 5/30/2014 + */ +public class AddNetNode extends PcbNode { + public AddNetNode(String nodeName, int i, List children) { + super(nodeName, i, children); + } +} diff --git a/src/main/java/com/rusefi/pcb/nodes/CirclePadNode.java b/src/main/java/com/rusefi/pcb/nodes/CirclePadNode.java new file mode 100644 index 0000000..0f908db --- /dev/null +++ b/src/main/java/com/rusefi/pcb/nodes/CirclePadNode.java @@ -0,0 +1,24 @@ +package com.rusefi.pcb.nodes; + +import com.rusefi.pcb.nodes.PadNode; + +import java.util.List; + +/** + * (c) Andrey Belomutskiy + * 1/21/14. + */ +public class CirclePadNode extends PadNode { + public CirclePadNode(String nodeName, int i, List children) { + super(nodeName, i, children); + } + + @Override + public String toString() { + return "CirclePadNode{" + + "at=" + at + + ", size=" + size + + '}'; + } + +} diff --git a/src/main/java/com/rusefi/pcb/nodes/GrLineNode.java b/src/main/java/com/rusefi/pcb/nodes/GrLineNode.java new file mode 100644 index 0000000..f1105c6 --- /dev/null +++ b/src/main/java/com/rusefi/pcb/nodes/GrLineNode.java @@ -0,0 +1,15 @@ +package com.rusefi.pcb.nodes; + +import java.util.List; + +/** + * @author Andrey Belomutskiy + * 2/7/14. + */ +public class GrLineNode extends PcbNode { + public final LayerNode layerNode; + public GrLineNode(String nodeName, int i, List children) { + super(nodeName, i, children); + layerNode = (LayerNode) find("layer"); + } +} diff --git a/src/main/java/com/rusefi/pcb/nodes/LayerNode.java b/src/main/java/com/rusefi/pcb/nodes/LayerNode.java new file mode 100644 index 0000000..b6999fb --- /dev/null +++ b/src/main/java/com/rusefi/pcb/nodes/LayerNode.java @@ -0,0 +1,22 @@ +package com.rusefi.pcb.nodes; + +import com.rusefi.pcb.nodes.PcbNode; + +import java.util.List; + +/** + * @author Andrey Belomutskiy + * 2/7/14. + */ +public class LayerNode extends PcbNode { + public final String name; + + public LayerNode(String nodeName, int closingIndex, List children) { + super(nodeName, closingIndex, children); + name = (String) children.get(0); + } + + public boolean isSilkcreenLayer() { + return name.equals("B.SilkS") || name.equals("F.SilkS"); + } +} diff --git a/src/main/java/com/rusefi/pcb/nodes/ModuleNode.java b/src/main/java/com/rusefi/pcb/nodes/ModuleNode.java new file mode 100644 index 0000000..02fa978 --- /dev/null +++ b/src/main/java/com/rusefi/pcb/nodes/ModuleNode.java @@ -0,0 +1,45 @@ +package com.rusefi.pcb.nodes; + +import java.util.List; + +/** + * @author Andrey Belomutskiy + * 1/21/14 + */ +public class ModuleNode extends PcbNode { + final List pads; + public final PointNode at; + private final String reference; + + public ModuleNode(String nodeName, int i, List children) { + super(nodeName, i, children); + Object o = iterate("pad"); + pads = (List) o; + at = (PointNode) find("at"); + + reference = iterate("fp_text").get(0).getChild(1); + } + + public String getReference() { + return reference; + } + + @Override + public String toString() { + return "ModuleNode{" + + "reference=" + reference + + ", pads.size=" + pads.size() + + '}'; + } + + @Override + public boolean isConnected(PointNode point) { + PointNode offsetPoint = at.translate(point); + + for (PadNode pad : pads) { + if (pad.isConnected(offsetPoint)) + return true; + } + return false; + } +} diff --git a/src/main/java/com/rusefi/pcb/nodes/NetNode.java b/src/main/java/com/rusefi/pcb/nodes/NetNode.java new file mode 100644 index 0000000..f3e445a --- /dev/null +++ b/src/main/java/com/rusefi/pcb/nodes/NetNode.java @@ -0,0 +1,37 @@ +package com.rusefi.pcb.nodes; + +import com.rusefi.pcb.nodes.PcbNode; + +import java.util.List; + +/** + * @author Andrey Belomutskiy + * 1/21/14 + */ +public class NetNode extends PcbNode { + public final String id; + final String name; + public static int GND_NET_ID; + + public NetNode(String nodeName, int i, List children) { + super(nodeName, i, children); + id = getChild(0); + name = children.size() > 1 ? getChild(1) : null; + if (name != null) + System.out.println("NetNode(" + name + " network: " + id + ")"); + + if ("GND".equalsIgnoreCase(name)) + GND_NET_ID = Integer.parseInt(id); + } + + public String getName() { + return name; + } + + @Override + public String toString() { + return "NetNode{" + + "id='" + id + '\'' + + '}'; + } +} diff --git a/src/main/java/com/rusefi/pcb/nodes/PadNode.java b/src/main/java/com/rusefi/pcb/nodes/PadNode.java new file mode 100644 index 0000000..8d56f9a --- /dev/null +++ b/src/main/java/com/rusefi/pcb/nodes/PadNode.java @@ -0,0 +1,44 @@ +package com.rusefi.pcb.nodes; + +import java.util.List; + +/** + * (c) Andrey Belomutskiy + * 1/21/14. + */ +public abstract class PadNode extends PcbNode { + protected final PointNode at; + protected final SizeNode size; + protected final String name; + + public PadNode(String nodeName, int i, List children) { + super(nodeName, i, children); + name = (String) children.get(0); + at = (PointNode) find("at"); + size = (SizeNode) find("size"); + } + + @Override + public boolean isConnected(PointNode point) { + return point.isConnected(at, size); + } + + public static PcbNode parse(String nodeName, int i, List children) { + Object shape = children.get(2); + if ("rect".equals(shape)) + return new RectPadNode(nodeName, i, children); + if ("circle".equals(shape)) + return new CirclePadNode(nodeName, i, children); + if ("oval".equals(shape)) + return new CirclePadNode(nodeName, i, children); // yes, let's treat oval as circle. good enough + throw new IllegalStateException("Unknown pad shape: " + shape.toString()); + } + + @Override + public String toString() { + return "PadNode{" + + "at=" + at + + ", size=" + size + + '}'; + } +} diff --git a/src/main/java/com/rusefi/pcb/nodes/PcbNode.java b/src/main/java/com/rusefi/pcb/nodes/PcbNode.java new file mode 100644 index 0000000..f1cd18d --- /dev/null +++ b/src/main/java/com/rusefi/pcb/nodes/PcbNode.java @@ -0,0 +1,257 @@ +package com.rusefi.pcb.nodes; + +import com.rusefi.pcb.*; +import com.rusefi.util.FileUtils; + +import java.io.BufferedWriter; +import java.io.FileWriter; +import java.io.IOException; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +/** + * (c) Andrey Belomutskiy + * 12/8/13 + */ +public class PcbNode { + public final String nodeName; + public final int closingIndex; + public final List children; + + public PcbNode(String nodeName, int closingIndex, List children) { + this.nodeName = nodeName; + this.closingIndex = closingIndex; + this.children = children; + } + + /** + * @see #write + */ + public static PcbNode readFromFile(String fileName) throws IOException { + String content = FileUtils.readFile(fileName); + PcbNode node = parse(content); + System.out.println("GND network: " + NetNode.GND_NET_ID); + return node; + } + + public static void copy(String inputFileName, String outputFileName) throws IOException { + System.out.println("From " + inputFileName + " to " + outputFileName); + PcbNode node = readFromFile(inputFileName); + + PcbMergeTool.removeNodes(node); + + node.write(outputFileName); + } + + @Override + public String toString() { + return "PcbNode{" + + nodeName + + ", children=" + children.size() + + '}'; + } + + private static PcbNode parse(String s, int index, int depth) { + log("Reading node from " + index, depth); + if (s.charAt(index) != '(') + throw new IllegalStateException("opening bracket expected"); + index++; + String nodeName = readToken(s, index, depth); + index += nodeName.length(); + + List children = new ArrayList(); + while (true) { + while (isWhitespace(s.charAt(index))) + index++; + + char c = s.charAt(index); + if (c == ')') + break; + + if (s.charAt(index) == '(') { + PcbNode child = parse(s, index, depth + 1); + children.add(child); + index = child.closingIndex; + continue; + } + + String child = readToken(s, index, depth); + children.add(child); + index += child.length(); + } + if ("segment".equals(nodeName)) { + return new SegmentNode(nodeName, index + 1, children); + } else if ("pad".equals(nodeName)) { + return PadNode.parse(nodeName, index + 1, children); + } else if ("net".equals(nodeName)) { + return new NetNode(nodeName, index + 1, children); + } else if ("add_net".equals(nodeName)) { + return new AddNetNode(nodeName, index + 1, children); + } else if ("gr_line".equals(nodeName)) { + return new GrLineNode(nodeName, index + 1, children); + } else if ("layer".equals(nodeName)) { + return new LayerNode(nodeName, index + 1, children); + } else if ("module".equals(nodeName)) { + return new ModuleNode(nodeName, index + 1, children); + } else if ("size".equals(nodeName) || "width".equals(nodeName)) { + return new SizeNode(nodeName, index + 1, children); + } else if ("zone".equals(nodeName)) { + return new ZoneNode(nodeName, index + 1, children); + } else if ("via".equals(nodeName)) { + return new ViaNode(nodeName, index + 1, children); + } else if ("start".equals(nodeName) || "end".equals(nodeName) || "at".equals(nodeName)) { + return new PointNode(nodeName, index + 1, children); + } + + return new PcbNode(nodeName, index + 1, children); + } + + private static String readToken(String s, int index, int depth) { + log("Reading token from " + index, depth); + if (s.charAt(index) == '"') { + String result = s.substring(index, s.indexOf('"', index + 1) + 1); + log("Got quoted token: " + result, depth); + return result; + } + + String result = ""; + while (index < s.length()) { + char c = s.charAt(index); + if (c == ')' || isWhitespace(c)) + break; + result += c; + index++; + } + if (result.length() == 0) + throw new IllegalStateException("Empty token"); + log("Got token: " + result, depth); + return result; + } + + private static void log(String s, int depth) { +// for (int i = 0; i < depth; i++) +// System.out.print(' '); +// System.out.println(s); + } + + private static void log(String s) { + log(s, 0); + } + + private static boolean isWhitespace(char c) { + return c == ' ' || c == '\r' || c == '\n'; + } + + public static PcbNode parse(String content) { + return parse(content, 0, 0); + } + + public String pack() { + StringBuilder sb = new StringBuilder(); + pack(sb, ""); + return sb.toString(); + } + + private void pack(StringBuilder sb, String prefix) { + sb.append(prefix).append("(").append(nodeName); + + for (Object child : children) { + if (child instanceof String) { + sb.append(" ").append(child); + continue; + } + PcbNode p = (PcbNode) child; + if (p == null) + throw new NullPointerException("Null child node"); + sb.append("\r\n"); + p.pack(sb, prefix + " "); + } + + + sb.append(")\r\n"); + } + + public void write(String fileName) throws IOException { + System.out.println("Writing to " + fileName); + String content = pack(); + BufferedWriter bw = new BufferedWriter(new FileWriter(fileName)); + bw.write(content); + bw.close(); + } + + public void setDouble(int i, double value) { + children.set(i, "" + value); + } + + public double asDouble(int index) { + return Double.parseDouble((String) children.get(index)); + } + + public boolean hasChild(String key) { + return !iterate(key).isEmpty(); + } + + // @Nullable + public PcbNode findIfExists(String key) { + List r = iterate(key); + if (r.isEmpty()) + return null; + return find(key); + } + + // @NotNull + public PcbNode find(String key) { + List r = iterate(key); + if (r.size() != 1) + throw new IllegalStateException("More that one " + key + " in " + nodeName); + return r.get(0); + } + + public List nodes() { + List result = new ArrayList(); + for (Object child : children) { + if (child instanceof String) + continue; + result.add((PcbNode) child); + } + return result; + } + + public List iterate(String key) { + List result = new ArrayList(); + for (PcbNode p : nodes()) { + if (p.nodeName.equals(key)) + result.add(p); + } + return result; + } + + public void addChild(PcbNode node) { + children.add(node); + } + + public List getChildren() { + return Collections.unmodifiableList(children); + } + + public String getChild(int index) { + return (String) children.get(index); + } + + public void setString(int index, String value) { + children.set(index, value); + } + + public void setInt(int index, int value) { + children.set(index, "" + value); + } + + public boolean removeChild(Object child) { + return children.remove(child); + } + + public boolean isConnected(PointNode point) { + return false; + } +} diff --git a/src/main/java/com/rusefi/pcb/nodes/PointNode.java b/src/main/java/com/rusefi/pcb/nodes/PointNode.java new file mode 100644 index 0000000..2aa2f74 --- /dev/null +++ b/src/main/java/com/rusefi/pcb/nodes/PointNode.java @@ -0,0 +1,77 @@ +package com.rusefi.pcb.nodes; + +import java.util.Collections; +import java.util.List; + +/** + * @author Andrey Belomutskiy + * 1/21/14 + */ +public class PointNode extends PcbNode { + public final double x; + public final double y; + public final double angle; + + public PointNode(String nodeName, int i, List children) { + super(nodeName, i, children); + if (children.size() == 1) { + // xyz use-case + x = 0; + y = 0; + angle = 0; + return; + } + + if (children.size() != 2 && children.size() != 3) + throw new IllegalStateException("Unexpected children count"); + x = Double.parseDouble((String) children.get(0)); + y = Double.parseDouble((String) children.get(1)); + angle = children.size() == 2 ? 0 : Double.parseDouble((String) children.get(2)); + } + + public PointNode(double x, double y) { + this(x, y, 0); + } + + public PointNode(double x, double y, double angle) { + super("", 0, Collections.emptyList()); + this.x = x; + this.y = y; + this.angle = angle; + } + + public boolean isConnected(PointNode at, SizeNode size) { + boolean isConnectedX = (x >= at.x - size.w / 2) && (x <= at.x + size.w / 2); + boolean isConnectedY = (y >= at.y - size.h / 2) && (y <= at.y + size.h / 2); + return isConnectedX && isConnectedY; + } + + @Override + public String toString() { + return "PointNode{" + + "x=" + x + + ", y=" + y + + ", angle=" + angle + + '}'; + } + +// public boolean isSameLocation(PointNode point) { +// return x == point.x && y == point.y; +// } + + public PointNode translate(PointNode at) { + double nx = at.x - x; + double ny = at.y - y; + + double radian = angle / 180 * Math.PI; + double rx = Math.cos(radian) * nx - Math.sin(radian) * ny; + double ry = Math.sin(radian) * nx + Math.cos(radian) * ny; + + return new PointNode(rx, ry); + } + + public void setLocation(double x, double y) { + children.set(0, Double.toString(x)); + children.set(1, Double.toString(y)); + } +} diff --git a/src/main/java/com/rusefi/pcb/nodes/RectPadNode.java b/src/main/java/com/rusefi/pcb/nodes/RectPadNode.java new file mode 100644 index 0000000..f3a1cdd --- /dev/null +++ b/src/main/java/com/rusefi/pcb/nodes/RectPadNode.java @@ -0,0 +1,24 @@ +package com.rusefi.pcb.nodes; + +import com.rusefi.pcb.nodes.PadNode; + +import java.util.List; + +/** + * (c) Andrey Belomutskiy + * 1/21/14. + */ +public class RectPadNode extends PadNode { + public RectPadNode(String nodeName, int i, List children) { + super(nodeName, i, children); + } + + @Override + public String toString() { + return "RectPadNode{" + + "name=" + name + + ", at=" + at + + ", size=" + size + + '}'; + } +} diff --git a/src/main/java/com/rusefi/pcb/nodes/SegmentNode.java b/src/main/java/com/rusefi/pcb/nodes/SegmentNode.java new file mode 100644 index 0000000..d6b7a93 --- /dev/null +++ b/src/main/java/com/rusefi/pcb/nodes/SegmentNode.java @@ -0,0 +1,35 @@ +package com.rusefi.pcb.nodes; + +import java.util.List; + +/** + * @author Andrey Belomutskiy + * 1/21/14 + */ +public class SegmentNode extends PcbNode { + public final NetNode net; + public final PointNode start; + public final PointNode end; + private final SizeNode size; + + public SegmentNode(String nodeName, int closingIndex, List children) { + super(nodeName, closingIndex, children); + net = (NetNode) find("net"); + start = (PointNode) find("start"); + end = (PointNode) find("end"); + size = (SizeNode) find("width"); + } + + public boolean isConnected(PointNode point) { + return point.isConnected(start, size) || point.isConnected(end, size); + } + + @Override + public String toString() { + return "SegmentNode{" + + "net=" + net + + ", start=" + start + + ", end=" + end + + '}'; + } +} diff --git a/src/main/java/com/rusefi/pcb/nodes/SizeNode.java b/src/main/java/com/rusefi/pcb/nodes/SizeNode.java new file mode 100644 index 0000000..119f44d --- /dev/null +++ b/src/main/java/com/rusefi/pcb/nodes/SizeNode.java @@ -0,0 +1,33 @@ +package com.rusefi.pcb.nodes; + +import java.util.List; + +/** + * @author Andrey Belomutskiy + * 1/21/14 + */ +public class SizeNode extends PcbNode { + public final double w; + public final double h; + + public SizeNode(String nodeName, int i, List children) { + super(nodeName, i, children); + if (children.size() == 1) { + w = h = Double.parseDouble((String) children.get(0)); + return; + } + + if (children.size() != 2) + throw new IllegalStateException("Size: " + children.size()); + w = Double.parseDouble((String) children.get(0)); + h = Double.parseDouble((String) children.get(1)); + } + + @Override + public String toString() { + return "SizeNode{" + + "w=" + w + + ", h=" + h + + '}'; + } +} diff --git a/src/main/java/com/rusefi/pcb/nodes/ViaNode.java b/src/main/java/com/rusefi/pcb/nodes/ViaNode.java new file mode 100644 index 0000000..98ea842 --- /dev/null +++ b/src/main/java/com/rusefi/pcb/nodes/ViaNode.java @@ -0,0 +1,36 @@ +package com.rusefi.pcb.nodes; + +import com.rusefi.pcb.nodes.PcbNode; +import com.rusefi.pcb.nodes.PointNode; +import com.rusefi.pcb.nodes.SizeNode; + +import java.util.List; + +/** + * @author Andrey Belomutskiy + * 1/21/14 + */ +public class ViaNode extends PcbNode { + public final PointNode location; + final SizeNode size; + public final int netId; + + public ViaNode(String nodeName, int i, List children) { + super(nodeName, i, children); + location = (PointNode) find("at"); + size = (SizeNode) find("size"); + netId = Integer.parseInt(find("net").getChild(0)); + } + + @Override + public String toString() { + return "ViaNode{" + + "location=" + location + + '}'; + } + + @Override + public boolean isConnected(PointNode point) { + return point.isConnected(location, size); + } +} diff --git a/src/main/java/com/rusefi/pcb/nodes/ZoneNode.java b/src/main/java/com/rusefi/pcb/nodes/ZoneNode.java new file mode 100644 index 0000000..e5a644a --- /dev/null +++ b/src/main/java/com/rusefi/pcb/nodes/ZoneNode.java @@ -0,0 +1,23 @@ +package com.rusefi.pcb.nodes; + +import com.rusefi.pcb.nodes.LayerNode; +import com.rusefi.pcb.nodes.PcbNode; + +import java.util.List; + +/** + * (c) Andrey Belomutskiy + * 2/11/14. + */ +public class ZoneNode extends PcbNode { + private final LayerNode layerNode; + + public ZoneNode(String nodeName, int i, List children) { + super(nodeName, i, children); + layerNode = (LayerNode) find("layer"); + } + + public LayerNode getLayerNode() { + return layerNode; + } +} diff --git a/src/main/java/com/rusefi/util/FileUtils.java b/src/main/java/com/rusefi/util/FileUtils.java new file mode 100644 index 0000000..700be2d --- /dev/null +++ b/src/main/java/com/rusefi/util/FileUtils.java @@ -0,0 +1,59 @@ +package com.rusefi.util; + +import java.io.BufferedReader; +import java.io.File; +import java.io.FileReader; +import java.io.IOException; +import java.util.ArrayList; +import java.util.List; + +/** + * Generic file utilities + *

+ * 12/9/13 + * (c) Andrey Belomutskiy + */ +public class FileUtils { + private FileUtils() { + } + + /** + * @param fileName + * @return Full content of the file as one String + * @throws IOException + */ + public static String readFile(String fileName) throws IOException { + checkExistence(fileName); + + System.out.println("Reading " + fileName); + StringBuilder sb = new StringBuilder(); + String line; + BufferedReader br = new BufferedReader(new FileReader(fileName)); + + while (((line = br.readLine()) != null)) + sb.append(line).append("\r\n"); + br.close(); + return sb.toString(); + } + + private static void checkExistence(String fileName) { + if (!new File(fileName).isFile()) { + System.err.println("File not found: " + fileName); + System.exit(-1); + } + } + + public static List readFileToList(String fileName) throws IOException { + checkExistence(fileName); + + List result = new ArrayList(); + + System.out.println("Reading " + fileName); + String line; + BufferedReader br = new BufferedReader(new FileReader(fileName)); + + while (((line = br.readLine()) != null)) + result.add(line); + return result; + } +} diff --git a/src/test/java/com/rusefi/netlist/NameAndOffsetTest.java b/src/test/java/com/rusefi/netlist/NameAndOffsetTest.java new file mode 100644 index 0000000..b8e480f --- /dev/null +++ b/src/test/java/com/rusefi/netlist/NameAndOffsetTest.java @@ -0,0 +1,14 @@ +package com.rusefi.netlist; + +import com.rusefi.pcb.NameAndOffset; +import org.junit.Test; + +import static org.junit.Assert.assertEquals; + +public class NameAndOffsetTest { + @Test + public void test() { + NameAndOffset n = NameAndOffset.parseNameAndOffset("1 2 3"); + assertEquals("1", n.getName()); + } +}