mirror of
https://github.com/xibyte/jsketcher
synced 2025-12-24 17:33:27 +01:00
action subsystem
This commit is contained in:
parent
0156a974d5
commit
79e345bffc
13 changed files with 152 additions and 61 deletions
17
web/app/3d/actions/action-helpers.js
Normal file
17
web/app/3d/actions/action-helpers.js
Normal 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';
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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 = () => {};
|
||||
|
|
|
|||
|
|
@ -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'],
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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' ]
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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":[]}}');
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
};
|
||||
|
|
|
|||
|
|
@ -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');
|
||||
}
|
||||
|
|
@ -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 : {
|
||||
|
|
|
|||
|
|
@ -212,7 +212,6 @@ body {
|
|||
display: none;
|
||||
position: absolute;
|
||||
max-width: 400px;
|
||||
padding: 5px;
|
||||
padding: 2px 5px 2px 5px;
|
||||
.aux-win;
|
||||
color: #ccc;
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
Loading…
Reference in a new issue