mirror of
https://github.com/bancika/diy-layout-creator
synced 2026-11-13 02:32:09 +10:00
'Highlight Connected Areas', netlists and AI analysis could ignore trace cuts after the board under them was moved or edited, or when the cuts were scrolled out of view, and showed the whole strip as connected
This commit is contained in:
+2
-1
@@ -312,7 +312,8 @@ public class ChatbotService {
|
||||
return null;
|
||||
}
|
||||
DrawingManager drawingManager = plugInPort.getDrawingManager();
|
||||
List<ContinuityArea> continuityAreas = drawingManager.getContinuityAreas();
|
||||
List<ContinuityArea> continuityAreas =
|
||||
drawingManager.getContinuityAreas(plugInPort.getCurrentProject());
|
||||
return AiProjectBuilder.build(plugInPort.getCurrentProject(), continuityAreas, component -> {
|
||||
ComponentArea area = drawingManager.getComponentArea(component);
|
||||
return area == null ? null : area.getOutlineBounds();
|
||||
|
||||
@@ -133,7 +133,10 @@ public class DrawingCache {
|
||||
.extractComponentTypeFrom((Class<? extends IDIYComponent<?>>) component.getClass());
|
||||
|
||||
CacheValue value = null;
|
||||
// a component out of view is drawn only to record its areas, which drawing it directly does
|
||||
// without rendering an image that would push visible ones out of the cache
|
||||
if (type != null && type.getEnableCache()
|
||||
&& (visibleRect == null || visibleRect.intersects(component.getCachingBounds()))
|
||||
&& shouldCache(component, componentState, zoom, scaleFactor)) {
|
||||
value = renderAndCache(component, g2d, componentState, outlineMode, project, zoom,
|
||||
scaleFactor, zOrder, drawOptions, false);
|
||||
|
||||
@@ -32,6 +32,7 @@ import java.awt.Stroke;
|
||||
import java.awt.geom.AffineTransform;
|
||||
import java.awt.geom.Area;
|
||||
import java.awt.geom.Ellipse2D;
|
||||
import java.awt.geom.NoninvertibleTransformException;
|
||||
import java.awt.geom.Point2D;
|
||||
import java.awt.geom.Rectangle2D;
|
||||
import java.util.ArrayList;
|
||||
@@ -234,7 +235,9 @@ public class DrawingManager {
|
||||
|
||||
g2dWrapper.setColor(theme.getBgColor());
|
||||
g2dWrapper.fillRect(0, 0, canvasDimension.width, canvasDimension.height);
|
||||
g2d.clip(new Rectangle(new Point(0, 0), canvasDimension));
|
||||
Rectangle canvasRect = new Rectangle(new Point(0, 0), canvasDimension);
|
||||
g2d.clip(canvasRect);
|
||||
Shape canvasDeviceClip = g2d.getTransform().createTransformedShape(canvasRect);
|
||||
|
||||
Rectangle2D extraSpaceRect = new Rectangle2D.Double();
|
||||
AffineTransform extraSpaceTx = new AffineTransform();
|
||||
@@ -253,6 +256,11 @@ public class DrawingManager {
|
||||
visibleRect.getWidth() / zoom, visibleRect.getHeight() / zoom);
|
||||
}
|
||||
|
||||
// components skip drawing themselves when the clip leaves them out, so a component whose
|
||||
// areas are recorded is drawn against the whole canvas rather than the visible part of it;
|
||||
// otherwise a trace cut scrolled out of view would not cut anything
|
||||
Shape recordingClip = toUserSpace(g2d, canvasDeviceClip);
|
||||
|
||||
// Composite mainComposite = g2d.getComposite();
|
||||
// Composite alphaComposite =
|
||||
// AlphaComposite.getInstance(AlphaComposite.SRC_OVER, 0.5f);
|
||||
@@ -304,10 +312,15 @@ public class DrawingManager {
|
||||
|
||||
synchronized (this) {
|
||||
g2dWrapper.startedDrawingComponent(i);
|
||||
Shape visibleClip = null;
|
||||
if (!trackArea) {
|
||||
g2dWrapper.stopTracking();
|
||||
} else {
|
||||
g2dWrapper.startRecordingDrawnBounds();
|
||||
if (recordingClip != null) {
|
||||
visibleClip = g2d.getClip();
|
||||
g2d.setClip(recordingClip);
|
||||
}
|
||||
}
|
||||
Composite oldComposite = g2d.getComposite();
|
||||
Font oldFont = g2d.getFont();
|
||||
@@ -346,9 +359,15 @@ public class DrawingManager {
|
||||
}
|
||||
}
|
||||
ComponentArea area = g2dWrapper.finishedDrawingComponent();
|
||||
// after finishedDrawingComponent, which reverts the transform the clip was taken in
|
||||
if (visibleClip != null) {
|
||||
g2d.setClip(visibleClip);
|
||||
}
|
||||
if (trackArea && area != null && area.hasOutline()) {
|
||||
componentAreaMap.put(component, area);
|
||||
lastDrawnStateMap.put(component, state);
|
||||
// the graph was built from the areas this one replaces
|
||||
continuityGraphCache = null;
|
||||
}
|
||||
|
||||
try {
|
||||
@@ -641,6 +660,18 @@ public class DrawingManager {
|
||||
bounds.getHeight() + 2 * OFF_SCREEN_MARGIN);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the specified device-space shape in the current user space of the graphics, or null
|
||||
* when its transform cannot be inverted
|
||||
*/
|
||||
private static Shape toUserSpace(Graphics2D g2d, Shape deviceShape) {
|
||||
try {
|
||||
return g2d.getTransform().createInverse().createTransformedShape(deviceShape);
|
||||
} catch (NoninvertibleTransformException e) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
private void configureRenderingHints(Graphics2D g2d, Set<DrawOption> drawOptions) {
|
||||
if (drawOptions.contains(DrawOption.ANTIALIASING)) {
|
||||
g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
|
||||
@@ -805,26 +836,35 @@ public class DrawingManager {
|
||||
messageDispatcher.dispatchMessage(EventType.REPAINT);
|
||||
}
|
||||
|
||||
public void findContinuityAreaAtPoint(Point2D p) {
|
||||
if (continuityGraphCache == null)
|
||||
continuityGraphCache = getContinuityGraph();
|
||||
public void findContinuityAreaAtPoint(Project project, Point2D p) {
|
||||
if (continuityGraphCache == null) {
|
||||
continuityGraphCache = getContinuityGraph(project);
|
||||
}
|
||||
|
||||
currentContinuityAreas = continuityGraphCache.findAreasFor(p);
|
||||
}
|
||||
|
||||
public List<ContinuityArea> getContinuityAreas() {
|
||||
|
||||
/**
|
||||
* Returns the continuity areas recorded for the components of the specified project, with
|
||||
* negative areas subtracted from the positive areas of the components below them.
|
||||
*
|
||||
* @param project
|
||||
* @return
|
||||
*/
|
||||
public List<ContinuityArea> getContinuityAreas(Project project) {
|
||||
// Find all individual continuity areas for all components
|
||||
List<ContinuityArea> preliminaryAreas = new ArrayList<ContinuityArea>();
|
||||
List<Boolean> checkBreakout = new ArrayList<Boolean>();
|
||||
|
||||
for (Map.Entry<IDIYComponent<?>, ComponentArea> entry : this.componentAreaMap.entrySet()) {
|
||||
ComponentArea a = entry.getValue();
|
||||
// in z-order rather than the order the areas were recorded in, which changes every time a
|
||||
// component is redrawn after an edit; a trace cut only cuts what lies below it
|
||||
for (IDIYComponent<?> component : project.getComponents()) {
|
||||
ComponentArea a = componentAreaMap.get(component);
|
||||
|
||||
if (a == null)
|
||||
if (a == null) {
|
||||
continue;
|
||||
|
||||
IDIYComponent<?> component = entry.getKey();
|
||||
|
||||
}
|
||||
|
||||
int layerId;
|
||||
|
||||
if (component instanceof ILayeredComponent) {
|
||||
@@ -877,10 +917,13 @@ public class DrawingManager {
|
||||
return areas;
|
||||
}
|
||||
|
||||
public ContinuityGraph getContinuityGraph() {
|
||||
public ContinuityGraph getContinuityGraph(Project project) {
|
||||
Set<Connection> connections = new HashSet<Connection>();
|
||||
int z = 0;
|
||||
for (IDIYComponent<?> c : this.componentAreaMap.keySet()) {
|
||||
for (IDIYComponent<?> c : project.getComponents()) {
|
||||
if (!componentAreaMap.containsKey(c)) {
|
||||
continue;
|
||||
}
|
||||
if (c instanceof IContinuity) {
|
||||
for (int i = 0; i < c.getControlPointCount() - 1; i++)
|
||||
for (int j = i + 1; j < c.getControlPointCount(); j++)
|
||||
@@ -890,13 +933,13 @@ public class DrawingManager {
|
||||
z++;
|
||||
}
|
||||
|
||||
List<ContinuityArea> areas = getContinuityAreas();
|
||||
List<ContinuityArea> areas = getContinuityAreas(project);
|
||||
|
||||
return NetlistBuilder.buildContinuityGraph(areas, connections);
|
||||
}
|
||||
|
||||
public List<Area> getContinuityAreaProximity(float threshold) {
|
||||
List<ContinuityArea> continuityAreas = getContinuityAreas();
|
||||
public List<Area> getContinuityAreaProximity(Project project, float threshold) {
|
||||
List<ContinuityArea> continuityAreas = getContinuityAreas(project);
|
||||
Stroke s = ObjectCache.getInstance().fetchBasicStroke(threshold - 1); // value eyeballed for
|
||||
// approx good results
|
||||
|
||||
|
||||
@@ -685,7 +685,7 @@ public class Presenter implements IPlugInPort {
|
||||
LOG.error("Unknown creation method: " + componentTypeSlot.getCreationMethod());
|
||||
}
|
||||
} else if (operationMode == OperationMode.HIGHLIGHT_CONNECTED_AREAS || altDown) {
|
||||
drawingManager.findContinuityAreaAtPoint(scaledPoint);
|
||||
drawingManager.findContinuityAreaAtPoint(currentProject, scaledPoint);
|
||||
messageDispatcher.dispatchMessage(EventType.REPAINT);
|
||||
} else {
|
||||
List<IDIYComponent<?>> newSelection = new ArrayList<IDIYComponent<?>>(selectedComponents);
|
||||
@@ -3188,7 +3188,7 @@ public class Presenter implements IPlugInPort {
|
||||
|
||||
@Override
|
||||
public List<Netlist> extractNetlists(boolean includeSwitches) throws NetlistException {
|
||||
List<ContinuityArea> continuityAreas = drawingManager.getContinuityAreas();
|
||||
List<ContinuityArea> continuityAreas = drawingManager.getContinuityAreas(currentProject);
|
||||
|
||||
return NetlistBuilder.extractNetlists(includeSwitches, currentProject, continuityAreas);
|
||||
}
|
||||
@@ -3246,12 +3246,13 @@ public class Presenter implements IPlugInPort {
|
||||
areas.put(c, componentArea);
|
||||
}
|
||||
return new Snapshot(currentProject.clone(), new HashSet<IDIYComponent<?>>(selectedComponents), areas,
|
||||
new HashSet<ContinuityArea>(drawingManager.getContinuityAreas()));
|
||||
new HashSet<ContinuityArea>(drawingManager.getContinuityAreas(currentProject)));
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<Area> checkContinuityAreaProximity(Size threshold) {
|
||||
return drawingManager.getContinuityAreaProximity((float)threshold.convertToPixels());
|
||||
return drawingManager.getContinuityAreaProximity(currentProject,
|
||||
(float) threshold.convertToPixels());
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -641,6 +641,10 @@
|
||||
<changeType>BUG_FIX</changeType>
|
||||
<description>Projects with a resistor, capacitor or other leaded component whose Width or Length had been cleared in the property editor could not be opened; such values now fall back to the default size, and Width and Length can no longer be left empty</description>
|
||||
</org.diylc.appframework.update.Change>
|
||||
<org.diylc.appframework.update.Change>
|
||||
<changeType>BUG_FIX</changeType>
|
||||
<description>'Highlight Connected Areas', netlists and AI analysis could ignore trace cuts after the board under them was moved or edited, or when the cuts were scrolled out of view, and showed the whole strip as connected</description>
|
||||
</org.diylc.appframework.update.Change>
|
||||
</changes>
|
||||
<url>https://github.com/bancika/diy-layout-creator/releases</url>
|
||||
</org.diylc.appframework.update.Version>
|
||||
|
||||
@@ -445,7 +445,7 @@ public class DrawingManagerTest {
|
||||
|
||||
drawCachedAndClipped(drawingManager, project, 100);
|
||||
// the netlist and continuity checks are built from the areas recorded while painting
|
||||
assertEquals(1, drawingManager.getContinuityAreas().size());
|
||||
assertEquals(1, drawingManager.getContinuityAreas(project).size());
|
||||
assertEquals(1, component.drawCount);
|
||||
|
||||
drawCachedAndClipped(drawingManager, project, 100);
|
||||
@@ -639,7 +639,7 @@ public class DrawingManagerTest {
|
||||
drawVisibleRect(drawingManager, project, null);
|
||||
|
||||
// the cut splits the strip in two for the netlist...
|
||||
assertEquals(2, drawingManager.getContinuityAreas().size());
|
||||
assertEquals(2, drawingManager.getContinuityAreas(project).size());
|
||||
// ...but the strip's own recorded area must stay whole, so that moving or deleting the cut
|
||||
// does not leave a hole behind
|
||||
Area recorded =
|
||||
@@ -647,6 +647,66 @@ public class DrawingManagerTest {
|
||||
assertTrue(recorded.contains(50, 5));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCutAppliesAfterStripIsRedrawn() {
|
||||
Project project = new Project();
|
||||
ConductorComponent strip = new ConductorComponent(new Rectangle2D.Double(0, 0, 100, 10), true);
|
||||
ConductorComponent cut = new ConductorComponent(new Rectangle2D.Double(40, 0, 20, 10), false);
|
||||
project.getComponents().add(strip);
|
||||
project.getComponents().add(cut);
|
||||
DrawingManager drawingManager = createDrawingManager();
|
||||
drawVisibleRect(drawingManager, project, null);
|
||||
|
||||
// an edit to the board records its areas again, after those of the cuts already on it
|
||||
drawingManager.invalidateComponent(strip);
|
||||
drawVisibleRect(drawingManager, project, null);
|
||||
|
||||
assertEquals(2, drawingManager.getContinuityAreas(project).size());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCutOutOfViewAppliesWhenAreasAreRecorded() {
|
||||
Project project = new Project();
|
||||
project.getComponents()
|
||||
.add(new ConductorComponent(new Rectangle2D.Double(0, 0, 300, 10), true));
|
||||
project.getComponents()
|
||||
.add(new SelfCullingConductorComponent(new Rectangle2D.Double(240, 0, 20, 10), false));
|
||||
DrawingManager drawingManager = createDrawingManager();
|
||||
|
||||
drawCachedAndClipped(drawingManager, project, 100);
|
||||
|
||||
assertEquals(2, drawingManager.getContinuityAreas(project).size());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testHighlightFollowsAreasRecordedAfterIt() {
|
||||
Project project = new Project();
|
||||
project.getComponents()
|
||||
.add(new ConductorComponent(new Rectangle2D.Double(0, 0, 100, 10), true));
|
||||
DrawingManager drawingManager = createDrawingManager();
|
||||
drawVisibleRect(drawingManager, project, null);
|
||||
drawingManager.findContinuityAreaAtPoint(project, new Point2D.Double(10, 5));
|
||||
|
||||
// nothing was edited, the cut was only drawn for the first time, e.g. scrolled into view
|
||||
project.getComponents()
|
||||
.add(new ConductorComponent(new Rectangle2D.Double(40, 0, 20, 10), false));
|
||||
drawVisibleRect(drawingManager, project, null);
|
||||
drawingManager.findContinuityAreaAtPoint(project, new Point2D.Double(10, 5));
|
||||
|
||||
BufferedImage img = render(drawingManager, project);
|
||||
assertTrue(img.getRGB(10, 5) != img.getRGB(80, 5));
|
||||
}
|
||||
|
||||
private BufferedImage render(DrawingManager drawingManager, Project project) {
|
||||
BufferedImage img = new BufferedImage(1000, 1000, BufferedImage.TYPE_INT_ARGB);
|
||||
Graphics2D g2d = img.createGraphics();
|
||||
drawingManager.drawProject(g2d, project, EnumSet.of(DrawOption.ZOOM), null, null,
|
||||
Collections.emptyList(), Collections.emptySet(), Collections.emptySet(), null, null, false,
|
||||
Collections.emptySet(), 1.0, 1.0, null);
|
||||
g2d.dispose();
|
||||
return img;
|
||||
}
|
||||
|
||||
private void drawVisibleRect(DrawingManager drawingManager, Project project,
|
||||
Rectangle2D visibleRect) {
|
||||
BufferedImage img = new BufferedImage(1000, 1000, BufferedImage.TYPE_INT_ARGB);
|
||||
@@ -732,6 +792,38 @@ public class DrawingManagerTest {
|
||||
}
|
||||
}
|
||||
|
||||
// skips drawing when clipped out, as TraceCut and the boards do
|
||||
public static class SelfCullingConductorComponent extends ConductorComponent {
|
||||
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
private Point2D center;
|
||||
|
||||
public SelfCullingConductorComponent(Rectangle2D shape, boolean positive) {
|
||||
super(shape, positive);
|
||||
this.center = new Point2D.Double(shape.getCenterX(), shape.getCenterY());
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getControlPointCount() {
|
||||
return 1;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Point2D getControlPoint(int index) {
|
||||
return center;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void draw(Graphics2D g2d, ComponentState componentState, boolean outlineMode,
|
||||
Project project, IDrawingObserver drawingObserver) {
|
||||
if (checkPointsClipped(g2d.getClip())) {
|
||||
return;
|
||||
}
|
||||
super.draw(g2d, componentState, outlineMode, project, drawingObserver);
|
||||
}
|
||||
}
|
||||
|
||||
// Simple delegating Graphics2D proxy for testing
|
||||
private static class Graphics2DProxy extends Graphics2D {
|
||||
private final Graphics2D delegate;
|
||||
|
||||
Reference in New Issue
Block a user