10C Inc 2: feature tail

119 of the app's 138 symbols now resolve; only the I/O modules and the Inc 4
tail are left. New: GCE2d, GCPnts, BRepFilletAPI, BRepOffsetAPI,
ShapeAnalysis, ShapeFix, ShapeUpgrade, BRepCheck, BRepTools, BRepLib,
BRepExtrema, BRepClass3d, IntCurvesFace, plus TopTools_HSequenceOfShape.

The fillet and chamfer builders derive BRepBuilderAPI_MakeShape, so their
history comes from the base bound in Inc 1 — which is what lets the app read a
blend's provenance the same way it reads a boolean's. The Inc 2 gate compares
that history exactly, alongside the splitter's, which is what sketch-region
attribution depends on.

Two shapes of deviation, both commented where they are bound:

- Trailing enum arguments are left off four constructors (fillet's
  ChFi3d_FilletShape, MakeThickSolidByJoin's mode/join pair, BRepExtrema's
  Extrema flags, and Inc 1's GeomAPI_ProjectPointOnSurf). An unregistered enum
  cannot serve as a default argument — nanobind converts defaults at binding
  time, so it fails the whole module's import with std::bad_cast. The app never
  overrides any of them, so OCCT's own defaults apply and behaviour is
  unchanged.
- ShapeAnalysis_FreeBounds.ConnectEdgesToWires reassigns a Handle& out
  parameter, which the caster cannot honour because it hands C++ a copy of the
  handle. The lambda splices the result into the sequence the caller passed, so
  the pass-empty-then-read shape the app uses still works. This is the one
  place upstream's holder semantics and ours differ observably.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DfriM8XUkn7uYf5Dwe2xo6
This commit is contained in:
2026-08-10 20:09:29 +02:00
parent 340315b6e9
commit 0ce43a94aa
20 changed files with 1261 additions and 1 deletions

View File

@@ -314,6 +314,92 @@ def _constructions() -> dict:
}
def _inc2_reference() -> dict:
"""Fillet, chamfer, splitter, healing and the query classes.
The fillet history is the point of this block: a blend's provenance is read
exactly like a boolean's, and it is what attributes the new faces back to
the feature that made them.
"""
from OCP.BRepAlgoAPI import BRepAlgoAPI_Splitter
from OCP.BRepBuilderAPI import BRepBuilderAPI_MakeEdge
from OCP.BRepClass3d import BRepClass3d_SolidClassifier
from OCP.BRepExtrema import BRepExtrema_DistShapeShape
from OCP.BRepFilletAPI import BRepFilletAPI_MakeChamfer, BRepFilletAPI_MakeFillet
from OCP.BRepTools import BRepTools
from OCP.ShapeUpgrade import ShapeUpgrade_UnifySameDomain
from OCP.TopAbs import TopAbs_EDGE
from OCP.TopoDS import TopoDS
from OCP.TopTools import TopTools_ListOfShape
box = _box()
edges = _sub_shapes(box, TopAbs_EDGE)
fillet = BRepFilletAPI_MakeFillet(box)
fillet.Add(2.0, TopoDS.Edge_s(edges[0]))
fillet.Build()
filleted = fillet.Shape()
# The edge must belong to the very shape the builder was given, not an
# equal one built separately — OCCT matches by identity.
chamfer_box = _box()
chamfer = BRepFilletAPI_MakeChamfer(chamfer_box)
chamfer.Add(1.5, TopoDS.Edge_s(_sub_shapes(chamfer_box, TopAbs_EDGE)[0]))
chamfer.Build()
chamfered = chamfer.Shape()
# A splitter over an edge, mirroring how sketch regions are cut apart.
splitter = BRepAlgoAPI_Splitter()
split_box = _box()
args, tools = TopTools_ListOfShape(), TopTools_ListOfShape()
args.Append(split_box)
tools.Append(
BRepPrimAPI_MakeBox(gp_Pnt(-5, 5, -5), 20.0, 1.0, 40.0).Shape()
)
splitter.SetArguments(args)
splitter.SetTools(tools)
splitter.Build()
unify = ShapeUpgrade_UnifySameDomain(_fused(), True, True, True)
unify.Build()
dist = BRepExtrema_DistShapeShape(
_box(), BRepPrimAPI_MakeBox(gp_Pnt(40, 0, 0), 5.0, 5.0, 5.0).Shape()
)
inside = BRepClass3d_SolidClassifier(box, gp_Pnt(5, 10, 15), 1e-7)
outside = BRepClass3d_SolidClassifier(box, gp_Pnt(50, 10, 15), 1e-7)
face0 = TopoDS.Face_s(_sub_shapes(box, TopAbs_FACE)[0])
return {
"fillet": {
"sha256": hashlib.sha256(write_bytes(filleted)).hexdigest(),
**_measure(filleted),
"history": _history(fillet, box, filleted),
},
"chamfer": {
"sha256": hashlib.sha256(write_bytes(chamfered)).hexdigest(),
**_measure(chamfered),
},
"splitter": {
**_measure(splitter.Shape()),
"history": _history(splitter, split_box, splitter.Shape()),
},
"unified": _measure(unify.Shape()),
"distance": {
"is_done": bool(dist.IsDone()),
"value": dist.Value(),
"n_solutions": dist.NbSolution(),
},
"classifier": {"inside": int(inside.State()), "outside": int(outside.State())},
"uv_bounds": list(BRepTools.UVBounds_s(face0)),
"outer_wire_edges": len(
_sub_shapes(BRepTools.OuterWire_s(face0), TopAbs_EDGE)
),
}
def _inc1_reference() -> dict:
op, base, _tool = _cut_cylinder_op()
return {
@@ -359,6 +445,7 @@ def main() -> int:
print(f"{name}: {len(data)} bytes, {manifest['shapes'][name]['faces']} faces")
manifest["inc1"] = _inc1_reference()
manifest["inc2"] = _inc2_reference()
(OUT / "manifest.json").write_text(json.dumps(manifest, indent=2) + "\n")
print(f"\nwrote {len(SHAPES)} fixtures + manifest to {OUT}")