diff --git a/diylc/diylc-core/src/org/diylc/common/IPlugInPort.java b/diylc/diylc-core/src/org/diylc/common/IPlugInPort.java index de1e43a6..f05d1c9e 100644 --- a/diylc/diylc-core/src/org/diylc/common/IPlugInPort.java +++ b/diylc/diylc-core/src/org/diylc/common/IPlugInPort.java @@ -25,6 +25,7 @@ import java.awt.Cursor; import java.awt.Dimension; import java.awt.Graphics2D; import java.awt.Point; +import java.awt.geom.Area; import java.awt.geom.Point2D; import java.awt.geom.Rectangle2D; import java.util.Collection; @@ -40,6 +41,7 @@ import org.diylc.core.IDIYComponent; import org.diylc.core.Project; import org.diylc.core.Template; import org.diylc.core.Theme; +import org.diylc.core.measures.Size; import org.diylc.test.ITestProcessor; /** @@ -396,5 +398,12 @@ public interface IPlugInPort extends ISelectionProcessor, IMouseProcessor, IKeyP /** * Locks or unlocks a single component. */ - void lockComponent(IDIYComponent c, boolean locked); + void lockComponent(IDIYComponent c, boolean locked); + + /** + * Checks for proximity between continuity areas and creates additional components around any problematic areas to visually identify them. + * + * @param threshold + */ + List checkContinuityAreaProximity(Size threshold); } diff --git a/diylc/diylc-core/src/org/diylc/core/IView.java b/diylc/diylc-core/src/org/diylc/core/IView.java index 8cff063e..75e2ca38 100644 --- a/diylc/diylc-core/src/org/diylc/core/IView.java +++ b/diylc/diylc-core/src/org/diylc/core/IView.java @@ -56,7 +56,7 @@ public interface IView { int showConfirmDialog(String message, String title, int optionType, int messageType); - boolean editProperties(List properties, Set defaultedProperties); + boolean editProperties(List properties, Set defaultedProperties, String title); String showInputDialog(String message, String title); diff --git a/diylc/diylc-core/src/org/diylc/presenter/AreaUtils.java b/diylc/diylc-core/src/org/diylc/presenter/AreaUtils.java index 4a8674e7..ee30f1d3 100644 --- a/diylc/diylc-core/src/org/diylc/presenter/AreaUtils.java +++ b/diylc/diylc-core/src/org/diylc/presenter/AreaUtils.java @@ -60,7 +60,7 @@ public class AreaUtils { if (intersection != null && !intersection.isEmpty()) { a1.add(a2); consumed.set(j, true); - } else { // maybe there's a connection between them + } else if (connections != null) { // maybe there's a connection between them for (Connection p : connections) { // use getBounds to optimize the computation, don't get into complex math if not needed if ((a1.getBounds().contains(p.getP1()) && a2.getBounds().contains(p.getP2()) && a1.contains(p.getP1()) && a2.contains(p.getP2())) || (a1.getBounds().contains(p.getP2()) && a2.getBounds().contains(p.getP1())) && a1.contains(p.getP2()) && a2.contains(p.getP1())) { diff --git a/diylc/diylc-core/src/org/diylc/presenter/DrawingManager.java b/diylc/diylc-core/src/org/diylc/presenter/DrawingManager.java index 4b78cf0c..b4e3c55a 100644 --- a/diylc/diylc-core/src/org/diylc/presenter/DrawingManager.java +++ b/diylc/diylc-core/src/org/diylc/presenter/DrawingManager.java @@ -26,8 +26,11 @@ import java.awt.Graphics2D; import java.awt.Point; import java.awt.Rectangle; import java.awt.RenderingHints; +import java.awt.Shape; +import java.awt.Stroke; import java.awt.geom.AffineTransform; import java.awt.geom.Area; +import java.awt.geom.Ellipse2D; import java.awt.geom.Line2D; import java.awt.geom.Point2D; import java.awt.geom.Rectangle2D; @@ -55,6 +58,8 @@ import org.diylc.core.IDIYComponent; import org.diylc.core.Project; 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.utils.Constants; /** @@ -112,6 +117,8 @@ public class DrawingManager { private IConfigurationManager configManager; + private List proximityMarkers = null; + public DrawingManager(MessageDispatcher messageDispatcher, IConfigurationManager configManager) { super(); this.messageDispatcher = messageDispatcher; @@ -470,6 +477,14 @@ public class DrawingManager { g2d.draw(a); } } + + if (this.proximityMarkers != null) + { + g2d.setStroke(ObjectCache.getInstance().fetchBasicStroke(2)); + g2d.setColor(Color.red); + for (Shape s : this.proximityMarkers) + g2d.draw(s); + } if (currentContinuityArea != null) { Composite oldComposite = g2d.getComposite(); @@ -524,6 +539,7 @@ public class DrawingManager { public void invalidateComponent(IDIYComponent component) { componentAreaMap.remove(component); lastDrawnStateMap.remove(component); + proximityMarkers = null; } public ComponentArea getComponentArea(IDIYComponent component) { @@ -533,11 +549,13 @@ public class DrawingManager { public void clearComponentAreaMap() { componentAreaMap.clear(); lastDrawnStateMap.clear(); + proximityMarkers = null; } public void clearContinuityArea() { currentContinuityArea = null; continuityAreaCache = null; + proximityMarkers = null; } public List> findComponentsAt(Point2D point, Project project) { @@ -654,5 +672,41 @@ public class DrawingManager { AreaUtils.crunchAreas(areas, connections); return areas; - } + } + + public List getContinuityAreaProximity(Project project, float threshold) { + List continuityAreas = getContinuityAreas(project); + Stroke s = ObjectCache.getInstance().fetchBasicStroke(threshold - 1); // value eyeballed for approx good results + List expanded = continuityAreas.parallelStream().map((a) -> new Area(s.createStrokedShape(a))).collect(Collectors.toList()); + List intersections = new ArrayList(); + for (int i = 0; i < expanded.size() - 1; i++) { + for (int j = i + 1; j < expanded.size(); j++) { + Area first = new Area(expanded.get(i)); + Area second = expanded.get(j); + first.intersect(second); + if (!first.isEmpty()) + intersections.add(first); + } + } + + AreaUtils.crunchAreas(intersections, null); + double minSize = new Size(1d, SizeUnit.cm).convertToPixels(); + + List shapes = new ArrayList(); + for (Area a : intersections) { + Rectangle2D bounds2d = a.getBounds2D(); + if (bounds2d.getWidth() < minSize) + bounds2d = new Rectangle2D.Double(bounds2d.getCenterX() - minSize / 2, bounds2d.getY(), minSize, bounds2d.getHeight()); + if (bounds2d.getHeight() < minSize) + bounds2d = new Rectangle2D.Double(bounds2d.getX(), bounds2d.getCenterY() - minSize / 2, bounds2d.getWidth(), minSize); + + shapes.add(new Area(new Ellipse2D.Double(bounds2d.getX(), bounds2d.getY(), bounds2d.getWidth(), bounds2d.getHeight()))); + } + + //AreaUtils.crunchAreas(shapes, null); + + this.proximityMarkers = shapes; + + return shapes; + } } diff --git a/diylc/diylc-core/src/org/diylc/presenter/Presenter.java b/diylc/diylc-core/src/org/diylc/presenter/Presenter.java index 9f9bc944..b7a879b9 100644 --- a/diylc/diylc-core/src/org/diylc/presenter/Presenter.java +++ b/diylc/diylc-core/src/org/diylc/presenter/Presenter.java @@ -863,7 +863,7 @@ public class Presenter implements IPlugInPort { List properties = getMutualSelectionProperties(); if (properties != null && !properties.isEmpty()) { Set defaultedProperties = new HashSet(); - boolean edited = view.editProperties(properties, defaultedProperties); + boolean edited = view.editProperties(properties, defaultedProperties, LangUtil.translate("Edit Selection")); if (edited) { try { applyPropertiesToSelection(properties); @@ -3412,4 +3412,9 @@ public class Presenter implements IPlugInPort { } return new Snapshot(currentProject.clone(), new HashSet>(selectedComponents), areas, new HashSet(drawingManager.getContinuityAreas(currentProject))); } + + @Override + public List checkContinuityAreaProximity(Size threshold) { + return drawingManager.getContinuityAreaProximity(this.currentProject, (float)threshold.convertToPixels()); + } } diff --git a/diylc/diylc-core/src/org/diylc/presenter/update.xml b/diylc/diylc-core/src/org/diylc/presenter/update.xml index 44bf0a97..47e0c829 100644 --- a/diylc/diylc-core/src/org/diylc/presenter/update.xml +++ b/diylc/diylc-core/src/org/diylc/presenter/update.xml @@ -3354,6 +3354,10 @@ 2020-12-06 00:00:00.000 CET + + NEW_FEATURE + Added 'Check Trace Proximity' functionality under File -> Analyze menu that checks if there are any traces or pads that are too close to each other + NEW_FEATURE 'Tactile Micro-Switch' component added diff --git a/diylc/diylc-library/lib/diylc-core.jar b/diylc/diylc-library/lib/diylc-core.jar index 720a6978..5a07cd64 100644 Binary files a/diylc/diylc-library/lib/diylc-core.jar and b/diylc/diylc-library/lib/diylc-core.jar differ diff --git a/diylc/diylc-swing/lib/diylc-core.jar b/diylc/diylc-swing/lib/diylc-core.jar index 720a6978..5a07cd64 100644 Binary files a/diylc/diylc-swing/lib/diylc-core.jar and b/diylc/diylc-swing/lib/diylc-core.jar differ diff --git a/diylc/diylc-swing/lib/swing-framework.jar b/diylc/diylc-swing/lib/swing-framework.jar index 5c5c54e9..8688c27f 100644 Binary files a/diylc/diylc-swing/lib/swing-framework.jar and b/diylc/diylc-swing/lib/swing-framework.jar differ diff --git a/diylc/diylc-swing/library/main.jar b/diylc/diylc-swing/library/main.jar index 7ddfd9ff..a19b18e8 100644 Binary files a/diylc/diylc-swing/library/main.jar and b/diylc/diylc-swing/library/main.jar differ diff --git a/diylc/diylc-swing/src/org/diylc/swing/ActionFactory.java b/diylc/diylc-swing/src/org/diylc/swing/ActionFactory.java index 738df72b..e3e4ae02 100644 --- a/diylc/diylc-swing/src/org/diylc/swing/ActionFactory.java +++ b/diylc/diylc-swing/src/org/diylc/swing/ActionFactory.java @@ -23,6 +23,7 @@ import java.awt.datatransfer.Clipboard; import java.awt.datatransfer.ClipboardOwner; import java.awt.event.ActionEvent; import java.awt.event.KeyEvent; +import java.awt.geom.Area; import java.awt.print.PrinterException; import java.io.BufferedOutputStream; import java.io.File; @@ -34,6 +35,7 @@ import java.util.Collection; import java.util.Collections; import java.util.Comparator; import java.util.HashMap; +import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Set; @@ -63,17 +65,19 @@ import org.diylc.core.IView; import org.diylc.core.Project; import org.diylc.core.Template; import org.diylc.core.Theme; -import org.diylc.swing.images.IconLoader; +import org.diylc.core.annotations.PositiveNonZeroMeasureValidator; import org.diylc.core.measures.Nudge; import org.diylc.core.measures.Size; import org.diylc.core.measures.SizeUnit; import org.diylc.editor.FlexibleLeadsEditor; +import org.diylc.lang.LangUtil; import org.diylc.netlist.Group; import org.diylc.netlist.Netlist; import org.diylc.netlist.Summary; import org.diylc.presenter.Presenter; import org.diylc.swing.gui.DialogFactory; import org.diylc.swing.gui.editor.PropertyEditorDialog; +import org.diylc.swing.images.IconLoader; import org.diylc.swing.plugins.config.ConfigPlugin; import org.diylc.swing.plugins.edit.ComponentTransferable; import org.diylc.swing.plugins.edit.FindDialog; @@ -292,6 +296,10 @@ public class ActionFactory { ISwingUI swingUI, INetlistAnalyzer summarizer) { return new SummarizeNetlistAction(plugInPort, swingUI, summarizer); } + + public CheckProximityAction createCheckProximityAction(IPlugInPort plugInPort, ISwingUI swingUI) { + return new CheckProximityAction(plugInPort, swingUI); + } // File menu actions. @@ -419,7 +427,7 @@ public class ActionFactory { @Override public boolean editProperties(List properties, - Set defaultedProperties) { + Set defaultedProperties, String title) { return false; } }, InMemoryConfigurationManager.getInstance()); @@ -1931,4 +1939,76 @@ public class ActionFactory { }, true); } } + + public static class CheckProximityAction extends AbstractAction implements Cloneable { + + private static final long serialVersionUID = 1L; + + private IPlugInPort plugInPort; + private ISwingUI swingUI; + private Size threshold; + + public CheckProximityAction(IPlugInPort plugInPort, ISwingUI swingUI) { + super(); + this.plugInPort = plugInPort; + 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); + try { + wrapper.readFrom(CheckProximityAction.this); + } catch (Exception e1) { + LOG.error("Error reading proximity threshold", e1); + } + + if (swingUI.editProperties(Arrays.asList(wrapper), new HashSet(), (String)CheckProximityAction.this.getValue(NAME))) { + try { + wrapper.writeTo(CheckProximityAction.this); + } catch (Exception e1) { + LOG.error("Error writing proximity threshold", e1); + } + + swingUI.executeBackgroundTask(new ITask>() { + + @Override + public List doInBackground() throws Exception { + return plugInPort.checkContinuityAreaProximity(threshold); + } + + @Override + public void failed(Exception e) { + swingUI.showMessage(e.getMessage(), (String)CheckProximityAction.this.getValue(NAME), ISwingUI.INFORMATION_MESSAGE); + } + + @Override + public void complete(List res) { + if (res == null || res.size() == 0) + swingUI.showMessage(LangUtil.translate("No proximity issues detected."), + (String)CheckProximityAction.this.getValue(NAME), ISwingUI.INFORMATION_MESSAGE); + else + swingUI.showMessage(String.format("%s potential proximity issue(s) detected and marked red.", res.size()), + (String)CheckProximityAction.this.getValue(NAME), ISwingUI.WARNING_MESSAGE); + } + }, true); + } + } + + @Override + public Object clone() throws CloneNotSupportedException { + return new CheckProximityAction(plugInPort, swingUI); + } + } } diff --git a/diylc/diylc-swing/src/org/diylc/swing/gui/DummyView.java b/diylc/diylc-swing/src/org/diylc/swing/gui/DummyView.java index 845d8af2..0206d24b 100644 --- a/diylc/diylc-swing/src/org/diylc/swing/gui/DummyView.java +++ b/diylc/diylc-swing/src/org/diylc/swing/gui/DummyView.java @@ -44,7 +44,7 @@ public class DummyView implements IView { } @Override - public boolean editProperties(List properties, Set defaultedProperties) { + public boolean editProperties(List properties, Set defaultedProperties, String title) { return false; } diff --git a/diylc/diylc-swing/src/org/diylc/swing/gui/MainFrame.java b/diylc/diylc-swing/src/org/diylc/swing/gui/MainFrame.java index 4556a48e..ed0117f3 100644 --- a/diylc/diylc-swing/src/org/diylc/swing/gui/MainFrame.java +++ b/diylc/diylc-swing/src/org/diylc/swing/gui/MainFrame.java @@ -298,9 +298,9 @@ public class MainFrame extends JFrame implements ISwingUI { } @Override - public boolean editProperties(List properties, Set defaultedProperties) { + public boolean editProperties(List properties, Set defaultedProperties, String title) { PropertyEditorDialog editor = - DialogFactory.getInstance().createPropertyEditorDialog(properties, LangUtil.translate("Edit Selection"), true); + DialogFactory.getInstance().createPropertyEditorDialog(properties, title, true); editor.setVisible(true); defaultedProperties.addAll(editor.getDefaultedProperties()); return ButtonDialog.OK.equals(editor.getSelectedButtonCaption()); diff --git a/diylc/diylc-swing/src/org/diylc/swing/gui/TemplateDialog.java b/diylc/diylc-swing/src/org/diylc/swing/gui/TemplateDialog.java index 70e5886e..6e3f1ce2 100644 --- a/diylc/diylc-swing/src/org/diylc/swing/gui/TemplateDialog.java +++ b/diylc/diylc-swing/src/org/diylc/swing/gui/TemplateDialog.java @@ -110,7 +110,7 @@ public class TemplateDialog extends JDialog { } @Override - public boolean editProperties(List properties, Set defaultedProperties) { + public boolean editProperties(List properties, Set defaultedProperties, String title) { return false; } }, InMemoryConfigurationManager.getInstance()); diff --git a/diylc/diylc-swing/src/org/diylc/swing/images/IconLoader.java b/diylc/diylc-swing/src/org/diylc/swing/images/IconLoader.java index 8d320b3e..03072b31 100644 --- a/diylc/diylc-swing/src/org/diylc/swing/images/IconLoader.java +++ b/diylc/diylc-swing/src/org/diylc/swing/images/IconLoader.java @@ -67,7 +67,7 @@ public enum IconLoader { JarBeanInto("jar_bean_into.png"), GraphEdgeDirected("graph_edge_directed.png"), LaserPointer("laserpointer.png"), TapeMeasure("tape_measure1.png"), Elements("elements1.png"), GraphNodes("graph_nodes.png"), User("user1.png"), FlexibleLeads("flexible_leads.png"), Pads("pads.png"), Lock("lock.png"), Unlock("lock_open.png"), Earth("earth.png"), Loadline("loadline.png"), LoadlineAdd("loadline_add.png"), SnapToGrid("snap_to_grid.png"), - SnapToComponents("snap_to_components.png"), SnapToNone("snap_to_none.png"); + SnapToComponents("snap_to_components.png"), SnapToNone("snap_to_none.png"), TraceProximity("trace_proximity.png"); protected String name; diff --git a/diylc/diylc-swing/src/org/diylc/swing/images/trace_proximity.png b/diylc/diylc-swing/src/org/diylc/swing/images/trace_proximity.png new file mode 100644 index 00000000..ec5be80e Binary files /dev/null and b/diylc/diylc-swing/src/org/diylc/swing/images/trace_proximity.png differ diff --git a/diylc/diylc-swing/src/org/diylc/swing/loadline/LoadlineEditorFrame.java b/diylc/diylc-swing/src/org/diylc/swing/loadline/LoadlineEditorFrame.java index 43499abb..5b4ab7ab 100644 --- a/diylc/diylc-swing/src/org/diylc/swing/loadline/LoadlineEditorFrame.java +++ b/diylc/diylc-swing/src/org/diylc/swing/loadline/LoadlineEditorFrame.java @@ -54,16 +54,15 @@ import org.diylc.components.misc.LoadlineEntity; import org.diylc.core.IDIYComponent; import org.diylc.core.IView; import org.diylc.core.Project; -import org.diylc.swing.images.IconLoader; import org.diylc.core.measures.Size; import org.diylc.core.measures.SizeUnit; import org.diylc.core.measures.Voltage; import org.diylc.core.measures.VoltageUnit; -import org.diylc.lang.LangUtil; import org.diylc.presenter.Presenter; import org.diylc.swing.gui.DialogFactory; import org.diylc.swing.gui.TranslatedPanel; import org.diylc.swing.gui.editor.PropertyEditorDialog; +import org.diylc.swing.images.IconLoader; import org.diylc.swing.plugins.canvas.CanvasPanel; import org.diylc.swing.plugins.file.FileFilterEnum; import org.diylc.swingframework.ButtonDialog; @@ -520,9 +519,9 @@ public class LoadlineEditorFrame extends JFrame implements IView { @Override public boolean editProperties(List properties, - Set defaultedProperties) { + Set defaultedProperties, String title) { PropertyEditorDialog editor = DialogFactory.getInstance().createPropertyEditorDialog(LoadlineEditorFrame.this, properties, - LangUtil.translate("Edit Selection"), true); + title, true); editor.setVisible(true); defaultedProperties.addAll(editor.getDefaultedProperties()); return ButtonDialog.OK.equals(editor.getSelectedButtonCaption()); diff --git a/diylc/diylc-swing/src/org/diylc/swing/plugins/file/FileMenuPlugin.java b/diylc/diylc-swing/src/org/diylc/swing/plugins/file/FileMenuPlugin.java index f6a3c857..b0d4af1e 100644 --- a/diylc/diylc-swing/src/org/diylc/swing/plugins/file/FileMenuPlugin.java +++ b/diylc/diylc-swing/src/org/diylc/swing/plugins/file/FileMenuPlugin.java @@ -91,6 +91,7 @@ public class FileMenuPlugin implements IPlugIn, IDynamicSubmenuHandler { swingUI.injectSubmenu(ANALYZE_TITLE, IconLoader.Scientist.getIcon(), FILE_TITLE); swingUI.injectMenuAction(actionFactory.createBomAction(plugInPort), ANALYZE_TITLE); swingUI.injectMenuAction(actionFactory.createGenerateNetlistAction(plugInPort, swingUI), ANALYZE_TITLE); + swingUI.injectMenuAction(actionFactory.createCheckProximityAction(plugInPort, swingUI), ANALYZE_TITLE); List summarizers = plugInPort.getNetlistAnalyzers(); if (summarizers != null) { diff --git a/diylc/diylc-swing/test/org/diylc/MockView.java b/diylc/diylc-swing/test/org/diylc/MockView.java index 756e9700..7f070b38 100644 --- a/diylc/diylc-swing/test/org/diylc/MockView.java +++ b/diylc/diylc-swing/test/org/diylc/MockView.java @@ -23,7 +23,7 @@ public class MockView implements IView { @Override public boolean editProperties(List properties, - Set defaultedProperties) { + Set defaultedProperties, String title) { // TODO Auto-generated method stub return false; }