separates mesh operations from BREP

This commit is contained in:
Val Erastov 2017-01-16 01:46:28 -08:00
parent 829d8dafae
commit 109e16fdf7
28 changed files with 620 additions and 277 deletions

View file

@ -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) {

View file

@ -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));
}
}

View file

@ -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);
}

View file

@ -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;
}
}

View file

@ -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
});

View file

@ -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 = {};

106
web/app/3d/craft/craft.js Normal file
View file

@ -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();
};

View file

@ -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 = [];

View file

@ -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) {

View file

@ -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) {

View file

@ -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);

View file

@ -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);

View file

@ -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'

View file

@ -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) {

View file

@ -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) {

View file

@ -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,

View file

@ -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,

View file

@ -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) {

View file

@ -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);

View file

@ -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'

View file

@ -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'

View file

@ -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'

View file

@ -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;

View file

@ -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
});
};

View file

@ -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) {

View file

@ -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());
}
}

View file

@ -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);
}
}
}
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;
};

View file

@ -2,7 +2,7 @@
export class TopoObject {
constructor() {
this.debugName = '';
this.role = '';
this.data = {};
}