diff --git a/web/app/3d/actions/operation-actions.js b/web/app/3d/actions/operation-actions.js index 023a274b..3f807af1 100644 --- a/web/app/3d/actions/operation-actions.js +++ b/web/app/3d/actions/operation-actions.js @@ -1,4 +1,4 @@ -import * as Operations from '../operations' +import * as Operations from '../craft/operations' import * as ActionHelpers from './action-helpers' function mergeInfo(opName, action) { diff --git a/web/app/3d/craft/brep/cut-extrude.js b/web/app/3d/craft/brep/cut-extrude.js new file mode 100644 index 00000000..249babc1 --- /dev/null +++ b/web/app/3d/craft/brep/cut-extrude.js @@ -0,0 +1,50 @@ +import {Matrix3, ORIGIN} from '../../../math/l3space' +import * as math from '../../../math/math' +import Vector from '../../../math/vector' +import {Extruder} from '../../../brep/brep-builder' + +export function Extrude(app, params) { + +} + + + + +export function Cut(face, params) { + +} + +export class ParametricExtruder extends Extruder { + + constructor(face, params) { + super(); + this.face = face; + this.params = params; + } + + prepareLidCalculation(baseNormal, lidNormal) { + let target; + if (this.params.rotation != 0) { + const basis = this.face.basis(); + target = Matrix3.rotateMatrix(this.params.rotation * Math.PI / 180, basis[0], ORIGIN).apply(lidNormal); + if (this.params.angle != 0) { + target = Matrix3.rotateMatrix(this.params.angle * Math.PI / 180, basis[2], ORIGIN)._apply(target); + } + target._multiply(Math.abs(this.params.value)); + } else { + target = normal.multiply(Math.abs(this.params.value)); + } + this.target = target; + } + + calculateLid(basePoints) { + if (this.params.prism != 1) { + const scale = this.params.prism < 0.001 ? 0.0001 : this.params.prism; + const _3Dtr = this.face.surface.get3DTransformation(); + const _2Dtr = _3Dtr.invert(); + const poly2d = basePoints.map(p => _2Dtr.apply(p)); + basePoints = math.polygonOffset(poly2d, scale).map(p => _2Dtr.apply(p)); + } + return basePoints.map(p => p.plus(this.target)); + } +} diff --git a/web/app/3d/craft/brep/sketch-reader.js b/web/app/3d/craft/brep/sketch-reader.js new file mode 100644 index 00000000..5d146f77 --- /dev/null +++ b/web/app/3d/craft/brep/sketch-reader.js @@ -0,0 +1,9 @@ +import {sortPolygons, getSketchedPolygons3D} from '../mesh/workbench' + + + +// here will be function of conversion sketch objects to brep DS + +export function ReadSketchFromFace(app, faceId) { + return getSketchedPolygons3D(app, faceId); +} \ No newline at end of file diff --git a/web/app/3d/craft/brep/wizards/cut-extrude.js b/web/app/3d/craft/brep/wizards/cut-extrude.js new file mode 100644 index 00000000..83938a0e --- /dev/null +++ b/web/app/3d/craft/brep/wizards/cut-extrude.js @@ -0,0 +1,61 @@ +import {CURRENT_SELECTION as S} from './wizard' +import {PreviewWizard, SketchBasedPreviewMaker} from './preview-wizard' +import {ParametricExtruder} from '../cut-extrude' + +const METADATA = [ + ['value' , 'number', 50, {min: 0}], + ['prism' , 'number', 1 , {min: 0, step: 0.1, round: 1}], + ['angle' , 'number', 0 , {}], + ['rotation', 'number', 0 , {step: 5}], + ['face' , 'face' , S ] +]; + +class Cut extends PreviewWizard { + constructor(app, initialState) { + super(app, 'CUT', METADATA, null, initialState) + } + + uiLabel(name) { + if ('value' == name) return 'depth'; + return super.uiLabel(name); + } +} + +class Extrude extends PreviewWizard { + constructor(app, initialState) { + super(app, 'EXTRUDE', METADATA, new ExtrudePreviewMaker(), initialState) + } + + uiLabel(name) { + if ('value' == name) return 'height'; + return super.uiLabel(name); + } +} + +export class ExtrudePreviewMaker extends SketchBasedPreviewMaker{ + + constructor(cut) { + super(); + this.cut = cut; + } + + createImpl(app, params, sketch, face) { + const parametricExtruder = new ParametricExtruder(face, params); + + const baseNormal = this.cut ? face.surface.normal : face.surface.normal.negate(); + const lidNormal = this.cut ? baseNormal.negate() : face.surface.normal; + + parametricExtruder.prepareLidCalculation(baseNormal, lidNormal); + + const triangles = []; + for (let base of sketch) { + var lid = parametricExtruder.calculateLid(base); + const n = base.length; + for (let p = n - 1, q = 0; q < n; p = q ++) { + triangles.push([ base[p], base[q], lid[q] ]); + triangles.push([ lid[q], lid[p], base[p] ]); + } + } + return triangles; + } +} \ No newline at end of file diff --git a/web/app/3d/craft/brep/wizards/preview-wizard.js b/web/app/3d/craft/brep/wizards/preview-wizard.js new file mode 100644 index 00000000..8ccfb673 --- /dev/null +++ b/web/app/3d/craft/brep/wizards/preview-wizard.js @@ -0,0 +1,92 @@ +import {Wizard} from './wizard' +import {ReadSketchFromFace} from '../sketch-reader' +import {Loop} from '../../../../brep/topo/loop' + +export class PreviewWizard extends Wizard { + + constructor(app, opearation, metadata, previewMaker, initialState) { + super(app, opearation, metadata, initialState); + this.previewGroup = new THREE.Object3D(); + this.previewMaker = previewMaker; + this.previewObject = null; + this.app.viewer.workGroup.add(this.previewGroup); + this.updatePreview(); + } + + updatePreview() { + if (this.previewObject != null) { + this.destroyPreviewObject(); + } + this.previewObject = this.previewMaker.create(this.app, this.readFormFields()); + if (this.previewObject != null) { + this.previewGroup.add( this.previewObject ); + } + this.app.viewer.render(); + } + + destroyPreviewObject() { + this.previewGroup.parent.remove( this.previewObject ); + this.previewObject.geometry.dispose(); + this.previewGroup = null; + } + + + dispose() { + this.app.viewer.workGroup.remove(this.previewGroup); + super.dispose(); + } +} + +PreviewWizard.createMesh = function(triangles) { + const geometry = new THREE.Geometry(); + + for (let tr of triangles) { + const a = geometry.vertices.length; + const b = a + 1; + const c = a + 2; + const face = new THREE.Face3(a, b, c); + tr.forEach(v => geometry.vertices.push(v.three())); + geometry.faces.push(face); + } + geometry.mergeVertices(); + geometry.computeFaceNormals(); + + return new THREE.Mesh(geometry, IMAGINARY_SURFACE_MATERIAL); +}; + +export class SketchBasedPreviewMaker { + + constructor() { + this.fixToCCW = true; + } + + createImpl(app, params, sketch, face) { + throw 'not implemented'; + } + + create(app, params) { + const face = app.findFace(params.face); + if (!face) return null; + const needSketchRead = !this.sketch || params.face != this.face; + if (needSketchRead) { + this.sketch = ReadSketchFromFace(app, params.face); + for (let polygon of this.sketch) { + if (!Loop.isPolygonCCWOnSurface(polygon, face.surface) && this.fixToCCW) { + polygon.reverse(); + } + } + this.face = params.face; + } + return this.createImpl(app, params, this.sketch, face); + } +} + +export const IMAGINARY_SURFACE_MATERIAL = new THREE.MeshPhongMaterial({ + vertexColors: THREE.FaceColors, + color: 0xFA8072, + transparent: true, + opacity: 0.5, + shininess: 0, + side : THREE.DoubleSide +}); + diff --git a/web/app/3d/craft/brep/wizards/wizard.js b/web/app/3d/craft/brep/wizards/wizard.js new file mode 100644 index 00000000..3ac1e438 --- /dev/null +++ b/web/app/3d/craft/brep/wizards/wizard.js @@ -0,0 +1,118 @@ +import * as tk from '../../../../ui/toolkit' + +export class Wizard { + + constructor(app, opearation, metadata, initialState) { + this.app = app; + this.metadata = params; + this.formFields = {}; + this.box = this.createUI(opearation, metadata); + if (initialState != undefined) { + this.setFormFields(initialState); + } + } + + uiLabel(name) { + return name; + } + + createUI(operation, metadata) { + const box = new tk.Box($('#view-3d')); + const folder = new tk.Folder(operation); + tk.add(box, folder); + for (let def of metadata) { + const name = def[0]; + const type = def[1]; + const defaultValue = def[1]; + const params = def[3]; + const label = this.uiLabel(name); + const formItem = createFormField(name, label, type, defaultValue, params); + formItem.setter(defaultValue); + tk.add(folder, formItem.ui); + this.formFields[name] = formItem; + } + const buttons = new tk.ButtonRow(["Cancel", "OK"], [() => this.cancelClick(), () => this.okClick()]); + tk.add(folder, buttons); + box.root.keydown((e) => { + switch (e.keyCode) { + case 27 : this.cancelClick(); break; + case 13 : this.okClick(); break; + } + }); + + return box; + } + + cancelClick() { + this.dispose(); + } + + okClick() { + this.dispose(); + } + + onUIChange() { + + } + + readFormFields() { + const params = {}; + for (let field of this.formFields) { + params[field.name] = field.getter(); + } + return params; + } + + setFormFields(params) { + const keys = Object.keys(params); + for (let key of keys) { + const formField = this.formFields[name]; + if (formField) { + formField.setter(params[key]); + } + } + } + + dispose() { + this.disposed = true; + this.box.close(); + } + + createFormField(name, label, type, params) { + if (type == 'number') { + const number = tk.config(tk.Number(label, 0, params.step, params.round), params); + number.input.on('t-change', () => this.onUIChange(name)); + return Field.fromInput(number.input); + } else if (type == 'face') { + const face = new tk.Text(label, ''); + face.input.on('change', () => this.onUIChange(name)); + return Field.fromInput(face.input, undefined, (faceId) => { + if (faceId === CURRENT_SELECTION) { + let selection = this.app.viewer.selectionMgr.selection[0]; + return selection ? selection.id : ''; + } + }); + } + } +} + +function FaceSelectionListener() { + this.callbacks = []; +} + +function Field(getter, setter) { + this.getter = getter; + this.setter = setter; +} + +Field.NO_COERCION = (v) => v; + +Field.fromInput = function (input, getterCoercer, setterCoercer) { + getterCoercer = getterCoercer || Field.NO_COERCION; + setterCoercer = setterCoercer || Field.NO_COERCION; + return new Field(() => getterCoercer(input.val()), (value) => input.val(setterCoercer(value))); +}; + + + +export const CURRENT_SELECTION = {}; \ No newline at end of file diff --git a/web/app/3d/craft/craft.js b/web/app/3d/craft/craft.js new file mode 100644 index 00000000..7a39c1aa --- /dev/null +++ b/web/app/3d/craft/craft.js @@ -0,0 +1,106 @@ +import Counters from '../counters' + +export function Craft(app) { + this.app = app; + this.history = []; + this.solids = []; + this._historyPointer = 0; + Object.defineProperty(this, "historyPointer", { + get: function() {return this._historyPointer}, + set: function(value) { + if (this._historyPointer === value) return; + this._historyPointer = value; + this.reset(this.history.slice(0, this._historyPointer)); + this.app.bus.notify('craft'); + this.app.bus.notify('historyPointer'); + this.app.viewer.render(); + } + }); +} + +Craft.prototype.remove = function(modificationIndex) { + const history = this.history; + history.splice(modificationIndex, history.length - modificationIndex); + + if (this.historyPointer >= history.length) { + this.finishHistoryEditing(); + } else { + this.app.bus.notify('historyShrink'); + } +}; + +Craft.prototype.loadHistory = function(history) { + this.history = history; + this._historyPointer = history.length; + this.reset(history); + this.app.bus.notify('craft'); + this.app.bus.notify('historyPointer'); + this.app.viewer.render(); +}; + +Craft.prototype.reset = function(modifications) { + Counters.solid = 0; + Counters.shared = 0; + this.solids = []; + this.app.findAllSolids().forEach(function(s) {s.vanish()}); + for (var i = 0; i < modifications.length; i++) { + const request = modifications[i]; + this.modifyInternal(request); + } +}; + +Craft.prototype.finishHistoryEditing = function() { + this.loadHistory(this.history); +}; + +Craft.prototype.current = function() { + return this.history[this.history.length - 1]; +}; + + +Craft.prototype.modifyInternal = function(request) { + var op = this.operations[request.type]; + if (!op) return; + + var newSolids = op(this.app, request.params); + if (newSolids == null) return; + const toUpdate = []; + for (let i = 0; i < request.solids.length; i++) { + let solid = request.solids[i]; + var indexToRemove = this.solids.indexOf(solid); + if (indexToRemove != -1) { + let updatedIdx = newSolids.findIndex((s) => s.id == solid.id); + if (updatedIdx != -1) { + toUpdate[updatedIdx] = indexToRemove; + } else { + this.solids.splice(indexToRemove, 1); + } + } + solid.vanish(); + } + for (let i = 0; i < newSolids.length; i++) { + let solid = newSolids[i]; + if (toUpdate[i] !== undefined) { + this.solids[toUpdate[i]] = solid; + } else { + this.solids.push(solid); + } + this.app.viewer.workGroup.add(solid.cadGroup); + } + this.app.bus.notify('solid-list', { + solids: this.solids, + needRefresh: newSolids + }); +}; + +Craft.prototype.modify = function(request, overriding) { + this.modifyInternal(request); + if (!overriding && this._historyPointer != this.history.length) { + this.history.splice(this._historyPointer + 1, 0, null); + } + this.history[this._historyPointer] = request; + this._historyPointer ++; + this.app.bus.notify('craft'); + this.app.bus.notify('historyPointer'); + this.app.viewer.render(); +}; \ No newline at end of file diff --git a/web/app/3d/revolve.js b/web/app/3d/craft/mesh/revolve.js similarity index 95% rename from web/app/3d/revolve.js rename to web/app/3d/craft/mesh/revolve.js index e2f69654..f743c897 100644 --- a/web/app/3d/revolve.js +++ b/web/app/3d/craft/mesh/revolve.js @@ -1,7 +1,7 @@ -import {Matrix3} from '../math/l3space' -import Vector from '../math/vector' -import * as math from '../math/math' -import {createShared} from './cad-utils' +import {Matrix3} from '../../../math/l3space' +import Vector from '../../../math/vector' +import * as math from '../../../math/math' +import {createShared} from '../../cad-utils' function Group(derivedFrom) { this.polygons = []; diff --git a/web/app/3d/wizards/box.js b/web/app/3d/craft/mesh/wizards/box.js similarity index 93% rename from web/app/3d/wizards/box.js rename to web/app/3d/craft/mesh/wizards/box.js index ecdb6791..e70adc8b 100644 --- a/web/app/3d/wizards/box.js +++ b/web/app/3d/craft/mesh/wizards/box.js @@ -1,6 +1,6 @@ -import {AXIS, IDENTITY_BASIS} from '../../math/l3space' -import * as tk from '../../ui/toolkit.js' -import {FACE_COLOR} from '../cad-utils' +import {AXIS, IDENTITY_BASIS} from '../../../../math/l3space' +import * as tk from '../../../../ui/toolkit.js' +import {FACE_COLOR} from '../../../cad-utils' import {Wizard} from './wizard-commons' export function BoxWizard(viewer, initParams) { diff --git a/web/app/3d/wizards/extrude.js b/web/app/3d/craft/mesh/wizards/extrude.js similarity index 94% rename from web/app/3d/wizards/extrude.js rename to web/app/3d/craft/mesh/wizards/extrude.js index 55ad0d4d..2a8703f7 100644 --- a/web/app/3d/wizards/extrude.js +++ b/web/app/3d/craft/mesh/wizards/extrude.js @@ -1,8 +1,8 @@ -import * as tk from '../../ui/toolkit.js' +import * as tk from '../../../../ui/toolkit.js' import * as workbench from '../workbench' -import * as cad_utils from '../cad-utils' -import Vector from '../../math/vector' -import {Matrix3, ORIGIN} from '../../math/l3space' +import * as cad_utils from '../../../cad-utils' +import Vector from '../../../../math/vector' +import {Matrix3, ORIGIN} from '../../../../math/l3space' import {OpWizard, IMAGINE_MATERIAL, BASE_MATERIAL} from './wizard-commons' export function ExtrudeWizard(app, face, invert, initParams) { diff --git a/web/app/3d/wizards/import.js b/web/app/3d/craft/mesh/wizards/import.js similarity index 86% rename from web/app/3d/wizards/import.js rename to web/app/3d/craft/mesh/wizards/import.js index 903ff689..27492631 100644 --- a/web/app/3d/wizards/import.js +++ b/web/app/3d/craft/mesh/wizards/import.js @@ -1,9 +1,9 @@ -import * as tk from '../../ui/toolkit.js' +import * as tk from '../../../../ui/toolkit.js' import * as workbench from '../workbench' -import * as cad_utils from '../cad-utils' -import Vector from '../../math/vector' +import * as cad_utils from '../../../cad-utils' +import Vector from '../../../../math/vector' import {Wizard} from './wizard-commons' -import {LoadSTLFromURL} from '../io' +import {LoadSTLFromURL} from '../../../io' export function ImportWizard(viewer, initParams) { Wizard.call(this, viewer, initParams); diff --git a/web/app/3d/wizards/plane.js b/web/app/3d/craft/mesh/wizards/plane.js similarity index 95% rename from web/app/3d/wizards/plane.js rename to web/app/3d/craft/mesh/wizards/plane.js index cd738c07..1cd7b624 100644 --- a/web/app/3d/wizards/plane.js +++ b/web/app/3d/craft/mesh/wizards/plane.js @@ -1,8 +1,8 @@ -import {AXIS, IDENTITY_BASIS} from '../../math/l3space' -import * as tk from '../../ui/toolkit.js' -import {FACE_COLOR} from '../cad-utils' +import {AXIS, IDENTITY_BASIS} from '../../../../math/l3space' +import * as tk from '../../../../ui/toolkit.js' +import {FACE_COLOR} from '../../../cad-utils' import {Wizard} from './wizard-commons' -import {Matrix3} from '../../math/l3space' +import {Matrix3} from '../../../../math/l3space' export function PlaneWizard(app, initParams) { Wizard.call(this, app.viewer, initParams); diff --git a/web/app/3d/wizards/revolve.js b/web/app/3d/craft/mesh/wizards/revolve.js similarity index 96% rename from web/app/3d/wizards/revolve.js rename to web/app/3d/craft/mesh/wizards/revolve.js index 31ebefdc..5115d910 100644 --- a/web/app/3d/wizards/revolve.js +++ b/web/app/3d/craft/mesh/wizards/revolve.js @@ -1,8 +1,8 @@ -import * as tk from '../../ui/toolkit.js' +import * as tk from '../../../../ui/toolkit.js' import * as workbench from '../workbench' -import * as cad_utils from '../cad-utils' -import Vector from '../../math/vector' -import {Matrix3, ORIGIN} from '../../math/l3space' +import * as cad_utils from '../../../cad-utils' +import Vector from '../../../../math/vector' +import {Matrix3, ORIGIN} from '../../../../math/l3space' import {revolveToTriangles} from '../revolve' import {OpWizard, IMAGINARY_SURFACE_MATERIAL, } from './wizard-commons' diff --git a/web/app/3d/wizards/sphere.js b/web/app/3d/craft/mesh/wizards/sphere.js similarity index 91% rename from web/app/3d/wizards/sphere.js rename to web/app/3d/craft/mesh/wizards/sphere.js index 120973b0..f1a7e998 100644 --- a/web/app/3d/wizards/sphere.js +++ b/web/app/3d/craft/mesh/wizards/sphere.js @@ -1,6 +1,6 @@ -import {AXIS, IDENTITY_BASIS} from '../../math/l3space' -import * as tk from '../../ui/toolkit.js' -import {FACE_COLOR} from '../cad-utils' +import {AXIS, IDENTITY_BASIS} from '../../../../math/l3space' +import * as tk from '../../../../ui/toolkit.js' +import {FACE_COLOR} from '../../../cad-utils' import {Wizard} from './wizard-commons' export function SphereWizard(viewer, initParams) { diff --git a/web/app/3d/wizards/transform.js b/web/app/3d/craft/mesh/wizards/transform.js similarity index 96% rename from web/app/3d/wizards/transform.js rename to web/app/3d/craft/mesh/wizards/transform.js index b348124a..4d89caf9 100644 --- a/web/app/3d/wizards/transform.js +++ b/web/app/3d/craft/mesh/wizards/transform.js @@ -1,6 +1,6 @@ -import {AXIS, IDENTITY_BASIS} from '../../math/l3space' -import * as tk from '../../ui/toolkit.js' -import {FACE_COLOR} from '../cad-utils' +import {AXIS, IDENTITY_BASIS} from '../../../../math/l3space' +import * as tk from '../../../../ui/toolkit.js' +import {FACE_COLOR} from '../../../cad-utils' import {Wizard} from './wizard-commons' export function TransformWizard(viewer, solid, initParams) { diff --git a/web/app/3d/wizards/wizard-commons.js b/web/app/3d/craft/mesh/wizards/wizard-commons.js similarity index 97% rename from web/app/3d/wizards/wizard-commons.js rename to web/app/3d/craft/mesh/wizards/wizard-commons.js index 5234318a..ea5ae0fa 100644 --- a/web/app/3d/wizards/wizard-commons.js +++ b/web/app/3d/craft/mesh/wizards/wizard-commons.js @@ -1,5 +1,5 @@ -import DPR from '../../utils/dpr' -import * as tk from '../../ui/toolkit' +import DPR from '../../../../utils/dpr' +import * as tk from '../../../../ui/toolkit' const IMAGINE_MATERIAL = new THREE.LineBasicMaterial({ color: 0xFA8072, diff --git a/web/app/3d/workbench.js b/web/app/3d/craft/mesh/workbench.js similarity index 87% rename from web/app/3d/workbench.js rename to web/app/3d/craft/mesh/workbench.js index 3579d797..e475997d 100644 --- a/web/app/3d/workbench.js +++ b/web/app/3d/craft/mesh/workbench.js @@ -1,14 +1,13 @@ -import Vector from '../math/vector' -import * as cad_utils from './cad-utils' -import * as math from '../math/math' -import {LUT} from '../math/bezier-cubic' -import {Matrix3, AXIS, ORIGIN} from '../math/l3space' -import {HashTable} from '../utils/hashmap' -import Counters from './counters' -import {Mesh} from './mesh' -import {LoadSTLFromURL} from './io' +import Vector from '../../../math/vector' +import * as cad_utils from '../../cad-utils' +import * as math from '../../../math/math' +import {LUT} from '../../../math/bezier-cubic' +import {Matrix3, AXIS, ORIGIN} from '../../../math/l3space' +import {HashTable} from '../../../utils/hashmap' +import {Mesh} from '../../mesh' +import {LoadSTLFromURL} from '../../io' import revolve from './revolve' -import {Triangulate} from './triangulation' +import {Triangulate} from '../../triangulation' function SketchConnection(a, b, sketchObject) { this.a = a; @@ -200,7 +199,7 @@ export function getSketchedPolygons3D(app, face) { return sketchedPolygons; } -function sortPolygons(polygons) { +export function sortPolygons(polygons) { function Loop(polygon) { this.polygon = polygon; this.nesting = []; @@ -742,63 +741,6 @@ function recoverySketchInfo(polygons) { } } -export function Craft(app) { - this.app = app; - this.history = []; - this.solids = []; - this._historyPointer = 0; - Object.defineProperty(this, "historyPointer", { - get: function() {return this._historyPointer}, - set: function(value) { - if (this._historyPointer === value) return; - this._historyPointer = value; - this.reset(this.history.slice(0, this._historyPointer)); - this.app.bus.notify('craft'); - this.app.bus.notify('historyPointer'); - this.app.viewer.render(); - } - }); -} - -Craft.prototype.remove = function(modificationIndex) { - const history = this.history; - history.splice(modificationIndex, history.length - modificationIndex); - - if (this.historyPointer >= history.length) { - this.finishHistoryEditing(); - } else { - this.app.bus.notify('historyShrink'); - } -}; - -Craft.prototype.loadHistory = function(history) { - this.history = history; - this._historyPointer = history.length; - this.reset(history); - this.app.bus.notify('craft'); - this.app.bus.notify('historyPointer'); - this.app.viewer.render(); -}; - -Craft.prototype.reset = function(modifications) { - Counters.solid = 0; - Counters.shared = 0; - this.solids = []; - this.app.findAllSolids().forEach(function(s) {s.vanish()}); - for (var i = 0; i < modifications.length; i++) { - var request = materialize(this.app.indexEntities(), modifications[i]); - this.modifyInternal(request); - } -}; - -Craft.prototype.finishHistoryEditing = function() { - this.loadHistory(this.history); -}; - -Craft.prototype.current = function() { - return this.history[this.history.length - 1]; -}; - function detach(request) { var detachedConfig = {}; for (var prop in request) { @@ -849,55 +791,7 @@ function materialize(index, detachedConfig) { return request; } -Craft.prototype.modifyInternal = function(request) { - var op = OPERATIONS[request.type]; - if (!op) return; - - var newSolids = op(this.app, request); - if (newSolids == null) return; - const toUpdate = []; - for (let i = 0; i < request.solids.length; i++) { - let solid = request.solids[i]; - var indexToRemove = this.solids.indexOf(solid); - if (indexToRemove != -1) { - let updatedIdx = newSolids.findIndex((s) => s.id == solid.id); - if (updatedIdx != -1) { - toUpdate[updatedIdx] = indexToRemove; - } else { - this.solids.splice(indexToRemove, 1); - } - } - solid.vanish(); - } - for (let i = 0; i < newSolids.length; i++) { - let solid = newSolids[i]; - if (toUpdate[i] !== undefined) { - this.solids[toUpdate[i]] = solid; - } else { - this.solids.push(solid); - } - this.app.viewer.workGroup.add(solid.cadGroup); - } - this.app.bus.notify('solid-list', { - solids: this.solids, - needRefresh: newSolids - }); -}; - -Craft.prototype.modify = function(request, overriding) { - this.modifyInternal(request); - var detachedRequest = detach(request); - if (!overriding && this._historyPointer != this.history.length) { - this.history.splice(this._historyPointer + 1, 0, null); - } - this.history[this._historyPointer] = detachedRequest; - this._historyPointer ++; - this.app.bus.notify('craft'); - this.app.bus.notify('historyPointer'); - this.app.viewer.render(); -}; - -export const OPERATIONS = { +export const MESH_OPERATIONS = { CUT : cut, PAD : extrude, REVOLVE : performRevolve, diff --git a/web/app/3d/operations.js b/web/app/3d/craft/operations.js similarity index 63% rename from web/app/3d/operations.js rename to web/app/3d/craft/operations.js index 461655f3..4b90c01d 100644 --- a/web/app/3d/operations.js +++ b/web/app/3d/craft/operations.js @@ -1,21 +1,30 @@ -import * as math from '../math/math' +import {MESH_OPERATIONS} from './mesh/workbench' +import {Extrude} from './brep/cut-extrude' export const CUT = { icon: 'img/3d/cut', label: 'Cut', - info: (p) => '(' + r(math.norm2(p.target)) + ')' + info: (p) => '(' + r(p.depth) + ')', + action: (app, request) => { + } }; export const PAD = { icon: 'img/3d/extrude', label: 'Extrude', - info: (p) => '(' + r(math.norm2(p.target)) + ')' + info: (p) => '(' + r(p.height) + ')', + action: (app, request) => { + + } }; export const REVOLVE = { icon: 'img/3d/revolve', label: 'Revolve', - info: (p) => '(' + p.angle + ')' + info: (p) => '(' + p.angle + ')', + action: (app, request) => { + + } }; export const SHELL = { @@ -27,19 +36,28 @@ export const SHELL = { export const BOX = { icon: 'img/3d/cube', label: 'Box', - info: (p) => '(' + p.w + ', ' + p.h + ', ' + p.d + ')' + info: (p) => '(' + p.w + ', ' + p.h + ', ' + p.d + ')', + action: (app, request) => { + + } }; export const PLANE = { icon: 'img/3d/plane', label: 'Plane', - info: (p) => '(' + p.depth + ')' + info: (p) => '(' + p.depth + ')', + action: (app, request) => { + + } }; export const SPHERE = { icon: 'img/3d/sphere', label: 'Sphere', - info: (p) => '(' + p.radius + ')' + info: (p) => '(' + p.radius + ')', + action: (app, request) => { + + } }; export const INTERSECTION = { @@ -63,7 +81,10 @@ export const UNION = { export const IMPORT_STL = { icon: 'img/3d/stl', label: 'STL Import', - info: (p) => '(' + p.url.substring(p.url.lastIndexOf('/') + 1 ) + ')' + info: (p) => '(' + p.url.substring(p.url.lastIndexOf('/') + 1 ) + ')', + action: (app, request) => { + + } }; function r(value) { diff --git a/web/app/3d/modeler-app.js b/web/app/3d/modeler-app.js index 4ef3f018..c215de4d 100644 --- a/web/app/3d/modeler-app.js +++ b/web/app/3d/modeler-app.js @@ -8,7 +8,8 @@ import {ActionManager} from './actions/actions' import * as AllActions from './actions/all-actions' import Vector from '../math/vector' import {Matrix3, AXIS, ORIGIN, IDENTITY_BASIS} from '../math/l3space' -import * as workbench from './workbench' +import {Craft} from './craft/craft' +import * as workbench from './craft/mesh/workbench' import * as cad_utils from './cad-utils' import * as math from '../math/math' import {IO} from '../sketcher/io' @@ -33,7 +34,7 @@ function App() { this.controlBar = new ControlBar(this, $('#control-bar')); this.ui = new UI(this); - this.craft = new workbench.Craft(this); + this.craft = new Craft(this); AddDebugSupport(this); diff --git a/web/app/3d/scene/mesh-scene-object.js b/web/app/3d/scene/mesh-scene-object.js index 44768432..6fa97266 100644 --- a/web/app/3d/scene/mesh-scene-object.js +++ b/web/app/3d/scene/mesh-scene-object.js @@ -1,7 +1,7 @@ import {HashTable} from '../../utils/hashmap' import Vector from '../../math/vector' import Counters from '../counters' -import {findOutline, segmentsToPaths, reconstructSketchBounds} from '../workbench' +import {findOutline, segmentsToPaths, reconstructSketchBounds} from '../craft/mesh/workbench' import {Matrix3, AXIS} from '../../math/l3space' import {arrFlatten1L, isCurveClass} from '../cad-utils' import DPR from '../../utils/dpr' diff --git a/web/app/3d/scene/scene-object.js b/web/app/3d/scene/scene-object.js index 77315b34..c0b1cb64 100644 --- a/web/app/3d/scene/scene-object.js +++ b/web/app/3d/scene/scene-object.js @@ -1,7 +1,6 @@ import {HashTable} from '../../utils/hashmap' import Vector from '../../math/vector' import Counters from '../counters' -import {findOutline, segmentsToPaths} from '../workbench' import {Matrix3, AXIS} from '../../math/l3space' import {arrFlatten1L, isCurveClass} from '../cad-utils' import DPR from '../../utils/dpr' diff --git a/web/app/3d/ui/ctrl.js b/web/app/3d/ui/ctrl.js index 6b26932e..5709a4b5 100644 --- a/web/app/3d/ui/ctrl.js +++ b/web/app/3d/ui/ctrl.js @@ -1,18 +1,18 @@ import * as tk from '../../ui/toolkit' import * as cad_utils from '../cad-utils' import * as math from '../../math/math' -import * as workbench from '../workbench' +import * as workbench from '../craft/mesh/workbench' import ToolBar from './toolbar' import * as MenuConfig from '../menu/menu-config' -import * as Operations from '../operations' +import * as Operations from '../craft/operations' import Menu from '../menu/menu' -import {ExtrudeWizard} from '../wizards/extrude' -import {RevolveWizard} from '../wizards/revolve' -import {PlaneWizard} from '../wizards/plane' -import {BoxWizard} from '../wizards/box' -import {SphereWizard} from '../wizards/sphere' -import {TransformWizard} from '../wizards/transform' -import {ImportWizard} from '../wizards/import' +import {ExtrudeWizard} from '../craft/mesh/wizards/extrude' +import {RevolveWizard} from '../craft/mesh/wizards/revolve' +import {PlaneWizard} from '../craft/mesh/wizards/plane' +import {BoxWizard} from '../craft/mesh/wizards/box' +import {SphereWizard} from '../craft/mesh/wizards/sphere' +import {TransformWizard} from '../craft/mesh/wizards/transform' +import {ImportWizard} from '../craft/mesh/wizards/import' import {LoadTemplate} from './utils' import {BindArray} from './bind' import {SolidList} from './solid-list' diff --git a/web/app/3d/ui/modifications-panel.js b/web/app/3d/ui/modifications-panel.js index 0a353d55..1fc420d6 100644 --- a/web/app/3d/ui/modifications-panel.js +++ b/web/app/3d/ui/modifications-panel.js @@ -1,6 +1,6 @@ import {LoadTemplate} from './utils' import {Bind} from './bind' -import * as Operations from '../operations' +import * as Operations from '../craft/operations' export function ModificationsPanel(app) { this.app = app; diff --git a/web/app/3d/wizards/shell.js b/web/app/3d/wizards/shell.js deleted file mode 100644 index 2dc8749d..00000000 --- a/web/app/3d/wizards/shell.js +++ /dev/null @@ -1,45 +0,0 @@ -import {ExtrudeWizard} from './extrude' -import * as workbench from '../workbench' -import * as tk from '../../ui/toolkit.js' - - -export function ShellWizard(app, face, initParams) { - ExtrudeWizard.call(this, app, face, true, initParams); -} - -ShellWizard.prototype = Object.create( ExtrudeWizard.prototype ); - -ShellWizard.prototype.DEFAULT_PARAMS = [50, 1, 0, 0]; - -ShellWizard.prototype.title = function() { - return "Create a Shell"; -}; - -ShellWizard.prototype.update = function(d) { - ExtrudeWizard.prototype.update.call(this, d, 1, 0, 0); -}; - -ExtrudeWizard.prototype.updatePolygons = function() { - this.polygons = [];//workbench.reconstructOutline(this.face.solid.csg, this.face); -}; - -ShellWizard.prototype.createUI = function(d) { - this.ui.depth = tk.config(new tk.Number("Depth", d), {min : 0}); - tk.add(this.ui.folder, this.ui.depth); - var onChange = tk.methodRef(this, "synch"); - this.ui.depth.input.on('t-change', onChange); -}; - -ShellWizard.prototype.getParams = function() { - return [Number(this.ui.depth.input.val())]; -}; - -ShellWizard.prototype.createRequest = function(done) { - var params = this.getParams(); - done({ - type: 'SHELL', - solids : [], - params : {d : params[0]}, - protoParams : params - }); -}; diff --git a/web/app/brep/brep-builder.js b/web/app/brep/brep-builder.js index f8baa3b2..8c303416 100644 --- a/web/app/brep/brep-builder.js +++ b/web/app/brep/brep-builder.js @@ -10,57 +10,87 @@ import * as cad_utils from '../3d/cad-utils' export function createPrism(basePoints, height) { - const normal = cad_utils.normalOfCCWSeq(basePoints); - const baseLoop = createPlaneLoop(basePoints.map(p => new Vertex(p))); - const baseFace = createPlaneFace(normal, baseLoop); + return new SimpleExtruder(height).extrude(basePoints); +} - const lidNormal = normal.multiply(-1); - const offVector = lidNormal.multiply(height); +export class Extruder { - //iterateSegments(basePoints.map(p => new Vertex(p.plus(offVector))), (a, b) => lidSegments.push({a, b})); - const lidPoints = basePoints.map(p => p.plus(offVector)).reverse(); - const lidLoop = createPlaneLoop(lidPoints.map(p => new Vertex(p))); - - const shell = new Shell(); - - const n = baseLoop.halfEdges.length; - for (let i = 0; i < n; i++) { - let lidIdx = n - 2 - i; - if (lidIdx == -1) { - lidIdx = n - 1; - } - const baseHalfEdge = baseLoop.halfEdges[i]; - const lidHalfEdge = lidLoop.halfEdges[lidIdx]; - const wallPolygon = [baseHalfEdge.vertexB, baseHalfEdge.vertexA, lidHalfEdge.vertexB, lidHalfEdge.vertexA]; - const wallLoop = createPlaneLoop(wallPolygon); - - const baseEdge = new Edge(Line.fromSegment(baseHalfEdge.vertexA.point, baseHalfEdge.vertexB.point)); - linkHalfEdges(baseEdge, baseHalfEdge, wallLoop.halfEdges[0]); - - const lidEdge = new Edge(Line.fromSegment(lidHalfEdge.vertexA.point, lidHalfEdge.vertexB.point)); - linkHalfEdges(lidEdge, lidHalfEdge, wallLoop.halfEdges[2]); - - const wallNormal = cad_utils.normalOfCCWSeq(wallPolygon.map(v => v.point)); - - const wallFace = createPlaneFace(wallNormal, wallLoop); - wallFace.debugName = 'wall_' + i; - - shell.faces.push(wallFace); + prepareLidCalculation(baseNormal, lidNormal) { } - const lidFace = createPlaneFace(lidNormal, lidLoop); - iterateSegments(shell.faces, (a, b) => { - const halfEdgeA = a.outerLoop.halfEdges[3]; - const halfEdgeB = b.outerLoop.halfEdges[1]; - const curve = Line.fromSegment(halfEdgeA.vertexA.point, halfEdgeA.vertexB.point); - linkHalfEdges(new Edge(curve), halfEdgeA, halfEdgeB); - }); - - baseFace.debugName = 'base'; - lidFace.debugName = 'lid'; - shell.faces.push(baseFace, lidFace); - shell.faces.forEach(f => f.shell = shell); - return shell; + calculateLid(basePoints) { + throw 'not implemented'; + } + + extrude(basePoints) { + const normal = cad_utils.normalOfCCWSeq(basePoints); + const baseLoop = createPlaneLoop(basePoints.map(p => new Vertex(p))); + const baseFace = createPlaneFace(normal, baseLoop); + const lidNormal = normal.multiply(-1); + + this.prepareLidCalculation(normal, lidNormal); + + //iterateSegments(basePoints.map(p => new Vertex(p.plus(offVector))), (a, b) => lidSegments.push({a, b})); + const lidPoints = this.calculateLid(basePoints).reverse(); + const lidLoop = createPlaneLoop(lidPoints.map(p => new Vertex(p))); + + const shell = new Shell(); + + const n = baseLoop.halfEdges.length; + for (let i = 0; i < n; i++) { + let lidIdx = n - 2 - i; + if (lidIdx == -1) { + lidIdx = n - 1; + } + const baseHalfEdge = baseLoop.halfEdges[i]; + const lidHalfEdge = lidLoop.halfEdges[lidIdx]; + const wallPolygon = [baseHalfEdge.vertexB, baseHalfEdge.vertexA, lidHalfEdge.vertexB, lidHalfEdge.vertexA]; + const wallLoop = createPlaneLoop(wallPolygon); + + const baseEdge = new Edge(Line.fromSegment(baseHalfEdge.vertexA.point, baseHalfEdge.vertexB.point)); + linkHalfEdges(baseEdge, baseHalfEdge, wallLoop.halfEdges[0]); + + const lidEdge = new Edge(Line.fromSegment(lidHalfEdge.vertexA.point, lidHalfEdge.vertexB.point)); + linkHalfEdges(lidEdge, lidHalfEdge, wallLoop.halfEdges[2]); + + const wallNormal = cad_utils.normalOfCCWSeq(wallPolygon.map(v => v.point)); + + const wallFace = createPlaneFace(wallNormal, wallLoop); + wallFace.role = 'wall:' + i; + + shell.faces.push(wallFace); + } + const lidFace = createPlaneFace(lidNormal, lidLoop); + iterateSegments(shell.faces, (a, b) => { + const halfEdgeA = a.outerLoop.halfEdges[3]; + const halfEdgeB = b.outerLoop.halfEdges[1]; + const curve = Line.fromSegment(halfEdgeA.vertexA.point, halfEdgeA.vertexB.point); + linkHalfEdges(new Edge(curve), halfEdgeA, halfEdgeB); + }); + + baseFace.role = 'base'; + lidFace.role = 'lid'; + + shell.faces.push(baseFace, lidFace); + shell.faces.forEach(f => f.shell = shell); + return shell; + } +} + +export class SimpleExtruder extends Extruder { + + constructor(height) { + super(); + this.height = height; + } + + prepareLidCalculation(baseNormal, lidNormal) { + this.extrudeVector = lidNormal.multiply(this.height); + } + + calculateLid(basePoints) { + return basePoints.map(p => p.plus(this.extrudeVector)) + } } function createPlaneFace(normal, loop) { diff --git a/web/app/brep/geom/impl/plane.js b/web/app/brep/geom/impl/plane.js index 3d933160..252f7551 100644 --- a/web/app/brep/geom/impl/plane.js +++ b/web/app/brep/geom/impl/plane.js @@ -35,6 +35,10 @@ export class Plane extends Surface { } get2DTransformation() { - return new Matrix3().setBasis(this.calculateBasis()).invert(); + return this.get3DTransformation().invert(); + } + + get3DTransformation() { + return new Matrix3().setBasis(this.calculateBasis()); } } \ No newline at end of file diff --git a/web/app/brep/topo/loop.js b/web/app/brep/topo/loop.js index e3b02ea4..b482a19f 100644 --- a/web/app/brep/topo/loop.js +++ b/web/app/brep/topo/loop.js @@ -11,20 +11,23 @@ export class Loop extends TopoObject { } isCCW(surface) { - const tr = surface.get2DTransformation(); - const polygon = this.asPolygon(); - const polygon2d = polygon.map(p => tr.apply(p)); - const lowestLeftIdx = math.findLowestLeftPoint(polygon2d); - const n = polygon.length; - const nextIdx = ((lowestLeftIdx + 1) % n); - const prevIdx = ((n + lowestLeftIdx - 1) % n); - const o = polygon[lowestLeftIdx]; - const first = polygon[nextIdx].minus(o); - const last = o.minus(polygon[prevIdx]); - return last.cross(first).dot(surface.normal) >= 0; + Loop.isPolygonCCWOnSurface(this.asPolygon(), surface); } asPolygon() { return this.halfEdges.map(e => e.vertexA.point); } -} \ No newline at end of file +} + +Loop.isPolygonCCWOnSurface = function(polygon, surface) { + const tr = surface.get2DTransformation(); + const polygon2d = polygon.map(p => tr.apply(p)); + const lowestLeftIdx = math.findLowestLeftPoint(polygon2d); + const n = polygon.length; + const nextIdx = ((lowestLeftIdx + 1) % n); + const prevIdx = ((n + lowestLeftIdx - 1) % n); + const o = polygon[lowestLeftIdx]; + const first = polygon[nextIdx].minus(o); + const last = o.minus(polygon[prevIdx]); + return last.cross(first).dot(surface.normal) >= 0; +}; \ No newline at end of file diff --git a/web/app/brep/topo/topo-object.js b/web/app/brep/topo/topo-object.js index a34d4b36..256d822c 100644 --- a/web/app/brep/topo/topo-object.js +++ b/web/app/brep/topo/topo-object.js @@ -2,7 +2,7 @@ export class TopoObject { constructor() { - this.debugName = ''; + this.role = ''; this.data = {}; }