Refactor netlist calculation

This commit is contained in:
Bane Stojkovic
2022-10-20 18:36:39 +02:00
parent 770eb87a74
commit 24995d7c3f
17 changed files with 1254 additions and 279 deletions
+1
View File
@@ -9,6 +9,7 @@
<classpathentry kind="lib" path="lib/java-http-proxy.jar"/>
<classpathentry kind="lib" path="lib/AppleJavaExtensions.jar"/>
<classpathentry kind="lib" path="lib/xstream-1.4.19.jar"/>
<classpathentry kind="lib" path="lib/guava-31.1-jre.jar"/>
<classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER"/>
<classpathentry kind="output" path="bin"/>
</classpath>
Binary file not shown.
@@ -33,6 +33,7 @@ import java.util.Arrays;
import org.diylc.core.IDIYComponent;
import org.diylc.core.Project;
import org.diylc.core.annotations.EditableProperty;
import org.diylc.core.measures.Size;
/**
* Abstract implementation of {@link IDIYComponent} that contains component name and toString.
@@ -98,6 +99,17 @@ public abstract class AbstractComponent<T> implements IDIYComponent<T> {
*/
protected int getClosestOdd(double x) {
return ((int) x / 2) * 2 + 1;
}
/**
* Returns the closest odd number, i.e. x when x is odd, or x + 1 when x is even.
*
* @param x
* @return
*/
protected int getClosestOdd(Size s) {
double x = s.convertToPixels();
return ((int) x / 2) * 2 + 1;
}
/**
@@ -0,0 +1,70 @@
/*
*
* 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
* <http://www.gnu.org/licenses/>.
*
*/
package org.diylc.netlist;
import java.awt.geom.Area;
import java.awt.geom.Point2D;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import org.diylc.utils.RTree;
/***
* A graph containing continuity areas sorted into groups that are connected together.
*
* @author bancika
*
*/
public class ContinuityGraph {
private RTree<ContinuityNode> groupTree;
private Map<Integer, List<Area>> areaGroupMap;
public ContinuityGraph(RTree<ContinuityNode> groupTree, Map<Integer, List<Area>> areaGroupMap) {
super();
this.groupTree = groupTree;
this.areaGroupMap = areaGroupMap;
}
public List<Area> findAreasFor(Point2D point) {
Optional<ContinuityNode> first = groupTree.search(point, NetlistBuilder.eps).stream()
.filter(node -> node.getArea().contains(point)).findFirst();
return first.map(node -> areaGroupMap.get(node.getGroupId())).orElse(null);
}
public static class ContinuityNode {
private Area area;
private Integer groupId;
public ContinuityNode(Area area, Integer groupId) {
super();
this.area = area;
this.groupId = groupId;
}
public Area getArea() {
return area;
}
public Integer getGroupId() {
return groupId;
}
}
}
@@ -22,6 +22,7 @@
package org.diylc.netlist;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
@@ -40,6 +41,10 @@ public class Group implements Comparable<Group> {
public Group() {
}
public Group(Collection<Node> nodes) {
this.nodes = new HashSet<Node>(nodes);
}
public Group(Node node1, Node node2) {
nodes.add(node1);
@@ -0,0 +1,255 @@
/*
*
* 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
* <http://www.gnu.org/licenses/>.
*/
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.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.stream.Collectors;
import org.diylc.core.IDIYComponent;
import org.diylc.netlist.ContinuityGraph.ContinuityNode;
import org.diylc.presenter.Connection;
import org.diylc.utils.RTree;
import com.google.common.graph.GraphBuilder;
import com.google.common.graph.ImmutableGraph;
import com.google.common.graph.ImmutableGraph.Builder;
import com.google.common.graph.Traverser;
public class NetlistBuilder {
public static final float eps = 4; // consider any nodes closer than this as connected
public static Netlist buildNetlist(List<IDIYComponent<?>> components, Collection<Node> nodes,
Collection<Area> continuityAreas, Collection<Connection> connections) {
ImmutableGraph<ProjectGraphNode> graph = buildGraph(nodes, continuityAreas, connections);
Traverser<ProjectGraphNode> traverser = Traverser.forGraph(graph);
Set<ProjectGraphNode> visited = new HashSet<ProjectGraphNode>();
ProjectGraphNode[] nodeArr = graph.nodes().toArray(new ProjectGraphNode[0]);
Netlist netlist = new Netlist(components);
while (true) {
int i = 0;
while (i < nodeArr.length && visited.contains(nodeArr[i]))
i++;
if (i == graph.nodes().size())
break;
// build a group of connected nodes
Group g = new Group();
traverser.breadthFirst(nodeArr[i]).forEach(node -> {
visited.add(node);
if (node.getNode() != null) {
g.getNodes().add(node.getNode());
}
});
if (g.getNodes().size() > 1) {
netlist.add(g);
}
}
return netlist;
}
public static ContinuityGraph buildContinuityGraph(Collection<Area> continuityAreas, Collection<Connection> connections) {
ImmutableGraph<ProjectGraphNode> graph = buildGraph(new ArrayList<Node>(), continuityAreas, connections);
Traverser<ProjectGraphNode> traverser = Traverser.forGraph(graph);
Set<ProjectGraphNode> visited = new HashSet<ProjectGraphNode>();
ProjectGraphNode[] nodeArr = graph.nodes().toArray(new ProjectGraphNode[0]);
int groupId = 1;
RTree<ContinuityNode> groupTree = new RTree<ContinuityGraph.ContinuityNode>();
Map<Integer, List<Area>> areaGroupMap = new HashMap<Integer, List<Area>>();
while (true) {
int i = 0;
while (i < nodeArr.length && visited.contains(nodeArr[i]))
i++;
if (i == graph.nodes().size())
break;
final int finalGroupId = groupId;
List<Area> areaList = new ArrayList<Area>();
traverser.breadthFirst(nodeArr[i]).forEach(node -> {
visited.add(node);
if (node.getArea() != null) {
areaList.add(node.getArea());
groupTree.insert(node.getArea(), new ContinuityNode(node.getArea(), finalGroupId));
}
});
if (!areaList.isEmpty()) {
areaGroupMap.put(groupId, areaList);
groupId++;
}
}
return new ContinuityGraph(groupTree, areaGroupMap);
}
/****
* Build an {@link ImmutableGraph} containing the following nodes: project nodes, both ends of
* each connection and one node per continuity areas. All vertices are added based on object
* proximity or intersection.
*
* @param nodes
* @param continuityAreas
* @param connections
* @return
*/
private static ImmutableGraph<ProjectGraphNode> buildGraph(Collection<Node> nodes,
Collection<Area> continuityAreas, Collection<Connection> connections) {
Map<Point2D, List<ProjectGraphNode>> nodeMap = new HashMap<Point2D, List<ProjectGraphNode>>();
RTree<ProjectGraphNode> nodeTree = new RTree<ProjectGraphNode>();
RTree<ProjectGraphNode> areaTree = new RTree<ProjectGraphNode>();
Builder<ProjectGraphNode> builder = GraphBuilder.undirected().<ProjectGraphNode>immutable();
for (Node node : nodes) {
Point2D p = snapToEps(node.getPoint2D(), eps);
ProjectGraphNode graphNode = new ProjectGraphNode(p, node);
builder.addNode(graphNode);
nodeTree.insert(new float[] {(float) (p.getX() - eps), (float) (p.getY() - eps)},
new float[] {eps * 2, eps * 2}, graphNode);
nodeMap.computeIfAbsent(p, (point) -> new ArrayList<ProjectGraphNode>())
.add(new ProjectGraphNode(p, node));
}
for (Connection conn : connections) {
Point2D p1 = snapToEps(conn.getP1(), eps);
Point2D p2 = snapToEps(conn.getP2(), eps);
ProjectGraphNode graphNode1 = new ProjectGraphNode(p1);
ProjectGraphNode graphNode2 = new ProjectGraphNode(p2);
nodeTree.insert(new float[] {(float) p1.getX() - eps, (float) p1.getY() - eps},
new float[] {eps * 2, eps * 2}, graphNode1);
nodeTree.insert(new float[] {(float) p2.getX() - eps, (float) p2.getY() - eps},
new float[] {eps * 2, eps * 2}, graphNode2);
builder.addNode(graphNode1);
builder.addNode(graphNode2);
builder.putEdge(graphNode1, graphNode2);
nodeMap.computeIfAbsent(p1, (point) -> new ArrayList<ProjectGraphNode>()).add(graphNode1);
nodeMap.computeIfAbsent(p2, (point) -> new ArrayList<ProjectGraphNode>()).add(graphNode2);
}
for (Map.Entry<Point2D, List<ProjectGraphNode>> entry : nodeMap.entrySet()) {
ProjectGraphNode[] nodeArr = entry.getValue().toArray(new ProjectGraphNode[0]);
for (int i = 0; i < nodeArr.length - 1; i++) {
for (int j = i + 1; j < nodeArr.length; j++) {
builder.putEdge(nodeArr[i], nodeArr[j]);
}
}
}
for (Area area : continuityAreas) {
ProjectGraphNode areaNode = new ProjectGraphNode(area);
builder.addNode(areaNode);
ProjectGraphNode[] nodeArr = nodeTree.search(area).stream()
.filter(graphNode -> area.contains(graphNode.getPoint())
|| area.intersects(graphNode.getPoint().getX() - eps / 2,
graphNode.getPoint().getY() - eps / 2, eps, eps))
.collect(Collectors.toList()).toArray(new ProjectGraphNode[0]);
for (ProjectGraphNode node : nodeArr) {
builder.putEdge(node, areaNode);
}
for (int i = 0; i < nodeArr.length - 1; i++) {
for (int j = i + 1; j < nodeArr.length; j++) {
builder.putEdge(nodeArr[i], nodeArr[j]);
}
}
areaTree.search(area).stream().filter(graphNode -> {
if (graphNode.getArea() != null && graphNode.getArea() != area) {
Area a = new Area(graphNode.getArea());
a.intersect(area);
return !a.isEmpty();
}
return false;
}).forEach(graphNode -> {
builder.putEdge(graphNode, areaNode);
});
areaTree.insert(area, areaNode);
}
return builder.build();
}
private static Point2D snapToEps(Point2D point, double eps) {
return new Point2D.Double(Math.round(point.getX() / eps) * eps,
Math.round(point.getY() / eps) * eps);
}
private static class ProjectGraphNode {
private Point2D point;
private Node node;
private Area area;
public ProjectGraphNode(Area area) {
super();
this.area = area;
}
public ProjectGraphNode(Point2D point) {
super();
this.point = point;
this.node = null;
}
public ProjectGraphNode(Point2D point, Node node) {
super();
this.point = point;
this.node = node;
}
public Point2D getPoint() {
return point;
}
public Node getNode() {
return node;
}
public Area getArea() {
return area;
}
}
}
@@ -21,6 +21,7 @@
*/
package org.diylc.netlist;
import java.awt.geom.Point2D;
import org.diylc.core.IDIYComponent;
/**
@@ -51,6 +52,10 @@ public class Node implements Comparable<Node> {
String[] section = component.getSectionNames(pointIndex);
return (section == null || section.length > 1 ? "" : section[0] + ".") + component.getControlPointNodeName(pointIndex);
}
public Point2D getPoint2D() {
return component.getControlPoint(pointIndex);
}
@Override
public int hashCode() {
@@ -1,87 +1,65 @@
/*
*
* 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
* <http://www.gnu.org/licenses/>.
*
*/
package org.diylc.presenter;
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 <http://www.gnu.org/licenses/>.
*/
package org.diylc.presenter;
import java.awt.geom.Point2D;
import java.util.List;
import java.util.Set;
import java.util.TreeSet;
import org.diylc.core.IDIYComponent;
import org.diylc.core.measures.Size;
public class CalcUtils {
/**
* Rounds the number to the closest grid line.
*
* @param x
* @return
*/
public static double roundToGrid(double x, Size gridSpacing) {
double grid = gridSpacing.convertToPixels();
return (Math.round(1f * x / grid) * grid);
}
public static void snapPointToGrid(Point2D point, Size gridSpacing) {
double x = roundToGrid(point.getX(), gridSpacing);
double y = roundToGrid(point.getY(), gridSpacing);
point.setLocation(x, y);
import org.diylc.core.measures.Size;
public class CalcUtils {
/**
* Rounds the number to the closest grid line.
*
* @param x
* @return
*/
public static double roundToGrid(double x, Size gridSpacing) {
double grid = gridSpacing.convertToPixels();
return (Math.round(1f * x / grid) * grid);
}
public static void snapPointToGrid(Point2D point, Size gridSpacing) {
double x = roundToGrid(point.getX(), gridSpacing);
double y = roundToGrid(point.getY(), gridSpacing);
point.setLocation(x, y);
}
public static Size findClosestMultiplierOf(Size factor, Size target, int step) {
Size ret = new Size(0d, factor.getUnit());
while (ret.convertToPixels() < target.convertToPixels())
ret = new Size(ret.getValue() + factor.getValue() * step, factor.getUnit());
return ret;
}
public static void snapPointToObjects(Point2D point, Size gridSpacing, IDIYComponent<?> component, List<IDIYComponent<?>> components) {
public static void snapPointToObjects(Point2D point, Size gridSpacing, IDIYComponent<?> component,
List<IDIYComponent<?>> components) {
for (IDIYComponent<?> c : components) {
if (c == component)
continue;
for (int i = 0; i < c.getControlPointCount(); i++)
if (c.isControlPointSticky(i) && point.distance(c.getControlPoint(i)) < gridSpacing.convertToPixels() / 2) {
if (c.isControlPointSticky(i)
&& point.distance(c.getControlPoint(i)) < gridSpacing.convertToPixels() / 2) {
point.setLocation(c.getControlPoint(i));
return;
}
}
}
}
public static void expandConnections(Set<Connection> connections) {
Set<Connection> toAdd = new TreeSet<Connection>();
for (Connection c1 : connections)
for (Connection c2 : connections) {
if (c1 == c2)
continue;
if (c1.getP1().distance(c2.getP1()) < DrawingManager.CONTROL_POINT_SIZE)
toAdd.add(new Connection(c1.getP2(), c2.getP2()));
if (c1.getP1().distance(c2.getP2()) < DrawingManager.CONTROL_POINT_SIZE)
toAdd.add(new Connection(c1.getP2(), c2.getP1()));
if (c1.getP2().distance(c2.getP1()) < DrawingManager.CONTROL_POINT_SIZE)
toAdd.add(new Connection(c1.getP1(), c2.getP2()));
if (c1.getP2().distance(c2.getP2()) < DrawingManager.CONTROL_POINT_SIZE)
toAdd.add(new Connection(c1.getP1(), c2.getP1()));
}
if (connections.addAll(toAdd))
expandConnections(connections);
}
}
}
@@ -60,6 +60,8 @@ import org.diylc.core.Theme;
import org.diylc.core.VisibilityPolicy;
import org.diylc.core.measures.Size;
import org.diylc.core.measures.SizeUnit;
import org.diylc.netlist.ContinuityGraph;
import org.diylc.netlist.NetlistBuilder;
import org.diylc.utils.Constants;
/**
@@ -94,8 +96,8 @@ public class DrawingManager {
// determine which components are invalidated when they are not in the map.
private Map<IDIYComponent<?>, ComponentState> lastDrawnStateMap;
private Area currentContinuityArea;
private List<Area> continuityAreaCache = null;
private List<Area> currentContinuityAreas;
private ContinuityGraph continuityGraphCache = null;
private Composite slotComposite = AlphaComposite.getInstance(AlphaComposite.SRC_OVER, 0.3f);
private Composite lockedComposite = AlphaComposite.getInstance(AlphaComposite.SRC_OVER, 0.3f);
@@ -464,11 +466,12 @@ public class DrawingManager {
g2d.draw(s);
}
if (currentContinuityArea != null) {
if (currentContinuityAreas != null) {
Composite oldComposite = g2d.getComposite();
g2d.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_OVER, 0.5f));
g2d.setColor(Color.green);
g2d.fill(currentContinuityArea);
for (Area a : currentContinuityAreas)
g2d.fill(a);
g2d.setComposite(oldComposite);
}
@@ -625,8 +628,8 @@ public class DrawingManager {
}
public void clearContinuityArea() {
currentContinuityArea = null;
continuityAreaCache = null;
currentContinuityAreas = null;
continuityGraphCache = null;
proximityMarkers = null;
}
@@ -682,34 +685,20 @@ public class DrawingManager {
}
public void findContinuityAreaAtPoint(Project project, Point2D p) {
if (continuityAreaCache == null)
continuityAreaCache = getContinuityAreas(project);
if (continuityGraphCache == null)
continuityGraphCache = getContinuityGraph(project);
for (Area a : continuityAreaCache) {
if (a.contains(p)) {
currentContinuityArea = a;
return;
}
}
currentContinuityArea = null;
currentContinuityAreas = continuityGraphCache.findAreasFor(p);
}
public List<Area> getContinuityAreas(Project project) {
// Find all individual continuity areas for all components
List<Area> preliminaryAreas = new ArrayList<Area>();
List<Boolean> checkBreakout = new ArrayList<Boolean>();
Set<Connection> connections = new HashSet<Connection>();
for (IDIYComponent<?> c : project.getComponents()) {
ComponentArea a = getComponentArea(c);
if (c instanceof IContinuity) {
for (int i = 0; i < c.getControlPointCount() - 1; i++)
for (int j = i + 1; j < c.getControlPointCount(); j++)
if (((IContinuity) c).arePointsConnected(i, j))
connections.add(new Connection(c.getControlPoint(i), c.getControlPoint(j)));
}
if (a == null || a.getOutlineArea() == null)
continue;
Collection<Area> positiveAreas = a.getContinuityPositiveAreas();
@@ -741,13 +730,25 @@ public class DrawingManager {
areas.add(a);
}
CalcUtils.expandConnections(connections);
AreaUtils.crunchAreas(areas, connections);
return areas;
}
public ContinuityGraph getContinuityGraph(Project project) {
Set<Connection> connections = new HashSet<Connection>();
for (IDIYComponent<?> c : project.getComponents()) {
if (c instanceof IContinuity) {
for (int i = 0; i < c.getControlPointCount() - 1; i++)
for (int j = i + 1; j < c.getControlPointCount(); j++)
if (((IContinuity) c).arePointsConnected(i, j))
connections.add(new Connection(c.getControlPoint(i), c.getControlPoint(j)));
}
}
List<Area> areas = getContinuityAreas(project);
return NetlistBuilder.buildContinuityGraph(areas, connections);
}
public List<Area> getContinuityAreaProximity(Project project, float threshold) {
List<Area> continuityAreas = getContinuityAreas(project);
Stroke s = ObjectCache.getInstance().fetchBasicStroke(threshold - 1); // value eyeballed for
@@ -83,10 +83,10 @@ import org.diylc.core.annotations.IAutoCreator;
import org.diylc.core.measures.Size;
import org.diylc.core.measures.SizeUnit;
import org.diylc.lang.LangUtil;
import org.diylc.netlist.Group;
import org.diylc.netlist.INetlistParser;
import org.diylc.netlist.Netlist;
import org.diylc.netlist.NetlistAnalyzer;
import org.diylc.netlist.NetlistBuilder;
import org.diylc.netlist.Node;
import org.diylc.netlist.Position;
import org.diylc.netlist.SwitchSetup;
@@ -3191,7 +3191,7 @@ public class Presenter implements IPlugInPort {
positions[i] = 0;
// grab continuity areas
List<Area> continuity = drawingManager.getContinuityAreas(currentProject);
List<Area> continuityAreas = drawingManager.getContinuityAreas(currentProject);
int i = switches.size() - 1;
while (i >= 0) {
@@ -3203,14 +3203,15 @@ public class Presenter implements IPlugInPort {
posList.add(new Position(switches.get(j), positions[j]));
}
List<Connection> connections = getConnections(switchPositions);
Netlist graph = constructNetlist(nodes, connections, continuity, currentProject.getComponents());
Netlist netlist = NetlistBuilder.buildNetlist(currentProject.getComponents(), nodes, continuityAreas, connections);
// merge graphs that are effectively the same
if (result.containsKey(graph)) {
result.get(graph).getSwitchSetup().add(new SwitchSetup(posList));
if (result.containsKey(netlist)) {
result.get(netlist).getSwitchSetup().add(new SwitchSetup(posList));
} else {
graph.getSwitchSetup().add(new SwitchSetup(posList));
result.put(graph, graph);
netlist.getSwitchSetup().add(new SwitchSetup(posList));
result.put(netlist, netlist);
}
// find the next combination if possible
@@ -3235,112 +3236,6 @@ public class Presenter implements IPlugInPort {
return netlists;
}
private Netlist constructNetlist(List<Node> nodes, List<Connection> connections, List<Area> continuityAreas, List<IDIYComponent<?>> components) {
Netlist netlist = new Netlist(components);
// debugging code
// StringBuilder sb = new StringBuilder();
// sb.append("Nodes:").append("\n");
// for (Node n : nodes) {
// sb.append(n.toString()).append("\n");
// }
// sb.append("Connections:").append("\n");
// for (Line2D n : connections) {
// sb.append(n.getP1()).append(":").append(n.getP2()).append("\n");
// }
// LOG.debug(sb.toString());
double t = DrawingManager.CONTROL_POINT_SIZE;
for (int i = 0; i < nodes.size() - 1; i++)
for (int j = i + 1; j < nodes.size(); j++) {
Node node1 = nodes.get(i);
Node node2 = nodes.get(j);
Point2D point1 = node1.getComponent().getControlPoint(node1.getPointIndex());
Point2D point2 = node2.getComponent().getControlPoint(node2.getPointIndex());
String commonPoint1 = node1.getComponent().getCommonPointName(node1.getPointIndex());
String commonPoint2 = node2.getComponent().getCommonPointName(node2.getPointIndex());
// try both directions
if (point1.distance(point2) < t
|| checkGraphConnection(point1, point2, connections, continuityAreas, new boolean[connections.size()])
|| checkGraphConnection(point2, point1, connections, continuityAreas, new boolean[connections.size()])
|| (commonPoint1 != null && commonPoint1.equalsIgnoreCase(commonPoint2))) {
boolean added = false;
// add to an existing vertex if possible
for (Group g : netlist.getGroups())
if (g.getNodes().contains(node1)) {
g.getNodes().add(node2);
added = true;
} else if (g.getNodes().contains(node2)) {
g.getNodes().add(node1);
added = true;
}
if (!added)
netlist.getGroups().add(new Group(node1, node2));
}
}
// merge overlapping groups if needed
boolean reduce = true;
while (reduce) {
reduce = false;
List<Group> groups = netlist.getSortedGroups();
Iterator<Group> i = groups.iterator();
while (i.hasNext()) {
Group g1 = i.next();
for (Group g2 : groups) {
if (g1 != g2 && !Collections.disjoint(g1.getNodes(), g2.getNodes())) {
i.remove();
g2.getNodes().addAll(g1.getNodes());
reduce = true;
break;
}
}
}
if (reduce) {
netlist.getGroups().clear();
netlist.getGroups().addAll(groups);
}
}
Collections.sort(netlist.getSwitchSetup());
return netlist;
}
private boolean checkGraphConnection(Point2D point1, Point2D point2, List<Connection> connections,
List<Area> continuityAreas, boolean[] visited) {
double t = DrawingManager.CONTROL_POINT_SIZE;
if (point1.distance(point2) < t)
return true;
for (Area a : continuityAreas) {
if (a.contains(point1) && a.contains(point2))
return true;
}
for (int i = 0; i < connections.size(); i++) {
if (visited[i])
continue;
Connection c = connections.get(i);
if (point1.distance(c.getP1()) < t) {
visited[i] = true;
if (checkGraphConnection(c.getP2(), point2, connections, continuityAreas, visited))
return true;
}
if (point1.distance(c.getP2()) < t) {
visited[i] = true;
if (checkGraphConnection(c.getP1(), point2, connections, continuityAreas, visited))
return true;
}
}
return false;
}
@SuppressWarnings({"unchecked", "unlikely-arg-type"})
private List<Connection> getConnections(Map<ISwitch, Integer> switchPositions) {
Set<Connection> connections = new TreeSet<Connection>();
@@ -3366,8 +3261,6 @@ public class Presenter implements IPlugInPort {
}
}
CalcUtils.expandConnections(connections);
return new ArrayList<Connection>(connections);
}
@@ -4004,7 +4004,11 @@
<changes>
<org.diylc.appframework.update.Change>
<changeType>NEW_FEATURE</changeType>
<description>Added 'Rotary Switch (Sealed)' component</description>
<description>Added 'Rotary Switch (Sealed)' component in several switching configurations</description>
</org.diylc.appframework.update.Change>
<org.diylc.appframework.update.Change>
<changeType>NEW_FEATURE</changeType>
<description>Added 'Rotary Switch (Open)' component in several switching configurations</description>
</org.diylc.appframework.update.Change>
<org.diylc.appframework.update.Change>
<changeType>NEW_FEATURE</changeType>
@@ -4018,10 +4022,18 @@
<changeType>NEW_FEATURE</changeType>
<description>Ability to rotate 'Lever Switch' and 'Schaller Megaswitch' by any angle</description>
</org.diylc.appframework.update.Change>
<org.diylc.appframework.update.Change>
<changeType>NEW_FEATURE</changeType>
<description>DIYLC Netlists can be generated including or excluding switching configurations</description>
</org.diylc.appframework.update.Change>
<org.diylc.appframework.update.Change>
<changeType>IMPROVEMENT</changeType>
<description>Ability to rotate all guitar/bass pickups through the 'Transform Selection' feature or keyboard shortcut</description>
</org.diylc.appframework.update.Change>
<org.diylc.appframework.update.Change>
<changeType>IMPROVEMENT</changeType>
<description>Improved the speed and accuracy of netlist calculation</description>
</org.diylc.appframework.update.Change>
</changes>
<url>https://github.com/bancika/diy-layout-creator/releases</url>
</org.diylc.appframework.update.Version>
@@ -46,5 +46,36 @@ public class Pair<A, B> {
public void setSecond(B second) {
this.second = second;
}
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + ((first == null) ? 0 : first.hashCode());
result = prime * result + ((second == null) ? 0 : second.hashCode());
return result;
}
@Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (obj == null)
return false;
if (getClass() != obj.getClass())
return false;
Pair other = (Pair) obj;
if (first == null) {
if (other.first != null)
return false;
} else if (!first.equals(other.first))
return false;
if (second == null) {
if (other.second != null)
return false;
} else if (!second.equals(other.second))
return false;
return true;
}
}
@@ -0,0 +1,678 @@
package org.diylc.utils;
import java.awt.geom.Area;
import java.awt.geom.Point2D;
import java.awt.geom.Rectangle2D;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.ListIterator;
import java.util.Map;
import java.util.Queue;
import java.util.Set;
/**
* Implementation of an arbitrary-dimension RTree. Based on R-Trees: A Dynamic
* Index Structure for Spatial Searching (Antonn Guttmann, 1984)
* <p>
* This class is not thread-safe.
* <p>
* Copyright 2010 Russ Weeks rweeks@newbrightidea.com Licensed under the GNU
* LGPL License details here: http://www.gnu.org/licenses/lgpl-3.0.txt
*
* @param <T> the type of entry to store in this RTree.
*/
public class RTree<T> {
public enum SeedPicker {LINEAR, QUADRATIC}
private final int maxEntries;
private final int minEntries;
private final int numDims;
private final float[] pointDims;
private final SeedPicker seedPicker;
private Node root;
private volatile int size;
/**
* Creates a new RTree.
*
* @param maxEntries maximum number of entries per node
* @param minEntries minimum number of entries per node (except for the root node)
* @param numDims the number of dimensions of the RTree.
*/
public RTree(int maxEntries, int minEntries, int numDims, SeedPicker seedPicker) {
assert (minEntries <= (maxEntries / 2));
this.numDims = numDims;
this.maxEntries = maxEntries;
this.minEntries = minEntries;
this.seedPicker = seedPicker;
pointDims = new float[numDims];
root = buildRoot(true);
}
public RTree(int maxEntries, int minEntries, int numDims) {
this(maxEntries, minEntries, numDims, SeedPicker.LINEAR);
}
private Node buildRoot(boolean asLeaf) {
float[] initCoords = new float[numDims];
float[] initDimensions = new float[numDims];
for (int i = 0; i < this.numDims; i++) {
initCoords[i] = (float) Math.sqrt(Float.MAX_VALUE);
initDimensions[i] = -2.0f * (float) Math.sqrt(Float.MAX_VALUE);
}
return new Node(initCoords, initDimensions, asLeaf);
}
/**
* Builds a new RTree using default parameters: maximum 50 entries per node
* minimum 2 entries per node 2 dimensions
*/
public RTree() {
this(50, 2, 2, SeedPicker.LINEAR);
}
/**
* @return the maximum number of entries per node
*/
public int getMaxEntries() {
return maxEntries;
}
/**
* @return the minimum number of entries per node for all nodes except the
* root.
*/
public int getMinEntries() {
return minEntries;
}
/**
* @return the number of dimensions of the tree
*/
public int getNumDims() {
return numDims;
}
/**
* @return the number of items in this tree.
*/
public int size() {
return size;
}
/**
* Searches the RTree for objects overlapping with the given rectangle.
*
* @param coords the corner of the rectangle that is the lower bound of every
* dimension (eg. the top-left corner)
* @param dimensions the dimensions of the rectangle.
* @return a list of objects whose rectangles overlap with the given
* rectangle.
*/
public List<T> search(float[] coords, float[] dimensions) {
assert (coords.length == numDims);
assert (dimensions.length == numDims);
LinkedList<T> results = new LinkedList<T>();
search(coords, dimensions, root, results);
return results;
}
public List<T> search(Area area) {
Rectangle2D bounds2D = area.getBounds2D();
return search(new float[] { (float) bounds2D.getMinX(), (float) bounds2D.getMinY() } , new float[] { (float) bounds2D.getWidth(), (float) bounds2D.getHeight() });
}
public List<T> search(Point2D point, double t) {
return search(new float[] { (float) (point.getX() - t / 2), (float) (point.getY() - t / 2) },
new float[] { (float) t, (float) t });
}
private void search(float[] coords, float[] dimensions, Node n,
LinkedList<T> results) {
if (n.leaf) {
for (Node e : n.children) {
if (isOverlap(coords, dimensions, e.coords, e.dimensions)) {
results.add(((Entry) e).entry);
}
}
} else {
for (Node c : n.children) {
if (isOverlap(coords, dimensions, c.coords, c.dimensions)) {
search(coords, dimensions, c, results);
}
}
}
}
/**
* Deletes the entry associated with the given rectangle from the RTree
*
* @param coords the corner of the rectangle that is the lower bound in every
* dimension
* @param dimensions the dimensions of the rectangle
* @param entry the entry to delete
* @return true iff the entry was deleted from the RTree.
*/
public boolean delete(float[] coords, float[] dimensions, T entry) {
assert (coords.length == numDims);
assert (dimensions.length == numDims);
Node l = findLeaf(root, coords, dimensions, entry);
if (l == null) {
System.out.println("WTF?");
findLeaf(root, coords, dimensions, entry);
}
assert (l != null) : "Could not find leaf for entry to delete";
assert (l.leaf) : "Entry is not found at leaf?!?";
ListIterator<Node> li = l.children.listIterator();
T removed = null;
while (li.hasNext()) {
@SuppressWarnings("unchecked")
Entry e = (Entry) li.next();
if (e.entry.equals(entry)) {
removed = e.entry;
li.remove();
break;
}
}
if (removed != null) {
condenseTree(l);
size--;
}
if (size == 0) {
root = buildRoot(true);
}
return (removed != null);
}
public boolean delete(float[] coords, T entry) {
return delete(coords, pointDims, entry);
}
private Node findLeaf(Node n, float[] coords, float[] dimensions, T entry) {
if (n.leaf) {
for (Node c : n.children) {
if (((Entry) c).entry.equals(entry)) {
return n;
}
}
return null;
} else {
for (Node c : n.children) {
if (isOverlap(c.coords, c.dimensions, coords, dimensions)) {
Node result = findLeaf(c, coords, dimensions, entry);
if (result != null) {
return result;
}
}
}
return null;
}
}
private void condenseTree(Node n) {
Set<Node> q = new HashSet<Node>();
while (n != root) {
if (n.leaf && (n.children.size() < minEntries)) {
q.addAll(n.children);
n.parent.children.remove(n);
} else if (!n.leaf && (n.children.size() < minEntries)) {
// probably a more efficient way to do this...
LinkedList<Node> toVisit = new LinkedList<Node>(n.children);
while (!toVisit.isEmpty()) {
Node c = toVisit.pop();
if (c.leaf) {
q.addAll(c.children);
} else {
toVisit.addAll(c.children);
}
}
n.parent.children.remove(n);
} else {
tighten(n);
}
n = n.parent;
}
if (root.children.size() == 0) {
root = buildRoot(true);
} else if ((root.children.size() == 1) && (!root.leaf)) {
root = root.children.get(0);
root.parent = null;
} else {
tighten(root);
}
for (Node ne : q) {
@SuppressWarnings("unchecked")
Entry e = (Entry) ne;
insert(e.coords, e.dimensions, e.entry);
}
size -= q.size();
}
/**
* Empties the RTree
*/
public void clear() {
root = buildRoot(true);
// let the GC take care of the rest.
}
/**
* Inserts the given entry into the RTree, associated with the given
* rectangle.
*
* @param coords the corner of the rectangle that is the lower bound in every
* dimension
* @param dimensions the dimensions of the rectangle
* @param entry the entry to insert
*/
public void insert(float[] coords, float[] dimensions, T entry) {
assert (coords.length == numDims);
assert (dimensions.length == numDims);
Entry e = new Entry(coords, dimensions, entry);
Node l = chooseLeaf(root, e);
l.children.add(e);
size++;
e.parent = l;
if (l.children.size() > maxEntries) {
Node[] splits = splitNode(l);
adjustTree(splits[0], splits[1]);
} else {
adjustTree(l, null);
}
}
public void insert(Area a, T entry) {
Rectangle2D bounds2d = a.getBounds2D();
insert(new float[] {(float) bounds2d.getMinX(), (float) bounds2d.getMinY()},
new float[] {(float) bounds2d.getWidth(), (float) bounds2d.getHeight()}, entry);
}
public void insert(Point2D p, double t, T entry) {
insert(new float[] {(float) (p.getX() - t / 2), (float) (p.getY() - t /2) },
new float[] {(float) t, (float) t }, entry);
}
/**
* Convenience method for inserting a point
*
* @param coords
* @param entry
*/
public void insert(float[] coords, T entry) {
insert(coords, pointDims, entry);
}
public void visit(NodeVisitor<T> nv) {
float[] coordBuf = new float[numDims];
float[] dimBuf = new float[numDims];
Queue<Node> toVisit = new LinkedList<>();
Map<Node,Integer> nodeDepths = new HashMap<>();
nodeDepths.put(root, 0);
toVisit.add(root);
while (!toVisit.isEmpty()) {
Node currentNode = toVisit.remove();
if (currentNode.parent != null) {
nodeDepths.put(currentNode, nodeDepths.get(currentNode.parent) + 1);
}
System.arraycopy(currentNode.coords, 0, coordBuf, 0, numDims);
System.arraycopy(currentNode.dimensions, 0, dimBuf, 0, numDims);
if (currentNode instanceof RTree.Entry) {
nv.visit(nodeDepths.get(currentNode), coordBuf, dimBuf, ((Entry)currentNode).entry);
} else {
nv.visit(nodeDepths.get(currentNode), coordBuf, dimBuf, null);
}
toVisit.addAll(currentNode.children);
}
}
public interface NodeVisitor<V> {
public void visit(int depth, float[] coords, float[] dimensions, V value);
}
private void adjustTree(Node n, Node nn) {
if (n == root) {
if (nn != null) {
// build new root and add children.
root = buildRoot(false);
root.children.add(n);
n.parent = root;
root.children.add(nn);
nn.parent = root;
}
tighten(root);
return;
}
tighten(n);
if (nn != null) {
tighten(nn);
if (n.parent.children.size() > maxEntries) {
Node[] splits = splitNode(n.parent);
adjustTree(splits[0], splits[1]);
}
}
if (n.parent != null) {
adjustTree(n.parent, null);
}
}
private Node[] splitNode(Node n) {
// TODO: this class probably calls "tighten" a little too often.
// For instance the call at the end of the "while (!cc.isEmpty())" loop
// could be modified and inlined because it's only adjusting for the addition
// of a single node. Left as-is for now for readability.
@SuppressWarnings("unchecked")
Node[] nn = new RTree.Node[]
{n, new Node(n.coords, n.dimensions, n.leaf)};
nn[1].parent = n.parent;
if (nn[1].parent != null) {
nn[1].parent.children.add(nn[1]);
}
LinkedList<Node> cc = new LinkedList<Node>(n.children);
n.children.clear();
Node[] ss = seedPicker == SeedPicker.LINEAR ? lPickSeeds(cc) : qPickSeeds(cc);
nn[0].children.add(ss[0]);
nn[1].children.add(ss[1]);
tighten(nn);
while (!cc.isEmpty()) {
if ((nn[0].children.size() >= minEntries)
&& (nn[1].children.size() + cc.size() == minEntries)) {
nn[1].children.addAll(cc);
cc.clear();
tighten(nn); // Not sure this is required.
return nn;
} else if ((nn[1].children.size() >= minEntries)
&& (nn[0].children.size() + cc.size() == minEntries)) {
nn[0].children.addAll(cc);
cc.clear();
tighten(nn); // Not sure this is required.
return nn;
}
Node c = seedPicker == SeedPicker.LINEAR ? lPickNext(cc) : qPickNext(cc, nn);
Node preferred;
float e0 = getRequiredExpansion(nn[0].coords, nn[0].dimensions, c);
float e1 = getRequiredExpansion(nn[1].coords, nn[1].dimensions, c);
if (e0 < e1) {
preferred = nn[0];
} else if (e0 > e1) {
preferred = nn[1];
} else {
float a0 = getArea(nn[0].dimensions);
float a1 = getArea(nn[1].dimensions);
if (a0 < a1) {
preferred = nn[0];
} else if (e0 > a1) {
preferred = nn[1];
} else {
if (nn[0].children.size() < nn[1].children.size()) {
preferred = nn[0];
} else if (nn[0].children.size() > nn[1].children.size()) {
preferred = nn[1];
} else {
preferred = nn[(int) Math.round(Math.random())];
}
}
}
preferred.children.add(c);
tighten(preferred);
}
return nn;
}
// Implementation of Quadratic PickSeeds
private RTree<T>.Node[] qPickSeeds(LinkedList<Node> nn) {
@SuppressWarnings("unchecked")
RTree<T>.Node[] bestPair = new RTree.Node[2];
float maxWaste = -1.0f * Float.MAX_VALUE;
for (Node n1 : nn) {
for (Node n2 : nn) {
if (n1 == n2) continue;
float n1a = getArea(n1.dimensions);
float n2a = getArea(n2.dimensions);
float ja = 1.0f;
for (int i = 0; i < numDims; i++) {
float jc0 = Math.min(n1.coords[i], n2.coords[i]);
float jc1 = Math.max(n1.coords[i] + n1.dimensions[i], n2.coords[i] + n2.dimensions[i]);
ja *= (jc1 - jc0);
}
float waste = ja - n1a - n2a;
if (waste > maxWaste) {
maxWaste = waste;
bestPair[0] = n1;
bestPair[1] = n2;
}
}
}
nn.remove(bestPair[0]);
nn.remove(bestPair[1]);
return bestPair;
}
/**
* Implementation of QuadraticPickNext
*
* @param cc the children to be divided between the new nodes, one item will be removed from this list.
* @param nn the candidate nodes for the children to be added to.
*/
private Node qPickNext(LinkedList<Node> cc, Node[] nn) {
float maxDiff = -1.0f * Float.MAX_VALUE;
Node nextC = null;
for (Node c : cc) {
float n0Exp = getRequiredExpansion(nn[0].coords, nn[0].dimensions, c);
float n1Exp = getRequiredExpansion(nn[1].coords, nn[1].dimensions, c);
float diff = Math.abs(n1Exp - n0Exp);
if (diff > maxDiff) {
maxDiff = diff;
nextC = c;
}
}
assert (nextC != null) : "No node selected from qPickNext";
cc.remove(nextC);
return nextC;
}
// Implementation of LinearPickSeeds
private RTree<T>.Node[] lPickSeeds(LinkedList<Node> nn) {
@SuppressWarnings("unchecked")
RTree<T>.Node[] bestPair = new RTree.Node[2];
boolean foundBestPair = false;
float bestSep = 0.0f;
for (int i = 0; i < numDims; i++) {
float dimLb = Float.MAX_VALUE, dimMinUb = Float.MAX_VALUE;
float dimUb = -1.0f * Float.MAX_VALUE, dimMaxLb = -1.0f * Float.MAX_VALUE;
Node nMaxLb = null, nMinUb = null;
for (Node n : nn) {
if (n.coords[i] < dimLb) {
dimLb = n.coords[i];
}
if (n.dimensions[i] + n.coords[i] > dimUb) {
dimUb = n.dimensions[i] + n.coords[i];
}
if (n.coords[i] > dimMaxLb) {
dimMaxLb = n.coords[i];
nMaxLb = n;
}
if (n.dimensions[i] + n.coords[i] < dimMinUb) {
dimMinUb = n.dimensions[i] + n.coords[i];
nMinUb = n;
}
}
float sep = (nMaxLb == nMinUb) ? -1.0f :
Math.abs((dimMinUb - dimMaxLb) / (dimUb - dimLb));
if (sep >= bestSep) {
bestPair[0] = nMaxLb;
bestPair[1] = nMinUb;
bestSep = sep;
foundBestPair = true;
}
}
// In the degenerate case where all points are the same, the above
// algorithm does not find a best pair. Just pick the first 2
// children.
if (!foundBestPair) {
bestPair = new RTree.Node[]{nn.get(0), nn.get(1)};
}
nn.remove(bestPair[0]);
nn.remove(bestPair[1]);
return bestPair;
}
/**
* Implementation of LinearPickNext
*
* @param cc the children to be divided between the new nodes, one item will be removed from this list.
*/
private Node lPickNext(LinkedList<Node> cc) {
return cc.pop();
}
private void tighten(Node... nodes) {
assert (nodes.length >= 1) : "Pass some nodes to tighten!";
for (Node n : nodes) {
assert (n.children.size() > 0) : "tighten() called on empty node!";
float[] minCoords = new float[numDims];
float[] maxCoords = new float[numDims];
for (int i = 0; i < numDims; i++) {
minCoords[i] = Float.MAX_VALUE;
maxCoords[i] = -1.0f * Float.MAX_VALUE;
for (Node c : n.children) {
// we may have bulk-added a bunch of children to a node (eg. in
// splitNode)
// so here we just enforce the child->parent relationship.
c.parent = n;
if (c.coords[i] < minCoords[i]) {
minCoords[i] = c.coords[i];
}
if ((c.coords[i] + c.dimensions[i]) > maxCoords[i]) {
maxCoords[i] = (c.coords[i] + c.dimensions[i]);
}
}
}
for (int i = 0; i < numDims; i++) {
// Convert max coords to dimensions
maxCoords[i] -= minCoords[i];
}
System.arraycopy(minCoords, 0, n.coords, 0, numDims);
System.arraycopy(maxCoords, 0, n.dimensions, 0, numDims);
}
}
private RTree<T>.Node chooseLeaf(RTree<T>.Node n, RTree<T>.Entry e) {
if (n.leaf) {
return n;
}
float minInc = Float.MAX_VALUE;
Node next = null;
for (RTree<T>.Node c : n.children) {
float inc = getRequiredExpansion(c.coords, c.dimensions, e);
if (inc < minInc) {
minInc = inc;
next = c;
} else if (inc == minInc) {
float curArea = 1.0f;
float thisArea = 1.0f;
for (int i = 0; i < c.dimensions.length; i++) {
curArea *= next.dimensions[i];
thisArea *= c.dimensions[i];
}
if (thisArea < curArea) {
next = c;
}
}
}
return chooseLeaf(next, e);
}
/**
* Returns the increase in area necessary for the given rectangle to cover the
* given entry.
*/
private float getRequiredExpansion(float[] coords, float[] dimensions, Node e) {
float area = getArea(dimensions);
float[] deltas = new float[dimensions.length];
for (int i = 0; i < deltas.length; i++) {
if (coords[i] + dimensions[i] < e.coords[i] + e.dimensions[i]) {
deltas[i] = e.coords[i] + e.dimensions[i] - coords[i] - dimensions[i];
} else if (coords[i] + dimensions[i] > e.coords[i] + e.dimensions[i]) {
deltas[i] = coords[i] - e.coords[i];
}
}
float expanded = 1.0f;
for (int i = 0; i < dimensions.length; i++) {
expanded *= dimensions[i] + deltas[i];
}
return (expanded - area);
}
private float getArea(float[] dimensions) {
float area = 1.0f;
for (int i = 0; i < dimensions.length; i++) {
area *= dimensions[i];
}
return area;
}
private boolean isOverlap(float[] scoords, float[] sdimensions,
float[] coords, float[] dimensions) {
final float FUDGE_FACTOR = 1.001f;
for (int i = 0; i < scoords.length; i++) {
boolean overlapInThisDimension = false;
if (scoords[i] == coords[i]) {
overlapInThisDimension = true;
} else if (scoords[i] < coords[i]) {
if (scoords[i] + FUDGE_FACTOR * sdimensions[i] >= coords[i]) {
overlapInThisDimension = true;
}
} else if (scoords[i] > coords[i]) {
if (coords[i] + FUDGE_FACTOR * dimensions[i] >= scoords[i]) {
overlapInThisDimension = true;
}
}
if (!overlapInThisDimension) {
return false;
}
}
return true;
}
private class Node {
final float[] coords;
final float[] dimensions;
final LinkedList<Node> children;
final boolean leaf;
Node parent;
private Node(float[] coords, float[] dimensions, boolean leaf) {
this.coords = new float[coords.length];
this.dimensions = new float[dimensions.length];
System.arraycopy(coords, 0, this.coords, 0, coords.length);
System.arraycopy(dimensions, 0, this.dimensions, 0, dimensions.length);
this.leaf = leaf;
children = new LinkedList<Node>();
}
}
private class Entry extends Node {
final T entry;
public Entry(float[] coords, float[] dimensions, T entry) {
// an entry isn't actually a leaf (its parent is a leaf)
// but all the algorithms should stop at the first leaf they encounter,
// so this little hack shouldn't be a problem.
super(coords, dimensions, true);
this.entry = entry;
}
public String toString() {
return "Entry: " + entry;
}
}
}
@@ -70,7 +70,7 @@ public class Eyelet extends AbstractComponent<String> {
}
int diameter = getClosestOdd((int) size.convertToPixels());
int holeDiameter = getClosestOdd((int) holeSize.convertToPixels());
g2d.setStroke(ObjectCache.getInstance().fetchZoomableStroke(1f));
g2d.setStroke(ObjectCache.getInstance().fetchBasicStroke(1f));
g2d.setColor(color);
drawingObserver.startTrackingContinuityArea(true);
g2d.fillOval((int)(point.getX() - diameter / 2), (int)(point.getY() - diameter / 2), diameter, diameter);
@@ -123,7 +123,7 @@ public class ActionFactory {
public ImportAction createImportAction(IPlugInPort plugInPort, ISwingUI swingUI) {
return new ImportAction(plugInPort, swingUI);
}
public ImportNetlistAction createImportNetlistAction(IPlugInPort plugInPort, ISwingUI swingUI) {
return new ImportNetlistAction(plugInPort, swingUI);
}
@@ -275,7 +275,7 @@ public class ActionFactory {
String defaultValue) {
return new ToggleAction(title, configKey, groupName, defaultValue, null);
}
public ToggleAction createToggleAction(String title, String configKey, String groupName,
String defaultValue, Icon icon) {
return new ToggleAction(title, configKey, groupName, defaultValue, icon);
@@ -293,16 +293,16 @@ public class ActionFactory {
return new RenumberAction(plugInPort, xAxisFirst);
}
public GenerateNetlistAction createGenerateNetlistAction(IPlugInPort plugInPort,
ISwingUI swingUI) {
return new GenerateNetlistAction(plugInPort, swingUI);
public GenerateNetlistAction createGenerateNetlistAction(IPlugInPort plugInPort, ISwingUI swingUI,
boolean includeSwitches) {
return new GenerateNetlistAction(plugInPort, swingUI, includeSwitches);
}
public SummarizeNetlistAction createSummarizeNetlistAction(IPlugInPort plugInPort,
ISwingUI swingUI, INetlistAnalyzer summarizer) {
return new SummarizeNetlistAction(plugInPort, swingUI, summarizer);
}
public CheckProximityAction createCheckProximityAction(IPlugInPort plugInPort, ISwingUI swingUI) {
return new CheckProximityAction(plugInPort, swingUI);
}
@@ -420,7 +420,7 @@ public class ActionFactory {
public void showMessage(String message, String title, int messageType) {
JOptionPane.showMessageDialog(null, message, title, messageType);
}
@Override
public String showInputDialog(String message, String title) {
return JOptionPane.showInputDialog(null, message, title, JOptionPane.QUESTION_MESSAGE);
@@ -459,8 +459,9 @@ public class ActionFactory {
presenter.loadProjectFromFile(file.getAbsolutePath());
// Grab all components and paste them into the main
// presenter
plugInPort.pasteComponents(ComponentTransferableFactory.getInstance()
.build(presenter.getCurrentProject().getComponents(), presenter.getCurrentProject().getGroups()), false, false);
plugInPort.pasteComponents(ComponentTransferableFactory.getInstance().build(
presenter.getCurrentProject().getComponents(),
presenter.getCurrentProject().getGroups()), false, false);
// Cleanup components in the temp presenter, don't need
// them anymore
presenter.selectAll(0);
@@ -480,7 +481,7 @@ public class ActionFactory {
}
}
}
public static class ImportNetlistAction extends AbstractAction {
private static final long serialVersionUID = 1L;
@@ -495,13 +496,14 @@ public class ActionFactory {
public ImportNetlistAction(IPlugInPort plugInPort, ISwingUI swingUI) {
super();
this.plugInPort = plugInPort;
this.swingUI = swingUI;
this.swingUI = swingUI;
putValue(AbstractAction.NAME, "Import Netlist");
putValue(AbstractAction.SMALL_ICON, IconLoader.ImportNetlist.getIcon());
this.parserDefinitions = plugInPort.getNetlistParserDefinitions();
this.extensions = parserDefinitions.stream().map(x -> x.getFileExt()).collect(Collectors.toList());
this.parserDefinitions = plugInPort.getNetlistParserDefinitions();
this.extensions =
parserDefinitions.stream().map(x -> x.getFileExt()).collect(Collectors.toList());
this.filter = new FileFilter() {
@Override
@@ -530,12 +532,12 @@ public class ActionFactory {
@Override
public void actionPerformed(ActionEvent e) {
LOG.info("ImportNetlistAction triggered");
final File file = DialogFactory.getInstance().showOpenDialog(this.filter,
null, this.extensions.get(0), null);
final File file = DialogFactory.getInstance().showOpenDialog(this.filter, null,
this.extensions.get(0), null);
// TODO: identity by extension
final INetlistParser parser = parserDefinitions.get(0);
if (file != null) {
swingUI.executeBackgroundTask(new ITask<List<ParsedNetlistEntry>>() {
@@ -543,7 +545,8 @@ public class ActionFactory {
public List<ParsedNetlistEntry> doInBackground() throws Exception {
LOG.debug("Importing netlist from " + file.getAbsolutePath());
List<String> outputWarnings = new ArrayList<String>();
List<ParsedNetlistEntry> entries = parser.parseFile(file.getAbsolutePath(), outputWarnings);
List<ParsedNetlistEntry> entries =
parser.parseFile(file.getAbsolutePath(), outputWarnings);
if (!outputWarnings.isEmpty())
LOG.warn("Parsing produced warnings:\n" + String.join("\n", outputWarnings));
return entries;
@@ -552,17 +555,22 @@ public class ActionFactory {
@Override
public void complete(List<ParsedNetlistEntry> entries) {
try {
NetlistImportDialog dialog = DialogFactory.getInstance().createNetlistImportDialog(plugInPort,entries);
NetlistImportDialog dialog =
DialogFactory.getInstance().createNetlistImportDialog(plugInPort, entries);
dialog.setVisible(true);
if (ButtonDialog.OK.equals(dialog.getSelectedButtonCaption())) {
List<String> outputWarnings = new ArrayList<String>();
Map<String, Class<?>> results = dialog.getResults();
List<ParsedNetlistComponent> parsedComponents = entries.stream().map(entry ->
new ParsedNetlistComponent(results.get(entry.getRawType()), entry.getValues())).collect(Collectors.toList());
List<IDIYComponent<?>> components = parser.generateComponents(parsedComponents, outputWarnings);
List<ParsedNetlistComponent> parsedComponents = entries.stream()
.map(entry -> new ParsedNetlistComponent(results.get(entry.getRawType()),
entry.getValues()))
.collect(Collectors.toList());
List<IDIYComponent<?>> components =
parser.generateComponents(parsedComponents, outputWarnings);
if (!outputWarnings.isEmpty())
LOG.warn("Component creation produced warnings:\n" + String.join("\n", outputWarnings));
plugInPort.pasteComponents(new ComponentTransferable(components), false, false);
LOG.warn("Component creation produced warnings:\n"
+ String.join("\n", outputWarnings));
plugInPort.pasteComponents(new ComponentTransferable(components), false, false);
}
} catch (Exception e) {
swingUI.showMessage("Could not import netlist file: " + e.getMessage(), "Error",
@@ -962,7 +970,7 @@ public class ActionFactory {
LOG.debug("Exporting variants to " + file.getAbsolutePath());
try {
BufferedOutputStream out = new BufferedOutputStream(new FileOutputStream(file));
BufferedOutputStream out = new BufferedOutputStream(new FileOutputStream(file));
ProjectFileManager.xStreamSerializer.toXML(variantPkg, out);
out.close();
LOG.info("Exported variants succesfully");
@@ -1117,7 +1125,7 @@ public class ActionFactory {
LOG.debug("Exporting variants to " + file.getAbsolutePath());
try {
BufferedOutputStream out = new BufferedOutputStream(new FileOutputStream(file));
BufferedOutputStream out = new BufferedOutputStream(new FileOutputStream(file));
ProjectFileManager.xStreamSerializer.toXML(variantPkg, out);
out.close();
LOG.info("Exported building blocks succesfully");
@@ -1237,8 +1245,8 @@ public class ActionFactory {
@Override
public void actionPerformed(ActionEvent e) {
LOG.info("Cut triggered");
clipboard.setContents(
ComponentTransferableFactory.getInstance().build(plugInPort.getSelectedComponents(), plugInPort.getCurrentProject().getGroups()),
clipboard.setContents(ComponentTransferableFactory.getInstance()
.build(plugInPort.getSelectedComponents(), plugInPort.getCurrentProject().getGroups()),
clipboardOwner);
plugInPort.deleteSelectedComponents();
}
@@ -1297,8 +1305,8 @@ public class ActionFactory {
@Override
public void actionPerformed(ActionEvent e) {
LOG.info("Copy triggered");
clipboard.setContents(
ComponentTransferableFactory.getInstance().build(plugInPort.getSelectedComponents(), plugInPort.getCurrentProject().getGroups()),
clipboard.setContents(ComponentTransferableFactory.getInstance()
.build(plugInPort.getSelectedComponents(), plugInPort.getCurrentProject().getGroups()),
clipboardOwner);
}
}
@@ -1505,9 +1513,9 @@ public class ActionFactory {
this.plugInPort = plugInPort;
putValue(AbstractAction.NAME, "Delete Selection");
if (Utils.isMac()) {
putValue(AbstractAction.ACCELERATOR_KEY, KeyStroke.getKeyStroke(KeyEvent.VK_BACK_SPACE, 0));
putValue(AbstractAction.ACCELERATOR_KEY, KeyStroke.getKeyStroke(KeyEvent.VK_BACK_SPACE, 0));
} else {
putValue(AbstractAction.ACCELERATOR_KEY, KeyStroke.getKeyStroke(KeyEvent.VK_DELETE, 0));
putValue(AbstractAction.ACCELERATOR_KEY, KeyStroke.getKeyStroke(KeyEvent.VK_DELETE, 0));
}
putValue(AbstractAction.SMALL_ICON, IconLoader.Delete.getIcon());
}
@@ -1779,9 +1787,9 @@ public class ActionFactory {
putValue(IView.CHECK_BOX_MENU_ITEM, true);
putValue(AbstractAction.SELECTED_KEY,
ConfigurationManager.getInstance().readBoolean(configKey, defaultValue));
LOG.info("Initializing " + configKey + " to " + getValue(AbstractAction.SELECTED_KEY));
ConfigurationManager.getInstance().addConfigListener(configKey, new IConfigListener() {
@Override
@@ -1815,7 +1823,8 @@ public class ActionFactory {
private String configKey;
public ToggleAction(String title, String configKey, String groupName, String defaultValue, Icon icon) {
public ToggleAction(String title, String configKey, String groupName, String defaultValue,
Icon icon) {
super();
this.configKey = configKey;
putValue(AbstractAction.NAME, title);
@@ -1836,7 +1845,8 @@ public class ActionFactory {
@Override
public void valueChanged(String key, Object value) {
putValue(AbstractAction.SELECTED_KEY, getValue(AbstractAction.NAME).toString().equalsIgnoreCase(value.toString()));
putValue(AbstractAction.SELECTED_KEY,
getValue(AbstractAction.NAME).toString().equalsIgnoreCase(value.toString()));
}
}
@@ -1915,12 +1925,19 @@ public class ActionFactory {
private IPlugInPort plugInPort;
private ISwingUI swingUI;
private boolean includeSwitches;
public GenerateNetlistAction(IPlugInPort plugInPort, ISwingUI swingUI) {
public GenerateNetlistAction(IPlugInPort plugInPort, ISwingUI swingUI,
boolean includeSwitches) {
super();
this.plugInPort = plugInPort;
this.swingUI = swingUI;
putValue(AbstractAction.NAME, "Generate DIYLC Netlist");
this.includeSwitches = includeSwitches;
if (includeSwitches) {
putValue(AbstractAction.NAME, "Generate DIYLC Netlist (incl. Switches)");
} else {
putValue(AbstractAction.NAME, "Generate DIYLC Netlist (excl. Switches)");
}
putValue(AbstractAction.SMALL_ICON, IconLoader.Web.getIcon());
}
@@ -1930,7 +1947,8 @@ public class ActionFactory {
@Override
public List<Netlist> doInBackground() throws Exception {
return plugInPort.extractNetlists(true);
LOG.info("Generating netlist, includeSwitches = " + includeSwitches + "...");
return plugInPort.extractNetlists(includeSwitches);
}
@Override
@@ -1941,6 +1959,7 @@ public class ActionFactory {
@Override
public void complete(List<Netlist> res) {
LOG.info("Finished fenerating netlist");
if (res == null) {
swingUI.showMessage("The generated netlist is empty, nothing to show.", "DIYLC Netlist",
ISwingUI.INFORMATION_MESSAGE);
@@ -1950,8 +1969,12 @@ public class ActionFactory {
for (Netlist netlist : res) {
sb.append("<p style=\"font-family: " + new JLabel().getFont().getName()
+ "; font-size: 9px\"><b>Switch configuration: ").append(netlist.getSwitchSetup())
.append("</b><br><br>Connected node groups:<br>");
+ "; font-size: 9px\">");
if (includeSwitches) {
sb.append("<b>Switch configuration: ").append(netlist.getSwitchSetup())
.append("</b><br><br>");
}
sb.append("Connected node groups:<br><br>");
for (Group v : netlist.getSortedGroups()) {
sb.append("&nbsp;&nbsp;").append(v.getSortedNodes()).append("<br>");
}
@@ -2035,7 +2058,7 @@ public class ActionFactory {
}, true);
}
}
public static class CheckProximityAction extends AbstractAction implements Cloneable {
private static final long serialVersionUID = 1L;
@@ -2047,65 +2070,75 @@ public class ActionFactory {
public CheckProximityAction(IPlugInPort plugInPort, ISwingUI swingUI) {
super();
this.plugInPort = plugInPort;
this.swingUI = swingUI;
this.swingUI = swingUI;
putValue(AbstractAction.NAME, "Check Trace Proximity");
putValue(AbstractAction.SMALL_ICON, IconLoader.TraceProximity.getIcon());
this.threshold = new Size(0.5d, SizeUnit.mm);
}
public Size getThreshold() {
return threshold;
}
public void setThreshold(Size threshold) {
this.threshold = threshold;
}
@Override
public void actionPerformed(ActionEvent e) {
PropertyWrapper wrapper = new PropertyWrapper("Threshold", Size.class, "getThreshold", "setThreshold", false, new PositiveNonZeroMeasureValidator(), 0);
public void actionPerformed(ActionEvent e) {
PropertyWrapper wrapper = new PropertyWrapper("Threshold", Size.class, "getThreshold",
"setThreshold", false, new PositiveNonZeroMeasureValidator(), 0);
try {
wrapper.readFrom(CheckProximityAction.this);
wrapper.readFrom(CheckProximityAction.this);
} catch (Exception e1) {
LOG.error("Error reading proximity threshold", e1);
}
List<PropertyWrapper> properties = Arrays.asList(wrapper);
PropertyEditorDialog editor =
DialogFactory.getInstance().createPropertyEditorDialog(properties, (String)CheckProximityAction.this.getValue(NAME), true);
PropertyEditorDialog editor = DialogFactory.getInstance().createPropertyEditorDialog(
properties, (String) CheckProximityAction.this.getValue(NAME), true);
editor.setVisible(true);
if (ButtonDialog.OK.equals(editor.getSelectedButtonCaption())) {
if (ButtonDialog.OK.equals(editor.getSelectedButtonCaption())) {
try {
wrapper.writeTo(CheckProximityAction.this);
} catch (Exception e1) {
LOG.error("Error writing proximity threshold", e1);
}
swingUI.executeBackgroundTask(new ITask<List<Area>>() {
@Override
public List<Area> doInBackground() throws Exception {
return plugInPort.checkContinuityAreaProximity(threshold);
}
@Override
public void failed(Exception e) {
swingUI.showMessage(e.getMessage(), LangUtil.translate((String)CheckProximityAction.this.getValue(NAME)), ISwingUI.INFORMATION_MESSAGE);
swingUI.showMessage(e.getMessage(),
LangUtil.translate((String) CheckProximityAction.this.getValue(NAME)),
ISwingUI.INFORMATION_MESSAGE);
}
@Override
public void complete(List<Area> res) {
if (res == null || res.size() == 0)
swingUI.showMessage(LangUtil.translate("No proximity issues detected."),
LangUtil.translate((String)CheckProximityAction.this.getValue(NAME)), ISwingUI.INFORMATION_MESSAGE);
LangUtil.translate((String) CheckProximityAction.this.getValue(NAME)),
ISwingUI.INFORMATION_MESSAGE);
else
swingUI.showMessage(String.format(LangUtil.translate("%s potential proximity issue(s) detected and marked red."), res.size()),
LangUtil.translate((String)CheckProximityAction.this.getValue(NAME)), ISwingUI.WARNING_MESSAGE);
swingUI
.showMessage(
String.format(
LangUtil.translate(
"%s potential proximity issue(s) detected and marked red."),
res.size()),
LangUtil.translate((String) CheckProximityAction.this.getValue(NAME)),
ISwingUI.WARNING_MESSAGE);
}
}, true);
}
}
@Override
public Object clone() throws CloneNotSupportedException {
return new CheckProximityAction(plugInPort, swingUI);
@@ -96,7 +96,9 @@ public class FileMenuPlugin implements IPlugIn, IDynamicSubmenuHandler {
swingUI.injectMenuAction(actionFactory.createCheckProximityAction(plugInPort, swingUI),
ANALYZE_TITLE);
swingUI.injectMenuAction(actionFactory.createBomAction(plugInPort), ANALYZE_TITLE);
swingUI.injectMenuAction(actionFactory.createGenerateNetlistAction(plugInPort, swingUI),
swingUI.injectMenuAction(actionFactory.createGenerateNetlistAction(plugInPort, swingUI, false),
ANALYZE_TITLE);
swingUI.injectMenuAction(actionFactory.createGenerateNetlistAction(plugInPort, swingUI, true),
ANALYZE_TITLE);
List<INetlistAnalyzer> summarizers = plugInPort.getNetlistAnalyzers();
@@ -227,8 +227,7 @@ public class RegressionTestRunner {
}
List<Netlist> netlists = presenter.extractNetlists(true);
File netlistOutputDir = new File(
file.getParentFile().getParentFile().getAbsolutePath() + File.separator + "netlist");
File netlistOutputDir = new File(outputDir.getAbsolutePath() + File.separator + "netlist");
File netlistOutputFile = new File(netlistOutputDir.getAbsolutePath() + File.separator
+ file.getName().replace(".diy", ".txt"));
File netlistInputFile = new File(inputDir.getAbsolutePath() + File.separator + "netlist"