mirror of
https://github.com/xibyte/jsketcher
synced 2025-12-16 05:23:19 +01:00
improve cut/extrude wizard
This commit is contained in:
parent
77569411a4
commit
7037d3224c
19 changed files with 153 additions and 37 deletions
|
|
@ -1,2 +1,18 @@
|
|||
|
||||
export const NOT_INITIALIZED = Object.freeze({});
|
||||
|
||||
export function propsChangeTracker(props, onChange) {
|
||||
|
||||
const values = props.map(() => NOT_INITIALIZED);
|
||||
|
||||
return function(obj) {
|
||||
for (let i = 0; i < props.length; i++) {
|
||||
const prop = props[i];
|
||||
const prevValue = values[i];
|
||||
const currValue = obj[prop];
|
||||
if (prevValue !== currValue) {
|
||||
onChange(obj, prop, currValue, prevValue);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -26,11 +26,15 @@ export default class Folder extends React.Component{
|
|||
render() {
|
||||
let {title, closable, className, children} = this.props;
|
||||
return <div className={cx(ls.root, className)}>
|
||||
<div className={ls.title} onClick={closable ? this.tweakClose : null}>
|
||||
<span className={ls.handle}><Fa fw icon={this.isClosed() ? 'chevron-right' : 'chevron-down'}/></span>
|
||||
{' '}{title}
|
||||
</div>
|
||||
<Title title={title} onClick={closable ? this.tweakClose : null} isClosed={this.isClosed()}/>
|
||||
{!this.isClosed() && children}
|
||||
</div>
|
||||
}
|
||||
}
|
||||
|
||||
export function Title({title, isClosed, onClick}) {
|
||||
return <div className={ls.title} onClick={onClick}>
|
||||
<span className={ls.handle}><Fa fw icon={isClosed ? 'chevron-right' : 'chevron-down'}/></span>
|
||||
{' '}{title}
|
||||
</div>;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -6,6 +6,6 @@ export default class CheckboxControl extends React.Component {
|
|||
let {onChange, value} = this.props;
|
||||
return <input type='checkbox'
|
||||
defaultValue={value}
|
||||
onChange={e => onChange(e.target.value)} />
|
||||
onChange={e => onChange(e.target.checked)} />
|
||||
}
|
||||
}
|
||||
|
|
|
|||
14
modules/ui/components/controls/FormSection.jsx
Normal file
14
modules/ui/components/controls/FormSection.jsx
Normal file
|
|
@ -0,0 +1,14 @@
|
|||
import React from 'react';
|
||||
import {Title} from '../Folder';
|
||||
|
||||
export class StackSection extends React.Component {
|
||||
|
||||
render() {
|
||||
const {title, children} = this.props;
|
||||
return <React.Fragment>
|
||||
<Title title={title} />
|
||||
{children}
|
||||
</React.Fragment>;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -18,8 +18,16 @@ export default class BrepCurve {
|
|||
this.uMid = (uMax - uMin) * 0.5;
|
||||
}
|
||||
|
||||
get degree() {
|
||||
return this.impl.degree();
|
||||
}
|
||||
|
||||
translate(vector) {
|
||||
const tr = new Matrix3().translate(vector.x, vector.y, vector.z);
|
||||
return this.transform(tr);
|
||||
}
|
||||
|
||||
transform(tr) {
|
||||
return new BrepCurve(this.impl.transform(tr.toArray()), this.uMin, this.uMax);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -65,7 +65,7 @@ export default [
|
|||
label: 'deselect all',
|
||||
info: 'deselect everything',
|
||||
},
|
||||
invoke: (context) => context.services.selection.deselectAll()
|
||||
invoke: (context) => context.services.pickControl.deselectAll()
|
||||
},
|
||||
|
||||
{
|
||||
|
|
|
|||
|
|
@ -27,18 +27,42 @@ export function doOperation(params, {cadRegistry, sketcher}, cut) {
|
|||
|
||||
export function resolveExtrudeVector(cadRegistry, face, params, invert) {
|
||||
let vector = null;
|
||||
if (params.vector) {
|
||||
const datumAxis = cadRegistry.findDatumAxis(params.vector);
|
||||
if (params.datumAxisVector) {
|
||||
const datumAxis = cadRegistry.findDatumAxis(params.datumAxisVector);
|
||||
if (datumAxis) {
|
||||
vector = datumAxis.dir;
|
||||
invert = false;
|
||||
}
|
||||
} else if (params.edgeVector) {
|
||||
const edge = cadRegistry.findEdge(params.edgeVector);
|
||||
const curve = edge.brepEdge.curve;
|
||||
if (curve.degree === 1) {
|
||||
vector = edge.brepEdge.curve.tangentAtParam(edge.brepEdge.curve.uMin);
|
||||
if (vector.dot(face.csys.z) < 0 === invert) {
|
||||
vector = vector.negate();
|
||||
}
|
||||
invert = false;
|
||||
}
|
||||
} else if (params.sketchSegmentVector) {
|
||||
const mSegment = cadRegistry.findSketchObject(params.sketchSegmentVector);
|
||||
if (mSegment.sketchPrimitive.isSegment) {
|
||||
let [a, b] = mSegment.sketchPrimitive.tessellate().map(mSegment.face.sketchToWorldTransformation.apply);
|
||||
vector = b.minus(a)._normalize();
|
||||
if (vector.dot(face.csys.z) < 0 === invert) {
|
||||
vector._negate();
|
||||
}
|
||||
invert = false;
|
||||
}
|
||||
}
|
||||
if (!vector) {
|
||||
invert = !invert;
|
||||
vector = face.csys.z;
|
||||
}
|
||||
|
||||
if (params.flip) {
|
||||
invert = !invert;
|
||||
}
|
||||
|
||||
let value = params.value;
|
||||
if (value < 0) {
|
||||
value = Math.abs(value);
|
||||
|
|
@ -60,12 +84,17 @@ export function getEncloseDetails(params, contours, target, csys, sketchSurface,
|
|||
if (invert) contour.reverse();
|
||||
|
||||
const lidPath = [];
|
||||
let applyPrism = !math.equal(params.prism, 1);
|
||||
let applyPrism = !math.equal(params.prism, 1);
|
||||
let prismTr = null;
|
||||
if (applyPrism) {
|
||||
prismTr = new Matrix3();
|
||||
prismTr.scale(params.prism, params.prism, params.prism);
|
||||
}
|
||||
for (let i = 0; i < basePath.length; ++i) {
|
||||
const curve = basePath[i];
|
||||
let lidCurve = curve.translate(target);
|
||||
if (applyPrism) {
|
||||
lidCurve = lidCurve.offset(params.prism);
|
||||
lidCurve = lidCurve.transform(prismTr);
|
||||
}
|
||||
lidPath.push(lidCurve);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@ import {createPreviewGeomProvider} from './previewer';
|
|||
import {Cut} from './cutExtrude';
|
||||
import {requiresFaceSelection} from '../../actions/actionHelpers';
|
||||
import schema from './schema';
|
||||
import {onParamsUpdate} from './extrudeOperation';
|
||||
|
||||
export default {
|
||||
id: 'CUT',
|
||||
|
|
@ -11,6 +12,7 @@ export default {
|
|||
icon: 'img/cad/cut',
|
||||
info: 'makes a cut based on 2D sketch',
|
||||
paramsInfo: ({value}) => `(${r(value)})`,
|
||||
onParamsUpdate,
|
||||
previewGeomProvider: createPreviewGeomProvider(true),
|
||||
run: Cut,
|
||||
actionParams: {
|
||||
|
|
|
|||
|
|
@ -11,6 +11,7 @@ export default {
|
|||
icon: 'img/cad/extrude',
|
||||
info: 'extrudes 2D sketch',
|
||||
paramsInfo: ({value}) => `(${r(value)})`,
|
||||
onParamsUpdate,
|
||||
previewGeomProvider: createPreviewGeomProvider(false),
|
||||
run: Extrude,
|
||||
actionParams: {
|
||||
|
|
@ -20,3 +21,14 @@ export default {
|
|||
schema
|
||||
};
|
||||
|
||||
const INVARIANT = ['datumAxisVector', 'edgeVector', 'sketchSegmentVector'];
|
||||
|
||||
export function onParamsUpdate(params, name, value) {
|
||||
if (INVARIANT.includes(name)) {
|
||||
INVARIANT.forEach(param => {
|
||||
if (param !== name) {
|
||||
delete params[param];
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
@ -1,7 +1,8 @@
|
|||
import React from 'react';
|
||||
import {NumberField} from '../wizard/components/form/Fields';
|
||||
import {CheckboxField, NumberField} from '../wizard/components/form/Fields';
|
||||
import {Group} from '../wizard/components/form/Form';
|
||||
import Entity from '../wizard/components/form/Entity';
|
||||
import {StackSection} from 'ui/components/controls/FormSection';
|
||||
|
||||
export default function (valueLabel) {
|
||||
return function PrismForm() {
|
||||
|
|
@ -9,7 +10,12 @@ export default function (valueLabel) {
|
|||
<NumberField name='value' defaultValue={50} label={valueLabel}/>
|
||||
<NumberField name='prism' defaultValue={1} min={0} step={0.1} round={1}/>
|
||||
<Entity name='face'/>
|
||||
<Entity name='vector' />
|
||||
<StackSection title='vector'>
|
||||
<Entity label='datum axis' name='datumAxisVector' />
|
||||
<Entity label='edge' name='edgeVector' />
|
||||
<Entity label='sketch segment' name='sketchSegmentVector' />
|
||||
<CheckboxField name='flip'/>
|
||||
</StackSection>
|
||||
</Group>;
|
||||
};
|
||||
}
|
||||
|
|
@ -20,9 +20,22 @@ export default {
|
|||
type: 'face',
|
||||
defaultValue: {type: 'selection'}
|
||||
},
|
||||
vector: {
|
||||
datumAxisVector: {
|
||||
type: 'datumAxis',
|
||||
optional: true
|
||||
},
|
||||
edgeVector: {
|
||||
type: 'edge',
|
||||
optional: true,
|
||||
defaultValue: {type: 'selection'}
|
||||
accept: edge => edge.brepEdge.curve.degree === 1
|
||||
},
|
||||
sketchSegmentVector: {
|
||||
type: 'sketchObject',
|
||||
optional: true,
|
||||
accept: obj => obj.isSegment
|
||||
},
|
||||
flip: {
|
||||
type: 'boolean',
|
||||
defaultValue: false,
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -188,7 +188,7 @@ function readSketch(face, request, sketcher) {
|
|||
let paths = sketch.fetchContours().map(c => {
|
||||
let path = [];
|
||||
c.segments.forEach(s => {
|
||||
if (s.isCurve()) {
|
||||
if (s.isCurve) {
|
||||
if (s.constructor.name === 'Circle') {
|
||||
const dir = face.csys.z.data();
|
||||
path.push({TYPE: CURVE_TYPES.CIRCLE, c: tr.apply(s.c).data(), dir, r: s.r});
|
||||
|
|
|
|||
|
|
@ -11,7 +11,7 @@ export default function materializeParams(services, params, schema, result, erro
|
|||
}
|
||||
let value = params[field];
|
||||
if (value === undefined || value === null || value === '') {
|
||||
if (!md.optional) {
|
||||
if (!md.optional && !md.hasOwnProperty('defaultValue')) {
|
||||
errors.push({path: [...parentPath, field], message: 'required'});
|
||||
}
|
||||
} else {
|
||||
|
|
@ -41,6 +41,8 @@ export default function materializeParams(services, params, schema, result, erro
|
|||
if (typeof value !== 'string') {
|
||||
errors.push({path: [...parentPath, field], message: 'not a string type'});
|
||||
}
|
||||
} else if (md.type === 'boolean') {
|
||||
value = !!value;
|
||||
} else if (md.type === 'enum') {
|
||||
if (md.values.indexOf(value) === -1) {
|
||||
value = md.defaultValue || md.values[0];
|
||||
|
|
|
|||
|
|
@ -88,6 +88,7 @@ function createSchemaIndex(schema) {
|
|||
}
|
||||
}
|
||||
return {entitiesByType, entitiesByParam,
|
||||
entityParams: Object.keys(entitiesByParam)
|
||||
entityParams: Object.keys(entitiesByParam),
|
||||
params: Object.keys(schema)
|
||||
};
|
||||
}
|
||||
|
|
@ -22,7 +22,7 @@ export default class Wizard extends React.Component {
|
|||
};
|
||||
|
||||
updateParam = (name, value) => {
|
||||
this.props.context.updateParams(params => params[name] = value);
|
||||
this.props.context.updateParam(name, value);
|
||||
};
|
||||
|
||||
setActiveParam = param => {
|
||||
|
|
|
|||
|
|
@ -3,6 +3,8 @@ import initializeBySchema from '../intializeBySchema';
|
|||
import {clone, EMPTY_OBJECT} from 'gems/objects';
|
||||
import materializeParams from '../materializeParams';
|
||||
import {createFunctionList} from 'gems/func';
|
||||
import {onParamsUpdate} from '../cutExtrude/extrudeOperation';
|
||||
import {propsChangeTracker} from '../../../../../modules/lstream/utils';
|
||||
|
||||
export function activate(ctx) {
|
||||
|
||||
|
|
@ -84,9 +86,18 @@ export function activate(ctx) {
|
|||
const state$ = state({});
|
||||
const updateParams = mutator => workingRequest$.mutate(data => mutator(data.params));
|
||||
const updateState = mutator => state$.mutate(state => mutator(state));
|
||||
const updateParam = (name, value) => {
|
||||
updateParams(params => {
|
||||
if (operation.onParamsUpdate) {
|
||||
operation.onParamsUpdate(params, name, value, params[name]);
|
||||
}
|
||||
params[name] = value;
|
||||
});
|
||||
};
|
||||
|
||||
const disposerList = createFunctionList();
|
||||
wizCtx = {
|
||||
workingRequest$, materializedWorkingRequest$, state$, updateParams, updateState,
|
||||
workingRequest$, materializedWorkingRequest$, state$, updateParams, updateParam, updateState,
|
||||
operation, changingHistory,
|
||||
addDisposer: disposerList.add,
|
||||
dispose: disposerList.call,
|
||||
|
|
|
|||
|
|
@ -31,20 +31,20 @@ export function activate(ctx) {
|
|||
});
|
||||
}
|
||||
|
||||
const singleUpdater = (params, param, id) => params[param] = id;
|
||||
const arrayUpdater = (params, param, id) => {
|
||||
let arr = params[param];
|
||||
const singleValue = (id, current) => id;
|
||||
const arrayValue = (id, arr) => {
|
||||
if (!arr) {
|
||||
params[param] = [id];
|
||||
return [id];
|
||||
}
|
||||
if (arr.indexOf(id) === -1) {
|
||||
arr.push(id);
|
||||
}
|
||||
return arr;
|
||||
};
|
||||
|
||||
function createPickHandlerFromSchema(wizCtx) {
|
||||
function update(paramsMutator, paramToMakeActive) {
|
||||
wizCtx.updateParams(paramsMutator);
|
||||
function update(param, value, paramToMakeActive) {
|
||||
wizCtx.updateParam(param, value);
|
||||
wizCtx.updateState(state => {
|
||||
state.activeParam = paramToMakeActive;
|
||||
});
|
||||
|
|
@ -62,11 +62,9 @@ function createPickHandlerFromSchema(wizCtx) {
|
|||
const activeEntity = state.activeParam && entitiesByParam[state.activeParam];
|
||||
|
||||
function select(param, entity, md, id) {
|
||||
const updater = md.type === 'array' ? arrayUpdater : singleUpdater;
|
||||
const valueGetter = md.type === 'array' ? arrayValue : singleValue;
|
||||
let paramToMakeActive = getNextActiveParam(param, entity, md);
|
||||
update(params => {
|
||||
updater(params, param, id);
|
||||
}, paramToMakeActive);
|
||||
update(param, valueGetter(id, params[param]), paramToMakeActive);
|
||||
}
|
||||
|
||||
function getNextActiveParam(currParam, entity, currMd) {
|
||||
|
|
@ -98,16 +96,12 @@ function createPickHandlerFromSchema(wizCtx) {
|
|||
for (let param of entityParams) {
|
||||
let val = params[param];
|
||||
if (val === id) {
|
||||
update(params => {
|
||||
params[param] = undefined;
|
||||
}, param);
|
||||
update(param, undefined, param);
|
||||
return true;
|
||||
} else if (Array.isArray(val)) {
|
||||
let index = val.indexOf(id);
|
||||
if (index !== -1) {
|
||||
update(params => {
|
||||
params[param].splice(index, 1)
|
||||
}, param);
|
||||
update(param, params[param].splice(index, 1), param);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -28,10 +28,14 @@ class SketchPrimitive {
|
|||
return tessellation;
|
||||
}
|
||||
|
||||
isCurve() {
|
||||
get isCurve() {
|
||||
return this.constructor.name !== 'Segment';
|
||||
}
|
||||
|
||||
get isSegment() {
|
||||
return !this.isCurve;
|
||||
}
|
||||
|
||||
toNurbs(csys) {
|
||||
let verbNurbs = this.toVerbNurbs(csys.outTransformation.apply, csys);
|
||||
if (this.inverted) {
|
||||
|
|
|
|||
|
|
@ -144,7 +144,7 @@ function findClosedContoursFromPairedCurves(segments, result) {
|
|||
for (let j = i; j < segments.length; j++) {
|
||||
if (i == j) continue;
|
||||
const s2 = segments[j];
|
||||
if (s1.isCurve() && s2.isCurve()) {
|
||||
if (s1.isCurve && s2.isCurve) {
|
||||
let paired = false;
|
||||
if (math.strictEqual2D(s1.a, s2.a) && math.strictEqual2D(s1.b, s2.b)) {
|
||||
paired = true;
|
||||
|
|
|
|||
Loading…
Reference in a new issue