part import from repository

This commit is contained in:
Val Erastov (xibyte) 2020-06-09 21:52:49 -07:00
parent e6d23ab4bf
commit 66c4707a83
66 changed files with 1432 additions and 992 deletions

View file

@ -1,5 +1,11 @@
export interface ApplicationContext {
/**
* CoreContext shouldn't contain any UI services because it can be potentially used in the headless mode
*/
export interface CoreContext {
}
export interface ApplicationContext extends CoreContext {
services: any,
streams: any,
}

View file

@ -84,7 +84,7 @@ export function removeInPlace(arr, val) {
return arr;
}
export function indexById(array) {
export function indexById<T extends {id: string}>(array: T[]): {[id: string]: T} {
const out = {};
array.forEach(i => out[i.id] = i);
return out;

View file

@ -32,10 +32,20 @@ export class Emitter extends StreamBase {
try {
this.state = EMITTING;
for (let i = 0; i < this.observers.length; i++) {
this.observers[i](value);
callObserver(this.observers[i], value);
}
} finally {
this.state = READY;
}
}
}
export function callObserver(observer, value) {
try {
observer(value);
} catch (e) {
console.error('Error while observer call:');
console.error(observer);
console.error(e);
}
}

View file

@ -1,4 +1,4 @@
import {Emitter} from './emitter';
import {callObserver, Emitter} from './emitter';
export class StateStream extends Emitter {
@ -30,7 +30,7 @@ export class StateStream extends Emitter {
}
attach(observer) {
observer(this._value);
callObserver(observer, this._value);
return super.attach(observer);
}
}

View file

@ -1,100 +1,104 @@
export default class Vector {
constructor(x, y, z) {
x: number;
y: number;
z: number;
constructor(x = 0, y = 0, z = 0) {
this.x = x || 0;
this.y = y || 0;
this.z = z || 0;
}
set(x, y, z) {
set(x, y, z): Vector {
this.x = x || 0;
this.y = y || 0;
this.z = z || 0;
return this;
}
set3(data) {
set3(data: [number, number, number]): Vector {
this.x = data[0] || 0;
this.y = data[1] || 0;
this.z = data[2] || 0;
return this;
}
setV(data) {
setV(data: Vector): Vector {
this.x = data.x;
this.y = data.y;
this.z = data.z;
return this;
}
multiply(scalar) {
multiply(scalar: number): Vector {
return new Vector(this.x * scalar, this.y * scalar, this.z * scalar);
}
_multiply(scalar) {
_multiply(scalar: number): Vector {
return this.set(this.x * scalar, this.y * scalar, this.z * scalar);
}
divide(scalar) {
divide(scalar: number): Vector {
return new Vector(this.x / scalar, this.y / scalar, this.z / scalar);
}
_divide(scalar) {
_divide(scalar: number): Vector {
return this.set(this.x / scalar, this.y / scalar, this.z / scalar);
}
dot(vector) {
dot(vector: Vector): number {
return this.x * vector.x + this.y * vector.y + this.z * vector.z;
}
copy() {
copy(): Vector {
return new Vector(this.x, this.y, this.z);
}
length() {
length(): number {
return Math.sqrt(this.x*this.x + this.y*this.y + this.z*this.z);
};
lengthSquared() {
lengthSquared(): number {
return this.dot(this);
}
distanceToSquared(a) {
distanceToSquared(a: Vector): number {
return this.minus(a).lengthSquared();
}
distanceTo(a) {
distanceTo(a: Vector): number {
return Math.sqrt(this.distanceToSquared(a));
}
minus(vector) {
minus(vector: Vector): Vector {
return new Vector(this.x - vector.x, this.y - vector.y, this.z - vector.z);
}
_minus(vector) {
_minus(vector: Vector): Vector {
this.x -= vector.x;
this.y -= vector.y;
this.z -= vector.z;
return this;
}
_minusXYZ(x, y, z) {
_minusXYZ(x, y, z): Vector {
this.x -= x;
this.y -= y;
this.z -= z;
return this;
}
plusXYZ(x, y, z) {
plusXYZ(x, y, z): Vector {
return new Vector(this.x + x, this.y + y, this.z + z);
}
plus(vector) {
plus(vector: Vector): Vector {
return new Vector(this.x + vector.x, this.y + vector.y, this.z + vector.z);
}
_plus(vector) {
_plus(vector: Vector): Vector {
this.x += vector.x;
this.y += vector.y;
this.z += vector.z;
@ -117,11 +121,11 @@ export default class Vector {
return this.set(this.x / mag, this.y / mag, this.z / mag)
};
cross(a) {
cross(a: Vector): Vector {
return this.copy()._cross(a);
};
_cross(a) {
_cross(a: Vector): Vector {
return this.set(
this.y * a.z - this.z * a.y,
this.z * a.x - this.x * a.z,
@ -129,19 +133,19 @@ export default class Vector {
);
};
negate() {
negate(): Vector {
return this.multiply(-1);
}
_negate() {
_negate(): Vector {
return this._multiply(-1);
}
_perpXY() {
_perpXY(): Vector {
return this.set(-this.y, this.x, this.z);
}
toArray() {
toArray(): [number, number, number] {
return [this.x, this.y, this.z];
}
@ -151,15 +155,14 @@ export default class Vector {
data[2] = this.z;
}
static fromData(arr) {
static fromData(arr: [number, number, number]): Vector {
return new Vector().set3(arr);
}
data: () => [number, number, number] = Vector.prototype.toArray;
unit: () => (Vector) = Vector.prototype.normalize;
_unit: () => (Vector) = Vector.prototype._normalize;
scale: (scalar: number) => Vector = Vector.prototype.multiply;
_scale: (scalar: number) => Vector = Vector.prototype._multiply;
}
Vector.prototype.data = Vector.prototype.toArray;
Vector.prototype.unit = Vector.prototype.normalize;
Vector.prototype._unit = Vector.prototype._normalize;
Vector.prototype.scale = Vector.prototype.multiply;
Vector.prototype._scale = Vector.prototype._multiply;

View file

@ -1,41 +1,16 @@
import React from 'react';
import context from 'context';
import React, {useContext} from 'react';
import {useStreamWithUpdater} from "./effects";
import {AppContext} from "../../web/app/cad/dom/components/AppContext";
export default function bind(streamProvider) {
return function (Component) {
return class Connected extends React.Component {
state = {hasError: false, value: null};
onChange = value => streamProvider(context.streams, this.props).next(value);
UNSAFE_componentWillMount() {
this.stream = streamProvider(context.streams, this.props);
this.detacher = this.stream.attach(value => {
this.setState({
hasError: false,
value
});
});
}
return function Connected (props) {
componentWillUnmount() {
this.detacher();
}
render() {
if (this.state.hasError) {
return null;
}
return <Component value={this.state.value}
onChange={this.onChange}
{...this.props} />;
const context = useContext(AppContext);
const [value, updater] = useStreamWithUpdater(streamProvider(context, props));
}
componentDidCatch() {
this.setState({hasError: true});
}
return <Component value={value} onChange={updater} {...props} />;
};
};
}

View file

@ -1,7 +1,14 @@
import React from 'react';
import cx from 'classnames';
export default function Fa({icon, fw, fa, stack, className, ...props}) {
export default function Fa({icon, fw, fa, stack, className, ...props}: {
icon: string,
fw?: boolean,
fa?: string[],
stack?: string,
className?: string,
props?: any
}) {
let faCss = fa ? fa.map(s => 'fa-' + s) : [];
if (icon) {
icon = 'fa-' + icon;

View file

@ -2,12 +2,14 @@ import React, {useState, ReactNode} from "react";
import cx from 'classnames';
import {GoPrimitiveDot, GoTriangleDown, GoTriangleRight} from "react-icons/go";
export function Tree({children, icon, label, initCollapsed = false, className} : {
export function Tree({children, icon, label, initCollapsed = false, className, onClick, ...props} : {
initCollapsed?: boolean
children?: ReactNode,
icon?: ReactNode,
label?: ReactNode,
className?: string
onClick?: (e: any) => void,
props?: JSX.IntrinsicAttributes
}) {
const headless = !label;
@ -22,7 +24,7 @@ export function Tree({children, icon, label, initCollapsed = false, className} :
<span className='tree-placeholder'><GoPrimitiveDot /></span>
}
<span className='tree-icon'>{icon}</span>
<span className='tree-label'>{label}</span>
<span onClick={onClick} className='tree-label text-button' {...props}>{label}</span>
</div>}
{children && <div className='tree-content'>{children}</div>}

356
package-lock.json generated
View file

@ -1529,41 +1529,49 @@
"cli-cursor": "^1.0.2",
"date-fns": "^1.27.2",
"figures": "^1.7.0"
}
},
"@cypress/request": {
"version": "2.88.5",
"resolved": "https://registry.npmjs.org/@cypress/request/-/request-2.88.5.tgz",
"integrity": "sha512-TzEC1XMi1hJkywWpRfD2clreTa/Z+lOrXDCxxBTBPEcY5azdPi56A6Xw+O4tWJnaJH3iIE7G5aDXZC6JgRZLcA==",
"dev": true,
"requires": {
"aws-sign2": "~0.7.0",
"aws4": "^1.8.0",
"caseless": "~0.12.0",
"combined-stream": "~1.0.6",
"extend": "~3.0.2",
"forever-agent": "~0.6.1",
"form-data": "~2.3.2",
"har-validator": "~5.1.3",
"http-signature": "~1.2.0",
"is-typedarray": "~1.0.0",
"isstream": "~0.1.2",
"json-stringify-safe": "~5.0.1",
"mime-types": "~2.1.19",
"oauth-sign": "~0.9.0",
"performance-now": "^2.1.0",
"qs": "~6.5.2",
"safe-buffer": "^5.1.2",
"tough-cookie": "~2.5.0",
"tunnel-agent": "^0.6.0",
"uuid": "^3.3.2"
},
"dependencies": {
"cli-cursor": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-1.0.2.tgz",
"integrity": "sha1-ZNo/fValRBLll5S9Ytw1KV6PKYc=",
"dev": true,
"requires": {
"restore-cursor": "^1.0.1"
}
},
"figures": {
"version": "1.7.0",
"resolved": "https://registry.npmjs.org/figures/-/figures-1.7.0.tgz",
"integrity": "sha1-y+Hjr/zxzUS4DK3+0o3Hk6lwHS4=",
"dev": true,
"requires": {
"escape-string-regexp": "^1.0.5",
"object-assign": "^4.1.0"
}
},
"onetime": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/onetime/-/onetime-1.1.0.tgz",
"integrity": "sha1-ofeDj4MUxRbwXs78vEzP4EtO14k=",
"mime-db": {
"version": "1.44.0",
"resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.44.0.tgz",
"integrity": "sha512-/NOTfLrsPBVeH7YtFPgsVWveuL+4SjjYxaQ1xtM1KMFj7HdxlBlxeyNLzhyJVx7r4rZGJAZ/6lkKCitSc/Nmpg==",
"dev": true
},
"restore-cursor": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-1.0.1.tgz",
"integrity": "sha1-NGYfRohjJ/7SmRR5FSJS35LapUE=",
"mime-types": {
"version": "2.1.27",
"resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.27.tgz",
"integrity": "sha512-JIhqnCasI9yD+SsmkquHBxTSEuZdQX5BuQnS2Vc7puQQQ+8yiP5AY5uWhpdv4YL4VM5c6iliiYWPgJ/nJQLp7w==",
"dev": true,
"requires": {
"exit-hook": "^1.0.0",
"onetime": "^1.0.0"
"mime-db": "1.44.0"
}
}
}
@ -1700,6 +1708,12 @@
"@types/react": "*"
}
},
"@types/sinonjs__fake-timers": {
"version": "6.0.1",
"resolved": "https://registry.npmjs.org/@types/sinonjs__fake-timers/-/sinonjs__fake-timers-6.0.1.tgz",
"integrity": "sha512-yYezQwGWty8ziyYLdZjwxyMb0CZR49h8JALHGrxjQHWlqGgc8kLdHEgWrgL0uZ29DMvEVBDnHU2Wg36zKSIUtA==",
"dev": true
},
"@types/sizzle": {
"version": "2.3.2",
"resolved": "https://registry.npmjs.org/@types/sizzle/-/sizzle-2.3.2.tgz",
@ -1980,9 +1994,9 @@
"dev": true
},
"ansi-escapes": {
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-3.1.0.tgz",
"integrity": "sha512-UgAb8H9D41AQnu/PbWlCofQVcnV4Gs2bBJi9eZPxfU/hgglFh3SMDMENRIqdr7H6XFnXdoknctFByVsCOotTVw==",
"version": "3.2.0",
"resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-3.2.0.tgz",
"integrity": "sha512-cBhpre4ma+U0T1oM5fXg7Dy1Jw7zzwv7lt/GoCpr+hDQJoYnKVPLL4dCvSEFMmQurOQvSrwT7SL/DAlhBI97RQ==",
"dev": true
},
"ansi-html": {
@ -3104,12 +3118,12 @@
"integrity": "sha1-+zgB1FNGdknvNgPH1hoCvRKb3m0="
},
"cli-cursor": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-2.1.0.tgz",
"integrity": "sha1-s12sN2R5+sw+lHR9QdDQ9SOP/LU=",
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-1.0.2.tgz",
"integrity": "sha1-ZNo/fValRBLll5S9Ytw1KV6PKYc=",
"dev": true,
"requires": {
"restore-cursor": "^2.0.0"
"restore-cursor": "^1.0.1"
}
},
"cli-table3": {
@ -3644,13 +3658,15 @@
"dev": true
},
"cypress": {
"version": "4.2.0",
"resolved": "https://registry.npmjs.org/cypress/-/cypress-4.2.0.tgz",
"integrity": "sha512-8LdreL91S/QiTCLYLNbIjLL8Ht4fJmu/4HGLxUI20Tc7JSfqEfCmXELrRfuPT0kjosJwJJZacdSji9XSRkPKUw==",
"version": "4.7.0",
"resolved": "https://registry.npmjs.org/cypress/-/cypress-4.7.0.tgz",
"integrity": "sha512-Vav6wUFhPRlImIND/2lOQlUnAWzgCC/iXyJlJjX9nJOJul5LC1vUpf/m8Oiae870PFPyT0ZLLwPHKTXZNdXmHw==",
"dev": true,
"requires": {
"@cypress/listr-verbose-renderer": "0.4.1",
"@cypress/request": "2.88.5",
"@cypress/xvfb": "1.2.4",
"@types/sinonjs__fake-timers": "6.0.1",
"@types/sizzle": "2.3.2",
"arch": "2.1.1",
"bluebird": "3.7.2",
@ -3664,7 +3680,7 @@
"eventemitter2": "4.1.2",
"execa": "1.0.0",
"executable": "4.1.1",
"extract-zip": "1.6.7",
"extract-zip": "1.7.0",
"fs-extra": "8.1.0",
"getos": "3.1.4",
"is-ci": "2.0.0",
@ -3673,12 +3689,11 @@
"listr": "0.14.3",
"lodash": "4.17.15",
"log-symbols": "3.0.0",
"minimist": "1.2.2",
"minimist": "1.2.5",
"moment": "2.24.0",
"ospath": "1.2.2",
"pretty-bytes": "5.3.0",
"ramda": "0.26.1",
"request": "github:cypress-io/request#b5af0d1fa47eec97ba980cde90a13e69a2afcd16",
"request-progress": "3.0.0",
"supports-color": "7.1.0",
"tmp": "0.1.0",
@ -3724,19 +3739,6 @@
"integrity": "sha512-NIQrwvv9V39FHgGFm36+U9SMQzbiHvU79k+iADraJTpmrFFfx7Ds0IvDoAdZsDrknlkRk14OYoWXb57uTh7/sw==",
"dev": true
},
"cross-spawn": {
"version": "6.0.5",
"resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-6.0.5.tgz",
"integrity": "sha512-eTVLrBSt7fjbDygz805pMnstIs2VTBNkRm0qxZd+M7A5XDdxVRWO5MxGBXZhjY4cqLYLdtrGqRf8mBPmzwSpWQ==",
"dev": true,
"requires": {
"nice-try": "^1.0.4",
"path-key": "^2.0.1",
"semver": "^5.5.0",
"shebang-command": "^1.2.0",
"which": "^1.2.9"
}
},
"debug": {
"version": "4.1.1",
"resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz",
@ -3752,30 +3754,6 @@
"integrity": "sha1-DhqEd6+CGm7zmVsxG/dMI6UkfxU=",
"dev": true
},
"execa": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/execa/-/execa-1.0.0.tgz",
"integrity": "sha512-adbxcyWV46qiHyvSp50TKt05tB4tK3HcmF7/nxfAdhnox83seTDbwnaqKO4sXRy7roHAIFqJP/Rw/AuEbX61LA==",
"dev": true,
"requires": {
"cross-spawn": "^6.0.0",
"get-stream": "^4.0.0",
"is-stream": "^1.1.0",
"npm-run-path": "^2.0.0",
"p-finally": "^1.0.0",
"signal-exit": "^3.0.0",
"strip-eof": "^1.0.0"
}
},
"get-stream": {
"version": "4.1.0",
"resolved": "https://registry.npmjs.org/get-stream/-/get-stream-4.1.0.tgz",
"integrity": "sha512-GMat4EJ5161kIy2HevLlr4luNjBgvmj413KaQA7jt4V8B4RDsfpHk7WQ9GVqfYyyx8OS/L66Kox+rJRNklLK7w==",
"dev": true,
"requires": {
"pump": "^3.0.0"
}
},
"glob": {
"version": "7.1.6",
"resolved": "https://registry.npmjs.org/glob/-/glob-7.1.6.tgz",
@ -3790,66 +3768,12 @@
"path-is-absolute": "^1.0.0"
}
},
"mime-db": {
"version": "1.43.0",
"resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.43.0.tgz",
"integrity": "sha512-+5dsGEEovYbT8UY9yD7eE4XTc4UwJ1jBYlgaQQF38ENsKR3wj/8q8RFZrF9WIZpB2V1ArTVFUva8sAul1NzRzQ==",
"dev": true
},
"mime-types": {
"version": "2.1.26",
"resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.26.tgz",
"integrity": "sha512-01paPWYgLrkqAyrlDorC1uDwl2p3qZT7yl806vW7DvDoxwXi46jsjFbg+WdwotBIk6/MbEhO/dh5aZ5sNj/dWQ==",
"dev": true,
"requires": {
"mime-db": "1.43.0"
}
},
"minimist": {
"version": "1.2.2",
"resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.2.tgz",
"integrity": "sha512-rIqbOrKb8GJmx/5bc2M0QchhUouMXSpd1RTclXsB41JdL+VtnojfaJR+h7F9k18/4kHUsBFgk80Uk+q569vjPA==",
"dev": true
},
"ms": {
"version": "2.1.2",
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz",
"integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==",
"dev": true
},
"punycode": {
"version": "2.1.1",
"resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz",
"integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==",
"dev": true
},
"request": {
"version": "github:cypress-io/request#b5af0d1fa47eec97ba980cde90a13e69a2afcd16",
"from": "github:cypress-io/request#b5af0d1fa47eec97ba980cde90a13e69a2afcd16",
"dev": true,
"requires": {
"aws-sign2": "~0.7.0",
"aws4": "^1.8.0",
"caseless": "~0.12.0",
"combined-stream": "~1.0.6",
"extend": "~3.0.2",
"forever-agent": "~0.6.1",
"form-data": "~2.3.2",
"har-validator": "~5.1.3",
"http-signature": "~1.2.0",
"is-typedarray": "~1.0.0",
"isstream": "~0.1.2",
"json-stringify-safe": "~5.0.1",
"mime-types": "~2.1.19",
"oauth-sign": "~0.9.0",
"performance-now": "^2.1.0",
"qs": "~6.5.2",
"safe-buffer": "^5.1.2",
"tough-cookie": "~2.5.0",
"tunnel-agent": "^0.6.0",
"uuid": "^3.3.2"
}
},
"rimraf": {
"version": "2.7.1",
"resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.7.1.tgz",
@ -3884,16 +3808,6 @@
"requires": {
"rimraf": "^2.6.3"
}
},
"tough-cookie": {
"version": "2.5.0",
"resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-2.5.0.tgz",
"integrity": "sha512-nlLsUzgm1kfLXSXfRZMc1KLAugd4hqJHDTvc2hDIwS3mZAfMEuMbc03SujMF+GEcpaX/qboeycw6iO8JwVv2+g==",
"dev": true,
"requires": {
"psl": "^1.1.28",
"punycode": "^2.1.1"
}
}
}
},
@ -5051,24 +4965,24 @@
}
},
"extract-zip": {
"version": "1.6.7",
"resolved": "https://registry.npmjs.org/extract-zip/-/extract-zip-1.6.7.tgz",
"integrity": "sha1-qEC0uK9kAyZMjbV/Txp0Mz74H+k=",
"version": "1.7.0",
"resolved": "https://registry.npmjs.org/extract-zip/-/extract-zip-1.7.0.tgz",
"integrity": "sha512-xoh5G1W/PB0/27lXgMQyIhP5DSY/LhoCsOyZgb+6iMmRtCwVBo55uKaMoEYrDCKQhWvqEip5ZPKAc6eFNyf/MA==",
"dev": true,
"requires": {
"concat-stream": "1.6.2",
"debug": "2.6.9",
"mkdirp": "0.5.1",
"yauzl": "2.4.1"
"concat-stream": "^1.6.2",
"debug": "^2.6.9",
"mkdirp": "^0.5.4",
"yauzl": "^2.10.0"
},
"dependencies": {
"yauzl": {
"version": "2.4.1",
"resolved": "https://registry.npmjs.org/yauzl/-/yauzl-2.4.1.tgz",
"integrity": "sha1-lSj0QtqxsihOWLQ3m7GU4i4MQAU=",
"mkdirp": {
"version": "0.5.5",
"resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.5.tgz",
"integrity": "sha512-NKmAlESf6jMGym1++R0Ra7wvhV+wFW63FaSOFPwRahvea0gMUcGUhVeAg/0BC0wiv9ih5NYPB1Wn1UEI1/L+xQ==",
"dev": true,
"requires": {
"fd-slicer": "~1.0.1"
"minimist": "^1.2.5"
}
}
}
@ -5119,9 +5033,9 @@
}
},
"fd-slicer": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/fd-slicer/-/fd-slicer-1.0.1.tgz",
"integrity": "sha1-i1vL2ewyfFBBv5qwI/1nUPEXfmU=",
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/fd-slicer/-/fd-slicer-1.1.0.tgz",
"integrity": "sha1-JcfInLH5B3+IkbvmHY85Dq4lbx4=",
"dev": true,
"requires": {
"pend": "~1.2.0"
@ -5134,12 +5048,13 @@
"dev": true
},
"figures": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/figures/-/figures-2.0.0.tgz",
"integrity": "sha1-OrGi0qYsi/tDGgyUy3l6L84nyWI=",
"version": "1.7.0",
"resolved": "https://registry.npmjs.org/figures/-/figures-1.7.0.tgz",
"integrity": "sha1-y+Hjr/zxzUS4DK3+0o3Hk6lwHS4=",
"dev": true,
"requires": {
"escape-string-regexp": "^1.0.5"
"escape-string-regexp": "^1.0.5",
"object-assign": "^4.1.0"
}
},
"file-entry-cache": {
@ -5387,9 +5302,9 @@
},
"dependencies": {
"graceful-fs": {
"version": "4.2.3",
"resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.3.tgz",
"integrity": "sha512-a30VEBm4PEdx1dRB7MFK7BejejvCvBronbLjht+sHuGYj8PHs7M/5Z+rt5lw551vZ7yfTCj4Vuyy3mSJytDWRQ==",
"version": "4.2.4",
"resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.4.tgz",
"integrity": "sha512-WjKPNJF79dtJAVniUlGGWHYGz2jWxT6VhN/4m1NdkbZ2nOsEF+cI1Edgql5zCRhs/VsQYRvrXctxktVXZUkixw==",
"dev": true
}
}
@ -7581,16 +7496,6 @@
"strip-ansi": "^3.0.1"
},
"dependencies": {
"figures": {
"version": "1.7.0",
"resolved": "https://registry.npmjs.org/figures/-/figures-1.7.0.tgz",
"integrity": "sha1-y+Hjr/zxzUS4DK3+0o3Hk6lwHS4=",
"dev": true,
"requires": {
"escape-string-regexp": "^1.0.5",
"object-assign": "^4.1.0"
}
},
"indent-string": {
"version": "3.2.0",
"resolved": "https://registry.npmjs.org/indent-string/-/indent-string-3.2.0.tgz",
@ -7640,6 +7545,43 @@
"supports-color": "^5.3.0"
}
},
"cli-cursor": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-2.1.0.tgz",
"integrity": "sha1-s12sN2R5+sw+lHR9QdDQ9SOP/LU=",
"dev": true,
"requires": {
"restore-cursor": "^2.0.0"
}
},
"figures": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/figures/-/figures-2.0.0.tgz",
"integrity": "sha1-OrGi0qYsi/tDGgyUy3l6L84nyWI=",
"dev": true,
"requires": {
"escape-string-regexp": "^1.0.5"
}
},
"onetime": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/onetime/-/onetime-2.0.1.tgz",
"integrity": "sha1-BnQoIw/WdEOyeUsiu6UotoZ5YtQ=",
"dev": true,
"requires": {
"mimic-fn": "^1.0.0"
}
},
"restore-cursor": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-2.0.0.tgz",
"integrity": "sha1-n37ih/gv0ybU/RYpI9YhKe7g368=",
"dev": true,
"requires": {
"onetime": "^2.0.0",
"signal-exit": "^3.0.2"
}
},
"supports-color": {
"version": "5.5.0",
"resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz",
@ -7774,6 +7716,34 @@
"integrity": "sha1-7QMXwyIGT3lGbAKWa922Bas32Zg=",
"dev": true
},
"cli-cursor": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-2.1.0.tgz",
"integrity": "sha1-s12sN2R5+sw+lHR9QdDQ9SOP/LU=",
"dev": true,
"requires": {
"restore-cursor": "^2.0.0"
}
},
"onetime": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/onetime/-/onetime-2.0.1.tgz",
"integrity": "sha1-BnQoIw/WdEOyeUsiu6UotoZ5YtQ=",
"dev": true,
"requires": {
"mimic-fn": "^1.0.0"
}
},
"restore-cursor": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-2.0.0.tgz",
"integrity": "sha1-n37ih/gv0ybU/RYpI9YhKe7g368=",
"dev": true,
"requires": {
"onetime": "^2.0.0",
"signal-exit": "^3.0.2"
}
},
"strip-ansi": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-4.0.0.tgz",
@ -8604,13 +8574,10 @@
}
},
"onetime": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/onetime/-/onetime-2.0.1.tgz",
"integrity": "sha1-BnQoIw/WdEOyeUsiu6UotoZ5YtQ=",
"dev": true,
"requires": {
"mimic-fn": "^1.0.0"
}
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/onetime/-/onetime-1.1.0.tgz",
"integrity": "sha1-ofeDj4MUxRbwXs78vEzP4EtO14k=",
"dev": true
},
"opn": {
"version": "5.5.0",
@ -10018,13 +9985,13 @@
"dev": true
},
"restore-cursor": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-2.0.0.tgz",
"integrity": "sha1-n37ih/gv0ybU/RYpI9YhKe7g368=",
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-1.0.1.tgz",
"integrity": "sha1-NGYfRohjJ/7SmRR5FSJS35LapUE=",
"dev": true,
"requires": {
"onetime": "^2.0.0",
"signal-exit": "^3.0.2"
"exit-hook": "^1.0.0",
"onetime": "^1.0.0"
}
},
"ret": {
@ -11275,7 +11242,6 @@
"version": "2.5.0",
"resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-2.5.0.tgz",
"integrity": "sha512-nlLsUzgm1kfLXSXfRZMc1KLAugd4hqJHDTvc2hDIwS3mZAfMEuMbc03SujMF+GEcpaX/qboeycw6iO8JwVv2+g==",
"optional": true,
"requires": {
"psl": "^1.1.28",
"punycode": "^2.1.1"
@ -11284,8 +11250,7 @@
"punycode": {
"version": "2.1.1",
"resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz",
"integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==",
"optional": true
"integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A=="
}
}
},
@ -12704,17 +12669,6 @@
"requires": {
"buffer-crc32": "~0.2.3",
"fd-slicer": "~1.1.0"
},
"dependencies": {
"fd-slicer": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/fd-slicer/-/fd-slicer-1.1.0.tgz",
"integrity": "sha1-JcfInLH5B3+IkbvmHY85Dq4lbx4=",
"dev": true,
"requires": {
"pend": "~1.2.0"
}
}
}
}
}

View file

@ -43,7 +43,7 @@
"babel-plugin-transform-decorators-legacy": "^1.3.5",
"babel-polyfill": "^6.26.0",
"css-loader": "^3.4.2",
"cypress": "^4.2.0",
"cypress": "^4.7.0",
"cypress-wait-until": "^1.7.1",
"eslint": "^6.8.0",
"eslint-plugin-babel": "^5.3.0",

View file

@ -5,9 +5,32 @@ describe("Part Import", () => {
cy.openModeller();
});
it("import from web-cad.org smoke test", () => {
it("import from web-cad.org basic flow", () => {
cy.getActionButton('IMPORT_PART').click();
cy.get('.wizard').should('have.attr', 'data-operation-id', 'IMPORT_PART');
cy.get('.part-catalog-chooser').should('exist');
cy.get('.part-catalog-chooser [data-part-ref="web-cad.org/primitives.box"]').click();
cy.wizardOK();
cy.simulateClickByRayCast([-84, 242, 415], [84, 232, 307]);
cy.showEntitySelection();
cy.getEntitySelection('face').should('have.text', 'web-cad.org/primitives.box:0:S:0/F:0');
});
it("should refer to right face while extrude operation of external part", () => {
cy.getActionButton('IMPORT_PART').click();
cy.get('.wizard').should('have.attr', 'data-operation-id', 'IMPORT_PART');
cy.get('.part-catalog-chooser').should('exist');
cy.get('.part-catalog-chooser [data-part-ref="web-cad.org/lumber.2x4"]').click();
cy.wizardOK();
cy.simulateClickByRayCast([-84, 242, 415], [84, 232, 307]);
cy.showEntitySelection();
cy.getEntitySelection('face').should('have.text', 'web-cad.org/lumber.2x4:0:S:2/F:0');
});
});

View file

@ -45,6 +45,20 @@ Cypress.Commands.add("commitSketch", () => {
});
Cypress.Commands.add("wizardOK", () => {
cy.getModellerTPI().then(tpi => tpi.wizardOK())
});
Cypress.Commands.add("getModellerTPI", () => {
return cy.window().then(win => modellerUISubject(win.__CAD_APP));
});
Cypress.Commands.add("showEntitySelection", () => {
return cy.get('.float-view-btn[data-view="selection"]').click();
});
Cypress.Commands.add("getEntitySelection", (type) => {
return cy.get(`.selection-view [data-entity="${type}"] li`);
});

View file

@ -9,15 +9,20 @@ declare namespace Cypress {
simulateClickByRayCast(from :vec3, to: vec3): Chainable<any>;
openSketcher(): Chainable<SketcherTPI>;
commitSketch(): Chainable<void>;
wizardOK(): Chainable<void>;
getModellerTPI(): Chainable<ModellerTPI>;
showEntitySelection(): Chainable<Element>;
getEntitySelection(type: string): Chainable<Element>;
}
export interface ModellerTPI {
wizardOK(): void;
}
export interface SketcherTPI {
addRectangle(x1: number, y1: number, x2: number, y2: number);
addRectangle(x1: number, y1: number, x2: number, y2: number): any;
}

View file

@ -2,10 +2,10 @@ import {enableAnonymousActionHint} from './anonHint';
import * as stream from 'lstream';
import {state, StateStream, Stream} from 'lstream';
import {LOG_FLAGS} from '../logFlags';
import {ApplicationContext} from "context";
import {CoreContext} from "context";
import {IconType} from "react-icons";
export function activate(context: ApplicationContext) {
export function activate(context: CoreContext) {
let {streams} = context;
@ -157,7 +157,7 @@ export interface ActionService {
}
declare module 'context' {
interface ApplicationContext {
interface CoreContext {
actionService: ActionService;
}

View file

@ -34,7 +34,7 @@ export default [
label: 'save',
info: 'save project to storage',
},
invoke: (context) => context.services.project.save()
invoke: (context) => context.projectService.save()
},
{
@ -104,7 +104,7 @@ export default [
label: 'Clone Project...',
info: 'clone current project and open in a new tab',
},
invoke: (context) => context.services.projectManager.cloneProject(context.services.project.id)
invoke: (context) => context.services.projectManager.cloneProject(context.projectService.id)
},
{

View file

@ -1,105 +1,60 @@
import {DATUM, DATUM_AXIS, EDGE, FACE, LOOP, SHELL, SKETCH_OBJECT} from '../scene/entites';
import {MShell} from '../model/mshell';
import {Emitter} from "lstream";
import {ShowDialogRequest} from "ui/showDialogRequest";
import {CatalogCategory, CatalogPart, PartsCatalog} from "../partImport/partImportPlugin";
import {MObject} from "../model/mobject";
import {ApplicationContext} from "context";
export function activate(ctx) {
export function activate(ctx: ApplicationContext) {
const {streams, services} = ctx;
const shells$ = streams.craft.models.map(models => models.filter(m => m instanceof MShell)).remember();
const modelIndex$ = streams.craft.models.map(models => {
const index = new Map();
models.forEach(model => model.traverse(m => index.set(m.id, m)));
return index;
}).remember();
streams.cadRegistry = {
shells: streams.craft.models.map(models => models.filter(m => m instanceof MShell)).remember(),
modelIndex: streams.craft.models.map(models => models.reduce((i, v)=> i.set(v.id, v), new Map())).remember()
shells: shells$, modelIndex: modelIndex$
};
streams.cadRegistry.update = streams.cadRegistry.modelIndex;
const index = () => modelIndex$.value;
function getAllShells() {
return streams.cadRegistry.shells.value;
}
function findShell(shellId) {
let shells = getAllShells();
for (let shell of shells) {
if (shell.id === shellId) {
return shell;
}
}
return null;
return index().get(shellId);
}
function findFace(faceId) {
let shells = getAllShells();
for (let shell of shells) {
for (let face of shell.faces) {
if (face.id === faceId) {
return face;
}
}
}
return null;
return index().get(faceId);
}
function findEdge(edgeId) {
let shells = getAllShells();
for (let shell of shells) {
for (let edge of shell.edges) {
if (edge.id === edgeId) {
return edge;
}
}
}
return null;
return index().get(edgeId);
}
function findSketchObject(sketchObjectGlobalId) {
let [shellId, faceId, sketchObjectId] = sketchObjectGlobalId.split('/');
let face = findFace(shellId+'/'+faceId);
if (face) {
return face.findSketchObjectById(sketchObjectGlobalId);
}
return null;
return index().get(sketchObjectGlobalId);
}
function findDatum(datumId) {
return streams.cadRegistry.modelIndex.value.get(datumId)||null;
return index().get(datumId);
}
function findDatumAxis(datumAxisId) {
const [datumId, axisLiteral] = datumAxisId.split('/');
let datum = streams.cadRegistry.modelIndex.value.get(datumId);
if (!datum) {
return null;
}
return datum.getAxisByLiteral(axisLiteral);
return index().get(datumAxisId);
}
function findLoop(loopId) {
let [shellId, faceId, loopLocalId] = loopId.split('/');
let face = findFace(shellId+'/'+faceId);
if (face) {
for (let loop of face.sketchLoops) {
if (loop.id === loopId) {
return loop;
}
}
}
return null;
return index().get(loopId);
}
function findEntity(entity, id) {
switch (entity) {
case FACE: return findFace(id);
case SHELL: return findShell(id);
case EDGE: return findEdge(id);
case SKETCH_OBJECT: return findSketchObject(id);
case DATUM: return findDatum(id);
case DATUM_AXIS: return findDatumAxis(id);
case LOOP: return findLoop(id);
default: throw 'unsupported';
}
return index().get(id);
}
services.cadRegistry = {

View file

@ -1,183 +0,0 @@
import {addModification, stepOverriding} from './craftHistoryUtils';
import {state, stream} from 'lstream';
import materializeParams from './materializeParams';
import CadError from '../../utils/errors';
import {MObjectIdGenerator} from '../model/mobject';
import {intercept} from "../../../../modules/lstream/intercept";
export function activate(ctx) {
const {streams, services} = ctx;
streams.craft = {
modifications: state({
history: [],
pointer: -1
}),
models: state([]),
update: stream()
};
let preRun = null;
function modifyWithPreRun(request, modificationsUpdater, onAccepted, onError) {
runRequest(request).then(result => {
onAccepted();
preRun = {
request,
result
};
modificationsUpdater(request);
}).catch(onError);
}
function modify(request, onAccepted, onError) {
modifyWithPreRun(request,
request => streams.craft.modifications.update(modifications => addModification(modifications, request)), onAccepted, onError);
}
function modifyInHistoryAndStep(request, onAccepted, onError) {
modifyWithPreRun(request,
request => streams.craft.modifications.update(modifications => stepOverriding(modifications, request)), onAccepted, onError);
}
function reset(modifications) {
streams.craft.modifications.next({
history: modifications,
pointer: modifications.length - 1
});
}
function rebuild() {
const mods = streams.craft.modifications.value;
reset([]);
streams.craft.modifications.next(mods);
}
function runRequest(request) {
let op = services.operation.get(request.type);
if (!op) {
return Promise.reject(new Error(`unknown operation ${request.type}`));
}
let params = {};
let errors = [];
materializeParams(services, request.params, op.schema, params, errors);
if (errors.length) {
return Promise.reject(new CadError({
kind: CadError.KIND.INVALID_PARAMS,
userMessage: errors.map(err => `${err.path.join('.')}: ${err.message}`).join('\n')
}));
}
const result = op.run(params, ctx);
return result.then ? result : Promise.resolve(result);
}
function runOrGetPreRunResults(request) {
if (preRun !== null && preRun.request === request) {
const result = preRun.result;
preRun = null;
return Promise.resolve(result);
} else {
return runRequest(request);
}
}
services.craft = {
modify, modifyInHistoryAndStep, reset, runRequest, rebuild,
historyTravel: historyTravel(streams.craft.modifications)
};
let locked = false;
intercept(streams.craft.modifications, (curr, stream, next) => {
const prev = stream.value;
if (locked) {
console.error('concurrent modification');
}
locked = true;
let models;
let beginIndex;
if (isAdditiveChange(prev, curr)) {
beginIndex = prev.pointer + 1;
} else {
MObjectIdGenerator.reset();
beginIndex = 0;
streams.craft.models.next([]);
}
models = new Set(streams.craft.models.value);
let {history, pointer} = curr;
function runPromise(i) {
if (i > pointer) {
locked = false;
next(curr);
return;
}
let request = history[i];
const promise = runOrGetPreRunResults(request)
promise.then(({consumed, created}) => {
consumed.forEach(m => models.delete(m));
created.forEach(m => models.add(m));
streams.craft.models.next(Array.from(models).sort(m => m.id));
runPromise(i + 1);
}).catch(e => {
locked = false;
console.error(e);
//TODO: need to find a way to propagate the error to the wizard.
next({
...curr,
pointer: i-1
});
})
}
runPromise(beginIndex);
})
}
function isAdditiveChange({history:oldHistory, pointer:oldPointer}, {history, pointer}) {
if (pointer < oldPointer) {
return false;
}
for (let i = 0; i <= oldPointer; i++) {
let modCurr = history[i];
let modPrev = oldHistory[i];
if (modCurr !== modPrev) {
return false;
}
}
return true;
}
function historyTravel(modifications$) {
return {
setPointer: function(pointer, hints) {
let mod = modifications$.value;
if (pointer >= mod.history.length || pointer < -1) {
return;
}
modifications$.update(({history}) => ({history, pointer, hints}));
},
begin: function(hints) {
this.setPointer(-1, hints);
},
end: function(hints) {
this.setPointer(modifications$.value.history.length - 1, hints);
},
forward: function(hints) {
this.setPointer(modifications$.value.pointer + 1, hints);
},
backward: function (hints) {
this.setPointer(modifications$.value.pointer - 1, hints);
},
}
}

View file

@ -0,0 +1,263 @@
import {addModification, stepOverriding} from './craftHistoryUtils';
import {Emitter, state, StateStream, stream} from 'lstream';
import materializeParams from './materializeParams';
import CadError from '../../utils/errors';
import {MObject, MObjectIdGenerator} from '../model/mobject';
import {intercept} from "lstream/intercept";
import {CoreContext} from "context";
export function activate(ctx: CoreContext) {
const modifications$ = state({
history: [],
pointer: -1
});
const models$ = state<MObject[]>([]);
const update$ = stream<void>();
let preRun = null;
function modifyWithPreRun(request, modificationsUpdater, onAccepted, onError) {
runRequest(request).then(result => {
onAccepted();
preRun = {
request,
result
};
modificationsUpdater(request);
}).catch(error => {
console.error(error);
onError(error);
});
}
function modify(request, onAccepted, onError) {
modifyWithPreRun(request,
request => modifications$.update(modifications => addModification(modifications, request)), onAccepted, onError);
}
function modifyInHistoryAndStep(request, onAccepted, onError) {
modifyWithPreRun(request,
request => modifications$.update(modifications => stepOverriding(modifications, request)), onAccepted, onError);
}
function reset(modifications: OperationRequest[]) {
modifications$.next({
history: modifications,
pointer: modifications.length - 1
});
}
function rebuild() {
const mods = modifications$.value;
reset([]);
modifications$.next(mods);
}
function runRequest(request): Promise<OperationResult> {
let op = ctx.operationService.get(request.type);
if (!op) {
return Promise.reject(new Error(`unknown operation ${request.type}`));
}
let params = {};
let errors = [];
materializeParams(ctx, request.params, op.schema, params, errors);
if (errors.length) {
return Promise.reject(new CadError({
kind: CadError.KIND.INVALID_PARAMS,
userMessage: errors.map(err => `${err.path.join('.')}: ${err.message}`).join('\n')
}));
}
const result = op.run(params, ctx);
// @ts-ignore
return result.then ? result : Promise.resolve(result);
}
function runOrGetPreRunResults(request) {
if (preRun !== null && preRun.request === request) {
const result = preRun.result;
preRun = null;
return Promise.resolve(result);
} else {
return runRequest(request);
}
}
ctx.craftService = {
modify, modifyInHistoryAndStep, reset, rebuild, runRequest, runPipeline,
historyTravel: historyTravel(modifications$),
modifications$, models$, update$
};
// @ts-ignore
ctx.services.craft = ctx.craftService;
// @ts-ignore
ctx.streams.craft = {
modifications: modifications$,
models: models$,
update: update$
};
function runPipeline(history: OperationRequest[], beginIndex: number, endIndex: number): Promise<void> {
const models: Set<MObject> = new Set(models$.value);
return new Promise((resolve, reject) => {
function runPromise(i) {
if (i > endIndex) {
resolve();
return;
}
const request = history[i];
const promise = runOrGetPreRunResults(request);
promise.then(({consumed, created}) => {
consumed.forEach(m => models.delete(m));
created.forEach(m => models.add(m));
models$.next(Array.from(models).sort((m1, m2) => (m1.id||'').localeCompare(m2.id)));
runPromise(i + 1);
}).catch(error => {
reject({
failIndex: i,
error
});
})
}
runPromise(beginIndex);
});
}
let locked = false;
intercept(modifications$, (curr, stream, next) => {
if (locked) {
console.error('[CRAFT] concurrent modification');
}
locked = true;
const prev = stream.value;
let beginIndex;
if (isAdditiveChange(prev, curr)) {
beginIndex = prev.pointer + 1;
} else {
MObjectIdGenerator.reset();
beginIndex = 0;
models$.next([]);
}
let {history, pointer} = curr;
runPipeline(history, beginIndex, pointer)
.then(() => next(curr))
.finally(() => locked = false)
.catch(reason => {
console.error(reason.error);
//TODO: need to find a way to propagate the error to the wizard.
next({
...curr,
pointer: reason.failIndex
});
});
})
}
function isAdditiveChange({history:oldHistory, pointer:oldPointer}, {history, pointer}) {
if (pointer < oldPointer) {
return false;
}
for (let i = 0; i <= oldPointer; i++) {
let modCurr = history[i];
let modPrev = oldHistory[i];
if (modCurr !== modPrev) {
return false;
}
}
return true;
}
function historyTravel(modifications$) {
return {
setPointer: function(pointer, hints) {
let mod = modifications$.value;
if (pointer >= mod.history.length || pointer < -1) {
return;
}
modifications$.update(({history}) => ({history, pointer, hints}));
},
begin: function(hints) {
this.setPointer(-1, hints);
},
end: function(hints) {
this.setPointer(modifications$.value.history.length - 1, hints);
},
forward: function(hints) {
this.setPointer(modifications$.value.pointer + 1, hints);
},
backward: function (hints) {
this.setPointer(modifications$.value.pointer - 1, hints);
},
}
}
export interface OperationRequest {
type: string;
params: any;
}
export interface OperationResult {
consumed: MObject[];
created: MObject[];
}
interface CraftHistory {
history: OperationRequest[];
pointer: number;
}
interface CraftService {
modifications$: StateStream<CraftHistory>;
models$: StateStream<MObject[]>;
update$: Emitter<void>;
modify(request: OperationRequest, onAccepted: () => void, onError: () => Error);
modifyInHistoryAndStep(request: OperationRequest, onAccepted: () => void, onError: () => Error);
reset(modifications: OperationRequest[]);
rebuild(): void;
historyTravel: HistoryTravel;
runRequest(request: OperationRequest): Promise<OperationResult>;
runPipeline(history: OperationRequest[], beginIndex: number, endIndex: number): Promise<void>
}
interface HistoryTravel {
setPointer(pointer, hints:any);
begin(hints: any);
end(hints: any);
forward(hints: any);
backward(hints: any);
}
declare module 'context' {
interface CoreContext {
craftService: CraftService;
}
}

View file

@ -1,4 +1,4 @@
import {Matrix3, ORIGIN} from '../../../math/l3space'
import {Matrix3} from '../../../math/l3space'
import * as math from '../../../math/math'
import {enclose} from '../../../brep/brep-enclose'
import {BooleanOperation, combineShells} from '../booleanOperation'
@ -12,12 +12,13 @@ export function Cut(params, ctx) {
return doOperation(params, ctx, true);
}
export function doOperation(params, {cadRegistry, sketcherService}, cut) {
export function doOperation(params, ctx, cut) {
const {cadRegistry, sketchStorageService} = ctx;
const face = cadRegistry.findFace(params.face);
const solid = face.solid;
let sketch = sketcherService.readSketch(face.id);
if (!sketch) throw 'illegal state';
let sketch = sketchStorageService.readSketch(face.id);
if (!sketch) throw 'sketch not found for the face ' + face.id;
let vector = resolveExtrudeVector(cadRegistry, face, params, !cut);
const details = getEncloseDetails(params, sketch.fetchContours(), vector, face.csys, face.surface, !cut, false);

View file

@ -77,9 +77,9 @@ export function readSketchContour(contour, face) {
return path;
}
export function readSketch(face, request, sketcher) {
let sketch = sketcher.readSketch(face.id);
if (!sketch) throw 'illegal state';
export function readSketch(face, request, sketchStorageService) {
let sketch = sketchStorageService.readSketch(face.id);
if (!sketch) throw 'sketch not found for the face ' + face.id;
return sketch.fetchContours().map(c => readSketchContour(c, face));
}

View file

@ -91,9 +91,9 @@ function booleanBasedOperation(engineParams, params, impl) {
function cutExtrude(isCut, request) {
function createExtrudeCommand(request, {cadRegistry, sketcherService}, invert) {
function createExtrudeCommand(request, {cadRegistry, sketchStorageService}, invert) {
const face = cadRegistry.findFace(request.face);
const paths = readSketch(face, request, sketcherService);
const paths = readSketch(face, request, sketchStorageService);
return {
face,

View file

@ -41,9 +41,9 @@ export default function operationHandler(id, request, services) {
}
}
function createExtrudeCommand(request, {cadRegistry, sketcherService}, invert) {
function createExtrudeCommand(request, {cadRegistry, sketchStorageService}, invert) {
const face = cadRegistry.findFace(request.face);
const paths = readSketch(face, request, sketcherService);
const paths = readSketch(face, request, sketchStorageService);
return {
face,
@ -56,9 +56,9 @@ function createExtrudeCommand(request, {cadRegistry, sketcherService}, invert) {
};
}
function createRevolveCommand(request, {cadRegistry, sketcherService}) {
function createRevolveCommand(request, {cadRegistry, sketchStorageService}) {
const face = cadRegistry.findFace(request.face);
const paths = readSketch(face, request, sketcherService);
const paths = readSketch(face, request, sketchStorageService);
let pivot = cadRegistry.findSketchObject(request.axis).sketchPrimitive;
let tr = face.csys.outTransformation;

View file

@ -1,6 +1,6 @@
import {isEntityType} from './schemaUtils';
export default function materializeParams(services, params, schema, result, errors, parentPath) {
export default function materializeParams(ctx, params, schema, result, errors, parentPath) {
parentPath = parentPath || ROOT_PATH;
@ -19,7 +19,7 @@ export default function materializeParams(services, params, schema, result, erro
try {
const valueType = typeof value;
if (valueType === 'string') {
value = services.expressions.evaluateExpression(value);
value = ctx.expressionService.evaluateExpression(value);
} else if (valueType !== 'number') {
errors.push({path: [...parentPath, field], message: 'invalid value'});
}
@ -55,7 +55,7 @@ export default function materializeParams(services, params, schema, result, erro
if (!ref && !md.optional) {
errors.push({path: [...parentPath, field], message: 'required'});
}
let model = services.cadRegistry.findEntity(md.type, ref);
let model = ctx.cadRegistry.findEntity(md.type, ref);
if (!model) {
errors.push({path: [...parentPath, field], message: 'referrers to nonexistent ' + md.type});
}
@ -73,13 +73,13 @@ export default function materializeParams(services, params, schema, result, erro
if (md.itemType === 'object') {
value = value.map((item , i) => {
let itemResult = {};
materializeParams(services, item, md.schema, itemResult, errors, [...parentPath, i]);
materializeParams(ctx, item, md.schema, itemResult, errors, [...parentPath, i]);
return itemResult;
});
} else {
if (isEntityType(md.itemType)) {
value.forEach(ref => {
if (!services.cadRegistry.findEntity(md.itemType, ref)) {
if (!ctx.cadRegistry.findEntity(md.itemType, ref)) {
errors.push({path: [...parentPath, field], message: 'referrers to nonexistent ' + md.itemType});
}
})

View file

@ -3,18 +3,17 @@ import {state} from 'lstream';
import {IconType} from "react-icons";
import {isEntityType} from './schemaUtils';
import {ActionAppearance} from "../actions/actionSystemPlugin";
import {MObject} from "../model/mobject";
import {ApplicationContext} from "context";
import {ApplicationContext, CoreContext} from "context";
import {OperationResult} from "./craftPlugin";
export function activate(context) {
let {services} = context;
export function activate(ctx: ApplicationContext) {
context.streams.operation = {
registry: state({})
const registry$ = state({});
ctx.streams.operation = {
registry:registry$
};
let registry$ = context.streams.operation.registry;
function addOperation(descriptor, actions) {
let {id, label, info, icon, actionParams} = descriptor;
let appearance: ActionAppearance = {
@ -30,7 +29,7 @@ export function activate(context) {
let opAction = {
id: id,
appearance,
invoke: () => services.wizard.open(id),
invoke: () => ctx.services.wizard.open(id),
...actionParams
};
actions.push(opAction);
@ -47,10 +46,10 @@ export function activate(context) {
for (let op of operations) {
addOperation(op, actions);
}
services.action.registerActions(actions);
ctx.actionService.registerActions(actions);
}
function get(id) {
function get<T>(id: string): Operation<T> {
let op = registry$.value[id];
if (!op) {
throw `operation ${id} is not registered`;
@ -70,13 +69,13 @@ export function activate(context) {
return descriptor.run(request, opContext);
}
services.operation = {
ctx.operationService = {
registerOperations,
get,
handlers
};
context.operationService = services.operation;
ctx.services.operation = ctx.operationService;
}
export interface Operation<R> extends OperationDescriptor<R>{
@ -128,7 +127,7 @@ export interface OperationDescriptor<R> {
info: string;
icon: IconType | string;
actionParams?: any;
run: (request: R, opContext: OperationContext) => OperationResult;
run: (request: R, opContext: CoreContext) => OperationResult | Promise<OperationResult>;
paramsInfo: (params: R) => string,
previewGeomProvider: (params: R) => OperationGeometryProvider,
form: () => React.ReactNode,
@ -137,11 +136,11 @@ export interface OperationDescriptor<R> {
export interface OperationService {
registerOperations(descriptior: OperationDescriptor<any>[]);
get(operationId: string): Operation<any>;
get<T>(operationId: string): Operation<T>;
handlers: ((
id: string,
request: any,
opContext: OperationContext
opContext: CoreContext
) => void)[]
}
@ -149,19 +148,8 @@ export interface OperationGeometryProvider {
}
export interface OperationResult {
consumed: MObject[];
created: MObject[];
}
export interface OperationContext {
cadRegistry: any
}
declare module 'context' {
interface ApplicationContext {
interface CoreContext {
operationService: OperationService;
}

View file

@ -28,7 +28,7 @@
align-items: flex-end;
height: 100%;
overflow-x: hidden;
padding: 0 5px;
padding: 0 5px 0 10px;
}
.scroller {
padding: 0 5px;

View file

@ -57,14 +57,14 @@ export default class Wizard extends React.Component {
const error = this.props.error;
return <Window initWidth={250}
initLeft={left || 15}
title={title}
onClose={this.cancel}
onKeyDown={this.onKeyDown}
setFocus={this.focusFirstInput}
className='Wizard mid-typography'
data-operation-id={operation.id}
controlButtons={<>
initLeft={left || 15}
title={title}
onClose={this.cancel}
onKeyDown={this.onKeyDown}
setFocus={this.focusFirstInput}
className='Wizard mid-typography'
data-operation-id={operation.id}
controlButtons={<>
<WindowControlButton title='help' onClick={(e) => DocumentationTopic$.next({
topic: operation.id,
x: e.pageX + 40,
@ -88,6 +88,7 @@ export default class Wizard extends React.Component {
</span>}
{error.code && <div className={ls.errorCode}>{error.code}</div>}
{error.userMessage && <div className={ls.userErrorMessage}>{error.userMessage}</div>}
{!error.userMessage && <div>internal error processing operation, check the log</div>}
</div>}
</Stack>
</Window>;

View file

@ -75,7 +75,7 @@ export function activate(ctx) {
let materializedWorkingRequest$ = workingRequest$.map(req => {
let params = {};
let errors = [];
materializeParams(ctx.services, req.params, operation.schema, params, errors, []);
materializeParams(ctx, req.params, operation.schema, params, errors, []);
if (errors.length !== 0) {
return INVALID_REQUEST;
}

View file

@ -388,12 +388,12 @@ const DebugActions = [
label: 'generate unit test',
info: 'it will generate a unit code code containing sketches and operation sequence and output it to terminal',
},
invoke: ({bus, services: {project, storage, sketcher, cadRegistry}}) => {
invoke: ({bus, services: {project, storage, sketchStorageService, cadRegistry}}) => {
const pt = ({x, y}) => [x, y];
let sketches = sketcher.getAllSketches().reduce((sketches, {id, url}) => {
let sketch = sketcher.readSketch(id).getAllObjects().reduce((byType, obj) => {
let sketches = sketchStorageService.getAllSketches().reduce((sketches, {id, url}) => {
let sketch = sketchStorageService.readSketch(id).getAllObjects().reduce((byType, obj) => {
let type = obj.constructor.name;

View file

@ -7,7 +7,7 @@ const CONTRIBUTED_COMPONENTS$ = state([]);
export function ContributedComponents() {
const contrib = useStream(CONTRIBUTED_COMPONENTS$);
return contrib.map((Comp, i) => <Scope><Comp key={i} /></Scope> );
return contrib.map((Comp, i) => <Scope key={i}><Comp /></Scope> );
}
export function contributeComponent(comp) {

View file

@ -9,7 +9,7 @@ import ToolButton from 'ui/components/ToolButton';
@connect(state => state.ui.floatViews.map(views => ({views})))
@mapContext(ctx => ({
getDescriptor: ctx.services.ui.getFloatView,
initialView: ctx.services.project.hints.FloatView || null
initialView: ctx.projectService.hints.FloatView || null
}))
export default class FloatView extends React.Component {

View file

@ -9,7 +9,7 @@ export function activate(ctx) {
}
function stlAscii() {
exportTextData(toStlAsciiString(), ctx.services.project.id + ".stl");
exportTextData(toStlAsciiString(), ctx.projectService.id + ".stl");
}
function imagePng() {
@ -23,7 +23,7 @@ export function activate(ctx) {
let link = document.getElementById("downloader");
link.href = renderer.domElement.toDataURL('image/png');
link.download = ctx.services.project.id + "-snapshot.png";
link.download = ctx.projectService.id + "-snapshot.png";
link.click();
renderer.preserveDrawingBuffer = false;
@ -34,7 +34,7 @@ export function activate(ctx) {
}
function nativeFormat() {
ctx.services.projectManager.exportProject(ctx.services.project.id);
ctx.services.projectManager.exportProject(ctx.projectService.id);
}
ctx.services.export = {

View file

@ -1,92 +0,0 @@
import React, {Fragment} from 'react';
import ls from './Expressions.less';
import cmn from 'ui/styles/common.less';
import ToolButton from 'ui/components/ToolButton';
import Fa from 'ui/components/Fa';
import Row from 'ui/components/Row';
import connect from 'ui/connect';
import mapContext from 'ui/mapContext';
import bind from 'ui/bind';
import cx from 'classnames';
import {actionDecorator} from '../actions/actionDecorators';
import {combine} from 'lstream';
import Folder from 'ui/components/Folder';
import Stack from '../../../../modules/ui/components/Stack';
@connect(streams => combine(streams.expressions.synced, streams.expressions.errors)
.map(([synced, errors])=> ({synced, errors})))
@mapContext(ctx => ({
reevaluateExpressions: ctx.services.expressions.reevaluateExpressions
}))
export default class Expressions extends React.Component {
state = {
activeTab: 'Script'
};
render() {
let {errors, synced, table, reevaluateExpressions} = this.props;
const tabBtn = (name, icon) => {
return <ToolButton onClick={() => this.setState({activeTab: name})} pressed={this.state.activeTab === name}>{icon} {name}</ToolButton>;
};
return <div className={ls.root}>
<Row className={ls.switcher}>
{tabBtn('Script', <Fa fw icon='pencil' />)}
{tabBtn('Table', <Fa fw icon='table' />)}
{errors.length > 0 && <span><Fa icon='warning' className={cx(cmn.dangerColor, cmn.inlineBlock)} /></span>}
{!synced && <ReevaluateActionButton type='accent' className={cmn.floatRight}><Fa fw icon='check'/></ReevaluateActionButton>}
</Row>
<div className={ls.workingArea}>
{this.state.activeTab === 'Script' && <Script reevaluateExpressions={reevaluateExpressions}/>}
{this.state.activeTab === 'Table' && <VarTable table={table} errors={errors}/>}
{errors.length > 0 && <Folder title={<Fragment><Fa icon='warning' className={cx(cmn.dangerColor)} /> Script Errors</Fragment>}>
<Stack>
{errors.map(err => <div key={err.line}>
line {err.line + 1}: {err.message}
</div>)}
</Stack>
</Folder>}
</div>
</div>;
}
}
const ReevaluateActionButton = actionDecorator('expressionsUpdateTable')(ToolButton);
const Script = bind(streams => streams.expressions.script)(
function Script({value, onChange, reevaluateExpressions}) {
return <textarea placeholder='for example: A = 50'
className={ls.script}
value={value}
onChange={e => onChange(e.target.value)}
onBlur={e => reevaluateExpressions(e.target.value)} />
}
);
const VarTable = bind(streams => streams.expressions.list)(
function VarTable({value}) {
return <table className={cx(cmn.fullWidth, 'striped', 'delineated')}>
<thead>
<tr>
<th>Name</th>
<th>Value</th>
</tr>
</thead>
<tbody>
{value.map(({name, value}, i) => <tr key={i}>
<td>{name}</td>
<td>{value}</td>
</tr>)}
</tbody>
</table>
}
);

View file

@ -0,0 +1,89 @@
import React, {Fragment, useContext, useState} from 'react';
import ls from './Expressions.less';
import cmn from 'ui/styles/common.less';
import ToolButton from 'ui/components/ToolButton';
import Fa from 'ui/components/Fa';
import Row from 'ui/components/Row';
import bind from 'ui/bind';
import cx from 'classnames';
import {actionDecorator} from '../actions/actionDecorators';
import Folder from 'ui/components/Folder';
import Stack from 'ui/components/Stack';
import {AppContext} from "../dom/components/AppContext";
import {useStream} from "ui/effects";
export default function Expressions() {
const [activeTab, setActiveTab] = useState('Script');
const ctx = useContext(AppContext);
const synced = useStream(ctx => ctx.expressionService.synced$);
const errors = useStream(ctx => ctx.expressionService.errors$);
const reevaluateExpressions = ctx.expressionService.reevaluateExpressions;
const tabBtn = (name, icon) => {
// @ts-ignore
return <ToolButton type='' onClick={() => setActiveTab(name)} pressed={activeTab === name}>{icon} {name}</ToolButton>;
};
return <div className={ls.root}>
<Row className={ls.switcher}>
{tabBtn('Script', <Fa fw icon='pencil' />)}
{tabBtn('Table', <Fa fw icon='table' />)}
{errors.length > 0 && <span><Fa icon='warning' className={cx(cmn.dangerColor, cmn.inlineBlock)} /></span>}
{!synced && <ReevaluateActionButton type='accent' className={cmn.floatRight}><Fa fw icon='check'/></ReevaluateActionButton>}
</Row>
<div className={ls.workingArea}>
{activeTab === 'Script' && <Script reevaluateExpressions={reevaluateExpressions}/>}
{activeTab === 'Table' && <VarTable errors={errors}/>}
{errors.length > 0 && <Folder title={<Fragment><Fa icon='warning' className={cx(cmn.dangerColor)} /> Script Errors</Fragment>}>
<Stack>
{errors.map(err => <div key={err.line}>
line {err.line + 1}: {err.message}
</div>)}
</Stack>
</Folder>}
</div>
</div>;
}
const ReevaluateActionButton = actionDecorator('expressionsUpdateTable')(ToolButton);
const Script = bind(ctx => ctx.expressionService.script$)(
function Script({value, onChange, reevaluateExpressions}) {
return <textarea placeholder='for example: A = 50'
className={ls.script}
value={value}
onChange={e => onChange(e.target.value)}
onBlur={e => reevaluateExpressions(e.target.value)} />
}
);
const VarTable = bind(ctx => ctx.expressionService.list$)(
function VarTable({value}) {
return <table className={cx(cmn.fullWidth, 'striped', 'delineated')}>
<thead>
<tr>
<th>Name</th>
<th>Value</th>
</tr>
</thead>
<tbody>
{value.map(({name, value}, i) => <tr key={i}>
<td>{name}</td>
<td>{value}</td>
</tr>)}
</tbody>
</table>
}
);

View file

@ -1,35 +1,34 @@
import {merge, state} from 'lstream';
import {merge, state, StateStream, Stream} from 'lstream';
import {indexArray} from 'gems/iterables';
import {NOOP} from 'gems/func';
import {CoreContext} from "context";
export function defineStreams(ctx) {
const script = state('');
const list = state([]);
const table = list.map(varList => indexArray(varList, i => i.name, i => i.value)).remember();
const synced = merge(script.map(() => false), list.map(() => true));
ctx.streams.expressions = {
script, list, table, synced,
errors: state([])
};
}
export function activate(ctx) {
let _evaluateExpression = NOOP;
export function activate(ctx: CoreContext) {
let _evaluateExpression: (string) => any = () => {};
const script$ = state('');
const list$ = state([]);
const table$ = list$.map(varList => indexArray(varList, i => i.name, i => i.value)).remember();
const synced$ = merge(script$.map(() => false), list$.map(() => true));
const errors$ = state([]);
function reevaluateExpressions() {
let {varList, errors, evaluateExpression} = rebuildVariableTable(ctx.streams.expressions.script.value);
ctx.streams.expressions.list.next(varList);
ctx.streams.expressions.errors.next(errors);
let {varList, errors, evaluateExpression} = rebuildVariableTable(script$.value);
list$.next(varList);
errors$.next(errors);
_evaluateExpression = evaluateExpression;
}
function load(script) {
ctx.streams.expressions.script.next(script);
script$.next(script);
reevaluateExpressions();
}
function evaluateExpression(expr) {
if (typeof expr === 'number') {
return expr;
}
let value = ctx.streams.expressions.table.value[expr];
let value = table$.value[expr];
if (value === undefined) {
value = parseFloat(expr);
if (isNaN(value)) {
@ -38,12 +37,9 @@ export function activate(ctx) {
}
return value;
}
ctx.services.expressions = {
reevaluateExpressions, load, evaluateExpression,
signature: ''
};
ctx.streams.expressions.table.attach(() => ctx.services.expressions.signature = Date.now() + '');
ctx.services.action.registerAction({
ctx.actionService.registerAction({
id: 'expressionsUpdateTable',
appearance: {
info: 'reevaluate expression script (happens automatically on script focus lost)',
@ -52,7 +48,15 @@ export function activate(ctx) {
invoke: ({services}) => {
services.extension.reevaluateExpressions();
}
})
});
ctx.expressionService = {
script$, list$, table$, synced$, errors$,
reevaluateExpressions, load, evaluateExpression,
signature: ''
};
table$.attach(() => ctx.expressionService.signature = Date.now() + '');
}
function rebuildVariableTable(script) {
@ -85,3 +89,35 @@ function rebuildVariableTable(script) {
return {varList, errors, evaluateExpression};
}
export interface ExpressionService {
script$: StateStream<string>;
list$: StateStream<string[]>,
table$: Stream<{[key:string]: string}>,
synced$: Stream<any>,
errors$: Stream<ExpressionError[]>
reevaluateExpressions(): void;
load(script: string):void;
evaluateExpression(string): any;
signature: string;
}
export interface ExpressionError {
message: string;
line: string;
}
declare module 'context' {
interface CoreContext {
expressionService: ExpressionService;
}
}

View file

@ -1,6 +1,7 @@
import {state} from 'lstream';
export function activate({streams, services}) {
export function activate(ctx) {
const {streams, services} = ctx;
const asyncInitializingJobs = new Set();
const startTime = performance.now();
streams.lifecycle = {
@ -24,7 +25,7 @@ export function activate({streams, services}) {
asyncInitializingJobs.size === 0) {
services.extension.activateAllExtensions();
services.project.load();
ctx.projectService.load();
streams.lifecycle.projectLoaded.value = true;
const onLoadTime = performance.now();
console.log("project loaded, took: " + ((onLoadTime - startTime) / 1000).toFixed(2) + ' sec');

View file

@ -16,27 +16,30 @@ import * as OperationPlugin from '../craft/operationPlugin';
import * as ExtensionsPlugin from '../craft/extensionsPlugin';
import * as CadRegistryPlugin from '../craft/cadRegistryPlugin';
import * as CraftPlugin from '../craft/craftPlugin';
import * as RemotePartsPlugin from '../partImport/remotePartsPlugin';
import * as CraftUiPlugin from '../craft/craftUiPlugin';
import * as StoragePlugin from '../storage/storagePlugin';
import * as ProjectPlugin from '../projectPlugin';
import * as ProjectManagerPlugin from '../projectManager/projectManagerPlugin';
import * as SketcherPlugin from '../sketch/sketcherPlugin';
import * as SketcherStoragePlugin from '../sketch/sketchStoragePlugin';
import * as ExportPlugin from '../exportPlugin';
import * as ExposurePlugin from '../exposure/exposurePlugin';
import * as ViewSyncPlugin from '../scene/viewSyncPlugin';
import * as EntityContextPlugin from '../scene/entityContextPlugin';
import * as E0Plugin from '../craft/e0/e0Plugin';
import PartModellerPlugins from '../part/partModelerPlugins';
import context from 'context';
import startReact from "../dom/startReact";
import * as UIConfigPlugin from "../part/uiConfigPlugin";
import * as DebugPlugin from "../debugPlugin";
import * as ExpressionsPlugin from "../expressions/expressionsPlugin";
import * as PartOperationsPlugin from "../part/partOperationsPlugin";
export default function startApplication(callback) {
let applicationPlugins = PartModellerPlugins;
let preUIPlugins = [
LifecyclePlugin,
ProjectPlugin,
@ -46,9 +49,11 @@ export default function startApplication(callback) {
UiPlugin,
MenuPlugin,
KeyboardPlugin,
ExpressionsPlugin,
OperationPlugin,
CraftPlugin,
ExtensionsPlugin,
SketcherStoragePlugin,
WizardPlugin,
PreviewPlugin,
CraftUiPlugin,
@ -67,7 +72,10 @@ export default function startApplication(callback) {
PickControlPlugin,
EntityContextPlugin,
SketcherPlugin,
...applicationPlugins,
UIConfigPlugin,
DebugPlugin,
PartOperationsPlugin,
RemotePartsPlugin,
ViewSyncPlugin,
WizardSelectionPlugin
];

View file

@ -1,11 +1,17 @@
import {MObject, MObjectIdGenerator} from './mobject';
import CSys from "../../math/csys";
import Vector from "math/vector";
export class MDatum extends MObject {
static TYPE = 'datum';
csys: CSys;
xAxis: MDatumAxis;
yAxis: MDatumAxis;
zAxis: MDatumAxis;
constructor(csys) {
super(MDatum.TYPE, 'D:' + MObjectIdGenerator.next(MDatum.TYPE));
super(MDatum.TYPE, MObjectIdGenerator.next(MDatum.TYPE, 'D'));
this.csys = csys;
this.xAxis = new MDatumAxis(this.id + '/X', this.csys.origin, this.csys.x);
this.yAxis = new MDatumAxis(this.id + '/Y', this.csys.origin, this.csys.y);
@ -20,11 +26,20 @@ export class MDatum extends MObject {
default: return null;
}
}
traverse(callback: (obj: MObject) => {}) {
super.traverse(callback);
this.xAxis.traverse(callback);
this.yAxis.traverse(callback);
this.zAxis.traverse(callback);
}
}
export class MDatumAxis extends MObject {
static TYPE = 'datumAxis';
origin: Vector;
dir: Vector;
constructor(id, origin, dir) {
super(MDatumAxis.TYPE, id);

View file

@ -1,8 +1,11 @@
import {MObject} from './mobject';
import {MBrepShell, MShell} from "./mshell";
export class MEdge extends MObject {
static TYPE = 'edge';
shell: MBrepShell;
brepEdge: any;
constructor(id, shell, brepEdge) {
super(MEdge.TYPE, id);
@ -22,5 +25,4 @@ export class MEdge extends MObject {
}
return out;
}
}

View file

@ -6,12 +6,26 @@ import {EMPTY_ARRAY} from 'gems/iterables';
import CSys from '../../math/csys';
import {MSketchLoop} from './mloop';
import {ProductionInfo} from './productionInfo';
import {MBrepShell, MShell} from "./mshell";
export class MFace extends MObject {
static TYPE = 'face';
shell: MShell;
surface: any;
sketchObjects: MSketchObject[];
sketchLoops: MSketchLoop[];
sketch: any;
brepFace: any;
constructor(id, shell, surface, csys) {
private _csys: any;
private w: number;
private _basis: [Vector, Vector, Vector];
private _sketchToWorldTransformation: any;
private _worldToSketchTransformation: any;
private _productionInfo: any;
constructor(id, shell, surface, csys?) {
super(MFace.TYPE, id);
this.shell = shell;
this.surface = surface;
@ -100,8 +114,7 @@ export class MFace extends MObject {
addSketchObjects(sketch.constructionSegments);
addSketchObjects(sketch.connections);
addSketchObjects(sketch.loops);
const index = new Map();
this.sketchObjects.forEach(o => index.set(o.sketchPrimitive, o));
@ -146,6 +159,13 @@ export class MFace extends MObject {
}
return this._productionInfo;
}
traverse(callback: (obj: MObject) => {}) {
callback(this);
this.sketchObjects.forEach(callback);
this.sketchLoops.forEach(callback);
}
}
export class MBrepFace extends MFace {
@ -159,7 +179,7 @@ export class MBrepFace extends MFace {
get edges() {
let out = [];
for (let he of this.brepFace.edges) {
let edge = this.shell.brepRegistry.get(he.edge);
let edge = (this.shell as MBrepShell).brepRegistry.get(he.edge);
if (edge) {
out.push(edge);
}

View file

@ -1,4 +1,6 @@
import {MObject} from './mobject';
import {MFace} from "./mface";
import {MSketchObject} from "./msketchObject";
export class MLoop extends MObject {
@ -11,6 +13,9 @@ export class MLoop extends MObject {
}
export class MSketchLoop extends MLoop {
face: MFace;
sketchObjects: MSketchObject[];
contour: any;
constructor(id, face, sketchObjects, contour) {
super(id);

View file

@ -1,5 +1,5 @@
export class MObject {
export abstract class MObject {
TYPE: string;
@ -10,15 +10,43 @@ export class MObject {
this.TYPE = TYPE;
this.id = id;
}
traverse(callback: (obj: MObject) => {}) {
callback(this);
}
}
const ID_REGISTRY = new Map();
export const MObjectIdGenerator = {
next: entityType => {
const id = ID_REGISTRY.get(entityType) || 0;
ID_REGISTRY.set(entityType, id + 1);
return id;
contexts: [{
namespace: '',
ID_REGISTRY: new Map()
}],
get context() {
return this.contexts[this.contexts.length - 1];
},
reset: () => ID_REGISTRY.clear()
next(entityType, prefix) {
const context = this.context;
const id = context.ID_REGISTRY.get(entityType) || 0;
context.ID_REGISTRY.set(entityType, id + 1);
return (context.namespace && '|') + prefix + ':' + id;
},
reset() {
this.context.ID_REGISTRY.clear()
},
pushContext(namespace: string) {
this.contexts.push({
namespace,
ID_REGISTRY: new Map()
})
},
popContext() {
this.contexts.pop();
}
};

View file

@ -2,6 +2,8 @@ import {MShell} from './mshell';
import {MFace} from './mface';
export class MOpenFaceShell extends MShell {
private surfacePrototype: any;
constructor(surfacePrototype, csys) {
super();

View file

@ -7,19 +7,31 @@ import CSys from '../../math/csys';
export class MShell extends MObject {
static TYPE = 'shell';
csys: CSys;
constructor() {
super(MShell.TYPE, 'S:' + MObjectIdGenerator.next(MShell.TYPE))
super(MShell.TYPE, MObjectIdGenerator.next(MShell.TYPE, 'S'))
}
shell;
faces = [];
edges = [];
vertices = [];
traverse(callback: (obj: MObject) => {}) {
callback(this);
this.faces.forEach(callback);
this.edges.forEach(callback);
this.vertices.forEach(callback);
}
}
export class MBrepShell extends MShell {
brepShell: any;
csys: CSys;
brepRegistry: Map<string, MObject>;
constructor(shell, csys) {
super();
this.brepShell = shell;
@ -48,4 +60,5 @@ export class MBrepShell extends MShell {
this.brepRegistry.set(brepVertex, mVertex);
}
}
}

View file

@ -1,8 +1,12 @@
import {MObject} from './mobject';
import {MFace} from "./mface";
export class MSketchObject extends MObject {
static TYPE = 'sketchObject';
face: MFace;
sketchPrimitive: any;
construction: boolean;
constructor(face, sketchPrimitive) {
super(MSketchObject.TYPE, sketchPrimitive.id);

View file

@ -1,8 +1,11 @@
import {MObject} from './mobject';
import {MShell} from "./mshell";
export class MVertex extends MObject {
static TYPE = 'vertex';
shell: MShell;
brepVertex: any;
constructor(id, shell, brepVertex) {
super(MVertex.TYPE, id);

View file

@ -1,13 +0,0 @@
import * as UIConfigPlugin from './uiConfigPlugin';
import * as PartOperationsPlugin from './partOperationsPlugin';
import * as PartImportPlugin from '../partImport/partImportPlugin';
import * as DebugPlugin from '../debugPlugin';
import * as ExpressionsPlugin from '../expressions/expressionsPlugin';
export default [
UIConfigPlugin,
DebugPlugin,
ExpressionsPlugin,
PartOperationsPlugin,
PartImportPlugin
];

View file

@ -6,7 +6,7 @@ import {PartRefField} from "../ui/PartRefControl";
export function ImportPartForm() {
return <Group>
<PartRefField name='part' openIfEmpty={true}/>
<PartRefField name='partRef' label='part' openIfEmpty={true}/>
<EntityList name='datum' entity='datum' />
</Group>;
}

View file

@ -1,10 +1,9 @@
import {GrCloudDownload} from "react-icons/gr";
import {ImportPartForm} from "./ImportPartForm";
import importPartSchema from "./importPartSchema";
import {OperationDescriptor, OperationResult} from "../../craft/operationPlugin";
import CSys from "../../../math/csys";
import {MDatum} from "../../model/mdatum";
import {OperationDescriptor} from "../../craft/operationPlugin";
import {ApplicationContext} from "context";
import {OperationResult} from "../../craft/craftPlugin";
export interface ImportPartOperationParams {
partRef: string,
@ -26,28 +25,31 @@ export const ImportPartOperation: OperationDescriptor<ImportPartOperationParams>
};
function runImportOperation(params: ImportPartOperationParams, ctx: ApplicationContext): OperationResult {
function runImportOperation(params: ImportPartOperationParams, ctx: ApplicationContext): Promise<OperationResult> {
const {cadRegistry, partImportService} = ctx;
const {cadRegistry, remotePartsService} = ctx;
let mDatum = params.datum && cadRegistry.findDatum(params.datum);
const res = {
consumed: [],
created: []
};
// partImportService.resolvePartReference(params.);
return remotePartsService.resolvePartReference(params.partRef).then(parts => {
if (mDatum) {
parts.forEach(part => res.created.push(part));
if (mDatum) {
if (params.consumeDatum) {
res.consumed.push(mDatum);
}
if (params.consumeDatum) {
res.consumed.push(mDatum);
}
}
return res;
});
return res;
}

View file

@ -1,16 +0,0 @@
import {PartsCatalog} from "./partImportPlugin";
import {GitHubRepoRepository} from "../repository/GitHubRepoRepository";
import {GrCubes} from "react-icons/gr";
export const WEB_CAD_ORG_COMMONS: PartsCatalog = Object.freeze({
id: 'web-cad.org',
name: 'Commons Parts',
description: 'Public parts repository by web-cad.org',
icon: GrCubes,
repo: new GitHubRepoRepository('xibyte/web-cad', 'master'),
metadataPath: 'commons.catalog.json',
partsPath: 'parts'
});

View file

@ -1,90 +0,0 @@
import {ApplicationContext} from "context";
import {Repository} from "../repository/repository";
import {IconType} from "react-icons";
import {WEB_CAD_ORG_COMMONS} from "./partCatalogConfig";
import {Emitter, stream} from "lstream";
import {ShowDialogRequest} from "ui/showDialogRequest";
import {CatalogPartChooser} from "./ui/CatalogPartChooser";
import {ImportPartOperation} from "./importPartOperation/importPartOperation";
export function activate(ctx: ApplicationContext) {
ctx.domService.contributeComponent(CatalogPartChooser);
ctx.operationService.registerOperations([ImportPartOperation]);
function loadDefinedCatalogs(): Promise<[CatalogCategory, PartsCatalog][]> {
return Promise.all(ctx.partImportService.partCatalogs.map(descriptor => loadCatalog(descriptor).then(entry => ([entry, descriptor] as [CatalogCategory, PartsCatalog])) ));
}
ctx.partImportService = {
choosePartRequest$: stream(),
partCatalogs: [
WEB_CAD_ORG_COMMONS
],
readPartResource,
loadDefinedCatalogs
};
}
function loadCatalog(descriptor: PartsCatalog): Promise<CatalogCategory> {
return descriptor.repo.get(descriptor.metadataPath)
.then(r => r.json())
.then(catalog => catalog as CatalogCategory);
}
export interface PartsCatalog {
id: string;
name: string;
description: string;
icon: IconType,
repo: Repository;
partsPath: string;
metadataPath: string;
}
function readPartResource(catalog: PartsCatalog, partId: string, resourceName: string): Promise<Response> {
return catalog.repo.get(catalog.partsPath + '/' + partId + '/' + resourceName);
}
export interface CatalogEntry {
readonly name: string;
readonly type: string;
}
export interface CatalogPart extends CatalogEntry {
readonly id: string;
}
export interface CatalogCategory extends CatalogEntry {
entries: CatalogEntry[];
}
export interface PartImportService {
choosePartRequest$: Emitter<ShowDialogRequest<void, CatalogPart>>;
partCatalogs: PartsCatalog[];
readPartResource: (catalog: PartsCatalog, partId: string, resourceName: string) => Promise<Response>;
loadDefinedCatalogs: () => Promise<[CatalogCategory, PartsCatalog][]>
}
declare module 'context' {
interface ApplicationContext {
partImportService: PartImportService;
}
}

View file

@ -0,0 +1,19 @@
import {Repository} from "../repository/repository";
export class PartRepository {
readonly id: string;
readonly repo: Repository;
readonly partsPath: string;
constructor(id: string, repo: Repository, partsPath: string) {
this.id = id;
this.repo = repo;
this.partsPath = partsPath;
}
readPartResource = (partId: string, resourceName: string) => {
return this.repo.get(this.partsPath + '/' + partId + '/' + partId + '.' + resourceName);
}
}

View file

@ -0,0 +1,19 @@
import {PartsCatalog} from "./remotePartsPlugin";
import {GitHubRepoRepository} from "../repository/GitHubRepoRepository";
import {GrCubes} from "react-icons/gr";
import {PartRepository} from "./partRepository";
const WEB_CAD_ORG_REPO = new GitHubRepoRepository('xibyte/web-cad', 'master');
export const WEB_CAD_ORG_COMMONS_CATALOG: PartsCatalog = Object.freeze({
id: 'web-cad.org',
name: 'Commons Parts',
description: 'Public parts repository by web-cad.org',
icon: GrCubes,
repo: WEB_CAD_ORG_REPO,
metadataPath: 'commons.catalog.json',
});
export const WEB_CAD_ORG_PARTS_REPO: PartRepository = new PartRepository('web-cad.org', WEB_CAD_ORG_REPO, 'parts');

View file

@ -0,0 +1,188 @@
import {ApplicationContext, CoreContext} from "context";
import {Repository} from "../repository/repository";
import {IconType} from "react-icons";
import {Emitter, stream} from "lstream";
import {ShowDialogRequest} from "ui/showDialogRequest";
import {CatalogPartChooser} from "./ui/CatalogPartChooser";
import {ImportPartOperation} from "./importPartOperation/importPartOperation";
import {MObject, MObjectIdGenerator} from "../model/mobject";
import {WEB_CAD_ORG_PARTS_REPO, WEB_CAD_ORG_COMMONS_CATALOG} from "./remotePartsConfig";
import {indexById} from "gems/iterables";
import {ModelBundle} from "../projectManager/projectManagerPlugin";
import {PartRepository} from "./partRepository";
import {initProjectService} from "../projectPlugin";
import {activate as activateCraftPlugin} from '../craft/craftPlugin';
import {activate as activateExpressionsPlugin} from '../expressions/expressionsPlugin';
import {activate as activateCadRegistryPlugin} from '../craft/cadRegistryPlugin';
import {activate as activateStoragePlugin} from '../storage/storagePlugin';
import {activate as activateSketchStoragePlugin} from '../sketch/sketchStoragePlugin';
export function activate(ctx: ApplicationContext) {
ctx.domService.contributeComponent(CatalogPartChooser);
ctx.operationService.registerOperations([ImportPartOperation]);
function loadDefinedCatalogs(): Promise<[CatalogCategory, PartsCatalog][]> {
return Promise.all(ctx.remotePartsService.partCatalogs.map(descriptor => loadCatalog(descriptor).then(entry => ([entry, descriptor] as [CatalogCategory, PartsCatalog])) ));
}
async function resolvePartReference(partRef: string): Promise<MObject[]> {
const splitIdx = partRef.indexOf('/');
if (splitIdx !== -1) {
const partRepoId = partRef.substring(0, splitIdx);
const partId = partRef.substring(splitIdx + 1);
const partRepository = ctx.remotePartsService.partRepositories[partRepoId];
if (!partRepository) {
throw "Can't resolve reference to part repository " + partRepoId;
}
try {
const bundle: ModelBundle = await partRepository.readPartResource(partId, 'model.json').then(res => res.json());
ctx.projectManager.importBundle(partRef, bundle);
} catch (e) {
if (!ctx.projectManager.exists(partRef)) {
throw e;
}
console.error(e);
console.warn('cannot load remote part ' + partRef + ', using cached version');
}
}
// ctx.craftService.pushContext(partRef);
const projectModel = ctx.projectManager.loadExternalProject(partRef);
const evalContext: CoreContext = {
// @ts-ignore add to the core context
craftEngine: ctx.services.craftEngine,
actionService: ctx.actionService,
operationService: ctx.operationService,
sketchStorageService: undefined,
storageService: undefined,
craftService: undefined,
expressionService: undefined,
projectService: undefined,
// @ts-ignore
services: {
},
streams: {}
};
initProjectService(evalContext, partRef, {});
activateStoragePlugin(evalContext);
activateSketchStoragePlugin(evalContext);
activateExpressionsPlugin(evalContext);
activateCraftPlugin(evalContext);
// @ts-ignore
activateCadRegistryPlugin(evalContext);
// initProject(evalContext, partRef, {});
evalContext.expressionService.load(projectModel.expressions);
try {
MObjectIdGenerator.pushContext('');
await evalContext.craftService.runPipeline(projectModel.history, 0, projectModel.history.length - 1);
} finally {
MObjectIdGenerator.popContext();
}
const subSetId = MObjectIdGenerator.next(partRef, partRef);
const models = evalContext.craftService.models$.value;
models.forEach(model => {
model.traverse(m => m.id = subSetId + ':' + m.id);
});
return models;
}
ctx.remotePartsService = {
choosePartRequest$: stream(),
partCatalogs: [
WEB_CAD_ORG_COMMONS_CATALOG
],
partRepositories: indexById([
WEB_CAD_ORG_PARTS_REPO
]),
resolvePartReference,
loadDefinedCatalogs
};
}
function loadCatalog(descriptor: PartsCatalog): Promise<CatalogCategory> {
return descriptor.repo.get(descriptor.metadataPath)
.then(r => r.json())
.then(catalog => catalog as CatalogCategory);
}
export interface PartsCatalog {
id: string;
name: string;
description: string;
icon: IconType,
repo: Repository;
metadataPath: string;
}
export interface CatalogEntry {
readonly name: string;
readonly type: string;
}
export interface CatalogPart extends CatalogEntry {
readonly id: string;
}
export interface CatalogCategory extends CatalogEntry {
entries: CatalogEntry[];
}
export class RemotePart {
readonly id: string;
readonly name: string;
readonly partRepo: PartRepository;
constructor(id: string, name: string, partRepo: PartRepository) {
this.id = id;
this.name = name;
this.partRepo = partRepo;
}
readPartResource = (resourceName: string) => {
return this.partRepo.readPartResource(this.id, resourceName);
}
}
export interface RemotePartsService {
choosePartRequest$: Emitter<ShowDialogRequest<void, CatalogPart>>;
partCatalogs: PartsCatalog[];
partRepositories: {
[id: string]: PartRepository
}
loadDefinedCatalogs: () => Promise<[CatalogCategory, PartsCatalog][]>
resolvePartReference(partId: string): Promise<MObject[]>;
}
declare module 'context' {
interface ApplicationContext {
remotePartsService: RemotePartsService;
}
}

View file

@ -10,33 +10,43 @@ import theme from "ui/styles/theme";
export function CatalogPartChooser() {
const [req, setReq] = useStreamWithUpdater(ctx => ctx.partImportService.choosePartRequest$);
const [req, setReq] = useStreamWithUpdater(ctx => ctx.remotePartsService.choosePartRequest$);
const ctx = useContext(AppContext);
const [chosen, setChosen] = useState(null);
const loader = useDataLoader('parts', () => ctx.remotePartsService.loadDefinedCatalogs());
const loader = useDataLoader('parts', () => ctx.partImportService.loadDefinedCatalogs());
if (!req) {
return null;
}
const close = () => {
setReq(null);
req.onDone(null);
};
if (!req) {
return null;
}
const partChosen = part => {
setReq(null);
req.onDone(part);
};
return <Dialog initWidth={800} initHeight={600} centerScreen={req.centerScreen} initLeft={req.x} initTop={req.y}
title='PART CATALOG'
enableResize={true}
onClose={close}
cancelText='Close'
className='part-catalog-chooser'
>
<WhenDataReady loader={loader}>
{catalogs => catalogs.map(([partCatalog, descriptor]) => {
const Icon = descriptor.icon || GrCubes;
return <PartCatalog root={partCatalog} initCollapsed={false} name={descriptor.name + descriptor.description} icon={<Icon color={theme.onColorHighlightVariantPink}/>} />
{catalogs => catalogs.map(([entry, partCatalog]) => {
const Icon = partCatalog.icon || GrCubes;
return <PartCatalog key={partCatalog.id}
root={entry}
initCollapsed={false}
catalogId={partCatalog.id}
name={partCatalog.name + partCatalog.description}
icon={<Icon color={theme.onColorHighlightVariantPink}/>}
onChoose={partChosen}
/>
})}
</WhenDataReady>

View file

@ -1,15 +1,17 @@
import React, {ReactNode} from "react";
import {CatalogCategory, CatalogPart} from "../partImportPlugin";
import {CatalogCategory, CatalogPart} from "../remotePartsPlugin";
import {Tree} from "ui/components/Tree";
import {FiBox} from "react-icons/fi";
import {GrCubes} from "react-icons/gr";
import theme from "ui/styles/theme";
export function PartCatalog({root, initCollapsed, name, icon} : {
export function PartCatalog({catalogId, root, initCollapsed, name, icon, onChoose} : {
catalogId: string,
root: CatalogCategory,
initCollapsed: boolean,
name: string,
icon: ReactNode
icon: ReactNode,
onChoose: (part: string) => void
}) {
return <Tree initCollapsed={initCollapsed} label={name} icon={icon}>
@ -19,13 +21,20 @@ export function PartCatalog({root, initCollapsed, name, icon} : {
const category = entry as CatalogCategory;
return <PartCatalog root={category} initCollapsed={true} name={category.name} icon={<GrCubes color={theme.onColorHighlightVariantYellow}/>}/>
return <PartCatalog key={entry.name} root={category} initCollapsed={true}
name={category.name} catalogId={catalogId} onChoose={onChoose}
icon={<GrCubes color={theme.onColorHighlightVariantYellow}/>}/>
} else if (entry.type === 'part') {
const part = entry as CatalogPart;
return <Tree label={part.name} icon={<FiBox color={theme.onColorHighlightVariantGreen}/>}/>
const partRef = catalogId + '/' + part.id;
return <Tree key={entry.name}
label={part.name}
onClick={(e) => onChoose(partRef)}
data-part-ref={partRef}
icon={<FiBox color={theme.onColorHighlightVariantGreen}/>}/>
}
})}

View file

@ -5,10 +5,10 @@ import {AppContext} from "../../dom/components/AppContext";
export function PartRefControl(props) {
let {onChange, initValue, onFocus, openIfEmpty} = props;
let {onChange, value, onFocus, openIfEmpty} = props;
useEffect(() => {
if (openIfEmpty && !initValue) {
if (openIfEmpty && !value) {
openChooser(undefined);
}
@ -18,9 +18,11 @@ export function PartRefControl(props) {
const openChooser = e => {
ctx.partImportService.choosePartRequest$.next({
ctx.remotePartsService.choosePartRequest$.next({
centerScreen: true,
onDone: () => {}
onDone: (partId: string) => {
onChange(partId);
}
})
};
@ -28,7 +30,7 @@ export function PartRefControl(props) {
display: 'flex',
}}>
<InputControl type='text'
defaultValue={initValue}
value={value || ''}
onChange={e => onChange(e.target.value)}
onFocus={onFocus}
style={{

View file

@ -1,8 +1,11 @@
import {PROJECTS_PREFIX, SKETCH_SUFFIX} from '../projectPlugin';
import {ProjectManager} from './ProjectManager';
import exportTextData from '../../../../modules/gems/exportTextData';
import {SketchFormat_V3} from "../../sketcher/io";
import {ApplicationContext} from "context";
import {OperationRequest} from "../craft/craftPlugin";
export function activate(ctx) {
export function activate(ctx: ApplicationContext) {
function importProjectImpl(getId, onDone) {
let uploader = document.createElement('input');
@ -15,14 +18,12 @@ export function activate(ctx) {
let reader = new FileReader();
reader.onload = () => {
try {
let bundle = JSON.parse(reader.result);
let projectId = getId(uploader.value, bundle);
const bundle = JSON.parse(reader.result as string);
const projectId = getId(uploader.value, bundle);
if (projectId) {
let sketchesNamespace = PROJECTS_PREFIX + projectId + SKETCH_SUFFIX;
ctx.services.storage.set(PROJECTS_PREFIX + projectId, JSON.stringify(bundle.model));
bundle.sketches.forEach(s => ctx.services.storage.set(sketchesNamespace + s.id, JSON.stringify(s.data)));
importBundle(projectId, bundle);
onDone(projectId);
}
} finally {
@ -34,6 +35,12 @@ export function activate(ctx) {
uploader.addEventListener('change', read, false);
}
function importBundle(projectId: string, bundle: ModelBundle) {
let sketchesNamespace = PROJECTS_PREFIX + projectId + SKETCH_SUFFIX;
ctx.services.storage.set(PROJECTS_PREFIX + projectId, JSON.stringify(bundle.model));
bundle.sketches.forEach(sketch => ctx.services.storage.set(sketchesNamespace + sketch.id, JSON.stringify(sketch.data)));
}
function importProjectAs() {
function promptId(fileName, bundle) {
let promptedId = fileName;
@ -53,11 +60,16 @@ export function activate(ctx) {
function importProject() {
if (confirm('Current project will be wiped off and replaced with the being imported one. Continue?')) {
ctx.services.project.empty();
importProjectImpl(() => ctx.services.project.id, ctx.services.project.load);
ctx.projectService.empty();
importProjectImpl(() => ctx.projectService.id, ctx.projectService.load);
}
}
function loadExternalProject(projectId: string): ProjectModel {
const dataStr = ctx.services.storage.get(PROJECTS_PREFIX + projectId);
return JSON.parse(dataStr) as ProjectModel;
}
function exportProject(id) {
let modelData = ctx.services.storage.get(PROJECTS_PREFIX + id);
if (modelData) {
@ -174,10 +186,66 @@ export function activate(ctx) {
window.open('?' + projectId);
}
ctx.services.ui.registerFloatView('ProjectManager', ProjectManager, 'Project Manager', 'database');
ctx.services.projectManager = {
listProjects, openProject, newProject, renameProject, deleteProject,
exists, cloneProject, exportProject, importProjectAs, importProject
ctx.services.ui.registerFloatView('ProjectManager', ProjectManager, 'Project Manager', 'database');
ctx.projectManager = {
listProjects, openProject, newProject, renameProject, deleteProject, importBundle,
exists, cloneProject, exportProject, importProjectAs, importProject, loadExternalProject
};
ctx.services.projectManager = ctx.projectManager;
}
export interface ProjectModel {
history: OperationRequest[],
expressions: string
}
export interface ModelBundle {
model: ProjectModel,
sketches: {
id: string,
data: SketchFormat_V3
}[];
}
interface IProjectManager {
importBundle(projectId: string, dataJson: ModelBundle): void;
importProjectAs(): void;
importProject(): void;
exportProject(id: string): void;
cloneProject(oldProjectId: string, silent?: boolean): void;
exists(projectId: string): boolean;
listProjects(): string[];
deleteProject(projectId: string): void;
renameProject(oldProjectId: string, silent: boolean): void
newProject(): void;
openProject(projectId: string): void;
loadExternalProject(projectId: string): ProjectModel;
}
declare module 'context' {
interface ApplicationContext {
projectManager: IProjectManager;
}
}
}

View file

@ -1,21 +1,28 @@
import {setSketchPrecision} from './sketch/sketchReader';
import {runSandbox} from './sandbox';
import {LOG_FLAGS} from './logFlags';
import {CoreContext} from "context";
import {SketchFormat_V3} from "../sketcher/io";
import {OperationRequest} from "./craft/craftPlugin";
import {ProjectModel} from "./projectManager/projectManagerPlugin";
export const STORAGE_GLOBAL_PREFIX = 'TCAD';
export const PROJECTS_PREFIX = `${STORAGE_GLOBAL_PREFIX}.projects.`;
export const SKETCH_SUFFIX = '.sketch.';
export function activate(context) {
export function activate(ctx: CoreContext) {
const {streams, services} = context;
const [id, hints] = parseHintsFromLocation();
processParams(hints, context);
initProjectService(ctx, id, hints);
}
const sketchNamespace = id + SKETCH_SUFFIX;
export function initProjectService(ctx: CoreContext, id: string, hints: any) {
processParams(hints, ctx);
const sketchNamespace = id + SKETCH_SUFFIX;
const sketchStorageNamespace = PROJECTS_PREFIX + sketchNamespace;
function sketchStorageKey(sketchIdId) {
@ -29,17 +36,18 @@ export function activate(context) {
function getSketchURL(sketchId) {
return sketchNamespace + sketchId;
}
function save() {
let data = {};
data.history = streams.craft.modifications.value.history;
data.expressions = streams.expressions.script.value;
services.storage.set(projectStorageKey(), JSON.stringify(data));
let data = {
history: ctx.craftService.modifications$.value.history,
expressions: ctx.expressionService.script$.value
};
ctx.storageService.set(projectStorageKey(), JSON.stringify(data));
}
function load() {
try {
let dataStr = services.storage.get(services.project.projectStorageKey());
let dataStr = ctx.storageService.get(ctx.projectService.projectStorageKey());
if (dataStr) {
let data = JSON.parse(dataStr);
loadData(data);
@ -50,12 +58,12 @@ export function activate(context) {
}
function loadData(data) {
function loadData(data: ProjectModel) {
if (data.expressions) {
services.expressions.load(data.expressions);
ctx.expressionService.load(data.expressions);
}
if (data.history) {
services.craft.reset(data.history);
ctx.craftService.reset(data.history);
}
}
@ -66,10 +74,11 @@ export function activate(context) {
});
}
services.project = {
id, sketchStorageKey, projectStorageKey, sketchStorageNamespace, getSketchURL, save, load, empty,
ctx.projectService = {
id, sketchStorageKey, projectStorageKey, sketchStorageNamespace, getSketchURL, save, load, loadData, empty,
hints
}
};
}
function parseHintsFromLocation() {
@ -100,7 +109,7 @@ function parseHints(hints) {
function processParams(params, context) {
if (params.sketchPrecision) {
setSketchPrecision(parseInt(sketchPrecision));
setSketchPrecision(parseInt(params.sketchPrecision));
}
if (params.sandbox) {
setTimeout(() => runSandbox(context));
@ -113,3 +122,36 @@ function processParams(params, context) {
}
})
}
export interface ProjectService {
readonly id: string;
readonly sketchStorageNamespace: string;
hints: any;
sketchStorageKey(sketchId: string): string;
projectStorageKey(): string
getSketchURL(sketchId: string): string
save(): void;
load(): void
loadData(data: ProjectModel);
empty(): void;
}
declare module 'context' {
interface CoreContext {
projectService: ProjectService;
}
}

View file

@ -22,7 +22,7 @@ function createMarker(findEntity, requestRender) {
function doMark(entity, id, color) {
let mObj = findEntity(entity, id);
if (!mObj) {
throw 'illegal state';
console.warn('no entity found to highlight: ' + entity + ' ' + id);
}
marked.set(id, mObj);
mObj.ext.view && mObj.ext.view.mark(color);

View file

@ -38,7 +38,7 @@ export class InPlaceSketcher {
container.appendChild(canvas);
this.sketcherAppContext = createEssentialAppContext(canvas);
this.viewer.parametricManager.externalConstantResolver = this.ctx.services.expressions.evaluateExpression;
this.viewer.parametricManager.externalConstantResolver = this.ctx.expressionService.evaluateExpression;
this.syncWithCamera();
this.viewer.toolManager.setDefaultTool(new DelegatingPanTool(this.viewer, viewer3d.sceneSetup.renderer.domElement));
@ -55,7 +55,7 @@ export class InPlaceSketcher {
}
get sketchStorageKey() {
return this.ctx.services.project.sketchStorageKey(this.face.defaultSketchId);
return this.ctx.projectService.sketchStorageKey(this.face.defaultSketchId);
}
exit() {
@ -117,7 +117,7 @@ export class InPlaceSketcher {
save() {
this.ctx.services.storage.set(this.sketchStorageKey, this.viewer.io.serializeSketch({
expressionsSignature: this.ctx.services.expressions.signature
expressionsSignature: this.ctx.expressionService.signature
}));
}
}

View file

@ -0,0 +1,77 @@
import {CoreContext} from "context";
import {ReadSketch} from "./sketchReader";
export function activate(ctx: CoreContext) {
function getAllSketches() {
let nm = ctx.projectService.sketchStorageNamespace;
return ctx.storageService.getAllKeysFromNamespace(nm).map(fqn => ({
fqn, id: fqn.substring(nm.length)
}));
}
function getSketchData(sketchId) {
let sketchStorageKey = ctx.projectService.sketchStorageKey(sketchId);
return ctx.storageService.get(sketchStorageKey);
}
function setSketchData(sketchId, data) {
let sketchStorageKey = ctx.projectService.sketchStorageKey(sketchId);
return ctx.storageService.set(sketchStorageKey, data);
}
function removeSketchData(sketchId) {
let sketchStorageKey = ctx.projectService.sketchStorageKey(sketchId);
return ctx.storageService.remove(sketchStorageKey);
}
function hasSketch(sketchId) {
let sketchStorageKey = ctx.projectService.sketchStorageKey(sketchId);
return !!ctx.storageService.get(sketchStorageKey);
}
function readSketch(sketchId) {
let savedSketch = getSketchData(sketchId);
if (savedSketch === null) {
return null;
}
try {
return ReadSketch(JSON.parse(savedSketch), sketchId, true);
} catch (e) {
console.error(e);
return null;
}
}
ctx.sketchStorageService = {
getAllSketches, readSketch, hasSketch, getSketchData, setSketchData, removeSketchData
}
}
export interface SketchStorageService {
getAllSketches(): {
id: string,
fqn: string
}[];
getSketchData(sketchId: string): string;
setSketchData(sketchId: string, data: string): void;
removeSketchData(sketchId: string): void;
hasSketch(sketchId: string): boolean;
readSketch(sketchId: string): any;
}
declare module 'context' {
interface CoreContext {
sketchStorageService: SketchStorageService;
}
}

View file

@ -1,15 +1,10 @@
import {ReadSketch} from './sketchReader';
import {getSketchBoundaries} from './sketchBoundaries';
import {state, stream} from 'lstream';
import {InPlaceSketcher} from './inPlaceSketcher';
import sketcherUIContrib from './sketcherUIContrib';
import initReassignSketchMode from './reassignSketchMode';
import sketcherStreams from '../../sketcher/sketcherStreams';
import {Viewer} from "../../sketcher/viewer2d";
import {IO} from "../../sketcher/io";
import {DelegatingPanTool} from "../../sketcher/tools/pan";
import {CadRegistry} from "../craft/cadRegistryPlugin";
import {MObject} from "../model/mobject";
export function defineStreams(ctx) {
ctx.streams.sketcher = {
@ -17,7 +12,7 @@ export function defineStreams(ctx) {
sketchingFace: state(null),
sketcherAppContext: state(null)
};
ctx.streams.sketcher.sketchingMode = ctx.streams.sketcher.sketchingFace.map(face => !!face);
ctx.streams.sketcher.sketchingMode = ctx.streams.sketcher.sketcherAppContext.map(ctx => !!ctx);
}
export function activate(ctx) {
@ -27,7 +22,7 @@ export function activate(ctx) {
sketcherUIContrib(ctx);
const onSketchUpdate = evt => {
let prefix = services.project.sketchStorageNamespace;
let prefix = ctx.projectService.sketchStorageNamespace;
if (evt.key.indexOf(prefix) < 0) return;
let sketchFaceId = evt.key.substring(prefix.length);
let sketchFace = services.cadRegistry.findFace(sketchFaceId);
@ -39,52 +34,16 @@ export function activate(ctx) {
services.storage.addListener(onSketchUpdate);
function getAllSketches() {
let nm = services.project.sketchStorageNamespace;
return services.storage.getAllKeysFromNamespace(nm).map(fqn => ({
fqn, id: fqn.substring(nm.length)
}));
}
function getSketchData(sketchId) {
let sketchStorageKey = services.project.sketchStorageKey(sketchId);
return services.storage.get(sketchStorageKey);
}
function setSketchData(sketchId, data) {
let sketchStorageKey = services.project.sketchStorageKey(sketchId);
return services.storage.set(sketchStorageKey, data);
}
function removeSketchData(sketchId) {
let sketchStorageKey = services.project.sketchStorageKey(sketchId);
return services.storage.remove(sketchStorageKey);
}
const headlessCanvas = document.createElement('canvas');
document.createElement('div').appendChild(headlessCanvas);
function readSketch(sketchId) {
let savedSketch = getSketchData(sketchId);
if (savedSketch === null) {
return null;
}
try {
return ReadSketch(JSON.parse(savedSketch), sketchId, true);
} catch (e) {
console.error(e);
return null;
}
}
function reevaluateAllSketches() {
let allShells = services.cadRegistry.getAllShells();
allShells.forEach(mShell => mShell.faces.forEach(mFace => reevaluateSketch(mFace.defaultSketchId)));
}
function reevaluateSketch(sketchId) {
let savedSketch = getSketchData(sketchId);
let savedSketch = ctx.sketchStorageService.getSketchData(sketchId);
if (savedSketch === null) {
return null;
}
@ -97,7 +56,7 @@ export function activate(ctx) {
return null;
}
let signature = services.expressions.signature;
let signature = ctx.expressionService.signature;
if (sketch && (!sketch.metadata || sketch.metadata.expressionsSignature !== signature)) {
try {
const viewer = new Viewer(headlessCanvas, IO);
@ -105,7 +64,7 @@ export function activate(ctx) {
// viewer.historyManager.init(savedSketch);
viewer.io._loadSketch(sketch);
viewer.parametricManager.refresh();
services.storage.set(services.project.sketchStorageKey(sketchId), viewer.io.serializeSketch({
services.storage.set(ctx.projectService.sketchStorageKey(sketchId), viewer.io.serializeSketch({
expressionsSignature: signature
}));
} catch (e) {
@ -115,13 +74,8 @@ export function activate(ctx) {
}
}
function hasSketch(sketchId) {
let sketchStorageKey = services.project.sketchStorageKey(sketchId);
return !!services.storage.get(sketchStorageKey);
}
function updateSketchForFace(mFace) {
let sketch = readSketch(mFace.defaultSketchId);
let sketch = ctx.sketchStorageService.readSketch(mFace.defaultSketchId);
mFace.setSketch(sketch);
streams.sketcher.update.next(mFace);
}
@ -134,7 +88,7 @@ export function activate(ctx) {
function updateSketchBoundaries(sceneFace) {
let sketchStorageKey = services.project.sketchStorageKey(sceneFace.id);
let sketchStorageKey = ctx.projectService.sketchStorageKey(sceneFace.id);
let sketch = services.storage.get(sketchStorageKey);
@ -155,17 +109,17 @@ export function activate(ctx) {
function sketchFace2D(face) {
updateSketchBoundaries(face);
let sketchURL = services.project.getSketchURL(face.id);
let sketchURL = ctx.projectService.getSketchURL(face.id);
ctx.appTabsService.show(face.id, 'Sketch ' + face.id, 'sketcher.html#' + sketchURL);
}
function reassignSketch(fromId, toId) {
let sketchData = getSketchData(fromId);
let sketchData = ctx.sketchStorageService.getSketchData(fromId);
if (!sketchData) {
return;
}
setSketchData(toId, sketchData);
removeSketchData(fromId);
ctx.sketchStorageService.setSketchData(toId, sketchData);
ctx.sketchStorageService.removeSketchData(fromId);
updateSketchForFace(services.cadRegistry.findFace(fromId));
updateSketchForFace(services.cadRegistry.findFace(toId));
services.viewer.requestRender();
@ -179,7 +133,7 @@ export function activate(ctx) {
}
}
});
streams.expressions.table.attach(() => {
ctx.expressionService.table$.attach(() => {
if (inPlaceEditor.viewer !== null) {
inPlaceEditor.viewer.parametricManager.refresh();
}
@ -188,7 +142,7 @@ export function activate(ctx) {
services.sketcher = {
sketchFace, sketchFace2D, updateAllSketches, getAllSketches, readSketch, hasSketch, inPlaceEditor, reassignSketch,
sketchFace, sketchFace2D, updateAllSketches, inPlaceEditor, reassignSketch,
reassignSketchMode: initReassignSketchMode(ctx)
};
ctx.sketcherService = services.sketcher;

View file

@ -1,5 +1,5 @@
import {stream} from 'lstream';
import {ApplicationContext} from "context";
import {CoreContext} from "context";
const updates$ = stream();
@ -9,7 +9,7 @@ export function defineStreams(ctx) {
}
}
export function activate(ctx: ApplicationContext) {
export function activate(ctx: CoreContext) {
const {services, streams} = ctx;
@ -83,7 +83,7 @@ export interface StorageService {
}
declare module 'context' {
interface ApplicationContext {
interface CoreContext {
storageService: StorageService;
}

View file

@ -1,7 +1,17 @@
import {AXIS, Matrix3, ORIGIN} from './l3space';
import Vector from "math/vector";
export default class CSys {
static ORIGIN: CSys;
origin: Vector;
x: Vector;
y: Vector;
z: Vector;
private _outTr: Matrix3;
private _inTr: Matrix3;
static fromNormalAndDir(origin, normal, dir) {
return new CSys(origin, dir, normal.cross(dir), normal)
}

View file

@ -43,6 +43,7 @@ export class SketchGenerator {
layer.objects.push(obj);
obj.syncGeometry()
});
viewer.objectsUpdate();
}
regenerate(viewer) {

View file

@ -2,7 +2,7 @@
export default class CadError extends Error {
constructor({kind, code, relatedTopoObjects, userMessage} = __EMPTY) {
super();
super(`[${kind}] ${code||''} ${userMessage}`);
this.kind = kind || CadError.KIND.INTERNAL_ERROR;
this.code = code;
this.relatedTopoObjects = relatedTopoObjects;