diff --git a/diylc/diylc-core/src/main/java/org/diylc/common/Orientation.java b/diylc/diylc-core/src/main/java/org/diylc/common/Orientation.java
index 97901e06..fe16c0ed 100644
--- a/diylc/diylc-core/src/main/java/org/diylc/common/Orientation.java
+++ b/diylc/diylc-core/src/main/java/org/diylc/common/Orientation.java
@@ -35,19 +35,9 @@ public enum Orientation {
}
public double toDegrees() {
- switch (this) {
- case DEFAULT:
- return 0;
- case _180:
- return 180;
- case _270:
- return 270;
- case _90:
- return 90;
- default:
- break;
- }
- return 0;
+ if (this == DEFAULT)
+ return 0;
+ return Integer.parseInt(name().replace("_", ""));
}
public double toRadians() {
diff --git a/diylc/diylc-core/src/main/java/org/diylc/common/Orientation45.java b/diylc/diylc-core/src/main/java/org/diylc/common/Orientation45.java
new file mode 100644
index 00000000..dffdfd8a
--- /dev/null
+++ b/diylc/diylc-core/src/main/java/org/diylc/common/Orientation45.java
@@ -0,0 +1,46 @@
+/*
+
+ 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.common;
+
+public enum Orientation45 {
+
+ DEFAULT, _45, _90, _135, _180, _225, _270, _315;
+
+ @Override
+ public String toString() {
+ if (this == DEFAULT) {
+ return "Default";
+ } else {
+ return name().replace("_", "") + " degrees clockwise";
+ }
+ }
+
+ public double toDegrees() {
+ if (this == DEFAULT)
+ return 0;
+ return Integer.parseInt(name().replace("_", ""));
+ }
+
+ public double toRadians() {
+ return Math.toRadians(toDegrees());
+ }
+}
diff --git a/diylc/diylc-core/src/main/java/org/diylc/core/images/IconLoader.java b/diylc/diylc-core/src/main/java/org/diylc/core/images/IconLoader.java
index d0f0288c..d792f9bb 100644
--- a/diylc/diylc-core/src/main/java/org/diylc/core/images/IconLoader.java
+++ b/diylc/diylc-core/src/main/java/org/diylc/core/images/IconLoader.java
@@ -72,7 +72,7 @@ public enum IconLoader {
BufferedImage img = null;
try {
img = ImageIO.read(getClass().getResourceAsStream("/images/" + name));
- } catch (IOException e) {
+ } catch (Exception e) {
Logger.getLogger(IconLoader.class).error("Couldn't find file: " + name);
}
return img;
diff --git a/diylc/diylc-core/src/main/java/org/diylc/netlist/NetlistAnalyzer.java b/diylc/diylc-core/src/main/java/org/diylc/netlist/AbstractNetlistAnalyzer.java
similarity index 99%
rename from diylc/diylc-core/src/main/java/org/diylc/netlist/NetlistAnalyzer.java
rename to diylc/diylc-core/src/main/java/org/diylc/netlist/AbstractNetlistAnalyzer.java
index 64fd22f3..58c46752 100644
--- a/diylc/diylc-core/src/main/java/org/diylc/netlist/NetlistAnalyzer.java
+++ b/diylc/diylc-core/src/main/java/org/diylc/netlist/AbstractNetlistAnalyzer.java
@@ -27,9 +27,9 @@ import java.util.Set;
import org.diylc.core.IDIYComponent;
-public abstract class NetlistAnalyzer {
+public abstract class AbstractNetlistAnalyzer {
- public NetlistAnalyzer() {
+ public AbstractNetlistAnalyzer() {
}
public List summarize(List netlists, Node preferredOutput)
diff --git a/diylc/diylc-core/src/main/java/org/diylc/netlist/Netlist.java b/diylc/diylc-core/src/main/java/org/diylc/netlist/Netlist.java
index 8c269ccf..49794072 100644
--- a/diylc/diylc-core/src/main/java/org/diylc/netlist/Netlist.java
+++ b/diylc/diylc-core/src/main/java/org/diylc/netlist/Netlist.java
@@ -23,6 +23,7 @@ import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
+import java.util.stream.Collectors;
import org.diylc.core.IDIYComponent;
@@ -59,10 +60,6 @@ public class Netlist implements Comparable {
return switchSetup;
}
- public void done() {
- Collections.sort(switchSetup);
- }
-
@Override
public int hashCode() {
final int prime = 31;
@@ -88,10 +85,15 @@ public class Netlist implements Comparable {
return true;
}
+ public String getSwitchSetupString() {
+ return getSwitchSetup().isEmpty() ? "[ ALL ]" : "[ " +
+ getSwitchSetup().stream().map(x -> x.toString()).collect(Collectors.joining(",")) + " ]";
+ }
+
@Override
public String toString() {
StringBuilder sb = new StringBuilder();
- sb.append("Switch setup: ").append(getSwitchSetup()).append("\n\n");
+ sb.append("Switch setup: ").append(getSwitchSetupString()).append("\n\n");
List list = getSortedGroups();
for (Group g : list) {
sb.append("\t").append(g).append("\n");
diff --git a/diylc/diylc-core/src/main/java/org/diylc/netlist/NetlistBuilder.java b/diylc/diylc-core/src/main/java/org/diylc/netlist/NetlistBuilder.java
index 5178513a..47aa6066 100644
--- a/diylc/diylc-core/src/main/java/org/diylc/netlist/NetlistBuilder.java
+++ b/diylc/diylc-core/src/main/java/org/diylc/netlist/NetlistBuilder.java
@@ -19,15 +19,7 @@ package org.diylc.netlist;
import java.awt.geom.Area;
import java.awt.geom.Point2D;
-import java.util.ArrayList;
-import java.util.Collection;
-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 java.util.TreeSet;
+import java.util.*;
import java.util.stream.Collectors;
import org.apache.log4j.Logger;
@@ -191,10 +183,29 @@ public class NetlistBuilder {
// sort everything alphabetically
List netlists = new ArrayList(result.keySet());
Collections.sort(netlists);
+ netlists.forEach(netlist -> cleanupRedundantSwitches(netlist.getSwitchSetup()));
return netlists;
}
+ public static void cleanupRedundantSwitches(List switchSetups) {
+ Map> positionCountMap = new HashMap<>();
+ switchSetups.forEach(s -> s.getPositions().forEach(pos -> {
+ positionCountMap.computeIfAbsent(pos.getSwitch(), k -> new HashSet()).add(pos.getPosition());
+ }));
+ // find switches that are here in all the positions, meaning that they don't affect the circuit
+ Set switchesToPrune = positionCountMap.entrySet().stream()
+ .filter(e -> e.getKey().getPositionCount() == e.getValue().size())
+ .map(x -> x.getKey())
+ .collect(Collectors.toSet());
+ switchSetups.forEach(s -> s.getPositions().removeIf(x -> switchesToPrune.contains(x.getSwitch())));
+ switchSetups.removeIf(x -> x.getPositions().isEmpty());
+ HashSet uniqueSetups = new HashSet<>(switchSetups);
+ switchSetups.clear();
+ switchSetups.addAll(uniqueSetups);
+ switchSetups.sort(Comparator.comparing(x -> x.toString()));
+ }
+
public static Netlist buildNetlist(List> components, Collection nodes,
Collection continuityAreas, Collection connections) {
ImmutableGraph graph = buildGraph(nodes, continuityAreas, connections);
diff --git a/diylc/diylc-core/src/main/java/org/diylc/netlist/Tree.java b/diylc/diylc-core/src/main/java/org/diylc/netlist/Tree.java
index 71cebbd5..96173b8b 100644
--- a/diylc/diylc-core/src/main/java/org/diylc/netlist/Tree.java
+++ b/diylc/diylc-core/src/main/java/org/diylc/netlist/Tree.java
@@ -172,10 +172,10 @@ public class Tree {
}
public String toAsciiString() {
- return String.join("
", toAscii());
+ return String.join("
", toAsciiLines());
}
- private List toAscii() {
+ private List toAsciiLines() {
if (leaf != null) {
return Arrays.asList(TreeAsciiUtil.generateString(TreeAsciiUtil.BOXH,
1) + " " + leaf.toHTML() + " " + TreeAsciiUtil.generateString(TreeAsciiUtil.BOXH, 1));
@@ -184,7 +184,7 @@ public class Tree {
List children = getOrderedChildren();
List> childrenAscii =
- children.stream().map(c -> c.toAscii()).collect(Collectors.toList());
+ children.stream().map(c -> c.toAsciiLines()).collect(Collectors.toList());
if (TreeConnectionType.Series.equals(connectionType)) {
return TreeAsciiUtil.concatenateMultiLineSerial(
diff --git a/diylc/diylc-core/src/main/java/org/diylc/presenter/InstantiationManager.java b/diylc/diylc-core/src/main/java/org/diylc/presenter/InstantiationManager.java
index a54dbc75..ef28e0cc 100644
--- a/diylc/diylc-core/src/main/java/org/diylc/presenter/InstantiationManager.java
+++ b/diylc/diylc-core/src/main/java/org/diylc/presenter/InstantiationManager.java
@@ -36,11 +36,7 @@ import org.apache.log4j.Logger;
import org.diylc.appframework.miscutils.ConfigurationManager;
import org.diylc.clipboard.ComponentTransferable;
-import org.diylc.common.ComponentType;
-import org.diylc.common.IPlugInPort;
-import org.diylc.common.Orientation;
-import org.diylc.common.OrientationHV;
-import org.diylc.common.PropertyWrapper;
+import org.diylc.common.*;
import org.diylc.core.Angle;
import org.diylc.core.CreationMethod;
import org.diylc.core.IDIYComponent;
@@ -437,6 +433,7 @@ public class InstantiationManager {
for (PropertyWrapper propertyWrapper : properties) {
if (propertyWrapper.getType().getName().equals(Orientation.class.getName())
|| propertyWrapper.getType().getName().equals(OrientationHV.class.getName())
+ || propertyWrapper.getType().getName().equals(Orientation45.class.getName())
|| propertyWrapper.getName().equalsIgnoreCase("angle")) {
angleProperty = propertyWrapper;
break;
@@ -456,6 +453,9 @@ public class InstantiationManager {
} else if (value instanceof OrientationHV) {
angleProperty.setValue(OrientationHV.values()[(((OrientationHV) value).ordinal() + 1)
% OrientationHV.values().length]);
+ } else if (value instanceof Orientation45) {
+ angleProperty.setValue(Orientation45.values()[(((Orientation45) value).ordinal() + 1)
+ % Orientation45.values().length]);
} else if (value instanceof Angle) {
Angle angle = (Angle) angleProperty.getValue();
Angle newAngle = angle.rotate(1);
diff --git a/diylc/diylc-core/src/main/java/org/diylc/presenter/Presenter.java b/diylc/diylc-core/src/main/java/org/diylc/presenter/Presenter.java
index a1f57634..a8b9c3b3 100644
--- a/diylc/diylc-core/src/main/java/org/diylc/presenter/Presenter.java
+++ b/diylc/diylc-core/src/main/java/org/diylc/presenter/Presenter.java
@@ -32,7 +32,6 @@ import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.lang.reflect.Modifier;
-import java.net.URL;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
@@ -50,7 +49,6 @@ import javax.swing.JOptionPane;
import org.apache.log4j.Logger;
import org.diylc.appframework.miscutils.IConfigListener;
import org.diylc.appframework.miscutils.IConfigurationManager;
-import org.diylc.appframework.miscutils.JarScanner;
import org.diylc.appframework.miscutils.Utils;
import org.diylc.appframework.simplemq.MessageDispatcher;
import org.diylc.appframework.update.Version;
@@ -88,7 +86,7 @@ import org.diylc.core.measures.SizeUnit;
import org.diylc.lang.LangUtil;
import org.diylc.netlist.INetlistParser;
import org.diylc.netlist.Netlist;
-import org.diylc.netlist.NetlistAnalyzer;
+import org.diylc.netlist.AbstractNetlistAnalyzer;
import org.diylc.netlist.NetlistBuilder;
import org.diylc.netlist.NetlistException;
import org.diylc.serialization.ProjectFileManager;
@@ -2030,7 +2028,7 @@ public class Presenter implements IPlugInPort, IConfigListener {
if (netlists == null)
return;
- List>> allGroups = NetlistAnalyzer.extractComponentGroups(netlists);
+ List>> allGroups = AbstractNetlistAnalyzer.extractComponentGroups(netlists);
// Find control points of all selected components and all types
Set selectedNamePrefixes = new HashSet();
if (expansionMode == ExpansionMode.SAME_TYPE) {
diff --git a/diylc/diylc-core/src/test/java/org/diylc/netlist/TreeAsciiUtilTest.java b/diylc/diylc-core/src/test/java/org/diylc/netlist/TreeAsciiUtilTest.java
index 8868d801..5c00ee58 100644
--- a/diylc/diylc-core/src/test/java/org/diylc/netlist/TreeAsciiUtilTest.java
+++ b/diylc/diylc-core/src/test/java/org/diylc/netlist/TreeAsciiUtilTest.java
@@ -56,4 +56,15 @@ public class TreeAsciiUtilTest {
// assertEquals("yy-yyy", result.get(1));
// assertEquals(" aaa", result.get(2));
}
+
+ @Test
+ public void testSimpleParallel() {
+ List result = TreeAsciiUtil.concatenateMultiLineParallel(
+ Arrays.asList(Arrays.asList("xx"), Arrays.asList("yy")));
+ assertNotNull(result);
+ assertEquals(3, result.size());
+ assertEquals(" ┌xx┐ ", result.get(0));
+ assertEquals("─┤ ├─", result.get(1));
+ assertEquals(" └yy┘ ", result.get(2));
+ }
}
diff --git a/diylc/diylc-library/src/main/java/org/diylc/components/connectivity/MultimeterProbe.java b/diylc/diylc-library/src/main/java/org/diylc/components/connectivity/MultimeterProbe.java
new file mode 100644
index 00000000..3eb6efe1
--- /dev/null
+++ b/diylc/diylc-library/src/main/java/org/diylc/components/connectivity/MultimeterProbe.java
@@ -0,0 +1,243 @@
+/*
+ *
+ * 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.components.connectivity;
+
+import java.awt.AlphaComposite;
+import java.awt.Color;
+import java.awt.Composite;
+import java.awt.Graphics2D;
+import java.awt.Shape;
+import java.awt.geom.AffineTransform;
+import java.awt.geom.Path2D;
+import java.awt.geom.Point2D;
+import java.awt.geom.Rectangle2D;
+
+import org.diylc.common.ObjectCache;
+import org.diylc.common.Orientation45;
+import org.diylc.components.AbstractTransparentComponent;
+import org.diylc.components.transform.SimpleComponentTransformer;
+import org.diylc.core.ComponentState;
+import org.diylc.core.IDIYComponent;
+import org.diylc.core.IDrawingObserver;
+import org.diylc.core.Project;
+import org.diylc.core.VisibilityPolicy;
+import org.diylc.core.annotations.BomPolicy;
+import org.diylc.core.annotations.ComponentDescriptor;
+import org.diylc.core.annotations.EditableProperty;
+import org.diylc.core.annotations.KeywordPolicy;
+import org.diylc.core.measures.Size;
+import org.diylc.core.measures.SizeUnit;
+
+@ComponentDescriptor(name = "Multimeter Probe", category = "Connectivity",
+ author = "Branislav Stojkovic", description = "Multimeter Probe", instanceNamePrefix = "Probe",
+ zOrder = IDIYComponent.TEXT, bomPolicy = BomPolicy.NEVER_SHOW, autoEdit = false,
+ keywordPolicy = KeywordPolicy.SHOW_TYPE_NAME, transformer = SimpleComponentTransformer.class,
+ enableCache = true)
+public class MultimeterProbe extends AbstractTransparentComponent {
+
+ private static final long serialVersionUID = 1L;
+
+ public static Size PROBE_DIAMETER = new Size(0.05d, SizeUnit.in);
+ public static Size PROBE_LENGTH = new Size(0.4d, SizeUnit.in);
+ public static Color PROBE_COLOR = METAL_COLOR;
+ public static Size HANDLE_DIAMETER = new Size(0.2d, SizeUnit.in);
+ public static Size HANDLE_LENGTH = new Size(0.4d, SizeUnit.in);
+ public static Color HANDLE_COLOR = Color.red;
+
+ private Point2D.Double point = new Point2D.Double(0, 0);
+ private Color handleColor = HANDLE_COLOR;
+ private Orientation45 orientation = Orientation45._315;
+
+ private transient Shape[] body = null;
+
+ public MultimeterProbe() {
+ this.alpha = (byte) (MAX_ALPHA / 2);
+ }
+
+ @Override
+ public void draw(Graphics2D g2d, ComponentState componentState, boolean outlineMode,
+ Project project, IDrawingObserver drawingObserver) {
+
+ Shape[] shapes = getBody();
+ g2d.setStroke(ObjectCache.getInstance().fetchBasicStroke(1f));
+
+ if (!outlineMode) {
+ Composite oldComposite = g2d.getComposite();
+ if (alpha < MAX_ALPHA) {
+ g2d.setComposite(
+ AlphaComposite.getInstance(AlphaComposite.SRC_OVER, 1f * alpha / MAX_ALPHA));
+ }
+ g2d.setColor(PROBE_COLOR);
+ g2d.fill(shapes[0]);
+ g2d.setColor(handleColor);
+ g2d.fill(shapes[1]);
+ g2d.setComposite(oldComposite);
+ }
+ g2d.setColor(
+ componentState == ComponentState.SELECTED || componentState == ComponentState.DRAGGING
+ ? SELECTION_COLOR
+ : handleColor.darker());
+ g2d.draw(shapes[1]);
+ g2d.setColor(
+ componentState == ComponentState.SELECTED || componentState == ComponentState.DRAGGING
+ ? SELECTION_COLOR
+ : PROBE_COLOR.darker());
+ g2d.draw(shapes[0]);
+ }
+
+ @Override
+ public void drawIcon(Graphics2D g2d, int width, int height) {
+ double probeDiameter = 2;
+ double probeLength = height / 2 + 2;
+ double handleDiameter = width / 4;
+ double handleLength = height / 2 + 2;
+ double x = 2;
+ double y = height - 2;
+ Shape probe = buildProbeShape(probeDiameter, probeLength, x, y);
+ Shape handle = buildHandleShape(probeDiameter, probeLength, handleDiameter, handleLength, x, y);
+
+ double theta = Math.PI / 4;
+ AffineTransform tx = AffineTransform.getRotateInstance(theta, x, y);
+ probe = tx.createTransformedShape(probe);
+ handle = tx.createTransformedShape(handle);
+
+ g2d.setColor(PROBE_COLOR);
+ g2d.fill(probe);
+ g2d.setColor(HANDLE_COLOR);
+ g2d.fill(handle);
+
+ g2d.setStroke(ObjectCache.getInstance().fetchBasicStroke(1));
+ g2d.setColor(PROBE_COLOR.darker());
+ g2d.draw(probe);
+ g2d.setColor(HANDLE_COLOR.darker());
+ g2d.draw(handle);
+ }
+
+ public Shape[] getBody() {
+ if (body == null) {
+ double probeDiameter = PROBE_DIAMETER.convertToPixels();
+ double probeLength = PROBE_LENGTH.convertToPixels();
+ double handleDiameter = HANDLE_DIAMETER.convertToPixels();
+ double handleLength = HANDLE_LENGTH.convertToPixels();
+ double x = point.getX();
+ double y = point.getY();
+ body = new Shape[2];
+ Path2D probe = buildProbeShape(probeDiameter, probeLength, x, y);
+ body[0] = probe;
+ Path2D handle =
+ buildHandleShape(probeDiameter, probeLength, handleDiameter, handleLength, x, y);
+ body[1] = handle;
+
+ double theta = orientation.toRadians();
+ if (theta != 0) {
+ AffineTransform tx = AffineTransform.getRotateInstance(theta, x, y);
+ body[0] = tx.createTransformedShape(body[0]);
+ body[1] = tx.createTransformedShape(body[1]);
+ }
+ }
+ return body;
+ }
+
+ private Path2D buildHandleShape(double probeDiameter, double probeLength, double handleDiameter,
+ double handleLength, double x, double y) {
+ Path2D handle = new Path2D.Double();
+ handle.moveTo(x + probeDiameter, y - probeLength);
+ handle.lineTo(x + handleDiameter / 2, y - handleLength - probeLength);
+ handle.lineTo(x - handleDiameter / 2, y - handleLength - probeLength);
+ handle.lineTo(x - probeDiameter, y - probeLength);
+ handle.moveTo(x + probeDiameter, y - probeLength);
+ handle.closePath();
+ return handle;
+ }
+
+ private Path2D buildProbeShape(double probeDiameter, double probeLength, double x, double y) {
+ Path2D probe = new Path2D.Double();
+ probe.moveTo(x, y);
+ probe.lineTo(x - probeDiameter / 2, y - probeDiameter);
+ probe.lineTo(x - probeDiameter / 2, y - probeLength);
+ probe.lineTo(x + probeDiameter / 2, y - probeLength);
+ probe.lineTo(x + probeDiameter / 2, y - probeDiameter);
+ probe.lineTo(x, y);
+ probe.closePath();
+ return probe;
+ }
+
+ @Override
+ public int getControlPointCount() {
+ return 1;
+ }
+
+ @Override
+ public boolean isControlPointSticky(int index) {
+ return true;
+ }
+
+ @Override
+ public VisibilityPolicy getControlPointVisibilityPolicy(int index) {
+ return VisibilityPolicy.NEVER;
+ }
+
+ @Override
+ public Point2D getControlPoint(int index) {
+ return point;
+ }
+
+ @Override
+ public void setControlPoint(Point2D point, int index) {
+ this.point.setLocation(point);
+ this.body = null;
+ }
+
+ @Override
+ @EditableProperty(name = "Color")
+ public Color getValue() {
+ return handleColor;
+ }
+
+ public void setValue(Color color) {
+ this.handleColor = color;
+ }
+
+ @EditableProperty(name = "Angle")
+ public Orientation45 getOrientation() {
+ return orientation;
+ }
+
+ public void setOrientation(Orientation45 orientation) {
+ this.orientation = orientation;
+ this.body = null;
+ }
+
+ @Override
+ public String getControlPointNodeName(int index) {
+ return "Tip";
+ }
+
+ @Override
+ public Rectangle2D getCachingBounds() {
+ Shape[] shapes = getBody();
+ Rectangle2D bounds1 = shapes[0].getBounds2D();
+ Rectangle2D bounds2 = shapes[1].getBounds2D();
+ double minX = Math.min(bounds1.getMinX(), bounds2.getMinX());
+ double minY = Math.min(bounds1.getMinY(), bounds2.getMinY());
+ double maxX = Math.max(bounds1.getMaxX(), bounds2.getMaxX());
+ double maxY = Math.max(bounds1.getMaxY(), bounds2.getMaxY());
+ return new Rectangle2D.Double(minX - 1, minY - 1, maxX - minX + 2, maxY - minY + 2);
+ }
+}
diff --git a/diylc/diylc-library/src/main/java/org/diylc/netlist/GuitarDiagramAnalyzer.java b/diylc/diylc-library/src/main/java/org/diylc/netlist/GuitarDiagramAnalyzer.java
index 8d178706..b0dc53f9 100644
--- a/diylc/diylc-library/src/main/java/org/diylc/netlist/GuitarDiagramAnalyzer.java
+++ b/diylc/diylc-library/src/main/java/org/diylc/netlist/GuitarDiagramAnalyzer.java
@@ -46,17 +46,9 @@ import org.diylc.components.passive.RadialFilmCapacitor;
import org.diylc.components.passive.RadialMicaCapacitor;
import org.diylc.components.passive.Resistor;
import org.diylc.core.IDIYComponent;
-import org.diylc.netlist.Netlist;
-import org.diylc.netlist.NetlistAnalyzer;
-import org.diylc.netlist.Node;
-import org.diylc.netlist.Summary;
-import org.diylc.netlist.Tree;
-import org.diylc.netlist.TreeConnectionType;
-import org.diylc.netlist.TreeException;
-import org.diylc.netlist.TreeLeaf;
import org.diylc.netlist.Tree.ITreeWalker;
-public class GuitarDiagramAnalyzer extends NetlistAnalyzer implements INetlistAnalyzer {
+public class GuitarDiagramAnalyzer extends AbstractNetlistAnalyzer implements INetlistAnalyzer {
private static Set JACK_TYPES = new HashSet();
private static Set POT_TYPES = new HashSet();
diff --git a/diylc/diylc-library/src/main/java/org/diylc/netlist/MultimeterAnalyzer.java b/diylc/diylc-library/src/main/java/org/diylc/netlist/MultimeterAnalyzer.java
new file mode 100644
index 00000000..0080ccbf
--- /dev/null
+++ b/diylc/diylc-library/src/main/java/org/diylc/netlist/MultimeterAnalyzer.java
@@ -0,0 +1,91 @@
+/*
+ *
+ * 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.List;
+import java.util.Set;
+import javax.swing.JLabel;
+import org.diylc.common.INetlistAnalyzer;
+import org.diylc.components.connectivity.MultimeterProbe;
+
+public class MultimeterAnalyzer extends AbstractNetlistAnalyzer implements INetlistAnalyzer {
+
+ @Override
+ public String getName() {
+ return "Virtual Multimeter";
+ }
+
+ @Override
+ public String getShortName() {
+ return "multimeter";
+ }
+
+ @Override
+ public String getIconName() {
+ return "Multimeter";
+ }
+
+ @Override
+ public String getFontName() {
+ return new JLabel().getFont().getName();
+ }
+
+ protected Summary summarize(Netlist netlist, Node preferredOutput) throws TreeException {
+ Tree tree = constructTree(netlist);
+
+ if (tree == null) {
+ return new Summary(netlist, "No components found connecting the two multimeter probes.");
+ }
+
+ List notes = new ArrayList();
+
+ StringBuilder sb = new StringBuilder();
+
+ String asciiTree = tree.toAsciiString();
+
+ sb.append("
")
+ .append("")
+ .append(asciiTree)
+ .append("");
+ if (!notes.isEmpty()) {
+ Collections.sort(notes);
+ sb.append("
");
+ for (String v : notes) {
+ sb.append(v).append("
");
+ }
+ }
+
+ return new Summary(netlist, sb.toString());
+ }
+
+ public Tree constructTree(Netlist netlist) throws TreeException {
+ List probeNodes = find(Set.of(MultimeterProbe.class.getCanonicalName()), null, netlist);
+
+ if (probeNodes.isEmpty()) {
+ throw new TreeException("No multimeter probes are placed on the circuit.");
+ } else if (probeNodes.size() != 2) {
+ throw new TreeException(
+ "Place exactly two multimeter probes on the circuit in order to run the analysis.");
+ } else {
+ return constructTreeBetween(netlist, probeNodes.get(0), probeNodes.get(1));
+ }
+ }
+}
diff --git a/diylc/diylc-library/src/main/java/org/diylc/netlist/SpiceAnalyzer.java b/diylc/diylc-library/src/main/java/org/diylc/netlist/SpiceAnalyzer.java
index 7ba4652b..642ca708 100644
--- a/diylc/diylc-library/src/main/java/org/diylc/netlist/SpiceAnalyzer.java
+++ b/diylc/diylc-library/src/main/java/org/diylc/netlist/SpiceAnalyzer.java
@@ -27,14 +27,8 @@ import java.util.stream.Collectors;
import org.diylc.common.INetlistAnalyzer;
import org.diylc.core.IDIYComponent;
-import org.diylc.netlist.Group;
-import org.diylc.netlist.Netlist;
-import org.diylc.netlist.NetlistAnalyzer;
-import org.diylc.netlist.Node;
-import org.diylc.netlist.Summary;
-import org.diylc.netlist.TreeException;
-public class SpiceAnalyzer extends NetlistAnalyzer implements INetlistAnalyzer {
+public class SpiceAnalyzer extends AbstractNetlistAnalyzer implements INetlistAnalyzer {
public SpiceAnalyzer() {
}
diff --git a/diylc/diylc-library/src/test/java/org/diylc/netlist/GuitarNetlistAnalyzerTests.java b/diylc/diylc-library/src/test/java/org/diylc/netlist/GuitarAbstractNetlistAnalyzerTests.java
similarity index 97%
rename from diylc/diylc-library/src/test/java/org/diylc/netlist/GuitarNetlistAnalyzerTests.java
rename to diylc/diylc-library/src/test/java/org/diylc/netlist/GuitarAbstractNetlistAnalyzerTests.java
index 0ef96c6f..3e1aff50 100644
--- a/diylc/diylc-library/src/test/java/org/diylc/netlist/GuitarNetlistAnalyzerTests.java
+++ b/diylc/diylc-library/src/test/java/org/diylc/netlist/GuitarAbstractNetlistAnalyzerTests.java
@@ -34,7 +34,7 @@ import org.diylc.netlist.Netlist;
import org.diylc.netlist.Tree;
import org.diylc.netlist.TreeException;
-public class GuitarNetlistAnalyzerTests {
+public class GuitarAbstractNetlistAnalyzerTests {
@Test
public void testOneSinglePickup() throws TreeException {
diff --git a/diylc/diylc-swing/src/main/java/org/diylc/utils/IconLoader.java b/diylc/diylc-swing/src/main/java/org/diylc/utils/IconLoader.java
index 54574782..6969ad18 100644
--- a/diylc/diylc-swing/src/main/java/org/diylc/utils/IconLoader.java
+++ b/diylc/diylc-swing/src/main/java/org/diylc/utils/IconLoader.java
@@ -71,7 +71,7 @@ public enum IconLoader {
SnapToComponents("snap_to_components.png"), SnapToNone("snap_to_none.png"), TraceProximity("trace_proximity.png"),
ComponentLarge("component_large.png"), ImportNetlist("import_netlist.png"), SplashIC("splash_ic.png"),
ComponentPreferences("component_preferences.png"), ComponentReplace("component_replace.png"), Barcode("barcode.png"),
- DocumentGerber("document_gerber.png"), DocumentX2("document_x2.png"), Export("export.png");
+ DocumentGerber("document_gerber.png"), DocumentX2("document_x2.png"), Export("export.png"), Multimeter("multimeter.png");
protected String name;
diff --git a/diylc/diylc-swing/src/main/resources/images/multimeter.png b/diylc/diylc-swing/src/main/resources/images/multimeter.png
new file mode 100644
index 00000000..4454b968
Binary files /dev/null and b/diylc/diylc-swing/src/main/resources/images/multimeter.png differ