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) {