import {Constraints} from './constraints'; import {AlgNumConstraint, ConstraintDefinitions} from "./constr/ANConstraints"; import {AlgNumSubSystem} from "./constr/AlgNumSystem"; import {state, stream} from 'lstream'; import {toast} from "react-toastify"; import {ISolveStage, SolvableObject} from "./constr/solvableObject"; import {Viewer} from "./viewer2d"; export {Constraints, ParametricManager} class ParametricManager { constantTable = {}; externalConstantResolver = null; $update = stream(); inTransaction = false; $constraints = this.$update .map(() => [...this.algNumSystem.allConstraints].sort((c1, c2) => c1.id - c2.id)) .remember([]); $generators = this.$update .map(() => [...this.stage.generators].sort((c1, c2) => c1.id - c2.id)) .remember([]); $stages = state({ list: null, pointer: -1 }); $constantDefinition = state(''); groundStage = new SolveStage(this); viewer: Viewer; messageSink: (msg) => void; constructor(viewer) { this.viewer = viewer; this.$constantDefinition.attach((def) => this.rebuildConstantTable(def)) this.reset(); this.$stages.pipe(this.$update); this.$stages.attach(() => this.viewer.refresh()); this.messageSink = msg => alert(msg); } reset() { this.$stages.next({ list: [new SolveStage(this)], pointer: 0 }); } get stage() { const {list, pointer} = this.$stages.value; return list[pointer]; } get stages() { return this.$stages.value.list; } get algNumSystem() { return this.stage.algNumSystem; } get constantDefinition() { return this.$constantDefinition.value; } startTransaction() { this.inTransaction = true; for (const stage of this.stages) { stage.algNumSystem.startTransaction(); } } finishTransaction() { this.inTransaction = false; for (const stage of this.stages) { stage.algNumSystem.finishTransaction(); } this.refresh(); } get allConstraints() { return this.$constraints.value; } addAlgNum(constr) { this.add(constr); } constantResolver = value => { let _value = this.constantTable[value]; if (_value === undefined && this.externalConstantResolver) { _value = this.externalConstantResolver(value); } if (_value !== undefined) { value = _value; } return value; }; rebuildConstantTable(constantDefinition) { this.constantTable = {}; if (constantDefinition == null) return; const lines = constantDefinition.split('\n'); let prefix = "(function() { \n"; for (let i = 0; i < lines.length; i++) { const line = lines[i]; const m = line.match(/^\s*([^\s]+)\s*=(.+)$/); if (m != null && m.length === 3) { const constant = m[1]; try { const value = eval(prefix + "return " + m[2] + "; \n})()"); this.constantTable[constant] = value; prefix += "const " + constant + " = " + value + ";\n" } catch(e) { } } } } onConstantsExternalChange(constantDefinition) { this.rebuildConstantTable(constantDefinition); // this.refresh(); } defineNewConstant(name, value) { let constantDefinition = this.constantDefinition; const constantText = name + ' = ' + value; if (constantDefinition) { constantDefinition += '\n' + constantText; } else { constantDefinition = constantText; } this.$constantDefinition.next(constantDefinition); } updateConstraintConstants(constr) { this.viewer.parametricManager.refresh(); } notify() { this.$update.next(); } commit() { this.refresh(); } _add(constr) { let highestStage = this.stages[0]; constr.objects.forEach(obj => { if (obj.stage.index > highestStage.index) { highestStage = obj.stage; } }); for (const obj of constr.objects) { if (obj.generator && obj.stage === highestStage) { toast("Cannot refer to a generated object from the same stage is being added to."); return; } } highestStage.addConstraint(constr); if (highestStage !== this.stage && !this.inTransaction) { toast("Constraint's been added to stage " + highestStage.index + "!") } } refresh() { if (this.inTransaction) { return; } this.notify(); this.viewer.refresh(); } add(constr) { this.viewer.historyManager.checkpoint(); this._add(constr); this.refresh(); } addAll(constrs) { this.viewer.historyManager.checkpoint(); for (let i = 0; i < constrs.length; i++) { this._add(constrs[i]); } this.refresh(); } remove(constr) { this.viewer.historyManager.checkpoint(); constr.stage.algNumSystem.removeConstraint(constr); constr.annotations.forEach(ann => ann.layer.remove(ann)); this.refresh(); } _removeConstraint(constr) { constr.stage.algNumSystem.removeConstraint(constr); constr.annotations.forEach(ann => ann.layer.remove(ann)); } removeGenerator(generator) { this.viewer.deselectAll(); this.startTransaction(); this._removeGenerator(generator); this.finishTransaction(); } _removeGenerator(generator) { if (generator.__disposed) { return; } generator.__disposed = true; this._removeObjects(generator.generatedObjects, true); generator.stage.removeGenerator(generator); } removeObjects(objects) { this.startTransaction(); this._removeObjects(objects); this.finishTransaction(); } _removeObjects(objects, force = false) { objects.forEach(obj => { if (obj.isRoot) { this._removeObject(obj, force); } }); } _removeObject = (obj, force?) => { if (obj.__disposed) { return; } obj.__disposed = true; if (obj.isGenerated && !force) { return; } obj.traverse(o => o.constraints.forEach(c => this._removeConstraint(c))); if (obj.layer != null) { obj.layer.remove(obj); } obj.traverse(co => co.generators.forEach(gen => { gen.removeObject(co, o => this._removeObject(o, true), () => this._removeGenerator(gen)); })); obj.constraints.clear(); obj.generators.clear(); [this.viewer.dimLayer, this.viewer.labelLayer].forEach(l => { l.traverseSketchObjects(dim => { obj.accept(o => { if (dim.dependsOn && dim.dependsOn(o)) { this._removeObject(dim); return false; } return true; }); }); }) }; invalidate() { for (const stage of this.stages) { stage.algNumSystem.invalidate(); } } revalidateConstraint(constraint) { constraint.stage.algNumSystem.revalidateConstraint(constraint); this.notify(); } constraintUpdated(constraint) { this.revalidateConstraint(constraint); this.invalidate(); } prepare(interactiveObjects) { this.groundStage.prepare(interactiveObjects); for (const stage of this.stages) { stage.prepare(interactiveObjects); } } solve(rough) { this.groundStage.solve(rough); for (const stage of this.stages) { stage.solve(rough); } } addGenerator(generator) { let highestStage = this.stages[0]; generator.sourceObjects(obj => { if (obj.stage.index > highestStage.index) { highestStage = obj.stage; } }); this.addGeneratorToStage(generator, highestStage); if (highestStage !== this.stage && !this.inTransaction) { toast("Generator's been added to stage " + highestStage.index + "!") } this.refresh(); } addGeneratorToStage(generator, stage) { const fail = false; generator.sourceObjects(obj => { if (obj.isGenerated && obj.stage === stage) { toast("Cannot refer to a generated object from the same stage is being added to."); } }); if (fail) { return; } stage.addGenerator(generator); generator.generate(this.viewer); } coincidePoints(pt1, pt2) { this.add(new AlgNumConstraint(ConstraintDefinitions.PCoincident, [pt1, pt2])); } lockPoint(pt) { const lockConstr = new AlgNumConstraint(ConstraintDefinitions.LockPoint, [pt]); lockConstr.initConstants(); this.add(lockConstr); } lockAngle(segment) { const constr = new AlgNumConstraint(ConstraintDefinitions.Angle, [segment]); constr.initConstants(); this.add(constr); } lockLength(segment) { const constr = new AlgNumConstraint(ConstraintDefinitions.SegmentLength, [segment]); constr.initConstants(); this.add(constr); } setConstantsFromGeometry(obj) { obj.visitLinked(o => { o.ancestry(ao => { ao.syncGeometry(); ao.constraints.forEach(c => { c.setConstantsFromGeometry && c.setConstantsFromGeometry() }) }); }); } newStage() { this.$stages.update(s => ({ pointer: s.pointer + 1, list: [...s.list, new SolveStage(this)] })); } accommodateStages(uptoIndex) { const list = this.$stages.value.list; if (uptoIndex < list.length) { return; } let i = list.length; const createdStages = []; for (;i<=uptoIndex;i++) { createdStages.push(new SolveStage(this)); } this.$stages.update(s => ({ pointer: uptoIndex, list: [...s.list, ...createdStages] })); } getStage(index) { return this.stages[index]; } getStageIndex(stage) { return this.stages.indexOf(stage); } } class SolveStage implements ISolveStage{ generators = new Set(); objects = new Set(); private parametricManager: ParametricManager; private algNumSystem: AlgNumSubSystem; constructor(parametricManager) { this.parametricManager = parametricManager; this.algNumSystem = this.createAlgNumSystem(); } get viewer() { return this.parametricManager.viewer; } assignObject(object) { object.stage = this; this.objects.add(object); } unassignObject(object) { object.stage = null; this.objects.delete(object); } addConstraint(constraint) { constraint.stage = this; constraint.annotations.forEach(ann => this.viewer.annotationLayer.add(ann)); this.algNumSystem.addConstraint(constraint) } addGenerator(generator) { generator.stage = this; this.generators.add(generator); } removeGenerator(generator) { this.generators.delete(generator); generator.sourceObjects(obj => obj.generators.delete(generator)) } createAlgNumSystem() { const pt = {x:0,y:0}; const limit = 30; //px const calcVisualLimit = () => { //100 px limit this.viewer.screenToModel2(0, 0, pt); const x1 = pt.x; this.viewer.screenToModel2(limit, 0, pt); const x2 = pt.x; return Math.abs(x2 - x1); }; return new AlgNumSubSystem(calcVisualLimit, this.parametricManager.constantResolver, this); } prepare(interactiveObjects) { this.algNumSystem.prepare(interactiveObjects); } solve(rough) { this.algNumSystem.solve(rough); this.generators.forEach(gen => { // @ts-ignore gen.regenerate(this.viewer); }) } get index() { return this.viewer.parametricManager.getStageIndex(this); } }