Loft between two planar sections can create BSpline side faces that fail offset
Problem
A user reported (#1351) that a part created with loft() in build123d v0.11.0 fails during a later offset() operation, while an equivalent part created with extrude() + draft() offsets successfully.
A simplified reproducer is:
skt2 = RectangleRounded(length, WIDTH, 7) - Pos(Y=-WIDTH / 2) * Rectangle(40, 20)
skt2 = fillet(skt2.vertices(), 2.5)
bottom_face = offset(skt2, -1).face()
top_face = (Pos(Z=40) * skt2).face()
loft_part = loft([bottom_face, top_face])
open_loft_part = offset(
loft_part, -1, openings=loft_part.faces().filter_by(Axis.Z)
) # RuntimeError
draft_part = extrude(skt2, 40)
draft_part = draft(
draft_part.faces().filter_by(Axis.Z, reverse=True),
Plane.XY.offset(40),
-degrees(atan2(1, 40)),
)
open_draft_part = offset(draft_part, -1, openings=draft_part.faces().filter_by(Axis.Z))
Inspection shows that the lofted part contains faces with geometry types:
PLANE, CONE, BSPLINE
while the drafted part contains only:
PLANE, CONE
The BSPLINE side faces appear to be geometrically simple ruled faces. In some cases their boundary edges are:
LINE, BSPLINE, LINE, BSPLINE
or:
CIRCLE, BSPLINE, CIRCLE, BSPLINE
The BSpline edges appear to be longitudinal connector edges generated by OCCT’s BRepOffsetAPI_ThruSections. These surfaces are usually planar or conical, but they are not represented analytically, and BRepOffsetAPI_MakeThickSolid can fail when offsetting the resulting solid.
This may be related to OCCT/OCP behavior changes between the v0.10.0 and v0.11.0 dependency sets. build123d moved from cadquery-ocp >= 7.8, < 7.9 to cadquery-ocp-novtk >= 7.9, < 8.0.
Proposed Limited Fix
Keep the existing BRepOffsetAPI_ThruSections loft as the primary loft builder. After the loft is created, inspect the generated faces. For any face whose geom_type is BSPLINE, attempt a targeted analytic reconstruction.
This reconstruction would only be attempted for lofts with exactly two parallel planar input sections, where the BSpline face appears to be a simple ruled patch between corresponding section edges.
The cleanup would examine the BSpline face boundary. In the observed failure cases, these faces have boundary patterns such as:
LINE, BSPLINE, LINE, BSPLINE
or:
CIRCLE, BSPLINE, CIRCLE, BSPLINE
The non-BSpline edges are the original section/profile edges. The BSpline edges are longitudinal connector edges generated by OCCT. The proposed cleanup would keep the original section/profile edges, replace the longitudinal BSpline connector edges with straight connector edges, and then attempt to rebuild the face analytically.
Initial reconstruction cases:
- LINE / LINE: attempt to rebuild the face as a planar face from the two line section edges and two straight longitudinal connector edges. This is not mathematically guaranteed to be planar in all cases, but should cover common user cases where the loft is equivalent to a drafted extrusion.
- CIRCLE / CIRCLE: attempt to detect whether the two circular section edges define a cylindrical or conical patch from their centers, axes, and radii. If detected, rebuild the face as an analytic cylindrical or conical face.
If analytic reconstruction succeeds, replace the original BSpline face in the loft result. If reconstruction fails, leave the original BSpline face unchanged.
The fallback is therefore per-face, not all-or-nothing: unsupported or ambiguous BSpline faces remain as generated by BRepOffsetAPI_ThruSections.
Notes
This should not attempt to become a general BSpline-surface simplifier. The goal is a targeted fix for a common “draft-like loft between two planar profiles” case where the expected result is ruled and analytic.
This may also improve downstream operations such as offset() by avoiding unnecessary BSpline surfaces in simple lofts.
Loft between two planar sections can create BSpline side faces that fail offset
Problem
A user reported (#1351) that a part created with
loft()in build123d v0.11.0 fails during a lateroffset()operation, while an equivalent part created withextrude()+draft()offsets successfully.A simplified reproducer is:
Inspection shows that the lofted part contains faces with geometry types:
PLANE, CONE, BSPLINE
while the drafted part contains only:
PLANE, CONE
The BSPLINE side faces appear to be geometrically simple ruled faces. In some cases their boundary edges are:
The BSpline edges appear to be longitudinal connector edges generated by OCCT’s
BRepOffsetAPI_ThruSections. These surfaces are usually planar or conical, but they are not represented analytically, and BRepOffsetAPI_MakeThickSolid can fail when offsetting the resulting solid.This may be related to OCCT/OCP behavior changes between the v0.10.0 and v0.11.0 dependency sets. build123d moved from cadquery-ocp >= 7.8, < 7.9 to cadquery-ocp-novtk >= 7.9, < 8.0.
Proposed Limited Fix
Keep the existing
BRepOffsetAPI_ThruSectionsloft as the primary loft builder. After the loft is created, inspect the generated faces. For any face whosegeom_typeisBSPLINE, attempt a targeted analytic reconstruction.This reconstruction would only be attempted for lofts with exactly two parallel planar input sections, where the BSpline face appears to be a simple ruled patch between corresponding section edges.
The cleanup would examine the BSpline face boundary. In the observed failure cases, these faces have boundary patterns such as:
The non-BSpline edges are the original section/profile edges. The BSpline edges are longitudinal connector edges generated by OCCT. The proposed cleanup would keep the original section/profile edges, replace the longitudinal BSpline connector edges with straight connector edges, and then attempt to rebuild the face analytically.
Initial reconstruction cases:
If analytic reconstruction succeeds, replace the original BSpline face in the loft result. If reconstruction fails, leave the original BSpline face unchanged.
The fallback is therefore per-face, not all-or-nothing: unsupported or ambiguous BSpline faces remain as generated by
BRepOffsetAPI_ThruSections.Notes
This should not attempt to become a general BSpline-surface simplifier. The goal is a targeted fix for a common “draft-like loft between two planar profiles” case where the expected result is ruled and analytic.
This may also improve downstream operations such as offset() by avoiding unnecessary BSpline surfaces in simple lofts.