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 diff --git a/.github/workflows/sign-windows.yml b/.github/workflows/sign-windows.yml new file mode 100644 index 00000000..5d7bc51f --- /dev/null +++ b/.github/workflows/sign-windows.yml @@ -0,0 +1,133 @@ +name: Sign Windows Artifacts + +# 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: + 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 installer 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: + 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 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 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" + + # 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: | + mkdir -p diylc-swing/target + chmod a+rwx diylc-swing/target + mvn -B -DskipTests -P win64-installer package + + # 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 + 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 + + # 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 + 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-setup.exe || true + + - 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-installer-signed + path: release/diylc-${{ env.DIYLC_VERSION }}-win64.exe + if-no-files-found: error 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/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/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-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-core/src/main/resources/update.xml b/diylc/diylc-core/src/main/resources/update.xml index 410c65df..70c4e135 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 @@ -593,6 +593,14 @@ 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 + 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 @@ -648,5 +656,25 @@ 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 + + + 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-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-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/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/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/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; + } + } } 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/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 ")); + } +} 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")); + } +} 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/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) diff --git a/diylc/diylc-swing/pom.xml b/diylc/diylc-swing/pom.xml index a43d01b1..0796c83b 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 @@ -145,10 +145,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-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()); 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 09c27e27..00000000 Binary files a/diylc/lib/gerber-writer-api-1.0.0.jar and /dev/null differ 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 00000000..502ca791 Binary files /dev/null and b/diylc/lib/gerber-writer-api-1.1.0.jar differ 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