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 @@
-->