UNPKG

@bitbybit-dev/occt

Version:

Bit By Bit Developers CAD algorithms using OpenCascade Technology kernel. Run in Node and in Browser.

489 lines (488 loc) 23 kB
export class FilletsService { constructor(occ, vecHelper, iteratorService, converterService, entitiesService, transformsService, shapeGettersService, edgesService, operationsService, facesService) { this.occ = occ; this.vecHelper = vecHelper; this.iteratorService = iteratorService; this.converterService = converterService; this.entitiesService = entitiesService; this.transformsService = transformsService; this.shapeGettersService = shapeGettersService; this.edgesService = edgesService; this.operationsService = operationsService; this.facesService = facesService; } filletEdges(inputs) { if (!inputs.indexes || (inputs.indexes.length && inputs.indexes.length === 0)) { if (inputs.radius === undefined) { throw (Error("Radius not defined")); } const mkFillet = new this.occ.BRepFilletAPI_MakeFillet(inputs.shape, this.occ.ChFi3d_FilletShape.ChFi3d_Rational); const anEdgeExplorer = new this.occ.TopExp_Explorer_2(inputs.shape, this.occ.TopAbs_ShapeEnum.TopAbs_EDGE, this.occ.TopAbs_ShapeEnum.TopAbs_SHAPE); const edges = []; while (anEdgeExplorer.More()) { const anEdge = this.occ.TopoDS.Edge_1(anEdgeExplorer.Current()); edges.push(anEdge); mkFillet.Add_2(inputs.radius, anEdge); anEdgeExplorer.Next(); } const result = mkFillet.Shape(); mkFillet.delete(); anEdgeExplorer.delete(); edges.forEach(e => e.delete()); return result; } else if (inputs.indexes && inputs.indexes.length > 0) { const mkFillet = new this.occ.BRepFilletAPI_MakeFillet(inputs.shape, this.occ.ChFi3d_FilletShape.ChFi3d_Rational); let foundEdges = 0; let curFillet; let radiusIndex = 0; const inputIndexes = inputs.indexes; this.iteratorService.forEachEdge(inputs.shape, (index, edge) => { if (inputIndexes.includes(index)) { let radius = inputs.radius; if (inputs.radiusList) { radius = inputs.radiusList[radiusIndex]; radiusIndex++; } if (radius === undefined) { throw (Error("Radius not defined, or radiusList not correct length")); } mkFillet.Add_2(radius, edge); foundEdges++; } }); if (foundEdges === 0) { throw (new Error("Fillet Edges Not Found! Make sure you are looking at the object _before_ the Fillet is applied!")); } else { curFillet = mkFillet.Shape(); } mkFillet.delete(); const result = this.converterService.getActualTypeOfShape(curFillet); curFillet.delete(); return result; } return undefined; } filletEdgesListOneRadius(inputs) { if (inputs.edges && inputs.edges.length > 0) { const mkFillet = new this.occ.BRepFilletAPI_MakeFillet(inputs.shape, this.occ.ChFi3d_FilletShape.ChFi3d_Rational); inputs.edges.forEach((edge) => { mkFillet.Add_2(inputs.radius, edge); }); const curFillet = mkFillet.Shape(); mkFillet.delete(); const result = this.converterService.getActualTypeOfShape(curFillet); curFillet.delete(); return result; } return undefined; } filletEdgesList(inputs) { if (inputs.edges && inputs.edges.length > 0 && inputs.radiusList && inputs.radiusList.length > 0 && inputs.edges.length === inputs.radiusList.length) { const mkFillet = new this.occ.BRepFilletAPI_MakeFillet(inputs.shape, this.occ.ChFi3d_FilletShape.ChFi3d_Rational); inputs.edges.forEach((edge, index) => { mkFillet.Add_2(inputs.radiusList[index], edge); }); const curFillet = mkFillet.Shape(); mkFillet.delete(); const result = this.converterService.getActualTypeOfShape(curFillet); curFillet.delete(); return result; } return undefined; } filletEdgeVariableRadius(inputs) { if (inputs.paramsU && inputs.paramsU.length > 0 && inputs.radiusList && inputs.radiusList.length > 0 && inputs.paramsU.length === inputs.radiusList.length) { const mkFillet = new this.occ.BRepFilletAPI_MakeFillet(inputs.shape, this.occ.ChFi3d_FilletShape.ChFi3d_Rational); this.assignVariableFilletToEdge(inputs, mkFillet); const curFillet = mkFillet.Shape(); mkFillet.delete(); const result = this.converterService.getActualTypeOfShape(curFillet); curFillet.delete(); return result; } return undefined; } filletEdgesSameVariableRadius(inputs) { if (inputs.edges && inputs.edges.length > 0 && inputs.radiusList && inputs.radiusList.length > 0 && inputs.paramsU.length === inputs.radiusList.length) { const mkFillet = new this.occ.BRepFilletAPI_MakeFillet(inputs.shape, this.occ.ChFi3d_FilletShape.ChFi3d_Rational); inputs.edges.forEach((edge) => { this.assignVariableFilletToEdge({ edge, paramsU: inputs.paramsU, radiusList: inputs.radiusList, shape: inputs.shape, }, mkFillet); }); const curFillet = mkFillet.Shape(); mkFillet.delete(); const result = this.converterService.getActualTypeOfShape(curFillet); curFillet.delete(); return result; } return undefined; } filletEdgesVariableRadius(inputs) { if (inputs.edges && inputs.edges.length > 0 && inputs.radiusLists && inputs.radiusLists.length > 0 && inputs.paramsULists.length === inputs.radiusLists.length && inputs.paramsULists.length === inputs.edges.length && inputs.radiusLists.length === inputs.edges.length) { const mkFillet = new this.occ.BRepFilletAPI_MakeFillet(inputs.shape, this.occ.ChFi3d_FilletShape.ChFi3d_Rational); inputs.edges.forEach((edge, index) => { this.assignVariableFilletToEdge({ edge, paramsU: inputs.paramsULists[index], radiusList: inputs.radiusLists[index], shape: inputs.shape, }, mkFillet); }); const curFillet = mkFillet.Shape(); mkFillet.delete(); const result = this.converterService.getActualTypeOfShape(curFillet); curFillet.delete(); return result; } return undefined; } assignVariableFilletToEdge(inputs, mkFillet) { const array = new this.occ.TColgp_Array1OfPnt2d_2(1, inputs.paramsU.length); inputs.paramsU.forEach((param, index) => { array.SetValue(index + 1, this.entitiesService.gpPnt2d([param, inputs.radiusList[index]])); }); mkFillet.Add_5(array, inputs.edge); } chamferEdges(inputs) { if (!inputs.indexes || (inputs.indexes.length && inputs.indexes.length === 0)) { if (inputs.distance === undefined) { throw (Error("Distance is undefined")); } const mkChamfer = new this.occ.BRepFilletAPI_MakeChamfer(inputs.shape); const anEdgeExplorer = new this.occ.TopExp_Explorer_2(inputs.shape, this.occ.TopAbs_ShapeEnum.TopAbs_EDGE, this.occ.TopAbs_ShapeEnum.TopAbs_SHAPE); const edges = []; while (anEdgeExplorer.More()) { const anEdge = this.occ.TopoDS.Edge_1(anEdgeExplorer.Current()); edges.push(anEdge); mkChamfer.Add_2(inputs.distance, anEdge); anEdgeExplorer.Next(); } const result = mkChamfer.Shape(); mkChamfer.delete(); anEdgeExplorer.delete(); edges.forEach(e => e.delete()); return result; } else if (inputs.indexes && inputs.indexes.length > 0) { const mkChamfer = new this.occ.BRepFilletAPI_MakeChamfer(inputs.shape); let foundEdges = 0; let curChamfer; let distanceIndex = 0; const inputIndexes = inputs.indexes; this.iteratorService.forEachEdge(inputs.shape, (index, edge) => { if (inputIndexes.includes(index)) { let distance = inputs.distance; if (inputs.distanceList) { distance = inputs.distanceList[distanceIndex]; distanceIndex++; } if (distance === undefined) { throw (Error("Distance not defined and/or distance list incorrect length")); } mkChamfer.Add_2(distance, edge); foundEdges++; } }); if (foundEdges === 0) { console.error("Chamfer Edges Not Found! Make sure you are looking at the object _before_ the Fillet is applied!"); curChamfer = inputs.shape; } else { curChamfer = mkChamfer.Shape(); } mkChamfer.delete(); const result = this.converterService.getActualTypeOfShape(curChamfer); curChamfer.delete(); return result; } return undefined; } chamferEdgesList(inputs) { if (inputs.edges && inputs.edges.length > 0 && inputs.distanceList && inputs.distanceList.length > 0 && inputs.edges.length === inputs.distanceList.length) { const mkChamfer = new this.occ.BRepFilletAPI_MakeChamfer(inputs.shape); inputs.edges.forEach((edge, index) => { const distance = inputs.distanceList[index]; if (distance === undefined) { throw (Error("Distance is not defined")); } mkChamfer.Add_2(distance, edge); }); const curChamfer = mkChamfer.Shape(); mkChamfer.delete(); const result = this.converterService.getActualTypeOfShape(curChamfer); curChamfer.delete(); return result; } return undefined; } chamferEdgeTwoDistances(inputs) { const mkChamfer = new this.occ.BRepFilletAPI_MakeChamfer(inputs.shape); mkChamfer.Add_3(inputs.distance1, inputs.distance2, inputs.edge, inputs.face); const curChamfer = mkChamfer.Shape(); mkChamfer.delete(); const result = this.converterService.getActualTypeOfShape(curChamfer); curChamfer.delete(); return result; } chamferEdgesTwoDistances(inputs) { if (inputs.edges && inputs.edges.length > 0 && inputs.edges.length === inputs.faces.length) { const mkChamfer = new this.occ.BRepFilletAPI_MakeChamfer(inputs.shape); inputs.edges.forEach((edge, index) => { mkChamfer.Add_3(inputs.distance1, inputs.distance2, edge, inputs.faces[index]); }); const curChamfer = mkChamfer.Shape(); mkChamfer.delete(); const result = this.converterService.getActualTypeOfShape(curChamfer); curChamfer.delete(); return result; } else { return undefined; } } chamferEdgesTwoDistancesLists(inputs) { if (inputs.edges && inputs.edges.length > 0 && inputs.faces && inputs.faces.length > 0 && inputs.distances1 && inputs.distances1.length > 0 && inputs.distances2 && inputs.distances2.length > 0 && inputs.edges.length === inputs.faces.length && inputs.edges.length === inputs.distances1.length && inputs.edges.length === inputs.distances2.length) { const mkChamfer = new this.occ.BRepFilletAPI_MakeChamfer(inputs.shape); inputs.edges.forEach((edge, index) => { mkChamfer.Add_3(inputs.distances1[index], inputs.distances2[index], edge, inputs.faces[index]); }); const curChamfer = mkChamfer.Shape(); mkChamfer.delete(); const result = this.converterService.getActualTypeOfShape(curChamfer); curChamfer.delete(); return result; } else { return undefined; } } chamferEdgeDistAngle(inputs) { const mkChamfer = new this.occ.BRepFilletAPI_MakeChamfer(inputs.shape); const radians = this.vecHelper.degToRad(inputs.angle); mkChamfer.AddDA(inputs.distance, radians, inputs.edge, inputs.face); const curChamfer = mkChamfer.Shape(); mkChamfer.delete(); const result = this.converterService.getActualTypeOfShape(curChamfer); curChamfer.delete(); return result; } chamferEdgesDistsAngles(inputs) { if (inputs.edges && inputs.edges.length > 0 && inputs.faces && inputs.faces.length > 0 && inputs.distances && inputs.distances.length > 0 && inputs.angles && inputs.angles.length > 0 && inputs.edges.length === inputs.distances.length && inputs.edges.length === inputs.faces.length && inputs.edges.length === inputs.angles.length) { const mkChamfer = new this.occ.BRepFilletAPI_MakeChamfer(inputs.shape); inputs.edges.forEach((edge, index) => { const radians = this.vecHelper.degToRad(inputs.angles[index]); mkChamfer.AddDA(inputs.distances[index], radians, edge, inputs.faces[index]); }); const curChamfer = mkChamfer.Shape(); mkChamfer.delete(); const result = this.converterService.getActualTypeOfShape(curChamfer); curChamfer.delete(); return result; } else { return undefined; } } chamferEdgesDistAngle(inputs) { if (inputs.edges && inputs.edges.length > 0 && inputs.faces && inputs.faces.length > 0 && inputs.edges.length === inputs.faces.length) { const mkChamfer = new this.occ.BRepFilletAPI_MakeChamfer(inputs.shape); const radians = this.vecHelper.degToRad(inputs.angle); inputs.edges.forEach((edge, index) => { mkChamfer.AddDA(inputs.distance, radians, edge, inputs.faces[index]); }); const curChamfer = mkChamfer.Shape(); mkChamfer.delete(); const result = this.converterService.getActualTypeOfShape(curChamfer); curChamfer.delete(); return result; } else { return undefined; } } fillet2d(inputs) { if (inputs.indexes && inputs.radiusList && inputs.radiusList.length !== inputs.indexes.length) { throw new Error("When using radius list, length of the list must match index list of corners that you want to fillet."); } let face; let isShapeFace = false; if (inputs.shape.ShapeType() === this.occ.TopAbs_ShapeEnum.TopAbs_FACE) { face = this.converterService.getActualTypeOfShape(inputs.shape); isShapeFace = true; } else if (inputs.shape.ShapeType() === this.occ.TopAbs_ShapeEnum.TopAbs_WIRE) { const faceBuilder = new this.occ.BRepBuilderAPI_MakeFace_15(inputs.shape, true); const messageProgress = new this.occ.Message_ProgressRange_1(); faceBuilder.Build(messageProgress); const shape = faceBuilder.Shape(); face = this.converterService.getActualTypeOfShape(shape); shape.delete(); messageProgress.delete(); faceBuilder.delete(); } else { throw new Error("You can only fillet a 2d wire or a 2d face."); } const filletMaker = new this.occ.BRepFilletAPI_MakeFillet2d_2(face); const anVertexExplorer = new this.occ.TopExp_Explorer_2(inputs.shape, this.occ.TopAbs_ShapeEnum.TopAbs_VERTEX, this.occ.TopAbs_ShapeEnum.TopAbs_SHAPE); let i = 1; const cornerVertices = []; for (anVertexExplorer; anVertexExplorer.More(); anVertexExplorer.Next()) { const vertex = this.occ.TopoDS.Vertex_1(anVertexExplorer.Current()); if (i % 2 === 0) { cornerVertices.push(vertex); } i++; } if (!isShapeFace) { const wire = inputs.shape; if (!wire.Closed_1()) { cornerVertices.pop(); } } let radiusAddedCounter = 0; cornerVertices.forEach((cvx, index) => { if (!inputs.indexes) { this.applyRadiusToVertex(inputs, filletMaker, cvx, index); } else if (inputs.indexes.includes(index + 1)) { this.applyRadiusToVertex(inputs, filletMaker, cvx, radiusAddedCounter); radiusAddedCounter++; } }); const messageProgress = new this.occ.Message_ProgressRange_1(); filletMaker.Build(messageProgress); let result; if (isShapeFace) { result = filletMaker.Shape(); } else { const isDone = filletMaker.IsDone(); if (isDone) { const shape = filletMaker.Shape(); const filletedWires = this.shapeGettersService.getWires({ shape }); if (filletedWires.length === 1) { result = filletedWires[0]; } } else { // Previous algorithm fails if the wire is not made up of circular or straight edges. This algorithm is a failover. const normal = this.facesService.faceNormalOnUV({ shape: face, paramU: 0.5, paramV: 0.5 }); result = this.fillet3DWire({ shape: inputs.shape, radius: inputs.radius, radiusList: inputs.radiusList, indexes: inputs.indexes, direction: normal }); } } anVertexExplorer.delete(); filletMaker.delete(); messageProgress.delete(); cornerVertices.forEach(cvx => cvx.delete()); return result; } fillet3DWire(inputs) { let useRadiusList = false; if (inputs.radiusList && inputs.radiusList.length > 0 && inputs.indexes && inputs.indexes.length > 0) { if (inputs.radiusList.length !== inputs.indexes.length) { throw new Error("Radius list and indexes are not the same length"); } else { useRadiusList = true; } } // the goal is to make this fillet the same corner indices as fillet 2d command does with the same radius list. // This makes this algorithm quite complex when counting which actual edge indices need to be rounded as it is based on // extrusion, which creates specific index definitions. // let adjustedRadiusList = [...inputs.radiusList]; // radius list does not need to be adjusted // Closed shapes start corners differently on the connection of the first corner, so we need to readjust the edges let wireTouse = this.edgesService.fixEdgeOrientationsAlongWire({ shape: inputs.shape }); if (useRadiusList && inputs.shape.Closed_1()) { const edgesOfWire = this.edgesService.getEdgesAlongWire({ shape: inputs.shape }); const firstEdge = edgesOfWire.shift(); const adjustEdges = [...edgesOfWire, firstEdge]; wireTouse = this.converterService.combineEdgesAndWiresIntoAWire({ shapes: adjustEdges }); } else { wireTouse = this.converterService.getActualTypeOfShape(inputs.shape.Reversed()); } const extrusion = this.operationsService.extrude({ shape: wireTouse, direction: inputs.direction }); let adjustedIndexes = inputs.indexes; if (useRadiusList) { // So original indexes are based on the number of corners between edges. These corner indexes are used as an input, but extrusion creates 3D edges // with different indexes, so we need to adjust the indexes to match the 3D edges. // the original indexes are [3, 4, 5, 6, 7, 8, 9, 10, 11, ...] // the order is [5, 8, 11, 14, 17, 20, 23, 26, 29, ...] // this is needed because of the way edge indexes are made on such shapes const filteredEnd = inputs.indexes.filter(i => i > 2); const maxNr = Math.max(...filteredEnd); const adjacentList = [5]; let lastNr = 5; for (let i = 0; i < maxNr; i++) { lastNr += 3; adjacentList.push(lastNr); } adjustedIndexes = inputs.indexes.map((index) => { if (inputs.shape.Closed_1()) { if (index <= 2) { return index; } else { return adjacentList[index - 3]; } } else { if (index === 1) { return 2; } else { return adjacentList[index - 2]; } } }); } const filletShape = this.filletEdges({ shape: extrusion, radius: inputs.radius, indexes: adjustedIndexes, radiusList: inputs.radiusList }); const faceEdges = []; const faces = this.shapeGettersService.getFaces({ shape: filletShape }); faces.forEach((f, i) => { // due to reversal of wire in the beginning this is stable index now // also we need to translate these edges back along direction const edgeToAdd = this.shapeGettersService.getEdges({ shape: f })[3]; faceEdges.push(edgeToAdd); }); const res = this.converterService.combineEdgesAndWiresIntoAWire({ shapes: faceEdges }); const result = this.transformsService.translate({ shape: res, translation: inputs.direction.map(s => -s) }); extrusion.delete(); filletShape.delete(); faces.forEach(f => f.delete()); faceEdges.forEach(e => e.delete()); return result; } applyRadiusToVertex(inputs, filletMaker, cvx, index) { if (inputs.radiusList) { const radiusList = inputs.radiusList; filletMaker.AddFillet(cvx, radiusList[index]); } else if (inputs.radius) { filletMaker.AddFillet(cvx, inputs.radius); } } }