diff --git a/modules/gems/iterables.js b/modules/gems/iterables.js
index b6b55ef2..4c43a9e2 100644
--- a/modules/gems/iterables.js
+++ b/modules/gems/iterables.js
@@ -47,4 +47,21 @@ export function indexArray(array, getKey, getValue = v => v) {
return obj;
}
+export function addToListInMap(map, key, value) {
+ let list = map.get(key);
+ if (!list) {
+ list = [];
+ map.set(key, list);
+ }
+ list.push(value);
+}
+
+export function removeInPlace(arr, val) {
+ let index = arr.indexOf(val);
+ if (index !== -1) {
+ arr.splice(index, 1);
+ }
+ return arr;
+}
+
export const EMPTY_ARRAY = Object.freeze([]);
diff --git a/modules/gems/linkedMap.js b/modules/gems/linkedMap.js
new file mode 100644
index 00000000..081783d6
--- /dev/null
+++ b/modules/gems/linkedMap.js
@@ -0,0 +1,44 @@
+export class OrderedMap {
+
+ constructor() {
+ this.order = [];
+ this.map = new Map();
+ }
+
+ forEach(cb) {
+ this.order.forEach(key => cb(this.map.get(key), key));
+ }
+
+ set(key, value) {
+ if (!this.map.has(key)) {
+ this.map.set(key, value);
+ this.order.push(key)
+ }
+ }
+
+ get(key) {
+ return this.map(key);
+ }
+
+ has(key) {
+ this.map.has(key);
+ }
+
+ delete(key) {
+ this.map.delete(key);
+ let index = this.order.indexOf(key);
+ if (index !== -1) {
+ this.order.splice(index, 1);
+ }
+ }
+
+ clear() {
+ this.map.clear();
+ this.order.length = 0;
+ }
+
+ get size() {
+ return this.map.size;
+ }
+
+}
diff --git a/modules/lstream/base.js b/modules/lstream/base.js
index 6349a201..88d55074 100644
--- a/modules/lstream/base.js
+++ b/modules/lstream/base.js
@@ -26,6 +26,10 @@ export class StreamBase {
this.attach(v => stateStream.next(v));
return stateStream;
}
+
+ distinct() {
+ return new DistinctStream(this);
+ }
}
const {MapStream} = require('./map');
@@ -33,3 +37,4 @@ const {FilterStream} = require('./filter');
const {StateStream} = require('./state');
const {PairwiseStream} = require('./pairwise');
const {ScanStream} = require('./scan');
+const {DistinctStream} = require('./distinct');
diff --git a/modules/lstream/distinct.js b/modules/lstream/distinct.js
new file mode 100644
index 00000000..6593a621
--- /dev/null
+++ b/modules/lstream/distinct.js
@@ -0,0 +1,19 @@
+import {StreamBase} from './base';
+
+export class DistinctStream extends StreamBase {
+
+ constructor(stream) {
+ super();
+ this.stream = stream;
+ this.latest = undefined;
+ }
+
+ attach(observer) {
+ return this.stream.attach(v => {
+ if (this.latest !== v) {
+ observer(v);
+ this.latest = v;
+ }
+ });
+ }
+}
diff --git a/modules/lstream/index.js b/modules/lstream/index.js
index 10582200..2b0d2777 100644
--- a/modules/lstream/index.js
+++ b/modules/lstream/index.js
@@ -12,6 +12,8 @@ export function stream() {
return new Emitter();
}
+export const eventStream = stream;
+
export function combine(...streams) {
return new CombineStream(streams);
}
diff --git a/modules/ui/components/controls/Field.jsx b/modules/ui/components/controls/Field.jsx
index 9561ce7e..58c77e97 100644
--- a/modules/ui/components/controls/Field.jsx
+++ b/modules/ui/components/controls/Field.jsx
@@ -1,10 +1,8 @@
import React from 'react';
import ls from './Field.less'
+import cx from 'classnames';
-export default function Field({children}) {
-
- return
- {children}
-
;
+export default function Field({active, ...props}) {
+ return
}
diff --git a/modules/ui/components/controls/Field.less b/modules/ui/components/controls/Field.less
index 352f1da8..49dd08b4 100644
--- a/modules/ui/components/controls/Field.less
+++ b/modules/ui/components/controls/Field.less
@@ -1,5 +1,11 @@
+@import "~ui/styles/theme.less";
+
.root {
display: flex;
justify-content: space-between;
align-items: baseline;
-}
\ No newline at end of file
+}
+
+.active {
+ background-color: @color-highlight
+}
diff --git a/modules/ui/components/controls/NumberControl.jsx b/modules/ui/components/controls/NumberControl.jsx
index dbe16b87..b23d2166 100644
--- a/modules/ui/components/controls/NumberControl.jsx
+++ b/modules/ui/components/controls/NumberControl.jsx
@@ -5,11 +5,12 @@ import InputControl from './InputControl';
export default class NumberControl extends React.Component {
render() {
- let {onChange, value} = this.props;
+ let {onChange, onFocus, value} = this.props;
return this.input = input} />
}
diff --git a/modules/ui/components/controls/TextControl.jsx b/modules/ui/components/controls/TextControl.jsx
index 759e1080..c2bbc815 100644
--- a/modules/ui/components/controls/TextControl.jsx
+++ b/modules/ui/components/controls/TextControl.jsx
@@ -5,9 +5,9 @@ import InputControl from './InputControl';
export default class TextControl extends React.Component {
render() {
- let {onChange, initValue} = this.props;
+ let {onChange, initValue, onFocus} = this.props;
return onChange(e.target.value)} />
+ defaultValue={initValue}
+ onChange={e => onChange(e.target.value)} onFocus={onFocus} />
}
}
diff --git a/web/app/cad/actions/anonHint.js b/web/app/cad/actions/anonHint.js
index 86111de6..eadbc2d6 100644
--- a/web/app/cad/actions/anonHint.js
+++ b/web/app/cad/actions/anonHint.js
@@ -10,7 +10,8 @@ export function enableAnonymousActionHint({streams, services}) {
requester: 'anonymous'
});
setTimeout(() => {
- if (!streams.action.hint.value.requester === 'anonymous') {
+ let value = streams.action.hint.value;
+ if (value && value.requester !== 'anonymous') {
services.action.showHintFor(null);
}
}, 1000);
diff --git a/web/app/cad/craft/boolean/BooleanWizard.jsx b/web/app/cad/craft/boolean/BooleanWizard.jsx
index 198c05da..6b8d1dc5 100644
--- a/web/app/cad/craft/boolean/BooleanWizard.jsx
+++ b/web/app/cad/craft/boolean/BooleanWizard.jsx
@@ -1,11 +1,10 @@
import React from 'react';
import {Group} from '../wizard/components/form/Form';
-import BooleanChoice from '../wizard/components/form/BooleanChioce';
-import SingleEntity from '../wizard/components/form/SingleEntity';
+import EntityList from '../wizard/components/form/EntityList';
export default function BooleanWizard() {
return
-
-
+
+
;
}
\ No newline at end of file
diff --git a/web/app/cad/craft/boolean/booleanOpSchema.js b/web/app/cad/craft/boolean/booleanOpSchema.js
index 5984adf5..fb309ac1 100644
--- a/web/app/cad/craft/boolean/booleanOpSchema.js
+++ b/web/app/cad/craft/boolean/booleanOpSchema.js
@@ -1,10 +1,12 @@
export default {
operandA: {
type: 'shell',
+ markColor: 0xC9FFBC,
defaultValue: {type: 'selection'}
},
operandB: {
type: 'shell',
+ markColor: 0xFFBEB4,
defaultValue: {type: 'selection'}
}
};
diff --git a/web/app/cad/craft/boolean/booleanOperation.js b/web/app/cad/craft/boolean/booleanOperation.js
index d8d96ddf..9ffc238d 100644
--- a/web/app/cad/craft/boolean/booleanOperation.js
+++ b/web/app/cad/craft/boolean/booleanOperation.js
@@ -11,10 +11,6 @@ const run = type => (params, services) => {
const paramsInfo = ({operandA, operandB}) => `(${operandA}, ${operandB})`;
-const selectionMode = {
- shell: true
-};
-
export const intersectionOperation = {
id: 'INTERSECTION',
label: 'intersection',
@@ -24,7 +20,6 @@ export const intersectionOperation = {
form: BooleanWizard,
schema,
run: run('INTERSECTION'),
- selectionMode
};
export const subtractOperation = {
@@ -36,7 +31,6 @@ export const subtractOperation = {
form: BooleanWizard,
schema,
run: run('SUBTRACT'),
- selectionMode
};
export const unionOperation = {
@@ -48,5 +42,4 @@ export const unionOperation = {
form: BooleanWizard,
schema,
run: run('UNION'),
- selectionMode
};
diff --git a/web/app/cad/craft/cutExtrude/form.js b/web/app/cad/craft/cutExtrude/form.js
index a6d65019..23fc3e7a 100644
--- a/web/app/cad/craft/cutExtrude/form.js
+++ b/web/app/cad/craft/cutExtrude/form.js
@@ -1,7 +1,7 @@
import React from 'react';
import {NumberField} from '../wizard/components/form/Fields';
import {Group} from '../wizard/components/form/Form';
-import SingleEntity from '../wizard/components/form/SingleEntity';
+import EntityList from '../wizard/components/form/EntityList';
export default function (valueLabel) {
return function PrismForm() {
@@ -10,7 +10,7 @@ export default function (valueLabel) {
-
+
;
};
}
\ No newline at end of file
diff --git a/web/app/cad/craft/cutExtrude/previewer.js b/web/app/cad/craft/cutExtrude/previewer.js
index 29b41cfa..c7f45d10 100644
--- a/web/app/cad/craft/cutExtrude/previewer.js
+++ b/web/app/cad/craft/cutExtrude/previewer.js
@@ -11,7 +11,7 @@ export function createPreviewGeomProvider(inversed) {
return function previewGeomProvider(params, services) {
const face = services.cadRegistry.findFace(params.face);
- if (!face) return null;
+ if (!face || !face.sketch) return null;
let sketch = face.sketch.fetchContours();
const encloseDetails = getEncloseDetails(params, sketch, face.csys, face.surface, !inversed);
diff --git a/web/app/cad/craft/datum/create/CreateDatumWizard.jsx b/web/app/cad/craft/datum/create/CreateDatumWizard.jsx
index 7730ba4f..8dccbee9 100644
--- a/web/app/cad/craft/datum/create/CreateDatumWizard.jsx
+++ b/web/app/cad/craft/datum/create/CreateDatumWizard.jsx
@@ -1,13 +1,13 @@
import React from 'react';
import {Group} from '../../wizard/components/form/Form';
import {NumberField} from '../../wizard/components/form/Fields';
-import SingleEntity from '../../wizard/components/form/SingleEntity';
+import EntityList from '../../wizard/components/form/EntityList';
export default function CreateDatumWizard() {
return
-
+
;
}
\ No newline at end of file
diff --git a/web/app/cad/craft/e0/e0Plugin.js b/web/app/cad/craft/e0/e0Plugin.js
index 6ae7d016..384c2c35 100644
--- a/web/app/cad/craft/e0/e0Plugin.js
+++ b/web/app/cad/craft/e0/e0Plugin.js
@@ -143,13 +143,15 @@ function operationHandler(id, request, services) {
return singleShellRespone(face.shell, data);
}
case 'FILLET': {
- let edge = services.cadRegistry.findEdge(request.edges[0].edge);
-
- let engineReq = Object.assign({}, request, {
+ let edge = services.cadRegistry.findEdge(request.edges[0]);
+ let engineReq = {
deflection: DEFLECTION,
solid: edge.shell.brepShell.data.externals.ptr,
- edges: request.edges.map(e => Object.assign({}, e, {edge: services.cadRegistry.findEdge(e.edge).brepEdge.data.externals.ptr}))
- });
+ edges: request.edges.map(e => ({
+ edge: services.cadRegistry.findEdge(e).brepEdge.data.externals.ptr,
+ thickness: request.thickness
+ }))
+ };
let data = callEngine(engineReq, Module._SPI_fillet);
return singleShellRespone(edge.shell, data);
diff --git a/web/app/cad/craft/fillet/FilletForm.jsx b/web/app/cad/craft/fillet/FilletForm.jsx
index 96b53023..6618ea73 100644
--- a/web/app/cad/craft/fillet/FilletForm.jsx
+++ b/web/app/cad/craft/fillet/FilletForm.jsx
@@ -1,16 +1,11 @@
import React from 'react';
-import MultiEntity from '../wizard/components/form/MultiEntity';
import {NumberField} from '../wizard/components/form/Fields';
-import filletSchema from './schema';
+import EntityList from '../wizard/components/form/EntityList';
+import {Group} from '../wizard/components/form/Form';
export default function FilletWizard() {
-
- let {defaultValue: {itemField, entity}, schema} = filletSchema.edges;
-
- return
+ return
+
- ;
+ ;
}
\ No newline at end of file
diff --git a/web/app/cad/craft/fillet/schema.js b/web/app/cad/craft/fillet/schema.js
index fdf8ca3d..bac6f420 100644
--- a/web/app/cad/craft/fillet/schema.js
+++ b/web/app/cad/craft/fillet/schema.js
@@ -1,19 +1,13 @@
export default {
edges: {
type: 'array',
+ itemType: 'edge',
defaultValue: {
type: 'selection',
- entity: 'edge',
- itemField: 'edge'
- },
- schema: {
- edge: {
- type: 'edge',
- },
- thickness: {
- type: 'number',
- defaultValue: 20
- }
}
+ },
+ thickness: {
+ type: 'number',
+ defaultValue: 20
}
}
diff --git a/web/app/cad/craft/intializeBySchema.js b/web/app/cad/craft/intializeBySchema.js
index 2014e860..bcd2a2e4 100644
--- a/web/app/cad/craft/intializeBySchema.js
+++ b/web/app/cad/craft/intializeBySchema.js
@@ -1,4 +1,5 @@
import {ENTITIES} from '../scene/entites';
+import {isEntityType} from './schemaUtils';
export default function initializeBySchema(schema, context) {
let fields = Object.keys(schema);
@@ -7,21 +8,31 @@ export default function initializeBySchema(schema, context) {
let val;
let md = schema[field];
if (md.type === 'array') {
- if (md.defaultValue) {
- if (md.defaultValue.type === 'selection') {
- let {itemField, entity} = md.defaultValue;
- val = context.streams.selection[entity].value.map(s => {
- let item = initializeBySchema(md.schema, context);
- item[itemField] = s;
- return item;
- });
+ if (md.itemType === 'object') {
+ if (md.defaultValue) {
+ if (md.defaultValue.type === 'selection') {
+ let {itemField, entity} = md.defaultValue;
+ val = context.streams.selection[entity].value.map(s => {
+ let item = initializeBySchema(md.schema, context);
+ item[itemField] = s;
+ return item;
+ });
+ } else {
+ val = md.defaultValue;
+ }
} else {
- val = md.defaultValue;
+ val = [];
+ }
+ } else if (isEntityType(md.itemType)) {
+ if (md.defaultValue && md.defaultValue.type === 'selection') {
+ val = [...context.streams.selection[md.itemType].value];
+ } else {
+ val = []
}
} else {
- val = [];
+ throw 'unsupport';
}
- } else if (ENTITIES.indexOf(md.type) !== -1 && md.defaultValue && md.defaultValue.type === 'selection') {
+ } else if (isEntityType(md.type) && md.defaultValue && md.defaultValue.type === 'selection') {
val = context.streams.selection[md.type].value[0];
} else if (md.type === 'object') {
val = initializeBySchema(md.schema, context);
diff --git a/web/app/cad/craft/materializeParams.js b/web/app/cad/craft/materializeParams.js
index 967af637..026a908a 100644
--- a/web/app/cad/craft/materializeParams.js
+++ b/web/app/cad/craft/materializeParams.js
@@ -1,4 +1,4 @@
-import {ENTITIES} from '../scene/entites';
+import {isEntityType} from './schemaUtils';
export default function materializeParams(services, params, schema, result, errors, parentPath) {
@@ -45,7 +45,7 @@ export default function materializeParams(services, params, schema, result, erro
if (md.values.indexOf(value) === -1) {
errors.push({path: [...parentPath, field], message: 'invalid value'});
}
- } else if (ENTITIES.indexOf(md.type) !== -1) {
+ } else if (isEntityType(md.type)) {
if (typeof value !== 'string') {
errors.push({path: [...parentPath, field], message: 'not a valid model reference'});
}
@@ -55,17 +55,27 @@ export default function materializeParams(services, params, schema, result, erro
}
let model = services.cadRegistry.findEntity(md.type, ref);
if (!model) {
- errors.push({path: [...parentPath, field], message: 'referrers to nonexistent ' + md.entity});
+ errors.push({path: [...parentPath, field], message: 'referrers to nonexistent ' + md.type});
}
} else if (md.type === 'array') {
if (!Array.isArray(value)) {
errors.push({path: [...parentPath, field], message: 'not an array type'});
}
- value = value.map((item , i) => {
- let itemResult = {};
- materializeParams(services, item, md.schema, itemResult, errors, [...parentPath, i]);
- return itemResult;
- });
+ if (md.itemType === 'object') {
+ value = value.map((item , i) => {
+ let itemResult = {};
+ materializeParams(services, item, md.schema, itemResult, errors, [...parentPath, i]);
+ return itemResult;
+ });
+ } else {
+ if (isEntityType(md.itemType)) {
+ value.forEach(ref => {
+ if (!services.cadRegistry.findEntity(md.itemType, ref)) {
+ errors.push({path: [...parentPath, field], message: 'referrers to nonexistent ' + md.itemType});
+ }
+ })
+ }
+ }
}
result[field] = value;
}
diff --git a/web/app/cad/craft/operationPlugin.js b/web/app/cad/craft/operationPlugin.js
index a5e5fd72..b2754c84 100644
--- a/web/app/cad/craft/operationPlugin.js
+++ b/web/app/cad/craft/operationPlugin.js
@@ -1,4 +1,5 @@
import {state} from 'lstream';
+import {isEntityType} from './schemaUtils';
export function activate(context) {
let {services} = context;
@@ -25,7 +26,9 @@ export function activate(context) {
};
actions.push(opAction);
- registry$.mutate(registry => registry[id] = Object.assign({appearance}, descriptor, {
+ let schemaIndex = createSchemaIndex(descriptor.schema);
+
+ registry$.mutate(registry => registry[id] = Object.assign({appearance, schemaIndex}, descriptor, {
run: (request, services) => runOperation(request, descriptor, services)
}));
}
@@ -64,3 +67,27 @@ export function activate(context) {
handlers
};
}
+
+function createSchemaIndex(schema) {
+ const entitiesByType = {};
+ const entitiesByParam = {};
+ const entityParams = [];
+ for (let field of Object.keys(schema)) {
+ let md = schema[field];
+ let entityType = md.type === 'array' ? md.itemType : md.type;
+
+ if (isEntityType(entityType)) {
+ let byType = entitiesByType[entityType];
+ if (!byType) {
+ byType = [];
+ entitiesByType[entityType] = byType;
+ }
+ byType.push(field);
+ entitiesByParam[field] = entityType;
+ entityParams.push(field);
+ }
+ }
+ return {entitiesByType, entitiesByParam,
+ entityParams: Object.keys(entitiesByParam)
+ };
+}
\ No newline at end of file
diff --git a/web/app/cad/craft/primitives/plane/PlaneWizard.jsx b/web/app/cad/craft/primitives/plane/PlaneWizard.jsx
index be08e525..aa74b2ea 100644
--- a/web/app/cad/craft/primitives/plane/PlaneWizard.jsx
+++ b/web/app/cad/craft/primitives/plane/PlaneWizard.jsx
@@ -1,8 +1,6 @@
import React from 'react';
import {Group} from '../../wizard/components/form/Form';
-import {NumberField, RadioButtonsField, ReadOnlyValueField} from '../../wizard/components/form/Fields';
-import SingleEntity from '../../wizard/components/form/SingleEntity';
-import {RadioButton} from 'ui/components/controls/RadioButtons';
+import {ReadOnlyValueField} from '../../wizard/components/form/Fields';
export default function PlaneWizard() {
diff --git a/web/app/cad/craft/primitives/simplePlane/SimplePlaneWizard.jsx b/web/app/cad/craft/primitives/simplePlane/SimplePlaneWizard.jsx
index 94ad4ca4..b26a1817 100644
--- a/web/app/cad/craft/primitives/simplePlane/SimplePlaneWizard.jsx
+++ b/web/app/cad/craft/primitives/simplePlane/SimplePlaneWizard.jsx
@@ -1,7 +1,7 @@
import React from 'react';
import {Group} from '../../wizard/components/form/Form';
import {NumberField, RadioButtonsField} from '../../wizard/components/form/Fields';
-import SingleEntity from '../../wizard/components/form/SingleEntity';
+import EntityList from '../../wizard/components/form/EntityList';
import {RadioButton} from 'ui/components/controls/RadioButtons';
@@ -12,7 +12,7 @@ export default function PlaneWizard() {
-
+
;
}
\ No newline at end of file
diff --git a/web/app/cad/craft/revolve/RevolveForm.jsx b/web/app/cad/craft/revolve/RevolveForm.jsx
index c29c3f2a..eef95c7a 100644
--- a/web/app/cad/craft/revolve/RevolveForm.jsx
+++ b/web/app/cad/craft/revolve/RevolveForm.jsx
@@ -1,14 +1,14 @@
import React from 'react';
import {CheckboxField, NumberField} from '../wizard/components/form/Fields';
import {Group} from '../wizard/components/form/Form';
-import SingleEntity from '../wizard/components/form/SingleEntity';
+import EntityList from '../wizard/components/form/EntityList';
export default function RevolveForm() {
return
-
-
+
+
;
}
\ No newline at end of file
diff --git a/web/app/cad/craft/schemaUtils.js b/web/app/cad/craft/schemaUtils.js
new file mode 100644
index 00000000..d51af5ef
--- /dev/null
+++ b/web/app/cad/craft/schemaUtils.js
@@ -0,0 +1,5 @@
+import {ENTITIES} from '../scene/entites';
+
+export function isEntityType(type) {
+ return ENTITIES.indexOf(type) !== -1
+}
\ No newline at end of file
diff --git a/web/app/cad/craft/ui/ObjectExplorer.jsx b/web/app/cad/craft/ui/ObjectExplorer.jsx
index bf28ce99..12d2f329 100644
--- a/web/app/cad/craft/ui/ObjectExplorer.jsx
+++ b/web/app/cad/craft/ui/ObjectExplorer.jsx
@@ -46,7 +46,7 @@ function FaceSection({face}) {
const ModelSection = decoratorChain(
mapContext((ctx, props) => ({
- select: () => ctx.services.selection[props.type].select([props.model.id])
+ select: () => ctx.services.pickControl.pick(props.model)
})),
connect((streams, props) => (streams.selection[props.type] || constant([])).map(selection => ({selection}))))
(
diff --git a/web/app/cad/craft/wizard/components/Wizard.jsx b/web/app/cad/craft/wizard/components/Wizard.jsx
index 58baa831..bdbe5ec4 100644
--- a/web/app/cad/craft/wizard/components/Wizard.jsx
+++ b/web/app/cad/craft/wizard/components/Wizard.jsx
@@ -15,9 +15,13 @@ import mapContext from 'ui/mapContext';
updateParam: (name, value) => {
let workingRequest$ = ctx.streams.wizard.workingRequest;
if (workingRequest$.value.params && workingRequest$.value.type) {
- workingRequest$.mutate(data => data.params[name] = value)
+ workingRequest$.mutate(data => {
+ data.params[name] = value;
+ data.state.activeParam = name;
+ })
}
- }
+ },
+ setActiveParam: name => ctx.streams.wizard.workingRequest.mutate(data => data.state && (data.state.activeParam = name))
}))
export default class Wizard extends React.Component {
@@ -35,7 +39,7 @@ export default class Wizard extends React.Component {
return operation error;
}
- let {left, type, params, resolveOperation, updateParam} = this.props;
+ let {left, type, params, state, resolveOperation, updateParam, setActiveParam} = this.props;
if (!type) {
return null;
}
@@ -50,6 +54,8 @@ export default class Wizard extends React.Component {
let formContext = {
data: params,
+ activeParam: state.activeParam,
+ setActiveParam,
updateParam
};
diff --git a/web/app/cad/craft/wizard/components/form/EntityList.jsx b/web/app/cad/craft/wizard/components/form/EntityList.jsx
new file mode 100644
index 00000000..3549fb0e
--- /dev/null
+++ b/web/app/cad/craft/wizard/components/form/EntityList.jsx
@@ -0,0 +1,45 @@
+import React from 'react';
+import ls from './EntityList.less';
+import Label from 'ui/components/controls/Label';
+import Field from 'ui/components/controls/Field';
+import Fa from 'ui/components/Fa';
+import {attachToForm} from './Form';
+import {camelCaseSplitToStr} from 'gems/camelCaseSplit';
+import {EMPTY_ARRAY, removeInPlace} from '../../../../../../../modules/gems/iterables';
+
+@attachToForm
+export default class EntityList extends React.Component {
+
+ deselect = (entityId) => {
+ let {value, onChange} = this.props;
+ if (Array.isArray(value)) {
+ onChange(removeInPlace(value, entityId));
+ } else {
+ onChange(undefined);
+ }
+ };
+
+ render() {
+ let {name, label, active, setActive, value} = this.props;
+ if (!Array.isArray(value)) {
+ value = value ? asArray(value) : EMPTY_ARRAY;
+ }
+ return
+
+ {value.length === 0 ?
+ {''} :
+ value.map((entity, i) =>
+ {entity} this.deselect(entity)}>
+ )}
+
+ ;
+ }
+}
+
+function asArray(val) {
+ _arr[0] = val;
+ return _arr;
+}
+
+const _arr = [];
+
diff --git a/web/app/cad/craft/wizard/components/form/EntityList.less b/web/app/cad/craft/wizard/components/form/EntityList.less
new file mode 100644
index 00000000..9b7b75e8
--- /dev/null
+++ b/web/app/cad/craft/wizard/components/form/EntityList.less
@@ -0,0 +1,23 @@
+.emptySelection {
+ font-style: initial;
+ color: #BFBFBF;
+}
+
+.entityRef {
+ background-color: #3a687d;
+ border-radius: 2px;
+ border: 1px solid #d2d0e0;
+ padding: 0 3px;
+ margin: 0 2px 2px 2px;
+ display: inline-block;
+ & .rm {
+ background: none;
+ cursor: pointer;
+ &:hover {
+ color: salmon;
+ }
+ &:active {
+ color: red;
+ }
+ }
+}
\ No newline at end of file
diff --git a/web/app/cad/craft/wizard/components/form/Form.jsx b/web/app/cad/craft/wizard/components/form/Form.jsx
index ddf911e6..8d36e7fc 100644
--- a/web/app/cad/craft/wizard/components/form/Form.jsx
+++ b/web/app/cad/craft/wizard/components/form/Form.jsx
@@ -13,8 +13,8 @@ export function Group({children}) {
}
export function formField(Control) {
- return function FormPrimitive({label, name, ...props}) {
- return
+ return function FormPrimitive({label, name, active, setActive, ...props}) {
+ return
;
@@ -27,8 +27,13 @@ export function attachToForm(Control) {
{
ctx => {
const onChange = val => ctx.updateParam(name, val);
+ const setActive = val => ctx.setActiveParam(name);
return
-
+
;
}
}
diff --git a/web/app/cad/craft/wizard/components/form/MultiEntity.jsx b/web/app/cad/craft/wizard/components/form/MultiEntity.jsx
index 886d396a..28988c5a 100644
--- a/web/app/cad/craft/wizard/components/form/MultiEntity.jsx
+++ b/web/app/cad/craft/wizard/components/form/MultiEntity.jsx
@@ -44,7 +44,8 @@ export default class MultiEntity extends React.Component {
updateParam: (name, value) => {
data[name] = value;
ctx.updateParam(this.props.name, this.props.value);
- }
+ },
+ ...ctx
};
let {itemField} = this.props;
let entityId = data[itemField];
diff --git a/web/app/cad/craft/wizard/components/form/SingleEntity.jsx b/web/app/cad/craft/wizard/components/form/SingleEntity.jsx
deleted file mode 100644
index 63601cfe..00000000
--- a/web/app/cad/craft/wizard/components/form/SingleEntity.jsx
+++ /dev/null
@@ -1,53 +0,0 @@
-import React from 'react';
-import mapContext from 'ui/mapContext';
-import ls from './SingleEntity.less';
-import Label from 'ui/components/controls/Label';
-import Field from 'ui/components/controls/Field';
-import Fa from 'ui/components/Fa';
-import Button from 'ui/components/controls/Button';
-import {attachToForm} from './Form';
-import {camelCaseSplitToStr} from 'gems/camelCaseSplit';
-import NumberControl from '../../../../../../../modules/ui/components/controls/NumberControl';
-
-@attachToForm
-@mapContext(({streams, services}) => ({
- streams,
- findEntity: services.cadRegistry.findEntity
-}))
-export default class SingleEntity extends React.Component {
-
- componentDidMount() {
- let {streams, entity, onChange, value, selectionIndex, findEntity} = this.props;
- let selection$ = streams.selection[entity];
- if (value && findEntity(entity, value)) {
- if (selectionIndex === 0) {
- selection$.next([value]);
- }
- }
- this.detacher = selection$.attach(selection => onChange(selection[selectionIndex]));
- }
-
- componentWillUnmount() {
- this.detacher();
- }
-
- deselect = () => {
- let {streams, entity} = this.props;
- streams.selection[entity].next([]);
- };
-
- render() {
- let {name, label, streams, selectionIndex, entity} = this.props;
- let selection = streams.selection[entity].value[selectionIndex];
- return
-
- {selection ?
- {selection} :
- {''}}
- ;
- }
-}
-
-SingleEntity.defaultProps = {
- selectionIndex: 0
-};
\ No newline at end of file
diff --git a/web/app/cad/craft/wizard/components/form/SingleEntity.less b/web/app/cad/craft/wizard/components/form/SingleEntity.less
deleted file mode 100644
index 0a8c1207..00000000
--- a/web/app/cad/craft/wizard/components/form/SingleEntity.less
+++ /dev/null
@@ -1,4 +0,0 @@
-.emptySelection {
- font-style: initial;
- color: #BFBFBF;
-}
\ No newline at end of file
diff --git a/web/app/cad/craft/wizard/wizardPlugin.js b/web/app/cad/craft/wizard/wizardPlugin.js
index 2c751303..cc20f470 100644
--- a/web/app/cad/craft/wizard/wizardPlugin.js
+++ b/web/app/cad/craft/wizard/wizardPlugin.js
@@ -1,4 +1,4 @@
-import {state} from 'lstream';
+import {stream, state} from 'lstream';
import initializeBySchema from '../intializeBySchema';
import {clone, EMPTY_OBJECT} from 'gems/objects';
@@ -45,24 +45,29 @@ export function activate(ctx) {
gotoEditHistoryModeIfNeeded(mod);
});
+ streams.wizard.workingRequestChanged = stream();
+
streams.wizard.workingRequest = streams.wizard.effectiveOperation.map(opRequest => {
- if (!opRequest.type) {
- return EMPTY_OBJECT;
- }
- let operation = ctx.services.operation.get(opRequest.type);
- let params;
- if (opRequest.changingHistory) {
- params = clone(opRequest.params)
- } else {
- params = initializeBySchema(operation.schema, ctx);
- if (opRequest.initialOverrides) {
- applyOverrides(params, opRequest.initialOverrides);
+ let request = EMPTY_OBJECT;
+ if (opRequest.type) {
+ let operation = ctx.services.operation.get(opRequest.type);
+ let params;
+ if (opRequest.changingHistory) {
+ params = clone(opRequest.params)
+ } else {
+ params = initializeBySchema(operation.schema, ctx);
+ if (opRequest.initialOverrides) {
+ applyOverrides(params, opRequest.initialOverrides);
+ }
}
+ request = {
+ type: opRequest.type,
+ params,
+ state: {}
+ };
}
- return {
- type: opRequest.type,
- params,
- }
+ streams.wizard.workingRequestChanged.next(request);
+ return request
}).remember(EMPTY_OBJECT);
services.wizard = {
@@ -80,7 +85,8 @@ export function activate(ctx) {
},
applyWorkingRequest: () => {
- let request = clone(streams.wizard.workingRequest.value);
+ let {type, params} = streams.wizard.workingRequest.value;
+ let request = clone({type, params});
if (streams.wizard.insertOperation.value.type) {
ctx.services.craft.modify(request, () => streams.wizard.insertOperation.value = EMPTY_OBJECT);
} else {
diff --git a/web/app/cad/craft/wizard/wizardSelectionModeSwitcherPlugin.js b/web/app/cad/craft/wizard/wizardSelectionModeSwitcherPlugin.js
deleted file mode 100644
index c50168c7..00000000
--- a/web/app/cad/craft/wizard/wizardSelectionModeSwitcherPlugin.js
+++ /dev/null
@@ -1,13 +0,0 @@
-
-export function activate(ctx) {
- ctx.streams.wizard.workingRequest.attach(({type}) => {
- if (type) {
- let operation = ctx.services.operation.get(type);
- if (operation.selectionMode) {
- ctx.services.pickControl.setSelectionMode(operation.selectionMode);
- }
- } else {
- ctx.services.pickControl.switchToDefaultSelectionMode();
- }
- });
-}
\ No newline at end of file
diff --git a/web/app/cad/craft/wizard/wizardSelectionPlugin.js b/web/app/cad/craft/wizard/wizardSelectionPlugin.js
new file mode 100644
index 00000000..55e7ec07
--- /dev/null
+++ b/web/app/cad/craft/wizard/wizardSelectionPlugin.js
@@ -0,0 +1,149 @@
+import {EDGE, FACE, SHELL, SKETCH_OBJECT} from '../../scene/entites';
+
+export function activate(ctx) {
+ const wizardPickHandler = createPickHandlerFromSchema(ctx);
+
+ ctx.streams.wizard.workingRequestChanged.attach(({type, params}) => {
+ ctx.services.marker.clear();
+ if (type) {
+ ctx.services.pickControl.setPickHandler(wizardPickHandler);
+ } else {
+ ctx.services.pickControl.setPickHandler(null);
+ }
+ });
+
+ ctx.streams.wizard.workingRequest.attach(({type, params}) => {
+ const marker = ctx.services.marker;
+ marker.startSession();
+ if (type && params) {
+ let {schema, schemaIndex} = ctx.services.operation.get(type);
+ schemaIndex.entityParams.forEach(param => {
+ let color = schema[param].markColor;
+ let val = params[param];
+ let entity = schemaIndex.entitiesByParam[param];
+ if (Array.isArray(val)) {
+ val.forEach(id => marker.mark(entity, id, color));
+ } else {
+ if (val) {
+ marker.mark(entity, val, color);
+ }
+ }
+ });
+ }
+ marker.commit();
+ });
+}
+
+const singleUpdater = (params, param, id) => params[param] = id;
+const arrayUpdater = (params, param, id) => {
+ let arr = params[param];
+ if (!arr) {
+ params[param] = [id];
+ }
+ if (arr.indexOf(id) === -1) {
+ arr.push(id);
+ }
+};
+
+function createPickHandlerFromSchema(ctx) {
+ return model => {
+ const modelType = model.TYPE;
+ const {type: opType, state, params} = ctx.streams.wizard.workingRequest.value;
+ let {schema, schemaIndex} = ctx.services.operation.get(opType);
+ const {entitiesByType, entitiesByParam, entityParams} = schemaIndex;
+
+ const activeMd = state.activeParam && schema[state.activeParam];
+ const activeEntity = state.activeParam && entitiesByParam[state.activeParam];
+
+ function select(param, entity, md, id) {
+ const updater = md.type === 'array' ? arrayUpdater : singleUpdater;
+ let paramToMakeActive = getNextActiveParam(param, md);
+ ctx.streams.wizard.workingRequest.mutate(r => {
+ updater(r.params, param, id);
+ r.state.activeParam = paramToMakeActive;
+ });
+ }
+
+ function getNextActiveParam(currParam, currMd) {
+ if (currMd.type !== 'array') {
+ let entityGroup = entitiesByType[currMd.type];
+ if (entityGroup) {
+ const index = entityGroup.indexOf(currParam);
+ const nextIndex = (index + 1) % entityGroup.length;
+ return entityGroup[nextIndex];
+ }
+ }
+ return currParam;
+ }
+
+ function selectActive(id) {
+ select(state.activeParam, activeEntity, activeMd, id);
+ }
+
+ function selectToFirst(entity, id) {
+ let entities = entitiesByType[entity];
+ if (!entities) {
+ return false;
+ }
+ let param = entities[0];
+ select(param, entity, schema[param], id);
+ }
+
+ function deselectIfNeeded(id) {
+ for (let param of entityParams) {
+ let val = params[param];
+ if (val === id) {
+ ctx.streams.wizard.workingRequest.mutate(r => {
+ r.params[param] = undefined;
+ r.state.activeParam = param;
+ });
+ return true;
+ } else if (Array.isArray(val)) {
+ let index = val.indexOf(id);
+ if (index !== -1) {
+ ctx.streams.wizard.workingRequest.mutate(r => {
+ r.params[param].splice(index, 1);
+ r.state.activeParam = param;
+ });
+ return true;
+ }
+ }
+ }
+
+ }
+
+ if (deselectIfNeeded(model.id)) {
+ return false;
+ } else if (model.shell) {
+ if (deselectIfNeeded(model.shell.id)) {
+ return false;
+ }
+ }
+
+ if (modelType === FACE) {
+ if (activeEntity === SHELL) {
+ selectActive(model.shell.id);
+ } else if (activeEntity === FACE) {
+ selectActive(model.id);
+ } else {
+ if (!selectToFirst(FACE, model.id)) {
+ selectToFirst(SHELL, model.shell.id)
+ }
+ }
+ } else if (modelType === SKETCH_OBJECT) {
+ if (activeEntity === SKETCH_OBJECT) {
+ selectActive(model.id);
+ } else {
+ selectToFirst(SKETCH_OBJECT, model.id);
+ }
+ } else if (modelType === EDGE) {
+ if (activeEntity === EDGE) {
+ selectActive(model.id);
+ } else {
+ selectToFirst(EDGE, model.id);
+ }
+ }
+ return false;
+ };
+}
+
diff --git a/web/app/cad/init/startApplication.js b/web/app/cad/init/startApplication.js
index d14b97b1..e72a0b92 100644
--- a/web/app/cad/init/startApplication.js
+++ b/web/app/cad/init/startApplication.js
@@ -4,13 +4,13 @@ import * as DomPlugin from '../dom/domPlugin';
import * as PickControlPlugin from '../scene/controls/pickControlPlugin';
import * as MouseEventSystemPlugin from '../scene/controls/mouseEventSystemPlugin';
import * as ScenePlugin from '../scene/scenePlugin';
-import * as SelectionMarkerPlugin from '../scene/selectionMarker/selectionMarkerPlugin';
+import * as MarkerPlugin from '../scene/selectionMarker/markerPlugin';
import * as ActionSystemPlugin from '../actions/actionSystemPlugin';
import * as UiPlugin from '../dom/uiPlugin';
import * as MenuPlugin from '../dom/menu/menuPlugin';
import * as KeyboardPlugin from '../keyboard/keyboardPlugin';
import * as WizardPlugin from '../craft/wizard/wizardPlugin';
-import * as WizardSelectionModeSwitcherPlugin from '../craft/wizard/wizardSelectionModeSwitcherPlugin';
+import * as WizardSelectionPlugin from '../craft/wizard/wizardSelectionPlugin';
import * as PreviewPlugin from '../preview/previewPlugin';
import * as OperationPlugin from '../craft/operationPlugin';
import * as ExtensionsPlugin from '../craft/extensionsPlugin';
@@ -23,6 +23,7 @@ import * as SketcherPlugin from '../sketch/sketcherPlugin';
import * as ExportPlugin from '../exportPlugin';
import * as TpiPlugin from '../tpi/tpiPlugin';
import * as ViewSyncPlugin from '../scene/viewSyncPlugin';
+import * as EntityContextPlugin from '../scene/entityContextPlugin';
import * as E0Plugin from '../craft/e0/e0Plugin';
import PartModellerPlugins from '../part/partModelerPlugins';
@@ -60,12 +61,13 @@ export default function startApplication(callback) {
DomPlugin,
ScenePlugin,
MouseEventSystemPlugin,
+ MarkerPlugin,
PickControlPlugin,
- SelectionMarkerPlugin,
+ EntityContextPlugin,
SketcherPlugin,
...applicationPlugins,
ViewSyncPlugin,
- WizardSelectionModeSwitcherPlugin
+ WizardSelectionPlugin
];
let allPlugins = [...preUIPlugins, ...plugins];
diff --git a/web/app/cad/plugins.js b/web/app/cad/plugins.js
index 4b8ae458..516c6424 100644
--- a/web/app/cad/plugins.js
+++ b/web/app/cad/plugins.js
@@ -1,7 +1,7 @@
import * as DomPlugin from './dom/domPlugin';
import * as PickControlPlugin from './scene/controls/pickControlPlugin';
import * as ScenePlugin from './scene/scenePlugin';
-import * as SelectionMarkerPlugin from './scene/selectionMarker/selectionMarkerPlugin';
+import * as SelectionMarkerPlugin from './scene/selectionMarker/markerPlugin';
export default [
DomPlugin,
diff --git a/web/app/cad/scene/controls/defaultSelectionState.js b/web/app/cad/scene/controls/defaultSelectionState.js
new file mode 100644
index 00000000..a06e99de
--- /dev/null
+++ b/web/app/cad/scene/controls/defaultSelectionState.js
@@ -0,0 +1,28 @@
+import {state} from '../../../../../modules/lstream';
+import {DATUM, EDGE, FACE, SHELL, SKETCH_OBJECT} from '../entites';
+
+const SELECTABLE_ENTITIES = [FACE, EDGE, SKETCH_OBJECT, DATUM, SHELL];
+
+
+export function defineDefaultSelectionState(ctx) {
+ ctx.streams.selection = {
+ };
+ SELECTABLE_ENTITIES.forEach(entity => {
+ ctx.streams.selection[entity] = state([]);
+ });
+
+ SELECTABLE_ENTITIES.forEach(entity => {
+ let entitySelectApi = {
+ objects: [],
+ single: undefined
+ };
+ ctx.services.selection[entity] = entitySelectApi;
+ let selectionState = streams.selection[entity];
+ selectionState.attach(selection => {
+ entitySelectApi.objects = selection.map(id => services.cadRegistry.findEntity(entity, id));
+ entitySelectApi.single = entitySelectApi.objects[0];
+ });
+ entitySelectApi.select = selection => selectionState.value = selection;
+ });
+
+}
\ No newline at end of file
diff --git a/web/app/cad/scene/controls/pickControlPlugin.js b/web/app/cad/scene/controls/pickControlPlugin.js
index 7c6140d8..dbee87ca 100644
--- a/web/app/cad/scene/controls/pickControlPlugin.js
+++ b/web/app/cad/scene/controls/pickControlPlugin.js
@@ -10,7 +10,7 @@ export const PICK_KIND = {
EDGE: mask.type(3)
};
-const SELECTABLE_ENTITIES = [FACE, EDGE, SKETCH_OBJECT, DATUM, SHELL];
+export const SELECTABLE_ENTITIES = [FACE, EDGE, SKETCH_OBJECT, DATUM, SHELL];
const DEFAULT_SELECTION_MODE = Object.freeze({
shell: false,
@@ -23,7 +23,36 @@ const DEFAULT_SELECTION_MODE = Object.freeze({
export function activate(context) {
const {services, streams} = context;
- initStateAndServices(context);
+
+ const defaultHandler = (model, event) => {
+ const type = model.TYPE;
+ let selectionMode = DEFAULT_SELECTION_MODE;
+ let modelId = model.id;
+ if (type === FACE) {
+ if (selectionMode.shell) {
+ if (dispatchSelection(SHELL, model.shell.id, event)) {
+ return false;
+ }
+ } else {
+ if (dispatchSelection(FACE, modelId, event)) {
+ services.cadScene.showGlobalCsys(model.csys);
+ return false;
+ }
+ }
+ } else if (type === SKETCH_OBJECT) {
+ if (dispatchSelection(SKETCH_OBJECT, modelId, event)) {
+ return false;
+ }
+ } else if (type === EDGE) {
+ if (dispatchSelection(EDGE, modelId, event)) {
+ return false;
+ }
+ }
+ return true;
+ };
+
+ let pickHandler = defaultHandler;
+
let domElement = services.viewer.sceneSetup.domElement();
domElement.addEventListener('mousedown', mousedown, false);
@@ -52,44 +81,33 @@ export function activate(context) {
}
}
- function selected(selection, object) {
- return selection.value.indexOf(object) !== -1;
+ function setPickHandler(handler) {
+ pickHandler = handler || defaultHandler;
+ services.marker.clear();
}
+ const deselectAll = () => services.marker.clear();
+
function handlePick(event) {
- raycastObjects(event, PICK_KIND.FACE | PICK_KIND.SKETCH | PICK_KIND.EDGE, (view, kind) => {
- let selectionMode = streams.pickControl.selectionMode.value;
- let modelId = view.model.id;
- if (kind === PICK_KIND.FACE) {
- if (selectionMode.shell) {
- if (dispatchSelection(streams.selection.shell, view.model.shell.id, event)) {
- return false;
- }
- } else {
- if (dispatchSelection(streams.selection.face, modelId, event)) {
- services.cadScene.showGlobalCsys(view.model.csys);
- return false;
- }
- }
- } else if (kind === PICK_KIND.SKETCH) {
- if (dispatchSelection(streams.selection.sketchObject, modelId, event)) {
- return false;
- }
- } else if (kind === PICK_KIND.EDGE) {
- if (dispatchSelection(streams.selection.edge, modelId, event)) {
- return false;
- }
- }
- return true;
- });
+ raycastObjects(event, PICK_KIND.FACE | PICK_KIND.SKETCH | PICK_KIND.EDGE, pickHandler);
}
- function dispatchSelection(selection, selectee, event) {
- if (selected(selection, selectee)) {
+ function pick(obj) {
+ pickHandler(obj, null);
+ }
+
+ function dispatchSelection(entityType, selectee, event) {
+ let marker = services.marker;
+ if (marker.isMarked(selectee)) {
return false;
}
- let multiMode = event.shiftKey;
- selection.update(value => multiMode ? [...value, selectee] : [selectee]);
+ let multiMode = event && event.shiftKey;
+
+ if (multiMode) {
+ marker.markAdding(entityType, selectee)
+ } else {
+ marker.markExclusively(entityType, selectee)
+ }
return true;
}
@@ -108,7 +126,7 @@ export function activate(context) {
if (mask.is(kind, PICK_KIND.SKETCH) && pickResult.object instanceof THREE.Line) {
let sketchObjectV = getAttribute(pickResult.object, SKETCH_OBJECT);
if (sketchObjectV) {
- return !visitor(sketchObjectV, PICK_KIND.SKETCH);
+ return !visitor(sketchObjectV.model, event);
}
}
return false;
@@ -117,7 +135,7 @@ export function activate(context) {
if (mask.is(kind, PICK_KIND.EDGE)) {
let edgeV = getAttribute(pickResult.object, EDGE);
if (edgeV) {
- return !visitor(edgeV, PICK_KIND.EDGE);
+ return !visitor(edgeV.model, event);
}
}
return false;
@@ -126,7 +144,7 @@ export function activate(context) {
if (mask.is(kind, PICK_KIND.FACE) && !!pickResult.face) {
let faceV = getAttribute(pickResult.face, FACE);
if (faceV) {
- return !visitor(faceV, PICK_KIND.FACE);
+ return !visitor(faceV.model, event);
}
}
return false;
@@ -141,64 +159,7 @@ export function activate(context) {
}
}
}
-}
-
-export function defineStreams({streams}) {
- streams.selection = {
- };
- SELECTABLE_ENTITIES.forEach(entity => {
- streams.selection[entity] = state([]);
- });
- streams.pickControl = {
- selectionMode: distinctState(DEFAULT_SELECTION_MODE)
- }
-}
-
-function initStateAndServices({streams, services}) {
-
- streams.pickControl.selectionMode.pairwise().attach(([prev, curr]) => {
- SELECTABLE_ENTITIES.forEach(entity => {
- if (prev[entity] !== curr[entity]) {
- streams.selection[entity].next([]);
- }
- });
- });
-
- function setSelectionMode(selectionMode) {
- streams.pickControl.selectionMode.next({
- ...DEFAULT_SELECTION_MODE, ...selectionMode
- });
- }
-
- function switchToDefaultSelectionMode() {
- streams.pickControl.selectionMode.next(DEFAULT_SELECTION_MODE);
- }
-
services.pickControl = {
- setSelectionMode, switchToDefaultSelectionMode
+ setPickHandler, deselectAll, pick
};
-
- services.selection = {};
-
- SELECTABLE_ENTITIES.forEach(entity => {
- let entitySelectApi = {
- objects: [],
- single: undefined
- };
- services.selection[entity] = entitySelectApi;
- let selectionState = streams.selection[entity];
- selectionState.attach(selection => {
- entitySelectApi.objects = selection.map(id => services.cadRegistry.findEntity(entity, id));
- entitySelectApi.single = entitySelectApi.objects[0];
- });
- entitySelectApi.select = selection => selectionState.value = selection;
- });
-
- streams.craft.models.attach(() => {
- withdrawAll(streams.selection)
- });
-}
-
-export function withdrawAll(selectionStreams) {
- Object.values(selectionStreams).forEach(stream => stream.next([]))
}
diff --git a/web/app/cad/scene/entityContextPlugin.js b/web/app/cad/scene/entityContextPlugin.js
new file mode 100644
index 00000000..02fa5fd4
--- /dev/null
+++ b/web/app/cad/scene/entityContextPlugin.js
@@ -0,0 +1,45 @@
+import {state} from 'lstream';
+import {SELECTABLE_ENTITIES} from './controls/pickControlPlugin';
+import {addToListInMap} from 'gems/iterables';
+import {EMPTY_ARRAY} from '../../../../modules/gems/iterables';
+
+export function defineStreams(ctx) {
+ ctx.streams.selection = {};
+ SELECTABLE_ENTITIES.forEach(entity => {
+ ctx.streams.selection[entity] = state([]);
+ });
+}
+
+export function activate(ctx) {
+ ctx.services.selection = {};
+ SELECTABLE_ENTITIES.forEach(entity => {
+ let entitySelectApi = {
+ objects: [],
+ single: undefined
+ };
+ ctx.services.selection[entity] = entitySelectApi;
+ let selectionState = ctx.streams.selection[entity];
+
+ selectionState.attach(selection => {
+ entitySelectApi.objects = selection.map(id => ctx.services.cadRegistry.findEntity(entity, id));
+ entitySelectApi.single = entitySelectApi.objects[0];
+ });
+
+ entitySelectApi.select = ids => ctx.services.marker.markArrayExclusively(entity, ids);
+ });
+
+ ctx.services.marker.$markedEntities.attach(marked => {
+ let byType = new Map();
+ marked.forEach((obj) => {
+ addToListInMap(byType, obj.TYPE, obj);
+ });
+ SELECTABLE_ENTITIES.forEach(entityType => {
+ let entities = byType.get(entityType);
+ if (entities) {
+ ctx.streams.selection[entityType].next(entities.map(obj => obj.id));
+ } else {
+ ctx.streams.selection[entityType].next(EMPTY_ARRAY);
+ }
+ });
+ })
+}
\ No newline at end of file
diff --git a/web/app/cad/scene/selectionMarker/markerPlugin.js b/web/app/cad/scene/selectionMarker/markerPlugin.js
new file mode 100644
index 00000000..028d6259
--- /dev/null
+++ b/web/app/cad/scene/selectionMarker/markerPlugin.js
@@ -0,0 +1,110 @@
+import {OrderedMap} from 'gems/linkedMap';
+import {eventStream} from 'lstream';
+
+export function activate(ctx) {
+ ctx.services.marker = createMarker(ctx.services.cadRegistry.findEntity, ctx.services.viewer.requestRender);
+ ctx.streams.craft.models.attach(() => {
+ ctx.services.marker.clear();
+ });
+}
+
+function createMarker(findEntity, requestRender) {
+
+ let markingSession = new Set();
+ let marked = new OrderedMap();
+ let needUpdate = false;
+ let sessionInProgress = false;
+ let $markedEntities = eventStream();
+
+ const notify = () => $markedEntities.next(marked);
+ const isMarked = id => marked.has(id);
+
+ function doMark(entity, id, color) {
+ let mObj = findEntity(entity, id);
+ marked.set(id, mObj);
+ mObj.ext.view && mObj.ext.view.mark(color);
+ }
+
+ function doWithdraw(obj) {
+ marked.delete(obj.id);
+ obj.ext.view && obj.ext.view.withdraw();
+ }
+
+ function onUpdate() {
+ requestRender();
+ notify();
+ }
+
+ function clear() {
+ if (marked.size !== 0) {
+ marked.forEach(m => m.ext.view && m.ext.view.withdraw());
+ marked.clear();
+ onUpdate();
+ }
+ }
+
+ function withdrawAllOfType(entityType) {
+ marked.forEach(obj => {
+ if (obj.TYPE === entityType) {
+ doWithdraw(obj);
+ }
+ });
+ }
+
+ function markExclusively(entity, id, color) {
+ withdrawAllOfType(entity);
+ doMark(entity, id, color);
+ onUpdate();
+ }
+
+ function markArrayExclusively(entity, ids, color) {
+ withdrawAllOfType(entity);
+ ids.forEach(id => doMark(entity, id, color));
+ onUpdate();
+ }
+
+ function markAdding(entity, id, color) {
+ if (!marked.has(id)) {
+ doMark(entity, id, color);
+ onUpdate();
+ }
+ }
+
+
+ function startSession() {
+ markingSession.clear();
+ sessionInProgress = true;
+ needUpdate = false;
+ }
+
+ function mark(entity, id, color) {
+ if (!sessionInProgress) {
+ throw 'can be called only withing a session';
+ }
+ markingSession.add(id);
+ if (!marked.has(id)) {
+ doMark(entity, id, color);
+ needUpdate = true;
+ }
+ }
+
+ function commit() {
+ marked.forEach((obj) => {
+ if (!markingSession.has(obj.id)) {
+ doWithdraw(obj);
+ needUpdate = true;
+ }
+ });
+ if (needUpdate) {
+ onUpdate();
+ }
+ sessionInProgress = false;
+ needUpdate = false;
+ markingSession.clear();
+ }
+
+ return {
+ clear, startSession, mark, commit, markExclusively, markArrayExclusively, markAdding, isMarked, $markedEntities
+ };
+}
+
diff --git a/web/app/cad/scene/selectionMarker/selectionMarker.js b/web/app/cad/scene/selectionMarker/selectionMarker.js
index 9621dcb3..a907a784 100644
--- a/web/app/cad/scene/selectionMarker/selectionMarker.js
+++ b/web/app/cad/scene/selectionMarker/selectionMarker.js
@@ -3,15 +3,15 @@ import {AbstractSelectionMarker, setFacesColor} from "./abstractSelectionMarker"
export class SelectionMarker extends AbstractSelectionMarker {
- constructor(context, selectionColor, readOnlyColor, defaultColor) {
+ constructor(context, markColor, readOnlyColor, defaultColor) {
super(context, 'face');
- this.selectionColor = selectionColor;
+ this.markColor = markColor;
this.defaultColor = defaultColor;
this.readOnlyColor = readOnlyColor;
}
mark(sceneFace) {
- this.setColor(sceneFace, this.selectionColor, this.readOnlyColor);
+ this.setColor(sceneFace, this.markColor, this.readOnlyColor);
}
unMark(sceneFace) {
diff --git a/web/app/cad/scene/selectionMarker/selectionMarkerPlugin.js b/web/app/cad/scene/selectionMarker/selectionMarkerPlugin.js
deleted file mode 100644
index 9edf18fc..00000000
--- a/web/app/cad/scene/selectionMarker/selectionMarkerPlugin.js
+++ /dev/null
@@ -1,27 +0,0 @@
-import {EDGE, FACE, SHELL, SKETCH_OBJECT} from '../entites';
-import {findDiff} from '../../../../../modules/gems/iterables';
-
-export function activate({streams, services}) {
- let selectionSync = entity => ([old, curr]) => {
- let [, toWithdraw, toMark] = findDiff(old, curr);
- toWithdraw.forEach(id => {
- let model = services.cadRegistry.findEntity(entity, id);
- if (model) {
- model.ext.view.withdraw();
- }
- });
- toMark.forEach(id => {
- let model = services.cadRegistry.findEntity(entity, id);
- if (model) {
- model.ext.view.mark();
- }
- });
- services.viewer.requestRender();
- };
-
- streams.selection.face.pairwise([]).attach(selectionSync(FACE));
- streams.selection.shell.pairwise([]).attach(selectionSync(SHELL));
- streams.selection.edge.pairwise([]).attach(selectionSync(EDGE));
- streams.selection.sketchObject.pairwise([]).attach(selectionSync(SKETCH_OBJECT));
-}
-
diff --git a/web/app/cad/scene/selectionMarker/selectionSynchronizer.js b/web/app/cad/scene/selectionMarker/selectionSynchronizer.js
new file mode 100644
index 00000000..774edbed
--- /dev/null
+++ b/web/app/cad/scene/selectionMarker/selectionSynchronizer.js
@@ -0,0 +1,17 @@
+import {findDiff} from 'gems/iterables';
+
+export const selectionSynchronizer = (entity, findEntity, color) => ([old, curr]) => {
+ let [, toWithdraw, toMark] = findDiff(old, curr);
+ toWithdraw.forEach(id => {
+ let model = findEntity(entity, id);
+ if (model) {
+ model.ext.view.withdraw();
+ }
+ });
+ toMark.forEach(id => {
+ let model = findEntity(entity, id);
+ if (model) {
+ model.ext.view.mark(color);
+ }
+ });
+};
\ No newline at end of file
diff --git a/web/app/cad/sketch/inPlaceSketcher.js b/web/app/cad/sketch/inPlaceSketcher.js
index f2943b1d..3e576bd6 100644
--- a/web/app/cad/sketch/inPlaceSketcher.js
+++ b/web/app/cad/sketch/inPlaceSketcher.js
@@ -52,8 +52,8 @@ export class InPlaceSketcher {
let viewer3d = this.ctx.services.viewer;
viewer3d.sceneSetup.trackballControls.removeEventListener( 'change', this.onCameraChange);
this.face = null;
- this.viewer.dispose();
this.viewer.canvas.parentNode.removeChild(this.viewer.canvas);
+ this.viewer.dispose();
this.ctx.streams.sketcher.sketchingFace.value = null;
viewer3d.requestRender();
}