mirror of
https://github.com/xibyte/jsketcher
synced 2025-12-06 16:33:15 +01:00
ellipse rework
This commit is contained in:
parent
91e44214cf
commit
4f7e7267ea
9 changed files with 275 additions and 228 deletions
|
|
@ -86,7 +86,7 @@ export function addArc(ctx, cX, cY, aX, aY, bX, bY) {
|
||||||
export function addCircle(ctx, cX, cY, R) {
|
export function addCircle(ctx, cX, cY, R) {
|
||||||
let [rX, rY] = modelToScreen(ctx.viewer, cX + R, cY);
|
let [rX, rY] = modelToScreen(ctx.viewer, cX + R, cY);
|
||||||
[cX, cY] = modelToScreen(ctx.viewer, cX, cY);
|
[cX, cY] = modelToScreen(ctx.viewer, cX, cY);
|
||||||
ctx.actions['addCircle'].action();
|
ctx.actions.CircleTool.invoke(ctx);
|
||||||
moveAndClickXY(ctx, cX, cY);
|
moveAndClickXY(ctx, cX, cY);
|
||||||
let circle = ctx.viewer.toolManager.tool.circle;
|
let circle = ctx.viewer.toolManager.tool.circle;
|
||||||
moveAndClickXY(ctx, rX, rY);
|
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);
|
[aX, aY] = modelToScreen(ctx.viewer, aX, aY);
|
||||||
[bX, bY] = modelToScreen(ctx.viewer, bX, bY);
|
[bX, bY] = modelToScreen(ctx.viewer, bX, bY);
|
||||||
[rX, rY] = modelToScreen(ctx.viewer, rX, rY);
|
[rX, rY] = modelToScreen(ctx.viewer, rX, rY);
|
||||||
ctx.actions['addEllipse'].action();
|
ctx.actions.EllipseTool.invoke(ctx);
|
||||||
moveAndClickXY(ctx, aX, aY);
|
moveAndClickXY(ctx, aX, aY);
|
||||||
let ellipse = ctx.viewer.toolManager.tool.ellipse;
|
let ellipse = ctx.viewer.toolManager.tool.ellipse;
|
||||||
moveAndClickXY(ctx, bX, bY);
|
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);
|
[aX, aY] = modelToScreen(ctx.viewer, aX, aY);
|
||||||
[bX, bY] = modelToScreen(ctx.viewer, bX, bY);
|
[bX, bY] = modelToScreen(ctx.viewer, bX, bY);
|
||||||
[rX, rY] = modelToScreen(ctx.viewer, rX, rY);
|
[rX, rY] = modelToScreen(ctx.viewer, rX, rY);
|
||||||
ctx.actions['addEllipticalArc'].action();
|
ctx.actions.EllipseArcTool.invoke(ctx);
|
||||||
moveAndClickXY(ctx, aX, aY);
|
moveAndClickXY(ctx, aX, aY);
|
||||||
let ellipse = ctx.viewer.toolManager.tool.ellipse;
|
let ellipse = ctx.viewer.toolManager.tool.ellipse;
|
||||||
moveAndClickXY(ctx, bX, bY);
|
moveAndClickXY(ctx, bX, bY);
|
||||||
|
|
|
||||||
|
|
@ -7,6 +7,7 @@ import {distanceAB, isCCW, makeAngle0_360} from '../../math/math'
|
||||||
import {normalizeCurveEnds} from '../../brep/geom/impl/nurbs-ext';
|
import {normalizeCurveEnds} from '../../brep/geom/impl/nurbs-ext';
|
||||||
import Vector from '../../../../modules/math/vector';
|
import Vector from '../../../../modules/math/vector';
|
||||||
import {AXIS, ORIGIN} from '../../../../modules/math/l3space';
|
import {AXIS, ORIGIN} from '../../../../modules/math/l3space';
|
||||||
|
import CSys from "../../../../modules/math/csys";
|
||||||
|
|
||||||
const RESOLUTION = 20;
|
const RESOLUTION = 20;
|
||||||
|
|
||||||
|
|
@ -21,11 +22,14 @@ class SketchPrimitive {
|
||||||
}
|
}
|
||||||
|
|
||||||
tessellate(resolution) {
|
tessellate(resolution) {
|
||||||
const tessellation = this.tessellateImpl(resolution);
|
return this.toNurbs(CSys.ORIGIN).tessellate();
|
||||||
if (this.inverted) {
|
// return brepCurve.impl.verb.tessellate().map(p => new Vector().set3(p) );
|
||||||
tessellation.reverse();
|
|
||||||
}
|
// const tessellation = this.tessellateImpl(resolution);
|
||||||
return tessellation;
|
// if (this.inverted) {
|
||||||
|
// tessellation.reverse();
|
||||||
|
// }
|
||||||
|
// return tessellation;
|
||||||
}
|
}
|
||||||
|
|
||||||
get isCurve() {
|
get isCurve() {
|
||||||
|
|
@ -64,7 +68,7 @@ export class Segment extends SketchPrimitive {
|
||||||
this.b = b;
|
this.b = b;
|
||||||
}
|
}
|
||||||
|
|
||||||
tessellateImpl(resolution) {
|
tessellate(resolution) {
|
||||||
return [this.a, this.b];
|
return [this.a, this.b];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -81,33 +85,6 @@ export class Arc extends SketchPrimitive {
|
||||||
this.c = c;
|
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) {
|
toVerbNurbs(tr, csys) {
|
||||||
|
|
||||||
const basisX = csys.x;
|
const basisX = csys.x;
|
||||||
|
|
@ -143,69 +120,42 @@ export class BezierCurve extends SketchPrimitive {
|
||||||
this.cp2 = cp2;
|
this.cp2 = cp2;
|
||||||
}
|
}
|
||||||
|
|
||||||
tessellateImpl(resolution) {
|
|
||||||
return LUT(this.a, this.b, this.cp1, this.cp2, 10);
|
|
||||||
}
|
|
||||||
|
|
||||||
toVerbNurbs(tr) {
|
toVerbNurbs(tr) {
|
||||||
return new verb.geom.BezierCurve([tr(this.a).data(), tr(this.cp1).data(), tr(this.cp2).data(), tr(this.b).data()], null);
|
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 {
|
export class EllipticalArc extends SketchPrimitive {
|
||||||
constructor(id, ep1, ep2, a, b, r) {
|
constructor(id, c, rx, ry, rot, a, b) {
|
||||||
super(id);
|
super(id);
|
||||||
this.ep1 = ep1;
|
this.c = c;
|
||||||
this.ep2 = ep2;
|
this.rx = rx;
|
||||||
|
this.ry = ry;
|
||||||
|
this.rot = rot;
|
||||||
this.a = a;
|
this.a = a;
|
||||||
this.b = b;
|
this.b = b;
|
||||||
this.r = r;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
tessellateImpl(resolution) {
|
toVerbNurbs(tr, csys) {
|
||||||
return EllipticalArc.tessEllipticalArc(this.ep1, this.ep2, this.a, this.b, this.r, resolution);
|
const ax = Math.cos(this.rot);
|
||||||
}
|
const ay = Math.sin(this.rot);
|
||||||
|
|
||||||
static tessEllipticalArc(ep1, ep2, ao, bo, radiusY, resolution) {
|
const xAxis = new Vector(ax, ay)._multiply(this.rx);
|
||||||
const axisX = ep2.minus(ep1);
|
const yAxis = new Vector(-ay, ax)._multiply(this.ry);
|
||||||
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 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) {
|
// let arc = new verb.geom.EllipseArc(tr(this.c).data(), tr(xAxis).data(), tr(yAxis).data(), startAngle, endAngle);
|
||||||
const xAxis = this.ep2.minus(this.ep1)._multiply(0.5);
|
let arc = new verb.geom.EllipseArc(this.c.data(), xAxis.data(), yAxis.data(), startAngle, endAngle);
|
||||||
const yAxis = new Vector(xAxis.y, xAxis.x)._normalize()._multiply(this.r) ;
|
arc = arc.transform(csys.outTransformation.toArray());
|
||||||
const center = this.ep1.plus(xAxis);
|
|
||||||
|
|
||||||
const startAngle = makeAngle0_360(Math.atan2(this.a.y - center.y, this.a.x - center.x));
|
return arc;
|
||||||
const endAngle = makeAngle0_360(Math.atan2(this.b.y - center.y, this.b.x - center.x));
|
// return adjustEnds(arc, tr(this.a), tr(this.b))
|
||||||
|
|
||||||
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))
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -216,25 +166,6 @@ export class Circle extends SketchPrimitive {
|
||||||
this.r = r;
|
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) {
|
toVerbNurbs(tr, csys) {
|
||||||
const basisX = csys.x;
|
const basisX = csys.x;
|
||||||
const basisY = csys.y;
|
const basisY = csys.y;
|
||||||
|
|
@ -243,22 +174,23 @@ export class Circle extends SketchPrimitive {
|
||||||
}
|
}
|
||||||
|
|
||||||
export class Ellipse extends SketchPrimitive {
|
export class Ellipse extends SketchPrimitive {
|
||||||
constructor(id, ep1, ep2, r) {
|
constructor(id, c, rx, ry, rot) {
|
||||||
super(id);
|
super(id);
|
||||||
this.ep1 = ep1;
|
this.c = c;
|
||||||
this.ep2 = ep2;
|
this.rx = rx;
|
||||||
this.r = r;
|
this.ry = ry;
|
||||||
}
|
this.rot = rot;
|
||||||
|
|
||||||
tessellateImpl(resolution) {
|
|
||||||
return EllipticalArc.tessEllipticalArc(this.ep1, this.ep2, this.ep1, this.ep1, this.r, resolution);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
toVerbNurbs(tr) {
|
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 ax = Math.cos(this.rot);
|
||||||
const center = this.ep1.plus(xAxis);
|
const ay = Math.sin(this.rot);
|
||||||
return new verb.geom.Ellipse(tr(center).data(), tr(xAxis).data(), tr(yAxis).data());
|
|
||||||
|
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 = [];
|
const tessellation = [];
|
||||||
for (let segment of this.segments) {
|
for (let segment of this.segments) {
|
||||||
const segmentTessellation = segment.tessellate(resolution);
|
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) {
|
for (let i = 0; i < segmentTessellation.length - 1; ++i) {
|
||||||
tessellation.push(segmentTessellation[i]);
|
tessellation.push(segmentTessellation[i]);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -85,11 +85,16 @@ export function ReadSketch(sketch, sketchId, readConstructionSegments) {
|
||||||
const arcCenter = ReadSketchPoint(data.c);
|
const arcCenter = ReadSketchPoint(data.c);
|
||||||
out.connections.push(new sm.Arc(getID(obj), arcA, arcB, arcCenter));
|
out.connections.push(new sm.Arc(getID(obj), arcA, arcB, arcCenter));
|
||||||
} else if (obj.type === 'EllipticalArc') {
|
} else if (obj.type === 'EllipticalArc') {
|
||||||
const ep1 = ReadSketchPoint(data.ep1);
|
if (data.ep1) {
|
||||||
const ep2 = ReadSketchPoint(data.ep2);
|
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 a = ReadSketchPoint(data.a);
|
||||||
const b = ReadSketchPoint(data.b);
|
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') {
|
} else if (obj.type === 'BezierCurve') {
|
||||||
const a = ReadSketchPoint(data.cp1);
|
const a = ReadSketchPoint(data.cp1);
|
||||||
const b = ReadSketchPoint(data.cp4);
|
const b = ReadSketchPoint(data.cp4);
|
||||||
|
|
@ -100,9 +105,14 @@ export function ReadSketch(sketch, sketchId, readConstructionSegments) {
|
||||||
const circleCenter = ReadSketchPoint(data.c);
|
const circleCenter = ReadSketchPoint(data.c);
|
||||||
out.loops.push(new sm.Circle(getID(obj), circleCenter, readSketchFloat(data.r)));
|
out.loops.push(new sm.Circle(getID(obj), circleCenter, readSketchFloat(data.r)));
|
||||||
} else if (obj.type === 'Ellipse') {
|
} else if (obj.type === 'Ellipse') {
|
||||||
const ep1 = ReadSketchPoint(data.ep1);
|
if (data.ep1) {
|
||||||
const ep2 = ReadSketchPoint(data.ep2);
|
continue;
|
||||||
out.loops.push(new sm.Ellipse(getID(obj), ep1, ep2, readSketchFloat(data.r)));
|
}
|
||||||
|
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;
|
return out;
|
||||||
|
|
|
||||||
|
|
@ -10,17 +10,17 @@ import {EndPoint} from "./point";
|
||||||
|
|
||||||
export class BezierCurve extends SketchObject {
|
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);
|
super(id);
|
||||||
const s1 = new Segment(ax, ay, cp1x, cp1y, this.id + ':1');
|
const s1 = new Segment(ax, ay, cp1x, cp1y, this.id + ':1');
|
||||||
const s2 = new Segment(bx, by, cp2x, cp2y, this.id + ':2');
|
const s2 = new Segment(bx, by, cp2x, cp2y, this.id + ':2');
|
||||||
this.addChild(s1);
|
this.addChild(s1);
|
||||||
this.addChild(s2);
|
this.addChild(s2);
|
||||||
|
|
||||||
this.a = this.s1.a;
|
this.a = s1.a;
|
||||||
this.b = this.s2.b;
|
this.b = s2.b;
|
||||||
this.cp1 = this.s1.b;
|
this.cp1 = s1.b;
|
||||||
this.cp2 = this.s2.a;
|
this.cp2 = s2.a;
|
||||||
|
|
||||||
for (let c of this.children) {
|
for (let c of this.children) {
|
||||||
c.role = 'objectConstruction';
|
c.role = 'objectConstruction';
|
||||||
|
|
|
||||||
|
|
@ -2,60 +2,64 @@ import {SketchObject} from './sketch-object'
|
||||||
|
|
||||||
import * as math from '../../math/math';
|
import * as math from '../../math/math';
|
||||||
import {Param} from "./param";
|
import {Param} from "./param";
|
||||||
import {SketchSegmentSerializationData} from "./segment";
|
|
||||||
import {EndPoint} from "./point";
|
import {EndPoint} from "./point";
|
||||||
|
|
||||||
export class Ellipse extends SketchObject {
|
export class Ellipse extends SketchObject {
|
||||||
|
|
||||||
constructor(x1, y1, x2, y2, r, id) {
|
constructor(cx, cy, rx, ry, rot, id) {
|
||||||
super(id);
|
super(id);
|
||||||
this.ep1 = new EndPoint(x1, y1, this.id + ':1');
|
|
||||||
this.ep2 = new EndPoint(x2, y2, this.id + ':2');
|
this.c = new EndPoint(cx, cy, this.id + ':C');
|
||||||
this.addChild(this.ep1);
|
|
||||||
this.addChild(this.ep2);
|
this.addChild(this.c);
|
||||||
this.r = new Param(r === undefined ? 0 : this.radiusX * 0.5, 'R');
|
|
||||||
this.r.enforceVisualLimit = true;
|
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() {
|
recoverIfNecessary() {
|
||||||
let recovered = false;
|
let recovered = false;
|
||||||
if (math.distanceAB(this.ep1, this.ep2) <= math.TOLERANCE) {
|
if (this.radiusX <= 0.1) {
|
||||||
this.ep1.translate(-RECOVER_LENGTH, -RECOVER_LENGTH);
|
this.rx.set(RECOVER_LENGTH);
|
||||||
this.ep2.translate(RECOVER_LENGTH, RECOVER_LENGTH);
|
|
||||||
recovered = true;
|
recovered = true;
|
||||||
}
|
}
|
||||||
if (this.radiusY <= 0.1) {
|
if (this.radiusY <= 0.1) {
|
||||||
this.r.set(RECOVER_LENGTH);
|
this.ry.set(RECOVER_LENGTH);
|
||||||
recovered = true;
|
recovered = true;
|
||||||
}
|
}
|
||||||
return recovered;
|
return recovered;
|
||||||
}
|
}
|
||||||
|
|
||||||
visitParams(callback) {
|
visitParams(callback) {
|
||||||
this.ep1.visitParams(callback);
|
this.c.visitParams(callback);
|
||||||
this.ep2.visitParams(callback);
|
callback(this.rx);
|
||||||
callback(this.r);
|
callback(this.ry);
|
||||||
|
callback(this.rot);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
get rotation() {
|
get rotation() {
|
||||||
return Math.atan2(this.ep2.y - this.ep1.y, this.ep2.x - this.ep1.x);
|
return this.rot.get();
|
||||||
}
|
}
|
||||||
|
|
||||||
get radiusX() {
|
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() {
|
get radiusY() {
|
||||||
return this.r.get();
|
return this.ry.get();
|
||||||
}
|
}
|
||||||
|
|
||||||
get centerX() {
|
get centerX() {
|
||||||
return this.ep1.x + (this.ep2.x - this.ep1.x) * 0.5;
|
return this.c.x;
|
||||||
}
|
}
|
||||||
|
|
||||||
get centerY() {
|
get centerY() {
|
||||||
return this.ep1.y + (this.ep2.y - this.ep1.y) * 0.5;
|
return this.c.y;
|
||||||
}
|
}
|
||||||
|
|
||||||
drawImpl(ctx, scale) {
|
drawImpl(ctx, scale) {
|
||||||
|
|
@ -92,19 +96,23 @@ export class Ellipse extends SketchObject {
|
||||||
|
|
||||||
write() {
|
write() {
|
||||||
return {
|
return {
|
||||||
ep1: this.ep1.write(),
|
c: this.c.write(),
|
||||||
ep2: this.ep2.write(),
|
rx: this.rx.get(),
|
||||||
r: this.r.get()
|
ry: this.ry.get(),
|
||||||
|
rot: this.rot.get(),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
static read(id, data) {
|
static read(id, data) {
|
||||||
|
if (data.ep1) {
|
||||||
|
return readFormatV1(id, data);
|
||||||
|
}
|
||||||
return new Ellipse(
|
return new Ellipse(
|
||||||
data.ep1.x,
|
data.c.x,
|
||||||
data.ep1.y,
|
data.c.y,
|
||||||
data.ep2.x,
|
data.rx,
|
||||||
data.ep2.y,
|
data.ry,
|
||||||
data.r,
|
data.rot,
|
||||||
id
|
id
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
@ -115,3 +123,14 @@ Ellipse.prototype.TYPE = 'Ellipse';
|
||||||
|
|
||||||
const sq = (a) => a * a;
|
const sq = (a) => a * a;
|
||||||
const RECOVER_LENGTH = 100;
|
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);
|
||||||
|
}
|
||||||
|
|
@ -1,14 +1,14 @@
|
||||||
import {Ellipse} from './ellipse'
|
import {Ellipse} from './ellipse'
|
||||||
import {Constraints} from '../parametric'
|
|
||||||
|
|
||||||
import * as math from '../../math/math';
|
import * as math from '../../math/math';
|
||||||
import {swap} from '../../utils/utils'
|
import {swap} from '../../utils/utils'
|
||||||
import {EndPoint} from "./point";
|
import {EndPoint} from "./point";
|
||||||
|
import {AlgNumConstraint, ConstraintDefinitions} from "../constr/ANConstraints";
|
||||||
|
|
||||||
export class EllipticalArc extends Ellipse {
|
export class EllipticalArc extends Ellipse {
|
||||||
|
|
||||||
constructor(x1, y1, x2, y2, ax, ay, bx, by, r, id) {
|
constructor(cx, cy, rx, ry, rot, ax, ay, bx, by, id) {
|
||||||
super(x1, y1, x2, y2, r, id);
|
super(cx, cy, rx, ry, rot, id);
|
||||||
this.a = new EndPoint(ax, ay, this.id + ':A');
|
this.a = new EndPoint(ax, ay, this.id + ':A');
|
||||||
this.b = new EndPoint(bx, by, this.id + ':B');
|
this.b = new EndPoint(bx, by, this.id + ':B');
|
||||||
this.addChild(this.a);
|
this.addChild(this.a);
|
||||||
|
|
@ -20,8 +20,14 @@ export class EllipticalArc extends Ellipse {
|
||||||
}
|
}
|
||||||
|
|
||||||
stabilize(viewer) {
|
stabilize(viewer) {
|
||||||
this.stage.addConstraint(new Constraints.PointOnEllipseInternal(this.b, this));
|
const c1 = new AlgNumConstraint(ConstraintDefinitions.PointOnEllipse, [this.b, this]);
|
||||||
this.stage.addConstraint(new Constraints.PointOnEllipseInternal(this.a, 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) {
|
drawImpl(ctx, scale) {
|
||||||
|
|
@ -53,29 +59,41 @@ export class EllipticalArc extends Ellipse {
|
||||||
|
|
||||||
write() {
|
write() {
|
||||||
return {
|
return {
|
||||||
ep1: this.ep1.write(),
|
c: this.c.write(),
|
||||||
ep2: this.ep2.write(),
|
rx: this.rx.get(),
|
||||||
|
ry: this.ry.get(),
|
||||||
|
rot: this.rot.get(),
|
||||||
a: this.a.write(),
|
a: this.a.write(),
|
||||||
b: this.b.write(),
|
b: this.b.write()
|
||||||
r: this.r.get()
|
};
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static read(id, data) {
|
static read(id, data) {
|
||||||
|
if (data.ep1) {
|
||||||
|
return readFormatV1(id, data);
|
||||||
|
}
|
||||||
return new EllipticalArc(
|
return new EllipticalArc(
|
||||||
data.ep1.x,
|
data.c.x,
|
||||||
data.ep1.y,
|
data.c.y,
|
||||||
data.ep2.x,
|
data.rx,
|
||||||
data.ep2.y,
|
data.ry,
|
||||||
data.a.x,
|
data.rot,
|
||||||
data.a.y,
|
data.a.x, data.a.y, data.b.x, data.b.y,
|
||||||
data.b.x,
|
|
||||||
data.b.y,
|
|
||||||
data.r,
|
|
||||||
id
|
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._class = 'TCAD.TWO.EllipticalArc';
|
||||||
EllipticalArc.prototype.TYPE = 'EllipticalArc';
|
EllipticalArc.prototype.TYPE = 'EllipticalArc';
|
||||||
|
|
|
||||||
|
|
@ -5,6 +5,7 @@ import {DragTool} from './drag'
|
||||||
import {EllipseTool, STATE_RADIUS} from './ellipse'
|
import {EllipseTool, STATE_RADIUS} from './ellipse'
|
||||||
import {AngleBetweenDimension} from "../shapes/dim";
|
import {AngleBetweenDimension} from "../shapes/dim";
|
||||||
import {AddAngleBetweenDimTool} from "./dim";
|
import {AddAngleBetweenDimTool} from "./dim";
|
||||||
|
import {EllipseEditTool} from "./ellipse-edit";
|
||||||
|
|
||||||
export function GetShapeEditTool(viewer, obj, alternative) {
|
export function GetShapeEditTool(viewer, obj, alternative) {
|
||||||
if (obj instanceof Circle && !alternative) {
|
if (obj instanceof Circle && !alternative) {
|
||||||
|
|
@ -12,13 +13,7 @@ export function GetShapeEditTool(viewer, obj, alternative) {
|
||||||
tool.circle = obj;
|
tool.circle = obj;
|
||||||
return tool;
|
return tool;
|
||||||
} else if (obj instanceof Ellipse && !alternative) {
|
} else if (obj instanceof Ellipse && !alternative) {
|
||||||
// even for an ell-arc we should act as it would be an ellipse to
|
return new EllipseEditTool(viewer, obj);
|
||||||
// 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 if (obj instanceof AngleBetweenDimension && !alternative) {
|
} else if (obj instanceof AngleBetweenDimension && !alternative) {
|
||||||
const tool = new AddAngleBetweenDimTool(viewer, viewer.dimLayer);
|
const tool = new AddAngleBetweenDimTool(viewer, viewer.dimLayer);
|
||||||
tool.a = obj.a;
|
tool.a = obj.a;
|
||||||
|
|
|
||||||
73
web/app/sketcher/tools/ellipse-edit.js
Normal file
73
web/app/sketcher/tools/ellipse-edit.js
Normal file
|
|
@ -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);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
@ -1,26 +1,25 @@
|
||||||
import {Tool} from './tool'
|
import {Tool} from './tool'
|
||||||
import {EndPoint} from '../shapes/point'
|
|
||||||
import {Ellipse} from '../shapes/ellipse'
|
import {Ellipse} from '../shapes/ellipse'
|
||||||
import {EllipticalArc} from '../shapes/elliptical-arc'
|
import {EllipticalArc} from '../shapes/elliptical-arc'
|
||||||
import Vector from 'math/vector';
|
|
||||||
|
|
||||||
export const STATE_POINT1 = 0;
|
export const CENTER = 0;
|
||||||
export const STATE_POINT2 = 1;
|
export const RADIUS_X = 1;
|
||||||
export const STATE_RADIUS = 2;
|
export const RADIUS_Y = 2;
|
||||||
|
|
||||||
|
|
||||||
export class EllipseTool extends Tool {
|
export class EllipseTool extends Tool {
|
||||||
|
|
||||||
constructor(viewer, arc) {
|
constructor(viewer, arc) {
|
||||||
super(arc ? 'ellipse' : 'elliptical arc', viewer);
|
super(arc ? 'elliptical arc' : 'ellipse', viewer);
|
||||||
this.arc = arc;
|
this.arc = arc;
|
||||||
this.ellipse = null;
|
this.ellipse = null;
|
||||||
this.state = STATE_POINT1;
|
this.state = CENTER;
|
||||||
}
|
}
|
||||||
|
|
||||||
restart() {
|
restart() {
|
||||||
this.ellipse = null;
|
this.ellipse = null;
|
||||||
this.state = STATE_POINT1;
|
this.state = CENTER;
|
||||||
this.sendHint('specify first major axis point')
|
this.sendHint('specify a center of the ellipse')
|
||||||
}
|
}
|
||||||
|
|
||||||
cleanup(e) {
|
cleanup(e) {
|
||||||
|
|
@ -32,8 +31,7 @@ export class EllipseTool extends Tool {
|
||||||
}
|
}
|
||||||
|
|
||||||
newEllipse(p) {
|
newEllipse(p) {
|
||||||
|
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);
|
||||||
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);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
demoBPoint() {
|
demoBPoint() {
|
||||||
|
|
@ -47,26 +45,22 @@ export class EllipseTool extends Tool {
|
||||||
|
|
||||||
mouseup(e) {
|
mouseup(e) {
|
||||||
switch (this.state) {
|
switch (this.state) {
|
||||||
case STATE_POINT1: {
|
case CENTER: {
|
||||||
const p = this.point(e);
|
const p = this.point(e);
|
||||||
this.ellipse = this.newEllipse(p);
|
this.ellipse = this.newEllipse(p);
|
||||||
this.snapIfNeed(this.ellipse.ep1);
|
this.snapIfNeed(this.ellipse.c);
|
||||||
this.viewer.activeLayer.add(this.ellipse);
|
this.viewer.activeLayer.add(this.ellipse);
|
||||||
this.viewer.refresh();
|
this.viewer.refresh();
|
||||||
this.state = STATE_POINT2;
|
this.state = RADIUS_X;
|
||||||
this.sendHint('specify second major axis point');
|
this.sendHint('specify major radius');
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case STATE_POINT2: {
|
case RADIUS_X: {
|
||||||
const p = this.point(e);
|
this.state = RADIUS_Y;
|
||||||
this.ellipse.ep2.setFromPoint(p);
|
this.sendHint('specify minor radius');
|
||||||
this.snapIfNeed(this.ellipse.ep2);
|
|
||||||
this.viewer.refresh();
|
|
||||||
this.state = STATE_RADIUS;
|
|
||||||
this.sendHint('specify minor axis radius');
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case STATE_RADIUS:
|
case RADIUS_Y:
|
||||||
if (this.arc) {
|
if (this.arc) {
|
||||||
this.ellipse.stabilize(this.viewer);
|
this.ellipse.stabilize(this.viewer);
|
||||||
}
|
}
|
||||||
|
|
@ -77,41 +71,47 @@ export class EllipseTool extends Tool {
|
||||||
mousemove(e) {
|
mousemove(e) {
|
||||||
const p = this.viewer.screenToModel(e);
|
const p = this.viewer.screenToModel(e);
|
||||||
switch (this.state) {
|
switch (this.state) {
|
||||||
case STATE_POINT1:
|
case CENTER: {
|
||||||
this.viewer.snap(p.x, p.y, []);
|
this.viewer.snap(p.x, p.y, []);
|
||||||
break;
|
break;
|
||||||
case STATE_POINT2:
|
}
|
||||||
this.ellipse.ep2.setFromPoint(this.viewer.screenToModel(e));
|
case RADIUS_X: {
|
||||||
this.ellipse.r.value = this.ellipse.radiusX * 0.5;
|
|
||||||
this.viewer.snap(p.x, p.y, this.ellipse.children);
|
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) {
|
if (this.arc) {
|
||||||
this.ellipse.a.setFromPoint(this.ellipse.ep2);
|
this.ellipse.a.setFromPoint(p);
|
||||||
this.demoBPoint();
|
this.demoBPoint();
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case STATE_RADIUS:
|
}
|
||||||
const polarPoint = this.ellipse.toEllipseCoordinateSystem(p);
|
case RADIUS_Y: {
|
||||||
let minorRadius = Ellipse.findMinorRadius(this.ellipse.radiusX, polarPoint.radius, polarPoint.angle);
|
|
||||||
if (isNaN(minorRadius)) {
|
const dx = p.x - this.ellipse.c.x;
|
||||||
const projAxis = new Vector(-(this.ellipse.ep2.y - this.ellipse.ep1.y), this.ellipse.ep2.x - this.ellipse.ep1.x);
|
const dy = p.y - this.ellipse.c.y;
|
||||||
projAxis._normalize();
|
|
||||||
const v = new Vector(this.ellipse.ep2.x - p.x, this.ellipse.ep2.y - p.y);
|
const rot = this.ellipse.rotation;
|
||||||
minorRadius = Math.abs(projAxis.dot(v));
|
const axisX = - Math.sin(rot);
|
||||||
}
|
const axisY = Math.cos(rot);
|
||||||
this.ellipse.r.set(minorRadius);
|
|
||||||
if (!Tool.dumbMode(e)) {
|
|
||||||
this.solveRequest(true);
|
const ry = Math.abs(dx * axisX + dy * axisY);
|
||||||
}
|
|
||||||
|
this.ellipse.ry.set(ry);
|
||||||
|
|
||||||
if (this.arc) {
|
if (this.arc) {
|
||||||
this.demoBPoint();
|
this.demoBPoint();
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
this.viewer.refresh();
|
this.viewer.refresh();
|
||||||
}
|
}
|
||||||
|
|
||||||
solveRequest(rough) {
|
|
||||||
this.viewer.parametricManager.prepare([this.ellipse.r]);
|
|
||||||
this.viewer.parametricManager.solve(rough);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
Loading…
Reference in a new issue