From 13b31e6dd57d611f25a6b8b4fafc6786f3af13bf Mon Sep 17 00:00:00 2001 From: Bane Stojkovic Date: Wed, 11 Nov 2026 17:32:46 +0100 Subject: [PATCH] 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 --- .../org/diylc/core/IBoardSideComponent.java | 9 ++ .../org/diylc/core/IPCBLayerComponent.java | 38 ++++++++ .../org/diylc/core/gerber/GerberExporter.java | 3 +- .../java/org/diylc/filter/PCBLayerFilter.java | 12 +-- .../org/diylc/filter/SilkScreenFilter.java | 8 +- .../org/diylc/presenter/DrawingCache.java | 3 +- .../org/diylc/presenter/DrawingManager.java | 51 ++++++----- .../org/diylc/presenter/MirrorPlacement.java | 26 ++---- .../java/org/diylc/presenter/Presenter.java | 88 ++++++++++--------- .../org/diylc/presenter/UndersideMirror.java | 14 ++- .../diylc/presenter/DrawingManagerTest.java | 58 ++++++++++-- .../diylc/presenter/MirrorPlacementTest.java | 5 +- .../diylc/presenter/UndersideMirrorTest.java | 6 +- .../components/connectivity/CopperTrace.java | 6 +- .../components/connectivity/CurvedTrace.java | 6 +- .../components/connectivity/GroundFill.java | 5 +- .../connectivity/PCBTerminalBlock.java | 3 +- .../components/connectivity/SolderPad.java | 5 +- .../electromechanical/CliffJack1_4.java | 3 +- .../electromechanical/CliffJack1_8.java | 3 +- .../electromechanical/ClosedJack1_4.java | 3 +- .../electromechanical/MiniRelay.java | 3 +- .../electromechanical/MiniToggleSwitch.java | 3 +- .../electromechanical/PlasticDCJack.java | 3 +- .../RotarySelectorSwitch.java | 3 +- .../electromechanical/RotarySwitchSealed.java | 3 +- .../electromechanical/SlideSwitch.java | 3 +- .../org/diylc/components/misc/PCBText.java | 6 +- .../passive/AbstractFilmCapacitor.java | 3 +- .../passive/AbstractPotentiometer.java | 3 +- .../passive/AbstractRadialComponent.java | 3 +- .../components/passive/AudioTransformer.java | 3 +- .../passive/AxialElectrolyticCapacitor.java | 3 +- .../components/passive/CrystalOscillator.java | 3 +- .../passive/MultiSectionCapacitor.java | 3 +- .../components/passive/ToroidalInductor.java | 3 +- .../semiconductors/BridgeRectifier.java | 3 +- .../components/smd/PassiveSMDComponent.java | 3 +- .../file/SilkScreenDrawingProvider.java | 7 +- .../file/TraceMaskDrawingProvider.java | 10 +-- .../test/java/org/diylc/PresenterTests.java | 43 ++++++++- .../file/SilkScreenDrawingProviderTest.java | 11 +++ diylc/docs/plans/bottom-side-components.md | 35 +++++--- 43 files changed, 345 insertions(+), 170 deletions(-) create mode 100644 diylc/diylc-core/src/main/java/org/diylc/core/IPCBLayerComponent.java diff --git a/diylc/diylc-core/src/main/java/org/diylc/core/IBoardSideComponent.java b/diylc/diylc-core/src/main/java/org/diylc/core/IBoardSideComponent.java index f8357afe7..98410ad12 100644 --- a/diylc/diylc-core/src/main/java/org/diylc/core/IBoardSideComponent.java +++ b/diylc/diylc-core/src/main/java/org/diylc/core/IBoardSideComponent.java @@ -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; + } } diff --git a/diylc/diylc-core/src/main/java/org/diylc/core/IPCBLayerComponent.java b/diylc/diylc-core/src/main/java/org/diylc/core/IPCBLayerComponent.java new file mode 100644 index 000000000..f2a33327f --- /dev/null +++ b/diylc/diylc-core/src/main/java/org/diylc/core/IPCBLayerComponent.java @@ -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 . + +*/ +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); +} diff --git a/diylc/diylc-core/src/main/java/org/diylc/core/gerber/GerberExporter.java b/diylc/diylc-core/src/main/java/org/diylc/core/gerber/GerberExporter.java index a7315c8da..e485b273c 100644 --- a/diylc/diylc-core/src/main/java/org/diylc/core/gerber/GerberExporter.java +++ b/diylc/diylc-core/src/main/java/org/diylc/core/gerber/GerberExporter.java @@ -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(); } diff --git a/diylc/diylc-core/src/main/java/org/diylc/filter/PCBLayerFilter.java b/diylc/diylc-core/src/main/java/org/diylc/filter/PCBLayerFilter.java index e01c07074..8a01d24c7 100644 --- a/diylc/diylc-core/src/main/java/org/diylc/filter/PCBLayerFilter.java +++ b/diylc/diylc-core/src/main/java/org/diylc/filter/PCBLayerFilter.java @@ -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; } } diff --git a/diylc/diylc-core/src/main/java/org/diylc/filter/SilkScreenFilter.java b/diylc/diylc-core/src/main/java/org/diylc/filter/SilkScreenFilter.java index cc4da55ee..d833e2f13 100644 --- a/diylc/diylc-core/src/main/java/org/diylc/filter/SilkScreenFilter.java +++ b/diylc/diylc-core/src/main/java/org/diylc/filter/SilkScreenFilter.java @@ -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 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; - } } diff --git a/diylc/diylc-core/src/main/java/org/diylc/presenter/DrawingCache.java b/diylc/diylc-core/src/main/java/org/diylc/presenter/DrawingCache.java index 717c20b6c..9c85079e5 100644 --- a/diylc/diylc-core/src/main/java/org/diylc/presenter/DrawingCache.java +++ b/diylc/diylc-core/src/main/java/org/diylc/presenter/DrawingCache.java @@ -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(); diff --git a/diylc/diylc-core/src/main/java/org/diylc/presenter/DrawingManager.java b/diylc/diylc-core/src/main/java/org/diylc/presenter/DrawingManager.java index 9f006e3b5..cdab1e2fd 100644 --- a/diylc/diylc-core/src/main/java/org/diylc/presenter/DrawingManager.java +++ b/diylc/diylc-core/src/main/java/org/diylc/presenter/DrawingManager.java @@ -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 drawOptions, Double scaleFactor, Rectangle2D visibleRect, IDIYComponent component, G2DWrapper g2dWrapper, ComponentState state, double zoom, int i, boolean outlineMode, boolean cacheable) { diff --git a/diylc/diylc-core/src/main/java/org/diylc/presenter/MirrorPlacement.java b/diylc/diylc-core/src/main/java/org/diylc/presenter/MirrorPlacement.java index 649a1d615..bc8975b04 100644 --- a/diylc/diylc-core/src/main/java/org/diylc/presenter/MirrorPlacement.java +++ b/diylc/diylc-core/src/main/java/org/diylc/presenter/MirrorPlacement.java @@ -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> 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); } } } diff --git a/diylc/diylc-core/src/main/java/org/diylc/presenter/Presenter.java b/diylc/diylc-core/src/main/java/org/diylc/presenter/Presenter.java index 46cee2bad..696efdccc 100644 --- a/diylc/diylc-core/src/main/java/org/diylc/presenter/Presenter.java +++ b/diylc/diylc-core/src/main/java/org/diylc/presenter/Presenter.java @@ -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, Set> findControlPointsAt(Point2D point, IBoard mirrorBoard) { + private Map, Set> findControlPointsAt(Point2D point, + IBoard mirrorBoard) { Map, Set> components = new HashMap, Set>(); // 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> 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> components = - forceSelectionRect ? null : findComponentsAtScaled(scaledPoint, false, false); if (controlPointMap != null && !this.controlPointMap.isEmpty()) { // If we're dragging control points reset selection. updateSelection(new ArrayList>(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> components = drawingManager.findMirroredComponentsAt(scaledPoint, currentProject, board); removeUnselectable(components, false); if (!components.isEmpty()) { - return UndersideMirror.createTransform(board); + return board; } } return null; diff --git a/diylc/diylc-core/src/main/java/org/diylc/presenter/UndersideMirror.java b/diylc/diylc-core/src/main/java/org/diylc/presenter/UndersideMirror.java index 7147d216b..2aecdb605 100644 --- a/diylc/diylc-core/src/main/java/org/diylc/presenter/UndersideMirror.java +++ b/diylc/diylc-core/src/main/java/org/diylc/presenter/UndersideMirror.java @@ -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 findBoardsMirroredAt(Project project, Point2D point) { List boards = new ArrayList(); 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(); diff --git a/diylc/diylc-core/src/test/java/org/diylc/presenter/DrawingManagerTest.java b/diylc/diylc-core/src/test/java/org/diylc/presenter/DrawingManagerTest.java index f5667e2a8..166159438 100644 --- a/diylc/diylc-core/src/test/java/org/diylc/presenter/DrawingManagerTest.java +++ b/diylc/diylc-core/src/test/java/org/diylc/presenter/DrawingManagerTest.java @@ -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; diff --git a/diylc/diylc-core/src/test/java/org/diylc/presenter/MirrorPlacementTest.java b/diylc/diylc-core/src/test/java/org/diylc/presenter/MirrorPlacementTest.java index 8f6023703..749ae9061 100644 --- a/diylc/diylc-core/src/test/java/org/diylc/presenter/MirrorPlacementTest.java +++ b/diylc/diylc-core/src/test/java/org/diylc/presenter/MirrorPlacementTest.java @@ -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; } diff --git a/diylc/diylc-core/src/test/java/org/diylc/presenter/UndersideMirrorTest.java b/diylc/diylc-core/src/test/java/org/diylc/presenter/UndersideMirrorTest.java index 75ec13469..163e6bde5 100644 --- a/diylc/diylc-core/src/test/java/org/diylc/presenter/UndersideMirrorTest.java +++ b/diylc/diylc-core/src/test/java/org/diylc/presenter/UndersideMirrorTest.java @@ -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) { diff --git a/diylc/diylc-library/src/main/java/org/diylc/components/connectivity/CopperTrace.java b/diylc/diylc-library/src/main/java/org/diylc/components/connectivity/CopperTrace.java index 1fa771ee9..5ae46797b 100644 --- a/diylc/diylc-library/src/main/java/org/diylc/components/connectivity/CopperTrace.java +++ b/diylc/diylc-library/src/main/java/org/diylc/components/connectivity/CopperTrace.java @@ -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 implements IConnective, ILayeredComponent, IGerberComponentSimple { +public class CopperTrace extends AbstractLeadedComponent + implements IConnective, ILayeredComponent, IGerberComponentSimple, IPCBLayerComponent { private static final long serialVersionUID = 1L; @@ -116,6 +118,7 @@ public class CopperTrace extends AbstractLeadedComponent implements IConne } @EditableProperty + @Override public PCBLayer getLayer() { if (layer == null) { layer = PCBLayer._1; @@ -123,6 +126,7 @@ public class CopperTrace extends AbstractLeadedComponent implements IConne return layer; } + @Override public void setLayer(PCBLayer layer) { this.layer = layer; } diff --git a/diylc/diylc-library/src/main/java/org/diylc/components/connectivity/CurvedTrace.java b/diylc/diylc-library/src/main/java/org/diylc/components/connectivity/CurvedTrace.java index 8ebcb54b2..11eda202e 100644 --- a/diylc/diylc-library/src/main/java/org/diylc/components/connectivity/CurvedTrace.java +++ b/diylc/diylc-library/src/main/java/org/diylc/components/connectivity/CurvedTrace.java @@ -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 implements IConnective, ILayeredComponent, IGerberComponentSimple { +public class CurvedTrace extends AbstractCurvedComponent + implements IConnective, ILayeredComponent, IGerberComponentSimple, IPCBLayerComponent { private static final long serialVersionUID = 1L; @@ -104,6 +106,7 @@ public class CurvedTrace extends AbstractCurvedComponent implements IConne } @EditableProperty + @Override public PCBLayer getLayer() { if (layer == null) { layer = PCBLayer._1; @@ -111,6 +114,7 @@ public class CurvedTrace extends AbstractCurvedComponent implements IConne return layer; } + @Override public void setLayer(PCBLayer layer) { this.layer = layer; } diff --git a/diylc/diylc-library/src/main/java/org/diylc/components/connectivity/GroundFill.java b/diylc/diylc-library/src/main/java/org/diylc/components/connectivity/GroundFill.java index 36c5901f7..edbb56fe7 100644 --- a/diylc/diylc-library/src/main/java/org/diylc/components/connectivity/GroundFill.java +++ b/diylc/diylc-library/src/main/java/org/diylc/components/connectivity/GroundFill.java @@ -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 - implements ILayeredComponent, IGerberComponentSimple { + implements ILayeredComponent, IGerberComponentSimple, IPCBLayerComponent { private static final long serialVersionUID = 1L; @@ -152,6 +153,7 @@ public class GroundFill extends AbstractTransparentComponent } @EditableProperty + @Override public PCBLayer getLayer() { if (layer == null) { layer = PCBLayer._1; @@ -159,6 +161,7 @@ public class GroundFill extends AbstractTransparentComponent return layer; } + @Override public void setLayer(PCBLayer layer) { this.layer = layer; } diff --git a/diylc/diylc-library/src/main/java/org/diylc/components/connectivity/PCBTerminalBlock.java b/diylc/diylc-library/src/main/java/org/diylc/components/connectivity/PCBTerminalBlock.java index fb9abf044..11080b2de 100644 --- a/diylc/diylc-library/src/main/java/org/diylc/components/connectivity/PCBTerminalBlock.java +++ b/diylc/diylc-library/src/main/java/org/diylc/components/connectivity/PCBTerminalBlock.java @@ -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 implements IGerberComponentSimple, IBoardSideComponent { +public class PCBTerminalBlock extends AbstractTransparentComponent + implements IGerberComponentSimple, IBoardSideComponent { private static final long serialVersionUID = 1L; diff --git a/diylc/diylc-library/src/main/java/org/diylc/components/connectivity/SolderPad.java b/diylc/diylc-library/src/main/java/org/diylc/components/connectivity/SolderPad.java index e254955bc..179a8a95a 100644 --- a/diylc/diylc-library/src/main/java/org/diylc/components/connectivity/SolderPad.java +++ b/diylc/diylc-library/src/main/java/org/diylc/components/connectivity/SolderPad.java @@ -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 - implements IConnective, ILayeredComponent, IGerberComponentCustom { + implements IConnective, ILayeredComponent, IGerberComponentCustom, IPCBLayerComponent { private static final long serialVersionUID = 1L; @@ -190,6 +191,7 @@ public class SolderPad extends AbstractTransparentComponent } @EditableProperty + @Override public PCBLayer getLayer() { if (layer == null) { layer = PCBLayer._1; @@ -197,6 +199,7 @@ public class SolderPad extends AbstractTransparentComponent return layer; } + @Override public void setLayer(PCBLayer layer) { this.layer = layer; } diff --git a/diylc/diylc-library/src/main/java/org/diylc/components/electromechanical/CliffJack1_4.java b/diylc/diylc-library/src/main/java/org/diylc/components/electromechanical/CliffJack1_4.java index 6c9b4af19..1ebf91583 100644 --- a/diylc/diylc-library/src/main/java/org/diylc/components/electromechanical/CliffJack1_4.java +++ b/diylc/diylc-library/src/main/java/org/diylc/components/electromechanical/CliffJack1_4.java @@ -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 implements IGerberComponentSimple, IBoardSideComponent { +public class CliffJack1_4 extends AbstractMultiPartComponent + implements IGerberComponentSimple, IBoardSideComponent { private static final long serialVersionUID = 1L; diff --git a/diylc/diylc-library/src/main/java/org/diylc/components/electromechanical/CliffJack1_8.java b/diylc/diylc-library/src/main/java/org/diylc/components/electromechanical/CliffJack1_8.java index 81a696baa..58bef3370 100644 --- a/diylc/diylc-library/src/main/java/org/diylc/components/electromechanical/CliffJack1_8.java +++ b/diylc/diylc-library/src/main/java/org/diylc/components/electromechanical/CliffJack1_8.java @@ -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 implements IGerberComponentSimple, IBoardSideComponent { +public class CliffJack1_8 extends AbstractMultiPartComponent + implements IGerberComponentSimple, IBoardSideComponent { private static final long serialVersionUID = 1L; diff --git a/diylc/diylc-library/src/main/java/org/diylc/components/electromechanical/ClosedJack1_4.java b/diylc/diylc-library/src/main/java/org/diylc/components/electromechanical/ClosedJack1_4.java index 0959bddee..b61954667 100644 --- a/diylc/diylc-library/src/main/java/org/diylc/components/electromechanical/ClosedJack1_4.java +++ b/diylc/diylc-library/src/main/java/org/diylc/components/electromechanical/ClosedJack1_4.java @@ -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 implements IGerberComponentSimple, IBoardSideComponent { +public class ClosedJack1_4 extends AbstractMultiPartComponent + implements IGerberComponentSimple, IBoardSideComponent { private static final long serialVersionUID = 1L; diff --git a/diylc/diylc-library/src/main/java/org/diylc/components/electromechanical/MiniRelay.java b/diylc/diylc-library/src/main/java/org/diylc/components/electromechanical/MiniRelay.java index 01952a3aa..598f4fa6e 100644 --- a/diylc/diylc-library/src/main/java/org/diylc/components/electromechanical/MiniRelay.java +++ b/diylc/diylc-library/src/main/java/org/diylc/components/electromechanical/MiniRelay.java @@ -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 implements IGerberComponentSimple, IBoardSideComponent { +public class MiniRelay extends AbstractLabeledComponent + implements IGerberComponentSimple, IBoardSideComponent { private static final long serialVersionUID = 1L; diff --git a/diylc/diylc-library/src/main/java/org/diylc/components/electromechanical/MiniToggleSwitch.java b/diylc/diylc-library/src/main/java/org/diylc/components/electromechanical/MiniToggleSwitch.java index ab9f6fbca..5c21226ff 100644 --- a/diylc/diylc-library/src/main/java/org/diylc/components/electromechanical/MiniToggleSwitch.java +++ b/diylc/diylc-library/src/main/java/org/diylc/components/electromechanical/MiniToggleSwitch.java @@ -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 implements ISwitch, ILayeredComponent, IGerberComponentSimple, IBoardSideComponent { +public class MiniToggleSwitch extends AbstractTransparentComponent + implements ISwitch, ILayeredComponent, IGerberComponentSimple, IBoardSideComponent { private static final long serialVersionUID = 1L; diff --git a/diylc/diylc-library/src/main/java/org/diylc/components/electromechanical/PlasticDCJack.java b/diylc/diylc-library/src/main/java/org/diylc/components/electromechanical/PlasticDCJack.java index e040e2f23..610bc1acc 100644 --- a/diylc/diylc-library/src/main/java/org/diylc/components/electromechanical/PlasticDCJack.java +++ b/diylc/diylc-library/src/main/java/org/diylc/components/electromechanical/PlasticDCJack.java @@ -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 implements IGerberComponentSimple, IBoardSideComponent { +public class PlasticDCJack extends AbstractMultiPartComponent + implements IGerberComponentSimple, IBoardSideComponent { private static final long serialVersionUID = 1L; diff --git a/diylc/diylc-library/src/main/java/org/diylc/components/electromechanical/RotarySelectorSwitch.java b/diylc/diylc-library/src/main/java/org/diylc/components/electromechanical/RotarySelectorSwitch.java index e7bb05972..0995e41ad 100644 --- a/diylc/diylc-library/src/main/java/org/diylc/components/electromechanical/RotarySelectorSwitch.java +++ b/diylc/diylc-library/src/main/java/org/diylc/components/electromechanical/RotarySelectorSwitch.java @@ -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 implements IGerberComponentSimple, IBoardSideComponent { +public class RotarySelectorSwitch extends AbstractMultiPartComponent + implements IGerberComponentSimple, IBoardSideComponent { private static final long serialVersionUID = 1L; diff --git a/diylc/diylc-library/src/main/java/org/diylc/components/electromechanical/RotarySwitchSealed.java b/diylc/diylc-library/src/main/java/org/diylc/components/electromechanical/RotarySwitchSealed.java index c3bdb3912..0802865c6 100644 --- a/diylc/diylc-library/src/main/java/org/diylc/components/electromechanical/RotarySwitchSealed.java +++ b/diylc/diylc-library/src/main/java/org/diylc/components/electromechanical/RotarySwitchSealed.java @@ -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 implements ISwitch, IGerberComponentSimple, IBoardSideComponent { +public class RotarySwitchSealed extends AbstractAngledComponent + implements ISwitch, IGerberComponentSimple, IBoardSideComponent { private static final int OUTER_ANGLE_OFFSET = 15; private static final int INNER_ANGLE_OFFSET = 30; diff --git a/diylc/diylc-library/src/main/java/org/diylc/components/electromechanical/SlideSwitch.java b/diylc/diylc-library/src/main/java/org/diylc/components/electromechanical/SlideSwitch.java index 5349e4de4..a9d522658 100644 --- a/diylc/diylc-library/src/main/java/org/diylc/components/electromechanical/SlideSwitch.java +++ b/diylc/diylc-library/src/main/java/org/diylc/components/electromechanical/SlideSwitch.java @@ -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 implements ISwitch, IGerberComponentSimple, IBoardSideComponent { +public class SlideSwitch extends AbstractTransparentComponent + implements ISwitch, IGerberComponentSimple, IBoardSideComponent { private static final long serialVersionUID = 1L; diff --git a/diylc/diylc-library/src/main/java/org/diylc/components/misc/PCBText.java b/diylc/diylc-library/src/main/java/org/diylc/components/misc/PCBText.java index 6c90abb0c..b525381e3 100644 --- a/diylc/diylc-library/src/main/java/org/diylc/components/misc/PCBText.java +++ b/diylc/diylc-library/src/main/java/org/diylc/components/misc/PCBText.java @@ -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 implements ILayeredComponent, IGerberComponentCustom { +public class PCBText extends AbstractComponent + implements ILayeredComponent, IGerberComponentCustom, IPCBLayerComponent { public static String DEFAULT_TEXT = "Double click to edit text"; @@ -324,6 +326,7 @@ public class PCBText extends AbstractComponent implements ILayeredComponen } @EditableProperty + @Override public PCBLayer getLayer() { if (layer == null) { layer = PCBLayer._1; @@ -331,6 +334,7 @@ public class PCBText extends AbstractComponent implements ILayeredComponen return layer; } + @Override public void setLayer(PCBLayer layer) { this.layer = layer; } diff --git a/diylc/diylc-library/src/main/java/org/diylc/components/passive/AbstractFilmCapacitor.java b/diylc/diylc-library/src/main/java/org/diylc/components/passive/AbstractFilmCapacitor.java index 8fe1f381d..7c2606000 100644 --- a/diylc/diylc-library/src/main/java/org/diylc/components/passive/AbstractFilmCapacitor.java +++ b/diylc/diylc-library/src/main/java/org/diylc/components/passive/AbstractFilmCapacitor.java @@ -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 implements IGerberComponentSimple, IBoardSideComponent { +public abstract class AbstractFilmCapacitor extends AbstractLeadedComponent + implements IGerberComponentSimple, IBoardSideComponent { private static final long serialVersionUID = 1L; diff --git a/diylc/diylc-library/src/main/java/org/diylc/components/passive/AbstractPotentiometer.java b/diylc/diylc-library/src/main/java/org/diylc/components/passive/AbstractPotentiometer.java index 05fb56514..4481c1922 100644 --- a/diylc/diylc-library/src/main/java/org/diylc/components/passive/AbstractPotentiometer.java +++ b/diylc/diylc-library/src/main/java/org/diylc/components/passive/AbstractPotentiometer.java @@ -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 implements IGerberComponentSimple, IBoardSideComponent { +public abstract class AbstractPotentiometer extends AbstractLabeledComponent + implements IGerberComponentSimple, IBoardSideComponent { private static final long serialVersionUID = 1L; diff --git a/diylc/diylc-library/src/main/java/org/diylc/components/passive/AbstractRadialComponent.java b/diylc/diylc-library/src/main/java/org/diylc/components/passive/AbstractRadialComponent.java index d5ea57950..fb679ae50 100644 --- a/diylc/diylc-library/src/main/java/org/diylc/components/passive/AbstractRadialComponent.java +++ b/diylc/diylc-library/src/main/java/org/diylc/components/passive/AbstractRadialComponent.java @@ -41,7 +41,8 @@ import org.diylc.core.measures.SizeUnit; * @param */ @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 extends AbstractLeadedComponent implements IGerberComponentSimple, IBoardSideComponent { +public abstract class AbstractRadialComponent extends AbstractLeadedComponent + implements IGerberComponentSimple, IBoardSideComponent { private static final long serialVersionUID = 1L; diff --git a/diylc/diylc-library/src/main/java/org/diylc/components/passive/AudioTransformer.java b/diylc/diylc-library/src/main/java/org/diylc/components/passive/AudioTransformer.java index 3f75e160b..5aaf24120 100644 --- a/diylc/diylc-library/src/main/java/org/diylc/components/passive/AudioTransformer.java +++ b/diylc/diylc-library/src/main/java/org/diylc/components/passive/AudioTransformer.java @@ -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 implements IGerberComponentSimple, IBoardSideComponent { +public class AudioTransformer extends AbstractMultiPartComponent + implements IGerberComponentSimple, IBoardSideComponent { private static final long serialVersionUID = 1L; diff --git a/diylc/diylc-library/src/main/java/org/diylc/components/passive/AxialElectrolyticCapacitor.java b/diylc/diylc-library/src/main/java/org/diylc/components/passive/AxialElectrolyticCapacitor.java index fa5bf8838..c279641e6 100644 --- a/diylc/diylc-library/src/main/java/org/diylc/components/passive/AxialElectrolyticCapacitor.java +++ b/diylc/diylc-library/src/main/java/org/diylc/components/passive/AxialElectrolyticCapacitor.java @@ -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 implements IDatasheetSupport, IGerberComponentSimple, IBoardSideComponent { +public class AxialElectrolyticCapacitor extends AbstractLeadedComponent + implements IDatasheetSupport, IGerberComponentSimple, IBoardSideComponent { private static final long serialVersionUID = 1L; diff --git a/diylc/diylc-library/src/main/java/org/diylc/components/passive/CrystalOscillator.java b/diylc/diylc-library/src/main/java/org/diylc/components/passive/CrystalOscillator.java index 036922967..6f150c232 100644 --- a/diylc/diylc-library/src/main/java/org/diylc/components/passive/CrystalOscillator.java +++ b/diylc/diylc-library/src/main/java/org/diylc/components/passive/CrystalOscillator.java @@ -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 implements IGerberComponentSimple, IBoardSideComponent { +public class CrystalOscillator extends AbstractLeadedComponent + implements IGerberComponentSimple, IBoardSideComponent { private static final long serialVersionUID = 1L; diff --git a/diylc/diylc-library/src/main/java/org/diylc/components/passive/MultiSectionCapacitor.java b/diylc/diylc-library/src/main/java/org/diylc/components/passive/MultiSectionCapacitor.java index 24308848f..92fd419a5 100644 --- a/diylc/diylc-library/src/main/java/org/diylc/components/passive/MultiSectionCapacitor.java +++ b/diylc/diylc-library/src/main/java/org/diylc/components/passive/MultiSectionCapacitor.java @@ -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 implements IGerberComponentSimple, IBoardSideComponent { +public class MultiSectionCapacitor extends AbstractLabeledComponent + implements IGerberComponentSimple, IBoardSideComponent { private static final long serialVersionUID = 1L; diff --git a/diylc/diylc-library/src/main/java/org/diylc/components/passive/ToroidalInductor.java b/diylc/diylc-library/src/main/java/org/diylc/components/passive/ToroidalInductor.java index e9937b620..bcf9cf639 100644 --- a/diylc/diylc-library/src/main/java/org/diylc/components/passive/ToroidalInductor.java +++ b/diylc/diylc-library/src/main/java/org/diylc/components/passive/ToroidalInductor.java @@ -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 implements IGerberComponentSimple, IBoardSideComponent { +public class ToroidalInductor extends AbstractLeadedComponent + implements IGerberComponentSimple, IBoardSideComponent { private static final long serialVersionUID = 1L; diff --git a/diylc/diylc-library/src/main/java/org/diylc/components/semiconductors/BridgeRectifier.java b/diylc/diylc-library/src/main/java/org/diylc/components/semiconductors/BridgeRectifier.java index f30be8cab..d0d30c981 100644 --- a/diylc/diylc-library/src/main/java/org/diylc/components/semiconductors/BridgeRectifier.java +++ b/diylc/diylc-library/src/main/java/org/diylc/components/semiconductors/BridgeRectifier.java @@ -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 implements IGerberComponentSimple, IBoardSideComponent { +public class BridgeRectifier extends AbstractLabeledComponent + implements IGerberComponentSimple, IBoardSideComponent { private static final long serialVersionUID = 1L; diff --git a/diylc/diylc-library/src/main/java/org/diylc/components/smd/PassiveSMDComponent.java b/diylc/diylc-library/src/main/java/org/diylc/components/smd/PassiveSMDComponent.java index a3e9cd2c3..496e4c5a2 100644 --- a/diylc/diylc-library/src/main/java/org/diylc/components/smd/PassiveSMDComponent.java +++ b/diylc/diylc-library/src/main/java/org/diylc/components/smd/PassiveSMDComponent.java @@ -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 extends AbstractLabeledComponent implements IGerberComponentSimple, IBoardSideComponent { +public abstract class PassiveSMDComponent extends AbstractLabeledComponent + implements IGerberComponentSimple, IBoardSideComponent { private static final long serialVersionUID = 1L; diff --git a/diylc/diylc-swing/src/main/java/org/diylc/swing/plugins/file/SilkScreenDrawingProvider.java b/diylc/diylc-swing/src/main/java/org/diylc/swing/plugins/file/SilkScreenDrawingProvider.java index bcba2cb1a..f8ec5a14b 100644 --- a/diylc/diylc-swing/src/main/java/org/diylc/swing/plugins/file/SilkScreenDrawingProvider.java +++ b/diylc/diylc-swing/src/main/java/org/diylc/swing/plugins/file/SilkScreenDrawingProvider.java @@ -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; } } diff --git a/diylc/diylc-swing/src/main/java/org/diylc/swing/plugins/file/TraceMaskDrawingProvider.java b/diylc/diylc-swing/src/main/java/org/diylc/swing/plugins/file/TraceMaskDrawingProvider.java index daf01b85f..58e38575f 100644 --- a/diylc/diylc-swing/src/main/java/org/diylc/swing/plugins/file/TraceMaskDrawingProvider.java +++ b/diylc/diylc-swing/src/main/java/org/diylc/swing/plugins/file/TraceMaskDrawingProvider.java @@ -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 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 sorted = new ArrayList(layers); diff --git a/diylc/diylc-swing/src/test/java/org/diylc/PresenterTests.java b/diylc/diylc-swing/src/test/java/org/diylc/PresenterTests.java index a6f35dfc7..f0622d3c0 100644 --- a/diylc/diylc-swing/src/test/java/org/diylc/PresenterTests.java +++ b/diylc/diylc-swing/src/test/java/org/diylc/PresenterTests.java @@ -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); diff --git a/diylc/diylc-swing/src/test/java/org/diylc/swing/plugins/file/SilkScreenDrawingProviderTest.java b/diylc/diylc-swing/src/test/java/org/diylc/swing/plugins/file/SilkScreenDrawingProviderTest.java index 834296b23..271db2568 100644 --- a/diylc/diylc-swing/src/test/java/org/diylc/swing/plugins/file/SilkScreenDrawingProviderTest.java +++ b/diylc/diylc-swing/src/test/java/org/diylc/swing/plugins/file/SilkScreenDrawingProviderTest.java @@ -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()); + } } diff --git a/diylc/docs/plans/bottom-side-components.md b/diylc/docs/plans/bottom-side-components.md index 66b7ec72e..ae636d8eb 100644 --- a/diylc/docs/plans/bottom-side-components.md +++ b/diylc/docs/plans/bottom-side-components.md @@ -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.