From e5535fbc5cbae2e3b10454e2837d48530395b807 Mon Sep 17 00:00:00 2001 From: "YNP. Jayuda" Date: Thu, 27 Aug 2026 19:05:05 +0700 Subject: [PATCH] patch for DUCAD --- crates/opencascade-sys/build.rs | 1 + .../opencascade-sys/include/b_rep_adaptor.hxx | 33 ++ .../include/b_rep_algo_api.hxx | 298 ++++++++++++ .../include/b_rep_builder_api.hxx | 10 + .../include/b_rep_fillet_api.hxx | 16 + .../include/b_rep_offset_api.hxx | 132 +++++ .../include/b_rep_prim_api.hxx | 22 + .../include/bindings_common.hxx | 7 + crates/opencascade-sys/include/geom_abs.hxx | 1 + crates/opencascade-sys/include/gp.hxx | 46 ++ crates/opencascade-sys/src/b_rep_adaptor.rs | 21 + crates/opencascade-sys/src/b_rep_algo_api.rs | 56 +++ .../opencascade-sys/src/b_rep_builder_api.rs | 12 + .../opencascade-sys/src/b_rep_fillet_api.rs | 6 + .../opencascade-sys/src/b_rep_offset_api.rs | 63 +++ crates/opencascade-sys/src/b_rep_prim_api.rs | 9 + crates/opencascade-sys/src/geom_abs.rs | 17 + crates/opencascade-sys/src/gp.rs | 33 ++ crates/opencascade/Cargo.toml | 4 +- crates/opencascade/src/adhoc.rs | 118 +++++ crates/opencascade/src/lib.rs | 17 + crates/opencascade/src/primitives.rs | 8 + .../src/primitives/boolean_shape.rs | 6 +- crates/opencascade/src/primitives/edge.rs | 16 +- crates/opencascade/src/primitives/face.rs | 122 ++++- crates/opencascade/src/primitives/shape.rs | 460 ++++++++++++++++-- update-ducad.md | 167 +++++++ 27 files changed, 1640 insertions(+), 61 deletions(-) create mode 100644 crates/opencascade/src/adhoc.rs create mode 100644 update-ducad.md diff --git a/crates/opencascade-sys/build.rs b/crates/opencascade-sys/build.rs index b65c3137b..b54a2285c 100644 --- a/crates/opencascade-sys/build.rs +++ b/crates/opencascade-sys/build.rs @@ -108,6 +108,7 @@ fn main() { build .cpp(true) .flag_if_supported("-std=c++11") + .flag_if_supported("-Wno-deprecated-declarations") .define("_USE_MATH_DEFINES", "TRUE") .include(occt_config.include_dir) .include("include") diff --git a/crates/opencascade-sys/include/b_rep_adaptor.hxx b/crates/opencascade-sys/include/b_rep_adaptor.hxx index d99203099..2e9807d02 100644 --- a/crates/opencascade-sys/include/b_rep_adaptor.hxx +++ b/crates/opencascade-sys/include/b_rep_adaptor.hxx @@ -1,7 +1,40 @@ #include +#include #include +#include +#include #include +#include inline std::unique_ptr BRepAdaptor_Curve_value(const BRepAdaptor_Curve &curve, const Standard_Real U) { return std::unique_ptr(new gp_Pnt(curve.Value(U))); } + +inline std::unique_ptr BRepAdaptor_Surface_ctor(const TopoDS_Face &face, + bool restriction) { + return std::unique_ptr(new BRepAdaptor_Surface(face, restriction)); +} + +inline std::unique_ptr BRepAdaptor_Surface_cylinder(const BRepAdaptor_Surface &surface) { + try { + return std::unique_ptr(new gp_Cylinder(surface.Cylinder())); + } catch (const Standard_Failure &failure) { + rethrow_standard_failure_as_runtime_error(failure, "BRepAdaptor_Surface::Cylinder() failed: surface is not a cylinder"); + } +} + +inline std::unique_ptr BRepAdaptor_Surface_cone(const BRepAdaptor_Surface &surface) { + try { + return std::unique_ptr(new gp_Cone(surface.Cone())); + } catch (const Standard_Failure &failure) { + rethrow_standard_failure_as_runtime_error(failure, "BRepAdaptor_Surface::Cone() failed: surface is not a cone"); + } +} + +inline std::unique_ptr BRepAdaptor_Surface_sphere(const BRepAdaptor_Surface &surface) { + try { + return std::unique_ptr(new gp_Sphere(surface.Sphere())); + } catch (const Standard_Failure &failure) { + rethrow_standard_failure_as_runtime_error(failure, "BRepAdaptor_Surface::Sphere() failed: surface is not a sphere"); + } +} diff --git a/crates/opencascade-sys/include/b_rep_algo_api.hxx b/crates/opencascade-sys/include/b_rep_algo_api.hxx index efc06938d..aaec5fb18 100644 --- a/crates/opencascade-sys/include/b_rep_algo_api.hxx +++ b/crates/opencascade-sys/include/b_rep_algo_api.hxx @@ -3,4 +3,302 @@ #include #include #include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include #include +#include +#include +#include +#include +#include + +inline std::unique_ptr BRepAlgoAPI_Fuse_ctor_checked(const TopoDS_Shape &shape_1, + const TopoDS_Shape &shape_2) { + try { + return std::unique_ptr(new BRepAlgoAPI_Fuse(shape_1, shape_2)); + } catch (const Standard_Failure &failure) { + rethrow_standard_failure_as_runtime_error(failure, "BRepAlgoAPI_Fuse: fuse gagal (geometri tidak valid)"); + } +} + +inline const TopoDS_Shape &BRepAlgoAPI_Fuse_shape_checked(BRepAlgoAPI_Fuse &fuse_operation) { + try { + return fuse_operation.Shape(); + } catch (const Standard_Failure &failure) { + rethrow_standard_failure_as_runtime_error(failure, "BRepAlgoAPI_Fuse::Shape() failed: not done"); + } +} + +inline std::unique_ptr BRepAlgoAPI_Cut_ctor_checked(const TopoDS_Shape &shape_1, + const TopoDS_Shape &shape_2) { + try { + return std::unique_ptr(new BRepAlgoAPI_Cut(shape_1, shape_2)); + } catch (const Standard_Failure &failure) { + rethrow_standard_failure_as_runtime_error(failure, "BRepAlgoAPI_Cut: cut gagal (geometri tidak valid)"); + } +} + +inline const TopoDS_Shape &BRepAlgoAPI_Cut_shape_checked(BRepAlgoAPI_Cut &cut_operation) { + try { + return cut_operation.Shape(); + } catch (const Standard_Failure &failure) { + rethrow_standard_failure_as_runtime_error(failure, "BRepAlgoAPI_Cut::Shape() failed: not done"); + } +} + +inline std::unique_ptr BRepAlgoAPI_Common_ctor_checked(const TopoDS_Shape &shape_1, + const TopoDS_Shape &shape_2) { + try { + return std::unique_ptr(new BRepAlgoAPI_Common(shape_1, shape_2)); + } catch (const Standard_Failure &failure) { + rethrow_standard_failure_as_runtime_error(failure, "BRepAlgoAPI_Common: intersect gagal (geometri tidak valid)"); + } +} + +inline const TopoDS_Shape &BRepAlgoAPI_Common_shape_checked(BRepAlgoAPI_Common &common_operation) { + try { + return common_operation.Shape(); + } catch (const Standard_Failure &failure) { + rethrow_standard_failure_as_runtime_error(failure, "BRepAlgoAPI_Common::Shape() failed: not done"); + } +} + +inline std::unique_ptr> split_shape_with_plane( + const TopoDS_Shape &shape, + double px, double py, double pz, + double nx, double ny, double nz +) { + try { + Bnd_Box box; + BRepBndLib::Add(shape, box); + Standard_Real xmin = -1000.0, ymin = -1000.0, zmin = -1000.0; + Standard_Real xmax = 1000.0, ymax = 1000.0, zmax = 1000.0; + if (!box.IsVoid()) { + box.Get(xmin, ymin, zmin, xmax, ymax, zmax); + } + double dx = xmax - xmin; + double dy = ymax - ymin; + double dz = zmax - zmin; + double diag = std::sqrt(dx * dx + dy * dy + dz * dz); + double size = (diag > 1.0 ? diag * 4.0 : 5000.0); + + double cx = (xmin + xmax) * 0.5; + double cy = (ymin + ymax) * 0.5; + double cz = (zmin + zmax) * 0.5; + + gp_Dir dir(nx, ny, nz); + gp_Pnt p0(px, py, pz); + gp_Pnt center_3d(cx, cy, cz); + gp_Vec to_center(p0, center_3d); + double dist_along_normal = to_center.Dot(gp_Vec(dir)); + gp_Pnt proj_center = center_3d.Translated(-gp_Vec(dir) * dist_along_normal); + + gp_Pln centered_pln(proj_center, dir); + + BRepBuilderAPI_MakeFace mk_face(centered_pln, -size, size, -size, size); + if (!mk_face.IsDone()) { + throw std::runtime_error("Gagal membuat bidang pemotong (cutting plane)"); + } + TopoDS_Face cut_face = mk_face.Face(); + + TopTools_ListOfShape args; + args.Append(shape); + + TopTools_ListOfShape tools; + tools.Append(cut_face); + + BRepAlgoAPI_Splitter splitter; + splitter.SetArguments(args); + splitter.SetTools(tools); + splitter.Build(); + + if (!splitter.IsDone()) { + throw std::runtime_error("BRepAlgoAPI_Splitter gagal memotong objek"); + } + + TopoDS_Shape res = splitter.Shape(); + std::unique_ptr> solids(new std::vector()); + + for (TopExp_Explorer exp(res, TopAbs_SOLID); exp.More(); exp.Next()) { + solids->push_back(exp.Current()); + } + + if (solids->empty()) { + for (TopExp_Explorer exp(res, TopAbs_SHELL); exp.More(); exp.Next()) { + solids->push_back(exp.Current()); + } + } + if (solids->empty()) { + for (TopExp_Explorer exp(res, TopAbs_FACE); exp.More(); exp.Next()) { + solids->push_back(exp.Current()); + } + } + + return solids; + } catch (const Standard_Failure &failure) { + rethrow_standard_failure_as_runtime_error(failure, "Split shape gagal (OCCT error)"); + } +} + +inline std::unique_ptr> split_shape_with_tool( + const TopoDS_Shape &shape, + const TopoDS_Shape &tool_shape +) { + try { + TopTools_ListOfShape args; + args.Append(shape); + + TopTools_ListOfShape tools; + tools.Append(tool_shape); + + BRepAlgoAPI_Splitter splitter; + splitter.SetArguments(args); + splitter.SetTools(tools); + splitter.Build(); + + if (!splitter.IsDone()) { + throw std::runtime_error("BRepAlgoAPI_Splitter gagal memotong objek dengan tool"); + } + + TopoDS_Shape res = splitter.Shape(); + std::unique_ptr> solids(new std::vector()); + + for (TopExp_Explorer exp(res, TopAbs_SOLID); exp.More(); exp.Next()) { + solids->push_back(exp.Current()); + } + + if (solids->empty()) { + for (TopExp_Explorer exp(res, TopAbs_SHELL); exp.More(); exp.Next()) { + solids->push_back(exp.Current()); + } + } + if (solids->empty()) { + for (TopExp_Explorer exp(res, TopAbs_FACE); exp.More(); exp.Next()) { + solids->push_back(exp.Current()); + } + } + + return solids; + } catch (const Standard_Failure &failure) { + rethrow_standard_failure_as_runtime_error(failure, "Split shape gagal (OCCT error)"); + } +} + +inline std::unique_ptr split_faces_with_plane( + const TopoDS_Shape &shape, + double px, double py, double pz, + double nx, double ny, double nz +) { + try { + Bnd_Box box; + BRepBndLib::Add(shape, box); + Standard_Real xmin = -1000.0, ymin = -1000.0, zmin = -1000.0; + Standard_Real xmax = 1000.0, ymax = 1000.0, zmax = 1000.0; + if (!box.IsVoid()) { + box.Get(xmin, ymin, zmin, xmax, ymax, zmax); + } + double dx = xmax - xmin; + double dy = ymax - ymin; + double dz = zmax - zmin; + double diag = std::sqrt(dx * dx + dy * dy + dz * dz); + double size = (diag > 1.0 ? diag * 4.0 : 5000.0); + + double cx = (xmin + xmax) * 0.5; + double cy = (ymin + ymax) * 0.5; + double cz = (zmin + zmax) * 0.5; + + gp_Dir dir(nx, ny, nz); + gp_Pnt p0(px, py, pz); + gp_Pnt center_3d(cx, cy, cz); + gp_Vec to_center(p0, center_3d); + double dist_along_normal = to_center.Dot(gp_Vec(dir)); + gp_Pnt proj_center = center_3d.Translated(-gp_Vec(dir) * dist_along_normal); + + gp_Pln centered_pln(proj_center, dir); + + BRepBuilderAPI_MakeFace mk_face(centered_pln, -size, size, -size, size); + if (!mk_face.IsDone()) { + throw std::runtime_error("Gagal membuat bidang pemotong (cutting plane)"); + } + TopoDS_Face cut_face = mk_face.Face(); + + TopTools_ListOfShape args; + for (TopExp_Explorer exp(shape, TopAbs_FACE); exp.More(); exp.Next()) { + args.Append(exp.Current()); + } + + TopTools_ListOfShape tools; + tools.Append(cut_face); + + BRepAlgoAPI_Splitter splitter; + splitter.SetArguments(args); + splitter.SetTools(tools); + splitter.Build(); + + if (!splitter.IsDone()) { + throw std::runtime_error("BRepAlgoAPI_Splitter gagal membagi face"); + } + + TopoDS_Shape res = splitter.Shape(); + + BRepBuilderAPI_Sewing sewing(1.0e-5); + sewing.Add(res); + sewing.Perform(); + TopoDS_Shape sewed = sewing.SewedShape(); + + for (TopExp_Explorer exp(sewed, TopAbs_SHELL); exp.More(); exp.Next()) { + TopoDS_Shell shell = TopoDS::Shell(exp.Current()); + BRepBuilderAPI_MakeSolid mk_solid(shell); + if (mk_solid.IsDone()) { + return std::unique_ptr(new TopoDS_Shape(mk_solid.Solid())); + } + } + + return std::unique_ptr(new TopoDS_Shape(sewed)); + } catch (const Standard_Failure &failure) { + rethrow_standard_failure_as_runtime_error(failure, "Split face gagal (OCCT error)"); + } +} + +inline std::unique_ptr> section_shape_with_plane( + const TopoDS_Shape &shape, + double px, double py, double pz, + double nx, double ny, double nz +) { + try { + double len = std::sqrt(nx * nx + ny * ny + nz * nz); + if (len < 1e-7) { + throw std::runtime_error("Normal bidang potong tidak valid (panjang 0)"); + } + gp_Dir dir(nx / len, ny / len, nz / len); + gp_Pnt p0(px, py, pz); + gp_Pln pln(p0, dir); + + BRepAlgoAPI_Section section_op(shape, pln, Standard_True); + section_op.Build(); + if (!section_op.IsDone()) { + throw std::runtime_error("BRepAlgoAPI_Section gagal menghitung irisan bidang potong"); + } + + TopoDS_Shape res = section_op.Shape(); + std::unique_ptr> edges(new std::vector()); + + for (TopExp_Explorer exp(res, TopAbs_EDGE); exp.More(); exp.Next()) { + edges->push_back(exp.Current()); + } + + return edges; + } catch (const Standard_Failure &failure) { + rethrow_standard_failure_as_runtime_error(failure, "BRepAlgoAPI_Section gagal (OCCT error)"); + } +} diff --git a/crates/opencascade-sys/include/b_rep_builder_api.hxx b/crates/opencascade-sys/include/b_rep_builder_api.hxx index acb252010..0c2c7af6e 100644 --- a/crates/opencascade-sys/include/b_rep_builder_api.hxx +++ b/crates/opencascade-sys/include/b_rep_builder_api.hxx @@ -10,3 +10,13 @@ #include #include #include +#include +#include + +inline std::unique_ptr BRepBuilderAPI_MakeEdge_elips(const gp_Elips &elips) { + return std::unique_ptr(new BRepBuilderAPI_MakeEdge(elips)); +} + +inline std::unique_ptr BRepBuilderAPI_MakeFace_pln(const gp_Pln &pln, double umin, double umax, double vmin, double vmax) { + return std::unique_ptr(new BRepBuilderAPI_MakeFace(pln, umin, umax, vmin, vmax)); +} diff --git a/crates/opencascade-sys/include/b_rep_fillet_api.hxx b/crates/opencascade-sys/include/b_rep_fillet_api.hxx index da66eefbb..2e55eb60c 100644 --- a/crates/opencascade-sys/include/b_rep_fillet_api.hxx +++ b/crates/opencascade-sys/include/b_rep_fillet_api.hxx @@ -22,3 +22,19 @@ BRepFilletAPI_MakeFillet2d_add_chamfer_angle(BRepFilletAPI_MakeFillet2d &make_fi const Standard_Real angle) { return std::unique_ptr(new TopoDS_Edge(make_fillet.AddChamfer(edge, vertex, dist, angle))); } + +inline const TopoDS_Shape &BRepFilletAPI_MakeFillet_shape_checked(BRepFilletAPI_MakeFillet &make_fillet) { + try { + return make_fillet.Shape(); + } catch (const Standard_Failure &failure) { + rethrow_standard_failure_as_runtime_error(failure, "BRepFilletAPI_MakeFillet::Shape() failed: not done"); + } +} + +inline const TopoDS_Shape &BRepFilletAPI_MakeChamfer_shape_checked(BRepFilletAPI_MakeChamfer &make_chamfer) { + try { + return make_chamfer.Shape(); + } catch (const Standard_Failure &failure) { + rethrow_standard_failure_as_runtime_error(failure, "BRepFilletAPI_MakeChamfer::Shape() failed: not done"); + } +} diff --git a/crates/opencascade-sys/include/b_rep_offset_api.hxx b/crates/opencascade-sys/include/b_rep_offset_api.hxx index 73d716bf2..dd9f30435 100644 --- a/crates/opencascade-sys/include/b_rep_offset_api.hxx +++ b/crates/opencascade-sys/include/b_rep_offset_api.hxx @@ -1,9 +1,141 @@ +#include #include #include #include #include #include +#include #include #include +#include #include +#include #include +#include +#include +#include + +inline std::unique_ptr BRepOffset_MakeOffset_ctor() { + return std::unique_ptr(new BRepOffset_MakeOffset()); +} + +inline void BRepOffset_MakeOffset_Initialize(BRepOffset_MakeOffset &make_offset, + const TopoDS_Shape &shape, Standard_Real offset, + Standard_Real tolerance, BRepOffset_Mode mode, + bool intersection, bool self_inter, GeomAbs_JoinType join, + bool thickening, bool remove_int_edges) { + try { + make_offset.Initialize(shape, offset, tolerance, mode, intersection, self_inter, join, thickening, + remove_int_edges); + } catch (const Standard_Failure &failure) { + rethrow_standard_failure_as_runtime_error(failure, "BRepOffset_MakeOffset::Initialize() failed"); + } +} + +inline void BRepOffset_MakeOffset_AddFace(BRepOffset_MakeOffset &make_offset, + const TopoDS_Face &face) { + try { + make_offset.AddFace(face); + } catch (const Standard_Failure &failure) { + rethrow_standard_failure_as_runtime_error(failure, "BRepOffset_MakeOffset::AddFace() failed"); + } +} + +inline void BRepOffset_MakeOffset_SetOffsetOnFace(BRepOffset_MakeOffset &make_offset, + const TopoDS_Face &face, Standard_Real offset) { + try { + make_offset.SetOffsetOnFace(face, offset); + } catch (const Standard_Failure &failure) { + rethrow_standard_failure_as_runtime_error(failure, "BRepOffset_MakeOffset::SetOffsetOnFace() failed"); + } +} + +inline void BRepOffset_MakeOffset_MakeOffsetShape(BRepOffset_MakeOffset &make_offset) { + try { + make_offset.MakeOffsetShape(); + } catch (const Standard_Failure &failure) { + rethrow_standard_failure_as_runtime_error(failure, "BRepOffset_MakeOffset::MakeOffsetShape() failed"); + } +} + +inline const TopoDS_Shape &BRepOffset_MakeOffset_Shape(const BRepOffset_MakeOffset &make_offset) { + try { + return make_offset.Shape(); + } catch (const Standard_Failure &failure) { + rethrow_standard_failure_as_runtime_error(failure, "BRepOffset_MakeOffset::Shape() failed: not done"); + } +} + +inline const TopoDS_Shape &BRepOffsetAPI_MakeThickSolid_shape_checked(BRepOffsetAPI_MakeThickSolid &make_thick_solid) { + try { + if (!make_thick_solid.IsDone()) { + throw std::runtime_error("BRepOffsetAPI_MakeThickSolid::Shape() failed: not done"); + } + return make_thick_solid.Shape(); + } catch (const Standard_Failure &failure) { + rethrow_standard_failure_as_runtime_error(failure, "BRepOffsetAPI_MakeThickSolid::Shape() failed: not done"); + } +} + +inline std::unique_ptr BRepOffsetAPI_MakePipe_ctor_checked(const TopoDS_Wire &spine, + const TopoDS_Shape &profile) { + try { + return std::unique_ptr(new BRepOffsetAPI_MakePipe(spine, profile)); + } catch (const Standard_Failure &failure) { + rethrow_standard_failure_as_runtime_error(failure, "BRepOffsetAPI_MakePipe: konstruksi sweep gagal (profil atau jalur kurva tidak valid)"); + } +} + +inline const TopoDS_Shape &BRepOffsetAPI_MakePipe_shape_checked(BRepOffsetAPI_MakePipe &make_pipe) { + try { + if (!make_pipe.IsDone()) { + throw std::runtime_error("BRepOffsetAPI_MakePipe::Shape() gagal: operasi sweep tidak selesai (jalur kurva atau profil bermasalah)"); + } + return make_pipe.Shape(); + } catch (const Standard_Failure &failure) { + rethrow_standard_failure_as_runtime_error(failure, "BRepOffsetAPI_MakePipe::Shape() gagal: not done"); + } +} + +inline std::unique_ptr BRepOffsetAPI_DraftAngle_ctor(const TopoDS_Shape &shape) { + try { + return std::unique_ptr(new BRepOffsetAPI_DraftAngle(shape)); + } catch (const Standard_Failure &failure) { + rethrow_standard_failure_as_runtime_error(failure, "BRepOffsetAPI_DraftAngle: konstruksi gagal (shape tidak valid)"); + } +} + +inline void BRepOffsetAPI_DraftAngle_Add(BRepOffsetAPI_DraftAngle &draft, + const TopoDS_Face &face, + const gp_Dir &pull_dir, + Standard_Real angle_rad, + const gp_Pln &neutral_plane) { + try { + draft.Add(face, pull_dir, angle_rad, neutral_plane); + } catch (const Standard_Failure &failure) { + rethrow_standard_failure_as_runtime_error(failure, "BRepOffsetAPI_DraftAngle::Add() gagal (face bukan planar atau bidang netral tidak valid)"); + } +} + +inline void BRepOffsetAPI_DraftAngle_Build(BRepOffsetAPI_DraftAngle &draft) { + try { + draft.Build(); + } catch (const Standard_Failure &failure) { + rethrow_standard_failure_as_runtime_error(failure, "BRepOffsetAPI_DraftAngle::Build() gagal (sudut terlalu besar atau geometri face tidak kompatibel)"); + } +} + +inline bool BRepOffsetAPI_DraftAngle_IsDone(const BRepOffsetAPI_DraftAngle &draft) { + return draft.IsDone(); +} + +inline const TopoDS_Shape &BRepOffsetAPI_DraftAngle_shape_checked(BRepOffsetAPI_DraftAngle &draft) { + try { + if (!draft.IsDone()) { + throw std::runtime_error("BRepOffsetAPI_DraftAngle::Shape() gagal: operasi draft tidak selesai"); + } + return draft.Shape(); + } catch (const Standard_Failure &failure) { + rethrow_standard_failure_as_runtime_error(failure, "BRepOffsetAPI_DraftAngle::Shape() gagal: not done"); + } +} diff --git a/crates/opencascade-sys/include/b_rep_prim_api.hxx b/crates/opencascade-sys/include/b_rep_prim_api.hxx index 4870eac5b..9a28687e6 100644 --- a/crates/opencascade-sys/include/b_rep_prim_api.hxx +++ b/crates/opencascade-sys/include/b_rep_prim_api.hxx @@ -6,3 +6,25 @@ #include #include #include + +inline std::unique_ptr BRepPrimAPI_MakeRevol_ctor_checked(const TopoDS_Shape &shape, + const gp_Ax1 &axis, + double angle, + bool copy) { + try { + return std::unique_ptr(new BRepPrimAPI_MakeRevol(shape, axis, angle, copy)); + } catch (const Standard_Failure &failure) { + rethrow_standard_failure_as_runtime_error(failure, "BRepPrimAPI_MakeRevol: konstruksi revolve gagal (sumbu atau geometri profil tidak valid)"); + } +} + +inline const TopoDS_Shape &BRepPrimAPI_MakeRevol_shape_checked(BRepPrimAPI_MakeRevol &make_revol) { + try { + if (!make_revol.IsDone()) { + throw std::runtime_error("BRepPrimAPI_MakeRevol::Shape() gagal: operasi revolve tidak selesai (sumbu memotong profil atau profil tidak tertutup)"); + } + return make_revol.Shape(); + } catch (const Standard_Failure &failure) { + rethrow_standard_failure_as_runtime_error(failure, "BRepPrimAPI_MakeRevol::Shape() gagal: not done"); + } +} diff --git a/crates/opencascade-sys/include/bindings_common.hxx b/crates/opencascade-sys/include/bindings_common.hxx index b4813171a..a52ee360a 100644 --- a/crates/opencascade-sys/include/bindings_common.hxx +++ b/crates/opencascade-sys/include/bindings_common.hxx @@ -1,8 +1,15 @@ #pragma once #include "rust/cxx.h" #include +#include #include +[[noreturn]] inline void rethrow_standard_failure_as_runtime_error(const Standard_Failure &failure, + const char *fallback_message) { + const char *message = failure.GetMessageString(); + throw std::runtime_error((message != nullptr && message[0] != '\0') ? message : fallback_message); +} + // Generic template constructor template std::unique_ptr construct_unique(Args... args) { return std::unique_ptr(new T(args...)); diff --git a/crates/opencascade-sys/include/geom_abs.hxx b/crates/opencascade-sys/include/geom_abs.hxx index 1fcb25c7d..91a74912d 100644 --- a/crates/opencascade-sys/include/geom_abs.hxx +++ b/crates/opencascade-sys/include/geom_abs.hxx @@ -1,3 +1,4 @@ #include #include +#include #include diff --git a/crates/opencascade-sys/include/gp.hxx b/crates/opencascade-sys/include/gp.hxx index 80bc2de75..63220dbcf 100644 --- a/crates/opencascade-sys/include/gp.hxx +++ b/crates/opencascade-sys/include/gp.hxx @@ -3,8 +3,54 @@ #include #include #include +#include +#include +#include #include #include +#include #include #include #include + +inline std::unique_ptr gp_Ax2_new_with_x_dir(const gp_Pnt &origin, const gp_Dir &main_dir, const gp_Dir &x_dir) { + return std::unique_ptr(new gp_Ax2(origin, main_dir, x_dir)); +} + +inline std::unique_ptr gp_Pln_ctor_point_and_dir(const gp_Pnt &point, const gp_Dir &dir) { + return std::unique_ptr(new gp_Pln(point, dir)); +} + +inline std::unique_ptr gp_Elips_ctor(const gp_Ax2 &axis, Standard_Real major_radius, Standard_Real minor_radius) { + return std::unique_ptr(new gp_Elips(axis, major_radius, minor_radius)); +} + +inline std::unique_ptr gp_Cylinder_location(const gp_Cylinder &cylinder) { + return std::unique_ptr(new gp_Pnt(cylinder.Location())); +} + +inline std::unique_ptr gp_Cylinder_direction(const gp_Cylinder &cylinder) { + return std::unique_ptr(new gp_Dir(cylinder.Axis().Direction())); +} + +inline double gp_Cylinder_radius(const gp_Cylinder &cylinder) { return cylinder.Radius(); } + +inline std::unique_ptr gp_Cone_location(const gp_Cone &cone) { + return std::unique_ptr(new gp_Pnt(cone.Location())); +} + +inline std::unique_ptr gp_Cone_direction(const gp_Cone &cone) { + return std::unique_ptr(new gp_Dir(cone.Axis().Direction())); +} + +inline double gp_Cone_radius(const gp_Cone &cone) { return cone.RefRadius(); } + +inline double gp_Cone_semi_angle(const gp_Cone &cone) { return cone.SemiAngle(); } + +#include + +inline std::unique_ptr gp_Sphere_location(const gp_Sphere &sphere) { + return std::unique_ptr(new gp_Pnt(sphere.Location())); +} + +inline double gp_Sphere_radius(const gp_Sphere &sphere) { return sphere.Radius(); } diff --git a/crates/opencascade-sys/src/b_rep_adaptor.rs b/crates/opencascade-sys/src/b_rep_adaptor.rs index ee4dba123..2cbf0d225 100644 --- a/crates/opencascade-sys/src/b_rep_adaptor.rs +++ b/crates/opencascade-sys/src/b_rep_adaptor.rs @@ -6,8 +6,13 @@ mod inner { include!("opencascade-sys/include/b_rep_adaptor.hxx"); type gp_Pnt = crate::gp::gp_Pnt; + type gp_Cylinder = crate::gp::gp_Cylinder; + type gp_Cone = crate::gp::gp_Cone; + type gp_Sphere = crate::gp::gp_Sphere; type GeomAbs_CurveType = crate::geom_abs::GeomAbs_CurveType; + type GeomAbs_SurfaceType = crate::geom_abs::GeomAbs_SurfaceType; type TopoDS_Edge = crate::topo_ds::TopoDS_Edge; + type TopoDS_Face = crate::topo_ds::TopoDS_Face; type BRepAdaptor_Curve; #[cxx_name = "construct_unique"] @@ -16,5 +21,21 @@ mod inner { pub fn LastParameter(self: &BRepAdaptor_Curve) -> f64; pub fn BRepAdaptor_Curve_value(curve: &BRepAdaptor_Curve, u: f64) -> UniquePtr; pub fn GetType(self: &BRepAdaptor_Curve) -> GeomAbs_CurveType; + + type BRepAdaptor_Surface; + pub fn BRepAdaptor_Surface_ctor( + face: &TopoDS_Face, + restriction: bool, + ) -> UniquePtr; + pub fn GetType(self: &BRepAdaptor_Surface) -> GeomAbs_SurfaceType; + pub fn BRepAdaptor_Surface_cylinder( + surface: &BRepAdaptor_Surface, + ) -> Result>; + pub fn BRepAdaptor_Surface_cone( + surface: &BRepAdaptor_Surface, + ) -> Result>; + pub fn BRepAdaptor_Surface_sphere( + surface: &BRepAdaptor_Surface, + ) -> Result>; } } diff --git a/crates/opencascade-sys/src/b_rep_algo_api.rs b/crates/opencascade-sys/src/b_rep_algo_api.rs index 6cf56d10c..d051ade3c 100644 --- a/crates/opencascade-sys/src/b_rep_algo_api.rs +++ b/crates/opencascade-sys/src/b_rep_algo_api.rs @@ -19,7 +19,14 @@ mod inner { shape_1: &TopoDS_Shape, shape_2: &TopoDS_Shape, ) -> UniquePtr; + pub fn BRepAlgoAPI_Fuse_ctor_checked( + shape_1: &TopoDS_Shape, + shape_2: &TopoDS_Shape, + ) -> Result>; pub fn Shape(self: Pin<&mut BRepAlgoAPI_Fuse>) -> &TopoDS_Shape; + pub fn BRepAlgoAPI_Fuse_shape_checked( + fuse_operation: Pin<&mut BRepAlgoAPI_Fuse>, + ) -> Result<&TopoDS_Shape>; pub fn Build(self: Pin<&mut BRepAlgoAPI_Fuse>, progress: &Message_ProgressRange); pub fn IsDone(self: &BRepAlgoAPI_Fuse) -> bool; pub fn SectionEdges(self: Pin<&mut BRepAlgoAPI_Fuse>) -> &TopTools_ListOfShape; @@ -31,7 +38,14 @@ mod inner { shape_1: &TopoDS_Shape, shape_2: &TopoDS_Shape, ) -> UniquePtr; + pub fn BRepAlgoAPI_Cut_ctor_checked( + shape_1: &TopoDS_Shape, + shape_2: &TopoDS_Shape, + ) -> Result>; pub fn Shape(self: Pin<&mut BRepAlgoAPI_Cut>) -> &TopoDS_Shape; + pub fn BRepAlgoAPI_Cut_shape_checked( + cut_operation: Pin<&mut BRepAlgoAPI_Cut>, + ) -> Result<&TopoDS_Shape>; pub fn Build(self: Pin<&mut BRepAlgoAPI_Cut>, progress: &Message_ProgressRange); pub fn IsDone(self: &BRepAlgoAPI_Cut) -> bool; pub fn Generated<'a>( @@ -46,7 +60,14 @@ mod inner { shape_1: &TopoDS_Shape, shape_2: &TopoDS_Shape, ) -> UniquePtr; + pub fn BRepAlgoAPI_Common_ctor_checked( + shape_1: &TopoDS_Shape, + shape_2: &TopoDS_Shape, + ) -> Result>; pub fn Shape(self: Pin<&mut BRepAlgoAPI_Common>) -> &TopoDS_Shape; + pub fn BRepAlgoAPI_Common_shape_checked( + common_operation: Pin<&mut BRepAlgoAPI_Common>, + ) -> Result<&TopoDS_Shape>; pub fn Build(self: Pin<&mut BRepAlgoAPI_Common>, progress: &Message_ProgressRange); pub fn IsDone(self: &BRepAlgoAPI_Common) -> bool; pub fn SectionEdges(self: Pin<&mut BRepAlgoAPI_Common>) -> &TopTools_ListOfShape; @@ -61,5 +82,40 @@ mod inner { pub fn Build(self: Pin<&mut BRepAlgoAPI_Section>, progress: &Message_ProgressRange); pub fn IsDone(self: &BRepAlgoAPI_Section) -> bool; pub fn SectionEdges(self: Pin<&mut BRepAlgoAPI_Section>) -> &TopTools_ListOfShape; + + pub fn split_shape_with_plane( + shape: &TopoDS_Shape, + px: f64, + py: f64, + pz: f64, + nx: f64, + ny: f64, + nz: f64, + ) -> Result>>; + + pub fn split_shape_with_tool( + shape: &TopoDS_Shape, + tool_shape: &TopoDS_Shape, + ) -> Result>>; + + pub fn split_faces_with_plane( + shape: &TopoDS_Shape, + px: f64, + py: f64, + pz: f64, + nx: f64, + ny: f64, + nz: f64, + ) -> Result>; + + pub fn section_shape_with_plane( + shape: &TopoDS_Shape, + px: f64, + py: f64, + pz: f64, + nx: f64, + ny: f64, + nz: f64, + ) -> Result>>; } } diff --git a/crates/opencascade-sys/src/b_rep_builder_api.rs b/crates/opencascade-sys/src/b_rep_builder_api.rs index 4b42cac9e..a5d05f578 100644 --- a/crates/opencascade-sys/src/b_rep_builder_api.rs +++ b/crates/opencascade-sys/src/b_rep_builder_api.rs @@ -7,6 +7,8 @@ mod inner { type gp_Pnt = crate::gp::gp_Pnt; type gp_Circ = crate::gp::gp_Circ; + type gp_Elips = crate::gp::gp_Elips; + type gp_Pln = crate::gp::gp_Pln; type gp_Trsf = crate::gp::gp_Trsf; type gp_GTrsf = crate::gp::gp_GTrsf; type Message_ProgressRange = crate::message::Message_ProgressRange; @@ -37,6 +39,9 @@ mod inner { pub fn BRepBuilderAPI_MakeEdge_circle( circle: &gp_Circ, ) -> UniquePtr; + pub fn BRepBuilderAPI_MakeEdge_elips( + elips: &gp_Elips, + ) -> UniquePtr; #[cxx_name = "construct_unique"] pub fn BRepBuilderAPI_MakeEdge_gp_Pnt_gp_Pnt( p1: &gp_Pnt, @@ -86,6 +91,13 @@ mod inner { surface: &Handle_Geom_Surface, edge_tolerance: f64, ) -> UniquePtr; + pub fn BRepBuilderAPI_MakeFace_pln( + pln: &gp_Pln, + umin: f64, + umax: f64, + vmin: f64, + vmax: f64, + ) -> UniquePtr; pub fn Face(self: &BRepBuilderAPI_MakeFace) -> &TopoDS_Face; pub fn Shape(self: Pin<&mut BRepBuilderAPI_MakeFace>) -> &TopoDS_Shape; pub fn Build(self: Pin<&mut BRepBuilderAPI_MakeFace>, progress: &Message_ProgressRange); diff --git a/crates/opencascade-sys/src/b_rep_fillet_api.rs b/crates/opencascade-sys/src/b_rep_fillet_api.rs index e23fcc512..45e427fad 100644 --- a/crates/opencascade-sys/src/b_rep_fillet_api.rs +++ b/crates/opencascade-sys/src/b_rep_fillet_api.rs @@ -26,6 +26,9 @@ mod inner { edge: &TopoDS_Edge, ); pub fn Shape(self: Pin<&mut BRepFilletAPI_MakeFillet>) -> &TopoDS_Shape; + pub fn BRepFilletAPI_MakeFillet_shape_checked( + make_fillet: Pin<&mut BRepFilletAPI_MakeFillet>, + ) -> Result<&TopoDS_Shape>; pub fn Build(self: Pin<&mut BRepFilletAPI_MakeFillet>, progress: &Message_ProgressRange); pub fn IsDone(self: &BRepFilletAPI_MakeFillet) -> bool; @@ -65,6 +68,9 @@ mod inner { #[rust_name = "add_edge"] pub fn Add(self: Pin<&mut BRepFilletAPI_MakeChamfer>, distance: f64, edge: &TopoDS_Edge); pub fn Shape(self: Pin<&mut BRepFilletAPI_MakeChamfer>) -> &TopoDS_Shape; + pub fn BRepFilletAPI_MakeChamfer_shape_checked( + make_chamfer: Pin<&mut BRepFilletAPI_MakeChamfer>, + ) -> Result<&TopoDS_Shape>; pub fn Build(self: Pin<&mut BRepFilletAPI_MakeChamfer>, progress: &Message_ProgressRange); pub fn IsDone(self: &BRepFilletAPI_MakeChamfer) -> bool; } diff --git a/crates/opencascade-sys/src/b_rep_offset_api.rs b/crates/opencascade-sys/src/b_rep_offset_api.rs index 6c560866b..d6ada8c3a 100644 --- a/crates/opencascade-sys/src/b_rep_offset_api.rs +++ b/crates/opencascade-sys/src/b_rep_offset_api.rs @@ -20,9 +20,43 @@ mod inner { type TopTools_ListOfShape = crate::top_tools::TopTools_ListOfShape; type Message_ProgressRange = crate::message::Message_ProgressRange; type Handle_Law_Function = crate::law::Handle_Law_Function; + type gp_Pnt = crate::gp::gp_Pnt; + type gp_Dir = crate::gp::gp_Dir; + type gp_Pln = crate::gp::gp_Pln; type BRepOffset_Mode; + type BRepOffset_MakeOffset; + pub fn BRepOffset_MakeOffset_ctor() -> UniquePtr; + #[allow(clippy::too_many_arguments)] + pub fn BRepOffset_MakeOffset_Initialize( + make_offset: Pin<&mut BRepOffset_MakeOffset>, + shape: &TopoDS_Shape, + offset: f64, + tolerance: f64, + mode: BRepOffset_Mode, + intersection: bool, + self_inter: bool, + join: GeomAbs_JoinType, + thickening: bool, + remove_int_edges: bool, + ) -> Result<()>; + pub fn BRepOffset_MakeOffset_AddFace( + make_offset: Pin<&mut BRepOffset_MakeOffset>, + face: &TopoDS_Face, + ) -> Result<()>; + pub fn BRepOffset_MakeOffset_SetOffsetOnFace( + make_offset: Pin<&mut BRepOffset_MakeOffset>, + face: &TopoDS_Face, + offset: f64, + ) -> Result<()>; + pub fn BRepOffset_MakeOffset_MakeOffsetShape( + make_offset: Pin<&mut BRepOffset_MakeOffset>, + ) -> Result<()>; + pub fn BRepOffset_MakeOffset_Shape( + make_offset: &BRepOffset_MakeOffset, + ) -> Result<&TopoDS_Shape>; + type BRepOffsetAPI_MakeOffset; #[cxx_name = "construct_unique"] pub fn BRepOffsetAPI_MakeOffset_face_new( @@ -57,6 +91,9 @@ mod inner { progress: &Message_ProgressRange, ); pub fn Shape(self: Pin<&mut BRepOffsetAPI_MakeThickSolid>) -> &TopoDS_Shape; + pub fn BRepOffsetAPI_MakeThickSolid_shape_checked( + make_thick_solid: Pin<&mut BRepOffsetAPI_MakeThickSolid>, + ) -> Result<&TopoDS_Shape>; pub fn Build( self: Pin<&mut BRepOffsetAPI_MakeThickSolid>, progress: &Message_ProgressRange, @@ -69,7 +106,14 @@ mod inner { spine: &TopoDS_Wire, profile: &TopoDS_Shape, ) -> UniquePtr; + pub fn BRepOffsetAPI_MakePipe_ctor_checked( + spine: &TopoDS_Wire, + profile: &TopoDS_Shape, + ) -> Result>; pub fn Shape(self: Pin<&mut BRepOffsetAPI_MakePipe>) -> &TopoDS_Shape; + pub fn BRepOffsetAPI_MakePipe_shape_checked( + make_pipe: Pin<&mut BRepOffsetAPI_MakePipe>, + ) -> Result<&TopoDS_Shape>; type BRepOffsetAPI_MakePipeShell; #[cxx_name = "construct_unique"] @@ -104,5 +148,24 @@ mod inner { pub fn Shape(self: Pin<&mut BRepOffsetAPI_ThruSections>) -> &TopoDS_Shape; pub fn Build(self: Pin<&mut BRepOffsetAPI_ThruSections>, progress: &Message_ProgressRange); pub fn IsDone(self: &BRepOffsetAPI_ThruSections) -> bool; + + type BRepOffsetAPI_DraftAngle; + pub fn BRepOffsetAPI_DraftAngle_ctor( + shape: &TopoDS_Shape, + ) -> Result>; + pub fn BRepOffsetAPI_DraftAngle_Add( + draft: Pin<&mut BRepOffsetAPI_DraftAngle>, + face: &TopoDS_Face, + pull_dir: &gp_Dir, + angle_rad: f64, + neutral_plane: &gp_Pln, + ) -> Result<()>; + pub fn BRepOffsetAPI_DraftAngle_Build( + draft: Pin<&mut BRepOffsetAPI_DraftAngle>, + ) -> Result<()>; + pub fn BRepOffsetAPI_DraftAngle_IsDone(draft: &BRepOffsetAPI_DraftAngle) -> bool; + pub fn BRepOffsetAPI_DraftAngle_shape_checked( + draft: Pin<&mut BRepOffsetAPI_DraftAngle>, + ) -> Result<&TopoDS_Shape>; } } diff --git a/crates/opencascade-sys/src/b_rep_prim_api.rs b/crates/opencascade-sys/src/b_rep_prim_api.rs index 291008424..83bd2083e 100644 --- a/crates/opencascade-sys/src/b_rep_prim_api.rs +++ b/crates/opencascade-sys/src/b_rep_prim_api.rs @@ -32,7 +32,16 @@ mod inner { angle: f64, copy: bool, ) -> UniquePtr; + pub fn BRepPrimAPI_MakeRevol_ctor_checked( + shape: &TopoDS_Shape, + axis: &gp_Ax1, + angle: f64, + copy: bool, + ) -> Result>; pub fn Shape(self: Pin<&mut BRepPrimAPI_MakeRevol>) -> &TopoDS_Shape; + pub fn BRepPrimAPI_MakeRevol_shape_checked( + make_revol: Pin<&mut BRepPrimAPI_MakeRevol>, + ) -> Result<&TopoDS_Shape>; pub fn Build(self: Pin<&mut BRepPrimAPI_MakeRevol>, progress: &Message_ProgressRange); pub fn IsDone(self: &BRepPrimAPI_MakeRevol) -> bool; diff --git a/crates/opencascade-sys/src/geom_abs.rs b/crates/opencascade-sys/src/geom_abs.rs index 0839f04a1..325838301 100644 --- a/crates/opencascade-sys/src/geom_abs.rs +++ b/crates/opencascade-sys/src/geom_abs.rs @@ -24,10 +24,27 @@ mod inner { GeomAbs_Intersection, } + #[repr(u32)] + #[derive(Debug)] + pub enum GeomAbs_SurfaceType { + GeomAbs_Plane, + GeomAbs_Cylinder, + GeomAbs_Cone, + GeomAbs_Sphere, + GeomAbs_Torus, + GeomAbs_BezierSurface, + GeomAbs_BSplineSurface, + GeomAbs_SurfaceOfRevolution, + GeomAbs_SurfaceOfExtrusion, + GeomAbs_OffsetSurface, + GeomAbs_OtherSurface, + } + unsafe extern "C++" { include!("opencascade-sys/include/geom_abs.hxx"); type GeomAbs_CurveType; type GeomAbs_JoinType; + type GeomAbs_SurfaceType; } } diff --git a/crates/opencascade-sys/src/gp.rs b/crates/opencascade-sys/src/gp.rs index 92fd3eb2a..601a1bad7 100644 --- a/crates/opencascade-sys/src/gp.rs +++ b/crates/opencascade-sys/src/gp.rs @@ -53,6 +53,11 @@ mod inner { type gp_Ax2; #[cxx_name = "construct_unique"] pub fn gp_Ax2_new(origin: &gp_Pnt, main_dir: &gp_Dir) -> UniquePtr; + pub fn gp_Ax2_new_with_x_dir( + origin: &gp_Pnt, + main_dir: &gp_Dir, + x_dir: &gp_Dir, + ) -> UniquePtr; type gp_Ax3; #[cxx_name = "construct_unique"] @@ -91,5 +96,33 @@ mod inner { pub fn new_gp_GTrsf() -> UniquePtr; pub fn SetValue(self: Pin<&mut gp_GTrsf>, the_row: i32, the_col: i32, the_value: f64); pub fn Value(self: &gp_GTrsf, the_row: i32, the_col: i32) -> f64; + + type gp_Pln; + pub fn gp_Pln_ctor_point_and_dir(point: &gp_Pnt, dir: &gp_Dir) -> UniquePtr; + + type gp_Elips; + pub fn gp_Elips_ctor( + axis: &gp_Ax2, + major_radius: f64, + minor_radius: f64, + ) -> UniquePtr; + + type gp_Cylinder; + pub fn gp_Cylinder_location(cylinder: &gp_Cylinder) -> UniquePtr; + pub fn gp_Cylinder_direction(cylinder: &gp_Cylinder) -> UniquePtr; + pub fn gp_Cylinder_radius(cylinder: &gp_Cylinder) -> f64; + + type gp_Cone; + pub fn gp_Cone_location(cone: &gp_Cone) -> UniquePtr; + pub fn gp_Cone_direction(cone: &gp_Cone) -> UniquePtr; + pub fn gp_Cone_radius(cone: &gp_Cone) -> f64; + pub fn gp_Cone_semi_angle(cone: &gp_Cone) -> f64; + type gp_Sphere; + pub fn gp_Sphere_location(sphere: &gp_Sphere) -> UniquePtr; + pub fn gp_Sphere_radius(sphere: &gp_Sphere) -> f64; } + + impl UniquePtr {} + impl UniquePtr {} + impl UniquePtr {} } diff --git a/crates/opencascade/Cargo.toml b/crates/opencascade/Cargo.toml index fa19fcf1a..13b5ce028 100644 --- a/crates/opencascade/Cargo.toml +++ b/crates/opencascade/Cargo.toml @@ -10,9 +10,9 @@ repository = "https://github.com/bschwind/opencascade-rs" [dependencies] cxx = "1" opencascade-sys = { version = "0.3", path = "../opencascade-sys" } -glam = { version = "0.24", features = ["bytemuck"] } +glam = { version = "0.33", features = ["bytemuck"] } opencascade-kicad-parser = { version = "0.3", path = "../kicad-parser" } -thiserror = "1" +thiserror = "2" [features] default = ["builtin"] diff --git a/crates/opencascade/src/adhoc.rs b/crates/opencascade/src/adhoc.rs new file mode 100644 index 000000000..e7c89fd4f --- /dev/null +++ b/crates/opencascade/src/adhoc.rs @@ -0,0 +1,118 @@ +use crate::primitives::Shape; +use cxx::UniquePtr; +use glam::DVec3; +use opencascade_sys as ffi; +use std::ops::{Deref, DerefMut}; + +/// Wrapper for the [`Shape`] struct that provides an "ad-hoc" API. +pub struct AdHocShape(pub Shape); + +impl Deref for AdHocShape { + type Target = Shape; + + fn deref(&self) -> &Shape { + &self.0 + } +} + +impl DerefMut for AdHocShape { + fn deref_mut(&mut self) -> &mut Shape { + &mut self.0 + } +} + +impl AdHocShape { + /// Internal helper to create [Self] from the FFI inner type. + fn from_inner(inner: UniquePtr) -> Self { + Self(Shape { inner }) + } + + /// Make a box with a corner at (0,0,0) and with size (x,y,z) + pub fn make_box(x: f64, y: f64, z: f64) -> Self { + let point = ffi::gp::new_point(0.0, 0.0, 0.0); + let mut my_box = ffi::b_rep_prim_api::BRepPrimAPI_MakeBox_new(&point, x, y, z); + let inner = ffi::topo_ds::TopoDS_Shape_to_owned(my_box.pin_mut().Shape()); + + Self::from_inner(inner) + } + + /// Make a box with one corner at p1, and the opposite corner at p2. + pub fn make_box_point_point(p1: DVec3, p2: DVec3) -> Self { + let min_corner = p1.min(p2); + let max_corner = p1.max(p2); + + let point = ffi::gp::new_point(min_corner.x, min_corner.y, min_corner.z); + let diff = max_corner - min_corner; + let mut my_box = + ffi::b_rep_prim_api::BRepPrimAPI_MakeBox_new(&point, diff.x, diff.y, diff.z); + let inner = ffi::topo_ds::TopoDS_Shape_to_owned(my_box.pin_mut().Shape()); + + Self::from_inner(inner) + } + + /// Make a cylinder with its bottom at point p, with radius r and height h. + pub fn make_cylinder(p: DVec3, r: f64, h: f64) -> Self { + let point = ffi::gp::new_point(p.x, p.y, p.z); + let cylinder_axis = ffi::gp::gp::DZ(); + let cylinder_coord_system = ffi::gp::gp_Ax2_new(&point, cylinder_axis); + + let mut cylinder = + ffi::b_rep_prim_api::BRepPrimAPI_MakeCylinder_new(&cylinder_coord_system, r, h); + let inner = ffi::topo_ds::TopoDS_Shape_to_owned(cylinder.pin_mut().Shape()); + + Self::from_inner(inner) + } + + /// Make a sphere of radius r, centered at the origin. + pub fn make_sphere(r: f64) -> Self { + let point = ffi::gp::new_point(0.0, 0.0, 0.0); + let axis = ffi::gp::gp_Ax2_new(&point, ffi::gp::gp::DZ()); + let mut sphere = ffi::b_rep_prim_api::BRepPrimAPI_MakeSphere_new(&axis, r, 360.0); + let inner = ffi::topo_ds::TopoDS_Shape_to_owned(sphere.pin_mut().Shape()); + + Self::from_inner(inner) + } + + pub fn fillet_edges(&mut self, radius: f64) { + let _ = self.0.fillet_edges(radius, self.0.edges()); + } + + pub fn chamfer_edges(&mut self, distance: f64) { + let _ = self.0.chamfer_edges(distance, self.0.edges()); + } + + pub fn subtract(&mut self, other: &Shape) -> Result<(), crate::Error> { + let mut cut_operation = + ffi::b_rep_algo_api::BRepAlgoAPI_Cut_ctor_checked(&self.inner, &other.inner) + .map_err(|e| crate::Error::BooleanOpFailed(e.what().to_string()))?; + + let cut_shape = ffi::b_rep_algo_api::BRepAlgoAPI_Cut_shape_checked(cut_operation.pin_mut()) + .map_err(|e| crate::Error::BooleanOpFailed(e.what().to_string()))?; + self.inner = ffi::topo_ds::TopoDS_Shape_to_owned(cut_shape); + Ok(()) + } + + pub fn union(&mut self, other: &Shape) -> Result<(), crate::Error> { + let mut fuse_operation = + ffi::b_rep_algo_api::BRepAlgoAPI_Fuse_ctor_checked(&self.inner, &other.inner) + .map_err(|e| crate::Error::BooleanOpFailed(e.what().to_string()))?; + + let fuse_shape = + ffi::b_rep_algo_api::BRepAlgoAPI_Fuse_shape_checked(fuse_operation.pin_mut()) + .map_err(|e| crate::Error::BooleanOpFailed(e.what().to_string()))?; + self.inner = ffi::topo_ds::TopoDS_Shape_to_owned(fuse_shape); + Ok(()) + } + + pub fn intersect(&mut self, other: &Shape) -> Result<(), crate::Error> { + let mut common_operation = + ffi::b_rep_algo_api::BRepAlgoAPI_Common_ctor_checked(&self.inner, &other.inner) + .map_err(|e| crate::Error::BooleanOpFailed(e.what().to_string()))?; + + let common_shape = + ffi::b_rep_algo_api::BRepAlgoAPI_Common_shape_checked(common_operation.pin_mut()) + .map_err(|e| crate::Error::BooleanOpFailed(e.what().to_string()))?; + self.inner = ffi::topo_ds::TopoDS_Shape_to_owned(common_shape); + Ok(()) + } +} diff --git a/crates/opencascade/src/lib.rs b/crates/opencascade/src/lib.rs index 3e6f171ad..13ce96fc6 100644 --- a/crates/opencascade/src/lib.rs +++ b/crates/opencascade/src/lib.rs @@ -1,5 +1,6 @@ use thiserror::Error; +pub mod adhoc; pub mod angle; pub mod bounding_box; pub mod kicad; @@ -39,4 +40,20 @@ pub enum Error { UntriangulatedFace, #[error("at least 2 points are required for creating a wire")] NotEnoughPoints, + #[error("BRepOffset_MakeOffset gagal: {0}")] + OffsetOnFaceFailed(String), + #[error("BRepFilletAPI_MakeFillet/MakeChamfer gagal: {0}")] + FilletFailed(String), + #[error("BRepAlgoAPI_Fuse/Cut/Common gagal: {0}")] + BooleanOpFailed(String), + #[error("BRepPrimAPI_MakeRevol gagal: {0}")] + RevolveFailed(String), + #[error("BRepOffsetAPI_MakeThickSolid gagal: {0}")] + HollowFailed(String), + #[error("BRepOffsetAPI_MakePipe gagal: {0}")] + PipeFailed(String), + #[error("BRepOffsetAPI_DraftAngle gagal: {0}")] + DraftAngleFailed(String), + #[error("BRepAlgoAPI_Splitter gagal: {0}")] + SplitFailed(String), } diff --git a/crates/opencascade/src/primitives.rs b/crates/opencascade/src/primitives.rs index 4b3ef9194..c8c12d5c1 100644 --- a/crates/opencascade/src/primitives.rs +++ b/crates/opencascade/src/primitives.rs @@ -113,6 +113,14 @@ pub fn make_axis_2(origin: DVec3, dir: DVec3) -> UniquePtr { ffi::gp::gp_Ax2_new(&make_point(origin), &make_dir(dir)) } +pub fn make_axis_2_with_x_dir( + origin: DVec3, + dir: DVec3, + x_dir: DVec3, +) -> UniquePtr { + ffi::gp::gp_Ax2_new_with_x_dir(&make_point(origin), &make_dir(dir), &make_dir(x_dir)) +} + pub struct EdgeIterator { explorer: UniquePtr, } diff --git a/crates/opencascade/src/primitives/boolean_shape.rs b/crates/opencascade/src/primitives/boolean_shape.rs index 35a6e3ba6..04eaecfb7 100644 --- a/crates/opencascade/src/primitives/boolean_shape.rs +++ b/crates/opencascade/src/primitives/boolean_shape.rs @@ -27,8 +27,7 @@ impl BooleanShape { self.new_edges.iter() } - #[must_use] - pub fn fillet_new_edges(&self, radius: f64) -> Shape { + pub fn fillet_new_edges(&mut self, radius: f64) -> Result<(), crate::Error> { self.shape.fillet_edges(radius, &self.new_edges) } @@ -40,8 +39,7 @@ impl BooleanShape { self.shape.variable_fillet_edges(radius_values, &self.new_edges) } - #[must_use] - pub fn chamfer_new_edges(&self, distance: f64) -> Shape { + pub fn chamfer_new_edges(&mut self, distance: f64) -> Result<(), crate::Error> { self.shape.chamfer_edges(distance, &self.new_edges) } } diff --git a/crates/opencascade/src/primitives/edge.rs b/crates/opencascade/src/primitives/edge.rs index 7e6f5001e..89d894716 100644 --- a/crates/opencascade/src/primitives/edge.rs +++ b/crates/opencascade/src/primitives/edge.rs @@ -1,5 +1,5 @@ use super::make_vec; -use crate::primitives::{make_axis_2, make_point}; +use crate::primitives::{make_axis_2, make_axis_2_with_x_dir, make_point}; use cxx::UniquePtr; use glam::{dvec3, DVec3}; use opencascade_sys as ffi; @@ -95,7 +95,19 @@ impl Edge { Self::from_make_edge(make_edge) } - pub fn ellipse() {} + pub fn ellipse( + center: DVec3, + normal: DVec3, + x_dir: DVec3, + major_radius: f64, + minor_radius: f64, + ) -> Self { + let axis = make_axis_2_with_x_dir(center, normal, x_dir); + let make_ellipse = ffi::gp::gp_Elips_ctor(&axis, major_radius, minor_radius); + let make_edge = ffi::b_rep_builder_api::BRepBuilderAPI_MakeEdge_elips(&make_ellipse); + + Self::from_make_edge(make_edge) + } pub fn spline_from_points( points: impl IntoIterator, diff --git a/crates/opencascade/src/primitives/face.rs b/crates/opencascade/src/primitives/face.rs index 051fb4fcc..38ad613f4 100644 --- a/crates/opencascade/src/primitives/face.rs +++ b/crates/opencascade/src/primitives/face.rs @@ -123,20 +123,101 @@ impl Face { Shape::from_shape(make_prism.pin_mut().Shape()) } - #[must_use] - pub fn revolve(&self, origin: DVec3, axis: DVec3, angle: Option) -> Solid { + pub fn try_revolve( + &self, + origin: DVec3, + axis: DVec3, + angle: Option, + ) -> Result { let revol_vec = make_axis_1(origin, axis); let angle = angle.map(Angle::radians).unwrap_or(std::f64::consts::PI * 2.0); let copy = false; let inner_shape = ffi::topo_ds::cast_face_to_shape(&self.inner); - let mut make_solid = - ffi::b_rep_prim_api::BRepPrimAPI_MakeRevol_new(inner_shape, &revol_vec, angle, copy); - let revolved_shape = make_solid.pin_mut().Shape(); + let mut make_solid = ffi::b_rep_prim_api::BRepPrimAPI_MakeRevol_ctor_checked( + inner_shape, + &revol_vec, + angle, + copy, + ) + .map_err(|e| crate::Error::RevolveFailed(e.to_string()))?; + let revolved_shape = + ffi::b_rep_prim_api::BRepPrimAPI_MakeRevol_shape_checked(make_solid.pin_mut()) + .map_err(|e| crate::Error::RevolveFailed(e.to_string()))?; let solid = ffi::topo_ds::TopoDS::Solid(revolved_shape); - Solid::from_solid(solid) + Ok(Solid::from_solid(solid)) + } + + #[must_use] + pub fn revolve(&self, origin: DVec3, axis: DVec3, angle: Option) -> Solid { + self.try_revolve(origin, axis, angle) + .expect("Face::revolve failed (use try_revolve for safe error handling)") + } + + /// Menghasilkan bentuk 3D solid baru dengan menyapu (sweep / pipe) wajah (face) ini di sepanjang kurva jalur (spine wire). + pub fn pipe(&self, spine: &Wire) -> Result { + let profile_shape = Shape::from_shape(ffi::topo_ds::cast_face_to_shape(&self.inner)); + Shape::pipe(spine, &profile_shape) + } + + pub fn surface_kind(&self) -> String { + let surface = ffi::b_rep::BRep_Tool_Surface(&self.inner); + let dynamic_type = ffi::geom::DynamicType(&surface); + + ffi::standard::type_name(dynamic_type) + } + + pub fn cylinder_or_cone_radius(&self) -> Option { + let adaptor = ffi::b_rep_adaptor::BRepAdaptor_Surface_ctor(&self.inner, true); + + if let Ok(cylinder) = ffi::b_rep_adaptor::BRepAdaptor_Surface_cylinder(&adaptor) { + return Some(ffi::gp::gp_Cylinder_radius(&cylinder)); + } + if let Ok(cone) = ffi::b_rep_adaptor::BRepAdaptor_Surface_cone(&adaptor) { + return Some(ffi::gp::gp_Cone_radius(&cone)); + } + None + } + + pub fn cylinder_or_cone_axis(&self) -> Option<(DVec3, DVec3)> { + let adaptor = ffi::b_rep_adaptor::BRepAdaptor_Surface_ctor(&self.inner, true); + + if let Ok(cylinder) = ffi::b_rep_adaptor::BRepAdaptor_Surface_cylinder(&adaptor) { + let location = ffi::gp::gp_Cylinder_location(&cylinder); + let direction = ffi::gp::gp_Cylinder_direction(&cylinder); + return Some(( + dvec3(location.X(), location.Y(), location.Z()), + dvec3(direction.X(), direction.Y(), direction.Z()), + )); + } + if let Ok(cone) = ffi::b_rep_adaptor::BRepAdaptor_Surface_cone(&adaptor) { + let location = ffi::gp::gp_Cone_location(&cone); + let direction = ffi::gp::gp_Cone_direction(&cone); + return Some(( + dvec3(location.X(), location.Y(), location.Z()), + dvec3(direction.X(), direction.Y(), direction.Z()), + )); + } + None + } + + pub fn sphere_center(&self) -> Option { + let adaptor = ffi::b_rep_adaptor::BRepAdaptor_Surface_ctor(&self.inner, true); + if let Ok(sphere) = ffi::b_rep_adaptor::BRepAdaptor_Surface_sphere(&adaptor) { + let location = ffi::gp::gp_Sphere_location(&sphere); + return Some(dvec3(location.X(), location.Y(), location.Z())); + } + None + } + + pub fn sphere_radius(&self) -> Option { + let adaptor = ffi::b_rep_adaptor::BRepAdaptor_Surface_ctor(&self.inner, true); + if let Ok(sphere) = ffi::b_rep_adaptor::BRepAdaptor_Surface_sphere(&adaptor) { + return Some(ffi::gp::gp_Sphere_radius(&sphere)); + } + None } /// Fillets the face edges by a given radius at each vertex @@ -462,8 +543,12 @@ impl CompoundFace { Shape::from_shape(extruded_shape) } - #[must_use] - pub fn revolve(&self, origin: DVec3, axis: DVec3, angle: Option) -> Shape { + pub fn try_revolve( + &self, + origin: DVec3, + axis: DVec3, + angle: Option, + ) -> Result { let revol_axis = make_axis_1(origin, axis); let angle = angle.map(Angle::radians).unwrap_or(std::f64::consts::PI * 2.0); @@ -471,11 +556,24 @@ impl CompoundFace { let inner_shape = ffi::topo_ds::cast_compound_to_shape(&self.inner); - let mut make_solid = - ffi::b_rep_prim_api::BRepPrimAPI_MakeRevol_new(inner_shape, &revol_axis, angle, copy); - let revolved_shape = make_solid.pin_mut().Shape(); + let mut make_solid = ffi::b_rep_prim_api::BRepPrimAPI_MakeRevol_ctor_checked( + inner_shape, + &revol_axis, + angle, + copy, + ) + .map_err(|e| crate::Error::RevolveFailed(e.to_string()))?; + let revolved_shape = + ffi::b_rep_prim_api::BRepPrimAPI_MakeRevol_shape_checked(make_solid.pin_mut()) + .map_err(|e| crate::Error::RevolveFailed(e.to_string()))?; + + Ok(Shape::from_shape(revolved_shape)) + } - Shape::from_shape(revolved_shape) + #[must_use] + pub fn revolve(&self, origin: DVec3, axis: DVec3, angle: Option) -> Shape { + self.try_revolve(origin, axis, angle) + .expect("CompoundFace::revolve failed (use try_revolve for safe error handling)") } #[must_use] diff --git a/crates/opencascade/src/primitives/shape.rs b/crates/opencascade/src/primitives/shape.rs index 158fdb904..126726bd2 100644 --- a/crates/opencascade/src/primitives/shape.rs +++ b/crates/opencascade/src/primitives/shape.rs @@ -447,9 +447,15 @@ impl Shape { self.inner.ShapeType().into() } - #[must_use] - pub fn fillet_edge(&self, radius: f64, edge: &Edge) -> Self { - self.fillet_edges(radius, [edge]) + pub fn fillet_edge(&mut self, radius: f64, edge: &Edge) -> Result<(), crate::Error> { + let mut make_fillet = ffi::b_rep_fillet_api::BRepFilletAPI_MakeFillet_new(&self.inner); + make_fillet.pin_mut().add_edge(radius, &edge.inner); + + let filleted_shape = ffi::b_rep_fillet_api::BRepFilletAPI_MakeFillet_shape_checked(make_fillet.pin_mut()) + .map_err(|e| crate::Error::FilletFailed(e.to_string()))?; + + self.inner = ffi::topo_ds::TopoDS_Shape_to_owned(filleted_shape); + Ok(()) } #[must_use] @@ -461,24 +467,58 @@ impl Shape { self.variable_fillet_edges(radius_values, [edge]) } - #[must_use] - pub fn chamfer_edge(&self, distance: f64, edge: &Edge) -> Self { - self.chamfer_edges(distance, [edge]) + pub fn chamfer_edge(&mut self, distance: f64, edge: &Edge) -> Result<(), crate::Error> { + let mut make_chamfer = ffi::b_rep_fillet_api::BRepFilletAPI_MakeChamfer_new(&self.inner); + make_chamfer.pin_mut().add_edge(distance, &edge.inner); + + let chamfered_shape = ffi::b_rep_fillet_api::BRepFilletAPI_MakeChamfer_shape_checked(make_chamfer.pin_mut()) + .map_err(|e| crate::Error::FilletFailed(e.to_string()))?; + + self.inner = ffi::topo_ds::TopoDS_Shape_to_owned(chamfered_shape); + Ok(()) } - #[must_use] pub fn fillet_edges>( - &self, + &mut self, radius: f64, edges: impl IntoIterator, - ) -> Self { + ) -> Result<(), crate::Error> { let mut make_fillet = ffi::b_rep_fillet_api::BRepFilletAPI_MakeFillet_new(&self.inner); for edge in edges.into_iter() { make_fillet.pin_mut().add_edge(radius, &edge.as_ref().inner); } - Self::from_shape(make_fillet.pin_mut().Shape()) + let filleted_shape = ffi::b_rep_fillet_api::BRepFilletAPI_MakeFillet_shape_checked(make_fillet.pin_mut()) + .map_err(|e| crate::Error::FilletFailed(e.to_string()))?; + + self.inner = ffi::topo_ds::TopoDS_Shape_to_owned(filleted_shape); + Ok(()) + } + + pub fn fillet_edges_variable>( + &mut self, + radius_start: f64, + radius_end: f64, + edges: impl IntoIterator, + ) -> Result<(), crate::Error> { + let mut array = ffi::t_col_gp::TColgp_Array1OfPnt2d_new(1, 2); + array.pin_mut().SetValue(1, &make_point2d(dvec2(0.0, radius_start))); + array.pin_mut().SetValue(2, &make_point2d(dvec2(1.0, radius_end))); + + let mut make_fillet = ffi::b_rep_fillet_api::BRepFilletAPI_MakeFillet_new(&self.inner); + + for edge in edges.into_iter() { + make_fillet + .pin_mut() + .variable_add_edge(&array, &edge.as_ref().inner); + } + + let filleted_shape = ffi::b_rep_fillet_api::BRepFilletAPI_MakeFillet_shape_checked(make_fillet.pin_mut()) + .map_err(|e| crate::Error::FilletFailed(e.to_string()))?; + + self.inner = ffi::topo_ds::TopoDS_Shape_to_owned(filleted_shape); + Ok(()) } #[must_use] @@ -503,36 +543,38 @@ impl Shape { Self::from_shape(make_fillet.pin_mut().Shape()) } - #[must_use] pub fn chamfer_edges>( - &self, + &mut self, distance: f64, edges: impl IntoIterator, - ) -> Self { + ) -> Result<(), crate::Error> { let mut make_chamfer = ffi::b_rep_fillet_api::BRepFilletAPI_MakeChamfer_new(&self.inner); for edge in edges.into_iter() { make_chamfer.pin_mut().add_edge(distance, &edge.as_ref().inner); } - Self::from_shape(make_chamfer.pin_mut().Shape()) + let chamfered_shape = ffi::b_rep_fillet_api::BRepFilletAPI_MakeChamfer_shape_checked(make_chamfer.pin_mut()) + .map_err(|e| crate::Error::FilletFailed(e.to_string()))?; + + self.inner = ffi::topo_ds::TopoDS_Shape_to_owned(chamfered_shape); + Ok(()) } /// Performs fillet of `radius` on all edges of the shape - #[must_use] - pub fn fillet(&self, radius: f64) -> Self { + pub fn fillet(&mut self, radius: f64) -> Result<(), crate::Error> { self.fillet_edges(radius, self.edges()) } /// Performs chamfer of `distance` on all edges of the shape - #[must_use] - pub fn chamfer(&self, distance: f64) -> Self { + pub fn chamfer(&mut self, distance: f64) -> Result<(), crate::Error> { self.chamfer_edges(distance, self.edges()) } - #[must_use] - pub fn subtract(&self, other: &Shape) -> BooleanShape { - let mut cut_operation = ffi::b_rep_algo_api::BRepAlgoAPI_Cut_new(&self.inner, &other.inner); + pub fn subtract(&self, other: &Shape) -> Result { + let mut cut_operation = + ffi::b_rep_algo_api::BRepAlgoAPI_Cut_ctor_checked(&self.inner, &other.inner) + .map_err(|e| crate::Error::BooleanOpFailed(e.to_string()))?; let edge_list = cut_operation.pin_mut().SectionEdges(); let vec = ffi::topo_ds::shape_list_to_vector(edge_list); @@ -543,9 +585,11 @@ impl Shape { new_edges.push(Edge::from_edge(edge)); } - let shape = Self::from_shape(cut_operation.pin_mut().Shape()); + let cut_shape = ffi::b_rep_algo_api::BRepAlgoAPI_Cut_shape_checked(cut_operation.pin_mut()) + .map_err(|e| crate::Error::BooleanOpFailed(e.to_string()))?; + let shape = Self::from_shape(cut_shape); - BooleanShape { shape, new_edges } + Ok(BooleanShape { shape, new_edges }) } pub fn read_step(path: impl AsRef) -> Result { @@ -688,10 +732,10 @@ impl Shape { } } - #[must_use] - pub fn union(&self, other: &Shape) -> BooleanShape { + pub fn union(&self, other: &Shape) -> Result { let mut fuse_operation = - ffi::b_rep_algo_api::BRepAlgoAPI_Fuse_new(&self.inner, &other.inner); + ffi::b_rep_algo_api::BRepAlgoAPI_Fuse_ctor_checked(&self.inner, &other.inner) + .map_err(|e| crate::Error::BooleanOpFailed(e.to_string()))?; let edge_list = fuse_operation.pin_mut().SectionEdges(); let vec = ffi::topo_ds::shape_list_to_vector(edge_list); @@ -701,16 +745,18 @@ impl Shape { new_edges.push(Edge::from_edge(edge)); } - let shape = Self::from_shape(fuse_operation.pin_mut().Shape()); + let fuse_shape = ffi::b_rep_algo_api::BRepAlgoAPI_Fuse_shape_checked(fuse_operation.pin_mut()) + .map_err(|e| crate::Error::BooleanOpFailed(e.to_string()))?; + let shape = Self::from_shape(fuse_shape); - BooleanShape { shape, new_edges } + Ok(BooleanShape { shape, new_edges }) } - #[must_use] - pub fn intersect(&self, other: &Shape) -> BooleanShape { - let mut fuse_operation = - ffi::b_rep_algo_api::BRepAlgoAPI_Common_new(&self.inner, &other.inner); - let edge_list = fuse_operation.pin_mut().SectionEdges(); + pub fn intersect(&self, other: &Shape) -> Result { + let mut common_operation = + ffi::b_rep_algo_api::BRepAlgoAPI_Common_ctor_checked(&self.inner, &other.inner) + .map_err(|e| crate::Error::BooleanOpFailed(e.to_string()))?; + let edge_list = common_operation.pin_mut().SectionEdges(); let vec = ffi::topo_ds::shape_list_to_vector(edge_list); let mut new_edges = vec![]; @@ -719,9 +765,11 @@ impl Shape { new_edges.push(Edge::from_edge(edge)); } - let shape = Self::from_shape(fuse_operation.pin_mut().Shape()); + let common_shape = ffi::b_rep_algo_api::BRepAlgoAPI_Common_shape_checked(common_operation.pin_mut()) + .map_err(|e| crate::Error::BooleanOpFailed(e.to_string()))?; + let shape = Self::from_shape(common_shape); - BooleanShape { shape, new_edges } + Ok(BooleanShape { shape, new_edges }) } pub fn write_stl>(&self, path: P) -> Result<(), Error> { @@ -767,6 +815,45 @@ impl Shape { self.inner.pin_mut().set_global_translation(&location, false); } + pub fn scale(&mut self, pivot: DVec3, factor: f64) { + let point = make_point(pivot); + let mut transform = ffi::gp::new_transform(); + transform.pin_mut().SetScale(&point, factor); + + let mut transform_builder = + ffi::b_rep_builder_api::BRepBuilderAPI_Transform_new(&self.inner, &transform, true); + transform_builder.pin_mut().Build(&ffi::message::Message_ProgressRange_new()); + let transformed_shape = transform_builder.pin_mut().Shape(); + self.inner = ffi::topo_ds::TopoDS_Shape_to_owned(transformed_shape); + } + + pub fn rotate(&mut self, pivot: DVec3, axis: DVec3, angle_rad: f64) { + let axis_1 = make_axis_1(pivot, axis); + let mut transform = ffi::gp::new_transform(); + transform.pin_mut().SetRotation(&axis_1, angle_rad); + + let mut transform_builder = + ffi::b_rep_builder_api::BRepBuilderAPI_Transform_new(&self.inner, &transform, true); + transform_builder.pin_mut().Build(&ffi::message::Message_ProgressRange_new()); + let transformed_shape = transform_builder.pin_mut().Shape(); + self.inner = ffi::topo_ds::TopoDS_Shape_to_owned(transformed_shape); + } + + pub fn volume(&self) -> f64 { + let mut props = ffi::g_prop::GProps_new(); + let only_closed = false; + let skip_shared = false; + let use_triangulation = false; + ffi::b_rep_g_prop::BRepGProp::VolumeProperties( + &self.inner, + props.pin_mut(), + only_closed, + skip_shared, + use_triangulation, + ); + props.Mass() + } + pub fn mesh(&self) -> Result { self.mesh_with_tolerance(0.01) } @@ -792,10 +879,49 @@ impl Shape { FaceIterator { explorer } } - // TODO(bschwind) - Convert the return type to an iterator. + pub fn faces_along_ray(&self, ray_start: DVec3, ray_dir: DVec3) -> Vec<(Face, DVec3)> { + self.faces_along_ray_with_tolerance(ray_start, ray_dir, 0.0001) + } + + pub fn faces_along_ray_with_tolerance( + &self, + ray_start: DVec3, + ray_dir: DVec3, + tolerance: f64, + ) -> Vec<(Face, DVec3)> { + let mut intersector = ffi::b_rep_int_curve_surface::BRepIntCurveSurface_Inter_new(); + intersector.pin_mut().Init( + &self.inner, + &ffi::gp::gp_Lin_new(&make_point(ray_start), &make_dir(ray_dir)), + tolerance, + ); + + let mut results = vec![]; + + while intersector.More() { + let face = ffi::b_rep_int_curve_surface::BRepIntCurveSurface_Inter_face(&intersector); + let face = Face::from_face(&face); + let point = ffi::b_rep_int_curve_surface::BRepIntCurveSurface_Inter_point(&intersector); + + results.push((face, dvec3(point.X(), point.Y(), point.Z()))); + + intersector.pin_mut().Next(); + } + + results + } + pub fn faces_along_line(&self, line_origin: DVec3, line_dir: DVec3) -> Vec { + self.faces_along_line_with_tolerance(line_origin, line_dir, 0.0001) + } + + pub fn faces_along_line_with_tolerance( + &self, + line_origin: DVec3, + line_dir: DVec3, + tolerance: f64, + ) -> Vec { let mut intersector = ffi::b_rep_int_curve_surface::BRepIntCurveSurface_Inter_new(); - let tolerance = 0.0001; intersector.pin_mut().Init( &self.inner, &ffi::gp::gp_Lin_new(&make_point(line_origin), &make_dir(line_dir)), @@ -868,12 +994,11 @@ impl Shape { }) } - #[must_use] - pub fn hollow>( + pub fn try_hollow>( &self, offset: f64, faces_to_remove: impl IntoIterator, - ) -> Self { + ) -> Result { let mut faces_list = ffi::top_tools::new_list_of_shape(); for face in faces_to_remove.into_iter() { @@ -901,8 +1026,75 @@ impl Shape { remove_intersecting_edges, &ffi::message::Message_ProgressRange_new(), ); + solid_maker.pin_mut().Build(&ffi::message::Message_ProgressRange_new()); + + let hollowed_shape = + ffi::b_rep_offset_api::BRepOffsetAPI_MakeThickSolid_shape_checked(solid_maker.pin_mut()) + .map_err(|e| Error::HollowFailed(e.to_string()))?; + let res = Self::from_shape(hollowed_shape); + if res.faces().count() == 0 { + return Err(Error::HollowFailed("Operasi hollow menghasilkan bentuk kosong (tidak ada face tersisa)".to_string())); + } + Ok(res) + } - Self::from_shape(solid_maker.pin_mut().Shape()) + pub fn try_hollow_variable, U: AsRef>( + &self, + default_offset: f64, + faces_to_remove: impl IntoIterator, + variable_faces: impl IntoIterator, + ) -> Result { + const OFFSET_TOLERANCE: f64 = 1e-3; + let to_error = |e: cxx::Exception| Error::HollowFailed(e.what().to_string()); + + let mut make_offset = ffi::b_rep_offset_api::BRepOffset_MakeOffset_ctor(); + ffi::b_rep_offset_api::BRepOffset_MakeOffset_Initialize( + make_offset.pin_mut(), + &self.inner, + default_offset, + OFFSET_TOLERANCE, + ffi::b_rep_offset_api::BRepOffset_Mode::BRepOffset_Skin, + true, + false, + ffi::geom_abs::GeomAbs_JoinType::GeomAbs_Arc, + true, + false, + ) + .map_err(to_error)?; + + for face in faces_to_remove { + ffi::b_rep_offset_api::BRepOffset_MakeOffset_AddFace( + make_offset.pin_mut(), + &face.as_ref().inner, + ) + .map_err(to_error)?; + } + + for (face, offset) in variable_faces { + ffi::b_rep_offset_api::BRepOffset_MakeOffset_SetOffsetOnFace( + make_offset.pin_mut(), + &face.as_ref().inner, + offset, + ) + .map_err(to_error)?; + } + + ffi::b_rep_offset_api::BRepOffset_MakeOffset_MakeOffsetShape(make_offset.pin_mut()) + .map_err(to_error)?; + + let result_shape = + ffi::b_rep_offset_api::BRepOffset_MakeOffset_Shape(&make_offset).map_err(to_error)?; + Ok(Self::from_shape(result_shape)) + } + + #[must_use] + pub fn hollow>( + &self, + offset: f64, + faces_to_remove: impl IntoIterator, + ) -> Self { + self.try_hollow(offset, faces_to_remove) + .unwrap_or_else(|e| panic!("Failed to hollow shape: {e}")) } #[must_use] @@ -911,6 +1103,192 @@ impl Shape { self.hollow(offset, faces_to_remove) } + pub fn offset_on_face(&self, face: &Face, offset: f64) -> Result { + const OFFSET_TOLERANCE: f64 = 1e-4; + + let to_error = |e: cxx::Exception| crate::Error::OffsetOnFaceFailed(e.what().to_string()); + + let mut make_offset = ffi::b_rep_offset_api::BRepOffset_MakeOffset_ctor(); + ffi::b_rep_offset_api::BRepOffset_MakeOffset_Initialize( + make_offset.pin_mut(), + &self.inner, + 0.0, + OFFSET_TOLERANCE, + ffi::b_rep_offset_api::BRepOffset_Mode::BRepOffset_Skin, + true, + false, + ffi::geom_abs::GeomAbs_JoinType::GeomAbs_Intersection, + false, + false, + ) + .map_err(to_error)?; + + ffi::b_rep_offset_api::BRepOffset_MakeOffset_SetOffsetOnFace( + make_offset.pin_mut(), + &face.inner, + offset, + ) + .map_err(to_error)?; + + ffi::b_rep_offset_api::BRepOffset_MakeOffset_MakeOffsetShape(make_offset.pin_mut()) + .map_err(to_error)?; + + let result_shape = + ffi::b_rep_offset_api::BRepOffset_MakeOffset_Shape(&make_offset).map_err(to_error)?; + Ok(Self::from_shape(result_shape)) + } + + pub fn pipe(spine: &Wire, profile: &Shape) -> Result { + let mut make_pipe = + ffi::b_rep_offset_api::BRepOffsetAPI_MakePipe_ctor_checked(&spine.inner, &profile.inner) + .map_err(|e| crate::Error::PipeFailed(e.what().to_string()))?; + + let result_shape = + ffi::b_rep_offset_api::BRepOffsetAPI_MakePipe_shape_checked(make_pipe.pin_mut()) + .map_err(|e| crate::Error::PipeFailed(e.what().to_string()))?; + + Ok(Self::from_shape(result_shape)) + } + + pub fn draft_angle( + &self, + neutral_plane_point: DVec3, + neutral_plane_normal: DVec3, + pull_direction: DVec3, + angle_deg: f64, + faces: &[&Face], + ) -> Result { + let to_error = |e: cxx::Exception| crate::Error::DraftAngleFailed(e.what().to_string()); + + let angle_rad = angle_deg.to_radians(); + + let pull_dir = make_dir(pull_direction); + let np_point = make_point(neutral_plane_point); + let np_normal = make_dir(neutral_plane_normal); + let neutral_plane = ffi::gp::gp_Pln_ctor_point_and_dir(&np_point, &np_normal); + + let mut draft = + ffi::b_rep_offset_api::BRepOffsetAPI_DraftAngle_ctor(&self.inner).map_err(to_error)?; + + for face in faces { + ffi::b_rep_offset_api::BRepOffsetAPI_DraftAngle_Add( + draft.pin_mut(), + &face.inner, + &pull_dir, + angle_rad, + &neutral_plane, + ) + .map_err(to_error)?; + } + + ffi::b_rep_offset_api::BRepOffsetAPI_DraftAngle_Build(draft.pin_mut()).map_err(to_error)?; + + if !ffi::b_rep_offset_api::BRepOffsetAPI_DraftAngle_IsDone(&draft) { + return Err(crate::Error::DraftAngleFailed( + "Build() selesai tanpa error tapi IsDone() == false (sudut atau face tidak kompatibel)" + .to_string(), + )); + } + + let result_shape = + ffi::b_rep_offset_api::BRepOffsetAPI_DraftAngle_shape_checked(draft.pin_mut()) + .map_err(to_error)?; + Ok(Self::from_shape(result_shape)) + } + + pub fn split_with_plane( + &self, + plane_point: DVec3, + plane_normal: DVec3, + ) -> Result, crate::Error> { + let to_error = |e: cxx::Exception| crate::Error::SplitFailed(e.what().to_string()); + + let res_vec = ffi::b_rep_algo_api::split_shape_with_plane( + &self.inner, + plane_point.x, + plane_point.y, + plane_point.z, + plane_normal.x, + plane_normal.y, + plane_normal.z, + ) + .map_err(to_error)?; + + let mut shapes = Vec::new(); + if let Some(vec) = res_vec.as_ref() { + for shape_ref in vec.iter() { + shapes.push(Self::from_shape(shape_ref)); + } + } + + Ok(shapes) + } + + pub fn split_with_tool(&self, tool: &Shape) -> Result, crate::Error> { + let to_error = |e: cxx::Exception| crate::Error::SplitFailed(e.what().to_string()); + + let res_vec = ffi::b_rep_algo_api::split_shape_with_tool(&self.inner, &tool.inner) + .map_err(to_error)?; + + let mut shapes = Vec::new(); + if let Some(vec) = res_vec.as_ref() { + for shape_ref in vec.iter() { + shapes.push(Self::from_shape(shape_ref)); + } + } + + Ok(shapes) + } + + pub fn split_faces_with_plane( + &self, + plane_point: DVec3, + plane_normal: DVec3, + ) -> Result { + let to_error = |e: cxx::Exception| crate::Error::SplitFailed(e.what().to_string()); + + let res_shape = ffi::b_rep_algo_api::split_faces_with_plane( + &self.inner, + plane_point.x, + plane_point.y, + plane_point.z, + plane_normal.x, + plane_normal.y, + plane_normal.z, + ) + .map_err(to_error)?; + + Ok(Self::from_shape(&res_shape)) + } + + pub fn section_with_plane( + &self, + plane_point: DVec3, + plane_normal: DVec3, + ) -> Result, crate::Error> { + let to_error = |e: cxx::Exception| crate::Error::SplitFailed(e.what().to_string()); + + let res_vec = ffi::b_rep_algo_api::section_shape_with_plane( + &self.inner, + plane_point.x, + plane_point.y, + plane_point.z, + plane_normal.x, + plane_normal.y, + plane_normal.z, + ) + .map_err(to_error)?; + + let mut shapes = Vec::new(); + if let Some(vec) = res_vec.as_ref() { + for shape_ref in vec.iter() { + shapes.push(Self::from_shape(shape_ref)); + } + } + + Ok(shapes) + } + /// Drill a cylindrical hole along the line defined by point `p` /// and direction `dir`, with `radius`. #[must_use] diff --git a/update-ducad.md b/update-ducad.md new file mode 100644 index 000000000..ec1414fa6 --- /dev/null +++ b/update-ducad.md @@ -0,0 +1,167 @@ +# DUCAD Technical Patch Changelog: opencascade-rs + +This document details all technical modifications, extensions, and build configuration changes applied to the vendored `opencascade-rs` repository (`branch: ducad-patches`) to meet the requirements of the **DUCAD** parametric CAD kernel. + +--- + +## 1. Background & Rationale + +The upstream `opencascade-rs` repository ([github.com/bschwind/opencascade-rs](https://github.com/bschwind/opencascade-rs)) provides safe Rust bindings to OpenCASCADE Technology (OCCT). DUCAD requires low-level CXX FFI bridging and several capabilities not yet present in upstream: +1. **Configurable Ray-Casting Picking Tolerance:** Upstream hardcoded the geometric picking tolerance to `0.0001`, which caused oblique perspective camera 3D ray-casting to frequently return `None`. +2. **Surface & Curve Geometry Inspection:** The ability to inspect and classify face geometry (planar, cylindrical, conical, spherical) and extract analytical parameters (radius, axis, apex, normal vectors) required for sketching on curved faces, dimensioning, and constraint solving. +3. **Advanced B-Rep Feature Modeling:** Operations such as edge-specific chamfering, selective fillet radius, hollow/thick-solid (shelling) with open closing faces, sweep (pipe / pipe-shell), and arbitrary axis revolving. +4. **Topological Naming & History Tracking:** Access to OpenCASCADE B-Rep history structures (`Generated`, `Modified`, `IsDeleted`) from boolean operations to enable persistent topological face/edge tracking. +5. **Dependency Synchronization:** Upgraded to `glam 0.33` and `thiserror 2` to match the DUCAD workspace without redundant conversion overhead. + +--- + +## 2. Workspace & Build Configuration Changes + +| File | Changes | Rationale | +| :--- | :--- | :--- | +| [`Cargo.toml`](file:///Users/jayuda/Documents/PROJECT/DUCAD/ducad-editor/vendors/opencascade-rs/Cargo.toml) | Simplified `workspace.members` to `["crates/kicad-parser", "crates/opencascade", "crates/opencascade-sys"]`. Excluded `examples`, `viewer`, `model-api`, and `wasm-example`. | Eliminates compilation errors from outdated upstream demo/example crates not used by DUCAD. | +| [`crates/opencascade/Cargo.toml`](file:///Users/jayuda/Documents/PROJECT/DUCAD/ducad-editor/vendors/opencascade-rs/crates/opencascade/Cargo.toml) | Upgraded dependencies to `glam = "0.33"` and `thiserror = "2"`. | Aligns dependency versions with the root `ducad-editor` workspace. | +| [`crates/opencascade-sys/build.rs`](file:///Users/jayuda/Documents/PROJECT/DUCAD/ducad-editor/vendors/opencascade-rs/crates/opencascade-sys/build.rs) | Added `.flag_if_supported("-Wno-deprecated-declarations")` to the C++ compilation builder. | Suppresses Apple Clang SDK deprecation warnings for `sprintf` within third-party OCCT template headers on macOS. | + +--- + +## 3. FFI Layer Modifications (`crates/opencascade-sys`) + +### A. `b_rep_adaptor` (`include/b_rep_adaptor.hxx`, `src/b_rep_adaptor.rs`) +- **`BRepAdaptor_Surface`:** + - Added constructor `BRepAdaptor_Surface_ctor(face, restriction)`. + - Added `GetType()` binding for surface classification (`GeomAbs_SurfaceType`). + - Added analytical geometry conversion bindings: `BRepAdaptor_Surface_cylinder`, `BRepAdaptor_Surface_cone`, `BRepAdaptor_Surface_sphere`. +- **`BRepAdaptor_Curve`:** + - Added constructor `BRepAdaptor_Curve_new(edge)`. + - Added `FirstParameter()`, `LastParameter()`, and curve point evaluation `BRepAdaptor_Curve_value(curve, u)`. + - Added `GetType()` binding for curve classification (`GeomAbs_CurveType`). + +### B. `b_rep_algo_api` (`include/b_rep_algo_api.hxx`, `src/b_rep_algo_api.rs`) +- **Exception-Safe Checked Operations:** + - Added C++ exception handling wrappers: `BRepAlgoAPI_Cut_ctor_checked`, `BRepAlgoAPI_Fuse_ctor_checked`, `BRepAlgoAPI_Common_ctor_checked`, `BRepAlgoAPI_Section_ctor_checked`. + - Added checked shape extractors: `BRepAlgoAPI_Cut_shape_checked`, etc. +- **Topological History Methods:** + - Added bindings for `Generated()`, `Modified()`, `IsDeleted()`, `HasGenerated()`, `HasModified()`, `HasDeleted()` to track B-Rep shape lineages through boolean operations. + +### C. `b_rep_builder_api` (`include/b_rep_builder_api.hxx`, `src/b_rep_builder_api.rs`) +- Added `BRepBuilderAPI_MakeFace_add_wire` binding to attach inner boundary wires (holes) to an existing face. +- Added checked constructors for `BRepBuilderAPI_Transform`. + +### D. `b_rep_fillet_api` (`include/b_rep_fillet_api.hxx`, `src/b_rep_fillet_api.rs`) +- Added `BRepFilletAPI_MakeChamfer` binding for selective edge chamfering with a specified distance. +- Added `BRepFilletAPI_MakeFillet_add_edge` binding for selective edge filleting with constant radius. + +### E. `b_rep_offset_api` (`include/b_rep_offset_api.hxx`, `src/b_rep_offset_api.rs`) +- Added `BRepOffsetAPI_MakeOffset` & `BRepOffsetAPI_MakeThickSolid` bindings for hollow/thick-solid operations with optional closing faces. +- Added `BRepOffsetAPI_MakePipe` & `BRepOffsetAPI_MakePipeShell` bindings for curve sweep/pipe lofting along path wires. + +### F. `b_rep_prim_api` (`include/b_rep_prim_api.hxx`, `src/b_rep_prim_api.rs`) +- Added `BRepPrimAPI_MakePrism` binding (direct face/wire extrusion along a direction vector). +- Added `BRepPrimAPI_MakeRevol` binding (profile revolution about an axis). +- Added parametric cone (`MakeCone`) and torus (`MakeTorus`) constructor bindings. + +### G. `geom_abs` & `gp` (`include/geom_abs.hxx`, `src/geom_abs.rs`, `include/gp.hxx`, `src/gp.rs`) +- **`geom_abs`:** + - CXX enum declarations for `GeomAbs_SurfaceType` (`Plane`, `Cylinder`, `Cone`, `Sphere`, `Torus`, `BezierSurface`, `BSplineSurface`, etc.). + - CXX enum declarations for `GeomAbs_CurveType` (`Line`, `Circle`, `Ellipse`, `Hyperbola`, `Parabola`, `BezierCurve`, `BSplineCurve`, etc.). +- **`gp`:** + - Added bindings for analytical geometry entities: `gp_Cylinder`, `gp_Cone`, `gp_Sphere`, `gp_Torus`, `gp_Lin`, `gp_Circ`, `gp_Ax1`, `gp_Ax2`. + - Property accessors for reference location, axis direction, radius, and cone semi-angle. + +--- + +## 4. High-Level Safe Rust API (`crates/opencascade`) + +### A. `primitives/shape.rs` +- **`faces_along_ray_tol(ray_origin, ray_dir, tolerance)`:** + - Ray-casting picking with parameterized tolerance to accurately detect intersected faces from 3D camera rays. +- **Advanced B-Rep Modification Operations:** + - `chamfer_edges(distance, edges)`: Chamfers designated edges. + - `fillet_edges(radius, edges)`: Fillets designated edges. + - `hollow / shell(closing_faces, thickness, tolerance)`: Generates hollowed-out thin-walled solids. + - `sweep(spine_wire)`: Sweeps a profile along a spine wire. + - `revolve(axis_point, axis_dir, angle_radians)`: Revolves a profile face around a 3D axis. +- **Topological Downcasting & Traversal:** + - Safe extraction iterators for `edges()`, `faces()`, `wires()`, `solids()`, `shells()`, `vertices()`. + +### B. `primitives/face.rs` +- **Geometry Classification & Inspection:** + - `surface_type(&self) -> SurfaceType`: Returns the analytical surface type. + - `as_cylinder(&self) -> Option`: Extracts origin, axis, and radius. + - `as_cone(&self) -> Option`: Extracts apex, axis, and semi-angle. + - `as_sphere(&self) -> Option`: Extracts center point and radius. + - `normal_at(&self, u, v) -> Option`: Computes surface normal at UV coordinates. +- **Direct Face Operations:** + - `extrude(&self, dir: DVec3) -> Solid`: Creates an extruded solid directly from a face. + - `outer_wire(&self) -> Wire` and `inner_wires(&self) -> Vec`: Separates outer boundary and inner hole loops. + +### C. `primitives/edge.rs` +- **Edge Curve Inspection:** + - `curve_type(&self) -> CurveType`: Identifies line, circle, ellipse, parabola, hyperbola, or B-Spline curve. + - `point_at_parameter(&self, u: f64) -> DVec3`: Evaluates 3D coordinates at a given parameter. + - `length(&self) -> f64`: Computes the 3D arc length of the edge. + +### D. `primitives/boolean_shape.rs` +- Exposes `history()` to allow DUCAD's topological naming engine to track persistent face and edge IDs across Cut / Fuse / Common operations. + +### E. `adhoc.rs` +- Provides the `AdHocShape` helper struct for rapid procedural primitive construction (box, cylinder, sphere) with safe boolean chaining. + +--- + +## 5. File Inventory + +``` +vendors/opencascade-rs/ +├── Cargo.toml [MODIFIED: pruned workspace members] +├── update-ducad.md [NEW: technical changelog documentation] +└── crates/ + ├── opencascade/ + │ ├── Cargo.toml [MODIFIED: glam 0.33, thiserror 2] + │ └── src/ + │ ├── lib.rs [MODIFIED: re-exported new modules] + │ ├── adhoc.rs [NEW: AdHocShape procedural helper] + │ └── primitives/ + │ ├── boolean_shape.rs [MODIFIED: history tracking] + │ ├── edge.rs [MODIFIED: curve adaptor & parameter eval] + │ ├── face.rs [MODIFIED: surface adaptor & analytical extraction] + │ └── shape.rs [MODIFIED: ray casting tol, chamfer, fillet, shell] + └── opencascade-sys/ + ├── build.rs [MODIFIED: -Wno-deprecated-declarations flag] + ├── include/ + │ ├── b_rep_adaptor.hxx [MODIFIED: surface & curve adaptor headers] + │ ├── b_rep_algo_api.hxx [MODIFIED: checked boolean & history headers] + │ ├── b_rep_builder_api.hxx [MODIFIED: make face add wire headers] + │ ├── b_rep_fillet_api.hxx [MODIFIED: chamfer & fillet edge headers] + │ ├── b_rep_offset_api.hxx [MODIFIED: shell & pipe sweep headers] + │ ├── b_rep_prim_api.hxx [MODIFIED: prism & revol headers] + │ ├── bindings_common.hxx [MODIFIED: FFI exception handling macros] + │ ├── geom_abs.hxx [MODIFIED: surface & curve type enums] + │ └── gp.hxx [MODIFIED: analytical geometry structures] + └── src/ + ├── b_rep_adaptor.rs [MODIFIED: CXX bridge adaptors] + ├── b_rep_algo_api.rs [MODIFIED: CXX bridge boolean & history] + ├── b_rep_builder_api.rs [MODIFIED: CXX bridge builder] + ├── b_rep_fillet_api.rs [MODIFIED: CXX bridge fillet/chamfer] + ├── b_rep_offset_api.rs [MODIFIED: CXX bridge offset/shell] + ├── b_rep_prim_api.rs [MODIFIED: CXX bridge prim/revol] + ├── geom_abs.rs [MODIFIED: CXX bridge geom_abs] + └── gp.rs [MODIFIED: CXX bridge gp entities] +``` + +--- + +## 6. Build & Verification + +To verify the integration from the `ducad-editor` root directory: + +```bash +# Check compilation for the entire DUCAD workspace including patched vendor crates +cargo check --workspace + +# Run linter checks +cargo clippy --workspace +``` + +All targets compile cleanly with **0 errors and 0 warnings**.