From f03a0b1417989b9352726cd1c4444ece7ada94e4 Mon Sep 17 00:00:00 2001 From: Bane Stojkovic Date: Sat, 5 Apr 2025 19:44:02 +0200 Subject: [PATCH] Use spice netlist for chatbot input --- .../netlist/AbstractNetlistAnalyzer.java | 6 +- .../java/org/diylc/netlist/ISpiceMapper.java | 0 .../java/org/diylc/netlist/SpiceAnalyzer.java | 68 +++++++ .../org/diylc/netlist/SpiceSumarizer.java | 133 +++++++++++++ .../chatbot/service/ChatbotService.java | 64 ++++--- .../java/org/diylc/netlist/SpiceAnalyzer.java | 181 ------------------ 6 files changed, 239 insertions(+), 213 deletions(-) rename diylc/{diylc-library => diylc-core}/src/main/java/org/diylc/netlist/ISpiceMapper.java (100%) create mode 100644 diylc/diylc-core/src/main/java/org/diylc/netlist/SpiceAnalyzer.java create mode 100644 diylc/diylc-core/src/main/java/org/diylc/netlist/SpiceSumarizer.java delete mode 100644 diylc/diylc-library/src/main/java/org/diylc/netlist/SpiceAnalyzer.java diff --git a/diylc/diylc-core/src/main/java/org/diylc/netlist/AbstractNetlistAnalyzer.java b/diylc/diylc-core/src/main/java/org/diylc/netlist/AbstractNetlistAnalyzer.java index 80b56218..c7c53192 100644 --- a/diylc/diylc-core/src/main/java/org/diylc/netlist/AbstractNetlistAnalyzer.java +++ b/diylc/diylc-core/src/main/java/org/diylc/netlist/AbstractNetlistAnalyzer.java @@ -271,21 +271,21 @@ public abstract class AbstractNetlistAnalyzer { return res; } - protected int find(Node node, List groups) { + public static int find(Node node, List groups) { for (int i = 0; i < groups.size(); i++) if (groups.get(i).getNodes().contains(node)) return i; return -1; } - protected boolean intersect(List groups, Node node) { + public static boolean intersect(List groups, Node node) { for (Group g : groups) if (g.getNodes().contains(node)) return true; return false; } - protected Node intersect(List groups, Group group) { + public static Node intersect(List groups, Group group) { for (Group g : groups) for (Node node : group.getNodes()) if (g.getNodes().contains(node)) diff --git a/diylc/diylc-library/src/main/java/org/diylc/netlist/ISpiceMapper.java b/diylc/diylc-core/src/main/java/org/diylc/netlist/ISpiceMapper.java similarity index 100% rename from diylc/diylc-library/src/main/java/org/diylc/netlist/ISpiceMapper.java rename to diylc/diylc-core/src/main/java/org/diylc/netlist/ISpiceMapper.java diff --git a/diylc/diylc-core/src/main/java/org/diylc/netlist/SpiceAnalyzer.java b/diylc/diylc-core/src/main/java/org/diylc/netlist/SpiceAnalyzer.java new file mode 100644 index 00000000..36d2422a --- /dev/null +++ b/diylc/diylc-core/src/main/java/org/diylc/netlist/SpiceAnalyzer.java @@ -0,0 +1,68 @@ +/* + + DIY Layout Creator (DIYLC). + Copyright (c) 2009-2018 held jointly by the individual authors. + + This file is part of DIYLC. + + DIYLC is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + DIYLC is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with DIYLC. If not, see . + +*/ +package org.diylc.netlist; + +import java.util.ArrayList; +import java.util.EnumSet; +import java.util.List; +import java.util.Set; +import java.util.stream.Collectors; + +import org.diylc.common.INetlistAnalyzer; +import org.diylc.common.NetlistSwitchPreference; +import org.diylc.core.IDIYComponent; +import org.diylc.presenter.ComponentProcessor; + +public class SpiceAnalyzer extends AbstractNetlistAnalyzer implements INetlistAnalyzer { + + public SpiceAnalyzer() { + } + + @Override + public String getName() { + return "Generate Spice Netlist"; + } + + @Override + public String getShortName() { + return "spice"; + } + + @Override + public String getIconName() { + return "JarBeanInto"; + } + + @Override + public String getFontName() { + return "Courier New"; + } + + @Override + public Set getSwitchPreference() { + return EnumSet.allOf(NetlistSwitchPreference.class); + } + + protected Summary summarize(Netlist netlist) throws TreeException { + return new Summary(netlist, SpiceSumarizer.summarize(netlist, true)); + } +} diff --git a/diylc/diylc-core/src/main/java/org/diylc/netlist/SpiceSumarizer.java b/diylc/diylc-core/src/main/java/org/diylc/netlist/SpiceSumarizer.java new file mode 100644 index 00000000..50f1cfb8 --- /dev/null +++ b/diylc/diylc-core/src/main/java/org/diylc/netlist/SpiceSumarizer.java @@ -0,0 +1,133 @@ +package org.diylc.netlist; + +import org.diylc.common.ComponentType; +import org.diylc.core.IDIYComponent; +import org.diylc.presenter.ComponentProcessor; + +import java.util.ArrayList; +import java.util.List; +import java.util.stream.Collectors; + +import static org.diylc.netlist.AbstractNetlistAnalyzer.find; + +public class SpiceSumarizer { + + public static String summarize(Netlist netlist, boolean useHTML) throws TreeException { + + // grab all components that are in the netlist + List groups = netlist.getSortedGroups(); + + List> allComponents = groups.stream() + .flatMap(x -> x.getNodes().stream() + .map(y -> y.getComponent())) + .distinct() + .collect(Collectors.toList()); + + int unconnectedIndex = groups.size(); + + int maxLen = 0; + for (IDIYComponent c : allComponents) { + if (c.getName().length() > maxLen) + maxLen = c.getName().length(); + } + + List lines = new ArrayList(); + + for (IDIYComponent c : allComponents) { + StringBuilder sb = new StringBuilder(); + // change the prefix to match spice convention if needed + String name = c.getName(); + String prefix = null; + if (c instanceof ISpiceMapper) + prefix = ((ISpiceMapper)c).getPrefix(); + if (prefix != null && !name.toLowerCase().startsWith(prefix.toLowerCase())) + name = prefix + name; + + sb.append(fill(name, (int) (Math.ceil(maxLen / 5.0) * 5), useHTML)); + sb.append(" "); + List nodeIndices = new ArrayList(); + + // find node indices for each control point + for (int i = 0; i < c.getControlPointCount(); i++) { + // skip non-sticky points + if (c.getControlPointNodeName(i) == null) + continue; + + int pointIndex = i; + + // remap if needed + if (c instanceof ISpiceMapper) + pointIndex = ((ISpiceMapper)c).mapToSpiceNode(pointIndex); + + int nodeIndex = find(new Node(c, pointIndex, 0), groups); + if (nodeIndex < 0) + nodeIndex = unconnectedIndex++; + + // 1-based convention + nodeIndex++; + + nodeIndices.add(nodeIndex); + } + + // output to spice + for (Integer nodeIndex : nodeIndices) { + sb.append(fill(formatSpiceNode(nodeIndex), 5, useHTML)); + sb.append(" "); + } + + String comment = outputComponentValue(c, sb); + + if (c instanceof ISpiceMapper) { + String spiceComment = ((ISpiceMapper)c).getComment(); + if (spiceComment != null) + comment += spiceComment; + } + sb.append(" ; ").append(comment); + if (useHTML) + sb.append("
"); + lines.add(sb.toString()); + } + + return lines + .stream() + .sorted() + .collect(Collectors.joining("\n")); + } + + private static String outputComponentValue(IDIYComponent c, StringBuilder sb) { + ComponentType componentType = ComponentProcessor.getInstance() + .extractComponentTypeFrom((Class>) c.getClass()); + + if (c.getValue() == null || c.getValue().toString().trim().isEmpty()) { + // if ( + // Void.class.equals(((ParameterizedType) c.getClass() + // .getGenericSuperclass()).getActualTypeArguments()[0])) { + // return typeName; + // } + String typeName = componentType.getName(); + if (typeName.contains(" ")) { + sb.append("\"").append(typeName).append("\""); + } else { + sb.append(typeName); + } + } else { + if (c.getValue().toString().contains(" ")) { + sb.append("\"").append(c.getValue()).append("\""); + } else { + sb.append(c.getValue()); + } + } + return componentType.getName() + " [" + componentType.getCategory() + "]"; + } + + private static String formatSpiceNode(int i) { + return String.format("N%03d" , i); + } + + private static String fill(String source, int desiredLength, boolean useHTML) { + String res = source; + for (int i = 0; i < desiredLength - source.length(); i++) + res += useHTML ? " " : " "; + return res; + } +} diff --git a/diylc/diylc-core/src/main/java/org/diylc/plugins/chatbot/service/ChatbotService.java b/diylc/diylc-core/src/main/java/org/diylc/plugins/chatbot/service/ChatbotService.java index 3691b09d..eb70a265 100644 --- a/diylc/diylc-core/src/main/java/org/diylc/plugins/chatbot/service/ChatbotService.java +++ b/diylc/diylc-core/src/main/java/org/diylc/plugins/chatbot/service/ChatbotService.java @@ -31,10 +31,7 @@ import org.diylc.common.IPlugInPort; import org.diylc.common.PropertyWrapper; import org.diylc.core.IDIYComponent; import org.diylc.core.ISwitch; -import org.diylc.netlist.Group; -import org.diylc.netlist.Netlist; -import org.diylc.netlist.NetlistException; -import org.diylc.netlist.Node; +import org.diylc.netlist.*; import org.diylc.plugins.chatbot.model.ChatMessageEntity; import org.diylc.plugins.chatbot.model.IChatbotAPI; import org.diylc.plugins.chatbot.model.SubscriptionEntity; @@ -127,7 +124,6 @@ public class ChatbotService { private static final Set> PROPERTY_TYPES_TO_SKIP = Set.of(Font.class, Color.class); - @SuppressWarnings("unchecked") private String extractNetlists() throws NetlistException { List netlists = plugInPort.extractNetlists(true); @@ -137,26 +133,27 @@ public class ChatbotService { StringBuilder sb = new StringBuilder(); - Set> components = netlists.stream() - .flatMap(n -> n.getGroups().stream() - .flatMap(g -> g.getNodes().stream(). - map(Node::getComponent))) - .collect(Collectors.toSet()); +// Set> components = netlists.stream() +// .flatMap(n -> n.getGroups().stream() +// .flatMap(g -> g.getNodes().stream(). +// map(Node::getComponent))) +// .collect(Collectors.toSet()); +// +// if (!components.isEmpty()) { +// sb.append( +// "Components (each element of the JSON array represents one component with all the relevant properties): "); +// outputComponents(components, sb); +// } - if (!components.isEmpty()) { - sb.append( - "Components (each element of the JSON array represents one component with all the relevant properties): "); - outputComponents(components, sb); - } - - Set> switches = plugInPort.getCurrentProject().getComponents() + Set switches = plugInPort.getCurrentProject().getComponents() .stream().filter(x -> x instanceof ISwitch) + .map(x -> (ISwitch)x) .collect(Collectors.toSet());; if (!switches.isEmpty()) { sb.append( - "Switches (each element of the JSON array represents one switch with all the relevant properties): "); - outputComponents(switches, sb); + "Switches (each row represents one switch, starts with the switch name and lists key properties of a switch):\n\n"); + outputSwitches(switches, sb); } for (Netlist netlist : netlists) { @@ -164,21 +161,30 @@ public class ChatbotService { if (!netlist.getSwitchSetup().isEmpty()) { sb.append("Switch configuration: ").append(netlist.getSwitchSetup()).append(". "); } - sb.append("Each line below represents one JSON array that lists all the nodes that are connected together. Nodes are labeled as (component name).(node name):\n\n"); - for (Group v : netlist.getSortedGroups()) { -// sb.append(v.getSortedNodes()).append("\n"); - List nodeList = v.getSortedNodes().stream().map(x -> x.getComponent().getName() + "." + x.getDisplayName()).toList(); - try { - String json = MAPPER.writeValueAsString(nodeList); - sb.append(json).append("\n"); - } catch (Exception e) { - LOG.error("Failed to serialize component descriptors to JSON", e); - } + sb.append("Effective circuit netlist") + .append(netlist.getSwitchSetup().isEmpty() ? "" : " in this switch configuration") + .append(":\n\n"); + try { + sb.append(SpiceSumarizer.summarize(netlist, false)); + } catch (TreeException e) { + LOG.error("Error exporting netlist to Spice", e); } } return sb.toString(); } + private static void outputSwitches(Set switches, StringBuilder sb) { + for (ISwitch sw : switches) { + IDIYComponent component = (IDIYComponent) sw; + ComponentType componentType = ComponentProcessor.getInstance() + .extractComponentTypeFrom((Class>) component.getClass()); + sb.append(component.getName()) + .append(" ").append(component.getValueForDisplay()).append(" ") + .append(componentType.getName()) + .append(" [").append(componentType.getCategory()).append("]\n"); + } + } + private static void outputComponents(Set> components, StringBuilder sb) { List> componentDescriptors = new ArrayList<>(); diff --git a/diylc/diylc-library/src/main/java/org/diylc/netlist/SpiceAnalyzer.java b/diylc/diylc-library/src/main/java/org/diylc/netlist/SpiceAnalyzer.java deleted file mode 100644 index eda49bde..00000000 --- a/diylc/diylc-library/src/main/java/org/diylc/netlist/SpiceAnalyzer.java +++ /dev/null @@ -1,181 +0,0 @@ -/* - - 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.lang.reflect.ParameterizedType; -import java.util.ArrayList; -import java.util.EnumSet; -import java.util.List; -import java.util.Set; -import java.util.stream.Collectors; - -import org.diylc.common.INetlistAnalyzer; -import org.diylc.common.NetlistSwitchPreference; -import org.diylc.core.IDIYComponent; -import org.diylc.netlist.Group; -import org.diylc.netlist.Netlist; -import org.diylc.netlist.NetlistAnalyzer; -import org.diylc.netlist.Node; -import org.diylc.netlist.Summary; -import org.diylc.netlist.TreeException; -import org.diylc.presenter.ComponentProcessor; - -public class SpiceAnalyzer extends AbstractNetlistAnalyzer implements INetlistAnalyzer { - - public SpiceAnalyzer() { - } - - @Override - public String getName() { - return "Generate Spice Netlist"; - } - - @Override - public String getShortName() { - return "spice"; - } - - @Override - public String getIconName() { - return "JarBeanInto"; - } - - @Override - public String getFontName() { - return "Courier New"; - } - - @Override - public Set getSwitchPreference() { - return EnumSet.allOf(NetlistSwitchPreference.class); - } - - protected Summary summarize(Netlist netlist) throws TreeException { - // grab all components that are in the netlist - List groups = netlist.getSortedGroups(); - - List> allComponents = groups.stream() - .flatMap(x -> x.getNodes().stream() - .map(y -> y.getComponent())) - .distinct() - .collect(Collectors.toList()); - - int unconnectedIndex = groups.size(); - - int maxLen = 0; - for (IDIYComponent c : allComponents) { - if (c.getName().length() > maxLen) - maxLen = c.getName().length(); - } - - List lines = new ArrayList(); - - for (IDIYComponent c : allComponents) { - StringBuilder sb = new StringBuilder(); - // change the prefix to match spice convention if needed - String name = c.getName(); - String prefix = null; - if (c instanceof ISpiceMapper) - prefix = ((ISpiceMapper)c).getPrefix(); - if (prefix != null && !name.toLowerCase().startsWith(prefix.toLowerCase())) - name = prefix + name; - - sb.append(fill(name, (int) (Math.ceil(maxLen / 5.0) * 5))); - sb.append(" "); - List nodeIndices = new ArrayList(); - - // find node indices for each control point - for (int i = 0; i < c.getControlPointCount(); i++) { - // skip non-sticky points - if (c.getControlPointNodeName(i) == null) - continue; - - int pointIndex = i; - - // remap if needed - if (c instanceof ISpiceMapper) - pointIndex = ((ISpiceMapper)c).mapToSpiceNode(pointIndex); - - int nodeIndex = find(new Node(c, pointIndex, 0), groups); - if (nodeIndex < 0) - nodeIndex = unconnectedIndex++; - - // 1-based convention - nodeIndex++; - - nodeIndices.add(nodeIndex); - } - - // output to spice - for (Integer nodeIndex : nodeIndices) { - sb.append(fill(formatSpiceNode(nodeIndex), 5)); - sb.append(" "); - } - - sb.append(formatValue(c)); - - if (c instanceof ISpiceMapper) { - String comment = ((ISpiceMapper)c).getComment(); - if (comment != null) - sb.append(" ; ").append(comment); - } - sb.append("
"); - lines.add(sb.toString()); - } - - return new Summary(netlist, lines - .stream() - .sorted() - .collect(Collectors.joining("\n"))); - } - - private static Object formatValue(IDIYComponent c) { - if (c.getValue() == null || c.getValue().toString().trim().isEmpty()) { - String typeName = ComponentProcessor.getInstance() - .extractComponentTypeFrom((Class>) c.getClass()).getName(); -// if ( -// Void.class.equals(((ParameterizedType) c.getClass() -// .getGenericSuperclass()).getActualTypeArguments()[0])) { -// return typeName; -// } - if (typeName.contains(" ")) { - return "\"" + typeName + "\""; - } - return typeName; - } - if (c.getValue().toString().contains(" ")) { - return "\"" + c.getValue() + "\""; - } - return c.getValue(); - } - - private static String formatSpiceNode(int i) { - return String.format("N%03d" , i); - } - - private String fill(String source, int desiredLength) { - String res = source; - for (int i = 0; i < desiredLength - source.length(); i++) - res += " "; - return res; - } -}