diff --git a/packages/dev/occt/bitbybit-dev-occt/bitbybit-dev-occt.d.ts b/packages/dev/occt/bitbybit-dev-occt/bitbybit-dev-occt.d.ts index 4f753abf..f5e89ef0 100644 --- a/packages/dev/occt/bitbybit-dev-occt/bitbybit-dev-occt.d.ts +++ b/packages/dev/occt/bitbybit-dev-occt/bitbybit-dev-occt.d.ts @@ -2071,6 +2071,35 @@ export declare class GeomAPI { delete(): void; } +export declare class GeomAPI_ProjectPointOnCurve { + Init_1(P: gp_Pnt, Curve: Handle_Geom_Curve): void; + Init_2(P: gp_Pnt, Curve: Handle_Geom_Curve, Umin: Standard_Real, Usup: Standard_Real): void; + Init_3(Curve: Handle_Geom_Curve, Umin: Standard_Real, Usup: Standard_Real): void; + Perform(P: gp_Pnt): void; + NbPoints(): Graphic3d_ZLayerId; + Point(Index: Graphic3d_ZLayerId): gp_Pnt; + Parameter_1(Index: Graphic3d_ZLayerId): Standard_Real; + Parameter_2(Index: Graphic3d_ZLayerId, U: Standard_Real): void; + Distance(Index: Graphic3d_ZLayerId): Standard_Real; + NearestPoint(): gp_Pnt; + LowerDistanceParameter(): Standard_Real; + LowerDistance(): Standard_Real; + Extrema(): Extrema_ExtPC; + delete(): void; +} + + export declare class GeomAPI_ProjectPointOnCurve_1 extends GeomAPI_ProjectPointOnCurve { + constructor(); + } + + export declare class GeomAPI_ProjectPointOnCurve_2 extends GeomAPI_ProjectPointOnCurve { + constructor(P: gp_Pnt, Curve: Handle_Geom_Curve); + } + + export declare class GeomAPI_ProjectPointOnCurve_3 extends GeomAPI_ProjectPointOnCurve { + constructor(P: gp_Pnt, Curve: Handle_Geom_Curve, Umin: Standard_Real, Usup: Standard_Real); + } + export declare class GeomAPI_PointsToBSpline { Init_1(Points: TColgp_Array1OfPnt, DegMin: Graphic3d_ZLayerId, DegMax: Graphic3d_ZLayerId, Continuity: GeomAbs_Shape, Tol3D: Standard_Real): void; Init_2(Points: TColgp_Array1OfPnt, ParType: Approx_ParametrizationType, DegMin: Graphic3d_ZLayerId, DegMax: Graphic3d_ZLayerId, Continuity: GeomAbs_Shape, Tol3D: Standard_Real): void; @@ -10595,6 +10624,10 @@ export type OpenCascadeInstance = {FS: typeof FS} & { ShapeUpgrade_UnifySameDomain_1: typeof ShapeUpgrade_UnifySameDomain_1; ShapeUpgrade_UnifySameDomain_2: typeof ShapeUpgrade_UnifySameDomain_2; GeomAPI: typeof GeomAPI; + GeomAPI_ProjectPointOnCurve: typeof GeomAPI_ProjectPointOnCurve; + GeomAPI_ProjectPointOnCurve_1: typeof GeomAPI_ProjectPointOnCurve_1; + GeomAPI_ProjectPointOnCurve_2: typeof GeomAPI_ProjectPointOnCurve_2; + GeomAPI_ProjectPointOnCurve_3: typeof GeomAPI_ProjectPointOnCurve_3; GeomAPI_PointsToBSpline: typeof GeomAPI_PointsToBSpline; GeomAPI_PointsToBSpline_1: typeof GeomAPI_PointsToBSpline_1; GeomAPI_PointsToBSpline_2: typeof GeomAPI_PointsToBSpline_2; diff --git a/packages/dev/occt/bitbybit-dev-occt/bitbybit-dev-occt.wasm b/packages/dev/occt/bitbybit-dev-occt/bitbybit-dev-occt.wasm index 98928a35..725e79e3 100755 Binary files a/packages/dev/occt/bitbybit-dev-occt/bitbybit-dev-occt.wasm and b/packages/dev/occt/bitbybit-dev-occt/bitbybit-dev-occt.wasm differ diff --git a/packages/dev/occt/bitbybit-dev-occt/bitbybit-dev-occt.yml b/packages/dev/occt/bitbybit-dev-occt/bitbybit-dev-occt.yml index 397540f5..6deffc72 100644 --- a/packages/dev/occt/bitbybit-dev-occt/bitbybit-dev-occt.yml +++ b/packages/dev/occt/bitbybit-dev-occt/bitbybit-dev-occt.yml @@ -191,6 +191,7 @@ - symbol: Geom2d_VectorWithMagnitude - symbol: GeomAPI - symbol: GeomAPI_PointsToBSpline + - symbol: GeomAPI_ProjectPointOnCurve - symbol: GeomAbs_JoinType - symbol: GeomAbs_Shape - symbol: GeomLProp_SLProps diff --git a/packages/dev/occt/bitbybit-dev-occt/cdn.js b/packages/dev/occt/bitbybit-dev-occt/cdn.js new file mode 100644 index 00000000..99a180c7 --- /dev/null +++ b/packages/dev/occt/bitbybit-dev-occt/cdn.js @@ -0,0 +1,31 @@ +import ocFullJS from "./bitbybit-dev-occt.js"; + +const initOpenCascade = ({ + mainJS = ocFullJS, + mainWasm = "https://cdn.jsdelivr.net/gh/bitbybit-dev/bitbybit-assets@0.19.6/wasm/bitbybit-dev-occt.a9520351.wasm", + worker = undefined, + libs = [], + module = {}, +} = {}) => { + return new Promise((resolve, reject) => { + new mainJS({ + locateFile(path) { + if (path.endsWith('.wasm')) { + return mainWasm; + } + if (path.endsWith('.worker.js') && !!worker) { + return worker; + } + return path; + }, + ...module + }).then(async oc => { + for (let lib of libs) { + await oc.loadDynamicLibrary(lib, { loadAsync: true, global: true, nodelete: true, allowUndefined: false }); + } + resolve(oc); + }); + }); +}; + +export default initOpenCascade; diff --git a/packages/dev/occt/bitbybit-dev-occt/index.js b/packages/dev/occt/bitbybit-dev-occt/index.js index 99a180c7..776f2719 100644 --- a/packages/dev/occt/bitbybit-dev-occt/index.js +++ b/packages/dev/occt/bitbybit-dev-occt/index.js @@ -1,8 +1,9 @@ import ocFullJS from "./bitbybit-dev-occt.js"; +import ocFullWasm from "./bitbybit-dev-occt.wasm"; const initOpenCascade = ({ mainJS = ocFullJS, - mainWasm = "https://cdn.jsdelivr.net/gh/bitbybit-dev/bitbybit-assets@0.19.6/wasm/bitbybit-dev-occt.a9520351.wasm", + mainWasm = ocFullWasm, worker = undefined, libs = [], module = {}, diff --git a/packages/dev/occt/generate-prod-build-yaml.js b/packages/dev/occt/generate-prod-build-yaml.js index 5cb49ab0..3b716ec1 100644 --- a/packages/dev/occt/generate-prod-build-yaml.js +++ b/packages/dev/occt/generate-prod-build-yaml.js @@ -143,6 +143,7 @@ async function start() { "GeomLib", "GeomAPI", "GeomAPI_PointsToBSpline", + "GeomAPI_ProjectPointOnCurve", "Geom_BezierCurve", "BitByBitDev", "gp_Trsf2d", diff --git a/packages/dev/occt/lib/api/inputs/occ-inputs.ts b/packages/dev/occt/lib/api/inputs/occ-inputs.ts index c59758ef..61450763 100644 --- a/packages/dev/occt/lib/api/inputs/occ-inputs.ts +++ b/packages/dev/occt/lib/api/inputs/occ-inputs.ts @@ -3581,9 +3581,11 @@ export namespace OCCT { face: U; } export class PipeWiresCylindricalDto { - constructor(shapes?: T[], radius?: number) { + constructor(shapes?: T[], radius?: number, withContact?: boolean, withCorrection?: boolean) { if (shapes !== undefined) { this.shapes = shapes; } if (radius !== undefined) { this.radius = radius; } + if (withContact !== undefined) { this.withContact = withContact; } + if (withCorrection !== undefined) { this.withCorrection; } } /** * Wire paths to pipe @@ -3598,11 +3600,23 @@ export namespace OCCT { * @step 1 */ radius = 0.1; + /** + * If withContact is true, the section is translated to be in contact with the spine. + * @default false + */ + withContact = false; + /** + * If withCorrection is true, the section is rotated to be orthogonal to the spine's tangent in the correspondent point. + * @default false + */ + withCorrection = false; } export class PipeWireCylindricalDto { - constructor(shape?: T, radius?: number) { + constructor(shape?: T, radius?: number, withContact?: boolean, withCorrection?: boolean) { if (shape !== undefined) { this.shape = shape; } if (radius !== undefined) { this.radius = radius; } + if (withContact !== undefined) { this.withContact = withContact; } + if (withCorrection !== undefined) { this.withCorrection; } } /** * Wire path to pipe @@ -3617,12 +3631,24 @@ export namespace OCCT { * @step 1 */ radius = 0.1; + /** + * If withContact is true, the section is translated to be in contact with the spine. + * @default false + */ + withContact = false; + /** + * If withCorrection is true, the section is rotated to be orthogonal to the spine's tangent in the correspondent point. + * @default false + */ + withCorrection = false; } export class PipePolygonWireNGonDto { - constructor(shapes?: T, radius?: number, nrCorners?: number) { + constructor(shapes?: T, radius?: number, nrCorners?: number, withContact?: boolean, withCorrection?: boolean) { if (shapes !== undefined) { this.shape = shapes; } if (radius !== undefined) { this.radius = radius; } if (nrCorners !== undefined) { this.nrCorners = nrCorners; } + if (withContact !== undefined) { this.withContact = withContact; } + if (withCorrection !== undefined) { this.withCorrection; } } /** * Wire path to pipe @@ -3645,6 +3671,16 @@ export namespace OCCT { * @step 1 */ nrCorners = 6; + /** + * If withContact is true, the section is translated to be in contact with the spine. + * @default false + */ + withContact = false; + /** + * If withCorrection is true, the section is rotated to be orthogonal to the spine's tangent in the correspondent point. + * @default false + */ + withCorrection = false; } export class ExtrudeDto { constructor(shape?: T, direction?: Base.Vector3) { @@ -3954,10 +3990,11 @@ export namespace OCCT { index = 1; } export class RotationExtrudeDto { - constructor(shape?: T, height?: number, angle?: number) { + constructor(shape?: T, height?: number, angle?: number, makeSolid?: boolean) { if (shape !== undefined) { this.shape = shape; } if (height !== undefined) { this.height = height; } if (angle !== undefined) { this.angle = angle; } + if (makeSolid !== undefined) { this.makeSolid = makeSolid; } } /** * Wire to extrude by rotating @@ -3980,6 +4017,11 @@ export namespace OCCT { * @step 1 */ angle = 360; + /** + * Make solid of the result + * @default true + */ + makeSolid = true; } // Threading : Create Surfaces diff --git a/packages/dev/occt/lib/services/base/booleans.service.ts b/packages/dev/occt/lib/services/base/booleans.service.ts index e61b51ff..e6582363 100644 --- a/packages/dev/occt/lib/services/base/booleans.service.ts +++ b/packages/dev/occt/lib/services/base/booleans.service.ts @@ -75,7 +75,6 @@ export class BooleansService { if (this.shapeGettersService.getNumSolidsInCompound(difference) === 1) { const solid = this.shapeGettersService.getSolidFromCompound(difference, 0); - difference.delete(); difference = solid; } diff --git a/packages/dev/occt/lib/services/base/operations.service.ts b/packages/dev/occt/lib/services/base/operations.service.ts index cc19ffc5..c5d9fecc 100644 --- a/packages/dev/occt/lib/services/base/operations.service.ts +++ b/packages/dev/occt/lib/services/base/operations.service.ts @@ -312,13 +312,13 @@ export class OperationsService { }); bopalgoBuilder.Perform(new this.occ.Message_ProgressRange_1()); let shapes; - if(!inputs.nonDestructive){ + if (!inputs.nonDestructive) { const res = bopalgoBuilder.Modified(inputs.shape); const shapeCompound = this.occ.BitByBitDev.BitListOfShapesToCompound(res); - shapes = this.shapeGettersService.getShapesOfCompound({shape: shapeCompound}); + shapes = this.shapeGettersService.getShapesOfCompound({ shape: shapeCompound }); } else { const res = bopalgoBuilder.Shape(); - shapes = this.shapeGettersService.getShapesOfCompound({shape: res}); + shapes = this.shapeGettersService.getShapesOfCompound({ shape: res }); } return shapes; @@ -399,7 +399,11 @@ export class OperationsService { pipe.Add_1(inputs.shape, false, false); pipe.Add_1(upperPolygon, false, false); pipe.Build(new this.occ.Message_ProgressRange_1()); - pipe.MakeSolid(); + + // default should be to make the solid for backwards compatibility + if (inputs.makeSolid || inputs.makeSolid === undefined) { + pipe.MakeSolid(); + } const pipeShape = pipe.Shape(); const result = this.converterService.getActualTypeOfShape(pipeShape); @@ -430,30 +434,52 @@ export class OperationsService { const wire = inputs.shape; const shapesToPassThrough: TopoDS_Shape[] = []; const edges = this.shapeGettersService.getEdges({ shape: wire }); + + // Check if the wire is closed + const isClosed = this.wiresService.isWireClosed({ shape: wire }); // Assuming such a method exists + edges.forEach((e, index) => { const edgeStartPt = this.edgesService.startPointOnEdge({ shape: e }); const tangent = this.edgesService.tangentOnEdgeAtParam({ shape: e, param: 0 }); - let tangentPreviousEdgeEnd: Inputs.Base.Vector3; let averageTangentVec = tangent; - if (index > 0 && index < edges.length - 1) { - const previousEdge = edges[index - 1]; - tangentPreviousEdgeEnd = this.edgesService.tangentOnEdgeAtParam({ shape: previousEdge, param: 1 }); - averageTangentVec = [tangent[0] + tangentPreviousEdgeEnd[0] / 2, tangent[1] + tangentPreviousEdgeEnd[1] / 2, tangent[2] + tangentPreviousEdgeEnd[2] / 2]; + if (edges.length > 1) { // Only average tangents if there’s more than one edge + if (index > 0 || (isClosed && index === 0)) { + const previousEdge = edges[(index - 1 + edges.length) % edges.length]; // Wrap around for closed wires + const tangentPreviousEdgeEnd = this.edgesService.tangentOnEdgeAtParam({ shape: previousEdge, param: 1 }); + averageTangentVec = [ + (tangent[0] + tangentPreviousEdgeEnd[0]) / 2, + (tangent[1] + tangentPreviousEdgeEnd[1]) / 2, + (tangent[2] + tangentPreviousEdgeEnd[2]) / 2 + ]; + } } - const ngon = this.wiresService.createNGonWire({ radius: inputs.radius, center: edgeStartPt, direction: averageTangentVec, nrCorners: inputs.nrCorners }) as TopoDS_Wire; + + const ngon = this.wiresService.createNGonWire({ + radius: inputs.radius, + center: edgeStartPt, + direction: averageTangentVec, + nrCorners: inputs.nrCorners + }) as TopoDS_Wire; shapesToPassThrough.push(ngon); - if (index === edges.length - 1) { + + // For open wires, add a final n-gon at the end of the last edge + if (!isClosed && index === edges.length - 1) { const edgeEndPt = this.edgesService.endPointOnEdge({ shape: e }); const tangentEndPt = this.edgesService.tangentOnEdgeAtParam({ shape: e, param: 1 }); - const ngon = this.wiresService.createNGonWire({ radius: inputs.radius, center: edgeEndPt, direction: tangentEndPt, nrCorners: inputs.nrCorners }) as TopoDS_Wire; + const ngon = this.wiresService.createNGonWire({ + radius: inputs.radius, + center: edgeEndPt, + direction: tangentEndPt, + nrCorners: inputs.nrCorners + }) as TopoDS_Wire; shapesToPassThrough.push(ngon); } }); const pipe = new this.occ.BRepOffsetAPI_MakePipeShell(wire); shapesToPassThrough.forEach(s => { - pipe.Add_1(s, false, false); + pipe.Add_1(s, inputs.withContact === true ? true : false, inputs.withCorrection === true ? true : false); }); pipe.Build(new this.occ.Message_ProgressRange_1()); @@ -469,30 +495,52 @@ export class OperationsService { const wire = inputs.shape; const shapesToPassThrough: TopoDS_Shape[] = []; const edges = this.shapeGettersService.getEdges({ shape: wire }); + + // Check if the wire is closed + const isClosed = this.wiresService.isWireClosed({ shape: wire }); // Assuming such a method exists + edges.forEach((e, index) => { const edgeStartPt = this.edgesService.startPointOnEdge({ shape: e }); const tangent = this.edgesService.tangentOnEdgeAtParam({ shape: e, param: 0 }); - let tangentPreviousEdgeEnd: Inputs.Base.Vector3; let averageTangentVec = tangent; - if (index > 0 && index < edges.length - 1) { - const previousEdge = edges[index - 1]; - tangentPreviousEdgeEnd = this.edgesService.tangentOnEdgeAtParam({ shape: previousEdge, param: 1 }); - averageTangentVec = [tangent[0] + tangentPreviousEdgeEnd[0] / 2, tangent[1] + tangentPreviousEdgeEnd[1] / 2, tangent[2] + tangentPreviousEdgeEnd[2] / 2]; + if (edges.length > 1) { // Only average tangents if there’s more than one edge + if (index > 0 || (isClosed && index === 0)) { + const previousEdge = edges[(index - 1 + edges.length) % edges.length]; // Wrap around for closed wires + const tangentPreviousEdgeEnd = this.edgesService.tangentOnEdgeAtParam({ shape: previousEdge, param: 1 }); + averageTangentVec = [ + (tangent[0] + tangentPreviousEdgeEnd[0]) / 2, + (tangent[1] + tangentPreviousEdgeEnd[1]) / 2, + (tangent[2] + tangentPreviousEdgeEnd[2]) / 2 + ]; + } } - const circle = this.entitiesService.createCircle(inputs.radius, edgeStartPt, averageTangentVec, Inputs.OCCT.typeSpecificityEnum.wire) as TopoDS_Wire; + + const circle = this.entitiesService.createCircle( + inputs.radius, + edgeStartPt, + averageTangentVec, + Inputs.OCCT.typeSpecificityEnum.wire + ) as TopoDS_Wire; shapesToPassThrough.push(circle); - if (index === edges.length - 1) { + + // For open wires, add a final circle at the end of the last edge + if (!isClosed && index === edges.length - 1) { const edgeEndPt = this.edgesService.endPointOnEdge({ shape: e }); const tangentEndPt = this.edgesService.tangentOnEdgeAtParam({ shape: e, param: 1 }); - const line = this.entitiesService.createCircle(inputs.radius, edgeEndPt, tangentEndPt, Inputs.OCCT.typeSpecificityEnum.wire) as TopoDS_Wire; - shapesToPassThrough.push(line); + const circle = this.entitiesService.createCircle( + inputs.radius, + edgeEndPt, + tangentEndPt, + Inputs.OCCT.typeSpecificityEnum.wire + ) as TopoDS_Wire; + shapesToPassThrough.push(circle); } }); const pipe = new this.occ.BRepOffsetAPI_MakePipeShell(wire); shapesToPassThrough.forEach(s => { - pipe.Add_1(s, false, false); + pipe.Add_1(s, inputs.withContact === true ? true : false, inputs.withCorrection === true ? true : false); }); pipe.Build(new this.occ.Message_ProgressRange_1()); @@ -506,7 +554,7 @@ export class OperationsService { pipeWiresCylindrical(inputs: Inputs.OCCT.PipeWiresCylindricalDto) { return inputs.shapes.map(wire => { - return this.pipeWireCylindrical({ shape: wire, radius: inputs.radius }); + return this.pipeWireCylindrical({ shape: wire, radius: inputs.radius, withContact: inputs.withContact, withCorrection: inputs.withCorrection }); }); } diff --git a/packages/dev/occt/lib/services/base/wires.service.ts b/packages/dev/occt/lib/services/base/wires.service.ts index d8335b5b..efd42727 100644 --- a/packages/dev/occt/lib/services/base/wires.service.ts +++ b/packages/dev/occt/lib/services/base/wires.service.ts @@ -91,7 +91,7 @@ export class WiresService { reversedEdges.forEach(e => e.delete()); return result; } - + createChristmasTreeWire(inputs: Inputs.OCCT.ChristmasTreeDto) { const frameInner = this.createLineWire({ start: [inputs.innerDist, 0, 0], @@ -492,84 +492,303 @@ export class WiresService { } - splitOnPoints(inputs: Inputs.OCCT.SplitWireOnPointsDto): TopoDS_Wire[] { - + isWireClosed(inputs: Inputs.OCCT.ShapeDto): boolean { const tolerance = 1.0e-7; const startPointOnWire = this.startPointOnWire({ shape: inputs.shape }); const endPointOnWire = this.endPointOnWire({ shape: inputs.shape }); // This is needed to make follow up algorithm to work properly on open wires const wireIsClosed = this.vecHelper.vectorsTheSame(endPointOnWire, startPointOnWire, tolerance); - if (!wireIsClosed) { - if (!inputs.points.some(p => this.vecHelper.vectorsTheSame(p, startPointOnWire, tolerance))) { - inputs.points.push(startPointOnWire); - } - - if (!inputs.points.some(p => this.vecHelper.vectorsTheSame(p, endPointOnWire, tolerance))) { - inputs.points.push(endPointOnWire); - } - } + return wireIsClosed; + } - const shortLines = this.createLines({ - lines: inputs.points.map(p => ({ start: p, end: [p[0], p[1] + tolerance, p[2]] as Inputs.Base.Point3 })), - returnCompound: false - }) as TopoDS_Wire[]; - - const diff = this.booleansService.difference({ shape: inputs.shape, shapes: shortLines, keepEdges: true }); - const edges = this.shapeGettersService.getEdges({ shape: diff }); - - const groupedEdges: TopoDS_Edge[][] = []; - edges.forEach((e) => { - const latestArray = groupedEdges[groupedEdges.length - 1]; - - // direction of edges seem to be reversed, but it does not matter for this algorithm - // better not to start reversing things and just deal with it - const endPointOnEdge = this.edgesService.endPointOnEdge({ shape: e }); - const startPointOnEdge = this.edgesService.startPointOnEdge({ shape: e }); - const pointExistsOnEdgeEnd = inputs.points.some(p => this.vecHelper.vectorsTheSame(endPointOnEdge, p, tolerance)); - const pointExistsOnEdgeStart = inputs.points.some(p => this.vecHelper.vectorsTheSame(startPointOnEdge, p, tolerance)); - - if (pointExistsOnEdgeEnd && !pointExistsOnEdgeStart) { - groupedEdges.push([e]); - } else if (!pointExistsOnEdgeEnd && pointExistsOnEdgeStart) { - if (latestArray) { - latestArray.push(e); - } else { - groupedEdges.push([e]); - } - } else if (pointExistsOnEdgeEnd && pointExistsOnEdgeStart) { - groupedEdges.push([e]); - } else if (!pointExistsOnEdgeEnd && !pointExistsOnEdgeStart) { - if (latestArray) { - latestArray.push(e); - } else { - groupedEdges.push([e]); + splitOnPoints(inputs: Inputs.OCCT.SplitWireOnPointsDto): TopoDS_Wire[] { + const wire = inputs.shape; + const splitPoints = inputs.points; + + // 1. Get the list of edges from the wire + const edges = this.shapeGettersService.getEdges({ shape: wire }); + if (edges.length === 0) return []; + + // 2. Collect split locations as {edgeIndex, parameter} + const splitLocations: { edgeIndex: number; parameter: number }[] = []; + + // Add the wire's start point + const firstEdge = edges[0]; + let first = { current: 0 }; + let last = { current: 0 }; + this.occRefReturns.BRep_Tool_Curve_2(firstEdge, first, last); + splitLocations.push({ edgeIndex: 0, parameter: first.current }); + + // Project each split point onto the wire + splitPoints.forEach((pt) => { + let minDist = Infinity; + let bestEdgeIndex = -1; + let bestParam = 0; + + edges.forEach((edge, index) => { + const first = { current: 0 }; + const last = { current: 0 }; + const curve = this.occRefReturns.BRep_Tool_Curve_2(edge, first, last); + const firstVal = first.current; + const lastVal = last.current; + + const gpPnt = this.entitiesService.gpPnt(pt); + const projector = new this.occ.GeomAPI_ProjectPointOnCurve_2(gpPnt, curve); + if (projector.NbPoints() > 0) { + const param = projector.LowerDistanceParameter(); + // Clamp the parameter to the edge's range + const clampedParam = Math.max(firstVal, Math.min(lastVal, param)); + const dist = projector.Distance(1); // Use the first projection point + if (dist < minDist) { + minDist = dist; + bestEdgeIndex = index; + bestParam = clampedParam; + } } + }); + + if (bestEdgeIndex >= 0) { + splitLocations.push({ edgeIndex: bestEdgeIndex, parameter: bestParam }); } }); - - const wires = []; - - groupedEdges.forEach(g => { - const wire = this.converterService.combineEdgesAndWiresIntoAWire({ - shapes: g - }); - wires.push(wire); + + // Add the wire's end point + const lastEdge = edges[edges.length - 1]; + first = { current: 0 }; + last = { current: 0 }; + this.occRefReturns.BRep_Tool_Curve_2(lastEdge, first, last); + splitLocations.push({ edgeIndex: edges.length - 1, parameter: last.current }); + + // 3. Remove duplicates and sort split locations by edgeIndex, then parameter + const uniqueLocations = splitLocations.filter((loc, index, self) => + index === self.findIndex((t) => t.edgeIndex === loc.edgeIndex && t.parameter === loc.parameter) + ); + uniqueLocations.sort((a, b) => { + if (a.edgeIndex !== b.edgeIndex) return a.edgeIndex - b.edgeIndex; + return a.parameter - b.parameter; }); - // when wire is closed and first wire and last wire share the corner, they need to be combined if there's no split point that matches that corner - if (wireIsClosed && wires.length > 1) { - const endPointOnFirstWire = this.endPointOnWire({ shape: wires[0] }); - const startPointOnLastWire = this.startPointOnWire({ shape: wires[wires.length - 1] }); - if (this.vecHelper.vectorsTheSame(endPointOnFirstWire, startPointOnLastWire, tolerance)) { - const pt = inputs.points.find(p => this.vecHelper.vectorsTheSame(p, endPointOnFirstWire, tolerance)); - if (!pt) { - const combined = this.addEdgesAndWiresToWire({ shape: wires[wires.length - 1], shapes: [wires[0]] }); - wires[0] = combined; - wires.pop(); + + // 4. Create new wires between consecutive split locations + const newWires: TopoDS_Wire[] = []; + for (let i = 0; i < uniqueLocations.length - 1; i++) { + const startLoc = uniqueLocations[i]; + const endLoc = uniqueLocations[i + 1]; + const wireBuilder = new this.occ.BRepBuilderAPI_MakeWire_1(); + + if (startLoc.edgeIndex === endLoc.edgeIndex) { + // Same edge: create a single trimmed edge + const edge = edges[startLoc.edgeIndex]; + const first = { current: 0 }; + const last = { current: 0 }; + const curve = this.occRefReturns.BRep_Tool_Curve_2(edge, first, last); + + // Avoid zero-length segments + if (startLoc.parameter === endLoc.parameter) continue; + + const trimmedCurve = new this.occ.Geom_TrimmedCurve( + curve, + startLoc.parameter, + endLoc.parameter, + true, + true + ); + const handleTrimmedCurve = new this.occ.Handle_Geom_Curve_2(trimmedCurve); + const newEdge = new this.occ.BRepBuilderAPI_MakeEdge_24(handleTrimmedCurve).Edge(); + wireBuilder.Add_1(newEdge); + } else { + // Spans multiple edges + // Trim the start edge + const startEdge = edges[startLoc.edgeIndex]; + const startFirst = { current: 0 }; + const startLast = { current: 0 }; + const startCurve = this.occRefReturns.BRep_Tool_Curve_2(startEdge, startFirst, startLast); + const startLastVal = startLast.current; + + if (startLoc.parameter < startLastVal) { + const trimmedStartCurve = new this.occ.Geom_TrimmedCurve( + startCurve, + startLoc.parameter, + startLastVal, + true, + true + ); + const handleTrimmedStartCurve = new this.occ.Handle_Geom_Curve_2(trimmedStartCurve); + const newStartEdge = new this.occ.BRepBuilderAPI_MakeEdge_24(handleTrimmedStartCurve).Edge(); + wireBuilder.Add_1(newStartEdge); } + + // Add full edges in between + for (let j = startLoc.edgeIndex + 1; j < endLoc.edgeIndex; j++) { + wireBuilder.Add_1(edges[j]); + } + + // Trim the end edge + const endEdge = edges[endLoc.edgeIndex]; + const endFirst = { current: 0 }; + const endLast = { current: 0 }; + const endCurve = this.occRefReturns.BRep_Tool_Curve_2(endEdge, endFirst, endLast); + const endFirstVal = endFirst.current; + + if (endLoc.parameter > endFirstVal) { + const trimmedEndCurve = new this.occ.Geom_TrimmedCurve( + endCurve, + endFirstVal, + endLoc.parameter, + true, + true + ); + const handleTrimmedEndCurve = new this.occ.Handle_Geom_Curve_2(trimmedEndCurve); + const newEndEdge = new this.occ.BRepBuilderAPI_MakeEdge_24(handleTrimmedEndCurve).Edge(); + wireBuilder.Add_1(newEndEdge); + } + } + + if (wireBuilder.IsDone()) { + const newWire = wireBuilder.Wire(); + newWires.push(newWire); } } - return wires; + + return newWires; + + //////// FIRST VAR + // const wire = inputs.shape; + // const splitPoints = inputs.points; + + // // 1. Get the list of edges from the wire + // const edges = this.shapeGettersService.getEdges({ shape: wire }); + // if (edges.length === 0) return []; + + // // 2. Collect split locations as {edgeIndex, parameter} + // const splitLocations: { edgeIndex: number; parameter: number }[] = []; + + // // Add the wire's start point + // const firstEdge = edges[0]; + // let first = { current: 0 }; + // let last = { current: 0 }; + // this.occRefReturns.BRep_Tool_Curve_2(firstEdge, first, last); + // splitLocations.push({ edgeIndex: 0, parameter: first.current }); + + // // Project each split point onto the wire + // splitPoints.forEach((pt) => { + // let minDist = Infinity; + // let bestEdgeIndex = -1; + // let bestParam = 0; + + // edges.forEach((edge, index) => { + // const first = { current: 0 }; + // const last = { current: 0 }; + // const curve = this.occRefReturns.BRep_Tool_Curve_2(edge, first, last); + // const firstVal = first.current; + // const lastVal = last.current; + + // const gpPnt = this.entitiesService.gpPnt(pt); + // const projector = new this.occ.GeomAPI_ProjectPointOnCurve_2(gpPnt, curve); + // if (projector.NbPoints() > 0) { + // const param = projector.LowerDistanceParameter(); + // // Ensure the parameter is within the edge's range + // if (param >= firstVal && param <= lastVal) { + // const dist = projector.LowerDistance(); + // if (dist < minDist) { + // minDist = dist; + // bestEdgeIndex = index; + // bestParam = param; + // } + // } + // } + // }); + + // if (bestEdgeIndex >= 0) { + // splitLocations.push({ edgeIndex: bestEdgeIndex, parameter: bestParam }); + // } + // }); + + // // Add the wire's end point + // const lastEdge = edges[edges.length - 1]; + // first = { current: 0 }; + // last = { current: 0 }; + // const curveLast = this.occRefReturns.BRep_Tool_Curve_2(lastEdge, first, last); + // const lastLastVal = last.current; + // splitLocations.push({ edgeIndex: edges.length - 1, parameter: lastLastVal }); + + // // 3. Sort split locations by edgeIndex, then parameter + // splitLocations.sort((a, b) => { + // if (a.edgeIndex !== b.edgeIndex) return a.edgeIndex - b.edgeIndex; + // return a.parameter - b.parameter; + // }); + + // // 4. Create new wires between consecutive split locations + // const newWires: TopoDS_Wire[] = []; + // for (let i = 0; i < splitLocations.length - 1; i++) { + // const startLoc = splitLocations[i]; + // const endLoc = splitLocations[i + 1]; + // const wireBuilder = new this.occ.BRepBuilderAPI_MakeWire_1(); + + // if (startLoc.edgeIndex === endLoc.edgeIndex) { + // // Same edge: create a single trimmed edge + // const edge = edges[startLoc.edgeIndex]; + // const first = { current: 0 }; + // const last = { current: 0 }; + // const curve = this.occRefReturns.BRep_Tool_Curve_2(edge, first, last); + // const trimmedCurve = new this.occ.Geom_TrimmedCurve( + // curve, + // startLoc.parameter, + // endLoc.parameter, + // true, + // true + // ); + // const handleTrimmedCurve = new this.occ.Handle_Geom_Curve_2(trimmedCurve); + // const newEdge = new this.occ.BRepBuilderAPI_MakeEdge_24(handleTrimmedCurve).Edge(); + // wireBuilder.Add_1(newEdge); + // } else { + // // Spans multiple edges + // // Trim the start edge + // const startEdge = edges[startLoc.edgeIndex]; + // const startFirst = { current: 0 }; + // const startLast = { current: 0 }; + // const startCurve = this.occRefReturns.BRep_Tool_Curve_2(startEdge, startFirst, startLast); + // const startLastVal = startLast.current; + // const trimmedStartCurve = new this.occ.Geom_TrimmedCurve( + // startCurve, + // startLoc.parameter, + // startLastVal, + // true, + // true + // ); + // const handleTrimmedStartCurve = new this.occ.Handle_Geom_Curve_2(trimmedStartCurve); + + // const newStartEdge = new this.occ.BRepBuilderAPI_MakeEdge_24(handleTrimmedStartCurve).Edge(); + // wireBuilder.Add_1(newStartEdge); + + // // Add full edges in between + // for (let j = startLoc.edgeIndex + 1; j < endLoc.edgeIndex; j++) { + // wireBuilder.Add_1(edges[j]); + // } + + // // Trim the end edge + // const endEdge = edges[endLoc.edgeIndex]; + // const endFirst = { current: 0 }; + // const endLast = { current: 0 }; + // const endCurve = this.occRefReturns.BRep_Tool_Curve_2(endEdge, endFirst, endLast); + // const endFirstVal = endFirst.current; + // const trimmedEndCurve = new this.occ.Geom_TrimmedCurve( + // endCurve, + // endFirstVal, + // endLoc.parameter, + // true, + // true, + // ); + // const handleTrimmedEndCurve = new this.occ.Handle_Geom_Curve_2(trimmedEndCurve); + // const newEndEdge = new this.occ.BRepBuilderAPI_MakeEdge_24(handleTrimmedEndCurve).Edge(); + // wireBuilder.Add_1(newEndEdge); + // } + + // const newWire = wireBuilder.Wire(); + // newWires.push(newWire); + // } + + // return newWires; } createLines(inputs: Inputs.OCCT.LinesDto): TopoDS_Wire[] | TopoDS_Compound { diff --git a/packages/dev/occt/lib/services/operations.test.ts b/packages/dev/occt/lib/services/operations.test.ts index d8744047..bbf48d1d 100644 --- a/packages/dev/occt/lib/services/operations.test.ts +++ b/packages/dev/occt/lib/services/operations.test.ts @@ -752,7 +752,7 @@ describe("OCCT operations unit tests", () => { tolerance: 1e-7, periodic: false }); - const res = operations.pipePolylineWireNGon({ shape: interpolatedWire, nrCorners: 6, radius: 0.2 }); + const res = operations.pipePolylineWireNGon({ shape: interpolatedWire, nrCorners: 6, radius: 0.2, withContact: false, withCorrection: false }); const vol = solid.getSolidVolume({ shape: res }); expect(vol).toEqual(0.518460713602639); interpolatedWire.delete(); @@ -769,9 +769,9 @@ describe("OCCT operations unit tests", () => { [0, 4, 0], ] }); - const res = operations.pipePolylineWireNGon({ shape: polyline, nrCorners: 6, radius: 0.2 }); + const res = operations.pipePolylineWireNGon({ shape: polyline, nrCorners: 6, radius: 0.2, withContact: false, withCorrection: false }); const vol = solid.getSolidVolume({ shape: res }); - expect(vol).toEqual(0.49841360246611033); + expect(vol).toEqual(0.5099367481546556); polyline.delete(); res.delete(); }); @@ -788,7 +788,7 @@ describe("OCCT operations unit tests", () => { tolerance: 1e-7, periodic: false }); - const res = operations.pipeWireCylindrical({ shape: interpolatedWire, radius: 0.2 }); + const res = operations.pipeWireCylindrical({ shape: interpolatedWire, radius: 0.2, withContact: false, withCorrection: false }); const vol = solid.getSolidVolume({ shape: res }); expect(vol).toEqual(0.6269224771598202); interpolatedWire.delete(); @@ -805,9 +805,9 @@ describe("OCCT operations unit tests", () => { [0, 4, 0], ] }); - const res = operations.pipeWireCylindrical({ shape: polyline, radius: 0.2 }); + const res = operations.pipeWireCylindrical({ shape: polyline, radius: 0.2, withContact: false, withCorrection: false }); const vol = solid.getSolidVolume({ shape: res }); - expect(vol).toEqual(0.6034549016876298); + expect(vol).toEqual(0.6178829238950824); polyline.delete(); res.delete(); }); @@ -834,9 +834,9 @@ describe("OCCT operations unit tests", () => { tolerance: 1e-7, periodic: false }); - const res = operations.pipeWiresCylindrical({ shapes: [polyline, interpolatedWire], radius: 0.2 }); + const res = operations.pipeWiresCylindrical({ shapes: [polyline, interpolatedWire], radius: 0.2, withContact: false, withCorrection: false }); const vols = res.map(s => solid.getSolidVolume({ shape: s })); - expect(vols).toEqual([0.6034549016876298, 0.62692247715982,]); + expect(vols).toEqual([0.6178829238950824, 0.62692247715982,]); polyline.delete(); res.forEach(s => s.delete()); }); diff --git a/packages/dev/occt/lib/services/shapes/wire.test.ts b/packages/dev/occt/lib/services/shapes/wire.test.ts index 4848abe2..ea61ffeb 100644 --- a/packages/dev/occt/lib/services/shapes/wire.test.ts +++ b/packages/dev/occt/lib/services/shapes/wire.test.ts @@ -1062,8 +1062,8 @@ describe("OCCT wire unit tests", () => { }); it("should split circle wire by points", () => { - const circle = wire.createSquareWire({ - size: 1, + const circle = wire.createCircleWire({ + radius: 1, center: [0, 0, 0], direction: [0, 1, 0] }); @@ -1077,14 +1077,13 @@ describe("OCCT wire unit tests", () => { expect(split.length).toBe(10); const segmentLengths = split.map((s) => wire.getWireLength({ shape: s })); segmentLengths.forEach(l => { - expect(l).toBeCloseTo(0.4, 10); + expect(l).toBeCloseTo(0.6283185307179586, 10); }); }); - it("should split circle wire by points", () => { - const circle = wire.createSquareWire({ - size: 1, + const circle = wire.createCircleWire({ + radius: 1, center: [0, 0, 0], direction: [0, 1, 0] }); @@ -1095,18 +1094,48 @@ describe("OCCT wire unit tests", () => { nrOfDivisions: 10 }); const split = wire.splitOnPoints({ shape: circle, points: pts }); - expect(split.length).toBe(9); + expect(split.length).toBe(10); const segmentLengths = split.map((s) => wire.getWireLength({ shape: s })); - expect(segmentLengths).toEqual([ - 0.8000000000000003, - 0.3999999999999999, - 0.39999999999999986, - 0.3999999999999999, - 0.3999999999999999, - 0.3999999999999999, - 0.3999999999999999, - 0.40000000000000013, - 0.4]); + expect(segmentLengths).toEqual([0.6283185307179586, 0.6283185307179586, 0.6283185307179588, 0.6283185307179584, 0.6283185307179586, 0.6283185307179586, 0.6283185307179595, 0.6283185307179577, 0.6283185307179586, 0.6283185307179586]); + }); + + it("should split square wire by points", () => { + const square = wire.createSquareWire({ + size: 1, + center: [0, 0, 0], + direction: [0, 1, 0] + }); + const pts = wire.divideWireByEqualDistanceToPoints({ + shape: square, + removeEndPoint: false, + removeStartPoint: false, + nrOfDivisions: 10 + }); + const split = wire.splitOnPoints({ shape: square, points: pts }); + expect(split.length).toBe(10); + const segmentLengths = split.map((s) => wire.getWireLength({ shape: s })); + segmentLengths.forEach(l => { + expect(l).toBeCloseTo(0.4, 10); + }); + }); + + + it("should split square wire by points", () => { + const square = wire.createSquareWire({ + size: 1, + center: [0, 0, 0], + direction: [0, 1, 0] + }); + const pts = wire.divideWireByEqualDistanceToPoints({ + shape: square, + removeEndPoint: true, + removeStartPoint: true, + nrOfDivisions: 10 + }); + const split = wire.splitOnPoints({ shape: square, points: pts }); + expect(split.length).toBe(10); + const segmentLengths = split.map((s) => wire.getWireLength({ shape: s })); + expect(segmentLengths).toEqual([0.4, 0.4, 0.40000000000000013, 0.3999999999999999, 0.3999999999999999, 0.3999999999999999, 0.3999999999999999, 0.39999999999999986, 0.3999999999999999, 0.4000000000000003]); }); it("should split heart wire by points", () => { @@ -1236,10 +1265,10 @@ describe("OCCT wire unit tests", () => { }); expect(edges.length).toBe(10); const lengths = edges.map(e => e.length); - expect(lengths).toEqual([1, 1, 1, 2, 1, 1, 1, 1, 2, 1]); + expect(lengths).toEqual([1, 1, 2, 1, 1, 1, 1, 2, 1, 1]); }); - it("should create less wires than there are edges on the wire and group edges correctly even if end point is not added to the wire", () => { + it("should create wires that contain few edges on the wire and group edges correctly even if end point is not added to the wire", () => { const rectangle = wire.createRectangleWire({ width: 2, length: 2, @@ -1260,10 +1289,10 @@ describe("OCCT wire unit tests", () => { }); expect(edges.length).toBe(10); const lengths = edges.map(e => e.length); - expect(lengths).toEqual([1, 1, 1, 2, 1, 1, 1, 1, 2, 1]); + expect(lengths).toEqual([1, 1, 2, 1, 1, 1, 1, 2, 1, 1]); }); - it("should create less wires than there are edges on the wire and group edges correctly even if start and end point is not added to the wire", () => { + it("should create wires that contain few edges on the wire and group edges correctly even if start and end point is not added to the wire", () => { const rectangle = wire.createRectangleWire({ width: 2, length: 2, @@ -1277,14 +1306,15 @@ describe("OCCT wire unit tests", () => { nrOfDivisions: 10 }); const split = wire.splitOnPoints({ shape: rectangle, points: pts }); - expect(split.length).toBe(9); + expect(split.length).toBe(10); const edges = []; split.forEach(s => { edges.push(edge.getEdges({ shape: s })); }); - expect(edges.length).toBe(9); + expect(edges.length).toBe(10); const lengths = edges.map(e => e.length); - expect(lengths).toEqual([2, 1, 2, 1, 1, 1, 1, 2, 1]); + expect(lengths).toEqual([1, 1, 2, 1, 1, 1, 1, 2, 1, 1]); + }); it("should split star wire by points", () => { @@ -1303,18 +1333,9 @@ describe("OCCT wire unit tests", () => { nrOfDivisions: 10 }); const split = wire.splitOnPoints({ shape: star, points: pts }); - expect(split.length).toBe(9); + expect(split.length).toBe(10); const segmentLengths = split.map((s) => wire.getWireLength({ shape: s })); - expect(segmentLengths).toEqual([ - 9.369811423392672, - 4.684905711696324, - 4.6849057116963175, - 4.684905711696346, - 4.684905711696367, - 4.68490571169632, - 4.684905711696342, - 4.684905711696341, - 4.684905711696342]); + expect(segmentLengths).toEqual([4.684905711696341, 4.684905711696342, 4.684905711696341, 4.684905711696342, 4.68490571169632, 4.684905711696367, 4.684905711696346, 4.6849057116963175, 4.684905711696324, 4.684905711696332]); }); it("should close open wire", () => {