diff --git a/web/app/sketcher/io.js b/web/app/sketcher/io.js
index cc4c88d8..8109e59c 100644
--- a/web/app/sketcher/io.js
+++ b/web/app/sketcher/io.js
@@ -5,6 +5,7 @@ import {EndPoint} from './shapes/point'
import {Segment} from './shapes/segment'
import {Circle} from './shapes/circle'
import {Ellipse} from './shapes/ellipse'
+import {EllipticalArc} from './shapes/elliptical-arc'
import {HDimension, VDimension, Dimension, DiameterDimension} from './shapes/dim'
import {Constraints} from './parametric'
import Vector from '../math/vector'
@@ -14,7 +15,8 @@ var Types = {
SEGMENT : 'TCAD.TWO.Segment',
ARC : 'TCAD.TWO.Arc',
CIRCLE : 'TCAD.TWO.Circle',
- ELLIPSE : 'TCAD.TWO.Ellipse',
+ ELLIPSE : 'TCAD.TWO.Ellipse',
+ ELL_ARC : 'TCAD.TWO.EllipticalArc',
DIM : 'TCAD.TWO.Dimension',
HDIM : 'TCAD.TWO.HDimension',
VDIM : 'TCAD.TWO.VDimension',
@@ -120,6 +122,13 @@ IO.prototype._loadSketch = function(sketch) {
const ep2 = endPoint(obj['ep2']);
skobj = new Ellipse(ep1, ep2);
skobj.r.set(obj['r']);
+ } else if (_class === T.ELL_ARC) {
+ const ep1 = endPoint(obj['ep1']);
+ const ep2 = endPoint(obj['ep2']);
+ const a = endPoint(obj['a']);
+ const b = endPoint(obj['b']);
+ skobj = new EllipticalArc(ep1, ep2, a, b);
+ skobj.r.set(obj['r']);
} else if (_class === T.HDIM) {
skobj = new HDimension(obj['a'], obj['b']);
skobj.flip = obj['flip'];
@@ -299,6 +308,12 @@ IO.prototype._serializeSketch = function() {
to['ep1'] = point(obj.ep1);
to['ep2'] = point(obj.ep2);
to['r'] = obj.r.get();
+ } else if (obj._class === T.ELL_ARC) {
+ to['ep1'] = point(obj.ep1);
+ to['ep2'] = point(obj.ep2);
+ to['a'] = point(obj.a);
+ to['b'] = point(obj.b);
+ to['r'] = obj.r.get();
} else if (obj._class === T.DIM || obj._class === T.HDIM || obj._class === T.VDIM) {
to['a'] = obj.a.id;
to['b'] = obj.b.id;
diff --git a/web/app/sketcher/parametric.js b/web/app/sketcher/parametric.js
index be1d500e..bfdec522 100644
--- a/web/app/sketcher/parametric.js
+++ b/web/app/sketcher/parametric.js
@@ -267,7 +267,7 @@ ParametricManager.prototype.lockConvex = function(objs, warnCallback) {
};
ParametricManager.prototype.tangent = function(objs) {
- const ellipses = fetch.generic(objs, ['TCAD.TWO.Ellipse'], 0);
+ const ellipses = fetch.generic(objs, ['TCAD.TWO.Ellipse', 'TCAD.TWO.EllipticalArc'], 0);
const lines = fetch.generic(objs, ['TCAD.TWO.Segment'], 1);
if (ellipses.length > 0) {
this.add(new Constraints.EllipseTangent(lines[0], ellipses[0]));
@@ -337,7 +337,7 @@ ParametricManager.prototype.pointOnArc = function(objs) {
ParametricManager.prototype.pointOnEllipse = function(objs) {
const points = fetch.generic(objs, ['TCAD.TWO.EndPoint'], 1);
- const ellipses = fetch.generic(objs, ['TCAD.TWO.Ellipse'], 1);
+ const ellipses = fetch.generic(objs, ['TCAD.TWO.Ellipse', 'TCAD.TWO.EllipticalArc'], 1);
this.add(new Constraints.PointOnEllipse(points[0], ellipses[0]));
};
@@ -1374,6 +1374,7 @@ Constraints.PointOnEllipseInternal = function(point, ellipse) {
Constraints.PointOnEllipseInternal.prototype.NAME = 'PointOnEllipseI';
Constraints.PointOnEllipseInternal.prototype.UI_NAME = 'Point On Ellipse';
+Constraints.PointOnEllipseInternal.prototype.aux = true;
Constraints.PointOnEllipseInternal.prototype.getSolveData = function() {
var params = [];
diff --git a/web/app/sketcher/shapes/circle.js b/web/app/sketcher/shapes/circle.js
index 6953c0a9..8b4ccff5 100644
--- a/web/app/sketcher/shapes/circle.js
+++ b/web/app/sketcher/shapes/circle.js
@@ -44,9 +44,6 @@ export class Circle extends SketchObject {
if (alternative) {
return super.getDefaultTool(viewer, alternative);
} else {
- const editTool = new EditCircleTool(viewer, null);
- editTool.circle = this;
- return editTool;
}
}
}
diff --git a/web/app/sketcher/shapes/ellipse.js b/web/app/sketcher/shapes/ellipse.js
index 0619fb38..807b1025 100644
--- a/web/app/sketcher/shapes/ellipse.js
+++ b/web/app/sketcher/shapes/ellipse.js
@@ -1,6 +1,5 @@
import {Ref} from './ref'
import {SketchObject} from './sketch-object'
-import {EllipseTool, STATE_RADIUS} from '../tools/ellipse'
import {Constraints} from '../parametric'
import * as math from '../../math/math';
@@ -95,10 +94,6 @@ export class Ellipse extends SketchObject {
if (alternative) {
return super.getDefaultTool(viewer, alternative);
} else {
- const editTool = new EllipseTool(viewer);
- editTool.ellipse = this;
- editTool.state = STATE_RADIUS;
- return editTool;
}
}
}
diff --git a/web/app/sketcher/shapes/elliptical-arc.js b/web/app/sketcher/shapes/elliptical-arc.js
index 70d7b1fc..0114bd32 100644
--- a/web/app/sketcher/shapes/elliptical-arc.js
+++ b/web/app/sketcher/shapes/elliptical-arc.js
@@ -1,19 +1,54 @@
-import {Ref} from './ref'
-import {SketchObject} from './sketch-object'
+import {Ellipse} from './ellipse'
+import {Constraints} from '../parametric'
import * as math from '../../math/math';
+import {swap} from '../../utils/utils'
-export class EllipticalArc extends SketchObject {
+export class EllipticalArc extends Ellipse {
constructor(ep1, ep2, a, b) {
+ super(ep1, ep2);
+ this.a = a;
+ this.b = b;
+ this.addChild(a);
+ this.addChild(b);
+
+ //we'd like to have angles points have higher selection order
+ swap(this.children, 0, this.children.length - 2);
+ swap(this.children, 1, this.children.length - 1);
+ }
+
+ stabilize(viewer) {
+ viewer.parametricManager._add(new Constraints.PointOnEllipseInternal(this.b, this));
+ viewer.parametricManager._add(new Constraints.PointOnEllipseInternal(this.a, this));
}
drawImpl(ctx, scale) {
- }
-
-
- normalDistance(aim) {
+ ctx.beginPath();
+ const radiusX = Math.max(this.radiusX, 1e-8);
+ const radiusY = Math.max(this.radiusY, 1e-8);
+ let aAngle = this.drawAngle(this.a);
+ let bAngle;
+ if (math.areEqual(this.a.x, this.b.x, math.TOLERANCE) &&
+ math.areEqual(this.a.y, this.b.y, math.TOLERANCE)) {
+ bAngle = aAngle + 2 * Math.PI;
+ } else {
+ bAngle = this.drawAngle(this.b)
+ }
+ ctx.ellipse(this.centerX, this.centerY, radiusX, radiusY, this.rotation, aAngle, bAngle );
+ ctx.stroke();
}
+ drawAngle(point) {
+ let deformScale = this.radiusY / this.radiusX;
+ let x = point.x - this.centerX;
+ let y = point.y - this.centerY;
+ const rotation = - this.rotation;
+ let xx = x * Math.cos(rotation) - y * Math.sin(rotation);
+ let yy = x * Math.sin(rotation) + y * Math.cos(rotation);
+ xx *= deformScale;
+ return Math.atan2(yy, xx);
+ }
}
+
EllipticalArc.prototype._class = 'TCAD.TWO.EllipticalArc';
diff --git a/web/app/sketcher/shapes/sketch-object.js b/web/app/sketcher/shapes/sketch-object.js
index 2c87a0cf..859fbd5e 100644
--- a/web/app/sketcher/shapes/sketch-object.js
+++ b/web/app/sketcher/shapes/sketch-object.js
@@ -35,10 +35,6 @@ export class SketchObject extends Shape {
return false;
}
- getDefaultTool(viewer) {
- return new DragTool(this, viewer);
- }
-
isAuxOrLinkedTo() {
if (!!this.aux) {
return true;
diff --git a/web/app/sketcher/sketcher-app.js b/web/app/sketcher/sketcher-app.js
index 4739fb16..3684c707 100644
--- a/web/app/sketcher/sketcher-app.js
+++ b/web/app/sketcher/sketcher-app.js
@@ -136,7 +136,11 @@ function App2D() {
}, 'circle');
this.registerAction('addEllipse', "Add Ellipse", function () {
- app.viewer.toolManager.takeControl(new EllipseTool(app.viewer));
+ app.viewer.toolManager.takeControl(new EllipseTool(app.viewer, false));
+ });
+
+ this.registerAction('addEllipticalArc', "Add Elliptical Arc", function () {
+ app.viewer.toolManager.takeControl(new EllipseTool(app.viewer, true));
});
this.registerAction('pan', "Pan", function () {
diff --git a/web/app/sketcher/tools/edit-tools-map.js b/web/app/sketcher/tools/edit-tools-map.js
new file mode 100644
index 00000000..2361784e
--- /dev/null
+++ b/web/app/sketcher/tools/edit-tools-map.js
@@ -0,0 +1,24 @@
+import {Ellipse} from '../shapes/ellipse'
+import {EllipticalArc} from '../shapes/elliptical-arc'
+import {Circle} from '../shapes/circle'
+import {EditCircleTool} from './circle'
+import {DragTool} from './drag'
+import {EllipseTool, STATE_RADIUS} from './ellipse'
+
+export function GetShapeEditTool(viewer, obj, alternative) {
+ if (obj instanceof Circle && !alternative) {
+ const tool = new EditCircleTool(viewer);
+ tool.circle = obj;
+ return tool;
+ } else if (obj instanceof Ellipse && !alternative) {
+ // even for an ell-arc we should act as it would be an ellipse to
+ // avoid stabilize constraints added and demoing B point on move
+ // so second arg must be FALSE!
+ const tool = new EllipseTool(viewer, false);
+ tool.ellipse = obj;
+ tool.state = STATE_RADIUS;
+ return tool;
+ } else {
+ return new DragTool(obj, viewer);
+ }
+}
diff --git a/web/app/sketcher/tools/ellipse.js b/web/app/sketcher/tools/ellipse.js
index f9cf59f8..d07ce0bb 100644
--- a/web/app/sketcher/tools/ellipse.js
+++ b/web/app/sketcher/tools/ellipse.js
@@ -1,6 +1,7 @@
import {Tool} from './tool'
import {EndPoint} from '../shapes/point'
import {Ellipse} from '../shapes/ellipse'
+import {EllipticalArc} from '../shapes/elliptical-arc'
import Vector from '../../math/vector'
export const STATE_POINT1 = 0;
@@ -9,8 +10,9 @@ export const STATE_RADIUS = 2;
export class EllipseTool extends Tool {
- constructor(viewer) {
- super('ellipse', viewer);
+ constructor(viewer, arc) {
+ super(arc ? 'ellipse' : 'elliptical arc', viewer);
+ this.arc = arc;
this.ellipse = null;
this.state = STATE_POINT1;
}
@@ -29,11 +31,25 @@ export class EllipseTool extends Tool {
return this.viewer.snapped ? this.viewer.snapped : this.viewer.screenToModel(e);
}
+ newEllipse(p) {
+ const ep = () => new EndPoint(p.x, p.y);
+ return this.arc ? new EllipticalArc(ep(), ep(), ep(), ep()) : new Ellipse(ep(), ep());
+ }
+
+ demoBPoint() {
+ const arc = this.ellipse;
+ let ang = Math.atan2(arc.a.y - arc.centerY, arc.a.x - arc.centerX) + (2 * Math.PI - 0.3);
+ ang %= 2 * Math.PI;
+ const r = arc.radiusAtAngle(ang - arc.rotation);
+ arc.b.x = arc.centerX + r * Math.cos(ang);
+ arc.b.y = arc.centerY + r * Math.sin(ang);
+ }
+
mouseup(e) {
switch (this.state) {
case STATE_POINT1: {
const p = this.point(e);
- this.ellipse = new Ellipse(new EndPoint(p.x, p.y), new EndPoint(p.x, p.y));
+ this.ellipse = this.newEllipse(p);
this.snapIfNeed(this.ellipse.ep1);
this.viewer.activeLayer.objects.push(this.ellipse);
this.viewer.refresh();
@@ -51,6 +67,9 @@ export class EllipseTool extends Tool {
break;
}
case STATE_RADIUS:
+ if (this.arc) {
+ this.ellipse.stabilize(this.viewer);
+ }
this.viewer.toolManager.releaseControl();
}
}
@@ -64,7 +83,11 @@ export class EllipseTool extends Tool {
case STATE_POINT2:
this.ellipse.ep2.setFromPoint(this.viewer.screenToModel(e));
this.ellipse.r.value = this.ellipse.radiusX * 0.5;
- this.viewer.snap(p.x, p.y, [this.ellipse.ep1, this.ellipse.ep2]);
+ this.viewer.snap(p.x, p.y, this.ellipse.children);
+ if (this.arc) {
+ this.ellipse.a.setFromPoint(this.ellipse.ep2);
+ this.demoBPoint();
+ }
break;
case STATE_RADIUS:
const polarPoint = this.ellipse.toEllipseCoordinateSystem(p);
@@ -79,6 +102,9 @@ export class EllipseTool extends Tool {
if (!Tool.dumbMode(e)) {
this.solveRequest(true);
}
+ if (this.arc) {
+ this.demoBPoint();
+ }
break;
}
this.viewer.refresh();
diff --git a/web/app/sketcher/tools/pan.js b/web/app/sketcher/tools/pan.js
index 4628465e..3302526e 100644
--- a/web/app/sketcher/tools/pan.js
+++ b/web/app/sketcher/tools/pan.js
@@ -1,4 +1,5 @@
import {Tool} from './tool'
+import {GetShapeEditTool} from './edit-tools-map'
export class PanTool extends Tool {
constructor(viewer) {
@@ -57,7 +58,7 @@ export class PanTool extends Tool {
}
this.viewer.select([toSelect], true);
if (!toSelect.isAuxOrLinkedTo()) {
- var tool = toSelect.getDefaultTool(this.viewer, e.altKey);
+ const tool = GetShapeEditTool(this.viewer, toSelect, e.altKey);
tool.mousedown(e);
this.viewer.toolManager.switchTool(tool);
}
diff --git a/web/app/utils/utils.js b/web/app/utils/utils.js
index f5cc5c6b..d9d2240c 100644
--- a/web/app/utils/utils.js
+++ b/web/app/utils/utils.js
@@ -33,4 +33,10 @@ export function constRef(value) {
return function() {
return value;
};
-}
\ No newline at end of file
+}
+
+export function swap(arr, i1, i2) {
+ const tmp = arr[i1];
+ arr[i1] = arr[i2];
+ arr[i2] = tmp;
+}
\ No newline at end of file
diff --git a/web/sketcher.html b/web/sketcher.html
index 967b2e51..4f5473dd 100644
--- a/web/sketcher.html
+++ b/web/sketcher.html
@@ -29,7 +29,8 @@
-->