@bitbybit-dev/occt
Version:
Bit By Bit Developers CAD algorithms using OpenCascade Technology kernel. Run in Node and in Browser.
1,091 lines • 51 kB
JavaScript
import * as Inputs from "../../api/inputs/inputs";
export class FacesService {
constructor(occ, occRefReturns, entitiesService, enumService, shapeGettersService, converterService, booleansService, wiresService, transformsService, vectorService, base, filletsService) {
this.occ = occ;
this.occRefReturns = occRefReturns;
this.entitiesService = entitiesService;
this.enumService = enumService;
this.shapeGettersService = shapeGettersService;
this.converterService = converterService;
this.booleansService = booleansService;
this.wiresService = wiresService;
this.transformsService = transformsService;
this.vectorService = vectorService;
this.base = base;
this.filletsService = filletsService;
}
createFaceFromWireOnFace(inputs) {
const result = this.entitiesService.bRepBuilderAPIMakeFaceFromWireOnFace(inputs.face, inputs.wire, inputs.inside);
return result;
}
createFacesFromWiresOnFace(inputs) {
const result = this.entitiesService.bRepBuilderAPIMakeFacesFromWiresOnFace(inputs.face, inputs.wires, inputs.inside);
return result;
}
createFaceFromWire(inputs) {
let result;
if (this.enumService.getShapeTypeEnum(inputs.shape) !== Inputs.OCCT.shapeTypeEnum.wire) {
throw new Error("Provided input shape is not a wire");
}
if (inputs.planar) {
const wire = this.occ.TopoDS.Wire_1(inputs.shape);
result = this.entitiesService.bRepBuilderAPIMakeFaceFromWire(wire, inputs.planar);
wire.delete();
}
else {
const Degree = 3;
const NbPtsOnCur = 15;
const NbIter = 2;
const Anisotropie = false;
const Tol2d = 0.00001;
const Tol3d = 0.0001;
const TolAng = 0.01;
const TolCurv = 0.1;
const MaxDeg = 8;
const MaxSegments = 9;
const bs = new this.occ.BRepFill_Filling(Degree, NbPtsOnCur, NbIter, Anisotropie, Tol2d, Tol3d, TolAng, TolCurv, MaxDeg, MaxSegments);
const edges = this.shapeGettersService.getEdges(inputs);
edges.forEach(e => {
bs.Add_1(e, this.occ.GeomAbs_Shape.GeomAbs_C0, true);
});
bs.Build();
if (!bs.IsDone()) {
throw new Error("Could not create non planar face");
}
result = bs.Face();
bs.delete();
edges.forEach(e => e.delete());
}
return result;
}
getFaceArea(inputs) {
const gprops = new this.occ.GProp_GProps_1();
this.occ.BRepGProp.SurfaceProperties_1(inputs.shape, gprops, false, false);
const area = gprops.Mass();
gprops.delete();
return area;
}
getFacesAreas(inputs) {
if (inputs.shapes === undefined) {
throw (Error(("Shapes are not defined")));
}
return inputs.shapes.map(face => this.getFaceArea({ shape: face }));
}
getFaceCenterOfMass(inputs) {
const gprops = new this.occ.GProp_GProps_1();
this.occ.BRepGProp.SurfaceProperties_1(inputs.shape, gprops, false, false);
const gppnt = gprops.CentreOfMass();
const pt = [gppnt.X(), gppnt.Y(), gppnt.Z()];
gprops.delete();
gppnt.delete();
return pt;
}
getFacesCentersOfMass(inputs) {
if (inputs.shapes === undefined) {
throw (Error(("Shapes are not defined")));
}
return inputs.shapes.map(face => this.getFaceCenterOfMass({ shape: face }));
}
filterFacePoints(inputs) {
const points = [];
if (inputs.points.length > 0) {
const classifier = new this.occ.BRepClass_FaceClassifier_1();
inputs.points.forEach(pt => {
const gpPnt = this.entitiesService.gpPnt(pt);
classifier.Perform_2(inputs.shape, gpPnt, inputs.tolerance, inputs.useBndBox, inputs.gapTolerance);
const top = classifier.State();
const type = this.enumService.getTopAbsStateEnum(top);
if (inputs.keepOn && type === Inputs.OCCT.topAbsStateEnum.on) {
points.push(pt);
}
if (inputs.keepIn && type === Inputs.OCCT.topAbsStateEnum.in) {
points.push(pt);
}
if (inputs.keepOut && type === Inputs.OCCT.topAbsStateEnum.out) {
points.push(pt);
}
if (inputs.keepUnknown && type === Inputs.OCCT.topAbsStateEnum.unknown) {
points.push(pt);
}
gpPnt.delete();
});
classifier.delete();
return points;
}
else {
return [];
}
}
createSquareFace(inputs) {
const squareWire = this.wiresService.createSquareWire(inputs);
const faceMakerFromWire = this.entitiesService.bRepBuilderAPIMakeFaceFromWire(squareWire, true);
squareWire.delete();
return faceMakerFromWire;
}
createRectangleFace(inputs) {
const rectangleWire = this.wiresService.createRectangleWire(inputs);
const faceMakerFromWire = this.entitiesService.bRepBuilderAPIMakeFaceFromWire(rectangleWire, true);
rectangleWire.delete();
return faceMakerFromWire;
}
createFaceFromMultipleCircleTanWires(inputs) {
const circleWires = inputs.circles;
const faces = [];
if (inputs.combination === Inputs.OCCT.combinationCirclesForFaceEnum.allWithAll) {
for (let i = 0; i < circleWires.length; i++) {
for (let j = i + 1; j < circleWires.length; j++) {
const wire = this.wiresService.createWireFromTwoCirclesTan({
circle1: circleWires[i],
circle2: circleWires[j],
keepLines: Inputs.OCCT.twoSidesStrictEnum.outside,
circleRemainders: Inputs.OCCT.fourSidesStrictEnum.outside,
tolerance: inputs.tolerance,
});
const face = this.entitiesService.bRepBuilderAPIMakeFaceFromWire(wire, true);
faces.push(face);
}
}
}
else if (inputs.combination === Inputs.OCCT.combinationCirclesForFaceEnum.inOrder) {
for (let i = 0; i < circleWires.length - 1; i++) {
const wire = this.wiresService.createWireFromTwoCirclesTan({
circle1: circleWires[i],
circle2: circleWires[i + 1],
keepLines: Inputs.OCCT.twoSidesStrictEnum.outside,
circleRemainders: Inputs.OCCT.fourSidesStrictEnum.outside,
tolerance: inputs.tolerance,
});
const face = this.entitiesService.bRepBuilderAPIMakeFaceFromWire(wire, true);
faces.push(face);
}
}
else if (inputs.combination === Inputs.OCCT.combinationCirclesForFaceEnum.inOrderClosed) {
for (let i = 0; i < circleWires.length; i++) {
const wire = this.wiresService.createWireFromTwoCirclesTan({
circle1: circleWires[i],
circle2: circleWires[(i + 1) % circleWires.length],
keepLines: Inputs.OCCT.twoSidesStrictEnum.outside,
circleRemainders: Inputs.OCCT.fourSidesStrictEnum.outside,
tolerance: inputs.tolerance,
});
const face = this.entitiesService.bRepBuilderAPIMakeFaceFromWire(wire, true);
faces.push(face);
}
}
let result;
if (inputs.unify) {
result = this.booleansService.union({ shapes: faces, keepEdges: false });
}
else {
result = this.converterService.makeCompound({ shapes: faces });
}
return result;
}
createFaceFromMultipleCircleTanWireCollections(inputs) {
const listsOfCircles = inputs.listsOfCircles;
const faces = [];
if (inputs.combination === Inputs.OCCT.combinationCirclesForFaceEnum.allWithAll) {
for (let i = 0; i < listsOfCircles.length; i++) {
// lists of circles is a 2D array of circular wires
const currentCirclesList = listsOfCircles[i];
const nextCirclesList = listsOfCircles[(i + 1)];
if (nextCirclesList) {
for (let j = 0; j < currentCirclesList.length; j++) {
for (let k = 0; k < nextCirclesList.length; k++) {
const circle1 = currentCirclesList[j];
const circle2 = nextCirclesList[k];
const wire = this.wiresService.createWireFromTwoCirclesTan({
circle1,
circle2,
keepLines: Inputs.OCCT.twoSidesStrictEnum.outside,
circleRemainders: Inputs.OCCT.fourSidesStrictEnum.outside,
tolerance: inputs.tolerance,
});
const face = this.entitiesService.bRepBuilderAPIMakeFaceFromWire(wire, true);
faces.push(face);
}
}
}
else {
break;
}
}
}
else if (inputs.combination === Inputs.OCCT.combinationCirclesForFaceEnum.inOrder) {
for (let i = 0; i < listsOfCircles.length; i++) {
if (listsOfCircles[i].length !== listsOfCircles[0].length) {
throw new Error("All lists of circles must have the same length in order to use inOrder strategy.");
}
}
for (let i = 0; i < listsOfCircles.length - 1; i++) {
for (let j = 0; j < listsOfCircles[i].length; j++) {
const wire = this.wiresService.createWireFromTwoCirclesTan({
circle1: listsOfCircles[i][j],
circle2: listsOfCircles[i + 1][j],
keepLines: Inputs.OCCT.twoSidesStrictEnum.outside,
circleRemainders: Inputs.OCCT.fourSidesStrictEnum.outside,
tolerance: inputs.tolerance,
});
const face = this.entitiesService.bRepBuilderAPIMakeFaceFromWire(wire, true);
faces.push(face);
}
}
}
else if (inputs.combination === Inputs.OCCT.combinationCirclesForFaceEnum.inOrderClosed) {
// check if all lists are of the same length
for (let i = 0; i < listsOfCircles.length; i++) {
if (listsOfCircles[i].length !== listsOfCircles[0].length) {
throw new Error("All lists of circles must have the same length in order to use inOrderClosed strategy.");
}
}
for (let i = 0; i < listsOfCircles.length - 1; i++) {
for (let j = 0; j < listsOfCircles[i].length; j++) {
const wire = this.wiresService.createWireFromTwoCirclesTan({
circle1: listsOfCircles[i][j],
circle2: listsOfCircles[i + 1][j],
keepLines: Inputs.OCCT.twoSidesStrictEnum.outside,
circleRemainders: Inputs.OCCT.fourSidesStrictEnum.outside,
tolerance: inputs.tolerance,
});
const face = this.entitiesService.bRepBuilderAPIMakeFaceFromWire(wire, true);
faces.push(face);
}
}
for (let i = 0; i < listsOfCircles.length; i++) {
for (let j = 0; j < listsOfCircles[i].length; j++) {
const wire = this.wiresService.createWireFromTwoCirclesTan({
circle1: listsOfCircles[i][j],
circle2: listsOfCircles[i][(j + 1) % listsOfCircles[i].length],
keepLines: Inputs.OCCT.twoSidesStrictEnum.outside,
circleRemainders: Inputs.OCCT.fourSidesStrictEnum.outside,
tolerance: inputs.tolerance,
});
const face = this.entitiesService.bRepBuilderAPIMakeFaceFromWire(wire, true);
faces.push(face);
}
}
}
let result;
if (inputs.unify) {
result = this.booleansService.union({ shapes: faces, keepEdges: false });
}
else {
result = this.converterService.makeCompound({ shapes: faces });
}
return result;
}
faceNormalOnUV(inputs) {
if (inputs.shape === undefined) {
throw (Error(("Face not defined")));
}
const face = inputs.shape;
const handle = this.occ.BRep_Tool.Surface_2(face);
const { uMin, uMax, vMin, vMax } = this.getUVBounds(face);
const u = uMin + (uMax - uMin) * inputs.paramU;
const v = vMin + (vMax - vMin) * inputs.paramV;
const gpDir = this.entitiesService.gpDir([0, 1, 0]);
this.occ.GeomLib.NormEstim(handle, this.entitiesService.gpPnt2d([u, v]), 1e-7, gpDir);
if (face.Orientation_1() === this.occ.TopAbs_Orientation.TopAbs_REVERSED) {
gpDir.Reverse();
}
const dir = [gpDir.X(), gpDir.Y(), gpDir.Z()];
gpDir.delete();
handle.delete();
return dir;
}
getUVBounds(face) {
const uMin = { current: 0 };
const uMax = { current: 0 };
const vMin = { current: 0 };
const vMax = { current: 0 };
this.occRefReturns.BRepTools_UVBounds_1(face, uMin, uMax, vMin, vMax);
return { uMin: uMin.current, uMax: uMax.current, vMin: vMin.current, vMax: vMax.current };
}
createFaceFromWires(inputs) {
const result = this.entitiesService.bRepBuilderAPIMakeFaceFromWires(inputs.shapes, inputs.planar);
return result;
}
createFaceFromWiresOnFace(inputs) {
const result = this.entitiesService.bRepBuilderAPIMakeFaceFromWires(inputs.wires, false, inputs.face, inputs.inside);
return result;
}
createFacesFromWires(inputs) {
const result = inputs.shapes.map(shape => {
return this.createFaceFromWire({ shape, planar: inputs.planar });
});
return result;
}
faceFromSurface(inputs) {
const face = this.entitiesService.bRepBuilderAPIMakeFaceFromSurface(inputs.shape, inputs.tolerance);
if (face.IsNull()) {
face.delete();
throw new Error("Could not construct a face from the surface. Check if surface is not infinite.");
}
else {
return face;
}
}
faceFromSurfaceAndWire(inputs) {
const face = this.entitiesService.bRepBuilderAPIMakeFaceFromSurfaceAndWire(inputs.surface, inputs.wire, inputs.inside);
if (face.IsNull()) {
face.delete();
throw new Error("Could not construct a face from the surface. Check if surface is not infinite.");
}
else {
return face;
}
}
getUMinBound(inputs) {
const face = inputs.shape;
const { uMin } = this.getUVBounds(face);
return uMin;
}
getUMaxBound(inputs) {
const face = inputs.shape;
const { uMax } = this.getUVBounds(face);
return uMax;
}
getVMinBound(inputs) {
const face = inputs.shape;
const { vMin } = this.getUVBounds(face);
return vMin;
}
getVMaxBound(inputs) {
const face = inputs.shape;
const { vMax } = this.getUVBounds(face);
return vMax;
}
subdivideToPointsControlled(inputs) {
if (inputs.shape === undefined) {
throw (Error(("Face not defined")));
}
const face = inputs.shape;
const handle = this.occ.BRep_Tool.Surface_2(face);
const surface = handle.get();
const { uMin, uMax, vMin, vMax } = this.getUVBounds(face);
const points = [];
for (let i = 0; i < inputs.nrDivisionsU; i++) {
const stepU = (uMax - uMin) / (inputs.nrDivisionsU - 1);
const halfStepU = stepU / 2;
const stepsU = stepU * i;
for (let j = 0; j < inputs.nrDivisionsV; j++) {
const stepV = (vMax - vMin) / (inputs.nrDivisionsV - 1);
const halfStepV = stepV / 2;
const stepsV = stepV * j;
let v = vMin + stepsV;
v += (inputs.shiftHalfStepNthV && (i + inputs.shiftHalfStepVOffsetN) % inputs.shiftHalfStepNthV === 0) ? halfStepV : 0;
let u = uMin + stepsU;
u += (inputs.shiftHalfStepNthU && (j + inputs.shiftHalfStepUOffsetN) % inputs.shiftHalfStepNthU === 0) ? halfStepU : 0;
const gpPnt = this.entitiesService.gpPnt([0, 0, 0]);
surface.D0(u, v, gpPnt);
const pt = [gpPnt.X(), gpPnt.Y(), gpPnt.Z()];
let shouldPush = true;
if (i === 0 && inputs.removeStartEdgeNthU && (j + inputs.removeStartEdgeUOffsetN) % inputs.removeStartEdgeNthU === 0) {
shouldPush = false;
}
else if (i === inputs.nrDivisionsU - 1 && inputs.removeEndEdgeNthU && (j + inputs.removeEndEdgeUOffsetN) % inputs.removeEndEdgeNthU === 0) {
shouldPush = false;
}
else if (j === 0 && inputs.removeStartEdgeNthV && (i + inputs.removeStartEdgeVOffsetN) % inputs.removeStartEdgeNthV === 0) {
shouldPush = false;
}
else if (j === inputs.nrDivisionsV - 1 && inputs.removeEndEdgeNthV && (i + inputs.removeEndEdgeVOffsetN) % inputs.removeEndEdgeNthV === 0) {
shouldPush = false;
}
if (shouldPush) {
points.push(pt);
}
gpPnt.delete();
}
}
handle.delete();
return points;
}
subdivideToPoints(inputs) {
if (inputs.shape === undefined) {
throw (Error(("Face not defined")));
}
const face = inputs.shape;
const handle = this.occ.BRep_Tool.Surface_2(face);
const surface = handle.get();
const { uMin, uMax, vMin, vMax } = this.getUVBounds(face);
const points = [];
const uStartRemoval = inputs.removeStartEdgeU ? 1 : 0;
const uEndRemoval = inputs.removeEndEdgeU ? 1 : 0;
const vStartRemoval = inputs.removeStartEdgeV ? 1 : 0;
const vEndRemoval = inputs.removeEndEdgeV ? 1 : 0;
for (let i = 0 + uStartRemoval; i < inputs.nrDivisionsU - uEndRemoval; i++) {
const stepU = (uMax - uMin) / (inputs.nrDivisionsU - 1);
const halfStepU = stepU / 2;
const stepsU = stepU * i;
const u = uMin + (inputs.shiftHalfStepU ? halfStepU : 0) + stepsU;
for (let j = 0 + vStartRemoval; j < inputs.nrDivisionsV - vEndRemoval; j++) {
const stepV = (vMax - vMin) / (inputs.nrDivisionsV - 1);
const halfStepV = stepV / 2;
const stepsV = stepV * j;
const v = vMin + (inputs.shiftHalfStepV ? halfStepV : 0) + stepsV;
const gpPnt = this.entitiesService.gpPnt([0, 0, 0]);
surface.D0(u, v, gpPnt);
const pt = [gpPnt.X(), gpPnt.Y(), gpPnt.Z()];
points.push(pt);
gpPnt.delete();
}
}
handle.delete();
return points;
}
subdivideToWires(inputs) {
if (inputs.shape === undefined) {
throw (Error(("Face not defined")));
}
const face = inputs.shape;
const handle = this.occ.BRep_Tool.Surface_2(face);
const surface = handle.get();
const { uMin, uMax, vMin, vMax } = this.getUVBounds(face);
const params = [];
const step = 1 / inputs.nrDivisions;
for (let i = 0; i <= inputs.nrDivisions; i++) {
const p = step * i;
params.push(p);
}
if (inputs.removeStart) {
params.shift();
}
if (inputs.removeEnd) {
params.pop();
}
if (inputs.shiftHalfStep) {
const halfStep = step / 2;
params.forEach((p, i) => {
params[i] = params[i] + halfStep;
});
}
const wires = [];
for (let i = 0; i < params.length; i++) {
const param = params[i];
const placedWire = this.placeWireOnParamSurface(inputs.isU, param, uMin, uMax, vMin, vMax, surface);
wires.push(placedWire);
}
handle.delete();
return wires;
}
subdivideToRectangleWires(inputs) {
var _a;
if (inputs.shape === undefined) {
throw (Error(("Face not defined")));
}
const shapesToDelete = [];
const face = inputs.shape;
const handle = this.occ.BRep_Tool.Surface_2(face);
const surface = handle.get();
const { uMin, uMax, vMin, vMax } = this.getUVBounds(face);
const paramsU = [];
const stepU = (1 - inputs.offsetFromBorderU * 2) / inputs.nrRectanglesU;
const halfStepU = stepU / 2;
for (let i = 0; i < inputs.nrRectanglesU; i++) {
const pU = stepU * i + halfStepU + inputs.offsetFromBorderU;
paramsU.push(pU);
}
const paramsV = [];
const stepV = (1 - inputs.offsetFromBorderV * 2) / inputs.nrRectanglesV;
const halfStepV = stepV / 2;
for (let i = 0; i < inputs.nrRectanglesV; i++) {
const pV = stepV * i + halfStepV + inputs.offsetFromBorderV;
paramsV.push(pV);
}
// figure out actual parametric scale
const line1 = this.wiresService.createLineWire({
start: [0, 0, 0],
end: [1, 0, 0],
});
const line2 = this.wiresService.createLineWire({
start: [0, 0, 0],
end: [0, 0, 1],
});
const placedLine1 = this.wiresService.placeWire(line1, surface);
const placedLine2 = this.wiresService.placeWire(line2, surface);
const scaleX = this.wiresService.getWireLength({ shape: placedLine1 });
const scaleZ = this.wiresService.getWireLength({ shape: placedLine2 });
const scaleU = (uMax - uMin);
const scaleV = (vMax - vMin);
const wires = [];
let currentScalePatternUIndex = 0;
let currentScalePatternVIndex = 0;
let currentInclusionPatternIndex = 0;
let currentFilletPatternIndex = 0;
// potentially each rectangle can have unique fillets and scale factors due to patterns applied
// we can though optimise this by using cached rectangles to speed up the algorithm
const cachedRectangles = [];
for (let i = 0; i < paramsU.length; i++) {
for (let j = 0; j < paramsV.length; j++) {
let scaleFromPatternU = 1;
if (inputs.scalePatternU && inputs.scalePatternU.length > 0) {
scaleFromPatternU = inputs.scalePatternU[currentScalePatternUIndex];
currentScalePatternUIndex++;
if (currentScalePatternUIndex >= inputs.scalePatternU.length) {
currentScalePatternUIndex = 0;
}
}
let scaleFromPatternV = 1;
if (inputs.scalePatternV && inputs.scalePatternV.length > 0) {
scaleFromPatternV = inputs.scalePatternV[currentScalePatternVIndex];
currentScalePatternVIndex++;
if (currentScalePatternVIndex >= inputs.scalePatternV.length) {
currentScalePatternVIndex = 0;
}
}
let include = true;
if (inputs.inclusionPattern && inputs.inclusionPattern.length > 0) {
include = inputs.inclusionPattern[currentInclusionPatternIndex];
currentInclusionPatternIndex++;
if (currentInclusionPatternIndex >= inputs.inclusionPattern.length) {
currentInclusionPatternIndex = 0;
}
}
let fillet = 0;
if (inputs.filletPattern && inputs.filletPattern.length > 0) {
fillet = inputs.filletPattern[currentFilletPatternIndex];
currentFilletPatternIndex++;
if (currentFilletPatternIndex >= inputs.filletPattern.length) {
currentFilletPatternIndex = 0;
}
}
if (include) {
const width = stepV * scaleFromPatternV;
const length = stepU * scaleFromPatternU;
const minForFillet = Math.min(width * scaleV * scaleX, length * scaleU * scaleZ);
if (minForFillet === width * scaleV * scaleX) {
fillet = minForFillet / 2 * fillet;
}
else if (minForFillet === length * scaleU * scaleZ) {
fillet = minForFillet / 2 * fillet;
}
const useRec = (_a = cachedRectangles.find(r => r.id === `${width}-${length}-${fillet}`)) === null || _a === void 0 ? void 0 : _a.shape;
const translation = [paramsV[j] * scaleV + vMin, 0, paramsU[i] * scaleU + uMin];
if (useRec) {
const translated = this.transformsService.translate({
shape: useRec,
translation,
});
const placedRec = this.wiresService.placeWire(translated, surface);
wires.push(placedRec);
}
else {
const rectangle = this.wiresService.createRectangleWire({
width,
length,
center: [0, 0, 0],
direction: [0, 1, 0],
});
if (fillet > 0) {
const scaleVec2 = [scaleV * scaleX, 1, scaleU * scaleZ];
const scaledRec2 = this.transformsService.scale3d({
shape: rectangle,
center: [0, 0, 0],
scale: scaleVec2,
});
const filletRectangle = this.filletsService.fillet2d({
shape: scaledRec2,
radius: fillet,
});
const scaleVec3 = [1 / scaleX, 1, 1 / scaleZ];
let scaledRec3 = filletRectangle;
if (!this.vectorService.vectorsTheSame(scaleVec3, [1, 1, 1], 1e-7)) {
scaledRec3 = this.transformsService.scale3d({
shape: filletRectangle,
center: [0, 0, 0],
scale: scaleVec3,
});
}
const translated = this.transformsService.translate({
shape: scaledRec3,
translation,
});
shapesToDelete.push(rectangle);
const placedRec = this.wiresService.placeWire(translated, surface);
wires.push(placedRec);
cachedRectangles.push({ id: `${width}-${length}-${fillet}`, shape: scaledRec3 });
}
else {
const scaledRec = this.transformsService.scale3d({
shape: rectangle,
center: [0, 0, 0],
scale: [scaleV, 1, scaleU],
});
const translated = this.transformsService.translate({
shape: scaledRec,
translation,
});
shapesToDelete.push(rectangle);
const placedRec = this.wiresService.placeWire(translated, surface);
wires.push(placedRec);
cachedRectangles.push({ id: `${width}-${length}-${fillet}`, shape: scaledRec });
}
}
}
}
}
shapesToDelete.forEach(s => s.delete());
return wires;
}
subdivideToRectangleHoles(inputs) {
// default should be smaller then 1 as that can't punch holes or create faces nicely.
if (inputs.scalePatternU === undefined) {
inputs.scalePatternU = [0.5];
}
if (inputs.scalePatternV === undefined) {
inputs.scalePatternV = [0.5];
}
const wires = this.subdivideToRectangleWires(inputs);
const faceWires = this.shapeGettersService.getWires({ shape: inputs.shape });
const wireLengths = this.wiresService.getWiresLengths({ shapes: faceWires });
const longestFaceWire = faceWires[wireLengths.indexOf(Math.max(...wireLengths))];
const revWires = wires.map(wire => { return this.wiresService.reversedWire({ shape: wire }); });
const listOfWires = [longestFaceWire, ...revWires];
const newFace = this.createFaceFromWiresOnFace({ wires: listOfWires, face: inputs.shape, inside: true });
// check if the normals are the same, if not reverse the face
const normalOriginal = this.faceNormalOnUV({ shape: inputs.shape, paramU: 0, paramV: 0 });
const normalNew = this.faceNormalOnUV({ shape: newFace, paramU: 0, paramV: 0 });
let shouldReverse = false;
if (this.vectorService.angleBetweenVectors(normalOriginal, normalNew) > 1e-7) {
shouldReverse = true;
newFace.Reverse();
}
let faces = [];
if (inputs.holesToFaces) {
faces = wires.map(wire => {
return this.createFaceFromWireOnFace({ wire, face: inputs.shape, inside: true });
});
if (shouldReverse) {
faces.forEach(f => f.Reverse());
}
}
wires.forEach(w => w.delete());
longestFaceWire.delete();
revWires.forEach(w => w.delete());
return [newFace, ...faces];
}
subdivideToHexagonWires(inputs) {
var _a, _b, _c, _d;
if (inputs.shape === undefined) {
throw new Error("Face not defined");
}
const shapesToDelete = [];
const face = inputs.shape;
const handle = this.occ.BRep_Tool.Surface_2(face);
const surface = handle.get();
const { uMin, uMax, vMin, vMax } = this.getUVBounds(face);
// Calculate parametric range
const scaleU = uMax - uMin;
const scaleV = vMax - vMin;
if (scaleU <= 0 || scaleV <= 0) {
console.warn("Face has zero or negative parametric range. Skipping.");
return [];
}
// Calculate target parametric dimensions and origin for the grid
const gridHeightU = scaleU * (1 - inputs.offsetFromBorderU * 2);
const gridWidthV = scaleV * (1 - inputs.offsetFromBorderV * 2);
const gridOriginU = uMin + scaleU * inputs.offsetFromBorderU;
const gridOriginV = vMin + scaleV * inputs.offsetFromBorderV;
if (gridHeightU <= 0 || gridWidthV <= 0) {
console.warn("Grid dimensions are zero or negative after applying offset. Skipping.");
return [];
}
// Generate hexagon grid in local 2D space (assuming X maps to V, Z maps to U)
const hex = this.base.point.hexGridScaledToFit({
width: gridWidthV,
height: gridHeightU,
nrHexagonsInHeight: inputs.nrHexagonsU,
nrHexagonsInWidth: inputs.nrHexagonsV,
centerGrid: false,
pointsOnGround: true,
flatTop: inputs.flatU,
extendTop: inputs.extendUUp,
extendBottom: inputs.extendUBottom,
extendLeft: inputs.extendVBottom,
extendRight: inputs.extendVUp,
});
// Create wires in local 2D space
const localHexWires = hex.hexagons.map(hexPoints => {
return this.wiresService.createPolygonWire({
points: hexPoints
});
});
shapesToDelete.push(...localHexWires);
// Define the translation vector to map local grid origin to parametric grid origin
const uvTranslation = [gridOriginV, 0, gridOriginU];
// Translate wires to parametric UV space
const uvHexWires = localHexWires.map(h => {
return this.transformsService.translate({
shape: h,
translation: uvTranslation
});
});
shapesToDelete.push(...uvHexWires);
// Translate centers to parametric UV space
const uvHexCenters = this.base.point.translatePoints({
points: hex.centers,
translation: uvTranslation
});
const finalPlacedWires = [];
let currentScalePatternUIndex = 0;
let currentScalePatternVIndex = 0;
let currentInclusionPatternIndex = 0;
let currentFilletPatternIndex = 0;
// Ensure we have enough hexagons generated for the loop counts
const totalHexagons = inputs.nrHexagonsU * inputs.nrHexagonsV;
if (uvHexWires.length !== totalHexagons || uvHexCenters.length !== totalHexagons) {
console.error(`Generated ${uvHexWires.length} hexagons, but expected ${totalHexagons}. Check hexGridScaledToFit logic.`);
return [];
}
// Process each hexagon (scale, fillet, place)
for (let i = 0; i < inputs.nrHexagonsU; i++) {
for (let j = 0; j < inputs.nrHexagonsV; j++) {
const hexIndex = i * inputs.nrHexagonsV + j;
// Get scale/inclusion/fillet values from patterns
let scaleFromPatternU = 1;
if (((_a = inputs.scalePatternU) === null || _a === void 0 ? void 0 : _a.length) > 0) {
scaleFromPatternU = inputs.scalePatternU[currentScalePatternUIndex % inputs.scalePatternU.length];
currentScalePatternUIndex++;
}
let scaleFromPatternV = 1;
if (((_b = inputs.scalePatternV) === null || _b === void 0 ? void 0 : _b.length) > 0) {
scaleFromPatternV = inputs.scalePatternV[currentScalePatternVIndex % inputs.scalePatternV.length];
currentScalePatternVIndex++;
}
let include = true;
if (((_c = inputs.inclusionPattern) === null || _c === void 0 ? void 0 : _c.length) > 0) {
include = inputs.inclusionPattern[currentInclusionPatternIndex % inputs.inclusionPattern.length];
currentInclusionPatternIndex++;
}
let filletFactor = 0;
if (((_d = inputs.filletPattern) === null || _d === void 0 ? void 0 : _d.length) > 0) {
filletFactor = inputs.filletPattern[currentFilletPatternIndex % inputs.filletPattern.length];
currentFilletPatternIndex++;
}
if (include) {
const uvHexagon = uvHexWires[hexIndex];
const uvCenter = uvHexCenters[hexIndex];
let shapeToScale = uvHexagon;
// Apply Fillet (using the factor)
const filletRadius = hex.maxFilletRadius * filletFactor;
if (filletRadius > 1e-6) {
const filletedHex = this.filletsService.fillet2d({
shape: uvHexagon,
radius: filletRadius,
});
shapesToDelete.push(filletedHex);
shapeToScale = filletedHex;
}
// Apply Scaling (around the correct UV center)
let shapeToPlace = shapeToScale;
// Scaling vector maps to V
const scaleVec = [scaleFromPatternV, 1, scaleFromPatternU];
if (Math.abs(scaleFromPatternU - 1.0) > 1e-6 || Math.abs(scaleFromPatternV - 1.0) > 1e-6) {
const scaledHex = this.transformsService.scale3d({
shape: shapeToScale,
center: uvCenter,
scale: scaleVec,
});
shapesToDelete.push(scaledHex);
shapeToPlace = scaledHex;
}
// Place the final processed wire onto the surface
const placedWire = this.wiresService.placeWire(shapeToPlace, surface);
finalPlacedWires.push(placedWire);
}
}
}
shapesToDelete.forEach(s => {
s.delete();
});
return finalPlacedWires;
}
subdivideToHexagonHoles(inputs) {
if (inputs.scalePatternU === undefined) {
inputs.scalePatternU = [0.5];
}
if (inputs.scalePatternV === undefined) {
inputs.scalePatternV = [0.5];
}
const wires = this.subdivideToHexagonWires(inputs);
const faceWires = this.shapeGettersService.getWires({ shape: inputs.shape });
const wireLengths = this.wiresService.getWiresLengths({ shapes: faceWires });
const longestFaceWire = faceWires[wireLengths.indexOf(Math.max(...wireLengths))];
const revWires = wires.map(wire => { return this.wiresService.reversedWire({ shape: wire }); });
const listOfWires = [longestFaceWire, ...revWires];
const newFace = this.createFaceFromWiresOnFace({ wires: listOfWires, face: inputs.shape, inside: true });
// check if the normals are the same, if not reverse the face
const normalOriginal = this.faceNormalOnUV({ shape: inputs.shape, paramU: 0, paramV: 0 });
const normalNew = this.faceNormalOnUV({ shape: newFace, paramU: 0, paramV: 0 });
let shouldReverse = false;
if (this.vectorService.angleBetweenVectors(normalOriginal, normalNew) > 1e-7) {
shouldReverse = true;
newFace.Reverse();
}
let faces = [];
if (inputs.holesToFaces) {
faces = wires.map(wire => {
return this.createFaceFromWireOnFace({ wire, face: inputs.shape, inside: true });
});
if (shouldReverse) {
faces.forEach(f => f.Reverse());
}
}
wires.forEach(w => w.delete());
longestFaceWire.delete();
revWires.forEach(w => w.delete());
return [newFace, ...faces];
}
subdivideToNormals(inputs) {
if (inputs.shape === undefined) {
throw (Error(("Face not defined")));
}
const face = inputs.shape;
const handle = this.occ.BRep_Tool.Surface_2(face);
const { uMin, uMax, vMin, vMax } = this.getUVBounds(face);
const points = [];
const uStartRemoval = inputs.removeStartEdgeU ? 1 : 0;
const uEndRemoval = inputs.removeEndEdgeU ? 1 : 0;
const vStartRemoval = inputs.removeStartEdgeV ? 1 : 0;
const vEndRemoval = inputs.removeEndEdgeV ? 1 : 0;
for (let i = 0 + uStartRemoval; i < inputs.nrDivisionsU - uEndRemoval; i++) {
const stepU = (uMax - uMin) / (inputs.nrDivisionsU - 1);
const halfStepU = stepU / 2;
const stepsU = stepU * i;
const u = uMin + (inputs.shiftHalfStepU ? halfStepU : 0) + stepsU;
for (let j = 0 + vStartRemoval; j < inputs.nrDivisionsV - vEndRemoval; j++) {
const stepV = (vMax - vMin) / (inputs.nrDivisionsV - 1);
const halfStepV = stepV / 2;
const stepsV = stepV * j;
const v = vMin + (inputs.shiftHalfStepV ? halfStepV : 0) + stepsV;
const gpDir = this.entitiesService.gpDir([0, 1, 0]);
const gpUv = this.entitiesService.gpPnt2d([u, v]);
this.occ.GeomLib.NormEstim(handle, gpUv, 1e-7, gpDir);
// Sometimes face gets reversed and its original surface is not reversed, thus we need to adjust for such situation.
if (face.Orientation_1() === this.occ.TopAbs_Orientation.TopAbs_REVERSED) {
gpDir.Reverse();
}
const pt = [gpDir.X(), gpDir.Y(), gpDir.Z()];
points.push(pt);
gpDir.delete();
gpUv.delete();
}
}
handle.delete();
return points;
}
wireAlongParam(inputs) {
if (inputs.shape === undefined) {
throw (Error(("Face not defined")));
}
const face = inputs.shape;
const handle = this.occ.BRep_Tool.Surface_2(face);
const surface = handle.get();
const { uMin, uMax, vMin, vMax } = this.getUVBounds(face);
const placedWire = this.placeWireOnParamSurface(inputs.isU, inputs.param, uMin, uMax, vMin, vMax, surface);
handle.delete();
return placedWire;
}
placeWireOnParamSurface(isU, param, uMin, uMax, vMin, vMax, surface) {
let paramToUse = param;
let wire;
if (isU) {
paramToUse = uMin + (uMax - uMin) * param;
wire = this.wiresService.createLineWire({
start: [vMin, 0, paramToUse],
end: [vMax, 0, paramToUse],
});
}
else {
paramToUse = vMin + (vMax - vMin) * param;
wire = this.wiresService.createLineWire({
start: [paramToUse, 0, uMin],
end: [paramToUse, 0, uMax],
});
}
const placedWire = this.wiresService.placeWire(wire, surface);
wire.delete();
return placedWire;
}
wiresAlongParams(inputs) {
if (inputs.shape === undefined) {
throw (Error(("Face not defined")));
}
const face = inputs.shape;
const handle = this.occ.BRep_Tool.Surface_2(face);
const surface = handle.get();
const { uMin, uMax, vMin, vMax } = this.getUVBounds(face);
const wires = [];
for (let i = 0; i < inputs.params.length; i++) {
const param = inputs.params[i];
const placedWire = this.placeWireOnParamSurface(inputs.isU, param, uMin, uMax, vMin, vMax, surface);
wires.push(placedWire);
}
handle.delete();
return wires;
}
subdivideToPointsOnParam(inputs) {
if (inputs.shape === undefined) {
throw (Error(("Face not defined")));
}
const face = inputs.shape;
const handle = this.occ.BRep_Tool.Surface_2(face);
const surface = handle.get();
const { uMin, uMax, vMin, vMax } = this.getUVBounds(face);
const points = [];
const removeStart = inputs.removeStartPoint ? 1 : 0;
const removeEnd = inputs.removeEndPoint ? 1 : 0;
let param = inputs.param;
if (inputs.isU) {
param = uMin + (uMax - uMin) * param;
}
else {
param = vMin + (vMax - vMin) * param;
}
for (let j = 0 + removeStart; j < inputs.nrPoints - removeEnd; j++) {
let p;
if (inputs.isU) {
const stepV = (vMax - vMin) / (inputs.nrPoints - 1);
const halfStepV = stepV / 2;
const stepsV = stepV * j;
p = vMin + (inputs.shiftHalfStep ? halfStepV : 0) + stepsV;
}
else {
const stepU = (uMax - uMin) / (inputs.nrPoints - 1);
const halfStepU = stepU / 2;
const stepsU = stepU * j;
p = uMin + (inputs.shiftHalfStep ? halfStepU : 0) + stepsU;
}
const gpPnt = this.entitiesService.gpPnt([0, 0, 0]);
if (inputs.isU) {
surface.D0(param, p, gpPnt);
}
else {
surface.D0(p, param, gpPnt);
}
const pt = [gpPnt.X(), gpPnt.Y(), gpPnt.Z()];
points.push(pt);
gpPnt.delete();
}
handle.delete();
return points;
}
subdivideToUVOnParam(inputs) {
if (inputs.shape === undefined) {
throw (Error(("Face not defined")));
}
const face = inputs.shape;
const { uMin, uMax, vMin, vMax } = this.getUVBounds(face);
const uvs = [];
const removeStart = inputs.removeStartPoint ? 1 : 0;
const removeEnd = inputs.removeEndPoint ? 1 : 0;
let param = inputs.param;
if (inputs.isU) {
param = uMin + (uMax - uMin) * param;
}
else {
param = vMin + (vMax - vMin) * param;
}
for (let j = 0 + removeStart; j < inputs.nrPoints - removeEnd; j++) {
let p;
if (inputs.isU) {
const stepV = (vMax - vMin) / (inputs.nrPoints - 1);
const halfStepV = stepV / 2;
const stepsV = stepV * j;
p = vMin + (inputs.shiftHalfStep ? halfStepV : 0) + stepsV;
}
else {
const stepU = (uMax - uMin) / (inputs.nrPoints - 1);
const halfStepU = stepU / 2;
const stepsU = stepU * j;
p = uMin + (inputs.shiftHalfStep ? halfStepU : 0) + stepsU;
}
let uv;
if (inputs.isU) {
uv = [param, p];
}
else {
uv = [p, param];
}
uvs.push(uv);
}
return uvs;
}
subdivideToUV(inputs) {
if (inputs.shape === undefined) {
throw (Error(("Face not defined")));
}
const face = inputs.shape;
const { uMin, uMax, vMin, vMax } = this.getUVBounds(face);
const uvs = [];
const uStartRemoval = inputs.removeStartEdgeU ? 1 : 0;
const uEndRemoval = inputs.removeEndEdgeU ? 1 : 0;
const vStartRemoval = inputs.removeStartEdgeV ? 1 : 0;
const vEndRemoval = inputs.removeEndEdgeV ? 1 : 0;
for (let i = 0 + uStartRemoval; i < inputs.nrDivisionsU - uEndRemoval; i++) {
const stepU = (uMax - uMin) / (inputs.nrDivisionsU - 1);
const halfStepU = stepU / 2;
const stepsU = stepU * i;
const u = uMin + (inputs.shiftHalfStepU ? halfStepU : 0) + stepsU;
for (let j = 0 + vStartRemoval; j < inputs.nrDivisionsV - vEndRemoval; j++) {
const stepV = (vMax - vMin) / (inputs.nrDivisionsV - 1);
const halfStepV = stepV / 2;
const stepsV = stepV * j;
const v = vMin + (inputs.shiftHalfStepV ? halfStepV : 0) + stepsV;
uvs.push([u, v]);
}
}
return uvs;
}
uvOnFace(inputs) {
if (inputs.shape === undefined) {
throw (Error(("Face not defined")));
}
const face = inputs.shape;
const { uMin, uMax, vMin, vMax } = this.getUVBounds(face);
const u = uMin + (uMax - uMin) * inputs.paramU;
const v = vMin + (vMax - vMin) * inputs.paramV;
return [u, v];
}
pointsOnUVs(inputs) {
if (inputs.shape === undefined) {
throw (Error(("Face not defined")));
}
const face = inputs.shape;
const handle = this.occ.BRep_Tool.Surface_2(face);
const surface = handle.get();
const { uMin, uMax, vMin, vMax } = this.getUVBounds(face);
const pts = inputs.paramsUV.map(uv => {
const u = uMin + (uMax - uMin) * uv[0];
const v = vMin + (vMax - vMin) * uv[1];
const gpPnt = this.entitiesService.gpPnt([0, 0, 0]);
surface.D0(u, v, gpPnt);
const pt = [gpPnt.X(), gpPnt.Y(), gpPnt.Z()];
gpPnt.delete();
return pt;
});
return pts;
}
normalsOnUVs(inputs) {
if (inputs.shape === undefined) {
throw (Error(("Face not defined")));
}
const face = inputs.shape;
const handle = this.occ.BRep_Tool.Surface_2(face);
const { uMin, uMax, vMin, vMax } = this.getUVBounds(face);
const nrmls = inputs.paramsUV.map(uv => {
const u = uMin + (uMax - uMin) * uv[0];
const v = vMin + (vMax - vMin) * uv[1];
const gpDir = this.entitiesService.gpDir([0, 1, 0]);
this.occ.GeomLib.NormEstim(handle, this.entitiesService.gpPnt2d([u, v]), 1e-7, gpDir);
const pt = [gpDir.X(), gpDir.Y(), gpDir.Z()];
gpDir.delete();
return pt;
});
handle.delete();
return nrmls;
}
pointOnUV(inputs) {
if (inputs.shape === undefined) {
throw