@polygonjs/polygonjs
Version:
node-based WebGL 3D engine https://polygonjs.com
307 lines (284 loc) • 10.3 kB
text/typescript
/**
* Applies a boolean operation
*
*
*/
import {CADSopNode} from './_BaseCAD';
import {NodeParamsConfig, ParamConfig} from '../utils/params/ParamsConfig';
// import {CadCoreGroup} from '../../../core/geometry/modules/cad/CadCoreGroup';
import {
OpenCascadeInstance,
TopoDS_Shape,
CadGeometryTypeShape,
cadGeometryTypeFromShape,
} from '../../../core/geometry/modules/cad/CadCommon';
import {CadGeometryType} from '../../../core/geometry/modules/cad/CadCommon';
import {CadObject} from '../../../core/geometry/modules/cad/CadObject';
import {TypeAssert} from '../../poly/Assert';
import {SopType} from '../../poly/registers/nodes/types/Sop';
import {CoreGroup} from '../../../core/geometry/Group';
import {CadLoaderSync} from '../../../core/geometry/modules/cad/CadLoaderSync';
enum BooleanMode {
ALL_IN_SEQUENCE = 'process all in sequence',
ONE_TO_ONE = 'one to one',
ONE_TO_MANY = 'one to many',
}
const BOOLEAN_MODES: BooleanMode[] = [BooleanMode.ALL_IN_SEQUENCE, BooleanMode.ONE_TO_ONE, BooleanMode.ONE_TO_MANY];
export enum BooleanCadOperationType {
INTERSECT = 'intersect',
SECTION = 'section',
SUBTRACT = 'subtract',
UNION = 'union',
}
export const BOOLEAN_CAD_OPERATION_TYPES: BooleanCadOperationType[] = [
BooleanCadOperationType.INTERSECT,
BooleanCadOperationType.SUBTRACT,
BooleanCadOperationType.UNION,
BooleanCadOperationType.SECTION,
];
class CADBooleanSopParamsConfig extends NodeParamsConfig {
/** @param operation */
operation = ParamConfig.INTEGER(BOOLEAN_CAD_OPERATION_TYPES.indexOf(BooleanCadOperationType.INTERSECT), {
menu: {entries: BOOLEAN_CAD_OPERATION_TYPES.map((name, value) => ({name, value}))},
});
/** @param mode */
mode = ParamConfig.INTEGER(BOOLEAN_MODES.indexOf(BooleanMode.ONE_TO_MANY), {
menu: {
entries: BOOLEAN_MODES.map((name, value) => ({name, value})),
},
});
}
const ParamsConfig = new CADBooleanSopParamsConfig();
export class CADBooleanSopNode extends CADSopNode<CADBooleanSopParamsConfig> {
override paramsConfig = ParamsConfig;
static override type() {
return SopType.CAD_BOOLEAN;
}
protected override initializeNode() {
this.io.inputs.setCount(1, 2);
}
setOperation(operation: BooleanCadOperationType) {
this.p.operation.set(BOOLEAN_CAD_OPERATION_TYPES.indexOf(operation));
}
override cook(inputCoreGroups: CoreGroup[]) {
const oc = CadLoaderSync.oc();
const operation = BOOLEAN_CAD_OPERATION_TYPES[this.pv.operation];
const mode = BOOLEAN_MODES[this.pv.mode];
switch (mode) {
case BooleanMode.ALL_IN_SEQUENCE: {
return this._createBooleansAllInSequence(oc, operation, inputCoreGroups);
}
case BooleanMode.ONE_TO_ONE: {
return this._createBooleansOneToOne(oc, operation, inputCoreGroups);
}
case BooleanMode.ONE_TO_MANY: {
return this._createBooleansOneToMany(oc, operation, inputCoreGroups);
}
}
TypeAssert.unreachable(mode);
}
private _createBooleansAllInSequence(
oc: OpenCascadeInstance,
operation: BooleanCadOperationType,
inputCoreGroups: CoreGroup[]
) {
const newObjects: CadObject<CadGeometryType>[] = [];
const inputCoreGroup0 = inputCoreGroups[0];
const inputCoreGroup1 = inputCoreGroups[1];
let shapeObjects0 = inputCoreGroup0.cadObjectsWithShape();
if (shapeObjects0) {
if (inputCoreGroup1) {
const shapeObjects1 = inputCoreGroup1.cadObjectsWithShape();
if (shapeObjects1) {
shapeObjects0 = shapeObjects0.concat(shapeObjects1);
}
}
// TODO: shapes with same types should be processed together?
// Or maybe not if dealing with curves and surfaces
_createBooleansAllInSequence(oc, operation, shapeObjects0, newObjects);
}
this.setCADObjects(newObjects);
}
private _createBooleansOneToOne(
oc: OpenCascadeInstance,
operation: BooleanCadOperationType,
inputCoreGroups: CoreGroup[]
) {
const newObjects: CadObject<CadGeometryType>[] = [];
const inputCoreGroup0 = inputCoreGroups[0];
const inputCoreGroup1 = inputCoreGroups[1];
if (!inputCoreGroup1) {
this.states.error.set('input 1 required for this mode');
return;
}
const shapeObjects0 = inputCoreGroup0.cadObjectsWithShape();
const shapeObjects1 = inputCoreGroup1.cadObjectsWithShape();
if (shapeObjects0 && shapeObjects1) {
_createBooleansOneToOne(oc, operation, shapeObjects0, shapeObjects1, newObjects);
}
this.setCADObjects(newObjects);
}
private _createBooleansOneToMany(
oc: OpenCascadeInstance,
operation: BooleanCadOperationType,
inputCoreGroups: CoreGroup[]
) {
const newObjects: CadObject<CadGeometryType>[] = [];
const inputCoreGroup0 = inputCoreGroups[0];
const inputCoreGroup1 = inputCoreGroups[1];
if (!inputCoreGroup1) {
this.states.error.set('input 1 required for this mode');
return;
}
const shapeObjects0 = inputCoreGroup0.cadObjectsWithShape();
const shapeObjects1 = inputCoreGroup1.cadObjectsWithShape();
if (shapeObjects0 && shapeObjects1) {
_createBooleansOneToMany(oc, operation, shapeObjects0, shapeObjects1, newObjects);
}
this.setCADObjects(newObjects);
}
// override async cook(inputCoreGroups: CadCoreGroup[]) {
// const oc = await CadLoader.core();
// const coreGroup0 = inputCoreGroups[0];
// const coreGroup1 = inputCoreGroups[1];
// const object0 = coreGroup0.objects()[0];
// const object1 = coreGroup1.objects()[0];
// const operation = BOOLEAN_CAD_OPERATION_TYPES[this.pv.operation];
// const apiClass = {
// [BooleanCadOperationType.INTERSECT]: oc.BRepAlgoAPI_Common_3,
// [BooleanCadOperationType.SUBTRACT]: oc.BRepAlgoAPI_Cut_3,
// [BooleanCadOperationType.UNION]: oc.BRepAlgoAPI_Fuse_3,
// [BooleanCadOperationType.SECTION]: oc.BRepAlgoAPI_Section_3,
// }[operation];
// if (object0 && object1 && CoreCadType.isShape(object0) && CoreCadType.isShape(object1)) {
// const cut = new apiClass(object0.object(), object1.object(), new oc.Message_ProgressRange_1());
// cut.Build(new oc.Message_ProgressRange_1());
// const shape = cut.Shape();
// this.setShell(shape);
// } else {
// this.setCadObjects([object0]);
// }
// }
}
function _createBooleansAllInSequence(
oc: OpenCascadeInstance,
operation: BooleanCadOperationType,
shapeObjects: CadObject<CadGeometryTypeShape>[],
newObjects: CadObject<CadGeometryType>[]
) {
let previousShape: TopoDS_Shape | undefined;
for (const shapeObject of shapeObjects) {
if (previousShape) {
const newShape = _booleanOperation(oc, operation, previousShape, shapeObject.cadGeometry());
previousShape = newShape;
} else {
previousShape = shapeObject.cadGeometry();
}
}
if (previousShape) {
const type = cadGeometryTypeFromShape(oc, previousShape);
if (type) {
newObjects.push(new CadObject(previousShape, type));
} else {
console.log('no type', previousShape);
}
}
}
function _createBooleansOneToOne(
oc: OpenCascadeInstance,
operation: BooleanCadOperationType,
shapeObjects0: CadObject<CadGeometryTypeShape>[],
shapeObjects1: CadObject<CadGeometryTypeShape>[],
newObjects: CadObject<CadGeometryType>[]
) {
const minVerticesCount = Math.min(shapeObjects0.length, shapeObjects1.length);
for (let i = 0; i < minVerticesCount; i++) {
const shape0 = shapeObjects0[i].cadGeometry();
const shape1 = shapeObjects1[i].cadGeometry();
const newShape = _booleanOperation(oc, operation, shape0, shape1);
const type = cadGeometryTypeFromShape(oc, newShape);
if (type) {
newObjects.push(new CadObject(newShape, type));
} else {
console.log('no type', newShape);
}
}
}
function _createBooleansOneToMany(
oc: OpenCascadeInstance,
operation: BooleanCadOperationType,
shapeObjects0: CadObject<CadGeometryTypeShape>[],
shapeObjects1: CadObject<CadGeometryTypeShape>[],
newObjects: CadObject<CadGeometryType>[]
) {
for (const shapeObject0 of shapeObjects0) {
let previousBooleanShapeResult: TopoDS_Shape = shapeObject0.cadGeometry();
for (const shapeObject1 of shapeObjects1) {
previousBooleanShapeResult = _booleanOperation(
oc,
operation,
previousBooleanShapeResult,
shapeObject1.cadGeometry()
);
}
if (previousBooleanShapeResult) {
const type = cadGeometryTypeFromShape(oc, previousBooleanShapeResult);
if (type) {
newObjects.push(new CadObject(previousBooleanShapeResult, type));
} else {
console.log('no type', previousBooleanShapeResult);
}
}
// return previousBooleanShapeResult;
}
}
function _booleanOperation(
oc: OpenCascadeInstance,
operation: BooleanCadOperationType,
shape0: TopoDS_Shape,
shape1: TopoDS_Shape
) {
switch (operation) {
case BooleanCadOperationType.INTERSECT: {
return _booleanOperationIntersect(oc, shape0, shape1);
}
case BooleanCadOperationType.SUBTRACT: {
return _booleanOperationSubtract(oc, shape0, shape1);
}
case BooleanCadOperationType.UNION: {
return _booleanOperationUnion(oc, shape0, shape1);
}
case BooleanCadOperationType.SECTION: {
return _booleanOperationSection(oc, shape0, shape1);
}
}
TypeAssert.unreachable(operation);
}
function _booleanOperationIntersect(oc: OpenCascadeInstance, shape0: TopoDS_Shape, shape1: TopoDS_Shape) {
const operation = new oc.BRepAlgoAPI_Common_3(shape0, shape1, CadLoaderSync.Message_ProgressRange);
operation.Build(CadLoaderSync.Message_ProgressRange);
const shape = operation.Shape();
operation.delete();
return shape;
}
function _booleanOperationSubtract(oc: OpenCascadeInstance, shape0: TopoDS_Shape, shape1: TopoDS_Shape) {
const operation = new oc.BRepAlgoAPI_Cut_3(shape0, shape1, CadLoaderSync.Message_ProgressRange);
operation.Build(CadLoaderSync.Message_ProgressRange);
const shape = operation.Shape();
operation.delete();
return shape;
}
function _booleanOperationUnion(oc: OpenCascadeInstance, shape0: TopoDS_Shape, shape1: TopoDS_Shape) {
const operation = new oc.BRepAlgoAPI_Fuse_3(shape0, shape1, CadLoaderSync.Message_ProgressRange);
operation.Build(CadLoaderSync.Message_ProgressRange);
const shape = operation.Shape();
operation.delete();
return shape;
}
function _booleanOperationSection(oc: OpenCascadeInstance, shape0: TopoDS_Shape, shape1: TopoDS_Shape) {
const operation = new oc.BRepAlgoAPI_Section_3(shape0, shape1, true);
// cut.Build(CadLoaderSync.Message_ProgressRange);
const shape = operation.Shape();
operation.delete();
return shape;
}