mirror of
https://github.com/xibyte/jsketcher
synced 2025-12-06 16:33:15 +01:00
basic support for bezier curve
This commit is contained in:
parent
ea4892035a
commit
32632b81ac
12 changed files with 205 additions and 12 deletions
|
|
@ -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;
|
||||||
|
|
|
||||||
|
|
@ -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;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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;
|
||||||
|
|
|
||||||
43
web/app/sketcher/shapes/bezier-curve.js
Normal file
43
web/app/sketcher/shapes/bezier-curve.js
Normal 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;
|
||||||
|
|
@ -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';
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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) {
|
||||||
|
|
|
||||||
|
|
@ -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();
|
||||||
|
|
|
||||||
|
|
@ -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();
|
||||||
});
|
});
|
||||||
|
|
|
||||||
85
web/app/sketcher/tools/bezier-curve.js
Normal file
85
web/app/sketcher/tools/bezier-curve.js
Normal 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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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();
|
||||||
|
|
|
||||||
|
|
@ -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><!--
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue