diff --git a/test/coreTests/utils/sketcherUtils.js b/test/coreTests/utils/sketcherUtils.js index 4e98c6ea..726d3801 100644 --- a/test/coreTests/utils/sketcherUtils.js +++ b/test/coreTests/utils/sketcherUtils.js @@ -86,7 +86,7 @@ export function addArc(ctx, cX, cY, aX, aY, bX, bY) { export function addCircle(ctx, cX, cY, R) { let [rX, rY] = modelToScreen(ctx.viewer, cX + R, cY); [cX, cY] = modelToScreen(ctx.viewer, cX, cY); - ctx.actions['addCircle'].action(); + ctx.actions.CircleTool.invoke(ctx); moveAndClickXY(ctx, cX, cY); let circle = ctx.viewer.toolManager.tool.circle; moveAndClickXY(ctx, rX, rY); @@ -98,7 +98,7 @@ export function addEllipse(ctx, aX, aY, bX, bY, rX, rY) { [aX, aY] = modelToScreen(ctx.viewer, aX, aY); [bX, bY] = modelToScreen(ctx.viewer, bX, bY); [rX, rY] = modelToScreen(ctx.viewer, rX, rY); - ctx.actions['addEllipse'].action(); + ctx.actions.EllipseTool.invoke(ctx); moveAndClickXY(ctx, aX, aY); let ellipse = ctx.viewer.toolManager.tool.ellipse; moveAndClickXY(ctx, bX, bY); @@ -111,7 +111,7 @@ export function addEllipticalArc(ctx, aX, aY, bX, bY, rX, rY) { [aX, aY] = modelToScreen(ctx.viewer, aX, aY); [bX, bY] = modelToScreen(ctx.viewer, bX, bY); [rX, rY] = modelToScreen(ctx.viewer, rX, rY); - ctx.actions['addEllipticalArc'].action(); + ctx.actions.EllipseArcTool.invoke(ctx); moveAndClickXY(ctx, aX, aY); let ellipse = ctx.viewer.toolManager.tool.ellipse; moveAndClickXY(ctx, bX, bY); diff --git a/web/app/cad/sketch/sketchModel.js b/web/app/cad/sketch/sketchModel.js index 051ddb56..d065ff40 100644 --- a/web/app/cad/sketch/sketchModel.js +++ b/web/app/cad/sketch/sketchModel.js @@ -7,6 +7,7 @@ import {distanceAB, isCCW, makeAngle0_360} from '../../math/math' import {normalizeCurveEnds} from '../../brep/geom/impl/nurbs-ext'; import Vector from '../../../../modules/math/vector'; import {AXIS, ORIGIN} from '../../../../modules/math/l3space'; +import CSys from "../../../../modules/math/csys"; const RESOLUTION = 20; @@ -21,11 +22,14 @@ class SketchPrimitive { } tessellate(resolution) { - const tessellation = this.tessellateImpl(resolution); - if (this.inverted) { - tessellation.reverse(); - } - return tessellation; + return this.toNurbs(CSys.ORIGIN).tessellate(); + // return brepCurve.impl.verb.tessellate().map(p => new Vector().set3(p) ); + + // const tessellation = this.tessellateImpl(resolution); + // if (this.inverted) { + // tessellation.reverse(); + // } + // return tessellation; } get isCurve() { @@ -64,7 +68,7 @@ export class Segment extends SketchPrimitive { this.b = b; } - tessellateImpl(resolution) { + tessellate(resolution) { return [this.a, this.b]; } @@ -81,33 +85,6 @@ export class Arc extends SketchPrimitive { this.c = c; } - tessellateImpl(resolution) { - return Arc.tessellateArc(this.a, this.b, this.c, resolution); - } - - static tessellateArc(ao, bo, c, resolution) { - var a = ao.minus(c); - var b = bo.minus(c); - var points = [ao]; - var abAngle = Math.atan2(b.y, b.x) - Math.atan2(a.y, a.x); - if (abAngle > Math.PI * 2) abAngle = Math.PI / 2 - abAngle; - if (abAngle < 0) abAngle = Math.PI * 2 + abAngle; - - var r = a.length(); - resolution = 1; - //var step = Math.acos(1 - ((resolution * resolution) / (2 * r * r))); - var step = resolution / (2 * Math.PI); - var k = Math.round(abAngle / step); - var angle = Math.atan2(a.y, a.x) + step; - - for (var i = 0; i < k - 1; ++i) { - points.push(new Point(c.x + r*Math.cos(angle), c.y + r*Math.sin(angle))); - angle += step; - } - points.push(bo); - return points; - } - toVerbNurbs(tr, csys) { const basisX = csys.x; @@ -143,69 +120,42 @@ export class BezierCurve extends SketchPrimitive { this.cp2 = cp2; } - tessellateImpl(resolution) { - return LUT(this.a, this.b, this.cp1, this.cp2, 10); - } - toVerbNurbs(tr) { return new verb.geom.BezierCurve([tr(this.a).data(), tr(this.cp1).data(), tr(this.cp2).data(), tr(this.b).data()], null); } } export class EllipticalArc extends SketchPrimitive { - constructor(id, ep1, ep2, a, b, r) { + constructor(id, c, rx, ry, rot, a, b) { super(id); - this.ep1 = ep1; - this.ep2 = ep2; + this.c = c; + this.rx = rx; + this.ry = ry; + this.rot = rot; this.a = a; this.b = b; - this.r = r; } - tessellateImpl(resolution) { - return EllipticalArc.tessEllipticalArc(this.ep1, this.ep2, this.a, this.b, this.r, resolution); - } + toVerbNurbs(tr, csys) { + const ax = Math.cos(this.rot); + const ay = Math.sin(this.rot); - static tessEllipticalArc(ep1, ep2, ao, bo, radiusY, resolution) { - const axisX = ep2.minus(ep1); - const radiusX = axisX.length() * 0.5; - axisX._normalize(); - const c = ep1.plus(axisX.multiply(radiusX)); - const a = ao.minus(c); - const b = bo.minus(c); - const points = [ao]; - const rotation = Math.atan2(axisX.y, axisX.x); - let abAngle = Math.atan2(b.y, b.x) - Math.atan2(a.y, a.x); - if (abAngle > Math.PI * 2) abAngle = Math.PI / 2 - abAngle; - if (abAngle < 0) abAngle = Math.PI * 2 + abAngle; + const xAxis = new Vector(ax, ay)._multiply(this.rx); + const yAxis = new Vector(-ay, ax)._multiply(this.ry); - const sq = (a) => a * a; + const startAngle = Math.atan2(this.a.y - this.c.y, this.a.x - this.c.x) - this.rot; + const endAngle = Math.atan2(this.b.y - this.c.y, this.b.x - this.c.x) - this.rot; - resolution = 1; + if (startAngle > endAngle) { - const step = resolution / (2 * Math.PI); - const k = Math.round(abAngle / step); - let angle = Math.atan2(a.y, a.x) + step - rotation; - - for (let i = 0; i < k - 1; ++i) { - const r = Math.sqrt(1/( sq(Math.cos(angle)/radiusX) + sq(Math.sin(angle)/radiusY))); - points.push(new Point(c.x + r*Math.cos(angle + rotation), c.y + r*Math.sin(angle + rotation))); - angle += step; } - points.push(bo); - return points; - } - - toVerbNurbs(tr) { - const xAxis = this.ep2.minus(this.ep1)._multiply(0.5); - const yAxis = new Vector(xAxis.y, xAxis.x)._normalize()._multiply(this.r) ; - const center = this.ep1.plus(xAxis); - const startAngle = makeAngle0_360(Math.atan2(this.a.y - center.y, this.a.x - center.x)); - const endAngle = makeAngle0_360(Math.atan2(this.b.y - center.y, this.b.x - center.x)); - - let arc = new verb.geom.EllipseArc(tr(center).data(), tr(xAxis).data(), tr(yAxis).data(), startAngle, endAngle); - return adjustEnds(arc, tr(this.a), tr(this.b)) + // let arc = new verb.geom.EllipseArc(tr(this.c).data(), tr(xAxis).data(), tr(yAxis).data(), startAngle, endAngle); + let arc = new verb.geom.EllipseArc(this.c.data(), xAxis.data(), yAxis.data(), startAngle, endAngle); + arc = arc.transform(csys.outTransformation.toArray()); + + return arc; + // return adjustEnds(arc, tr(this.a), tr(this.b)) } } @@ -216,25 +166,6 @@ export class Circle extends SketchPrimitive { this.r = r; } - tessellateImpl(resolution) { - return Circle.tessCircle(this.c, this.r, resolution); - } - - static tessCircle(c, r, resolution) { - var points = []; - resolution = 1; - //var step = Math.acos(1 - ((resolution * resolution) / (2 * r * r))); - var step = resolution / (2 * Math.PI); - var k = Math.round((2 * Math.PI) / step); - - for (var i = 0, angle = 0; i < k; ++i, angle += step) { - points.push(new Point(c.x + r*Math.cos(angle), c.y + r*Math.sin(angle))); - } - points.push(points[0]); // close it - return points; - } - - toVerbNurbs(tr, csys) { const basisX = csys.x; const basisY = csys.y; @@ -243,22 +174,23 @@ export class Circle extends SketchPrimitive { } export class Ellipse extends SketchPrimitive { - constructor(id, ep1, ep2, r) { + constructor(id, c, rx, ry, rot) { super(id); - this.ep1 = ep1; - this.ep2 = ep2; - this.r = r; - } - - tessellateImpl(resolution) { - return EllipticalArc.tessEllipticalArc(this.ep1, this.ep2, this.ep1, this.ep1, this.r, resolution); + this.c = c; + this.rx = rx; + this.ry = ry; + this.rot = rot; } toVerbNurbs(tr) { - const xAxis = this.ep2.minus(this.ep1)._multiply(0.5); - const yAxis = new Vector(xAxis.y, xAxis.x)._normalize()._multiply(this.r) ; - const center = this.ep1.plus(xAxis); - return new verb.geom.Ellipse(tr(center).data(), tr(xAxis).data(), tr(yAxis).data()); + + const ax = Math.cos(this.rot); + const ay = Math.sin(this.rot); + + const xAxis = new Vector(ax, ay)._multiply(this.rx); + const yAxis = new Vector(-ay, ax)._multiply(this.ry); + + return new verb.geom.Ellipse(tr(this.c).data(), tr(xAxis).data(), tr(yAxis).data()); } } @@ -295,7 +227,7 @@ export class Contour { const tessellation = []; for (let segment of this.segments) { const segmentTessellation = segment.tessellate(resolution); - //skip last one cuz it's guaranteed to be closed + //skip last one because it's guaranteed to be closed for (let i = 0; i < segmentTessellation.length - 1; ++i) { tessellation.push(segmentTessellation[i]); } diff --git a/web/app/cad/sketch/sketchReader.js b/web/app/cad/sketch/sketchReader.js index 45db93da..a285c973 100644 --- a/web/app/cad/sketch/sketchReader.js +++ b/web/app/cad/sketch/sketchReader.js @@ -85,11 +85,16 @@ export function ReadSketch(sketch, sketchId, readConstructionSegments) { const arcCenter = ReadSketchPoint(data.c); out.connections.push(new sm.Arc(getID(obj), arcA, arcB, arcCenter)); } else if (obj.type === 'EllipticalArc') { - const ep1 = ReadSketchPoint(data.ep1); - const ep2 = ReadSketchPoint(data.ep2); + if (data.ep1) { + continue; + } + const c = ReadSketchPoint(data.c); + const rx = readSketchFloat(data.rx); + const ry = readSketchFloat(data.ry); + const rot = readSketchFloat(data.rot); const a = ReadSketchPoint(data.a); const b = ReadSketchPoint(data.b); - out.connections.push(new sm.EllipticalArc(getID(obj), ep1, ep2, a, b, readSketchFloat(data.r))); + out.loops.push(new sm.EllipticalArc(getID(obj), c, rx, ry, rot, a, b)); } else if (obj.type === 'BezierCurve') { const a = ReadSketchPoint(data.cp1); const b = ReadSketchPoint(data.cp4); @@ -100,9 +105,14 @@ export function ReadSketch(sketch, sketchId, readConstructionSegments) { const circleCenter = ReadSketchPoint(data.c); out.loops.push(new sm.Circle(getID(obj), circleCenter, readSketchFloat(data.r))); } else if (obj.type === 'Ellipse') { - const ep1 = ReadSketchPoint(data.ep1); - const ep2 = ReadSketchPoint(data.ep2); - out.loops.push(new sm.Ellipse(getID(obj), ep1, ep2, readSketchFloat(data.r))); + if (data.ep1) { + continue; + } + const c = ReadSketchPoint(data.c); + const rx = readSketchFloat(data.rx); + const ry = readSketchFloat(data.ry); + const rot = readSketchFloat(data.rot); + out.loops.push(new sm.Ellipse(getID(obj), c, rx, ry, rot)); } } return out; diff --git a/web/app/sketcher/shapes/bezier-curve.js b/web/app/sketcher/shapes/bezier-curve.js index c7a60701..9f59bd11 100644 --- a/web/app/sketcher/shapes/bezier-curve.js +++ b/web/app/sketcher/shapes/bezier-curve.js @@ -10,17 +10,17 @@ import {EndPoint} from "./point"; export class BezierCurve extends SketchObject { - constructor(ax, ay, bx, by, cp1x, cp1y, cp2x, cp2y, id) { + constructor(ax, ay, cp1x, cp1y, cp2x, cp2y, bx, by, id) { super(id); const s1 = new Segment(ax, ay, cp1x, cp1y, this.id + ':1'); const s2 = new Segment(bx, by, cp2x, cp2y, this.id + ':2'); this.addChild(s1); this.addChild(s2); - this.a = this.s1.a; - this.b = this.s2.b; - this.cp1 = this.s1.b; - this.cp2 = this.s2.a; + this.a = s1.a; + this.b = s2.b; + this.cp1 = s1.b; + this.cp2 = s2.a; for (let c of this.children) { c.role = 'objectConstruction'; diff --git a/web/app/sketcher/shapes/ellipse.js b/web/app/sketcher/shapes/ellipse.js index b67f3497..e1d2cee9 100644 --- a/web/app/sketcher/shapes/ellipse.js +++ b/web/app/sketcher/shapes/ellipse.js @@ -2,60 +2,64 @@ import {SketchObject} from './sketch-object' import * as math from '../../math/math'; import {Param} from "./param"; -import {SketchSegmentSerializationData} from "./segment"; import {EndPoint} from "./point"; export class Ellipse extends SketchObject { - constructor(x1, y1, x2, y2, r, id) { + constructor(cx, cy, rx, ry, rot, id) { super(id); - this.ep1 = new EndPoint(x1, y1, this.id + ':1'); - this.ep2 = new EndPoint(x2, y2, this.id + ':2'); - this.addChild(this.ep1); - this.addChild(this.ep2); - this.r = new Param(r === undefined ? 0 : this.radiusX * 0.5, 'R'); - this.r.enforceVisualLimit = true; + + this.c = new EndPoint(cx, cy, this.id + ':C'); + + this.addChild(this.c); + + this.rot = new Param(rot, 'A'); + this.rx = new Param(rx, 'Rx'); + this.ry = new Param(ry, 'Rx'); + + this.rx.enforceVisualLimit = true; + this.ry.enforceVisualLimit = true; } recoverIfNecessary() { let recovered = false; - if (math.distanceAB(this.ep1, this.ep2) <= math.TOLERANCE) { - this.ep1.translate(-RECOVER_LENGTH, -RECOVER_LENGTH); - this.ep2.translate(RECOVER_LENGTH, RECOVER_LENGTH); + if (this.radiusX <= 0.1) { + this.rx.set(RECOVER_LENGTH); recovered = true; } if (this.radiusY <= 0.1) { - this.r.set(RECOVER_LENGTH); + this.ry.set(RECOVER_LENGTH); recovered = true; } return recovered; } visitParams(callback) { - this.ep1.visitParams(callback); - this.ep2.visitParams(callback); - callback(this.r); + this.c.visitParams(callback); + callback(this.rx); + callback(this.ry); + callback(this.rot); } get rotation() { - return Math.atan2(this.ep2.y - this.ep1.y, this.ep2.x - this.ep1.x); + return this.rot.get(); } get radiusX() { - return math.distance(this.ep1.x, this.ep1.y, this.ep2.x, this.ep2.y) * 0.5; + return this.rx.get(); } get radiusY() { - return this.r.get(); + return this.ry.get(); } get centerX() { - return this.ep1.x + (this.ep2.x - this.ep1.x) * 0.5; + return this.c.x; } get centerY() { - return this.ep1.y + (this.ep2.y - this.ep1.y) * 0.5; + return this.c.y; } drawImpl(ctx, scale) { @@ -92,19 +96,23 @@ export class Ellipse extends SketchObject { write() { return { - ep1: this.ep1.write(), - ep2: this.ep2.write(), - r: this.r.get() + c: this.c.write(), + rx: this.rx.get(), + ry: this.ry.get(), + rot: this.rot.get(), }; } static read(id, data) { + if (data.ep1) { + return readFormatV1(id, data); + } return new Ellipse( - data.ep1.x, - data.ep1.y, - data.ep2.x, - data.ep2.y, - data.r, + data.c.x, + data.c.y, + data.rx, + data.ry, + data.rot, id ) } @@ -114,4 +122,15 @@ Ellipse.prototype._class = 'TCAD.TWO.Ellipse'; Ellipse.prototype.TYPE = 'Ellipse'; const sq = (a) => a * a; -const RECOVER_LENGTH = 100; \ No newline at end of file +const RECOVER_LENGTH = 100; + +function readFormatV1(id, data) { + + const cx = data.ep1.x + (data.ep2.x - data.ep1.x) * 0.5; + const cy = data.ep1.y + (data.ep2.y - data.ep1.y) * 0.5; + const rx = math.distance(data.ep1.x, data.ep1.y, data.ep2.x, data.ep2.y) * 0.5; + const ry = data.r; + const rot = Math.atan2(data.ep2.y - data.ep1.y, data.ep2.x - data.ep1.x); + + return new Ellipse(cx, cy, rx, ry, rot, id); +} \ No newline at end of file diff --git a/web/app/sketcher/shapes/elliptical-arc.js b/web/app/sketcher/shapes/elliptical-arc.js index 1f3fc363..b44543a1 100644 --- a/web/app/sketcher/shapes/elliptical-arc.js +++ b/web/app/sketcher/shapes/elliptical-arc.js @@ -1,14 +1,14 @@ import {Ellipse} from './ellipse' -import {Constraints} from '../parametric' import * as math from '../../math/math'; import {swap} from '../../utils/utils' import {EndPoint} from "./point"; +import {AlgNumConstraint, ConstraintDefinitions} from "../constr/ANConstraints"; export class EllipticalArc extends Ellipse { - constructor(x1, y1, x2, y2, ax, ay, bx, by, r, id) { - super(x1, y1, x2, y2, r, id); + constructor(cx, cy, rx, ry, rot, ax, ay, bx, by, id) { + super(cx, cy, rx, ry, rot, id); this.a = new EndPoint(ax, ay, this.id + ':A'); this.b = new EndPoint(bx, by, this.id + ':B'); this.addChild(this.a); @@ -20,8 +20,14 @@ export class EllipticalArc extends Ellipse { } stabilize(viewer) { - this.stage.addConstraint(new Constraints.PointOnEllipseInternal(this.b, this)); - this.stage.addConstraint(new Constraints.PointOnEllipseInternal(this.a, this)); + const c1 = new AlgNumConstraint(ConstraintDefinitions.PointOnEllipse, [this.b, this]); + c1.internal = true; + + const c2 = new AlgNumConstraint(ConstraintDefinitions.PointOnEllipse, [this.a, this]); + c2.internal = true; + + this.stage.addConstraint(c1); + this.stage.addConstraint(c2); } drawImpl(ctx, scale) { @@ -53,29 +59,41 @@ export class EllipticalArc extends Ellipse { write() { return { - ep1: this.ep1.write(), - ep2: this.ep2.write(), + c: this.c.write(), + rx: this.rx.get(), + ry: this.ry.get(), + rot: this.rot.get(), a: this.a.write(), - b: this.b.write(), - r: this.r.get() - } + b: this.b.write() + }; } static read(id, data) { + if (data.ep1) { + return readFormatV1(id, data); + } return new EllipticalArc( - data.ep1.x, - data.ep1.y, - data.ep2.x, - data.ep2.y, - data.a.x, - data.a.y, - data.b.x, - data.b.y, - data.r, + data.c.x, + data.c.y, + data.rx, + data.ry, + data.rot, + data.a.x, data.a.y, data.b.x, data.b.y, id - ); + ) } } +function readFormatV1(id, data) { + + const cx = data.ep1.x + (data.ep2.x - data.ep1.x) * 0.5; + const cy = data.ep1.y + (data.ep2.y - data.ep1.y) * 0.5; + const rx = math.distance(data.ep1.x, data.ep1.y, data.ep2.x, data.ep2.y) * 0.5; + const ry = data.r; + const rot = Math.atan2(data.ep2.y - data.ep1.y, data.ep2.x - data.ep1.x); + + return new EllipticalArc(cx, cy, rx, ry, rot, data.a.x, data.a.y, data.b.x, data.b.y, id); +} + EllipticalArc.prototype._class = 'TCAD.TWO.EllipticalArc'; EllipticalArc.prototype.TYPE = 'EllipticalArc'; diff --git a/web/app/sketcher/tools/edit-tools-map.js b/web/app/sketcher/tools/edit-tools-map.js index d8670ad0..8ac4b1fa 100644 --- a/web/app/sketcher/tools/edit-tools-map.js +++ b/web/app/sketcher/tools/edit-tools-map.js @@ -5,6 +5,7 @@ import {DragTool} from './drag' import {EllipseTool, STATE_RADIUS} from './ellipse' import {AngleBetweenDimension} from "../shapes/dim"; import {AddAngleBetweenDimTool} from "./dim"; +import {EllipseEditTool} from "./ellipse-edit"; export function GetShapeEditTool(viewer, obj, alternative) { if (obj instanceof Circle && !alternative) { @@ -12,13 +13,7 @@ export function GetShapeEditTool(viewer, obj, alternative) { 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; + return new EllipseEditTool(viewer, obj); } else if (obj instanceof AngleBetweenDimension && !alternative) { const tool = new AddAngleBetweenDimTool(viewer, viewer.dimLayer); tool.a = obj.a; diff --git a/web/app/sketcher/tools/ellipse-edit.js b/web/app/sketcher/tools/ellipse-edit.js new file mode 100644 index 00000000..67b3bec6 --- /dev/null +++ b/web/app/sketcher/tools/ellipse-edit.js @@ -0,0 +1,73 @@ +import {Tool} from './tool' + +export class EllipseEditTool extends Tool { + + constructor(viewer, ellipse) { + super('edit ellipse', viewer); + this.ellipse = ellipse; + } + + restart() { + // this.sendHint('specify a center of the ellipse') + } + + cleanup(e) { + } + + mousedown(e) { + const p = this.viewer.screenToModel(e); + + const dx = p.x - this.ellipse.c.x; + const dy = p.y - this.ellipse.c.y; + + this.dRot = this.ellipse.rot.get() - Math.atan2(dy, dx); + + } + + mouseup(e) { + this.solveRequest(false); + this.viewer.toolManager.releaseControl(); + } + + mousemove(e) { + const p = this.viewer.screenToModel(e); + + const dx = p.x - this.ellipse.c.x; + const dy = p.y - this.ellipse.c.y; + + const rot = Math.atan2(dy, dx); + + const ellRot = this.dRot + rot; + this.ellipse.rot.set(ellRot); + + const eAng = - this.dRot; + const rm = (eAng + 10 * Math.PI) % Math.PI; + + if (rm > Math.PI / 4 && rm < 3/4*Math.PI) { + const axisX = - Math.sin(ellRot); + const axisY = Math.cos(ellRot); + this.ellipse.ry.set(Math.abs(dx * axisX + dy * axisY)) + } else { + const axisX = Math.cos(ellRot); + const axisY = Math.sin(ellRot); + this.ellipse.rx.set(Math.abs(dx * axisX + dy * axisY)) + } + + if (!Tool.dumbMode(e)) { + this.solveRequest(true); + } + + this.viewer.refresh(); + } + + solveRequest(rough) { + this.viewer.parametricManager.prepare([{ + visitParams: (cb) => { + cb(this.ellipse.rx); + cb(this.ellipse.ry); + } + }]); + this.viewer.parametricManager.solve(rough); + } + +} \ No newline at end of file diff --git a/web/app/sketcher/tools/ellipse.js b/web/app/sketcher/tools/ellipse.js index eb885337..8eab4d87 100644 --- a/web/app/sketcher/tools/ellipse.js +++ b/web/app/sketcher/tools/ellipse.js @@ -1,26 +1,25 @@ 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; -export const STATE_POINT2 = 1; -export const STATE_RADIUS = 2; +export const CENTER = 0; +export const RADIUS_X = 1; +export const RADIUS_Y = 2; + export class EllipseTool extends Tool { constructor(viewer, arc) { - super(arc ? 'ellipse' : 'elliptical arc', viewer); + super(arc ? 'elliptical arc' : 'ellipse', viewer); this.arc = arc; this.ellipse = null; - this.state = STATE_POINT1; + this.state = CENTER; } restart() { this.ellipse = null; - this.state = STATE_POINT1; - this.sendHint('specify first major axis point') + this.state = CENTER; + this.sendHint('specify a center of the ellipse') } cleanup(e) { @@ -32,8 +31,7 @@ export class EllipseTool extends Tool { } newEllipse(p) { - - return this.arc ? new EllipticalArc(p.x, p.y, p.x, p.y, p.x, p.y, p.x, p.y) : new Ellipse(p.x, p.y, p.x, p.y); + return this.arc ? new EllipticalArc(p.x, p.y, 0, 0, 0, p.x, p.y, p.x, p.y) : new Ellipse(p.x, p.y, 0, 0, 0); } demoBPoint() { @@ -47,26 +45,22 @@ export class EllipseTool extends Tool { mouseup(e) { switch (this.state) { - case STATE_POINT1: { + case CENTER: { const p = this.point(e); this.ellipse = this.newEllipse(p); - this.snapIfNeed(this.ellipse.ep1); + this.snapIfNeed(this.ellipse.c); this.viewer.activeLayer.add(this.ellipse); this.viewer.refresh(); - this.state = STATE_POINT2; - this.sendHint('specify second major axis point'); + this.state = RADIUS_X; + this.sendHint('specify major radius'); break; } - case STATE_POINT2: { - const p = this.point(e); - this.ellipse.ep2.setFromPoint(p); - this.snapIfNeed(this.ellipse.ep2); - this.viewer.refresh(); - this.state = STATE_RADIUS; - this.sendHint('specify minor axis radius'); + case RADIUS_X: { + this.state = RADIUS_Y; + this.sendHint('specify minor radius'); break; } - case STATE_RADIUS: + case RADIUS_Y: if (this.arc) { this.ellipse.stabilize(this.viewer); } @@ -77,41 +71,47 @@ export class EllipseTool extends Tool { mousemove(e) { const p = this.viewer.screenToModel(e); switch (this.state) { - case STATE_POINT1: + case CENTER: { this.viewer.snap(p.x, p.y, []); break; - 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.children); + } + case RADIUS_X: { + + const dx = p.x - this.ellipse.c.x; + const dy = p.y - this.ellipse.c.y; + + const rot = Math.atan2(dy, dx); + const rx = Math.sqrt(dx*dx + dy*dy); + + this.ellipse.rx.set(rx); + this.ellipse.rot.set(rot); + if (this.arc) { - this.ellipse.a.setFromPoint(this.ellipse.ep2); + this.ellipse.a.setFromPoint(p); this.demoBPoint(); } break; - case STATE_RADIUS: - const polarPoint = this.ellipse.toEllipseCoordinateSystem(p); - let minorRadius = Ellipse.findMinorRadius(this.ellipse.radiusX, polarPoint.radius, polarPoint.angle); - if (isNaN(minorRadius)) { - const projAxis = new Vector(-(this.ellipse.ep2.y - this.ellipse.ep1.y), this.ellipse.ep2.x - this.ellipse.ep1.x); - projAxis._normalize(); - const v = new Vector(this.ellipse.ep2.x - p.x, this.ellipse.ep2.y - p.y); - minorRadius = Math.abs(projAxis.dot(v)); - } - this.ellipse.r.set(minorRadius); - if (!Tool.dumbMode(e)) { - this.solveRequest(true); - } + } + case RADIUS_Y: { + + const dx = p.x - this.ellipse.c.x; + const dy = p.y - this.ellipse.c.y; + + const rot = this.ellipse.rotation; + const axisX = - Math.sin(rot); + const axisY = Math.cos(rot); + + + const ry = Math.abs(dx * axisX + dy * axisY); + + this.ellipse.ry.set(ry); + if (this.arc) { this.demoBPoint(); } break; + } } this.viewer.refresh(); } - - solveRequest(rough) { - this.viewer.parametricManager.prepare([this.ellipse.r]); - this.viewer.parametricManager.solve(rough); - } } \ No newline at end of file