diff --git a/web/app/math/math.js b/web/app/math/math.js index 55b16120..3b02eb37 100644 --- a/web/app/math/math.js +++ b/web/app/math/math.js @@ -98,12 +98,12 @@ export function rotateInPlace(px, py, angle, out) { return out; } -export function polygonOffset( polygon, scale ) { +export function polygonOffsetXY(polygon, scaleX, scaleY) { const origBBox = new BBox(); const scaledBBox = new BBox(); const result = []; for (let point of polygon) { - const scaledPoint = new Vector(point.x, point.y)._multiply(scale); + const scaledPoint = new Vector(point.x * scaleX, point.y * scaleY); result.push(scaledPoint); origBBox.checkPoint(point); scaledBBox.checkPoint(scaledPoint); @@ -115,6 +115,21 @@ export function polygonOffset( polygon, scale ) { return result; } + +export function polygonOffset( polygon, scale ) { + return polygonOffsetXY( polygon, scale, scale ); +} + +export function polygonOffsetByDelta( polygon, delta ) { + const origBBox = new BBox(); + for (let point of polygon) { + origBBox.checkPoint(point); + } + const width = origBBox.width(); + const height = origBBox.height(); + return polygonOffsetXY(polygon, (width + delta) / width, (height + delta) / height); +} + export function isPointInsidePolygon( inPt, inPolygon ) { var EPSILON = TOLERANCE; diff --git a/web/app/sketcher/io.js b/web/app/sketcher/io.js index b553612c..c9d93b0a 100644 --- a/web/app/sketcher/io.js +++ b/web/app/sketcher/io.js @@ -10,6 +10,7 @@ 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 {HashTable} from '../utils/hashmap' import Vector from '../math/vector' const Types = { @@ -192,6 +193,10 @@ IO.prototype._loadSketch = function(sketch) { if (boundaryNeedsUpdate) { this.addNewBoundaryObjects(boundary, maxEdge); } + const boundaryLayer = this.viewer.findLayerByName(IO.BOUNDARY_LAYER_NAME); + if (boundaryLayer != null) { + this.linkEndPoints(boundaryLayer.objects); + } var sketchConstraints = sketch['constraints']; if (sketchConstraints !== undefined) { @@ -211,6 +216,24 @@ IO.prototype._loadSketch = function(sketch) { } }; +IO.prototype.linkEndPoints = function(objects) { + const index = HashTable.forVector2d(); + for (let obj of objects) { + obj.accept((o) => { + if (o._class == Types.END_POINT) { + const equalPoint = index.get(o); + if (equalPoint == null) { + index.put(o, o); + } else { + o.linked.push(equalPoint); + equalPoint.linked.push(o); + } + } + return true; + }) + } +}; + IO.prototype.synchLine = function(skobj, edgeObj) { skobj.a.x = edgeObj.a.x; skobj.a.y = edgeObj.a.y; diff --git a/web/app/sketcher/sketcher-app.js b/web/app/sketcher/sketcher-app.js index 5ac65560..43ad102a 100644 --- a/web/app/sketcher/sketcher-app.js +++ b/web/app/sketcher/sketcher-app.js @@ -11,6 +11,7 @@ import {FilletTool} from './tools/fillet' import {EllipseTool} from './tools/ellipse' import {BezierCurveTool} from './tools/bezier-curve' import {RectangleTool} from './tools/rectangle' +import {OffsetTool} from './tools/offset' import {ReferencePointTool} from './tools/origin' import {InputManager} from './input-manager' @@ -153,6 +154,10 @@ function App2D() { app.viewer.toolManager.takeControl(new RectangleTool(app.viewer)); }, 'rect'); + this.registerAction('offsetTool', "Polygon Offset", function () { + app.viewer.toolManager.takeControl(new OffsetTool(app.viewer)); + }); + this.registerAction('pan', "Pan", function () { app.viewer.toolManager.releaseControl(); }); diff --git a/web/app/sketcher/tools/loop-pick.js b/web/app/sketcher/tools/loop-pick.js new file mode 100644 index 00000000..53ba8a26 --- /dev/null +++ b/web/app/sketcher/tools/loop-pick.js @@ -0,0 +1,117 @@ +import {Tool} from './tool' +import {Graph} from '../../math/graph' +import {Styles} from '../styles' + +export class LoopPickTool extends Tool { + + constructor(name, viewer) { + super(name, viewer); + this.loops = new Map(); + this.marked = new Set(); + this.pickedLoop = null; + } + + restart() { + this.sendHint('pick a polygon'); + this.reindexLoops(); + this.marked.clear(); + this.pickedLoop = null; + }; + + cleanup() { + this.clearMarked(); + } + + clearMarked() { + for (let obj of this.marked) { + obj.marked = null; + } + this.marked.clear(); + } + + mark(obj) { + if (!this.marked.has(obj)) { + obj.marked = Styles.SNAP; + this.marked.add(obj); + } + } + + otherEnd(point) { + if (point.parent.a.id == point.id) { + return point.parent.b; + } else { + return point.parent.a; + } + } + + reindexLoops() { + this.loops.clear(); + const points = []; + this.viewer.accept((obj) => { + if (obj._class == 'TCAD.TWO.EndPoint' && obj.parent && obj.parent.a && obj.parent.b) { + points.push(obj); + } + return true; + }); + const graph = { + + connections : (p) => { + const conns = [this.otherEnd(p)]; + p.linked.forEach(l => conns.push(this.otherEnd(l))); + return conns; + }, + + at : function(index) { + return points[index]; + }, + + size : function() { + return points.length; + } + }; + const loops = Graph.findAllLoops(graph, (p) => p.id, (a, b) => a.id == b.id); + for (let loop of loops) { + for (let point of loop) { + this.loops.set(point, loop); + } + } + } + + mousemove(e) { + this.clearMarked(); + this.pickedLoop = null; + const p = this.viewer.screenToModel(e); + this.pickedLoop = this.pickLoop(p); + if (this.pickedLoop != null) { + for (let p of this.pickedLoop) { + this.mark(p.parent); + } + } + this.viewer.refresh(); + }; + + pickLoop(p) { + const pickResult = this.viewer.search(p.x, p.y, 20 / this.viewer.scale, true, false, []); + for (let obj of pickResult) { + for (let point of [obj.a, obj.b]) { + const loop = this.loops.get(point); + if (loop) { + return loop; + } + } + } + return null; + } + + mousedown(e) { + if (this.pickedLoop == null) { + this.viewer.toolManager.releaseControl(); + this.viewer.toolManager.tool.mousedown(e); + } else { + this.onMousedown(e); + } + }; + + onMousedown(e) {}; + +} diff --git a/web/app/sketcher/tools/offset.js b/web/app/sketcher/tools/offset.js new file mode 100644 index 00000000..9d1e5f7c --- /dev/null +++ b/web/app/sketcher/tools/offset.js @@ -0,0 +1,27 @@ +import {LoopPickTool} from './loop-pick' +import * as math from '../../math/math'; + +export class OffsetTool extends LoopPickTool { + + constructor(viewer) { + super('offset', viewer); + } + + onMousedown(e) { + const delta = prompt('offset distance?'); + const offsetPolygon = math.polygonOffsetByDelta(this.pickedLoop, parseInt(delta)); + const length = offsetPolygon.length; + const segments = []; + for (let p = length - 1, q = 0; q < length; p = q++) { + const a = offsetPolygon[p]; + const b = offsetPolygon[q]; + const segment = this.viewer.addSegment(a.x, a.y, b.x, b.y, this.viewer.activeLayer); + segments.push(segment); + } + for (var i = 0; i < segments.length; i++) { + this.viewer.parametricManager.linkObjects([segments[i].b, segments[(i + 1) % segments.length].a]); + } + this.viewer.toolManager.releaseControl(); + this.viewer.refresh(); + } +} diff --git a/web/sketcher.html b/web/sketcher.html index 9e7accab..83b95431 100644 --- a/web/sketcher.html +++ b/web/sketcher.html @@ -32,7 +32,8 @@ -->