diff --git a/modules/scene/geoms.js b/modules/scene/geoms.js index 5972ef71..feb0d418 100644 --- a/modules/scene/geoms.js +++ b/modules/scene/geoms.js @@ -1,20 +1,41 @@ +import {BoxGeometry, Face3, Geometry, Vector3} from 'three'; export function createBoxGeometry(width, height, depth) { - return new THREE.BoxGeometry(width, height, depth); + return new BoxGeometry(width, height, depth); } export function createMeshGeometry(triangles) { - const geometry = new THREE.Geometry(); + const geometry = new 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); + const face = new Face3(a, b, c); tr.forEach(v => geometry.vertices.push(v.three())); geometry.faces.push(face); } geometry.mergeVertices(); geometry.computeFaceNormals(); return geometry; +} + +export function createSmoothMeshGeometryFromData(tessInfo) { + const geometry = new Geometry(); + const vec = arr => new Vector3().fromArray(arr); + + for (let [tr, normals] of tessInfo) { + if (!normals || normals.find(n => n[0] === null || n[1] === null || n[2] === null)) { + normals = undefined; + } + const a = geometry.vertices.length; + const b = a + 1; + const c = a + 2; + const face = new Face3(a, b, c, normals && normals.map(vec)); + tr.forEach(v => geometry.vertices.push(vec(v))); + geometry.faces.push(face); + } + geometry.mergeVertices(); + geometry.computeFaceNormals(); + return geometry; } \ No newline at end of file diff --git a/web/app/cad/craft/cadRegistryPlugin.js b/web/app/cad/craft/cadRegistryPlugin.js index eadb647f..65317a48 100644 --- a/web/app/cad/craft/cadRegistryPlugin.js +++ b/web/app/cad/craft/cadRegistryPlugin.js @@ -1,4 +1,4 @@ -import {DATUM, DATUM_AXIS, EDGE, FACE, SHELL, SKETCH_OBJECT} from '../scene/entites'; +import {DATUM, DATUM_AXIS, EDGE, FACE, LOOP, SHELL, SKETCH_OBJECT} from '../scene/entites'; import {MShell} from '../model/mshell'; @@ -70,6 +70,19 @@ export function activate({streams, services}) { } return datum.getAxisByLiteral(axisLiteral); } + + function findLoop(loopId) { + let [shellId, faceId, loopLocalId] = loopId.split('/'); + let face = findFace(shellId+'/'+faceId); + if (face) { + for (let loop of face.sketchLoops) { + if (loop.id === loopId) { + return loop; + } + } + } + return null; + } function findEntity(entity, id) { switch (entity) { @@ -79,12 +92,13 @@ export function activate({streams, services}) { case SKETCH_OBJECT: return findSketchObject(id); case DATUM: return findDatum(id); case DATUM_AXIS: return findDatumAxis(id); + case LOOP: return findLoop(id); default: throw 'unsupported'; } } services.cadRegistry = { - getAllShells, findShell, findFace, findEdge, findSketchObject, findEntity, findDatum, findDatumAxis, + getAllShells, findShell, findFace, findEdge, findSketchObject, findEntity, findDatum, findDatumAxis, findLoop, get modelIndex() { return streams.cadRegistry.modelIndex.value; }, diff --git a/web/app/cad/craft/e0/e0Plugin.js b/web/app/cad/craft/e0/e0Plugin.js index 972a4366..f83e86de 100644 --- a/web/app/cad/craft/e0/e0Plugin.js +++ b/web/app/cad/craft/e0/e0Plugin.js @@ -99,6 +99,24 @@ export function activate(ctx) { consumed, created: [readShellData(data.result, consumed, operandsA[0].csys)] } + }, + loft: function(params) { + let engineParams = { + sections: params.sections.map(sec => readSketchContour(sec.contour, sec.face)), + preview: params.preview, + tolerance: TOLERANCE, + deflection: DEFLECTION, + }; + let data = callEngine(engineParams, Module._SPI_loft); + if (params.preview) { + return data; + } + throw 'unsupported'; + // let consumed = [...operandsA, ...operandsB]; + // return { + // consumed, + // created: [readShellData(data.result, consumed, operandsA[0].csys)] + // } } } } @@ -180,47 +198,47 @@ function managedByE0(mShell) { return externals && externals.engine === 'e0'; } +function readSketchContour(contour, face) { + let tr = face.csys.outTransformation; + let path = []; + contour.segments.forEach(s => { + if (s.isCurve) { + if (s.constructor.name === 'Circle') { + const dir = face.csys.z.data(); + path.push({TYPE: CURVE_TYPES.CIRCLE, c: tr.apply(s.c).data(), dir, r: s.r}); + } else if (s.constructor.name === 'Arc') { + let a = s.inverted ? s.b : s.a; + let b = s.inverted ? s.a : s.b; + let tangent = tr._apply(s.c.minus(a))._cross(face.csys.z)._normalize(); + if (s.inverted) { + tangent._negate(); + } + path.push({ + TYPE: CURVE_TYPES.ARC, + a: tr.apply(a).data(), + b: tr.apply(b).data(), + tangent: tangent.data() + }); + } else { + let nurbs = s.toNurbs(face.csys).impl; + path.push(Object.assign({TYPE: CURVE_TYPES.B_SPLINE}, nurbs.serialize())); + } + } else { + let ab = [s.a, s.b]; + if (s.inverted) { + ab.reverse(); + } + ab = ab.map(v => tr.apply(v).data()); + path.push({TYPE: CURVE_TYPES.SEGMENT, a: ab[0], b: ab[1]}); + } + }); + return path; +} + function readSketch(face, request, sketcher) { let sketch = sketcher.readSketch(face.id); if (!sketch) throw 'illegal state'; - - let tr = face.csys.outTransformation; - let paths = sketch.fetchContours().map(c => { - let path = []; - c.segments.forEach(s => { - if (s.isCurve) { - if (s.constructor.name === 'Circle') { - const dir = face.csys.z.data(); - path.push({TYPE: CURVE_TYPES.CIRCLE, c: tr.apply(s.c).data(), dir, r: s.r}); - } else if (s.constructor.name === 'Arc') { - let a = s.inverted ? s.b : s.a; - let b = s.inverted ? s.a : s.b; - let tangent = tr._apply(s.c.minus(a))._cross(face.csys.z)._normalize(); - if (s.inverted) { - tangent._negate(); - } - path.push({ - TYPE: CURVE_TYPES.ARC, - a: tr.apply(a).data(), - b: tr.apply(b).data(), - tangent: tangent.data() - }); - } else { - let nurbs = s.toNurbs(face.csys).impl; - path.push(Object.assign({TYPE: CURVE_TYPES.B_SPLINE}, nurbs.serialize())); - } - } else { - let ab = [s.a, s.b]; - if (s.inverted) { - ab.reverse(); - } - ab = ab.map(v => tr.apply(v).data()); - path.push({TYPE: CURVE_TYPES.SEGMENT, a: ab[0], b: ab[1]}); - } - }); - return path; - }); - return paths; + return sketch.fetchContours().map(c => readSketchContour(c, face)); } function createExtrudeCommand(request, {cadRegistry, sketcher}, invert) { diff --git a/web/app/cad/craft/loft/LoftForm.jsx b/web/app/cad/craft/loft/LoftForm.jsx new file mode 100644 index 00000000..654caae0 --- /dev/null +++ b/web/app/cad/craft/loft/LoftForm.jsx @@ -0,0 +1,12 @@ +import React from 'react'; +import {Group} from '../wizard/components/form/Form'; +import Entity from '../wizard/components/form/Entity'; +import BooleanChoice from '../wizard/components/form/BooleanChioce'; + +export default function LoftForm() { + + return + + + ; +} \ No newline at end of file diff --git a/web/app/cad/craft/loft/loftOperation.js b/web/app/cad/craft/loft/loftOperation.js new file mode 100644 index 00000000..d06fbfe6 --- /dev/null +++ b/web/app/cad/craft/loft/loftOperation.js @@ -0,0 +1,25 @@ +import schema from './schema'; +import {loftPreviewGeomProvider} from './loftPreviewer'; +import {assignBooleanParams} from '../primitives/booleanOptionHelper'; +import LoftForm from './LoftForm'; + +export default { + id: 'LOFT', + label: 'Loft', + icon: 'img/cad/revolve', + info: 'creates a loft cross selected sections shape', + paramsInfo: () => '', + previewGeomProvider: loftPreviewGeomProvider, + run: runLoft, + form: LoftForm, + schema +}; + + +function runLoft(params, services) { + + services.craftEngine.loft(assignBooleanParams({ + sections: params.sections.map(services.cadRegistry.findLoop), + }, params, services.cadRegistry.getAllShells)); +} + diff --git a/web/app/cad/craft/loft/loftPreviewer.js b/web/app/cad/craft/loft/loftPreviewer.js new file mode 100644 index 00000000..7652d303 --- /dev/null +++ b/web/app/cad/craft/loft/loftPreviewer.js @@ -0,0 +1,14 @@ +import {createSmoothMeshGeometryFromData} from '../../../../../modules/scene/geoms'; + +export function loftPreviewGeomProvider(params, services) { + + const tessInfo = services.craftEngine.loft({ + sections: params.sections.map(services.cadRegistry.findLoop), + preview: true + }); + + console.dir(tessInfo); + + return createSmoothMeshGeometryFromData(tessInfo); + +} diff --git a/web/app/cad/craft/loft/schema.js b/web/app/cad/craft/loft/schema.js new file mode 100644 index 00000000..b90e170b --- /dev/null +++ b/web/app/cad/craft/loft/schema.js @@ -0,0 +1,15 @@ +export default { + sections: { + type: 'array', + itemType: 'loop', + defaultValue: { + type: 'selection', + } + }, + boolean: { + type: 'enum', + values: ['INTERSECT', 'SUBTRACT', 'UNION'], + defaultValue: 'UNION', + optional: true + } +} diff --git a/web/app/cad/craft/wizard/wizardSelectionPlugin.js b/web/app/cad/craft/wizard/wizardSelectionPlugin.js index 9be09d6d..958adca3 100644 --- a/web/app/cad/craft/wizard/wizardSelectionPlugin.js +++ b/web/app/cad/craft/wizard/wizardSelectionPlugin.js @@ -1,4 +1,4 @@ -import {DATUM, DATUM_AXIS, EDGE, FACE, SHELL, SKETCH_OBJECT} from '../../scene/entites'; +import {DATUM, DATUM_AXIS, EDGE, FACE, LOOP, SHELL, SKETCH_OBJECT} from '../../scene/entites'; export function activate(ctx) { ctx.streams.wizard.wizardContext.attach(wizCtx => { @@ -150,6 +150,12 @@ function createPickHandlerFromSchema(wizCtx) { } else { selectToFirst(DATUM_AXIS, model.id); } + } else if (modelType === LOOP) { + if (activeEntity === LOOP) { + selectActive(model.id); + } else { + selectToFirst(LOOP, model.id); + } } return false; }; diff --git a/web/app/cad/model/mloop.js b/web/app/cad/model/mloop.js index 0bcad2d7..41a52e4c 100644 --- a/web/app/cad/model/mloop.js +++ b/web/app/cad/model/mloop.js @@ -10,9 +10,7 @@ export class MLoop extends MObject { } -export class MSketchLoop extends MObject { - - static TYPE = 'loop'; +export class MSketchLoop extends MLoop { constructor(id, face, sketchObjects, contour) { super(id); diff --git a/web/app/cad/part/partOperationsPlugin.js b/web/app/cad/part/partOperationsPlugin.js index b073bd28..9c9a438c 100644 --- a/web/app/cad/part/partOperationsPlugin.js +++ b/web/app/cad/part/partOperationsPlugin.js @@ -12,7 +12,8 @@ import sphereOperation from '../craft/primitives/sphere/sphereOperation'; import cylinderOperation from '../craft/primitives/cylinder/cylinderOperation'; import torusOperation from '../craft/primitives/torus/torusOperation'; import coneOperation from '../craft/primitives/cone/coneOperation'; -import spatialCurve from '../craft/spatialCurve/spatialCurveOperation'; +import spatialCurveOperation from '../craft/spatialCurve/spatialCurveOperation'; +import loftOperation from '../craft/loft/loftOperation'; import {intersectionOperation, subtractOperation, unionOperation} from '../craft/boolean/booleanOperation'; @@ -32,7 +33,8 @@ export function activate({services}) { cylinderOperation, torusOperation, coneOperation, - spatialCurve, + spatialCurveOperation, + loftOperation, intersectionOperation, subtractOperation, unionOperation diff --git a/web/app/cad/sandbox.js b/web/app/cad/sandbox.js index 8bba8917..6ead4ccc 100644 --- a/web/app/cad/sandbox.js +++ b/web/app/cad/sandbox.js @@ -250,7 +250,7 @@ export function runSandbox({bus, services, services: { viewer, cadScene, cadRegi // o2.setMoveMode(DatumObject3D.AXIS.Z); // cadScene.auxGroup.add(o2); - services.action.run('SPATIAL_CURVE'); + services.action.run('LOFT'); } diff --git a/web/app/cad/scene/controls/pickControlPlugin.js b/web/app/cad/scene/controls/pickControlPlugin.js index 4df794a0..dbf6e21e 100644 --- a/web/app/cad/scene/controls/pickControlPlugin.js +++ b/web/app/cad/scene/controls/pickControlPlugin.js @@ -1,19 +1,16 @@ import * as mask from 'gems/mask' import {getAttribute, setAttribute} from 'scene/objectData'; -import {FACE, EDGE, SKETCH_OBJECT, DATUM, SHELL, DATUM_AXIS} from '../entites'; -import {state} from 'lstream'; -import {distinctState} from '../../../../../modules/lstream'; +import {FACE, EDGE, SKETCH_OBJECT, DATUM, SHELL, DATUM_AXIS, LOOP} from '../entites'; export const PICK_KIND = { FACE: mask.type(1), SKETCH: mask.type(2), EDGE: mask.type(3), DATUM: mask.type(4), - DATUM_AXIS: mask.type(5) + DATUM_AXIS: mask.type(5), + LOOP: mask.type(6) }; -export const SELECTABLE_ENTITIES = [FACE, EDGE, SKETCH_OBJECT, DATUM, SHELL]; - const DEFAULT_SELECTION_MODE = Object.freeze({ shell: false, vertex: false, @@ -95,7 +92,7 @@ export function activate(context) { const deselectAll = () => services.marker.clear(); function handlePick(event) { - raycastObjects(event, PICK_KIND.FACE | PICK_KIND.SKETCH | PICK_KIND.EDGE | PICK_KIND.DATUM_AXIS, pickHandler); + raycastObjects(event, PICK_KIND.FACE | PICK_KIND.SKETCH | PICK_KIND.EDGE | PICK_KIND.DATUM_AXIS | PICK_KIND.LOOP, pickHandler); } function pick(obj) { @@ -146,6 +143,15 @@ export function activate(context) { } return false; }, + (pickResult) => { + if (mask.is(kind, PICK_KIND.LOOP) && !!pickResult.face) { + let faceV = getAttribute(pickResult.face, LOOP); + if (faceV) { + return !visitor(faceV.model, event); + } + } + return false; + }, (pickResult) => { if (mask.is(kind, PICK_KIND.FACE) && !!pickResult.face) { let faceV = getAttribute(pickResult.face, FACE); diff --git a/web/app/cad/scene/entites.js b/web/app/cad/scene/entites.js index cf681cf3..54494743 100644 --- a/web/app/cad/scene/entites.js +++ b/web/app/cad/scene/entites.js @@ -4,6 +4,7 @@ import {MEdge} from '../model/medge'; import {MVertex} from '../model/mvertex'; import {MSketchObject} from '../model/msketchObject'; import {MDatum, MDatumAxis} from '../model/mdatum'; +import {MLoop} from '../model/mloop'; export const SHELL = MShell.TYPE; export const FACE = MFace.TYPE; @@ -12,8 +13,9 @@ export const VERTEX = MVertex.TYPE; export const SKETCH_OBJECT = MSketchObject.TYPE; export const DATUM = MDatum.TYPE; export const DATUM_AXIS = MDatumAxis.TYPE; +export const LOOP = MLoop.TYPE; -export const ENTITIES = [SHELL, DATUM, FACE, EDGE, VERTEX, SKETCH_OBJECT, DATUM_AXIS]; +export const ENTITIES = [SHELL, DATUM, FACE, EDGE, VERTEX, SKETCH_OBJECT, DATUM_AXIS, LOOP]; export const PART_MODELING_ENTITIES = [SHELL, FACE, EDGE, VERTEX, SKETCH_OBJECT]; export const ASSEMBLY_ENTITIES = [SHELL, FACE, EDGE, VERTEX]; diff --git a/web/app/cad/scene/entityContextPlugin.js b/web/app/cad/scene/entityContextPlugin.js index 02fa5fd4..e1dd639f 100644 --- a/web/app/cad/scene/entityContextPlugin.js +++ b/web/app/cad/scene/entityContextPlugin.js @@ -1,7 +1,10 @@ import {state} from 'lstream'; -import {SELECTABLE_ENTITIES} from './controls/pickControlPlugin'; + import {addToListInMap} from 'gems/iterables'; import {EMPTY_ARRAY} from '../../../../modules/gems/iterables'; +import {DATUM, FACE, SHELL, SKETCH_OBJECT, EDGE} from './entites'; + +export const SELECTABLE_ENTITIES = [FACE, EDGE, SKETCH_OBJECT, DATUM, SHELL]; export function defineStreams(ctx) { ctx.streams.selection = {}; diff --git a/web/app/cad/scene/views/sketchLoopView.js b/web/app/cad/scene/views/sketchLoopView.js index 2670defc..f593c7a3 100644 --- a/web/app/cad/scene/views/sketchLoopView.js +++ b/web/app/cad/scene/views/sketchLoopView.js @@ -6,6 +6,8 @@ import {DoubleSide, Geometry, Mesh} from 'three'; import {surfaceAndPolygonsToGeom} from '../wrappers/brepSceneObject'; import {TriangulatePolygons} from '../../tess/triangulation'; import Vector from '../../../../../modules/math/vector'; +import {LOOP} from '../entites'; +import {setAttribute} from '../../../../../modules/scene/objectData'; export class SketchLoopView extends View { constructor(mLoop) { @@ -35,6 +37,11 @@ export class SketchLoopView extends View { surfaceAndPolygonsToGeom(surface, tess, this.mesh.geometry); this.mesh.geometry.mergeVertices(); + for (let i = 0; i < geometry.faces.length; i++) { + const meshFace = geometry.faces[i]; + setAttribute(meshFace, LOOP, this); + } + this.rootGroup.add(this.mesh); this.mesh.onMouseEnter = (e) => { this.mesh.material.visible = true;