work on MDF

This commit is contained in:
xibyte 2022-02-13 15:17:19 -08:00 committed by Val Erastov
parent a2acb1ec00
commit 0fc56d1cfc
36 changed files with 761 additions and 493 deletions

31
modules/math/axis.ts Normal file
View file

@ -0,0 +1,31 @@
import Vector from "math/vector";
export default class Axis {
origin: Vector;
direction: Vector;
constructor(origin, direction) {
this.origin = origin;
this.direction = direction;
}
copy(axis: Axis) {
this.origin.setV(axis.origin);
this.direction.setV(axis.direction);
return this;
}
clone() {
return new Axis(this.origin.copy(), this.direction.copy());
}
move(x, y, z) {
this.origin.set(x, y, z);
return this;
}
invert() {
return new Axis(this.origin, this.direction.negate());
}
}

View file

@ -3,13 +3,13 @@ import {MFace} from "cad/model/mface";
import {ApplicationContext} from "context"; import {ApplicationContext} from "context";
import {MDFCommand} from "cad/mdf/mdf"; import {MDFCommand} from "cad/mdf/mdf";
import {EntityKind} from "cad/model/entities"; import {EntityKind} from "cad/model/entities";
import Vector from "math/vector";
import {BooleanDefinition} from "cad/craft/schema/common/BooleanDefinition"; import {BooleanDefinition} from "cad/craft/schema/common/BooleanDefinition";
import Axis from "math/axis";
interface ExtrudeParams { interface ExtrudeParams {
length: number; length: number;
face: MFace; face: MFace;
direction?: Vector, direction?: Axis,
boolean: BooleanDefinition boolean: BooleanDefinition
} }
@ -32,7 +32,7 @@ const ExtrudeOperation: MDFCommand<ExtrudeParams> = {
const occFaces = occ.utils.sketchToFaces(sketch, face.csys); const occFaces = occ.utils.sketchToFaces(sketch, face.csys);
const dir = (params.direction && params.direction) || face.normal(); const dir = (params.direction && params.direction.direction) || face.normal();
const extrusionVector = dir.normalize()._multiply(params.length).data(); const extrusionVector = dir.normalize()._multiply(params.length).data();
@ -103,7 +103,7 @@ const ExtrudeOperation: MDFCommand<ExtrudeParams> = {
// } // }
// }, // },
{ {
type: 'vector', type: 'axis',
name: 'direction', name: 'direction',
label: 'direction', label: 'direction',
optional: true optional: true
@ -113,7 +113,6 @@ const ExtrudeOperation: MDFCommand<ExtrudeParams> = {
name: 'boolean', name: 'boolean',
label: 'boolean', label: 'boolean',
optional: true, optional: true,
defaultValue: 'NONE'
} }
], ],

View file

@ -5,11 +5,13 @@ import { MDFCommand } from "cad/mdf/mdf";
import { EntityKind } from "cad/model/entities"; import { EntityKind } from "cad/model/entities";
import Vector from "math/vector"; import Vector from "math/vector";
import { BooleanDefinition } from "cad/craft/schema/common/BooleanDefinition"; import { BooleanDefinition } from "cad/craft/schema/common/BooleanDefinition";
import * as vec from "math/vec";
import Axis from "math/axis";
interface RevolveParams { interface RevolveParams {
angle: number; angle: number;
face: MFace; face: MFace;
direction?: Vector, axis: Axis,
boolean: BooleanDefinition boolean: BooleanDefinition
} }
@ -31,16 +33,10 @@ const RevolveOperation: MDFCommand<RevolveParams> = {
if (!sketch) throw 'sketch not found for the face ' + face.id; if (!sketch) throw 'sketch not found for the face ' + face.id;
const occFaces = occ.utils.sketchToFaces(sketch, face.csys); const occFaces = occ.utils.sketchToFaces(sketch, face.csys);
const dir = params.direction.normalize().data();
const point = params.direction;
console.log(params.direction);
console.log(dir);
const tools = occFaces.map((faceName, i) => { const tools = occFaces.map((faceName, i) => {
const shapeName = "Tool/" + i; const shapeName = "Tool/" + i;
var args = [shapeName, faceName, point.x, point.y, point.z, ...dir, params.angle]; var args = [shapeName, faceName, ...params.axis.origin.data(), ...params.axis.direction.data(), params.angle];
oci.revol(...args); oci.revol(...args);
return shapeName; return shapeName;
@ -75,10 +71,10 @@ const RevolveOperation: MDFCommand<RevolveParams> = {
}, },
}, },
{ {
type: 'vector', type: 'axis',
name: 'direction', name: 'axis',
label: 'direction', label: 'axis',
optional: true optional: false
}, },
{ {
type: 'boolean', type: 'boolean',

View file

@ -4,8 +4,11 @@ import {IconType} from "react-icons";
import {ActionAppearance} from "../actions/actionSystemPlugin"; import {ActionAppearance} from "../actions/actionSystemPlugin";
import {ApplicationContext, CoreContext} from "context"; import {ApplicationContext, CoreContext} from "context";
import {OperationResult} from "./craftPlugin"; import {OperationResult} from "./craftPlugin";
import {OperationFlattenSchema, OperationSchema, schemaIterator} from "cad/craft/schema/schema"; import {OperationSchema, SchemaField, schemaIterator, unwrapMetadata} from "cad/craft/schema/schema";
import {FieldWidgetProps, UIDefinition} from "cad/mdf/ui/uiDefinition"; import {FieldWidgetProps} from "cad/mdf/ui/uiDefinition";
import {Types} from "cad/craft/schema/types";
import {EntityTypeSchema} from "cad/craft/schema/types/entityType";
import {FlattenPath, ParamsPath} from "cad/craft/wizard/wizardTypes";
export function activate(ctx: ApplicationContext) { export function activate(ctx: ApplicationContext) {
@ -35,9 +38,8 @@ export function activate(ctx: ApplicationContext) {
}; };
actions.push(opAction); actions.push(opAction);
const workingSchema = flattenSchema(descriptor.schema); let schemaIndex = createSchemaIndex(descriptor.schema);
registry$.mutate(registry => registry[id] = Object.assign({appearance, schemaIndex}, descriptor, {
registry$.mutate(registry => registry[id] = Object.assign({appearance, workingSchema}, descriptor, {
run: (request, opContext) => runOperation(request, descriptor, opContext) run: (request, opContext) => runOperation(request, descriptor, opContext)
})); }));
} }
@ -88,7 +90,7 @@ export interface Operation<R> extends OperationDescriptor<R>{
icon96: string; icon96: string;
icon: string|IconType; icon: string|IconType;
}; };
workingSchema: OperationFlattenSchema; schemaIndex: SchemaIndex
} }
export interface OperationDescriptor<R> { export interface OperationDescriptor<R> {
@ -102,7 +104,6 @@ export interface OperationDescriptor<R> {
previewGeomProvider?: (params: R) => OperationGeometryProvider, previewGeomProvider?: (params: R) => OperationGeometryProvider,
form: () => React.ReactNode, form: () => React.ReactNode,
schema: OperationSchema, schema: OperationSchema,
formFields: FieldWidgetProps[],
onParamsUpdate?: (params, name, value) => void, onParamsUpdate?: (params, name, value) => void,
} }
@ -116,16 +117,72 @@ export interface OperationService {
) => void)[] ) => void)[]
} }
export type Index<T> = {
[beanPath: string]: T
};
export interface SchemaIndexField {
path: ParamsPath,
flattenedPath: FlattenPath,
metadata: SchemaField
}
export interface EntityReference {
field: SchemaIndexField;
metadata: EntityTypeSchema;
isArray: boolean;
}
export interface SchemaIndex {
fields: SchemaIndexField[],
entities: EntityReference[],
fieldsByFlattenedPaths: Index<SchemaIndexField>;
entitiesByFlattenedPaths: Index<EntityReference>;
}
export interface OperationGeometryProvider { export interface OperationGeometryProvider {
} }
function flattenSchema(schema: OperationSchema): OperationFlattenSchema { function createSchemaIndex(schema: OperationSchema): SchemaIndex {
const flatSchema = {} as OperationFlattenSchema;
schemaIterator(schema, (path, flattenedPath, schemaField) => { const index = {
flatSchema[flattenedPath] = schemaField; fields: [],
fieldsByFlattenedPaths: {},
entities: [],
entitiesByFlattenedPaths: {}
} as SchemaIndex;
schemaIterator(schema, (path, flattenedPath, metadata) => {
const indexField = {
path: [...path],
flattenedPath,
metadata
};
index.fields.push(indexField);
index.fieldsByFlattenedPaths[flattenedPath] = indexField;
}); });
return flatSchema;
index.fields.forEach(f => {
const unwrappedMd = unwrapMetadata(f.metadata);
if (unwrappedMd.type !== Types.entity) {
return;
}
const entity: EntityReference= {
field: f,
isArray: f.metadata.type === Types.array,
metadata: unwrappedMd
};
index.entities.push(entity);
index.entitiesByFlattenedPaths[f.flattenedPath] = entity;
});
return index;
} }
declare module 'context' { declare module 'context' {

View file

@ -31,9 +31,10 @@ function materializeParamsImpl(ctx: CoreContext,
params: OperationParams, params: OperationParams,
schema: OperationSchema, schema: OperationSchema,
result: any, result: any,
reportError: OperationParamsErrorReporter) { parentReportError: OperationParamsErrorReporter) {
for (let field of Object.keys(schema)) { for (let field of Object.keys(schema)) {
const reportError = parentReportError.dot(field);
const md = schema[field]; const md = schema[field];
let value = params[field]; let value = params[field];
@ -43,11 +44,11 @@ function materializeParamsImpl(ctx: CoreContext,
} }
} else { } else {
const typeDef = TypeRegistry[md.type]; const typeDef = TypeRegistry[md.type];
value = typeDef.resolve(ctx, value, md as any, reportError.dot(field), materializeParamsImpl); value = typeDef.resolve(ctx, value, md as any, reportError, materializeParamsImpl);
if (md.resolve !== undefined) { if (md.resolve !== undefined) {
value = md.resolve( value = md.resolve(
ctx, value, md as any, reportError.dot(field), materializeParamsImpl ctx, value, md as any, reportError, materializeParamsImpl
) )
} }

View file

@ -1,31 +1,27 @@
import {Materializer} from "cad/craft/schema/types/index"; import {Materializer} from "cad/craft/schema/types/index";
import {CoreContext} from "context"; import {CoreContext} from "context";
import {OperationParamsErrorReporter} from "cad/craft/schema/schema"; import {OperationParamsErrorReporter} from "cad/craft/schema/schema";
import Vector from "math/vector";
import {MObject} from "cad/model/mobject"; import {MObject} from "cad/model/mobject";
import {ObjectTypeSchema} from "cad/craft/schema/types/objectType"; import {ObjectTypeSchema} from "cad/craft/schema/types/objectType";
import Axis from "math/axis";
type VectorInput = { type AxisInput = {
vectorEntity: MObject, vectorEntity: MObject,
flip: boolean flip: boolean
} }
export function VectorResolver(ctx: CoreContext, export function AxisResolver(ctx: CoreContext,
value: VectorInput, value: AxisInput,
md: ObjectTypeSchema, md: ObjectTypeSchema,
reportError: OperationParamsErrorReporter, reportError: OperationParamsErrorReporter, materializer: Materializer): Axis {
materializer: Materializer): Vector {
if (!value.vectorEntity) { if (!value.vectorEntity) {
return null; return null;
} }
let vector = value.vectorEntity.toDirection(); let axis = value.vectorEntity.toAxis(value.flip);
if (!vector) { if (!axis) {
throw 'unsupported entity type: ' + value.vectorEntity.TYPE; throw 'unsupported entity type: ' + value.vectorEntity.TYPE;
} }
if (value.flip) { return axis;
vector = vector.negate();
}
return vector;
} }

View file

@ -4,34 +4,46 @@ import {ArrayTypeSchema} from "cad/craft/schema/types/arrayType";
import {ObjectTypeSchema} from "cad/craft/schema/types/objectType"; import {ObjectTypeSchema} from "cad/craft/schema/types/objectType";
import {StringTypeSchema} from "cad/craft/schema/types/stringType"; import {StringTypeSchema} from "cad/craft/schema/types/stringType";
import {BooleanTypeSchema} from "cad/craft/schema/types/booleanType"; import {BooleanTypeSchema} from "cad/craft/schema/types/booleanType";
import {Materializer, Types} from "cad/craft/schema/types";
import {CoreContext} from "context";
import {ParamsPath} from "cad/craft/wizard/wizardTypes";
export type FlatSchemaField = export type Coercable = any;
| ArrayTypeSchema
export type PrimitiveSchemaField =
| EntityTypeSchema | EntityTypeSchema
| NumberTypeSchema | NumberTypeSchema
| StringTypeSchema | StringTypeSchema
| BooleanTypeSchema; | BooleanTypeSchema
| ArrayTypeSchema;
export type SchemaField = FlatSchemaField | ObjectTypeSchema; export type SchemaField = PrimitiveSchemaField | ObjectTypeSchema;
export type OperationSchema = { export type OperationSchema = {
[key: string]: SchemaField; [key: string]: SchemaField;
}; };
export type OperationFlattenSchema = { export type OperationFlattenSchema = {
[key: string]: FlatSchemaField; [key: string]: PrimitiveSchemaField;
}; };
export interface BaseSchemaField { export interface BaseSchemaField {
defaultValue: Coercable, defaultValue: OperationParamValue,
optional: boolean, optional: boolean,
label?: string label?: string,
resolve?: ValueResolver
} }
export type Coercable = any; export type OperationParamPrimitive = number|boolean|string;
export type OperationParamValue = OperationParamPrimitive|OperationParamPrimitive[]|OperationParams;
export type OperationParams = { export type OperationParams = {
[key: string]: Coercable [key: string]: OperationParamValue;
}
export type MaterializedOperationParams = {
[key: string]: any;
} }
export type OperationParamsError = { export type OperationParamsError = {
@ -43,24 +55,42 @@ export type OperationParamsErrorReporter = ((msg: string) => void) & {
dot: (pathPart: string|number) => OperationParamsErrorReporter dot: (pathPart: string|number) => OperationParamsErrorReporter
}; };
export type ValueResolver = (ctx: CoreContext,
value: any,
md: SchemaField,
reportError: OperationParamsErrorReporter, materializer: Materializer) => any;
export function flattenPath(path: ParamsPath): string {
return path.join('/');
}
export function schemaIterator(schema: OperationSchema, export function schemaIterator(schema: OperationSchema,
callback: (path: string[], flattenedPath: string, field: FlatSchemaField) => void) { callback: (path: string[], flattenedPath: string, field: PrimitiveSchemaField) => void) {
function inorder(schema: OperationSchema, parentPath: string[]) { function inorder(schema: OperationSchema, parentPath: string[]) {
Object.keys(schema).forEach(key => { Object.keys(schema).forEach(key => {
const path = [...parentPath, key] const path = [...parentPath, key]
const flattenedPath = path.join('/'); const flattenedPath = flattenPath(path);
const schemaField = schema[key]; const schemaField = schema[key];
if (schemaField.type === 'object') { if (schemaField.type === 'object') {
inorder(schemaField.schema, path); inorder(schemaField.schema, path);
} else { } else {
callback(path, flattenedPath, schemaField as FlatSchemaField); callback(path, flattenedPath, schemaField as PrimitiveSchemaField);
} }
}) })
} }
inorder(schema, []); inorder(schema, []);
}
export function unwrapMetadata(fieldMd: SchemaField) {
if (fieldMd.type === Types.array) {
return unwrapMetadata(fieldMd.items||
(fieldMd as any).itemType // backward compatibility, remove me
);
}
return fieldMd;
} }

View file

@ -5,7 +5,7 @@ import ButtonGroup from 'ui/components/controls/ButtonGroup';
import ls from './Wizard.less'; import ls from './Wizard.less';
import CadError from '../../../../utils/errors'; import CadError from '../../../../utils/errors';
import {FormContext} from './form/Form'; import {FormContext, FormContextData} from './form/Form';
import connect from 'ui/connect'; import connect from 'ui/connect';
import {combine} from 'lstream'; import {combine} from 'lstream';
import {GenericWizard} from "ui/components/GenericWizard"; import {GenericWizard} from "ui/components/GenericWizard";
@ -26,10 +26,6 @@ export default class Wizard extends React.Component {
this.props.context.updateParam(name, value); this.props.context.updateParam(name, value);
}; };
setActiveParam = param => {
this.props.context.updateState(state => state.activeParam = param);
};
componentDidCatch() { componentDidCatch() {
this.setState({hasInternalError: true}); this.setState({hasInternalError: true});
} }
@ -44,13 +40,6 @@ export default class Wizard extends React.Component {
let title = (operation.label || type).toUpperCase(); let title = (operation.label || type).toUpperCase();
let formContext = {
data: params,
activeParam: this.props.activeParam,
setActiveParam: this.setActiveParam,
updateParam: this.updateParam
};
let Form = operation.form; let Form = operation.form;
const error = this.props.error; const error = this.props.error;
@ -75,7 +64,7 @@ export default class Wizard extends React.Component {
{!error.userMessage && <div>internal error processing operation, check the log</div>} {!error.userMessage && <div>internal error processing operation, check the log</div>}
</div>} </div>}
> >
<FormContext.Provider value={formContext}> <FormContext.Provider value={new FormContextData(this.props.context, [])}>
<Form/> <Form/>
</FormContext.Provider> </FormContext.Provider>
</GenericWizard>; </GenericWizard>;

View file

@ -1,42 +0,0 @@
import React from 'react';
import Label from 'ui/components/controls/Label';
import Field from 'ui/components/controls/Field';
import Stack from 'ui/components/Stack';
import {camelCaseSplitToStr} from 'gems/camelCaseSplit';
export const FormContext = React.createContext({});
export function Group({children}) {
return <Stack>
{children}
</Stack>;
}
export function formField(Control) {
return function FormPrimitive({label, name, active, setActive, ...props}) {
return <Field active={active} name={name} onFocus={setActive} onClick={setActive}>
<Label>{label || camelCaseSplitToStr(name)}</Label>
<Control {...props} />
</Field>;
}
}
export function attachToForm(Control) {
return function FormField({name, ...props}) {
return <FormContext.Consumer>
{
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}
setActive={setActive}
active={ctx.activeParam === name} />
</React.Fragment>;
}
}
</FormContext.Consumer>;
};
}

View file

@ -0,0 +1,96 @@
import React from 'react';
import Label from 'ui/components/controls/Label';
import Field from 'ui/components/controls/Field';
import Stack from 'ui/components/Stack';
import {camelCaseSplitToStr} from 'gems/camelCaseSplit';
import {FlattenPath, ParamsPath, ParamsPathSegment, WizardContext} from "cad/craft/wizard/wizardTypes";
import {flattenPath, OperationParamValue} from "cad/craft/schema/schema";
export const FormContext: React.Context<FormContextData> = React.createContext(null);
export class FormContextData {
wizardContext: WizardContext;
prefix: ParamsPath;
constructor(wizardContext: WizardContext, prefix: ParamsPath) {
this.wizardContext = wizardContext;
this.prefix = prefix;
}
updateParam(segment: ParamsPathSegment, value: OperationParamValue): void {
this.wizardContext.updateParam([...this.prefix, segment], value);
}
readParam(segment: ParamsPathSegment): OperationParamValue {
return this.wizardContext.readParam([...this.prefix, segment]);
}
dot(segment: ParamsPathSegment): FormContextData {
return new FormContextData(this.wizardContext, [...this.prefix, segment]);
}
setActiveParam = (path: FlattenPath) => {
this.wizardContext.updateState(state => state.activeParam = path);
}
get activeParam(): FlattenPath {
return this.wizardContext.state$.value.activeParam;
}
}
export function Group({children}) {
return <Stack>
{children}
</Stack>;
}
export function formField(Control) {
return function FormPrimitive({label, name, active, setActive, ...props}) {
return <Field active={active} name={name} onFocus={setActive} onClick={setActive}>
<Label>{label || camelCaseSplitToStr(name)}</Label>
<Control {...props} />
</Field>;
}
}
interface FormFieldProps {
name: ParamsPathSegment,
defaultValue: OperationParamValue,
label: string,
children: any
}
export function attachToForm(Control) {
return function FormField({name, ...props}: FormFieldProps) {
return <FormContext.Consumer>
{
(ctx: FormContextData) => {
const fullPath = flattenPath([...ctx.prefix, name]);
const onChange = val => ctx.updateParam(name, val);
const setActive = val => ctx.setActiveParam(val ? fullPath : undefined);
return <React.Fragment>
<Control value={ctx.readParam(name)}
onChange={onChange}
name={name} {...props}
setActive={setActive}
active={ctx.activeParam === fullPath} />
</React.Fragment>;
}
}
</FormContext.Consumer>;
};
}
export function SubForm(props: {name: ParamsPathSegment, children: any}) {
return <FormContext.Consumer>
{
(ctx: FormContextData) => {
return <FormContext.Provider value={ctx.dot(props.name)}>
{props.children}
</FormContext.Provider>
}
}
</FormContext.Consumer>
}

View file

@ -1,7 +1,7 @@
import React from 'react'; import React from 'react';
import {attachToForm} from './Form'; import {attachToForm} from './Form';
import Stack from 'ui/components/Stack'; import Stack from 'ui/components/Stack';
import {FormContext} from '../form/Form'; import {FormContext} from './Form';
import mapContext from 'ui/mapContext'; import mapContext from 'ui/mapContext';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import initializeBySchema from '../../../schema/initializeBySchema'; import initializeBySchema from '../../../schema/initializeBySchema';

View file

@ -3,9 +3,10 @@ import initializeBySchema from '../schema/initializeBySchema';
import {clone, EMPTY_OBJECT} from 'gems/objects'; import {clone, EMPTY_OBJECT} from 'gems/objects';
import materializeParams from '../schema/materializeParams'; import materializeParams from '../schema/materializeParams';
import {createFunctionList} from 'gems/func'; import {createFunctionList} from 'gems/func';
import {onParamsUpdate} from '../cutExtrude/extrudeOperation'; import {OperationRequest} from "cad/craft/craftPlugin";
import {propsChangeTracker} from 'lstream/utils'; import {ParamsPath, WizardContext, WizardState} from "cad/craft/wizard/wizardTypes";
import {OperationSchema, schemaIterator} from "cad/craft/schema/schema"; import _ from "lodash";
import {OperationParamValue} from "cad/craft/schema/schema";
export function activate(ctx) { export function activate(ctx) {
@ -51,8 +52,8 @@ export function activate(ctx) {
gotoEditHistoryModeIfNeeded(mod); gotoEditHistoryModeIfNeeded(mod);
}); });
streams.wizard.wizardContext = streams.wizard.effectiveOperation.map(opRequest => { streams.wizard.wizardContext = streams.wizard.effectiveOperation.map((opRequest: OperationRequest) => {
let wizCtx = null; let wizCtx: WizardContext = null;
if (opRequest.type) { if (opRequest.type) {
let operation = ctx.services.operation.get(opRequest.type); let operation = ctx.services.operation.get(opRequest.type);
@ -60,9 +61,9 @@ export function activate(ctx) {
let params; let params;
let {changingHistory, noWizardFocus} = opRequest; let {changingHistory, noWizardFocus} = opRequest;
if (changingHistory) { if (changingHistory) {
params = flattenParams(opRequest.params, operation.schema); params = clone(opRequest.params)
} else { } else {
params = initializeBySchema(operation.workingSchema, ctx); params = initializeBySchema(operation.schema, ctx);
if (opRequest.initialOverrides) { if (opRequest.initialOverrides) {
applyOverrides(params, opRequest.initialOverrides); applyOverrides(params, opRequest.initialOverrides);
} }
@ -76,8 +77,7 @@ export function activate(ctx) {
let materializedWorkingRequest$ = workingRequest$.map(req => { let materializedWorkingRequest$ = workingRequest$.map(req => {
let params = {}; let params = {};
let errors = []; let errors = [];
let unflatten = unflattenParams(req.params, operation.schema); materializeParams(ctx, req.params, operation.schema, params, errors);
materializeParams(ctx, unflatten, operation.schema, params, errors);
if (errors.length !== 0) { if (errors.length !== 0) {
return INVALID_REQUEST; return INVALID_REQUEST;
} }
@ -86,22 +86,32 @@ export function activate(ctx) {
params params
}; };
}).remember(INVALID_REQUEST).filter(r => r !== INVALID_REQUEST).throttle(500); }).remember(INVALID_REQUEST).filter(r => r !== INVALID_REQUEST).throttle(500);
const state$ = state({}); const state$ = state<WizardState>({
activeParam: null
});
const updateParams = mutator => workingRequest$.mutate(data => mutator(data.params)); const updateParams = mutator => workingRequest$.mutate(data => mutator(data.params));
const updateState = mutator => state$.mutate(state => mutator(state)); const updateState = mutator => state$.mutate(state => mutator(state));
const updateParam = (name, value) => { const updateParam = (path: ParamsPath, value: OperationParamValue) => {
updateParams(params => { updateParams(params => {
if (operation.onParamsUpdate) { // if (operation.onParamsUpdate) {
operation.onParamsUpdate(params, name, value, params[name]); // operation.onParamsUpdate(params, name, value, params[name]);
} // }
params[name] = value; if (!Array.isArray(path)) {
path = [path]
}
_.set(params, path, value);
}); });
}; };
const readParam = (path: ParamsPath) => {
return _.get(params, path);
};
const disposerList = createFunctionList(); const disposerList = createFunctionList();
wizCtx = { wizCtx = {
workingRequest$, materializedWorkingRequest$, state$, updateParams, updateParam, updateState, workingRequest$, materializedWorkingRequest$, state$,
operation, changingHistory, noWizardFocus, updateParams, updateParam, readParam, updateState,
operation, changingHistory, noWizardFocus,
addDisposer: disposerList.add, addDisposer: disposerList.add,
dispose: disposerList.call, dispose: disposerList.call,
ID: ++REQUEST_COUNTER, ID: ++REQUEST_COUNTER,
@ -131,12 +141,8 @@ export function activate(ctx) {
}, },
applyWorkingRequest: () => { applyWorkingRequest: () => {
let wizCtx = streams.wizard.wizardContext.value; let {type, params} = streams.wizard.wizardContext.value.workingRequest$.value;
let {type, params} = wizCtx.workingRequest$.value; let request = clone({type, params});
let request = {
type,
params: unflattenParams(params, wizCtx.operation.schema)
};
const setError = error => streams.wizard.wizardContext.mutate(ctx => ctx.state$.mutate(state => state.error = error)); const setError = error => streams.wizard.wizardContext.mutate(ctx => ctx.state$.mutate(state => state.error = error));
if (streams.wizard.insertOperation.value.type) { if (streams.wizard.insertOperation.value.type) {
ctx.services.craft.modify(request, () => streams.wizard.insertOperation.value = EMPTY_OBJECT, setError ); ctx.services.craft.modify(request, () => streams.wizard.insertOperation.value = EMPTY_OBJECT, setError );
@ -149,23 +155,6 @@ export function activate(ctx) {
}; };
} }
function flattenParams(params, originalSchema) {
const flatParams = {};
schemaIterator(originalSchema, (path, flattenedPath, schemaField) => {
flatParams[flattenedPath] = _.get(params, path);
});
return flatParams;
}
function unflattenParams(params, originalSchema) {
const unflatParams = {};
schemaIterator(originalSchema, (path, flattenedPath, schemaField) => {
_.set(unflatParams, path, params[flattenedPath]);
});
return unflatParams;
}
function applyOverrides(params, initialOverrides) { function applyOverrides(params, initialOverrides) {
Object.assign(params, initialOverrides); Object.assign(params, initialOverrides);
} }

View file

@ -1,151 +0,0 @@
import {FACE, SHELL} from '../../model/entities';
import {memoize} from "lodash/function";
import {Types} from "cad/craft/schema/types";
export function activate(ctx) {
ctx.streams.wizard.wizardContext.attach(wizCtx => {
ctx.services.marker.clear();
if (wizCtx) {
const wizardPickHandler = createPickHandlerFromSchema(wizCtx);
ctx.services.pickControl.setPickHandler(wizardPickHandler);
wizCtx.workingRequest$.attach(({type, params}) => {
const marker = ctx.services.marker;
marker.startSession();
let {workingSchema: schema} = wizCtx.operation;
Object.keys(schema).forEach(param => {
let md = schema[param];
if (md.type !== 'entity' && !(md.type === 'array' && md.items.type === 'entity')) {
return;
}
//TODO: move to uiDefinition
let color = md.markColor;
let val = params[param];
if (Array.isArray(val)) {
val.forEach(id => marker.mark(id, color));
} else {
if (val) {
marker.mark(val, color);
}
}
});
marker.commit();
});
} else {
ctx.services.pickControl.setPickHandler(null);
}
});
}
const singleValue = (id, current) => id;
const arrayValue = (id, arr) => {
if (!arr) {
return [id];
}
if (arr.indexOf(id) === -1) {
arr.push(id);
}
return arr;
};
const getEntityParams = memoize(schema => Object.keys(schema).filter(key => schema[key].type === 'entity'));
function createPickHandlerFromSchema(wizCtx) {
function update(param, value, paramToMakeActive) {
wizCtx.updateParam(param, value);
wizCtx.updateState(state => {
state.activeParam = paramToMakeActive;
});
}
return model => {
const modelType = model.TYPE;
const params = wizCtx.workingRequest$.value.params;
const state = wizCtx.state$.value;
let {workingSchema: schema} = wizCtx.operation;
const activeMd = state.activeParam && schema[state.activeParam];
const unwrappedActiveMd = activeMd.type === Types.array ? activeMd.items : activeMd;
const activeCanTakeIt = kind => unwrappedActiveMd.allowedKinds && unwrappedActiveMd.allowedKinds.includes(kind);
function select(param, md, id) {
const valueGetter = md.type === 'array' ? arrayValue : singleValue;
let paramToMakeActive = getNextActiveParam(param, md);
update(param, valueGetter(id, params[param]), paramToMakeActive);
}
function getNextActiveParam(currParam, currMd) {
if (currMd.type !== 'array') {
const entityParams = getEntityParams(schema);
const index = entityParams.indexOf(currParam);
const nextIndex = (index + 1) % entityParams.length;
return entityParams[nextIndex];
}
return currParam;
}
function selectActive(id) {
select(state.activeParam, activeMd, id);
}
function selectToFirst(entity, id) {
const entityParams = getEntityParams(schema);
for (let param of entityParams) {
const md = schema[param];
if (md.allowedKinds.includes(entity)) {
select(param, md, id);
}
}
return true;
}
function deselectIfNeeded(id) {
const entityParams = getEntityParams(schema);
for (let param of entityParams) {
let val = params[param];
if (val === id) {
update(param, undefined, param);
return true;
} else if (Array.isArray(val)) {
let index = val.indexOf(id);
if (index !== -1) {
update(param, params[param].splice(index, 1), param);
return true;
}
}
}
}
if (deselectIfNeeded(model.id)) {
return false;
} else if (model.shell) {
if (deselectIfNeeded(model.shell.id)) {
return false;
}
}
if (modelType === FACE) {
if (activeCanTakeIt(SHELL)) {
selectActive(model.shell.id);
} else if (activeCanTakeIt(FACE)) {
selectActive(model.id);
} else {
if (!selectToFirst(FACE, model.id)) {
selectToFirst(SHELL, model.shell.id)
}
}
} else{
if (activeCanTakeIt(modelType)) {
selectActive(model.id);
} else {
selectToFirst(modelType, model.id);
}
}
return false;
};
}

View file

@ -0,0 +1,156 @@
import {FACE, SHELL} from '../../model/entities';
import {memoize} from "lodash/function";
import {Types} from "cad/craft/schema/types";
import {OperationRequest} from "cad/craft/craftPlugin";
import {FlattenPath, ParamsPath, WizardContext} from "cad/craft/wizard/wizardTypes";
import {OperationParamValue, SchemaField} from "cad/craft/schema/schema";
import {EntityReference, SchemaIndexField} from "cad/craft/operationPlugin";
export function activate(ctx) {
ctx.streams.wizard.wizardContext.attach((wizCtx: WizardContext) => {
ctx.services.marker.clear();
if (wizCtx) {
const wizardPickHandler = createPickHandlerFromSchema(wizCtx);
ctx.services.pickControl.setPickHandler(wizardPickHandler);
wizCtx.workingRequest$.attach(({type, params}: OperationRequest) => {
const marker = ctx.services.marker;
marker.startSession();
let {schemaIndex} = wizCtx.operation;
schemaIndex.entities.forEach(entityRef => {
//TODO: move to uiDefinition
let color = entityRef.metadata.markColor;
let val = wizCtx.readParam(entityRef.field.path);
if (Array.isArray(val)) {
val.forEach(id => marker.mark(id, color));
} else {
if (val) {
marker.mark(val, color);
}
}
});
marker.commit();
});
} else {
ctx.services.pickControl.setPickHandler(null);
}
});
}
const singleValue = (id, current) => id;
const arrayValue = (id, arr) => {
if (!arr) {
return [id];
}
if (arr.indexOf(id) === -1) {
arr.push(id);
}
return arr;
};
const getEntityParams = memoize(schema => Object.keys(schema).filter(key => schema[key].type === 'entity'));
function createPickHandlerFromSchema(wizCtx: WizardContext) {
function update(param: ParamsPath, value: OperationParamValue, paramToMakeActive: FlattenPath) {
wizCtx.updateParam(param, value);
wizCtx.updateState(state => {
state.activeParam = paramToMakeActive;
});
}
return model => {
const modelType = model.TYPE;
let {schemaIndex} = wizCtx.operation;
let activeEntityRef = () => {
const state = wizCtx.state$.value;
return schemaIndex.entitiesByFlattenedPaths[state.activeParam];
}
function activeCanTakeIt(kind) {
let activeRef: EntityReference = activeEntityRef();
if (!activeRef) {
return false;
}
const activeMd = activeRef?.metadata;
return activeMd && activeMd.allowedKinds.includes(kind);
}
function select(entityRef: EntityReference, id: string) {
const param = entityRef.field;
const valueGetter = entityRef.isArray ? arrayValue : singleValue;
let paramToMakeActive = getNextActiveParam(entityRef);
const currentValue = wizCtx.readParam(param.path);
update(param.path, valueGetter(id, currentValue), paramToMakeActive.field.flattenedPath);
}
function getNextActiveParam(entityRef: EntityReference): EntityReference {
if (!entityRef.isArray) {
const index = schemaIndex.entities.indexOf(entityRef);
const nextIndex = (index + 1) % schemaIndex.entities.length;
return schemaIndex.entities[nextIndex];
}
return entityRef;
}
function selectActive(id: string) {
select(activeEntityRef(), id);
}
function selectToFirst(entity, id) {
for (let eRef of schemaIndex.entities) {
if (eRef.metadata.allowedKinds.includes(entity)) {
select(eRef, id);
}
}
return true;
}
function deselectIfNeeded(id) {
for (let entityRef of schemaIndex.entities) {
const val = wizCtx.readParam(entityRef.field.path);
if (val === id) {
update(entityRef.field.path, undefined, entityRef.field.flattenedPath);
return true;
} else if (Array.isArray(val)) {
let index = val.indexOf(id);
if (index !== -1) {
update(entityRef.field.path, val.splice(index, 1), entityRef.field.flattenedPath);
return true;
}
}
}
}
if (deselectIfNeeded(model.id)) {
return false;
} else if (model.shell) {
if (deselectIfNeeded(model.shell.id)) {
return false;
}
}
if (modelType === FACE) {
if (activeCanTakeIt(SHELL)) {
selectActive(model.shell.id);
} else if (activeCanTakeIt(FACE)) {
selectActive(model.id);
} else {
if (!selectToFirst(FACE, model.id)) {
selectToFirst(SHELL, model.shell.id)
}
}
} else{
if (activeCanTakeIt(modelType)) {
selectActive(model.id);
} else {
selectToFirst(modelType, model.id);
}
}
return false;
};
}

View file

@ -0,0 +1,43 @@
import {StateStream} from "lstream";
import {OperationRequest} from "cad/craft/craftPlugin";
import {MaterializedOperationParams, OperationParamValue, OperationParams} from "cad/craft/schema/schema";
import {Operation} from "cad/craft/operationPlugin";
export type ParamsPathSegment = string|number;
export type ParamsPath = ParamsPathSegment[];
export type FlattenPath = string;
export type WizardState = {
activeParam: FlattenPath
};
export interface WizardContext {
workingRequest$: StateStream<OperationRequest>;
materializedWorkingRequest$: StateStream<MaterializedOperationParams>;
state$: StateStream<WizardState>;
updateParams: (mutator: (params: OperationParams) => void) => void;
updateParam: (path: ParamsPath, value: OperationParamValue) => void;
readParam: (path: ParamsPath) => OperationParamValue;
updateState: (mutator: (state: WizardState) => void) => void;
operation: Operation<any>;
changingHistory: boolean;
noWizardFocus: boolean;
addDisposer: (disposer: () => any|void) => void;
dispose: () => void;
ID: number;
}

View file

@ -7,13 +7,14 @@ import {resolveMDFIcon} from "./mdfIconResolver";
import {OperationSchema} from "cad/craft/schema/schema"; import {OperationSchema} from "cad/craft/schema/schema";
import { import {
DynamicWidgetProps, DynamicWidgetProps,
FieldWidgetProps, FormDefinition, FieldWidgetProps,
FormDefinition,
isContainerWidgetProps, isContainerWidgetProps,
isFieldWidgetProps, isFieldWidgetProps, isSubFormWidgetProps,
UIDefinition UIDefinition
} from "cad/mdf/ui/uiDefinition"; } from "cad/mdf/ui/uiDefinition";
import {uiDefinitionToReact} from "cad/mdf/ui/render"; import {uiDefinitionToReact} from "cad/mdf/ui/render";
import {DynamicComponents} from "cad/mdf/ui/componentRegistry"; import {ComponentLibrary, DynamicComponents} from "cad/mdf/ui/componentRegistry";
export interface MDFCommand<R> { export interface MDFCommand<R> {
id: string; id: string;
@ -32,7 +33,7 @@ export function loadMDFCommand<R>(mdfCommand: MDFCommand<R>): OperationDescripto
type: 'group', type: 'group',
content: mdfCommand.form content: mdfCommand.form
} }
const {schema: derivedSchema, formFields} = deriveSchema(uiDefinition); const derivedSchema = deriveSchema(uiDefinition);
return { return {
id: mdfCommand.id, id: mdfCommand.id,
label: mdfCommand.label, label: mdfCommand.label,
@ -49,45 +50,50 @@ export function loadMDFCommand<R>(mdfCommand: MDFCommand<R>): OperationDescripto
// ...requiresFaceSelection(1) // ...requiresFaceSelection(1)
// }, // },
form: uiDefinitionToReact(uiDefinition), form: uiDefinitionToReact(uiDefinition),
formFields,
schema: derivedSchema schema: derivedSchema
} }
} }
function extractFormFields(uiDefinition: UIDefinition): FieldWidgetProps[] { function traverseUIDefinition(uiDefinition: UIDefinition|UIDefinition[], onField: (comp: FieldWidgetProps) => void) {
const fields: FieldWidgetProps[] = [];
function inorder(comp: DynamicWidgetProps) { function inorder(comp: DynamicWidgetProps) {
const libraryItemFn = ComponentLibrary[comp.type];
if (libraryItemFn) {
const libraryItem = libraryItemFn(comp);
inorder(libraryItem)
return;
}
if (isFieldWidgetProps(comp)) { if (isFieldWidgetProps(comp)) {
fields.push(comp); onField(comp);
} }
if (isContainerWidgetProps(comp)) { if (isContainerWidgetProps(comp)) {
comp.content.forEach(inorder) if (!isSubFormWidgetProps(comp)) {
comp.content.forEach(comp => inorder(comp))
}
} }
} }
inorder(uiDefinition); if (Array.isArray(uiDefinition)) {
uiDefinition.forEach(def => traverseUIDefinition(def, onField));
return fields; } else {
inorder(uiDefinition);
}
} }
export function deriveSchema(uiDefinition: UIDefinition): { export function deriveSchema(uiDefinition: UIDefinition): OperationSchema {
schema: OperationSchema,
formFields: FieldWidgetProps[] const schema: OperationSchema = {};
} {
const formFields: FieldWidgetProps[] = extractFormFields(uiDefinition) traverseUIDefinition(uiDefinition, (field) => {
const schema = {}; let propsToSchema = DynamicComponents[field.type].propsToSchema;
formFields.forEach(f => { let fieldSchema = propsToSchema(field as any, deriveSchema);
let propsToSchema = DynamicComponents[f.type].propsToSchema; schema[field.name] = fieldSchema;
schema[f.name] = propsToSchema(schema, f as any);
}); });
return {
schema, return schema;
formFields
};
} }

View file

@ -0,0 +1,53 @@
import React from "react";
import {OperationSchema} from "cad/craft/schema/schema";
import {FieldBasicProps, fieldToSchemaGeneric} from "cad/mdf/ui/field";
import {Types} from "cad/craft/schema/types";
import {EntityKind} from "cad/model/entities";
import {SectionWidgetProps} from "cad/mdf/ui/SectionWidget";
import {DynamicComponentWidget} from "cad/mdf/ui/DynamicComponentWidget";
import {AxisResolver} from "cad/craft/schema/resolvers/axisResolver";
export interface AxisWidgetProps extends FieldBasicProps {
type: 'axis';
}
const ENTITY_CAPTURE = [EntityKind.EDGE, EntityKind.SKETCH_OBJECT, EntityKind.DATUM_AXIS, EntityKind.FACE];
export const AxisWidgetDefinition = ({name, label}: AxisWidgetProps) => ({
type: 'section',
title: label || name,
collapsible: true,
initialCollapse: false,
content: [
{
name: name,
type: 'sub-form',
resolve: AxisResolver,
content: [
{
name: "vectorEntity",
label: 'vector',
type: "selection",
capture: ENTITY_CAPTURE,
multi: false,
optional: true
},
{
name: "flip",
label: 'flip',
type: "checkbox",
defaultValue: false
}
]
},
]
} as SectionWidgetProps);

View file

@ -5,7 +5,7 @@ import {Types} from "cad/craft/schema/types";
import {EntityKind} from "cad/model/entities"; import {EntityKind} from "cad/model/entities";
import {SectionWidgetProps} from "cad/mdf/ui/SectionWidget"; import {SectionWidgetProps} from "cad/mdf/ui/SectionWidget";
import {DynamicComponentWidget} from "cad/mdf/ui/DynamicComponentWidget"; import {DynamicComponentWidget} from "cad/mdf/ui/DynamicComponentWidget";
import {VectorResolver} from "cad/craft/schema/resolvers/vectorResolver"; import {AxisResolver} from "cad/craft/schema/resolvers/axisResolver";
export interface BooleanWidgetProps extends FieldBasicProps { export interface BooleanWidgetProps extends FieldBasicProps {
@ -17,11 +17,11 @@ const ENTITY_CAPTURE = [EntityKind.SHELL];
const BOOLEAN_OPTIONS = ['NONE', 'UNION', 'SUBTRACT', 'INTERSECT']; const BOOLEAN_OPTIONS = ['NONE', 'UNION', 'SUBTRACT', 'INTERSECT'];
const BooleanUIDefinition = (fieldName: string, label: string) => ({ export const BooleanWidgetDefinition = (props: BooleanWidgetProps) => ({
type: 'section', type: 'section',
title: label, title: props.label,
collapsible: true, collapsible: true,
@ -29,56 +29,29 @@ const BooleanUIDefinition = (fieldName: string, label: string) => ({
content: [ content: [
{ {
name: fieldName+"/kind", type: 'sub-form',
label: 'kind', name: props.name,
type: "choice", optional: props.optional,
optional: true, content: [
values: BOOLEAN_OPTIONS {
name: "kind",
label: 'kind',
type: "choice",
optional: true,
defaultValue: 'NONE',
values: BOOLEAN_OPTIONS
},
{
name: "targets",
label: 'target',
type: "selection",
capture: ENTITY_CAPTURE,
multi: true,
optional: true,
}
]
}, },
{
name: fieldName+"/targets",
label: 'target',
type: "selection",
capture: ENTITY_CAPTURE,
multi: true,
optional: true,
}
] ]
} as SectionWidgetProps); } as SectionWidgetProps);
export function BooleanWidget(props: BooleanWidgetProps) {
let vectorUIDefinition = BooleanUIDefinition(props.name, props.label);
return <DynamicComponentWidget {...vectorUIDefinition} />
}
BooleanWidget.propsToSchema = (consumer: OperationSchema, props: BooleanWidgetProps) => {
return {
type: Types.object,
schema: {
kind: {
label: 'kind',
type: Types.string,
enum: BOOLEAN_OPTIONS,
defaultValue: props.defaultValue || 'NONE',
optional: false
},
targets: {
label: 'targets',
type: Types.array,
items: {
type: Types.entity,
allowedKinds: ENTITY_CAPTURE,
},
optional: true,
applicable: 'kind !== "NONE"'
}
},
...fieldToSchemaGeneric(props),
}
};

View file

@ -17,7 +17,7 @@ export function CheckboxWidget(props: CheckboxWidgetProps) {
return <CheckboxField name={props.name} defaultValue={props.defaultValue} label={props.label} /> return <CheckboxField name={props.name} defaultValue={props.defaultValue} label={props.label} />
} }
CheckboxWidget.propsToSchema = (consumer: OperationSchema, props: CheckboxWidgetProps) => { CheckboxWidget.propsToSchema = (props: CheckboxWidgetProps) => {
return { return {
type: Types.boolean, type: Types.boolean,
...fieldToSchemaGeneric(props), ...fieldToSchemaGeneric(props),

View file

@ -18,14 +18,14 @@ export interface ChoiceWidgetProps extends FieldBasicProps {
export function ChoiceWidget(props: ChoiceWidgetProps) { export function ChoiceWidget(props: ChoiceWidgetProps) {
if (!props.style || props.style === 'dropdown') { if (!props.style || props.style === 'dropdown') {
return <ComboBoxField name={props.name} defaultValue={props.defaultValue} label={props.label} > return <ComboBoxField name={props.name} defaultValue={props.defaultValue} label={props.label} >
{props.values.map(value => <ComboBoxOption value={value}>{value}</ComboBoxOption>)} {props.values.map(value => <ComboBoxOption value={value} key={value}>{value}</ComboBoxOption>)}
</ComboBoxField> </ComboBoxField>
} else { } else {
throw 'implement me'; throw 'implement me';
} }
} }
ChoiceWidget.propsToSchema = (consumer: OperationSchema, props: ChoiceWidgetProps) => { ChoiceWidget.propsToSchema = (props: ChoiceWidgetProps) => {
return { return {
type: Types.string, type: Types.string,
enum: props.values, enum: props.values,

View file

@ -1,12 +1,17 @@
import React from 'react'; import React from 'react';
import {DynamicComponents} from "cad/mdf/ui/componentRegistry"; import {ComponentLibrary, DynamicComponents} from "cad/mdf/ui/componentRegistry";
import {DynamicWidgetProps} from "cad/mdf/ui/uiDefinition"; import {DynamicWidgetProps} from "cad/mdf/ui/uiDefinition";
export function DynamicComponentWidget(props: DynamicWidgetProps) { export function DynamicComponentWidget(props: DynamicWidgetProps) {
const ToRender = DynamicComponents[props.type]; const ToRender = DynamicComponents[props.type];
if (!ToRender) { if (!ToRender) {
const uiDefinitionTemplate = ComponentLibrary[props.type];
if (uiDefinitionTemplate) {
const uiDefinition = uiDefinitionTemplate(props);
return <DynamicComponentWidget {...uiDefinition} />
}
return <span>Unknown component: {props.type}</span> return <span>Unknown component: {props.type}</span>
} }
return <ToRender {...props}/> return <ToRender {...props}/>

View file

@ -19,7 +19,7 @@ export function NumberWidget(props: NumberWidgetProps) {
return <NumberField name={props.name} defaultValue={props.defaultValue} label={props.label} /> return <NumberField name={props.name} defaultValue={props.defaultValue} label={props.label} />
} }
NumberWidget.propsToSchema = (consumer: OperationSchema, props: NumberWidgetProps) => { NumberWidget.propsToSchema = (props: NumberWidgetProps) => {
return { return {
type: Types.number, type: Types.number,
min: props.min, min: props.min,

View file

@ -1,11 +1,10 @@
import React from "react"; import React from "react";
import Entity from "cad/craft/wizard/components/form/EntityList"; import Entity from "cad/craft/wizard/components/form/EntityList";
import {EntityType} from "cad/craft/schema/types/entityType"; import {SchemaField} from "cad/craft/schema/schema";
import {OperationSchema, SchemaField} from "cad/craft/schema/schema";
import {FieldBasicProps, fieldToSchemaGeneric} from "cad/mdf/ui/field"; import {FieldBasicProps, fieldToSchemaGeneric} from "cad/mdf/ui/field";
import {EntityKind} from "cad/model/entities"; import {EntityKind} from "cad/model/entities";
import {ArrayType, ArrayTypeSchema} from "cad/craft/schema/types/arrayType"; import {ArrayTypeSchema} from "cad/craft/schema/types/arrayType";
import {Types} from "cad/craft/schema/types"; import {Types} from "cad/craft/schema/types";
@ -26,13 +25,12 @@ export function SelectionWidget(props: SelectionWidgetProps) {
return <Entity name={props.name} label={props.label} />; return <Entity name={props.name} label={props.label} />;
} }
SelectionWidget.propsToSchema = (consumer: OperationSchema, props: SelectionWidgetProps) => { SelectionWidget.propsToSchema = (props: SelectionWidgetProps) => {
let value = { let value = {
type: Types.entity, type: Types.entity,
allowedKinds: props.capture, allowedKinds: props.capture,
initializeBySelection: true, initializeBySelection: true,
...fieldToSchemaGeneric(props),
} as SchemaField; } as SchemaField;
if (props.multi) { if (props.multi) {
@ -41,8 +39,11 @@ SelectionWidget.propsToSchema = (consumer: OperationSchema, props: SelectionWidg
type: Types.array, type: Types.array,
min: props.min, min: props.min,
max: props.max, max: props.max,
items items,
...fieldToSchemaGeneric(props)
} as ArrayTypeSchema; } as ArrayTypeSchema;
} else {
Object.assign(value, fieldToSchemaGeneric(props))
} }
return value; return value;
}; };

View file

@ -0,0 +1,34 @@
import React from 'react';
import {ContainerBasicProps, ContainerWidget} from "cad/mdf/ui/ContainerWidget";
import {Group, SubForm} from "cad/craft/wizard/components/form/Form";
import {ParamsPathSegment} from "cad/craft/wizard/wizardTypes";
import {Types} from "cad/craft/schema/types";
import {FieldBasicProps, fieldToSchemaGeneric} from "cad/mdf/ui/field";
export interface SubFormWidgetProps extends ContainerBasicProps, FieldBasicProps {
type: 'sub-form',
name: ParamsPathSegment
}
export function SubFormWidget({name, content}: SubFormWidgetProps) {
return <Group>
<SubForm name={name}>
<ContainerWidget content={content} />
</SubForm>
</Group>;
}
SubFormWidget.propsToSchema = (props: SubFormWidgetProps, deriveSchema) => {
return {
type: Types.object,
schema: deriveSchema(props.content),
...fieldToSchemaGeneric(props),
}
}

View file

@ -1,75 +0,0 @@
import React from "react";
import {OperationSchema} from "cad/craft/schema/schema";
import {FieldBasicProps, fieldToSchemaGeneric} from "cad/mdf/ui/field";
import {Types} from "cad/craft/schema/types";
import {EntityKind} from "cad/model/entities";
import {SectionWidgetProps} from "cad/mdf/ui/SectionWidget";
import {DynamicComponentWidget} from "cad/mdf/ui/DynamicComponentWidget";
import {VectorResolver} from "cad/craft/schema/resolvers/vectorResolver";
export interface VectorWidgetProps extends FieldBasicProps {
type: 'vector';
}
const ENTITY_CAPTURE = [EntityKind.EDGE, EntityKind.SKETCH_OBJECT, EntityKind.DATUM_AXIS, EntityKind.FACE];
const VectorUIDefinition = (fieldName: string, label: string) => ({
type: 'section',
title: label,
collapsible: true,
initialCollapse: false,
content: [
{
name: fieldName+"/vectorEntity",
label: 'vector',
type: "selection",
capture: ENTITY_CAPTURE,
multi: false,
},
{
name: fieldName+"/flip",
label: 'flip',
type: "checkbox",
defaultValue: false
}
]
} as SectionWidgetProps);
export function VectorWidget(props: VectorWidgetProps) {
let vectorUIDefinition = VectorUIDefinition(props.name, props.label);
return <DynamicComponentWidget {...vectorUIDefinition} />
}
VectorWidget.propsToSchema = (consumer: OperationSchema, props: VectorWidgetProps) => {
return {
type: Types.object,
schema: {
vectorEntity: {
label: 'vector',
type: Types.entity,
allowedKinds: ENTITY_CAPTURE,
optional: true
},
flip: {
label: 'flip',
type: Types.boolean,
defaultValue: false,
optional: false
}
},
resolve: VectorResolver,
...fieldToSchemaGeneric(props),
}
};

View file

@ -3,10 +3,11 @@ import {SelectionWidget} from "cad/mdf/ui/SelectionWidget";
import {ContainerWidget} from "cad/mdf/ui/ContainerWidget"; import {ContainerWidget} from "cad/mdf/ui/ContainerWidget";
import {GroupWidget} from "cad/mdf/ui/GroupWidget"; import {GroupWidget} from "cad/mdf/ui/GroupWidget";
import {SectionWidget} from "cad/mdf/ui/SectionWidget"; import {SectionWidget} from "cad/mdf/ui/SectionWidget";
import {VectorWidget} from "cad/mdf/ui/VectorWidget"; import {AxisWidgetDefinition} from "cad/mdf/ui/AxisWidget";
import {CheckboxWidget} from "cad/mdf/ui/CheckboxWidget"; import {CheckboxWidget} from "cad/mdf/ui/CheckboxWidget";
import {BooleanWidget} from "cad/mdf/ui/BooleanWidget";
import {ChoiceWidget} from "cad/mdf/ui/ChoiceWidget"; import {ChoiceWidget} from "cad/mdf/ui/ChoiceWidget";
import {SubFormWidget} from "cad/mdf/ui/SubFormWidget";
import {BooleanWidgetDefinition} from "cad/mdf/ui/BooleanWidget";
export const DynamicComponents = { export const DynamicComponents = {
@ -18,14 +19,16 @@ export const DynamicComponents = {
'group': GroupWidget, 'group': GroupWidget,
'section': SectionWidget, 'sub-form': SubFormWidget,
'vector': VectorWidget, 'section': SectionWidget,
'checkbox': CheckboxWidget, 'checkbox': CheckboxWidget,
'boolean': BooleanWidget,
'choice': ChoiceWidget, 'choice': ChoiceWidget,
}
} export const ComponentLibrary = {
'axis': AxisWidgetDefinition,
'boolean': BooleanWidgetDefinition
};

View file

@ -1,14 +1,17 @@
import {Coercable} from "cad/craft/schema/schema"; import {OperationParamValue, ValueResolver} from "cad/craft/schema/schema";
import {ParamsPathSegment} from "cad/craft/wizard/wizardTypes";
export interface FieldBasicProps { export interface FieldBasicProps {
name: string; name: ParamsPathSegment;
label?: string; label?: string;
defaultValue?: Coercable; defaultValue?: OperationParamValue;
optional?: boolean optional?: boolean;
resolve?: ValueResolver
} }
export function fieldToSchemaGeneric(props: FieldBasicProps) { export function fieldToSchemaGeneric(props: FieldBasicProps) {
@ -16,5 +19,6 @@ export function fieldToSchemaGeneric(props: FieldBasicProps) {
label: props.label, label: props.label,
defaultValue: props.defaultValue, defaultValue: props.defaultValue,
optional: !!props.optional, optional: !!props.optional,
resolve: props.resolve
} }
} }

View file

@ -5,13 +5,15 @@ import {DynamicComponents} from "cad/mdf/ui/componentRegistry";
import {ContainerWidgetProps} from "cad/mdf/ui/ContainerWidget"; import {ContainerWidgetProps} from "cad/mdf/ui/ContainerWidget";
import {GroupWidgetProps} from "cad/mdf/ui/GroupWidget"; import {GroupWidgetProps} from "cad/mdf/ui/GroupWidget";
import {CheckboxWidgetProps} from "cad/mdf/ui/CheckboxWidget"; import {CheckboxWidgetProps} from "cad/mdf/ui/CheckboxWidget";
import {VectorWidgetProps} from "cad/mdf/ui/VectorWidget"; import {AxisWidgetProps} from "cad/mdf/ui/AxisWidget";
import {BooleanWidgetProps} from "cad/mdf/ui/BooleanWidget"; import {BooleanWidgetProps} from "cad/mdf/ui/BooleanWidget";
import {ChoiceWidgetProps} from "cad/mdf/ui/ChoiceWidget"; import {ChoiceWidgetProps} from "cad/mdf/ui/ChoiceWidget";
import {SubFormWidgetProps} from "cad/mdf/ui/SubFormWidget";
export type FieldWidgetProps = NumberWidgetProps | CheckboxWidgetProps | ChoiceWidgetProps | SelectionWidgetProps | VectorWidgetProps | BooleanWidgetProps; export type FieldWidgetProps = NumberWidgetProps | CheckboxWidgetProps | ChoiceWidgetProps | SelectionWidgetProps
| AxisWidgetProps | BooleanWidgetProps;
export type BasicWidgetProps = ContainerWidgetProps | SectionWidgetProps | GroupWidgetProps; export type BasicWidgetProps = ContainerWidgetProps | SectionWidgetProps | GroupWidgetProps | SubFormWidgetProps;
export type DynamicWidgetProps = FieldWidgetProps | BasicWidgetProps; export type DynamicWidgetProps = FieldWidgetProps | BasicWidgetProps;
@ -27,3 +29,7 @@ export function isContainerWidgetProps(comp: DynamicWidgetProps): comp is Contai
export function isFieldWidgetProps(comp: DynamicWidgetProps): comp is FieldWidgetProps { export function isFieldWidgetProps(comp: DynamicWidgetProps): comp is FieldWidgetProps {
return DynamicComponents[comp.type].propsToSchema !== undefined; return DynamicComponents[comp.type].propsToSchema !== undefined;
} }
export function isSubFormWidgetProps(comp: DynamicWidgetProps): comp is SubFormWidgetProps {
return (comp as SubFormWidgetProps).type == 'sub-form';
}

View file

@ -2,6 +2,7 @@ import {MObject, MObjectIdGenerator} from './mobject';
import CSys from "math/csys"; import CSys from "math/csys";
import Vector from "math/vector"; import Vector from "math/vector";
import {EntityKind} from "cad/model/entities"; import {EntityKind} from "cad/model/entities";
import Axis from "math/axis";
export class MDatum extends MObject { export class MDatum extends MObject {
@ -43,17 +44,23 @@ export class MDatum extends MObject {
export class MDatumAxis extends MObject { export class MDatumAxis extends MObject {
static TYPE = EntityKind.DATUM_AXIS; static TYPE = EntityKind.DATUM_AXIS;
origin: Vector; axis: Axis;
dir: Vector;
holder: MObject; holder: MObject;
constructor(id, origin, dir, holder) { constructor(id, origin, dir, holder) {
super(MDatumAxis.TYPE, id); super(MDatumAxis.TYPE, id);
this.origin = origin; this.axis = new Axis(origin, dir);
this.dir = dir;
this.holder = holder; this.holder = holder;
} }
get origin(): Vector {
return this.axis.origin;
}
get dir(): Vector {
return this.axis.direction;
}
get parent() { get parent() {
return this.holder; return this.holder;
} }
@ -61,4 +68,12 @@ export class MDatumAxis extends MObject {
toDirection(): Vector { toDirection(): Vector {
return this.dir; return this.dir;
}; };
toAxis(reverse: boolean): Axis {
let axis = this.axis;
if (reverse) {
axis = axis.invert();
}
return axis;
}
} }

View file

@ -4,6 +4,8 @@ import {EntityKind} from "cad/model/entities";
import {Edge} from "brep/topo/edge"; import {Edge} from "brep/topo/edge";
import Vector from "math/vector"; import Vector from "math/vector";
import {TopoObject} from "brep/topo/topo-object"; import {TopoObject} from "brep/topo/topo-object";
import Axis from "math/axis";
import {Segment} from "cad/sketch/sketchModel";
export class MEdge extends MObject { export class MEdge extends MObject {
@ -42,6 +44,20 @@ export class MEdge extends MObject {
return this.brepEdge.halfEdge1.tangentAtStart(); return this.brepEdge.halfEdge1.tangentAtStart();
}; };
toAxis(reverse: boolean): Axis {
let tan;
let origin;
let he = this.brepEdge.halfEdge1;
if (reverse) {
tan = he.tangentAtStart();
origin = he.vertexA.point;
} else {
tan = he.tangentAtEnd();
origin = he.vertexB.point;
}
return new Axis(origin, tan);
};
get topology(): TopoObject { get topology(): TopoObject {
return this.brepEdge; return this.brepEdge;
} }

View file

@ -12,6 +12,7 @@ import {Face} from "brep/topo/face";
import {EntityKind} from "cad/model/entities"; import {EntityKind} from "cad/model/entities";
import {Matrix3x4} from "math/matrix"; import {Matrix3x4} from "math/matrix";
import {TopoObject} from "brep/topo/topo-object"; import {TopoObject} from "brep/topo/topo-object";
import Axis from "math/axis";
export class MFace extends MObject { export class MFace extends MObject {
@ -235,4 +236,12 @@ export class MBrepFace extends MFace {
return this.normal(); return this.normal();
}; };
toAxis(reverse: boolean): Axis {
const dir = this.toDirection();
if (reverse) {
dir._negate();
}
return new Axis(this.favorablePoint, dir);
};
} }

View file

@ -2,6 +2,7 @@ import {IDENTITY_MATRIX, Matrix3x4} from "math/matrix";
import {EntityKind} from "cad/model/entities"; import {EntityKind} from "cad/model/entities";
import Vector from "math/vector"; import Vector from "math/vector";
import {TopoObject} from "brep/topo/topo-object"; import {TopoObject} from "brep/topo/topo-object";
import Axis from "math/axis";
export abstract class MObject { export abstract class MObject {
@ -21,10 +22,14 @@ export abstract class MObject {
abstract get parent(); abstract get parent();
toDirection() { toDirection(): Vector {
return null; return null;
}; };
toAxis(reverse: boolean): Axis {
return null;
}
get root(): MObject { get root(): MObject {
let obj = this; let obj = this;
while (obj.parent) { while (obj.parent) {

View file

@ -3,6 +3,7 @@ import {MFace} from "./mface";
import {EntityKind} from "cad/model/entities"; import {EntityKind} from "cad/model/entities";
import Vector from "math/vector"; import Vector from "math/vector";
import {Segment} from "cad/sketch/sketchModel"; import {Segment} from "cad/sketch/sketchModel";
import Axis from "math/axis";
export class MSketchObject extends MObject { export class MSketchObject extends MObject {
@ -24,7 +25,23 @@ export class MSketchObject extends MObject {
toDirection(): Vector { toDirection(): Vector {
const tangent = (this.sketchPrimitive as Segment).tangentAtStart(); const tangent = (this.sketchPrimitive as Segment).tangentAtStart();
return this.face.sketchToWorldTransformation.apply(tangent); return this.face.sketchToWorldTransformation.apply(tangent)._normalize();
};
toAxis(reverse: boolean): Axis {
let seg = this.sketchPrimitive as Segment;
let tan;
let origin;
if (reverse) {
tan = seg.tangentAtStart();
origin = seg.a;
} else {
tan = seg.tangentAtEnd();
origin = seg.b;
}
tan = this.face.sketchToWorldTransformation.applyNoTranslation(tan)._normalize();
origin = this.face.sketchToWorldTransformation.apply(origin);
return new Axis(origin, tan);
}; };
} }

View file

@ -103,11 +103,12 @@ export function activate(ctx: ApplicationContext) {
choosePartRequest$: stream(), choosePartRequest$: stream(),
partCatalogs: [ partCatalogs: [
WEB_CAD_ORG_COMMONS_CATALOG //causes resolving on loading - must be lazy.
// WEB_CAD_ORG_COMMONS_CATALOG
], ],
partRepositories: indexById([ partRepositories: indexById([
WEB_CAD_ORG_PARTS_REPO // WEB_CAD_ORG_PARTS_REPO
]), ]),
resolvePartReference, resolvePartReference,

View file

@ -22,7 +22,7 @@ function createMarker(findEntity, requestRender) {
function doMark(id, color) { function doMark(id, color) {
let mObj = findEntity(id); let mObj = findEntity(id);
if (!mObj) { if (!mObj) {
console.warn('no entity found to highlight: ' + entity + ' ' + id); console.warn('no entity found to highlight: ' + id);
return; return;
} }
marked.set(id, mObj); marked.set(id, mObj);

View file

@ -111,6 +111,11 @@ export class Segment extends SketchPrimitive {
tangentAtStart(): Vector { tangentAtStart(): Vector {
return this.b.minus(this.a); return this.b.minus(this.a);
} }
tangentAtEnd(): Vector {
return this.a.minus(this.b);
}
} }
export class Arc extends SketchPrimitive { export class Arc extends SketchPrimitive {