From 32632b81acbafe95267e3f48e14503e1589e9ca9 Mon Sep 17 00:00:00 2001 From: Val Erastov Date: Thu, 8 Dec 2016 22:58:18 -0800 Subject: [PATCH] basic support for bezier curve --- web/app/math/math.js | 6 ++ web/app/math/vector.js | 6 +- web/app/sketcher/io.js | 13 ++++ web/app/sketcher/shapes/bezier-curve.js | 43 ++++++++++++ web/app/sketcher/shapes/point.js | 4 ++ web/app/sketcher/shapes/shape.js | 2 + web/app/sketcher/shapes/sketch-object.js | 4 +- web/app/sketcher/sketcher-app.js | 5 ++ web/app/sketcher/tools/bezier-curve.js | 85 ++++++++++++++++++++++++ web/app/sketcher/tools/tool.js | 16 ++++- web/app/sketcher/viewer2d.js | 30 ++++++++- web/sketcher.html | 3 +- 12 files changed, 205 insertions(+), 12 deletions(-) create mode 100644 web/app/sketcher/shapes/bezier-curve.js create mode 100644 web/app/sketcher/tools/bezier-curve.js diff --git a/web/app/math/math.js b/web/app/math/math.js index 93a52a45..67135dc4 100644 --- a/web/app/math/math.js +++ b/web/app/math/math.js @@ -87,4 +87,10 @@ export function _matrix(m, n) { 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; diff --git a/web/app/math/vector.js b/web/app/math/vector.js index 316ba3f3..478ac474 100644 --- a/web/app/math/vector.js +++ b/web/app/math/vector.js @@ -8,9 +8,9 @@ function Vector(x, y, z) { } Vector.prototype.set = function(x, y, z) { - this.x = x; - this.y = y; - this.z = z; + this.x = x || 0; + this.y = y || 0; + this.z = z || 0; return this; }; diff --git a/web/app/sketcher/io.js b/web/app/sketcher/io.js index c3689878..cdf8ca2f 100644 --- a/web/app/sketcher/io.js +++ b/web/app/sketcher/io.js @@ -7,6 +7,7 @@ import {Segment} from './shapes/segment' import {Circle} from './shapes/circle' import {Ellipse} from './shapes/ellipse' import {EllipticalArc} from './shapes/elliptical-arc' +import {BezierCurve} from './shapes/bezier-curve' import {HDimension, VDimension, Dimension, DiameterDimension} from './shapes/dim' import {Constraints} from './parametric' import Vector from '../math/vector' @@ -18,6 +19,7 @@ var Types = { CIRCLE : 'TCAD.TWO.Circle', ELLIPSE : 'TCAD.TWO.Ellipse', ELL_ARC : 'TCAD.TWO.EllipticalArc', + BEZIER : 'TCAD.TWO.BezierCurve', DIM : 'TCAD.TWO.Dimension', HDIM : 'TCAD.TWO.HDimension', VDIM : 'TCAD.TWO.VDimension', @@ -130,6 +132,12 @@ IO.prototype._loadSketch = function(sketch) { const b = endPoint(obj['b']); skobj = new EllipticalArc(ep1, ep2, a, b); 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) { skobj = new HDimension(obj['a'], obj['b']); skobj.flip = obj['flip']; @@ -315,6 +323,11 @@ IO.prototype._serializeSketch = function() { to['a'] = point(obj.a); to['b'] = point(obj.b); 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) { to['a'] = obj.a.id; to['b'] = obj.b.id; diff --git a/web/app/sketcher/shapes/bezier-curve.js b/web/app/sketcher/shapes/bezier-curve.js new file mode 100644 index 00000000..0d503bfe --- /dev/null +++ b/web/app/sketcher/shapes/bezier-curve.js @@ -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; \ No newline at end of file diff --git a/web/app/sketcher/shapes/point.js b/web/app/sketcher/shapes/point.js index b7ca3a3a..bedbe8f1 100644 --- a/web/app/sketcher/shapes/point.js +++ b/web/app/sketcher/shapes/point.js @@ -48,6 +48,10 @@ export class EndPoint extends SketchObject { setFromArray(arr) { this.setXY(arr[0], arr[1]); } + + copy() { + return new EndPoint(this.x, this.y); + } } EndPoint.prototype._class = 'TCAD.TWO.EndPoint'; diff --git a/web/app/sketcher/shapes/shape.js b/web/app/sketcher/shapes/shape.js index e7c4e69f..2b85b370 100644 --- a/web/app/sketcher/shapes/shape.js +++ b/web/app/sketcher/shapes/shape.js @@ -3,6 +3,8 @@ export class Shape { constructor() { this.visible = true; + this.style = null; + this.role = null; } accept(visitor) { diff --git a/web/app/sketcher/shapes/sketch-object.js b/web/app/sketcher/shapes/sketch-object.js index 859fbd5e..d1db2a20 100644 --- a/web/app/sketcher/shapes/sketch-object.js +++ b/web/app/sketcher/shapes/sketch-object.js @@ -1,6 +1,4 @@ import {Generator} from '../id-generator' -import {SetStyle} from './draw-utils' -import {DragTool} from '../tools/drag' import {Shape} from './shape' export class SketchObject extends Shape { @@ -78,7 +76,7 @@ export class SketchObject extends Shape { if (!this.visible) return; if (this.marked != null) { ctx.save(); - SetStyle(this.marked, ctx, scale); + viewer.setStyle(this.marked, ctx); } this.drawImpl(ctx, scale, viewer); if (this.marked != null) ctx.restore(); diff --git a/web/app/sketcher/sketcher-app.js b/web/app/sketcher/sketcher-app.js index 3684c707..4d76cf8b 100644 --- a/web/app/sketcher/sketcher-app.js +++ b/web/app/sketcher/sketcher-app.js @@ -9,6 +9,7 @@ import {AddArcTool} from './tools/arc' import {EditCircleTool} from './tools/circle' import {FilletTool} from './tools/fillet' import {EllipseTool} from './tools/ellipse' +import {BezierCurveTool} from './tools/bezier-curve' import {ReferencePointTool} from './tools/origin' import {InputManager} from './input-manager' @@ -143,6 +144,10 @@ function App2D() { 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 () { app.viewer.toolManager.releaseControl(); }); diff --git a/web/app/sketcher/tools/bezier-curve.js b/web/app/sketcher/tools/bezier-curve.js new file mode 100644 index 00000000..5f459977 --- /dev/null +++ b/web/app/sketcher/tools/bezier-curve.js @@ -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); + } + } +} \ No newline at end of file diff --git a/web/app/sketcher/tools/tool.js b/web/app/sketcher/tools/tool.js index 8c7f9e3d..f3dc6cb7 100644 --- a/web/app/sketcher/tools/tool.js +++ b/web/app/sketcher/tools/tool.js @@ -1,3 +1,4 @@ +import {EndPoint} from '../shapes/point' export class Tool { @@ -44,13 +45,24 @@ export class Tool { snapIfNeed(p) { if (this.viewer.snapped != null) { - var snapWith = this.viewer.snapped; + const snapWith = this.viewer.snapped; this.viewer.cleanSnap(); + p.setFromPoint(snapWith); this.viewer.parametricManager.linkObjects([p, snapWith]); 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) { return e.ctrlKey || e.metaKey; } diff --git a/web/app/sketcher/viewer2d.js b/web/app/sketcher/viewer2d.js index cf1afc63..a7ff5d41 100644 --- a/web/app/sketcher/viewer2d.js +++ b/web/app/sketcher/viewer2d.js @@ -137,7 +137,7 @@ Viewer.prototype.search = function(x, y, buffer, deep, onlyPoints, filter) { objs[j].accept(function(o) { if (!o.visible) return true; if (onlyPoints && !isEndPoint(o)) { - return false; + return true; } l = o.normalDistance(aim); 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) { - let style = obj.style != null ? obj.style : layer.style; + const style = this.getStyleForObject(layer, obj); if (style !== this.__prevStyle) { - draw_utils.SetStyle(style, ctx, this.scale / this.retinaPxielRatio); + this.setStyle(style, ctx); } this.__prevStyle = style; 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) { this.cleanSnap(); var snapTo = this.search(x, y, 20 / this.scale, true, true, excl); @@ -412,10 +428,18 @@ Viewer.prototype.equalizeLinkedEndpoints = function() { function Layer(name, style) { this.name = name; this.style = style; + this.stylesByRoles = { + 'construction': Styles.CONSTRUCTION_OF_OBJECT + }; this.objects = []; 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() { this.refresh(); this.parametricManager.notify(); diff --git a/web/sketcher.html b/web/sketcher.html index 4f5473dd..e63b3a88 100644 --- a/web/sketcher.html +++ b/web/sketcher.html @@ -30,7 +30,8 @@ -->