Review fixes: a drag in the Underside Display picks the part a click would, the part being placed there is drawn like the parts already there, a bottom Dot no longer adds an empty silkscreen page; one helper decides a part's board side and copper layers share an interface

This commit is contained in:
Bane Stojkovic
2026-11-11 17:32:46 +01:00
parent 5ef6952ef4
commit 13b31e6dd5
43 changed files with 345 additions and 170 deletions
@@ -46,4 +46,13 @@ public interface IBoardSideComponent {
* @param boardSide {@link PCBLayer#_2} for the top or {@link PCBLayer#_1} for the bottom
*/
void setBoardSide(PCBLayer boardSide);
/**
* @return {@link PCBLayer#_1} for a component mounted on the bottom of the board, and
* {@link PCBLayer#_2} for any other, including components that have no side
*/
static PCBLayer sideOf(IDIYComponent<?> component) {
return component instanceof IBoardSideComponent sided && sided.getBoardSide() == PCBLayer._1
? PCBLayer._1 : PCBLayer._2;
}
}
@@ -0,0 +1,38 @@
/*
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.core;
import org.diylc.common.PCBLayer;
/**
* Interface for copper drawn on a layer of a PCB, such as a trace, pad, ground fill or PCB text.
* Unlike {@link IBoardSideComponent}, the layer is where the copper itself is, and it also decides
* which other copper it connects to.
*
* @author Branislav Stojkovic
*/
public interface IPCBLayerComponent {
PCBLayer getLayer();
void setLayer(PCBLayer layer);
}
@@ -162,8 +162,7 @@ public class GerberExporter {
IGerberComponentSimple gerberComponentSimple = (IGerberComponentSimple) gerberComponent;
GerberLayer layer = gerberComponentSimple.getGerberLayer();
// components emit their top-side layer; a part mounted underneath goes to the bottom one
boolean bottomSide = c instanceof IBoardSideComponent
&& ((IBoardSideComponent) c).getBoardSide() == PCBLayer._1;
boolean bottomSide = IBoardSideComponent.sideOf(c) == PCBLayer._1;
if (bottomSide) {
layer = layer.flip();
}
@@ -21,10 +21,9 @@
*/
package org.diylc.filter;
import java.lang.reflect.Method;
import org.diylc.common.PCBLayer;
import org.diylc.core.IDIYComponent;
import org.diylc.core.IPCBLayerComponent;
public class PCBLayerFilter implements IComponentFilter {
@@ -37,13 +36,6 @@ public class PCBLayerFilter implements IComponentFilter {
@Override
public boolean testComponent(IDIYComponent<?> component) {
Class<?> clazz = component.getClass();
try {
Method m = clazz.getMethod("getLayer");
PCBLayer l = (PCBLayer) m.invoke(component);
return layer == l;
} catch (Exception e) {
return false;
}
return component instanceof IPCBLayerComponent copper && copper.getLayer() == layer;
}
}
@@ -33,16 +33,10 @@ public class SilkScreenFilter implements IComponentFilter {
if (!(component instanceof IGerberComponent gerberComponent)) {
return false;
}
if (side != null && getSide(component) != side) {
if (side != null && IBoardSideComponent.sideOf(component) != side) {
return false;
}
Set<GerberRenderMode> gerberRenderModes = gerberComponent.getGerberRenderModes();
return gerberRenderModes.contains(GerberRenderMode.Outline);
}
// components that do not say which side they are on are on top
private static PCBLayer getSide(IDIYComponent<?> component) {
return component instanceof IBoardSideComponent sided && sided.getBoardSide() == PCBLayer._1
? PCBLayer._1 : PCBLayer._2;
}
}
@@ -37,6 +37,7 @@ import org.diylc.common.ComponentType;
import org.diylc.common.DrawOption;
import org.diylc.core.ComponentForRender;
import org.diylc.core.ComponentState;
import org.diylc.core.IBoardSideComponent;
import org.diylc.core.IDIYComponent;
import org.diylc.core.Project;
import org.diylc.core.annotations.ComponentDescriptor;
@@ -351,7 +352,7 @@ public class DrawingCache {
// initialize wrapper
wrapper.startedDrawingComponent(zOrder);
wrapper.setUndeclaredSide(DrawingManager.getMountedSide(component));
wrapper.setUndeclaredSide(IBoardSideComponent.sideOf(component));
long componentStart = System.nanoTime();
@@ -307,7 +307,7 @@ public class DrawingManager {
g2d.setComposite(lockedComposite);
}
// after the locked composite, which is set on the canvas and would replace the fade
g2dWrapper.setUndeclaredSide(getMountedSide(component));
g2dWrapper.setUndeclaredSide(IBoardSideComponent.sideOf(component));
// Draw the component through the g2dWrapper.
long componentStart = System.nanoTime();
try {
@@ -433,8 +433,7 @@ public class DrawingManager {
g2dWrapper.startedDrawingComponent(project.getComponents().size());
g2dWrapper.setComposite(slotComposite);
if (slotTx != null) {
g2dWrapper.transform(slotTx);
g2dWrapper.setMirrorDirection(UndersideMirror.getMirrorDirection(slotMirrorBoard));
viewThroughMirror(g2dWrapper, slotMirrorBoard, slotTx, component);
}
try {
component.draw(g2dWrapper, ComponentState.NORMAL,
@@ -443,7 +442,9 @@ public class DrawingManager {
LOG.error("Error drawing " + component.getName(), e);
failedComponents.add(component);
} finally {
g2dWrapper.setMirrorDirection(G2DWrapper.MirrorDirection.NONE);
if (slotTx != null) {
stopViewingThroughMirror(g2dWrapper);
}
g2dWrapper.finishedDrawingComponent();
}
}
@@ -544,15 +545,6 @@ public class DrawingManager {
return failedComponents;
}
/**
* @return the side of the board the component is mounted on; components that do not say are
* on top
*/
static PCBLayer getMountedSide(IDIYComponent<?> component) {
return component instanceof IBoardSideComponent
? ((IBoardSideComponent) component).getBoardSide() : PCBLayer._2;
}
private static void drawSelectedControlPoint(G2DWrapper g2dWrapper, Point2D controlPoint) {
g2dWrapper.setColor(SELECTED_CONTROL_POINT_COLOR.darker());
g2dWrapper.fillOval((int) Math.round(controlPoint.getX() - CONTROL_POINT_SIZE / 2),
@@ -571,29 +563,40 @@ public class DrawingManager {
return;
}
G2DWrapper.MirrorDirection mirrorDirection = UndersideMirror.getMirrorDirection(board);
AffineTransform oldTx = g2dWrapper.getTransform();
Composite oldComposite = g2dWrapper.getComposite();
g2dWrapper.setMirrorDirection(mirrorDirection);
try {
g2dWrapper.transform(mirrorTx);
// the mirror is the copper side, so copper and the parts mounted on it are drawn solid and
// the component side faded
g2dWrapper.setViewedSide(PCBLayer._1, Boolean.TRUE.equals(board.getUndersideTransparency()),
component instanceof IBoard ? PCBLayer._1 : getMountedSide(component));
viewThroughMirror(g2dWrapper, board, mirrorTx, component);
component.draw(g2dWrapper, state, outlineMode, project, g2dWrapper);
} catch (Exception e) {
LOG.error("Error drawing mirrored " + component.getName(), e);
} finally {
g2dWrapper.setMirrorDirection(G2DWrapper.MirrorDirection.NONE);
stopViewingThroughMirror(g2dWrapper);
g2dWrapper.setTransform(oldTx);
g2dWrapper.stopDrawingSide();
g2dWrapper.resetViewedSide();
g2dWrapper.setComposite(oldComposite);
}
}
/**
* Sets the wrapper up to draw the component into the board's underside mirror: reflected, with
* text kept readable, and seen from the bottom.
*/
private static void viewThroughMirror(G2DWrapper g2dWrapper, IBoard board,
AffineTransform mirrorTx, IDIYComponent<?> component) {
g2dWrapper.setMirrorDirection(UndersideMirror.getMirrorDirection(board));
g2dWrapper.transform(mirrorTx);
// the mirror is the copper side, so copper and the parts mounted on it are drawn solid and
// the component side faded
g2dWrapper.setViewedSide(PCBLayer._1, Boolean.TRUE.equals(board.getUndersideTransparency()),
component instanceof IBoard ? PCBLayer._1 : IBoardSideComponent.sideOf(component));
}
private static void stopViewingThroughMirror(G2DWrapper g2dWrapper) {
g2dWrapper.setMirrorDirection(G2DWrapper.MirrorDirection.NONE);
g2dWrapper.stopDrawingSide();
g2dWrapper.resetViewedSide();
}
private static void drawComponent(Project project, Set<DrawOption> drawOptions, Double scaleFactor,
Rectangle2D visibleRect, IDIYComponent<?> component, G2DWrapper g2dWrapper, ComponentState state,
double zoom, int i, boolean outlineMode, boolean cacheable) {
@@ -21,14 +21,12 @@
*/
package org.diylc.presenter;
import java.lang.reflect.Method;
import java.util.Collection;
import org.apache.log4j.Logger;
import org.diylc.common.PCBLayer;
import org.diylc.core.IBoardSideComponent;
import org.diylc.core.IDIYComponent;
import org.diylc.core.IPCBLayerComponent;
/**
* Puts components placed through an Underside Display mirror on the bottom of the board. A part
@@ -37,29 +35,15 @@ import org.diylc.core.IDIYComponent;
*/
public class MirrorPlacement {
private static final Logger LOG = Logger.getLogger(MirrorPlacement.class);
private MirrorPlacement() {
}
public static void placeOnBottom(Collection<IDIYComponent<?>> components) {
for (IDIYComponent<?> component : components) {
if (component instanceof IBoardSideComponent) {
((IBoardSideComponent) component).setBoardSide(PCBLayer._1);
continue;
}
// traces, pads and PCB text share no interface for their layer, so it is found the same way
// PCBLayerFilter reads it
Method setLayer;
try {
setLayer = component.getClass().getMethod("setLayer", PCBLayer.class);
} catch (NoSuchMethodException e) {
continue;
}
try {
setLayer.invoke(component, PCBLayer._1);
} catch (Exception e) {
LOG.warn("Could not put " + component.getName() + " on the bottom layer", e);
if (component instanceof IBoardSideComponent sided) {
sided.setBoardSide(PCBLayer._1);
} else if (component instanceof IPCBLayerComponent copper) {
copper.setLayer(PCBLayer._1);
}
}
}
@@ -172,11 +172,12 @@ public class Presenter implements IPlugInPort {
private Point2D dragStartPoint = null;
private boolean dragShiftDown = false;
private AxisLock lockedAxis = null;
// The reflection of the Underside Display mirror a drag started in. Every point of that drag goes
// through it, so the drag acts on the components themselves; null for a drag in the main view.
private AffineTransform dragMirrorTx = null;
// the reflection of the mirror that the control points in controlPointMap were found through
private AffineTransform hoverMirrorTx = null;
// The board whose Underside Display mirror a drag started in. Every point of that drag is
// reflected through it, so the drag acts on the components themselves; null for a drag in the
// main view.
private IBoard dragMirrorBoard = null;
// the board whose mirror the control points in controlPointMap were found through
private IBoard hoverMirrorBoard = null;
// the board whose Underside Display mirror the component in the slot is being placed through;
// null when it is placed in the main view
private IBoard placementMirrorBoard = null;
@@ -310,6 +311,7 @@ public class Presenter implements IPlugInPort {
public void loadProject(Project project, boolean freshStart, String filename) {
LOG.info(String.format("loadProject(%s, %s)", project.getTitle(), freshStart));
this.currentProject = project;
placementMirrorBoard = null;
groupAndLockIndex.invalidate();
drawingManager.clearComponentAreaMap();
drawingManager.clearContinuityArea();
@@ -632,7 +634,7 @@ public class Presenter implements IPlugInPort {
return;
}
updatePlacementMirror(scaledPoint);
scaledPoint = toPlacementSpace(scaledPoint);
scaledPoint = UndersideMirror.reflect(placementMirrorBoard, scaledPoint);
// Keep the reference to component type for later.
ComponentType componentTypeSlot = instantiationManager.getComponentTypeSlot();
Template template = instantiationManager.getTemplate();
@@ -965,7 +967,8 @@ public class Presenter implements IPlugInPort {
* specified point, one per component. When the point was reflected out of the underside mirror of
* a board, only the components that mirror shows are considered.
*/
private Map<IDIYComponent<?>, Set<Integer>> findControlPointsAt(Point2D point, IBoard mirrorBoard) {
private Map<IDIYComponent<?>, Set<Integer>> findControlPointsAt(Point2D point,
IBoard mirrorBoard) {
Map<IDIYComponent<?>, Set<Integer>> components = new HashMap<IDIYComponent<?>, Set<Integer>>();
// Go backwards so we take the highest z-order components first.
for (int i = currentProject.getComponents().size() - 1; i >= 0; i--) {
@@ -988,7 +991,8 @@ public class Presenter implements IPlugInPort {
break;
}
} catch (Exception e) {
LOG.warn("Error reading control point for component of type: " + component.getClass().getName());
LOG.warn("Error reading control point for component of type: "
+ component.getClass().getName());
}
}
}
@@ -1023,11 +1027,12 @@ public class Presenter implements IPlugInPort {
IBoard oldPlacementMirrorBoard = placementMirrorBoard;
updatePlacementMirror(previousScaledPoint);
// reflected before snapping, so that a component placed through a mirror lands on the grid
Point2D placementPoint = toPlacementSpace(previousScaledPoint);
Point2D placementPoint = UndersideMirror.reflect(placementMirrorBoard, previousScaledPoint);
if (isSnapToGrid()) {
CalcUtils.snapPointToGrid(placementPoint, currentProject.getGridSpacing());
} else if (isSnapToObjects()) {
CalcUtils.snapPointToObjects(placementPoint, currentProject.getGridSpacing(), null, currentProject.getComponents());
CalcUtils.snapPointToObjects(placementPoint, currentProject.getGridSpacing(), null,
currentProject.getComponents());
}
// What the cursor does is decided by how the slot was armed rather than by the creation
// method: a slot instantiated up front carries a whole component that follows the cursor,
@@ -1040,22 +1045,24 @@ public class Presenter implements IPlugInPort {
currentProject.getGridSpacing());
} else {
refresh = instantiationManager.updatePointByPoint(
constrainToAxis(instantiationManager.getFirstControlPoint(), placementPoint, shiftDown));
constrainToAxis(instantiationManager.getFirstControlPoint(), placementPoint,
shiftDown));
}
if (refresh || placementMirrorBoard != oldPlacementMirrorBoard) {
messageDispatcher.dispatchMessage(EventType.REPAINT);
}
} else {
components = findControlPointsAt(previousScaledPoint, null);
hoverMirrorTx = null;
for (IBoard board : UndersideMirror.findBoardsMirroredAt(currentProject, previousScaledPoint)) {
hoverMirrorBoard = null;
for (IBoard board :
UndersideMirror.findBoardsMirroredAt(currentProject, previousScaledPoint)) {
if (!components.isEmpty()) {
break;
}
AffineTransform mirrorTx = UndersideMirror.createTransform(board);
components = findControlPointsAt(mirrorTx.transform(previousScaledPoint, null), board);
components =
findControlPointsAt(UndersideMirror.reflect(board, previousScaledPoint), board);
if (!components.isEmpty()) {
hoverMirrorTx = mirrorTx;
hoverMirrorBoard = board;
}
}
}
@@ -1404,20 +1411,26 @@ public class Presenter implements IPlugInPort {
this.dragAction = dragAction;
this.preDragProject = currentProject.clone();
Point2D scaledPoint = scalePoint(point);
List<IDIYComponent<?>> components =
forceSelectionRect ? null : findComponentsAtScaled(scaledPoint, false, false);
if (controlPointMap != null && !controlPointMap.isEmpty()) {
this.dragMirrorTx = hoverMirrorTx;
} else if (forceSelectionRect || !findComponentsAtScaled(scaledPoint, false, false).isEmpty()) {
this.dragMirrorTx = null;
this.dragMirrorBoard = hoverMirrorBoard;
} else if (components == null || !components.isEmpty()) {
this.dragMirrorBoard = null;
} else {
this.dragMirrorTx = findMirrorWithComponentAt(scaledPoint);
// the parts a click there would select, not whatever covers the reflected point
this.dragMirrorBoard = findMirrorBoardWithComponentAt(scaledPoint);
if (dragMirrorBoard != null) {
components =
drawingManager.findMirroredComponentsAt(scaledPoint, currentProject, dragMirrorBoard);
removeUnselectable(components, false);
}
}
scaledPoint = toDragSpace(scaledPoint);
scaledPoint = UndersideMirror.reflect(dragMirrorBoard, scaledPoint);
this.previousDragPoint = scaledPoint;
this.dragStartPoint = new Point2D.Double(scaledPoint.getX(), scaledPoint.getY());
this.dragShiftDown = shiftDown;
this.lockedAxis = null;
List<IDIYComponent<?>> components =
forceSelectionRect ? null : findComponentsAtScaled(scaledPoint, false, false);
if (controlPointMap != null && !this.controlPointMap.isEmpty()) {
// If we're dragging control points reset selection.
updateSelection(new ArrayList<IDIYComponent<?>>(this.controlPointMap.keySet()));
@@ -1634,7 +1647,7 @@ public class Presenter implements IPlugInPort {
boolean resizeInProgress = false;
Point2D scaledPoint = toDragSpace(scalePoint(point));
Point2D scaledPoint = UndersideMirror.reflect(dragMirrorBoard, scalePoint(point));
if (controlPointMap != null && !controlPointMap.isEmpty()) {
// We're dragging control point(s).
Point2D targetPoint = constrainToAxis(dragStartPoint, scaledPoint, dragShiftDown);
@@ -1671,7 +1684,8 @@ public class Presenter implements IPlugInPort {
// a component dragged in from the component tree or the toolbox
this.previousScaledPoint = scalePoint(point);
updatePlacementMirror(previousScaledPoint);
instantiationManager.updateSingleClick(toPlacementSpace(previousScaledPoint), isSnapToGrid(),
instantiationManager.updateSingleClick(
UndersideMirror.reflect(placementMirrorBoard, previousScaledPoint), isSnapToGrid(),
currentProject.getGridSpacing());
}
@@ -2055,7 +2069,7 @@ public class Presenter implements IPlugInPort {
test.addStep(DIYTest.DRAG_END, params);
}
Point2D scaledPoint = toDragSpace(scalePoint(point));
Point2D scaledPoint = UndersideMirror.reflect(dragMirrorBoard, scalePoint(point));
if (!dragInProgress && instantiationManager.getComponentSlot() == null) {
return;
@@ -2110,7 +2124,7 @@ public class Presenter implements IPlugInPort {
dragStartPoint = null;
dragShiftDown = false;
lockedAxis = null;
dragMirrorTx = null;
dragMirrorBoard = null;
}
@Override
@@ -3210,11 +3224,6 @@ public class Presenter implements IPlugInPort {
return p;
}
private Point2D toDragSpace(Point2D scaledPoint) {
return dragMirrorTx == null || scaledPoint == null ? scaledPoint
: dragMirrorTx.transform(scaledPoint, null);
}
// a component placed point by point stays in the mirror its first point went into
private void updatePlacementMirror(Point2D scaledPoint) {
if (instantiationManager.getFirstControlPoint() != null) {
@@ -3224,24 +3233,17 @@ public class Presenter implements IPlugInPort {
placementMirrorBoard = boards.isEmpty() ? null : boards.get(0);
}
private Point2D toPlacementSpace(Point2D scaledPoint) {
AffineTransform mirrorTx =
placementMirrorBoard == null ? null : UndersideMirror.createTransform(placementMirrorBoard);
return mirrorTx == null || scaledPoint == null ? scaledPoint
: mirrorTx.transform(scaledPoint, null);
}
/**
* @return the reflection of the first underside mirror that shows a selectable component at the
* specified point, or null
* @return the topmost board whose underside mirror shows a selectable component at the specified
* point, or null
*/
private AffineTransform findMirrorWithComponentAt(Point2D scaledPoint) {
private IBoard findMirrorBoardWithComponentAt(Point2D scaledPoint) {
for (IBoard board : UndersideMirror.findBoardsMirroredAt(currentProject, scaledPoint)) {
List<IDIYComponent<?>> components =
drawingManager.findMirroredComponentsAt(scaledPoint, currentProject, board);
removeUnselectable(components, false);
if (!components.isEmpty()) {
return UndersideMirror.createTransform(board);
return board;
}
}
return null;
@@ -161,18 +161,28 @@ public class UndersideMirror {
}
/**
* @return the boards whose mirror contains the point, in z-order
* @return the boards whose mirror contains the point, the topmost first, which is the one drawn
* over the others where mirrors overlap
*/
public static List<IBoard> findBoardsMirroredAt(Project project, Point2D point) {
List<IBoard> boards = new ArrayList<IBoard>();
for (IBoard board : getMirroringBoards(project)) {
if (getMirrorRegion(board).contains(point)) {
boards.add(board);
boards.add(0, board);
}
}
return boards;
}
/**
* @return the point reflected through the board's mirror, or the point itself when the board is
* null or shows no mirror
*/
public static Point2D reflect(IBoard board, Point2D point) {
AffineTransform mirrorTx = board == null ? null : createTransform(board);
return mirrorTx == null || point == null ? point : mirrorTx.transform(point, null);
}
// the board rectangle may be stored with its corners in either order
private static Rectangle2D getBoardBounds(IBoard board) {
Rectangle2D rect = board.getBoardRectangle();
@@ -24,6 +24,7 @@ import org.diylc.appframework.simplemq.MessageDispatcher;
import org.diylc.common.DrawOption;
import org.diylc.common.EventType;
import org.diylc.common.PCBLayer;
import org.diylc.common.Percentage;
import org.diylc.core.BoardUndersideDisplay;
import org.diylc.core.ComponentState;
import org.diylc.core.IBoard;
@@ -687,8 +688,10 @@ public class DrawingManagerTest {
@Test
public void testBottomMountedPartFadedOnlyWithOption() {
Project project = new Project();
project.getComponents().add(new MountedComponent(new Rectangle2D.Double(150, 150, 10, 10), PCBLayer._2));
project.getComponents().add(new MountedComponent(new Rectangle2D.Double(200, 200, 10, 10), PCBLayer._1));
project.getComponents()
.add(new MountedComponent(new Rectangle2D.Double(150, 150, 10, 10), PCBLayer._2));
project.getComponents()
.add(new MountedComponent(new Rectangle2D.Double(200, 200, 10, 10), PCBLayer._1));
BufferedImage faded = renderSides(project, EnumSet.of(DrawOption.ZOOM, DrawOption.FADE_FAR_SIDE));
assertSolid(faded, 155, 155);
@@ -701,7 +704,8 @@ public class DrawingManagerTest {
@Test
public void testCachedBottomMountedPartFaded() {
Project project = new Project();
project.getComponents().add(new MountedComponent(new Rectangle2D.Double(200, 200, 10, 10), PCBLayer._1));
project.getComponents()
.add(new MountedComponent(new Rectangle2D.Double(200, 200, 10, 10), PCBLayer._1));
BufferedImage img = renderSides(project,
EnumSet.of(DrawOption.ZOOM, DrawOption.FADE_FAR_SIDE, DrawOption.ENABLE_CACHING));
@@ -717,8 +721,10 @@ public class DrawingManagerTest {
board.setUndersideOffset(new Size(20.0, SizeUnit.px));
board.setUndersideTransparency(true);
project.getComponents().add(board);
project.getComponents().add(new MountedComponent(new Rectangle2D.Double(150, 150, 10, 10), PCBLayer._2));
project.getComponents().add(new MountedComponent(new Rectangle2D.Double(200, 200, 10, 10), PCBLayer._1));
project.getComponents()
.add(new MountedComponent(new Rectangle2D.Double(150, 150, 10, 10), PCBLayer._2));
project.getComponents()
.add(new MountedComponent(new Rectangle2D.Double(200, 200, 10, 10), PCBLayer._1));
// reflection across y = 410 puts the top part at y 660..670 and the bottom one at 610..620
BufferedImage img = renderSides(project, EnumSet.of(DrawOption.ZOOM, DrawOption.FADE_FAR_SIDE));
@@ -726,10 +732,44 @@ public class DrawingManagerTest {
assertSolid(img, 205, 615);
}
@Test
public void testPartBeingPlacedInMirrorSeenFromBelow() {
Percentage farSideAlpha = DrawingManager.FAR_SIDE_ALPHA;
// below the 50% of a part being placed, so that a far-side fade would show
DrawingManager.FAR_SIDE_ALPHA = new Percentage(20);
try {
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);
// bottom copper, which the mirror shows as the near side
SidePaintingComponent copper =
new SidePaintingComponent(new Rectangle2D.Double(200, 200, 10, 10), PCBLayer._1);
DrawingManager drawingManager = createDrawingManager();
drawingManager.setSlotMirrorBoard(board);
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),
null, null, Collections.emptyList(), Collections.emptySet(), Collections.emptySet(),
null, Collections.singletonList(copper), false, Collections.emptySet(), 1.0, 1.0, null);
g2d.dispose();
// drawn at the 50% of a part being placed, not faded further; the square lands at 610..620
assertFaded(img, 205, 615, 0.5f);
} finally {
DrawingManager.FAR_SIDE_ALPHA = farSideAlpha;
}
}
@Test
public void testLockedBottomMountedPartNotFadedTwice() {
Project project = new Project();
MountedComponent bottom = new MountedComponent(new Rectangle2D.Double(200, 200, 10, 10), PCBLayer._1);
MountedComponent bottom =
new MountedComponent(new Rectangle2D.Double(200, 200, 10, 10), PCBLayer._1);
project.getComponents().add(bottom);
DrawingManager drawingManager = createDrawingManager();
@@ -1052,10 +1092,12 @@ public class DrawingManagerTest {
// paints an opaque black square, declared on the given side or, when null, left undeclared
// paints an undeclared black square, like a part that leaves its side to the Board Side property
@ComponentDescriptor(name = "Mounted", author = "test", category = "Test", instanceNamePrefix = "M",
@ComponentDescriptor(name = "Mounted", author = "test", category = "Test",
instanceNamePrefix = "M",
description = "Part mounted on one side of the board", zOrder = IDIYComponent.COMPONENT,
enableCache = true)
public static class MountedComponent extends SidePaintingComponent implements IBoardSideComponent {
public static class MountedComponent extends SidePaintingComponent
implements IBoardSideComponent {
private static final long serialVersionUID = 1L;
@@ -28,6 +28,7 @@ import java.util.Arrays;
import org.diylc.common.PCBLayer;
import org.diylc.core.IDIYComponent;
import org.diylc.core.IPCBLayerComponent;
import org.diylc.presenter.DrawingManagerTest.MountedComponent;
import org.diylc.testcomponents.MockDIYComponent;
import org.junit.Test;
@@ -46,16 +47,18 @@ public class MirrorPlacementTest {
assertEquals(PCBLayer._1, copper.getLayer());
}
public static class LayeredComponent extends MockDIYComponent {
public static class LayeredComponent extends MockDIYComponent implements IPCBLayerComponent {
private static final long serialVersionUID = 1L;
private PCBLayer layer = PCBLayer._2;
@Override
public PCBLayer getLayer() {
return layer;
}
@Override
public void setLayer(PCBLayer layer) {
this.layer = layer;
}
@@ -71,7 +71,8 @@ public class UndersideMirrorTest {
public void testMirrorRegionFollowsOffset() {
MockBoard board = board(BoardUndersideDisplay.BELOW);
board.setUndersideOffset(new Size(60.0, SizeUnit.px));
assertEquals(new Rectangle2D.Double(100, 460, 200, 300), UndersideMirror.getMirrorRegion(board));
assertEquals(new Rectangle2D.Double(100, 460, 200, 300),
UndersideMirror.getMirrorRegion(board));
}
@Test
@@ -111,7 +112,8 @@ public class UndersideMirrorTest {
assertEquals(Collections.singletonList(below),
UndersideMirror.findBoardsMirroredAt(project, new Point2D.Double(150, 500)));
assertTrue(UndersideMirror.findBoardsMirroredAt(project, new Point2D.Double(150, 300)).isEmpty());
assertTrue(
UndersideMirror.findBoardsMirroredAt(project, new Point2D.Double(150, 300)).isEmpty());
}
private static MockBoard board(BoardUndersideDisplay display) {
@@ -36,6 +36,7 @@ import org.diylc.core.IConnective;
import org.diylc.core.IDIYComponent;
import org.diylc.core.IDrawingObserver;
import org.diylc.core.ILayeredComponent;
import org.diylc.core.IPCBLayerComponent;
import org.diylc.core.Project;
import org.diylc.core.VisibilityPolicy;
import org.diylc.core.annotations.AiNote.Scope;
@@ -58,7 +59,8 @@ import org.diylc.core.measures.SizeUnit;
description = "Straight copper trace", zOrder = IDIYComponent.TRACE, bomPolicy = BomPolicy.NEVER_SHOW,
keywordPolicy = KeywordPolicy.SHOW_TAG, keywordTag = "PCB",
transformer = SimpleComponentTransformer.class)
public class CopperTrace extends AbstractLeadedComponent<Void> implements IConnective, ILayeredComponent, IGerberComponentSimple {
public class CopperTrace extends AbstractLeadedComponent<Void>
implements IConnective, ILayeredComponent, IGerberComponentSimple, IPCBLayerComponent {
private static final long serialVersionUID = 1L;
@@ -116,6 +118,7 @@ public class CopperTrace extends AbstractLeadedComponent<Void> implements IConne
}
@EditableProperty
@Override
public PCBLayer getLayer() {
if (layer == null) {
layer = PCBLayer._1;
@@ -123,6 +126,7 @@ public class CopperTrace extends AbstractLeadedComponent<Void> implements IConne
return layer;
}
@Override
public void setLayer(PCBLayer layer) {
this.layer = layer;
}
@@ -38,6 +38,7 @@ import org.diylc.core.IConnective;
import org.diylc.core.IDIYComponent;
import org.diylc.core.IDrawingObserver;
import org.diylc.core.ILayeredComponent;
import org.diylc.core.IPCBLayerComponent;
import org.diylc.core.annotations.AiNote.Scope;
import org.diylc.core.annotations.AiNote;
import org.diylc.core.annotations.BomPolicy;
@@ -58,7 +59,8 @@ import org.diylc.core.measures.SizeUnit;
zOrder = IDIYComponent.TRACE, bomPolicy = BomPolicy.NEVER_SHOW,
keywordPolicy = KeywordPolicy.SHOW_TAG, keywordTag = "PCB", transformer = SimpleComponentTransformer.class,
enableCache = true, creationMethod = CreationMethod.POINT_BY_POINT)
public class CurvedTrace extends AbstractCurvedComponent<Void> implements IConnective, ILayeredComponent, IGerberComponentSimple {
public class CurvedTrace extends AbstractCurvedComponent<Void>
implements IConnective, ILayeredComponent, IGerberComponentSimple, IPCBLayerComponent {
private static final long serialVersionUID = 1L;
@@ -104,6 +106,7 @@ public class CurvedTrace extends AbstractCurvedComponent<Void> implements IConne
}
@EditableProperty
@Override
public PCBLayer getLayer() {
if (layer == null) {
layer = PCBLayer._1;
@@ -111,6 +114,7 @@ public class CurvedTrace extends AbstractCurvedComponent<Void> implements IConne
return layer;
}
@Override
public void setLayer(PCBLayer layer) {
this.layer = layer;
}
@@ -38,6 +38,7 @@ import org.diylc.core.ComponentState;
import org.diylc.core.IDIYComponent;
import org.diylc.core.IDrawingObserver;
import org.diylc.core.ILayeredComponent;
import org.diylc.core.IPCBLayerComponent;
import org.diylc.core.Project;
import org.diylc.core.VisibilityPolicy;
import org.diylc.core.annotations.BomPolicy;
@@ -53,7 +54,7 @@ import org.diylc.core.measures.SizeUnit;
instanceNamePrefix = "GF", description = "Polygonal ground fill area", zOrder = IDIYComponent.TRACE,
bomPolicy = BomPolicy.NEVER_SHOW, autoEdit = false, transformer = SimpleComponentTransformer.class)
public class GroundFill extends AbstractTransparentComponent<Void>
implements ILayeredComponent, IGerberComponentSimple {
implements ILayeredComponent, IGerberComponentSimple, IPCBLayerComponent {
private static final long serialVersionUID = 1L;
@@ -152,6 +153,7 @@ public class GroundFill extends AbstractTransparentComponent<Void>
}
@EditableProperty
@Override
public PCBLayer getLayer() {
if (layer == null) {
layer = PCBLayer._1;
@@ -159,6 +161,7 @@ public class GroundFill extends AbstractTransparentComponent<Void>
return layer;
}
@Override
public void setLayer(PCBLayer layer) {
this.layer = layer;
}
@@ -54,7 +54,8 @@ import org.diylc.utils.Constants;
author = "Branislav Stojkovic", description = "Horizontal PCB terminal block with configurable pitch",
zOrder = IDIYComponent.COMPONENT, instanceNamePrefix = "TB",
enableCache = true, transformer = PCBTerminalBlockTransformer.class)
public class PCBTerminalBlock extends AbstractTransparentComponent<PCBTerminalBlockCount> implements IGerberComponentSimple, IBoardSideComponent {
public class PCBTerminalBlock extends AbstractTransparentComponent<PCBTerminalBlockCount>
implements IGerberComponentSimple, IBoardSideComponent {
private static final long serialVersionUID = 1L;
@@ -35,6 +35,7 @@ import org.diylc.core.IConnective;
import org.diylc.core.IDIYComponent;
import org.diylc.core.IDrawingObserver;
import org.diylc.core.ILayeredComponent;
import org.diylc.core.IPCBLayerComponent;
import org.diylc.core.Project;
import org.diylc.core.VisibilityPolicy;
import org.diylc.core.annotations.AiNote.Scope;
@@ -60,7 +61,7 @@ import org.diylc.utils.Constants;
keywordPolicy = KeywordPolicy.SHOW_TAG, keywordTag = "PCB",
transformer = SimpleComponentTransformer.class, enableCache = true)
public class SolderPad extends AbstractTransparentComponent<Void>
implements IConnective, ILayeredComponent, IGerberComponentCustom {
implements IConnective, ILayeredComponent, IGerberComponentCustom, IPCBLayerComponent {
private static final long serialVersionUID = 1L;
@@ -190,6 +191,7 @@ public class SolderPad extends AbstractTransparentComponent<Void>
}
@EditableProperty
@Override
public PCBLayer getLayer() {
if (layer == null) {
layer = PCBLayer._1;
@@ -197,6 +199,7 @@ public class SolderPad extends AbstractTransparentComponent<Void>
return layer;
}
@Override
public void setLayer(PCBLayer layer) {
this.layer = layer;
}
@@ -64,7 +64,8 @@ import org.diylc.utils.Constants;
description = "Cliff-style closed panel mount 1/4\" phono jack",
zOrder = IDIYComponent.COMPONENT, instanceNamePrefix = "J", transformer = CliffJackTransformer.class,
enableCache = true)
public class CliffJack1_4 extends AbstractMultiPartComponent<JackType> implements IGerberComponentSimple, IBoardSideComponent {
public class CliffJack1_4 extends AbstractMultiPartComponent<JackType>
implements IGerberComponentSimple, IBoardSideComponent {
private static final long serialVersionUID = 1L;
@@ -58,7 +58,8 @@ import org.diylc.utils.Constants;
author = "Branislav Stojkovic", description = "Cliff-style closed 1/8\" phono jack",
zOrder = IDIYComponent.COMPONENT, instanceNamePrefix = "J",
transformer = CliffJackTransformer.class, enableCache = true)
public class CliffJack1_8 extends AbstractMultiPartComponent<JackType> implements IGerberComponentSimple, IBoardSideComponent {
public class CliffJack1_8 extends AbstractMultiPartComponent<JackType>
implements IGerberComponentSimple, IBoardSideComponent {
private static final long serialVersionUID = 1L;
@@ -64,7 +64,8 @@ import org.diylc.utils.Constants;
@ComponentDescriptor(name = "Closed 1/4\" Jack", category = "Electro-Mechanical", author = "Branislav Stojkovic",
description = "Enclosed panel mount 1/4\" phono jack", zOrder = IDIYComponent.COMPONENT,
instanceNamePrefix = "J", transformer = ClosedJackTransformer.class, enableCache = true)
public class ClosedJack1_4 extends AbstractMultiPartComponent<JackType> implements IGerberComponentSimple, IBoardSideComponent {
public class ClosedJack1_4 extends AbstractMultiPartComponent<JackType>
implements IGerberComponentSimple, IBoardSideComponent {
private static final long serialVersionUID = 1L;
@@ -60,7 +60,8 @@ import org.diylc.utils.Constants;
@ComponentDescriptor(name = "Mini Relay", author = "Branislav Stojkovic", category = "Electro-Mechanical",
instanceNamePrefix = "RY", description = "Miniature PCB mount relay, like Omron G5V-1/G5V-2 or Fujitsu FTR-B3",
zOrder = IDIYComponent.COMPONENT, enableCache = true)
public class MiniRelay extends AbstractLabeledComponent<String> implements IGerberComponentSimple, IBoardSideComponent {
public class MiniRelay extends AbstractLabeledComponent<String>
implements IGerberComponentSimple, IBoardSideComponent {
private static final long serialVersionUID = 1L;
@@ -63,7 +63,8 @@ import org.diylc.utils.SwitchUtils;
@ComponentDescriptor(name = "Mini Toggle Switch", category = "Electro-Mechanical", author = "Branislav Stojkovic",
description = "Panel mounted mini toggle switch", zOrder = IDIYComponent.COMPONENT,
instanceNamePrefix = "SW", enableCache = true, transformer = MiniToggleSwitchTransformer.class)
public class MiniToggleSwitch extends AbstractTransparentComponent<ToggleSwitchType> implements ISwitch, ILayeredComponent, IGerberComponentSimple, IBoardSideComponent {
public class MiniToggleSwitch extends AbstractTransparentComponent<ToggleSwitchType>
implements ISwitch, ILayeredComponent, IGerberComponentSimple, IBoardSideComponent {
private static final long serialVersionUID = 1L;
@@ -58,7 +58,8 @@ import org.diylc.utils.Constants;
@ComponentDescriptor(name = "Plastic DC Jack", category = "Electro-Mechanical", author = "Branislav Stojkovic",
description = "Panel mount plastic DC jack", zOrder = IDIYComponent.COMPONENT,
instanceNamePrefix = "J", autoEdit = false, enableCache = true)
public class PlasticDCJack extends AbstractMultiPartComponent<String> implements IGerberComponentSimple, IBoardSideComponent {
public class PlasticDCJack extends AbstractMultiPartComponent<String>
implements IGerberComponentSimple, IBoardSideComponent {
private static final long serialVersionUID = 1L;
@@ -63,7 +63,8 @@ import org.diylc.utils.Constants;
description = "Single pole rotary switch, typically used for impedance selector",
zOrder = IDIYComponent.COMPONENT, transformer = PotentiometerTransformer.class,
enableCache = true)
public class RotarySelectorSwitch extends AbstractMultiPartComponent<String> implements IGerberComponentSimple, IBoardSideComponent {
public class RotarySelectorSwitch extends AbstractMultiPartComponent<String>
implements IGerberComponentSimple, IBoardSideComponent {
private static final long serialVersionUID = 1L;
@@ -62,7 +62,8 @@ import org.diylc.utils.Constants;
instanceNamePrefix = "SW", description = "Sealed plastic rotary switch in several different switching configurations",
zOrder = IDIYComponent.COMPONENT, keywordPolicy = KeywordPolicy.SHOW_VALUE, enableCache = true,
transformer = AngledComponentTransformer.class)
public class RotarySwitchSealed extends AbstractAngledComponent<RotarySwitchSealedType> implements ISwitch, IGerberComponentSimple, IBoardSideComponent {
public class RotarySwitchSealed extends AbstractAngledComponent<RotarySwitchSealedType>
implements ISwitch, IGerberComponentSimple, IBoardSideComponent {
private static final int OUTER_ANGLE_OFFSET = 15;
private static final int INNER_ANGLE_OFFSET = 30;
@@ -62,7 +62,8 @@ import org.diylc.utils.SwitchUtils;
author = "Branislav Stojkovic", description = "Panel mounted slide switch",
zOrder = IDIYComponent.COMPONENT, instanceNamePrefix = "SW",
enableCache = true, transformer = MiniToggleSwitchTransformer.class)
public class SlideSwitch extends AbstractTransparentComponent<SlideSwitchType> implements ISwitch, IGerberComponentSimple, IBoardSideComponent {
public class SlideSwitch extends AbstractTransparentComponent<SlideSwitchType>
implements ISwitch, IGerberComponentSimple, IBoardSideComponent {
private static final long serialVersionUID = 1L;
@@ -41,6 +41,7 @@ import org.diylc.core.ComponentState;
import org.diylc.core.IDIYComponent;
import org.diylc.core.IDrawingObserver;
import org.diylc.core.ILayeredComponent;
import org.diylc.core.IPCBLayerComponent;
import org.diylc.core.Project;
import org.diylc.core.VisibilityPolicy;
import org.diylc.core.annotations.BomPolicy;
@@ -53,7 +54,8 @@ import org.diylc.core.gerber.IGerberDrawingObserver;
@ComponentDescriptor(name = "PCB Text", author = "Branislav Stojkovic", category = "Misc",
description = "Mirrored text for PCB artwork", instanceNamePrefix = "L", zOrder = IDIYComponent.TRACE,
flexibleZOrder = false, bomPolicy = BomPolicy.NEVER_SHOW, transformer = TextTransformer.class)
public class PCBText extends AbstractComponent<Void> implements ILayeredComponent, IGerberComponentCustom {
public class PCBText extends AbstractComponent<Void>
implements ILayeredComponent, IGerberComponentCustom, IPCBLayerComponent {
public static String DEFAULT_TEXT = "Double click to edit text";
@@ -324,6 +326,7 @@ public class PCBText extends AbstractComponent<Void> implements ILayeredComponen
}
@EditableProperty
@Override
public PCBLayer getLayer() {
if (layer == null) {
layer = PCBLayer._1;
@@ -331,6 +334,7 @@ public class PCBText extends AbstractComponent<Void> implements ILayeredComponen
return layer;
}
@Override
public void setLayer(PCBLayer layer) {
this.layer = layer;
}
@@ -36,7 +36,8 @@ import org.diylc.core.measures.Size;
import org.diylc.core.measures.SizeUnit;
import org.diylc.utils.Constants;
public abstract class AbstractFilmCapacitor extends AbstractLeadedComponent<Capacitance> implements IGerberComponentSimple, IBoardSideComponent {
public abstract class AbstractFilmCapacitor extends AbstractLeadedComponent<Capacitance>
implements IGerberComponentSimple, IBoardSideComponent {
private static final long serialVersionUID = 1L;
@@ -33,7 +33,8 @@ import org.diylc.core.annotations.PositiveMeasureValidator;
import org.diylc.core.gerber.IGerberComponentSimple;
import org.diylc.core.measures.Resistance;
public abstract class AbstractPotentiometer extends AbstractLabeledComponent<Resistance> implements IGerberComponentSimple, IBoardSideComponent {
public abstract class AbstractPotentiometer extends AbstractLabeledComponent<Resistance>
implements IGerberComponentSimple, IBoardSideComponent {
private static final long serialVersionUID = 1L;
@@ -41,7 +41,8 @@ import org.diylc.core.measures.SizeUnit;
* @param <T>
*/
@AiNote("The two control points are the holes; the body's own leads are 'Lead Spacing' apart and centred between them, with lead wire drawn out to each hole. Place the points exactly 'Lead Spacing' apart for straight leads")
public abstract class AbstractRadialComponent<T> extends AbstractLeadedComponent<T> implements IGerberComponentSimple, IBoardSideComponent {
public abstract class AbstractRadialComponent<T> extends AbstractLeadedComponent<T>
implements IGerberComponentSimple, IBoardSideComponent {
private static final long serialVersionUID = 1L;
@@ -64,7 +64,8 @@ import org.diylc.utils.Constants;
@ComponentDescriptor(name = "Mini Signal Transformer", author = "Branislav Stojkovic", category = "Passive",
instanceNamePrefix = "TR", description = "Miniature PCB-mount signal transformer with EI core",
zOrder = IDIYComponent.COMPONENT, keywordPolicy = KeywordPolicy.SHOW_VALUE)
public class AudioTransformer extends AbstractMultiPartComponent<String> implements IGerberComponentSimple, IBoardSideComponent {
public class AudioTransformer extends AbstractMultiPartComponent<String>
implements IGerberComponentSimple, IBoardSideComponent {
private static final long serialVersionUID = 1L;
@@ -55,7 +55,8 @@ import org.diylc.utils.Constants;
description = "Axial electrolytic capacitor, similar to Sprague Atom, F&T, etc",
zOrder = IDIYComponent.COMPONENT, transformer = SimpleComponentTransformer.class,
enableCache = true, enableDatasheet = true, datasheetCreationStepCount = 3)
public class AxialElectrolyticCapacitor extends AbstractLeadedComponent<Capacitance> implements IDatasheetSupport, IGerberComponentSimple, IBoardSideComponent {
public class AxialElectrolyticCapacitor extends AbstractLeadedComponent<Capacitance>
implements IDatasheetSupport, IGerberComponentSimple, IBoardSideComponent {
private static final long serialVersionUID = 1L;
@@ -45,7 +45,8 @@ import org.diylc.core.measures.SizeUnit;
creationMethod = CreationMethod.POINT_BY_POINT, instanceNamePrefix = "X",
description = "Radial crystal oscillator", zOrder = IDIYComponent.COMPONENT,
transformer = SimpleComponentTransformer.class)
public class CrystalOscillator extends AbstractLeadedComponent<Frequency> implements IGerberComponentSimple, IBoardSideComponent {
public class CrystalOscillator extends AbstractLeadedComponent<Frequency>
implements IGerberComponentSimple, IBoardSideComponent {
private static final long serialVersionUID = 1L;
@@ -65,7 +65,8 @@ import org.diylc.utils.Constants;
@ComponentDescriptor(name = "Multi-Section Capacitor", author = "Branislav Stojkovic", category = "Passive",
instanceNamePrefix = "C", description = "Multi-section vertically mounted electrolytic capacitor, similar to JJ, CE and others",
zOrder = IDIYComponent.COMPONENT, keywordPolicy = KeywordPolicy.SHOW_VALUE, enableCache = true)
public class MultiSectionCapacitor extends AbstractLabeledComponent<Capacitance[]> implements IGerberComponentSimple, IBoardSideComponent {
public class MultiSectionCapacitor extends AbstractLabeledComponent<Capacitance[]>
implements IGerberComponentSimple, IBoardSideComponent {
private static final long serialVersionUID = 1L;
@@ -47,7 +47,8 @@ import org.diylc.core.measures.SizeUnit;
creationMethod = CreationMethod.POINT_BY_POINT, instanceNamePrefix = "L",
description = "Ferrite core toroidal inductor mounted vertically", zOrder = IDIYComponent.COMPONENT,
transformer = SimpleComponentTransformer.class)
public class ToroidalInductor extends AbstractLeadedComponent<Inductance> implements IGerberComponentSimple, IBoardSideComponent {
public class ToroidalInductor extends AbstractLeadedComponent<Inductance>
implements IGerberComponentSimple, IBoardSideComponent {
private static final long serialVersionUID = 1L;
@@ -59,7 +59,8 @@ import org.diylc.utils.Constants;
category = "Semiconductors", instanceNamePrefix = "BR",
description = "Few variations of bridge rectifier chips", zOrder = IDIYComponent.COMPONENT,
keywordPolicy = KeywordPolicy.SHOW_VALUE, enableCache = true)
public class BridgeRectifier extends AbstractLabeledComponent<String> implements IGerberComponentSimple, IBoardSideComponent {
public class BridgeRectifier extends AbstractLabeledComponent<String>
implements IGerberComponentSimple, IBoardSideComponent {
private static final long serialVersionUID = 1L;
@@ -51,7 +51,8 @@ import org.diylc.core.measures.Size;
import org.diylc.core.measures.SizeUnit;
import org.diylc.utils.Constants;
public abstract class PassiveSMDComponent<T> extends AbstractLabeledComponent<T> implements IGerberComponentSimple, IBoardSideComponent {
public abstract class PassiveSMDComponent<T> extends AbstractLabeledComponent<T>
implements IGerberComponentSimple, IBoardSideComponent {
private static final long serialVersionUID = 1L;
@@ -24,7 +24,7 @@ package org.diylc.swing.plugins.file;
import org.diylc.common.DrawOption;
import org.diylc.common.IPlugInPort;
import org.diylc.common.PCBLayer;
import org.diylc.core.IBoardSideComponent;
import org.diylc.core.IBoard;
import org.diylc.core.IDIYComponent;
import org.diylc.filter.PCBLayerFilter;
import org.diylc.filter.SilkScreenFilter;
@@ -59,11 +59,12 @@ public class SilkScreenDrawingProvider implements IDrawingProvider {
new SilkScreenFilter(page == 0 ? PCBLayer._2 : PCBLayer._1), zoomFactor, null, null);
}
// the top side, followed by the bottom side when a part is mounted there
// the top side, followed by the bottom side when it has silkscreen other than board outlines
@Override
public int getPageCount() {
SilkScreenFilter bottom = new SilkScreenFilter(PCBLayer._1);
for (IDIYComponent<?> c : plugInPort.getCurrentProject().getComponents()) {
if (c instanceof IBoardSideComponent && ((IBoardSideComponent) c).getBoardSide() == PCBLayer._1) {
if (!(c instanceof IBoard) && bottom.testComponent(c)) {
return 2;
}
}
@@ -24,7 +24,6 @@ package org.diylc.swing.plugins.file;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Collections;
import java.util.EnumSet;
@@ -37,6 +36,7 @@ import org.diylc.common.DrawOption;
import org.diylc.common.IPlugInPort;
import org.diylc.common.PCBLayer;
import org.diylc.core.IDIYComponent;
import org.diylc.core.IPCBLayerComponent;
import org.diylc.filter.PCBLayerFilter;
/**
@@ -72,12 +72,8 @@ public class TraceMaskDrawingProvider implements IDrawingProvider {
private PCBLayer[] getUsedLayers() {
Set<PCBLayer> layers = EnumSet.noneOf(PCBLayer.class);
for (IDIYComponent<?> c : plugInPort.getCurrentProject().getComponents()) {
Class<?> clazz = c.getClass();
try {
Method m = clazz.getMethod("getLayer");
PCBLayer l = (PCBLayer) m.invoke(c);
layers.add(l);
} catch (Exception e) {
if (c instanceof IPCBLayerComponent copper) {
layers.add(copper.getLayer());
}
}
List<PCBLayer> sorted = new ArrayList<PCBLayer>(layers);
@@ -27,6 +27,7 @@ import org.diylc.common.IPlugInPort;
import org.diylc.common.PCBLayer;
import org.diylc.components.boards.BlankBoard;
import org.diylc.components.chassis.ChassisPanel;
import org.diylc.components.connectivity.Jumper;
import org.diylc.components.passive.Resistor;
import org.diylc.components.composite.CompositeComponent;
import org.diylc.core.BoardUndersideDisplay;
@@ -296,7 +297,8 @@ public class PresenterTests extends TestBase {
instantiateTwoClick("Passive", "Resistor", new Point(1000, 1000), new Point(1200, 1000));
IDIYComponent<?> resistor = presenter.getCurrentProject().getComponents().get(0);
IBoard board = boardUnder(resistor);
Point2D start = new Point2D.Double(resistor.getControlPoint(1).getX(), resistor.getControlPoint(1).getY());
Point2D start = new Point2D.Double(resistor.getControlPoint(1).getX(),
resistor.getControlPoint(1).getY());
Point inMirror = toScreen(UndersideMirror.createTransform(board).transform(start, null));
presenter.mouseMoved(inMirror, false, false, false);
@@ -314,8 +316,10 @@ public class PresenterTests extends TestBase {
instantiateTwoClick("Passive", "Resistor", new Point(1000, 1000), new Point(1200, 1000));
IDIYComponent<?> resistor = presenter.getCurrentProject().getComponents().get(0);
IBoard board = boardUnder(resistor);
Point2D start0 = new Point2D.Double(resistor.getControlPoint(0).getX(), resistor.getControlPoint(0).getY());
Point2D start1 = new Point2D.Double(resistor.getControlPoint(1).getX(), resistor.getControlPoint(1).getY());
Point2D start0 = new Point2D.Double(resistor.getControlPoint(0).getX(),
resistor.getControlPoint(0).getY());
Point2D start1 = new Point2D.Double(resistor.getControlPoint(1).getX(),
resistor.getControlPoint(1).getY());
// the hit test reads the areas recorded while drawing
BufferedImage img = new BufferedImage(100, 100, BufferedImage.TYPE_INT_ARGB);
Graphics2D g2d = img.createGraphics();
@@ -399,6 +403,39 @@ public class PresenterTests extends TestBase {
// a board reaching 200px past the component on every side, with its underside mirror below it;
// the component stays selected
@Test
public void testDragThroughUndersideMirrorIgnoresPartsTheMirrorDoesNotShow() {
instantiateTwoClick("Passive", "Resistor", new Point(1000, 1000), new Point(1200, 1000));
IDIYComponent<?> resistor = presenter.getCurrentProject().getComponents().get(0);
IBoard board = boardUnder(resistor);
// a jumper drawn over the resistor with both ends off the board, so the mirror does not show it
double y = resistor.getControlPoint(0).getY();
Jumper jumper = new Jumper();
jumper.setControlPoint(new Point2D.Double(board.getBoardRectangle().getMinX() - 100, y), 0);
jumper.setControlPoint(new Point2D.Double(board.getBoardRectangle().getMaxX() + 100, y), 1);
presenter.getCurrentProject().getComponents().add(jumper);
Point2D jumperStart =
new Point2D.Double(jumper.getControlPoint(0).getX(), jumper.getControlPoint(0).getY());
Point2D resistorStart =
new Point2D.Double(resistor.getControlPoint(0).getX(), resistor.getControlPoint(0).getY());
BufferedImage img = new BufferedImage(100, 100, BufferedImage.TYPE_INT_ARGB);
Graphics2D g2d = img.createGraphics();
presenter.draw(g2d, EnumSet.of(DrawOption.ZOOM), null, null, null, null);
g2d.dispose();
Point2D middle = new Point2D.Double(
(resistor.getControlPoint(0).getX() + resistor.getControlPoint(1).getX()) / 2,
resistor.getControlPoint(0).getY());
Point inMirror = toScreen(UndersideMirror.createTransform(board).transform(middle, null));
presenter.mouseMoved(inMirror, false, false, false);
presenter.dragStarted(inMirror, java.awt.dnd.DnDConstants.ACTION_MOVE, false, false);
presenter.dragOver(new Point(inMirror.x + 100, inMirror.y + 100));
presenter.dragEnded(new Point(inMirror.x + 100, inMirror.y + 100));
Assert.assertTrue(resistor.getControlPoint(0).getX() > resistorStart.getX());
Assert.assertEquals(jumperStart, jumper.getControlPoint(0));
}
private IBoard boardUnder(IDIYComponent<?> component) {
Point2D first = component.getControlPoint(0);
Point2D last = component.getControlPoint(component.getControlPointCount() - 1);
@@ -27,6 +27,7 @@ import java.awt.Point;
import org.diylc.TestBase;
import org.diylc.common.PCBLayer;
import org.diylc.components.connectivity.Dot;
import org.diylc.components.passive.Resistor;
import org.junit.Test;
@@ -47,4 +48,14 @@ public class SilkScreenDrawingProviderTest extends TestBase {
assertEquals(2, new SilkScreenDrawingProvider(presenter).getPageCount());
}
// a dot has no silkscreen, so a bottom page would show nothing but the board outline
@Test
public void testNoBottomPageForBottomDot() {
Dot dot = new Dot();
dot.setBoardSide(PCBLayer._1);
presenter.getCurrentProject().getComponents().add(dot);
assertEquals(1, new SilkScreenDrawingProvider(presenter).getPageCount());
}
}
+24 -11
View File
@@ -24,6 +24,11 @@ projects; the open questions below remain.
default (resistors, film and radial capacitors, diodes, LEDs, transistor packages, DIL/SIL ICs,
SMD passives, switches, jacks, relays, transformers, tube sockets, ...). None of them emit copper.
Their pads are separate `SolderPad` components, which already have a layer.
- `IBoardSideComponent.sideOf(component)` is the one place that answers which side a component is
on (Top for components without a side); drawing, the Gerber export and the silkscreen filter all
use it. Copper that sits on a layer - `CopperTrace`, `CurvedTrace`, `GroundFill`, `SolderPad` and
`PCBText` - implements `IPCBLayerComponent` (`getLayer`/`setLayer`), which `PCBLayerFilter`, the
trace-mask print-out and `MirrorPlacement` use instead of looking the methods up by name.
- One method decides the layer for a simple component: `GerberExporter.drawComponent`. It is
package-private so that `GerberExporterTest` can call it. It uses `getGerberLayer()`, flipped
with `GerberLayer.flip()` when the component is an `IBoardSideComponent` on `_1` (Bottom).
@@ -55,7 +60,7 @@ projects; the open questions below remain.
- **Canvas rendering (done)**: bottom-side parts use the far-side fade from `copper-side-view.md`,
set centrally rather than in each component. Wherever a component is drawn - the main loop in
`DrawingManager`, `DrawingCache` rendering an image, and the Underside Display mirror - the
wrapper's undeclared side is set to `DrawingManager.getMountedSide(component)` with
wrapper's undeclared side is set to `IBoardSideComponent.sideOf(component)` with
`G2DWrapper.setUndeclaredSide`. A bottom part is therefore faded in the main view (with Fade
Copper Under Board on) and solid in the mirror, the same as bottom copper. In the main loop the
side is set after the locked composite, which is set on the canvas directly and would otherwise
@@ -89,7 +94,8 @@ projects; the open questions below remain.
### diylc-swing
- **PCB print-outs (done)**: `SilkScreenDrawingProvider` prints the top side, and a second page for
the bottom when a part is mounted there, so a project without bottom parts still prints one page.
the bottom when the bottom has silkscreen other than board outlines, so a project without bottom
parts, or with only a bottom `Dot`, still prints one page.
`SilkScreenFilter` takes the side: a part with a Board Side goes on its own side's page, parts
without one count as top, and board outlines go on both pages so that they line up. Neither
page is mirrored or captioned, the same as the trace-mask pages.
@@ -114,22 +120,24 @@ order.
delete, copy and the popup menu all work unchanged. Selection highlighting and the selected
control points are drawn on both copies.
- **Stage 2: dragging in the mirror (done).** A drag that starts on a part in a mirror, or on a
control point of a selected part there, keeps that mirror's reflection in
`Presenter.dragMirrorTx` and reflects every mouse point of the drag before it reaches the existing
drag code. Movement along the mirrored axis is therefore reversed, which is what the user sees,
control point of a selected part there, keeps that mirror's board in `Presenter.dragMirrorBoard`
and reflects every mouse point of the drag through it (`UndersideMirror.reflect`) before it
reaches the existing drag code. The part it picks is one the mirror shows, the same one a click
there selects, even where something else covers the reflected point on the board. Movement along the mirrored axis is therefore reversed, which is what the user sees,
and snapping and the shift axis lock apply to the reflected point. Control-point hover
(`Presenter.findControlPointsAt`) and the hand cursor look through the mirror when the main view
has nothing. A drag that starts on empty mirror space still draws a selection rectangle in
screen space. Moving the board, or changing its Underside Offset, cancels nothing; the mirror
has nothing. Where mirrors overlap, the topmost board's wins, here and for placing. A drag that
starts on empty mirror space still draws a selection rectangle in screen space. Moving the board, or changing its Underside Offset, cancels nothing; the mirror
simply follows.
- **Stage 3: placing in the mirror (done).** While a component is being placed,
`Presenter.placementMirrorBoard` holds the board whose mirror the cursor is in; a single-click
part follows the cursor in and out of mirrors, and a part placed point by point stays in the
mirror its first point went into. The cursor is reflected before it is snapped to the grid, so
the part lands on the grid, and `DrawingManager.setSlotMirrorBoard` draws the part being placed
through that mirror, under the cursor. On placement, `MirrorPlacement.placeOnBottom` sets an
`IBoardSideComponent` to `_1` (Bottom) and a component with a `PCBLayer` (`setLayer`, found the
same way `PCBLayerFilter` reads `getLayer`) to `_1`; nothing is mirrored. Anything else, such as
through that mirror, under the cursor, seen from below like the parts already there. The board is
forgotten when a project is loaded. On placement, `MirrorPlacement.placeOnBottom` sets an
`IBoardSideComponent` to `_1` (Bottom) and an `IPCBLayerComponent` to layer `_1`; nothing is
mirrored. Anything else, such as
a jumper, eyelet or wire, is placed at the reflected point unchanged. A part dragged in from
the component tree or the toolbox goes through `Presenter.dragOver` and `dragEnded` instead,
which pick the mirror and put the part on the bottom the same way.
@@ -152,8 +160,13 @@ order.
`PresenterTests.testPlaceThroughUndersideMirror` and `testPlaceUnsidedPartThroughUndersideMirror`
place a resistor and an eyelet in a mirror and check that they land on the board, the resistor on
the bottom; `testDropFromComponentTreeIntoUndersideMirror` does the same for a drag and drop.
`testDragThroughUndersideMirrorIgnoresPartsTheMirrorDoesNotShow` drags a resistor in the mirror
under a jumper that only crosses the board, and `DrawingManagerTest`
`.testPartBeingPlacedInMirrorSeenFromBelow` checks that the part being placed is drawn as the
mirror draws placed parts.
- `SilkScreenFilterTest` covers which side's page each part and board outline goes on, and
`SilkScreenDrawingProviderTest` checks that the bottom page appears only for a bottom part.
`SilkScreenDrawingProviderTest` checks that the bottom page appears only for a bottom part with
silkscreen, not for a bottom `Dot`.
- `DrawingManagerTest` checks rendered pixels for a part that reports its own side: a bottom part
is faded only with `FADE_FAR_SIDE`, also when drawn from the image cache; it is solid in the
mirror, where a top part is faded; and a locked bottom part is faded once, not twice.