mirror of
https://github.com/bancika/diy-layout-creator
synced 2026-11-13 02:32:09 +10:00
phase 1
This commit is contained in:
@@ -72,5 +72,9 @@ public enum DrawOption {
|
|||||||
/**
|
/**
|
||||||
* When applied, all colors will be converted to blackest black or whitest white
|
* When applied, all colors will be converted to blackest black or whitest white
|
||||||
*/
|
*/
|
||||||
MONOCHROMATIC;
|
MONOCHROMATIC,
|
||||||
|
/**
|
||||||
|
* Paint on the bottom side of a board is drawn at reduced opacity, as if seen through the board.
|
||||||
|
*/
|
||||||
|
FADE_FAR_SIDE;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -110,6 +110,8 @@ public interface IPlugInPort extends ISelectionProcessor, IMouseProcessor, IKeyP
|
|||||||
|
|
||||||
public static final String LOCKED_ALPHA = "lockedAlpha";
|
public static final String LOCKED_ALPHA = "lockedAlpha";
|
||||||
|
|
||||||
|
public static final String FADE_FAR_SIDE_KEY = "fadeFarSide";
|
||||||
|
|
||||||
public static final String LANGUAGE = "language";
|
public static final String LANGUAGE = "language";
|
||||||
public static final String LANGUAGE_DEFAULT = "English";
|
public static final String LANGUAGE_DEFAULT = "English";
|
||||||
|
|
||||||
|
|||||||
@@ -25,7 +25,11 @@ import org.diylc.core.gerber.GerberLayer;
|
|||||||
|
|
||||||
public enum PCBLayer {
|
public enum PCBLayer {
|
||||||
|
|
||||||
_1("Bottom"), _2("Top"), _3("Inner 1"), _4("Inner 2"), _5("Inner 3"), _6("Inner 4"), _7("Inner 5"), _8("Inner 6");
|
_1("Bottom"), _2("Top"),
|
||||||
|
// DIYLC models two-sided boards only. The inner layers are kept, in order, so that files using
|
||||||
|
// them still load with the same layer ids; the editor hides them and Gerber export rejects them.
|
||||||
|
@Deprecated _3("Inner 1"), @Deprecated _4("Inner 2"), @Deprecated _5("Inner 3"),
|
||||||
|
@Deprecated _6("Inner 4"), @Deprecated _7("Inner 5"), @Deprecated _8("Inner 6");
|
||||||
|
|
||||||
private String label;
|
private String label;
|
||||||
|
|
||||||
|
|||||||
@@ -21,6 +21,8 @@
|
|||||||
*/
|
*/
|
||||||
package org.diylc.core;
|
package org.diylc.core;
|
||||||
|
|
||||||
|
import org.diylc.common.PCBLayer;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Interface for tracking component drawing process. Components may use it to stop or restart
|
* Interface for tracking component drawing process. Components may use it to stop or restart
|
||||||
* tracking. Anything drawn while tracking is stopped will not be added to the mouse hot-spot area.
|
* tracking. Anything drawn while tracking is stopped will not be added to the mouse hot-spot area.
|
||||||
@@ -65,4 +67,26 @@ public interface IDrawingObserver {
|
|||||||
* @param marker
|
* @param marker
|
||||||
*/
|
*/
|
||||||
void setContinuityMarker(String marker);
|
void setContinuityMarker(String marker);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Notifies the observer that what follows is painted on the given side of the board, until
|
||||||
|
* {@link #stopDrawingSide()}. Paint that is not bracketed is on the top (component) side. The
|
||||||
|
* observer may draw paint on the side facing away from the viewer at reduced opacity, as if seen
|
||||||
|
* through the board. Inner layers count as the bottom side.
|
||||||
|
*
|
||||||
|
* @param side the layer the paint lies on
|
||||||
|
*/
|
||||||
|
default void startDrawingSide(PCBLayer side) {}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Notifies the observer that what follows is visible from both sides of the board, such as a
|
||||||
|
* drilled hole or a cut through the copper, and is never drawn faded, until
|
||||||
|
* {@link #stopDrawingSide()}.
|
||||||
|
*/
|
||||||
|
default void startDrawingBothSides() {}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Notifies the observer that paint is back on the top (component) side.
|
||||||
|
*/
|
||||||
|
default void stopDrawingSide() {}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -65,7 +65,7 @@ import org.diylc.utils.Constants;
|
|||||||
public class DrawingManager {
|
public class DrawingManager {
|
||||||
|
|
||||||
private static final Logger LOG = Logger.getLogger(DrawingManager.class);
|
private static final Logger LOG = Logger.getLogger(DrawingManager.class);
|
||||||
public static final Percentage MIRROR_ALPHA = new Percentage(50);
|
public static Percentage FAR_SIDE_ALPHA = new Percentage(55);
|
||||||
|
|
||||||
public static int CONTROL_POINT_SIZE = 7;
|
public static int CONTROL_POINT_SIZE = 7;
|
||||||
public static double EXTRA_SPACE = 0.25;
|
public static double EXTRA_SPACE = 0.25;
|
||||||
@@ -536,13 +536,58 @@ public class DrawingManager {
|
|||||||
|
|
||||||
private static void drawMirroredComponent(Project project, IBoard board, IDIYComponent<?> component,
|
private static void drawMirroredComponent(Project project, IBoard board, IDIYComponent<?> component,
|
||||||
G2DWrapper g2dWrapper, ComponentState state, boolean outlineMode) {
|
G2DWrapper g2dWrapper, ComponentState state, boolean outlineMode) {
|
||||||
if (board.getUndersideDisplay() == null || board.getUndersideDisplay() == BoardUndersideDisplay.NONE) {
|
AffineTransform mirrorTx = createMirrorTransform(board);
|
||||||
|
if (mirrorTx == null) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
G2DWrapper.MirrorDirection mirrorDirection;
|
||||||
|
switch (board.getUndersideDisplay()) {
|
||||||
|
case ABOVE:
|
||||||
|
case BELOW:
|
||||||
|
mirrorDirection = G2DWrapper.MirrorDirection.VERTICAL;
|
||||||
|
break;
|
||||||
|
case LEFT:
|
||||||
|
case RIGHT:
|
||||||
|
mirrorDirection = G2DWrapper.MirrorDirection.HORIZONTAL;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
mirrorDirection = G2DWrapper.MirrorDirection.NONE;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
AffineTransform oldTx = g2dWrapper.getTransform();
|
||||||
|
Composite oldComposite = g2dWrapper.getComposite();
|
||||||
|
g2dWrapper.setMirrorDirection(mirrorDirection);
|
||||||
|
try {
|
||||||
|
g2dWrapper.transform(mirrorTx);
|
||||||
|
// the mirror is the copper side, so copper is drawn solid and the component side faded
|
||||||
|
g2dWrapper.setViewedSide(PCBLayer._1, Boolean.TRUE.equals(board.getUndersideTransparency()),
|
||||||
|
component instanceof IBoard ? PCBLayer._1 : PCBLayer._2);
|
||||||
|
component.draw(g2dWrapper, state, outlineMode, project, g2dWrapper);
|
||||||
|
} catch (Exception e) {
|
||||||
|
LOG.error("Error drawing mirrored " + component.getName(), e);
|
||||||
|
} finally {
|
||||||
|
g2dWrapper.setMirrorDirection(G2DWrapper.MirrorDirection.NONE);
|
||||||
|
g2dWrapper.setTransform(oldTx);
|
||||||
|
g2dWrapper.stopDrawingSide();
|
||||||
|
g2dWrapper.resetViewedSide();
|
||||||
|
g2dWrapper.setComposite(oldComposite);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the reflection that maps the board onto its underside mirror, or null when the board
|
||||||
|
* has no underside display.
|
||||||
|
*/
|
||||||
|
private static AffineTransform createMirrorTransform(IBoard board) {
|
||||||
|
if (board.getUndersideDisplay() == null || board.getUndersideDisplay() == BoardUndersideDisplay.NONE) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
double offset = board.getUndersideOffset() == null ? 0 : board.getUndersideOffset().convertToPixels();
|
double offset = board.getUndersideOffset() == null ? 0 : board.getUndersideOffset().convertToPixels();
|
||||||
Rectangle2D boardRectangle = board.getBoardRectangle();
|
Rectangle2D boardRectangle = board.getBoardRectangle();
|
||||||
if (boardRectangle == null) {
|
if (boardRectangle == null) {
|
||||||
return;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
double minX = Math.min(boardRectangle.getMinX(), boardRectangle.getMaxX());
|
double minX = Math.min(boardRectangle.getMinX(), boardRectangle.getMaxX());
|
||||||
@@ -581,40 +626,9 @@ public class DrawingManager {
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
default:
|
default:
|
||||||
return;
|
return null;
|
||||||
}
|
|
||||||
|
|
||||||
G2DWrapper.MirrorDirection mirrorDirection;
|
|
||||||
switch (board.getUndersideDisplay()) {
|
|
||||||
case ABOVE:
|
|
||||||
case BELOW:
|
|
||||||
mirrorDirection = G2DWrapper.MirrorDirection.VERTICAL;
|
|
||||||
break;
|
|
||||||
case LEFT:
|
|
||||||
case RIGHT:
|
|
||||||
mirrorDirection = G2DWrapper.MirrorDirection.HORIZONTAL;
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
mirrorDirection = G2DWrapper.MirrorDirection.NONE;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
AffineTransform oldTx = g2dWrapper.getTransform();
|
|
||||||
Composite oldComposite = g2dWrapper.getComposite();
|
|
||||||
g2dWrapper.setMirrorDirection(mirrorDirection);
|
|
||||||
try {
|
|
||||||
g2dWrapper.transform(mirrorTx);
|
|
||||||
if (Boolean.TRUE.equals(board.getUndersideTransparency())) {
|
|
||||||
g2dWrapper.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_OVER, (float)(MIRROR_ALPHA.getValue().doubleValue() / 100d)));
|
|
||||||
}
|
|
||||||
component.draw(g2dWrapper, state, outlineMode, project, g2dWrapper);
|
|
||||||
} catch (Exception e) {
|
|
||||||
LOG.error("Error drawing mirrored " + component.getName(), e);
|
|
||||||
} finally {
|
|
||||||
g2dWrapper.setMirrorDirection(G2DWrapper.MirrorDirection.NONE);
|
|
||||||
g2dWrapper.setTransform(oldTx);
|
|
||||||
g2dWrapper.setComposite(oldComposite);
|
|
||||||
}
|
}
|
||||||
|
return mirrorTx;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void drawComponent(Project project, Set<DrawOption> drawOptions, Double scaleFactor,
|
private static void drawComponent(Project project, Set<DrawOption> drawOptions, Double scaleFactor,
|
||||||
|
|||||||
@@ -17,6 +17,7 @@
|
|||||||
*/
|
*/
|
||||||
package org.diylc.presenter;
|
package org.diylc.presenter;
|
||||||
|
|
||||||
|
import java.awt.AlphaComposite;
|
||||||
import java.awt.BasicStroke;
|
import java.awt.BasicStroke;
|
||||||
import java.awt.Color;
|
import java.awt.Color;
|
||||||
import java.awt.Composite;
|
import java.awt.Composite;
|
||||||
@@ -60,6 +61,7 @@ import java.util.Map.Entry;
|
|||||||
import org.apache.log4j.Logger;
|
import org.apache.log4j.Logger;
|
||||||
import org.diylc.common.DrawOption;
|
import org.diylc.common.DrawOption;
|
||||||
import org.diylc.common.ObjectCache;
|
import org.diylc.common.ObjectCache;
|
||||||
|
import org.diylc.common.PCBLayer;
|
||||||
import org.diylc.common.ZoomableStroke;
|
import org.diylc.common.ZoomableStroke;
|
||||||
import org.diylc.core.IDrawingObserver;
|
import org.diylc.core.IDrawingObserver;
|
||||||
|
|
||||||
@@ -118,6 +120,17 @@ class G2DWrapper extends Graphics2D implements IDrawingObserver {
|
|||||||
|
|
||||||
private MirrorDirection mirrorDirection = MirrorDirection.NONE;
|
private MirrorDirection mirrorDirection = MirrorDirection.NONE;
|
||||||
|
|
||||||
|
private PCBLayer viewedSide = PCBLayer._2;
|
||||||
|
// the side assumed for paint a component does not bracket with startDrawingSide
|
||||||
|
private PCBLayer undeclaredSide = PCBLayer._2;
|
||||||
|
private PCBLayer paintedSide = PCBLayer._2;
|
||||||
|
private boolean fadeFarSide;
|
||||||
|
// the composite the component asked for, held while far-side paint is drawn faded on top of it
|
||||||
|
private Composite unfadedComposite;
|
||||||
|
// boards switch sides once per strip or pad, so the last faded composite is reused
|
||||||
|
private Composite lastFadeInput;
|
||||||
|
private Composite lastFadeOutput;
|
||||||
|
|
||||||
private double zoom;
|
private double zoom;
|
||||||
private int zOrder;
|
private int zOrder;
|
||||||
|
|
||||||
@@ -131,6 +144,7 @@ class G2DWrapper extends Graphics2D implements IDrawingObserver {
|
|||||||
super();
|
super();
|
||||||
this.canvasGraphics = canvasGraphics;
|
this.canvasGraphics = canvasGraphics;
|
||||||
this.drawOptions = drawOptions;
|
this.drawOptions = drawOptions;
|
||||||
|
this.fadeFarSide = drawOptions.contains(DrawOption.FADE_FAR_SIDE);
|
||||||
String debugAreaPerformanceStr = System.getProperty(DEBUG_AREA_PERFORMANCE);
|
String debugAreaPerformanceStr = System.getProperty(DEBUG_AREA_PERFORMANCE);
|
||||||
debugAreaPerformance = "true".equalsIgnoreCase(debugAreaPerformanceStr);
|
debugAreaPerformance = "true".equalsIgnoreCase(debugAreaPerformanceStr);
|
||||||
this.zoom = zoom;
|
this.zoom = zoom;
|
||||||
@@ -154,6 +168,31 @@ class G2DWrapper extends Graphics2D implements IDrawingObserver {
|
|||||||
return mirrorDirection;
|
return mirrorDirection;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the side of the board the viewer is looking at and whether paint on the other side is
|
||||||
|
* drawn faded. The main view looks at the top side; the underside mirror looks at the bottom.
|
||||||
|
*
|
||||||
|
* @param viewedSide
|
||||||
|
* @param fadeFarSide
|
||||||
|
* @param undeclaredSide the side assumed for paint that is not bracketed with
|
||||||
|
* {@link #startDrawingSide(PCBLayer)}; a board body is seen from both sides, so the mirror
|
||||||
|
* draws it as the viewed side
|
||||||
|
*/
|
||||||
|
public void setViewedSide(PCBLayer viewedSide, boolean fadeFarSide, PCBLayer undeclaredSide) {
|
||||||
|
this.viewedSide = viewedSide;
|
||||||
|
this.fadeFarSide = fadeFarSide;
|
||||||
|
this.undeclaredSide = undeclaredSide;
|
||||||
|
this.paintedSide = undeclaredSide;
|
||||||
|
updateFarSideFade();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns to the top-side view, fading the bottom side only if the draw options ask for it.
|
||||||
|
*/
|
||||||
|
public void resetViewedSide() {
|
||||||
|
setViewedSide(PCBLayer._2, drawOptions.contains(DrawOption.FADE_FAR_SIDE), PCBLayer._2);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Clears out the current area and caches canvas settings.
|
* Clears out the current area and caches canvas settings.
|
||||||
*
|
*
|
||||||
@@ -168,6 +207,8 @@ class G2DWrapper extends Graphics2D implements IDrawingObserver {
|
|||||||
this.debugOutline = new Area();
|
this.debugOutline = new Area();
|
||||||
// trackingStacks.clear();
|
// trackingStacks.clear();
|
||||||
this.continuityMarker = null;
|
this.continuityMarker = null;
|
||||||
|
this.paintedSide = undeclaredSide;
|
||||||
|
updateFarSideFade();
|
||||||
this.originalStroke = canvasGraphics.getStroke();
|
this.originalStroke = canvasGraphics.getStroke();
|
||||||
this.originalColor = canvasGraphics.getColor();
|
this.originalColor = canvasGraphics.getColor();
|
||||||
this.originalTx = canvasGraphics.getTransform();
|
this.originalTx = canvasGraphics.getTransform();
|
||||||
@@ -189,6 +230,8 @@ class G2DWrapper extends Graphics2D implements IDrawingObserver {
|
|||||||
public ComponentArea finishedDrawingComponent() {
|
public ComponentArea finishedDrawingComponent() {
|
||||||
drawingComponent = false;
|
drawingComponent = false;
|
||||||
recordingDrawnBounds = false;
|
recordingDrawnBounds = false;
|
||||||
|
paintedSide = undeclaredSide;
|
||||||
|
unfadedComposite = null;
|
||||||
canvasGraphics.setStroke(originalStroke);
|
canvasGraphics.setStroke(originalStroke);
|
||||||
canvasGraphics.setColor(originalColor);
|
canvasGraphics.setColor(originalColor);
|
||||||
canvasGraphics.setTransform(originalTx);
|
canvasGraphics.setTransform(originalTx);
|
||||||
@@ -280,6 +323,54 @@ class G2DWrapper extends Graphics2D implements IDrawingObserver {
|
|||||||
continuityMarker = marker;
|
continuityMarker = marker;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void startDrawingSide(PCBLayer side) {
|
||||||
|
this.paintedSide = side == null ? undeclaredSide : side;
|
||||||
|
updateFarSideFade();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void startDrawingBothSides() {
|
||||||
|
this.paintedSide = viewedSide;
|
||||||
|
updateFarSideFade();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void stopDrawingSide() {
|
||||||
|
this.paintedSide = undeclaredSide;
|
||||||
|
updateFarSideFade();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void updateFarSideFade() {
|
||||||
|
boolean fade = fadeFarSide && !drawOptions.contains(DrawOption.OUTLINE_MODE)
|
||||||
|
&& isBottom(paintedSide) != isBottom(viewedSide);
|
||||||
|
if (fade && unfadedComposite == null) {
|
||||||
|
unfadedComposite = canvasGraphics.getComposite();
|
||||||
|
canvasGraphics.setComposite(fade(unfadedComposite));
|
||||||
|
} else if (!fade && unfadedComposite != null) {
|
||||||
|
canvasGraphics.setComposite(unfadedComposite);
|
||||||
|
unfadedComposite = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static boolean isBottom(PCBLayer layer) {
|
||||||
|
return layer != PCBLayer._2;
|
||||||
|
}
|
||||||
|
|
||||||
|
// the stronger of the two fades wins rather than multiplying, so that copper on a locked board,
|
||||||
|
// already drawn at the locked alpha, does not all but disappear
|
||||||
|
private Composite fade(Composite composite) {
|
||||||
|
if (composite != lastFadeInput || lastFadeOutput == null) {
|
||||||
|
float alpha = (float) (DrawingManager.FAR_SIDE_ALPHA.getValue().doubleValue() / 100d);
|
||||||
|
if (composite instanceof AlphaComposite ac && ac.getRule() == AlphaComposite.SRC_OVER) {
|
||||||
|
alpha = Math.min(alpha, ac.getAlpha());
|
||||||
|
}
|
||||||
|
lastFadeInput = composite;
|
||||||
|
lastFadeOutput = AlphaComposite.getInstance(AlphaComposite.SRC_OVER, alpha);
|
||||||
|
}
|
||||||
|
return lastFadeOutput;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Appends shape interior to the current component area.
|
* Appends shape interior to the current component area.
|
||||||
*
|
*
|
||||||
@@ -669,7 +760,7 @@ class G2DWrapper extends Graphics2D implements IDrawingObserver {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Composite getComposite() {
|
public Composite getComposite() {
|
||||||
return canvasGraphics.getComposite();
|
return unfadedComposite != null ? unfadedComposite : canvasGraphics.getComposite();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -738,7 +829,13 @@ class G2DWrapper extends Graphics2D implements IDrawingObserver {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void setComposite(Composite comp) {
|
public void setComposite(Composite comp) {
|
||||||
if (!drawOptions.contains(DrawOption.OUTLINE_MODE)) {
|
if (drawOptions.contains(DrawOption.OUTLINE_MODE)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (unfadedComposite != null) {
|
||||||
|
unfadedComposite = comp;
|
||||||
|
canvasGraphics.setComposite(fade(comp));
|
||||||
|
} else {
|
||||||
canvasGraphics.setComposite(comp);
|
canvasGraphics.setComposite(comp);
|
||||||
}
|
}
|
||||||
// FIXME: check this.
|
// FIXME: check this.
|
||||||
|
|||||||
@@ -1,9 +1,12 @@
|
|||||||
package org.diylc.presenter;
|
package org.diylc.presenter;
|
||||||
|
|
||||||
import static org.junit.Assert.assertEquals;
|
import static org.junit.Assert.assertEquals;
|
||||||
|
import static org.junit.Assert.assertSame;
|
||||||
import static org.junit.Assert.assertTrue;
|
import static org.junit.Assert.assertTrue;
|
||||||
|
|
||||||
import java.awt.AlphaComposite;
|
import java.awt.AlphaComposite;
|
||||||
|
import java.awt.Color;
|
||||||
|
import java.awt.Composite;
|
||||||
import java.awt.Graphics2D;
|
import java.awt.Graphics2D;
|
||||||
import java.awt.geom.AffineTransform;
|
import java.awt.geom.AffineTransform;
|
||||||
import java.awt.geom.Area;
|
import java.awt.geom.Area;
|
||||||
@@ -20,6 +23,7 @@ import org.diylc.appframework.miscutils.InMemoryConfigurationManager;
|
|||||||
import org.diylc.appframework.simplemq.MessageDispatcher;
|
import org.diylc.appframework.simplemq.MessageDispatcher;
|
||||||
import org.diylc.common.DrawOption;
|
import org.diylc.common.DrawOption;
|
||||||
import org.diylc.common.EventType;
|
import org.diylc.common.EventType;
|
||||||
|
import org.diylc.common.PCBLayer;
|
||||||
import org.diylc.core.BoardUndersideDisplay;
|
import org.diylc.core.BoardUndersideDisplay;
|
||||||
import org.diylc.core.ComponentState;
|
import org.diylc.core.ComponentState;
|
||||||
import org.diylc.core.IBoard;
|
import org.diylc.core.IBoard;
|
||||||
@@ -175,10 +179,10 @@ public class DrawingManagerTest {
|
|||||||
assertEquals(150.0, mirroredP.getX(), 0.001);
|
assertEquals(150.0, mirroredP.getX(), 0.001);
|
||||||
assertEquals(670.0, mirroredP.getY(), 0.001);
|
assertEquals(670.0, mirroredP.getY(), 0.001);
|
||||||
|
|
||||||
// Check transparency
|
// the mirror fades through the wrapper, so the component still sees its own composite
|
||||||
assertEquals(2, component.capturedAlphas.size());
|
assertEquals(2, component.capturedAlphas.size());
|
||||||
assertEquals(1.0f, component.capturedAlphas.get(0), 0.01f);
|
assertEquals(1.0f, component.capturedAlphas.get(0), 0.01f);
|
||||||
assertEquals(0.5f, component.capturedAlphas.get(1), 0.01f);
|
assertEquals(1.0f, component.capturedAlphas.get(1), 0.01f);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@@ -213,7 +217,6 @@ public class DrawingManagerTest {
|
|||||||
assertEquals(150.0, mirroredP.getX(), 0.001);
|
assertEquals(150.0, mirroredP.getX(), 0.001);
|
||||||
assertEquals(10.0, mirroredP.getY(), 0.001);
|
assertEquals(10.0, mirroredP.getY(), 0.001);
|
||||||
|
|
||||||
// Transparency should not be 0.5 when undersideTransparency is false
|
|
||||||
assertEquals(1.0f, component.capturedAlphas.get(1), 0.01f);
|
assertEquals(1.0f, component.capturedAlphas.get(1), 0.01f);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -647,6 +650,177 @@ public class DrawingManagerTest {
|
|||||||
assertTrue(recorded.contains(50, 5));
|
assertTrue(recorded.contains(50, 5));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testBottomSideFadedOnlyWithOption() {
|
||||||
|
Project project = new Project();
|
||||||
|
project.getComponents().add(new SidePaintingComponent(new Rectangle2D.Double(150, 150, 10, 10), null));
|
||||||
|
project.getComponents().add(new SidePaintingComponent(new Rectangle2D.Double(200, 200, 10, 10), PCBLayer._1));
|
||||||
|
project.getComponents().add(new SidePaintingComponent(new Rectangle2D.Double(250, 250, 10, 10), PCBLayer._3));
|
||||||
|
|
||||||
|
BufferedImage faded = renderSides(project, EnumSet.of(DrawOption.ZOOM, DrawOption.FADE_FAR_SIDE));
|
||||||
|
assertSolid(faded, 155, 155);
|
||||||
|
assertFaded(faded, 205, 205);
|
||||||
|
// inner layers count as the bottom side
|
||||||
|
assertFaded(faded, 255, 255);
|
||||||
|
|
||||||
|
BufferedImage plain = renderSides(project, EnumSet.of(DrawOption.ZOOM));
|
||||||
|
assertSolid(plain, 205, 205);
|
||||||
|
assertSolid(plain, 255, 255);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testBothSidesNeverFaded() {
|
||||||
|
Project project = new Project();
|
||||||
|
MockBoard board = new MockBoard();
|
||||||
|
board.setBoardRectangle(new Rectangle2D.Double(100, 100, 200, 300));
|
||||||
|
board.setUndersideDisplay(BoardUndersideDisplay.BELOW);
|
||||||
|
board.setUndersideOffset(new Size(20.0, SizeUnit.px));
|
||||||
|
project.getComponents().add(board);
|
||||||
|
project.getComponents().add(SidePaintingComponent.bothSides(new Rectangle2D.Double(150, 150, 10, 10)));
|
||||||
|
|
||||||
|
BufferedImage img = renderSides(project, EnumSet.of(DrawOption.ZOOM, DrawOption.FADE_FAR_SIDE));
|
||||||
|
assertSolid(img, 155, 155);
|
||||||
|
assertSolid(img, 155, 665);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testLockedBottomSideNotFadedTwice() {
|
||||||
|
Project project = new Project();
|
||||||
|
SidePaintingComponent bottom = new SidePaintingComponent(new Rectangle2D.Double(200, 200, 10, 10), PCBLayer._1);
|
||||||
|
project.getComponents().add(bottom);
|
||||||
|
|
||||||
|
DrawingManager drawingManager = createDrawingManager();
|
||||||
|
BufferedImage img = new BufferedImage(1000, 1000, BufferedImage.TYPE_INT_ARGB);
|
||||||
|
Graphics2D g2d = img.createGraphics();
|
||||||
|
drawingManager.drawProject(g2d, project,
|
||||||
|
EnumSet.of(DrawOption.ZOOM, DrawOption.FADE_FAR_SIDE, DrawOption.LOCKED_ALPHA), null, null,
|
||||||
|
Collections.emptyList(), Collections.singleton(bottom), Collections.emptySet(), null, null,
|
||||||
|
false, Collections.emptySet(), 1.0, 1.0, null);
|
||||||
|
g2d.dispose();
|
||||||
|
|
||||||
|
// the stronger of the 50% locked alpha and the far-side alpha decides, not their product
|
||||||
|
assertFaded(img, 205, 205, Math.min(0.5f, farSideAlpha()));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testMirrorShowsCopperSideSolid() {
|
||||||
|
Project project = new Project();
|
||||||
|
MockBoard board = new MockBoard();
|
||||||
|
board.setBoardRectangle(new Rectangle2D.Double(100, 100, 200, 300));
|
||||||
|
board.setUndersideDisplay(BoardUndersideDisplay.BELOW);
|
||||||
|
board.setUndersideOffset(new Size(20.0, SizeUnit.px));
|
||||||
|
board.setUndersideTransparency(true);
|
||||||
|
project.getComponents().add(board);
|
||||||
|
project.getComponents().add(new SidePaintingComponent(new Rectangle2D.Double(150, 150, 10, 10), null));
|
||||||
|
project.getComponents().add(new SidePaintingComponent(new Rectangle2D.Double(200, 200, 10, 10), PCBLayer._1));
|
||||||
|
|
||||||
|
// reflection across y = 410 puts the top-side square at y 660..670 and the bottom one at 610..620
|
||||||
|
BufferedImage img = renderSides(project, EnumSet.of(DrawOption.ZOOM, DrawOption.FADE_FAR_SIDE));
|
||||||
|
assertFaded(img, 155, 665);
|
||||||
|
assertSolid(img, 205, 615);
|
||||||
|
|
||||||
|
board.setUndersideTransparency(false);
|
||||||
|
img = renderSides(project, EnumSet.of(DrawOption.ZOOM, DrawOption.FADE_FAR_SIDE));
|
||||||
|
assertSolid(img, 155, 665);
|
||||||
|
assertSolid(img, 205, 615);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testFarSideFadeKeepsComponentComposite() {
|
||||||
|
BufferedImage img = new BufferedImage(100, 100, BufferedImage.TYPE_INT_ARGB);
|
||||||
|
Graphics2D canvas = img.createGraphics();
|
||||||
|
G2DWrapper wrapper = new G2DWrapper(canvas, 1.0, EnumSet.of(DrawOption.FADE_FAR_SIDE));
|
||||||
|
float farSideAlpha = farSideAlpha();
|
||||||
|
|
||||||
|
Composite half = AlphaComposite.getInstance(AlphaComposite.SRC_OVER, 0.5f);
|
||||||
|
wrapper.setComposite(half);
|
||||||
|
wrapper.startDrawingSide(PCBLayer._1);
|
||||||
|
assertSame(half, wrapper.getComposite());
|
||||||
|
// the stronger of the two fades wins rather than multiplying
|
||||||
|
assertEquals(Math.min(0.5f, farSideAlpha), ((AlphaComposite) canvas.getComposite()).getAlpha(), 0.001f);
|
||||||
|
|
||||||
|
wrapper.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_OVER, 0.25f));
|
||||||
|
assertEquals(Math.min(0.25f, farSideAlpha), ((AlphaComposite) canvas.getComposite()).getAlpha(), 0.001f);
|
||||||
|
|
||||||
|
wrapper.setComposite(half);
|
||||||
|
wrapper.stopDrawingSide();
|
||||||
|
assertSame(half, canvas.getComposite());
|
||||||
|
canvas.dispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testFarSideNotFadedInOutlineMode() {
|
||||||
|
BufferedImage img = new BufferedImage(100, 100, BufferedImage.TYPE_INT_ARGB);
|
||||||
|
Graphics2D canvas = img.createGraphics();
|
||||||
|
Composite original = canvas.getComposite();
|
||||||
|
G2DWrapper wrapper = new G2DWrapper(canvas, 1.0,
|
||||||
|
EnumSet.of(DrawOption.FADE_FAR_SIDE, DrawOption.OUTLINE_MODE));
|
||||||
|
wrapper.startDrawingSide(PCBLayer._1);
|
||||||
|
assertSame(original, canvas.getComposite());
|
||||||
|
wrapper.stopDrawingSide();
|
||||||
|
canvas.dispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testFarSideFadeLeavesContinuityUnchanged() {
|
||||||
|
Area faded = drawBottomConductor(EnumSet.of(DrawOption.FADE_FAR_SIDE));
|
||||||
|
Area plain = drawBottomConductor(EnumSet.noneOf(DrawOption.class));
|
||||||
|
assertTrue(faded.equals(plain));
|
||||||
|
assertEquals(new Rectangle2D.Double(10, 10, 30, 20), faded.getBounds2D());
|
||||||
|
}
|
||||||
|
|
||||||
|
private Area drawBottomConductor(Set<DrawOption> drawOptions) {
|
||||||
|
BufferedImage img = new BufferedImage(100, 100, BufferedImage.TYPE_INT_ARGB);
|
||||||
|
Graphics2D canvas = img.createGraphics();
|
||||||
|
G2DWrapper wrapper = new G2DWrapper(canvas, 1.0, drawOptions);
|
||||||
|
wrapper.startedDrawingComponent(0);
|
||||||
|
wrapper.startDrawingSide(PCBLayer._1);
|
||||||
|
wrapper.startTrackingContinuityArea(true);
|
||||||
|
wrapper.fill(new Rectangle2D.Double(10, 10, 30, 20));
|
||||||
|
wrapper.stopTrackingContinuityArea();
|
||||||
|
wrapper.stopDrawingSide();
|
||||||
|
ComponentArea componentArea = wrapper.finishedDrawingComponent();
|
||||||
|
canvas.dispose();
|
||||||
|
Area area = new Area();
|
||||||
|
for (Area a : componentArea.getContinuityPositiveAreas()) {
|
||||||
|
area.add(a);
|
||||||
|
}
|
||||||
|
return area;
|
||||||
|
}
|
||||||
|
|
||||||
|
private BufferedImage renderSides(Project project, Set<DrawOption> drawOptions) {
|
||||||
|
DrawingManager drawingManager = createDrawingManager();
|
||||||
|
BufferedImage img = new BufferedImage(1000, 1000, BufferedImage.TYPE_INT_ARGB);
|
||||||
|
Graphics2D g2d = img.createGraphics();
|
||||||
|
drawingManager.drawProject(g2d, project, drawOptions, null, null, Collections.emptyList(),
|
||||||
|
Collections.emptySet(), Collections.emptySet(), null, null, false, Collections.emptySet(),
|
||||||
|
1.0, 1.0, null);
|
||||||
|
g2d.dispose();
|
||||||
|
return img;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static float farSideAlpha() {
|
||||||
|
return (float) (DrawingManager.FAR_SIDE_ALPHA.getValue().doubleValue() / 100d);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static int red(BufferedImage img, int x, int y) {
|
||||||
|
return new Color(img.getRGB(x, y)).getRed();
|
||||||
|
}
|
||||||
|
|
||||||
|
// the squares are painted black, so the red channel alone shows how much of the background is left
|
||||||
|
private static void assertSolid(BufferedImage img, int x, int y) {
|
||||||
|
assertEquals(0, red(img, x, y));
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void assertFaded(BufferedImage img, int x, int y) {
|
||||||
|
assertFaded(img, x, y, farSideAlpha());
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void assertFaded(BufferedImage img, int x, int y, float alpha) {
|
||||||
|
double background = red(img, 5, 5);
|
||||||
|
assertEquals(background * (1 - alpha), red(img, x, y), 3);
|
||||||
|
}
|
||||||
|
|
||||||
private void drawVisibleRect(DrawingManager drawingManager, Project project,
|
private void drawVisibleRect(DrawingManager drawingManager, Project project,
|
||||||
Rectangle2D visibleRect) {
|
Rectangle2D visibleRect) {
|
||||||
BufferedImage img = new BufferedImage(1000, 1000, BufferedImage.TYPE_INT_ARGB);
|
BufferedImage img = new BufferedImage(1000, 1000, BufferedImage.TYPE_INT_ARGB);
|
||||||
@@ -732,6 +906,50 @@ public class DrawingManagerTest {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// paints an opaque black square, declared on the given side or, when null, left undeclared
|
||||||
|
public static class SidePaintingComponent extends MockDIYComponent {
|
||||||
|
|
||||||
|
private static final long serialVersionUID = 1L;
|
||||||
|
|
||||||
|
private Rectangle2D shape;
|
||||||
|
private PCBLayer side;
|
||||||
|
private boolean bothSides;
|
||||||
|
|
||||||
|
public SidePaintingComponent(Rectangle2D shape, PCBLayer side) {
|
||||||
|
this.shape = shape;
|
||||||
|
this.side = side;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static SidePaintingComponent bothSides(Rectangle2D shape) {
|
||||||
|
SidePaintingComponent component = new SidePaintingComponent(shape, null);
|
||||||
|
component.bothSides = true;
|
||||||
|
return component;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getControlPointCount() {
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Point2D getControlPoint(int index) {
|
||||||
|
return new Point2D.Double(shape.getCenterX(), shape.getCenterY());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void draw(Graphics2D g2d, ComponentState componentState, boolean outlineMode,
|
||||||
|
Project project, IDrawingObserver drawingObserver) {
|
||||||
|
if (bothSides) {
|
||||||
|
drawingObserver.startDrawingBothSides();
|
||||||
|
} else if (side != null) {
|
||||||
|
drawingObserver.startDrawingSide(side);
|
||||||
|
}
|
||||||
|
g2d.setColor(Color.black);
|
||||||
|
g2d.fill(shape);
|
||||||
|
drawingObserver.stopDrawingSide();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Simple delegating Graphics2D proxy for testing
|
// Simple delegating Graphics2D proxy for testing
|
||||||
private static class Graphics2DProxy extends Graphics2D {
|
private static class Graphics2DProxy extends Graphics2D {
|
||||||
private final Graphics2D delegate;
|
private final Graphics2D delegate;
|
||||||
|
|||||||
@@ -26,6 +26,7 @@ import java.awt.Graphics2D;
|
|||||||
import java.awt.Shape;
|
import java.awt.Shape;
|
||||||
import java.awt.geom.Point2D;
|
import java.awt.geom.Point2D;
|
||||||
|
|
||||||
|
import org.diylc.common.PCBLayer;
|
||||||
import org.diylc.components.transform.AbstractBoardTransformer;
|
import org.diylc.components.transform.AbstractBoardTransformer;
|
||||||
import org.diylc.core.ComponentState;
|
import org.diylc.core.ComponentState;
|
||||||
import org.diylc.core.IDIYComponent;
|
import org.diylc.core.IDIYComponent;
|
||||||
@@ -81,10 +82,12 @@ public class PerfBoard extends AbstractBoard implements ILayeredComponent {
|
|||||||
y += spacing;
|
y += spacing;
|
||||||
while (x < finalSecondPoint.getX() - spacing - diameter) {
|
while (x < finalSecondPoint.getX() - spacing - diameter) {
|
||||||
x += spacing;
|
x += spacing;
|
||||||
|
drawingObserver.startDrawingSide(PCBLayer._1);
|
||||||
g2d.setColor(padColor);
|
g2d.setColor(padColor);
|
||||||
g2d.fillOval((int)(x - diameter / 2), (int)(y - diameter / 2), diameter, diameter);
|
g2d.fillOval((int)(x - diameter / 2), (int)(y - diameter / 2), diameter, diameter);
|
||||||
g2d.setColor(padColor.darker());
|
g2d.setColor(padColor.darker());
|
||||||
g2d.drawOval((int)(x - diameter / 2), (int)(y - diameter / 2), diameter, diameter);
|
g2d.drawOval((int)(x - diameter / 2), (int)(y - diameter / 2), diameter, diameter);
|
||||||
|
drawingObserver.stopDrawingSide();
|
||||||
g2d.setColor(Constants.CANVAS_COLOR);
|
g2d.setColor(Constants.CANVAS_COLOR);
|
||||||
g2d.fillOval((int)(x - holeDiameter / 2), (int)(y - holeDiameter / 2), holeDiameter, holeDiameter);
|
g2d.fillOval((int)(x - holeDiameter / 2), (int)(y - holeDiameter / 2), holeDiameter, holeDiameter);
|
||||||
g2d.setColor(padColor.darker());
|
g2d.setColor(padColor.darker());
|
||||||
|
|||||||
@@ -31,6 +31,7 @@ import java.awt.geom.Rectangle2D;
|
|||||||
import org.diylc.awt.StringUtils;
|
import org.diylc.awt.StringUtils;
|
||||||
import org.diylc.common.HorizontalAlignment;
|
import org.diylc.common.HorizontalAlignment;
|
||||||
import org.diylc.common.ObjectCache;
|
import org.diylc.common.ObjectCache;
|
||||||
|
import org.diylc.common.PCBLayer;
|
||||||
import org.diylc.common.VerticalAlignment;
|
import org.diylc.common.VerticalAlignment;
|
||||||
import org.diylc.components.transform.ProtoBoardTransformer;
|
import org.diylc.components.transform.ProtoBoardTransformer;
|
||||||
import org.diylc.core.ComponentState;
|
import org.diylc.core.ComponentState;
|
||||||
@@ -169,6 +170,7 @@ public class ProtoBoard extends AbstractProtoBoard {
|
|||||||
(int) (point.getX() + (1 + SEGMENTS_SUM[3] + 2.5) * spacing), (int) (point.getY() + (yOffset + y - 0.25d) * spacing + 1));
|
(int) (point.getX() + (1 + SEGMENTS_SUM[3] + 2.5) * spacing), (int) (point.getY() + (yOffset + y - 0.25d) * spacing + 1));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
drawingObserver.startDrawingSide(PCBLayer._1);
|
||||||
g2d.setColor(undersideTraceColor);
|
g2d.setColor(undersideTraceColor);
|
||||||
|
|
||||||
// bottom row.
|
// bottom row.
|
||||||
@@ -182,6 +184,7 @@ public class ProtoBoard extends AbstractProtoBoard {
|
|||||||
// vertical ground line
|
// vertical ground line
|
||||||
g2d.drawLine((int) (point.getX() + (1 + SEGMENTS_SUM[3] + 1.5) * spacing), (int) (point.getY() + (yOffset - 1.5) * spacing),
|
g2d.drawLine((int) (point.getX() + (1 + SEGMENTS_SUM[3] + 1.5) * spacing), (int) (point.getY() + (yOffset - 1.5) * spacing),
|
||||||
(int) (point.getX() + (1 + SEGMENTS_SUM[3] + 1.5) * spacing), (int) (point.getY() + (yOffset + ROW_COUNT + 0.5) * spacing));
|
(int) (point.getX() + (1 + SEGMENTS_SUM[3] + 1.5) * spacing), (int) (point.getY() + (yOffset + ROW_COUNT + 0.5) * spacing));
|
||||||
|
drawingObserver.stopDrawingSide();
|
||||||
|
|
||||||
g2d.setColor(traceColor);
|
g2d.setColor(traceColor);
|
||||||
|
|
||||||
@@ -494,6 +497,7 @@ public class ProtoBoard extends AbstractProtoBoard {
|
|||||||
g2d.drawRect((int) (point.getX() + (1 + SEGMENTS_SUM[3] + 0.5) * spacing) + 1, (int) (point.getY() + (yOffset + y - 0.5d) * spacing + 2), miniPadSize, miniPadSize);
|
g2d.drawRect((int) (point.getX() + (1 + SEGMENTS_SUM[3] + 0.5) * spacing) + 1, (int) (point.getY() + (yOffset + y - 0.5d) * spacing + 2), miniPadSize, miniPadSize);
|
||||||
g2d.drawRect((int) (point.getX() + (1 + SEGMENTS_SUM[3] + 0.5) * spacing) + miniPadSize + 3, (int) (point.getY() + (yOffset + y - 0.5d) * spacing + 2), miniPadSize, miniPadSize);
|
g2d.drawRect((int) (point.getX() + (1 + SEGMENTS_SUM[3] + 0.5) * spacing) + miniPadSize + 3, (int) (point.getY() + (yOffset + y - 0.5d) * spacing + 2), miniPadSize, miniPadSize);
|
||||||
|
|
||||||
|
drawingObserver.startDrawingSide(PCBLayer._1);
|
||||||
g2d.setColor(undersidePadColor);
|
g2d.setColor(undersidePadColor);
|
||||||
|
|
||||||
drawingObserver.startTrackingContinuityArea(true);
|
drawingObserver.startTrackingContinuityArea(true);
|
||||||
@@ -507,15 +511,18 @@ public class ProtoBoard extends AbstractProtoBoard {
|
|||||||
g2d.fillRect((int) (point.getX() + (1 + SEGMENTS_SUM[3] + 1.5) * spacing) + miniPadSize / 2 + 3, (int) (point.getY() + (yOffset + y + 0.5d) * spacing - 1 - miniPadSize), miniPadSize, miniPadSize);
|
g2d.fillRect((int) (point.getX() + (1 + SEGMENTS_SUM[3] + 1.5) * spacing) + miniPadSize / 2 + 3, (int) (point.getY() + (yOffset + y + 0.5d) * spacing - 1 - miniPadSize), miniPadSize, miniPadSize);
|
||||||
|
|
||||||
drawingObserver.stopTrackingContinuityArea();
|
drawingObserver.stopTrackingContinuityArea();
|
||||||
|
drawingObserver.stopDrawingSide();
|
||||||
|
|
||||||
g2d.setColor(undersidePadColor.darker());
|
g2d.setColor(undersidePadColor.darker());
|
||||||
|
|
||||||
g2d.drawRect((int) (point.getX() + (1 + SEGMENTS_SUM[3] + 1.5) * spacing) + 3, (int) (point.getY() + (yOffset + y - 0.5d) * spacing + 2), miniPadSize, miniPadSize);
|
g2d.drawRect((int) (point.getX() + (1 + SEGMENTS_SUM[3] + 1.5) * spacing) + 3, (int) (point.getY() + (yOffset + y - 0.5d) * spacing + 2), miniPadSize, miniPadSize);
|
||||||
g2d.drawRect((int) (point.getX() + (1 + SEGMENTS_SUM[3] + 1.5) * spacing) + miniPadSize + 5, (int) (point.getY() + (yOffset + y - 0.5d) * spacing + 2), miniPadSize, miniPadSize);
|
g2d.drawRect((int) (point.getX() + (1 + SEGMENTS_SUM[3] + 1.5) * spacing) + miniPadSize + 5, (int) (point.getY() + (yOffset + y - 0.5d) * spacing + 2), miniPadSize, miniPadSize);
|
||||||
|
|
||||||
|
drawingObserver.startDrawingSide(PCBLayer._1);
|
||||||
g2d.drawRect((int) (point.getX() + (1 + SEGMENTS_SUM[3] + 1.5) * spacing) - miniPadSize / 2, (int) (point.getY() + (yOffset + y + 0.5d) * spacing - 1 - miniPadSize), miniPadSize, miniPadSize);
|
g2d.drawRect((int) (point.getX() + (1 + SEGMENTS_SUM[3] + 1.5) * spacing) - miniPadSize / 2, (int) (point.getY() + (yOffset + y + 0.5d) * spacing - 1 - miniPadSize), miniPadSize, miniPadSize);
|
||||||
g2d.drawRect((int) (point.getX() + (1 + SEGMENTS_SUM[3] + 1.5) * spacing) - miniPadSize * 3 / 2 - 2, (int) (point.getY() + (yOffset + y + 0.5d) * spacing - 1 - miniPadSize), miniPadSize, miniPadSize);
|
g2d.drawRect((int) (point.getX() + (1 + SEGMENTS_SUM[3] + 1.5) * spacing) - miniPadSize * 3 / 2 - 2, (int) (point.getY() + (yOffset + y + 0.5d) * spacing - 1 - miniPadSize), miniPadSize, miniPadSize);
|
||||||
g2d.drawRect((int) (point.getX() + (1 + SEGMENTS_SUM[3] + 1.5) * spacing) + miniPadSize / 2 + 3, (int) (point.getY() + (yOffset + y + 0.5d) * spacing - 1 - miniPadSize), miniPadSize, miniPadSize);
|
g2d.drawRect((int) (point.getX() + (1 + SEGMENTS_SUM[3] + 1.5) * spacing) + miniPadSize / 2 + 3, (int) (point.getY() + (yOffset + y + 0.5d) * spacing - 1 - miniPadSize), miniPadSize, miniPadSize);
|
||||||
|
drawingObserver.stopDrawingSide();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -28,6 +28,7 @@ import java.awt.geom.Rectangle2D;
|
|||||||
import org.diylc.awt.StringUtils;
|
import org.diylc.awt.StringUtils;
|
||||||
import org.diylc.common.HorizontalAlignment;
|
import org.diylc.common.HorizontalAlignment;
|
||||||
import org.diylc.common.ObjectCache;
|
import org.diylc.common.ObjectCache;
|
||||||
|
import org.diylc.common.PCBLayer;
|
||||||
import org.diylc.common.VerticalAlignment;
|
import org.diylc.common.VerticalAlignment;
|
||||||
import org.diylc.components.transform.ProtoBoardTransformer;
|
import org.diylc.components.transform.ProtoBoardTransformer;
|
||||||
import org.diylc.core.ComponentState;
|
import org.diylc.core.ComponentState;
|
||||||
@@ -151,14 +152,17 @@ public class ProtoBoard780 extends AbstractProtoBoard {
|
|||||||
|
|
||||||
g2d.setColor(traceColor);
|
g2d.setColor(traceColor);
|
||||||
int padX = (int) Math.round(point.getX() + width / 2 - 7 * spacing + i * spacing);
|
int padX = (int) Math.round(point.getX() + width / 2 - 7 * spacing + i * spacing);
|
||||||
|
drawingObserver.startDrawingSide(PCBLayer._1);
|
||||||
drawingObserver.startTrackingContinuityArea(true);
|
drawingObserver.startTrackingContinuityArea(true);
|
||||||
g2d.drawLine(padX, (int) Math.round(point.getY() + offsetY), padX,
|
g2d.drawLine(padX, (int) Math.round(point.getY() + offsetY), padX,
|
||||||
(int) Math.round(point.getY() + spacing + offsetY));
|
(int) Math.round(point.getY() + spacing + offsetY));
|
||||||
drawingObserver.stopTrackingContinuityArea();
|
drawingObserver.stopTrackingContinuityArea();
|
||||||
|
drawingObserver.stopDrawingSide();
|
||||||
|
|
||||||
for (int j = 0; j < 2; j++) {
|
for (int j = 0; j < 2; j++) {
|
||||||
int padY = (int) Math.round(point.getY() + spacing * j + offsetY);
|
int padY = (int) Math.round(point.getY() + spacing * j + offsetY);
|
||||||
|
|
||||||
|
drawingObserver.startDrawingSide(PCBLayer._1);
|
||||||
g2d.setColor(padColor);
|
g2d.setColor(padColor);
|
||||||
|
|
||||||
drawingObserver.startTrackingContinuityArea(true);
|
drawingObserver.startTrackingContinuityArea(true);
|
||||||
@@ -171,6 +175,7 @@ public class ProtoBoard780 extends AbstractProtoBoard {
|
|||||||
g2d.setStroke(ObjectCache.getInstance().fetchBasicStroke(1f));
|
g2d.setStroke(ObjectCache.getInstance().fetchBasicStroke(1f));
|
||||||
|
|
||||||
g2d.drawOval(padX - padSize / 2, padY - padSize / 2, padSize, padSize);
|
g2d.drawOval(padX - padSize / 2, padY - padSize / 2, padSize, padSize);
|
||||||
|
drawingObserver.stopDrawingSide();
|
||||||
|
|
||||||
g2d.setColor(HOLE_COLOR);
|
g2d.setColor(HOLE_COLOR);
|
||||||
g2d.fillOval(padX - holeSize / 2, padY - holeSize / 2, holeSize, holeSize);
|
g2d.fillOval(padX - holeSize / 2, padY - holeSize / 2, holeSize, holeSize);
|
||||||
@@ -196,6 +201,7 @@ public class ProtoBoard780 extends AbstractProtoBoard {
|
|||||||
offsetY = spacing * 5.5;
|
offsetY = spacing * 5.5;
|
||||||
|
|
||||||
// draw 3-pad traces and vertical traces
|
// draw 3-pad traces and vertical traces
|
||||||
|
drawingObserver.startDrawingSide(PCBLayer._1);
|
||||||
drawingObserver.startTrackingContinuityArea(true);
|
drawingObserver.startTrackingContinuityArea(true);
|
||||||
g2d.setColor(traceColor);
|
g2d.setColor(traceColor);
|
||||||
g2d.setStroke(ObjectCache.getInstance().fetchBasicStroke(padSize - 2f));
|
g2d.setStroke(ObjectCache.getInstance().fetchBasicStroke(padSize - 2f));
|
||||||
@@ -239,6 +245,7 @@ public class ProtoBoard780 extends AbstractProtoBoard {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
drawingObserver.stopTrackingContinuityArea();
|
drawingObserver.stopTrackingContinuityArea();
|
||||||
|
drawingObserver.stopDrawingSide();
|
||||||
|
|
||||||
// draw main pads
|
// draw main pads
|
||||||
for (int i = 0; i < 25; i++) {
|
for (int i = 0; i < 25; i++) {
|
||||||
@@ -254,6 +261,7 @@ public class ProtoBoard780 extends AbstractProtoBoard {
|
|||||||
for (int j = 0; j < 30; j++) {
|
for (int j = 0; j < 30; j++) {
|
||||||
int padY = (int) Math.round(point.getY() + spacing * j + offsetY);
|
int padY = (int) Math.round(point.getY() + spacing * j + offsetY);
|
||||||
|
|
||||||
|
drawingObserver.startDrawingSide(PCBLayer._1);
|
||||||
g2d.setColor(padColor);
|
g2d.setColor(padColor);
|
||||||
|
|
||||||
drawingObserver.startTrackingContinuityArea(true);
|
drawingObserver.startTrackingContinuityArea(true);
|
||||||
@@ -266,6 +274,7 @@ public class ProtoBoard780 extends AbstractProtoBoard {
|
|||||||
g2d.setStroke(ObjectCache.getInstance().fetchBasicStroke(1f));
|
g2d.setStroke(ObjectCache.getInstance().fetchBasicStroke(1f));
|
||||||
|
|
||||||
g2d.drawOval(padX - padSize / 2, padY - padSize / 2, padSize, padSize);
|
g2d.drawOval(padX - padSize / 2, padY - padSize / 2, padSize, padSize);
|
||||||
|
drawingObserver.stopDrawingSide();
|
||||||
|
|
||||||
g2d.setColor(HOLE_COLOR);
|
g2d.setColor(HOLE_COLOR);
|
||||||
g2d.fillOval(padX - holeSize / 2, padY - holeSize / 2, holeSize, holeSize);
|
g2d.fillOval(padX - holeSize / 2, padY - holeSize / 2, holeSize, holeSize);
|
||||||
|
|||||||
@@ -28,6 +28,7 @@ import java.awt.Shape;
|
|||||||
import java.awt.geom.Point2D;
|
import java.awt.geom.Point2D;
|
||||||
|
|
||||||
import org.diylc.common.OrientationHV;
|
import org.diylc.common.OrientationHV;
|
||||||
|
import org.diylc.common.PCBLayer;
|
||||||
import org.diylc.components.transform.VeroBoardTransformer;
|
import org.diylc.components.transform.VeroBoardTransformer;
|
||||||
import org.diylc.core.ComponentState;
|
import org.diylc.core.ComponentState;
|
||||||
import org.diylc.core.IDIYComponent;
|
import org.diylc.core.IDIYComponent;
|
||||||
@@ -85,6 +86,7 @@ public class TriPadBoard extends AbstractVeroBoard implements ILayeredComponent
|
|||||||
spacesToDraw = (remainingSpace - stripSize) / spacing;
|
spacesToDraw = (remainingSpace - stripSize) / spacing;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
drawingObserver.startDrawingSide(PCBLayer._1);
|
||||||
g2d.setColor(stripColor);
|
g2d.setColor(stripColor);
|
||||||
drawingObserver.startTrackingContinuityArea(true);
|
drawingObserver.startTrackingContinuityArea(true);
|
||||||
g2d.fillRect((int)(x + spacing - stripSize / 2), (int)(y - stripSize / 2), (int)(spacing * (spacesToDraw - 1) + stripSize),
|
g2d.fillRect((int)(x + spacing - stripSize / 2), (int)(y - stripSize / 2), (int)(spacing * (spacesToDraw - 1) + stripSize),
|
||||||
@@ -94,6 +96,7 @@ public class TriPadBoard extends AbstractVeroBoard implements ILayeredComponent
|
|||||||
|
|
||||||
g2d.drawRect((int)(x + spacing - stripSize / 2), (int)(y - stripSize / 2), (int)(spacing * (spacesToDraw - 1) + stripSize),
|
g2d.drawRect((int)(x + spacing - stripSize / 2), (int)(y - stripSize / 2), (int)(spacing * (spacesToDraw - 1) + stripSize),
|
||||||
stripSize);
|
stripSize);
|
||||||
|
drawingObserver.stopDrawingSide();
|
||||||
|
|
||||||
x += spacing * spacesToDraw;
|
x += spacing * spacesToDraw;
|
||||||
}
|
}
|
||||||
@@ -123,6 +126,7 @@ public class TriPadBoard extends AbstractVeroBoard implements ILayeredComponent
|
|||||||
spacesToDraw = (remainingSpace - stripSize) / spacing;
|
spacesToDraw = (remainingSpace - stripSize) / spacing;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
drawingObserver.startDrawingSide(PCBLayer._1);
|
||||||
g2d.setColor(stripColor);
|
g2d.setColor(stripColor);
|
||||||
drawingObserver.startTrackingContinuityArea(true);
|
drawingObserver.startTrackingContinuityArea(true);
|
||||||
g2d.fillRect((int)(x - stripSize / 2), (int)(y + spacing - stripSize / 2), stripSize, (int)(spacing * (spacesToDraw - 1)
|
g2d.fillRect((int)(x - stripSize / 2), (int)(y + spacing - stripSize / 2), stripSize, (int)(spacing * (spacesToDraw - 1)
|
||||||
@@ -131,6 +135,7 @@ public class TriPadBoard extends AbstractVeroBoard implements ILayeredComponent
|
|||||||
g2d.setColor(stripColor.darker());
|
g2d.setColor(stripColor.darker());
|
||||||
g2d.drawRect((int)(x - stripSize / 2), (int)(y + spacing - stripSize / 2), stripSize, (int)(spacing * (spacesToDraw - 1)
|
g2d.drawRect((int)(x - stripSize / 2), (int)(y + spacing - stripSize / 2), stripSize, (int)(spacing * (spacesToDraw - 1)
|
||||||
+ stripSize));
|
+ stripSize));
|
||||||
|
drawingObserver.stopDrawingSide();
|
||||||
|
|
||||||
y += spacing * spacesToDraw;
|
y += spacing * spacesToDraw;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -28,6 +28,7 @@ import java.awt.Shape;
|
|||||||
import java.awt.geom.Point2D;
|
import java.awt.geom.Point2D;
|
||||||
|
|
||||||
import org.diylc.common.OrientationHV;
|
import org.diylc.common.OrientationHV;
|
||||||
|
import org.diylc.common.PCBLayer;
|
||||||
import org.diylc.components.transform.VeroBoardTransformer;
|
import org.diylc.components.transform.VeroBoardTransformer;
|
||||||
import org.diylc.core.ComponentState;
|
import org.diylc.core.ComponentState;
|
||||||
import org.diylc.core.IDIYComponent;
|
import org.diylc.core.IDIYComponent;
|
||||||
@@ -74,12 +75,14 @@ public class VeroBoard extends AbstractVeroBoard implements ILayeredComponent {
|
|||||||
while (y < finalSecondPoint.getY() - spacing - stripSize / 2) {
|
while (y < finalSecondPoint.getY() - spacing - stripSize / 2) {
|
||||||
x = firstPoint.getX();
|
x = firstPoint.getX();
|
||||||
y += spacing;
|
y += spacing;
|
||||||
|
drawingObserver.startDrawingSide(PCBLayer._1);
|
||||||
g2d.setColor(stripColor);
|
g2d.setColor(stripColor);
|
||||||
drawingObserver.startTrackingContinuityArea(true);
|
drawingObserver.startTrackingContinuityArea(true);
|
||||||
g2d.fillRect((int)(x + spacing / 2), (int)(y - stripSize / 2), (int) Math.round(finalSecondPoint.getX() - spacing - x), stripSize);
|
g2d.fillRect((int)(x + spacing / 2), (int)(y - stripSize / 2), (int) Math.round(finalSecondPoint.getX() - spacing - x), stripSize);
|
||||||
drawingObserver.stopTrackingContinuityArea();
|
drawingObserver.stopTrackingContinuityArea();
|
||||||
g2d.setColor(stripColor.darker());
|
g2d.setColor(stripColor.darker());
|
||||||
g2d.drawRect((int)(x + spacing / 2), (int)(y - stripSize / 2), (int) Math.round(finalSecondPoint.getX() - spacing - x), stripSize);
|
g2d.drawRect((int)(x + spacing / 2), (int)(y - stripSize / 2), (int) Math.round(finalSecondPoint.getX() - spacing - x), stripSize);
|
||||||
|
drawingObserver.stopDrawingSide();
|
||||||
while (x < finalSecondPoint.getX() - spacing - holeSize) {
|
while (x < finalSecondPoint.getX() - spacing - holeSize) {
|
||||||
x += spacing;
|
x += spacing;
|
||||||
g2d.setColor(Constants.CANVAS_COLOR);
|
g2d.setColor(Constants.CANVAS_COLOR);
|
||||||
@@ -92,12 +95,14 @@ public class VeroBoard extends AbstractVeroBoard implements ILayeredComponent {
|
|||||||
while (x < finalSecondPoint.getX() - spacing) {
|
while (x < finalSecondPoint.getX() - spacing) {
|
||||||
x += spacing;
|
x += spacing;
|
||||||
y = firstPoint.getY();
|
y = firstPoint.getY();
|
||||||
|
drawingObserver.startDrawingSide(PCBLayer._1);
|
||||||
g2d.setColor(stripColor);
|
g2d.setColor(stripColor);
|
||||||
drawingObserver.startTrackingContinuityArea(true);
|
drawingObserver.startTrackingContinuityArea(true);
|
||||||
g2d.fillRect((int)(x - stripSize / 2), (int)(y + spacing / 2), stripSize, (int) Math.round(finalSecondPoint.getY() - spacing - y));
|
g2d.fillRect((int)(x - stripSize / 2), (int)(y + spacing / 2), stripSize, (int) Math.round(finalSecondPoint.getY() - spacing - y));
|
||||||
drawingObserver.stopTrackingContinuityArea();
|
drawingObserver.stopTrackingContinuityArea();
|
||||||
g2d.setColor(stripColor.darker());
|
g2d.setColor(stripColor.darker());
|
||||||
g2d.drawRect((int)(x - stripSize / 2), (int)(y + spacing / 2), stripSize, (int) Math.round(finalSecondPoint.getY() - spacing - y));
|
g2d.drawRect((int)(x - stripSize / 2), (int)(y + spacing / 2), stripSize, (int) Math.round(finalSecondPoint.getY() - spacing - y));
|
||||||
|
drawingObserver.stopDrawingSide();
|
||||||
while (y < finalSecondPoint.getY() - spacing - holeSize) {
|
while (y < finalSecondPoint.getY() - spacing - holeSize) {
|
||||||
y += spacing;
|
y += spacing;
|
||||||
g2d.setColor(Constants.CANVAS_COLOR);
|
g2d.setColor(Constants.CANVAS_COLOR);
|
||||||
|
|||||||
@@ -34,7 +34,9 @@ import org.diylc.core.ComponentState;
|
|||||||
import org.diylc.core.CreationMethod;
|
import org.diylc.core.CreationMethod;
|
||||||
import org.diylc.core.IConnective;
|
import org.diylc.core.IConnective;
|
||||||
import org.diylc.core.IDIYComponent;
|
import org.diylc.core.IDIYComponent;
|
||||||
|
import org.diylc.core.IDrawingObserver;
|
||||||
import org.diylc.core.ILayeredComponent;
|
import org.diylc.core.ILayeredComponent;
|
||||||
|
import org.diylc.core.Project;
|
||||||
import org.diylc.core.VisibilityPolicy;
|
import org.diylc.core.VisibilityPolicy;
|
||||||
import org.diylc.core.annotations.AiNote.Scope;
|
import org.diylc.core.annotations.AiNote.Scope;
|
||||||
import org.diylc.core.annotations.AiNote;
|
import org.diylc.core.annotations.AiNote;
|
||||||
@@ -72,6 +74,14 @@ public class CopperTrace extends AbstractLeadedComponent<Void> implements IConne
|
|||||||
this.leadColor = COLOR;
|
this.leadColor = COLOR;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void draw(Graphics2D g2d, ComponentState componentState, boolean outlineMode,
|
||||||
|
Project project, IDrawingObserver drawingObserver) {
|
||||||
|
drawingObserver.startDrawingSide(getLayer());
|
||||||
|
super.draw(g2d, componentState, outlineMode, project, drawingObserver);
|
||||||
|
drawingObserver.stopDrawingSide();
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void drawIcon(Graphics2D g2d, int width, int height) {
|
public void drawIcon(Graphics2D g2d, int width, int height) {
|
||||||
g2d.setStroke(ObjectCache.getInstance().fetchBasicStroke(3));
|
g2d.setStroke(ObjectCache.getInstance().fetchBasicStroke(3));
|
||||||
|
|||||||
@@ -87,9 +87,11 @@ public class CurvedTrace extends AbstractCurvedComponent<Void> implements IConne
|
|||||||
componentState == ComponentState.SELECTED || componentState == ComponentState.DRAGGING ? SELECTION_COLOR
|
componentState == ComponentState.SELECTED || componentState == ComponentState.DRAGGING ? SELECTION_COLOR
|
||||||
: color;
|
: color;
|
||||||
g2d.setColor(curveColor);
|
g2d.setColor(curveColor);
|
||||||
|
drawingObserver.startDrawingSide(getLayer());
|
||||||
drawingObserver.startTrackingContinuityArea(true);
|
drawingObserver.startTrackingContinuityArea(true);
|
||||||
g2d.draw(curve);
|
g2d.draw(curve);
|
||||||
drawingObserver.stopTrackingContinuityArea();
|
drawingObserver.stopTrackingContinuityArea();
|
||||||
|
drawingObserver.stopDrawingSide();
|
||||||
}
|
}
|
||||||
|
|
||||||
@EditableProperty(name = "Width")
|
@EditableProperty(name = "Width")
|
||||||
|
|||||||
@@ -83,11 +83,13 @@ public class GroundFill extends AbstractTransparentComponent<Void>
|
|||||||
xPoints[i] = (int)controlPoints[i].getX();
|
xPoints[i] = (int)controlPoints[i].getX();
|
||||||
yPoints[i] = (int)controlPoints[i].getY();
|
yPoints[i] = (int)controlPoints[i].getY();
|
||||||
}
|
}
|
||||||
|
drawingObserver.startDrawingSide(getLayer());
|
||||||
drawingObserver.startTrackingContinuityArea(true);
|
drawingObserver.startTrackingContinuityArea(true);
|
||||||
Composite oldComposite = applyAlpha(g2d, componentState);
|
Composite oldComposite = applyAlpha(g2d, componentState);
|
||||||
g2d.fillPolygon(xPoints, yPoints, controlPoints.length);
|
g2d.fillPolygon(xPoints, yPoints, controlPoints.length);
|
||||||
g2d.setComposite(oldComposite);
|
g2d.setComposite(oldComposite);
|
||||||
drawingObserver.stopTrackingContinuityArea();
|
drawingObserver.stopTrackingContinuityArea();
|
||||||
|
drawingObserver.stopDrawingSide();
|
||||||
// Do not track any changes that follow because the whole board has been
|
// Do not track any changes that follow because the whole board has been
|
||||||
// tracked so far.
|
// tracked so far.
|
||||||
drawingObserver.stopTracking();
|
drawingObserver.stopTracking();
|
||||||
|
|||||||
@@ -97,6 +97,7 @@ public class SolderPad extends AbstractTransparentComponent<Void>
|
|||||||
gerberDrawingObserver.startGerberOutput(org.diylc.core.gerber.GerberLayer.SolderMaskBot, "Material", false);
|
gerberDrawingObserver.startGerberOutput(org.diylc.core.gerber.GerberLayer.SolderMaskBot, "Material", false);
|
||||||
}
|
}
|
||||||
Composite oldComposite = applyAlpha(g2d, componentState);
|
Composite oldComposite = applyAlpha(g2d, componentState);
|
||||||
|
drawingObserver.startDrawingSide(getLayer());
|
||||||
|
|
||||||
if (type == Type.ROUND) {
|
if (type == Type.ROUND) {
|
||||||
g2d.fill(new Ellipse2D.Double(point.getX() - diameter / 2, point.getY() - diameter / 2,
|
g2d.fill(new Ellipse2D.Double(point.getX() - diameter / 2, point.getY() - diameter / 2,
|
||||||
@@ -111,6 +112,7 @@ public class SolderPad extends AbstractTransparentComponent<Void>
|
|||||||
g2d.fill(new Rectangle2D.Double(point.getX() - diameter / 2, point.getY() - diameter / 2,
|
g2d.fill(new Rectangle2D.Double(point.getX() - diameter / 2, point.getY() - diameter / 2,
|
||||||
diameter, diameter));
|
diameter, diameter));
|
||||||
}
|
}
|
||||||
|
drawingObserver.stopDrawingSide();
|
||||||
drawingObserver.stopTracking();
|
drawingObserver.stopTracking();
|
||||||
drawingObserver.stopTrackingContinuityArea();
|
drawingObserver.stopTrackingContinuityArea();
|
||||||
if (gerberDrawingObserver != null)
|
if (gerberDrawingObserver != null)
|
||||||
@@ -121,9 +123,11 @@ public class SolderPad extends AbstractTransparentComponent<Void>
|
|||||||
GerberLayer holeLayer = HoleType.PTH.equals(getHoleType()) ? GerberLayer.DrillPlated : GerberLayer.DrillNonPlated;
|
GerberLayer holeLayer = HoleType.PTH.equals(getHoleType()) ? GerberLayer.DrillPlated : GerberLayer.DrillNonPlated;
|
||||||
gerberDrawingObserver.startGerberOutput(holeLayer, GerberFunctions.COMPONENT_DRILL, false);
|
gerberDrawingObserver.startGerberOutput(holeLayer, GerberFunctions.COMPONENT_DRILL, false);
|
||||||
}
|
}
|
||||||
|
drawingObserver.startDrawingBothSides();
|
||||||
g2d.setColor(Constants.CANVAS_COLOR);
|
g2d.setColor(Constants.CANVAS_COLOR);
|
||||||
g2d.fill(new Ellipse2D.Double(point.getX() - holeDiameter / 2,
|
g2d.fill(new Ellipse2D.Double(point.getX() - holeDiameter / 2,
|
||||||
point.getY() - holeDiameter / 2, holeDiameter, holeDiameter));
|
point.getY() - holeDiameter / 2, holeDiameter, holeDiameter));
|
||||||
|
drawingObserver.stopDrawingSide();
|
||||||
}
|
}
|
||||||
g2d.setComposite(oldComposite);
|
g2d.setComposite(oldComposite);
|
||||||
if (gerberDrawingObserver != null)
|
if (gerberDrawingObserver != null)
|
||||||
|
|||||||
@@ -83,6 +83,8 @@ public class TraceCut extends AbstractTransparentComponent<Void> implements ILay
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
Composite oldComposite = applyAlpha(g2d, componentState);
|
Composite oldComposite = applyAlpha(g2d, componentState);
|
||||||
|
// the cut is what tells the user a strip is broken, so it stays crisp from either side
|
||||||
|
drawingObserver.startDrawingBothSides();
|
||||||
g2d.setStroke(ObjectCache.getInstance().fetchZoomableStroke(1f));
|
g2d.setStroke(ObjectCache.getInstance().fetchZoomableStroke(1f));
|
||||||
int size = getClosestOdd((int) this.size.convertToPixels());
|
int size = getClosestOdd((int) this.size.convertToPixels());
|
||||||
int cutWidth = getClosestOdd((int) CUT_WIDTH.convertToPixels());
|
int cutWidth = getClosestOdd((int) CUT_WIDTH.convertToPixels());
|
||||||
@@ -109,6 +111,7 @@ public class TraceCut extends AbstractTransparentComponent<Void> implements ILay
|
|||||||
g2d.setColor(getBoardColor().darker());
|
g2d.setColor(getBoardColor().darker());
|
||||||
g2d.drawOval((int) Math.round(point.getX() - holeSize / 2), (int) Math.round(point.getY() - holeSize / 2), holeSize, holeSize);
|
g2d.drawOval((int) Math.round(point.getX() - holeSize / 2), (int) Math.round(point.getY() - holeSize / 2), holeSize, holeSize);
|
||||||
}
|
}
|
||||||
|
drawingObserver.stopDrawingSide();
|
||||||
g2d.setComposite(oldComposite);
|
g2d.setComposite(oldComposite);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -156,7 +156,9 @@ public class PCBText extends AbstractComponent<Void> implements ILayeredComponen
|
|||||||
@Override
|
@Override
|
||||||
public void draw(Graphics2D g2d, ComponentState componentState, boolean outlineMode, Project project,
|
public void draw(Graphics2D g2d, ComponentState componentState, boolean outlineMode, Project project,
|
||||||
IDrawingObserver drawingObserver) {
|
IDrawingObserver drawingObserver) {
|
||||||
|
drawingObserver.startDrawingSide(getLayer());
|
||||||
this.draw(g2d, componentState, outlineMode, project, drawingObserver, null);
|
this.draw(g2d, componentState, outlineMode, project, drawingObserver, null);
|
||||||
|
drawingObserver.stopDrawingSide();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|||||||
+57
@@ -0,0 +1,57 @@
|
|||||||
|
/*
|
||||||
|
|
||||||
|
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 com.thoughtworks.xstream.io.xml.DomDriver;
|
||||||
|
|
||||||
|
import org.diylc.common.PCBLayer;
|
||||||
|
import org.diylc.components.connectivity.CopperTrace;
|
||||||
|
import org.junit.Assert;
|
||||||
|
import org.junit.Test;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The inner PCB layers are deprecated but must keep loading from files that use them, with their
|
||||||
|
* layer and continuity id unchanged, because XStream resolves enum constants by name.
|
||||||
|
*/
|
||||||
|
public class InnerLayerCompatibilityTest {
|
||||||
|
|
||||||
|
private static XStream createXStream() {
|
||||||
|
XStream xStream = new XStream(new DomDriver("UTF-8"));
|
||||||
|
ProjectFileManager.configure(xStream);
|
||||||
|
return xStream;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@SuppressWarnings("deprecation")
|
||||||
|
public void innerLayerTraceLoadsUnchanged() {
|
||||||
|
CopperTrace trace = new CopperTrace();
|
||||||
|
trace.setLayer(PCBLayer._2);
|
||||||
|
String xml = createXStream().toXML(trace);
|
||||||
|
Assert.assertTrue(xml.contains("<layer>_2</layer>"));
|
||||||
|
|
||||||
|
CopperTrace loaded = (CopperTrace) createXStream().fromXML(xml.replace("<layer>_2</layer>", "<layer>_5</layer>"));
|
||||||
|
Assert.assertEquals(PCBLayer._5, loaded.getLayer());
|
||||||
|
Assert.assertEquals(5, loaded.getLayerId());
|
||||||
|
Assert.assertTrue(createXStream().toXML(loaded).contains("<layer>_5</layer>"));
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -25,6 +25,8 @@ import java.awt.Color;
|
|||||||
import java.awt.Component;
|
import java.awt.Component;
|
||||||
import java.awt.event.ItemEvent;
|
import java.awt.event.ItemEvent;
|
||||||
import java.awt.event.ItemListener;
|
import java.awt.event.ItemListener;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
import javax.swing.DefaultComboBoxModel;
|
import javax.swing.DefaultComboBoxModel;
|
||||||
import javax.swing.DefaultListCellRenderer;
|
import javax.swing.DefaultListCellRenderer;
|
||||||
import javax.swing.JComboBox;
|
import javax.swing.JComboBox;
|
||||||
@@ -44,7 +46,7 @@ public class EnumEditor extends JComboBox<Object> {
|
|||||||
|
|
||||||
public EnumEditor(final PropertyWrapper property) {
|
public EnumEditor(final PropertyWrapper property) {
|
||||||
this.property = property;
|
this.property = property;
|
||||||
Object[] values = property.getType().getEnumConstants();
|
Object[] values = getOfferedValues(property);
|
||||||
if (property.isReadOnly())
|
if (property.isReadOnly())
|
||||||
setEnabled(false);
|
setEnabled(false);
|
||||||
setModel(new DefaultComboBoxModel<Object>(values));
|
setModel(new DefaultComboBoxModel<Object>(values));
|
||||||
@@ -76,4 +78,24 @@ public class EnumEditor extends JComboBox<Object> {
|
|||||||
setBackground(Constants.MULTI_VALUE_COLOR);
|
setBackground(Constants.MULTI_VALUE_COLOR);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// deprecated constants stay loadable from old files but are not offered, unless one is the
|
||||||
|
// current value, which must stay visible rather than be silently replaced
|
||||||
|
private static Object[] getOfferedValues(PropertyWrapper property) {
|
||||||
|
List<Object> values = new ArrayList<Object>();
|
||||||
|
for (Object value : property.getType().getEnumConstants()) {
|
||||||
|
if (value.equals(property.getValue()) || !isDeprecated((Enum<?>) value)) {
|
||||||
|
values.add(value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return values.toArray();
|
||||||
|
}
|
||||||
|
|
||||||
|
private static boolean isDeprecated(Enum<?> value) {
|
||||||
|
try {
|
||||||
|
return value.getDeclaringClass().getField(value.name()).isAnnotationPresent(Deprecated.class);
|
||||||
|
} catch (NoSuchFieldException e) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -372,6 +372,9 @@ public class CanvasPanel extends JComponent implements Autoscroll {
|
|||||||
if (configManager.readBoolean(IPlugInPort.LOCKED_ALPHA, true)) {
|
if (configManager.readBoolean(IPlugInPort.LOCKED_ALPHA, true)) {
|
||||||
drawOptions.add(DrawOption.LOCKED_ALPHA);
|
drawOptions.add(DrawOption.LOCKED_ALPHA);
|
||||||
}
|
}
|
||||||
|
if (configManager.readBoolean(IPlugInPort.FADE_FAR_SIDE_KEY, true)) {
|
||||||
|
drawOptions.add(DrawOption.FADE_FAR_SIDE);
|
||||||
|
}
|
||||||
return drawOptions;
|
return drawOptions;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -113,6 +113,9 @@ public class ConfigPlugin implements IPlugIn {
|
|||||||
swingUI.injectMenuAction(
|
swingUI.injectMenuAction(
|
||||||
ActionFactory.getInstance().createConfigAction(plugInPort, "Extra Working Area", IPlugInPort.EXTRA_SPACE_KEY, true),
|
ActionFactory.getInstance().createConfigAction(plugInPort, "Extra Working Area", IPlugInPort.EXTRA_SPACE_KEY, true),
|
||||||
CONFIG_MENU);
|
CONFIG_MENU);
|
||||||
|
swingUI.injectMenuAction(
|
||||||
|
ActionFactory.getInstance().createConfigAction(plugInPort, "Fade Copper Under Board",
|
||||||
|
IPlugInPort.FADE_FAR_SIDE_KEY, true), CONFIG_MENU);
|
||||||
// swingUI.injectMenuAction(
|
// swingUI.injectMenuAction(
|
||||||
// ActionFactory.getInstance().createConfigAction(plugInPort, "Hardware Acceleration", IPlugInPort.HARDWARE_ACCELERATION, false),
|
// ActionFactory.getInstance().createConfigAction(plugInPort, "Hardware Acceleration", IPlugInPort.HARDWARE_ACCELERATION, false),
|
||||||
// CONFIG_MENU);
|
// CONFIG_MENU);
|
||||||
|
|||||||
+3
@@ -69,6 +69,9 @@ public class ProjectDrawingProvider implements IDrawingProvider {
|
|||||||
if (ConfigurationManager.getInstance().readBoolean(IPlugInPort.OUTLINE_KEY, false)) {
|
if (ConfigurationManager.getInstance().readBoolean(IPlugInPort.OUTLINE_KEY, false)) {
|
||||||
drawOptions.add(DrawOption.OUTLINE_MODE);
|
drawOptions.add(DrawOption.OUTLINE_MODE);
|
||||||
}
|
}
|
||||||
|
if (ConfigurationManager.getInstance().readBoolean(IPlugInPort.FADE_FAR_SIDE_KEY, true)) {
|
||||||
|
drawOptions.add(DrawOption.FADE_FAR_SIDE);
|
||||||
|
}
|
||||||
if (includeExtraSpace) {
|
if (includeExtraSpace) {
|
||||||
drawOptions.add(DrawOption.EXTRA_SPACE);
|
drawOptions.add(DrawOption.EXTRA_SPACE);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -732,3 +732,4 @@ Remove default model|Standardmodell entfernen
|
|||||||
Value Notation|Wertnotation
|
Value Notation|Wertnotation
|
||||||
Decimal (4.7K, 0.22uF, 6.3V)|Dezimal (4.7K, 0.22uF, 6.3V)
|
Decimal (4.7K, 0.22uF, 6.3V)|Dezimal (4.7K, 0.22uF, 6.3V)
|
||||||
RKM (4K7, 0u22, 6V3)|RKM (4K7, 0u22, 6V3)
|
RKM (4K7, 0u22, 6V3)|RKM (4K7, 0u22, 6V3)
|
||||||
|
Fade Copper Under Board|Kupfer unter der Platine abblenden
|
||||||
|
|||||||
@@ -725,3 +725,4 @@ Remove default model
|
|||||||
Value Notation
|
Value Notation
|
||||||
Decimal (4.7K, 0.22uF, 6.3V)
|
Decimal (4.7K, 0.22uF, 6.3V)
|
||||||
RKM (4K7, 0u22, 6V3)
|
RKM (4K7, 0u22, 6V3)
|
||||||
|
Fade Copper Under Board
|
||||||
|
|||||||
@@ -728,3 +728,4 @@ Remove default model|Quitar modelo predeterminado
|
|||||||
Value Notation|Notación de valores
|
Value Notation|Notación de valores
|
||||||
Decimal (4.7K, 0.22uF, 6.3V)|Decimal (4.7K, 0.22uF, 6.3V)
|
Decimal (4.7K, 0.22uF, 6.3V)|Decimal (4.7K, 0.22uF, 6.3V)
|
||||||
RKM (4K7, 0u22, 6V3)|RKM (4K7, 0u22, 6V3)
|
RKM (4K7, 0u22, 6V3)|RKM (4K7, 0u22, 6V3)
|
||||||
|
Fade Copper Under Board|Atenuar cobre bajo la placa
|
||||||
|
|||||||
@@ -727,3 +727,4 @@ Remove default model|Retirer le modèle par défaut
|
|||||||
Value Notation|Notation des valeurs
|
Value Notation|Notation des valeurs
|
||||||
Decimal (4.7K, 0.22uF, 6.3V)|Décimale (4.7K, 0.22uF, 6.3V)
|
Decimal (4.7K, 0.22uF, 6.3V)|Décimale (4.7K, 0.22uF, 6.3V)
|
||||||
RKM (4K7, 0u22, 6V3)|RKM (4K7, 0u22, 6V3)
|
RKM (4K7, 0u22, 6V3)|RKM (4K7, 0u22, 6V3)
|
||||||
|
Fade Copper Under Board|Estomper le cuivre sous la carte
|
||||||
|
|||||||
@@ -729,3 +729,4 @@ Remove default model|Rimuovi modello predefinito
|
|||||||
Value Notation|Notazione dei valori
|
Value Notation|Notazione dei valori
|
||||||
Decimal (4.7K, 0.22uF, 6.3V)|Decimale (4.7K, 0.22uF, 6.3V)
|
Decimal (4.7K, 0.22uF, 6.3V)|Decimale (4.7K, 0.22uF, 6.3V)
|
||||||
RKM (4K7, 0u22, 6V3)|RKM (4K7, 0u22, 6V3)
|
RKM (4K7, 0u22, 6V3)|RKM (4K7, 0u22, 6V3)
|
||||||
|
Fade Copper Under Board|Attenua il rame sotto la scheda
|
||||||
|
|||||||
@@ -733,3 +733,4 @@ Remove default model|Standaardmodel verwijderen
|
|||||||
Value Notation|Waardenotatie
|
Value Notation|Waardenotatie
|
||||||
Decimal (4.7K, 0.22uF, 6.3V)|Decimaal (4.7K, 0.22uF, 6.3V)
|
Decimal (4.7K, 0.22uF, 6.3V)|Decimaal (4.7K, 0.22uF, 6.3V)
|
||||||
RKM (4K7, 0u22, 6V3)|RKM (4K7, 0u22, 6V3)
|
RKM (4K7, 0u22, 6V3)|RKM (4K7, 0u22, 6V3)
|
||||||
|
Fade Copper Under Board|Koper onder de print vervagen
|
||||||
|
|||||||
@@ -728,3 +728,4 @@ Remove default model|Remover modelo padrão
|
|||||||
Value Notation|Notação de valores
|
Value Notation|Notação de valores
|
||||||
Decimal (4.7K, 0.22uF, 6.3V)|Decimal (4.7K, 0.22uF, 6.3V)
|
Decimal (4.7K, 0.22uF, 6.3V)|Decimal (4.7K, 0.22uF, 6.3V)
|
||||||
RKM (4K7, 0u22, 6V3)|RKM (4K7, 0u22, 6V3)
|
RKM (4K7, 0u22, 6V3)|RKM (4K7, 0u22, 6V3)
|
||||||
|
Fade Copper Under Board|Esmaecer cobre sob a placa
|
||||||
|
|||||||
@@ -724,3 +724,4 @@ Remove default model|Ukloni podrazumevani model
|
|||||||
Value Notation|Zapis vrednosti
|
Value Notation|Zapis vrednosti
|
||||||
Decimal (4.7K, 0.22uF, 6.3V)|Decimalni (4.7K, 0.22uF, 6.3V)
|
Decimal (4.7K, 0.22uF, 6.3V)|Decimalni (4.7K, 0.22uF, 6.3V)
|
||||||
RKM (4K7, 0u22, 6V3)|RKM (4K7, 0u22, 6V3)
|
RKM (4K7, 0u22, 6V3)|RKM (4K7, 0u22, 6V3)
|
||||||
|
Fade Copper Under Board|Prigušen Bakar Ispod Ploče
|
||||||
|
|||||||
@@ -726,3 +726,4 @@ Remove default model|डिफ़ॉल्ट मॉडल हटाएँ
|
|||||||
Value Notation|मान संकेतन
|
Value Notation|मान संकेतन
|
||||||
Decimal (4.7K, 0.22uF, 6.3V)|दशमलव (4.7K, 0.22uF, 6.3V)
|
Decimal (4.7K, 0.22uF, 6.3V)|दशमलव (4.7K, 0.22uF, 6.3V)
|
||||||
RKM (4K7, 0u22, 6V3)|RKM (4K7, 0u22, 6V3)
|
RKM (4K7, 0u22, 6V3)|RKM (4K7, 0u22, 6V3)
|
||||||
|
Fade Copper Under Board|बोर्ड के नीचे तांबा धुंधला करें
|
||||||
|
|||||||
@@ -726,3 +726,4 @@ Remove default model|取消默认型号
|
|||||||
Value Notation|数值表示法
|
Value Notation|数值表示法
|
||||||
Decimal (4.7K, 0.22uF, 6.3V)|小数 (4.7K, 0.22uF, 6.3V)
|
Decimal (4.7K, 0.22uF, 6.3V)|小数 (4.7K, 0.22uF, 6.3V)
|
||||||
RKM (4K7, 0u22, 6V3)|RKM (4K7, 0u22, 6V3)
|
RKM (4K7, 0u22, 6V3)|RKM (4K7, 0u22, 6V3)
|
||||||
|
Fade Copper Under Board|淡化板下铜箔
|
||||||
|
|||||||
@@ -7,9 +7,13 @@ Status: not started.
|
|||||||
|
|
||||||
## 1. Current state
|
## 1. Current state
|
||||||
|
|
||||||
- Traces, pads, PCB text and ground fills carry a `PCBLayer` (`Bottom`, `Top`, `Inner 1..6`).
|
- Traces, pads, PCB text and ground fills carry a `PCBLayer`: `Bottom` and `Top`, plus the
|
||||||
It feeds `PCBLayer.toGerberCopperLayer()`, `PCBLayerFilter`, the trace-mask print and
|
deprecated `Inner 1..6`, kept only so that old files load and hidden in the editor. It feeds
|
||||||
continuity (`ILayeredComponent.getLayerId()`).
|
`PCBLayer.toGerberCopperLayer()`, `PCBLayerFilter`, the trace-mask print and continuity
|
||||||
|
(`ILayeredComponent.getLayerId()`).
|
||||||
|
- `IDrawingObserver.startDrawingSide(PCBLayer)` / `stopDrawingSide()` already exist
|
||||||
|
(`copper-side-view.md`): paint on the side facing away from the viewer is drawn faded, in both
|
||||||
|
the main view and the Underside Display mirror.
|
||||||
- Every other Gerber-aware component implements `IGerberComponentSimple`, whose
|
- Every other Gerber-aware component implements `IGerberComponentSimple`, whose
|
||||||
`getGerberLayer()` defaults to `SilkscreenTop`. About 35 classes in `diylc-library` rely on that
|
`getGerberLayer()` defaults to `SilkscreenTop`. About 35 classes in `diylc-library` rely on that
|
||||||
default (resistors, film and radial capacitors, diodes, LEDs, transistor packages, DIL/SIL ICs,
|
default (resistors, film and radial capacitors, diodes, LEDs, transistor packages, DIL/SIL ICs,
|
||||||
@@ -25,45 +29,44 @@ Status: not started.
|
|||||||
|
|
||||||
| Decision | Rationale |
|
| Decision | Rationale |
|
||||||
| --- | --- |
|
| --- | --- |
|
||||||
| A new two-value `BoardSide` (`Top`, `Bottom`), not a reuse of `PCBLayer` | Parts mount on the outer faces only. Inner layers would be invalid choices in the editor. |
|
| The side is a `PCBLayer` (`_2` Top, `_1` Bottom). No separate side enum | DIYLC only models two-sided boards. The inner layers are deprecated and hidden in the editor (`copper-side-view.md`), so only the two valid choices are offered. |
|
||||||
|
| A part's side goes through its own `IBoardSideComponent`, never through `ILayeredComponent` | `getLayerId()` drives continuity, and a through-hole part's leads must connect to copper on both sides. Reporting a resistor as layer `_1` there would cut it off from top copper. |
|
||||||
| The side is the physical fact. Z-order stays paint order and is not tied to it | Boards are drawn opaque, so painting bottom parts beneath the board would hide them. Bottom-layer traces already follow this rule. |
|
| The side is the physical fact. Z-order stays paint order and is not tied to it | Boards are drawn opaque, so painting bottom parts beneath the board would hide them. Bottom-layer traces already follow this rule. |
|
||||||
| The canvas shows the side visually and does not rely on z-order for it | That is what removes the ambiguity of a "bottom" part drawn above the board. |
|
| The canvas shows the side visually and does not rely on z-order for it | That is what removes the ambiguity of a "bottom" part drawn above the board. |
|
||||||
| Flipping a part is an action that sets the side, mirrors the part and sends it back | A part seen from above through the board is a mirror image. Changing the side without mirroring gives a footprint that is wrong for fabrication. |
|
| Flipping a part is an action that sets the side, mirrors the part and sends it back | A part seen from above through the board is a mirror image. Changing the side without mirroring gives a footprint that is wrong for fabrication. |
|
||||||
| The Gerber layer is swapped centrally, not inside each component | Components keep emitting their `*Top` layer. One place maps it to the other side. |
|
| The Gerber layer is swapped centrally, not inside each component | Components keep emitting their `*Top` layer. One place maps it to the other side. |
|
||||||
| Default `Top`, with `null` meaning `Top` | Fields added to existing classes deserialize as `null` in old files. |
|
| Default `_2` (Top), with `null` meaning Top | Fields added to existing classes deserialize as `null` in old files. |
|
||||||
|
|
||||||
## 3. Design
|
## 3. Design
|
||||||
|
|
||||||
### diylc-core
|
### diylc-core
|
||||||
|
|
||||||
- **`org.diylc.common.BoardSide`**: enum `Top`, `Bottom`, with a `toString()` label like `PCBLayer`.
|
- **`org.diylc.core.IBoardSideComponent`**: `PCBLayer getBoardSide();` with Javadoc and
|
||||||
- **`org.diylc.core.IBoardSideComponent`**: `BoardSide getBoardSide();` with Javadoc and
|
`@author`, in the same shape as `ILayeredComponent` but deliberately separate from it (see
|
||||||
`@author`, in the same shape as `ILayeredComponent`. Core knows the side only through this
|
Decisions). It returns `_1` or `_2` only. Core knows the side only through this interface.
|
||||||
interface.
|
|
||||||
- **`GerberLayer.flip()`**: maps `CopperTop`/`CopperBot`, `SilkscreenTop`/`SilkscreenBot` and
|
- **`GerberLayer.flip()`**: maps `CopperTop`/`CopperBot`, `SilkscreenTop`/`SilkscreenBot` and
|
||||||
`SolderMaskTop`/`SolderMaskBot` to each other. `Outline` and the drill layers map to themselves.
|
`SolderMaskTop`/`SolderMaskBot` to each other. `Outline` and the drill layers map to themselves.
|
||||||
- **Gerber dispatch**: in the simple-component branch, use
|
- **Gerber dispatch**: in the simple-component branch, use
|
||||||
`getGerberLayer().flip()` when the component is an `IBoardSideComponent` on `Bottom`. Merge
|
`getGerberLayer().flip()` when the component is an `IBoardSideComponent` on `_1`. Merge
|
||||||
`GerberExporter.drawComponent` and `GerberPathRenderer.drawComponent` into one method first, so
|
`GerberExporter.drawComponent` and `GerberPathRenderer.drawComponent` into one method first, so
|
||||||
the rule is written once.
|
the rule is written once.
|
||||||
- **Canvas rendering**: `DrawingManager.drawProject` draws bottom-side components with a
|
- **Canvas rendering**: bottom-side components use the far-side fade from `copper-side-view.md`,
|
||||||
composite that sets them apart. Locked components already use a 0.5 alpha composite
|
wrapping their painting in `startDrawingSide(getBoardSide())`. They are then faded in the main
|
||||||
(`lockedComposite`), so the bottom-side treatment has to look different from that, for example
|
view and solid in the Underside Display mirror, the same as bottom copper.
|
||||||
a lower alpha or a tint. Pick one and check it against a locked bottom-side part.
|
|
||||||
|
|
||||||
### diylc-library
|
### diylc-library
|
||||||
|
|
||||||
- The ~35 classes that use the `SilkscreenTop` default implement `IBoardSideComponent`. Each adds a
|
- The ~35 classes that use the `SilkscreenTop` default implement `IBoardSideComponent`. Each adds a
|
||||||
nullable `BoardSide boardSide` field and a getter/setter pair, with
|
nullable `PCBLayer boardSide` field and a getter/setter pair, with
|
||||||
`@EditableProperty(name = "Board Side")` on the getter. The getter returns `Top` when the field
|
`@EditableProperty(name = "Board Side")` on the getter. The getter returns `_2` (Top) when the
|
||||||
is `null`. This duplicates the field across classes in the same way the `PCBLayer` field is
|
field is `null`. This duplicates the field across classes in the same way the `PCBLayer` field is
|
||||||
duplicated today. Where several of these classes share an abstract base
|
duplicated today. Where several of these classes share an abstract base
|
||||||
(`AbstractRadialComponent`, `AbstractFilmCapacitor`, `AbstractTransistorPackage`, ...), put it on
|
(`AbstractRadialComponent`, `AbstractFilmCapacitor`, `AbstractTransistorPackage`, ...), put it on
|
||||||
the base.
|
the base.
|
||||||
- `CopperTrace`, `CurvedTrace`, `GroundFill`, `SolderPad`, `PCBText` and `DrillHole` do **not**
|
- `CopperTrace`, `CurvedTrace`, `GroundFill`, `SolderPad`, `PCBText` and `DrillHole` do **not**
|
||||||
implement it. Their `PCBLayer` already says where they are.
|
implement it. Their `PCBLayer` already says where they are.
|
||||||
- Before allowing `Bottom` on a class, check that its transformer can mirror it
|
- Before allowing Bottom on a class, check that its transformer can mirror it
|
||||||
(`canMirror`). A class that cannot be mirrored must not offer `Bottom`, or the footprint would be
|
(`canMirror`). A class that cannot be mirrored must not offer Bottom, or the footprint would be
|
||||||
wrong.
|
wrong.
|
||||||
|
|
||||||
### diylc-swing
|
### diylc-swing
|
||||||
@@ -81,6 +84,35 @@ Status: not started.
|
|||||||
silkscreen outline. Split it into top and bottom pages, filtered by side, the same way
|
silkscreen outline. Split it into top and bottom pages, filtered by side, the same way
|
||||||
`TraceMaskDrawingProvider` gives one page per `PCBLayer`.
|
`TraceMaskDrawingProvider` gives one page per `PCBLayer`.
|
||||||
|
|
||||||
|
### Editing through the Underside Display mirror
|
||||||
|
|
||||||
|
The mirror is a second drawing of the same components, not a separate space: every component
|
||||||
|
lives in main-view coordinates. Editing in the mirror therefore means mapping the mouse back
|
||||||
|
through the mirror's reflection before the existing logic runs. A reflection is its own inverse,
|
||||||
|
so drawing and hit-testing use the same transform. Each stage is useful by itself and ships in
|
||||||
|
order.
|
||||||
|
|
||||||
|
- **Shared mirror geometry.** Move the transform built in `DrawingManager.drawMirroredComponent`
|
||||||
|
into a helper in `org.diylc.presenter` (not into `Presenter`). It answers two questions: the
|
||||||
|
reflection for a given `IBoard`, and which board's mirror region, if any, contains a model point.
|
||||||
|
The region is the board rectangle reflected about the pivot. `drawMirroredComponent` uses the
|
||||||
|
helper, so drawing and hit-testing cannot drift apart.
|
||||||
|
- **Stage 1: selecting in the mirror.** In `Presenter.findComponentsAt` and the click and hover
|
||||||
|
paths, a point that falls in a mirror region and hits nothing in main-view space is reflected
|
||||||
|
and tested again, limited to the components that board mirrors (those whose control point lies
|
||||||
|
inside it, as in `drawMirroredComponent`). Clicking a part in the mirror selects the original, so
|
||||||
|
the property editor, delete, copy and the popup menu all work unchanged. Selection highlighting
|
||||||
|
and control points are drawn on both copies.
|
||||||
|
- **Stage 2: dragging in the mirror.** A drag that starts in a mirror region reflects every
|
||||||
|
subsequent mouse point before it reaches the existing drag code. Movement along the mirrored axis
|
||||||
|
is therefore reversed, which is what the user sees. Snapping applies to the reflected point. Moving
|
||||||
|
the board, or changing its Underside Offset, cancels nothing; the mirror simply follows.
|
||||||
|
Control-point drags (stretching a trace) go through the same mapping.
|
||||||
|
- **Stage 3: placing in the mirror.** A component instantiated with its first point in a mirror
|
||||||
|
region is created at the reflected point. If it is an `IBoardSideComponent`, it is created with
|
||||||
|
side `_1` (Bottom) and mirrored, as Flip to Other Side would do. A `PCBLayer` component is created on
|
||||||
|
`_1`. Anything else is placed at the reflected point unchanged.
|
||||||
|
|
||||||
## 4. Open questions
|
## 4. Open questions
|
||||||
|
|
||||||
- **Bottom silkscreen text.** Component labels drawn through `IGerberComponentSimple` have to read
|
- **Bottom silkscreen text.** Component labels drawn through `IGerberComponentSimple` have to read
|
||||||
@@ -94,24 +126,40 @@ Status: not started.
|
|||||||
- **Plain property or action only.** A plain editable property lets a user change the side without
|
- **Plain property or action only.** A plain editable property lets a user change the side without
|
||||||
mirroring. Either keep both (the property for already-mirrored parts) or show the side as
|
mirroring. Either keep both (the property for already-mirrored parts) or show the side as
|
||||||
read-only and change it only through the action.
|
read-only and change it only through the action.
|
||||||
|
- **Components that belong to no side.** In mirror stage 3 a jumper, eyelet or wire dropped in the
|
||||||
|
mirror has no side to set. Confirm that placing it at the reflected point unchanged is what users
|
||||||
|
expect, or whether such parts should be refused in the mirror.
|
||||||
|
- **Components inside two boards.** A component whose control points fall inside two boards with
|
||||||
|
Underside Display on appears in both mirrors. Selecting through either is fine. Check that
|
||||||
|
dragging through one mirror while the other redraws does not confuse the user.
|
||||||
|
|
||||||
## 5. Verification
|
## 5. Verification
|
||||||
|
|
||||||
- Unit tests in `diylc-core` for `GerberLayer.flip()` and for the merged dispatch method, using a
|
- Unit tests in `diylc-core` for `GerberLayer.flip()` and for the merged dispatch method, using a
|
||||||
stub `IBoardSideComponent` (top, bottom and `null`).
|
stub `IBoardSideComponent` (top, bottom and `null`).
|
||||||
- Load an old `.diy` with PCB parts and confirm every component reports `Top` and that the Gerber
|
- Load an old `.diy` with PCB parts and confirm every component reports `_2` (Top) and that the Gerber
|
||||||
output is byte-identical to before.
|
output is byte-identical to before.
|
||||||
- Export a board with one part on each side and open the files in a Gerber viewer: the bottom part
|
- Export a board with one part on each side and open the files in a Gerber viewer: the bottom part
|
||||||
appears only in `.gbo`, mirrored, and in the right position.
|
appears only in `.gbo`, mirrored, and in the right position.
|
||||||
|
- Unit tests for the mirror geometry helper: the region for each `BoardUndersideDisplay` direction
|
||||||
|
and offset, and that reflecting a point twice returns it.
|
||||||
|
- In the app, with Underside Display set to each direction: select, drag and stretch a trace in
|
||||||
|
the mirror and confirm that the original follows correctly. Place a part in the mirror and confirm
|
||||||
|
that it is bottom-side, mirrored, faded on the main view and solid in the mirror.
|
||||||
- Run the regression suite, since rendering and serialization both change.
|
- Run the regression suite, since rendering and serialization both change.
|
||||||
|
|
||||||
## 6. Next steps
|
## 6. Next steps
|
||||||
|
|
||||||
1. Merge the two `drawComponent` copies in `diylc-core` and add `GerberLayer.flip()` with tests.
|
1. Merge the two `drawComponent` copies in `diylc-core` and add `GerberLayer.flip()` with tests.
|
||||||
2. Add `BoardSide` and `IBoardSideComponent`, and swap the Gerber layer in the dispatch.
|
2. Add `IBoardSideComponent` and swap the Gerber layer in the dispatch.
|
||||||
3. Add the field to the library components, putting it on shared bases where they exist. Settle
|
3. Add the field to the library components, putting it on shared bases where they exist. Settle
|
||||||
the silkscreen-text question while doing so.
|
the silkscreen-text question while doing so.
|
||||||
4. Add the bottom-side composite in `DrawingManager`.
|
4. Wrap bottom-side painting in `startDrawingSide(getBoardSide())`.
|
||||||
5. Add the Flip to Other Side action.
|
5. Add the Flip to Other Side action.
|
||||||
6. Split the silkscreen print-out by side.
|
6. Split the silkscreen print-out by side.
|
||||||
7. Run the regression suite.
|
7. Extract the mirror geometry helper, with tests, and use it in `drawMirroredComponent`.
|
||||||
|
8. Mirror stage 1: selecting through the mirror. It does not depend on steps 1-6 and can ship
|
||||||
|
early.
|
||||||
|
9. Mirror stage 2: dragging through the mirror.
|
||||||
|
10. Mirror stage 3: placing in the mirror, after Flip to Other Side exists.
|
||||||
|
11. Run the regression suite.
|
||||||
|
|||||||
@@ -0,0 +1,101 @@
|
|||||||
|
# Copper-Side View
|
||||||
|
|
||||||
|
Make it obvious which side of a board the canvas shows. The main view is the component side, and
|
||||||
|
copper on the bottom of the board is drawn faded, as if seen through it. The Underside Display
|
||||||
|
mirror is a true view from below: copper solid, components faded.
|
||||||
|
|
||||||
|
Status: implemented in the working tree; the open questions below and the regression run remain.
|
||||||
|
|
||||||
|
## 1. Background
|
||||||
|
|
||||||
|
- Z-order is paint order (`BOARD` < `TRACE` < `COMPONENT`), so boards, strips and traces were all
|
||||||
|
painted with copper on top and users mistook the main view for the copper side.
|
||||||
|
- Strips and pads are not separate components: the boards paint them inside their own `draw()`.
|
||||||
|
- `CopperTrace`, `CurvedTrace`, `SolderPad`, `GroundFill` and `PCBText` carry a `PCBLayer` that
|
||||||
|
defaults to `_1` (Bottom), so the model already placed them on the bottom.
|
||||||
|
|
||||||
|
## 2. Decisions
|
||||||
|
|
||||||
|
| Decision | Rationale |
|
||||||
|
| --- | --- |
|
||||||
|
| One rule for every view: near side solid, far side at reduced opacity, as if seen through the board | The same rule covers the main view, the mirror and, later, bottom-side components (`bottom-side-components.md`). |
|
||||||
|
| Paint order and z-order stay unchanged. Opacity alone shows the side | Boards are opaque, so painting copper beneath the board would hide it. Editing, hit-testing and selection are untouched. |
|
||||||
|
| Rendering-only change. No serialized field is added, renamed or retyped | Old files open with the new look and nothing to migrate. |
|
||||||
|
| `PCBLayer` is the side; no separate side enum. Inner layers are deprecated, not removed | DIYLC models two-sided boards only. XStream resolves enums by name, so `_3`-`_8` must stay for old files to load. |
|
||||||
|
| Far-side paint uses plain reduced opacity (`DrawingManager.FAR_SIDE_ALPHA`, 55%), not raised when selected | Simple, and reads clearly as "behind". 45% was too faint on the default phenolic strips. |
|
||||||
|
| The fade takes the stronger of `FAR_SIDE_ALPHA` and the component's own alpha; it does not multiply them | Multiplying with the 50% locked alpha left a locked board's copper at about 22%. The cost is that a bottom part already more translucent than `FAR_SIDE_ALPHA` does not fade further. |
|
||||||
|
| A global Config toggle, **Fade Copper Under Board**, on by default and not saved in projects | Users who prefer the old look can turn it off without a per-board property. |
|
||||||
|
| The canvas and the layout export, PDF and print follow the toggle. Trace mask, silkscreen and Gerber are unaffected | The layout outputs show what the canvas shows; the fabrication outputs build their own `DrawOption` sets. |
|
||||||
|
| The canvas draws no side labels. Users label the sides themselves | A caption drawn outside the mirror can sit on top of wiring, and only the user knows where a label fits. |
|
||||||
|
| `undersideTransparency` no longer fades the whole mirror: `true` (default) fades the far side, `false` draws the mirror fully solid | The near side of a true underside view has to be solid. The field keeps its name, so old files load. |
|
||||||
|
| Paint a component does not declare is component side, except a board's own paint in the mirror, which is drawn as near side | Components need no changes. A board's substrate is seen from both sides, so it must not fade in the mirror. |
|
||||||
|
| Holes and trace cuts are visible from both sides (`startDrawingBothSides()`) and never fade; a board's own holes are already solid as board paint | A faded hole reads as copper, and a faded cut hides the break the user is looking for. |
|
||||||
|
| `Jumper`, `Eyelet`, `Turret`, `Top`-layer copper and `EyeletBoard` eyelets are component side | Jumpers run over the board; eyelets and turrets are visible from the top. |
|
||||||
|
| `ProtoBoard780` declares its traces and pads bottom side, like `PerfBoard`. `ProtoBoard` declares only the traces and pads its code paints in the underside colours. `EurorackStripboard` declares nothing | The 780-hole boards carry copper on the bottom only. `ProtoBoard` is plated through with some traces on top. Which side `EurorackStripboard`'s traces are on is not sourced. |
|
||||||
|
| `Breadboard`, `BlankBoard`, `MarshallPerfBoard`, `TagStrip`, `TerminalStrip` and the maker boards declare nothing | They have no bottom copper to show. |
|
||||||
|
|
||||||
|
## 3. Design as built
|
||||||
|
|
||||||
|
### diylc-core
|
||||||
|
|
||||||
|
- `PCBLayer`: `_3`-`_8` are `@Deprecated`, in their original order so that `getId()` is stable.
|
||||||
|
They are not remapped and keep their own continuity layer. For fading they count as Bottom.
|
||||||
|
- `IDrawingObserver.startDrawingSide(PCBLayer)`, `startDrawingBothSides()` and
|
||||||
|
`stopDrawingSide()` are default no-op methods, so `GerberG2DWrapper` and test observers need no
|
||||||
|
change.
|
||||||
|
- `G2DWrapper` keeps the viewed side, the side assumed for undeclared paint, and the side being
|
||||||
|
painted. While the painted side differs from the viewed side and fading is on, it draws at the
|
||||||
|
smaller of `FAR_SIDE_ALPHA` and the component's own alpha. Both-sides paint is treated as the
|
||||||
|
viewed side. `getComposite()` returns the
|
||||||
|
component's own composite and `setComposite()` re-applies the fade, so components that save and
|
||||||
|
restore their composite keep working. Outline mode never fades. Only the composite changes, so
|
||||||
|
outline and continuity areas are identical with and without fading.
|
||||||
|
- `DrawOption.FADE_FAR_SIDE` turns fading on in the main view. `IPlugInPort.FADE_FAR_SIDE_KEY`
|
||||||
|
(`fadeFarSide`) is the config key.
|
||||||
|
- `DrawingManager.drawMirroredComponent` views the mirror from `_1`, with fading controlled by
|
||||||
|
`undersideTransparency`. The reflection is built by `createMirrorTransform`.
|
||||||
|
|
||||||
|
### diylc-library
|
||||||
|
|
||||||
|
- `VeroBoard` and `TriPadBoard` declare each strip `_1`; `PerfBoard` declares each pad `_1`. The
|
||||||
|
holes stay undeclared.
|
||||||
|
- `ProtoBoard780` declares its traces and pads `_1`; holes, mounting holes and labels stay
|
||||||
|
undeclared.
|
||||||
|
- `ProtoBoard` declares its underside traces and underside mini pads `_1`.
|
||||||
|
- `CopperTrace` and `PCBText` declare their whole canvas `draw` on `getLayer()`. `CurvedTrace` and
|
||||||
|
`GroundFill` declare their copper fill. `SolderPad` declares the pad on its layer and its
|
||||||
|
drilled hole as both sides.
|
||||||
|
- `TraceCut` draws its cut and hole as both sides.
|
||||||
|
|
||||||
|
### diylc-swing
|
||||||
|
|
||||||
|
- The **Fade Copper Under Board** checkbox is in the Config menu after Extra Working Area.
|
||||||
|
`CanvasPanel.getDrawOptions` and `ProjectDrawingProvider.draw` add `FADE_FAR_SIDE` from it.
|
||||||
|
- `EnumEditor` hides `@Deprecated` constants unless one is the current value.
|
||||||
|
- The language key **Fade Copper Under Board** is in every file in `lang/`.
|
||||||
|
|
||||||
|
## 4. Open questions
|
||||||
|
|
||||||
|
- **`Dot` on stripboard.** Users draw solder joints with `Dot`, which is generic and undeclared,
|
||||||
|
so dots are solid in the main view and faded in the mirror.
|
||||||
|
- **`EurorackStripboard`.** Unchanged until the side of its traces is sourced.
|
||||||
|
- **AI catalog.** `ComponentCatalogGenerator` still lists the deprecated inner layers as possible
|
||||||
|
values.
|
||||||
|
|
||||||
|
## 5. Verification
|
||||||
|
|
||||||
|
- `DrawingManagerTest` checks rendered pixels: undeclared paint is solid; bottom and inner-layer
|
||||||
|
paint is faded only with `FADE_FAR_SIDE`; both-sides paint is solid in both views; a locked
|
||||||
|
bottom part is faded once, not twice. In the mirror, copper is solid and the component side
|
||||||
|
is faded, and `undersideTransparency=false` draws everything solid. The composite is restored,
|
||||||
|
setting a composite mid-side re-fades it, outline mode never fades, and continuity areas do not
|
||||||
|
change.
|
||||||
|
- `InnerLayerCompatibilityTest` loads a trace saved on `_5` and checks that its layer and layer id
|
||||||
|
are kept.
|
||||||
|
- Still to do: run the regression suite and regenerate reference PNGs for the expected
|
||||||
|
faded-copper changes. Netlists must not change.
|
||||||
|
|
||||||
|
## 6. Next steps
|
||||||
|
|
||||||
|
1. Settle the open questions above.
|
||||||
|
2. Run the regression suite.
|
||||||
Reference in New Issue
Block a user