mirror of
https://github.com/xibyte/jsketcher
synced 2025-12-09 09:52:34 +01:00
loft operation and loft previewer
This commit is contained in:
parent
558ca766ae
commit
6e16e39383
15 changed files with 202 additions and 59 deletions
|
|
@ -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;
|
||||
}
|
||||
|
|
@ -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;
|
||||
},
|
||||
|
|
|
|||
|
|
@ -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) {
|
||||
|
|
|
|||
12
web/app/cad/craft/loft/LoftForm.jsx
Normal file
12
web/app/cad/craft/loft/LoftForm.jsx
Normal file
|
|
@ -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 <Group>
|
||||
<Entity name='sections' entity='loop' />
|
||||
<BooleanChoice name='boolean' />
|
||||
</Group>;
|
||||
}
|
||||
25
web/app/cad/craft/loft/loftOperation.js
Normal file
25
web/app/cad/craft/loft/loftOperation.js
Normal file
|
|
@ -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));
|
||||
}
|
||||
|
||||
14
web/app/cad/craft/loft/loftPreviewer.js
Normal file
14
web/app/cad/craft/loft/loftPreviewer.js
Normal file
|
|
@ -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);
|
||||
|
||||
}
|
||||
15
web/app/cad/craft/loft/schema.js
Normal file
15
web/app/cad/craft/loft/schema.js
Normal file
|
|
@ -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
|
||||
}
|
||||
}
|
||||
|
|
@ -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;
|
||||
};
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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');
|
||||
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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];
|
||||
|
|
|
|||
|
|
@ -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 = {};
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
Loading…
Reference in a new issue