diff --git a/modules/math/csys.ts b/modules/math/csys.ts index 808dbdb1..8f58eda2 100644 --- a/modules/math/csys.ts +++ b/modules/math/csys.ts @@ -35,7 +35,7 @@ export default class CSys { return new Matrix3x4().setBasisAxises(this.x, this.y, this.z); } - get outTransformation() { + get outTransformation(): Matrix3x4 { const mx = new Matrix3x4().setBasisAxises(this.x, this.y, this.z); mx.tx = this.origin.x; mx.ty = this.origin.y; diff --git a/modules/ui/components/Folder.jsx b/modules/ui/components/Folder.jsx index 2b595bf0..feb7fe11 100644 --- a/modules/ui/components/Folder.jsx +++ b/modules/ui/components/Folder.jsx @@ -33,7 +33,10 @@ export default class Folder extends React.Component{ } export function Title({children, isClosed, onClick}) { - return
+ const titleCss = onClick ? { + cursor: 'pointer' + } : {}; + return
{' '}{children}
; diff --git a/modules/ui/components/controls/FormSection.jsx b/modules/ui/components/controls/FormSection.jsx index c4ac8b4e..37bc5739 100644 --- a/modules/ui/components/controls/FormSection.jsx +++ b/modules/ui/components/controls/FormSection.jsx @@ -4,10 +4,10 @@ import {Title} from '../Folder'; export class StackSection extends React.Component { render() { - const {title, children} = this.props; + const {title, children, isClosed, onTitleClick} = this.props; return - {title} - {children} + {title} + {!isClosed && children} ; } diff --git a/modules/workbenches/modeler/features/extrude/extrude.operation.ts b/modules/workbenches/modeler/features/extrude/extrude.operation.ts index d9035fea..e0d0cde0 100644 --- a/modules/workbenches/modeler/features/extrude/extrude.operation.ts +++ b/modules/workbenches/modeler/features/extrude/extrude.operation.ts @@ -3,10 +3,14 @@ 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"; interface ExtrudeParams { length: number; face: MFace; + direction?: Vector, + boolean: BooleanDefinition } const ExtrudeOperation: MDFCommand = { @@ -28,17 +32,49 @@ const ExtrudeOperation: MDFCommand = { const occFaces = occ.utils.sketchToFaces(sketch, face.csys); - const shapeNames = occ.utils.prism(occFaces, [0, 0, params.length]); + const dir = (params.direction && params.direction) || face.normal(); - const created = shapeNames.map(shapeName => occ.io.getShell(shapeName)); + const extrusionVector = dir.normalize()._multiply(params.length).data(); + + const tools = occFaces.map((faceName, i) => { + const shapeName = "Tool" + i; + oci.prism(shapeName, faceName, ...extrusionVector) + + // occIterateFaces(oc, shape, face => { + // let role; + // if (face.IsSame(prismAPI.FirstShape())) { + // role = "bottom"; + // } else if (face.IsSame(prismAPI.LastShape())) { + // role = "top"; + // } else { + // role = "sweep"; + // } + // getProductionInfo(face).role = role; + // }); + // + // occIterateEdges(oc, wire, edge => { + // const generatedList = prismAPI.Generated(edge); + // occIterateListOfShape(oc, generatedList, face => { + // console.log(face); + // }) + // }) + + return shapeName; + }); return { - consumed: [face.parent], - created - }; + created: tools.map(shapeName => occ.io.getShell(shapeName)), + consumed: [] + } + // return occ.utils.applyBooleanModifier(tools, params.boolean); }, + // useBoolean: { + // booleanField: 'boolean', + // impliedTargetField: 'face' + // }, + form: [ { type: 'number', @@ -57,6 +93,33 @@ const ExtrudeOperation: MDFCommand = { preselectionIndex: 0 }, }, + // { + // type: 'vector', + // name: 'direction', + // label: 'direction' + // }, + // { + // type: 'boolean', + // name: 'boolean', + // label: 'boolean', + // defaultValue: { + // implyItFromField: 'face' + // } + // }, + { + type: 'vector', + name: 'direction', + label: 'direction', + optional: true + }, + { + type: 'boolean', + name: 'boolean', + label: 'boolean', + optional: true, + defaultValue: 'NONE' + } + ], } diff --git a/web/app/cad/craft/e0/OCCUtils.ts b/web/app/cad/craft/e0/OCCUtils.ts index 5367e6c7..d46b5aed 100644 --- a/web/app/cad/craft/e0/OCCUtils.ts +++ b/web/app/cad/craft/e0/OCCUtils.ts @@ -4,6 +4,10 @@ import {SketchGeom} from "cad/sketch/sketchReader"; import {OCCService} from "cad/craft/e0/occService"; import {CoreContext} from "context"; import CSys from "math/csys"; +import {OperationResult} from "cad/craft/craftPlugin"; +import {MShell} from "cad/model/mshell"; +import {BooleanDefinition, BooleanKind} from "cad/craft/schema/common/BooleanDefinition"; +import {bool} from "prop-types"; export interface OCCUtils { @@ -11,8 +15,9 @@ export interface OCCUtils { sketchToFaces(sketch: SketchGeom, csys: CSys): string[]; - prism(faces: string[], dir: Vec3): string[]; + // applyBoolean(tools: string[], kind: BooleanKind): string[]; + applyBooleanModifier(tools: string[], booleanDef?: BooleanDefinition): OperationResult; } export function createOCCUtils(ctx: CoreContext): OCCUtils { @@ -32,39 +37,67 @@ export function createOCCUtils(ctx: CoreContext): OCCUtils { }); } - function prism(faces: string[], dir: Vec3): string[] { - const oci = ctx.occService.commandInterface; - return faces.map((faceName, i) => { - const shapeName = "Shape:" + i; + function applyBoolean(tools: string[], target: string[], kind: BooleanKind): string[] { - oci.prism(shapeName, faceName, ...dir) - // occIterateFaces(oc, shape, face => { - // let role; - // if (face.IsSame(prismAPI.FirstShape())) { - // role = "bottom"; - // } else if (face.IsSame(prismAPI.LastShape())) { - // role = "top"; - // } else { - // role = "sweep"; - // } - // getProductionInfo(face).role = role; - // }); - // - // occIterateEdges(oc, wire, edge => { - // const generatedList = prismAPI.Generated(edge); - // occIterateListOfShape(oc, generatedList, face => { - // console.log(face); - // }) - // }) - return shapeName; - }); } + function applyBooleanModifier(tools: string[], booleanDef?: BooleanDefinition): OperationResult { + const occ = ctx.occService; + const oci = ctx.occService.commandInterface; + + if (!booleanDef || booleanDef.kind === 'NONE') { + + return { + created: tools.map(shapeName => occ.io.getShell(shapeName)), + consumed: [] + } + + } else { + const kind = booleanDef.kind; + + let targets = booleanDef.targets; + if (targets.length === 0) { + targets = ctx.cadRegistry.shells; + } + + let targetNames = targets.map((target, i) => { + const targetName = 'Target:' + i; + ctx.occService.io.pushModel(target, targetName) + return targetName; + }); + + oci.bfuzzyvalue(0.0001); + oci.bclearobjects(); + oci.bcleartools(); + + targetNames.forEach(targetName => oci.baddobjects(targetName)); + tools.forEach(toolName => oci.baddtools(toolName)); + + oci.bfillds(); + oci.bcbuild("BooleanResult"); + + // oci.bopcommon("result"); +//oci.bopfuse("result"); +//oci.bopcut("result"); + + return { + consumed: targets, + created: tools.map(shapeName => occ.io.getShell(shapeName), targets) + } + + + } + + + + } + + return { - wiresToFaces, sketchToFaces, prism + wiresToFaces, sketchToFaces, applyBooleanModifier } } \ No newline at end of file diff --git a/web/app/cad/craft/e0/OCI.d.ts b/web/app/cad/craft/e0/OCI.d.ts index f98b8efc..57cac4cf 100644 --- a/web/app/cad/craft/e0/OCI.d.ts +++ b/web/app/cad/craft/e0/OCI.d.ts @@ -54,6 +54,11 @@ interface OCCCommands { */ BRepIntCS(...args: any[]); + /* + Generic webcad engine command + */ + EngineCommand(...args: any[]); + /* XProgress [+|-t] [+|-c] [+|-g] The options are: diff --git a/web/app/cad/craft/e0/occCommandInterface.ts b/web/app/cad/craft/e0/occCommandInterface.ts index cf801351..c90d0683 100644 --- a/web/app/cad/craft/e0/occCommandInterface.ts +++ b/web/app/cad/craft/e0/occCommandInterface.ts @@ -6,7 +6,7 @@ export const OCI: OCCCommandInterface = new Proxy({}, { get: function (target, prop: string, receiver) { return prop in target ? target[prop] : function() { prop = prop.replace(/^_/, ''); - const args = Array.from(arguments).map(a => a + ""); + const args = Array.from(arguments).map(a => JSON.stringify(a)); console.log("ARGUMENTS:", args); const returnCode = CallCommand(prop, [prop, ...args]); // if (returnCode !== 0) { diff --git a/web/app/cad/craft/e0/occEngineInterface.ts b/web/app/cad/craft/e0/occEngineInterface.ts new file mode 100644 index 00000000..6c59e870 --- /dev/null +++ b/web/app/cad/craft/e0/occEngineInterface.ts @@ -0,0 +1,18 @@ +import {OCCCommandInterface} from "cad/craft/e0/occCommandInterface"; +import {MShell} from "cad/model/mshell"; +import {MObject} from "cad/model/mobject"; +import {Interrogate} from "cad/craft/e0/interact"; +import {readShellEntityFromJson} from "cad/scene/wrappers/entityIO"; +import {createOCCSketchLoader, OCCSketchLoader} from "cad/craft/e0/occSketchLoader"; + +export function createOCCEngineInterface(oci: OCCCommandInterface) { + + return { + io: { + pushModel: (params: { + name: string, operand: number, + }) => oci.EngineCommand(params) + } + } + +} \ No newline at end of file diff --git a/web/app/cad/craft/e0/occIO.ts b/web/app/cad/craft/e0/occIO.ts index 82cff5e5..5b6e3cc1 100644 --- a/web/app/cad/craft/e0/occIO.ts +++ b/web/app/cad/craft/e0/occIO.ts @@ -1,9 +1,10 @@ -import {OCCCommandInterface} from "cad/craft/e0/occCommandInterface"; +import {OCI} from "cad/craft/e0/occCommandInterface"; import {MShell} from "cad/model/mshell"; import {MObject} from "cad/model/mobject"; import {Interrogate} from "cad/craft/e0/interact"; import {readShellEntityFromJson} from "cad/scene/wrappers/entityIO"; import {createOCCSketchLoader, OCCSketchLoader} from "cad/craft/e0/occSketchLoader"; +import {CoreContext} from "context"; export interface OCCIO { @@ -16,7 +17,7 @@ export interface OCCIO { sketchLoader: OCCSketchLoader } -export function createOCCIO(oci: OCCCommandInterface): OCCIO { +export function createOCCIO(ctx: CoreContext): OCCIO { function getShell(shapeName: string, consumed: MShell[]): MShell { const shapeJson = Interrogate(shapeName); @@ -24,11 +25,10 @@ export function createOCCIO(oci: OCCCommandInterface): OCCIO { } function pushModel(model: MObject, name: string) { - - } - - function anchorModel() { - + ctx.occService.engineInterface.pushModel({ + name, + operand: model.brepShell.data.externals.ptr + }); } function cleanupRegistry() { @@ -38,7 +38,7 @@ export function createOCCIO(oci: OCCCommandInterface): OCCIO { return { getShell, pushModel, cleanupRegistry, - sketchLoader: createOCCSketchLoader(oci) + sketchLoader: createOCCSketchLoader(OCI) } } \ No newline at end of file diff --git a/web/app/cad/craft/e0/occService.ts b/web/app/cad/craft/e0/occService.ts index b5808fc9..9505a8f1 100644 --- a/web/app/cad/craft/e0/occService.ts +++ b/web/app/cad/craft/e0/occService.ts @@ -2,6 +2,7 @@ import {CoreContext} from "context"; import {OCCCommandInterface, OCI} from "cad/craft/e0/occCommandInterface"; import {createOCCIO, OCCIO} from "cad/craft/e0/occIO"; import {createOCCUtils, OCCUtils} from "cad/craft/e0/OCCUtils"; +import {createOCCEngineInterface} from "cad/craft/e0/occEngineInterface"; export interface OCCService { @@ -9,7 +10,9 @@ export interface OCCService { commandInterface: OCCCommandInterface; - utils: OCCUtils + utils: OCCUtils, + + engineInterface: any } export function createOCCService(ctx: CoreContext): OCCService { @@ -18,10 +21,12 @@ export function createOCCService(ctx: CoreContext): OCCService { return { - io: createOCCIO(oci), + io: createOCCIO(ctx), commandInterface: oci, + engineInterface: createOCCEngineInterface(oci), + utils: createOCCUtils(ctx) } diff --git a/web/app/cad/craft/operationPlugin.ts b/web/app/cad/craft/operationPlugin.ts index 1242a8a7..1ac19012 100644 --- a/web/app/cad/craft/operationPlugin.ts +++ b/web/app/cad/craft/operationPlugin.ts @@ -4,7 +4,7 @@ import {IconType} from "react-icons"; import {ActionAppearance} from "../actions/actionSystemPlugin"; import {ApplicationContext, CoreContext} from "context"; import {OperationResult} from "./craftPlugin"; -import {OperationSchema} from "cad/craft/schema/schema"; +import {OperationFlattenSchema, OperationSchema, schemaIterator} from "cad/craft/schema/schema"; import {FieldWidgetProps, UIDefinition} from "cad/mdf/ui/uiDefinition"; export function activate(ctx: ApplicationContext) { @@ -35,7 +35,9 @@ export function activate(ctx: ApplicationContext) { }; actions.push(opAction); - registry$.mutate(registry => registry[id] = Object.assign({appearance}, descriptor, { + const workingSchema = flattenSchema(descriptor.schema); + + registry$.mutate(registry => registry[id] = Object.assign({appearance, workingSchema}, descriptor, { run: (request, opContext) => runOperation(request, descriptor, opContext) })); } @@ -86,6 +88,7 @@ export interface Operation extends OperationDescriptor{ icon96: string; icon: string|IconType; }; + workingSchema: OperationFlattenSchema; } export interface OperationDescriptor { @@ -117,6 +120,14 @@ export interface OperationGeometryProvider { } +function flattenSchema(schema: OperationSchema): OperationFlattenSchema { + const flatSchema = {} as OperationFlattenSchema; + schemaIterator(schema, (path, flattenedPath, schemaField) => { + flatSchema[flattenedPath] = schemaField; + }); + return flatSchema; +} + declare module 'context' { interface CoreContext { diff --git a/web/app/cad/craft/primitives/simplePlane/simplePlaneOpSchema.js b/web/app/cad/craft/primitives/simplePlane/simplePlaneOpSchema.js index a7c1b4f2..cc1befa2 100644 --- a/web/app/cad/craft/primitives/simplePlane/simplePlaneOpSchema.js +++ b/web/app/cad/craft/primitives/simplePlane/simplePlaneOpSchema.js @@ -1,7 +1,7 @@ export default { orientation: { - type: 'enum', - values: ['XY', 'XZ', 'ZY'], + type: 'string', + enum: ['XY', 'XZ', 'ZY'], defaultValue: 'XY' }, parallelTo: { diff --git a/web/app/cad/craft/schema/common/BooleanDefinition.ts b/web/app/cad/craft/schema/common/BooleanDefinition.ts new file mode 100644 index 00000000..d7e2bbda --- /dev/null +++ b/web/app/cad/craft/schema/common/BooleanDefinition.ts @@ -0,0 +1,12 @@ +import {MObject} from "cad/model/mobject"; + +export type BooleanKind = 'NONE' | 'UNION' | 'SUBTRACT' | 'INTERSECT'; + +export interface BooleanDefinition { + + kind: BooleanKind; + + targets: MObject[]; + +} + diff --git a/web/app/cad/craft/schema/materializeParams.ts b/web/app/cad/craft/schema/materializeParams.ts index ceb553ce..0a1ee08b 100644 --- a/web/app/cad/craft/schema/materializeParams.ts +++ b/web/app/cad/craft/schema/materializeParams.ts @@ -45,6 +45,12 @@ function materializeParamsImpl(ctx: CoreContext, const typeDef = TypeRegistry[md.type]; value = typeDef.resolve(ctx, value, md as any, reportError.dot(field), materializeParamsImpl); + if (md.resolve !== undefined) { + value = md.resolve( + ctx, value, md as any, reportError.dot(field), materializeParamsImpl + ) + } + // if (md.type === Types.NUMBER) { // try { // const valueType = typeof value; diff --git a/web/app/cad/craft/schema/resolvers/vectorResolver.ts b/web/app/cad/craft/schema/resolvers/vectorResolver.ts new file mode 100644 index 00000000..eaa00be0 --- /dev/null +++ b/web/app/cad/craft/schema/resolvers/vectorResolver.ts @@ -0,0 +1,31 @@ +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"; + +type VectorInput = { + vectorEntity: MObject, + flip: boolean +} + +export function VectorResolver(ctx: CoreContext, + value: VectorInput, + md: ObjectTypeSchema, + reportError: OperationParamsErrorReporter, + materializer: Materializer): Vector { + + if (!value.vectorEntity) { + return null; + } + + let vector = value.vectorEntity.toDirection(); + if (!vector) { + throw 'unsupported entity type: ' + value.vectorEntity.TYPE; + } + if (value.flip) { + vector = vector.negate(); + } + return vector; +} diff --git a/web/app/cad/craft/schema/schema.ts b/web/app/cad/craft/schema/schema.ts index 5d350371..a5779c54 100644 --- a/web/app/cad/craft/schema/schema.ts +++ b/web/app/cad/craft/schema/schema.ts @@ -1,16 +1,27 @@ -import {Types} from "cad/craft/schema/types"; import {NumberTypeSchema} from "cad/craft/schema/types/numberType"; import {EntityTypeSchema} from "cad/craft/schema/types/entityType"; import {ArrayTypeSchema} from "cad/craft/schema/types/arrayType"; import {ObjectTypeSchema} from "cad/craft/schema/types/objectType"; -import {EnumTypeSchema} from "cad/craft/schema/types/enumType"; +import {StringTypeSchema} from "cad/craft/schema/types/stringType"; +import {BooleanTypeSchema} from "cad/craft/schema/types/booleanType"; -export type SchemaField = NumberTypeSchema | EntityTypeSchema | ArrayTypeSchema | ObjectTypeSchema | EnumTypeSchema; +export type FlatSchemaField = + | ArrayTypeSchema + | EntityTypeSchema + | NumberTypeSchema + | StringTypeSchema + | BooleanTypeSchema; + +export type SchemaField = FlatSchemaField | ObjectTypeSchema; export type OperationSchema = { [key: string]: SchemaField; }; +export type OperationFlattenSchema = { + [key: string]: FlatSchemaField; +}; + export interface BaseSchemaField { defaultValue: Coercable, optional: boolean, @@ -30,4 +41,26 @@ export type OperationParamsError = { export type OperationParamsErrorReporter = ((msg: string) => void) & { dot: (pathPart: string|number) => OperationParamsErrorReporter -}; \ No newline at end of file +}; + +export function schemaIterator(schema: OperationSchema, + callback: (path: string[], flattenedPath: string, field: FlatSchemaField) => void) { + + function inorder(schema: OperationSchema, parentPath: string[]) { + + Object.keys(schema).forEach(key => { + const path = [...parentPath, key] + const flattenedPath = path.join('/'); + const schemaField = schema[key]; + + + if (schemaField.type === 'object') { + inorder(schemaField.schema, path); + } else { + callback(path, flattenedPath, schemaField as FlatSchemaField); + } + }) + + } + inorder(schema, []); +} \ No newline at end of file diff --git a/web/app/cad/craft/schema/types/booleanType.ts b/web/app/cad/craft/schema/types/booleanType.ts new file mode 100644 index 00000000..9b1369fe --- /dev/null +++ b/web/app/cad/craft/schema/types/booleanType.ts @@ -0,0 +1,20 @@ +import {Materializer, Type, Types} from "cad/craft/schema/types/index"; +import {CoreContext} from "context"; +import {BaseSchemaField, OperationParamsErrorReporter} from "cad/craft/schema/schema"; + +export interface BooleanTypeSchema extends BaseSchemaField { + + type: Types.boolean, + +} + +export const BooleanType: Type = { + + resolve(ctx: CoreContext, + value: any, + md: BooleanTypeSchema, + reportError: OperationParamsErrorReporter, + materializer: Materializer): boolean { + return !!value; + } +} diff --git a/web/app/cad/craft/schema/types/index.ts b/web/app/cad/craft/schema/types/index.ts index 30d9fdaf..27e28c0b 100644 --- a/web/app/cad/craft/schema/types/index.ts +++ b/web/app/cad/craft/schema/types/index.ts @@ -4,7 +4,8 @@ import {ArrayType} from "cad/craft/schema/types/arrayType"; import {EntityType} from "cad/craft/schema/types/entityType"; import {NumberType} from "cad/craft/schema/types/numberType"; import {ObjectType} from "cad/craft/schema/types/objectType"; -import {EnumType} from "cad/craft/schema/types/enumType"; +import {StringType} from "cad/craft/schema/types/stringType"; +import {BooleanType} from "cad/craft/schema/types/booleanType"; export type Materializer = (ctx: CoreContext, params: OperationParams, @@ -22,16 +23,18 @@ export interface Type { export enum Types { array = 'array', + object = 'object', entity = 'entity', number = 'number', - object = 'object', - enum = 'enum', + boolean = 'boolean', + string = 'string' } export const TypeRegistry = { [Types.array]: ArrayType, + [Types.object]: ObjectType, [Types.entity]: EntityType, [Types.number]: NumberType, - [Types.object]: ObjectType, - [Types.enum]: EnumType, + [Types.boolean]: BooleanType, + [Types.string]: StringType, }; diff --git a/web/app/cad/craft/schema/types/objectType.ts b/web/app/cad/craft/schema/types/objectType.ts index d0a661af..695c4044 100644 --- a/web/app/cad/craft/schema/types/objectType.ts +++ b/web/app/cad/craft/schema/types/objectType.ts @@ -8,6 +8,8 @@ export interface ObjectTypeSchema extends BaseSchemaField { schema: OperationSchema; + resolver: (input: any) => any; + } export const ObjectType: Type = { diff --git a/web/app/cad/craft/schema/types/enumType.ts b/web/app/cad/craft/schema/types/stringType.ts similarity index 51% rename from web/app/cad/craft/schema/types/enumType.ts rename to web/app/cad/craft/schema/types/stringType.ts index b5ce89d2..663c8a53 100644 --- a/web/app/cad/craft/schema/types/enumType.ts +++ b/web/app/cad/craft/schema/types/stringType.ts @@ -2,25 +2,28 @@ import {Materializer, Type, TypeRegistry, Types} from "cad/craft/schema/types/in import {CoreContext} from "context"; import {BaseSchemaField, OperationParamsErrorReporter} from "cad/craft/schema/schema"; -export interface EnumTypeSchema extends BaseSchemaField { +export interface StringTypeSchema extends BaseSchemaField { type: Types.number, - values: string[] + enum?: string[] } -export const EnumType: Type = { +export const StringType: Type = { resolve(ctx: CoreContext, value: any, - md: EnumTypeSchema, + md: StringTypeSchema, reportError: OperationParamsErrorReporter, - materializer: Materializer): number { + materializer: Materializer): string { - if (md.values.indexOf(value) === -1) { - value = md.defaultValue || md.values[0]; + value = value + ''; + + if (md.enum && md.enum.indexOf(value) === -1) { + value = md.defaultValue || md.enum[0]; } + return value; } } diff --git a/web/app/cad/craft/wizard/wizardPlugin.js b/web/app/cad/craft/wizard/wizardPlugin.js index 52559e55..bab05b8a 100644 --- a/web/app/cad/craft/wizard/wizardPlugin.js +++ b/web/app/cad/craft/wizard/wizardPlugin.js @@ -5,6 +5,7 @@ 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"; export function activate(ctx) { @@ -59,9 +60,9 @@ export function activate(ctx) { let params; let {changingHistory, noWizardFocus} = opRequest; if (changingHistory) { - params = clone(opRequest.params) + params = flattenParams(opRequest.params, operation.schema); } else { - params = initializeBySchema(operation.schema, ctx); + params = initializeBySchema(operation.workingSchema, ctx); if (opRequest.initialOverrides) { applyOverrides(params, opRequest.initialOverrides); } @@ -75,7 +76,8 @@ export function activate(ctx) { let materializedWorkingRequest$ = workingRequest$.map(req => { let params = {}; let errors = []; - materializeParams(ctx, req.params, operation.schema, params, errors, []); + let unflatten = unflattenParams(req.params, operation.schema); + materializeParams(ctx, unflatten, operation.schema, params, errors); if (errors.length !== 0) { return INVALID_REQUEST; } @@ -83,7 +85,7 @@ export function activate(ctx) { type: req.type, params }; - }).remember(INVALID_REQUEST).filter(r => r !== INVALID_REQUEST); + }).remember(INVALID_REQUEST).filter(r => r !== INVALID_REQUEST).throttle(500); const state$ = state({}); const updateParams = mutator => workingRequest$.mutate(data => mutator(data.params)); const updateState = mutator => state$.mutate(state => mutator(state)); @@ -129,8 +131,12 @@ export function activate(ctx) { }, applyWorkingRequest: () => { - let {type, params} = streams.wizard.wizardContext.value.workingRequest$.value; - let request = clone({type, params}); + let wizCtx = streams.wizard.wizardContext.value; + let {type, params} = wizCtx.workingRequest$.value; + let request = { + type, + params: unflattenParams(params, wizCtx.operation.schema) + }; 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 ); @@ -143,6 +149,23 @@ 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 index 0d40e7d9..60a95700 100644 --- a/web/app/cad/craft/wizard/wizardSelectionPlugin.js +++ b/web/app/cad/craft/wizard/wizardSelectionPlugin.js @@ -10,7 +10,7 @@ export function activate(ctx) { wizCtx.workingRequest$.attach(({type, params}) => { const marker = ctx.services.marker; marker.startSession(); - let {schema} = wizCtx.operation; + let {workingSchema: schema} = wizCtx.operation; Object.keys(schema).forEach(param => { let md = schema[param]; @@ -65,7 +65,7 @@ function createPickHandlerFromSchema(wizCtx) { const params = wizCtx.workingRequest$.value.params; const state = wizCtx.state$.value; - let {schema} = wizCtx.operation; + let {workingSchema: schema} = wizCtx.operation; const activeMd = state.activeParam && schema[state.activeParam]; const activeCanTakeIt = kind => activeMd.allowedKinds && activeMd.allowedKinds.includes(kind); diff --git a/web/app/cad/mdf/generateForm.tsx b/web/app/cad/mdf/generateForm.tsx index 8a7d9e9f..fab219f6 100644 --- a/web/app/cad/mdf/generateForm.tsx +++ b/web/app/cad/mdf/generateForm.tsx @@ -3,7 +3,8 @@ import { ComboBoxOption } from 'ui/components/controls/ComboBoxControl'; import Entity from '../craft/wizard/components/form/Entity'; import { CheckboxField, NumberField, ComboBoxField, TextField } from '../craft/wizard/components/form/Fields'; import { Group } from '../craft/wizard/components/form/Form'; -import { OperationSchema, SchemaField } from './mdf'; +import {OperationSchema, SchemaField} from "cad/craft/schema/schema"; + export function generateForm(schema: OperationSchema) { diff --git a/web/app/cad/mdf/mdf.ts b/web/app/cad/mdf/mdf.ts index bc98885d..2faa1ae0 100644 --- a/web/app/cad/mdf/mdf.ts +++ b/web/app/cad/mdf/mdf.ts @@ -32,8 +32,7 @@ export function loadMDFCommand(mdfCommand: MDFCommand): OperationDescripto type: 'group', content: mdfCommand.form } - const formFields = extractFormFields(uiDefinition); - const derivedSchema = deriveSchema(formFields); + const {schema: derivedSchema, formFields} = deriveSchema(uiDefinition); return { id: mdfCommand.id, label: mdfCommand.label, @@ -75,13 +74,20 @@ function extractFormFields(uiDefinition: UIDefinition): FieldWidgetProps[] { return fields; } -export function deriveSchema(formFields: FieldWidgetProps[]): OperationSchema { +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); }); - return schema; + return { + schema, + formFields + }; } diff --git a/web/app/cad/mdf/ui/AccordionWidget.tsx b/web/app/cad/mdf/ui/AccordionWidget.tsx deleted file mode 100644 index 2b32d705..00000000 --- a/web/app/cad/mdf/ui/AccordionWidget.tsx +++ /dev/null @@ -1,15 +0,0 @@ -import React from "react"; -import {ContainerBasicProps, ContainerWidgetProps} from "cad/mdf/ui/ContainerWidget"; - -export interface AccordionWidgetProps extends ContainerBasicProps { - - type: 'accordion'; - -} - -export function AccordionWidget(props: AccordionWidgetProps) { - return "TBD" -} - - - diff --git a/web/app/cad/mdf/ui/BooleanWidget.tsx b/web/app/cad/mdf/ui/BooleanWidget.tsx new file mode 100644 index 00000000..5ef15991 --- /dev/null +++ b/web/app/cad/mdf/ui/BooleanWidget.tsx @@ -0,0 +1,84 @@ +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 BooleanWidgetProps extends FieldBasicProps { + + type: 'boolean'; + +} + +const ENTITY_CAPTURE = [EntityKind.SHELL]; + +const BOOLEAN_OPTIONS = ['NONE', 'UNION', 'SUBTRACT', 'INTERSECT']; + +const BooleanUIDefinition = (fieldName: string, label: string) => ({ + + type: 'section', + + title: label, + + collapsible: true, + + initialCollapse: false, + + content: [ + { + name: fieldName+"/kind", + label: 'kind', + type: "choice", + optional: true, + values: BOOLEAN_OPTIONS + }, + { + 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, + item: { + type: Types.boolean, + 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 new file mode 100644 index 00000000..23624d4c --- /dev/null +++ b/web/app/cad/mdf/ui/CheckboxWidget.tsx @@ -0,0 +1,27 @@ +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 {CheckboxField} from "cad/craft/wizard/components/form/Fields"; + +export interface CheckboxWidgetProps extends FieldBasicProps { + + type: 'checkbox'; + + min?: number; + + max?: number; +} + +export function CheckboxWidget(props: CheckboxWidgetProps) { + return +} + +CheckboxWidget.propsToSchema = (consumer: OperationSchema, 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 new file mode 100644 index 00000000..85723ae5 --- /dev/null +++ b/web/app/cad/mdf/ui/ChoiceWidget.tsx @@ -0,0 +1,36 @@ +import {ComboBoxField, NumberField} from "cad/craft/wizard/components/form/Fields"; +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 {ComboBoxOption} from "ui/components/controls/ComboBoxControl"; + +export interface ChoiceWidgetProps extends FieldBasicProps { + + type: 'choice'; + + style?: 'dropdown' | 'radio'; + + values: string[]; + +} + +export function ChoiceWidget(props: ChoiceWidgetProps) { + if (!props.style || props.style === 'dropdown') { + return + {props.values.map(value => {value})} + + } else { + throw 'implement me'; + } +} + +ChoiceWidget.propsToSchema = (consumer: OperationSchema, props: ChoiceWidgetProps) => { + return { + type: Types.string, + enum: props.values, + ...fieldToSchemaGeneric(props), + } +}; + + diff --git a/web/app/cad/mdf/ui/SectionWidget.tsx b/web/app/cad/mdf/ui/SectionWidget.tsx new file mode 100644 index 00000000..b42a5801 --- /dev/null +++ b/web/app/cad/mdf/ui/SectionWidget.tsx @@ -0,0 +1,31 @@ +import React, {useState} from "react"; +import {ContainerBasicProps, ContainerWidget, ContainerWidgetProps} from "cad/mdf/ui/ContainerWidget"; +import {Group} from "cad/craft/wizard/components/form/Form"; +import {StackSection} from "ui/components/controls/FormSection"; +import Entity from "cad/craft/wizard/components/form/EntityList"; +import {CheckboxField} from "cad/craft/wizard/components/form/Fields"; + +export interface SectionWidgetProps extends ContainerBasicProps { + + type: 'section'; + + title: string; + + collapsible: boolean; + + initialCollapse: boolean; + +} + +export function SectionWidget(props: SectionWidgetProps) { + const [visible, setVisible] = useState(!props.initialCollapse); + + const onTitleClick = props.collapsible ? () => setVisible(visible => !visible) : undefined; + + return + {visible && } + +} + + + diff --git a/web/app/cad/mdf/ui/VectorWidget.tsx b/web/app/cad/mdf/ui/VectorWidget.tsx new file mode 100644 index 00000000..e8462bb3 --- /dev/null +++ b/web/app/cad/mdf/ui/VectorWidget.tsx @@ -0,0 +1,75 @@ +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 ed15175a..3f224bf5 100644 --- a/web/app/cad/mdf/ui/componentRegistry.ts +++ b/web/app/cad/mdf/ui/componentRegistry.ts @@ -2,6 +2,11 @@ import {NumberWidget} from "cad/mdf/ui/NumberWidget"; 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 {CheckboxWidget} from "cad/mdf/ui/CheckboxWidget"; +import {BooleanWidget} from "cad/mdf/ui/BooleanWidget"; +import {ChoiceWidget} from "cad/mdf/ui/ChoiceWidget"; export const DynamicComponents = { @@ -12,4 +17,15 @@ export const DynamicComponents = { 'container': ContainerWidget, 'group': GroupWidget, + + 'section': SectionWidget, + + 'vector': VectorWidget, + + 'checkbox': CheckboxWidget, + + 'boolean': BooleanWidget, + + 'choice': ChoiceWidget, + } \ 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 f5ee11d7..fb89b4ca 100644 --- a/web/app/cad/mdf/ui/uiDefinition.ts +++ b/web/app/cad/mdf/ui/uiDefinition.ts @@ -1,12 +1,17 @@ import {NumberWidgetProps} from "cad/mdf/ui/NumberWidget"; import {SelectionWidgetProps} from "cad/mdf/ui/SelectionWidget"; -import {AccordionWidgetProps} from "cad/mdf/ui/AccordionWidget"; +import {SectionWidgetProps} from "cad/mdf/ui/SectionWidget"; 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 {BooleanWidgetProps} from "cad/mdf/ui/BooleanWidget"; +import {ChoiceWidgetProps} from "cad/mdf/ui/ChoiceWidget"; -export type FieldWidgetProps = NumberWidgetProps | SelectionWidgetProps; +export type FieldWidgetProps = NumberWidgetProps | CheckboxWidgetProps | ChoiceWidgetProps | SelectionWidgetProps | VectorWidgetProps | BooleanWidgetProps; -export type BasicWidgetProps = ContainerWidgetProps | AccordionWidgetProps; +export type BasicWidgetProps = ContainerWidgetProps | SectionWidgetProps | GroupWidgetProps; export type DynamicWidgetProps = FieldWidgetProps | BasicWidgetProps; diff --git a/web/app/cad/model/mdatum.ts b/web/app/cad/model/mdatum.ts index d36721c2..9bb5496b 100644 --- a/web/app/cad/model/mdatum.ts +++ b/web/app/cad/model/mdatum.ts @@ -57,4 +57,8 @@ export class MDatumAxis extends MObject { get parent() { return this.holder; } + + toDirection(): Vector { + return this.dir; + }; } \ No newline at end of file diff --git a/web/app/cad/model/medge.ts b/web/app/cad/model/medge.ts index 7e0b134b..e27c2891 100644 --- a/web/app/cad/model/medge.ts +++ b/web/app/cad/model/medge.ts @@ -1,12 +1,14 @@ import {MObject} from './mobject'; import {MBrepShell} from "./mshell"; import {EntityKind} from "cad/model/entities"; +import {Edge} from "brep/topo/edge"; +import Vector from "math/vector"; export class MEdge extends MObject { static TYPE = EntityKind.EDGE; shell: MBrepShell; - brepEdge: any; + brepEdge: Edge; constructor(id, shell, brepEdge) { super(MEdge.TYPE, id); @@ -34,4 +36,9 @@ export class MEdge extends MObject { get parent() { return this.shell; } + + toDirection(): Vector { + return this.brepEdge.halfEdge1.tangentAtStart(); + }; + } \ No newline at end of file diff --git a/web/app/cad/model/mface.ts b/web/app/cad/model/mface.ts index 2a882fd8..3b5d85c0 100644 --- a/web/app/cad/model/mface.ts +++ b/web/app/cad/model/mface.ts @@ -10,6 +10,7 @@ import BBox from "math/bbox"; import {Basis, BasisForPlane} from "math/basis"; import {Face} from "brep/topo/face"; import {EntityKind} from "cad/model/entities"; +import {Matrix3x4} from "math/matrix"; export class MFace extends MObject { @@ -141,14 +142,14 @@ export class MFace extends MObject { return EMPTY_ARRAY; } - get sketchToWorldTransformation() { + get sketchToWorldTransformation(): Matrix3x4 { if (!this._sketchToWorldTransformation) { this._sketchToWorldTransformation = this.csys.outTransformation; } return this._sketchToWorldTransformation; } - get worldToSketchTransformation() { + get worldToSketchTransformation(): Matrix3x4 { if (!this._worldToSketchTransformation) { this._worldToSketchTransformation = this.csys.inTransformation; } @@ -223,4 +224,9 @@ export class MBrepFace extends MFace { } return this.#favorablePoint; } + + toDirection(): Vector { + return this.normal(); + }; + } diff --git a/web/app/cad/model/mobject.ts b/web/app/cad/model/mobject.ts index eddfb2c5..ee115ce8 100644 --- a/web/app/cad/model/mobject.ts +++ b/web/app/cad/model/mobject.ts @@ -1,5 +1,6 @@ import {IDENTITY_MATRIX, Matrix3x4} from "math/matrix"; import {EntityKind} from "cad/model/entities"; +import Vector from "math/vector"; export abstract class MObject { @@ -19,6 +20,10 @@ export abstract class MObject { abstract get parent(); + toDirection() { + 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 654ebda0..5d6f0c39 100644 --- a/web/app/cad/model/msketchObject.ts +++ b/web/app/cad/model/msketchObject.ts @@ -1,6 +1,8 @@ import {MObject} from './mobject'; import {MFace} from "./mface"; import {EntityKind} from "cad/model/entities"; +import Vector from "math/vector"; +import {Segment} from "cad/sketch/sketchModel"; export class MSketchObject extends MObject { @@ -20,4 +22,9 @@ export class MSketchObject extends MObject { return this.face; } + toDirection(): Vector { + const tangent = (this.sketchPrimitive as Segment).tangentAtStart(); + return this.face.sketchToWorldTransformation.apply(tangent); + }; + } \ No newline at end of file diff --git a/web/app/cad/sketch/inPlaceSketcher.js b/web/app/cad/sketch/inPlaceSketcher.js index 11d779c4..33bd2342 100644 --- a/web/app/cad/sketch/inPlaceSketcher.js +++ b/web/app/cad/sketch/inPlaceSketcher.js @@ -48,7 +48,7 @@ export class InPlaceSketcher { this.ctx.streams.ui.toolbars.headsUpShowTitles.next(false); let sketchData = this.ctx.services.storage.get(this.sketchStorageKey); - this.viewer.historyManager.init(sketchData, md); + this.viewer.historyManager.init(sketchData); this.viewer.io.loadSketch(sketchData); this.ctx.streams.sketcher.sketchingFace.next(face); this.ctx.streams.sketcher.sketcherAppContext.next(this.sketcherAppContext); diff --git a/web/app/cad/sketch/sketchModel.ts b/web/app/cad/sketch/sketchModel.ts index f9b0e130..d54aa4e9 100644 --- a/web/app/cad/sketch/sketchModel.ts +++ b/web/app/cad/sketch/sketchModel.ts @@ -70,7 +70,7 @@ class SketchPrimitive { } } -export class Segment extends SketchPrimitive { +export class Segment extends SketchPrimitive { a: Vector; b: Vector; @@ -105,7 +105,12 @@ export class Segment extends SketchPrimitive { const [A, B] = genForm; oci.point(underName + "_A", A.x, A.y, A.z); oci.point(underName + "_B", B.x, B.y, B.z); - oci.gcarc(underName, "seg", underName + "_A", underName + "_B")} + oci.gcarc(underName, "seg", underName + "_A", underName + "_B") + } + + tangentAtStart(): Vector { + return this.b.minus(this.a); + } } export class Arc extends SketchPrimitive { diff --git a/web/app/sketcher/constr/constractibles.js b/web/app/sketcher/constr/constractibles.js index 988dc780..d47d199d 100644 --- a/web/app/sketcher/constr/constractibles.js +++ b/web/app/sketcher/constr/constractibles.js @@ -106,7 +106,7 @@ export class GCCircle extends ContractibleObject { static TYPE = 'GCCircle'; static newInstance(x, y, r) { - return GCCircle().init(new GCPoint(x, y), md) + return GCCircle().init(new GCPoint(x, y), new GCParam(r)) } init(c, r) { diff --git a/web/app/sketcher/project.js b/web/app/sketcher/project.js index 9509e765..d085b445 100644 --- a/web/app/sketcher/project.js +++ b/web/app/sketcher/project.js @@ -47,7 +47,7 @@ export class Project { let sketchId = this.getSketchId(); let sketchData = localStorage.getItem(sketchId); if (sketchData != null) { - this.viewer.historyManager.init(sketchData, md); + this.viewer.historyManager.init(sketchData); this.viewer.io.loadSketch(sketchData); } this.viewer.repaint();