diff --git a/diylc/diylc-core/src/org/diylc/common/INetlistProcessor.java b/diylc/diylc-core/src/org/diylc/common/INetlistProcessor.java index 9709ddea..3f2a7c56 100644 --- a/diylc/diylc-core/src/org/diylc/common/INetlistProcessor.java +++ b/diylc/diylc-core/src/org/diylc/common/INetlistProcessor.java @@ -23,6 +23,7 @@ package org.diylc.common; import java.util.List; +import org.diylc.netlist.INetlistParser; import org.diylc.netlist.Netlist; /** @@ -47,5 +48,11 @@ public interface INetlistProcessor { */ List getNetlistAnalyzers(); + /** + * Finds all available {@link INetlistParser} implementations. + * + * @return + */ + List getNetlistParserDefinitions(); } diff --git a/diylc/diylc-core/src/org/diylc/netlist/AbstractNetlistParser.java b/diylc/diylc-core/src/org/diylc/netlist/AbstractNetlistParser.java new file mode 100644 index 00000000..9b0875fd --- /dev/null +++ b/diylc/diylc-core/src/org/diylc/netlist/AbstractNetlistParser.java @@ -0,0 +1,163 @@ +/* + * + * 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.awt.geom.Point2D; +import java.io.IOException; +import java.lang.reflect.Method; +import java.nio.file.Files; +import java.nio.file.Paths; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.Comparator; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Optional; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +import org.diylc.core.IDIYComponent; +import org.diylc.core.measures.Size; +import org.diylc.core.measures.SizeUnit; + +public abstract class AbstractNetlistParser implements INetlistParser { + + private static Size X_OFFSET = new Size(1.5d, SizeUnit.in); + private static Size Y_OFFSET = new Size(0.3d, SizeUnit.in); + private static int ROW_COUNT = 15; + + public List parseFile(String fileName, List outputWarnings) throws NetlistParseException { + String content; + try { + content = new String(Files.readAllBytes(Paths.get(fileName))); + } catch (IOException e) { + throw new NetlistParseException("Error reading file", e); + } + + return parse(content, outputWarnings); + } + + public List parse(String content, List outputWarnings) throws NetlistParseException { + List result = new ArrayList(); + + Pattern pattern = Pattern.compile(getItemRegex(), Pattern.MULTILINE | Pattern.DOTALL); + Matcher matcher = pattern.matcher(content); + while (matcher.find()) { + String type; + try { + type = matcher.group("Type").trim(); + } catch (Exception e) { + outputWarnings.add("'Type' group not found."); + continue; + } + + if (type == null) { + outputWarnings.add("'Type' group not found."); + continue; + } + + Map valueMap = new HashMap(); + List> typeCandidates = getComponentTypeCandidates(type, valueMap); + if (typeCandidates == null) { + outputWarnings.add("Type mappings not found for " + type + "."); + continue; + } + + for (String propertyName : getPropertyNames()) { + String valueStr = matcher.group(propertyName).trim(); + try { + Object value = parseValue(type, propertyName, valueStr); + valueMap.put(propertyName, value); + } catch (Exception e) { + outputWarnings + .add("Error while parsing value for " + type + "." + propertyName + " = " + valueStr); + } + } + + result.add(new ParsedNetlistEntry(typeCandidates, type, valueMap)); + } + + Collections.sort(result, new Comparator() { + + @Override + public int compare(ParsedNetlistEntry o1, ParsedNetlistEntry o2) { + int comp = o1.getRawType().compareToIgnoreCase(o2.getRawType()); + if (comp == 0 && o1.getValues().containsKey("Name") && o2.getValues().containsKey("Name")) + comp = o1.getValues().get("Name").toString().compareToIgnoreCase(o2.getValues().get("Name").toString()); + return comp; + } + }); + + return result; + } + + public List> generateComponents(List components, + List outputWarnings) { + double dx = 0; + double dy = 0; + List> result = new ArrayList>(); + for (int i = 0; i < components.size(); i++) { + ParsedNetlistComponent parsedComponent = components.get(i); + if (parsedComponent.getType() == null) { + outputWarnings.add("Type not selected for " + parsedComponent.getValues().get("Name")); + continue; + } + Class clazz = parsedComponent.getType(); + IDIYComponent component; + try { + component = (IDIYComponent) clazz.newInstance(); + } catch (Exception e) { + outputWarnings.add(e.getMessage()); + continue; + } + for (int j = 0; j < component.getControlPointCount(); j++) { + Point2D p = component.getControlPoint(j); + Point2D controlPoint = new Point2D.Double(p.getX() + dx, p.getY() + dy); + component.setControlPoint(controlPoint, j); + } + + if (i > 0 && i % ROW_COUNT == 0) { + dx += X_OFFSET.convertToPixels(); + dy = 0; + } else { + dy += Y_OFFSET.convertToPixels(); + } + + Method[] methods = clazz.getMethods(); + for (Map.Entry value : parsedComponent.getValues().entrySet()) { + try { + Optional first = Arrays.stream(methods).filter(x -> x.getName().equals("set" + value.getKey())).findFirst(); + if (first.isPresent()) { + Method setter = first.get(); + setter.invoke(component, value.getValue()); + } else { + outputWarnings.add("No setter found for " + value.getKey()); + } + } catch (Exception e) { + outputWarnings.add("Error setting " + value.getKey() + ": " + e.getMessage()); + } + } + result.add(component); + + } + return result; + } +} diff --git a/diylc/diylc-core/src/org/diylc/netlist/INetlistParser.java b/diylc/diylc-core/src/org/diylc/netlist/INetlistParser.java new file mode 100644 index 00000000..df655bdc --- /dev/null +++ b/diylc/diylc-core/src/org/diylc/netlist/INetlistParser.java @@ -0,0 +1,66 @@ +/* + * + * 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.List; +import java.util.Map; + +import org.diylc.core.IDIYComponent; + +/* + * + * 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 + * . + * + */ +public interface INetlistParser { + + String getName(); + + String getFileExt(); + + String getItemRegex(); + + List> getComponentTypeCandidates(String type, Map additionalOutputs); + + List getPropertyNames(); + + Object parseValue(String type, String propertyName, String valueStr); + + List parseFile(String fileName, List outputWarnings) + throws NetlistParseException; + + List parse(String content, List outputWarnings) + throws NetlistParseException; + + List> generateComponents(List components, + List outputWarnings); +} diff --git a/diylc/diylc-core/src/org/diylc/netlist/NetlistParseException.java b/diylc/diylc-core/src/org/diylc/netlist/NetlistParseException.java new file mode 100644 index 00000000..386442c0 --- /dev/null +++ b/diylc/diylc-core/src/org/diylc/netlist/NetlistParseException.java @@ -0,0 +1,50 @@ +/* + * + * 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; + +public class NetlistParseException extends Exception { + + private static final long serialVersionUID = 1L; + + public NetlistParseException() { + super(); + // TODO Auto-generated constructor stub + } + + public NetlistParseException(String message, Throwable cause, boolean enableSuppression, + boolean writableStackTrace) { + super(message, cause, enableSuppression, writableStackTrace); + // TODO Auto-generated constructor stub + } + + public NetlistParseException(String message, Throwable cause) { + super(message, cause); + // TODO Auto-generated constructor stub + } + + public NetlistParseException(String message) { + super(message); + // TODO Auto-generated constructor stub + } + + public NetlistParseException(Throwable cause) { + super(cause); + // TODO Auto-generated constructor stub + } +} diff --git a/diylc/diylc-core/src/org/diylc/netlist/ParsedNetlistComponent.java b/diylc/diylc-core/src/org/diylc/netlist/ParsedNetlistComponent.java new file mode 100644 index 00000000..95f5be1b --- /dev/null +++ b/diylc/diylc-core/src/org/diylc/netlist/ParsedNetlistComponent.java @@ -0,0 +1,26 @@ +package org.diylc.netlist; + +import java.util.Map; + +public class ParsedNetlistComponent { + + public Class type; + public Map values; + + public ParsedNetlistComponent() { + } + + public ParsedNetlistComponent(Class type, Map values) { + super(); + this.type = type; + this.values = values; + } + + public Class getType() { + return type; + } + + public Map getValues() { + return values; + } +} diff --git a/diylc/diylc-core/src/org/diylc/netlist/ParsedNetlistEntry.java b/diylc/diylc-core/src/org/diylc/netlist/ParsedNetlistEntry.java new file mode 100644 index 00000000..61f0ee30 --- /dev/null +++ b/diylc/diylc-core/src/org/diylc/netlist/ParsedNetlistEntry.java @@ -0,0 +1,55 @@ +/* + + 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.List; +import java.util.Map; + +public class ParsedNetlistEntry { + + private List> typeCandidates; + private Map values; + private String rawType; + + public ParsedNetlistEntry() { + } + + public ParsedNetlistEntry(List> typeCandidates, String rawType, + Map values) { + super(); + this.typeCandidates = typeCandidates; + this.rawType = rawType; + this.values = values; + } + + public List> getTypeCandidates() { + return typeCandidates; + } + + public String getRawType() { + return rawType; + } + + public Map getValues() { + return values; + } +} diff --git a/diylc/diylc-core/src/org/diylc/presenter/Presenter.java b/diylc/diylc-core/src/org/diylc/presenter/Presenter.java index a609e324..e998b1f8 100644 --- a/diylc/diylc-core/src/org/diylc/presenter/Presenter.java +++ b/diylc/diylc-core/src/org/diylc/presenter/Presenter.java @@ -83,6 +83,7 @@ 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.Node; @@ -3403,6 +3404,34 @@ public class Presenter implements IPlugInPort { } } + @Override + public List getNetlistParserDefinitions() { + Set> classes; + try { + classes = Utils.getClasses("org.diylc.netlist"); + List result = new ArrayList(); + + for (Class clazz : classes) { + if (!Modifier.isAbstract(clazz.getModifiers()) && INetlistParser.class.isAssignableFrom(clazz)) { + result.add((INetlistParser) clazz.newInstance()); + } + } + + Collections.sort(result, new Comparator() { + + @Override + public int compare(INetlistParser o1, INetlistParser o2) { + return o1.getName().compareToIgnoreCase(o2.getName()); + } + }); + + return result; + } catch (Exception e) { + LOG.error("Could not load INetlistParserDefinition implementations", e); + return null; + } + } + // Test stuff @Override diff --git a/diylc/diylc-core/src/org/diylc/presenter/update.xml b/diylc/diylc-core/src/org/diylc/presenter/update.xml index e9a3afdc..6b65032b 100644 --- a/diylc/diylc-core/src/org/diylc/presenter/update.xml +++ b/diylc/diylc-core/src/org/diylc/presenter/update.xml @@ -3418,6 +3418,10 @@ 2020-12-28 00:00:00.000 CET + + NEW_FEATURE + Added 'File' -> 'Import Netlist' option that can import components from a Tango PCB file + NEW_FEATURE Português translation added (thanks Pedro Rizzi!) diff --git a/diylc/diylc-library/src/org/diylc/netlist/ISpiceMapper.java b/diylc/diylc-library/src/org/diylc/netlist/ISpiceMapper.java index 7a3063dc..c2613db1 100644 --- a/diylc/diylc-library/src/org/diylc/netlist/ISpiceMapper.java +++ b/diylc/diylc-library/src/org/diylc/netlist/ISpiceMapper.java @@ -28,7 +28,7 @@ package org.diylc.netlist; */ public interface ISpiceMapper { - int mapToSpiceNode(int pontIndex); + int mapToSpiceNode(int pointIndex); String getComment(); diff --git a/diylc/diylc-library/src/org/diylc/netlist/TangoNetlistParser.java b/diylc/diylc-library/src/org/diylc/netlist/TangoNetlistParser.java new file mode 100644 index 00000000..ed274b0e --- /dev/null +++ b/diylc/diylc-library/src/org/diylc/netlist/TangoNetlistParser.java @@ -0,0 +1,116 @@ +package org.diylc.netlist; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.Map; + +import org.diylc.components.passive.AxialElectrolyticCapacitor; +import org.diylc.components.passive.AxialFilmCapacitor; +import org.diylc.components.passive.CapacitorSymbol; +import org.diylc.components.passive.MultiSectionCapacitor; +import org.diylc.components.passive.RadialCeramicDiskCapacitor; +import org.diylc.components.passive.RadialElectrolytic; +import org.diylc.components.passive.RadialFilmCapacitor; +import org.diylc.components.passive.Resistor; +import org.diylc.components.passive.ResistorSymbol; +import org.diylc.components.semiconductors.DIL_IC; +import org.diylc.components.semiconductors.DiodeGlass; +import org.diylc.components.semiconductors.DiodePlastic; +import org.diylc.components.semiconductors.DiodeSymbol; +import org.diylc.components.semiconductors.LED; +import org.diylc.components.semiconductors.LEDSymbol; +import org.diylc.components.semiconductors.PhotoDiodeSymbol; +import org.diylc.components.semiconductors.SchottkyDiodeSymbol; +import org.diylc.components.semiconductors.ZenerDiodeSymbol; +import org.diylc.core.measures.Capacitance; +import org.diylc.core.measures.Resistance; + +public class TangoNetlistParser extends AbstractNetlistParser { + + @Override + public String getName() { + return "Tango"; + } + + @Override + public String getFileExt() { + return "pcb"; + } + + @Override + public String getItemRegex() { + return "\\[\\s*(?.+?)\\n(?.+?)\\n(?.+?)\\s*\\]"; + } + + @Override + public List> getComponentTypeCandidates(String type, + Map additionalOutputs) { + List> types = new ArrayList>(); + + if ("RESISTOR".equalsIgnoreCase(type)) { + types.add(Resistor.class); + types.add(ResistorSymbol.class); + } + + if (type.toUpperCase().startsWith("CAP_CERAMIC")) { + types.add(RadialCeramicDiskCapacitor.class); + types.add(CapacitorSymbol.class); + } + + if (type.toUpperCase().startsWith("CAP_FILM")) { + types.add(AxialFilmCapacitor.class); + types.add(RadialFilmCapacitor.class); + types.add(CapacitorSymbol.class); + } + + if (type.toUpperCase().startsWith("CAP_ELECTRO")) { + additionalOutputs.put("Polarized", true); + types.add(AxialElectrolyticCapacitor.class); + types.add(RadialElectrolytic.class); + types.add(MultiSectionCapacitor.class); + types.add(CapacitorSymbol.class); + } + + if (type.toUpperCase().startsWith("DIP")) { + try { + int pinCount = Integer.parseInt(type.substring(3)); + additionalOutputs.put("PinCount", DIL_IC.PinCount.valueOf("_" + pinCount)); + } catch (Exception e) { + } + + types.add(DIL_IC.class); + } + + if (type.equalsIgnoreCase("DIODE")) { + types.add(DiodeGlass.class); + types.add(DiodePlastic.class); + types.add(SchottkyDiodeSymbol.class); + types.add(PhotoDiodeSymbol.class); + types.add(DiodeSymbol.class); + types.add(ZenerDiodeSymbol.class); + } + + if (type.equalsIgnoreCase("LED")) { + types.add(LED.class); + types.add(LEDSymbol.class); + } + + return types; + } + + @Override + public List getPropertyNames() { + return Arrays.asList("Name", "Value"); + } + + @Override + public Object parseValue(String type, String propertyName, String valueStr) { + if ("RESISTOR".equalsIgnoreCase(type) && "Value".equals(propertyName)) + return Resistance.parseResistance(valueStr); + if ((type.toUpperCase().startsWith("CAP_CERAMIC") || type.toUpperCase().startsWith("CAP_FILM") + || type.toUpperCase().startsWith("CAP_ELECTRO")) && "Value".equals(propertyName)) + return Capacitance.parseCapacitance(valueStr + "F"); + return valueStr; + } +} diff --git a/diylc/diylc-library/test/org/diylc/netlist/TangoParserTests.java b/diylc/diylc-library/test/org/diylc/netlist/TangoParserTests.java new file mode 100644 index 00000000..26da2350 --- /dev/null +++ b/diylc/diylc-library/test/org/diylc/netlist/TangoParserTests.java @@ -0,0 +1,90 @@ +package org.diylc.netlist; + +import static org.junit.Assert.fail; + +import java.util.ArrayList; +import java.util.List; + +import org.junit.Assert; +import org.junit.Test; + +public class TangoParserTests { + + @Test + public void testParse1() { + String content = "[\r\n" + + "R9\r\n" + + "RESISTOR\r\n" + + "100k\r\n" + + "\r\n" + + "]\r\n" + + "[\r\n" + + "D4\r\n" + + "LED\r\n" + + "LED\r\n" + + "\r\n" + + "]\r\n" + + "[\r\n" + + "C3\r\n" + + "CAP_FILM\r\n" + + "82n\r\n" + + "\r\n" + + "]\r\n" + + "[\r\n" + + "C2\r\n" + + "CAP_ELECTRO_200\r\n" + + "1u\r\n" + + "\r\n" + + "]\r\n" + + "[\r\n" + + "C1\r\n" + + "CAP_CERAMIC1\r\n" + + "820p\r\n" + + "\r\n" + + "]\r\n" + + "[\r\n" + + "U2\r\n" + + "DIP14\r\n" + + "TL074\r\n" + + "\r\n" + + "]\r\n" + + "[\r\n" + + "D1\r\n" + + "DIODE\r\n" + + "Zener 12V\r\n" + + "\r\n" + + "]\r\n" + + "[\r\n" + + "B1\r\n" + + "PADS2\r\n" + + "9V\r\n" + + "\r\n" + + "]\r\n" + + "(\r\n" + + "unnamed_net15\r\n" + + "U4-11\r\n" + + ")\r\n" + + "(\r\n" + + "unnamed_net14\r\n" + + "U3-3\r\n" + + "U1-3\r\n" + + "R7-1\r\n" + + "R8-2\r\n" + + "U2-3\r\n" + + ")\r\n" + + "(\r\n" + + "unnamed_net13\r\n" + + "R1-2\r\n" + + "B1-1\r\n" + + ")"; + + List outputWarnings = new ArrayList(); + try { + TangoNetlistParser parser = new TangoNetlistParser(); + List parsed = parser.parse(content, outputWarnings); + Assert.assertNotNull(parsed); + } catch (NetlistParseException e) { + fail("Unexpected error:" + e.getMessage()); + } + } +} diff --git a/diylc/diylc-swing/lib/diylc-core.jar b/diylc/diylc-swing/lib/diylc-core.jar index b835fb11..3344bea2 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/library/main.jar b/diylc/diylc-swing/library/main.jar index 37eebdef..25252d94 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 b3584c76..b51ee85e 100644 --- a/diylc/diylc-swing/src/org/diylc/swing/ActionFactory.java +++ b/diylc/diylc-swing/src/org/diylc/swing/ActionFactory.java @@ -39,12 +39,14 @@ import java.util.List; import java.util.Map; import java.util.Set; import java.util.TreeMap; +import java.util.stream.Collectors; import javax.swing.AbstractAction; import javax.swing.Icon; import javax.swing.JLabel; import javax.swing.JOptionPane; import javax.swing.KeyStroke; +import javax.swing.filechooser.FileFilter; import org.apache.log4j.Logger; import org.diylc.appframework.miscutils.ConfigurationManager; @@ -71,7 +73,10 @@ 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.INetlistParser; import org.diylc.netlist.Netlist; +import org.diylc.netlist.ParsedNetlistComponent; +import org.diylc.netlist.ParsedNetlistEntry; import org.diylc.netlist.Summary; import org.diylc.presenter.Presenter; import org.diylc.serialization.ProjectFileManager; @@ -83,6 +88,7 @@ import org.diylc.swing.plugins.edit.ComponentTransferable; import org.diylc.swing.plugins.edit.FindDialog; import org.diylc.swing.plugins.file.BomDialog; import org.diylc.swing.plugins.file.FileFilterEnum; +import org.diylc.swing.plugins.file.NetlistImportDialog; import org.diylc.swingframework.ButtonDialog; import org.diylc.swingframework.CheckBoxListDialog; import org.diylc.swingframework.IDrawingProvider; @@ -121,6 +127,10 @@ 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); + } public SaveAction createSaveAction(IPlugInPort plugInPort, ISwingUI swingUI) { return new SaveAction(plugInPort, swingUI); @@ -473,6 +483,101 @@ public class ActionFactory { } } } + + public static class ImportNetlistAction extends AbstractAction { + + private static final long serialVersionUID = 1L; + + private IPlugInPort plugInPort; + private ISwingUI swingUI; + + private List parserDefinitions; + private List extensions; + private FileFilter filter; + + public ImportNetlistAction(IPlugInPort plugInPort, ISwingUI swingUI) { + super(); + this.plugInPort = plugInPort; + 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.filter = new FileFilter() { + + @Override + public boolean accept(File f) { + if (f.isDirectory()) { + return true; + } + String fileExt = f.getName(); + fileExt = fileExt.substring(fileExt.lastIndexOf('.') + 1).toLowerCase(); + for (String ext : extensions) { + if (ext.equals(fileExt)) { + return true; + } + } + return false; + } + + @Override + public String getDescription() { + return LangUtil.translate("Netlist files"); + } + }; + } + + @Override + public void actionPerformed(ActionEvent e) { + LOG.info("ImportNetlistAction triggered"); + + 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>() { + + @Override + public List doInBackground() throws Exception { + LOG.debug("Importing netlist from " + file.getAbsolutePath()); + List outputWarnings = new ArrayList(); + List entries = parser.parseFile(file.getAbsolutePath(), outputWarnings); + return entries; + } + + @Override + public void complete(List entries) { + try { + NetlistImportDialog dialog = DialogFactory.getInstance().createNetlistImportDialog(plugInPort,entries); + dialog.setVisible(true); + if (ButtonDialog.OK.equals(dialog.getSelectedButtonCaption())) { + List outputWarnings = new ArrayList(); + Map> results = dialog.getResults(); + List parsedComponents = entries.stream().map(entry -> + new ParsedNetlistComponent(results.get(entry.getRawType()), entry.getValues())).collect(Collectors.toList()); + List> components = parser.generateComponents(parsedComponents, outputWarnings); + plugInPort.pasteComponents(components, false, false); + } + } catch (Exception e) { + swingUI.showMessage("Could not import netlist file: " + e.getMessage(), "Error", + ISwingUI.ERROR_MESSAGE); + e.printStackTrace(); + } + } + + @Override + public void failed(Exception e) { + swingUI.showMessage("Could not import netlist file: " + e.getMessage(), "Error", + ISwingUI.ERROR_MESSAGE); + } + }, true); + } + } + } public static class SaveAction extends AbstractAction { diff --git a/diylc/diylc-swing/src/org/diylc/swing/gui/DialogFactory.java b/diylc/diylc-swing/src/org/diylc/swing/gui/DialogFactory.java index 459bad70..afd7c738 100644 --- a/diylc/diylc-swing/src/org/diylc/swing/gui/DialogFactory.java +++ b/diylc/diylc-swing/src/org/diylc/swing/gui/DialogFactory.java @@ -29,6 +29,7 @@ import javax.swing.filechooser.FileFilter; import org.diylc.appframework.miscutils.ConfigurationManager; import org.diylc.common.IPlugInPort; import org.diylc.common.PropertyWrapper; +import org.diylc.netlist.ParsedNetlistEntry; import org.diylc.plugins.cloud.model.UserEntity; import org.diylc.swing.gui.components.OverwritePromptFileChooser; import org.diylc.swing.gui.editor.PropertyEditorDialog; @@ -38,6 +39,7 @@ import org.diylc.swing.plugins.cloud.view.UploadDialog; import org.diylc.swing.plugins.cloud.view.UserEditDialog; import org.diylc.swing.plugins.edit.FindDialog; import org.diylc.swing.plugins.file.BomDialog; +import org.diylc.swing.plugins.file.NetlistImportDialog; import org.diylc.swingframework.AboutDialog; import org.diylc.swingframework.IFileChooserAccessory; import org.diylc.swingframework.ProgressDialog; @@ -229,4 +231,9 @@ public class DialogFactory { ProgressDialog dialog = new ProgressDialog(mainFrame, title, buttonCaptions, description, useProgress); return dialog; } + + public NetlistImportDialog createNetlistImportDialog(IPlugInPort plugInPort, List entries) { + NetlistImportDialog dialog = new NetlistImportDialog(mainFrame, plugInPort, entries); + return dialog; + } } 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 238a2154..17dd9b39 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,8 @@ 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"), TraceProximity("trace_proximity.png"), ComponentLarge("component_large.png"); + SnapToComponents("snap_to_components.png"), SnapToNone("snap_to_none.png"), TraceProximity("trace_proximity.png"), + ComponentLarge("component_large.png"), ImportNetlist("import_netlist.png"); protected String name; diff --git a/diylc/diylc-swing/src/org/diylc/swing/images/import_netlist.png b/diylc/diylc-swing/src/org/diylc/swing/images/import_netlist.png new file mode 100644 index 00000000..c41a35c9 Binary files /dev/null and b/diylc/diylc-swing/src/org/diylc/swing/images/import_netlist.png differ 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 0a61f47d..761ffaff 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 @@ -72,7 +72,8 @@ public class FileMenuPlugin implements IPlugIn, IDynamicSubmenuHandler { ActionFactory actionFactory = ActionFactory.getInstance(); swingUI.injectMenuAction(actionFactory.createNewAction(plugInPort), FILE_TITLE); swingUI.injectMenuAction(actionFactory.createOpenAction(plugInPort, swingUI), FILE_TITLE); - swingUI.injectMenuAction(actionFactory.createImportAction(plugInPort, swingUI), FILE_TITLE); + swingUI.injectMenuAction(actionFactory.createImportAction(plugInPort, swingUI), FILE_TITLE); + swingUI.injectMenuAction(actionFactory.createImportNetlistAction(plugInPort, swingUI), FILE_TITLE); swingUI.injectMenuAction(actionFactory.createSaveAction(plugInPort, swingUI), FILE_TITLE); swingUI.injectMenuAction(actionFactory.createSaveAsAction(plugInPort, swingUI), FILE_TITLE); swingUI.injectDynamicSubmenu("Recent Files", IconLoader.History.getIcon(), FILE_TITLE, this); diff --git a/diylc/diylc-swing/src/org/diylc/swing/plugins/file/NetlistImportDialog.java b/diylc/diylc-swing/src/org/diylc/swing/plugins/file/NetlistImportDialog.java new file mode 100644 index 00000000..b6d27ca9 --- /dev/null +++ b/diylc/diylc-swing/src/org/diylc/swing/plugins/file/NetlistImportDialog.java @@ -0,0 +1,316 @@ +/* + * + * 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.swing.plugins.file; + +import java.awt.Component; +import java.awt.Dimension; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Optional; +import java.util.stream.Collectors; + +import javax.swing.DefaultCellEditor; +import javax.swing.JComboBox; +import javax.swing.JComponent; +import javax.swing.JFrame; +import javax.swing.JLabel; +import javax.swing.JList; +import javax.swing.JScrollPane; +import javax.swing.JTable; +import javax.swing.ListCellRenderer; +import javax.swing.table.AbstractTableModel; +import javax.swing.table.DefaultTableCellRenderer; +import javax.swing.table.TableCellRenderer; +import javax.swing.table.TableColumnModel; + +import org.diylc.appframework.miscutils.ConfigurationManager; +import org.diylc.common.ComponentType; +import org.diylc.common.IPlugInPort; +import org.diylc.lang.LangUtil; +import org.diylc.netlist.ParsedNetlistEntry; +import org.diylc.swingframework.ButtonDialog; + +public class NetlistImportDialog extends ButtonDialog { + + private static final long serialVersionUID = 1L; + + public static String NETLIST_DEFAULT_TYPES = "netlistDefaultTypes"; + + private List> selectedTypes; + private List setDefault; + private Map, String> componentTypeDisplay; + private List> allTypes; + private Map netlistDefaultTypes; + private List rawTypes; + private Map>> availableTypes; + + private JTable table; + + @SuppressWarnings("unchecked") + public NetlistImportDialog(JFrame owner, IPlugInPort plugInPort, + List entries) { + super(owner, LangUtil.translate("Import Netlist"), + new String[] {ButtonDialog.OK, ButtonDialog.CANCEL}); + this.rawTypes = entries.stream().map(x -> x.getRawType()).distinct().sorted().collect(Collectors.toList()); + this.availableTypes = new HashMap>>(); + for (ParsedNetlistEntry entry : entries) { + this.availableTypes.put(entry.getRawType(), entry.getTypeCandidates()); + } + + this.netlistDefaultTypes = (Map) ConfigurationManager.getInstance() + .readObject(NETLIST_DEFAULT_TYPES, null); + this.setDefault = rawTypes.stream().map( + type -> netlistDefaultTypes != null && netlistDefaultTypes.containsKey(type)) + .collect(Collectors.toList()); + + Map> componentTypes = plugInPort.getComponentTypes(); + List allComponentTypes = new ArrayList(); + this.componentTypeDisplay = new HashMap, String>(); + for (Map.Entry> e : componentTypes.entrySet()) { + for (ComponentType type : e.getValue()) { + this.componentTypeDisplay.put(type.getInstanceClass(), e.getKey() + " - " + type.getName()); + allComponentTypes.add(type); + } + } + this.allTypes = allComponentTypes.stream() + .sorted((t1, t2) -> (t1.getCategory() + " = " + t1.getName()) + .compareToIgnoreCase((t2.getCategory() + " = " + t2.getName()))) + .map(x -> x.getInstanceClass()).collect(Collectors.toList()); + + this.selectedTypes = + rawTypes.stream().map(type -> getDefaultType(type)).collect(Collectors.toList()); + + setPreferredSize(new Dimension(600, 400)); + layoutGui(); + } + + private Class getDefaultType(String rawType) { + if (this.netlistDefaultTypes != null + && this.netlistDefaultTypes.containsKey(rawType)) { + + Optional> first = this.allTypes.stream() + .filter( + e -> e.getCanonicalName().equals(this.netlistDefaultTypes.get(rawType))) + .findFirst(); + if (first.isPresent()) + return first.get(); + } + + List> list = this.availableTypes.get(rawType); + return list == null || list.isEmpty() ? null : list.get(0); + } + + public Map> getResults() { + Map> result = new HashMap>(); + for (int i = 0; i < rawTypes.size(); i++) + result.put(rawTypes.get(i), selectedTypes.get(i)); + return result; + } + + @Override + protected JComponent getMainComponent() { + JScrollPane scrollPane = new JScrollPane(getTable()); + scrollPane.setVerticalScrollBarPolicy(JScrollPane.VERTICAL_SCROLLBAR_ALWAYS); + scrollPane.setHorizontalScrollBarPolicy(JScrollPane.HORIZONTAL_SCROLLBAR_AS_NEEDED); + return scrollPane; + } + + @SuppressWarnings("unchecked") + @Override + public void setVisible(boolean b) { + super.setVisible(b); + + // hiding, store changes in config + if (!b && OK.equals(getSelectedButtonCaption())) { + netlistDefaultTypes = (Map) ConfigurationManager.getInstance() + .readObject(NETLIST_DEFAULT_TYPES, null); + if (netlistDefaultTypes == null) + netlistDefaultTypes = new HashMap(); + for (int i = 0; i < rawTypes.size(); i++) + if (setDefault.get(i)) + netlistDefaultTypes.put(rawTypes.get(i), + selectedTypes.get(i).getCanonicalName()); + else + netlistDefaultTypes.remove(rawTypes.get(i)); + + ConfigurationManager.getInstance().writeValue(NETLIST_DEFAULT_TYPES, netlistDefaultTypes); + } + } + + public JTable getTable() { + if (table == null) { + table = new JTable(new NetlistTableModel()); + table.setFillsViewportHeight(true); + JComboBox> comboBox = new JComboBox>(); + comboBox.setRenderer(new TypeListCellRenderer()); + table.setDefaultRenderer(Class.class, new TypeTableCellRenderer()); + table.setDefaultEditor(Class.class, new TypeCellEditor(comboBox)); + resizeColumnWidth(table); + } + return table; + } + + public void resizeColumnWidth(JTable table) { + final TableColumnModel columnModel = table.getColumnModel(); + for (int column = 0; column < table.getColumnCount(); column++) { + int width = 48; // Min width + for (int row = 0; row < table.getRowCount(); row++) { + TableCellRenderer renderer = table.getCellRenderer(row, column); + Component comp = table.prepareRenderer(renderer, row, column); + width = Math.max(comp.getPreferredSize().width + 1, width); + } + if (width > 300) + width = 300; + columnModel.getColumn(column).setPreferredWidth(width); + } + } + + class NetlistTableModel extends AbstractTableModel { + + private static final long serialVersionUID = 1L; + + @Override + public int getRowCount() { + return rawTypes.size(); + } + + @Override + public int getColumnCount() { + return 3; + } + + @Override + public String getColumnName(int column) { + switch (column) { + case 0: + return "Imported Type"; + case 1: + return "DIYLC Component Type"; + case 2: + return "Default"; + } + return null; + } + + @Override + public Class getColumnClass(int columnIndex) { + switch (columnIndex) { + case 0: + return String.class; + case 1: + return Class.class; + case 2: + return Boolean.class; + } + return null; + } + + @Override + public boolean isCellEditable(int rowIndex, int columnIndex) { + return columnIndex >= 1; + } + + @Override + public Object getValueAt(int rowIndex, int columnIndex) { + switch (columnIndex) { + case 0: + return rawTypes.get(rowIndex); + case 1: + return selectedTypes.get(rowIndex); + case 2: + return setDefault.get(rowIndex); + } + return null; + } + + @Override + public void setValueAt(Object aValue, int rowIndex, int columnIndex) { + switch (columnIndex) { + case 1: + selectedTypes.set(rowIndex, (Class) aValue); + break; + case 2: + setDefault.set(rowIndex, (Boolean) aValue); + break; + } + } + } + + class TypeListCellRenderer extends JLabel implements ListCellRenderer> { + + private static final long serialVersionUID = 1L; + + @Override + public Component getListCellRendererComponent(JList> list, Class value, + int index, boolean isSelected, boolean cellHasFocus) { + if (value == null) + setText("Undefined - skip this component"); + else + setText(componentTypeDisplay.get(value)); + return this; + } + } + + class TypeTableCellRenderer extends DefaultTableCellRenderer { + + private static final long serialVersionUID = 1L; + + @Override + public Component getTableCellRendererComponent(JTable table, Object value, boolean isSelected, + boolean hasFocus, int row, int column) { + JLabel label = (JLabel) super.getTableCellRendererComponent(table, value, isSelected, + hasFocus, row, column); + if (value == null) + label.setText("Undefined - skip this component"); + else + label.setText(componentTypeDisplay.get(value)); + return label; + } + } + + class TypeCellEditor extends DefaultCellEditor { + + private static final long serialVersionUID = 1L; + + public TypeCellEditor(JComboBox> comboBox) { + super(comboBox); + } + + @SuppressWarnings("unchecked") + @Override + public Component getTableCellEditorComponent(JTable table, Object value, boolean isSelected, + int row, int column) { + JComboBox> comboBox = (JComboBox>) super.getTableCellEditorComponent(table, + value, isSelected, row, column); + comboBox.removeAllItems(); + List> showTypes = availableTypes.get(rawTypes.get(row)); + if (showTypes == null || showTypes.isEmpty()) + showTypes = allTypes; + + comboBox.addItem(null); + for (Class c : showTypes) { + comboBox.addItem(c); + } + comboBox.setSelectedItem(selectedTypes.get(row)); + return comboBox; + } + } +}