diff --git a/web/app/3d/craft/brep/wizards/plane.js b/web/app/3d/craft/brep/wizards/plane.js new file mode 100644 index 00000000..01b128a9 --- /dev/null +++ b/web/app/3d/craft/brep/wizards/plane.js @@ -0,0 +1,50 @@ +import {PreviewWizard, IMAGINARY_SURFACE_MATERIAL} from './preview-wizard' +import {CURRENT_SELECTION as S} from './wizard' +import {AXIS, IDENTITY_BASIS, STANDARD_BASES} from '../../../../math/l3space' +import Vector from '../../../../math/vector' + +const METADATA = [ + ['orientation', 'choice', 'XY', {options: ['XY', 'XZ', 'ZY']}], + ['parallelTo', 'face', S], + ['depth', 'number', 0, {}] +]; + +export class PlaneWizard extends PreviewWizard { + + constructor(app, initialState) { + super(app, 'PLANE', METADATA, initialState); + } + + createPreviewObject(app, params) { + let face = null; + if (params.face) { + face = this.app.findFace(params.face); + } + let basis; + let depth = params.depth; + if (face == null) { + basis = STANDARD_BASES[params.orientation]; + } else { + basis = face.basis(); + depth += face.depth(); + } + + const w = 375, h = 375; + const a = new Vector(-w, -h, 0); + const b = new Vector( w, -h, 0); + const c = new Vector( w, h, 0); + const d = new Vector(-w, h, 0); + + const plane = PreviewWizard.createMesh([[a, b, c], [a, c, d]]) + + const m = new THREE.Matrix4(); + m.makeBasis.apply(m, basis); + const wVec = new THREE.Vector3(0, 0, depth); + wVec.applyMatrix4(m); + m.setPosition(wVec); + plane.geometry.applyMatrix(m); + plane.geometry.computeFaceNormals(); + return plane; + } +} + diff --git a/web/app/3d/craft/brep/wizards/wizard.js b/web/app/3d/craft/brep/wizards/wizard.js index 4769c6d9..6bb34186 100644 --- a/web/app/3d/craft/brep/wizards/wizard.js +++ b/web/app/3d/craft/brep/wizards/wizard.js @@ -1,4 +1,5 @@ import * as tk from '../../../../ui/toolkit' +import {camelCaseSplit} from '../../../../utils/utils' export class Wizard { @@ -21,7 +22,7 @@ export class Wizard { } uiLabel(name) { - return name; + return camelCaseSplit(name).map(w => w.toLowerCase()).join(' '); } focus() { @@ -36,9 +37,9 @@ export class Wizard { const name = def[0]; const type = def[1]; const defaultValue = def[2]; - const params = def[3]; + const params = def[3] || {}; const label = this.uiLabel(name); - const formItem = this.createFormField(name, label, type, params); + const formItem = this.createFormField(name, label, type, params, defaultValue); formItem.setter(defaultValue); tk.add(folder, formItem.ui); this.formFields[name] = formItem; @@ -98,13 +99,20 @@ export class Wizard { this.box.close(); } - createFormField(name, label, type, params) { + createFormField(name, label, type, params, initValue) { if (type == 'number') { - const number = tk.config(new tk.Number(label, 0, params.step, params.round), params); + const number = tk.config(new tk.Number(label, initValue, params.step, params.round), params); number.input.on('t-change', () => this.onUIChange(name)); return Field.fromInput(number, Field.TEXT_TO_NUMBER_COERCION); + } else if (type == 'choice') { + const ops = params.options; + const radio = new tk.InlineRadio(ops, ops, ops.indexOf(initValue)); + radio.root.find('input[type=radio]').on('change', () => { + this.onUIChange(name); + }); + return new Field(radio, () => radio.getValue(), (v) => radio.setValue(v)); } else if (type == 'face') { - const face = new tk.Text(label, ''); + const face = new tk.Text(label, initValue); face.input.on('change', () => this.onUIChange(name)); return Field.fromInput(face, undefined, (faceId) => { if (faceId === CURRENT_SELECTION) { @@ -138,6 +146,4 @@ Field.fromInput = function (inputEl, getterCoercer, setterCoercer) { return new Field(inputEl, () => getterCoercer(inputEl.input.val()), (value) => inputEl.input.val(setterCoercer(value))); }; - - export const CURRENT_SELECTION = {}; \ No newline at end of file diff --git a/web/app/3d/craft/operations.js b/web/app/3d/craft/operations.js index 2d8c1d23..f4d67301 100644 --- a/web/app/3d/craft/operations.js +++ b/web/app/3d/craft/operations.js @@ -1,6 +1,7 @@ import {MESH_OPERATIONS} from './mesh/workbench' import {Extrude, Cut} from './brep/cut-extrude' import {BREPSceneSolid} from '../scene/brep-scene-object' +import {PlaneSceneObject} from '../scene/plane-scene-object' import {box} from '../../brep/brep-primitives' export const CUT = { @@ -49,7 +50,10 @@ export const PLANE = { label: 'Plane', info: (p) => '(' + p.depth + ')', action: (app, request) => { - + return { + outdated: [], + created: [PlaneSceneObject.create(request)] + } } }; diff --git a/web/app/3d/scene/brep-scene-object.js b/web/app/3d/scene/brep-scene-object.js index 16488691..14a10003 100644 --- a/web/app/3d/scene/brep-scene-object.js +++ b/web/app/3d/scene/brep-scene-object.js @@ -28,8 +28,7 @@ export class BREPSceneSolid extends SceneSolid { this.sceneFaces.push(sceneFace); for (let i = g.groupStart; i < g.groupEnd; i ++) { const face = geom.faces[i]; - sceneFace.meshFaces.push(face); - face.__TCAD_SceneFace = sceneFace; + sceneFace.registerMeshFace(face); } } geom.mergeVertices(); diff --git a/web/app/3d/scene/plane-scene-object.js b/web/app/3d/scene/plane-scene-object.js new file mode 100644 index 00000000..eca80296 --- /dev/null +++ b/web/app/3d/scene/plane-scene-object.js @@ -0,0 +1,89 @@ +import Vector from '../../math/vector' +import {STANDARD_BASES} from '../../math/l3space' +import {Plane} from '../../brep/geom/impl/plane' +import {SceneSolid, SceneFace} from './scene-object' + +const INIT_WIDTH_H = 750 * 0.5; +const INIT_HEIGHT_H = 750 * 0.5; + +export const INIT_BOUNDS = [ + new Vector(-INIT_WIDTH_H, -INIT_HEIGHT_H, 0), + new Vector( INIT_WIDTH_H, -INIT_HEIGHT_H, 0), + new Vector( INIT_WIDTH_H, INIT_HEIGHT_H, 0), + new Vector(-INIT_WIDTH_H, INIT_HEIGHT_H, 0) +]; + +export class PlaneSceneObject extends SceneSolid { + + constructor(plane, skin) { + super('PLANE', undefined, Object.assign({ + side : THREE.DoubleSide, + transparent: true, + opacity: 0.5 + }, skin)); + this.plane = plane; + this.sceneFace = new PlaneSceneFace(this); + this.sceneFaces.push(this.sceneFace); // as part of the API + this.updateBounds(INIT_BOUNDS); + } + + createGeometry() { + const geometry = new THREE.Geometry(); + geometry.dynamic = true; + this.bounds.forEach(v => geometry.vertices.push(v.three())); + geometry.faces.push(new THREE.Face3(0, 1, 2)); + geometry.faces.push(new THREE.Face3(0, 2, 3)); + geometry.faces.forEach(f => this.sceneFace.registerMeshFace(f)); + geometry.computeFaceNormals(); + this.mesh = new THREE.Mesh(geometry, this.material); + this.cadGroup.add(this.mesh); + } + + dropGeometry() { + if (this.mesh) { + this.cadGroup.remove( this.mesh ); + this.mesh.geometry.dispose(); + this.sceneFace.meshFaces = []; + } + } + + updateBounds(bounds2d) { + this.dropGeometry(); + const tr = this.plane.get3DTransformation(); + this.bounds = bounds2d.map(v => tr.apply(v.plusXYZ(0, 0, this.plane.w))); + this.createGeometry(); + } + + static create(params, faceResolver) { + let face = null; + if (params.face) { + face = faceResolver(params.face); + } + let plane = null; + if (face == null) { + const normal = STANDARD_BASES[params.orientation][2]; + plane = new Plane(normal, params.depth); + } else { + plane = new Plane(face.normal(), params.depth); + } + return new PlaneSceneObject(plane); + } +} + +class PlaneSceneFace extends SceneFace { + constructor(scenePlane) { + super(scenePlane); + } + + normal() { + return this.solid.plane.normal; + } + + depth() { + return this.solid.plane.w; + } + + getBounds() { + return []; + } +} diff --git a/web/app/3d/scene/scene-object.js b/web/app/3d/scene/scene-object.js index ca0ae65c..4222aaae 100644 --- a/web/app/3d/scene/scene-object.js +++ b/web/app/3d/scene/scene-object.js @@ -98,11 +98,15 @@ export class SceneFace { createMeshFace(a, b, c) { const face = new THREE.Face3(a, b, c); - this.meshFaces.push(face); - face.__TCAD_SceneFace = this; + this.registerMeshFace(face); return face; } - + + registerMeshFace(threeFace) { + this.meshFaces.push(threeFace); + threeFace.__TCAD_SceneFace = this; + } + syncSketches(geom) { const normal = this.normal(); const offVector = new Vector();//normal.multiply(0); // disable it. use polygon offset feature of material diff --git a/web/app/3d/ui/ctrl.js b/web/app/3d/ui/ctrl.js index 7bed6980..8cf76e86 100644 --- a/web/app/3d/ui/ctrl.js +++ b/web/app/3d/ui/ctrl.js @@ -9,7 +9,7 @@ import Menu from '../menu/menu' import {ExtrudeWizard, CutWizard} from '../craft/brep/wizards/cut-extrude' import {RevolveWizard} from '../craft/mesh/wizards/revolve' -import {PlaneWizard} from '../craft/mesh/wizards/plane' +import {PlaneWizard} from '../craft/brep/wizards/plane' import {BoxWizard} from '../craft/brep/wizards/box' import {SphereWizard} from '../craft/mesh/wizards/sphere' import {TransformWizard} from '../craft/mesh/wizards/transform' diff --git a/web/app/math/l3space.js b/web/app/math/l3space.js index ea4f7b01..7faa03ca 100644 --- a/web/app/math/l3space.js +++ b/web/app/math/l3space.js @@ -10,6 +10,13 @@ var AXIS = { var IDENTITY_BASIS = [AXIS.X, AXIS.Y, AXIS.Z]; +export const STANDARD_BASES = { + 'XY': IDENTITY_BASIS, + 'XZ': [AXIS.X, AXIS.Z, AXIS.Y], + 'ZY': [AXIS.Z, AXIS.Y, AXIS.X] +}; + + /** @constructor */ function Matrix3() { this.reset(); diff --git a/web/app/math/vector.js b/web/app/math/vector.js index d786269d..3e216167 100644 --- a/web/app/math/vector.js +++ b/web/app/math/vector.js @@ -74,6 +74,10 @@ Vector.prototype._minusXYZ = function(x, y, z) { return this; }; +Vector.prototype.plusXYZ = function(x, y, z) { + return new Vector(this.x + x, this.y + y, this.z + z); +}; + Vector.prototype.plus = function(vector) { return new Vector(this.x + vector.x, this.y + vector.y, this.z + vector.z); }; diff --git a/web/app/ui/toolkit.js b/web/app/ui/toolkit.js index c9c8fead..5eb749a1 100644 --- a/web/app/ui/toolkit.js +++ b/web/app/ui/toolkit.js @@ -71,6 +71,11 @@ InlineRadio.prototype.getValue = function() { return null; }; +InlineRadio.prototype.setValue = function(v) { + this.root.find('input[value='+v+']').prop('checked', true); +}; + + InlineRadio.COUNTER = 0; export function propLayout(root, name, valueEl) { diff --git a/web/app/utils/utils.js b/web/app/utils/utils.js index d9d2240c..08787a65 100644 --- a/web/app/utils/utils.js +++ b/web/app/utils/utils.js @@ -39,4 +39,30 @@ export function swap(arr, i1, i2) { const tmp = arr[i1]; arr[i1] = arr[i2]; arr[i2] = tmp; -} \ No newline at end of file +} + +export function camelCaseSplit(str) { + function isUpperCase(str) { + return str.toUpperCase() == str; + } + + const words = []; + let word = ''; + + for (let i = 0; i < str.length; i++) { + const c = str.charAt(i); + if (c == '_' || c == '-') { + continue; + } + const dot = c === '.'; + if ((dot || isUpperCase(c)) && word.length != 0) { + words.push(word); + word = ''; + } + if (!dot) word += c; + } + if (word.length != 0){ + words.push(word); + } + return words; +}