wizard react support

This commit is contained in:
Val Erastov 2018-01-19 01:25:26 -08:00
parent ea5a3ae93e
commit ceb9b89616
36 changed files with 580 additions and 262 deletions

View file

@ -105,6 +105,10 @@ export default class Bus {
this.state[forToken] = initValue; this.state[forToken] = initValue;
} }
disableState(forToken) {
this.keepStateFor.delete(forToken);
delete this.state[forToken];
}
} }
export function createToken(...fqn) { export function createToken(...fqn) {

View file

@ -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;
}

View file

@ -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;
};

20
modules/scene/geoms.js Normal file
View file

@ -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;
}

View file

@ -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);
}

View file

@ -1,5 +1,6 @@
import DPR from 'dpr'; import DPR from 'dpr';
import './utils/threeLoader' import './utils/threeLoader';
import './utils/vectorThreeEnhancement';
export default class SceneSetUp { export default class SceneSetUp {

View file

@ -35,7 +35,8 @@ export default class Window extends React.Component {
width: this.state.width, width: this.state.width,
height: this.state.height, height: this.state.height,
left: this.state.left, left: this.state.left,
top: this.state.top top: this.state.top,
zIndex: 1
} }
} }
} }

View file

@ -2,9 +2,9 @@ import React from 'react';
import ls from './Button.less' import ls from './Button.less'
export default function Button({text, type}) { export default function Button({text, type, onClick}) {
return <button className={ls[type]}>{text}</button> return <button onClick={onClick} className={ls[type]}>{text}</button>
} }

View file

@ -9,7 +9,7 @@ export default class InputControl extends React.Component {
let {type, inputRef, ...props} = this.props; let {type, inputRef, ...props} = this.props;
return <div className={ls[type]}> return <div className={ls[type]}>
<input type='text' ref={inputRef} {...props} spellcheck='false' /> <input type='text' ref={inputRef} {...props} spellCheck='false' />
</div>; </div>;
} }
} }

View file

@ -15,12 +15,7 @@ export default class NumberControl extends React.Component {
onWheel = (e) => { onWheel = (e) => {
let {baseStep, round, min, max, onChange, accelerator} = this.props; let {baseStep, round, min, max, onChange, accelerator} = this.props;
let delta = 0; let delta = e.deltaY;
if ( e.wheelDelta ) { // WebKit / Opera / Explorer 9
delta = e.wheelDelta;
} else if ( e.detail ) { // Firefox
delta = - e.detail;
}
let val = e.target.value; let val = e.target.value;
if (!val) val = 0; if (!val) val = 0;
let step = baseStep * (e.shiftKey ? accelerator : 1); let step = baseStep * (e.shiftKey ? accelerator : 1);

View file

@ -11,19 +11,3 @@ export default class TextControl extends React.Component {
onChange={e => onChange(e.target.value)} /> 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
};

View file

@ -1,15 +1,16 @@
import React from 'react'; import React from 'react';
import PropTypes from 'prop-types'; 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)) { if (!Array.isArray(tokens)) {
tokens = [tokens]; tokens = [tokens];
} }
mapper = createMapper(mapper); mapProps = createMapper(mapProps);
dispatchMapper = dispatchMapper || function(dispatch) { mapActions = mapActions || function(dispatch) {
return dispatch; return dispatch;
}; };
@ -19,7 +20,7 @@ export default function connect(tokens, WrappedComponent, staticProps, mapper, d
super(); super();
this.mounted = false; this.mounted = false;
this.stateProps = {}; this.stateProps = {};
this.dispatchProps = dispatchMapper(this.dispatch); this.dispatchProps = mapActions(this.dispatch);
} }
componentWillMount() { componentWillMount() {
@ -37,12 +38,17 @@ export default function connect(tokens, WrappedComponent, staticProps, mapper, d
} }
setExternalState = (state) => { setExternalState = (state) => {
this.stateProps = mapper(state); this.stateProps = mapProps(state);
if (this.mounted) { if (this.mounted) {
this.forceUpdate(); this.forceUpdate();
} }
}; };
shouldComponentUpdate(nextProps, nextState) {
return !shallowEqual(this.props, nextProps);
}
dispatch = (event, data) => { dispatch = (event, data) => {
this.context.bus.dispatch(event, data); this.context.bus.dispatch(event, data);
}; };
@ -51,6 +57,9 @@ export default function connect(tokens, WrappedComponent, staticProps, mapper, d
return <WrappedComponent {...this.stateProps} {...this.dispatchProps} {...staticProps} /> return <WrappedComponent {...this.stateProps} {...this.dispatchProps} {...staticProps} />
} }
componentDidCatch() {
}
static contextTypes = { static contextTypes = {
bus: PropTypes.object bus: PropTypes.object
}; };

View file

@ -29,12 +29,6 @@ const OPERATION_ACTIONS = [
}, },
...requiresFaceSelection(1) ...requiresFaceSelection(1)
}, },
{
id: 'BOX',
appearance: {
info: 'creates new object box'
},
},
{ {
id: 'PLANE', id: 'PLANE',
appearance: { appearance: {
@ -78,7 +72,7 @@ const OPERATION_ACTIONS = [
function mergeInfo(action) { function mergeInfo(action) {
const op = Operations[action.id]; 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, { Object.assign(action.appearance, {
label: op.label, label: op.label,
icon32: op.icon + '32.png', icon32: op.icon + '32.png',

View file

@ -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
});

View file

@ -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
}
}

View file

@ -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'),
};

View file

@ -1,9 +1,9 @@
// import {MESH_OPERATIONS} from './mesh/workbench' // import {MESH_OPERATIONS} from './mesh/workbench'
// import {Extrude, Cut} from './brep/cut-extrude' // import {Extrude, Cut} from './brep/cut-extrude'
// import {Revolve} from './brep/revolve' // import {Revolve} from './brep/revolve'
// import {BREPSceneSolid} from '../scene/wrappers/brepSceneObject' import {BREPSceneSolid} from '../scene/wrappers/brepSceneObject'
// import {PlaneSceneObject} from '../scene/wrappers/planeSceneObject' // import {PlaneSceneObject} from '../scene/wrappers/planeSceneObject'
// import {box} from '../../brep/brep-primitives' import {box} from '../../brep/brep-primitives'
export const CUT = { export const CUT = {
icon: 'img/cad/cut', icon: 'img/cad/cut',

View file

@ -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
};

View file

@ -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'),
};

View file

@ -4,7 +4,7 @@ import ls from './ActionInfo.less';
import AuxWidget from '../../../../../modules/ui/components/AuxWidget'; import AuxWidget from '../../../../../modules/ui/components/AuxWidget';
import connect from '../../../../../modules/ui/connect'; import connect from '../../../../../modules/ui/connect';
import {TOKENS as ACTION_TOKENS} from '../../actions/actionSystemPlugin'; 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}) { function ActionInfo({actionId, x, y, info, hint, hotKey}) {
let visible = !!actionId; let visible = !!actionId;
@ -19,6 +19,7 @@ function ActionInfo({actionId, x, y, info, hint, hotKey}) {
</AuxWidget>; </AuxWidget>;
} }
export default connect([ACTION_TOKENS.HINT, KeyboardTokens.KEYMAP], ActionInfo, undefined, export default connect(ActionInfo, [ACTION_TOKENS.HINT, KeyboardTokens.KEYMAP], {
([ hintInfo, keymap ]) => (Object.assign({hotKey: hintInfo && keymap[hintInfo.actionId]}, hintInfo)) ); mapProps: ([ hintInfo, keymap ]) => (Object.assign({hotKey: hintInfo && keymap[hintInfo.actionId]}, hintInfo))
});

View file

@ -15,10 +15,13 @@ export default function PlugableControlBar() {
function ButtonGroup({actions}) { function ButtonGroup({actions}) {
return actions.map(actionRef => { return actions.map(actionRef => {
let [id, overrides] = toIdAndOverrides(actionRef); let [id, overrides] = toIdAndOverrides(actionRef);
let Comp = connect([ACTION_TOKENS.actionAppearance(id), ACTION_TOKENS.actionState(id)], let Comp = connect(ActionButton,
ActionButton, {actionId: id}, [ACTION_TOKENS.actionAppearance(id), ACTION_TOKENS.actionState(id)],
([appearance, state]) => Object.assign({}, appearance, state, overrides), {
mapActionBehavior(id) staticProps: {actionId: id},
mapProps: ([appearance, state]) => Object.assign({}, appearance, state, overrides),
mapActions: mapActionBehavior(id)
}
); );
return <Comp key={id}/>; return <Comp key={id}/>;
}); });
@ -46,8 +49,12 @@ class ActionButton extends React.Component {
} }
} }
const LeftGroup = connect(UI_TOKENS.CONTROL_BAR_LEFT, ButtonGroup, undefined, ([actions]) => ({actions})); const BUTTON_CONNECTOR = {
const RightGroup = connect(UI_TOKENS.CONTROL_BAR_RIGHT, ButtonGroup, undefined, ([actions]) => ({actions})); 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) { function getMenuData(el) {
//TODO: make more generic //TODO: make more generic

View file

@ -15,9 +15,11 @@ function ConfigurableToolbar({actions, small, ...props}) {
{actions.map(actionRef => { {actions.map(actionRef => {
let [id, overrides] = toIdAndOverrides(actionRef); let [id, overrides] = toIdAndOverrides(actionRef);
let Comp = connect( let Comp = connect(
[ACTION_TOKENS.actionAppearance(id), ACTION_TOKENS.actionState(id)], ActionButton,
ActionButton, {small}, [ACTION_TOKENS.actionAppearance(id), ACTION_TOKENS.actionState(id)], {
([appearance, state]) => Object.assign({}, appearance, state, overrides)); staticProps: {small},
mapProps: ([appearance, state]) => Object.assign({}, appearance, state, overrides)
});
return <Comp key={id}/> return <Comp key={id}/>
})} })}
</Toolbar> </Toolbar>
@ -37,7 +39,10 @@ function ActionButton({label, icon96, cssIcons, small, enabled, visible, onClick
} }
export function createPlugableToolbar(configToken, small) { 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); export const PlugableToolbarLeft = createPlugableToolbar(UI_TOKENS.TOOLBAR_BAR_LEFT);

View file

@ -1,10 +1,10 @@
import React from 'react'; import React from 'react';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import MenuHolder from "../menu/MenuHolder"; import MenuHolder from '../menu/MenuHolder';
import {TOKENS as MENU_TOKENS} from '../menu/menuPlugin'; import {TOKENS as MENU_TOKENS} from '../menu/menuPlugin';
import WindowSystem from 'ui/WindowSystem'; import WindowSystem from 'ui/WindowSystem';
import ActionInfo from "../actionInfo/ActionInfo"; import ActionInfo from '../actionInfo/ActionInfo';
export default class UISystem extends React.Component { export default class UISystem extends React.Component {
@ -17,12 +17,15 @@ export default class UISystem extends React.Component {
</div> </div>
} }
shouldComponentUpdate() {
return false;
}
closeAllUpPopups = () => { closeAllUpPopups = () => {
let openedMenus = this.context.bus.state[MENU_TOKENS.OPENED]; let openedMenus = this.context.bus.state[MENU_TOKENS.OPENED];
if (openedMenus && openedMenus.length !== 0) { if (openedMenus && openedMenus.length !== 0) {
this.context.bus.dispatch(MENU_TOKENS.CLOSE_ALL); this.context.bus.dispatch(MENU_TOKENS.CLOSE_ALL);
} }
}; };
getChildContext() { getChildContext() {

View file

@ -24,9 +24,15 @@ import ButtonGroup from "ui/components/controls/ButtonGroup";
import Button from "ui/components/controls/Button"; import Button from "ui/components/controls/Button";
import TextControl from './../../../../../modules/ui/components/controls/TextControl'; import TextControl from './../../../../../modules/ui/components/controls/TextControl';
import UISystem from './UISystem'; 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() { render() {
return <UISystem className={ls.root} > return <UISystem className={ls.root} >
@ -43,8 +49,8 @@ export default class View3d extends React.PureComponent {
<PlugableToolbarRight /> <PlugableToolbarRight />
</Abs> </Abs>
<PlugableControlBar /> <PlugableControlBar />
<WizardManager />
<WindowSystem />
<Window initWidth={250} initLeft={500} title="Test"> <Window initWidth={250} initLeft={500} title="Test">
<Stack > <Stack >
<Field> <Field>

View file

@ -15,9 +15,9 @@ const DEFAULT_VIEW = {id: 'view3d', label: '3D View', Component: View3d};
export default class WebApplication extends React.Component { export default class WebApplication extends React.Component {
constructor({bus}) { constructor({appContext}) {
super(); super();
this.bus = bus; this.appContext = appContext;
this.views = [DEFAULT_VIEW, {id: 'XXX', label: '3D View2', Component: Fragment}]; this.views = [DEFAULT_VIEW, {id: 'XXX', label: '3D View2', Component: Fragment}];
this.state = { this.state = {
activeView: DEFAULT_VIEW activeView: DEFAULT_VIEW
@ -49,11 +49,12 @@ export default class WebApplication extends React.Component {
} }
getChildContext() { getChildContext() {
return {bus: this.bus}; return this.appContext;
} }
static childContextTypes = { static childContextTypes = {
bus: PropTypes.object bus: PropTypes.object,
services: PropTypes.object
}; };
} }

View file

@ -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 <Window initWidth={250} initLeft={left} title={title} onClose={onClose}>
<Stack >
{metadata.map(([name, type, , params], index) => {
return <Field key={index}>
<Label>{uiLabel(name)}</Label>
{this.controlForType(name, type, params)}
</Field>
} )}
<ButtonGroup>
<Button text='Cancel' onClick={onClose} />
<Button text='OK' type='accent' onClick={() => {
this.onClose();
onOK(this.params);
}} />
</ButtonGroup>
</Stack>
</Window>;
}
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 <NumberControl {...commonProps} />
} else {
return <TextControl {...commonProps} />
}
}
}
function uiLabel(name) {
return camelCaseSplit(name).map(w => w.toLowerCase()).join(' ');
}

View file

@ -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 <Wizard key={wizardIndex} previewer={previewer} metadata={metadata}
onOK={onOK}
onCancel={close}
initialState={initialState} title={type} left={70 + wizardIndex * 250 + 20} />
});
}
WizardManager.contextTypes = {
services: PropTypes.object
};
export default connect(WizardManager, WIZARD_TOKENS.WIZARDS, {
mapProps: ([wizards]) => ({wizards}),
mapActions: dispatch => ({
close: wizard => dispatch(WIZARD_TOKENS.CLOSE, wizard)
})
});

View file

@ -10,8 +10,11 @@ import {TOKENS as KeyboardTokens} from "../../keyboard/keyboardPlugin";
function MenuHolder({menus}) { function MenuHolder({menus}) {
return menus.map(({id, actions}) => { return menus.map(({id, actions}) => {
let menuToken = MENU_TOKENS.menuState(id); let menuToken = MENU_TOKENS.menuState(id);
return React.createElement(connect([menuToken, KeyboardTokens.KEYMAP], let connectedMenu = connect(ActionMenu, [menuToken, KeyboardTokens.KEYMAP], {
ActionMenu, {actions}, [,keymap => ({keymap})]), {key: id}); 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); const runToken = ACTION_TOKENS.actionRun(action);
return React.createElement( return React.createElement(
connect([ACTION_TOKENS.actionState(action), ACTION_TOKENS.actionAppearance(action)], connect(ActionMenuItem, [ACTION_TOKENS.actionState(action), ACTION_TOKENS.actionAppearance(action)], {
ActionMenuItem, staticProps: {hotKey: keymap[action]},
{hotKey: keymap[action]}, undefined, mapActions: dispatch => ({
dispatch => ({
onClick: () => dispatch(runToken) onClick: () => dispatch(runToken)
})), })
{key: action}); }), {key: action});
})} })}
</Menu>; </Menu>;
} }
@ -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})
});

View file

@ -2,9 +2,9 @@ import React from 'react';
import ReactDOM from 'react-dom'; import ReactDOM from 'react-dom';
import WebApplication from './components/WebApplication'; import WebApplication from './components/WebApplication';
export default function startReact(bus, callback) { export default function startReact(context, callback) {
return ReactDOM.render( return ReactDOM.render(
<WebApplication bus={bus} />, <WebApplication appContext={context} />,
document.getElementById('app'), document.getElementById('app'),
callback callback
); );

View file

@ -7,6 +7,8 @@ import * as ActionSystemPlugin from '../actions/actionSystemPlugin';
import * as UiEntryPointsPlugin from '../dom/uiEntryPointsPlugin'; import * as UiEntryPointsPlugin from '../dom/uiEntryPointsPlugin';
import * as MenuPlugin from '../dom/menu/menuPlugin'; import * as MenuPlugin from '../dom/menu/menuPlugin';
import * as KeyboardPlugin from '../keyboard/keyboardPlugin'; 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'; import * as PartModellerPlugin from '../part/partModellerPlugin';
@ -20,7 +22,9 @@ export default function startApplication(callback) {
ActionSystemPlugin, ActionSystemPlugin,
MenuPlugin, MenuPlugin,
UiEntryPointsPlugin, UiEntryPointsPlugin,
KeyboardPlugin KeyboardPlugin,
WizardPlugin,
OperationPlugin
]; ];
let plugins = [ let plugins = [
@ -38,14 +42,14 @@ export default function startApplication(callback) {
activatePlugins(preUIPlugins, context); activatePlugins(preUIPlugins, context);
startReact(context.bus, () => { startReact(context, () => {
activatePlugins(plugins, context); activatePlugins(plugins, context);
context.services.viewer.render(); context.services.viewer.render();
callback(context); callback(context);
}); });
} }
function activatePlugins(plugins, context) { export function activatePlugins(plugins, context) {
for (let plugin of plugins) { for (let plugin of plugins) {
plugin.activate(context); plugin.activate(context);
} }

View file

@ -1,4 +1,3 @@
import '../../../modules/scene/utils/vectorThreeEnhancement'
import Bus from 'bus' import Bus from 'bus'
import {Viewer} from './scene/viewer' import {Viewer} from './scene/viewer'
import {UI} from './ui/ctrl' import {UI} from './ui/ctrl'

View file

@ -1,25 +1,12 @@
import CoreActions from '../actions/coreActions'; import * as UIConfigPlugin from './uiConfigPlugin';
import OperationActions from '../actions/operationActions'; import * as PartOperationsPlugin from './partOperationsPlugin';
import HistoryActions from '../actions/historyActions'; import {activatePlugins} from "../init/startApplication";
import {TOKENS as UI_TOKENS} from '../dom/uiEntryPointsPlugin';
import menuConfig from "./menuConfig";
export function activate({bus, services}) { const PART_MODELLER_PLUGINS = [
UIConfigPlugin,
PartOperationsPlugin
];
services.action.registerActions(CoreActions); export function activate(context) {
services.action.registerActions(OperationActions); activatePlugins(PART_MODELLER_PLUGINS, context);
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']);
} }

View file

@ -0,0 +1,7 @@
import boxOperation from '../craft/primitives/boxOperation';
export function activate({bus, services}) {
services.operation.registerOperations([
boxOperation
])
}

View file

@ -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']);
}

View file

@ -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);

View file

@ -41,32 +41,6 @@ export function swap(arr, i1, i2) {
arr[i2] = tmp; 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) { export function defineIterable(obj, name, iteratorFactory) {
obj[name] = {}; obj[name] = {};
obj[name][Symbol.iterator] = iteratorFactory; obj[name][Symbol.iterator] = iteratorFactory;