Merge branch 'master' into ui-theme

This commit is contained in:
Bane Stojkovic
2026-10-19 10:37:29 +02:00
28 changed files with 730 additions and 48 deletions
@@ -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
+133
View File
@@ -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
+1
View File
@@ -35,6 +35,7 @@ Thumbs.db
# Package Files #
*.jar
!lib/*.jar
*.war
*.ear
*.zip
+3 -3
View File
@@ -7,7 +7,7 @@
<parent>
<groupId>org.diylc</groupId>
<artifactId>diylc-parent</artifactId>
<version>6.8.0</version>
<version>6.9.0</version>
</parent>
<artifactId>diylc-core</artifactId>
@@ -97,10 +97,10 @@
</dependency>
<dependency>
<groupId>org.diylc.local</groupId>
<artifactId>gerber-writer-api-1.0.0</artifactId>
<artifactId>gerber-writer-api-1.1.0</artifactId>
<version>1.0</version>
<scope>system</scope>
<systemPath>${project.basedir}/../lib/gerber-writer-api-1.0.0.jar</systemPath>
<systemPath>${project.basedir}/../lib/gerber-writer-api-1.1.0.jar</systemPath>
</dependency>
<dependency>
<groupId>org.diylc.local</groupId>
@@ -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);
}
@@ -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 <http://www.gnu.org/licenses/>.
*/
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);
}
@@ -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() {
+29 -1
View File
@@ -582,7 +582,7 @@
<minor>8</minor>
<build>0</build>
</versionNumber>
<releaseDate>2026-10-09 00:00:00.000 CET</releaseDate>
<releaseDate>2026-10-16 00:00:00.000 CET</releaseDate>
<name></name>
<changes>
<org.diylc.appframework.update.Change>
@@ -593,6 +593,14 @@
<changeType>NEW_FEATURE</changeType>
<description>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</description>
</org.diylc.appframework.update.Change>
<org.diylc.appframework.update.Change>
<changeType>NEW_FEATURE</changeType>
<description>Added Rectangular shape to LED component in addition to Round; added ability to set LED lead spacing</description>
</org.diylc.appframework.update.Change>
<org.diylc.appframework.update.Change>
<changeType>IMPROVEMENT</changeType>
<description>Upgraded AI Models to the latest GPT-6 generation across all tiers</description>
</org.diylc.appframework.update.Change>
<org.diylc.appframework.update.Change>
<changeType>IMPROVEMENT</changeType>
<description>'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</description>
@@ -648,5 +656,25 @@
</changes>
<url>https://github.com/bancika/diy-layout-creator/releases</url>
</org.diylc.appframework.update.Version>
<org.diylc.appframework.update.Version>
<versionNumber>
<major>6</major>
<minor>9</minor>
<build>0</build>
</versionNumber>
<releaseDate>2026-10-17 00:00:00.000 CET</releaseDate>
<name></name>
<changes>
<org.diylc.appframework.update.Change>
<changeType>BUG_FIX</changeType>
<description>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</description>
</org.diylc.appframework.update.Change>
<org.diylc.appframework.update.Change>
<changeType>BUG_FIX</changeType>
<description>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</description>
</org.diylc.appframework.update.Change>
</changes>
<url>https://github.com/bancika/diy-layout-creator/releases</url>
</org.diylc.appframework.update.Version>
</a>
</java.util.Arrays_-ArrayList>
+3 -3
View File
@@ -7,7 +7,7 @@
<parent>
<groupId>org.diylc</groupId>
<artifactId>diylc-parent</artifactId>
<version>6.8.0</version>
<version>6.9.0</version>
</parent>
<artifactId>diylc-library</artifactId>
@@ -73,10 +73,10 @@
</dependency>
<dependency>
<groupId>org.diylc.local</groupId>
<artifactId>gerber-writer-api-1.0.0</artifactId>
<artifactId>gerber-writer-api-1.1.0</artifactId>
<version>1.0</version>
<scope>system</scope>
<systemPath>${project.basedir}/../lib/gerber-writer-api-1.0.0.jar</systemPath>
<systemPath>${project.basedir}/../lib/gerber-writer-api-1.1.0.jar</systemPath>
</dependency>
<dependency>
<groupId>org.diylc.local</groupId>
@@ -79,7 +79,9 @@ public abstract class AbstractLeadedComponent<T> 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<T> 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<T> 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<T> 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<T> extends AbstractLabeledComponen
this.hideShortLeads = hideShortLeads;
}
public enum LabelOriantation {
public enum LabelOrientation {
Directional, Horizontal
}
}
@@ -187,9 +187,9 @@ public class CopperTrace extends AbstractLeadedComponent<Void> implements ILayer
@Deprecated
@Override
public LabelOriantation getLabelOriantation() {
public LabelOrientation getLabelOrientation() {
// TODO Auto-generated method stub
return super.getLabelOriantation();
return super.getLabelOrientation();
}
@Deprecated
@@ -163,9 +163,9 @@ public class Jumper extends AbstractLeadedComponent<Void> implements IContinuity
@Deprecated
@Override
public LabelOriantation getLabelOriantation() {
public LabelOrientation getLabelOrientation() {
// TODO Auto-generated method stub
return super.getLabelOriantation();
return super.getLabelOrientation();
}
@Override
@@ -291,7 +291,7 @@ public class Line extends AbstractLeadedComponent<Void> {
@Deprecated
@Override
public org.diylc.components.AbstractLeadedComponent.LabelOriantation getLabelOriantation() {
return super.getLabelOriantation();
public org.diylc.components.AbstractLeadedComponent.LabelOrientation getLabelOrientation() {
return super.getLabelOrientation();
}
}
@@ -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<String, CapacitorDatasheet> cache = new HashMap<String, CapacitorDatasheet>();
@@ -197,7 +197,7 @@ public class Resistor extends AbstractLeadedComponent<Resistance> 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);
@@ -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<String> 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<String> 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<String> 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
@@ -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<String> 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<String> 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<String> implements IGerberCompo
@Override
protected Size getDefaultWidth() {
return DEFAULT_SIZE;
return DEFAULT_WIDTH;
}
@Override
@@ -107,6 +142,10 @@ public class LED extends AbstractLeadedComponent<String> 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<String> 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<String> 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;
}
}
}
@@ -344,8 +344,8 @@ public class TapeMeasure extends AbstractLeadedComponent<String> {
@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
@@ -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 <http://www.gnu.org/licenses/>.
*/
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);
}
}
@@ -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 <http://www.gnu.org/licenses/>.
*/
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.<IDIYComponent<?>>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 "));
}
}
@@ -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 <http://www.gnu.org/licenses/>.
*/
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("<labelOrientation>Horizontal</labelOrientation>"));
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("<labelOrientation>Horizontal</labelOrientation>"));
Assert.assertFalse(savedXml.contains("labelOriantation"));
}
@Test
public void enumValueHeldAsObjectReadsOldClassName() {
String xml = "<map><entry><string>Label Orientation</string><" + OLD_ENUM_NAME
+ ">Horizontal</" + OLD_ENUM_NAME + "></entry></map>";
Map<?, ?> loaded = (Map<?, ?>) createXStream().fromXML(xml);
Assert.assertEquals(LabelOrientation.Horizontal, loaded.get("Label Orientation"));
}
@Test
public void enumValueHeldAsObjectReadsOldFullClassName() {
String xml = "<map><entry><string>Label Orientation</string><" + OLD_FULL_ENUM_NAME
+ ">Horizontal</" + OLD_FULL_ENUM_NAME + "></entry></map>";
Map<?, ?> loaded = (Map<?, ?>) createXStream().fromXML(xml);
Assert.assertEquals(LabelOrientation.Horizontal, loaded.get("Label Orientation"));
}
@Test
public void enumValueHeldAsObjectWritesOldClassName() {
Map<String, Object> values = new HashMap<String, Object>();
values.put("Label Orientation", LabelOrientation.Horizontal);
String xml = createXStream().toXML(values);
Assert.assertTrue(xml.contains("<" + OLD_ENUM_NAME + ">Horizontal</" + OLD_ENUM_NAME + ">"));
}
}
+1 -1
View File
@@ -7,7 +7,7 @@
<parent>
<groupId>org.diylc</groupId>
<artifactId>diylc-parent</artifactId>
<version>6.8.0</version>
<version>6.9.0</version>
</parent>
<artifactId>diylc-processor</artifactId>
@@ -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("<I", data, 0x3C)[0]
if data[pe:pe + 4] != b"PE\0\0":
raise ValueError("not a PE file: %s" % path)
optional_header = pe + 24
magic = struct.unpack_from("<H", data, optional_header)[0]
if magic == 0x10B:
directories = optional_header + 96
elif magic == 0x20B:
directories = optional_header + 112
else:
raise ValueError("unknown optional header magic 0x%x in %s" % (magic, path))
# data directory entry 4 is the Certificate Table
_, size = struct.unpack_from("<II", data, directories + 4 * 8)
return size > 0
if __name__ == "__main__":
signed = has_signature(sys.argv[1])
print("signed" if signed else "unsigned")
sys.exit(0 if signed else 1)
+3 -3
View File
@@ -7,7 +7,7 @@
<parent>
<groupId>org.diylc</groupId>
<artifactId>diylc-parent</artifactId>
<version>6.8.0</version>
<version>6.9.0</version>
</parent>
<artifactId>diylc-swing</artifactId>
@@ -145,10 +145,10 @@
</dependency>
<dependency>
<groupId>org.diylc.local</groupId>
<artifactId>gerber-writer-api-1.0.0</artifactId>
<artifactId>gerber-writer-api-1.1.0</artifactId>
<version>1.0</version>
<scope>system</scope>
<systemPath>${project.basedir}/../lib/gerber-writer-api-1.0.0.jar</systemPath>
<systemPath>${project.basedir}/../lib/gerber-writer-api-1.1.0.jar</systemPath>
</dependency>
<dependency>
<groupId>org.diylc.local</groupId>
@@ -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());
Binary file not shown.
Binary file not shown.
+1 -1
View File
@@ -6,7 +6,7 @@
<groupId>org.diylc</groupId>
<artifactId>diylc-parent</artifactId>
<version>6.8.0</version>
<version>6.9.0</version>
<packaging>pom</packaging>
<name>DIY Layout Creator</name>