diff --git a/web/app/sketcher.js b/web/app/sketcher.js index 458029ff..9328a417 100644 --- a/web/app/sketcher.js +++ b/web/app/sketcher.js @@ -150,11 +150,10 @@ function initializeSketcherApplication() { app.dock.views['Dimensions'].node.append(constantTextArea); - startReact(app.viewer); + startReact(app.context); } -function startReact(viewer) { - const appCtx = createAppContext(viewer) +function startReact(appCtx) { let reactControls = document.getElementById('react-controls'); reactControls.onkeydown = e => { @@ -167,14 +166,4 @@ function startReact(viewer) { ); } -function createAppContext(viewer) { - return { - viewer, - ui: { - $constraintEditRequest: stream() - } - }; -} - - $( () => initializeSketcherApplication() ); diff --git a/web/app/sketcher/actions/constraintActions.js b/web/app/sketcher/actions/constraintActions.js index 1ad2c6f6..72f598b9 100644 --- a/web/app/sketcher/actions/constraintActions.js +++ b/web/app/sketcher/actions/constraintActions.js @@ -12,6 +12,7 @@ export default [ { + id: 'Coincident', shortName: 'Coincident', description: 'Point Coincident', selectionMatcher: (selection, sortedByType) => matchAll(selection, EndPoint, 2), @@ -30,6 +31,7 @@ export default [ }, { + id: 'Tangent', shortName: 'Tangent', description: 'Tangent Between Line And Circle', selectionMatcher: [ @@ -51,7 +53,8 @@ export default [ }, { - shortName: 'EqualRadius', + id: 'EqualRadius', + shortName: 'Equal Radius', description: 'Equal Radius Between Two Circle', selectionMatcher: selection => { for (let obj of selection) { @@ -76,7 +79,8 @@ export default [ }, { - shortName: 'EqualLength', + id: 'EqualLength', + shortName: 'Equal Length', description: 'Equal Length Between Two Segments', selectionMatcher: selection => matchAll(selection, Segment, 2), @@ -92,6 +96,7 @@ export default [ }, { + id: 'PointOnLine', shortName: 'Point On Line', description: 'Point On Line', selectionMatcher: (selection, sortedByType) => matchTypes(sortedByType, EndPoint, 1, Segment, 1), @@ -105,6 +110,7 @@ export default [ }, { + id: 'Angle', shortName: 'Angle', description: 'Angle', selectionMatcher: (selection, sortedByType) => matchAll(sortedByType, Segment, 1), @@ -129,6 +135,26 @@ export default [ }, { + id: 'Vertical', + shortName: 'Vertical', + description: 'Vertical', + selectionMatcher: (selection, sortedByType) => matchAll(sortedByType, Segment, 1), + + invoke: ctx => { + const {viewer} = ctx; + const pm = viewer.parametricManager; + + viewer.selected.forEach(obj => { + const constr = new AlgNumConstraint(ConstraintDefinitions.Vertical, [obj]); + constr.initConstants(); + pm._add(constr); + }); + pm.commit(); + } + }, + + { + id: 'AngleBetween', shortName: 'Angle Between', description: 'Angle Between Lines', selectionMatcher: (selection, sortedByType) => matchAll(sortedByType, Segment, 2), @@ -154,6 +180,7 @@ export default [ }, { + id: 'Perpendicular', shortName: 'Perpendicular', description: 'Perpendicularity between two lines', selectionMatcher: (selection, sortedByType) => matchAll(sortedByType, Segment, 2), @@ -180,6 +207,7 @@ export default [ }, { + id: 'Length', shortName: 'Length', description: 'Segment Length', selectionMatcher: (selection) => matchAll(selection, Segment, 1), @@ -204,6 +232,7 @@ export default [ }, { + id: 'Lock', shortName: 'Lock', description: 'Lock Point', selectionMatcher: (selection) => matchTypes(selection, EndPoint, 1), @@ -220,6 +249,7 @@ export default [ }, { + id: 'Fillet', shortName: 'Fillet', description: 'Add a Fillet', selectionMatcher: (selection) => { @@ -255,34 +285,6 @@ export default [ } }, - { - shortName: 'Mirror', - description: 'Mirror Objects', - selectionMatcher: selection => isInstanceOf(selection[0], Segment) && selection.length > 1, - - - invoke: ctx => { - const {viewer} = ctx; - - const objects = viewer.selected; - const managedObjects = []; - for (let i = 1; i < objects.length; i++) { - let obj = objects[i]; - const copy = obj.copy(); - obj.layer.add(copy); - managedObjects.push(copy); - } - - ConstraintDefinitions.Mirror.modify(objects, managedObjects); - - - // const constr = new AlgNumConstraint(ConstraintDefinitions.Mirror, [...objects, ...managedObjects]); - - // viewer.parametricManager.addModifier(constr); - - } - } - ]; function editConstraint(ctx, constraint, onApply) { diff --git a/web/app/sketcher/actions/generatorActions.js b/web/app/sketcher/actions/generatorActions.js index a61b7185..6e233301 100644 --- a/web/app/sketcher/actions/generatorActions.js +++ b/web/app/sketcher/actions/generatorActions.js @@ -5,6 +5,7 @@ export default [ { + id: 'Mirror', shortName: 'Mirror', description: 'Mirror Objects', selectionMatcher: selection => isInstanceOf(selection[0]) && selection.length > 1, diff --git a/web/app/sketcher/actions/index.js b/web/app/sketcher/actions/index.js index 2766c9d8..d583d00d 100644 --- a/web/app/sketcher/actions/index.js +++ b/web/app/sketcher/actions/index.js @@ -32,4 +32,14 @@ export function matchAvailableActions(selection) { return matched; +} + + +//For backward compatibility +export function getActionIfAvailable(actionId, selection, cb) { + matchAvailableActions(selection).forEach(a => { + if (a.id === actionId) { + cb(a); + } + }) } \ No newline at end of file diff --git a/web/app/sketcher/constr/ANConstraints.js b/web/app/sketcher/constr/ANConstraints.js index d56b2674..b59f5342 100644 --- a/web/app/sketcher/constr/ANConstraints.js +++ b/web/app/sketcher/constr/ANConstraints.js @@ -43,23 +43,21 @@ export const ConstraintDefinitions = { type: 'boolean', description: 'whether the circle attached from the opposite side', initialValue: ([line, circle]) => { - const ang = line.params.ang.get(); - const w = line.params.w.get(); - return Math.cos(ang) * circle.c.x + Math.sin(ang) * circle.c.y < w; + return line.nx * circle.c.x + line.ny * circle.c.y < line.w; } } }, defineParamsScope: ([segment, circle], callback) => { callback(segment.params.ang); - callback(segment.params.w); + segment.a.visitParams(callback); circle.c.visitParams(callback); callback(circle.r); }, - collectPolynomials: (polynomials, [ang, w, cx, cy, r], {inverted}) => { - polynomials.push(tangentLCPolynomial(ang, w, cx, cy, r, inverted)); + collectPolynomials: (polynomials, [ang, ax, ay, cx, cy, r], {inverted}) => { + polynomials.push(tangentLCPolynomial(ang, ax, ay, cx, cy, r, inverted)); }, }, @@ -69,15 +67,11 @@ export const ConstraintDefinitions = { defineParamsScope: ([pt, segment], callback) => { pt.visitParams(callback); + segment.a.visitParams(callback); callback(segment.params.ang); - callback(segment.params.w); }, - collectResiduals: (residuals, params) => { - residuals.push([R_PointOnLine, params, []]); - }, - - collectPolynomials: (polynomials, [x, y, ang, w]) => { + collectPolynomials: (polynomials, [x, y, ax, ay, ang]) => { polynomials.push(new Polynomial(0) .monomial(1) .term(x, POW_1_FN) @@ -85,8 +79,12 @@ export const ConstraintDefinitions = { .monomial(1) .term(y, POW_1_FN) .term(ang, SIN_FN) + .monomial(1) + .term(ax, POW_1_FN) + .term(ang, SIN_FN) .monomial(-1) - .term(w, POW_1_FN) + .term(ay, POW_1_FN) + .term(ang, COS_FN) ); }, @@ -141,7 +139,7 @@ export const ConstraintDefinitions = { type: 'number', description: 'line angle', initialValue: ([seg]) => seg.getAngleFromNormal(), - transform: degree => ( (degree + 90) % 360 ) * DEG_RAD + transform: degree => ( (degree) % 360 ) * DEG_RAD } }, @@ -158,6 +156,31 @@ export const ConstraintDefinitions = { } }, + Vertical: { + id: 'Vertical', + name: 'Line Verticality', + constants: { + angle: { + readOnly: true, + type: 'number', + description: 'line angle', + initialValue: ([seg]) => { + const angleFromNormal = seg.getAngleFromNormal(); + return Math.abs(270 - angleFromNormal) > Math.abs(90 - angleFromNormal) ? 90 : 270; + }, + transform: degree => ( (degree ) % 360 ) * DEG_RAD + } + }, + + defineParamsScope: (objs, cb) => { + ConstraintDefinitions.Angle.defineParamsScope(objs, cb); + }, + + collectPolynomials: (polynomials, params, constants) => { + ConstraintDefinitions.Angle.collectPolynomials(polynomials, params, constants); + } + }, + AngleBetween: { id: 'AngleBetween', name: 'Angle Between Two Lines', @@ -255,16 +278,8 @@ export const ConstraintDefinitions = { }, collectPolynomials: (polynomials, [ang, t, x1, y1, x2, y2]) => { - - - // v = [sin(ang), - cos(ang)] - // v * t = pt2 - pt1 - - //sin(ang) * t - x2 + x1 - //-cos(ang) * t - y2 + y1 - - polynomials.push(new Polynomial().monomial() .term(ang, SIN_FN).term(t, POW_1_FN).monomial(-1).term(x2, POW_1_FN).monomial(1).term(x1, POW_1_FN)); - polynomials.push(new Polynomial().monomial(-1).term(ang, COS_FN).term(t, POW_1_FN).monomial(-1).term(y2, POW_1_FN).monomial(1).term(y1, POW_1_FN)); + polynomials.push(new Polynomial().monomial(1).term(x1, POW_1_FN).monomial(1).term(ang, COS_FN).term(t, POW_1_FN).monomial(-1).term(x2, POW_1_FN)); + polynomials.push(new Polynomial().monomial(1).term(y1, POW_1_FN).monomial(1).term(ang, SIN_FN).term(t, POW_1_FN).monomial(-1).term(y2, POW_1_FN)); }, }, @@ -369,16 +384,16 @@ export const ConstraintDefinitions = { defineParamsScope: ([l1, l2, arc], callback) => { callback(l1.params.ang); - callback(l1.params.w); + l1.a.visitParams(callback); callback(l2.params.ang); - callback(l2.params.w); + l2.a.visitParams(callback); arc.c.visitParams(callback); callback(arc.r); }, - collectPolynomials: (polynomials, [ang1, w1, ang2, w2, cx, cy, r], {inverted1, inverted2}) => { - polynomials.push(tangentLCPolynomial(ang1, w1, cx, cy, r, inverted1)); - polynomials.push(tangentLCPolynomial(ang2, w2, cx, cy, r, inverted2)); + collectPolynomials: (polynomials, [ang1, ax1, ay1, ang2, ax2, ay2, cx, cy, r], {inverted1, inverted2}) => { + polynomials.push(tangentLCPolynomial(ang1, ax1, ay1, cx, cy, r, inverted1)); + polynomials.push(tangentLCPolynomial(ang2, ax2, ay2, cx, cy, r, inverted2)); }, }, @@ -415,7 +430,7 @@ export const ConstraintDefinitions = { }; -function tangentLCPolynomial(ang, w, cx, cy, r, inverted) { +function tangentLCPolynomial(ang, ax, ay, cx, cy, r, inverted) { return new Polynomial(0) .monomial(1) .term(cx, POW_1_FN) @@ -423,8 +438,12 @@ function tangentLCPolynomial(ang, w, cx, cy, r, inverted) { .monomial(1) .term(cy, POW_1_FN) .term(ang, SIN_FN) + .monomial(1) + .term(ax, POW_1_FN) + .term(ang, SIN_FN) .monomial(-1) - .term(w, POW_1_FN) + .term(ay, POW_1_FN) + .term(ang, COS_FN) .monomial(- (inverted ? -1 : 1)) .term(r, POW_1_FN); } diff --git a/web/app/sketcher/constr/AlgNumSystem.js b/web/app/sketcher/constr/AlgNumSystem.js index c5648c59..23a087bd 100644 --- a/web/app/sketcher/constr/AlgNumSystem.js +++ b/web/app/sketcher/constr/AlgNumSystem.js @@ -24,6 +24,8 @@ export class AlgNumSubSystem { conflicting = new Set(); redundant = new Set(); + interactiveParams = new Set(); + snapshot = new Map(); constructor() { @@ -126,6 +128,7 @@ export class AlgNumSubSystem { this.eliminatedParams.clear(); this.polyToConstr.clear(); this.paramToIsolation.clear(); + this.interactiveParams.clear(); } evaluatePolynomials() { @@ -182,7 +185,14 @@ export class AlgNumSubSystem { this.polynomials[i] = null; } else if (polynomial.monomials.length === 2 && polynomial.isLinear) { - const [m1, m2] = polynomial.monomials; + let [m1, m2] = polynomial.monomials; + + if (this.interactiveParams.has(m1.linearParam)) { + const t = m1; + m1 = m2; + m2 = t; + } + let p1 = m1.linearParam; let p2 = m2.linearParam; @@ -240,9 +250,10 @@ export class AlgNumSubSystem { } - prepare() { + prepare(interactiveObjects = []) { this.reset(); + interactiveObjects.forEach(obj => obj.visitParams(p => this.interactiveParams.add(p))); this.validConstraints(c => c.params.forEach(p => p.normalizer && p.set(p.normalizer(p.get())))); diff --git a/web/app/sketcher/parametric.js b/web/app/sketcher/parametric.js index 35530f8f..71c1df29 100644 --- a/web/app/sketcher/parametric.js +++ b/web/app/sketcher/parametric.js @@ -153,8 +153,8 @@ class ParametricManager { this.refresh(); }; - prepare() { - this.algNumSystem.prepare(); + prepare(interactiveObjects) { + this.algNumSystem.prepare(interactiveObjects); } solve(rough) { diff --git a/web/app/sketcher/shapes/segment.js b/web/app/sketcher/shapes/segment.js index 0c19c4fa..3c3fdc99 100644 --- a/web/app/sketcher/shapes/segment.js +++ b/web/app/sketcher/shapes/segment.js @@ -18,7 +18,6 @@ export class Segment extends SketchObject { this.children.push(a, b); this.params = { ang: new Param(undefined), - w: new Param(undefined), t: new Param(undefined) }; this.params.ang.normalizer = makeAngle0_360; @@ -31,16 +30,24 @@ export class Segment extends SketchObject { } get w() { - return this.params.w.get(); + return this.nx*this.a.x + this.ny*this.a.y; } get t() { return this.params.t.get(); } + get nx() { + return -Math.sin(this.ang); + } + + get ny() { + return Math.cos(this.ang); + } + getAngleFromNormal() { const degrees = this.params.ang.get() / DEG_RAD; - return (degrees + 360 - 90) % 360; + return (degrees + 360) % 360; } syncGeometry() { @@ -48,24 +55,20 @@ export class Segment extends SketchObject { const dy = this.b.y - this.a.y; const l = Math.sqrt(dx*dx + dy*dy); - let nx = (- dy / l) || 0; - let ny = (dx / l) || 0; + let ux = (dx / l) || 0; + let uy = (dy / l) || 0; - let ang = Math.atan2(ny, nx); + let ang = Math.atan2(uy, ux); this.params.ang.set(makeAngle0_360(ang||0)); - this.params.w.set(nx * this.a.x + ny * this.a.y); this.params.t.set(l); } stabilize(viewer) { this.syncGeometry(); - const c1 = new AlgNumConstraint(ConstraintDefinitions.PointOnLine, [this.a, this]); - const c2 = new AlgNumConstraint(ConstraintDefinitions.Polar, [this, this.a, this.b]); - c1.internal = true; - c2.internal = true; - viewer.parametricManager._add(c1); - viewer.parametricManager._add(c2); + const c = new AlgNumConstraint(ConstraintDefinitions.Polar, [this, this.a, this.b]); + c.internal = true; + viewer.parametricManager._add(c); } recoverIfNecessary() { @@ -83,7 +86,7 @@ export class Segment extends SketchObject { this.a.visitParams(callback); this.b.visitParams(callback); callback(this.params.ang); - callback(this.params.w); + callback(this.params.t); } normalDistance(aim) { @@ -116,15 +119,14 @@ export class Segment extends SketchObject { translateImpl(dx, dy) { this.a.translate(dx, dy); this.b.translate(dx, dy); - this.params.w.set(Math.cos(this.ang) * this.a.x + Math.sin(this.ang) * this.a.y); } drawImpl(ctx, scale) { let ang = this.params.ang.get(); - let nx = Math.cos(ang) ; - let ny = Math.sin(ang) ; - let w = this.params.w.get(); + let nx = -Math.sin(ang); + let ny = Math.cos(ang); + let w = this.w; ctx.save(); draw_utils.SetStyle(Styles.CONSTRUCTION_OF_OBJECT, ctx, scale ); diff --git a/web/app/sketcher/sketcher-app.js b/web/app/sketcher/sketcher-app.js index 04cedef9..9d155af7 100644 --- a/web/app/sketcher/sketcher-app.js +++ b/web/app/sketcher/sketcher-app.js @@ -15,15 +15,15 @@ import {OffsetTool} from './tools/offset' import {ReferencePointTool} from './tools/origin' import {InputManager} from './input-manager' import genSerpinski from '../utils/genSerpinski'; -import context from 'context'; -import ReactDOM from "react-dom"; import React from "react"; -import {SketcherApp} from "./components/SketcherApp"; +import {getActionIfAvailable} from "./actions"; +import {stream} from "../../../modules/lstream"; function App2D() { var app = this; this.viewer = new Viewer(document.getElementById('viewer'), IO); + this.context = createAppContext(this.viewer); this.winManager = new ui.WinManager(); this.inputManager = new InputManager(this); @@ -198,11 +198,11 @@ function App2D() { }); this.registerAction('coincident', "Coincident", function () { - app.viewer.parametricManager.coincident(app.viewer.selected); + getActionIfAvailable('Coincident', app.viewer.selected, action => action.invoke(app.context)); }); this.registerAction('verticalConstraint', "Vertical Constraint", function () { - app.viewer.parametricManager.vertical(app.viewer.selected); + getActionIfAvailable('Vertical', app.viewer.selected, action => action.invoke(app.context)); }); this.registerAction('horizontalConstraint', "Horizontal Constraint", function () { @@ -473,6 +473,16 @@ App2D.prototype.handleTerminalInput = function(commandStr) { } }; +function createAppContext(viewer) { + return { + viewer, + ui: { + $constraintEditRequest: stream() + } + }; +} + App2D.STORAGE_PREFIX = "TCAD.projects."; -export default App2D; \ No newline at end of file +export default App2D; + diff --git a/web/app/sketcher/tools/drag.js b/web/app/sketcher/tools/drag.js index f57b84eb..e9862bc0 100644 --- a/web/app/sketcher/tools/drag.js +++ b/web/app/sketcher/tools/drag.js @@ -26,7 +26,7 @@ export class DragTool extends Tool { this.obj.translate(dx, dy); // this.viewer.parametricManager.setConstantsFromGeometry(this.obj); if (!Tool.dumbMode(e)) { - this.viewer.parametricManager.prepare(); + // this.viewer.parametricManager.prepare(); this.viewer.parametricManager.solve(true); } this.viewer.refresh(); @@ -37,8 +37,7 @@ export class DragTool extends Tool { this.origin.y = e.offsetY; this.viewer.screenToModel2(e.offsetX, e.offsetY, this._point); - this.viewer.parametricManager.prepare(); - + this.viewer.parametricManager.prepare([this.obj]); } mouseup(e) { diff --git a/web/app/sketcher/viewer2d.js b/web/app/sketcher/viewer2d.js index 52c35c82..32a4f047 100644 --- a/web/app/sketcher/viewer2d.js +++ b/web/app/sketcher/viewer2d.js @@ -292,12 +292,12 @@ class Viewer { }; showBounds(x1, y1, x2, y2, offset) { - var dx = Math.max(x2 - x1, 1); - var dy = Math.max(y2 - y1, 1); - if (this.canvas.width > this.canvas.height) { - this.scale = this.canvas.height / dy; + const dx = Math.max(x2 - x1, 1); + const dy = Math.max(y2 - y1, 1); + if (dx > dy) { + this.scale = this.canvas.height / dx; } else { - this.scale = this.canvas.width / dx; + this.scale = this.canvas.width / dy; } this.translate.x = -x1 * this.scale; this.translate.y = -y1 * this.scale;