UNPKG

@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
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