sketcher sub systems split

This commit is contained in:
Val Erastov 2019-06-01 00:33:14 -07:00
parent f325a875c1
commit 4713cc1fc8
38 changed files with 2063 additions and 1190 deletions

View file

@ -56,6 +56,26 @@ export function addToListInMap(map, key, value) {
list.push(value);
}
export function addToSetInMap(map, key, value) {
let set = map.get(key);
if (!set) {
set = new Set();
map.set(key, set);
}
set.add(value);
}
export function removeFromSetInMap(map, key, value) {
let set = map.get(key);
if (set) {
set.delete(value);
if (set.size === 0) {
map.delete(key);
}
}
}
export function removeInPlace(arr, val) {
let index = arr.indexOf(val);
if (index !== -1) {

8
package-lock.json generated
View file

@ -1153,6 +1153,14 @@
}
}
},
"@dagrejs/graphlib": {
"version": "2.1.4",
"resolved": "https://registry.npmjs.org/@dagrejs/graphlib/-/graphlib-2.1.4.tgz",
"integrity": "sha512-QCg9sL4uhjn468FDEsb/S9hS2xUZSrv/+dApb1Ze5VKO96pTXKNJZ6MGhIpgWkc1TVhbVGH9/7rq/Mf8/jWicw==",
"requires": {
"lodash": "^4.11.1"
}
},
"@webassemblyjs/ast": {
"version": "1.7.11",
"resolved": "https://registry.npmjs.org/@webassemblyjs/ast/-/ast-1.7.11.tgz",

View file

@ -50,6 +50,7 @@
"webpack-dev-server": "^3.1.14"
},
"dependencies": {
"@dagrejs/graphlib": "^2.1.4",
"classnames": "2.2.5",
"clipper-lib": "6.2.1",
"diff-match-patch": "1.0.0",

View file

@ -51,8 +51,8 @@ function initializeSketcherApplication() {
var constrList = new ui.List('constrs', {
items : function() {
var theItems = [];
for (var j = 0; j < pm.subSystems.length; j++) {
var sub = pm.subSystems[j];
for (var j = 0; j < pm.system.subSystems.length; j++) {
var sub = pm.system.subSystems[j];
for (var i = 0; i < sub.constraints.length; ++i) {
var constr = sub.constraints[i];
if (constr.aux !== true && app.constraintFilter[constr.NAME] != true) {

View file

@ -2,7 +2,7 @@ import * as utils from '../../utils/utils'
import * as math from '../../math/math'
import QR from '../../math/qr'
import LMOptimizer from '../../math/lm'
import {ConstantWrapper, EqualsTo} from './constraints'
import {ConstantWrapper, EqualsTo} from './solverConstraints'
import {dog_leg} from '../../math/optim'
import {newVector} from '../../math/vec';
@ -17,8 +17,8 @@ Param.prototype.reset = function(value) {
this.aux = false;
};
Param.prototype.set = function(value) {
if (this.aux) return;
Param.prototype.set = function(value, force) {
if (this.aux && !force) return;
this.value = value;
};
@ -139,7 +139,9 @@ System.prototype.calcGrad = function(out) {
var cParams = c.params;
var grad = [];
utils.fillArray(grad, 0, cParams.length, 0);
for (var p = 0; p < cParams.length; p++) {
c.gradient(grad);
for (var p = 0; p < cParams.length; p++) {
var param = cParams[p];
var j = param.j;
out[j] += this.constraints[i].error() * grad[p]; // (10.4)

File diff suppressed because it is too large Load diff

View file

@ -15,6 +15,7 @@ import Vector from 'math/vector';
import exportTextData from 'gems/exportTextData';
import NurbsCurve from '../brep/geom/curves/nurbsCurve';
import {NurbsObject} from './shapes/nurbsObject';
import {System} from './system';
const Types = {
END_POINT : 'TCAD.TWO.EndPoint',
@ -319,8 +320,8 @@ IO.prototype.cleanUpData = function() {
}
this.viewer.deselectAll();
Generator.resetIDGenerator(0);
if (this.viewer.parametricManager.subSystems.length != 0) {
this.viewer.parametricManager.subSystems = [];
if (this.viewer.parametricManager.system.subSystems.length !== 0) {
this.viewer.parametricManager.system = new System();
this.viewer.parametricManager.notify();
}
};
@ -386,7 +387,7 @@ IO.prototype._serializeSketch = function() {
}
var constrs = sketch['constraints'] = [];
var subSystems = this.viewer.parametricManager.subSystems;
var subSystems = this.viewer.parametricManager.system.subSystems;
for (var j = 0; j < subSystems.length; j++) {
var sub = subSystems[j];
for (i = 0; i < sub.constraints.length; ++i) {

View file

@ -0,0 +1,16 @@
function mirrorManager() {
let mirrors = [];
function add() {
}
}
function mirrorTool() {
}

File diff suppressed because it is too large Load diff

View file

@ -3,7 +3,7 @@ import * as math from '../../math/math';
import Vector from 'math/vector';
import {Ref} from './ref'
import {Constraints} from '../parametric'
import {SketchObject} from './sketch-object'
import {pointIterator, SketchObject} from './sketch-object';
export class Arc extends SketchObject {
@ -20,14 +20,14 @@ export class Arc extends SketchObject {
this.r.value = this.distanceA();
this.r.obj = this;
}
collectParams(params) {
this.a.collectParams(params);
this.b.collectParams(params);
this.c.collectParams(params);
params.push(this.r);
visitParams(callback) {
this.a.visitParams(callback);
this.b.visitParams(callback);
this.c.visitParams(callback);
callback(this.r);
}
getReferencePoint() {
return this.c;
}
@ -131,6 +131,12 @@ export class Arc extends SketchObject {
copy() {
return new Arc(this.a.copy(), this.b.copy(), this.c.copy());
}
mirror(dest, mirroringFunc) {
this.a.mirror(dest.b, mirroringFunc);
this.b.mirror(dest.a, mirroringFunc);
this.c.mirror(dest.c, mirroringFunc);
}
}
Arc.prototype._class = 'TCAD.TWO.Arc';

View file

@ -24,11 +24,11 @@ export class BezierCurve extends SketchObject {
}
}
collectParams(params) {
this.a.collectParams(params);
this.b.collectParams(params);
this.cp1.collectParams(params);
this.cp2.collectParams(params);
visitParams(callback) {
this.a.visitParams(callback);
this.b.visitParams(callback);
this.cp1.visitParams(callback);
this.cp2.visitParams(callback);
}
normalDistance(aim, scale) {

View file

@ -16,10 +16,10 @@ export class Circle extends SketchObject {
this.r = new Ref(0);
this.r.obj = this;
}
collectParams(params) {
this.c.collectParams(params);
params.push(this.r);
visitParams(callback) {
this.c.visitParams(callback);
callback(this.r);
}
getReferencePoint() {

View file

@ -11,8 +11,8 @@ class LinearDimension extends SketchObject {
this.b = b;
this.flip = false;
}
collectParams(params) {
visitParams(callback) {
}
getReferencePoint() {
@ -164,8 +164,8 @@ export class DiameterDimension extends SketchObject {
this.obj = obj;
this.angle = Math.PI / 4;
}
collectParams(params) {
visitParams(callback) {
}
getReferencePoint() {

View file

@ -30,11 +30,11 @@ export class Ellipse extends SketchObject {
}
return recovered;
}
collectParams(params) {
this.ep1.collectParams(params);
this.ep2.collectParams(params);
params.push(this.r);
visitParams(callback) {
this.ep1.visitParams(callback);
this.ep2.visitParams(callback);
callback(this.r);
}

View file

@ -19,9 +19,9 @@ export class NurbsObject extends SketchObject {
this.bezierPieces = this.calcBezierPiecewise();
}
collectParams(params) {
this.a.collectParams(params);
this.b.collectParams(params);
visitParams(callback) {
this.a.visitParams(callback);
this.b.visitParams(callback);
}
normalDistance(aim) {

View file

@ -13,25 +13,25 @@ export class EndPoint extends SketchObject {
this._x = new Param(this, 'x');
this._y = new Param(this, 'y');
}
collectParams(params) {
params.push(this._x);
params.push(this._y);
visitParams(callback) {
callback(this._x);
callback(this._y);
}
normalDistance(aim) {
return aim.minus(new Vector(this.x, this.y)).length();
}
getReferencePoint() {
return this;
}
translateImpl(dx, dy) {
this.x += dx;
this.y += dy;
}
drawImpl(ctx, scale) {
DrawPoint(ctx, this.x, this.y, 3, scale)
}
@ -48,14 +48,20 @@ export class EndPoint extends SketchObject {
setFromArray(arr) {
this.setXY(arr[0], arr[1]);
}
toVector() {
return new Vector(this.x, this.y);
}
copy() {
return new EndPoint(this.x, this.y);
}
mirror(dest, mirroringFunc) {
let {x, y} = mirroringFunc(this.x, this.y);
dest.x = x;
dest.y = y;
}
}
EndPoint.prototype._class = 'TCAD.TWO.EndPoint';

View file

@ -24,10 +24,10 @@ export class Segment extends SketchObject {
return true;
}
}
collectParams(params) {
this.a.collectParams(params);
this.b.collectParams(params);
visitParams(callback) {
this.a.visitParams(callback);
this.b.visitParams(callback);
}
normalDistance(aim) {
@ -72,6 +72,16 @@ export class Segment extends SketchObject {
// ctx.restore();
}
opposite(endPoint) {
if (endPoint === this.a) {
return this.b;
} else if (endPoint === this.b) {
return this.a;
} else {
return null;
}
}
copy() {
return new Segment(this.a.copy(), this.b.copy());
}

View file

@ -1,5 +1,6 @@
import {Generator} from '../id-generator'
import {Shape} from './shape'
import {Types} from '../io';
export class SketchObject extends Shape {
constructor() {
@ -89,5 +90,47 @@ export class SketchObject extends Shape {
copy() {
throw 'method not implemented';
}
mirror(dest, mirroringFunc) {
let sourcePoints = [];
pointIterator(this, o => {
sourcePoints.push(o);
});
let i = 0;
pointIterator(dest, o => {
sourcePoints[i++].mirror(o, mirroringFunc);
});
}
visitParams(callback) {
throw 'method not implemented';
}
collectParams(params) {
this.visitParams(p => params.push(p));
}
get effectiveLayer() {
let shape = this;
while (shape) {
if (shape.layer) {
return shape.layer;
}
shape = shape.parent;
}
return null;
}
}
export function pointIterator(shape, func) {
shape.accept(o => {
if (o._class === Types.END_POINT) {
func(o);
}
return true;
});
}

View file

@ -76,6 +76,10 @@ function App2D() {
}
checkForTerminalVisibility();
this.registerAction('new', "Create New Sketch", function () {
app.newSketch();
});
this.registerAction('terminal', "Open/Close Terminal Window", function () {
app.commandsWin.toggle();
checkForTerminalVisibility();
@ -214,6 +218,10 @@ function App2D() {
app.viewer.parametricManager.p2lDistance(app.viewer.selected, prompt);
});
this.registerAction('mirrorConstraint', "Mirror Constraint", function () {
app.viewer.parametricManager.mirror(app.viewer.selected);
});
this.registerAction('P2PDistanceConstraint', "Distance Between two Points", function () {
app.viewer.parametricManager.p2pDistance(app.viewer.selected, prompt);
});
@ -294,6 +302,10 @@ App2D.views = [
{
name: 'Constraints',
icon: 'cogs'
},
{
name: 'Mirroring',
icon: 'mirror'
}
];

View file

@ -4,7 +4,11 @@ export const Styles = {
strokeStyle : "#ffffff",
fillStyle : "#000000"
},
VIRTUAL: {
lineWidth : 2,
strokeStyle : "#ffffff88",
fillStyle : "#00000088"
},
SERVICE : {
lineWidth : 0.3,
strokeStyle : "#ff0000",

290
web/app/sketcher/system.js Normal file
View file

@ -0,0 +1,290 @@
import {addToSetInMap, removeFromSetInMap, removeInPlace} from '../../../modules/gems/iterables';
import {ParametricManager} from './parametric';
let SUB_SYSTEM_ORDER = 0;
class SubSystem {
constructor() {
this.alg = 1;
this.error = 0;
this.reduce = false;
this.constraints = [];
this.dependencies = [];
this.nativeParams = new Set();
this._internaOrder = SUB_SYSTEM_ORDER++;
}
mergeWith(other) {
other.constraints.forEach(c => this.constraints.push(c));
other.dependencies.forEach(d => {
if (this.dependencies.indexOf(d) === -1) {
this.dependencies.push(d);
}
});
other.nativeParams.forEach(p => this.nativeParams.add(p));
}
}
class Index {
constructor() {
this.constraints = [];
this.paramToConstraintsIndex = new Map();
this.paramToConstraintsGraph = new Map();
this.generatorConstraints = [];
this.generatedParams = new Map();
}
_reset() {
this.constraints = [];
this.paramToConstraintsIndex.clear();
this.paramToConstraintsGraph.clear();
this.generatorConstraints = [];
this.generatedParams.clear();
}
_pushConstraint(constr) {
this.constraints.push(constr);
visitParams(constr, true, p => addToSetInMap(this.paramToConstraintsGraph, p, constr));
visitParams(constr, false, p => addToSetInMap(this.paramToConstraintsIndex, p, constr));
if (constr.GENERATOR) {
this.generatorConstraints.push(constr);
constr.visitGeneratedParams(p => this.generatedParams.set(p, constr));
}
}
_popConstraint(constr) {
removeInPlace(this.constraints, constr);
visitParams(constr, true, p => removeFromSetInMap(this.paramToConstraintsGraph, p, constr));
visitParams(constr, false, p => removeFromSetInMap(this.paramToConstraintsIndex, p, constr));
if (constr.GENERATOR) {
removeInPlace(this.generatorConstraints, constr);
constr.visitGeneratedParams(p => this.generatedParams.delete(p));
}
}
}
export class System extends Index{
constructor() {
super();
this.subSystems = [];
this.constraintToSubSystem = new Map();
this.paramToSubSystem = new Map();
}
_reset() {
super._reset();
this.subSystems = [];
this.constraintToSubSystem.clear();
this.paramToSubSystem.clear();
}
_collectDependenciesForSubSystemFromConstraint(subSystem, constr) {
visitParams(constr, false, p => {
let generator = this.generatedParams.get(p);
if (generator) {
let generatorSS = this.constraintToSubSystem.get(generator);
if (generatorSS) {
if (subSystem.dependencies.indexOf(generatorSS) === -1) {
subSystem.dependencies.push(generatorSS);
}
}
}
});
}
_rebuildDependencies() {
this.subSystems.forEach(ss => {
if (ss.dependencies.length !== 0) {
ss.dependencies = [];
}
});
this.subSystems.forEach(subSystem => {
subSystem.constraints.forEach(constr => {
this._collectDependenciesForSubSystemFromConstraint(subSystem, constr);
});
});
}
_groupBySubsystems() {
if (this.subSystems.length !== 0) {
this.subSystems = [];
}
this.constraintToSubSystem.clear();
const visited = new Set();
this.constraints.forEach(constr => {
if (visited.has(constr)) {
return;
}
const subSystem = this.createSubSystem();
const stack = [constr];
while (stack.length) {
let workingConstr = stack.pop();
if (visited.has(workingConstr)) {
continue;
}
this._assignConstraint(workingConstr, subSystem);
visited.add(workingConstr);
visitParams(workingConstr, true, p => {
const constrs = this.paramToConstraintsGraph.get(p);
if (constrs) {
constrs.forEach(constrToAdvance => {
if (constrToAdvance !== workingConstr) {
stack.push(constrToAdvance);
}
})
}
});
}
});
}
_rebuild() {
this._groupBySubsystems();
this._rebuildDependencies();
}
_assignConstraint(constr, subSystem) {
subSystem.constraints.push(constr);
this.constraintToSubSystem.set(constr, subSystem);
}
add(constr) {
constr.id = "C_" + (COUNTER ++) ; //fixme
let affectedSubsystems = new Set();
let freeParams = [];
visitParams(constr, false, p => {
let subSystem = this.paramToSubSystem.get(p);
if (subSystem) {
affectedSubsystems.add(subSystem);
} else {
if (!isAuxParam(p) && !this.generatedParams.has(p)) {
freeParams.push(p);
}
}
});
affectedSubsystems.forEach(ss => {
ss.dependencies.forEach(d => affectedSubsystems.delete(d));
});
let toMerge = Array.from(affectedSubsystems).sort((a, b) => a._internaOrder - b._internaOrder);
let master;
if (toMerge.length === 0 ) {
console.error("system has circular dependencies");
master = this.createSubSystem();
} else {
[master, ...toMerge] = toMerge;
}
toMerge.forEach(s => {
master.mergeWith(s);
s.nativeParams.forEach(p => this.paramToSubSystem.set(p, master));
removeInPlace(this.subSystems, s);
});
freeParams.forEach(p => {
master.nativeParams.add(p);
this.paramToSubSystem.set(p, master)
});
master.constraints.push(constr);
this.constraintToSubSystem.set(constr, master);
if (constr.GENERATOR) {
let dependant = this.createSubSystem();
dependant.dependencies.push(master);
constr.visitGeneratedParams(p => {
this.generatedParams.set(p, constr)
this.paramToSubSystem.set(p, dependant);
});
}
this._pushConstraint(constr);
}
remove(constr) {
removeInPlace(this.constraints, constr);
this.setConstraints(this.constraints);
}
setConstraints(constraints) {
this._reset();
constraints.forEach(c => this.add(c));
}
subSystemsByParam(param, callback) {
let constraints = this.paramToConstraintsIndex.get(param);
if (constraints) {
constraints.forEach(c => callback(this.constraintToSubSystem.get(c)));
}
}
traverse(callback, onCircular) {
const visited = new Set();
const loop = new Set();
function doVisit(subSystem) {
if (loop.has(subSystem)) {
onCircular(subSystem);
return;
}
loop.add(subSystem);
subSystem.dependencies.forEach(dep => {
if (!visited.has(dep)) {
doVisit(dep);
}
});
callback(subSystem);
visited.add(subSystem);
loop.delete(subSystem)
}
this.subSystems.forEach(doVisit);
}
createSubSystem() {
const subSystem = new SubSystem();
this.subSystems.push(subSystem);
return subSystem;
}
}
function visitParams(constraint, skipAux, callback) {
if (skipAux) {
let delegate = callback;
callback = p => {
if (!isAuxParam(p)) {
delegate(p)
}
};
}
if (constraint.visitParams) {
constraint.visitParams(callback);
} else {
constraint.getSolveData().forEach(([, sParams]) => sParams.forEach(callback));
}
}
function isAuxParam(param) {
return ParametricManager.isAux(param.obj, GOT_NOTHING);
}
const GOT_NOTHING = {
has: () => false
};
let COUNTER = 0;

View file

@ -34,7 +34,6 @@ export class EditCircleTool extends Tool {
solveRequest(rough) {
this.solver = this.viewer.parametricManager.prepare([this.circle.r]);
this.solver.solve(rough, 1);
this.solver.sync();
}
mouseup(e) {

View file

@ -55,7 +55,6 @@ export class DragTool extends Tool {
solveRequest(rough) {
this.solver.solve(rough, 1);
this.solver.sync();
var paramsToUpdate = [];
this.viewer.accept(function (obj) {
@ -72,7 +71,6 @@ export class DragTool extends Tool {
this.solver.updateParameter(paramsToUpdate[i]);
}
this.solver.solve(rough, 1);
this.solver.sync();
}
}

View file

@ -113,6 +113,5 @@ export class EllipseTool extends Tool {
solveRequest(rough) {
this.solver = this.viewer.parametricManager.prepare([this.ellipse.r]);
this.solver.solve(rough, 1);
this.solver.sync();
}
}

View file

@ -67,11 +67,20 @@ export class FilletTool extends Tool {
point1.parent.layer.add(arc);
var pm = this.viewer.parametricManager;
arc.stabilize(this.viewer);
pm._add(new Constraints.Tangent( arc, point1.parent));
pm._add(new Constraints.Tangent( arc, point2.parent));
pm._add(new Constraints.Coincident( arc.a, point1));
pm._add(new Constraints.Coincident( arc.b, point2));
pm._add(new Constraints.Fillet( point1, point2, arc));
this.viewer.validators.push(() => {
function validOn(p, left) {
let op = p.parent.opposite(p);
let opV = op.toVector();
let dir = p.toVector()._minus(opV)._normalize();
let centerDir = arc.c.toVector()._minus(opV)._normalize();
let z = centerDir.cross(dir).z;
return left ? z < 0.1 : z > -0.1;
}
return validOn(point1, true) && validOn(point2, false);
});
//function otherEnd(point) {
// if (point.parent.a.id === point.id) {
// return point.parent.b;
@ -114,15 +123,18 @@ export class FilletTool extends Tool {
}
getCandidate(e) {
var picked = this.viewer.pick(e);
let preferSketchLayer = (a, b) => (a.effectiveLayer === b.effectiveLayer)? 0 : a.effectiveLayer.name === 'sketch' ? -1 : 1;
let picked = this.viewer.pick(e);
if (picked.length > 0) {
var res = fetch.sketchObjects(picked, true, ['TCAD.TWO.EndPoint']);
let res = fetch.sketchObjects(picked, true, ['TCAD.TWO.EndPoint']);
if (res == null) return null;
var point1 = res[0];
let point1 = res.sort(preferSketchLayer)[0];
if (!FilletTool.isLine(point1.parent)) return;
var line2 = null;
for (var i = 0; i < point1.linked.length; i++) {
var point2 = point1.linked[i];
let linked = [...point1.linked].sort(preferSketchLayer);
for (let i = 0; i < linked.length; i++) {
let point2 = linked[i];
if (FilletTool.isLine(point2.parent)) {
return [point1, point2];
}

View file

@ -50,9 +50,7 @@ export class ToolManager {
} else if (e.keyCode === 46 || e.keyCode === 8) {
let selection = viewer.selected.slice();
viewer.deselectAll();
for (let i = 0; i < selection.length; i++) {
viewer.remove(selection[i]);
}
viewer.removeAll(selection);
viewer.refresh();
}
}, false);

View file

@ -79,6 +79,7 @@ function Viewer(canvas, IO) {
this.historyManager = new HistoryManager(this);
this.transformation = null;
this.screenToModelMatrix = null;
this.validators = [];
this.refresh();
}
@ -118,11 +119,11 @@ Viewer.prototype.addSegment = function(x1, y1, x2, y2, layer) {
};
Viewer.prototype.remove = function(obj) {
if (obj.layer != null) {
if (obj.layer.remove(obj)) {
this.parametricManager.removeConstraintsByObj(obj);
}
}
this.removeAll([obj]);
};
Viewer.prototype.removeAll = function(objects) {
this.parametricManager.removeObjects(objects);
};
Viewer.prototype.add = function(obj, layer) {
@ -470,7 +471,8 @@ function Layer(name, style) {
this.name = name;
this.style = style;
this.stylesByRoles = {
'construction': Styles.CONSTRUCTION_OF_OBJECT
'construction': Styles.CONSTRUCTION_OF_OBJECT,
'virtual': Styles.VIRTUAL
};
this.objects = [];
this.readOnly = false; // This is actually a mark for boundary layers coming from 3D

View file

@ -45,8 +45,9 @@
<div id="dock" class="panel b-right scroll" style="float: left; width: 245px; height: 100%;"></div>
<div id="right-toolbar" class="panel b-left scroll" style="width: 50px; float: right; height: 100%; ">
<div style="width:50%; height: 2px"></div>
<div style="width:50%; height: 2px"></div>
<button class="btn rbtn act-coincident" style="background-image: url(img/coi.png);"></button>
<button class="btn rbtn act-mirrorConstraint" style="background-image: url(img/coi.png);"></button>
<button class="btn rbtn act-verticalConstraint" style="background-image: url(img/vert.png);"></button>
<button class="btn rbtn act-horizontalConstraint" style="background-image: url(img/hor.png);"></button>
<button class="btn rbtn act-parallelConstraint" style="background-image: url(img/par.png);"></button>

View file

@ -0,0 +1,216 @@
import {assertEquals, assertFalse, assertTrue} from '../utils/asserts';
import {NOOP} from '../../../modules/gems/func';
export const TEST_MODE = 'sketcherUI';
export function testEqualConstraints(env, ui) {
ui.addRectangle(10, 10, 100, 100);
assertEquals(4, ui.viewer.parametricManager.system.subSystems.length);
assertEquals(4, ui.viewer.parametricManager.system.constraints.length);
assertEquals(1, ui.viewer.parametricManager.system.subSystems[0].constraints.length);
env.done();
}
export function testBuildGraphBasics(env, ui) {
const seg1 = ui.addSegment(10, 10, 10, 100);
const seg2 = ui.addSegment(200, 10, 200, 100);
ui.select([seg1, seg2]);
ui.runAction('parallelConstraint');
assertEquals(1, ui.viewer.parametricManager.system.subSystems.length);
assertEquals(1, ui.viewer.parametricManager.system.constraints.length);
assertEquals(1, ui.viewer.parametricManager.system.subSystems[0].constraints.length);
const seg3 = ui.addSegment(500, 10, 500, 100);
const seg4 = ui.addSegment(700, 10, 700, 100);
ui.select([seg3, seg4]);
ui.runAction('parallelConstraint');
assertEquals(2, ui.viewer.parametricManager.system.subSystems.length);
assertEquals(2, ui.viewer.parametricManager.system.constraints.length);
assertEquals(1, ui.viewer.parametricManager.system.subSystems[0].constraints.length);
assertEquals(1, ui.viewer.parametricManager.system.subSystems[1].constraints.length);
env.done();
}
export function testThreeConnectedConstraints(env, ui) {
const seg1 = ui.addSegment(10, 10, 10, 100);
const seg2 = ui.addSegment(200, 10, 200, 100);
ui.select([seg1, seg2]);
ui.runAction('parallelConstraint');
assertEquals(1, ui.viewer.parametricManager.system.subSystems.length);
assertEquals(1, ui.viewer.parametricManager.system.constraints.length);
assertEquals(1, ui.viewer.parametricManager.system.subSystems[0].constraints.length);
const seg3 = ui.addSegment(500, 10, 500, 100);
const seg4 = ui.addSegment(700, 10, 700, 100);
ui.select([seg3, seg4]);
ui.runAction('parallelConstraint');
assertEquals(2, ui.viewer.parametricManager.system.subSystems.length);
assertEquals(2, ui.viewer.parametricManager.system.constraints.length);
assertEquals(1, ui.viewer.parametricManager.system.subSystems[0].constraints.length);
assertEquals(1, ui.viewer.parametricManager.system.subSystems[1].constraints.length);
ui.select([seg2, seg3]);
ui.runAction('parallelConstraint');
assertEquals(1, ui.viewer.parametricManager.system.subSystems.length);
assertEquals(3, ui.viewer.parametricManager.system.constraints.length);
assertEquals(3, ui.viewer.parametricManager.system.subSystems[0].constraints.length);
env.done();
}
export function testIgnoreBoundaries(env, ui) {
const boundary = ui.addSegment(500, 10, 500, 100);
boundary.aux = true;
const seg1 = ui.addSegment(100, 10, 100, 100);
ui.select([seg1, boundary]);
ui.runAction('parallelConstraint');
const seg2 = ui.addSegment(700, 10, 700, 100);
ui.select([seg1, boundary]);
ui.select([seg1, boundary]);
ui.runAction('parallelConstraint');
assertEquals(1, ui.viewer.parametricManager.system.subSystems.length);
assertEquals(2, ui.viewer.parametricManager.system.constraints.length);
assertEquals(2, ui.viewer.parametricManager.system.subSystems[0].constraints.length);
env.done();
}
export function testMirroring(env, ui) {
const seg1 = ui.addSegment(10, 10, 10, 100);
const seg2 = ui.addSegment(200, 10, 200, 100);
ui.select([seg2, seg1]);
ui.runAction('mirrorConstraint');
const seg3 = ui.viewer.parametricManager.system.constraints[0].reflectedObjects[0];
const seg4 = ui.addSegment(600, 10, 600, 100);
ui.select([seg4, seg3]);
ui.runAction('mirrorConstraint');
let system = ui.viewer.parametricManager.system;
let subSystems = ui.viewer.parametricManager.system.subSystems;
assertEquals(3, subSystems.length);
const subSystem0 = system.constraintToSubSystem.get(system.constraints[0]);
const subSystem1 = system.constraintToSubSystem.get(system.constraints[1]);
assertTrue( subSystem1.dependencies[0] === subSystem0, "Second subsystem depends on first one");
env.done();
}
export function testCircularDependencies(env, ui) {
const seg1 = ui.addSegment(10, 10, 10, 100);
const seg2 = ui.addSegment(200, 10, 200, 100);
ui.select([seg2, seg1]);
ui.runAction('mirrorConstraint');
const seg3 = ui.viewer.parametricManager.system.constraints[0].reflectedObjects[0];
const seg4 = ui.addSegment(600, 10, 600, 100);
ui.select([seg4, seg3]);
ui.runAction('mirrorConstraint');
let seg5 = ui.viewer.parametricManager.system.constraints[1].reflectedObjects[0];
ui.select([seg5, seg1]);
ui.runAction('parallelConstraint');
let isCircular = false;
ui.viewer.parametricManager.system.traverse(NOOP, () => isCircular = true);
assertEquals(2, ui.viewer.parametricManager.system.subSystems.length);
assertTrue(isCircular, "System should contain circular depending subsystems");
env.done();
}
export function testSimpleRemove(env, ui) {
const seg1 = ui.addSegment(10, 10, 10, 100);
const seg2 = ui.addSegment(200, 10, 200, 100);
ui.select([seg1, seg2]);
ui.runAction('parallelConstraint');
const seg3 = ui.addSegment(500, 10, 500, 100);
const seg4 = ui.addSegment(700, 10, 700, 100);
ui.select([seg3, seg4]);
ui.runAction('parallelConstraint');
ui.select([seg2, seg3]);
ui.runAction('parallelConstraint');
assertEquals(1, ui.viewer.parametricManager.system.subSystems.length);
assertEquals(3, ui.viewer.parametricManager.system.constraints.length);
assertEquals(3, ui.viewer.parametricManager.system.subSystems[0].constraints.length);
ui.select([]);
ui.viewer.remove(seg1);
assertEquals(1, ui.viewer.parametricManager.system.subSystems.length);
assertEquals(2, ui.viewer.parametricManager.system.constraints.length);
env.done();
}
export function testMirroringRemove(env, ui) {
const seg1 = ui.addSegment(10, 10, 10, 100);
const seg2 = ui.addSegment(200, 10, 200, 100);
ui.select([seg2, seg1]);
ui.runAction('mirrorConstraint');
const seg3 = ui.viewer.parametricManager.system.constraints[0].reflectedObjects[0];
const seg4 = ui.addSegment(600, 10, 600, 100);
ui.select([seg4, seg3]);
ui.runAction('mirrorConstraint');
assertEquals(3, ui.viewer.parametricManager.system.subSystems.length);
ui.viewer.remove(seg1);
assertEquals(0, ui.viewer.parametricManager.system.subSystems.length);
env.done();
}
export function testDoubleAngle(env, ui) {
const seg1 = ui.addSegment(100, 100, 100, 200);
const seg2 = ui.addSegment(100, 200, 200, 200);
const seg3 = ui.addSegment(10, 10, 300, 10);
ui.select([seg3, seg2, seg1]);
ui.runAction('mirrorConstraint');
const [seg4, seg5] = ui.viewer.parametricManager.system.constraints[1].reflectedObjects;
const seg6 = ui.addSegment(300, -200, 300, 300);
ui.select([seg6, seg5, seg4, seg2, seg1]);
ui.runAction('mirrorConstraint');
let isCircular = false;
ui.viewer.parametricManager.system.traverse(NOOP, () => isCircular = true);
assertFalse(isCircular, "shouldn't be circular");
env.done();
}

View file

@ -1,5 +1,6 @@
import * as test from './test';
import modellerUISubject from './utils/subjects/modeller/modellerUISubject';
import {createSketcherSubject} from './utils/subjects/modeller/sketcherUISubject';
export const modellerUI = func => env => {
test.emptyModeller(env.test(win => {
@ -8,3 +9,9 @@ export const modellerUI = func => env => {
}));
};
export const sketcherUI = func => env => {
test.emptySketch(env.test((win, app) => {
let subject = createSketcherSubject(app);
func(env, subject);
}));
};

View file

@ -3,6 +3,7 @@
<title></title>
<script src="../static/test_runner.bundle.js"></script>
<link rel="stylesheet" href="../lib/font-awesome/css/font-awesome.min.css?modeler">
<link rel="icon" href="data:;base64,iVBORw0KGgo=">
</head>
<body>
<div id="control-bar" class="page-row">

View file

@ -14,6 +14,7 @@ body {
width: 70%;
height: 70%;
background-color: #233930;
//visibility: hidden;
}
#test-list {

View file

@ -24,6 +24,7 @@ export default {
SketcherSolver: [
TestCase('constraints'),
TestCase('parametric'),
TestCase('solveSystems'),
],

View file

@ -1,7 +1,8 @@
export function FailError(msg) {
this.msg = msg;
this.stack = (new Error()).stack;
export function createFailError(msg) {
let error = new Error(msg);
error.assertionFail = true;
return error;
}
export class TestEnv {
@ -27,7 +28,7 @@ export class TestEnv {
this.failed = true;
this.error = msg + (optionalMsg === undefined ? '' : ' ' + optionalMsg);
this.done();
throw new FailError(this.error);
throw createFailError(this.error);
}
terminateOnError(error) {
@ -46,8 +47,7 @@ export class TestEnv {
if (!env.finished) {
env.terminateOnError(e);
}
console.error(e.stack);
throw e;
console.error(e);
}
}
}

View file

@ -1,17 +1,23 @@
import {FailError} from '../test';
import {createFailError} from '../test';
import sketchObjectGlobalId from '../../app/cad/sketch/sketchObjectGlobalId';
export function fail(msg, optionalMsg) {
throw new FailError(msg + (optionalMsg === undefined ? '' : ' ' + optionalMsg));
throw createFailError(msg + (optionalMsg === undefined ? '' : ' ' + optionalMsg));
}
export function assertTrue(stmt, msg) {
if (typeof stmt === 'string') {
throw 'wrong assertion usage, mixed up arguments';
}
if (!stmt) {
fail('assertTrue fails.', msg);
}
}
export function assertEmpty(array, msg) {
if (typeof stmt === 'string') {
throw 'wrong assertion usage, mixed up arguments';
}
if (array.length !== 0) {
fail('assertEmpty fails. Array length = ' + array.length, msg);
}

View file

@ -10,11 +10,7 @@ export function toModelP(app, point) {
}
export function getConstraints(app) {
const subSystems = app.viewer.parametricManager.subSystems;
if (subSystems.length == 0) {
return [];
}
return subSystems[0].constraints;
return app.viewer.parametricManager.system.constraints;
}
export function click(app, point, attrs) {
@ -170,10 +166,11 @@ export class TestSegment {
}
}
function modelToScreen(viewer, x, y) {
let modelToScreenMx = viewer.screenToModelMatrix.invert();
[x, y] = modelToScreenMx.apply3([x, y, 0]);
export function modelToScreen(viewer, x, y) {
if (viewer.screenToModelMatrix) {
let modelToScreenMx = viewer.screenToModelMatrix.invert();
[x, y] = modelToScreenMx.apply3([x, y, 0]);
}
x /= viewer.retinaPxielRatio;
y = (viewer.canvas.height - y) / viewer.retinaPxielRatio;
return [x, y];

View file

@ -4,7 +4,6 @@ import genSerpinski, {genSerpinskiImpl} from '../../../../app/utils/genSerpinski
import {distance, distanceAB} from '../../../../app/math/math';
export function createSubjectFromInPlaceSketcher(ctx) {
let actions = {};
for (const actionId of Object.keys(ctx.streams.action.state)) {
if (actionId.startsWith('sketch')) {
@ -15,19 +14,27 @@ export function createSubjectFromInPlaceSketcher(ctx) {
actions.addBezierCurve = actions.addCubicBezierSpline;
}
}
const oldStyleSketcherApp = {
viewer: ctx.services.sketcher.inPlaceEditor.viewer,
actions
};
return createSketcherSubject(oldStyleSketcherApp);
}
const addSegment = sketcher_utils.addSegmentInModel.bind(this, oldStyleSketcherApp);
const addArc = sketcher_utils.addArc.bind(this, oldStyleSketcherApp);
const addCircle = sketcher_utils.addCircle.bind(this, oldStyleSketcherApp);
const addEllipse = sketcher_utils.addEllipse.bind(this, oldStyleSketcherApp);
const addEllipticalArc = sketcher_utils.addEllipticalArc.bind(this, oldStyleSketcherApp);
const addBezier = sketcher_utils.addBezier.bind(this, oldStyleSketcherApp);
const move = sketcher_utils.moveInModel.bind(this, oldStyleSketcherApp);
export function createSketcherSubject(sketcherApp) {
const viewer = sketcherApp.viewer;
viewer.parametricManager.messageSink = msg => console.log(msg);
const addSegment = sketcher_utils.addSegmentInModel.bind(this, sketcherApp);
const addArc = sketcher_utils.addArc.bind(this, sketcherApp);
const addCircle = sketcher_utils.addCircle.bind(this, sketcherApp);
const addEllipse = sketcher_utils.addEllipse.bind(this, sketcherApp);
const addEllipticalArc = sketcher_utils.addEllipticalArc.bind(this, sketcherApp);
const addBezier = sketcher_utils.addBezier.bind(this, sketcherApp);
const move = sketcher_utils.moveInModel.bind(this, sketcherApp);
function addRectangle(x0, y0, x1, y1) {
return [
addSegment(x0, y0, x1, y0),
@ -38,7 +45,7 @@ export function createSubjectFromInPlaceSketcher(ctx) {
}
function addSerpinski(ax, ay, bx, by, depth) {
genSerpinskiImpl(ctx.services.sketcher.inPlaceEditor.viewer, {x: ax, y: ay}, {x: bx, y: by}, depth);
genSerpinskiImpl(viewer, {x: ax, y: ay}, {x: bx, y: by}, depth);
let jointWidth = distance(ax, ay, bx, by) / (depth + 1) / 2;
let dx = bx - ax;
let dy = by - ay;
@ -47,7 +54,7 @@ export function createSubjectFromInPlaceSketcher(ctx) {
dy /= D;
let ddx = -dy * jointWidth;
let ddy = dx * jointWidth;
genSerpinskiImpl(ctx.services.sketcher.inPlaceEditor.viewer, {x: bx-ddx, y: by-ddy}, {x: ax-ddx, y: ay-ddy}, depth);
genSerpinskiImpl(viewer, {x: bx-ddx, y: by-ddy}, {x: ax-ddx, y: ay-ddy}, depth);
addSegment(ax, ay, ax-ddx, ay-ddy);
addSegment(bx, by, bx-ddx, by-ddy);
}
@ -62,7 +69,7 @@ export function createSubjectFromInPlaceSketcher(ctx) {
}
function changeLayer(layerName) {
ctx.services.sketcher.inPlaceEditor.viewer.setActiveLayerName(layerName);
viewer.setActiveLayerName(layerName);
}
function changeToConstructionLayer() {
@ -73,9 +80,24 @@ export function createSubjectFromInPlaceSketcher(ctx) {
changeLayer('sketch');
}
function click(modelX, modelY, attrs) {
let [x, y] = sketcher_utils.modelToScreen(viewer, modelX, modelY);
sketcher_utils.clickXY(sketcherApp, x, y, attrs);
}
function select(objects, inclusive) {
sketcherApp.viewer.select(objects, !inclusive);
}
function runAction(id) {
sketcherApp.actions[id].action();
}
return {
addSegment, addRectangle, addArc, addCircle, addEllipse, addEllipticalArc, addSerpinski, addBezier, addPolygon,
move, changeLayer, changeToConstructionLayer, changeToDefaultLayer
move, changeLayer, changeToConstructionLayer, changeToDefaultLayer,
click, select, runAction,
viewer
}
}

View file

@ -31,6 +31,20 @@ module.exports = {
extensions: ['.js', '.jsx'],
modules: [MODULES, "node_modules"]
},
devServer: {
hot: false,
inline: false,
before: function(app) {
app.get('*.wasm', function(req, res) {
res.sendFile(req.url, {
root: path.join(__dirname, 'web'),
headers: {
'Content-Type': 'application/wasm'
}
});
});
},
},
module: {
rules: [{
test: /\.(js|jsx)$/,
@ -45,39 +59,25 @@ module.exports = {
'less-loader',
]
},
{
test: /\.(less|css)$/,
include: [MODULES, WEB_APP],
use: [
'style-loader',
{
loader: 'css-loader',
options: {
getLocalIdent: (context, localIdentName, localName) => generateCSSScopedName(localName, context.resourcePath),
modules: true,
url: false
}
},
'less-loader'
]
},
{
test: /\.html$/,
use: 'handlebars-loader?helperDirs[]=' + __dirname + '/web/app/ui/helpers'
}]
},
devServer: {
hot: false,
inline: false,
before: function(app) {
app.get('*.wasm', function(req, res) {
res.sendFile(req.url, {
root: path.join(__dirname, 'web'),
headers: {
'Content-Type': 'application/wasm'
}
});
});
},
{
test: /\.(less|css)$/,
include: [MODULES, WEB_APP],
use: [
'style-loader',
{
loader: 'css-loader',
options: {
getLocalIdent: (context, localIdentName, localName) => generateCSSScopedName(localName, context.resourcePath),
modules: true,
url: false
}
},
'less-loader'
]
},
{
test: /\.html$/,
use: 'handlebars-loader?helperDirs[]=' + __dirname + '/web/app/ui/helpers'
}]
}
};