mirror of
https://github.com/bancika/diy-layout-creator
synced 2026-11-13 02:32:09 +10:00
Rotating and mirroring the selection with 'Sticky Points' enabled now keeps the circuit connected
This commit is contained in:
@@ -23,6 +23,7 @@ import java.awt.Graphics2D;
|
|||||||
import java.awt.Point;
|
import java.awt.Point;
|
||||||
import java.awt.Rectangle;
|
import java.awt.Rectangle;
|
||||||
import java.awt.dnd.DnDConstants;
|
import java.awt.dnd.DnDConstants;
|
||||||
|
import java.awt.geom.AffineTransform;
|
||||||
import java.awt.geom.Area;
|
import java.awt.geom.Area;
|
||||||
import java.awt.geom.Point2D;
|
import java.awt.geom.Point2D;
|
||||||
import java.awt.geom.Rectangle2D;
|
import java.awt.geom.Rectangle2D;
|
||||||
@@ -846,20 +847,27 @@ public class Presenter implements IPlugInPort {
|
|||||||
boolean snapToGrid = snapTo.equalsIgnoreCase(IPlugInPort.SNAP_TO_GRID);
|
boolean snapToGrid = snapTo.equalsIgnoreCase(IPlugInPort.SNAP_TO_GRID);
|
||||||
boolean snapToObjects = snapTo.equalsIgnoreCase(IPlugInPort.SNAP_TO_COMPONENTS);
|
boolean snapToObjects = snapTo.equalsIgnoreCase(IPlugInPort.SNAP_TO_COMPONENTS);
|
||||||
|
|
||||||
|
boolean sticky = configManager.readBoolean(IPlugInPort.STICKY_POINTS_KEY, true);
|
||||||
|
if (ctrlDown) {
|
||||||
|
sticky = !sticky;
|
||||||
|
}
|
||||||
|
|
||||||
if (altDown) {
|
if (altDown) {
|
||||||
Project oldProject = null;
|
Project oldProject = null;
|
||||||
if (key == IKeyProcessor.VK_RIGHT) {
|
if (key == IKeyProcessor.VK_RIGHT) {
|
||||||
oldProject = currentProject.clone();
|
oldProject = currentProject.clone();
|
||||||
rotateComponents(this.selectedComponents, 1, snapToGrid);
|
rotateComponents(this.selectedComponents, 1, snapToGrid, sticky);
|
||||||
} else if (key == IKeyProcessor.VK_LEFT) {
|
} else if (key == IKeyProcessor.VK_LEFT) {
|
||||||
oldProject = currentProject.clone();
|
oldProject = currentProject.clone();
|
||||||
rotateComponents(this.selectedComponents, -1, snapToGrid);
|
rotateComponents(this.selectedComponents, -1, snapToGrid, sticky);
|
||||||
} else if (key == IKeyProcessor.VK_H) {
|
} else if (key == IKeyProcessor.VK_H) {
|
||||||
oldProject = currentProject.clone();
|
oldProject = currentProject.clone();
|
||||||
mirrorComponents(this.selectedComponents, IComponentTransformer.HORIZONTAL, snapToGrid);
|
mirrorComponents(this.selectedComponents, IComponentTransformer.HORIZONTAL, snapToGrid,
|
||||||
|
sticky);
|
||||||
} else if (key == IKeyProcessor.VK_V) {
|
} else if (key == IKeyProcessor.VK_V) {
|
||||||
oldProject = currentProject.clone();
|
oldProject = currentProject.clone();
|
||||||
mirrorComponents(this.selectedComponents, IComponentTransformer.VERTICAL, snapToGrid);
|
mirrorComponents(this.selectedComponents, IComponentTransformer.VERTICAL, snapToGrid,
|
||||||
|
sticky);
|
||||||
} else
|
} else
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
@@ -869,11 +877,6 @@ public class Presenter implements IPlugInPort {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Expand control points to include all stuck components.
|
// Expand control points to include all stuck components.
|
||||||
boolean sticky = configManager.readBoolean(IPlugInPort.STICKY_POINTS_KEY, true);
|
|
||||||
if (ctrlDown) {
|
|
||||||
sticky = !sticky;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (sticky) {
|
if (sticky) {
|
||||||
includeStuckComponents(controlPointMap);
|
includeStuckComponents(controlPointMap);
|
||||||
}
|
}
|
||||||
@@ -1442,7 +1445,9 @@ public class Presenter implements IPlugInPort {
|
|||||||
|
|
||||||
private void includeStuckComponents(Map<IDIYComponent<?>, Set<Integer>> controlPointMap,
|
private void includeStuckComponents(Map<IDIYComponent<?>, Set<Integer>> controlPointMap,
|
||||||
Set<IDIYComponent<?>> excludedComponents) {
|
Set<IDIYComponent<?>> excludedComponents) {
|
||||||
int oldSize = controlPointMap.size();
|
// counted in points rather than components, a component that already had a free point in the
|
||||||
|
// map and now gains its rigid ones exposes new points for the next iteration to match
|
||||||
|
int oldSize = countControlPoints(controlPointMap);
|
||||||
LOG.trace("Expanding selected component map");
|
LOG.trace("Expanding selected component map");
|
||||||
for (IDIYComponent<?> component : currentProject.getComponents()) {
|
for (IDIYComponent<?> component : currentProject.getComponents()) {
|
||||||
// Check if there's a control point in the current selection
|
// Check if there's a control point in the current selection
|
||||||
@@ -1496,13 +1501,13 @@ public class Presenter implements IPlugInPort {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
int newSize = controlPointMap.size();
|
int newSize = countControlPoints(controlPointMap);
|
||||||
// As long as we're adding new components, do another iteration.
|
// As long as we're adding new points, do another iteration.
|
||||||
if (newSize > oldSize) {
|
if (newSize > oldSize) {
|
||||||
LOG.trace("Component count changed, trying one more time.");
|
LOG.trace("Point count changed, trying one more time.");
|
||||||
includeStuckComponents(controlPointMap, excludedComponents);
|
includeStuckComponents(controlPointMap, excludedComponents);
|
||||||
} else {
|
} else {
|
||||||
LOG.trace("Component count didn't change, done with expanding.");
|
LOG.trace("Point count didn't change, done with expanding.");
|
||||||
for (Map.Entry<IDIYComponent<?>, Set<Integer>> entry : controlPointMap.entrySet()) {
|
for (Map.Entry<IDIYComponent<?>, Set<Integer>> entry : controlPointMap.entrySet()) {
|
||||||
IDIYComponent<?> component = entry.getKey();
|
IDIYComponent<?> component = entry.getKey();
|
||||||
Set<Integer> selectedPoints = entry.getValue();
|
Set<Integer> selectedPoints = entry.getValue();
|
||||||
@@ -1528,6 +1533,14 @@ public class Presenter implements IPlugInPort {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static int countControlPoints(Map<IDIYComponent<?>, Set<Integer>> controlPointMap) {
|
||||||
|
int count = 0;
|
||||||
|
for (Set<Integer> indices : controlPointMap.values()) {
|
||||||
|
count += indices.size();
|
||||||
|
}
|
||||||
|
return count;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A group moves as a single rigid unit, so when one member is dragged along by a stuck control
|
* A group moves as a single rigid unit, so when one member is dragged along by a stuck control
|
||||||
* point the rest of the group has to follow with all of its control points.
|
* point the rest of the group has to follow with all of its control points.
|
||||||
@@ -1829,7 +1842,8 @@ public class Presenter implements IPlugInPort {
|
|||||||
if (!selectedComponents.isEmpty()) {
|
if (!selectedComponents.isEmpty()) {
|
||||||
LOG.trace("Rotating selected components");
|
LOG.trace("Rotating selected components");
|
||||||
Project oldProject = currentProject.clone();
|
Project oldProject = currentProject.clone();
|
||||||
rotateComponents(this.selectedComponents, direction, isSnapToGrid());
|
rotateComponents(this.selectedComponents, direction, isSnapToGrid(),
|
||||||
|
configManager.readBoolean(IPlugInPort.STICKY_POINTS_KEY, true));
|
||||||
|
|
||||||
notifyProjectModifiedIfNeeded(oldProject, "Rotate Selection", true, true);
|
notifyProjectModifiedIfNeeded(oldProject, "Rotate Selection", true, true);
|
||||||
}
|
}
|
||||||
@@ -1840,11 +1854,13 @@ public class Presenter implements IPlugInPort {
|
|||||||
* @param direction 1 for clockwise, -1 for counter-clockwise
|
* @param direction 1 for clockwise, -1 for counter-clockwise
|
||||||
*/
|
*/
|
||||||
@SuppressWarnings("unchecked")
|
@SuppressWarnings("unchecked")
|
||||||
private void rotateComponents(Collection<IDIYComponent<?>> components, int direction, boolean snapToGrid) {
|
private void rotateComponents(Collection<IDIYComponent<?>> components, int direction,
|
||||||
|
boolean snapToGrid, boolean sticky) {
|
||||||
Point2D center = getCenterOf(components, snapToGrid);
|
Point2D center = getCenterOf(components, snapToGrid);
|
||||||
|
StuckPointFollower follower = createStuckPointFollower(components, sticky);
|
||||||
|
|
||||||
boolean canRotate = true;
|
boolean canRotate = true;
|
||||||
for (IDIYComponent<?> component : selectedComponents) {
|
for (IDIYComponent<?> component : follower.getTransformedComponents()) {
|
||||||
ComponentType type =
|
ComponentType type =
|
||||||
ComponentProcessor.getInstance().extractComponentTypeFrom(
|
ComponentProcessor.getInstance().extractComponentTypeFrom(
|
||||||
(Class<? extends IDIYComponent<?>>) component.getClass());
|
(Class<? extends IDIYComponent<?>>) component.getClass());
|
||||||
@@ -1859,7 +1875,7 @@ public class Presenter implements IPlugInPort {
|
|||||||
ROTATE_SELECTION, IView.YES_NO_OPTION, IView.QUESTION_MESSAGE) != IView.YES_OPTION)
|
ROTATE_SELECTION, IView.YES_NO_OPTION, IView.QUESTION_MESSAGE) != IView.YES_OPTION)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
for (IDIYComponent<?> component : selectedComponents) {
|
for (IDIYComponent<?> component : follower.getTransformedComponents()) {
|
||||||
ComponentType type =
|
ComponentType type =
|
||||||
ComponentProcessor.getInstance().extractComponentTypeFrom(
|
ComponentProcessor.getInstance().extractComponentTypeFrom(
|
||||||
(Class<? extends IDIYComponent<?>>) component.getClass());
|
(Class<? extends IDIYComponent<?>>) component.getClass());
|
||||||
@@ -1867,6 +1883,8 @@ public class Presenter implements IPlugInPort {
|
|||||||
type.getTransformer().rotate(component, center, direction);
|
type.getTransformer().rotate(component, center, direction);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
follower.follow(AffineTransform.getRotateInstance(Math.PI / 2 * direction, center.getX(),
|
||||||
|
center.getY()));
|
||||||
|
|
||||||
// Remove from area map.
|
// Remove from area map.
|
||||||
drawingManager.clearComponentAreaMap();
|
drawingManager.clearComponentAreaMap();
|
||||||
@@ -1879,19 +1897,22 @@ public class Presenter implements IPlugInPort {
|
|||||||
LOG.trace("Mirroring selected components");
|
LOG.trace("Mirroring selected components");
|
||||||
Project oldProject = currentProject.clone();
|
Project oldProject = currentProject.clone();
|
||||||
|
|
||||||
mirrorComponents(selectedComponents, direction, isSnapToGrid());
|
mirrorComponents(selectedComponents, direction, isSnapToGrid(),
|
||||||
|
configManager.readBoolean(IPlugInPort.STICKY_POINTS_KEY, true));
|
||||||
|
|
||||||
notifyProjectModifiedIfNeeded(oldProject, "Mirror Selection", true, true);
|
notifyProjectModifiedIfNeeded(oldProject, "Mirror Selection", true, true);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@SuppressWarnings("unchecked")
|
@SuppressWarnings("unchecked")
|
||||||
private void mirrorComponents(Collection<IDIYComponent<?>> components, int direction, boolean snapToGrid) {
|
private void mirrorComponents(Collection<IDIYComponent<?>> components, int direction,
|
||||||
|
boolean snapToGrid, boolean sticky) {
|
||||||
Point2D center = getCenterOf(components, snapToGrid);
|
Point2D center = getCenterOf(components, snapToGrid);
|
||||||
|
StuckPointFollower follower = createStuckPointFollower(components, sticky);
|
||||||
|
|
||||||
boolean canMirror = true;
|
boolean canMirror = true;
|
||||||
boolean changesCircuit = false;
|
boolean changesCircuit = false;
|
||||||
for (IDIYComponent<?> component : components) {
|
for (IDIYComponent<?> component : follower.getTransformedComponents()) {
|
||||||
ComponentType type =
|
ComponentType type =
|
||||||
ComponentProcessor.getInstance().extractComponentTypeFrom(
|
ComponentProcessor.getInstance().extractComponentTypeFrom(
|
||||||
(Class<? extends IDIYComponent<?>>) component.getClass());
|
(Class<? extends IDIYComponent<?>>) component.getClass());
|
||||||
@@ -1913,7 +1934,7 @@ public class Presenter implements IPlugInPort {
|
|||||||
MIRROR_SELECTION, IView.YES_NO_OPTION, IView.QUESTION_MESSAGE) != IView.YES_OPTION)
|
MIRROR_SELECTION, IView.YES_NO_OPTION, IView.QUESTION_MESSAGE) != IView.YES_OPTION)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
for (IDIYComponent<?> component : components) {
|
for (IDIYComponent<?> component : follower.getTransformedComponents()) {
|
||||||
ComponentType type =
|
ComponentType type =
|
||||||
ComponentProcessor.getInstance().extractComponentTypeFrom(
|
ComponentProcessor.getInstance().extractComponentTypeFrom(
|
||||||
(Class<? extends IDIYComponent<?>>) component.getClass());
|
(Class<? extends IDIYComponent<?>>) component.getClass());
|
||||||
@@ -1921,12 +1942,34 @@ public class Presenter implements IPlugInPort {
|
|||||||
type.getTransformer().mirror(component, center, direction);
|
type.getTransformer().mirror(component, center, direction);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if (direction == IComponentTransformer.HORIZONTAL) {
|
||||||
|
follower.follow(new AffineTransform(-1, 0, 0, 1, 2 * center.getX(), 0));
|
||||||
|
} else {
|
||||||
|
follower.follow(new AffineTransform(1, 0, 0, -1, 0, 2 * center.getY()));
|
||||||
|
}
|
||||||
|
|
||||||
// Remove from area map.
|
// Remove from area map.
|
||||||
drawingManager.clearComponentAreaMap();
|
drawingManager.clearComponentAreaMap();
|
||||||
drawingManager.clearContinuityArea();
|
drawingManager.clearContinuityArea();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private StuckPointFollower createStuckPointFollower(Collection<IDIYComponent<?>> components,
|
||||||
|
boolean sticky) {
|
||||||
|
Map<IDIYComponent<?>, Set<Integer>> controlPointMap =
|
||||||
|
new HashMap<IDIYComponent<?>, Set<Integer>>();
|
||||||
|
for (IDIYComponent<?> component : components) {
|
||||||
|
Set<Integer> indices = new HashSet<Integer>();
|
||||||
|
for (int i = 0; i < component.getControlPointCount(); i++) {
|
||||||
|
indices.add(i);
|
||||||
|
}
|
||||||
|
controlPointMap.put(component, indices);
|
||||||
|
}
|
||||||
|
if (sticky) {
|
||||||
|
includeStuckComponents(controlPointMap);
|
||||||
|
}
|
||||||
|
return new StuckPointFollower(controlPointMap, DrawingManager.CONTROL_POINT_SIZE);
|
||||||
|
}
|
||||||
|
|
||||||
private Point2D getCenterOf(Collection<IDIYComponent<?>> components, boolean snapToGrid) {
|
private Point2D getCenterOf(Collection<IDIYComponent<?>> components, boolean snapToGrid) {
|
||||||
// Determine center of rotation
|
// Determine center of rotation
|
||||||
double minX = Integer.MAX_VALUE;
|
double minX = Integer.MAX_VALUE;
|
||||||
|
|||||||
@@ -0,0 +1,114 @@
|
|||||||
|
/*
|
||||||
|
|
||||||
|
DIY Layout Creator (DIYLC).
|
||||||
|
Copyright (c) 2009-2025 held jointly by the individual authors.
|
||||||
|
|
||||||
|
This file is part of DIYLC.
|
||||||
|
|
||||||
|
DIYLC is free software: you can redistribute it and/or modify
|
||||||
|
it under the terms of the GNU General Public License as published by
|
||||||
|
the Free Software Foundation, either version 3 of the License, or
|
||||||
|
(at your option) any later version.
|
||||||
|
|
||||||
|
DIYLC is distributed in the hope that it will be useful,
|
||||||
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
GNU General Public License for more details.
|
||||||
|
|
||||||
|
You should have received a copy of the GNU General Public License
|
||||||
|
along with DIYLC. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
*/
|
||||||
|
package org.diylc.presenter;
|
||||||
|
|
||||||
|
import java.awt.geom.AffineTransform;
|
||||||
|
import java.awt.geom.Point2D;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.HashSet;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.Set;
|
||||||
|
|
||||||
|
import org.diylc.core.IDIYComponent;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Keeps stuck control points attached while a selection is rotated or mirrored. It takes a control
|
||||||
|
* point map expanded with the stuck components: components with all of their control points in the
|
||||||
|
* map are transformed as a whole, while the remaining points, typically the free end of a wire or
|
||||||
|
* a lead, are captured before the transformation and moved onto the point they were stuck to
|
||||||
|
* afterwards.
|
||||||
|
*
|
||||||
|
* @author Branislav Stojkovic
|
||||||
|
*/
|
||||||
|
public class StuckPointFollower {
|
||||||
|
|
||||||
|
private final Set<IDIYComponent<?>> transformedComponents = new HashSet<IDIYComponent<?>>();
|
||||||
|
private final List<StuckPoint> stuckPoints = new ArrayList<StuckPoint>();
|
||||||
|
|
||||||
|
public StuckPointFollower(Map<IDIYComponent<?>, Set<Integer>> controlPointMap,
|
||||||
|
double tolerance) {
|
||||||
|
for (Map.Entry<IDIYComponent<?>, Set<Integer>> entry : controlPointMap.entrySet()) {
|
||||||
|
if (entry.getValue().size() >= entry.getKey().getControlPointCount()) {
|
||||||
|
transformedComponents.add(entry.getKey());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for (Map.Entry<IDIYComponent<?>, Set<Integer>> entry : controlPointMap.entrySet()) {
|
||||||
|
IDIYComponent<?> component = entry.getKey();
|
||||||
|
if (transformedComponents.contains(component)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
for (Integer index : entry.getValue()) {
|
||||||
|
stuckPoints.add(findAnchor(component, index, tolerance));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public Set<IDIYComponent<?>> getTransformedComponents() {
|
||||||
|
return transformedComponents;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Moves every stuck point onto the current location of its anchor. A point that is not stuck
|
||||||
|
* directly to a transformed component, only to another stuck point, has no anchor and is mapped
|
||||||
|
* through {@code transform} instead.
|
||||||
|
*/
|
||||||
|
public void follow(AffineTransform transform) {
|
||||||
|
for (StuckPoint stuckPoint : stuckPoints) {
|
||||||
|
Point2D p;
|
||||||
|
if (stuckPoint.anchor() == null) {
|
||||||
|
p = transform.transform(stuckPoint.originalLocation(), null);
|
||||||
|
} else {
|
||||||
|
p = new Point2D.Double();
|
||||||
|
p.setLocation(stuckPoint.anchor().getControlPoint(stuckPoint.anchorIndex()));
|
||||||
|
}
|
||||||
|
stuckPoint.component().setControlPoint(p, stuckPoint.index());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// the anchor is remembered by index rather than by location, so the point stays on the same
|
||||||
|
// pin even when a transformer renumbers positions, as mirroring an IC does
|
||||||
|
private StuckPoint findAnchor(IDIYComponent<?> component, int index, double tolerance) {
|
||||||
|
Point2D location = new Point2D.Double();
|
||||||
|
location.setLocation(component.getControlPoint(index));
|
||||||
|
IDIYComponent<?> anchor = null;
|
||||||
|
int anchorIndex = -1;
|
||||||
|
double bestDistance = tolerance;
|
||||||
|
for (IDIYComponent<?> candidate : transformedComponents) {
|
||||||
|
for (int i = 0; i < candidate.getControlPointCount(); i++) {
|
||||||
|
if (candidate.isControlPointSticky(i)) {
|
||||||
|
double distance = location.distance(candidate.getControlPoint(i));
|
||||||
|
if (distance < bestDistance) {
|
||||||
|
bestDistance = distance;
|
||||||
|
anchor = candidate;
|
||||||
|
anchorIndex = i;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return new StuckPoint(component, index, location, anchor, anchorIndex);
|
||||||
|
}
|
||||||
|
|
||||||
|
private record StuckPoint(IDIYComponent<?> component, int index, Point2D originalLocation,
|
||||||
|
IDIYComponent<?> anchor, int anchorIndex) {
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -665,6 +665,10 @@
|
|||||||
<releaseDate>2026-10-17 00:00:00.000 CET</releaseDate>
|
<releaseDate>2026-10-17 00:00:00.000 CET</releaseDate>
|
||||||
<name></name>
|
<name></name>
|
||||||
<changes>
|
<changes>
|
||||||
|
<org.diylc.appframework.update.Change>
|
||||||
|
<changeType>IMPROVEMENT</changeType>
|
||||||
|
<description>Rotating and mirroring the selection with 'Sticky Points' enabled now keeps the circuit connected, the same way moving it does - wires and leads stuck to the selection stay attached to their pins, and other rigid components stuck to it are rotated or mirrored along with it</description>
|
||||||
|
</org.diylc.appframework.update.Change>
|
||||||
<org.diylc.appframework.update.Change>
|
<org.diylc.appframework.update.Change>
|
||||||
<changeType>BUG_FIX</changeType>
|
<changeType>BUG_FIX</changeType>
|
||||||
<description>Gerber export on computers set to a language that uses a decimal comma (e.g. German, French, Serbian) produced files in which drill holes, pads and traces had the wrong size - a 0.8mm hole came out as 0mm and traces and the board outline had no width, so the files could not be used for fabrication</description>
|
<description>Gerber export on computers set to a language that uses a decimal comma (e.g. German, French, Serbian) produced files in which drill holes, pads and traces had the wrong size - a 0.8mm hole came out as 0mm and traces and the board outline had no width, so the files could not be used for fabrication</description>
|
||||||
|
|||||||
@@ -0,0 +1,156 @@
|
|||||||
|
/*
|
||||||
|
|
||||||
|
DIY Layout Creator (DIYLC).
|
||||||
|
Copyright (c) 2009-2025 held jointly by the individual authors.
|
||||||
|
|
||||||
|
This file is part of DIYLC.
|
||||||
|
|
||||||
|
DIYLC is free software: you can redistribute it and/or modify
|
||||||
|
it under the terms of the GNU General Public License as published by
|
||||||
|
the Free Software Foundation, either version 3 of the License, or
|
||||||
|
(at your option) any later version.
|
||||||
|
|
||||||
|
DIYLC is distributed in the hope that it will be useful,
|
||||||
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
GNU General Public License for more details.
|
||||||
|
|
||||||
|
You should have received a copy of the GNU General Public License
|
||||||
|
along with DIYLC. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
*/
|
||||||
|
package org.diylc.presenter;
|
||||||
|
|
||||||
|
import static org.junit.Assert.assertEquals;
|
||||||
|
|
||||||
|
import java.awt.Graphics2D;
|
||||||
|
import java.awt.geom.AffineTransform;
|
||||||
|
import java.awt.geom.Point2D;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.Set;
|
||||||
|
|
||||||
|
import org.diylc.components.AbstractComponent;
|
||||||
|
import org.diylc.core.ComponentState;
|
||||||
|
import org.diylc.core.IDIYComponent;
|
||||||
|
import org.diylc.core.IDrawingObserver;
|
||||||
|
import org.diylc.core.Project;
|
||||||
|
import org.diylc.core.VisibilityPolicy;
|
||||||
|
import org.junit.Test;
|
||||||
|
|
||||||
|
public class StuckPointFollowerTest {
|
||||||
|
|
||||||
|
private static final double TOLERANCE = 7;
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testFullyIncludedComponentsAreTransformed() {
|
||||||
|
TestPart part = new TestPart(new Point2D.Double(0, 0), new Point2D.Double(40, 0));
|
||||||
|
TestPart wire = new TestPart(new Point2D.Double(40, 0), new Point2D.Double(100, 0));
|
||||||
|
Map<IDIYComponent<?>, Set<Integer>> controlPointMap =
|
||||||
|
new HashMap<IDIYComponent<?>, Set<Integer>>();
|
||||||
|
controlPointMap.put(part, Set.of(0, 1));
|
||||||
|
controlPointMap.put(wire, Set.of(0));
|
||||||
|
|
||||||
|
StuckPointFollower follower = new StuckPointFollower(controlPointMap, TOLERANCE);
|
||||||
|
|
||||||
|
assertEquals(Set.of(part), follower.getTransformedComponents());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testStuckPointFollowsAnchorByIndex() {
|
||||||
|
TestPart part = new TestPart(new Point2D.Double(0, 0), new Point2D.Double(40, 0));
|
||||||
|
TestPart wire = new TestPart(new Point2D.Double(40, 0), new Point2D.Double(100, 0));
|
||||||
|
Map<IDIYComponent<?>, Set<Integer>> controlPointMap =
|
||||||
|
new HashMap<IDIYComponent<?>, Set<Integer>>();
|
||||||
|
controlPointMap.put(part, Set.of(0, 1));
|
||||||
|
controlPointMap.put(wire, Set.of(0));
|
||||||
|
StuckPointFollower follower = new StuckPointFollower(controlPointMap, TOLERANCE);
|
||||||
|
|
||||||
|
// a transformer that does not map points through the plain reflection, the way mirroring an
|
||||||
|
// IC moves its pins
|
||||||
|
part.setControlPoint(new Point2D.Double(-20, 30), 1);
|
||||||
|
follower.follow(new AffineTransform(-1, 0, 0, 1, 40, 0));
|
||||||
|
|
||||||
|
assertEquals(new Point2D.Double(-20, 30), wire.getControlPoint(0));
|
||||||
|
assertEquals(new Point2D.Double(100, 0), wire.getControlPoint(1));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testPointWithoutAnchorIsTransformed() {
|
||||||
|
TestPart part = new TestPart(new Point2D.Double(0, 0), new Point2D.Double(40, 0));
|
||||||
|
TestPart wire = new TestPart(new Point2D.Double(40, 20), new Point2D.Double(100, 0));
|
||||||
|
Map<IDIYComponent<?>, Set<Integer>> controlPointMap =
|
||||||
|
new HashMap<IDIYComponent<?>, Set<Integer>>();
|
||||||
|
controlPointMap.put(part, Set.of(0, 1));
|
||||||
|
controlPointMap.put(wire, Set.of(0));
|
||||||
|
StuckPointFollower follower = new StuckPointFollower(controlPointMap, TOLERANCE);
|
||||||
|
|
||||||
|
follower.follow(AffineTransform.getRotateInstance(Math.PI / 2, 20, 0));
|
||||||
|
|
||||||
|
assertEquals(0, wire.getControlPoint(0).distance(0, 20), 1e-9);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static class TestPart extends AbstractComponent<Void> {
|
||||||
|
|
||||||
|
private static final long serialVersionUID = 1L;
|
||||||
|
|
||||||
|
private final Point2D[] points;
|
||||||
|
|
||||||
|
public TestPart(Point2D... points) {
|
||||||
|
this.points = points;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getControlPointCount() {
|
||||||
|
return points.length;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Point2D getControlPoint(int index) {
|
||||||
|
return points[index];
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setControlPoint(Point2D point, int index) {
|
||||||
|
points[index].setLocation(point);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isControlPointSticky(int index) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean canControlPointOverlap(int index) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public VisibilityPolicy getControlPointVisibilityPolicy(int index) {
|
||||||
|
return VisibilityPolicy.WHEN_SELECTED;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getControlPointNodeName(int index) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Void getValue() {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setValue(Void value) {
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void draw(Graphics2D g2d, ComponentState componentState, boolean outlineMode,
|
||||||
|
Project project, IDrawingObserver drawingObserver) {
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void drawIcon(Graphics2D g2d, int width, int height) {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user