mirror of
https://github.com/xibyte/jsketcher
synced 2025-12-15 12:53:52 +01:00
extrude operation
This commit is contained in:
parent
a8d94398d1
commit
9b49e0735e
15 changed files with 152 additions and 55 deletions
|
|
@ -9,12 +9,6 @@ const OPERATION_ACTIONS = [
|
|||
},
|
||||
...requiresFaceSelection(1)
|
||||
},
|
||||
{
|
||||
id: 'EXTRUDE',
|
||||
appearance: {
|
||||
info: 'extrudes 2D sketch',
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 'REVOLVE',
|
||||
appearance: {
|
||||
|
|
|
|||
|
|
@ -1,8 +1,8 @@
|
|||
import {subtract, union, intersect} from '../../../brep/operations/boolean'
|
||||
import {BREPSceneSolid} from '../../scene/wrappers/brepSceneObject'
|
||||
import {update as updateStitching} from '../../../brep/stitching'
|
||||
import {BREPValidator} from '../../../brep/brep-validator'
|
||||
import {Shell} from '../../../brep/topo/shell'
|
||||
import {subtract, union, intersect} from '../../brep/operations/boolean'
|
||||
import {BREPSceneSolid} from '../scene/wrappers/brepSceneObject'
|
||||
import {update as updateStitching} from '../../brep/stitching'
|
||||
import {BREPValidator} from '../../brep/brep-validator'
|
||||
import {Shell} from '../../brep/topo/shell'
|
||||
|
||||
const BoolOpMap = {
|
||||
'subtract': subtract,
|
||||
|
|
@ -43,7 +43,7 @@ export function activate({bus, services}) {
|
|||
|
||||
let result;
|
||||
try {
|
||||
result = op.run(services.cadRegistry.registry, request.params);
|
||||
result = op.run(services.cadRegistry, request.params);
|
||||
} catch (err) {
|
||||
return err;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,33 +1,22 @@
|
|||
import {Matrix3, BasisForPlane, ORIGIN} from '../../../math/l3space'
|
||||
import {Matrix3, ORIGIN} from '../../../math/l3space'
|
||||
import * as math from '../../../math/math'
|
||||
import Vector from 'math/vector';
|
||||
import {enclose, iterateSegments} from '../../../brep/brep-enclose'
|
||||
import * as stitching from '../../../brep/stitching'
|
||||
import {Loop} from '../../../brep/topo/loop'
|
||||
import {incRefCounter} from '../../../brep/topo/topo-object'
|
||||
import {Line} from '../../../brep/geom/impl/line'
|
||||
import {ReadSketchFromFace} from '../../sketch/sketchReader'
|
||||
import {Segment} from '../../sketch/sketchModel'
|
||||
import {isCurveClass} from '../../cad-utils'
|
||||
import {BooleanOperation, combineShells} from './boolean-operation'
|
||||
import {enclose} from '../../../brep/brep-enclose'
|
||||
import {BooleanOperation, combineShells} from '../booleanOperation'
|
||||
|
||||
|
||||
export function Extrude(app, params) {
|
||||
return doOperation(app, params, false);
|
||||
export function Extrude(cadRegistry, params) {
|
||||
return doOperation(cadRegistry, params, false);
|
||||
}
|
||||
|
||||
export function Cut(app, params) {
|
||||
return doOperation(app, params, true);
|
||||
export function Cut(cadRegistry, params) {
|
||||
return doOperation(cadRegistry, params, true);
|
||||
}
|
||||
|
||||
export function doOperation(app, params, cut) {
|
||||
const face = app.findFace(params.face);
|
||||
export function doOperation(cadRegistry, params, cut) {
|
||||
const face = cadRegistry.findFace(params.face);
|
||||
const solid = face.solid;
|
||||
|
||||
const savedFace = localStorage.getItem(app.faceStorageKey(face.id));
|
||||
if (savedFace == null) return null;
|
||||
|
||||
const sketch = ReadSketchFromFace(app, face);
|
||||
const sketch = face.sketch;
|
||||
let plane = face.surface().tangentPlane(0, 0);
|
||||
const details = getEncloseDetails(params, sketch.fetchContours(), plane, !cut, false);
|
||||
const operand = combineShells(details.map(d => enclose(d.basePath, d.lidPath, d.baseSurface, d.lidSurface)));
|
||||
|
|
@ -49,10 +38,10 @@ export function getEncloseDetails(params, contours, sketchSurface, invert) {
|
|||
|
||||
const targetDir = baseSurfaceNormal.negate();
|
||||
|
||||
if (params.rotation != 0) {
|
||||
if (params.rotation !== 0) {
|
||||
const basis = sketchSurface.basis();
|
||||
target = Matrix3.rotateMatrix(params.rotation * Math.PI / 180, basis[0], ORIGIN).apply(targetDir);
|
||||
if (params.angle != 0) {
|
||||
if (params.angle !== 0) {
|
||||
target = Matrix3.rotateMatrix(params.angle * Math.PI / 180, basis[2], ORIGIN)._apply(target);
|
||||
}
|
||||
target._multiply(value);
|
||||
|
|
@ -67,7 +56,7 @@ export function getEncloseDetails(params, contours, sketchSurface, invert) {
|
|||
if (invert) contour.reverse();
|
||||
|
||||
const lidPath = [];
|
||||
var applyPrism = !math.equal(params.prism, 1);
|
||||
let applyPrism = !math.equal(params.prism, 1);
|
||||
for (let i = 0; i < basePath.length; ++i) {
|
||||
const curve = basePath[i];
|
||||
let lidCurve = curve.translate(target);
|
||||
16
web/app/cad/craft/cutExtrude/extrudeOperation.js
Normal file
16
web/app/cad/craft/cutExtrude/extrudeOperation.js
Normal file
|
|
@ -0,0 +1,16 @@
|
|||
import {roundValueForPresentation as r} from "../operationHelper";
|
||||
import {createWizardMetadata} from "./wizardMetadata";
|
||||
import {createPreviewGeomProvider} from "./previewer";
|
||||
import {Extrude} from "./cutExtrude";
|
||||
|
||||
export default {
|
||||
id: 'EXTRUDE',
|
||||
metadata: createWizardMetadata('height'),
|
||||
label: 'Extrude',
|
||||
icon: 'img/cad/extrude',
|
||||
info: 'extrudes 2D sketch',
|
||||
paramsInfo: value => `(${r(value)})`,
|
||||
previewGeomProvider: createPreviewGeomProvider(false),
|
||||
run: Extrude
|
||||
};
|
||||
|
||||
53
web/app/cad/craft/cutExtrude/previewer.js
Normal file
53
web/app/cad/craft/cutExtrude/previewer.js
Normal file
|
|
@ -0,0 +1,53 @@
|
|||
|
||||
import {getEncloseDetails} from "./cutExtrude";
|
||||
import {curveTessParams} from "../../../brep/geom/impl/curve/curve-tess";
|
||||
import Vector from "../../../../../modules/math/vector";
|
||||
import {TriangulatePolygons} from "../../tess/triangulation";
|
||||
import {createMeshGeometry} from "../../../../../modules/scene/geoms";
|
||||
|
||||
|
||||
export function createPreviewGeomProvider(inversed) {
|
||||
|
||||
return function previewGeomProvider(params, services) {
|
||||
|
||||
const face = services.cadRegistry.findFace(params.face);
|
||||
if (!face) return null;
|
||||
let sketch = face.sketch.fetchContours();
|
||||
|
||||
const encloseDetails = getEncloseDetails(params, sketch, face.surface().tangentPlane(0, 0), !inversed);
|
||||
const triangles = [];
|
||||
|
||||
for (let {basePath, lidPath, baseSurface, lidSurface} of encloseDetails) {
|
||||
const basePoints = [];
|
||||
const lidPoints = [];
|
||||
for (let i = 0; i < basePath.length; ++i) {
|
||||
let baseNurbs = basePath[i];
|
||||
let lidNurbs = lidPath[i];
|
||||
|
||||
let tessCurve = params.prism > 1 ? lidNurbs : baseNurbs;
|
||||
|
||||
const us = curveTessParams(tessCurve.impl, tessCurve.uMin, tessCurve.uMax);
|
||||
const base = us.map(u => baseNurbs.point(u));
|
||||
const lid = us.map(u => lidNurbs.point(u));
|
||||
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]]);
|
||||
}
|
||||
base.forEach(p => basePoints.push(p));
|
||||
lid.forEach(p => lidPoints.push(p));
|
||||
}
|
||||
|
||||
function collectOnSurface(points, normal) {
|
||||
TriangulatePolygons([points], normal, (v) => v.toArray(), (arr) => new Vector().set3(arr))
|
||||
.forEach(tr => triangles.push(tr));
|
||||
}
|
||||
|
||||
collectOnSurface(basePoints, baseSurface.normal);
|
||||
collectOnSurface(lidPoints, lidSurface.normal);
|
||||
}
|
||||
|
||||
return createMeshGeometry(triangles);
|
||||
}
|
||||
}
|
||||
|
||||
11
web/app/cad/craft/cutExtrude/wizardMetadata.js
Normal file
11
web/app/cad/craft/cutExtrude/wizardMetadata.js
Normal file
|
|
@ -0,0 +1,11 @@
|
|||
import {CURRENT_SELECTION as S} from "../wizard/wizardPlugin";
|
||||
|
||||
export function createWizardMetadata(valueLabel) {
|
||||
return [
|
||||
['value' , 'number', 50, {label: valueLabel}],
|
||||
['prism' , 'number', 1 , {min: 0, step: 0.1, round: 1}],
|
||||
['angle' , 'number', 0 , {}],
|
||||
['rotation', 'number', 0 , {step: 5}],
|
||||
['face' , 'face' , S ]
|
||||
];
|
||||
}
|
||||
4
web/app/cad/craft/operationHelper.js
Normal file
4
web/app/cad/craft/operationHelper.js
Normal file
|
|
@ -0,0 +1,4 @@
|
|||
|
||||
export function roundValueForPresentation(value) {
|
||||
return value.toPrecision(4).replace(/\.0$/, '');
|
||||
}
|
||||
|
|
@ -9,7 +9,7 @@ const METADATA = [
|
|||
['depth' , 'number', 500, {min: 0}]
|
||||
];
|
||||
|
||||
function createBox(solids, {width, height, depth}) {
|
||||
function createBox(cadRegistry, {width, height, depth}) {
|
||||
return {
|
||||
outdated: [],
|
||||
created: [new BREPSceneSolid(box(width, height, depth))]
|
||||
|
|
@ -20,9 +20,10 @@ export default {
|
|||
id: 'BOX',
|
||||
metadata: METADATA,
|
||||
label: 'Box',
|
||||
icon: 'img/cad/box',
|
||||
info: 'creates new object box',
|
||||
paramsInfo: ({width, height, depth}) => `(${width}, ${height}, ${depth})`,
|
||||
previewer: createPreviewer(({width, height, depth}) => createBoxGeometry(width, height, depth)),
|
||||
previewGeomProvider: ({width, height, depth}) => createBoxGeometry(width, height, depth),
|
||||
run: createBox
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -23,4 +23,6 @@ export const TOKENS = {
|
|||
WIZARDS: createToken('wizards'),
|
||||
OPEN: createToken('wizards', 'open'),
|
||||
CLOSE: createToken('wizards', 'close'),
|
||||
};
|
||||
};
|
||||
|
||||
export const CURRENT_SELECTION = {};
|
||||
|
|
|
|||
|
|
@ -0,0 +1,8 @@
|
|||
import React from 'react';
|
||||
|
||||
import TextControl from "../../../../../../modules/ui/components/controls/TextControl";
|
||||
|
||||
export default function FaceSelectionControl(props) {
|
||||
return <TextControl {...props} />
|
||||
}
|
||||
|
||||
|
|
@ -1,4 +1,5 @@
|
|||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import Window from 'ui/components/Window';
|
||||
import Stack from 'ui/components/Stack';
|
||||
import Field from 'ui/components/controls/Field';
|
||||
|
|
@ -8,14 +9,22 @@ import NumberControl from 'ui/components/controls/NumberControl';
|
|||
import TextControl from 'ui/components/controls/TextControl';
|
||||
import Button from 'ui/components/controls/Button';
|
||||
import ButtonGroup from 'ui/components/controls/ButtonGroup';
|
||||
import FaceSelectionControl from './FaceSelectionControl';
|
||||
import {CURRENT_SELECTION} from "../../../craft/wizard/wizardPlugin";
|
||||
|
||||
export default class Wizard extends React.Component {
|
||||
|
||||
constructor({initialState, metadata, previewer}) {
|
||||
constructor({initialState, metadata, previewer}, {services: {selection}}) {
|
||||
super();
|
||||
this.params = {};
|
||||
|
||||
metadata.forEach(([name,, v]) => this.params[name] = v);
|
||||
metadata.forEach(([name, type, v]) => {
|
||||
if (type === 'face' && v === CURRENT_SELECTION) {
|
||||
let selectedFace = selection.face()[0];
|
||||
v = selectedFace ? selectedFace.id : '';
|
||||
}
|
||||
this.params[name] = v
|
||||
});
|
||||
|
||||
Object.assign(this.params, initialState);
|
||||
|
||||
|
|
@ -68,10 +77,17 @@ export default class Wizard extends React.Component {
|
|||
};
|
||||
if (type === 'number') {
|
||||
return <NumberControl {...commonProps} />
|
||||
} else if (type === 'face') {
|
||||
return <FaceSelectionControl {...commonProps} />
|
||||
} else {
|
||||
return <TextControl {...commonProps} />
|
||||
}
|
||||
}
|
||||
|
||||
static contextTypes = {
|
||||
services: PropTypes.object
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -3,17 +3,18 @@ import PropTypes from 'prop-types';
|
|||
import {TOKENS as WIZARD_TOKENS} from '../../../craft/wizard/wizardPlugin';
|
||||
import connect from 'ui/connect';
|
||||
import Wizard from "./Wizard";
|
||||
import {createPreviewer} from "../../../preview/scenePreviewer";
|
||||
|
||||
function WizardManager({wizards, close}, {services}) {
|
||||
return wizards.map( ({type, initialState}, wizardIndex) => {
|
||||
let {metadata, previewer, run} = services.operation.get(type);
|
||||
let {metadata, previewGeomProvider, run} = services.operation.get(type);
|
||||
|
||||
function onOK(params) {
|
||||
close();
|
||||
services.craft.modify({type, params});
|
||||
}
|
||||
|
||||
previewer = previewer.bind(null, {services});
|
||||
let previewer = createPreviewer(previewGeomProvider, {services});
|
||||
return <Wizard key={wizardIndex} previewer={previewer} metadata={metadata}
|
||||
onOK={onOK}
|
||||
onCancel={close}
|
||||
|
|
|
|||
|
|
@ -1,7 +1,9 @@
|
|||
import boxOperation from '../craft/primitives/boxOperation';
|
||||
import extrudeOperation from '../craft/cutExtrude/extrudeOperation';
|
||||
|
||||
export function activate({bus, services}) {
|
||||
export function activate({services}) {
|
||||
services.operation.registerOperations([
|
||||
boxOperation
|
||||
boxOperation,
|
||||
extrudeOperation
|
||||
])
|
||||
}
|
||||
|
|
@ -3,9 +3,9 @@ import {createTransparentPhongMaterial} from 'scene/materials';
|
|||
import {createMesh} from 'scene/objects/mesh';
|
||||
|
||||
|
||||
export function createPreviewer(sceneGeometryCreator) {
|
||||
export function createPreviewer(sceneGeometryCreator, {services}) {
|
||||
|
||||
return function({services}, initParams) {
|
||||
return function(params) {
|
||||
const previewGroup = SceneGraph.createGroup();
|
||||
SceneGraph.addToGroup(services.cadScene.workGroup, previewGroup);
|
||||
|
||||
|
|
@ -21,7 +21,7 @@ export function createPreviewer(sceneGeometryCreator) {
|
|||
|
||||
function update(params) {
|
||||
destroyPreviewObject();
|
||||
previewObject = createMesh(sceneGeometryCreator(params), IMAGINARY_SURFACE_MATERIAL);
|
||||
previewObject = createMesh(sceneGeometryCreator(params, services), IMAGINARY_SURFACE_MATERIAL);
|
||||
previewGroup.add(previewObject);
|
||||
services.viewer.render();
|
||||
}
|
||||
|
|
@ -31,19 +31,19 @@ export function createPreviewer(sceneGeometryCreator) {
|
|||
SceneGraph.removeFromGroup(services.cadScene.workGroup, previewGroup);
|
||||
services.viewer.render();
|
||||
}
|
||||
update(initParams);
|
||||
update(params);
|
||||
|
||||
return {update, dispose};
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// function sketchBasedPreviewCreator(params) {
|
||||
// const face = app.findFace(params.face);
|
||||
// if (!face) return null;
|
||||
// const triangles = this.createImpl(app, params, face.sketch.fetchContours(), face);
|
||||
// return createMeshFromTriangles(triangles, IMAGINARY_SURFACE_MATERIAL);
|
||||
// }
|
||||
function sketchBasedPreviewCreator(params) {
|
||||
const face = app.findFace(params.face);
|
||||
if (!face) return null;
|
||||
const triangles = this.createImpl(app, params, face.sketch.fetchContours(), face);
|
||||
return createMeshFromTriangles(triangles, IMAGINARY_SURFACE_MATERIAL);
|
||||
}
|
||||
//
|
||||
// function sketchBasedNurbsPreviewCreator(params) {
|
||||
// const face = app.findFace(params.face);
|
||||
|
|
|
|||
Loading…
Reference in a new issue