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