From eaeae002113fde32c43f860059f454b9af414c08 Mon Sep 17 00:00:00 2001 From: Bane Stojkovic Date: Thu, 15 Oct 2026 15:45:33 +0200 Subject: [PATCH 01/11] Added Rectangular shape to LED component in addition to Round; added ability to set LED lead spacing --- .../diylc-core/src/main/resources/update.xml | 4 + .../diylc/components/semiconductors/LED.java | 76 ++++++++++++++++++- 2 files changed, 78 insertions(+), 2 deletions(-) diff --git a/diylc/diylc-core/src/main/resources/update.xml b/diylc/diylc-core/src/main/resources/update.xml index 410c65df..f09b0b14 100644 --- a/diylc/diylc-core/src/main/resources/update.xml +++ b/diylc/diylc-core/src/main/resources/update.xml @@ -593,6 +593,10 @@ NEW_FEATURE Holding 'Shift' when starting to drag components or control points keeps the whole drag strictly horizontal or vertical, in the direction of the initial gesture. Holding 'Shift' while drawing a new wire or two-point component does the same + + NEW_FEATURE + Added Rectangular shape to LED component in addition to Round; added ability to set LED lead spacing + IMPROVEMENT 'S1 Switch' can now be rotated to match the orientation of the potentiometer it is mounted on, so the switching matrix stays correct relative to the pot lugs diff --git a/diylc/diylc-library/src/main/java/org/diylc/components/semiconductors/LED.java b/diylc/diylc-library/src/main/java/org/diylc/components/semiconductors/LED.java index ca3da9e9..a4cc0b02 100644 --- a/diylc/diylc-library/src/main/java/org/diylc/components/semiconductors/LED.java +++ b/diylc/diylc-library/src/main/java/org/diylc/components/semiconductors/LED.java @@ -23,6 +23,7 @@ package org.diylc.components.semiconductors; import java.awt.Color; import java.awt.Graphics2D; +import java.awt.Rectangle; import java.awt.Shape; import java.awt.geom.Area; import java.awt.geom.Ellipse2D; @@ -35,6 +36,7 @@ import org.diylc.core.CreationMethod; import org.diylc.core.IDIYComponent; import org.diylc.core.annotations.ComponentDescriptor; import org.diylc.core.annotations.EditableProperty; +import org.diylc.core.annotations.PositiveNonZeroMeasureValidator; import org.diylc.core.gerber.IGerberComponentSimple; import org.diylc.core.measures.Size; import org.diylc.core.measures.SizeUnit; @@ -47,10 +49,14 @@ public class LED extends AbstractLeadedComponent implements IGerberCompo private static final long serialVersionUID = 1L; public static Size DEFAULT_SIZE = new Size(5d, SizeUnit.mm); + public static Size DEFAULT_WIDTH = new Size(2d, SizeUnit.mm); + public static Size PIN_SPACING = new Size(2.54d, SizeUnit.mm); public static Color BODY_COLOR = Color.decode("#5DFC0A"); public static Color BORDER_COLOR = BODY_COLOR.darker(); private String value = ""; + private BodyShape shape = BodyShape.ROUND; + private Size pinSpacing = PIN_SPACING; public LED() { super(); @@ -67,6 +73,35 @@ public class LED extends AbstractLeadedComponent implements IGerberCompo this.value = value; } + @EditableProperty + public BodyShape getShape() { + if (shape == null) { + shape = BodyShape.ROUND; + } + return shape; + } + + public void setShape(BodyShape shape) { + this.shape = shape; + } + + @Override + protected int calculatePinSpacing(Rectangle shapeRect) { + return (int) getPinSpacing().convertToPixels(); + } + + @EditableProperty(name = "Lead Spacing") + public Size getPinSpacing() { + if (pinSpacing == null) { + pinSpacing = PIN_SPACING; + } + return pinSpacing; + } + + public void setPinSpacing(Size pinSpacing) { + this.pinSpacing = pinSpacing; + } + @Override public String getControlPointNodeName(int index) { switch (index) { @@ -97,7 +132,7 @@ public class LED extends AbstractLeadedComponent implements IGerberCompo @Override protected Size getDefaultWidth() { - return DEFAULT_SIZE; + return DEFAULT_WIDTH; } @Override @@ -107,6 +142,10 @@ public class LED extends AbstractLeadedComponent implements IGerberCompo @Override protected Shape getBodyShape() { + if (getShape() == BodyShape.RECTANGLE) { + return new Rectangle2D.Double(0, 0, getClosestOdd(getLength().convertToPixels()), + getClosestOdd(getWidth().convertToPixels())); + } int size = getClosestOdd((int) (getLength().convertToPixels() * 1.2)); Area area = new Area(new Ellipse2D.Double(0, 0, size, size)); area.intersect(new Area(new Rectangle2D.Double(0, 0, getLength().convertToPixels() * 1.15, size))); @@ -115,7 +154,7 @@ public class LED extends AbstractLeadedComponent implements IGerberCompo @Override protected void decorateComponentBody(Graphics2D g2d, boolean outlineMode) { - if (!outlineMode) { + if (!outlineMode && getShape() == BodyShape.ROUND) { int size = getClosestOdd((int) (getLength().convertToPixels() * 1.2)); int innerSize = getClosestOdd(getLength().convertToPixels()); int x = (size - innerSize) / 2; @@ -125,14 +164,47 @@ public class LED extends AbstractLeadedComponent implements IGerberCompo } } + // the border always follows the body color; the inherited borderColor field stays only so + // that older files and older builds keep reading it + @Override + public Color getBorderColor() { + Color bodyColor = getBodyColor(); + return bodyColor == null ? null : bodyColor.darker(); + } + + @Deprecated + @Override + public void setBorderColor(Color borderColor) { + super.setBorderColor(borderColor); + } + @EditableProperty(name = "Size") @Override public Size getLength() { return super.getLength(); } + // only rectangular LEDs use the width; a round body is sized by its diameter alone + @EditableProperty(name = "Width", validatorClass = PositiveNonZeroMeasureValidator.class, + sortOrder = 102) @Override public Size getWidth() { return super.getWidth(); } + + public enum BodyShape { + + ROUND("Round"), RECTANGLE("Rectangle"); + + private String label; + + private BodyShape(String label) { + this.label = label; + } + + @Override + public String toString() { + return label; + } + } } From 9a2303652ab2ced7d8bb309438dc8b994f0a9133 Mon Sep 17 00:00:00 2001 From: Bane Stojkovic Date: Thu, 15 Oct 2026 20:05:41 +0200 Subject: [PATCH 02/11] renamed misspelled property and type --- .../ISerializationAliasProvider.java | 44 +++++++ .../serialization/ProjectFileManager.java | 8 ++ .../components/AbstractLeadedComponent.java | 30 +++-- .../components/connectivity/CopperTrace.java | 4 +- .../diylc/components/connectivity/Jumper.java | 4 +- .../diylc/components/connectivity/Line.java | 4 +- .../diylc/components/passive/Resistor.java | 2 +- .../semiconductors/BridgeRectifier.java | 26 ++-- .../diylc/components/shapes/TapeMeasure.java | 4 +- .../serialization/ComponentAliasProvider.java | 41 +++++++ .../ComponentAliasProviderTest.java | 115 ++++++++++++++++++ 11 files changed, 253 insertions(+), 29 deletions(-) create mode 100644 diylc/diylc-core/src/main/java/org/diylc/serialization/ISerializationAliasProvider.java create mode 100644 diylc/diylc-library/src/main/java/org/diylc/serialization/ComponentAliasProvider.java create mode 100644 diylc/diylc-library/src/test/java/org/diylc/serialization/ComponentAliasProviderTest.java diff --git a/diylc/diylc-core/src/main/java/org/diylc/serialization/ISerializationAliasProvider.java b/diylc/diylc-core/src/main/java/org/diylc/serialization/ISerializationAliasProvider.java new file mode 100644 index 00000000..c522e509 --- /dev/null +++ b/diylc/diylc-core/src/main/java/org/diylc/serialization/ISerializationAliasProvider.java @@ -0,0 +1,44 @@ +/* + + DIY Layout Creator (DIYLC). + Copyright (c) 2009-2025 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.serialization; + +import com.thoughtworks.xstream.XStream; + +import org.diylc.core.annotations.Indexed; + +/** + * Registers XStream aliases for classes and fields that were renamed after they shipped, so that + * the old names keep resolving. Implementations live next to the classes they alias and are + * applied to every {@link XStream} instance prepared by {@link ProjectFileManager#configure(XStream)}. + * + * @author Branislav Stojkovic + */ +@Indexed +public interface ISerializationAliasProvider { + + /** + * Registers aliases on the provided {@link XStream} instance. + * + * @param xStream + */ + void registerAliases(XStream xStream); +} diff --git a/diylc/diylc-core/src/main/java/org/diylc/serialization/ProjectFileManager.java b/diylc/diylc-core/src/main/java/org/diylc/serialization/ProjectFileManager.java index ac107343..028cf348 100644 --- a/diylc/diylc-core/src/main/java/org/diylc/serialization/ProjectFileManager.java +++ b/diylc/diylc-core/src/main/java/org/diylc/serialization/ProjectFileManager.java @@ -133,6 +133,14 @@ public class ProjectFileManager { xStream.addImmutableType(org.diylc.core.measures.Inductance.class, true); xStream.addImmutableType(org.diylc.core.measures.Size.class, true); xStream.addPermission(AnyTypePermission.ANY); + try { + for (ISerializationAliasProvider provider : ReflectionUtils + .getIndexedInstances(ISerializationAliasProvider.class)) { + provider.registerAliases(xStream); + } + } catch (ReflectiveOperationException e) { + LOG.error("Could not register serialization aliases", e); + } } public void startNewFile() { diff --git a/diylc/diylc-library/src/main/java/org/diylc/components/AbstractLeadedComponent.java b/diylc/diylc-library/src/main/java/org/diylc/components/AbstractLeadedComponent.java index 373769f2..5701ef43 100644 --- a/diylc/diylc-library/src/main/java/org/diylc/components/AbstractLeadedComponent.java +++ b/diylc/diylc-library/src/main/java/org/diylc/components/AbstractLeadedComponent.java @@ -79,7 +79,9 @@ public abstract class AbstractLeadedComponent extends AbstractLabeledComponen protected Color leadColor = LEAD_COLOR; protected Display display = Display.NAME; private boolean flipStanding = false; - private LabelOriantation labelOriantation = LabelOriantation.Directional; + @Deprecated + private LabelOrientation labelOriantation; + private LabelOrientation labelOrientation = LabelOrientation.Directional; protected boolean moveLabel = false; // parameters for adjusting the label control point @@ -361,13 +363,13 @@ public abstract class AbstractLeadedComponent extends AbstractLabeledComponen if (getMoveLabel()) { g2d.setTransform(oldTransform); g2d.translate(getPoints()[2].getX(), getPoints()[2].getY()); - if (getLabelOriantation() != LabelOriantation.Horizontal) { + if (getLabelOrientation() != LabelOrientation.Horizontal) { g2d.rotate(theta); } StringUtils.drawCenteredText(g2d, label, offset, 0, HorizontalAlignment.CENTER, VerticalAlignment.CENTER); g2d.setTransform(oldTransform); } else { - if (isStanding() || getLabelOriantation() == LabelOriantation.Horizontal) { + if (isStanding() || getLabelOrientation() == LabelOrientation.Horizontal) { g2d.setTransform(oldTransform); double x = (getPoints()[0].getX() + getPoints()[1].getX() - length) / 2.0; double y = (getPoints()[0].getY() + getPoints()[1].getY() - width) / 2.0; @@ -381,7 +383,7 @@ public abstract class AbstractLeadedComponent extends AbstractLabeledComponen } drawingObserver.stopTracking(); } -// if (getLabelOriantation() == LabelOriantation.Horizontal) { +// if (getLabelOrientation() == LabelOrientation.Horizontal) { // // drawCenteredText(g2d, label, getNewPoints()[2].getX(), getNewPoints()[2].getX(), HorizontalAlignment.CENTER, VerticalAlignment.CENTER); // g2d.drawString(label, (int) (x + (length - textRect.getWidth()) / 2 + offset), @@ -698,14 +700,20 @@ public abstract class AbstractLeadedComponent extends AbstractLabeledComponen } @EditableProperty(name = "Label Orientation") - public LabelOriantation getLabelOriantation() { - if (labelOriantation == null) - labelOriantation = LabelOriantation.Directional; - return labelOriantation; + public LabelOrientation getLabelOrientation() { + if (labelOrientation == null) { + if (labelOriantation != null) { + labelOrientation = labelOriantation; + labelOriantation = null; + } else { + labelOrientation = LabelOrientation.Directional; + } + } + return labelOrientation; } - public void setLabelOriantation(LabelOriantation labelOriantation) { - this.labelOriantation = labelOriantation; + public void setLabelOrientation(LabelOrientation labelOrientation) { + this.labelOrientation = labelOrientation; } @Override @@ -785,7 +793,7 @@ public abstract class AbstractLeadedComponent extends AbstractLabeledComponen this.hideShortLeads = hideShortLeads; } - public enum LabelOriantation { + public enum LabelOrientation { Directional, Horizontal } } diff --git a/diylc/diylc-library/src/main/java/org/diylc/components/connectivity/CopperTrace.java b/diylc/diylc-library/src/main/java/org/diylc/components/connectivity/CopperTrace.java index b65b1e41..a8a7dfe6 100644 --- a/diylc/diylc-library/src/main/java/org/diylc/components/connectivity/CopperTrace.java +++ b/diylc/diylc-library/src/main/java/org/diylc/components/connectivity/CopperTrace.java @@ -187,9 +187,9 @@ public class CopperTrace extends AbstractLeadedComponent implements ILayer @Deprecated @Override - public LabelOriantation getLabelOriantation() { + public LabelOrientation getLabelOrientation() { // TODO Auto-generated method stub - return super.getLabelOriantation(); + return super.getLabelOrientation(); } @Deprecated diff --git a/diylc/diylc-library/src/main/java/org/diylc/components/connectivity/Jumper.java b/diylc/diylc-library/src/main/java/org/diylc/components/connectivity/Jumper.java index 3e2dfbbf..2c9f8d37 100644 --- a/diylc/diylc-library/src/main/java/org/diylc/components/connectivity/Jumper.java +++ b/diylc/diylc-library/src/main/java/org/diylc/components/connectivity/Jumper.java @@ -163,9 +163,9 @@ public class Jumper extends AbstractLeadedComponent implements IContinuity @Deprecated @Override - public LabelOriantation getLabelOriantation() { + public LabelOrientation getLabelOrientation() { // TODO Auto-generated method stub - return super.getLabelOriantation(); + return super.getLabelOrientation(); } @Override diff --git a/diylc/diylc-library/src/main/java/org/diylc/components/connectivity/Line.java b/diylc/diylc-library/src/main/java/org/diylc/components/connectivity/Line.java index ad8203fe..1779870d 100644 --- a/diylc/diylc-library/src/main/java/org/diylc/components/connectivity/Line.java +++ b/diylc/diylc-library/src/main/java/org/diylc/components/connectivity/Line.java @@ -291,7 +291,7 @@ public class Line extends AbstractLeadedComponent { @Deprecated @Override - public org.diylc.components.AbstractLeadedComponent.LabelOriantation getLabelOriantation() { - return super.getLabelOriantation(); + public org.diylc.components.AbstractLeadedComponent.LabelOrientation getLabelOrientation() { + return super.getLabelOrientation(); } } diff --git a/diylc/diylc-library/src/main/java/org/diylc/components/passive/Resistor.java b/diylc/diylc-library/src/main/java/org/diylc/components/passive/Resistor.java index c6c9d2aa..2a4a2c11 100644 --- a/diylc/diylc-library/src/main/java/org/diylc/components/passive/Resistor.java +++ b/diylc/diylc-library/src/main/java/org/diylc/components/passive/Resistor.java @@ -197,7 +197,7 @@ public class Resistor extends AbstractLeadedComponent implements IGe @Override protected int getLabelOffset(int bodyLength, int bodyWidth, int labelLength) { - if (value == null || getColorCode() == ResistorColorCode.NONE || getLabelOriantation() != AbstractLeadedComponent.LabelOriantation.Directional) + if (value == null || getColorCode() == ResistorColorCode.NONE || getLabelOrientation() != AbstractLeadedComponent.LabelOrientation.Directional) return 0; Color[] bands = value.getColorCode(colorCode); diff --git a/diylc/diylc-library/src/main/java/org/diylc/components/semiconductors/BridgeRectifier.java b/diylc/diylc-library/src/main/java/org/diylc/components/semiconductors/BridgeRectifier.java index e4c8296b..db3a16bf 100644 --- a/diylc/diylc-library/src/main/java/org/diylc/components/semiconductors/BridgeRectifier.java +++ b/diylc/diylc-library/src/main/java/org/diylc/components/semiconductors/BridgeRectifier.java @@ -37,7 +37,7 @@ import org.diylc.awt.StringUtils; import org.diylc.common.*; import org.diylc.components.AbstractLabeledComponent; import org.diylc.components.RoundedPolygon; -import org.diylc.components.AbstractLeadedComponent.LabelOriantation; +import org.diylc.components.AbstractLeadedComponent.LabelOrientation; import org.diylc.core.ComponentState; import org.diylc.core.IDIYComponent; import org.diylc.core.IDrawingObserver; @@ -107,7 +107,9 @@ public class BridgeRectifier extends AbstractLabeledComponent implements private Voltage voltage; private Current current; transient private Area[] body; - private LabelOriantation labelOriantation = LabelOriantation.Directional; + @Deprecated + private LabelOrientation labelOriantation; + private LabelOrientation labelOrientation = LabelOrientation.Directional; public BridgeRectifier() { super(); @@ -457,7 +459,7 @@ public class BridgeRectifier extends AbstractLabeledComponent implements AffineTransform oldTransform = g2d.getTransform(); - if (getLabelOriantation() == LabelOriantation.Directional + if (getLabelOrientation() == LabelOrientation.Directional && (getOrientation() == Orientation.DEFAULT || getOrientation() == Orientation._180)) { double centerX = bounds.getX() + bounds.width / 2; double centerY = bounds.getY() + bounds.height / 2; @@ -671,14 +673,20 @@ public class BridgeRectifier extends AbstractLabeledComponent implements } @EditableProperty(name = "Label Orientation") - public LabelOriantation getLabelOriantation() { - if (labelOriantation == null) - labelOriantation = LabelOriantation.Directional; - return labelOriantation; + public LabelOrientation getLabelOrientation() { + if (labelOrientation == null) { + if (labelOriantation != null) { + labelOrientation = labelOriantation; + labelOriantation = null; + } else { + labelOrientation = LabelOrientation.Directional; + } + } + return labelOrientation; } - public void setLabelOriantation(LabelOriantation labelOriantation) { - this.labelOriantation = labelOriantation; + public void setLabelOrientation(LabelOrientation labelOrientation) { + this.labelOrientation = labelOrientation; } @Override diff --git a/diylc/diylc-library/src/main/java/org/diylc/components/shapes/TapeMeasure.java b/diylc/diylc-library/src/main/java/org/diylc/components/shapes/TapeMeasure.java index c0867e68..d91f13fe 100644 --- a/diylc/diylc-library/src/main/java/org/diylc/components/shapes/TapeMeasure.java +++ b/diylc/diylc-library/src/main/java/org/diylc/components/shapes/TapeMeasure.java @@ -344,8 +344,8 @@ public class TapeMeasure extends AbstractLeadedComponent { @Deprecated @Override - public org.diylc.components.AbstractLeadedComponent.LabelOriantation getLabelOriantation() { - return org.diylc.components.AbstractLeadedComponent.LabelOriantation.Directional; + public org.diylc.components.AbstractLeadedComponent.LabelOrientation getLabelOrientation() { + return org.diylc.components.AbstractLeadedComponent.LabelOrientation.Directional; } @Deprecated diff --git a/diylc/diylc-library/src/main/java/org/diylc/serialization/ComponentAliasProvider.java b/diylc/diylc-library/src/main/java/org/diylc/serialization/ComponentAliasProvider.java new file mode 100644 index 00000000..ed83a411 --- /dev/null +++ b/diylc/diylc-library/src/main/java/org/diylc/serialization/ComponentAliasProvider.java @@ -0,0 +1,41 @@ +/* + + DIY Layout Creator (DIYLC). + Copyright (c) 2009-2025 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.serialization; + +import com.thoughtworks.xstream.XStream; + +import org.diylc.components.AbstractLeadedComponent; + +public class ComponentAliasProvider implements ISerializationAliasProvider { + + @Override + public void registerAliases(XStream xStream) { + // the enum shipped misspelled and its class name is saved wherever the value is held as an + // Object (variants, defaults in config.xml); configs from before the package alias carry the + // full name. The alias registered last is also the one written, so files stay readable by + // older builds + xStream.alias("org.diylc.components.AbstractLeadedComponent$LabelOriantation", + AbstractLeadedComponent.LabelOrientation.class); + xStream.alias("diylc.AbstractLeadedComponent$LabelOriantation", + AbstractLeadedComponent.LabelOrientation.class); + } +} diff --git a/diylc/diylc-library/src/test/java/org/diylc/serialization/ComponentAliasProviderTest.java b/diylc/diylc-library/src/test/java/org/diylc/serialization/ComponentAliasProviderTest.java new file mode 100644 index 00000000..04d09631 --- /dev/null +++ b/diylc/diylc-library/src/test/java/org/diylc/serialization/ComponentAliasProviderTest.java @@ -0,0 +1,115 @@ +/* + + DIY Layout Creator (DIYLC). + Copyright (c) 2009-2025 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.serialization; + +import java.util.HashMap; +import java.util.Map; + +import com.thoughtworks.xstream.XStream; +import com.thoughtworks.xstream.io.xml.DomDriver; + +import org.diylc.components.AbstractLeadedComponent.LabelOrientation; +import org.diylc.components.passive.Resistor; +import org.diylc.components.semiconductors.BridgeRectifier; +import org.junit.Assert; +import org.junit.Test; + +/** + * Files saved before {@code LabelOriantation} was renamed must keep loading. The field is migrated + * and saved under its new name; the enum keeps its shipped class name on disk, because older builds + * discard a whole config file that names a class they cannot resolve. + */ +public class ComponentAliasProviderTest { + + private static final String OLD_ENUM_NAME = "diylc.AbstractLeadedComponent_-LabelOriantation"; + private static final String OLD_FULL_ENUM_NAME = + "org.diylc.components.AbstractLeadedComponent_-LabelOriantation"; + + // a fresh instance has not seen any class yet, which is the state it is in while reading + // config.xml at startup + private static XStream createXStream() { + XStream xStream = new XStream(new DomDriver("UTF-8")); + ProjectFileManager.configure(xStream); + return xStream; + } + + // files saved before the rename carry only the misspelled field + private static String toOldFieldName(String xml) { + Assert.assertTrue(xml.contains("labelOrientation")); + return xml.replace("labelOrientation", "labelOriantation"); + } + + @Test + public void leadedComponentMigratesOldFieldName() { + Resistor resistor = new Resistor(); + resistor.setLabelOrientation(LabelOrientation.Horizontal); + String oldXml = toOldFieldName(createXStream().toXML(resistor)); + + Resistor loaded = (Resistor) createXStream().fromXML(oldXml); + Assert.assertEquals(LabelOrientation.Horizontal, loaded.getLabelOrientation()); + + String savedXml = createXStream().toXML(loaded); + Assert.assertTrue(savedXml.contains("Horizontal")); + Assert.assertFalse(savedXml.contains("labelOriantation")); + } + + @Test + public void bridgeRectifierMigratesOldFieldName() { + BridgeRectifier rectifier = new BridgeRectifier(); + rectifier.setLabelOrientation(LabelOrientation.Horizontal); + String oldXml = toOldFieldName(createXStream().toXML(rectifier)); + + BridgeRectifier loaded = (BridgeRectifier) createXStream().fromXML(oldXml); + Assert.assertEquals(LabelOrientation.Horizontal, loaded.getLabelOrientation()); + + String savedXml = createXStream().toXML(loaded); + Assert.assertTrue(savedXml.contains("Horizontal")); + Assert.assertFalse(savedXml.contains("labelOriantation")); + } + + @Test + public void enumValueHeldAsObjectReadsOldClassName() { + String xml = "Label Orientation<" + OLD_ENUM_NAME + + ">Horizontal"; + + Map loaded = (Map) createXStream().fromXML(xml); + Assert.assertEquals(LabelOrientation.Horizontal, loaded.get("Label Orientation")); + } + + @Test + public void enumValueHeldAsObjectReadsOldFullClassName() { + String xml = "Label Orientation<" + OLD_FULL_ENUM_NAME + + ">Horizontal"; + + Map loaded = (Map) createXStream().fromXML(xml); + Assert.assertEquals(LabelOrientation.Horizontal, loaded.get("Label Orientation")); + } + + @Test + public void enumValueHeldAsObjectWritesOldClassName() { + Map values = new HashMap(); + values.put("Label Orientation", LabelOrientation.Horizontal); + + String xml = createXStream().toXML(values); + Assert.assertTrue(xml.contains("<" + OLD_ENUM_NAME + ">Horizontal")); + } +} From f85e345f3a0ae81432b2fad3578f15e31ca37779 Mon Sep 17 00:00:00 2001 From: Bane Stojkovic Date: Fri, 16 Oct 2026 09:30:20 +0200 Subject: [PATCH 03/11] Update update.xml --- diylc/diylc-core/src/main/resources/update.xml | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/diylc/diylc-core/src/main/resources/update.xml b/diylc/diylc-core/src/main/resources/update.xml index f09b0b14..b3913927 100644 --- a/diylc/diylc-core/src/main/resources/update.xml +++ b/diylc/diylc-core/src/main/resources/update.xml @@ -582,7 +582,7 @@ 8 0 - 2026-10-09 00:00:00.000 CET + 2026-10-16 00:00:00.000 CET @@ -597,6 +597,10 @@ NEW_FEATURE Added Rectangular shape to LED component in addition to Round; added ability to set LED lead spacing + + IMPROVEMENT + Upgraded AI Models to the latest GPT-6 generation across all tiers + IMPROVEMENT 'S1 Switch' can now be rotated to match the orientation of the potentiometer it is mounted on, so the switching matrix stays correct relative to the pot lugs From 63f56edcba1091bf2e941c2171d2816722a0584f Mon Sep 17 00:00:00 2001 From: Bane Stojkovic Date: Fri, 16 Oct 2026 19:54:04 +0200 Subject: [PATCH 04/11] Create sign-windows.yml --- .github/workflows/sign-windows.yml | 116 +++++++++++++++++++++++++++++ 1 file changed, 116 insertions(+) create mode 100644 .github/workflows/sign-windows.yml diff --git a/.github/workflows/sign-windows.yml b/.github/workflows/sign-windows.yml new file mode 100644 index 00000000..3444e8f3 --- /dev/null +++ b/.github/workflows/sign-windows.yml @@ -0,0 +1,116 @@ +name: Sign Windows Artifacts + +# Signs the Inno Setup installer. The launcher it embeds is signed separately and committed +# by the Sign Windows Launcher workflow, because Inno Setup cannot sign its payload after the +# fact and the launcher changes only when launch4j regenerates it. + +on: + workflow_dispatch: + inputs: + signing-policy-slug: + description: 'SignPath signing policy' + type: choice + default: test-signing + options: + - test-signing + - release-signing + +permissions: + actions: read + contents: read + +env: + SIGNPATH_ORGANIZATION_ID: 95470a00-59e6-430c-9c81-7393ad16d4e8 + SIGNPATH_PROJECT_SLUG: diy-layout-creator + +jobs: + windows: + runs-on: ubuntu-latest + + steps: + - name: Checkout code + uses: actions/checkout@v7 + + - name: Set up JDK 21 + uses: actions/setup-java@v6 + with: + java-version: '21' + distribution: 'temurin' + cache: maven + + # Nothing downstream fails when the committed launcher is unsigned: the zip and the + # installed application would just silently ship an unsigned executable. + - name: Check the committed launcher carries a signature + env: + POLICY: ${{ inputs.signing-policy-slug }} + run: | + if python3 diylc/diylc-swing/deploy/win/signpath/is-signed.py \ + diylc/diylc-swing/deploy/win/diylc-x64.exe; then + exit 0 + fi + if [ "$POLICY" = "release-signing" ]; then + echo "::error::deploy/win/diylc-x64.exe is unsigned. Run the Sign Windows Launcher workflow with the release policy, commit the signed launcher, then re-run this workflow." + exit 1 + fi + echo "::warning::deploy/win/diylc-x64.exe is unsigned; the zip and the installed application will ship an unsigned launcher." + + - name: Resolve project version + working-directory: diylc + run: | + echo "DIYLC_VERSION=$(mvn -q -N help:evaluate -Dexpression=project.version -DforceStdout)" >> "$GITHUB_ENV" + + - name: Build Windows zip and installer + working-directory: diylc + run: | + mvn -B -DskipTests -P win64-zip package + mvn -B -DskipTests -P win64-installer package + + # The artifact configuration matches a fixed path, so the versioned installer is staged + # under a stable name for the round trip and renamed back afterwards. + - name: Stage installer for signing + run: | + mkdir -p staging + cp "diylc/diylc-swing/target/diylc-${DIYLC_VERSION}-win64.exe" staging/diylc-setup.exe + + - name: Upload unsigned installer + id: upload-unsigned-installer + uses: actions/upload-artifact@v7 + with: + name: unsigned-installer + path: staging/diylc-setup.exe + if-no-files-found: error + retention-days: 1 + + - name: Sign installer + uses: signpath/github-action-submit-signing-request@v3 + with: + api-token: ${{ secrets.SIGNPATH_API_TOKEN }} + organization-id: ${{ env.SIGNPATH_ORGANIZATION_ID }} + project-slug: ${{ env.SIGNPATH_PROJECT_SLUG }} + signing-policy-slug: ${{ inputs.signing-policy-slug }} + artifact-configuration-slug: windows-installer + github-artifact-id: ${{ steps.upload-unsigned-installer.outputs.artifact-id }} + wait-for-completion: true + output-artifact-directory: signed + + - name: Restore signed installer under its release name + run: | + python3 diylc/diylc-swing/deploy/win/signpath/is-signed.py signed/diylc-setup.exe + cp signed/diylc-setup.exe "diylc/diylc-swing/target/diylc-${DIYLC_VERSION}-win64.exe" + + - name: Report signatures + run: | + sudo apt-get update + sudo apt-get install -y osslsigncode + # chain validation fails against the self-signed test certificate by design; what + # matters is that a signature is present and names the expected subject + osslsigncode verify -in "diylc/diylc-swing/target/diylc-${DIYLC_VERSION}-win64.exe" || true + + - name: Upload signed Windows artifacts + uses: actions/upload-artifact@v7 + with: + name: diylc-windows-signed + path: | + diylc/diylc-swing/target/diylc-${{ env.DIYLC_VERSION }}-win64.exe + diylc/diylc-swing/target/diylc-${{ env.DIYLC_VERSION }}-win64.zip + if-no-files-found: error From f16422395e211678a8d73217e70431fb8a66bfaf Mon Sep 17 00:00:00 2001 From: Bane Stojkovic Date: Fri, 16 Oct 2026 20:15:57 +0200 Subject: [PATCH 05/11] Update sign-windows.yml --- .github/workflows/sign-windows.yml | 49 +++++++++++++++++------------- 1 file changed, 28 insertions(+), 21 deletions(-) diff --git a/.github/workflows/sign-windows.yml b/.github/workflows/sign-windows.yml index 3444e8f3..85463547 100644 --- a/.github/workflows/sign-windows.yml +++ b/.github/workflows/sign-windows.yml @@ -1,8 +1,9 @@ name: Sign Windows Artifacts -# Signs the Inno Setup installer. The launcher it embeds is signed separately and committed -# by the Sign Windows Launcher workflow, because Inno Setup cannot sign its payload after the -# fact and the launcher changes only when launch4j regenerates it. +# Builds and signs the Inno Setup installer; the zip package is not built or signed here. The +# launcher the installer embeds is signed separately and committed by the Sign Windows Launcher +# workflow, because Inno Setup cannot sign its payload after the fact and the launcher changes +# only when launch4j regenerates it. on: workflow_dispatch: @@ -38,8 +39,8 @@ jobs: distribution: 'temurin' cache: maven - # Nothing downstream fails when the committed launcher is unsigned: the zip and the - # installed application would just silently ship an unsigned executable. + # Nothing downstream fails when the committed launcher is unsigned: the installed + # application would just silently ship an unsigned executable. - name: Check the committed launcher carries a signature env: POLICY: ${{ inputs.signing-policy-slug }} @@ -52,21 +53,28 @@ jobs: echo "::error::deploy/win/diylc-x64.exe is unsigned. Run the Sign Windows Launcher workflow with the release policy, commit the signed launcher, then re-run this workflow." exit 1 fi - echo "::warning::deploy/win/diylc-x64.exe is unsigned; the zip and the installed application will ship an unsigned launcher." + echo "::warning::deploy/win/diylc-x64.exe is unsigned; the installed application will ship an unsigned launcher." - name: Resolve project version working-directory: diylc run: | echo "DIYLC_VERSION=$(mvn -q -N help:evaluate -Dexpression=project.version -DforceStdout)" >> "$GITHUB_ENV" - - name: Build Windows zip and installer + # amake/innosetup runs Inno Setup under wine as its own user (uid 1000), not as the runner + # (uid 1001), so it cannot write its output into a target directory the runner owns. Docker + # Desktop hides this on a developer machine; on a Linux runner it surfaces as "Error 5: + # Access denied" copying Setup.e32. + - name: Build Windows installer working-directory: diylc run: | - mvn -B -DskipTests -P win64-zip package + mkdir -p diylc-swing/target + chmod a+rwx diylc-swing/target mvn -B -DskipTests -P win64-installer package - # The artifact configuration matches a fixed path, so the versioned installer is staged - # under a stable name for the round trip and renamed back afterwards. + # upload-artifact zips the staged installer and SignPath signs the exe inside that zip; the + # action then downloads and unpacks the signed zip into signed/. The artifact configuration + # matches a fixed path, so the versioned installer travels under a stable name and is + # renamed back afterwards. - name: Stage installer for signing run: | mkdir -p staging @@ -93,24 +101,23 @@ jobs: wait-for-completion: true output-artifact-directory: signed - - name: Restore signed installer under its release name + - name: Verify signed installer run: | python3 diylc/diylc-swing/deploy/win/signpath/is-signed.py signed/diylc-setup.exe - cp signed/diylc-setup.exe "diylc/diylc-swing/target/diylc-${DIYLC_VERSION}-win64.exe" - - - name: Report signatures - run: | sudo apt-get update sudo apt-get install -y osslsigncode # chain validation fails against the self-signed test certificate by design; what # matters is that a signature is present and names the expected subject - osslsigncode verify -in "diylc/diylc-swing/target/diylc-${DIYLC_VERSION}-win64.exe" || true + osslsigncode verify -in signed/diylc-setup.exe || true - - name: Upload signed Windows artifacts + - name: Restore signed installer under its release name + run: | + mkdir -p release + cp signed/diylc-setup.exe "release/diylc-${DIYLC_VERSION}-win64.exe" + + - name: Upload signed installer uses: actions/upload-artifact@v7 with: - name: diylc-windows-signed - path: | - diylc/diylc-swing/target/diylc-${{ env.DIYLC_VERSION }}-win64.exe - diylc/diylc-swing/target/diylc-${{ env.DIYLC_VERSION }}-win64.zip + name: diylc-windows-installer-signed + path: release/diylc-${{ env.DIYLC_VERSION }}-win64.exe if-no-files-found: error From 846822e1569f600510be5865ee5e61952fd67a62 Mon Sep 17 00:00:00 2001 From: Bane Stojkovic Date: Fri, 16 Oct 2026 20:28:15 +0200 Subject: [PATCH 06/11] signpath --- .github/workflows/sign-windows.yml | 12 +++++- .../deploy/win/signpath/is-signed.py | 39 +++++++++++++++++++ 2 files changed, 50 insertions(+), 1 deletion(-) create mode 100644 diylc/diylc-swing/deploy/win/signpath/is-signed.py diff --git a/.github/workflows/sign-windows.yml b/.github/workflows/sign-windows.yml index 85463547..5d7bc51f 100644 --- a/.github/workflows/sign-windows.yml +++ b/.github/workflows/sign-windows.yml @@ -17,7 +17,8 @@ on: - release-signing permissions: - actions: read + # write is needed only to delete the unsigned installer once SignPath has fetched it + actions: write contents: read env: @@ -101,6 +102,15 @@ jobs: wait-for-completion: true output-artifact-directory: signed + # SignPath can only fetch the installer from a GitHub artifact, so the unsigned upload cannot + # be skipped; it is deleted as soon as signing is over, whether or not signing succeeded. + - name: Delete unsigned installer artifact + if: always() && steps.upload-unsigned-installer.outputs.artifact-id != '' + env: + GH_TOKEN: ${{ github.token }} + ARTIFACT_ID: ${{ steps.upload-unsigned-installer.outputs.artifact-id }} + run: gh api -X DELETE "repos/${GITHUB_REPOSITORY}/actions/artifacts/${ARTIFACT_ID}" + - name: Verify signed installer run: | python3 diylc/diylc-swing/deploy/win/signpath/is-signed.py signed/diylc-setup.exe diff --git a/diylc/diylc-swing/deploy/win/signpath/is-signed.py b/diylc/diylc-swing/deploy/win/signpath/is-signed.py new file mode 100644 index 00000000..44248ff2 --- /dev/null +++ b/diylc/diylc-swing/deploy/win/signpath/is-signed.py @@ -0,0 +1,39 @@ +#!/usr/bin/env python3 +"""Exits 0 if the given PE file carries an Authenticode signature, 1 if it does not. + +`osslsigncode verify` also fails on an untrusted chain, which is the normal result for the +SignPath test certificate, so it cannot answer this question on its own. This reads the PE +Certificate Table data directory instead, which says whether a signature is present without +making any claim about its validity. +""" + +import struct +import sys + + +def has_signature(path): + with open(path, "rb") as f: + data = f.read() + + pe = struct.unpack_from(" 0 + + +if __name__ == "__main__": + signed = has_signature(sys.argv[1]) + print("signed" if signed else "unsigned") + sys.exit(0 if signed else 1) From bb9f50e07af7f324f0bfd5c4994aa7b02281e7a2 Mon Sep 17 00:00:00 2001 From: Bane Stojkovic Date: Fri, 16 Oct 2026 20:41:53 +0200 Subject: [PATCH 07/11] Create sign-windows-launcher.yml --- .github/workflows/sign-windows-launcher.yml | 86 +++++++++++++++++++++ 1 file changed, 86 insertions(+) create mode 100644 .github/workflows/sign-windows-launcher.yml diff --git a/.github/workflows/sign-windows-launcher.yml b/.github/workflows/sign-windows-launcher.yml new file mode 100644 index 00000000..7a1d309e --- /dev/null +++ b/.github/workflows/sign-windows-launcher.yml @@ -0,0 +1,86 @@ +name: Sign Windows Launcher + +# Signs the committed launch4j launcher that the installer embeds. Run it only after launch4j +# regenerates the launcher, then commit the signed diylc-x64.exe from the artifact over +# diylc/diylc-swing/deploy/win/diylc-x64.exe so Sign Windows Artifacts packs it. + +on: + workflow_dispatch: + inputs: + signing-policy-slug: + description: 'SignPath signing policy' + type: choice + default: test-signing + options: + - test-signing + - release-signing + +permissions: + # write is needed only to delete the unsigned launcher once SignPath has fetched it + actions: write + contents: read + +env: + SIGNPATH_ORGANIZATION_ID: 95470a00-59e6-430c-9c81-7393ad16d4e8 + SIGNPATH_PROJECT_SLUG: diy-layout-creator + +jobs: + launcher: + runs-on: ubuntu-latest + + steps: + - name: Checkout code + uses: actions/checkout@v7 + + # re-signing would stack a second signature on top of the committed one + - name: Check the committed launcher is unsigned + run: | + if python3 diylc/diylc-swing/deploy/win/signpath/is-signed.py \ + diylc/diylc-swing/deploy/win/diylc-x64.exe; then + echo "::error::deploy/win/diylc-x64.exe is already signed. Commit an unsigned launcher from launch4j first." + exit 1 + fi + + - name: Upload unsigned launcher + id: upload-unsigned-launcher + uses: actions/upload-artifact@v7 + with: + name: unsigned-launcher + path: diylc/diylc-swing/deploy/win/diylc-x64.exe + if-no-files-found: error + retention-days: 1 + + - name: Sign launcher + uses: signpath/github-action-submit-signing-request@v3 + with: + api-token: ${{ secrets.SIGNPATH_API_TOKEN }} + organization-id: ${{ env.SIGNPATH_ORGANIZATION_ID }} + project-slug: ${{ env.SIGNPATH_PROJECT_SLUG }} + signing-policy-slug: ${{ inputs.signing-policy-slug }} + artifact-configuration-slug: windows-launcher + github-artifact-id: ${{ steps.upload-unsigned-launcher.outputs.artifact-id }} + wait-for-completion: true + output-artifact-directory: signed + + - name: Delete unsigned launcher artifact + if: always() && steps.upload-unsigned-launcher.outputs.artifact-id != '' + env: + GH_TOKEN: ${{ github.token }} + ARTIFACT_ID: ${{ steps.upload-unsigned-launcher.outputs.artifact-id }} + run: gh api -X DELETE "repos/${GITHUB_REPOSITORY}/actions/artifacts/${ARTIFACT_ID}" + + - name: Verify signed launcher + run: | + python3 diylc/diylc-swing/deploy/win/signpath/is-signed.py signed/diylc-x64.exe + sudo apt-get update + sudo apt-get install -y osslsigncode + # chain validation fails against the self-signed test certificate by design; what + # matters is that a signature is present and names the expected subject + osslsigncode verify -in signed/diylc-x64.exe || true + + - name: Upload signed launcher + uses: actions/upload-artifact@v7 + with: + name: diylc-windows-launcher-signed + path: signed/diylc-x64.exe + if-no-files-found: error From 8ec6900597b66843fe2609aa3f780a0e5dd7b893 Mon Sep 17 00:00:00 2001 From: Bane Stojkovic Date: Sat, 17 Oct 2026 15:45:39 +0200 Subject: [PATCH 08/11] Localization fix --- .../org/diylc/netlist/SpiceSumarizer.java | 25 ++++++- .../passive/CapacitorDatasheetService.java | 7 +- .../diylc/netlist/SpiceSumarizerTests.java | 65 +++++++++++++++++++ 3 files changed, 93 insertions(+), 4 deletions(-) create mode 100644 diylc/diylc-library/src/test/java/org/diylc/netlist/SpiceSumarizerTests.java 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 index 42e5598b..ed0a67fd 100644 --- a/diylc/diylc-core/src/main/java/org/diylc/netlist/SpiceSumarizer.java +++ b/diylc/diylc-core/src/main/java/org/diylc/netlist/SpiceSumarizer.java @@ -2,16 +2,26 @@ package org.diylc.netlist; import org.diylc.common.ComponentType; import org.diylc.core.IDIYComponent; +import org.diylc.core.measures.AbstractMeasure; import org.diylc.presenter.ComponentProcessor; +import java.text.DecimalFormat; +import java.text.DecimalFormatSymbols; +import java.text.Format; import java.util.ArrayList; import java.util.List; +import java.util.Locale; import java.util.stream.Collectors; import static org.diylc.netlist.AbstractNetlistAnalyzer.find; public class SpiceSumarizer { + // measures format with the user's locale for display, but SPICE only accepts '.' as the decimal + // separator + private static final Format VALUE_FORMAT = + new DecimalFormat("0.####", DecimalFormatSymbols.getInstance(Locale.ROOT)); + public static String summarize(Netlist netlist, boolean useHTML) throws TreeException { // grab all components that are in the netlist @@ -119,14 +129,23 @@ public class SpiceSumarizer { sb.append(typeName); } } else { - if (c.getValue().toString().contains(" ")) { - sb.append("\"").append(c.getValue()).append("\""); + String value = formatValue(c.getValue()); + if (value.contains(" ")) { + sb.append("\"").append(value).append("\""); } else { - sb.append(c.getValue()); + sb.append(value); } } } + private static String formatValue(Object value) { + if (value instanceof AbstractMeasure) { + AbstractMeasure measure = (AbstractMeasure) value; + return VALUE_FORMAT.format(measure.getValue()) + measure.getUnit(); + } + return value.toString(); + } + private static String formatSpiceNode(int i) { return String.format("N%03d" , i); } diff --git a/diylc/diylc-library/src/main/java/org/diylc/components/passive/CapacitorDatasheetService.java b/diylc/diylc-library/src/main/java/org/diylc/components/passive/CapacitorDatasheetService.java index 9241ff8c..3f2981d8 100644 --- a/diylc/diylc-library/src/main/java/org/diylc/components/passive/CapacitorDatasheetService.java +++ b/diylc/diylc-library/src/main/java/org/diylc/components/passive/CapacitorDatasheetService.java @@ -20,8 +20,10 @@ package org.diylc.components.passive; import java.awt.Color; import java.text.DecimalFormat; +import java.text.DecimalFormatSymbols; import java.text.NumberFormat; import java.util.HashMap; +import java.util.Locale; import java.util.Map; import java.util.Optional; @@ -37,7 +39,10 @@ public class CapacitorDatasheetService { private static CapacitorDatasheetService instance; - private static NumberFormat format = new DecimalFormat("##.###"); + // lookup keys are matched against the datasheet files, which always use '.' as the decimal + // separator + private static NumberFormat format = + new DecimalFormat("##.###", DecimalFormatSymbols.getInstance(Locale.ROOT)); private Map cache = new HashMap(); diff --git a/diylc/diylc-library/src/test/java/org/diylc/netlist/SpiceSumarizerTests.java b/diylc/diylc-library/src/test/java/org/diylc/netlist/SpiceSumarizerTests.java new file mode 100644 index 00000000..36e29de2 --- /dev/null +++ b/diylc/diylc-library/src/test/java/org/diylc/netlist/SpiceSumarizerTests.java @@ -0,0 +1,65 @@ +/* + + DIY Layout Creator (DIYLC). + Copyright (c) 2009-2025 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.Arrays; +import java.util.Locale; + +import org.junit.After; +import org.junit.Assert; +import org.junit.Before; +import org.junit.Test; + +import org.diylc.components.passive.Resistor; +import org.diylc.core.IDIYComponent; +import org.diylc.core.measures.Resistance; +import org.diylc.core.measures.ResistanceUnit; + +public class SpiceSumarizerTests { + + private Locale defaultLocale; + + @Before + public void setUp() { + defaultLocale = Locale.getDefault(); + } + + @After + public void tearDown() { + Locale.setDefault(defaultLocale); + } + + @Test + public void testDecimalValueIgnoresLocale() throws TreeException { + Locale.setDefault(Locale.GERMANY); + + Resistor resistor = new Resistor(); + resistor.setName("R1"); + resistor.setValue(new Resistance(4.7d, ResistanceUnit.K)); + + Netlist netlist = new Netlist(Arrays.>asList(resistor)); + netlist.add(new Group(new Node(resistor, 0), new Node(resistor, 1))); + + String spice = SpiceSumarizer.summarize(netlist, false); + Assert.assertTrue(spice, spice.contains(" 4.7K ")); + } +} From e16b6ca36d643158d069d9834fa5baccb32b2147 Mon Sep 17 00:00:00 2001 From: Bane Stojkovic Date: Sat, 17 Oct 2026 22:25:59 +0200 Subject: [PATCH 09/11] gerber upgrade --- diylc/diylc-core/pom.xml | 6 +++--- diylc/diylc-core/src/main/resources/update.xml | 16 ++++++++++++++++ diylc/diylc-library/pom.xml | 6 +++--- diylc/diylc-processor/pom.xml | 2 +- diylc/diylc-swing/pom.xml | 6 +++--- diylc/lib/gerber-writer-api-1.0.0.jar | Bin 37100 -> 0 bytes diylc/pom.xml | 2 +- 7 files changed, 27 insertions(+), 11 deletions(-) delete mode 100644 diylc/lib/gerber-writer-api-1.0.0.jar diff --git a/diylc/diylc-core/pom.xml b/diylc/diylc-core/pom.xml index 442b5f99..c785d7f4 100644 --- a/diylc/diylc-core/pom.xml +++ b/diylc/diylc-core/pom.xml @@ -7,7 +7,7 @@ org.diylc diylc-parent - 6.8.0 + 6.9.0 diylc-core @@ -97,10 +97,10 @@ org.diylc.local - gerber-writer-api-1.0.0 + gerber-writer-api-1.1.0 1.0 system - ${project.basedir}/../lib/gerber-writer-api-1.0.0.jar + ${project.basedir}/../lib/gerber-writer-api-1.1.0.jar org.diylc.local diff --git a/diylc/diylc-core/src/main/resources/update.xml b/diylc/diylc-core/src/main/resources/update.xml index b3913927..5bfa4eba 100644 --- a/diylc/diylc-core/src/main/resources/update.xml +++ b/diylc/diylc-core/src/main/resources/update.xml @@ -656,5 +656,21 @@ https://github.com/bancika/diy-layout-creator/releases + + + 6 + 9 + 0 + + 2026-10-17 00:00:00.000 CET + + + + BUG_FIX + Gerber export on computers set to a language that uses a decimal comma (e.g. German, French, Serbian) produced files in which drill holes, pads and traces had the wrong size - a 0.8mm hole came out as 0mm and traces and the board outline had no width, so the files could not be used for fabrication + + + https://github.com/bancika/diy-layout-creator/releases + diff --git a/diylc/diylc-library/pom.xml b/diylc/diylc-library/pom.xml index 8c802441..8c1ff92a 100644 --- a/diylc/diylc-library/pom.xml +++ b/diylc/diylc-library/pom.xml @@ -7,7 +7,7 @@ org.diylc diylc-parent - 6.8.0 + 6.9.0 diylc-library @@ -73,10 +73,10 @@ org.diylc.local - gerber-writer-api-1.0.0 + gerber-writer-api-1.1.0 1.0 system - ${project.basedir}/../lib/gerber-writer-api-1.0.0.jar + ${project.basedir}/../lib/gerber-writer-api-1.1.0.jar org.diylc.local diff --git a/diylc/diylc-processor/pom.xml b/diylc/diylc-processor/pom.xml index ee1af64a..6fa04cce 100644 --- a/diylc/diylc-processor/pom.xml +++ b/diylc/diylc-processor/pom.xml @@ -7,7 +7,7 @@ org.diylc diylc-parent - 6.8.0 + 6.9.0 diylc-processor diff --git a/diylc/diylc-swing/pom.xml b/diylc/diylc-swing/pom.xml index 4d41233b..d1582079 100644 --- a/diylc/diylc-swing/pom.xml +++ b/diylc/diylc-swing/pom.xml @@ -7,7 +7,7 @@ org.diylc diylc-parent - 6.8.0 + 6.9.0 diylc-swing @@ -140,10 +140,10 @@ org.diylc.local - gerber-writer-api-1.0.0 + gerber-writer-api-1.1.0 1.0 system - ${project.basedir}/../lib/gerber-writer-api-1.0.0.jar + ${project.basedir}/../lib/gerber-writer-api-1.1.0.jar org.diylc.local diff --git a/diylc/lib/gerber-writer-api-1.0.0.jar b/diylc/lib/gerber-writer-api-1.0.0.jar deleted file mode 100644 index 09c27e27eef800de19b48f38a83a35ad4c8928f2..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 37100 zcmb5W1#}}zvMuUvv)jzf%nWU2W@ct)w%g3i%*<_OX13eRGBYzhpP9M;zH|To^X6$~ zE|sLnsEFKKp~_mhB_|0C0tEyF0R$wPE~^3bzdazoj-`c__-VvtMCjzc%#gp#ApT)? zVWGmK@b&Ru{p;QTHk0O;5f>3wRHBs@xs{$6my)ERorRI4p`4nSu2-P{!?eBcKsh-f zK6@`Qx(GunCJhFuapk~)Rz#XySbX$TS%czi-cc$X5)8w$}fM^8ecc`d=0X`Zk86udjg8!?ojJC-PT~3LpPOP4P84? z*<>#2Pl&@M5%EA9S6N`xRCJ|6UD$HPq($8cS`1m><`{Ziy?=!SrNEuJ5@?2!b%K_Y zH2OPTcrZTmRaUVk`(fUGS4h)*kMym3(;7#GT+iXIl(Zj<2Jr$kHvSw~Kx!E`eu1+~wGP@?!=n7he#F}4Ayu9$42Rik5)0KE zw>5Fyoaj)$JiNr?{nirVgF~?DtTbCa+kT0M8xLl-Y z?l^$g0dL>ExR4<4h7ao;_qSMcGliT6-3xVOgZ%V^YuR2=rqP({G*X(iM8*4yQT<-i zy^`KXS(4}4&$Q?)r}b#oNO>3Tg!wXqr;DvaPwFh}(dIE1A1VguS(GDZQ}GU_yS4x} zV86zdr|mTzNZs-d=L1<5zck@IGZNp zq};)~oXA(yyWe7Na4#tDa3=Bj1N|xIqF>fgi!2NMdHn0hOB zBljTZu3DH$k}a5aK_JnKV9|@l>|dh>)uo?SchmyGL3O2#YJXhV>%ID`X#xz;pf*TW zyX+v}f{#sxwrQ+IF;|WHX4%5GmX8J?opNl8$X=}c_(w7S?*dRFC~BSa#SpYFhEV@! z43X0}`a3TYoB^chY=!%;1$j!PEGQ~Pn|xxW2oMzh5`342rVBRqXJ(zC*Y)f|V%&G& zR|Vx$Z9U&0HDI$Jxo%CeO*?sIVq}4=468z5ZCq!EaPTf(?C1sp;He0L1E&%#OK9{h zGgarAcNy7o*f*9~=N}?i6eLZ#I8+yo$17cu9h=7!Oap_|m9b3A%xh3}fKWl#s49urn08xzocBfK4~O>8l-RAC@scKbSCD zfEAGvBguqp#}wAG{7*F`Z}9Ng4TcRr%FkDy-S0M4MOl|>B6Bc4t9iKhrm+czECNBD zi0%r<qY5Y;D7 zDFGIM2+=3XD8F& zOTjmTJ&f!xpijR80nzoK7`njP!ZM@bI^-8nI@x+E@8f3*32{5NosgJzIiga+)zW!SdCvG+NOsM7{ z9ivr4QZnyLN#PsC&sHZQ3R|vg4KN6Zuo_v?NEct;S>%Dzwh*!nc!8@Mp?O zvj|CZ4LFU}k}lmlY0Bm#N7zpkFQ))7h&psV!TGRn><8wG650YP%wQVYEe+! zozo{wY8{+h#2T2M-PUg%RAZ|2GWOwKy})i>&@8xwdoWU05YlZCcz3C}l!+7f#w3ne zR3wg_9M{Klj$jFa(gsASRHt5jK^E`0e}|e`k438D3+nqXsJZ_`sBN8XjEs$x%#0nZ z^{wRe9rUdo{|S87SMf(;fcLIuYd2ac&I^nw^a}JBs`okrg(QGap@k@fAk?_$Sy3w+ zuSVm@AdG$^hDAUG_W|OCVqh&LlOu-BcF)G>c5gZyp0?fk#?^O@j__g~h|XTWJHcXF zE!u*iPaZpQ+&^$4!S7#wm1R++Q(Z;NL2N#rXl=JG z_a?N`<(0QLmt*mV-zKLk9t$UzN>jdrKr$sPSxuC@OLyU*!1cpu_v_)GnN%JM&qG@& zuid9yomGL(Kh>6pm2W$cSqlEJHYDsJF8<4Dw_=D^gkiN*S5LedH*_2~mBdWbn=-OW zvk)WPk0R}-6byR^p>6`-ow683w8^GC7Z2ZyAN^IqHP6~3c?4e^IgeFC?=GOQj?F;R z9yF;y%z3r)?t|vBT6$c1q}7nVa|mb_AB}h(jlQCffGl*gvAOhjJ3Kh5>j!mTY;+;uL=7VvvCj

M^wlJN$vTf_9W= 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 diff --git a/diylc/pom.xml b/diylc/pom.xml index 37fd8dbd..340bfc19 100644 --- a/diylc/pom.xml +++ b/diylc/pom.xml @@ -6,7 +6,7 @@ org.diylc diylc-parent - 6.8.0 + 6.9.0 pom DIY Layout Creator From b154eb9e969d7e32f388e0fa216cfc5abec9886f Mon Sep 17 00:00:00 2001 From: Bane Stojkovic Date: Sat, 17 Oct 2026 22:30:20 +0200 Subject: [PATCH 10/11] gerber --- diylc/.gitignore | 1 + diylc/lib/gerber-writer-api-1.1.0.jar | Bin 0 -> 37628 bytes 2 files changed, 1 insertion(+) create mode 100644 diylc/lib/gerber-writer-api-1.1.0.jar diff --git a/diylc/.gitignore b/diylc/.gitignore index 60898bc3..256c2939 100644 --- a/diylc/.gitignore +++ b/diylc/.gitignore @@ -35,6 +35,7 @@ Thumbs.db # Package Files # *.jar +!lib/*.jar *.war *.ear *.zip diff --git a/diylc/lib/gerber-writer-api-1.1.0.jar b/diylc/lib/gerber-writer-api-1.1.0.jar new file mode 100644 index 0000000000000000000000000000000000000000..502ca79198a53f67acf568b258f7367dd4f1beb6 GIT binary patch literal 37628 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 Date: Mon, 19 Oct 2026 10:16:06 +0200 Subject: [PATCH 11/11] Zooming out until the drawing is smaller than the window made the toolbar above the canvas grow taller and squeeze the canvas --- diylc/diylc-core/src/main/resources/update.xml | 4 ++++ .../diylc/swing/gui/actionbar/ActionBarPlugin.java | 12 +++++++++++- 2 files changed, 15 insertions(+), 1 deletion(-) diff --git a/diylc/diylc-core/src/main/resources/update.xml b/diylc/diylc-core/src/main/resources/update.xml index 5bfa4eba..70c4e135 100644 --- a/diylc/diylc-core/src/main/resources/update.xml +++ b/diylc/diylc-core/src/main/resources/update.xml @@ -669,6 +669,10 @@ BUG_FIX Gerber export on computers set to a language that uses a decimal comma (e.g. German, French, Serbian) produced files in which drill holes, pads and traces had the wrong size - a 0.8mm hole came out as 0mm and traces and the board outline had no width, so the files could not be used for fabrication + + BUG_FIX + Zooming out until the drawing is smaller than the window made the toolbar above the canvas grow taller and squeeze the canvas; the toolbar now keeps its height at every zoom level + https://github.com/bancika/diy-layout-creator/releases diff --git a/diylc/diylc-swing/src/main/java/org/diylc/swing/gui/actionbar/ActionBarPlugin.java b/diylc/diylc-swing/src/main/java/org/diylc/swing/gui/actionbar/ActionBarPlugin.java index 6b3c1e01..586a52ea 100644 --- a/diylc/diylc-swing/src/main/java/org/diylc/swing/gui/actionbar/ActionBarPlugin.java +++ b/diylc/diylc-swing/src/main/java/org/diylc/swing/gui/actionbar/ActionBarPlugin.java @@ -93,7 +93,17 @@ public class ActionBarPlugin implements IPlugIn { public JPanel getActionPanel() { if (actionPanel == null) { - actionPanel = new JPanel(); + actionPanel = new JPanel() { + + private static final long serialVersionUID = 1L; + + // shares a vertical BoxLayout with the canvas on Mac; without a height cap it absorbs + // half of the spare space whenever the zoomed-out canvas is smaller than the viewport + @Override + public Dimension getMaximumSize() { + return new Dimension(Integer.MAX_VALUE, getPreferredSize().height); + } + }; actionPanel.setOpaque(false); actionPanel.setLayout(new GridBagLayout());