From 0fc56d1cfc4f281f91601adb2a2dae12b24e82ba Mon Sep 17 00:00:00 2001 From: xibyte Date: Sun, 13 Feb 2022 15:17:19 -0800 Subject: [PATCH] work on MDF --- modules/math/axis.ts | 31 ++++ .../features/extrude/extrude.operation.ts | 9 +- .../revolve_tool/revolve.operation.ts | 20 +-- web/app/cad/craft/operationPlugin.ts | 81 +++++++-- web/app/cad/craft/schema/materializeParams.ts | 7 +- .../{vectorResolver.ts => axisResolver.ts} | 22 +-- web/app/cad/craft/schema/schema.ts | 54 ++++-- .../cad/craft/wizard/components/Wizard.jsx | 15 +- .../cad/craft/wizard/components/form/Form.jsx | 42 ----- .../cad/craft/wizard/components/form/Form.tsx | 96 +++++++++++ .../wizard/components/form/MultiEntity.jsx | 2 +- .../{wizardPlugin.js => wizardPlugin.ts} | 69 ++++---- .../cad/craft/wizard/wizardSelectionPlugin.js | 151 ----------------- .../cad/craft/wizard/wizardSelectionPlugin.ts | 156 ++++++++++++++++++ web/app/cad/craft/wizard/wizardTypes.ts | 43 +++++ web/app/cad/mdf/mdf.ts | 60 ++++--- web/app/cad/mdf/ui/AxisWidget.tsx | 53 ++++++ web/app/cad/mdf/ui/BooleanWidget.tsx | 79 +++------ web/app/cad/mdf/ui/CheckboxWidget.tsx | 2 +- web/app/cad/mdf/ui/ChoiceWidget.tsx | 4 +- web/app/cad/mdf/ui/DynamicComponentWidget.tsx | 7 +- web/app/cad/mdf/ui/NumberWidget.tsx | 2 +- web/app/cad/mdf/ui/SelectionWidget.tsx | 13 +- web/app/cad/mdf/ui/SubFormWidget.tsx | 34 ++++ web/app/cad/mdf/ui/VectorWidget.tsx | 75 --------- web/app/cad/mdf/ui/componentRegistry.ts | 17 +- web/app/cad/mdf/ui/field.ts | 12 +- web/app/cad/mdf/ui/uiDefinition.ts | 12 +- web/app/cad/model/mdatum.ts | 23 ++- web/app/cad/model/medge.ts | 16 ++ web/app/cad/model/mface.ts | 9 + web/app/cad/model/mobject.ts | 7 +- web/app/cad/model/msketchObject.ts | 19 ++- web/app/cad/partImport/remotePartsPlugin.ts | 5 +- .../cad/scene/selectionMarker/markerPlugin.js | 2 +- web/app/cad/sketch/sketchModel.ts | 5 + 36 files changed, 761 insertions(+), 493 deletions(-) create mode 100644 modules/math/axis.ts rename web/app/cad/craft/schema/resolvers/{vectorResolver.ts => axisResolver.ts} (52%) delete mode 100644 web/app/cad/craft/wizard/components/form/Form.jsx create mode 100644 web/app/cad/craft/wizard/components/form/Form.tsx rename web/app/cad/craft/wizard/{wizardPlugin.js => wizardPlugin.ts} (71%) delete mode 100644 web/app/cad/craft/wizard/wizardSelectionPlugin.js create mode 100644 web/app/cad/craft/wizard/wizardSelectionPlugin.ts create mode 100644 web/app/cad/craft/wizard/wizardTypes.ts create mode 100644 web/app/cad/mdf/ui/AxisWidget.tsx create mode 100644 web/app/cad/mdf/ui/SubFormWidget.tsx delete mode 100644 web/app/cad/mdf/ui/VectorWidget.tsx diff --git a/modules/math/axis.ts b/modules/math/axis.ts new file mode 100644 index 00000000..b15e6627 --- /dev/null +++ b/modules/math/axis.ts @@ -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()); + } +} diff --git a/modules/workbenches/modeler/features/extrude/extrude.operation.ts b/modules/workbenches/modeler/features/extrude/extrude.operation.ts index c5707b74..44b860ab 100644 --- a/modules/workbenches/modeler/features/extrude/extrude.operation.ts +++ b/modules/workbenches/modeler/features/extrude/extrude.operation.ts @@ -3,13 +3,13 @@ import {MFace} from "cad/model/mface"; import {ApplicationContext} from "context"; import {MDFCommand} from "cad/mdf/mdf"; import {EntityKind} from "cad/model/entities"; -import Vector from "math/vector"; import {BooleanDefinition} from "cad/craft/schema/common/BooleanDefinition"; +import Axis from "math/axis"; interface ExtrudeParams { length: number; face: MFace; - direction?: Vector, + direction?: Axis, boolean: BooleanDefinition } @@ -32,7 +32,7 @@ const ExtrudeOperation: MDFCommand = { 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(); @@ -103,7 +103,7 @@ const ExtrudeOperation: MDFCommand = { // } // }, { - type: 'vector', + type: 'axis', name: 'direction', label: 'direction', optional: true @@ -113,7 +113,6 @@ const ExtrudeOperation: MDFCommand = { name: 'boolean', label: 'boolean', optional: true, - defaultValue: 'NONE' } ], diff --git a/modules/workbenches/modeler/features/revolve_tool/revolve.operation.ts b/modules/workbenches/modeler/features/revolve_tool/revolve.operation.ts index fd90d325..f1993437 100644 --- a/modules/workbenches/modeler/features/revolve_tool/revolve.operation.ts +++ b/modules/workbenches/modeler/features/revolve_tool/revolve.operation.ts @@ -5,11 +5,13 @@ import { MDFCommand } from "cad/mdf/mdf"; import { EntityKind } from "cad/model/entities"; import Vector from "math/vector"; import { BooleanDefinition } from "cad/craft/schema/common/BooleanDefinition"; +import * as vec from "math/vec"; +import Axis from "math/axis"; interface RevolveParams { angle: number; face: MFace; - direction?: Vector, + axis: Axis, boolean: BooleanDefinition } @@ -31,16 +33,10 @@ const RevolveOperation: MDFCommand = { if (!sketch) throw 'sketch not found for the face ' + face.id; 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 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); return shapeName; @@ -75,10 +71,10 @@ const RevolveOperation: MDFCommand = { }, }, { - type: 'vector', - name: 'direction', - label: 'direction', - optional: true + type: 'axis', + name: 'axis', + label: 'axis', + optional: false }, { type: 'boolean', diff --git a/web/app/cad/craft/operationPlugin.ts b/web/app/cad/craft/operationPlugin.ts index 1ac19012..9d2896b2 100644 --- a/web/app/cad/craft/operationPlugin.ts +++ b/web/app/cad/craft/operationPlugin.ts @@ -4,8 +4,11 @@ import {IconType} from "react-icons"; import {ActionAppearance} from "../actions/actionSystemPlugin"; import {ApplicationContext, CoreContext} from "context"; import {OperationResult} from "./craftPlugin"; -import {OperationFlattenSchema, OperationSchema, schemaIterator} from "cad/craft/schema/schema"; -import {FieldWidgetProps, UIDefinition} from "cad/mdf/ui/uiDefinition"; +import {OperationSchema, SchemaField, schemaIterator, unwrapMetadata} from "cad/craft/schema/schema"; +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) { @@ -35,9 +38,8 @@ export function activate(ctx: ApplicationContext) { }; actions.push(opAction); - const workingSchema = flattenSchema(descriptor.schema); - - registry$.mutate(registry => registry[id] = Object.assign({appearance, workingSchema}, descriptor, { + let schemaIndex = createSchemaIndex(descriptor.schema); + registry$.mutate(registry => registry[id] = Object.assign({appearance, schemaIndex}, descriptor, { run: (request, opContext) => runOperation(request, descriptor, opContext) })); } @@ -88,7 +90,7 @@ export interface Operation extends OperationDescriptor{ icon96: string; icon: string|IconType; }; - workingSchema: OperationFlattenSchema; + schemaIndex: SchemaIndex } export interface OperationDescriptor { @@ -102,7 +104,6 @@ export interface OperationDescriptor { previewGeomProvider?: (params: R) => OperationGeometryProvider, form: () => React.ReactNode, schema: OperationSchema, - formFields: FieldWidgetProps[], onParamsUpdate?: (params, name, value) => void, } @@ -116,16 +117,72 @@ export interface OperationService { ) => void)[] } +export type Index = { + [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; + entitiesByFlattenedPaths: Index; +} + export interface OperationGeometryProvider { } -function flattenSchema(schema: OperationSchema): OperationFlattenSchema { - const flatSchema = {} as OperationFlattenSchema; - schemaIterator(schema, (path, flattenedPath, schemaField) => { - flatSchema[flattenedPath] = schemaField; +function createSchemaIndex(schema: OperationSchema): SchemaIndex { + + const index = { + 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' { diff --git a/web/app/cad/craft/schema/materializeParams.ts b/web/app/cad/craft/schema/materializeParams.ts index 0a1ee08b..d43dd980 100644 --- a/web/app/cad/craft/schema/materializeParams.ts +++ b/web/app/cad/craft/schema/materializeParams.ts @@ -31,9 +31,10 @@ function materializeParamsImpl(ctx: CoreContext, params: OperationParams, schema: OperationSchema, result: any, - reportError: OperationParamsErrorReporter) { + parentReportError: OperationParamsErrorReporter) { for (let field of Object.keys(schema)) { + const reportError = parentReportError.dot(field); const md = schema[field]; let value = params[field]; @@ -43,11 +44,11 @@ function materializeParamsImpl(ctx: CoreContext, } } else { 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) { value = md.resolve( - ctx, value, md as any, reportError.dot(field), materializeParamsImpl + ctx, value, md as any, reportError, materializeParamsImpl ) } diff --git a/web/app/cad/craft/schema/resolvers/vectorResolver.ts b/web/app/cad/craft/schema/resolvers/axisResolver.ts similarity index 52% rename from web/app/cad/craft/schema/resolvers/vectorResolver.ts rename to web/app/cad/craft/schema/resolvers/axisResolver.ts index eaa00be0..5517464a 100644 --- a/web/app/cad/craft/schema/resolvers/vectorResolver.ts +++ b/web/app/cad/craft/schema/resolvers/axisResolver.ts @@ -1,31 +1,27 @@ import {Materializer} from "cad/craft/schema/types/index"; import {CoreContext} from "context"; import {OperationParamsErrorReporter} from "cad/craft/schema/schema"; -import Vector from "math/vector"; import {MObject} from "cad/model/mobject"; import {ObjectTypeSchema} from "cad/craft/schema/types/objectType"; +import Axis from "math/axis"; -type VectorInput = { +type AxisInput = { vectorEntity: MObject, flip: boolean } -export function VectorResolver(ctx: CoreContext, - value: VectorInput, - md: ObjectTypeSchema, - reportError: OperationParamsErrorReporter, - materializer: Materializer): Vector { +export function AxisResolver(ctx: CoreContext, + value: AxisInput, + md: ObjectTypeSchema, + reportError: OperationParamsErrorReporter, materializer: Materializer): Axis { if (!value.vectorEntity) { return null; } - let vector = value.vectorEntity.toDirection(); - if (!vector) { + let axis = value.vectorEntity.toAxis(value.flip); + if (!axis) { throw 'unsupported entity type: ' + value.vectorEntity.TYPE; } - if (value.flip) { - vector = vector.negate(); - } - return vector; + return axis; } diff --git a/web/app/cad/craft/schema/schema.ts b/web/app/cad/craft/schema/schema.ts index a5779c54..ff0b22cb 100644 --- a/web/app/cad/craft/schema/schema.ts +++ b/web/app/cad/craft/schema/schema.ts @@ -4,34 +4,46 @@ import {ArrayTypeSchema} from "cad/craft/schema/types/arrayType"; import {ObjectTypeSchema} from "cad/craft/schema/types/objectType"; import {StringTypeSchema} from "cad/craft/schema/types/stringType"; 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 = - | ArrayTypeSchema +export type Coercable = any; + +export type PrimitiveSchemaField = | EntityTypeSchema | NumberTypeSchema | StringTypeSchema - | BooleanTypeSchema; + | BooleanTypeSchema + | ArrayTypeSchema; -export type SchemaField = FlatSchemaField | ObjectTypeSchema; +export type SchemaField = PrimitiveSchemaField | ObjectTypeSchema; export type OperationSchema = { [key: string]: SchemaField; }; export type OperationFlattenSchema = { - [key: string]: FlatSchemaField; + [key: string]: PrimitiveSchemaField; }; export interface BaseSchemaField { - defaultValue: Coercable, + defaultValue: OperationParamValue, 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 = { - [key: string]: Coercable + [key: string]: OperationParamValue; +} + +export type MaterializedOperationParams = { + [key: string]: any; } export type OperationParamsError = { @@ -43,24 +55,42 @@ export type OperationParamsErrorReporter = ((msg: string) => void) & { 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, - callback: (path: string[], flattenedPath: string, field: FlatSchemaField) => void) { + callback: (path: string[], flattenedPath: string, field: PrimitiveSchemaField) => void) { function inorder(schema: OperationSchema, parentPath: string[]) { Object.keys(schema).forEach(key => { const path = [...parentPath, key] - const flattenedPath = path.join('/'); + const flattenedPath = flattenPath(path); const schemaField = schema[key]; if (schemaField.type === 'object') { inorder(schemaField.schema, path); } else { - callback(path, flattenedPath, schemaField as FlatSchemaField); + callback(path, flattenedPath, schemaField as PrimitiveSchemaField); } }) } 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; } \ No newline at end of file diff --git a/web/app/cad/craft/wizard/components/Wizard.jsx b/web/app/cad/craft/wizard/components/Wizard.jsx index 78516e04..47d75c40 100644 --- a/web/app/cad/craft/wizard/components/Wizard.jsx +++ b/web/app/cad/craft/wizard/components/Wizard.jsx @@ -5,7 +5,7 @@ import ButtonGroup from 'ui/components/controls/ButtonGroup'; import ls from './Wizard.less'; import CadError from '../../../../utils/errors'; -import {FormContext} from './form/Form'; +import {FormContext, FormContextData} from './form/Form'; import connect from 'ui/connect'; import {combine} from 'lstream'; import {GenericWizard} from "ui/components/GenericWizard"; @@ -26,10 +26,6 @@ export default class Wizard extends React.Component { this.props.context.updateParam(name, value); }; - setActiveParam = param => { - this.props.context.updateState(state => state.activeParam = param); - }; - componentDidCatch() { this.setState({hasInternalError: true}); } @@ -44,13 +40,6 @@ export default class Wizard extends React.Component { let title = (operation.label || type).toUpperCase(); - let formContext = { - data: params, - activeParam: this.props.activeParam, - setActiveParam: this.setActiveParam, - updateParam: this.updateParam - }; - let Form = operation.form; const error = this.props.error; @@ -75,7 +64,7 @@ export default class Wizard extends React.Component { {!error.userMessage &&
internal error processing operation, check the log
} } > - +
; diff --git a/web/app/cad/craft/wizard/components/form/Form.jsx b/web/app/cad/craft/wizard/components/form/Form.jsx deleted file mode 100644 index c2bf685f..00000000 --- a/web/app/cad/craft/wizard/components/form/Form.jsx +++ /dev/null @@ -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 - {children} - ; -} - -export function formField(Control) { - return function FormPrimitive({label, name, active, setActive, ...props}) { - return - - - ; - } -} - -export function attachToForm(Control) { - return function FormField({name, ...props}) { - return - { - ctx => { - const onChange = val => ctx.updateParam(name, val); - const setActive = val => ctx.setActiveParam(name); - return - - ; - } - } - ; - }; -} diff --git a/web/app/cad/craft/wizard/components/form/Form.tsx b/web/app/cad/craft/wizard/components/form/Form.tsx new file mode 100644 index 00000000..d6b67660 --- /dev/null +++ b/web/app/cad/craft/wizard/components/form/Form.tsx @@ -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 = 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 + {children} + ; +} + +export function formField(Control) { + return function FormPrimitive({label, name, active, setActive, ...props}) { + return + + + ; + } +} + +interface FormFieldProps { + name: ParamsPathSegment, + defaultValue: OperationParamValue, + label: string, + children: any +} + +export function attachToForm(Control) { + return function FormField({name, ...props}: FormFieldProps) { + return + { + (ctx: FormContextData) => { + const fullPath = flattenPath([...ctx.prefix, name]); + const onChange = val => ctx.updateParam(name, val); + const setActive = val => ctx.setActiveParam(val ? fullPath : undefined); + return + + ; + } + } + ; + }; +} + +export function SubForm(props: {name: ParamsPathSegment, children: any}) { + + return + { + (ctx: FormContextData) => { + return + {props.children} + + } + } + +} \ No newline at end of file diff --git a/web/app/cad/craft/wizard/components/form/MultiEntity.jsx b/web/app/cad/craft/wizard/components/form/MultiEntity.jsx index 63582d60..5fb87e17 100644 --- a/web/app/cad/craft/wizard/components/form/MultiEntity.jsx +++ b/web/app/cad/craft/wizard/components/form/MultiEntity.jsx @@ -1,7 +1,7 @@ import React from 'react'; import {attachToForm} from './Form'; import Stack from 'ui/components/Stack'; -import {FormContext} from '../form/Form'; +import {FormContext} from './Form'; import mapContext from 'ui/mapContext'; import PropTypes from 'prop-types'; import initializeBySchema from '../../../schema/initializeBySchema'; diff --git a/web/app/cad/craft/wizard/wizardPlugin.js b/web/app/cad/craft/wizard/wizardPlugin.ts similarity index 71% rename from web/app/cad/craft/wizard/wizardPlugin.js rename to web/app/cad/craft/wizard/wizardPlugin.ts index bab05b8a..05c7f491 100644 --- a/web/app/cad/craft/wizard/wizardPlugin.js +++ b/web/app/cad/craft/wizard/wizardPlugin.ts @@ -3,9 +3,10 @@ import initializeBySchema from '../schema/initializeBySchema'; import {clone, EMPTY_OBJECT} from 'gems/objects'; import materializeParams from '../schema/materializeParams'; import {createFunctionList} from 'gems/func'; -import {onParamsUpdate} from '../cutExtrude/extrudeOperation'; -import {propsChangeTracker} from 'lstream/utils'; -import {OperationSchema, schemaIterator} from "cad/craft/schema/schema"; +import {OperationRequest} from "cad/craft/craftPlugin"; +import {ParamsPath, WizardContext, WizardState} from "cad/craft/wizard/wizardTypes"; +import _ from "lodash"; +import {OperationParamValue} from "cad/craft/schema/schema"; export function activate(ctx) { @@ -51,8 +52,8 @@ export function activate(ctx) { gotoEditHistoryModeIfNeeded(mod); }); - streams.wizard.wizardContext = streams.wizard.effectiveOperation.map(opRequest => { - let wizCtx = null; + streams.wizard.wizardContext = streams.wizard.effectiveOperation.map((opRequest: OperationRequest) => { + let wizCtx: WizardContext = null; if (opRequest.type) { let operation = ctx.services.operation.get(opRequest.type); @@ -60,9 +61,9 @@ export function activate(ctx) { let params; let {changingHistory, noWizardFocus} = opRequest; if (changingHistory) { - params = flattenParams(opRequest.params, operation.schema); + params = clone(opRequest.params) } else { - params = initializeBySchema(operation.workingSchema, ctx); + params = initializeBySchema(operation.schema, ctx); if (opRequest.initialOverrides) { applyOverrides(params, opRequest.initialOverrides); } @@ -76,8 +77,7 @@ export function activate(ctx) { let materializedWorkingRequest$ = workingRequest$.map(req => { let params = {}; let errors = []; - let unflatten = unflattenParams(req.params, operation.schema); - materializeParams(ctx, unflatten, operation.schema, params, errors); + materializeParams(ctx, req.params, operation.schema, params, errors); if (errors.length !== 0) { return INVALID_REQUEST; } @@ -86,22 +86,32 @@ export function activate(ctx) { params }; }).remember(INVALID_REQUEST).filter(r => r !== INVALID_REQUEST).throttle(500); - const state$ = state({}); + const state$ = state({ + activeParam: null + }); const updateParams = mutator => workingRequest$.mutate(data => mutator(data.params)); const updateState = mutator => state$.mutate(state => mutator(state)); - const updateParam = (name, value) => { + const updateParam = (path: ParamsPath, value: OperationParamValue) => { updateParams(params => { - if (operation.onParamsUpdate) { - operation.onParamsUpdate(params, name, value, params[name]); - } - params[name] = value; + // if (operation.onParamsUpdate) { + // operation.onParamsUpdate(params, name, value, params[name]); + // } + if (!Array.isArray(path)) { + path = [path] + } + _.set(params, path, value); }); }; + const readParam = (path: ParamsPath) => { + return _.get(params, path); + }; + const disposerList = createFunctionList(); wizCtx = { - workingRequest$, materializedWorkingRequest$, state$, updateParams, updateParam, updateState, - operation, changingHistory, noWizardFocus, + workingRequest$, materializedWorkingRequest$, state$, + updateParams, updateParam, readParam, updateState, + operation, changingHistory, noWizardFocus, addDisposer: disposerList.add, dispose: disposerList.call, ID: ++REQUEST_COUNTER, @@ -131,12 +141,8 @@ export function activate(ctx) { }, applyWorkingRequest: () => { - let wizCtx = streams.wizard.wizardContext.value; - let {type, params} = wizCtx.workingRequest$.value; - let request = { - type, - params: unflattenParams(params, wizCtx.operation.schema) - }; + let {type, params} = streams.wizard.wizardContext.value.workingRequest$.value; + let request = clone({type, params}); const setError = error => streams.wizard.wizardContext.mutate(ctx => ctx.state$.mutate(state => state.error = error)); if (streams.wizard.insertOperation.value.type) { 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) { Object.assign(params, initialOverrides); } diff --git a/web/app/cad/craft/wizard/wizardSelectionPlugin.js b/web/app/cad/craft/wizard/wizardSelectionPlugin.js deleted file mode 100644 index 0f5f07b3..00000000 --- a/web/app/cad/craft/wizard/wizardSelectionPlugin.js +++ /dev/null @@ -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; - }; -} - diff --git a/web/app/cad/craft/wizard/wizardSelectionPlugin.ts b/web/app/cad/craft/wizard/wizardSelectionPlugin.ts new file mode 100644 index 00000000..187e5b2a --- /dev/null +++ b/web/app/cad/craft/wizard/wizardSelectionPlugin.ts @@ -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; + }; +} + diff --git a/web/app/cad/craft/wizard/wizardTypes.ts b/web/app/cad/craft/wizard/wizardTypes.ts new file mode 100644 index 00000000..32db8be1 --- /dev/null +++ b/web/app/cad/craft/wizard/wizardTypes.ts @@ -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; + + materializedWorkingRequest$: StateStream; + + state$: StateStream; + + updateParams: (mutator: (params: OperationParams) => void) => void; + + updateParam: (path: ParamsPath, value: OperationParamValue) => void; + + readParam: (path: ParamsPath) => OperationParamValue; + + updateState: (mutator: (state: WizardState) => void) => void; + + operation: Operation; + + changingHistory: boolean; + + noWizardFocus: boolean; + + addDisposer: (disposer: () => any|void) => void; + + dispose: () => void; + + ID: number; +} \ No newline at end of file diff --git a/web/app/cad/mdf/mdf.ts b/web/app/cad/mdf/mdf.ts index 2faa1ae0..39c6e82d 100644 --- a/web/app/cad/mdf/mdf.ts +++ b/web/app/cad/mdf/mdf.ts @@ -7,13 +7,14 @@ import {resolveMDFIcon} from "./mdfIconResolver"; import {OperationSchema} from "cad/craft/schema/schema"; import { DynamicWidgetProps, - FieldWidgetProps, FormDefinition, + FieldWidgetProps, + FormDefinition, isContainerWidgetProps, - isFieldWidgetProps, + isFieldWidgetProps, isSubFormWidgetProps, UIDefinition } from "cad/mdf/ui/uiDefinition"; 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 { id: string; @@ -32,7 +33,7 @@ export function loadMDFCommand(mdfCommand: MDFCommand): OperationDescripto type: 'group', content: mdfCommand.form } - const {schema: derivedSchema, formFields} = deriveSchema(uiDefinition); + const derivedSchema = deriveSchema(uiDefinition); return { id: mdfCommand.id, label: mdfCommand.label, @@ -49,45 +50,50 @@ export function loadMDFCommand(mdfCommand: MDFCommand): OperationDescripto // ...requiresFaceSelection(1) // }, form: uiDefinitionToReact(uiDefinition), - formFields, schema: derivedSchema } } -function extractFormFields(uiDefinition: UIDefinition): FieldWidgetProps[] { - - const fields: FieldWidgetProps[] = []; - +function traverseUIDefinition(uiDefinition: UIDefinition|UIDefinition[], onField: (comp: FieldWidgetProps) => void) { function inorder(comp: DynamicWidgetProps) { + const libraryItemFn = ComponentLibrary[comp.type]; + + if (libraryItemFn) { + const libraryItem = libraryItemFn(comp); + inorder(libraryItem) + return; + } + if (isFieldWidgetProps(comp)) { - fields.push(comp); + onField(comp); } if (isContainerWidgetProps(comp)) { - comp.content.forEach(inorder) + if (!isSubFormWidgetProps(comp)) { + comp.content.forEach(comp => inorder(comp)) + } } } - inorder(uiDefinition); - - return fields; + if (Array.isArray(uiDefinition)) { + uiDefinition.forEach(def => traverseUIDefinition(def, onField)); + } else { + inorder(uiDefinition); + } } -export function deriveSchema(uiDefinition: UIDefinition): { - schema: OperationSchema, - formFields: FieldWidgetProps[] -} { - const formFields: FieldWidgetProps[] = extractFormFields(uiDefinition) - const schema = {}; - formFields.forEach(f => { - let propsToSchema = DynamicComponents[f.type].propsToSchema; - schema[f.name] = propsToSchema(schema, f as any); +export function deriveSchema(uiDefinition: UIDefinition): OperationSchema { + + const schema: OperationSchema = {}; + + traverseUIDefinition(uiDefinition, (field) => { + let propsToSchema = DynamicComponents[field.type].propsToSchema; + let fieldSchema = propsToSchema(field as any, deriveSchema); + schema[field.name] = fieldSchema; }); - return { - schema, - formFields - }; + + return schema; } diff --git a/web/app/cad/mdf/ui/AxisWidget.tsx b/web/app/cad/mdf/ui/AxisWidget.tsx new file mode 100644 index 00000000..983b299c --- /dev/null +++ b/web/app/cad/mdf/ui/AxisWidget.tsx @@ -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); + + diff --git a/web/app/cad/mdf/ui/BooleanWidget.tsx b/web/app/cad/mdf/ui/BooleanWidget.tsx index ee5df7c5..ee6de6f2 100644 --- a/web/app/cad/mdf/ui/BooleanWidget.tsx +++ b/web/app/cad/mdf/ui/BooleanWidget.tsx @@ -5,7 +5,7 @@ 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"; +import {AxisResolver} from "cad/craft/schema/resolvers/axisResolver"; export interface BooleanWidgetProps extends FieldBasicProps { @@ -17,11 +17,11 @@ const ENTITY_CAPTURE = [EntityKind.SHELL]; const BOOLEAN_OPTIONS = ['NONE', 'UNION', 'SUBTRACT', 'INTERSECT']; -const BooleanUIDefinition = (fieldName: string, label: string) => ({ +export const BooleanWidgetDefinition = (props: BooleanWidgetProps) => ({ type: 'section', - title: label, + title: props.label, collapsible: true, @@ -29,56 +29,29 @@ const BooleanUIDefinition = (fieldName: string, label: string) => ({ content: [ { - name: fieldName+"/kind", - label: 'kind', - type: "choice", - optional: true, - values: BOOLEAN_OPTIONS + type: 'sub-form', + name: props.name, + optional: props.optional, + content: [ + { + 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); - - -export function BooleanWidget(props: BooleanWidgetProps) { - - let vectorUIDefinition = BooleanUIDefinition(props.name, props.label); - - return -} - -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), - } -}; - - diff --git a/web/app/cad/mdf/ui/CheckboxWidget.tsx b/web/app/cad/mdf/ui/CheckboxWidget.tsx index 23624d4c..04932436 100644 --- a/web/app/cad/mdf/ui/CheckboxWidget.tsx +++ b/web/app/cad/mdf/ui/CheckboxWidget.tsx @@ -17,7 +17,7 @@ export function CheckboxWidget(props: CheckboxWidgetProps) { return } -CheckboxWidget.propsToSchema = (consumer: OperationSchema, props: CheckboxWidgetProps) => { +CheckboxWidget.propsToSchema = (props: CheckboxWidgetProps) => { return { type: Types.boolean, ...fieldToSchemaGeneric(props), diff --git a/web/app/cad/mdf/ui/ChoiceWidget.tsx b/web/app/cad/mdf/ui/ChoiceWidget.tsx index 85723ae5..0e24a2ec 100644 --- a/web/app/cad/mdf/ui/ChoiceWidget.tsx +++ b/web/app/cad/mdf/ui/ChoiceWidget.tsx @@ -18,14 +18,14 @@ export interface ChoiceWidgetProps extends FieldBasicProps { export function ChoiceWidget(props: ChoiceWidgetProps) { if (!props.style || props.style === 'dropdown') { return - {props.values.map(value => {value})} + {props.values.map(value => {value})} } else { throw 'implement me'; } } -ChoiceWidget.propsToSchema = (consumer: OperationSchema, props: ChoiceWidgetProps) => { +ChoiceWidget.propsToSchema = (props: ChoiceWidgetProps) => { return { type: Types.string, enum: props.values, diff --git a/web/app/cad/mdf/ui/DynamicComponentWidget.tsx b/web/app/cad/mdf/ui/DynamicComponentWidget.tsx index 80ec0a76..dab654ad 100644 --- a/web/app/cad/mdf/ui/DynamicComponentWidget.tsx +++ b/web/app/cad/mdf/ui/DynamicComponentWidget.tsx @@ -1,12 +1,17 @@ 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"; export function DynamicComponentWidget(props: DynamicWidgetProps) { const ToRender = DynamicComponents[props.type]; if (!ToRender) { + const uiDefinitionTemplate = ComponentLibrary[props.type]; + if (uiDefinitionTemplate) { + const uiDefinition = uiDefinitionTemplate(props); + return + } return Unknown component: {props.type} } return diff --git a/web/app/cad/mdf/ui/NumberWidget.tsx b/web/app/cad/mdf/ui/NumberWidget.tsx index 06fb6749..e477684f 100644 --- a/web/app/cad/mdf/ui/NumberWidget.tsx +++ b/web/app/cad/mdf/ui/NumberWidget.tsx @@ -19,7 +19,7 @@ export function NumberWidget(props: NumberWidgetProps) { return } -NumberWidget.propsToSchema = (consumer: OperationSchema, props: NumberWidgetProps) => { +NumberWidget.propsToSchema = (props: NumberWidgetProps) => { return { type: Types.number, min: props.min, diff --git a/web/app/cad/mdf/ui/SelectionWidget.tsx b/web/app/cad/mdf/ui/SelectionWidget.tsx index 26afb03b..aaba4812 100644 --- a/web/app/cad/mdf/ui/SelectionWidget.tsx +++ b/web/app/cad/mdf/ui/SelectionWidget.tsx @@ -1,11 +1,10 @@ import React from "react"; import Entity from "cad/craft/wizard/components/form/EntityList"; -import {EntityType} from "cad/craft/schema/types/entityType"; -import {OperationSchema, SchemaField} from "cad/craft/schema/schema"; +import {SchemaField} from "cad/craft/schema/schema"; import {FieldBasicProps, fieldToSchemaGeneric} from "cad/mdf/ui/field"; 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"; @@ -26,13 +25,12 @@ export function SelectionWidget(props: SelectionWidgetProps) { return ; } -SelectionWidget.propsToSchema = (consumer: OperationSchema, props: SelectionWidgetProps) => { +SelectionWidget.propsToSchema = (props: SelectionWidgetProps) => { let value = { type: Types.entity, allowedKinds: props.capture, initializeBySelection: true, - ...fieldToSchemaGeneric(props), } as SchemaField; if (props.multi) { @@ -41,8 +39,11 @@ SelectionWidget.propsToSchema = (consumer: OperationSchema, props: SelectionWidg type: Types.array, min: props.min, max: props.max, - items + items, + ...fieldToSchemaGeneric(props) } as ArrayTypeSchema; + } else { + Object.assign(value, fieldToSchemaGeneric(props)) } return value; }; diff --git a/web/app/cad/mdf/ui/SubFormWidget.tsx b/web/app/cad/mdf/ui/SubFormWidget.tsx new file mode 100644 index 00000000..817cb8d8 --- /dev/null +++ b/web/app/cad/mdf/ui/SubFormWidget.tsx @@ -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 + + + + ; +} + +SubFormWidget.propsToSchema = (props: SubFormWidgetProps, deriveSchema) => { + return { + type: Types.object, + schema: deriveSchema(props.content), + ...fieldToSchemaGeneric(props), + } +} + + + diff --git a/web/app/cad/mdf/ui/VectorWidget.tsx b/web/app/cad/mdf/ui/VectorWidget.tsx deleted file mode 100644 index e8462bb3..00000000 --- a/web/app/cad/mdf/ui/VectorWidget.tsx +++ /dev/null @@ -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 -} - -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), - } -}; - - diff --git a/web/app/cad/mdf/ui/componentRegistry.ts b/web/app/cad/mdf/ui/componentRegistry.ts index 3f224bf5..f631f9e2 100644 --- a/web/app/cad/mdf/ui/componentRegistry.ts +++ b/web/app/cad/mdf/ui/componentRegistry.ts @@ -3,10 +3,11 @@ import {SelectionWidget} from "cad/mdf/ui/SelectionWidget"; import {ContainerWidget} from "cad/mdf/ui/ContainerWidget"; import {GroupWidget} from "cad/mdf/ui/GroupWidget"; 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 {BooleanWidget} from "cad/mdf/ui/BooleanWidget"; import {ChoiceWidget} from "cad/mdf/ui/ChoiceWidget"; +import {SubFormWidget} from "cad/mdf/ui/SubFormWidget"; +import {BooleanWidgetDefinition} from "cad/mdf/ui/BooleanWidget"; export const DynamicComponents = { @@ -18,14 +19,16 @@ export const DynamicComponents = { 'group': GroupWidget, - 'section': SectionWidget, + 'sub-form': SubFormWidget, - 'vector': VectorWidget, + 'section': SectionWidget, 'checkbox': CheckboxWidget, - 'boolean': BooleanWidget, - 'choice': ChoiceWidget, +} -} \ No newline at end of file +export const ComponentLibrary = { + 'axis': AxisWidgetDefinition, + 'boolean': BooleanWidgetDefinition +}; \ No newline at end of file diff --git a/web/app/cad/mdf/ui/field.ts b/web/app/cad/mdf/ui/field.ts index 24498a8e..6c05179b 100644 --- a/web/app/cad/mdf/ui/field.ts +++ b/web/app/cad/mdf/ui/field.ts @@ -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 { - name: string; + name: ParamsPathSegment; label?: string; - defaultValue?: Coercable; + defaultValue?: OperationParamValue; - optional?: boolean + optional?: boolean; + + resolve?: ValueResolver } export function fieldToSchemaGeneric(props: FieldBasicProps) { @@ -16,5 +19,6 @@ export function fieldToSchemaGeneric(props: FieldBasicProps) { label: props.label, defaultValue: props.defaultValue, optional: !!props.optional, + resolve: props.resolve } } \ No newline at end of file diff --git a/web/app/cad/mdf/ui/uiDefinition.ts b/web/app/cad/mdf/ui/uiDefinition.ts index fb89b4ca..cd40dfc3 100644 --- a/web/app/cad/mdf/ui/uiDefinition.ts +++ b/web/app/cad/mdf/ui/uiDefinition.ts @@ -5,13 +5,15 @@ import {DynamicComponents} from "cad/mdf/ui/componentRegistry"; import {ContainerWidgetProps} from "cad/mdf/ui/ContainerWidget"; import {GroupWidgetProps} from "cad/mdf/ui/GroupWidget"; 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 {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; @@ -27,3 +29,7 @@ export function isContainerWidgetProps(comp: DynamicWidgetProps): comp is Contai export function isFieldWidgetProps(comp: DynamicWidgetProps): comp is FieldWidgetProps { return DynamicComponents[comp.type].propsToSchema !== undefined; } + +export function isSubFormWidgetProps(comp: DynamicWidgetProps): comp is SubFormWidgetProps { + return (comp as SubFormWidgetProps).type == 'sub-form'; +} diff --git a/web/app/cad/model/mdatum.ts b/web/app/cad/model/mdatum.ts index 9bb5496b..53a19b6d 100644 --- a/web/app/cad/model/mdatum.ts +++ b/web/app/cad/model/mdatum.ts @@ -2,6 +2,7 @@ import {MObject, MObjectIdGenerator} from './mobject'; import CSys from "math/csys"; import Vector from "math/vector"; import {EntityKind} from "cad/model/entities"; +import Axis from "math/axis"; export class MDatum extends MObject { @@ -43,17 +44,23 @@ export class MDatum extends MObject { export class MDatumAxis extends MObject { static TYPE = EntityKind.DATUM_AXIS; - origin: Vector; - dir: Vector; + axis: Axis; holder: MObject; constructor(id, origin, dir, holder) { super(MDatumAxis.TYPE, id); - this.origin = origin; - this.dir = dir; + this.axis = new Axis(origin, dir); this.holder = holder; } + get origin(): Vector { + return this.axis.origin; + } + + get dir(): Vector { + return this.axis.direction; + } + get parent() { return this.holder; } @@ -61,4 +68,12 @@ export class MDatumAxis extends MObject { toDirection(): Vector { return this.dir; }; + + toAxis(reverse: boolean): Axis { + let axis = this.axis; + if (reverse) { + axis = axis.invert(); + } + return axis; + } } \ No newline at end of file diff --git a/web/app/cad/model/medge.ts b/web/app/cad/model/medge.ts index ff787e4d..ed5a0669 100644 --- a/web/app/cad/model/medge.ts +++ b/web/app/cad/model/medge.ts @@ -4,6 +4,8 @@ import {EntityKind} from "cad/model/entities"; import {Edge} from "brep/topo/edge"; import Vector from "math/vector"; import {TopoObject} from "brep/topo/topo-object"; +import Axis from "math/axis"; +import {Segment} from "cad/sketch/sketchModel"; export class MEdge extends MObject { @@ -42,6 +44,20 @@ export class MEdge extends MObject { 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 { return this.brepEdge; } diff --git a/web/app/cad/model/mface.ts b/web/app/cad/model/mface.ts index d361e2e3..07829285 100644 --- a/web/app/cad/model/mface.ts +++ b/web/app/cad/model/mface.ts @@ -12,6 +12,7 @@ import {Face} from "brep/topo/face"; import {EntityKind} from "cad/model/entities"; import {Matrix3x4} from "math/matrix"; import {TopoObject} from "brep/topo/topo-object"; +import Axis from "math/axis"; export class MFace extends MObject { @@ -235,4 +236,12 @@ export class MBrepFace extends MFace { return this.normal(); }; + toAxis(reverse: boolean): Axis { + const dir = this.toDirection(); + if (reverse) { + dir._negate(); + } + return new Axis(this.favorablePoint, dir); + }; + } diff --git a/web/app/cad/model/mobject.ts b/web/app/cad/model/mobject.ts index e43eb190..8d49bbb4 100644 --- a/web/app/cad/model/mobject.ts +++ b/web/app/cad/model/mobject.ts @@ -2,6 +2,7 @@ import {IDENTITY_MATRIX, Matrix3x4} from "math/matrix"; import {EntityKind} from "cad/model/entities"; import Vector from "math/vector"; import {TopoObject} from "brep/topo/topo-object"; +import Axis from "math/axis"; export abstract class MObject { @@ -21,10 +22,14 @@ export abstract class MObject { abstract get parent(); - toDirection() { + toDirection(): Vector { return null; }; + toAxis(reverse: boolean): Axis { + return null; + } + get root(): MObject { let obj = this; while (obj.parent) { diff --git a/web/app/cad/model/msketchObject.ts b/web/app/cad/model/msketchObject.ts index 5d6f0c39..d0a34a6d 100644 --- a/web/app/cad/model/msketchObject.ts +++ b/web/app/cad/model/msketchObject.ts @@ -3,6 +3,7 @@ import {MFace} from "./mface"; import {EntityKind} from "cad/model/entities"; import Vector from "math/vector"; import {Segment} from "cad/sketch/sketchModel"; +import Axis from "math/axis"; export class MSketchObject extends MObject { @@ -24,7 +25,23 @@ export class MSketchObject extends MObject { toDirection(): Vector { 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); }; } \ No newline at end of file diff --git a/web/app/cad/partImport/remotePartsPlugin.ts b/web/app/cad/partImport/remotePartsPlugin.ts index b8f7fa05..090bcbe2 100644 --- a/web/app/cad/partImport/remotePartsPlugin.ts +++ b/web/app/cad/partImport/remotePartsPlugin.ts @@ -103,11 +103,12 @@ export function activate(ctx: ApplicationContext) { choosePartRequest$: stream(), partCatalogs: [ - WEB_CAD_ORG_COMMONS_CATALOG + //causes resolving on loading - must be lazy. + // WEB_CAD_ORG_COMMONS_CATALOG ], partRepositories: indexById([ - WEB_CAD_ORG_PARTS_REPO + // WEB_CAD_ORG_PARTS_REPO ]), resolvePartReference, diff --git a/web/app/cad/scene/selectionMarker/markerPlugin.js b/web/app/cad/scene/selectionMarker/markerPlugin.js index 756c0f53..d4876103 100644 --- a/web/app/cad/scene/selectionMarker/markerPlugin.js +++ b/web/app/cad/scene/selectionMarker/markerPlugin.js @@ -22,7 +22,7 @@ function createMarker(findEntity, requestRender) { function doMark(id, color) { let mObj = findEntity(id); if (!mObj) { - console.warn('no entity found to highlight: ' + entity + ' ' + id); + console.warn('no entity found to highlight: ' + id); return; } marked.set(id, mObj); diff --git a/web/app/cad/sketch/sketchModel.ts b/web/app/cad/sketch/sketchModel.ts index d54aa4e9..c11d585e 100644 --- a/web/app/cad/sketch/sketchModel.ts +++ b/web/app/cad/sketch/sketchModel.ts @@ -111,6 +111,11 @@ export class Segment extends SketchPrimitive { tangentAtStart(): Vector { return this.b.minus(this.a); } + + tangentAtEnd(): Vector { + return this.a.minus(this.b); + } + } export class Arc extends SketchPrimitive {