From bf19c9577ef1b50ca067cdedc9cf2b2202807c39 Mon Sep 17 00:00:00 2001 From: Val Erastov Date: Sat, 11 Jun 2022 02:03:44 -0700 Subject: [PATCH] implement cut-extrude masking --- modules/ui/components/GenericWizard.tsx | 6 ++- modules/ui/components/Window.less | 2 + modules/ui/components/Window.tsx | 2 +- .../features/extrude/extrude.operation.ts | 51 ++++++++++++++---- modules/workbenches/modeler/index.ts | 4 +- web/app/cad/craft/craftPlugin.ts | 2 +- web/app/cad/craft/operationHelper.js | 4 -- web/app/cad/craft/operationHelper.ts | 40 ++++++++++++++ web/app/cad/craft/operationPlugin.ts | 53 +++++++++++++++---- .../craft/production/productionAnalyzer.ts | 1 - web/app/cad/craft/ui/HistoryTimeline.jsx | 8 +-- .../cad/craft/ui/SelectedModificationInfo.jsx | 6 ++- .../cad/craft/wizard/components/Wizard.tsx | 9 ++-- web/app/cad/scene/viewSyncPlugin.js | 2 +- 14 files changed, 152 insertions(+), 38 deletions(-) delete mode 100644 web/app/cad/craft/operationHelper.js create mode 100644 web/app/cad/craft/operationHelper.ts diff --git a/modules/ui/components/GenericWizard.tsx b/modules/ui/components/GenericWizard.tsx index 21d3f9f1..452c7a09 100644 --- a/modules/ui/components/GenericWizard.tsx +++ b/modules/ui/components/GenericWizard.tsx @@ -7,7 +7,7 @@ import Stack from "ui/components/Stack"; import ButtonGroup from "ui/components/controls/ButtonGroup"; import Button from "ui/components/controls/Button"; -export function GenericWizard({topicId, title, left, top, className, children, onCancel, onOK, infoText, ...props}: { +export function GenericWizard({topicId, title, icon, left, top, className, children, onCancel, onOK, infoText, ...props}: { topicId: string, title: string, left?: number, @@ -15,13 +15,15 @@ export function GenericWizard({topicId, title, left, top, className, children, o onCancel: () => any, onOK: () => any, onKeyDown?: (e) => any, - infoText?: any + infoText?: any, + icon?: any } & WindowProps ) { return DocumentationTopic$.next({ diff --git a/modules/ui/components/Window.less b/modules/ui/components/Window.less index fba6c39b..e75d5b46 100644 --- a/modules/ui/components/Window.less +++ b/modules/ui/components/Window.less @@ -31,6 +31,8 @@ .title { padding: 0.3em 0 0.3em 0.5em; font-size: 1.1em; + display: flex; + align-items: center; } .controlButtons { diff --git a/modules/ui/components/Window.tsx b/modules/ui/components/Window.tsx index 3b236cdf..c563af6f 100644 --- a/modules/ui/components/Window.tsx +++ b/modules/ui/components/Window.tsx @@ -51,7 +51,7 @@ export default class Window extends React.Component { return
-
{icon}{title.toUpperCase()}
+
{icon} {title.toUpperCase()}
{controlButtons} {minimizable && _} diff --git a/modules/workbenches/modeler/features/extrude/extrude.operation.ts b/modules/workbenches/modeler/features/extrude/extrude.operation.ts index ac5f502a..f6be014d 100644 --- a/modules/workbenches/modeler/features/extrude/extrude.operation.ts +++ b/modules/workbenches/modeler/features/extrude/extrude.operation.ts @@ -6,14 +6,8 @@ import {BooleanDefinition} from "cad/craft/schema/common/BooleanDefinition"; import {UnitVector} from "math/vector"; import {OperationDescriptor} from "cad/craft/operationPlugin"; import {MObject} from "cad/model/mobject"; -import {Edge} from "brep/topo/edge"; import {FaceRef} from "cad/craft/e0/OCCUtils"; -import {GetRef} from "cad/craft/e0/interact"; -import { - FromMObjectProductionAnalyzer, - FromSketchProductionAnalyzer, NULL_ANALYZER, - ProductionAnalyzer, PushPullFaceProductionAnalyzer -} from "cad/craft/production/productionAnalyzer"; +import {FromSketchProductionAnalyzer, PushPullFaceProductionAnalyzer} from "cad/craft/production/productionAnalyzer"; interface ExtrudeParams { @@ -28,10 +22,24 @@ interface ExtrudeParams { export const ExtrudeOperation: OperationDescriptor = { id: 'EXTRUDE', label: 'Extrude', + dynamicLabel: params => { + switch (params.boolean?.kind) { + case 'SUBTRACT': return 'Extrude-Cut'; + case 'INTERSECT': return 'Extrude-Intersect'; + case 'UNION': return 'Extrude-Fuse'; + } + return null; + }, + dynamicIcon: params => { + switch (params.boolean?.kind) { + case 'SUBTRACT': return 'img/cad/cut'; + } + return null; + }, icon: 'img/cad/extrude', info: 'extrudes 2D sketch', paramsInfo: ({length}) => `(${r(length)})`, - run: (params: ExtrudeParams, ctx: ApplicationContext) => { + run: (params: ExtrudeParams, ctx: ApplicationContext, rawParams: any) => { let occ = ctx.occService; const oci = occ.commandInterface; @@ -43,7 +51,15 @@ export const ExtrudeOperation: OperationDescriptor = { params.profiles } - const dir: UnitVector = (params.direction || face.normal()).normalize(); + let dir: UnitVector; + if (params.direction) { + dir = params.direction.normalize(); + } else { + dir = face.normal().normalize(); + if (rawParams.direction?.flip) { + dir._negate(); + } + } let extrusionVector = dir._multiply(params.length); let sketchId = face.id; @@ -131,4 +147,21 @@ export const ExtrudeOperation: OperationDescriptor = { } ], + + masking: [ + { + id: 'CUT2', + label: 'Cut', + icon: 'img/cad/cut', + info: 'makes a cut based on 2D sketch', + maskingParams: { + direction: { + flip: true + }, + boolean: { + kind: 'SUBTRACT' + } + } + } + ] } diff --git a/modules/workbenches/modeler/index.ts b/modules/workbenches/modeler/index.ts index c47f5ee6..81af92dd 100644 --- a/modules/workbenches/modeler/index.ts +++ b/modules/workbenches/modeler/index.ts @@ -44,11 +44,11 @@ export const ModelerWorkspace: WorkbenchConfig = { PatternLinearOperation, PatternRadialOperation, ], - actions: [GetVolume], + actions: [], ui: { toolbar: [ 'DATUM_CREATE', 'PLANE', 'EditFace', '-', - "EXTRUDE", "REVOLVE", "LOFT", "SWEEP", "-", + "EXTRUDE", "CUT2", "REVOLVE", "LOFT", "SWEEP", "-", "BOOLEAN", "-", "SHELL_TOOL", "FILLET_TOOL", "SCALE_BODY","-", "MIRROR_BODY", "PATTERN_LINEAR", "PATTERN_RADIAL", "-", diff --git a/web/app/cad/craft/craftPlugin.ts b/web/app/cad/craft/craftPlugin.ts index 34420da5..851aad2a 100644 --- a/web/app/cad/craft/craftPlugin.ts +++ b/web/app/cad/craft/craftPlugin.ts @@ -80,7 +80,7 @@ export function activate(ctx: CoreContext) { })); } - const result = op.run(params, ctx); + const result = op.run(params, ctx, request.params); // @ts-ignore return result.then ? result : Promise.resolve(result); } catch (e) { diff --git a/web/app/cad/craft/operationHelper.js b/web/app/cad/craft/operationHelper.js deleted file mode 100644 index af9664ec..00000000 --- a/web/app/cad/craft/operationHelper.js +++ /dev/null @@ -1,4 +0,0 @@ - -export function roundValueForPresentation(value) { - return value.toPrecision ? value.toPrecision(4).replace(/\.0$/, '') : value; -} diff --git a/web/app/cad/craft/operationHelper.ts b/web/app/cad/craft/operationHelper.ts new file mode 100644 index 00000000..145918ef --- /dev/null +++ b/web/app/cad/craft/operationHelper.ts @@ -0,0 +1,40 @@ +import {Operation} from "cad/craft/operationPlugin"; +import {resolveIcon} from "cad/craft/ui/iconResolver"; + +export function roundValueForPresentation(value) { + return value.toPrecision ? value.toPrecision(4).replace(/\.0$/, '') : value; +} + +export function operationIconToActionIcon(icon, appearance) { + if (typeof icon === 'string') { + appearance.icon32 = icon + '32.png'; + appearance.icon96 = icon + '96.png'; + } else { + appearance.icon = resolveIcon(icon); + } +} + +export function resolveAppearance(op: Operation, params: R) { + let appearance = op.appearance; + if (!op.dynamicLabel && !op.dynamicIcon) { + return appearance; + } + appearance = {...appearance}; + + if (op.dynamicLabel) { + const label = op.dynamicLabel(params); + if (label) { + appearance.label = label; + } + } + + if (op.dynamicIcon) { + const icon = op.dynamicIcon(params); + if (icon) { + operationIconToActionIcon(icon, appearance); + } + } + + return appearance; +} + diff --git a/web/app/cad/craft/operationPlugin.ts b/web/app/cad/craft/operationPlugin.ts index cc3e666c..d377b6d7 100644 --- a/web/app/cad/craft/operationPlugin.ts +++ b/web/app/cad/craft/operationPlugin.ts @@ -12,6 +12,7 @@ import {FlattenPath, ParamsPath} from "cad/craft/wizard/wizardTypes"; import {IconDeclaration} from "cad/icons/IconDeclaration"; import {resolveIcon} from "cad/craft/ui/iconResolver"; import {loadDeclarativeForm} from "cad/mdf/declarativeFormLoader"; +import {operationIconToActionIcon} from "cad/craft/operationHelper"; export function activate(ctx: ApplicationContext) { @@ -30,23 +31,23 @@ export function activate(ctx: ApplicationContext) { form = loadedForm; } + if (!label) { + label = id; + } + let appearance: ActionAppearance = { label, info }; - if (typeof icon === 'string') { - appearance.icon32 = icon + '32.png'; - appearance.icon96 = icon + '96.png'; - } else { - appearance.icon = resolveIcon(icon); - } + + operationIconToActionIcon(icon, appearance); + let opAction = { id: id, appearance, invoke: () => ctx.services.wizard.open(id), ...actionParams }; - actions.push(opAction); let schemaIndex = createSchemaIndex(schema); @@ -58,6 +59,29 @@ export function activate(ctx: ApplicationContext) { }; registry$.mutate(registry => registry[id] = operation); + actions.push(opAction); + + if (descriptor.masking) { + descriptor.masking.forEach(masking => { + + + const appearance = { + ...opAction.appearance, + label: masking.label, + info: masking.info, + }; + + operationIconToActionIcon(masking.icon, appearance); + + actions.push({ + ...opAction, + id: masking.id, + appearance, + invoke: () => ctx.services.wizard.open(id, masking.maskingParams), + }); + + }); + } } function registerOperations(operations) { @@ -98,19 +122,30 @@ export interface Operation extends OperationDescriptor{ schema: OperationSchema; } +type OpIcon = IconDeclaration | IconType | string | ((props: any) => JSX.Element); + export interface OperationDescriptor { id: string; label: string; info: string; - icon: IconDeclaration | IconType | string | ((props: any) => JSX.Element); + icon: OpIcon; actionParams?: any; - run: (request: R, opContext: CoreContext) => OperationResult | Promise; + run: (params: {}, ctx: CoreContext, rawParams: R) => OperationResult | Promise; paramsInfo: (params: R) => string, previewGeomProvider?: (params: R) => OperationGeometryProvider, previewer?: any, form: FormDefinition | React.FunctionComponent, schema?: OperationSchema, onParamsUpdate?: (params, name, value) => void, + masking?: { + id: string, + label: string; + info: string, + icon: OpIcon; + maskingParams: any; + }[], + dynamicLabel?: (params: R) => string, + dynamicIcon?: (params: R) => OpIcon, } export interface OperationService { diff --git a/web/app/cad/craft/production/productionAnalyzer.ts b/web/app/cad/craft/production/productionAnalyzer.ts index 915b1311..68d00b12 100644 --- a/web/app/cad/craft/production/productionAnalyzer.ts +++ b/web/app/cad/craft/production/productionAnalyzer.ts @@ -460,7 +460,6 @@ export class PushPullFaceProductionAnalyzer extends FromMObjectProductionAnalyze const edgeMap = new Map(); for (let he of this.baseFace.edges) { - debugger const twin = he.twin(); if (twin) { edgeMap.set(twin.loop.face.data.id, twin.edge); diff --git a/web/app/cad/craft/ui/HistoryTimeline.jsx b/web/app/cad/craft/ui/HistoryTimeline.jsx index 401d528b..1788cb02 100644 --- a/web/app/cad/craft/ui/HistoryTimeline.jsx +++ b/web/app/cad/craft/ui/HistoryTimeline.jsx @@ -11,6 +11,7 @@ import {menuAboveElementHint} from '../../dom/menu/menuUtils'; import {combine} from 'lstream'; import {EMPTY_OBJECT} from 'gems/objects'; import {aboveElement} from 'ui/positionUtils'; +import {resolveAppearance} from "cad/craft/operationHelper"; @connect(streams => combine(streams.craft.modifications, streams.operation.registry, streams.wizard.insertOperation) .map(([modifications, operationRegistry, insertOperationReq]) => ({ @@ -133,8 +134,9 @@ const HistoryItem = decoratorChain( ) ( function HistoryItem({index, pointer, modification, getOperation, toggle, selected, disabled, inProgress}) { - let {appearance} = getOperation(modification.type); - return
toggle(index, modification, e.currentTarget)}> { index + 1 } @@ -142,7 +144,7 @@ function HistoryItem({index, pointer, modification, getOperation, toggle, select }); const AddButton = mapContext((ctx) => ({ - showCraftMenu: e => ctx.actionService.action.run('menu.craft', menuAboveElementHint(e.currentTarget)) + showCraftMenu: e => ctx.actionService.action.run(params, ctx, 'menu.craft') }))( function AddButton({showCraftMenu}) { return
diff --git a/web/app/cad/craft/ui/SelectedModificationInfo.jsx b/web/app/cad/craft/ui/SelectedModificationInfo.jsx index e4e9636c..b38545ce 100644 --- a/web/app/cad/craft/ui/SelectedModificationInfo.jsx +++ b/web/app/cad/craft/ui/SelectedModificationInfo.jsx @@ -12,6 +12,7 @@ import Button from 'ui/components/controls/Button'; import {removeAndDropDependants, removeFeature} from '../craftHistoryUtils'; import RenderObject from 'ui/components/RenderObject'; import {AppContext} from "cad/dom/components/AppContext"; +import {resolveAppearance} from "cad/craft/operationHelper"; function SelectedModificationInfo({ history, index, operationRegistry, @@ -41,13 +42,14 @@ function SelectedModificationInfo({ history, index, console.warn('unknown operation ' + m.type); return; } - let {appearance} = op; + const appearance = resolveAppearance(op, m.params); + let indexNumber = index + 1; return
diff --git a/web/app/cad/craft/wizard/components/Wizard.tsx b/web/app/cad/craft/wizard/components/Wizard.tsx index 8e0bb0c2..563037cb 100644 --- a/web/app/cad/craft/wizard/components/Wizard.tsx +++ b/web/app/cad/craft/wizard/components/Wizard.tsx @@ -6,6 +6,8 @@ import {FormEditContext, FormParamsContext, FormPathContext, FormStateContext} f import {GenericWizard} from "ui/components/GenericWizard"; import {useStream} from "ui/effects"; import {AppContext} from "cad/dom/components/AppContext"; +import {resolveAppearance} from "cad/craft/operationHelper"; +import ImgIcon from "ui/components/ImgIcon"; interface WizardProps { noFocus: boolean; @@ -73,15 +75,16 @@ export default function Wizard(props: WizardProps) { }; let {left} = props; - let wizardService = ctx.wizardService; - - let title = (operation.label || operation.id).toUpperCase(); + let appearance = resolveAppearance(operation, workingRequest.params); + let title = appearance.label.toUpperCase(); + let icon = ; let Form = operation.form; return pickControl.pick(datum), - e => action.run('menu.datum', e), + e => action.run(params, ctx, 'menu.datum'), wizard.isInProgress); } else { console.warn('unsupported model ' + model);