mirror of
https://github.com/xibyte/jsketcher
synced 2026-01-25 17:44:03 +01:00
entities selection rework, separate selection in normal mode from wizard mode
This commit is contained in:
parent
8b2ad87513
commit
14cdeb9356
47 changed files with 737 additions and 307 deletions
|
|
@ -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([]);
|
||||
|
|
|
|||
44
modules/gems/linkedMap.js
Normal file
44
modules/gems/linkedMap.js
Normal file
|
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -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');
|
||||
|
|
|
|||
19
modules/lstream/distinct.js
Normal file
19
modules/lstream/distinct.js
Normal file
|
|
@ -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;
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
@ -12,6 +12,8 @@ export function stream() {
|
|||
return new Emitter();
|
||||
}
|
||||
|
||||
export const eventStream = stream;
|
||||
|
||||
export function combine(...streams) {
|
||||
return new CombineStream(streams);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,10 +1,8 @@
|
|||
import React from 'react';
|
||||
|
||||
import ls from './Field.less'
|
||||
import cx from 'classnames';
|
||||
|
||||
export default function Field({children}) {
|
||||
|
||||
return <div className={ls.root}>
|
||||
{children}
|
||||
</div>;
|
||||
export default function Field({active, ...props}) {
|
||||
return <div className={cx(ls.root, active&&ls.active)} {...props} />
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,5 +1,11 @@
|
|||
@import "~ui/styles/theme.less";
|
||||
|
||||
.root {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: baseline;
|
||||
}
|
||||
}
|
||||
|
||||
.active {
|
||||
background-color: @color-highlight
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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 <InputControl type='number'
|
||||
onWheel={this.onWheel}
|
||||
value={ value }
|
||||
onChange={this.onChange}
|
||||
onChange={this.onChange}
|
||||
onFocus={onFocus}
|
||||
inputRef={input => this.input = input} />
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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 <InputControl type='text'
|
||||
defaultValue={initValue}
|
||||
onChange={e => onChange(e.target.value)} />
|
||||
defaultValue={initValue}
|
||||
onChange={e => onChange(e.target.value)} onFocus={onFocus} />
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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 <Group>
|
||||
<SingleEntity name='operandA' label='operand A' entity='shell' selectionIndex={0} />
|
||||
<SingleEntity name='operandB' label='operand B' entity='shell' selectionIndex={1} />
|
||||
<EntityList name='operandA' label='operand A' entity='shell' selectionIndex={0} />
|
||||
<EntityList name='operandB' label='operand B' entity='shell' selectionIndex={1} />
|
||||
</Group>;
|
||||
}
|
||||
|
|
@ -1,10 +1,12 @@
|
|||
export default {
|
||||
operandA: {
|
||||
type: 'shell',
|
||||
markColor: 0xC9FFBC,
|
||||
defaultValue: {type: 'selection'}
|
||||
},
|
||||
operandB: {
|
||||
type: 'shell',
|
||||
markColor: 0xFFBEB4,
|
||||
defaultValue: {type: 'selection'}
|
||||
}
|
||||
};
|
||||
|
|
|
|||
|
|
@ -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
|
||||
};
|
||||
|
|
|
|||
|
|
@ -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) {
|
|||
<NumberField name='prism' defaultValue={1} min={0} step={0.1} round={1}/>
|
||||
<NumberField name='angle' defaultValue={0}/>
|
||||
<NumberField name='rotation' defaultValue={0} step={5}/>
|
||||
<SingleEntity entity='face' name='face'/>
|
||||
<EntityList entity='face' name='face'/>
|
||||
</Group>;
|
||||
};
|
||||
}
|
||||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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 <Group>
|
||||
<NumberField name='x' label='X' />
|
||||
<NumberField name='y' label='Y' />
|
||||
<NumberField name='z' label='Z' />
|
||||
<SingleEntity name='face' label='off of' entity='face' />
|
||||
<EntityList name='face' label='off of' entity='face' />
|
||||
</Group>;
|
||||
}
|
||||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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 <MultiEntity schema={schema}
|
||||
entity={entity}
|
||||
itemField={itemField}
|
||||
name='edges'>
|
||||
return <Group>
|
||||
<EntityList name='edges' entity='edge' />
|
||||
<NumberField name='thickness' />
|
||||
</MultiEntity>;
|
||||
</Group>;
|
||||
}
|
||||
|
|
@ -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
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
};
|
||||
}
|
||||
|
|
@ -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() {
|
||||
|
|
|
|||
|
|
@ -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() {
|
|||
<RadioButton value='XZ' />
|
||||
<RadioButton value='ZY' />
|
||||
</RadioButtonsField>
|
||||
<SingleEntity name='parallelTo' entity='face' />
|
||||
<EntityList name='parallelTo' entity='face' />
|
||||
<NumberField name='depth' />
|
||||
</Group>;
|
||||
}
|
||||
|
|
@ -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 <Group>
|
||||
<NumberField name='angle' />
|
||||
<SingleEntity name='face' entity='face' />
|
||||
<SingleEntity name='axis' entity='sketchObject' />
|
||||
<EntityList name='face' entity='face' />
|
||||
<EntityList name='axis' entity='sketchObject' />
|
||||
<CheckboxField name='cut' />
|
||||
</Group>;
|
||||
}
|
||||
5
web/app/cad/craft/schemaUtils.js
Normal file
5
web/app/cad/craft/schemaUtils.js
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
import {ENTITIES} from '../scene/entites';
|
||||
|
||||
export function isEntityType(type) {
|
||||
return ENTITIES.indexOf(type) !== -1
|
||||
}
|
||||
|
|
@ -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}))))
|
||||
(
|
||||
|
|
|
|||
|
|
@ -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 <span>operation error</span>;
|
||||
}
|
||||
|
||||
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
|
||||
};
|
||||
|
||||
|
|
|
|||
45
web/app/cad/craft/wizard/components/form/EntityList.jsx
Normal file
45
web/app/cad/craft/wizard/components/form/EntityList.jsx
Normal file
|
|
@ -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 <Field active={active} onClick={setActive}>
|
||||
<Label>{label||camelCaseSplitToStr(name)}:</Label>
|
||||
<div>{value.length === 0 ?
|
||||
<span className={ls.emptySelection}>{'<not selected>'}</span> :
|
||||
value.map((entity, i) => <span className={ls.entityRef} key={i}>
|
||||
{entity} <span className={ls.rm} onClick={() => this.deselect(entity)}> <Fa icon='times'/></span>
|
||||
</span>)}
|
||||
</div>
|
||||
</Field>;
|
||||
}
|
||||
}
|
||||
|
||||
function asArray(val) {
|
||||
_arr[0] = val;
|
||||
return _arr;
|
||||
}
|
||||
|
||||
const _arr = [];
|
||||
|
||||
23
web/app/cad/craft/wizard/components/form/EntityList.less
Normal file
23
web/app/cad/craft/wizard/components/form/EntityList.less
Normal file
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -13,8 +13,8 @@ export function Group({children}) {
|
|||
}
|
||||
|
||||
export function formField(Control) {
|
||||
return function FormPrimitive({label, name, ...props}) {
|
||||
return <Field>
|
||||
return function FormPrimitive({label, name, active, setActive, ...props}) {
|
||||
return <Field active={active} onFocus={setActive} onClick={setActive}>
|
||||
<Label>{label || camelCaseSplitToStr(name)}</Label>
|
||||
<Control {...props} />
|
||||
</Field>;
|
||||
|
|
@ -27,8 +27,13 @@ export function attachToForm(Control) {
|
|||
{
|
||||
ctx => {
|
||||
const onChange = val => ctx.updateParam(name, val);
|
||||
const setActive = val => ctx.setActiveParam(name);
|
||||
return <React.Fragment>
|
||||
<Control value={ctx.data[name]} onChange={onChange} name={name} {...props} />
|
||||
<Control value={ctx.data[name]}
|
||||
onChange={onChange}
|
||||
name={name} {...props}
|
||||
setActive={setActive}
|
||||
active={ctx.activeParam === name} />
|
||||
</React.Fragment>;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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];
|
||||
|
|
|
|||
|
|
@ -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 <Field>
|
||||
<Label>{label||camelCaseSplitToStr(name)}:</Label>
|
||||
<div>{selection ?
|
||||
<span>{selection} <Button type='minor' onClick={this.deselect}> <Fa icon='times'/></Button></span> :
|
||||
<span className={ls.emptySelection}>{'<not selected>'}</span>}</div>
|
||||
</Field>;
|
||||
}
|
||||
}
|
||||
|
||||
SingleEntity.defaultProps = {
|
||||
selectionIndex: 0
|
||||
};
|
||||
|
|
@ -1,4 +0,0 @@
|
|||
.emptySelection {
|
||||
font-style: initial;
|
||||
color: #BFBFBF;
|
||||
}
|
||||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
}
|
||||
});
|
||||
}
|
||||
149
web/app/cad/craft/wizard/wizardSelectionPlugin.js
Normal file
149
web/app/cad/craft/wizard/wizardSelectionPlugin.js
Normal file
|
|
@ -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;
|
||||
};
|
||||
}
|
||||
|
||||
|
|
@ -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];
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
28
web/app/cad/scene/controls/defaultSelectionState.js
Normal file
28
web/app/cad/scene/controls/defaultSelectionState.js
Normal file
|
|
@ -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;
|
||||
});
|
||||
|
||||
}
|
||||
|
|
@ -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([]))
|
||||
}
|
||||
|
|
|
|||
45
web/app/cad/scene/entityContextPlugin.js
Normal file
45
web/app/cad/scene/entityContextPlugin.js
Normal file
|
|
@ -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);
|
||||
}
|
||||
});
|
||||
})
|
||||
}
|
||||
110
web/app/cad/scene/selectionMarker/markerPlugin.js
Normal file
110
web/app/cad/scene/selectionMarker/markerPlugin.js
Normal file
|
|
@ -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
|
||||
};
|
||||
}
|
||||
|
||||
|
|
@ -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) {
|
||||
|
|
|
|||
|
|
@ -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));
|
||||
}
|
||||
|
||||
17
web/app/cad/scene/selectionMarker/selectionSynchronizer.js
Normal file
17
web/app/cad/scene/selectionMarker/selectionSynchronizer.js
Normal file
|
|
@ -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);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
|
@ -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();
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue