diff --git a/modules/lstream/utils.js b/modules/lstream/utils.js
index 5ffcacf9..92c86f6a 100644
--- a/modules/lstream/utils.js
+++ b/modules/lstream/utils.js
@@ -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);
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/modules/ui/components/Folder.jsx b/modules/ui/components/Folder.jsx
index 8dba4e03..1b825779 100644
--- a/modules/ui/components/Folder.jsx
+++ b/modules/ui/components/Folder.jsx
@@ -26,11 +26,15 @@ export default class Folder extends React.Component{
render() {
let {title, closable, className, children} = this.props;
return
-
-
- {' '}{title}
-
+
{!this.isClosed() && children}
}
}
+
+export function Title({title, isClosed, onClick}) {
+ return
+
+ {' '}{title}
+
;
+}
diff --git a/modules/ui/components/controls/CheckboxControl.jsx b/modules/ui/components/controls/CheckboxControl.jsx
index f510abdc..78f4efe5 100644
--- a/modules/ui/components/controls/CheckboxControl.jsx
+++ b/modules/ui/components/controls/CheckboxControl.jsx
@@ -6,6 +6,6 @@ export default class CheckboxControl extends React.Component {
let {onChange, value} = this.props;
return onChange(e.target.value)} />
+ onChange={e => onChange(e.target.checked)} />
}
}
diff --git a/modules/ui/components/controls/FormSection.jsx b/modules/ui/components/controls/FormSection.jsx
new file mode 100644
index 00000000..f4c2e2da
--- /dev/null
+++ b/modules/ui/components/controls/FormSection.jsx
@@ -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
+
+ {children}
+ ;
+ }
+
+}
\ No newline at end of file
diff --git a/web/app/brep/geom/curves/brepCurve.js b/web/app/brep/geom/curves/brepCurve.js
index bdadbe5b..8b5284a8 100644
--- a/web/app/brep/geom/curves/brepCurve.js
+++ b/web/app/brep/geom/curves/brepCurve.js
@@ -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);
}
diff --git a/web/app/cad/actions/coreActions.js b/web/app/cad/actions/coreActions.js
index aad54724..dedfac07 100644
--- a/web/app/cad/actions/coreActions.js
+++ b/web/app/cad/actions/coreActions.js
@@ -65,7 +65,7 @@ export default [
label: 'deselect all',
info: 'deselect everything',
},
- invoke: (context) => context.services.selection.deselectAll()
+ invoke: (context) => context.services.pickControl.deselectAll()
},
{
diff --git a/web/app/cad/craft/cutExtrude/cutExtrude.js b/web/app/cad/craft/cutExtrude/cutExtrude.js
index 42aee3b3..250ed2c7 100644
--- a/web/app/cad/craft/cutExtrude/cutExtrude.js
+++ b/web/app/cad/craft/cutExtrude/cutExtrude.js
@@ -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);
}
diff --git a/web/app/cad/craft/cutExtrude/cutOperation.js b/web/app/cad/craft/cutExtrude/cutOperation.js
index 8dcaa6db..d117cffd 100644
--- a/web/app/cad/craft/cutExtrude/cutOperation.js
+++ b/web/app/cad/craft/cutExtrude/cutOperation.js
@@ -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: {
diff --git a/web/app/cad/craft/cutExtrude/extrudeOperation.js b/web/app/cad/craft/cutExtrude/extrudeOperation.js
index c6efaeb0..dda12b91 100644
--- a/web/app/cad/craft/cutExtrude/extrudeOperation.js
+++ b/web/app/cad/craft/cutExtrude/extrudeOperation.js
@@ -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];
+ }
+ })
+ }
+}
\ No newline at end of file
diff --git a/web/app/cad/craft/cutExtrude/form.js b/web/app/cad/craft/cutExtrude/form.js
index 89931d3f..f264cfcf 100644
--- a/web/app/cad/craft/cutExtrude/form.js
+++ b/web/app/cad/craft/cutExtrude/form.js
@@ -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) {
-
+
+
+
+
+
+
;
};
}
\ No newline at end of file
diff --git a/web/app/cad/craft/cutExtrude/schema.js b/web/app/cad/craft/cutExtrude/schema.js
index 4cb5674d..5d680392 100644
--- a/web/app/cad/craft/cutExtrude/schema.js
+++ b/web/app/cad/craft/cutExtrude/schema.js
@@ -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,
}
}
diff --git a/web/app/cad/craft/e0/e0Plugin.js b/web/app/cad/craft/e0/e0Plugin.js
index 6c83809a..731d401a 100644
--- a/web/app/cad/craft/e0/e0Plugin.js
+++ b/web/app/cad/craft/e0/e0Plugin.js
@@ -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});
diff --git a/web/app/cad/craft/materializeParams.js b/web/app/cad/craft/materializeParams.js
index fb2397d1..7a78960f 100644
--- a/web/app/cad/craft/materializeParams.js
+++ b/web/app/cad/craft/materializeParams.js
@@ -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];
diff --git a/web/app/cad/craft/operationPlugin.js b/web/app/cad/craft/operationPlugin.js
index b2754c84..537b123e 100644
--- a/web/app/cad/craft/operationPlugin.js
+++ b/web/app/cad/craft/operationPlugin.js
@@ -88,6 +88,7 @@ function createSchemaIndex(schema) {
}
}
return {entitiesByType, entitiesByParam,
- entityParams: Object.keys(entitiesByParam)
+ entityParams: Object.keys(entitiesByParam),
+ params: Object.keys(schema)
};
}
\ 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 80fbc880..3c408af7 100644
--- a/web/app/cad/craft/wizard/components/Wizard.jsx
+++ b/web/app/cad/craft/wizard/components/Wizard.jsx
@@ -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 => {
diff --git a/web/app/cad/craft/wizard/wizardPlugin.js b/web/app/cad/craft/wizard/wizardPlugin.js
index 85e051db..3ccac786 100644
--- a/web/app/cad/craft/wizard/wizardPlugin.js
+++ b/web/app/cad/craft/wizard/wizardPlugin.js
@@ -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,
diff --git a/web/app/cad/craft/wizard/wizardSelectionPlugin.js b/web/app/cad/craft/wizard/wizardSelectionPlugin.js
index 87dafd7c..9be09d6d 100644
--- a/web/app/cad/craft/wizard/wizardSelectionPlugin.js
+++ b/web/app/cad/craft/wizard/wizardSelectionPlugin.js
@@ -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;
}
}
diff --git a/web/app/cad/sketch/sketchModel.js b/web/app/cad/sketch/sketchModel.js
index fd93ae42..0e75514c 100644
--- a/web/app/cad/sketch/sketchModel.js
+++ b/web/app/cad/sketch/sketchModel.js
@@ -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) {
diff --git a/web/app/cad/sketch/sketchReader.js b/web/app/cad/sketch/sketchReader.js
index ee9f422b..43e6f569 100644
--- a/web/app/cad/sketch/sketchReader.js
+++ b/web/app/cad/sketch/sketchReader.js
@@ -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;