diff --git a/CLAUDE.md b/CLAUDE.md index d7adc9659..213841fb8 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -220,7 +220,6 @@ the reproducer). What a bridge author needs without opening it: - `BRepExtrema_ExtCC` crashes on parallel edges: `if (result.isParallel) { return result; }` before reading points. `Extrema_ExtCC::Points` itself over-reads on the same input (patch `0024`). -- `LocOpe_SplitDrafts::Perform()` throws on incompatible geometry: always `try`/`catch` it. - `OCC_CATCH_SIGNALS` is inert in this build (no `OCC_CONVERT_SIGNALS`). An OS signal raised inside OCCT is uncatchable in-process, and so is a C++ exception that reaches the Swift boundary (#345), which is why every `gp_Dir`/`gp_Ax*`/`Geom_Direction` construction from caller doubles diff --git a/README.md b/README.md index 55e146e00..78394b79d 100644 --- a/README.md +++ b/README.md @@ -9,7 +9,7 @@ A comprehensive Swift wrapper for [OpenCASCADE Technology (OCCT)](https://www.opencascade.com/) 8.0.1, providing B-Rep solid modeling for macOS and iOS. **v3.0.0. SemVer-stable; see [SEMVER.md](docs/SEMVER.md#v300) before upgrading from v2.x.** -**4,366 wrapped operations** | macOS 12+ / iOS 15+ (arm64), visionOS and tvOS untested | OCCT 8.0.1 +**4,365 wrapped operations** | macOS 12+ / iOS 15+ (arm64), visionOS and tvOS untested | OCCT 8.0.1 ## Quick Start ### Installation diff --git a/Scripts/repro/1393-splitdrafts/README.md b/Scripts/repro/1393-splitdrafts/README.md index 75c11d948..65c259b08 100644 --- a/Scripts/repro/1393-splitdrafts/README.md +++ b/Scripts/repro/1393-splitdrafts/README.md @@ -51,7 +51,25 @@ the process**. Through the bridge it is catchable (the Swift call returns `nil`, regression test asserts), so this is a difference in where the throw crosses, not in whether it happens. -## Disposition +## Outcome: the wrapper is gone + +`Shape.splitDrafts` was removed in v4.0.0 rather than repaired, and no patch is carried. + +The deciding fact is not the defect, it is upstream's own verdict on the class. OCCT deleted +`LocOpe_SplitDrafts` outright on 2026-08-07 in +[OCCT#1442](https://github.com/Open-Cascade-SAS/OCCT/pull/1442), a "clean up dead headers" pass, and +`git grep SplitDrafts upstream/master` returns nothing. It had no caller anywhere in the OCCT tree, +not even a DRAW command, which is also how a defect this total survived to 8.0.1 unnoticed. + +So there was no upstream PR to open, nothing to fix for anyone else's benefit, and a carried patch +would have been this project reviving a class its own maintainer had just removed, with a guaranteed +expiry at the first kernel bump past that commit. `okf/policies/scope-boundary.md` says stay +faithful to OCCT; wrapping what OCCT has deleted is the opposite of that. + +The record below is kept because the investigation is what made the decision possible, and because +the next reader who wonders why a 1996 class is missing deserves the answer. + +## Disposition, as it stood before the removal - **Test**: `Tests/OCCTModelingTests/Issue1393SplitDraftsTests.swift` asserts the refusal, and says in its own comment that a non-nil result means a repin fixed the kernel and the test should then @@ -64,3 +82,19 @@ happens. - **Not done**: no bridge-side guard. Refusing the call up front would be refusing the whole operation, and the `nil` it already returns is the same answer with the kernel's own reason behind it. + +## What is kept from the fix that was not taken + +`upstream/LocOpe_SplitDrafts_Test.cxx` is the GTest written for an upstream PR that could not be +filed, since the class it tests no longer exists upstream. It is kept for one reason: it is the +only executable statement of what a working `LocOpe_SplitDrafts` should produce, proven both ways +against a patched and an unpatched kernel (`IsDone=1`, seven faces, one of them tilted by exactly +the requested draft angle, a 10-unit cube's volume rising 1000 to 1022.04 against the wedge's +analytic 22.04). + +If OCCT ever restores the class, or a downstream fork keeps it, that file is the head start. If +nobody ever does, it is the record of what was measured before the wrapper was removed, which is +worth more than the diff that was discarded with it. + +The patch itself, `0034`, was never carried. It exists only in the closed PR +[#1627](https://github.com/SecondMouseAU/OCCTSwift/pull/1627). diff --git a/Scripts/repro/1393-splitdrafts/upstream/LocOpe_SplitDrafts_Test.cxx b/Scripts/repro/1393-splitdrafts/upstream/LocOpe_SplitDrafts_Test.cxx new file mode 100644 index 000000000..ad0be4eb6 --- /dev/null +++ b/Scripts/repro/1393-splitdrafts/upstream/LocOpe_SplitDrafts_Test.cxx @@ -0,0 +1,90 @@ +// Copyright (c) 2026 OPEN CASCADE SAS +// +// This file is part of Open CASCADE Technology software library. +// +// This library is free software; you can redistribute it and/or modify it under +// the terms of the GNU Lesser General Public License version 2.1 as published +// by the Free Software Foundation, with special exception defined in the file +// OCCT_LGPL_EXCEPTION.txt. Consult the file LICENSE_LGPL_21.txt included in OCCT +// distribution for complete text of the license and disclaimer of any warranty. +// +// Alternatively, this file may be used under the terms of Open CASCADE +// commercial license or contractual agreement. + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +namespace +{ +//! The +Z face of a box built at the origin. +TopoDS_Face TopFace(const TopoDS_Shape& theBox, const double theHeight) +{ + for (TopExp_Explorer anExp(theBox, TopAbs_FACE); anExp.More(); anExp.Next()) + { + const TopoDS_Face aFace = TopoDS::Face(anExp.Current()); + const occ::handle aPlane = occ::down_cast(BRep_Tool::Surface(aFace)); + if (!aPlane.IsNull() + && std::abs(aPlane->Pln().Location().Z() - theHeight) < Precision::Confusion()) + { + return aFace; + } + } + return TopoDS_Face(); +} +} // namespace + +// Perform() pipes along the intersection of the two drafted planes, which is always a line, and +// the pipe's path is the face normal, also a line. Both were passed to GeomFill_Pipe untrimmed, +// and GeomConvert::CurveToBSplineCurve rejects an infinite curve, so every planar request threw +// Standard_DomainError("No such curve"). +TEST(LocOpe_SplitDraftsTest, DraftPlanarFace) +{ + const TopoDS_Shape aBox = BRepPrimAPI_MakeBox(10.0, 10.0, 10.0).Shape(); + const TopoDS_Face aTop = TopFace(aBox, 10.0); + ASSERT_FALSE(aTop.IsNull()) << "Top face not found"; + + // The splitting wire, built on the face at u = 5 so that it carries a pcurve. + const occ::handle aSurf = BRep_Tool::Surface(aTop); + const occ::handle aLine2d = new Geom2d_Line(gp_Pnt2d(5.0, 0.0), gp_Dir2d(0.0, 1.0)); + TopoDS_Edge anEdge = BRepBuilderAPI_MakeEdge(aLine2d, aSurf, 0.0, 10.0).Edge(); + BRepLib::BuildCurves3d(anEdge); + const TopoDS_Wire aWire = BRepBuilderAPI_MakeWire(anEdge).Wire(); + + // The neutral plane must not be the face's own plane, or NewPlane() bails. + const gp_Pln aNeutral(gp_Pnt(5.0, 0.0, 0.0), gp_Dir(1.0, 0.0, 0.0)); + const double anAngle = 10.0 * M_PI / 180.0; + + LocOpe_SplitDrafts aSplit; + aSplit.Init(aBox); + ASSERT_NO_THROW(aSplit.Perform(aTop, aWire, gp_Dir(1.0, 0.0, 0.0), aNeutral, anAngle)); + ASSERT_TRUE(aSplit.IsDone()) << "Draft split not performed"; + + int aNbFaces = 0; + for (TopExp_Explorer anExp(aSplit.Shape(), TopAbs_FACE); anExp.More(); anExp.Next()) + { + ++aNbFaces; + } + EXPECT_EQ(aNbFaces, 7) << "The top face should be split in two"; + + // Half the top face (5 x 10) is tipped up by the draft angle, adding that wedge. + GProp_GProps aProps; + BRepGProp::VolumeProperties(aSplit.Shape(), aProps); + const double anExpected = 1000.0 + 0.5 * 5.0 * (5.0 * tan(anAngle)) * 10.0; + EXPECT_NEAR(aProps.Mass(), anExpected, 1.0e-4) << "Drafted volume is wrong"; +} diff --git a/Sources/OCCTBridge/include/OCCTBridge.h b/Sources/OCCTBridge/include/OCCTBridge.h index ec1c8a9ce..0d6c7c0de 100644 --- a/Sources/OCCTBridge/include/OCCTBridge.h +++ b/Sources/OCCTBridge/include/OCCTBridge.h @@ -539,7 +539,6 @@ // LocOpe_Prism → OCCTLocOpePrism // LocOpe_Revol → OCCTLocOpeRevol // LocOpe_RevolutionForm → OCCTLocOpeRevolutionForm -// LocOpe_SplitDrafts → OCCTLocOpeSplitDrafts // LocOpe_SplitShape → OCCTLocOpeSplitShape* // LocOpe_Spliter → OCCTLocOpeSplitByWire* // LocOpe_WiresOnShape → OCCTLocOpeBuildWires, OCCTLocOpeSplitByWire* diff --git a/Sources/OCCTBridge/include/OCCTBridge_Modeling.h b/Sources/OCCTBridge/include/OCCTBridge_Modeling.h index c6a42cbba..4b4c35a57 100644 --- a/Sources/OCCTBridge/include/OCCTBridge_Modeling.h +++ b/Sources/OCCTBridge/include/OCCTBridge_Modeling.h @@ -1906,31 +1906,6 @@ OCCTShapeRef _Nullable OCCTLocOpeSplitShapeByVertex(OCCTShapeRef shape, int32_t edgeIndex, double parameter); -// --- LocOpe_SplitDrafts --- - -/// Split a face with draft angles on both sides of a wire. -/// @param shape Shape containing the face -/// @param faceIndex Index of the face to split -/// @param wire Wire defining the split -/// @param dirX,dirY,dirZ Extraction direction -/// @param planeOriginX,Y,Z Neutral plane origin -/// @param planeNormalX,Y,Z Neutral plane normal -/// @param angle Draft angle in radians -/// @return Modified shape, or NULL on failure -OCCTShapeRef _Nullable OCCTLocOpeSplitDrafts(OCCTShapeRef shape, - int32_t faceIndex, - OCCTShapeRef wire, - double dirX, - double dirY, - double dirZ, - double planeOriginX, - double planeOriginY, - double planeOriginZ, - double planeNormalX, - double planeNormalY, - double planeNormalZ, - double angle); - // --- LocOpe_FindEdges --- // #613: both finders return a SELECTION of edges, not an enumeration, so the position of an entry diff --git a/Sources/OCCTBridge/src/OCCTBridge.mm b/Sources/OCCTBridge/src/OCCTBridge.mm index 78e096aa4..ca35f251d 100644 --- a/Sources/OCCTBridge/src/OCCTBridge.mm +++ b/Sources/OCCTBridge/src/OCCTBridge.mm @@ -373,7 +373,6 @@ void occtEnsureSignals() #include #include #include -#include #include #include #include diff --git a/Sources/OCCTBridge/src/OCCTBridge_Modeling_Boolean.mm b/Sources/OCCTBridge/src/OCCTBridge_Modeling_Boolean.mm index 9f95394bb..9cde4a518 100644 --- a/Sources/OCCTBridge/src/OCCTBridge_Modeling_Boolean.mm +++ b/Sources/OCCTBridge/src/OCCTBridge_Modeling_Boolean.mm @@ -62,7 +62,6 @@ #include #include #include -#include #include #include #include diff --git a/Sources/OCCTBridge/src/OCCTBridge_Modeling_Chamfer.mm b/Sources/OCCTBridge/src/OCCTBridge_Modeling_Chamfer.mm index 95dea8d74..538454cf4 100644 --- a/Sources/OCCTBridge/src/OCCTBridge_Modeling_Chamfer.mm +++ b/Sources/OCCTBridge/src/OCCTBridge_Modeling_Chamfer.mm @@ -61,7 +61,6 @@ #include #include #include -#include #include #include #include diff --git a/Sources/OCCTBridge/src/OCCTBridge_Modeling_Features.mm b/Sources/OCCTBridge/src/OCCTBridge_Modeling_Features.mm index d67de41be..b81f0c301 100644 --- a/Sources/OCCTBridge/src/OCCTBridge_Modeling_Features.mm +++ b/Sources/OCCTBridge/src/OCCTBridge_Modeling_Features.mm @@ -61,7 +61,6 @@ #include #include #include -#include #include #include #include @@ -1710,67 +1709,6 @@ OCCTShapeRef OCCTLocOpeSplitShapeByVertex(OCCTShapeRef shape, int32_t edgeIndex, } } -OCCTShapeRef OCCTLocOpeSplitDrafts(OCCTShapeRef shape, - int32_t faceIndex, - OCCTShapeRef wire, - double dirX, - double dirY, - double dirZ, - double planeOriginX, - double planeOriginY, - double planeOriginZ, - double planeNormalX, - double planeNormalY, - double planeNormalZ, - double angle) -{ - // #1026: the wire's ShapeType() read below is an unguarded myTShape dereference. - if (!occtShapeIsPresent(shape) || !occtShapeIsPresent(wire)) - return nullptr; - try - { - LocOpe_SplitDrafts splitDrafts; - splitDrafts.Init(shape->shape); - - // #541: the shared face enumeration, so this names the face face(at:) names. - TopoDS_Face face = occtFaceAt(shape->shape, faceIndex); - if (face.IsNull()) - return nullptr; - - // Extract wire - TopoDS_Wire w; - if (wire->shape.ShapeType() == TopAbs_WIRE) - { - w = TopoDS::Wire(wire->shape); - } - else - { - for (TopExp_Explorer exp(wire->shape, TopAbs_WIRE); exp.More(); exp.Next()) - { - w = TopoDS::Wire(exp.Current()); - break; - } - } - if (w.IsNull()) - return nullptr; - - gp_Dir dir(dirX, dirY, dirZ); - gp_Pln plane(gp_Pnt(planeOriginX, planeOriginY, planeOriginZ), - gp_Dir(planeNormalX, planeNormalY, planeNormalZ)); - - splitDrafts.Perform(face, w, dir, plane, angle); - - TopoDS_Shape result = splitDrafts.Shape(); - if (result.IsNull()) - return nullptr; - return new OCCTShape(result); - } - catch (...) - { - return nullptr; - } -} - // #613: a finder returns a SELECTION, so the position of an entry in outEdges is a result slot, not // an index into anything. Swift wrote that slot number into Edge.index all the same. Measured on a // 10mm box (identically for the origin-centred and origin-at-zero spellings), edgesInFace(at: 3) diff --git a/Sources/OCCTBridge/src/OCCTBridge_Modeling_Fillet.mm b/Sources/OCCTBridge/src/OCCTBridge_Modeling_Fillet.mm index 45da0321a..ee914c9ec 100644 --- a/Sources/OCCTBridge/src/OCCTBridge_Modeling_Fillet.mm +++ b/Sources/OCCTBridge/src/OCCTBridge_Modeling_Fillet.mm @@ -61,7 +61,6 @@ #include #include #include -#include #include #include #include diff --git a/Sources/OCCTBridge/src/OCCTBridge_Modeling_HLRProjection.mm b/Sources/OCCTBridge/src/OCCTBridge_Modeling_HLRProjection.mm index eef85be26..023816c3a 100644 --- a/Sources/OCCTBridge/src/OCCTBridge_Modeling_HLRProjection.mm +++ b/Sources/OCCTBridge/src/OCCTBridge_Modeling_HLRProjection.mm @@ -61,7 +61,6 @@ #include #include #include -#include #include #include #include diff --git a/Sources/OCCTBridge/src/OCCTBridge_Modeling_HealingSewing.mm b/Sources/OCCTBridge/src/OCCTBridge_Modeling_HealingSewing.mm index beaa0674b..f56833841 100644 --- a/Sources/OCCTBridge/src/OCCTBridge_Modeling_HealingSewing.mm +++ b/Sources/OCCTBridge/src/OCCTBridge_Modeling_HealingSewing.mm @@ -62,7 +62,6 @@ #include #include #include -#include #include #include #include diff --git a/Sources/OCCTBridge/src/OCCTBridge_Modeling_Misc.mm b/Sources/OCCTBridge/src/OCCTBridge_Modeling_Misc.mm index 47919579a..8293c120a 100644 --- a/Sources/OCCTBridge/src/OCCTBridge_Modeling_Misc.mm +++ b/Sources/OCCTBridge/src/OCCTBridge_Modeling_Misc.mm @@ -62,7 +62,6 @@ #include #include #include -#include #include #include #include diff --git a/Sources/OCCTBridge/src/OCCTBridge_Modeling_ShapeToolsHistory.mm b/Sources/OCCTBridge/src/OCCTBridge_Modeling_ShapeToolsHistory.mm index 9a4102994..b277c5c78 100644 --- a/Sources/OCCTBridge/src/OCCTBridge_Modeling_ShapeToolsHistory.mm +++ b/Sources/OCCTBridge/src/OCCTBridge_Modeling_ShapeToolsHistory.mm @@ -61,7 +61,6 @@ #include #include #include -#include #include #include #include diff --git a/Sources/OCCTBridge/src/OCCTBridge_Modeling_SolidPrimitives.mm b/Sources/OCCTBridge/src/OCCTBridge_Modeling_SolidPrimitives.mm index 951b26302..7ff526114 100644 --- a/Sources/OCCTBridge/src/OCCTBridge_Modeling_SolidPrimitives.mm +++ b/Sources/OCCTBridge/src/OCCTBridge_Modeling_SolidPrimitives.mm @@ -62,7 +62,6 @@ #include #include #include -#include #include #include #include diff --git a/Sources/OCCTBridge/src/OCCTBridge_Modeling_Sweep.mm b/Sources/OCCTBridge/src/OCCTBridge_Modeling_Sweep.mm index df70d2633..0e78f021e 100644 --- a/Sources/OCCTBridge/src/OCCTBridge_Modeling_Sweep.mm +++ b/Sources/OCCTBridge/src/OCCTBridge_Modeling_Sweep.mm @@ -62,7 +62,6 @@ #include #include #include -#include #include #include #include diff --git a/Sources/OCCTBridge/src/OCCTBridge_Modeling_Transform.mm b/Sources/OCCTBridge/src/OCCTBridge_Modeling_Transform.mm index 60dab8d77..09d7af915 100644 --- a/Sources/OCCTBridge/src/OCCTBridge_Modeling_Transform.mm +++ b/Sources/OCCTBridge/src/OCCTBridge_Modeling_Transform.mm @@ -62,7 +62,6 @@ #include #include #include -#include #include #include #include diff --git a/Sources/OCCTBridge/src/OCCTBridge_Modeling_WireEdgeFaceBuilders.mm b/Sources/OCCTBridge/src/OCCTBridge_Modeling_WireEdgeFaceBuilders.mm index 9195f98e6..1c51163af 100644 --- a/Sources/OCCTBridge/src/OCCTBridge_Modeling_WireEdgeFaceBuilders.mm +++ b/Sources/OCCTBridge/src/OCCTBridge_Modeling_WireEdgeFaceBuilders.mm @@ -62,7 +62,6 @@ #include #include #include -#include #include #include #include diff --git a/Sources/OCCTSwift/Shape+Modeling.swift b/Sources/OCCTSwift/Shape+Modeling.swift index d547592ab..17a4ab5c5 100644 --- a/Sources/OCCTSwift/Shape+Modeling.swift +++ b/Sources/OCCTSwift/Shape+Modeling.swift @@ -2033,39 +2033,6 @@ extension Shape { return Shape(handle: ref) } - // MARK: - LocOpe_SplitDrafts - - /// Split a face with draft angles on both sides of a wire. - /// - /// Uses LocOpe_SplitDrafts to create draft surfaces on a shape. - /// - /// - Parameters: - /// - faceIndex: Index of the face to split (0-based) - /// - wire: Wire defining the split line - /// - direction: Extraction direction - /// - planeOrigin: Origin of the neutral plane - /// - planeNormal: Normal of the neutral plane - /// - angle: Draft angle in radians - /// - Returns: Modified shape with draft, or nil on failure - public func splitDrafts( - faceIndex: Int, wire: Wire, - direction: SIMD3, - planeOrigin: SIMD3, - planeNormal: SIMD3, - angle: Double - ) -> Shape? { - let wireShape = Shape(handle: OCCTShapeFromWire(wire.handle)) - guard - let ref = OCCTLocOpeSplitDrafts( - handle, Int32(faceIndex), wireShape.handle, - direction.x, direction.y, direction.z, - planeOrigin.x, planeOrigin.y, planeOrigin.z, - planeNormal.x, planeNormal.y, planeNormal.z, - angle) - else { return nil } - return Shape(handle: ref) - } - /// Shape modification history for tracking what happened during operations. public class History { let historyRef: OCCTHistoryRef diff --git a/Tests/OCCTModelingTests/Issue1393SplitDraftsTests.swift b/Tests/OCCTModelingTests/Issue1393SplitDraftsTests.swift deleted file mode 100644 index 4e0e0befa..000000000 --- a/Tests/OCCTModelingTests/Issue1393SplitDraftsTests.swift +++ /dev/null @@ -1,62 +0,0 @@ -import Testing -import simd - -@testable import OCCTSwift - -/// `Shape.splitDrafts` had no test anywhere in the tree (#1393). It has one now, and what it -/// records is that the operation cannot succeed on this kernel. -/// -/// `LocOpe_SplitDrafts::Perform` only accepts a planar face (its `NewPlane()` helper intersects -/// the neutral plane with the face's own plane), and for a planar face it always ends up piping -/// along the intersection line of two planes, which is a `Geom_Line`. `GeomFill_Pipe` converts -/// its section curve with `GeomConvert::CurveToBSplineCurve`, whose type chain handles circle, -/// ellipse, hyperbola, parabola, Bezier, B-spline and offset curves and throws -/// `Standard_DomainError("No such curve")` on anything else, a line included. So every valid call -/// reaches the same throw. Measured, not inferred: -/// `Scripts/repro/1393-splitdrafts/`. -/// -/// The test asserts the refusal rather than a result, so it will fail loudly if a kernel repin -/// ever makes the operation work, which is the signal we want. -@Suite("Issue #1393, LocOpe_SplitDrafts coverage") -struct Issue1393SplitDraftsTests { - - @Test("splitDrafts refuses rather than crashing, on a well-formed planar request") - func planarRequestIsRefused() { - guard let box = Shape.box(width: 10, height: 10, depth: 10) else { - Issue.record("box construction failed") - return - } - // The top face of the box, found by its own geometry rather than by a pinned index. - let faces = box.faces() - guard - let topIndex = faces.firstIndex(where: { - guard $0.isPlanar, let b = $0.bounds else { return false } - // Shape.box is centred on the origin, so the top face sits at z = +5. - return abs(b.min.z - 5.0) < 1e-6 && abs(b.max.z - 5.0) < 1e-6 - }) - else { - Issue.record("no top face found") - return - } - - // A splitting wire lying on that face, along x = 0. - guard let wire = Wire.line(from: SIMD3(0, -5, 5), to: SIMD3(0, 5, 5)) else { - Issue.record("wire construction failed") - return - } - - // The neutral plane must not be the face's own plane, or the kernel's NewPlane() helper - // bails before doing anything: x = 0 is the plane whose intersection with the face is - // the line the wire lies along. - let result = box.splitDrafts( - faceIndex: topIndex, wire: wire, - direction: SIMD3(1, 0, 0), - planeOrigin: SIMD3(0, 0, 0), - planeNormal: SIMD3(1, 0, 0), - angle: 10.0 * .pi / 180.0) - - // A non-nil result means a kernel repin fixed GeomConvert's missing Geom_Line case, and - // this test should then become a real behavioural test rather than a refusal test. - #expect(result == nil, "LocOpe_SplitDrafts cannot complete on this kernel") - } -} diff --git a/docs/API_REFERENCE.md b/docs/API_REFERENCE.md index 53835a4fb..61566badc 100644 --- a/docs/API_REFERENCE.md +++ b/docs/API_REFERENCE.md @@ -46,7 +46,7 @@ a map of the major areas, and the `Total` as the count. | **Surfaces (Surface)** | 86 | plane, cylinder, cone, sphere, torus, extrusion, revolution, bezier, bspline, trim, offset, translate, rotate, scale, mirror, toBSpline, approximate, uIso, vIso, pipe, drawGrid, drawMesh, curvatures, projectCurve, projectCurveSegments, projectCurve3D, projectPoint, plateThrough, nlPlateDeformed, nlPlateDeformedG1, nlPlateDeformedG2, nlPlateDeformedG3, nlPlateDeformedIncremental, nlPlateDerivative, evaluateGrid, evaluateGridD1, intersections, toAnalytical, bezierFill(4-curve), bezierFill(2-curve), singularityCount, isDegenerated, hasSingularities, toBezierPatchGrid, bsplineFill(2-curve), bsplineFill(4-curve), extrema, valueOfUV, nextValueOfUV, conicalSurface(axis), conicalSurface(points), cylindricalSurface(axis), cylindricalSurface(points), planeFromPoints, planeFromPointNormal, trimmedCone, trimmedCylinder, knotSplitting, joinBezierPatches, convertToAnalytical, splitByContinuity, generatedFromSections, degeneratedBoundaryValue, isDegeneratedBoundary, boundaryWithSurfaceEvaluate, averagePlane | | **Face Analysis** | 22 | uvBounds, point(atU:v:), normal, orientation, gaussianCurvature, meanCurvature, principalCurvatures, surfaceType, area, project, allProjections, intersection, orientedFaces | | **Edge Analysis** | 26 | parameterBounds, curveType, point(at:), curvature, tangent, normal, centerOfCurvature, torsion, project, hasCurve3D, isClosed3D, isSeam, adjacentFaces, dihedralAngle, split | -| **Feature-Based** | 37 | boss, pocket, prism, drilled, split, glue, evolved, evolvedAdvanced, linearPattern, circularPattern, linearRib, revolutionForm, draftPrism, draftPrismThruAll, revolFeature, revolFeatureThruAll, pipeFeature, extrudedSemiInfinite, prismUntilFace, pipeFeatureFromProfile, localRevolution, localRevolutionWithOffset, locOpeDraftPrism, localPipe, localLinearForm, localRevolutionForm, splitFace, splitEdge, splitDrafts, commonEdges, edgesInFace, cylindricalHole, cylindricalHole(extent:), cylindricalHoleBlind, cylindricalHoleThruNext, cylindricalHoleStatus, locOpeGlue | +| **Feature-Based** | 36 | boss, pocket, prism, drilled, split, glue, evolved, evolvedAdvanced, linearPattern, circularPattern, linearRib, revolutionForm, draftPrism, draftPrismThruAll, revolFeature, revolFeatureThruAll, pipeFeature, extrudedSemiInfinite, prismUntilFace, pipeFeatureFromProfile, localRevolution, localRevolutionWithOffset, locOpeDraftPrism, localPipe, localLinearForm, localRevolutionForm, splitFace, splitEdge, commonEdges, edgesInFace, cylindricalHole, cylindricalHole(extent:), cylindricalHoleBlind, cylindricalHoleThruNext, cylindricalHoleStatus, locOpeGlue | | **Healing/Analysis** | 69 | analyze, fixed, unified, simplified, withoutSmallFaces, wire.fixed, face.fixed, divided, directFaces, scaledGeometry, bsplineRestriction, sweptToElementary, revolutionToElementary, convertedToBSpline, sewn, upgraded, fastSewn, normalProjection, fixedWireframe, removingInternalWires, fusedEdges, simpleOffset, fixingSmallFaces, removingLocations, quilt, splitByAngle, droppingSmallEdges, splittingFace, freeBounds, fixedFreeBounds, withSurfacesAsBSpline, withSurfacesAsRevolution, checkSmallFaces, purgedLocations, curveOnSurfaceCheck, connectedEdges, convertedToBezier, limitTolerance, setTolerance, splitCommonVertices, connectedFaces, fixEdgeSameParameter, fixEdgeVertexTolerance, fixWireVertices, removeSmallSolids, mergeSmallSolids, bsplineRestriction(advanced), freeBoundsAnalysis, closedFreeBoundInfo, openFreeBoundInfo, closedFreeBoundWire, openFreeBoundWire, wireVertexAnalysis, wireVertexStatus, nearestPlane, shellSewing, trsfModification, gtrsfModification, deepCopy(modifier), bsplineRestrictionConfigurable, convertToBSplineConfigurable | | **Measurement** | 36 | volume, surfaceArea, centerOfMass, properties, distance, distance(wire/edge/face), minDistance, intersects, intersects(wire/edge/face), inertiaProperties, surfaceInertiaProperties, allDistanceSolutions, isInside, findSurfaceEx, findPlane, analyzePointCloud, edgeEdgeExtrema, pointFaceExtrema, faceFaceExtrema, pointEdgeExtrema, edgeFaceExtrema, polyhedralDistance | | **Point Classification** | 3 | classify(point:) on solid, classify(point:) on face, classify(u:v:) on face | @@ -503,7 +503,7 @@ a map of the major areas, and the `Total` as the count. | **GeomEval TBezier/AHTBezier Curves** | 4 | tBezier (3D), tBezierRational (3D), ahtBezier (3D), ahtBezierRational (3D) | | **GeomEval TBezier/AHTBezier Surfaces** | 2 | tBezier surface, ahtBezier surface | | **Geom2dEval TBezier/AHTBezier** | 2 | tBezier (2D), ahtBezier (2D) | -| **Total** | **4,366** | | +| **Total** | **4,365** | | > **Note:** OCCTSwift wraps a curated subset of OCCT. To add new functions, see [docs/EXTENDING.md](docs/EXTENDING.md). diff --git a/docs/index.md b/docs/index.md index d75600e00..25a86a4ff 100644 --- a/docs/index.md +++ b/docs/index.md @@ -7,7 +7,7 @@ nav_order: 1 A comprehensive Swift wrapper for [OpenCASCADE Technology](https://dev.opencascade.org) (OCCT 8.0.1), B-Rep solid modeling, CAD data exchange, meshing and geometry for **macOS and iOS**. visionOS and tvOS are declared and buildable but untested, and need a local kernel rebuild (#978). -Three-layer architecture: Swift public API → Objective-C++ bridge → OCCT C++. **4,366 wrapped operations.** +Three-layer architecture: Swift public API → Objective-C++ bridge → OCCT C++. **4,365 wrapped operations.** ```swift import OCCTSwift diff --git a/docs/reference/Shape-Measurement.md b/docs/reference/Shape-Measurement.md index 10e64c04d..8146b2ee2 100644 --- a/docs/reference/Shape-Measurement.md +++ b/docs/reference/Shape-Measurement.md @@ -2100,38 +2100,6 @@ public func splitEdge(at edgeIndex: Int, parameter: Double) -> Shape? --- -### `splitDrafts(faceIndex:wire:direction:planeOrigin:planeNormal:angle:)` - -Split a face with draft angles on both sides of a wire. - -```swift -public func splitDrafts(faceIndex: Int, wire: Wire, - direction: SIMD3, - planeOrigin: SIMD3, - planeNormal: SIMD3, - angle: Double) -> Shape? -``` - -- **Parameters:** - - `faceIndex`: 0-based index of the face to split. - - `wire`: Wire defining the split line. - - `direction`: Extraction direction. - - `planeOrigin`: Origin of the neutral plane. - - `planeNormal`: Normal of the neutral plane. - - `angle`: Draft angle in radians. -- **Returns:** Modified shape with draft, or `nil` on failure. -- **OCCT:** `LocOpe_SplitDrafts` (via `OCCTLocOpeSplitDrafts`). -- **Note:** `LocOpe_SplitDrafts::Perform()` can throw on incompatible geometry; the bridge wraps it in a try-catch. -- **Returns `nil` on every input in OCCT 8.0.1 (#1393).** `LocOpe_SplitDrafts` accepts only a planar - face, and for a planar face it pipes along the intersection of two planes, which is always a - `Geom_Line`. `GeomConvert::CurveToBSplineCurve` has no line case and throws - `Standard_DomainError("No such curve")`, so the operation cannot complete on this kernel whatever - the caller passes. Measured in `Scripts/repro/1393-splitdrafts/`, with the fix queued upstream. - `Tests/OCCTModelingTests/Issue1393SplitDraftsTests.swift` asserts the refusal and will fail when - a repin makes it work. - ---- - ### `commonEdges(with:)` Find edges in common between this shape and another. diff --git a/okf/references/known-occt-bugs.md b/okf/references/known-occt-bugs.md index 2f265efab..491efacd9 100644 --- a/okf/references/known-occt-bugs.md +++ b/okf/references/known-occt-bugs.md @@ -36,7 +36,7 @@ fix shipped upstream and the pinned kernel has it natively, so the patch was ret | #319 | `Intf_Interference::Insert` / `BOPAlgo_CheckerSI` | O(n) `GetPoint` per comparison and no progress poll below a whole face, so `isSelfIntersecting(hardTimeout:)` ran 619 s past a 30 s deadline | patch `0010`, pinned; [OCCT#1386](https://github.com/Open-Cascade-SAS/OCCT/pull/1386) open | `Scripts/repro/319-selfintersection/` | | #323 | `ShapeAnalysis_FreeBounds` (stale `lwire`), `Geom_BSplineCurve::PeriodicNormalization` (O(N) loop that could never terminate), `StepData_StepWriter::AddString` (infinite loop on a >72-char token) | three proactive backports | 8.0.1, [OCCT#1331](https://github.com/Open-Cascade-SAS/OCCT/pull/1331) / [#1329](https://github.com/Open-Cascade-SAS/OCCT/pull/1329) / [#1318](https://github.com/Open-Cascade-SAS/OCCT/pull/1318); `0007`-`0009` retired | retired entries; `STEPWriterOversizedNameTests` | | #345 | `gp_Dir`, `gp_Ax1`/`Ax2`/`Ax3`, `Geom_Direction`, `D0`-`D2` evaluators | `Standard_ConstructionError` thrown from 49 bridge functions with no `try` in the chain, `std::terminate` at the Swift boundary, the SIGABRT nothing could localise | bridge: every site wrapped, `_Nonnull` returners fall back to a valid default axis. 70 clean full-suite runs | `Tests/OCCTStressTests/StressNullInvalidTests.swift`; the fourteen `occtBuildTrsf3D` callers all sit inside a `try` (#995) | -| #1393 | `GeomConvert::CurveToBSplineCurve`, reached through `GeomFill_Pipe` from `LocOpe_SplitDrafts::Perform` | the type chain has no `Geom_Line` case and its final `else` throws `Standard_DomainError("No such curve")`. `LocOpe_SplitDrafts` only accepts a planar face and then pipes along the intersection of two planes, which is always a line, so **every valid call throws** and `Shape.splitDrafts` can never succeed on this kernel | none yet; upstream fix queued (a line is a degree-1 B-spline). The bridge's `catch (...)` already turns it into `nil`, and no guard is added, since refusing up front would refuse the whole operation | `Scripts/repro/1393-splitdrafts/` | +| #1393 | `GeomConvert::CurveToBSplineCurve`, reached through `GeomFill_Pipe` from `LocOpe_SplitDrafts::Perform` | the untrimmed branch refuses an infinite curve by documented design, and `LocOpe_SplitDrafts` only accepts a planar face and then pipes along the intersection of two planes, which is always a line, so **every valid call threw** and `Shape.splitDrafts` could never succeed | **the wrapper is gone**. Upstream deleted `LocOpe_SplitDrafts` outright in [OCCT#1442](https://github.com/Open-Cascade-SAS/OCCT/pull/1442) (2026-08-07, dead code, no caller in the tree), so there was nothing to fix and nothing to carry: wrapping a class its own maintainer removed is not staying faithful to OCCT. Removed in v4.0.0, no patch carried | `Scripts/repro/1393-splitdrafts/` | | #348 | `ShapeUpgrade_UnifySameDomain::IntUnifyFaces` / `SplitWire` | five `CurveOnSurface` results dereferenced without `IsNull()` | 8.0.1, [OCCT#1392](https://github.com/Open-Cascade-SAS/OCCT/pull/1392); `0013` retired | `Scripts/repro/348-unify-null-pcurve/` | | #430, #433, #434 | `BRepFill_Filling::AddConstraints` + `GeomPlate_BuildPlateSurface::Perform` | face-less branch discards the pcurve's `f`/`l`, a ±2e100 constraint; the `!Ok` recovery then dereferences the handle `Perform` nullified on entry. Catchable on a planar support, an uncatchable SIGSEGV on a periodic one, and G1 was the default | bridge: `occtFillingSupportFaceFromPCurve`/`occtFillingAddConstraint` synthesise a support face so the trimming overload is used. Kernel one-liner proven, deliberately not carried or filed. #433/#434 converged `FillingSurface` onto the same builder | `Scripts/repro/430-fill-untrimmed-pcurve/` | | #484 | `ShapeFix_ComposeShell::Perform`, `ShapeUpgrade_WireDivide::Perform` | dereference an unset `ShapeBuild_ReShape` context | patch `0017`, pinned; [OCCT#1410](https://github.com/Open-Cascade-SAS/OCCT/pull/1410) open | `Scripts/repro/484-null-reshape-context/` |