basic support for bezier curve

This commit is contained in:
Val Erastov 2016-12-08 22:58:18 -08:00
parent ea4892035a
commit 32632b81ac
12 changed files with 205 additions and 12 deletions

View file

@ -87,4 +87,10 @@ export function _matrix(m, n) {
return out; return out;
} }
export function rotate(px, py, angle) {
const x = px * Math.cos(angle) - py * Math.sin(angle)
const y = px * Math.sin(angle) + py * Math.cos(angle);
return {x, y};
}
export const sq = (a) => a * a; export const sq = (a) => a * a;

View file

@ -8,9 +8,9 @@ function Vector(x, y, z) {
} }
Vector.prototype.set = function(x, y, z) { Vector.prototype.set = function(x, y, z) {
this.x = x; this.x = x || 0;
this.y = y; this.y = y || 0;
this.z = z; this.z = z || 0;
return this; return this;
}; };

View file

@ -7,6 +7,7 @@ import {Segment} from './shapes/segment'
import {Circle} from './shapes/circle' import {Circle} from './shapes/circle'
import {Ellipse} from './shapes/ellipse' import {Ellipse} from './shapes/ellipse'
import {EllipticalArc} from './shapes/elliptical-arc' import {EllipticalArc} from './shapes/elliptical-arc'
import {BezierCurve} from './shapes/bezier-curve'
import {HDimension, VDimension, Dimension, DiameterDimension} from './shapes/dim' import {HDimension, VDimension, Dimension, DiameterDimension} from './shapes/dim'
import {Constraints} from './parametric' import {Constraints} from './parametric'
import Vector from '../math/vector' import Vector from '../math/vector'
@ -18,6 +19,7 @@ var Types = {
CIRCLE : 'TCAD.TWO.Circle', CIRCLE : 'TCAD.TWO.Circle',
ELLIPSE : 'TCAD.TWO.Ellipse', ELLIPSE : 'TCAD.TWO.Ellipse',
ELL_ARC : 'TCAD.TWO.EllipticalArc', ELL_ARC : 'TCAD.TWO.EllipticalArc',
BEZIER : 'TCAD.TWO.BezierCurve',
DIM : 'TCAD.TWO.Dimension', DIM : 'TCAD.TWO.Dimension',
HDIM : 'TCAD.TWO.HDimension', HDIM : 'TCAD.TWO.HDimension',
VDIM : 'TCAD.TWO.VDimension', VDIM : 'TCAD.TWO.VDimension',
@ -130,6 +132,12 @@ IO.prototype._loadSketch = function(sketch) {
const b = endPoint(obj['b']); const b = endPoint(obj['b']);
skobj = new EllipticalArc(ep1, ep2, a, b); skobj = new EllipticalArc(ep1, ep2, a, b);
skobj.r.set(obj['r']); skobj.r.set(obj['r']);
} else if (_class === T.BEZIER) {
const a = endPoint(obj['a']);
const b = endPoint(obj['b']);
const cp1 = endPoint(obj['cp1']);
const cp2 = endPoint(obj['cp2']);
skobj = new BezierCurve(a, b, cp1, cp2);
} else if (_class === T.HDIM) { } else if (_class === T.HDIM) {
skobj = new HDimension(obj['a'], obj['b']); skobj = new HDimension(obj['a'], obj['b']);
skobj.flip = obj['flip']; skobj.flip = obj['flip'];
@ -315,6 +323,11 @@ IO.prototype._serializeSketch = function() {
to['a'] = point(obj.a); to['a'] = point(obj.a);
to['b'] = point(obj.b); to['b'] = point(obj.b);
to['r'] = obj.r.get(); to['r'] = obj.r.get();
} else if (obj._class === T.BEZIER) {
to['a'] = point(obj.a);
to['b'] = point(obj.b);
to['cp1'] = point(obj.cp1);
to['cp2'] = point(obj.cp2);
} else if (obj._class === T.DIM || obj._class === T.HDIM || obj._class === T.VDIM) { } else if (obj._class === T.DIM || obj._class === T.HDIM || obj._class === T.VDIM) {
to['a'] = obj.a.id; to['a'] = obj.a.id;
to['b'] = obj.b.id; to['b'] = obj.b.id;

View file

@ -0,0 +1,43 @@
import {Ref} from './ref'
import {SketchObject} from './sketch-object'
import {Segment} from './segment'
import * as math from '../../math/math';
export class BezierCurve extends SketchObject {
constructor(a, b, cp1, cp2) {
super();
this.a = a;
this.b = b;
this.cp1 = cp1;
this.cp2 = cp2;
this.addChild(new Segment(a, cp1));
this.addChild(new Segment(b, cp2));
for (let c of this.children) {
c.role = 'construction';
}
}
collectParams(params) {
this.a.collectParams(params);
this.b.collectParams(params);
this.cp1.collectParams(params);
this.cp2.collectParams(params);
}
normalDistance() {
return 1000000;
}
drawImpl(ctx, scale, viewer) {
ctx.beginPath();
ctx.moveTo(this.a.x, this.a.y);
ctx.bezierCurveTo(this.cp1.x, this.cp1.y, this.cp2.x, this.cp2.y, this.b.x, this.b.y);
ctx.stroke();
}
}
BezierCurve.prototype._class = 'TCAD.TWO.BezierCurve';
const RECOVER_LENGTH = 100;

View file

@ -48,6 +48,10 @@ export class EndPoint extends SketchObject {
setFromArray(arr) { setFromArray(arr) {
this.setXY(arr[0], arr[1]); this.setXY(arr[0], arr[1]);
} }
copy() {
return new EndPoint(this.x, this.y);
}
} }
EndPoint.prototype._class = 'TCAD.TWO.EndPoint'; EndPoint.prototype._class = 'TCAD.TWO.EndPoint';

View file

@ -3,6 +3,8 @@ export class Shape {
constructor() { constructor() {
this.visible = true; this.visible = true;
this.style = null;
this.role = null;
} }
accept(visitor) { accept(visitor) {

View file

@ -1,6 +1,4 @@
import {Generator} from '../id-generator' import {Generator} from '../id-generator'
import {SetStyle} from './draw-utils'
import {DragTool} from '../tools/drag'
import {Shape} from './shape' import {Shape} from './shape'
export class SketchObject extends Shape { export class SketchObject extends Shape {
@ -78,7 +76,7 @@ export class SketchObject extends Shape {
if (!this.visible) return; if (!this.visible) return;
if (this.marked != null) { if (this.marked != null) {
ctx.save(); ctx.save();
SetStyle(this.marked, ctx, scale); viewer.setStyle(this.marked, ctx);
} }
this.drawImpl(ctx, scale, viewer); this.drawImpl(ctx, scale, viewer);
if (this.marked != null) ctx.restore(); if (this.marked != null) ctx.restore();

View file

@ -9,6 +9,7 @@ import {AddArcTool} from './tools/arc'
import {EditCircleTool} from './tools/circle' import {EditCircleTool} from './tools/circle'
import {FilletTool} from './tools/fillet' import {FilletTool} from './tools/fillet'
import {EllipseTool} from './tools/ellipse' import {EllipseTool} from './tools/ellipse'
import {BezierCurveTool} from './tools/bezier-curve'
import {ReferencePointTool} from './tools/origin' import {ReferencePointTool} from './tools/origin'
import {InputManager} from './input-manager' import {InputManager} from './input-manager'
@ -143,6 +144,10 @@ function App2D() {
app.viewer.toolManager.takeControl(new EllipseTool(app.viewer, true)); app.viewer.toolManager.takeControl(new EllipseTool(app.viewer, true));
}); });
this.registerAction('addBezierCurve', "Add Bezier Curve", function () {
app.viewer.toolManager.takeControl(new BezierCurveTool(app.viewer));
});
this.registerAction('pan', "Pan", function () { this.registerAction('pan', "Pan", function () {
app.viewer.toolManager.releaseControl(); app.viewer.toolManager.releaseControl();
}); });

View file

@ -0,0 +1,85 @@
import {Tool} from './tool'
import {EndPoint} from '../shapes/point'
import {BezierCurve} from '../shapes/bezier-curve'
import {Constraints} from '../parametric'
import Vector from '../../math/vector'
import * as math from '../../math/math'
export class BezierCurveTool extends Tool {
constructor(viewer) {
super('bezier curve', viewer);
this.init();
this._v = new Vector();
}
init() {
this.curve = null;
this.otherCurveEndPoint = null;
}
restart() {
this.init();
this.sendHint('specify first point')
}
cleanup(e) {
this.viewer.cleanSnap();
}
mouseup(e) {
if (this.curve == null) {
this.checkIfConnectedToOtherCurve();
const p = this.endpoint(e);
this.curve = new BezierCurve(p, p.copy(), p.copy(), p.copy());
this.viewer.activeLayer.add(this.curve);
this.viewer.refresh();
} else {
this.snapIfNeed(this.curve.b);
if (this.otherCurveEndPoint != null) {
this.viewer.parametricManager.add(new Constraints.Parallel(this.otherCurveEndPoint.parent, this.curve.a.parent));
}
this.viewer.toolManager.releaseControl();
this.viewer.refresh();
}
}
mousemove(e) {
const p = this.viewer.screenToModel(e);
if (this.curve != null) {
this.curve.b.setFromPoint(p);
const axis = this._v.set(this.curve.b.x - this.curve.a.x, this.curve.b.y - this.curve.a.y)._multiply(0.7);
//controlSegment = {x: -controlSegment.y, y: controlSegment.x};
const controlSegment = math.rotate(- axis.y, axis.x, - Math.PI * 0.25);
if (this.otherCurveEndPoint != null) {
const ctrlLength = axis.length();
this.curve.cp1.x = this.curve.a.x + this.snappedControl.x * ctrlLength;
this.curve.cp1.y = this.curve.a.y + this.snappedControl.y * ctrlLength;
if (this.snappedControl.x * controlSegment.x + this.snappedControl.y * controlSegment.y < 0) {
controlSegment.x *= -1;
controlSegment.y *= -1;
}
} else {
this.curve.cp1.x = this.curve.a.x + controlSegment.x;
this.curve.cp1.y = this.curve.a.y + controlSegment.y;
}
this.curve.cp2.x = this.curve.b.x - controlSegment.x;
this.curve.cp2.y = this.curve.b.y - controlSegment.y;
this.viewer.snap(p.x, p.y, [this.curve.a, this.curve.b, this.curve.cp1, this.curve.cp2]);
} else {
this.viewer.snap(p.x, p.y, []);
}
this.viewer.refresh();
}
checkIfConnectedToOtherCurve() {
const snapped = this.viewer.snapped;
if (snapped != null && snapped.parent && snapped.parent.parent &&
snapped.parent.parent instanceof BezierCurve &&
snapped.parent.a === snapped) { //only a of Segment is a curve endpoint(other end is a control point)
const seg = snapped.parent;
this.otherCurveEndPoint = snapped;
this.snappedControl = new Vector(seg.b.x - seg.a.x, seg.b.y - seg.a.y)._normalize()._multiply(-1);
}
}
}

View file

@ -1,3 +1,4 @@
import {EndPoint} from '../shapes/point'
export class Tool { export class Tool {
@ -44,13 +45,24 @@ export class Tool {
snapIfNeed(p) { snapIfNeed(p) {
if (this.viewer.snapped != null) { if (this.viewer.snapped != null) {
var snapWith = this.viewer.snapped; const snapWith = this.viewer.snapped;
this.viewer.cleanSnap(); this.viewer.cleanSnap();
p.setFromPoint(snapWith);
this.viewer.parametricManager.linkObjects([p, snapWith]); this.viewer.parametricManager.linkObjects([p, snapWith]);
this.viewer.parametricManager.refresh(); this.viewer.parametricManager.refresh();
} }
} }
endpoint(e) {
const ep = new EndPoint();
if (this.viewer.snapped != null) {
this.snapIfNeed(ep);
} else {
ep.setFromPoint(this.viewer.screenToModel(e))
}
return ep;
}
static dumbMode(e) { static dumbMode(e) {
return e.ctrlKey || e.metaKey; return e.ctrlKey || e.metaKey;
} }

View file

@ -137,7 +137,7 @@ Viewer.prototype.search = function(x, y, buffer, deep, onlyPoints, filter) {
objs[j].accept(function(o) { objs[j].accept(function(o) {
if (!o.visible) return true; if (!o.visible) return true;
if (onlyPoints && !isEndPoint(o)) { if (onlyPoints && !isEndPoint(o)) {
return false; return true;
} }
l = o.normalDistance(aim); l = o.normalDistance(aim);
if (l >= 0 && l <= buffer && !isFiltered(o)) { if (l >= 0 && l <= buffer && !isFiltered(o)) {
@ -230,14 +230,30 @@ Viewer.prototype.__drawWorkspace = function(ctx, workspace, pipeline) {
}; };
Viewer.prototype.__draw = function(ctx, layer, obj) { Viewer.prototype.__draw = function(ctx, layer, obj) {
let style = obj.style != null ? obj.style : layer.style; const style = this.getStyleForObject(layer, obj);
if (style !== this.__prevStyle) { if (style !== this.__prevStyle) {
draw_utils.SetStyle(style, ctx, this.scale / this.retinaPxielRatio); this.setStyle(style, ctx);
} }
this.__prevStyle = style; this.__prevStyle = style;
obj.draw(ctx, this.scale / this.retinaPxielRatio, this); obj.draw(ctx, this.scale / this.retinaPxielRatio, this);
}; };
Viewer.prototype.getStyleForObject = function(layer, obj) {
if (obj.style != null) {
return obj.style;
} else if (obj.role != null) {
const style = layer.stylesByRoles[obj.role];
if (style) {
return style;
}
}
return layer.style;
};
Viewer.prototype.setStyle = function(style, ctx) {
draw_utils.SetStyle(style, ctx, this.scale / this.retinaPxielRatio);
};
Viewer.prototype.snap = function(x, y, excl) { Viewer.prototype.snap = function(x, y, excl) {
this.cleanSnap(); this.cleanSnap();
var snapTo = this.search(x, y, 20 / this.scale, true, true, excl); var snapTo = this.search(x, y, 20 / this.scale, true, true, excl);
@ -412,10 +428,18 @@ Viewer.prototype.equalizeLinkedEndpoints = function() {
function Layer(name, style) { function Layer(name, style) {
this.name = name; this.name = name;
this.style = style; this.style = style;
this.stylesByRoles = {
'construction': Styles.CONSTRUCTION_OF_OBJECT
};
this.objects = []; this.objects = [];
this.readOnly = false; // This is actually a mark for boundary layers coming from 3D this.readOnly = false; // This is actually a mark for boundary layers coming from 3D
} }
Layer.prototype.add = function(object) {
this.objects.push(object);
object.layer = this;
};
Viewer.prototype.fullHeavyUIRefresh = function() { Viewer.prototype.fullHeavyUIRefresh = function() {
this.refresh(); this.refresh();
this.parametricManager.notify(); this.parametricManager.notify();

View file

@ -30,7 +30,8 @@
--><button class="btn tbtn act-addCircle" style="background-image: url(img/circle.png);" type="submit" value=""></button><!-- --><button class="btn tbtn act-addCircle" style="background-image: url(img/circle.png);" type="submit" value=""></button><!--
--><button class="btn tbtn act-addArc" style="background-image: url(img/arc.png);" type="submit" value=""></button><!-- --><button class="btn tbtn act-addArc" style="background-image: url(img/arc.png);" type="submit" value=""></button><!--
--><button class="btn tbtn act-addEllipse" type="submit" value="">E</button><!-- --><button class="btn tbtn act-addEllipse" type="submit" value="">E</button><!--
--><button class="btn tbtn act-addEllipticalArc sep" type="submit" value="">EA</button><!-- --><button class="btn tbtn act-addEllipticalArc" type="submit" value="">EA</button><!--
--><button class="btn tbtn act-addBezierCurve sep" type="submit" value="">BZ</button><!--
--><button class="btn tbtn act-addHDim" style="background-image: url(img/hdim.png);" type="submit" value=""></button><!-- --><button class="btn tbtn act-addHDim" style="background-image: url(img/hdim.png);" type="submit" value=""></button><!--
--><button class="btn tbtn act-addVDim" style="background-image: url(img/vdim.png);" type="submit" value=""></button><!-- --><button class="btn tbtn act-addVDim" style="background-image: url(img/vdim.png);" type="submit" value=""></button><!--
--><button class="btn tbtn act-addDim" style="background-image: url(img/dim.png);" type="submit" value=""></button><!-- --><button class="btn tbtn act-addDim" style="background-image: url(img/dim.png);" type="submit" value=""></button><!--