implement cut-extrude masking

This commit is contained in:
Val Erastov 2022-06-11 02:03:44 -07:00
parent 7df876df50
commit bf19c9577e
14 changed files with 152 additions and 38 deletions

View file

@ -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 <Window initWidth={250}
initLeft={left || 15}
initTop={top}
title={(title||'').toUpperCase()}
icon={icon}
className={cx('mid-typography', className)}
controlButtons={<>
<WindowControlButton title='help' onClick={(e) => DocumentationTopic$.next({

View file

@ -31,6 +31,8 @@
.title {
padding: 0.3em 0 0.3em 0.5em;
font-size: 1.1em;
display: flex;
align-items: center;
}
.controlButtons {

View file

@ -51,7 +51,7 @@ export default class Window extends React.Component<WindowProps> {
return <div className={cx(ls.root, this.resizeConfig&&ls.mandatoryBorder, compact&&ls.compact, className)} {...props} ref={this.keepRef}>
<div className={ls.bar + ' disable-selection'} onMouseDown={this.startDrag} onMouseUp={this.stopDrag}>
<div className={ls.title}>{icon}<b>{title.toUpperCase()}</b></div>
<div className={ls.title}>{icon} <b>{title.toUpperCase()}</b></div>
<div className={ls.controlButtons}>
{controlButtons}
{minimizable && <WindowControlButton onClick={onClose}>_</WindowControlButton>}

View file

@ -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<ExtrudeParams> = {
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<ExtrudeParams> = {
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<ExtrudeParams> = {
}
],
masking: [
{
id: 'CUT2',
label: 'Cut',
icon: 'img/cad/cut',
info: 'makes a cut based on 2D sketch',
maskingParams: {
direction: {
flip: true
},
boolean: {
kind: 'SUBTRACT'
}
}
}
]
}

View file

@ -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", "-",

View file

@ -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) {

View file

@ -1,4 +0,0 @@
export function roundValueForPresentation(value) {
return value.toPrecision ? value.toPrecision(4).replace(/\.0$/, '') : value;
}

View file

@ -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<R>(op: Operation<R>, 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;
}

View file

@ -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<R> extends OperationDescriptor<R>{
schema: OperationSchema;
}
type OpIcon = IconDeclaration | IconType | string | ((props: any) => JSX.Element);
export interface OperationDescriptor<R> {
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<OperationResult>;
run: (params: {}, ctx: CoreContext, rawParams: R) => OperationResult | Promise<OperationResult>;
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 {

View file

@ -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);

View file

@ -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 <div className={cx(ls.historyItem, selected&&ls.selected, disabled&&ls.disabled, inProgress&&ls.inProgress)}
const operation = getOperation(modification.type);
const appearance = resolveAppearance(operation, modification.params);
return <div className={cx(ls.historyItem, selected&&ls.selected, disabled&&ls.disabled, inProgress&&ls.inProgress)}
onClick={e => toggle(index, modification, e.currentTarget)}>
<ImgIcon className={ls.opIcon} url={appearance&&appearance.icon96} size={24} />
<span className={ls.opIndex}>{ index + 1 }</span>
@ -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 <div className={ls.add} onClick={showCraftMenu}>

View file

@ -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 <Widget visible={visible}
left={lh && lh.x}
bottom={95}
flatRight={!lh}
title={m.type + ' operation #' + indexNumber}
title={appearance.label.toUpperCase() + ' operation #' + indexNumber}
onClose={close}>
<div className={ls.requestInfo}>
<ImgIcon className={ls.pic} url={appearance && appearance.icon96} size={48}/>

View file

@ -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 = <ImgIcon url={appearance.icon32} size={16}/>;
let Form = operation.form;
return <GenericWizard
left={left}
title={title}
icon={icon}
onClose={cancel}
onKeyDown={onKeyDown}
setFocus={focusFirstInput}

View file

@ -65,7 +65,7 @@ function sceneSynchronizer(ctx) {
} else if (model instanceof MDatum) {
modelView = new DatumView(ctx, model, wizard.open,
datum => pickControl.pick(datum),
e => action.run('menu.datum', e),
e => action.run(params, ctx, 'menu.datum'),
wizard.isInProgress);
} else {
console.warn('unsupported model ' + model);