diff --git a/modules/bus/index.js b/modules/bus/index.js
index eb5e5b6d..86669384 100644
--- a/modules/bus/index.js
+++ b/modules/bus/index.js
@@ -105,6 +105,10 @@ export default class Bus {
this.state[forToken] = initValue;
}
+ disableState(forToken) {
+ this.keepStateFor.delete(forToken);
+ delete this.state[forToken];
+ }
}
export function createToken(...fqn) {
diff --git a/modules/gems/camelCaseSplit.js b/modules/gems/camelCaseSplit.js
new file mode 100644
index 00000000..e1803979
--- /dev/null
+++ b/modules/gems/camelCaseSplit.js
@@ -0,0 +1,25 @@
+export default function camelCaseSplit(str) {
+ function isUpperCase(str) {
+ return str.toUpperCase() === str;
+ }
+
+ const words = [];
+ let word = '';
+
+ for (let i = 0; i < str.length; i++) {
+ const c = str.charAt(i);
+ if (c === '_' || c === '-') {
+ continue;
+ }
+ const dot = c === '.';
+ if ((dot || isUpperCase(c)) && word.length !== 0) {
+ words.push(word);
+ word = '';
+ }
+ if (!dot) word += c;
+ }
+ if (word.length !== 0){
+ words.push(word);
+ }
+ return words;
+}
diff --git a/modules/gems/shallowEqual.js b/modules/gems/shallowEqual.js
new file mode 100644
index 00000000..6485cff7
--- /dev/null
+++ b/modules/gems/shallowEqual.js
@@ -0,0 +1,23 @@
+export default function shallowEqual(objA, objB) {
+ if (objA === objB) {
+ return true;
+ }
+
+ let aKeys = Object.keys(objA);
+ let bKeys = Object.keys(objB);
+ let len = aKeys.length;
+
+ if (bKeys.length !== len) {
+ return false;
+ }
+
+ for (let i = 0; i < len; i++) {
+ let key = aKeys[i];
+
+ if (objA[key] !== objB[key]) {
+ return false;
+ }
+ }
+
+ return true;
+};
\ No newline at end of file
diff --git a/modules/scene/geoms.js b/modules/scene/geoms.js
new file mode 100644
index 00000000..5972ef71
--- /dev/null
+++ b/modules/scene/geoms.js
@@ -0,0 +1,20 @@
+
+export function createBoxGeometry(width, height, depth) {
+ return new THREE.BoxGeometry(width, height, depth);
+}
+
+export function createMeshGeometry(triangles) {
+ const geometry = new THREE.Geometry();
+
+ for (let tr of triangles) {
+ const a = geometry.vertices.length;
+ const b = a + 1;
+ const c = a + 2;
+ const face = new THREE.Face3(a, b, c);
+ tr.forEach(v => geometry.vertices.push(v.three()));
+ geometry.faces.push(face);
+ }
+ geometry.mergeVertices();
+ geometry.computeFaceNormals();
+ return geometry;
+}
\ No newline at end of file
diff --git a/modules/scene/objects/mesh.js b/modules/scene/objects/mesh.js
new file mode 100644
index 00000000..0c0f29b2
--- /dev/null
+++ b/modules/scene/objects/mesh.js
@@ -0,0 +1,9 @@
+import {createMeshGeometry} from '../geoms';
+
+export function createMesh(geometry, material) {
+ return new THREE.Mesh(geometry, material);
+}
+
+export function createMeshFromTriangles(triangles, material) {
+ return createMesh(createMeshGeometry(triangles), material);
+}
diff --git a/modules/scene/sceneSetup.js b/modules/scene/sceneSetup.js
index 204d64f4..4fd82f8e 100644
--- a/modules/scene/sceneSetup.js
+++ b/modules/scene/sceneSetup.js
@@ -1,5 +1,6 @@
import DPR from 'dpr';
-import './utils/threeLoader'
+import './utils/threeLoader';
+import './utils/vectorThreeEnhancement';
export default class SceneSetUp {
diff --git a/modules/ui/components/Window.jsx b/modules/ui/components/Window.jsx
index 7505950d..99cdc932 100644
--- a/modules/ui/components/Window.jsx
+++ b/modules/ui/components/Window.jsx
@@ -35,7 +35,8 @@ export default class Window extends React.Component {
width: this.state.width,
height: this.state.height,
left: this.state.left,
- top: this.state.top
+ top: this.state.top,
+ zIndex: 1
}
}
}
diff --git a/modules/ui/components/controls/Button.jsx b/modules/ui/components/controls/Button.jsx
index 2b110bdf..7370ccb8 100644
--- a/modules/ui/components/controls/Button.jsx
+++ b/modules/ui/components/controls/Button.jsx
@@ -2,9 +2,9 @@ import React from 'react';
import ls from './Button.less'
-export default function Button({text, type}) {
+export default function Button({text, type, onClick}) {
- return
+ return
}
diff --git a/modules/ui/components/controls/InputControl.jsx b/modules/ui/components/controls/InputControl.jsx
index f235af07..499e1d8d 100644
--- a/modules/ui/components/controls/InputControl.jsx
+++ b/modules/ui/components/controls/InputControl.jsx
@@ -9,7 +9,7 @@ export default class InputControl extends React.Component {
let {type, inputRef, ...props} = this.props;
return
-
+
;
}
}
diff --git a/modules/ui/components/controls/NumberControl.jsx b/modules/ui/components/controls/NumberControl.jsx
index 02d23145..167b2a46 100644
--- a/modules/ui/components/controls/NumberControl.jsx
+++ b/modules/ui/components/controls/NumberControl.jsx
@@ -15,12 +15,7 @@ export default class NumberControl extends React.Component {
onWheel = (e) => {
let {baseStep, round, min, max, onChange, accelerator} = this.props;
- let delta = 0;
- if ( e.wheelDelta ) { // WebKit / Opera / Explorer 9
- delta = e.wheelDelta;
- } else if ( e.detail ) { // Firefox
- delta = - e.detail;
- }
+ let delta = e.deltaY;
let val = e.target.value;
if (!val) val = 0;
let step = baseStep * (e.shiftKey ? accelerator : 1);
diff --git a/modules/ui/components/controls/TextControl.jsx b/modules/ui/components/controls/TextControl.jsx
index 56391cbc..759e1080 100644
--- a/modules/ui/components/controls/TextControl.jsx
+++ b/modules/ui/components/controls/TextControl.jsx
@@ -11,19 +11,3 @@ export default class TextControl extends React.Component {
onChange={e => onChange(e.target.value)} />
}
}
-
-TextControl.propTypes = {
- baseStep: PropTypes.number,
- round: PropTypes.number,
- min: PropTypes.number,
- max: PropTypes.number,
- accelerator: PropTypes.number,
- initValue: PropTypes.number.isRequired,
- onChange: PropTypes.func.isRequired
-};
-
-TextControl.defaultProps = {
- baseStep: 1,
- round: 0,
- accelerator: 100
-};
\ No newline at end of file
diff --git a/modules/ui/connect.jsx b/modules/ui/connect.jsx
index 456061d9..712fd8c4 100644
--- a/modules/ui/connect.jsx
+++ b/modules/ui/connect.jsx
@@ -1,15 +1,16 @@
import React from 'react';
import PropTypes from 'prop-types';
+import shallowEqual from "../gems/shallowEqual";
-export default function connect(tokens, WrappedComponent, staticProps, mapper, dispatchMapper) {
+export default function connect(WrappedComponent, tokens, {staticProps, mapProps, mapActions}) {
if (!Array.isArray(tokens)) {
tokens = [tokens];
}
- mapper = createMapper(mapper);
+ mapProps = createMapper(mapProps);
- dispatchMapper = dispatchMapper || function(dispatch) {
+ mapActions = mapActions || function(dispatch) {
return dispatch;
};
@@ -19,7 +20,7 @@ export default function connect(tokens, WrappedComponent, staticProps, mapper, d
super();
this.mounted = false;
this.stateProps = {};
- this.dispatchProps = dispatchMapper(this.dispatch);
+ this.dispatchProps = mapActions(this.dispatch);
}
componentWillMount() {
@@ -37,12 +38,17 @@ export default function connect(tokens, WrappedComponent, staticProps, mapper, d
}
setExternalState = (state) => {
- this.stateProps = mapper(state);
+ this.stateProps = mapProps(state);
if (this.mounted) {
this.forceUpdate();
}
};
+ shouldComponentUpdate(nextProps, nextState) {
+ return !shallowEqual(this.props, nextProps);
+
+ }
+
dispatch = (event, data) => {
this.context.bus.dispatch(event, data);
};
@@ -51,6 +57,9 @@ export default function connect(tokens, WrappedComponent, staticProps, mapper, d
return
}
+ componentDidCatch() {
+ }
+
static contextTypes = {
bus: PropTypes.object
};
diff --git a/web/app/cad/actions/operationActions.js b/web/app/cad/actions/operationActions.js
index 88fbee07..392e8de3 100644
--- a/web/app/cad/actions/operationActions.js
+++ b/web/app/cad/actions/operationActions.js
@@ -29,12 +29,6 @@ const OPERATION_ACTIONS = [
},
...requiresFaceSelection(1)
},
- {
- id: 'BOX',
- appearance: {
- info: 'creates new object box'
- },
- },
{
id: 'PLANE',
appearance: {
@@ -78,7 +72,7 @@ const OPERATION_ACTIONS = [
function mergeInfo(action) {
const op = Operations[action.id];
- action.invoke = app => app.ui.initOperation(action.id);
+ action.invoke = ({services}) => services.operation.startOperation(action.id);
Object.assign(action.appearance, {
label: op.label,
icon32: op.icon + '32.png',
diff --git a/web/app/cad/craft/brep/wizards/preview-wizard.js b/web/app/cad/craft/brep/wizards/preview-wizard.js
deleted file mode 100644
index e8ef2fa2..00000000
--- a/web/app/cad/craft/brep/wizards/preview-wizard.js
+++ /dev/null
@@ -1,136 +0,0 @@
-import {Wizard} from './wizard'
-import {ReadSketchFromFace} from '../../sketch/sketch-reader'
-import {Loop} from '../../../../brep/topo/loop'
-
-export class PreviewWizard extends Wizard {
-
- constructor(app, opearation, metadata, initialState) {
- super(app, opearation, metadata, initialState);
- this.operation = opearation;
- this.previewGroup = new THREE.Object3D();
- this.previewObject = null;
- this.app.viewer.workGroup.add(this.previewGroup);
- this.updatePreview();
- app.bus.subscribe('refreshSketch', this.onSketchUpdate);
- }
-
- onSketchUpdate = () => {
- this.updatePreview();
- };
-
- createPreviewObject() {throw 'abstract'};
-
- updatePreview() {
- this.destroyPreviewObject();
- this.previewObject = this.createPreviewObject(this.app, this.readFormFields());
- if (this.previewObject !== null) {
- this.previewGroup.add( this.previewObject );
- }
- this.app.viewer.render();
- }
-
- destroyPreviewObject() {
- if (this.previewObject !== null) {
- this.previewGroup.remove( this.previewObject );
- this.previewObject.geometry.dispose();
- this.previewObject = null;
- }
- }
-
- onUIChange() {
- super.onUIChange();
- this.updatePreview();
- }
-
- dispose() {
- this.app.bus.unSubscribe('refreshSketch', this.onSketchUpdate);
- this.destroyPreviewObject();
- this.app.viewer.workGroup.remove(this.previewGroup);
- this.app.viewer.render();
- super.dispose();
- }
-}
-
-PreviewWizard.createMesh = function(triangles) {
- const geometry = new THREE.Geometry();
-
- for (let tr of triangles) {
- const a = geometry.vertices.length;
- const b = a + 1;
- const c = a + 2;
- const face = new THREE.Face3(a, b, c);
- tr.forEach(v => geometry.vertices.push(v.three()));
- geometry.faces.push(face);
- }
- geometry.mergeVertices();
- geometry.computeFaceNormals();
-
- return new THREE.Mesh(geometry, IMAGINARY_SURFACE_MATERIAL);
-};
-
-export class SketchBasedPreviewer {
-
- constructor() {
- //this.fixToCCW = true;
- }
-
- createImpl(app, params, sketch, face) {
- throw 'not implemented';
- }
-
- create(app, params) {
- const face = app.findFace(params.face);
- if (!face) return null;
- const triangles = this.createImpl(app, params, face.sketch.fetchContours(), face);
- return PreviewWizard.createMesh(triangles);
- }
-}
-
-export class SketchBasedNurbsPreviewer {
-
- constructor() {
- }
-
- createNurbses(app, params, sketch, face) {
- throw 'not implemented';
- }
-
- createMesh(app, params) {
- const face = app.findFace(params.face);
- if (!face) return null;
- const needSketchRead = !this.sketch || params.face != this.face;
- if (needSketchRead) {
- this.sketch = ReadSketchFromFace(app, face);
- this.face = params.face;
- }
- const nurbses = this.createNurbses(app, params, this.sketch, face);
- const geom = new THREE.Geometry();
-
- for (let nurbs of nurbses) {
- const off = geom.vertices.length;
- const tess = nurbs.tessellate({maxDepth: 3});
- const points = [];
- tess.points.forEach(p => geom.vertices.push(new THREE.Vector3().fromArray(p)));
- for (let faceIndices of tess.faces) {
- let normales = faceIndices.map(function(x) {
- var vn = tess.normals[x];
- return new THREE.Vector3( vn[0], vn[1], vn[2] );
- });
- const face = new THREE.Face3(faceIndices[0] + off, faceIndices[1] + off, faceIndices[2] + off, normales);
- geom.faces.push(face);
- }
- }
- return new THREE.Mesh(geom, IMAGINARY_SURFACE_MATERIAL);
- }
-}
-
-export const IMAGINARY_SURFACE_MATERIAL = new THREE.MeshPhongMaterial({
- vertexColors: THREE.FaceColors,
- color: 0xFA8072,
- transparent: true,
- opacity: 0.5,
- shininess: 0,
- depthWrite: false,
- depthTest: false,
- side : THREE.DoubleSide
-});
diff --git a/web/app/cad/craft/craftPlugin.js b/web/app/cad/craft/craftPlugin.js
new file mode 100644
index 00000000..926d37a1
--- /dev/null
+++ b/web/app/cad/craft/craftPlugin.js
@@ -0,0 +1,67 @@
+import {BoxWizard} from "./mesh/wizards/box";
+
+export function activate({bus, services}) {
+
+
+
+
+
+ function createWizard(type, overridingHistory, initParams, face) {
+ let wizard = null;
+ if ('CUT' === type) {
+ wizard = new CutWizard(this.app, initParams);
+ } else if ('EXTRUDE' === type) {
+ wizard = new ExtrudeWizard(this.app, initParams);
+ } else if ('REVOLVE' === type) {
+ wizard = new RevolveWizard(this.app, face, initParams);
+ } else if ('PLANE' === type) {
+ wizard = new PlaneWizard(this.app, initParams);
+ } else if ('BOX' === type) {
+ wizard = new BoxWizard(this.app, initParams);
+ } else if ('SPHERE' === type) {
+ wizard = new SphereWizard(this.app.viewer, initParams);
+ } else if ('IMPORT_STL' === type) {
+ wizard = new ImportWizard(this.app.viewer, initParams);
+ } else {
+ console.log('unknown operation');
+ }
+ if (wizard != null) {
+ this.registerWizard(wizard, overridingHistory);
+ }
+ return wizard;
+ };
+
+
+ function startOperation(id) {
+ let selection = services.selection.face();
+
+ if ('CUT' === type) {
+ wizard = new CutWizard(this.app, initParams);
+ } else if ('EXTRUDE' === type) {
+ wizard = new ExtrudeWizard(this.app, initParams);
+ } else if ('REVOLVE' === type) {
+ wizard = new RevolveWizard(this.app, face, initParams);
+ } else if ('PLANE' === type) {
+ wizard = new PlaneWizard(this.app, initParams);
+ } else if ('BOX' === type) {
+ wizard = new BoxWizard(this.app, initParams);
+ } else if ('SPHERE' === type) {
+ wizard = new SphereWizard(this.app.viewer, initParams);
+ } else if ('IMPORT_STL' === type) {
+ wizard = new ImportWizard(this.app.viewer, initParams);
+ } else {
+ console.log('unknown operation');
+ }
+ if (wizard != null) {
+ this.registerWizard(wizard, overridingHistory);
+ }
+ return wizard;
+
+ return this.createWizard(op, false, undefined, selection[0]);
+
+ }
+
+ services.operation = {
+ startOperation
+ }
+}
\ No newline at end of file
diff --git a/web/app/cad/craft/operationPlugin.js b/web/app/cad/craft/operationPlugin.js
new file mode 100644
index 00000000..b3cc55d8
--- /dev/null
+++ b/web/app/cad/craft/operationPlugin.js
@@ -0,0 +1,56 @@
+import {createToken} from 'bus';
+import {TOKENS as WIZARD_TOKENS} from './wizard/wizardPlugin'
+
+export function activate(context) {
+ let {bus, services} = context;
+
+ let registry = {};
+
+ function addOperation(descriptor, actions) {
+ let {id, label, info, icon, actionParams} = descriptor;
+
+ let opAction = {
+ id: id,
+ appearance: {
+ label,
+ info,
+ icon32: icon + '32.png',
+ icon96: icon + '96.png',
+ },
+ invoke: () => bus.dispatch(WIZARD_TOKENS.OPEN, {type: id}),
+ ...actionParams
+ };
+ actions.push(opAction);
+
+ registry[id] = descriptor;
+
+ bus.subscribe(TOKENS.RUN, ({type, params}) => registry[type].run(params));
+ }
+
+ function registerOperations(operations) {
+ let actions = [];
+ for (let op of operations) {
+ addOperation(op, actions);
+ }
+ services.action.registerActions(actions);
+ }
+
+ function get(id) {
+ let op = registry[id];
+ if (!op) {
+ this `operation ${id} is not registered`;
+ }
+ return op;
+ }
+
+ services.operation = {
+ registerOperations,
+ registry,
+ get
+ }
+}
+
+
+export const TOKENS = {
+ RUN: createToken('operation', 'run'),
+};
\ No newline at end of file
diff --git a/web/app/cad/craft/operations.js b/web/app/cad/craft/operations.js
index de88447d..e18ec1f4 100644
--- a/web/app/cad/craft/operations.js
+++ b/web/app/cad/craft/operations.js
@@ -1,9 +1,9 @@
// import {MESH_OPERATIONS} from './mesh/workbench'
// import {Extrude, Cut} from './brep/cut-extrude'
// import {Revolve} from './brep/revolve'
-// import {BREPSceneSolid} from '../scene/wrappers/brepSceneObject'
+import {BREPSceneSolid} from '../scene/wrappers/brepSceneObject'
// import {PlaneSceneObject} from '../scene/wrappers/planeSceneObject'
-// import {box} from '../../brep/brep-primitives'
+import {box} from '../../brep/brep-primitives'
export const CUT = {
icon: 'img/cad/cut',
diff --git a/web/app/cad/craft/primitives/boxOperation.js b/web/app/cad/craft/primitives/boxOperation.js
new file mode 100644
index 00000000..9a3539c4
--- /dev/null
+++ b/web/app/cad/craft/primitives/boxOperation.js
@@ -0,0 +1,28 @@
+import {box} from '../../../brep/brep-primitives'
+import {BREPSceneSolid} from '../../scene/wrappers/brepSceneObject';
+import {createPreviewer} from "../../preview/scenePreviewer";
+import {createBoxGeometry} from "../../../../../modules/scene/geoms";
+
+const METADATA = [
+ ['width' , 'number', 500, {min: 0}],
+ ['height' , 'number', 500, {min: 0}],
+ ['depth' , 'number', 500, {min: 0}]
+];
+
+function createBox(solids, {width, height, depth}) {
+ return {
+ outdated: [],
+ created: [new BREPSceneSolid(box(width, height, depth))]
+ }
+}
+
+export default {
+ id: 'BOX',
+ metadata: METADATA,
+ label: 'Box',
+ info: 'creates new object box',
+ paramsInfo: ({width, height, depth}) => `(${width}, ${height}, ${depth})`,
+ previewer: createPreviewer(({width, height, depth}) => createBoxGeometry(width, height, depth)),
+ run: createBox
+};
+
diff --git a/web/app/cad/craft/wizard/wizardPlugin.js b/web/app/cad/craft/wizard/wizardPlugin.js
new file mode 100644
index 00000000..51853294
--- /dev/null
+++ b/web/app/cad/craft/wizard/wizardPlugin.js
@@ -0,0 +1,26 @@
+import {createToken} from 'bus';
+
+export function activate({bus, services}) {
+
+ bus.enableState(TOKENS.WIZARDS, []);
+ bus.subscribe(TOKENS.OPEN, ({type, initialState, overridingHistory}) => {
+
+ let wizard = {
+ type,
+ initialState,
+ overridingHistory,
+ };
+
+ bus.updateState(TOKENS.WIZARDS, opened => [...opened, wizard])
+ });
+
+ bus.subscribe(TOKENS.CLOSE, wizard => {
+ bus.updateState(TOKENS.WIZARDS, opened => opened.filter(w => w === wizard));
+ });
+}
+
+export const TOKENS = {
+ WIZARDS: createToken('wizards'),
+ OPEN: createToken('wizards', 'open'),
+ CLOSE: createToken('wizards', 'close'),
+};
\ No newline at end of file
diff --git a/web/app/cad/dom/actionInfo/ActionInfo.jsx b/web/app/cad/dom/actionInfo/ActionInfo.jsx
index ffb3cf77..5bcb5c4e 100644
--- a/web/app/cad/dom/actionInfo/ActionInfo.jsx
+++ b/web/app/cad/dom/actionInfo/ActionInfo.jsx
@@ -4,7 +4,7 @@ import ls from './ActionInfo.less';
import AuxWidget from '../../../../../modules/ui/components/AuxWidget';
import connect from '../../../../../modules/ui/connect';
import {TOKENS as ACTION_TOKENS} from '../../actions/actionSystemPlugin';
-import {TOKENS as KeyboardTokens} from "../../keyboard/keyboardPlugin";
+import {TOKENS as KeyboardTokens} from '../../keyboard/keyboardPlugin';
function ActionInfo({actionId, x, y, info, hint, hotKey}) {
let visible = !!actionId;
@@ -19,6 +19,7 @@ function ActionInfo({actionId, x, y, info, hint, hotKey}) {
;
}
-export default connect([ACTION_TOKENS.HINT, KeyboardTokens.KEYMAP], ActionInfo, undefined,
- ([ hintInfo, keymap ]) => (Object.assign({hotKey: hintInfo && keymap[hintInfo.actionId]}, hintInfo)) );
+export default connect(ActionInfo, [ACTION_TOKENS.HINT, KeyboardTokens.KEYMAP], {
+ mapProps: ([ hintInfo, keymap ]) => (Object.assign({hotKey: hintInfo && keymap[hintInfo.actionId]}, hintInfo))
+});
diff --git a/web/app/cad/dom/components/PlugableControlBar.jsx b/web/app/cad/dom/components/PlugableControlBar.jsx
index 3a0550d0..8839beb5 100644
--- a/web/app/cad/dom/components/PlugableControlBar.jsx
+++ b/web/app/cad/dom/components/PlugableControlBar.jsx
@@ -15,10 +15,13 @@ export default function PlugableControlBar() {
function ButtonGroup({actions}) {
return actions.map(actionRef => {
let [id, overrides] = toIdAndOverrides(actionRef);
- let Comp = connect([ACTION_TOKENS.actionAppearance(id), ACTION_TOKENS.actionState(id)],
- ActionButton, {actionId: id},
- ([appearance, state]) => Object.assign({}, appearance, state, overrides),
- mapActionBehavior(id)
+ let Comp = connect(ActionButton,
+ [ACTION_TOKENS.actionAppearance(id), ACTION_TOKENS.actionState(id)],
+ {
+ staticProps: {actionId: id},
+ mapProps: ([appearance, state]) => Object.assign({}, appearance, state, overrides),
+ mapActions: mapActionBehavior(id)
+ }
);
return ;
});
@@ -46,8 +49,12 @@ class ActionButton extends React.Component {
}
}
-const LeftGroup = connect(UI_TOKENS.CONTROL_BAR_LEFT, ButtonGroup, undefined, ([actions]) => ({actions}));
-const RightGroup = connect(UI_TOKENS.CONTROL_BAR_RIGHT, ButtonGroup, undefined, ([actions]) => ({actions}));
+const BUTTON_CONNECTOR = {
+ mapProps: ([actions]) => ({actions})
+};
+
+const LeftGroup = connect(ButtonGroup, UI_TOKENS.CONTROL_BAR_LEFT, BUTTON_CONNECTOR);
+const RightGroup = connect(ButtonGroup, UI_TOKENS.CONTROL_BAR_RIGHT, BUTTON_CONNECTOR);
function getMenuData(el) {
//TODO: make more generic
diff --git a/web/app/cad/dom/components/PlugableToolbar.jsx b/web/app/cad/dom/components/PlugableToolbar.jsx
index 095668af..62336064 100644
--- a/web/app/cad/dom/components/PlugableToolbar.jsx
+++ b/web/app/cad/dom/components/PlugableToolbar.jsx
@@ -15,9 +15,11 @@ function ConfigurableToolbar({actions, small, ...props}) {
{actions.map(actionRef => {
let [id, overrides] = toIdAndOverrides(actionRef);
let Comp = connect(
- [ACTION_TOKENS.actionAppearance(id), ACTION_TOKENS.actionState(id)],
- ActionButton, {small},
- ([appearance, state]) => Object.assign({}, appearance, state, overrides));
+ ActionButton,
+ [ACTION_TOKENS.actionAppearance(id), ACTION_TOKENS.actionState(id)], {
+ staticProps: {small},
+ mapProps: ([appearance, state]) => Object.assign({}, appearance, state, overrides)
+ });
return
})}
@@ -37,7 +39,10 @@ function ActionButton({label, icon96, cssIcons, small, enabled, visible, onClick
}
export function createPlugableToolbar(configToken, small) {
- return connect(configToken, ConfigurableToolbar, {small}, ([actions]) => ({actions}) );
+ return connect(ConfigurableToolbar, configToken, {
+ staticProps: {small},
+ mapProps: ([actions]) => ({actions})
+ });
}
export const PlugableToolbarLeft = createPlugableToolbar(UI_TOKENS.TOOLBAR_BAR_LEFT);
diff --git a/web/app/cad/dom/components/UISystem.jsx b/web/app/cad/dom/components/UISystem.jsx
index e6924dfd..5d39c41f 100644
--- a/web/app/cad/dom/components/UISystem.jsx
+++ b/web/app/cad/dom/components/UISystem.jsx
@@ -1,10 +1,10 @@
import React from 'react';
import PropTypes from 'prop-types';
-import MenuHolder from "../menu/MenuHolder";
+import MenuHolder from '../menu/MenuHolder';
import {TOKENS as MENU_TOKENS} from '../menu/menuPlugin';
import WindowSystem from 'ui/WindowSystem';
-import ActionInfo from "../actionInfo/ActionInfo";
+import ActionInfo from '../actionInfo/ActionInfo';
export default class UISystem extends React.Component {
@@ -17,12 +17,15 @@ export default class UISystem extends React.Component {
}
+ shouldComponentUpdate() {
+ return false;
+ }
+
closeAllUpPopups = () => {
let openedMenus = this.context.bus.state[MENU_TOKENS.OPENED];
if (openedMenus && openedMenus.length !== 0) {
this.context.bus.dispatch(MENU_TOKENS.CLOSE_ALL);
}
-
};
getChildContext() {
diff --git a/web/app/cad/dom/components/View3d.jsx b/web/app/cad/dom/components/View3d.jsx
index 6499ee02..480ba076 100644
--- a/web/app/cad/dom/components/View3d.jsx
+++ b/web/app/cad/dom/components/View3d.jsx
@@ -24,9 +24,15 @@ import ButtonGroup from "ui/components/controls/ButtonGroup";
import Button from "ui/components/controls/Button";
import TextControl from './../../../../../modules/ui/components/controls/TextControl';
import UISystem from './UISystem';
+import WizardManager from './wizard/WizardManager';
-export default class View3d extends React.PureComponent {
+export default class View3d extends React.Component {
+
+ shouldComponentUpdate() {
+ //we don't want the dom to be updated under any circumstances or we loose the WEB-GL container
+ return false;
+ }
render() {
return
@@ -43,8 +49,8 @@ export default class View3d extends React.PureComponent {
+
-
diff --git a/web/app/cad/dom/components/WebApplication.jsx b/web/app/cad/dom/components/WebApplication.jsx
index af6f7c54..92a5e1b4 100644
--- a/web/app/cad/dom/components/WebApplication.jsx
+++ b/web/app/cad/dom/components/WebApplication.jsx
@@ -15,9 +15,9 @@ const DEFAULT_VIEW = {id: 'view3d', label: '3D View', Component: View3d};
export default class WebApplication extends React.Component {
- constructor({bus}) {
+ constructor({appContext}) {
super();
- this.bus = bus;
+ this.appContext = appContext;
this.views = [DEFAULT_VIEW, {id: 'XXX', label: '3D View2', Component: Fragment}];
this.state = {
activeView: DEFAULT_VIEW
@@ -49,11 +49,12 @@ export default class WebApplication extends React.Component {
}
getChildContext() {
- return {bus: this.bus};
+ return this.appContext;
}
static childContextTypes = {
- bus: PropTypes.object
+ bus: PropTypes.object,
+ services: PropTypes.object
};
}
diff --git a/web/app/cad/dom/components/wizard/Wizard.jsx b/web/app/cad/dom/components/wizard/Wizard.jsx
new file mode 100644
index 00000000..44a99d01
--- /dev/null
+++ b/web/app/cad/dom/components/wizard/Wizard.jsx
@@ -0,0 +1,81 @@
+import React from 'react';
+import Window from 'ui/components/Window';
+import Stack from 'ui/components/Stack';
+import Field from 'ui/components/controls/Field';
+import Label from 'ui/components/controls/Label';
+import camelCaseSplit from 'gems/camelCaseSplit';
+import NumberControl from 'ui/components/controls/NumberControl';
+import TextControl from 'ui/components/controls/TextControl';
+import Button from 'ui/components/controls/Button';
+import ButtonGroup from 'ui/components/controls/ButtonGroup';
+
+export default class Wizard extends React.Component {
+
+ constructor({initialState, metadata, previewer}) {
+ super();
+ this.params = {};
+
+ metadata.forEach(([name,, v]) => this.params[name] = v);
+
+ Object.assign(this.params, initialState);
+
+ this.preview = previewer(this.params);
+ }
+
+ shouldComponentUpdate() {
+ // all controls are unmanaged and they should keep their state
+ // if the wizard manager gets updated when a new wizard appears
+ return false;
+ }
+
+ render() {
+ let {left, title, metadata, onOK, onCancel} = this.props;
+ let onClose = () => {
+ this.onClose();
+ onCancel();
+ };
+ return
+
+ {metadata.map(([name, type, , params], index) => {
+ return
+
+ {this.controlForType(name, type, params)}
+
+ } )}
+
+
+
+
+ ;
+ }
+
+ onClose() {
+ this.preview.dispose();
+ }
+
+ controlForType(name, type, params) {
+ const onChange = val => {
+ this.params[name] = val;
+ this.preview.update(this.params);
+ };
+ let initValue = this.params[name];
+ let commonProps = {
+ onChange, initValue, ...params
+ };
+ if (type === 'number') {
+ return
+ } else {
+ return
+ }
+ }
+}
+
+
+function uiLabel(name) {
+ return camelCaseSplit(name).map(w => w.toLowerCase()).join(' ');
+}
+
diff --git a/web/app/cad/dom/components/wizard/WizardManager.jsx b/web/app/cad/dom/components/wizard/WizardManager.jsx
new file mode 100644
index 00000000..2b771033
--- /dev/null
+++ b/web/app/cad/dom/components/wizard/WizardManager.jsx
@@ -0,0 +1,32 @@
+import React from 'react';
+import PropTypes from 'prop-types';
+import {TOKENS as WIZARD_TOKENS} from '../../../craft/wizard/wizardPlugin';
+import connect from 'ui/connect';
+import Wizard from "./Wizard";
+
+function WizardManager({wizards, close}, {services}) {
+ return wizards.map( ({type, initialState}, wizardIndex) => {
+ let {metadata, previewer, run} = services.operation.get(type);
+
+ function onOK(params) {
+ close();
+ run(type, params);
+ }
+ previewer = previewer.bind(null, {services});
+ return
+ });
+}
+
+WizardManager.contextTypes = {
+ services: PropTypes.object
+};
+
+export default connect(WizardManager, WIZARD_TOKENS.WIZARDS, {
+ mapProps: ([wizards]) => ({wizards}),
+ mapActions: dispatch => ({
+ close: wizard => dispatch(WIZARD_TOKENS.CLOSE, wizard)
+ })
+});
diff --git a/web/app/cad/dom/menu/MenuHolder.jsx b/web/app/cad/dom/menu/MenuHolder.jsx
index fafdc003..f1e8eaa8 100644
--- a/web/app/cad/dom/menu/MenuHolder.jsx
+++ b/web/app/cad/dom/menu/MenuHolder.jsx
@@ -10,8 +10,11 @@ import {TOKENS as KeyboardTokens} from "../../keyboard/keyboardPlugin";
function MenuHolder({menus}) {
return menus.map(({id, actions}) => {
let menuToken = MENU_TOKENS.menuState(id);
- return React.createElement(connect([menuToken, KeyboardTokens.KEYMAP],
- ActionMenu, {actions}, [,keymap => ({keymap})]), {key: id});
+ let connectedMenu = connect(ActionMenu, [menuToken, KeyboardTokens.KEYMAP], {
+ staticProps: {actions},
+ mapProps: [,keymap => ({keymap})]
+ });
+ return React.createElement(connectedMenu, {key: id});
});
}
@@ -23,13 +26,12 @@ function ActionMenu({actions, keymap, ...props}) {
}
const runToken = ACTION_TOKENS.actionRun(action);
return React.createElement(
- connect([ACTION_TOKENS.actionState(action), ACTION_TOKENS.actionAppearance(action)],
- ActionMenuItem,
- {hotKey: keymap[action]}, undefined,
- dispatch => ({
+ connect(ActionMenuItem, [ACTION_TOKENS.actionState(action), ACTION_TOKENS.actionAppearance(action)], {
+ staticProps: {hotKey: keymap[action]},
+ mapActions: dispatch => ({
onClick: () => dispatch(runToken)
- })),
- {key: action});
+ })
+ }), {key: action});
})}
;
}
@@ -60,7 +62,9 @@ function ActionMenuItem({label, cssIcons, icon32, icon96, onClick, enabled, hotK
}
-export default connect(MENU_TOKENS.MENUS, MenuHolder, undefined, ([menus]) => ({menus}));
+export default connect(MenuHolder, MENU_TOKENS.MENUS, {
+ mapProps: ([menus]) => ({menus})
+});
diff --git a/web/app/cad/dom/startReact.jsx b/web/app/cad/dom/startReact.jsx
index a16cce39..13bc44ba 100644
--- a/web/app/cad/dom/startReact.jsx
+++ b/web/app/cad/dom/startReact.jsx
@@ -2,9 +2,9 @@ import React from 'react';
import ReactDOM from 'react-dom';
import WebApplication from './components/WebApplication';
-export default function startReact(bus, callback) {
+export default function startReact(context, callback) {
return ReactDOM.render(
- ,
+ ,
document.getElementById('app'),
callback
);
diff --git a/web/app/cad/init/startApplication.js b/web/app/cad/init/startApplication.js
index cd4f2048..164ac3a3 100644
--- a/web/app/cad/init/startApplication.js
+++ b/web/app/cad/init/startApplication.js
@@ -7,6 +7,8 @@ import * as ActionSystemPlugin from '../actions/actionSystemPlugin';
import * as UiEntryPointsPlugin from '../dom/uiEntryPointsPlugin';
import * as MenuPlugin from '../dom/menu/menuPlugin';
import * as KeyboardPlugin from '../keyboard/keyboardPlugin';
+import * as WizardPlugin from '../craft/wizard/wizardPlugin';
+import * as OperationPlugin from '../craft/operationPlugin';
import * as PartModellerPlugin from '../part/partModellerPlugin';
@@ -20,7 +22,9 @@ export default function startApplication(callback) {
ActionSystemPlugin,
MenuPlugin,
UiEntryPointsPlugin,
- KeyboardPlugin
+ KeyboardPlugin,
+ WizardPlugin,
+ OperationPlugin
];
let plugins = [
@@ -38,14 +42,14 @@ export default function startApplication(callback) {
activatePlugins(preUIPlugins, context);
- startReact(context.bus, () => {
+ startReact(context, () => {
activatePlugins(plugins, context);
context.services.viewer.render();
callback(context);
});
}
-function activatePlugins(plugins, context) {
+export function activatePlugins(plugins, context) {
for (let plugin of plugins) {
plugin.activate(context);
}
diff --git a/web/app/cad/modeler-app.js b/web/app/cad/modeler-app.js
index 57e54d71..9e2d3f39 100644
--- a/web/app/cad/modeler-app.js
+++ b/web/app/cad/modeler-app.js
@@ -1,4 +1,3 @@
-import '../../../modules/scene/utils/vectorThreeEnhancement'
import Bus from 'bus'
import {Viewer} from './scene/viewer'
import {UI} from './ui/ctrl'
diff --git a/web/app/cad/part/partModellerPlugin.js b/web/app/cad/part/partModellerPlugin.js
index 6d6c787c..e95358c0 100644
--- a/web/app/cad/part/partModellerPlugin.js
+++ b/web/app/cad/part/partModellerPlugin.js
@@ -1,25 +1,12 @@
-import CoreActions from '../actions/coreActions';
-import OperationActions from '../actions/operationActions';
-import HistoryActions from '../actions/historyActions';
-import {TOKENS as UI_TOKENS} from '../dom/uiEntryPointsPlugin';
-import menuConfig from "./menuConfig";
+import * as UIConfigPlugin from './uiConfigPlugin';
+import * as PartOperationsPlugin from './partOperationsPlugin';
+import {activatePlugins} from "../init/startApplication";
-export function activate({bus, services}) {
+const PART_MODELLER_PLUGINS = [
+ UIConfigPlugin,
+ PartOperationsPlugin
+];
- services.action.registerActions(CoreActions);
- services.action.registerActions(OperationActions);
- services.action.registerActions(HistoryActions);
-
- services.menu.registerMenus(menuConfig);
-
- bus.dispatch(UI_TOKENS.CONTROL_BAR_LEFT, ['menu.file', 'menu.craft', 'menu.boolean', 'menu.primitives', 'Donate', 'GitHub']);
- bus.dispatch(UI_TOKENS.CONTROL_BAR_RIGHT, [
- ['Info', {label: null}],
- ['RefreshSketches', {label: null}],
- ['ShowSketches', {label: 'sketches'}], ['DeselectAll', {label: null}], ['ToggleCameraMode', {label: null}]
- ]);
-
- bus.dispatch(UI_TOKENS.TOOLBAR_BAR_LEFT, ['PLANE', 'EditFace', 'EXTRUDE', 'CUT', 'REVOLVE']);
- bus.dispatch(UI_TOKENS.TOOLBAR_BAR_LEFT_SECONDARY, ['INTERSECTION', 'DIFFERENCE', 'UNION']);
- bus.dispatch(UI_TOKENS.TOOLBAR_BAR_RIGHT, ['Save', 'StlExport']);
+export function activate(context) {
+ activatePlugins(PART_MODELLER_PLUGINS, context);
}
\ No newline at end of file
diff --git a/web/app/cad/part/partOperationsPlugin.js b/web/app/cad/part/partOperationsPlugin.js
new file mode 100644
index 00000000..896f2272
--- /dev/null
+++ b/web/app/cad/part/partOperationsPlugin.js
@@ -0,0 +1,7 @@
+import boxOperation from '../craft/primitives/boxOperation';
+
+export function activate({bus, services}) {
+ services.operation.registerOperations([
+ boxOperation
+ ])
+}
\ No newline at end of file
diff --git a/web/app/cad/part/uiConfigPlugin.js b/web/app/cad/part/uiConfigPlugin.js
new file mode 100644
index 00000000..ae933aec
--- /dev/null
+++ b/web/app/cad/part/uiConfigPlugin.js
@@ -0,0 +1,25 @@
+import CoreActions from '../actions/coreActions';
+import OperationActions from '../actions/operationActions';
+import HistoryActions from '../actions/historyActions';
+import {TOKENS as UI_TOKENS} from '../dom/uiEntryPointsPlugin';
+import menuConfig from "./menuConfig";
+
+export function activate({bus, services}) {
+
+ services.action.registerActions(CoreActions);
+ services.action.registerActions(OperationActions);
+ services.action.registerActions(HistoryActions);
+
+ services.menu.registerMenus(menuConfig);
+
+ bus.dispatch(UI_TOKENS.CONTROL_BAR_LEFT, ['menu.file', 'menu.craft', 'menu.boolean', 'menu.primitives', 'Donate', 'GitHub']);
+ bus.dispatch(UI_TOKENS.CONTROL_BAR_RIGHT, [
+ ['Info', {label: null}],
+ ['RefreshSketches', {label: null}],
+ ['ShowSketches', {label: 'sketches'}], ['DeselectAll', {label: null}], ['ToggleCameraMode', {label: null}]
+ ]);
+
+ bus.dispatch(UI_TOKENS.TOOLBAR_BAR_LEFT, ['PLANE', 'EditFace', 'EXTRUDE', 'CUT', 'REVOLVE']);
+ bus.dispatch(UI_TOKENS.TOOLBAR_BAR_LEFT_SECONDARY, ['INTERSECTION', 'DIFFERENCE', 'UNION']);
+ bus.dispatch(UI_TOKENS.TOOLBAR_BAR_RIGHT, ['Save', 'StlExport']);
+}
\ No newline at end of file
diff --git a/web/app/cad/preview/scenePreviewer.js b/web/app/cad/preview/scenePreviewer.js
new file mode 100644
index 00000000..4aa47f47
--- /dev/null
+++ b/web/app/cad/preview/scenePreviewer.js
@@ -0,0 +1,76 @@
+import * as SceneGraph from 'scene/sceneGraph';
+import {createTransparentPhongMaterial} from 'scene/materials';
+import {createMesh} from 'scene/objects/mesh';
+
+
+export function createPreviewer(sceneGeometryCreator) {
+
+ return function({services}, initParams) {
+ const previewGroup = SceneGraph.createGroup();
+ SceneGraph.addToGroup(services.cadScene.workGroup, previewGroup);
+
+ let previewObject = null;
+ function destroyPreviewObject() {
+ if (previewObject === null) {
+ return;
+ }
+ previewGroup.remove(previewObject);
+ previewObject.geometry.dispose();
+ previewObject = null;
+ }
+
+ function update(params) {
+ destroyPreviewObject();
+ previewObject = createMesh(sceneGeometryCreator(params), IMAGINARY_SURFACE_MATERIAL);
+ previewGroup.add(previewObject);
+ services.viewer.render();
+ }
+
+ function dispose() {
+ destroyPreviewObject();
+ SceneGraph.removeFromGroup(services.cadScene.workGroup, previewGroup);
+ services.viewer.render();
+ }
+ update(initParams);
+
+ return {update, dispose};
+ }
+}
+
+
+// function sketchBasedPreviewCreator(params) {
+// const face = app.findFace(params.face);
+// if (!face) return null;
+// const triangles = this.createImpl(app, params, face.sketch.fetchContours(), face);
+// return createMeshFromTriangles(triangles, IMAGINARY_SURFACE_MATERIAL);
+// }
+//
+// function sketchBasedNurbsPreviewCreator(params) {
+// const face = app.findFace(params.face);
+// if (!face) return null;
+// const needSketchRead = !this.sketch || params.face != this.face;
+// if (needSketchRead) {
+// this.sketch = ReadSketchFromFace(app, face);
+// this.face = params.face;
+// }
+// const nurbses = this.createNurbses(app, params, this.sketch, face);
+// const geom = new THREE.Geometry();
+//
+// for (let nurbs of nurbses) {
+// const off = geom.vertices.length;
+// const tess = nurbs.tessellate({maxDepth: 3});
+// const points = [];
+// tess.points.forEach(p => geom.vertices.push(new THREE.Vector3().fromArray(p)));
+// for (let faceIndices of tess.faces) {
+// let normales = faceIndices.map(function(x) {
+// var vn = tess.normals[x];
+// return new THREE.Vector3( vn[0], vn[1], vn[2] );
+// });
+// const face = new THREE.Face3(faceIndices[0] + off, faceIndices[1] + off, faceIndices[2] + off, normales);
+// geom.faces.push(face);
+// }
+// }
+// return new THREE.Mesh(geom, IMAGINARY_SURFACE_MATERIAL);
+// }
+
+export const IMAGINARY_SURFACE_MATERIAL = createTransparentPhongMaterial(0xFA8072, 0.5);
diff --git a/web/app/utils/utils.js b/web/app/utils/utils.js
index 9d95bc83..1ba1a3ff 100644
--- a/web/app/utils/utils.js
+++ b/web/app/utils/utils.js
@@ -41,32 +41,6 @@ export function swap(arr, i1, i2) {
arr[i2] = tmp;
}
-export function camelCaseSplit(str) {
- function isUpperCase(str) {
- return str.toUpperCase() == str;
- }
-
- const words = [];
- let word = '';
-
- for (let i = 0; i < str.length; i++) {
- const c = str.charAt(i);
- if (c == '_' || c == '-') {
- continue;
- }
- const dot = c === '.';
- if ((dot || isUpperCase(c)) && word.length != 0) {
- words.push(word);
- word = '';
- }
- if (!dot) word += c;
- }
- if (word.length != 0){
- words.push(word);
- }
- return words;
-}
-
export function defineIterable(obj, name, iteratorFactory) {
obj[name] = {};
obj[name][Symbol.iterator] = iteratorFactory;