mirror of
https://github.com/xibyte/jsketcher
synced 2025-12-15 21:05:22 +01:00
part import from repository
This commit is contained in:
parent
e6d23ab4bf
commit
66c4707a83
66 changed files with 1432 additions and 992 deletions
|
|
@ -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,
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
@ -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} />;
|
||||
};
|
||||
};
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
@ -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
356
package-lock.json
generated
|
|
@ -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"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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",
|
||||
|
|
|
|||
|
|
@ -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');
|
||||
|
||||
});
|
||||
|
||||
});
|
||||
|
|
|
|||
|
|
@ -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`);
|
||||
});
|
||||
|
||||
|
||||
|
|
|
|||
7
test/cypress/support/index.d.ts
vendored
7
test/cypress/support/index.d.ts
vendored
|
|
@ -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;
|
||||
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
},
|
||||
|
||||
{
|
||||
|
|
|
|||
|
|
@ -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 = {
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
},
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
263
web/app/cad/craft/craftPlugin.ts
Normal file
263
web/app/cad/craft/craftPlugin.ts
Normal 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;
|
||||
}
|
||||
}
|
||||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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));
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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});
|
||||
}
|
||||
})
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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>;
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
||||
|
|
|
|||
|
|
@ -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) {
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
||||
|
|
|
|||
|
|
@ -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 = {
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
}
|
||||
);
|
||||
89
web/app/cad/expressions/Expressions.tsx
Normal file
89
web/app/cad/expressions/Expressions.tsx
Normal 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>
|
||||
}
|
||||
);
|
||||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
@ -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');
|
||||
|
|
|
|||
|
|
@ -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
|
||||
];
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
|
|
@ -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);
|
||||
|
|
@ -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();
|
||||
}
|
||||
|
||||
};
|
||||
|
|
|
|||
|
|
@ -2,6 +2,8 @@ import {MShell} from './mshell';
|
|||
import {MFace} from './mface';
|
||||
|
||||
export class MOpenFaceShell extends MShell {
|
||||
|
||||
private surfacePrototype: any;
|
||||
|
||||
constructor(surfacePrototype, csys) {
|
||||
super();
|
||||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -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);
|
||||
|
|
@ -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);
|
||||
|
|
@ -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
|
||||
];
|
||||
|
|
@ -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>;
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
|
|
@ -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'
|
||||
|
||||
});
|
||||
|
||||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
19
web/app/cad/partImport/partRepository.ts
Normal file
19
web/app/cad/partImport/partRepository.ts
Normal 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);
|
||||
}
|
||||
|
||||
}
|
||||
19
web/app/cad/partImport/remotePartsConfig.ts
Normal file
19
web/app/cad/partImport/remotePartsConfig.ts
Normal 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');
|
||||
188
web/app/cad/partImport/remotePartsPlugin.ts
Normal file
188
web/app/cad/partImport/remotePartsPlugin.ts
Normal 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;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
|
@ -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>
|
||||
|
||||
|
|
|
|||
|
|
@ -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}/>}/>
|
||||
}
|
||||
})}
|
||||
|
||||
|
|
|
|||
|
|
@ -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={{
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}));
|
||||
}
|
||||
}
|
||||
|
|
|
|||
77
web/app/cad/sketch/sketchStoragePlugin.ts
Normal file
77
web/app/cad/sketch/sketchStoragePlugin.ts
Normal 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;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}
|
||||
|
|
@ -43,6 +43,7 @@ export class SketchGenerator {
|
|||
layer.objects.push(obj);
|
||||
obj.syncGeometry()
|
||||
});
|
||||
viewer.objectsUpdate();
|
||||
}
|
||||
|
||||
regenerate(viewer) {
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
Loading…
Reference in a new issue