diff --git a/diylc/.DS_Store b/diylc/.DS_Store index ba7e385a..1327d980 100644 Binary files a/diylc/.DS_Store and b/diylc/.DS_Store differ diff --git a/diylc/diylc-core/src/main/java/org/diylc/netlist/AbstractNetlistAnalyzer.java b/diylc/diylc-core/src/main/java/org/diylc/netlist/AbstractNetlistAnalyzer.java index 58c46752..75421b33 100644 --- a/diylc/diylc-core/src/main/java/org/diylc/netlist/AbstractNetlistAnalyzer.java +++ b/diylc/diylc-core/src/main/java/org/diylc/netlist/AbstractNetlistAnalyzer.java @@ -1,281 +1,283 @@ -/* - * - * DIY Layout Creator (DIYLC). Copyright (c) 2009-2018 held jointly by the individual authors. - * - * This file is part of DIYLC. - * - * DIYLC 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. - * - * DIYLC 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 DIYLC. If not, see - * . - */ -package org.diylc.netlist; - -import java.util.ArrayList; -import java.util.Collections; -import java.util.HashMap; -import java.util.HashSet; -import java.util.List; -import java.util.Map; -import java.util.Set; - -import org.diylc.core.IDIYComponent; - -public abstract class AbstractNetlistAnalyzer { - - public AbstractNetlistAnalyzer() { - } - - public List summarize(List netlists, Node preferredOutput) - throws TreeException { - Map summaries = new HashMap(); - for (Netlist n : netlists) { - Summary s = summarize(n, preferredOutput); - if (summaries.containsKey(s.getSummary())) - summaries.get(s.getSummary()).append(n); - else - summaries.put(s.getSummary(), s); - } - - List res = new ArrayList(summaries.values()); - Collections.sort(res); - return res; - } - - protected abstract Summary summarize(Netlist netlist, Node preferredOutput) throws TreeException; - - public Tree constructTreeBetween(Netlist netlist, Node nodeA, Node nodeB) { - Tree tree = new Tree(TreeConnectionType.Parallel); - connectNodes(netlist, nodeA, nodeB, tree.getChildren(), new Tree(TreeConnectionType.Series), - new HashSet()); - - return tree; - } - - protected void connectNodes(Netlist netlist, Node nodeA, Node nodeB, List concurrentPaths, - Tree currentPath, Set visited) { - if (nodeA == nodeB) { - concurrentPaths.add(currentPath); - return; - } - - Group groupA = findGroup(netlist, nodeA); - if (groupA == null) - return; - - if (groupA.getNodes().contains(nodeB)) { - concurrentPaths.add(currentPath); - return; - } - - Set pathVisited = new HashSet<>(visited); - pathVisited.add(nodeA); - - // First collect all immediate next nodes and their paths - Map> nextNodes = new HashMap<>(); - for (Node n : groupA.getNodes()) { - if (pathVisited.contains(n)) - continue; - - IDIYComponent c = n.getComponent(); - for (int i = 0; i < c.getControlPointCount(); i++) { - int targetPoint = i; - int sourcePoint = n.getPointIndex(); - - if (targetPoint == sourcePoint) - continue; - - if (c.getInternalLinkName(targetPoint, sourcePoint) != null) { - Node newNodeA = new Node(c, targetPoint, n.getZIndex()); - if (pathVisited.contains(newNodeA)) - continue; - - try { - Tree newPath = (Tree) currentPath.clone(); - TreeLeaf leaf = new TreeLeaf(c, targetPoint, sourcePoint); - Tree newItem = new Tree(leaf); - - if (!newPath.getChildren().contains(newItem)) { - newPath.getChildren().add(newItem); - nextNodes.computeIfAbsent(newNodeA, k -> new ArrayList<>()).add(newPath); - } - } catch (CloneNotSupportedException e) { - // Skip this path if clone fails - } - } - } - } - - // Now explore each next node - List allPaths = new ArrayList<>(); - for (Map.Entry> entry : nextNodes.entrySet()) { - for (Tree path : entry.getValue()) { - List branchPaths = new ArrayList<>(); - connectNodes(netlist, entry.getKey(), nodeB, branchPaths, path, pathVisited); - allPaths.addAll(branchPaths); - } - } - - if (allPaths.isEmpty()) - return; - - if (allPaths.size() > 1) { - // Find common prefix among all paths - List commonPrefix = findCommonElements(allPaths, true, false); - - // Create a new tree for the merged result - Tree mergedTree = new Tree(TreeConnectionType.Series); - - // Add common prefix elements - for (Tree prefixElement : commonPrefix) { - mergedTree.getChildren().add(prefixElement); - } - - // Extract unique parts - List uniqueParts = new ArrayList<>(); - for (Tree path : allPaths) { - Tree uniquePart = extractUniquePart(path, commonPrefix.size(), 0); - if (uniquePart != null) { - if (uniquePart.getChildren().size() == 1) { - uniqueParts.add(uniquePart.getChildren().get(0)); - } else { - uniqueParts.add(uniquePart); - } - } - } - - // Create parallel section for unique parts - if (!uniqueParts.isEmpty()) { - Tree parallelSection = new Tree(TreeConnectionType.Parallel); - parallelSection.getChildren().addAll(uniqueParts); - - // Add parallel section to series container - Tree parallelContainer = new Tree(TreeConnectionType.Series); - parallelContainer.getChildren().add(parallelSection); - mergedTree.getChildren().add(parallelContainer); - } - - concurrentPaths.add(mergedTree); - } else { - concurrentPaths.add(allPaths.get(0)); - } - } - - private List findCommonElements(List paths, boolean fromStart, boolean backward) { - if (paths.isEmpty()) - return new ArrayList<>(); - - List common = new ArrayList<>(); - Tree template = paths.get(0); - int maxLength = template.getChildren().size(); - - for (int i = 0; i < maxLength; i++) { - int index = fromStart ? - (backward ? template.getChildren().size() - i - 1 : i) : - (backward ? i : template.getChildren().size() - i - 1); - - if (index < 0 || index >= template.getChildren().size()) - break; - - Tree element = template.getChildren().get(index); - boolean isCommon = true; - - for (int j = 1; j < paths.size(); j++) { - Tree current = paths.get(j); - if (current.getChildren().size() <= i || index >= current.getChildren() - .size() || !current.getChildren().get(index).equals(element)) { - isCommon = false; - break; - } - } - - if (isCommon) { - common.add(element); - } else { - break; - } - } - - return common; - } - - private Tree extractUniquePart(Tree path, int prefixSize, int suffixSize) { - if (path.getChildren().size() <= prefixSize + suffixSize) - return null; - - Tree uniquePart = new Tree(TreeConnectionType.Series); - for (int i = prefixSize; i < path.getChildren().size() - suffixSize; i++) { - uniquePart.getChildren().add(path.getChildren().get(i)); - } - - return uniquePart.getChildren().isEmpty() ? null : uniquePart; - } - - protected Group findGroup(Netlist netlist, Node node) { - for (Group g : netlist.getGroups()) - if (g.getNodes().contains(node)) - return g; - return null; - } - - protected List find(Set typeNames, String nodeName, Netlist netlist) { - List res = new ArrayList(); - for (Group g : netlist.getGroups()) { - for (Node n : g.getNodes()) { - if (typeNames.contains(n.getComponent().getClass() - .getCanonicalName()) && (nodeName == null || n.getDisplayName() - .equalsIgnoreCase(nodeName))) { - res.add(n); - } - } - } - return res; - } - - public static List>> extractComponentGroups(List netlists) { - List>> res = new ArrayList>>(); - for (Netlist n : netlists) { - res.addAll(extractComponentGroups(n)); - } - return res; - } - - public static List>> extractComponentGroups(Netlist netlist) { - List>> res = new ArrayList>>(); - for (Group g : netlist.getGroups()) { - Set> components = new HashSet>(); - for (Node n : g.getNodes()) - components.add(n.getComponent()); - res.add(components); - } - return res; - } - - protected int find(Node node, List groups) { - for (int i = 0; i < groups.size(); i++) - if (groups.get(i).getNodes().contains(node)) - return i; - return -1; - } - - protected boolean intersect(List groups, Node node) { - for (Group g : groups) - if (g.getNodes().contains(node)) - return true; - return false; - } - - protected Node intersect(List groups, Group group) { - for (Group g : groups) - for (Node node : group.getNodes()) - if (g.getNodes().contains(node)) - return node; - return null; - } -} +/* + * + * DIY Layout Creator (DIYLC). Copyright (c) 2009-2018 held jointly by the individual authors. + * + * This file is part of DIYLC. + * + * DIYLC 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. + * + * DIYLC 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 DIYLC. If not, see + * . + */ +package org.diylc.netlist; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; +import org.diylc.core.IDIYComponent; + +public abstract class AbstractNetlistAnalyzer { + + public AbstractNetlistAnalyzer() {} + + public List summarize(List netlists, Node preferredOutput) throws TreeException { + Map summaries = new HashMap(); + for (Netlist n : netlists) { + Summary s = summarize(n, preferredOutput); + if (summaries.containsKey(s.getSummary())) + summaries.get(s.getSummary()).append(n); + else + summaries.put(s.getSummary(), s); + } + + List res = new ArrayList(summaries.values()); + Collections.sort(res); + return res; + } + + protected abstract Summary summarize(Netlist netlist, Node preferredOutput) throws TreeException; + + public Tree constructTreeBetween(Netlist netlist, Node nodeA, Node nodeB) { + Tree tree = new Tree(TreeConnectionType.Parallel); + connectNodes(netlist, nodeA, nodeB, tree.getChildren(), new Tree(TreeConnectionType.Series), new HashSet()); + + return tree; + } + +// protected Tree removeRedundantElements(Tree tree) { +// List children = tree.getChildren() +// .stream() +// .map(t -> removeRedundantElements(t)) +// .collect(Collectors.toList()); +// if (children.size() == 1) { +// Tree first = children.get(0); +// return first; +// } +// } + + protected void connectNodes(Netlist netlist, Node nodeA, Node nodeB, List concurrentPaths, Tree currentPath, + Set visited) { + if (nodeA == nodeB) { + concurrentPaths.add(currentPath); + return; + } + + Group groupA = findGroup(netlist, nodeA); + + if (groupA == null) + return; + + if (groupA.getNodes().contains(nodeB)) { + concurrentPaths.add(currentPath); + return; + } + + visited.addAll(groupA.getNodes()); + + List newConcurrentPaths = new ArrayList(); + + for (Node n : groupA.getNodes()) { + IDIYComponent c = n.getComponent(); + for (int i = 0; i < c.getControlPointCount(); i++) { + if (i != n.getPointIndex() && c.getInternalLinkName(i, n.getPointIndex()) != null) { + TreeLeaf l = new TreeLeaf(c, i, n.getPointIndex()); + Node newNodeA = new Node(c, i, n.getZIndex()); + if (!visited.contains(newNodeA)) { + // visited.add(n); + Tree newCurrentPath = null; + try { + newCurrentPath = (Tree) currentPath.clone(); + } catch (CloneNotSupportedException e) { + } + Tree newItem = new Tree(l); + if (!newCurrentPath.getChildren().contains(newItem)) + newCurrentPath.getChildren().add(newItem); + // if (!currentPath.contains(l)) + // newCurrentPath.add(l); + Set newVisited = new HashSet(visited); + connectNodes(netlist, newNodeA, nodeB, newConcurrentPaths, newCurrentPath, newVisited); + } + } + } + } + + if (newConcurrentPaths.size() == 0) + return; + + if (newConcurrentPaths.size() == 1) { + if (newConcurrentPaths.get(0).getConnectionType() == TreeConnectionType.Series) { + currentPath.getChildren().clear(); + currentPath.getChildren().addAll(newConcurrentPaths.get(0).getChildren()); + } else { + currentPath.getChildren().add(newConcurrentPaths.get(0)); + } + concurrentPaths.add(currentPath); + return; + } + + mergePaths(newConcurrentPaths, false); + concurrentPaths.addAll(newConcurrentPaths); + } + + protected void mergePaths(List paths, boolean backward) { + Map uniques = new HashMap(); + for (Tree t : paths) { + if (t.getConnectionType() != TreeConnectionType.Series) + continue; + Tree key = t.getChildren().get(backward ? t.getChildren().size() - 1 : 0); + Integer count = uniques.get(key); + uniques.put(key, count == null ? 1 : count + 1); + } + + List unmerged = new ArrayList(paths); + + for (Map.Entry e : uniques.entrySet()) { + if (e.getValue() > 1) { + List pathsToMerge = new ArrayList(); + for (Tree t : paths) { + Tree key = t.getChildren().get(backward ? t.getChildren().size() - 1 : 0); + if (key.equals(e.getKey())) + pathsToMerge.add(t); + } + unmerged.removeAll(pathsToMerge); + + Tree jointStart = new Tree(new ArrayList(), TreeConnectionType.Series); + Tree jointFinish = new Tree(new ArrayList(), TreeConnectionType.Series); + + Tree unique = e.getKey(); + + for (int i = 0; i < pathsToMerge.get(0).getChildren().size(); i++) { + boolean canMerge = true; + unique = pathsToMerge.get(0).getChildren().get(backward ? pathsToMerge.get(0).getChildren().size() - i - 1 : i); + for (Tree t : pathsToMerge) { + if (t.getChildren().size() <= i || !t.getChildren().get(backward ? t.getChildren().size() - i - 1 : i).equals(unique)) { + canMerge = false; + break; + } + } + if (canMerge) + (backward ? jointFinish : jointStart).getChildren().add(unique); + else + break; + } + + for (int i = 0; i < pathsToMerge.get(0).getChildren().size(); i++) { + boolean canMerge = true; + unique = pathsToMerge.get(0).getChildren().get(backward ? i : pathsToMerge.get(0).getChildren().size() - i - 1); + for (Tree t : pathsToMerge) { + if (t.getChildren().size() <= i || !t.getChildren().get(backward ? 0 : t.getChildren().size() - 1).equals(unique)) { + canMerge = false; + break; + } + } + if (canMerge) + (backward ? jointStart : jointFinish).getChildren().add(unique); + else + break; + } + + if (!jointStart.getChildren().isEmpty() || !jointFinish.getChildren().isEmpty()) { + Tree mergedTree = new Tree(new ArrayList(), TreeConnectionType.Series); + Tree parallelSection = new Tree(new ArrayList(pathsToMerge), TreeConnectionType.Parallel); + if (!jointStart.getChildren().isEmpty()) { + mergedTree.getChildren().add(jointStart); + for (Tree t : parallelSection.getChildren()) { + t.trimChildrenLeft(jointStart.getChildren().size()); + } + } + mergedTree.getChildren().add(parallelSection); + if (!jointFinish.getChildren().isEmpty()) { + mergedTree.getChildren().add(jointFinish); + for (Tree t : parallelSection.getChildren()) { + t.trimChildrenRight(jointFinish.getChildren().size()); + } + } + paths.removeAll(pathsToMerge); + paths.add(mergedTree); + } + } + } + + // try in the opposite direction for paths we haven't merged + if (!backward && !unmerged.isEmpty()) { + paths.removeAll(unmerged); + mergePaths(unmerged, true); + paths.addAll(unmerged); + } + } + + protected Group findGroup(Netlist netlist, Node node) { + for (Group g : netlist.getGroups()) + if (g.getNodes().contains(node)) + return g; + return null; + } + + protected List find(Set typeNames, String nodeName, Netlist netlist) { + List res = new ArrayList(); + for (Group g : netlist.getGroups()) { + for (Node n : g.getNodes()) { + if (typeNames.contains(n.getComponent().getClass().getCanonicalName()) + && (nodeName == null || n.getDisplayName().equalsIgnoreCase(nodeName))) { + res.add(n); + } + } + } + return res; + } + + protected String extractName(IDIYComponent c) { + return c.getName() + " " + (c.getValueForDisplay() == null ? "" : c.getValueForDisplay()); + } + + public static List>> extractComponentGroups(List netlists) { + List>> res = new ArrayList>>(); + for (Netlist n : netlists) { + res.addAll(extractComponentGroups(n)); + } + return res; + } + + public static List>> extractComponentGroups(Netlist netlist) { + List>> res = new ArrayList>>(); + for (Group g : netlist.getGroups()) { + Set> components = new HashSet>(); + for (Node n : g.getNodes()) + components.add(n.getComponent()); + res.add(components); + } + return res; + } + + protected int find(Node node, List groups) { + for (int i = 0; i < groups.size(); i++) + if (groups.get(i).getNodes().contains(node)) + return i; + return -1; + } + + protected boolean intersect(List groups, Node node) { + for (Group g : groups) + if (g.getNodes().contains(node)) + return true; + return false; + } + + protected Node intersect(List groups, Group group) { + for (Group g : groups) + for (Node node : group.getNodes()) + if (g.getNodes().contains(node)) + return node; + return null; + } +} diff --git a/diylc/diylc-library/src/test/java/org/diylc/netlist/GuitarAbstractNetlistAnalyzerTests.java b/diylc/diylc-library/src/test/java/org/diylc/netlist/GuitarDiagramAnalyzerTests.java similarity index 81% rename from diylc/diylc-library/src/test/java/org/diylc/netlist/GuitarAbstractNetlistAnalyzerTests.java rename to diylc/diylc-library/src/test/java/org/diylc/netlist/GuitarDiagramAnalyzerTests.java index 3e1aff50..4ca4a73d 100644 --- a/diylc/diylc-library/src/test/java/org/diylc/netlist/GuitarAbstractNetlistAnalyzerTests.java +++ b/diylc/diylc-library/src/test/java/org/diylc/netlist/GuitarDiagramAnalyzerTests.java @@ -21,6 +21,8 @@ package org.diylc.netlist; import static org.junit.Assert.assertEquals; import java.util.Arrays; +import org.diylc.components.passive.AxialFilmCapacitor; +import org.diylc.components.passive.Resistor; import org.junit.Test; import org.diylc.components.electromechanical.OpenJack1_4; @@ -28,13 +30,8 @@ import org.diylc.components.guitar.HumbuckerPickup; import org.diylc.components.guitar.SingleCoilPickup; import org.diylc.components.passive.PotentiometerPanel; import org.diylc.components.passive.RadialCeramicDiskCapacitor; -import org.diylc.netlist.Group; -import org.diylc.netlist.GuitarDiagramAnalyzer; -import org.diylc.netlist.Netlist; -import org.diylc.netlist.Tree; -import org.diylc.netlist.TreeException; -public class GuitarAbstractNetlistAnalyzerTests { +public class GuitarDiagramAnalyzerTests { @Test public void testOneSinglePickup() throws TreeException { @@ -67,6 +64,49 @@ public class GuitarAbstractNetlistAnalyzerTests { assertEquals("((Volume.1-2 + Pickup.North<-) || Volume.2-3)", s); } + @Test + public void testOneSinglePickupVolPotTrebleBleedSeries() throws TreeException { + OpenJack1_4 jack = new OpenJack1_4(); + SingleCoilPickup pickup = new SingleCoilPickup(); + pickup.setName("Pickup"); + PotentiometerPanel vol = new PotentiometerPanel(); + vol.setName("Volume"); + AxialFilmCapacitor cap = new AxialFilmCapacitor(); + cap.setName("Cbleed"); + Resistor resistor = new Resistor(); + resistor.setName("Rbleed"); + Netlist netlist = new Netlist(Arrays.asList(jack, pickup, vol)); + Group hotGroup = new Group().connect(pickup, 1).connect(vol, 0).connect(resistor, 1); + Group tipGroup = new Group().connect(jack, 0).connect(vol, 1).connect(cap, 1); + Group sleeveGroup = new Group().connect(jack, 1).connect(pickup, 2).connect(vol, 2); + Group trebleBleedGroup = new Group().connect(cap, 0).connect(resistor, 0); + netlist.add(tipGroup).add(tipGroup).add(sleeveGroup).add(hotGroup).add(trebleBleedGroup); + Tree tree = new GuitarDiagramAnalyzer().constructTree(netlist); + String s = tree.toString(); + assertEquals("((((Cbleed + Rbleed) || Volume.1-2) + Pickup.North<-) || Volume.2-3)", s); + } + + @Test + public void testOneSinglePickupVolPotTrebleBleedParallel() throws TreeException { + OpenJack1_4 jack = new OpenJack1_4(); + SingleCoilPickup pickup = new SingleCoilPickup(); + pickup.setName("Pickup"); + PotentiometerPanel vol = new PotentiometerPanel(); + vol.setName("Volume"); + AxialFilmCapacitor cap = new AxialFilmCapacitor(); + cap.setName("Cbleed"); + Resistor resistor = new Resistor(); + resistor.setName("Rbleed"); + Netlist netlist = new Netlist(Arrays.asList(jack, pickup, vol)); + Group hotGroup = new Group().connect(pickup, 1).connect(vol, 0).connect(resistor, 1).connect(cap, 1); + Group tipGroup = new Group().connect(jack, 0).connect(vol, 1).connect(resistor, 0).connect(cap, 0); + Group sleeveGroup = new Group().connect(jack, 1).connect(pickup, 2).connect(vol, 2); + netlist.add(tipGroup).add(tipGroup).add(sleeveGroup).add(hotGroup); + Tree tree = new GuitarDiagramAnalyzer().constructTree(netlist); + String s = tree.toString(); + assertEquals("(((Cbleed || Rbleed || Volume.1-2) + Pickup.North<-) || Volume.2-3)", s); + } + @Test public void testOneSinglePickupVolTonePot() throws TreeException { OpenJack1_4 jack = new OpenJack1_4(); @@ -87,7 +127,7 @@ public class GuitarAbstractNetlistAnalyzerTests { netlist.add(tipGroup).add(tipGroup).add(sleeveGroup).add(hotGroup).add(toneGroup); Tree tree = new GuitarDiagramAnalyzer().constructTree(netlist); String s = tree.toString(); - assertEquals("((Volume.1-2 + (Tone.2-3 + Cap) || Pickup.North<-) || Volume.2-3)", s); + assertEquals("((Volume.1-2 + ((Tone.2-3 + Cap) || Pickup.North<-)) || Volume.2-3)", s); } @Test @@ -121,7 +161,7 @@ public class GuitarAbstractNetlistAnalyzerTests { Tree tree = new GuitarDiagramAnalyzer().constructTree(netlist); String s = tree.toString(); assertEquals( - "((Volume.1-2 + (Tone1.2-3 + Cap1) || (Tone2.2-3 + Cap2) || Pickup1.North<- || Pickup2.North<-) || Volume.2-3)", + "((Volume.1-2 + ((Tone1.2-3 + Cap1) || (Tone2.2-3 + Cap2) || Pickup1.North<- || Pickup2.North<-)) || Volume.2-3)", s); } @@ -285,7 +325,7 @@ public class GuitarAbstractNetlistAnalyzerTests { Tree tree = new GuitarDiagramAnalyzer().constructTree(netlist); String s = tree.toString(); assertEquals( - "((Volume.1-2 + (Pickup1.North<- + Pickup1.South<-) || (Pickup2.North<- + Pickup2.South<-) || (Tone1.2-3 + Cap1) || (Tone2.2-3 + Cap2)) || Volume.2-3)", + "((Volume.1-2 + ((Pickup1.North<- + Pickup1.South<-) || (Pickup2.North<- + Pickup2.South<-) || (Tone1.2-3 + Cap1) || (Tone2.2-3 + Cap2))) || Volume.2-3)", s); } } diff --git a/diylc/diylc-swing/dict.txt b/diylc/diylc-swing/dict.txt index 65525c4c..f2477e3d 100644 --- a/diylc/diylc-swing/dict.txt +++ b/diylc/diylc-swing/dict.txt @@ -20,20 +20,24 @@ 10 throws / 10 positions 11 throws / 11 positions 12 throws / 12 positions +135 degrees clockwise 180 degrees clockwise 1P12T 2 throws / 2 positions 2 throws / 3 positions / OFF even positions +225 degrees clockwise 270 degrees clockwise 2P5T 2P6T 3 throws / 3 positions 3 throws / 5 positions / SHORT even positions +315 degrees clockwise 3P4T 3PDT 3PDT (On/Off/On) 4 band 4 throws / 4 positions +45 degrees clockwise 4P3T 4P5T 4P5T (Super/Mega) @@ -598,6 +602,7 @@ Vertical Inline Vertical Offset 1 Vertical Offset 2 View +Virtual Multimeter Visible Voltage Wafer