constraint action descriptions

This commit is contained in:
Val Erastov (xibyte) 2020-03-05 00:33:00 -08:00
parent d022e12e96
commit e20b7e892f
11 changed files with 488 additions and 185 deletions

View file

@ -0,0 +1,11 @@
import React from 'react';
export function Toaster({}) {
return <div>
</div>;
}

107
package-lock.json generated
View file

@ -1181,6 +1181,21 @@
"integrity": "sha512-A8ia2Wus0OAP6hh28ZgPSCBJEX3Jnql3kg9di/I+Lmg1gbJXgDZBrHr/UGZXl20Vi1lXgMuUq8c8J899KFr5gA==",
"dev": true
},
"@babel/runtime": {
"version": "7.8.7",
"resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.8.7.tgz",
"integrity": "sha512-+AATMUFppJDw6aiR5NVPHqIQBlV/Pj8wY/EZH+lmvRdUo9xBaz/rF3alAwFJQavvKfeOlPE7oaaDHVbcySbCsg==",
"requires": {
"regenerator-runtime": "^0.13.4"
},
"dependencies": {
"regenerator-runtime": {
"version": "0.13.4",
"resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.4.tgz",
"integrity": "sha512-plpwicqEzfEyTQohIKktWigcLzmNStMGwbOUbykx51/29Z3JOGYldaaNGK7ngNXV+UcoqvIMmloZ48Sr74sd+g=="
}
}
},
"@babel/template": {
"version": "7.4.4",
"resolved": "https://registry.npmjs.org/@babel/template/-/template-7.4.4.tgz",
@ -1287,14 +1302,6 @@
}
}
},
"@dagrejs/graphlib": {
"version": "2.1.4",
"resolved": "https://registry.npmjs.org/@dagrejs/graphlib/-/graphlib-2.1.4.tgz",
"integrity": "sha512-QCg9sL4uhjn468FDEsb/S9hS2xUZSrv/+dApb1Ze5VKO96pTXKNJZ6MGhIpgWkc1TVhbVGH9/7rq/Mf8/jWicw==",
"requires": {
"lodash": "^4.11.1"
}
},
"@types/events": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/@types/events/-/events-3.0.0.tgz",
@ -3107,6 +3114,11 @@
}
}
},
"csstype": {
"version": "2.6.9",
"resolved": "https://registry.npmjs.org/csstype/-/csstype-2.6.9.tgz",
"integrity": "sha512-xz39Sb4+OaTsULgUERcCk+TJj8ylkL4aSVDQiX/ksxbELSqwkgt4d4RD7fovIdgJGSuNYqwZEiVjYY5l0ask+Q=="
},
"currently-unhandled": {
"version": "0.4.1",
"resolved": "https://registry.npmjs.org/currently-unhandled/-/currently-unhandled-0.4.1.tgz",
@ -3405,6 +3417,15 @@
"esutils": "^2.0.2"
}
},
"dom-helpers": {
"version": "5.1.3",
"resolved": "https://registry.npmjs.org/dom-helpers/-/dom-helpers-5.1.3.tgz",
"integrity": "sha512-nZD1OtwfWGRBWlpANxacBEZrEuLa16o1nh7YopFWeoF68Zt8GGEmzHu6Xv4F3XaFIC+YXtTLrzgqKxFgLEe4jw==",
"requires": {
"@babel/runtime": "^7.6.3",
"csstype": "^2.6.7"
}
},
"domain-browser": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/domain-browser/-/domain-browser-1.2.0.tgz",
@ -6386,7 +6407,8 @@
"lodash": {
"version": "4.17.15",
"resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.15.tgz",
"integrity": "sha512-8xOcRHvCjnocdS5cpwXQXVzmmh5e5+saE2QGoeQmbKmRS6J3VQppPOIt0MnmE+4xlZoumy0GPG0D0MVIQbNA1A=="
"integrity": "sha512-8xOcRHvCjnocdS5cpwXQXVzmmh5e5+saE2QGoeQmbKmRS6J3VQppPOIt0MnmE+4xlZoumy0GPG0D0MVIQbNA1A==",
"dev": true
},
"lodash.camelcase": {
"version": "4.3.0",
@ -8337,6 +8359,73 @@
"resolved": "https://registry.npmjs.org/react-is/-/react-is-16.12.0.tgz",
"integrity": "sha512-rPCkf/mWBtKc97aLL9/txD8DZdemK0vkA3JMLShjlJB3Pj3s+lpf1KaBzMfQrAmhMQB0n1cU/SUGgKKBCe837Q=="
},
"react-toastify": {
"version": "5.5.0",
"resolved": "https://registry.npmjs.org/react-toastify/-/react-toastify-5.5.0.tgz",
"integrity": "sha512-jsVme7jALIFGRyQsri/g4YTsRuaaGI70T6/ikjwZMB4mwTZaCWqj5NqxhGrRStKlJc5npXKKvKeqTiRGQl78LQ==",
"requires": {
"@babel/runtime": "^7.4.2",
"classnames": "^2.2.6",
"prop-types": "^15.7.2",
"react-transition-group": "^4"
},
"dependencies": {
"classnames": {
"version": "2.2.6",
"resolved": "https://registry.npmjs.org/classnames/-/classnames-2.2.6.tgz",
"integrity": "sha512-JR/iSQOSt+LQIWwrwEzJ9uk0xfN3mTVYMwt1Ir5mUcSN6pU+V4zQFFaJsclJbPuAUQH+yfWef6tm7l1quW3C8Q=="
},
"loose-envify": {
"version": "1.4.0",
"resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz",
"integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==",
"requires": {
"js-tokens": "^3.0.0 || ^4.0.0"
}
},
"prop-types": {
"version": "15.7.2",
"resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.7.2.tgz",
"integrity": "sha512-8QQikdH7//R2vurIJSutZ1smHYTcLpRWEOlHnzcWHmBYrOGUysKwSsrC89BCiFj3CbrfJ/nXFdJepOVrY1GCHQ==",
"requires": {
"loose-envify": "^1.4.0",
"object-assign": "^4.1.1",
"react-is": "^16.8.1"
}
}
}
},
"react-transition-group": {
"version": "4.3.0",
"resolved": "https://registry.npmjs.org/react-transition-group/-/react-transition-group-4.3.0.tgz",
"integrity": "sha512-1qRV1ZuVSdxPlPf4O8t7inxUGpdyO5zG9IoNfJxSO0ImU2A1YWkEQvFPuIPZmMLkg5hYs7vv5mMOyfgSkvAwvw==",
"requires": {
"@babel/runtime": "^7.5.5",
"dom-helpers": "^5.0.1",
"loose-envify": "^1.4.0",
"prop-types": "^15.6.2"
},
"dependencies": {
"loose-envify": {
"version": "1.4.0",
"resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz",
"integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==",
"requires": {
"js-tokens": "^3.0.0 || ^4.0.0"
}
},
"prop-types": {
"version": "15.7.2",
"resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.7.2.tgz",
"integrity": "sha512-8QQikdH7//R2vurIJSutZ1smHYTcLpRWEOlHnzcWHmBYrOGUysKwSsrC89BCiFj3CbrfJ/nXFdJepOVrY1GCHQ==",
"requires": {
"loose-envify": "^1.4.0",
"object-assign": "^4.1.1",
"react-is": "^16.8.1"
}
}
}
},
"read-pkg": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-2.0.0.tgz",

View file

@ -64,6 +64,7 @@
"prop-types": "15.6.0",
"react": "^16.8.6",
"react-dom": "^16.8.6",
"react-toastify": "^5.5.0",
"sprintf": "0.1.5",
"three": "0.89.0"
}

View file

@ -2,8 +2,7 @@ import {AlgNumConstraint, ConstraintDefinitions} from "../constr/ANConstraints";
import {EndPoint} from "../shapes/point";
import {Circle} from "../shapes/circle";
import {Segment} from "../shapes/segment";
import {isInstanceOf, matchAll, matchTypes, sortSelectionByType} from "./matchUtils";
import constraints from "../../../test/cases/constraints";
import {isInstanceOf, matchAll, matchTypes} from "./matchUtils";
import {Arc} from "../shapes/arc";
import {FilletTool} from "../tools/fillet";
import {editConstraint as _editConstraint} from "../components/ConstraintEditor";
@ -13,13 +12,17 @@ export default [
{
id: 'Coincident',
shortName: 'Coincident',
shortName: 'Coincident Constraint',
description: 'Point Coincident',
selectionMatcher: (selection, sortedByType) => matchAll(selection, EndPoint, 2),
selectionMatcher: {
selector: 'matchAll',
types: [EndPoint],
minQuantity: 2
},
invoke: ctx => {
invoke: (ctx, matchedObjects) => {
const {viewer} = ctx;
const [first, ...others] = viewer.selected;
const [first, ...others] = matchedObjects;
let pm = viewer.parametricManager;
for (let obj of others) {
pm._add(
@ -32,17 +35,26 @@ export default [
{
id: 'Tangent',
shortName: 'Tangent',
shortName: 'Tangent Constraint',
description: 'Tangent Between Line And Circle',
selectionMatcher: [
(selection, sortedByType) => matchTypes(sortedByType, Circle, 1, Segment, 1),
(selection, sortedByType) => matchTypes(sortedByType, Arc, 1, Segment, 1),
],
selectionMatcher: {
selector: 'matchSequence',
sequence: [
{
types: [Circle, Arc],
quantity: 1
},
{
types: [Segment],
quantity: 1
},
]
},
invoke: ctx => {
invoke: (ctx, matchedObjects) => {
const {viewer} = ctx;
const [circle, line] = sortSelectionByType(viewer.selected);
const [circle, line] = matchedObjects;
const constraint = new AlgNumConstraint(ConstraintDefinitions.TangentLC, [line, circle]);
constraint.initConstants();
@ -54,24 +66,21 @@ export default [
{
id: 'EqualRadius',
shortName: 'Equal Radius',
shortName: 'Equal Radius Constraint',
description: 'Equal Radius Between Two Circle',
selectionMatcher: selection => {
for (let obj of selection) {
if (!(isInstanceOf(obj, Circle) || isInstanceOf(obj, Arc))) {
return false;
}
}
return true;
selectionMatcher: {
selector: 'matchAll',
types: [Circle, Arc],
minQuantity: 2
},
invoke: ctx => {
invoke: (ctx, matchedObjects) => {
const {viewer} = ctx;
const pm = viewer.parametricManager;
for (let i = 1; i < viewer.selected.length; ++i) {
pm._add(new AlgNumConstraint(ConstraintDefinitions.EqualRadius, [viewer.selected[i-1], viewer.selected[i]]));
for (let i = 1; i < matchedObjects.length; ++i) {
pm._add(new AlgNumConstraint(ConstraintDefinitions.EqualRadius, [matchedObjects[i-1], matchedObjects[i]]));
}
pm.commit();
}
@ -80,15 +89,18 @@ export default [
{
id: 'EqualLength',
shortName: 'Equal Length',
shortName: 'Equal Length Constraint',
description: 'Equal Length Between Two Segments',
selectionMatcher: selection => matchAll(selection, Segment, 2),
invoke: ctx => {
selectionMatcher: {
selector: 'matchAll',
types: [Segment],
minQuantity: 2
},
invoke: (ctx, matchedObjects) => {
const {viewer} = ctx;
const pm = viewer.parametricManager;
for (let i = 1; i < viewer.selected.length; ++i) {
pm._add(new AlgNumConstraint(ConstraintDefinitions.EqualLength, [viewer.selected[i-1], viewer.selected[i]]));
for (let i = 1; i < matchedObjects.length; ++i) {
pm._add(new AlgNumConstraint(ConstraintDefinitions.EqualLength, [matchedObjects[i-1], matchedObjects[i]]));
}
pm.commit();
}
@ -97,13 +109,25 @@ export default [
{
id: 'PointOnLine',
shortName: 'Point On Line',
shortName: 'Point On Line Constraint',
description: 'Point On Line',
selectionMatcher: (selection, sortedByType) => matchTypes(sortedByType, EndPoint, 1, Segment, 1),
selectionMatcher: {
selector: 'matchSequence',
sequence: [
{
types: [EndPoint],
quantity: 1
},
{
types: [Segment],
quantity: 1
},
]
},
invoke: ctx => {
invoke: (ctx, matchedObjects) => {
const {viewer} = ctx;
const [pt, line] = sortSelectionByType(viewer.selected);
const [pt, line] = matchedObjects;
let pm = viewer.parametricManager;
pm.add(new AlgNumConstraint(ConstraintDefinitions.PointOnLine, [pt, line]));
}
@ -111,14 +135,17 @@ export default [
{
id: 'Angle',
shortName: 'Angle',
shortName: 'Angle Constraint',
description: 'Angle',
selectionMatcher: (selection, sortedByType) => matchAll(sortedByType, Segment, 1),
invoke: ctx => {
selectionMatcher: {
selector: 'matchAll',
types: [Segment],
minQuantity: 1
},
invoke: (ctx, matchedObjects) => {
const {viewer} = ctx;
const firstSegment = viewer.selected[0];
const firstSegment = matchedObjects[0];
const firstConstr = new AlgNumConstraint(ConstraintDefinitions.Angle, [firstSegment]);
firstConstr.initConstants();
@ -126,8 +153,8 @@ export default [
editConstraint(ctx, firstConstr, () => {
const pm = viewer.parametricManager;
pm._add(firstConstr);
for (let i = 1; i < viewer.selected.length; ++i) {
pm._add(new AlgNumConstraint(ConstraintDefinitions.Angle, [viewer.selected[i]], {...firstConstr.constants}));
for (let i = 1; i < matchedObjects.length; ++i) {
pm._add(new AlgNumConstraint(ConstraintDefinitions.Angle, [matchedObjects[i]], {...firstConstr.constants}));
}
pm.commit();
});
@ -136,15 +163,20 @@ export default [
{
id: 'Vertical',
shortName: 'Vertical',
shortName: 'Vertical Constraint',
description: 'Vertical',
selectionMatcher: (selection, sortedByType) => matchAll(sortedByType, Segment, 1),
invoke: ctx => {
selectionMatcher: {
selector: 'matchAll',
types: [Segment],
minQuantity: 1
},
invoke: (ctx, matchedObjects) => {
const {viewer} = ctx;
const pm = viewer.parametricManager;
viewer.selected.forEach(obj => {
matchedObjects.forEach(obj => {
const constr = new AlgNumConstraint(ConstraintDefinitions.Vertical, [obj]);
constr.initConstants();
pm._add(constr);
@ -155,15 +187,20 @@ export default [
{
id: 'Horizontal',
shortName: 'Horizontal',
shortName: 'Horizontal Constraint',
description: 'Horizontal',
selectionMatcher: (selection, sortedByType) => matchAll(sortedByType, Segment, 1),
invoke: ctx => {
selectionMatcher: {
selector: 'matchAll',
types: [Segment],
minQuantity: 1
},
invoke: (ctx, matchedObjects) => {
const {viewer} = ctx;
const pm = viewer.parametricManager;
viewer.selected.forEach(obj => {
matchedObjects.forEach(obj => {
const constr = new AlgNumConstraint(ConstraintDefinitions.Horizontal, [obj]);
constr.initConstants();
pm._add(constr);
@ -174,14 +211,19 @@ export default [
{
id: 'AngleBetween',
shortName: 'Angle Between',
shortName: 'Angle Between Constraint',
description: 'Angle Between Lines',
selectionMatcher: (selection, sortedByType) => matchAll(sortedByType, Segment, 2),
invoke: ctx => {
selectionMatcher: {
selector: 'matchAll',
types: [Segment],
minQuantity: 2
},
invoke: (ctx, matchedObjects) => {
const {viewer} = ctx;
const [firstSegment, secondSegment] = viewer.selected;
const [firstSegment, secondSegment] = matchedObjects;
const firstConstr = new AlgNumConstraint(ConstraintDefinitions.AngleBetween, [firstSegment, secondSegment]);
firstConstr.initConstants();
@ -189,9 +231,9 @@ export default [
editConstraint(ctx, firstConstr, () => {
const pm = viewer.parametricManager;
pm._add(firstConstr);
for (let i = 2; i < viewer.selected.length; ++i) {
for (let i = 2; i < matchedObjects.length; ++i) {
pm._add(new AlgNumConstraint(ConstraintDefinitions.Angle,
[viewer.selected[i-1], viewer.selected[i]], {...firstConstr.constants}));
[matchedObjects[i-1], matchedObjects[i]], {...firstConstr.constants}));
}
pm.commit();
});
@ -200,17 +242,22 @@ export default [
{
id: 'Perpendicular',
shortName: 'Perpendicular',
shortName: 'Perpendicular Constraint',
description: 'Perpendicularity between two or more lines',
selectionMatcher: (selection, sortedByType) => matchAll(sortedByType, Segment, 2),
invoke: ctx => {
selectionMatcher: {
selector: 'matchAll',
types: [Segment],
minQuantity: 2
},
invoke: (ctx, matchedObjects) => {
const {viewer} = ctx;
const pm = viewer.parametricManager;
for (let i = 1; i < viewer.selected.length; ++i) {
const constr = new AlgNumConstraint(ConstraintDefinitions.Perpendicular, [viewer.selected[i-1], viewer.selected[i]]);
for (let i = 1; i < matchedObjects.length; ++i) {
const constr = new AlgNumConstraint(ConstraintDefinitions.Perpendicular, [matchedObjects[i-1], matchedObjects[i]]);
constr.initConstants();
pm._add(constr);
}
@ -220,17 +267,22 @@ export default [
{
id: 'Parallel',
shortName: 'Parallel',
shortName: 'Parallel Constraint',
description: 'Parallelism between two or more lines',
selectionMatcher: (selection, sortedByType) => matchAll(sortedByType, Segment, 2),
invoke: ctx => {
selectionMatcher: {
selector: 'matchAll',
types: [Segment],
minQuantity: 2
},
invoke: (ctx, matchedObjects) => {
const {viewer} = ctx;
const pm = viewer.parametricManager;
for (let i = 1; i < viewer.selected.length; ++i) {
const constr = new AlgNumConstraint(ConstraintDefinitions.Parallel, [viewer.selected[i-1], viewer.selected[i]]);
for (let i = 1; i < matchedObjects.length; ++i) {
const constr = new AlgNumConstraint(ConstraintDefinitions.Parallel, [matchedObjects[i-1], matchedObjects[i]]);
constr.initConstants();
pm._add(constr);
}
@ -240,14 +292,19 @@ export default [
{
id: 'Length',
shortName: 'Length',
shortName: 'Length Constraint',
description: 'Segment Length',
selectionMatcher: (selection) => matchAll(selection, Segment, 1),
invoke: ctx => {
selectionMatcher: {
selector: 'matchAll',
types: [Segment],
minQuantity: 1
},
invoke: (ctx, matchedObjects) => {
const {viewer} = ctx;
const [firstSegment, ...others] = viewer.selected;
const [firstSegment, ...others] = matchedObjects;
const firstConstr = new AlgNumConstraint(ConstraintDefinitions.SegmentLength, [firstSegment]);
firstConstr.initConstants();
@ -265,21 +322,19 @@ export default [
{
id: 'RadiusLength',
shortName: 'RadiusLength',
shortName: 'Radius Length Constraint',
description: 'Radius Length',
selectionMatcher: (selection) => {
for (let obj of selection) {
if (!(isInstanceOf(obj, Circle) || isInstanceOf(obj, Arc))) {
return false;
}
}
return true;
selectionMatcher: {
selector: 'matchAll',
types: [Circle, Arc],
minQuantity: 1
},
invoke: ctx => {
invoke: (ctx, matchedObjects) => {
const {viewer} = ctx;
const [firstCircle, ...others] = viewer.selected;
const [firstCircle, ...others] = matchedObjects;
const firstConstr = new AlgNumConstraint(ConstraintDefinitions.RadiusLength, [firstCircle]);
firstConstr.initConstants();
@ -297,14 +352,28 @@ export default [
{
id: 'DistancePL',
shortName: 'Point to Line Distance',
shortName: 'Point to Line Distance Constraint',
description: 'Distance between Point and Line',
selectionMatcher: (selection, sortedByType) => matchTypes(sortedByType, EndPoint, 1, Segment, 1),
invoke: ctx => {
selectionMatcher: {
selector: 'matchSequence',
sequence: [
{
types: [EndPoint],
quantity: 1
},
{
types: [Segment],
quantity: 1
},
]
},
invoke: (ctx, matchedObjects) => {
const {viewer} = ctx;
const [pt, seg] = sortSelectionByType(viewer.selected);
const [pt, seg] = matchedObjects;
const constr = new AlgNumConstraint(ConstraintDefinitions.DistancePL, [pt, seg]);
constr.initConstants();
@ -318,14 +387,23 @@ export default [
{
id: 'DistancePP',
shortName: 'Two Point Distance',
shortName: 'Two Point Distance Constraint',
description: 'Distance between two Points',
selectionMatcher: (selection, sortedByType) => matchTypes(sortedByType, EndPoint, 2),
invoke: ctx => {
selectionMatcher: {
selector: 'matchSequence',
sequence: [
{
types: [EndPoint],
quantity: 2
}
]
},
invoke: (ctx, matchedObjects) => {
const {viewer} = ctx;
const [p1, p2] = sortSelectionByType(viewer.selected);
const [p1, p2] = matchedObjects;
const constr = new AlgNumConstraint(ConstraintDefinitions.DistancePP, [p1, p2]);
constr.initConstants();
@ -339,14 +417,22 @@ export default [
{
id: 'Lock',
shortName: 'Lock',
shortName: 'Lock Point Constraint',
description: 'Lock Point',
selectionMatcher: (selection) => matchTypes(selection, EndPoint, 1),
selectionMatcher: {
selector: 'matchSequence',
sequence: [
{
types: [EndPoint],
quantity: 1
}
]
},
invoke: ctx => {
invoke: (ctx, matchedObjects) => {
const {viewer} = ctx;
const [point] = viewer.selected;
const [point] = matchedObjects;
const constr = new AlgNumConstraint(ConstraintDefinitions.LockPoint, [point]);
constr.initConstants();
@ -356,32 +442,33 @@ export default [
{
id: 'Fillet',
shortName: 'Fillet',
shortName: 'Fillet Tool',
description: 'Add a Fillet',
selectionMatcher: (selection) => {
if (matchTypes(selection, EndPoint, 1)) {
const [point] = selection;
if (isInstanceOf(point.parent, Segment)) {
let pair = null;
point.visitLinked(l => {
if (l !== point && isInstanceOf(l.parent, Segment)) {
pair = l;
selectionMatcher: {
selector: 'function',
match: (selection) => {
if (matchTypes(selection, EndPoint, 1)) {
const [point] = selection;
if (isInstanceOf(point.parent, Segment)) {
let pair = null;
point.visitLinked(l => {
if (l !== point && isInstanceOf(l.parent, Segment)) {
pair = l;
return true;
}
});
if (pair) {
return true;
}
});
if (pair) {
return true;
}
}
}
return false;
return false;
},
description: 'a point linking two segment'
},
invoke: ctx => {
invoke: (ctx, matchedObjects) => {
const {viewer} = ctx;
const filletTool = new FilletTool(ctx.viewer);
const cands = filletTool.getCandidateFromSelection(viewer.selected);
if (cands) {

View file

@ -1,42 +1,44 @@
import constraintActions from "./constraintActions";
import {sortSelectionByType} from "./matchUtils";
import {getDescription, MatchIndex, matchSelection} from "../selectionMatcher";
import {toast} from "react-toastify";
const ALL_CONTEXTUAL_ACTIONS = [
...constraintActions,
//keep going here
];
const index = {};
ALL_CONTEXTUAL_ACTIONS.forEach(a => index[a.id] = a);
Object.freeze(index);
export function matchAvailableActions(selection) {
let sortedByType = sortSelectionByType(selection);
let matched = [];
let matchIndex = new MatchIndex(selection);
if (selection.length) {
for (let action of ALL_CONTEXTUAL_ACTIONS) {
if (Array.isArray(action.selectionMatcher)) {
action.selectionMatcher.forEach(matcher => {
if (matcher(selection, sortedByType)) {
matched.push(action);
}
})
} else {
if (action.selectionMatcher(selection, sortedByType)) {
matched.push(action);
}
if (matchSelection(action.selectionMatcher, matchIndex, true)) {
matched.push(action);
}
}
}
return matched;
}
//For backward compatibility
export function getActionIfAvailable(actionId, selection, cb) {
export function runActionOrToastWhyNot(actionId, selection, ctx) {
const action = index[actionId];
if (action) {
const matched = matchSelection(action.selectionMatcher, new MatchIndex(selection), false);
if (matched) {
action.invoke(ctx, matched)
} else {
toast('The action "' + action.shortName + '" requires selection of ' + getDescription(action.selectionMatcher));
}
}
matchAvailableActions(selection).forEach(a => {
if (a.id === actionId) {
cb(a);

View file

@ -3,6 +3,7 @@ import ls from './ContextualControls.less';
import {matchAvailableActions} from "../actions";
import {useStream} from "../../../../modules/ui/effects";
import {SketcherAppContext} from "./SketcherApp";
import {MatchIndex, matchSelection} from "../selectionMatcher";
export function ContextualControls() {
@ -25,7 +26,7 @@ export function ContextualControls() {
<div className={ls.hr}>AVAILABLE ACTIONS:</div>
{
availableActions.map(a => <button onClick={() => a.invoke(ctx)}
availableActions.map(a => <button onClick={() => a.invoke(ctx, matchSelection(a.selectionMatcher, new MatchIndex(selection), false))}
title={a.description}>{a.shortName}</button>)
}

View file

@ -4,12 +4,15 @@ import {ConstraintEditor} from './ConstraintEditor';
import {ContextualControls} from './ContextualControls';
import {ConstraintList} from './ConstraintExplorer';
import {StreamsContext} from 'ui/streamsContext';
import {ToastContainer} from "react-toastify";
import 'react-toastify/dist/ReactToastify.css';
export const SketcherAppContext = React.createContext({});
export function SketcherApp({applicationContext}) {
return <SketcherAppContext.Provider value={applicationContext}>
<StreamsContext.Provider value={applicationContext}>
<ToastContainer />
<RightSideControls />
{ReactDOM.createPortal(
<ConstraintList />,

View file

@ -1,26 +1,126 @@
export function SelectionMatcher() {
export function matchSelection(definition, matchIndex, fast) {
const selection = matchIndex.selection;
if (definition.selector === 'function') {
return definition.match(selection, fast)
} else if (definition.selector === 'matchAll') {
const {minQuantity: min, types} = definition;
if (min !== undefined && selection.length < min) {
return false;
}
for (let obj of selection) {
const lowerBounds = [];
return {
moreThan(amount, type) {
lowerBounds.push(amount, type);
return this;
},
match: selection => {
for (let [amount, type] of lowerBounds) {
let hit = false;
for (let constructor of types) {
if (constructor.prototype._class === obj._class) {
hit = true;
break;
}
}
if (!hit) {
return false;
}
}
return fast ? true : selection;
} else if (definition.selector === 'matchSequence') {
matchIndex.reset(fast);
for (let item of definition.sequence) {
if (!matchIndex.mark(item.types, item.quantity)) {
return false;
}
}
return matchIndex.allHit() ? (fast ? true : matchIndex.result) : false;
} else {
throw 'unsupported'
}
}
export function getDescription(definition) {
if (definition.selector === 'function') {
return definition.description;
} else if (definition.selector === 'matchAll') {
return `at least ${definition.minQuantity} of ${stringifyTypes(definition.types, definition.minQuantity)}`;
} else if (definition.selector === 'matchSequence') {
let out = '';
for (let i = 0; i< definition.sequence.length; ++i) {
const item = definition.sequence[i];
if (i !== 0) {
out += i === definition.sequence.length - 1 ? ' and ': ', ';
}
out += item.quantity + ' ' + stringifyTypes(item.types, item.quantity);
}
return out;
} else {
throw 'unsupported'
}
}
function stringifyTypes(types, minQuantity) {
return types.map(t => t.prototype._class.replace('TCAD.TWO.', '') + (minQuantity > 1 ? 's' : '')).join(' or ');
}
export class MatchIndex {
typeMap = new Map();
overallHits = 0;
constructor(selection) {
this.selection = selection;
selection.forEach(obj => {
let info = this.typeMap.get(obj._class);
if (!info) {
info = {
hits: 0,
objects: []
};
this.typeMap.set(obj._class, info);
}
info.objects.push(obj);
})
}
reset(fast) {
this.overallHits = 0;
this.typeMap.forEach(i => i.hits = 0);
this.result = fast ? null : [];
}
mark(types, quantity) {
for (let type of types) {
const info = this.typeMap.get(type.prototype._class);
if (!info) {
return false;
}
const toAdd = Math.min(quantity, info.objects.length - info.hits);
if (this.result) {
for (let i = 0; i < toAdd; ++i) {
this.result.push(info.objects[info.hits + i]);
}
}
quantity -= toAdd;
info.hits += toAdd;
this.overallHits += toAdd;
}
return quantity === 0;
}
allHit() {
return this.selection.length === this.overallHits;
}
}

View file

@ -127,18 +127,18 @@ export class Segment extends SketchObject {
drawImpl(ctx, scale) {
let ang = this.params.ang.get();
let nx = -Math.sin(ang);
let ny = Math.cos(ang);
let w = this.w;
ctx.save();
draw_utils.SetStyle(Styles.CONSTRUCTION_OF_OBJECT, ctx, scale );
ctx.beginPath();
ctx.moveTo(nx * w + ny * 1000, ny * w - nx * 1000);
ctx.lineTo(nx * w - ny * 1000, ny * w + nx * 1000);
ctx.stroke();
ctx.restore();
// let ang = this.params.ang.get();
// let nx = -Math.sin(ang);
// let ny = Math.cos(ang);
// let w = this.w;
//
// ctx.save();
// draw_utils.SetStyle(Styles.CONSTRUCTION_OF_OBJECT, ctx, scale );
// ctx.beginPath();
// ctx.moveTo(nx * w + ny * 1000, ny * w - nx * 1000);
// ctx.lineTo(nx * w - ny * 1000, ny * w + nx * 1000);
// ctx.stroke();
// ctx.restore();
ctx.beginPath();
ctx.moveTo(this.a.x, this.a.y);

View file

@ -16,7 +16,7 @@ import {ReferencePointTool} from './tools/origin'
import {InputManager} from './input-manager'
import genSerpinski from '../utils/genSerpinski';
import React from "react";
import {getActionIfAvailable} from "./actions";
import {runActionOrToastWhyNot} from "./actions";
import {stream} from "../../../modules/lstream";
function App2D() {
@ -25,8 +25,8 @@ function App2D() {
this.viewer = new Viewer(document.getElementById('viewer'), IO);
this.context = createAppContext(this.viewer);
this.winManager = new ui.WinManager();
this.inputManager = new InputManager(this);
this.inputManager = new InputManager(this);
this.initSketchManager();
this._exportWin = new ui.Window($('#exportManager'), app.winManager);
@ -61,7 +61,7 @@ function App2D() {
this.winManager.registerResize(dockEl, ui.DIRECTIONS.EAST, function() {$('body').trigger('layout'); });
$('body').on('layout', this.viewer.onWindowResize);
this.registerAction = function(id, desc, action, command) {
app.actions[id] = {id, desc, action};
if (command) {
@ -69,7 +69,7 @@ function App2D() {
}
app._actionsOrder.push(id);
};
function checkForTerminalVisibility() {
const terminalVisible = app.commandsWin.root.is(':visible');
if (terminalVisible) {
@ -78,11 +78,11 @@ function App2D() {
app.viewer.referencePoint.visible = terminalVisible;
}
checkForTerminalVisibility();
this.registerAction('new', "Create New Sketch", function () {
app.newSketch();
});
this.registerAction('terminal', "Open/Close Terminal Window", function () {
app.commandsWin.toggle();
checkForTerminalVisibility();
@ -109,7 +109,7 @@ function App2D() {
this.registerAction('exportDXF', "Export To DXF", function () {
IO.exportTextData(app.viewer.io.dxfExport(), app.getSketchId() + ".dxf");
});
this.registerAction('undo', "Undo", function () {
app.viewer.historyManager.undo();
});
@ -129,7 +129,7 @@ function App2D() {
this.registerAction('addPoint', "Add Point", function () {
app.viewer.toolManager.takeControl(new AddPointTool(app.viewer));
}, "point");
this.registerAction('addSegment', "Add Segment", function () {
app.viewer.toolManager.takeControl(new AddSegmentTool(app.viewer, false));
}, 'line');
@ -157,7 +157,7 @@ function App2D() {
this.registerAction('addBezierCurve', "Add Bezier Curve", function () {
app.viewer.toolManager.takeControl(new BezierCurveTool(app.viewer));
});
this.registerAction('addRectangle', "Add Rectangle", function () {
app.viewer.toolManager.takeControl(new RectangleTool(app.viewer));
}, 'rect');
@ -169,7 +169,7 @@ function App2D() {
this.registerAction('pan', "Pan", function () {
app.viewer.toolManager.releaseControl();
});
this.registerAction('addFillet', "Add Fillet", function () {
app.viewer.toolManager.takeControl(new FilletTool(app.viewer));
});
@ -177,11 +177,11 @@ function App2D() {
this.registerAction('addDim', "Add Dimension", function () {
app.viewer.toolManager.takeControl(new AddFreeDimTool(app.viewer, app.viewer.dimLayer));
});
this.registerAction('addHDim', "Add Horizontal Dimension", function () {
app.viewer.toolManager.takeControl(new AddHorizontalDimTool(app.viewer, app.viewer.dimLayer));
});
this.registerAction('addVDim', "Add Vertical Dimension", function () {
app.viewer.toolManager.takeControl(new AddVerticalDimTool(app.viewer, app.viewer.dimLayer));
});
@ -198,27 +198,27 @@ function App2D() {
});
this.registerAction('coincident', "Coincident", function () {
getActionIfAvailable('Coincident', app.viewer.selected, action => action.invoke(app.context));
runActionOrToastWhyNot('Coincident', app.viewer.selected, app.context);
});
this.registerAction('verticalConstraint', "Vertical Constraint", function () {
getActionIfAvailable('Vertical', app.viewer.selected, action => action.invoke(app.context));
runActionOrToastWhyNot('Vertical', app.viewer.selected, app.context);
});
this.registerAction('horizontalConstraint', "Horizontal Constraint", function () {
getActionIfAvailable('Horizontal', app.viewer.selected, action => action.invoke(app.context));
runActionOrToastWhyNot('Horizontal', app.viewer.selected, app.context);
});
this.registerAction('parallelConstraint', "Parallel Constraint", function () {
getActionIfAvailable('Parallel', app.viewer.selected, action => action.invoke(app.context));
runActionOrToastWhyNot('Parallel', app.viewer.selected, app.context);
});
this.registerAction('perpendicularConstraint', "Perpendicular Constraint", function () {
getActionIfAvailable('Perpendicular', app.viewer.selected, action => action.invoke(app.context));
runActionOrToastWhyNot('Perpendicular', app.viewer.selected, app.context);
});
this.registerAction('P2LDistanceConstraint', "Distance Between Point and Line", function () {
getActionIfAvailable('DistancePL', app.viewer.selected, action => action.invoke(app.context));
runActionOrToastWhyNot('DistancePL', app.viewer.selected, app.context);
});
this.registerAction('mirrorConstraint', "Mirror Constraint", function () {
@ -226,7 +226,7 @@ function App2D() {
});
this.registerAction('P2PDistanceConstraint', "Distance Between two Points", function () {
getActionIfAvailable('DistancePP', app.viewer.selected, action => action.invoke(app.context));
runActionOrToastWhyNot('DistancePP', app.viewer.selected, app.context);
});
this.registerAction('RadiusConstraint', "Radius Constraint", function () {

View file

@ -4,6 +4,7 @@ const generateCSSScopedName = require('./build/cssScoopeGenerator')();
const WEB_APP = path.join(__dirname, 'web/app');
const MODULES = path.join(__dirname, 'modules');
const NODE_MODULES = path.join(__dirname, 'node_modules');
const INTEGRATION_TESTS = path.join(__dirname, 'web/test');
const GLOBAL_CSS = path.join(__dirname, 'web/css');
@ -59,6 +60,14 @@ module.exports = {
'less-loader',
]
},
{
test: /\.(css)$/,
include: [NODE_MODULES],
use: [
'style-loader',
'css-loader',
]
},
{
test: /\.(less|css)$/,
include: [MODULES, WEB_APP],