action subsystem

This commit is contained in:
Val Erastov 2016-10-13 02:05:47 -07:00
parent 0156a974d5
commit 79e345bffc
13 changed files with 152 additions and 61 deletions

View file

@ -0,0 +1,17 @@
export function checkForSelectedFaces(amount) {
return (state, app) => {
state.enabled = app.viewer.selectionMgr.selection.length >= amount;
if (!state.enabled) {
state.hint = amount == 1 ? 'requires a face to be selected' : 'requires ' + amount + ' faces to be selected';
}
}
}
export function checkForSelectedSolids(amount) {
return (state, app) => {
state.enabled = app.viewer.selectionMgr.selection.length >= amount;
if (!state.enabled) {
state.hint = amount == 1 ? 'requires a solid to be selected' : 'requires ' + amount + ' solids to be selected';
}
}
}

View file

@ -72,6 +72,11 @@ ActionManager.prototype.run = function(actionId, event) {
ActionManager.prototype.subscribe = function(actionId, callback) {
this.app.bus.subscribe('action.update.'+actionId, callback);
const action = this.actions[actionId];
if (action) {
callback(action.state);
}
return callback;
};
const NOOP = () => {};

View file

@ -1,18 +1,52 @@
export const refreshSketches = {
import * as ActionHelpers from './action-helpers'
export const EditFace = {
cssIcons: ['file-picture-o'],
label: 'edit',
icon32: 'img/3d/face-edit32.png',
icon96: 'img/3d/face-edit96.png',
info: 'open sketcher for a face/plane',
listens: ['selection'],
update: ActionHelpers.checkForSelectedFaces(1),
invoke: (app) => app.sketchFace()
};
export const Save = {
cssIcons: ['floppy-o'],
label: 'refresh sketches',
info: 'force refreshing sketches/loading from storage',
invoke: (app) => app.save()
};
export const StlExport = {
cssIcons: ['upload', 'flip-vertical'],
label: '',
info: 'refresh all visible sketches',
invoke: (app) => app.stlExport()
};
export const RefreshSketches = {
cssIcons: ['refresh'],
label: 'Refresh Sketches',
info: 'refresh all visible sketches',
invoke: (app) => app.refreshSketches()
};
export const info = {
export const DeselectAll = {
cssIcons: ['square-o'],
label: 'deselect all',
info: 'deselect everything',
invoke: (app) => app.viewer.selectionMgr.deselectAll()
};
export const Info = {
cssIcons: ['info-circle'],
label: 'info',
info: 'opens help dialog',
invoke: (app) => app.showInfo()
};
export const showSketches = {
export const ShowSketches = {
type: 'binary',
property: 'showSketches',
cssIcons: ['image'],

View file

@ -1,43 +1,46 @@
import * as Operations from '../operations'
import * as ActionHelpers from './action-helpers'
function mergeInfo(op, action) {
function mergeInfo(opName, action) {
const op = Operations[opName];
action.label = op.label;
action.icon32 = op.icon + '32.png';
action.icon96 = op.icon + '96.png';
action.invoke = (app) => app.ui.initOperation(opName);
return action;
}
export const OperationActions = {
'CUT': mergeInfo(Operations.CUT, {
'CUT': mergeInfo('CUT', {
info: 'makes a cut based on 2D sketch'
}),
'PAD': mergeInfo(Operations.PAD, {
'PAD': mergeInfo('PAD', {
info: 'extrudes 2D sketch'
}),
'BOX': mergeInfo(Operations.BOX, {
'BOX': mergeInfo('BOX', {
info: 'creates new object box'
}),
'PLANE': mergeInfo(Operations.PLANE, {
'PLANE': mergeInfo('PLANE', {
info: 'creates new object plane'
}),
'SPHERE': mergeInfo(Operations.SPHERE, {
'SPHERE': mergeInfo('SPHERE', {
info: 'creates new object sphere'
}),
'INTERSECTION': mergeInfo(Operations.INTERSECTION, {
'INTERSECTION': mergeInfo('INTERSECTION', {
info: 'intersection operation on two solids'
}),
'DIFFERENCE': mergeInfo(Operations.DIFFERENCE, {
'DIFFERENCE': mergeInfo('DIFFERENCE', {
info: 'difference operation on two solids'
}),
'UNION': mergeInfo(Operations.UNION, {
'UNION': mergeInfo('UNION', {
info: 'union operation on two solids'
})
};
@ -51,20 +54,10 @@ requiresSolidSelection(OperationActions.UNION, 2);
function requiresFaceSelection(action, amount) {
action.listens = ['selection'];
action.update = (state, app) => {
state.enabled = app.viewer.selectionMgr.selection.length >= amount;
if (!state.enabled) {
state.hint = 'requires at least one face to be selected';
}
}
action.update = ActionHelpers.checkForSelectedFaces(amount)
}
function requiresSolidSelection(action, amount) {
action.listens = ['selection'];
action.update = (state, app) => {
state.enabled = app.viewer.selectionMgr.selection.length >= amount;
if (!state.enabled) {
state.hint = 'requires at least two solids to be selected';
}
}
action.update = ActionHelpers.checkForSelectedSolids(amount)
}

View file

@ -28,7 +28,7 @@ export const main = {
label: 'start',
cssIcons: ['rocket'],
info: 'common set of actions',
actions: ['PAD', 'CUT', '-', 'INTERSECTION', 'DIFFERENCE', 'UNION', '-', 'PLANE', 'BOX', 'SPHERE', '-',
'deselectAll', 'refreshSketches' ]
actions: ['PAD', 'CUT', '-', 'INTERSECTION', 'DIFFERENCE', 'UNION', '-', 'PLANE', 'BOX', 'SPHERE', '-',
'EditFace', '-', 'DeselectAll', 'RefreshSketches' ]
};

View file

@ -39,16 +39,13 @@ export default function Menu(menuActions, inputManager) {
}
container.append(menuItem);
var uiUpdater = (state) => {
this.inputManager.app.actionManager.subscribe(action.id, (state) => {
if (state.enabled) {
menuItem.removeClass('action-disabled');
} else {
menuItem.addClass('action-disabled');
}
menuItem.data('actionHint', state.hint);
};
uiUpdater(action.state);
this.inputManager.app.actionManager.subscribe(action.id, uiUpdater);
});
}
this.node.hide();
$('body').append(this.node);

View file

@ -12,6 +12,7 @@ import {Matrix3, AXIS, ORIGIN, IDENTITY_BASIS} from '../math/l3space'
import * as workbench from './workbench'
import * as cad_utils from './cad-utils'
import * as math from '../math/math'
import {IO} from '../sketcher/io'
require('../../css/app3d.less');
function App() {
@ -388,6 +389,18 @@ App.prototype.load = function() {
}
};
App.prototype.stlExport = function() {
var allPolygons = cad_utils.arrFlatten1L(this.findAllSolids().map(function (s) {
return s.csg.toPolygons()
}));
var stl = CSG.fromPolygons(allPolygons).toStlString();
IO.exportTextData(stl.data[0], app.id + ".stl");
};
App.prototype.showInfo = function() {
alert('men at work');
};
App.prototype.initSample = function() {
localStorage.setItem("TCAD.projects.sample", '{"history":[{"type":"PLANE","solids":[],"params":{"basis":[[1,0,0],[0,0,1],[0,1,0]],"depth":"0"},"protoParams":["XZ","0"]},{"type":"PAD","solids":[0],"face":"0:0","params":{"target":[0,-50,0],"expansionFactor":"1"},"protoParams":["50","1","0","0"]},{"type":"PAD","solids":[1],"face":"1:1","params":{"target":[0,-50,0],"expansionFactor":"1"},"protoParams":["50","1","0","0"]},{"type":"CUT","solids":[2],"face":"2:0","params":{"target":[0,252,0],"expansionFactor":"1"},"protoParams":["252","1","0","0"]},{"type":"CUT","solids":[3],"face":"1:1$","params":{"target":[0,50,0],"expansionFactor":"1"},"protoParams":["50","1","0","0"]}]}');
localStorage.setItem("TCAD.projects.sample.sketch.0:0", '{"layers":[{"name":"_dim","style":{"lineWidth":1,"strokeStyle":"#bcffc1","fillStyle":"#00FF00"},"data":[]},{"name":"__bounds__","style":{"lineWidth":2,"strokeStyle":"#fff5c3","fillStyle":"#000000"},"data":[{"id":6,"_class":"TCAD.TWO.Segment","aux":true,"edge":0,"points":[[0,[1,-400],[2,400]],[3,[4,-400],[5,-400]]]},{"id":13,"_class":"TCAD.TWO.Segment","aux":true,"edge":2,"points":[[7,[8,-400],[9,-400]],[10,[11,400],[12,-400]]]},{"id":20,"_class":"TCAD.TWO.Segment","aux":true,"edge":4,"points":[[14,[15,400],[16,-400]],[17,[18,400],[19,400]]]},{"id":27,"_class":"TCAD.TWO.Segment","aux":true,"edge":6,"points":[[21,[22,400],[23,400]],[24,[25,-400],[26,400]]]}]},{"name":"sketch","style":{"lineWidth":2,"strokeStyle":"#ffffff","fillStyle":"#000000"},"data":[{"id":34,"_class":"TCAD.TWO.Segment","points":[[28,[29,-80.41502600578134],[30,240.48794311524324]],[31,[32,252.10163324769275],[33,71.15131239804411]]]},{"id":41,"_class":"TCAD.TWO.Segment","points":[[35,[36,255.946878629896],[37,-145.76094357167156]],[38,[39,-91.17342089039929],[40,-338.36716169336114]]]},{"id":48,"_class":"TCAD.TWO.Segment","points":[[42,[43,-172.00749593627577],[44,-240.71428346724593]],[45,[46,-88.51020843368133],[47,-140.46311545122035]]]},{"id":55,"_class":"TCAD.TWO.Segment","points":[[49,[50,-102.18982576106004],[51,18.31440664196805]],[52,[53,-182.7982464866314],[54,86.82364838151852]]]},{"id":72,"_class":"TCAD.TWO.Arc","points":[[63,[64,255.946878629896],[65,-145.76094357167156]],[66,[67,252.10163324769275],[68,71.15131239804411]],[69,[70,196.33682709088268],[71,-38.32745196977044]]]},{"id":83,"_class":"TCAD.TWO.Arc","points":[[74,[75,-80.41502600578134],[76,240.48794311524324]],[77,[78,-182.7982464866314],[79,86.82364838151852]],[80,[81,-122.59914075444685],[82,157.65429488839598]]]},{"id":94,"_class":"TCAD.TWO.Arc","points":[[85,[86,-88.51020843368133],[87,-140.46311545122035]],[88,[89,-102.18982576106004],[90,18.31440664196805]],[91,[92,-175.53398017227],[93,-67.98267439986091]]]},{"id":105,"_class":"TCAD.TWO.Arc","points":[[96,[97,-172.00749593627577],[98,-240.71428346724593]],[99,[100,-91.17342089039929],[101,-338.36716169336114]],[102,[103,-122.4591797419898],[104,-281.9821285194346]]]}]},{"name":"_construction_","style":{"lineWidth":1,"strokeStyle":"#aaaaaa","fillStyle":"#000000"},"data":[]}],"constraints":[["Tangent",[72,41]],["Tangent",[72,34]],["coi",[63,35]],["coi",[66,31]],["Tangent",[83,34]],["Tangent",[83,55]],["coi",[74,28]],["coi",[77,52]],["Tangent",[94,48]],["Tangent",[94,55]],["coi",[85,45]],["coi",[88,49]],["Tangent",[105,48]],["Tangent",[105,41]],["coi",[96,42]],["coi",[99,38]]],"boundary":{"lines":[{"a":{"x":-400,"y":400},"b":{"x":-400,"y":-400}},{"a":{"x":-400,"y":-400},"b":{"x":400,"y":-400}},{"a":{"x":400,"y":-400},"b":{"x":400,"y":400}},{"a":{"x":400,"y":400},"b":{"x":-400,"y":400}}],"arcs":[],"circles":[]}}');

View file

@ -11,7 +11,6 @@ import {PlaneWizard} from '../wizards/plane'
import {BoxWizard} from '../wizards/box'
import {SphereWizard} from '../wizards/sphere'
import {TransformWizard} from '../wizards/transform'
import {IO} from '../../sketcher/io'
function UI(app) {
this.app = app;
@ -142,13 +141,6 @@ function UI(app) {
deselectAll.root.click(function() {
app.viewer.selectionMgr.deselectAll();
});
stlExport.root.click(function() {
var allPolygons = cad_utils.arrFlatten1L(app.findAllSolids().map(function (s) {
return s.csg.toPolygons()
}));
var stl = CSG.fromPolygons(allPolygons).toStlString();
IO.exportTextData(stl.data[0], app.id + ".stl");
});
app.bus.subscribe("solid-pick", function(solid) {
ui.registerWizard(new TransformWizard(app.viewer, solid));
});
@ -165,20 +157,20 @@ UI.prototype.cutExtrude = function(isCut) {
};
UI.prototype.createCraftToolBar = function (vertPos) {
var toolBar = new ToolBar();
toolBar.add('Edit', 'img/3d/face-edit96.png', () => this.app.sketchFace());
toolBar.add('Cut', 'img/3d/cut96.png', this.cutExtrude(true));
toolBar.add('Extrude', 'img/3d/extrude96.png', this.cutExtrude(false));
toolBar.add('Plane', 'img/3d/plane96.png', () => this.registerWizard(new PlaneWizard(this.app.viewer), false));
toolBar.add('Box', 'img/3d/cube96.png', () => this.registerWizard(new BoxWizard(this.app.viewer), false));
toolBar.add('Sphere', 'img/3d/sphere96.png', () => this.registerWizard(new SphereWizard(this.app.viewer), false));
var toolBar = new ToolBar(this.app);
toolBar.add(this.app.actionManager.actions['EditFace']);
toolBar.add(this.app.actionManager.actions['CUT']);
toolBar.add(this.app.actionManager.actions['PAD']);
toolBar.add(this.app.actionManager.actions['PLANE']);
toolBar.add(this.app.actionManager.actions['BOX']);
toolBar.add(this.app.actionManager.actions['SPHERE']);
$('#viewer-container').append(toolBar.node);
toolBar.node.css({left: '10px',top : vertPos + 'px'});
return toolBar;
};
UI.prototype.createMiscToolBar = function (vertPos) {
var toolBar = new ToolBar();
var toolBar = new ToolBar(this.app);
toolBar.addFa('floppy-o', () => this.app.sketchFace());
toolBar.addFa('upload', () => this.app.sketchFace());
toolBar.addFa('refresh', () => this.app.sketchFace());
@ -190,10 +182,10 @@ UI.prototype.createMiscToolBar = function (vertPos) {
};
UI.prototype.createBoolToolBar = function(vertPos) {
var toolBar = new ToolBar();
toolBar.add('Intersection', 'img/3d/intersection96.png', () => this.app.sketchFace());
toolBar.add('Difference', 'img/3d/difference96.png', this.cutExtrude(true));
toolBar.add('Union', 'img/3d/union96.png', this.cutExtrude(false));
var toolBar = new ToolBar(this.app);
toolBar.add(this.app.actionManager.actions['INTERSECTION']);
toolBar.add(this.app.actionManager.actions['DIFFERENCE']);
toolBar.add(this.app.actionManager.actions['UNION']);
$('#viewer-container').append(toolBar.node);
toolBar.node.css({left: '10px', top : vertPos + 'px'});
return toolBar;
@ -214,9 +206,9 @@ UI.prototype.registerMenuActions = function() {
UI.prototype.fillControlBar = function() {
const LEFT = true;
const RIGHT = !LEFT;
this.app.controlBar.add('info', RIGHT, {'label': null});
this.app.controlBar.add('refreshSketches', RIGHT, {'label': null});
this.app.controlBar.add('showSketches', RIGHT, {'label': 'sketches'});
this.app.controlBar.add('Info', RIGHT, {'label': null});
this.app.controlBar.add('RefreshSketches', RIGHT, {'label': null});
this.app.controlBar.add('ShowSketches', RIGHT, {'label': 'sketches'});
this.app.controlBar.add('menu.craft', LEFT);
this.app.controlBar.add('menu.primitives', LEFT);
this.app.controlBar.add('menu.boolean', LEFT);
@ -264,6 +256,21 @@ UI.getIconForOp = function(op) {
};
UI.prototype.initOperation = function(op) {
if ('CUT' === op) {
this.cutExtrude(false)();
} else if ('PAD' === op) {
this.cutExtrude(false)();
} else if ('BOX' === op) {
this.registerWizard(new BoxWizard(this.app.viewer), false)
} else if ('PLANE' === op) {
this.registerWizard(new PlaneWizard(this.app.viewer), false)
} else if ('SPHERE' === op) {
this.registerWizard(new SphereWizard(this.app.viewer), false)
} else {
console.log('unknown operation');
}
};
UI.prototype.createWizardForOperation = function(op) {
var initParams = op.protoParams;

View file

@ -18,6 +18,7 @@ MessageSink.prototype.hide = function() {
MessageSink.prototype.showContent = function(dom) {
this.node.children().detach();
this.node.empty();
this.node.append(dom);
this.show();
};

View file

@ -72,6 +72,11 @@ export function fit(el, relativeEl) {
}
}
export function capitalize(str) {
if (!str) return;
return str.charAt(0).toUpperCase() + str.slice(1);
}
export function LoadTemplate(name) {
return require('./tmpl/' + name + '.html');
}

View file

@ -1,5 +1,7 @@
import {capitalize} from 'utils'
export default function ToolBar() {
export default function ToolBar(app) {
this.app = app;
this.node = $('<div>', {
css :{
'position': 'absolute',
@ -10,24 +12,33 @@ export default function ToolBar() {
});
}
ToolBar.prototype.add = function(caption, icon, action) {
ToolBar.prototype.add = function(action) {
if (!action) return;
var btn = $('<div>', {
'class': 'tc-toolbar-btn tc-squeezed-text',
text : caption,
'class': 'tc-toolbar-btn tc-squeezed-text action-item',
text : capitalize(action.label),
css: ToolBar.buttonCss({
'background-image': 'url('+icon+')',
'background-image': 'url('+action.icon96+')',
'background-repeat': 'no-repeat',
'background-position-x': 'center',
'background-position-y': 'top',
'background-size': '48px 48px'
})
});
btn.click(action);
btn.attr('data-action', action.id);
this.app.actionManager.subscribe(action.id, (state) => {
if (state.enabled) {
btn.removeClass('action-disabled');
} else {
btn.addClass('action-disabled');
}
});
this.node.append(btn);
return btn;
};
ToolBar.prototype.addFa = function(faIcon, action) {
if (!action) return;
var btn = $('<div>', {
'class': 'tc-toolbar-btn',
css : {

View file

@ -212,7 +212,6 @@ body {
display: none;
position: absolute;
max-width: 400px;
padding: 5px;
padding: 2px 5px 2px 5px;
.aux-win;
color: #ccc;

View file

@ -166,4 +166,13 @@
white-space: nowrap;
font-size: 12px;
font-family: "Arial Narrow", Arial, sans-serif;
}
.tc-toolbar-btn.action-disabled {
color: #999;
}
.tc-toolbar-btn.action-disabled:hover {
color: #aaa;
background-color: #888;
}