mirror of
https://github.com/bancika/diy-layout-creator
synced 2026-11-13 02:32:09 +10:00
Parts placed on the bottom of the board are drawn faded on the canvas and solid in the Underside Display mirror
This commit is contained in:
@@ -153,12 +153,16 @@ public class DrawingCache {
|
|||||||
if (Math.abs(1.0 - zoom) > 1e-4) {
|
if (Math.abs(1.0 - zoom) > 1e-4) {
|
||||||
g2d.getCanvasGraphics().scale(1 / zoom, 1 / zoom);
|
g2d.getCanvasGraphics().scale(1 / zoom, 1 / zoom);
|
||||||
}
|
}
|
||||||
|
// the image was rendered with the far-side fade already applied, so pasting it must not
|
||||||
|
// fade it again
|
||||||
|
g2d.startDrawingBothSides();
|
||||||
try {
|
try {
|
||||||
// draw cached image
|
// draw cached image
|
||||||
g2d.scale(1 / scaleFactor, 1 / scaleFactor);
|
g2d.scale(1 / scaleFactor, 1 / scaleFactor);
|
||||||
g2d.getCanvasGraphics().drawImage(value.getImage(), (int) ((firstPoint.getX() - value.getDx()) * zoom * scaleFactor),
|
g2d.getCanvasGraphics().drawImage(value.getImage(), (int) ((firstPoint.getX() - value.getDx()) * zoom * scaleFactor),
|
||||||
(int) ((firstPoint.getY() - value.getDy())* zoom * scaleFactor), null);
|
(int) ((firstPoint.getY() - value.getDy())* zoom * scaleFactor), null);
|
||||||
} finally {
|
} finally {
|
||||||
|
g2d.stopDrawingSide();
|
||||||
if (Math.abs(1.0 - zoom) > 1e-4) {
|
if (Math.abs(1.0 - zoom) > 1e-4) {
|
||||||
g2d.getCanvasGraphics().scale(zoom, zoom);
|
g2d.getCanvasGraphics().scale(zoom, zoom);
|
||||||
}
|
}
|
||||||
@@ -347,6 +351,7 @@ public class DrawingCache {
|
|||||||
|
|
||||||
// initialize wrapper
|
// initialize wrapper
|
||||||
wrapper.startedDrawingComponent(zOrder);
|
wrapper.startedDrawingComponent(zOrder);
|
||||||
|
wrapper.setUndeclaredSide(DrawingManager.getMountedSide(component));
|
||||||
|
|
||||||
long componentStart = System.nanoTime();
|
long componentStart = System.nanoTime();
|
||||||
|
|
||||||
|
|||||||
@@ -303,6 +303,8 @@ public class DrawingManager {
|
|||||||
if (lockedComponents.contains(component) && drawOptions.contains(DrawOption.LOCKED_ALPHA)) {
|
if (lockedComponents.contains(component) && drawOptions.contains(DrawOption.LOCKED_ALPHA)) {
|
||||||
g2d.setComposite(lockedComposite);
|
g2d.setComposite(lockedComposite);
|
||||||
}
|
}
|
||||||
|
// after the locked composite, which is set on the canvas and would replace the fade
|
||||||
|
g2dWrapper.setUndeclaredSide(getMountedSide(component));
|
||||||
// Draw the component through the g2dWrapper.
|
// Draw the component through the g2dWrapper.
|
||||||
long componentStart = System.nanoTime();
|
long componentStart = System.nanoTime();
|
||||||
try {
|
try {
|
||||||
@@ -334,6 +336,7 @@ public class DrawingManager {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
ComponentArea area = g2dWrapper.finishedDrawingComponent();
|
ComponentArea area = g2dWrapper.finishedDrawingComponent();
|
||||||
|
g2dWrapper.setUndeclaredSide(PCBLayer._2);
|
||||||
// after finishedDrawingComponent, which reverts the transform the clip was taken in
|
// after finishedDrawingComponent, which reverts the transform the clip was taken in
|
||||||
if (visibleClip != null) {
|
if (visibleClip != null) {
|
||||||
g2d.setClip(visibleClip);
|
g2d.setClip(visibleClip);
|
||||||
@@ -527,6 +530,15 @@ public class DrawingManager {
|
|||||||
return failedComponents;
|
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) {
|
private static void drawSelectedControlPoint(G2DWrapper g2dWrapper, Point2D controlPoint) {
|
||||||
g2dWrapper.setColor(SELECTED_CONTROL_POINT_COLOR.darker());
|
g2dWrapper.setColor(SELECTED_CONTROL_POINT_COLOR.darker());
|
||||||
g2dWrapper.fillOval((int) Math.round(controlPoint.getX() - CONTROL_POINT_SIZE / 2),
|
g2dWrapper.fillOval((int) Math.round(controlPoint.getX() - CONTROL_POINT_SIZE / 2),
|
||||||
@@ -565,9 +577,10 @@ public class DrawingManager {
|
|||||||
g2dWrapper.setMirrorDirection(mirrorDirection);
|
g2dWrapper.setMirrorDirection(mirrorDirection);
|
||||||
try {
|
try {
|
||||||
g2dWrapper.transform(mirrorTx);
|
g2dWrapper.transform(mirrorTx);
|
||||||
// the mirror is the copper side, so copper is drawn solid and the component side faded
|
// 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()),
|
g2dWrapper.setViewedSide(PCBLayer._1, Boolean.TRUE.equals(board.getUndersideTransparency()),
|
||||||
component instanceof IBoard ? PCBLayer._1 : PCBLayer._2);
|
component instanceof IBoard ? PCBLayer._1 : getMountedSide(component));
|
||||||
component.draw(g2dWrapper, state, outlineMode, project, g2dWrapper);
|
component.draw(g2dWrapper, state, outlineMode, project, g2dWrapper);
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
LOG.error("Error drawing mirrored " + component.getName(), e);
|
LOG.error("Error drawing mirrored " + component.getName(), e);
|
||||||
|
|||||||
@@ -186,6 +186,19 @@ class G2DWrapper extends Graphics2D implements IDrawingObserver {
|
|||||||
updateFarSideFade();
|
updateFarSideFade();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the side assumed for paint that is not bracketed with
|
||||||
|
* {@link #startDrawingSide(PCBLayer)}, keeping the viewed side. A part mounted on the bottom of
|
||||||
|
* the board is drawn on the bottom side as a whole.
|
||||||
|
*
|
||||||
|
* @param undeclaredSide
|
||||||
|
*/
|
||||||
|
public void setUndeclaredSide(PCBLayer undeclaredSide) {
|
||||||
|
this.undeclaredSide = undeclaredSide;
|
||||||
|
this.paintedSide = undeclaredSide;
|
||||||
|
updateFarSideFade();
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns to the top-side view, fading the bottom side only if the draw options ask for it.
|
* Returns to the top-side view, fading the bottom side only if the draw options ask for it.
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -27,6 +27,7 @@ import org.diylc.common.PCBLayer;
|
|||||||
import org.diylc.core.BoardUndersideDisplay;
|
import org.diylc.core.BoardUndersideDisplay;
|
||||||
import org.diylc.core.ComponentState;
|
import org.diylc.core.ComponentState;
|
||||||
import org.diylc.core.IBoard;
|
import org.diylc.core.IBoard;
|
||||||
|
import org.diylc.core.IBoardSideComponent;
|
||||||
import org.diylc.core.IDIYComponent;
|
import org.diylc.core.IDIYComponent;
|
||||||
import org.diylc.core.IDrawingObserver;
|
import org.diylc.core.IDrawingObserver;
|
||||||
import org.diylc.core.Project;
|
import org.diylc.core.Project;
|
||||||
@@ -683,6 +684,66 @@ public class DrawingManagerTest {
|
|||||||
assertSolid(img, 155, 665);
|
assertSolid(img, 155, 665);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@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));
|
||||||
|
|
||||||
|
BufferedImage faded = renderSides(project, EnumSet.of(DrawOption.ZOOM, DrawOption.FADE_FAR_SIDE));
|
||||||
|
assertSolid(faded, 155, 155);
|
||||||
|
assertFaded(faded, 205, 205);
|
||||||
|
|
||||||
|
BufferedImage plain = renderSides(project, EnumSet.of(DrawOption.ZOOM));
|
||||||
|
assertSolid(plain, 205, 205);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testCachedBottomMountedPartFaded() {
|
||||||
|
Project project = new Project();
|
||||||
|
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));
|
||||||
|
assertFaded(img, 205, 205);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testMirrorShowsBottomMountedPartSolid() {
|
||||||
|
Project project = new Project();
|
||||||
|
MockBoard board = new MockBoard();
|
||||||
|
board.setBoardRectangle(new Rectangle2D.Double(100, 100, 200, 300));
|
||||||
|
board.setUndersideDisplay(BoardUndersideDisplay.BELOW);
|
||||||
|
board.setUndersideOffset(new Size(20.0, SizeUnit.px));
|
||||||
|
board.setUndersideTransparency(true);
|
||||||
|
project.getComponents().add(board);
|
||||||
|
project.getComponents().add(new 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));
|
||||||
|
assertFaded(img, 155, 665);
|
||||||
|
assertSolid(img, 205, 615);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testLockedBottomMountedPartNotFadedTwice() {
|
||||||
|
Project project = new Project();
|
||||||
|
MountedComponent bottom = new MountedComponent(new Rectangle2D.Double(200, 200, 10, 10), PCBLayer._1);
|
||||||
|
project.getComponents().add(bottom);
|
||||||
|
|
||||||
|
DrawingManager drawingManager = createDrawingManager();
|
||||||
|
BufferedImage img = new BufferedImage(1000, 1000, BufferedImage.TYPE_INT_ARGB);
|
||||||
|
Graphics2D g2d = img.createGraphics();
|
||||||
|
drawingManager.drawProject(g2d, project,
|
||||||
|
EnumSet.of(DrawOption.ZOOM, DrawOption.FADE_FAR_SIDE, DrawOption.LOCKED_ALPHA), null, null,
|
||||||
|
Collections.emptyList(), Collections.singleton(bottom), Collections.emptySet(), null, null,
|
||||||
|
false, Collections.emptySet(), 1.0, 1.0, null);
|
||||||
|
g2d.dispose();
|
||||||
|
|
||||||
|
assertFaded(img, 205, 205, Math.min(0.5f, farSideAlpha()));
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testLockedBottomSideNotFadedTwice() {
|
public void testLockedBottomSideNotFadedTwice() {
|
||||||
Project project = new Project();
|
Project project = new Project();
|
||||||
@@ -990,6 +1051,34 @@ public class DrawingManagerTest {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// paints an opaque black square, declared on the given side or, when null, left undeclared
|
// 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",
|
||||||
|
description = "Part mounted on one side of the board", zOrder = IDIYComponent.COMPONENT,
|
||||||
|
enableCache = true)
|
||||||
|
public static class MountedComponent extends SidePaintingComponent implements IBoardSideComponent {
|
||||||
|
|
||||||
|
private static final long serialVersionUID = 1L;
|
||||||
|
|
||||||
|
private Rectangle2D bounds;
|
||||||
|
private PCBLayer boardSide;
|
||||||
|
|
||||||
|
public MountedComponent(Rectangle2D shape, PCBLayer boardSide) {
|
||||||
|
super(shape, null);
|
||||||
|
this.bounds = shape;
|
||||||
|
this.boardSide = boardSide;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public PCBLayer getBoardSide() {
|
||||||
|
return boardSide;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Rectangle2D getCachingBounds() {
|
||||||
|
return bounds;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public static class SidePaintingComponent extends MockDIYComponent {
|
public static class SidePaintingComponent extends MockDIYComponent {
|
||||||
|
|
||||||
private static final long serialVersionUID = 1L;
|
private static final long serialVersionUID = 1L;
|
||||||
|
|||||||
@@ -5,8 +5,9 @@ Gerber layers and the canvas shows it as a part on the far side of the board.
|
|||||||
|
|
||||||
Status: 32 parts have a Board Side property, and Gerber export sends a bottom-side part's
|
Status: 32 parts have a Board Side property, and Gerber export sends a bottom-side part's
|
||||||
silkscreen to the bottom layer with its text mirrored. Parts can be selected, dragged and stretched
|
silkscreen to the bottom layer with its text mirrored. Parts can be selected, dragged and stretched
|
||||||
through the Underside Display mirror. The canvas does not show the side yet, and the Flip to Other
|
through the Underside Display mirror. The canvas draws a bottom-side part faded in the main view and
|
||||||
Side action does not exist; the next steps below remain.
|
solid in the mirror. The Flip to Other Side action does not exist yet; the next steps below
|
||||||
|
remain.
|
||||||
|
|
||||||
## 1. Current state
|
## 1. Current state
|
||||||
|
|
||||||
@@ -50,9 +51,15 @@ Side action does not exist; the next steps below remain.
|
|||||||
|
|
||||||
### diylc-core
|
### diylc-core
|
||||||
|
|
||||||
- **Canvas rendering**: bottom-side components use the far-side fade from `copper-side-view.md`,
|
- **Canvas rendering (done)**: bottom-side parts use the far-side fade from `copper-side-view.md`,
|
||||||
wrapping their painting in `startDrawingSide(getBoardSide())`. They are then faded in the main
|
set centrally rather than in each component. Wherever a component is drawn - the main loop in
|
||||||
view and solid in the Underside Display mirror, the same as bottom copper.
|
`DrawingManager`, `DrawingCache` rendering an image, and the Underside Display mirror - the
|
||||||
|
wrapper's undeclared side is set to `DrawingManager.getMountedSide(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
|
||||||
|
replace the fade. `DrawingCache` pastes a cached image as both-sides paint, because the fade is
|
||||||
|
already in the image.
|
||||||
|
|
||||||
### diylc-library
|
### diylc-library
|
||||||
|
|
||||||
@@ -132,6 +139,10 @@ order.
|
|||||||
- **Components that belong to no side.** In mirror stage 3 a jumper, eyelet or wire dropped in the
|
- **Components that belong to no side.** In mirror stage 3 a jumper, eyelet or wire dropped in the
|
||||||
mirror has no side to set. Confirm that placing it at the reflected point unchanged is what users
|
mirror has no side to set. Confirm that placing it at the reflected point unchanged is what users
|
||||||
expect, or whether such parts should be refused in the mirror.
|
expect, or whether such parts should be refused in the mirror.
|
||||||
|
- **Locked bottom-side parts drawn from the cache.** An uncached locked part takes the stronger of
|
||||||
|
the locked alpha and the far-side alpha. A cached one is pasted at the locked alpha onto an
|
||||||
|
image that already carries the far-side fade, so it ends up fainter. The same applies to
|
||||||
|
bottom-side paint inside any cached component.
|
||||||
- **Components inside two boards.** A component whose control points fall inside two boards with
|
- **Components inside two boards.** A component whose control points fall inside two boards with
|
||||||
Underside Display on appears in both mirrors. Selecting through either is fine. Check that
|
Underside Display on appears in both mirrors. Selecting through either is fine. Check that
|
||||||
dragging through one mirror while the other redraws does not confuse the user.
|
dragging through one mirror while the other redraws does not confuse the user.
|
||||||
@@ -140,6 +151,9 @@ order.
|
|||||||
|
|
||||||
- `GerberLayerTest` covers `flip()`. `GerberExporterTest` covers the dispatch for simple components,
|
- `GerberLayerTest` covers `flip()`. `GerberExporterTest` covers the dispatch for simple components,
|
||||||
including a stub `IBoardSideComponent` on top, bottom and `null`.
|
including a stub `IBoardSideComponent` on top, bottom and `null`.
|
||||||
|
- `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.
|
||||||
- Load an old `.diy` with PCB parts and confirm every component reports `_2` (Top) and that the Gerber
|
- Load an old `.diy` with PCB parts and confirm every component reports `_2` (Top) and that the Gerber
|
||||||
output is byte-identical to before.
|
output is byte-identical to before.
|
||||||
- Export a board with one part on each side and open the files in a Gerber viewer: the bottom part
|
- Export a board with one part on each side and open the files in a Gerber viewer: the bottom part
|
||||||
@@ -156,8 +170,7 @@ order.
|
|||||||
|
|
||||||
## 6. Next steps
|
## 6. Next steps
|
||||||
|
|
||||||
1. Wrap bottom-side painting in `startDrawingSide(getBoardSide())`.
|
1. Add the Flip to Other Side action.
|
||||||
2. Add the Flip to Other Side action.
|
2. Split the silkscreen print-out by side.
|
||||||
3. Split the silkscreen print-out by side.
|
3. Mirror stage 3: placing in the mirror, after Flip to Other Side exists.
|
||||||
4. Mirror stage 3: placing in the mirror, after Flip to Other Side exists.
|
4. Run the regression suite.
|
||||||
5. Run the regression suite.
|
|
||||||
|
|||||||
Reference in New Issue
Block a user