mirror of
https://github.com/bancika/diy-layout-creator
synced 2026-11-07 12:24:58 +10:00
stretch between
This commit is contained in:
@@ -295,9 +295,39 @@ public abstract class AbstractComponent<T> implements IDIYComponent<T> {
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean canPointMoveFreely(int pointIndex) {
|
||||
public boolean canPointMoveFreely(int pointIndex) {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void stretchBetween(Point2D first, Point2D second) {
|
||||
int count = getControlPointCount();
|
||||
// fresh instances because implementations are free to keep the point objects they are handed
|
||||
setControlPoint(new Point2D.Double(first.getX(), first.getY()), 0);
|
||||
if (count > 1) {
|
||||
setControlPoint(new Point2D.Double(second.getX(), second.getY()), count - 1);
|
||||
}
|
||||
for (int i = 1; i < count - 1; i++) {
|
||||
setControlPoint(getStretchedPoint(first, second, i), i);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Decides where a control point that is neither of the two ends lands when the component is
|
||||
* stretched between them, see {@link #stretchBetween(Point2D, Point2D)}. The default spaces the
|
||||
* points evenly along the straight line joining the ends. Override to place a point that carries
|
||||
* a meaning of its own, such as one that positions a label.
|
||||
*
|
||||
* @param first
|
||||
* @param second
|
||||
* @param index
|
||||
* @return position of the control point at the specified index.
|
||||
*/
|
||||
protected Point2D getStretchedPoint(Point2D first, Point2D second, int index) {
|
||||
double t = (double) index / (getControlPointCount() - 1);
|
||||
return new Point2D.Double(first.getX() + t * (second.getX() - first.getX()),
|
||||
first.getY() + t * (second.getY() - first.getY()));
|
||||
}
|
||||
|
||||
@Override
|
||||
public Rectangle2D getCachingBounds() {
|
||||
|
||||
@@ -52,6 +52,7 @@ public abstract class AbstractCurvedComponent<T> extends AbstractTransparentComp
|
||||
|
||||
public static Color GUIDELINE_COLOR = Color.blue;
|
||||
public static Size DEFAULT_SIZE = new Size(1d, SizeUnit.in);
|
||||
public static double CREATION_CURVATURE = 0.1;
|
||||
|
||||
// for backward compatibility
|
||||
protected Point2D[] controlPoints = null;
|
||||
@@ -287,6 +288,31 @@ public abstract class AbstractCurvedComponent<T> extends AbstractTransparentComp
|
||||
// }
|
||||
}
|
||||
|
||||
/**
|
||||
* Bows the curve to one side instead of leaving the intermediate points on the straight line
|
||||
* joining the ends, so that a freshly created component reads as a curve that can be reshaped
|
||||
* rather than as a rigid line. The sine envelope vanishes at both ends and peaks in the middle,
|
||||
* which yields a symmetric arc for any point count. The offset always points down the screen so
|
||||
* that the wire sags the same way no matter which end was drawn first.
|
||||
*/
|
||||
@Override
|
||||
protected Point2D getStretchedPoint(Point2D first, Point2D second, int index) {
|
||||
Point2D p = super.getStretchedPoint(first, second, index);
|
||||
double dx = second.getX() - first.getX();
|
||||
double dy = second.getY() - first.getY();
|
||||
double length = Math.hypot(dx, dy);
|
||||
if (length == 0) {
|
||||
return p;
|
||||
}
|
||||
if (dx < 0 || (dx == 0 && dy > 0)) {
|
||||
dx = -dx;
|
||||
dy = -dy;
|
||||
}
|
||||
double t = (double) index / (getControlPointCount() - 1);
|
||||
double offset = CREATION_CURVATURE * length * Math.sin(Math.PI * t);
|
||||
return new Point2D.Double(p.getX() - dy / length * offset, p.getY() + dx / length * offset);
|
||||
}
|
||||
|
||||
private Point2D findThirdPoint(Point2D p0, Point2D p) {
|
||||
return new Point2D.Double(2 * p0.getX() - p.getX(), 2 * p0.getY() - p.getY());
|
||||
}
|
||||
|
||||
+26
-10
@@ -237,24 +237,40 @@ public abstract class AbstractOrthogonalComponent<T> extends AbstractTransparent
|
||||
newPoints[0] = start;
|
||||
newPoints[pointCount.count - 1] = end;
|
||||
|
||||
// spread the bend points so that the route comes out as an even staircase: they advance evenly
|
||||
// along the axis the route travels last, and sit halfway between those steps on the other axis
|
||||
int bendCount = pointCount.count - 2;
|
||||
boolean horizontalFirst = getStartDirection() == OrientationHV.HORIZONTAL;
|
||||
for (int i = 1; i <= bendCount; i++) {
|
||||
double along = (double) i / bendCount;
|
||||
double across = (2d * i - 1) / (2d * bendCount);
|
||||
double tx = horizontalFirst ? across : along;
|
||||
double ty = horizontalFirst ? along : across;
|
||||
newPoints[i] =
|
||||
new Point2D.Double(start.getX() + (end.getX() - start.getX()) * tx, start.getY()
|
||||
+ (end.getY() - start.getY()) * ty);
|
||||
newPoints[i] = getBendPoint(start, end, i, bendCount);
|
||||
}
|
||||
|
||||
this.controlPoints = newPoints;
|
||||
this.pointCount = pointCount;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Point2D getStretchedPoint(Point2D first, Point2D second, int index) {
|
||||
return getBendPoint(first, second, index, getControlPointCount() - 2);
|
||||
}
|
||||
|
||||
/**
|
||||
* Spreads the bend points so that the route comes out as an even staircase: they advance evenly
|
||||
* along the axis the route travels last, and sit halfway between those steps on the other axis.
|
||||
*
|
||||
* @param start
|
||||
* @param end
|
||||
* @param index
|
||||
* @param bendCount number of control points between the two ends
|
||||
* @return position of the bend point at the specified index.
|
||||
*/
|
||||
private Point2D getBendPoint(Point2D start, Point2D end, int index, int bendCount) {
|
||||
double along = (double) index / bendCount;
|
||||
double across = (2d * index - 1) / (2d * bendCount);
|
||||
boolean horizontalFirst = getStartDirection() == OrientationHV.HORIZONTAL;
|
||||
double tx = horizontalFirst ? across : along;
|
||||
double ty = horizontalFirst ? along : across;
|
||||
return new Point2D.Double(start.getX() + (end.getX() - start.getX()) * tx, start.getY()
|
||||
+ (end.getY() - start.getY()) * ty);
|
||||
}
|
||||
|
||||
/**
|
||||
* Not an editable property on purpose. Every route shape is reachable by dragging the bend
|
||||
* points, so exposing this would only give the user a second, less discoverable way to do the
|
||||
|
||||
@@ -105,6 +105,17 @@ public interface IDIYComponent<T> extends Serializable, Cloneable {
|
||||
*/
|
||||
void setControlPoint(Point2D point, int index);
|
||||
|
||||
/**
|
||||
* Positions the component so that it spans the two points, which is how components created with
|
||||
* <code>CreationMethod.POINT_BY_POINT</code> are shaped while the user is placing them. The first
|
||||
* and the last control point go to the two ends; where the points in between land is up to the
|
||||
* component.
|
||||
*
|
||||
* @param first
|
||||
* @param second
|
||||
*/
|
||||
void stretchBetween(Point2D first, Point2D second);
|
||||
|
||||
/**
|
||||
* @param index
|
||||
* @return true, if the specified control point may stick to control points of other components.
|
||||
|
||||
@@ -123,23 +123,21 @@ public class InstantiationManager {
|
||||
firstControlPoint = scaledPoint;
|
||||
componentSlot = instantiateComponent(componentTypeSlot, firstControlPoint, currentProject);
|
||||
|
||||
// Set the other control point to the same location, we'll
|
||||
// move it later when mouse moves.
|
||||
componentSlot.get(0).setControlPoint(firstControlPoint, 0);
|
||||
componentSlot.get(0).setControlPoint(firstControlPoint, 1);
|
||||
// Collapse the component onto the first point, we'll stretch it later when mouse moves.
|
||||
componentSlot.get(0).stretchBetween(firstControlPoint, firstControlPoint);
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates component in the slot with the new second control point.
|
||||
*
|
||||
* Updates component in the slot with the new end control point.
|
||||
*
|
||||
* @param scaledPoint
|
||||
* @return true, if any change is made
|
||||
*/
|
||||
public boolean updatePointByPoint(Point2D scaledPoint) {
|
||||
boolean changeMade = !scaledPoint.equals(potentialControlPoint);
|
||||
potentialControlPoint = scaledPoint;
|
||||
if (componentSlot != null && !componentSlot.isEmpty()) {
|
||||
componentSlot.get(0).setControlPoint(scaledPoint, 1);
|
||||
if (componentSlot != null && !componentSlot.isEmpty() && firstControlPoint != null) {
|
||||
componentSlot.get(0).stretchBetween(firstControlPoint, scaledPoint);
|
||||
}
|
||||
return changeMade;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,131 @@
|
||||
/*
|
||||
|
||||
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.components;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
|
||||
import java.awt.Color;
|
||||
import java.awt.Graphics2D;
|
||||
import java.awt.geom.Path2D;
|
||||
import java.awt.geom.Point2D;
|
||||
|
||||
import org.junit.Test;
|
||||
|
||||
import org.diylc.components.AbstractCurvedComponent.PointCount;
|
||||
import org.diylc.core.ComponentState;
|
||||
import org.diylc.core.IDrawingObserver;
|
||||
|
||||
public class AbstractCurvedComponentTest {
|
||||
|
||||
private static final double DELTA = 1e-6;
|
||||
|
||||
@Test
|
||||
public void stretchBetween_putsTheEndsExactlyOnTheSuppliedPoints() {
|
||||
CurvedTestComponent c = new CurvedTestComponent();
|
||||
c.stretchBetween(new Point2D.Double(10, 20), new Point2D.Double(110, 20));
|
||||
assertPoint(10, 20, c.getControlPoint(0));
|
||||
assertPoint(110, 20, c.getControlPoint(3));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void stretchBetween_bowsTheIntermediatePointsOffTheChord() {
|
||||
CurvedTestComponent c = new CurvedTestComponent();
|
||||
c.stretchBetween(new Point2D.Double(0, 0), new Point2D.Double(100, 0));
|
||||
assertTrue("first handle should not sit on the chord", c.getControlPoint(1).getY() > 1);
|
||||
assertTrue("second handle should not sit on the chord", c.getControlPoint(2).getY() > 1);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void stretchBetween_bowIsSymmetric() {
|
||||
CurvedTestComponent c = new CurvedTestComponent();
|
||||
c.stretchBetween(new Point2D.Double(0, 0), new Point2D.Double(100, 0));
|
||||
assertEquals(c.getControlPoint(1).getY(), c.getControlPoint(2).getY(), DELTA);
|
||||
assertEquals(100 - c.getControlPoint(2).getX(), c.getControlPoint(1).getX(), DELTA);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void stretchBetween_sagsTheSameWayRegardlessOfDrawingDirection() {
|
||||
CurvedTestComponent forward = new CurvedTestComponent();
|
||||
forward.stretchBetween(new Point2D.Double(0, 0), new Point2D.Double(100, 0));
|
||||
CurvedTestComponent backward = new CurvedTestComponent();
|
||||
backward.stretchBetween(new Point2D.Double(100, 0), new Point2D.Double(0, 0));
|
||||
assertEquals(forward.getControlPoint(1).getY(), backward.getControlPoint(2).getY(), DELTA);
|
||||
assertEquals(forward.getControlPoint(2).getY(), backward.getControlPoint(1).getY(), DELTA);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void stretchBetween_scalesTheBowWithTheDistance() {
|
||||
CurvedTestComponent shortWire = new CurvedTestComponent();
|
||||
shortWire.stretchBetween(new Point2D.Double(0, 0), new Point2D.Double(50, 0));
|
||||
CurvedTestComponent longWire = new CurvedTestComponent();
|
||||
longWire.stretchBetween(new Point2D.Double(0, 0), new Point2D.Double(200, 0));
|
||||
assertEquals(4 * shortWire.getControlPoint(1).getY(), longWire.getControlPoint(1).getY(), DELTA);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void stretchBetween_collapsesOntoASinglePointWithoutDividingByZero() {
|
||||
CurvedTestComponent c = new CurvedTestComponent();
|
||||
c.stretchBetween(new Point2D.Double(40, 40), new Point2D.Double(40, 40));
|
||||
for (int i = 0; i < c.getControlPointCount(); i++) {
|
||||
assertPoint(40, 40, c.getControlPoint(i));
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void stretchBetween_keepsTheEndsForEveryPointCount() {
|
||||
for (PointCount pointCount : PointCount.values()) {
|
||||
CurvedTestComponent c = new CurvedTestComponent();
|
||||
c.setPointCount(pointCount);
|
||||
c.stretchBetween(new Point2D.Double(0, 0), new Point2D.Double(100, 60));
|
||||
assertPoint(0, 0, c.getControlPoint(0));
|
||||
assertPoint(100, 60, c.getControlPoint(c.getControlPointCount() - 1));
|
||||
}
|
||||
}
|
||||
|
||||
private static void assertPoint(double x, double y, Point2D actual) {
|
||||
assertEquals(x, actual.getX(), DELTA);
|
||||
assertEquals(y, actual.getY(), DELTA);
|
||||
}
|
||||
|
||||
private static class CurvedTestComponent extends AbstractCurvedComponent<Void> {
|
||||
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
@Override
|
||||
protected void drawCurve(Path2D curve, Graphics2D g2d, ComponentState componentState,
|
||||
IDrawingObserver drawingObserver) {}
|
||||
|
||||
@Override
|
||||
protected Color getDefaultColor() {
|
||||
return Color.black;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Void getValue() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setValue(Void value) {}
|
||||
}
|
||||
}
|
||||
+26
@@ -130,6 +130,32 @@ public class AbstractOrthogonalComponentTest {
|
||||
assertPoint(100, 50, c.getControlPoint(2));
|
||||
}
|
||||
|
||||
// --- stretching ---
|
||||
|
||||
@Test
|
||||
public void stretchBetween_keepsTheEndsAndStaysAxisAligned() {
|
||||
OrthogonalTestComponent c = component(0, 0, 100, 50, 60, 20);
|
||||
c.stretchBetween(new Point2D.Double(10, 10), new Point2D.Double(210, 110));
|
||||
assertPoint(10, 10, c.getControlPoint(0));
|
||||
assertPoint(210, 110, c.getControlPoint(2));
|
||||
assertAxisAligned(c);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void stretchBetween_placesTheBendsWhereSetPointCountWould() {
|
||||
OrthogonalTestComponent stretched = component(0, 0, 100, 50, 60, 20);
|
||||
stretched.setPointCount(PointCount.FIVE);
|
||||
stretched.stretchBetween(new Point2D.Double(0, 0), new Point2D.Double(100, 50));
|
||||
|
||||
OrthogonalTestComponent expanded = component(0, 0, 100, 50, 60, 20);
|
||||
expanded.setPointCount(PointCount.FIVE);
|
||||
|
||||
for (int i = 0; i < expanded.getControlPointCount(); i++) {
|
||||
assertPoint(expanded.getControlPoint(i).getX(), expanded.getControlPoint(i).getY(),
|
||||
stretched.getControlPoint(i));
|
||||
}
|
||||
}
|
||||
|
||||
// --- transformer ---
|
||||
|
||||
@Test
|
||||
|
||||
+2
-1
@@ -33,6 +33,7 @@ import org.diylc.components.AbstractLeadedComponent;
|
||||
import org.diylc.components.LineEndingStyle;
|
||||
import org.diylc.components.transform.SimpleComponentTransformer;
|
||||
import org.diylc.core.ComponentState;
|
||||
import org.diylc.core.CreationMethod;
|
||||
import org.diylc.core.IDIYComponent;
|
||||
import org.diylc.core.IDrawingObserver;
|
||||
import org.diylc.core.ILayeredComponent;
|
||||
@@ -50,7 +51,7 @@ import org.diylc.core.measures.SizeUnit;
|
||||
instanceNamePrefix = "Trace", description = "Curved copper trace with two control points",
|
||||
zOrder = IDIYComponent.TRACE, bomPolicy = BomPolicy.NEVER_SHOW,
|
||||
keywordPolicy = KeywordPolicy.SHOW_TAG, keywordTag = "PCB", transformer = SimpleComponentTransformer.class,
|
||||
enableCache = true)
|
||||
enableCache = true, creationMethod = CreationMethod.POINT_BY_POINT)
|
||||
public class CurvedTrace extends AbstractCurvedComponent<Void> implements ILayeredComponent, IGerberComponentSimple {
|
||||
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
@@ -30,6 +30,7 @@ import org.diylc.common.ObjectCache;
|
||||
import org.diylc.components.AbstractCurvedComponent;
|
||||
import org.diylc.components.transform.SimpleComponentTransformer;
|
||||
import org.diylc.core.ComponentState;
|
||||
import org.diylc.core.CreationMethod;
|
||||
import org.diylc.core.IContinuity;
|
||||
import org.diylc.core.IDIYComponent;
|
||||
import org.diylc.core.IDrawingObserver;
|
||||
@@ -40,7 +41,7 @@ import org.diylc.utils.Constants;
|
||||
|
||||
@ComponentDescriptor(name = "Hookup Wire", author = "Branislav Stojkovic", category = "Connectivity",
|
||||
instanceNamePrefix = "W", description = "Flexible wire with two control points", zOrder = IDIYComponent.WIRING,
|
||||
flexibleZOrder = true, bomPolicy = BomPolicy.NEVER_SHOW,
|
||||
flexibleZOrder = true, bomPolicy = BomPolicy.NEVER_SHOW, creationMethod = CreationMethod.POINT_BY_POINT,
|
||||
transformer = SimpleComponentTransformer.class, enableCache = false)
|
||||
public class HookupWire extends AbstractCurvedComponent<AWG> implements IContinuity {
|
||||
|
||||
|
||||
+2
-1
@@ -35,6 +35,7 @@ import org.diylc.common.ObjectCache;
|
||||
import org.diylc.components.AbstractOrthogonalComponent;
|
||||
import org.diylc.components.transform.OrthogonalComponentTransformer;
|
||||
import org.diylc.core.ComponentState;
|
||||
import org.diylc.core.CreationMethod;
|
||||
import org.diylc.core.IContinuity;
|
||||
import org.diylc.core.IDIYComponent;
|
||||
import org.diylc.core.IDrawingObserver;
|
||||
@@ -49,7 +50,7 @@ import org.diylc.core.measures.SizeUnit;
|
||||
instanceNamePrefix = "LN", description = "Line with rounded right angle turns and optional arrows",
|
||||
zOrder = IDIYComponent.WIRING, flexibleZOrder = true, bomPolicy = BomPolicy.NEVER_SHOW, autoEdit = false,
|
||||
keywordPolicy = KeywordPolicy.SHOW_TAG, keywordTag = "Schematic",
|
||||
transformer = OrthogonalComponentTransformer.class, enableCache = false)
|
||||
transformer = OrthogonalComponentTransformer.class, enableCache = false, creationMethod = CreationMethod.POINT_BY_POINT)
|
||||
public class OrthogonalLine extends AbstractOrthogonalComponent<Void> implements IContinuity {
|
||||
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
+2
-1
@@ -34,6 +34,7 @@ import org.diylc.common.ObjectCache;
|
||||
import org.diylc.components.AbstractOrthogonalComponent;
|
||||
import org.diylc.components.transform.OrthogonalComponentTransformer;
|
||||
import org.diylc.core.ComponentState;
|
||||
import org.diylc.core.CreationMethod;
|
||||
import org.diylc.core.IContinuity;
|
||||
import org.diylc.core.IDIYComponent;
|
||||
import org.diylc.core.IDrawingObserver;
|
||||
@@ -45,7 +46,7 @@ import org.diylc.utils.Constants;
|
||||
@ComponentDescriptor(name = "Right Angle Wire", author = "Branislav Stojkovic", category = "Connectivity",
|
||||
instanceNamePrefix = "W", description = "Wire that runs horizontally and vertically and turns at right angles",
|
||||
zOrder = IDIYComponent.WIRING, flexibleZOrder = true, bomPolicy = BomPolicy.NEVER_SHOW,
|
||||
transformer = OrthogonalComponentTransformer.class, enableCache = false)
|
||||
transformer = OrthogonalComponentTransformer.class, enableCache = false, creationMethod = CreationMethod.POINT_BY_POINT)
|
||||
public class OrthogonalWire extends AbstractOrthogonalComponent<AWG> implements IContinuity {
|
||||
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
+3
-1
@@ -38,6 +38,7 @@ import org.diylc.common.ObjectCache;
|
||||
import org.diylc.components.AbstractCurvedComponent;
|
||||
import org.diylc.components.transform.SimpleComponentTransformer;
|
||||
import org.diylc.core.ComponentState;
|
||||
import org.diylc.core.CreationMethod;
|
||||
import org.diylc.core.IContinuity;
|
||||
import org.diylc.core.IDIYComponent;
|
||||
import org.diylc.core.IDrawingObserver;
|
||||
@@ -50,7 +51,8 @@ import org.diylc.utils.Constants;
|
||||
category = "Connectivity", instanceNamePrefix = "W",
|
||||
description = "A pair of flexible leads twisted tightly together", zOrder = IDIYComponent.WIRING,
|
||||
flexibleZOrder = true, bomPolicy = BomPolicy.NEVER_SHOW,
|
||||
transformer = SimpleComponentTransformer.class, enableCache = true)
|
||||
transformer = SimpleComponentTransformer.class, enableCache = true,
|
||||
creationMethod = CreationMethod.POINT_BY_POINT)
|
||||
public class TwistedWire extends AbstractCurvedComponent<Void> implements IContinuity {
|
||||
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
Reference in New Issue
Block a user