mirror of
https://github.com/OrcaSlicer/OrcaSlicer.git
synced 2026-09-27 19:01:02 +00:00
Pull a body's edges into a sketch as construction references
Port of snaporca e635627b81. Parity OK: 17 files identical, 8 diverging at their expected counts. The last reference an industrial sketcher offers that this one did not: Onshape's Use, SolidWorks' Convert Entities. Project already turned a body's 3D edges into 2D Lines, Circles and Arcs on a plane, but the result landed in its OWN feature, so while drawing in one sketch you could not borrow an existing body's edge and constrain to it. No new geometry code: Project's per-edge conversion loop is factored into project_edges_to_entities() and called from a second entry point that appends into an EXISTING sketch with construction = true. The loop appears once now, not twice. Construction is what makes it cheap and safe: SketchEngine already skips construction entities when building wires, so the references guide without being built, and being otherwise ordinary entities every constraint from this epic -- Collinear, EqualRadius, the axis-projected distances, PointOnLine, Symmetric -- works against them for free. Part of this refactors working code, so Project's behaviour identity is the invariant; the existing [CadDocument][project] cases guard it and a new case asserts a Project feature still emits construction == false. project_edges_into_sketch returns the number of entities appended, or -1 on a bad reference rather than throwing. VERIFICATION LIMIT, as with the previous four commits: this fork's kernel suite still cannot run (find_package(assimp) at configure time, snaporca-w80c). Shared sources are byte-identical to snaporca's, where kernel is 7700 assertions / 270 cases and ALL LADDERS HELD 7/7.
This commit is contained in:
@@ -1238,6 +1238,11 @@ int CadDocument::add_surface_offset(int target_body, double offset, const std::s
|
|||||||
return int(features.size()) - 1;
|
return int(features.size()) - 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static void project_edges_to_entities(const std::vector<TopoDS_Edge>& edges,
|
||||||
|
const SketchPlane& plane,
|
||||||
|
bool construction,
|
||||||
|
std::vector<SketchEntity>& out);
|
||||||
|
|
||||||
int CadDocument::add_project_edges(int source_body, const std::vector<int>& edge_ids, int face,
|
int CadDocument::add_project_edges(int source_body, const std::vector<int>& edge_ids, int face,
|
||||||
const SketchPlane& plane, const std::string& name)
|
const SketchPlane& plane, const std::string& name)
|
||||||
{
|
{
|
||||||
@@ -1252,6 +1257,37 @@ int CadDocument::add_project_edges(int source_body, const std::vector<int>& edge
|
|||||||
return int(features.size()) - 1;
|
return int(features.size()) - 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
int CadDocument::project_edges_into_sketch(int sketch_feature, int source_body,
|
||||||
|
const std::vector<int>& edge_ids, int face)
|
||||||
|
{
|
||||||
|
if (sketch_feature < 0 || sketch_feature >= int(features.size())) return -1;
|
||||||
|
CadFeature& sk = features[sketch_feature];
|
||||||
|
if (sk.type != CadFeatureType::Sketch && sk.type != CadFeatureType::Project) return -1;
|
||||||
|
if (source_body < 0 || source_body >= int(bodies.size())
|
||||||
|
|| bodies[source_body].shape.IsNull()) return -1;
|
||||||
|
const TopoDS_Shape& shape = bodies[source_body].shape;
|
||||||
|
|
||||||
|
std::vector<TopoDS_Edge> edges;
|
||||||
|
if (!edge_ids.empty()) {
|
||||||
|
for (int id : edge_ids) {
|
||||||
|
TopoDS_Edge e = GeometryEngine::edge_by_index(shape, id);
|
||||||
|
if (e.IsNull()) return -1;
|
||||||
|
edges.push_back(e);
|
||||||
|
}
|
||||||
|
} else if (face >= 0) {
|
||||||
|
TopoDS_Face fc = GeometryEngine::face_by_index(shape, face);
|
||||||
|
if (fc.IsNull()) return -1;
|
||||||
|
edges = GeometryEngine::edges_of_face(fc);
|
||||||
|
} else {
|
||||||
|
edges = GeometryEngine::edges_of(shape);
|
||||||
|
}
|
||||||
|
if (edges.empty()) return -1;
|
||||||
|
|
||||||
|
const size_t before = sk.entities.size();
|
||||||
|
project_edges_to_entities(edges, sk.plane, /*construction=*/true, sk.entities);
|
||||||
|
return int(sk.entities.size() - before);
|
||||||
|
}
|
||||||
|
|
||||||
int CadDocument::add_bridge(int sketch_ref, int ent_a, int end_a, int ent_b, int end_b,
|
int CadDocument::add_bridge(int sketch_ref, int ent_a, int end_a, int ent_b, int end_b,
|
||||||
const std::string& name)
|
const std::string& name)
|
||||||
{
|
{
|
||||||
@@ -3098,6 +3134,78 @@ void CadDocument::apply_surface_offset(std::vector<CadBody>& bodies, const CadFe
|
|||||||
bodies.push_back({off_shape, f.name.empty() ? std::string("SurfaceOffset") : f.name});
|
bodies.push_back({off_shape, f.name.empty() ? std::string("SurfaceOffset") : f.name});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Project `edges` of a shape onto `plane`, appending the resulting 2D entities to `out`.
|
||||||
|
// `construction` marks them as guides rather than built geometry. This is the body of the
|
||||||
|
// Project feature's conversion loop, factored out so a sketch can borrow the same geometry.
|
||||||
|
static void project_edges_to_entities(const std::vector<TopoDS_Edge>& edges,
|
||||||
|
const SketchPlane& plane,
|
||||||
|
bool construction,
|
||||||
|
std::vector<SketchEntity>& out)
|
||||||
|
{
|
||||||
|
auto to2d = [&](const gp_Pnt& p) -> Vec2d {
|
||||||
|
Vec3d d(p.X() - plane.origin.x(), p.Y() - plane.origin.y(), p.Z() - plane.origin.z());
|
||||||
|
return Vec2d(d.dot(plane.x_axis), d.dot(plane.y_axis));
|
||||||
|
};
|
||||||
|
|
||||||
|
// A segment whose endpoints coincide after projection carries no geometry: that is what
|
||||||
|
// an edge perpendicular to the target plane becomes. Emitting it as a zero-length line
|
||||||
|
// would poison the sketch downstream, so drop it here.
|
||||||
|
auto push_line = [&](const Vec2d& a, const Vec2d& b) {
|
||||||
|
if ((b - a).norm() < 1e-7) return;
|
||||||
|
SketchEntity se; se.type = SketchEntity::Type::Line;
|
||||||
|
se.p0 = a; se.p1 = b;
|
||||||
|
se.construction = construction;
|
||||||
|
out.push_back(se);
|
||||||
|
};
|
||||||
|
|
||||||
|
for (const TopoDS_Edge& e : edges) {
|
||||||
|
BRepAdaptor_Curve ac(e);
|
||||||
|
const GeomAbs_CurveType ct = ac.GetType();
|
||||||
|
if (ct == GeomAbs_Line) {
|
||||||
|
gp_Pnt a = ac.Value(ac.FirstParameter());
|
||||||
|
gp_Pnt b = ac.Value(ac.LastParameter());
|
||||||
|
push_line(to2d(a), to2d(b));
|
||||||
|
} else if (ct == GeomAbs_Circle) {
|
||||||
|
gp_Circ c = ac.Circle();
|
||||||
|
gp_Dir cn = c.Axis().Direction();
|
||||||
|
Vec3d cnv(cn.X(), cn.Y(), cn.Z());
|
||||||
|
const double par = std::abs(cnv.dot(plane.normal));
|
||||||
|
const bool full = BRep_Tool::IsClosed(e) ||
|
||||||
|
std::abs((ac.LastParameter() - ac.FirstParameter()) - 2.0 * M_PI) < 1e-6;
|
||||||
|
if (par > 0.999) {
|
||||||
|
Vec2d ctr = to2d(c.Location());
|
||||||
|
if (full) {
|
||||||
|
SketchEntity se; se.type = SketchEntity::Type::Circle;
|
||||||
|
se.center = ctr; se.radius = c.Radius();
|
||||||
|
se.construction = construction;
|
||||||
|
out.push_back(se);
|
||||||
|
} else {
|
||||||
|
gp_Pnt a = ac.Value(ac.FirstParameter());
|
||||||
|
gp_Pnt b = ac.Value(ac.LastParameter());
|
||||||
|
Vec2d a2 = to2d(a), b2 = to2d(b);
|
||||||
|
SketchEntity se; se.type = SketchEntity::Type::Arc;
|
||||||
|
se.center = ctr; se.radius = c.Radius();
|
||||||
|
se.p0 = a2; se.p1 = b2;
|
||||||
|
se.start_angle = std::atan2(a2.y() - ctr.y(), a2.x() - ctr.x());
|
||||||
|
se.end_angle = std::atan2(b2.y() - ctr.y(), b2.x() - ctr.x());
|
||||||
|
se.construction = construction;
|
||||||
|
out.push_back(se);
|
||||||
|
}
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
std::vector<Vec3d> pts = GeometryEngine::sample_edge_world(e);
|
||||||
|
for (size_t i = 1; i < pts.size(); ++i)
|
||||||
|
push_line(to2d(gp_Pnt(pts[i-1].x(), pts[i-1].y(), pts[i-1].z())),
|
||||||
|
to2d(gp_Pnt(pts[i].x(), pts[i].y(), pts[i].z())));
|
||||||
|
} else {
|
||||||
|
std::vector<Vec3d> pts = GeometryEngine::sample_edge_world(e);
|
||||||
|
for (size_t i = 1; i < pts.size(); ++i)
|
||||||
|
push_line(to2d(gp_Pnt(pts[i-1].x(), pts[i-1].y(), pts[i-1].z())),
|
||||||
|
to2d(gp_Pnt(pts[i].x(), pts[i].y(), pts[i].z())));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
void CadDocument::apply_project(const std::vector<CadBody>& bodies, CadFeature& f) const
|
void CadDocument::apply_project(const std::vector<CadBody>& bodies, CadFeature& f) const
|
||||||
{
|
{
|
||||||
f.entities.clear();
|
f.entities.clear();
|
||||||
@@ -3128,65 +3236,8 @@ void CadDocument::apply_project(const std::vector<CadBody>& bodies, CadFeature&
|
|||||||
}
|
}
|
||||||
if (edges.empty()) throw std::runtime_error("project: no edges to project");
|
if (edges.empty()) throw std::runtime_error("project: no edges to project");
|
||||||
|
|
||||||
auto to2d = [&](const gp_Pnt& p) -> Vec2d {
|
project_edges_to_entities(edges, f.plane, /*construction=*/false, f.entities);
|
||||||
Vec3d d(p.X() - f.plane.origin.x(), p.Y() - f.plane.origin.y(), p.Z() - f.plane.origin.z());
|
|
||||||
return Vec2d(d.dot(f.plane.x_axis), d.dot(f.plane.y_axis));
|
|
||||||
};
|
|
||||||
|
|
||||||
// A segment whose endpoints coincide after projection carries no geometry: that is what
|
|
||||||
// an edge perpendicular to the target plane becomes. Emitting it as a zero-length line
|
|
||||||
// would poison the sketch downstream, so drop it here.
|
|
||||||
auto push_line = [&](const Vec2d& a, const Vec2d& b) {
|
|
||||||
if ((b - a).norm() < 1e-7) return;
|
|
||||||
SketchEntity se; se.type = SketchEntity::Type::Line;
|
|
||||||
se.p0 = a; se.p1 = b;
|
|
||||||
f.entities.push_back(se);
|
|
||||||
};
|
|
||||||
|
|
||||||
for (const TopoDS_Edge& e : edges) {
|
|
||||||
BRepAdaptor_Curve ac(e);
|
|
||||||
const GeomAbs_CurveType ct = ac.GetType();
|
|
||||||
if (ct == GeomAbs_Line) {
|
|
||||||
gp_Pnt a = ac.Value(ac.FirstParameter());
|
|
||||||
gp_Pnt b = ac.Value(ac.LastParameter());
|
|
||||||
push_line(to2d(a), to2d(b));
|
|
||||||
} else if (ct == GeomAbs_Circle) {
|
|
||||||
gp_Circ c = ac.Circle();
|
|
||||||
gp_Dir cn = c.Axis().Direction();
|
|
||||||
Vec3d cnv(cn.X(), cn.Y(), cn.Z());
|
|
||||||
const double par = std::abs(cnv.dot(f.plane.normal));
|
|
||||||
const bool full = BRep_Tool::IsClosed(e) ||
|
|
||||||
std::abs((ac.LastParameter() - ac.FirstParameter()) - 2.0 * M_PI) < 1e-6;
|
|
||||||
if (par > 0.999) {
|
|
||||||
Vec2d ctr = to2d(c.Location());
|
|
||||||
if (full) {
|
|
||||||
SketchEntity se; se.type = SketchEntity::Type::Circle;
|
|
||||||
se.center = ctr; se.radius = c.Radius();
|
|
||||||
f.entities.push_back(se);
|
|
||||||
} else {
|
|
||||||
gp_Pnt a = ac.Value(ac.FirstParameter());
|
|
||||||
gp_Pnt b = ac.Value(ac.LastParameter());
|
|
||||||
Vec2d a2 = to2d(a), b2 = to2d(b);
|
|
||||||
SketchEntity se; se.type = SketchEntity::Type::Arc;
|
|
||||||
se.center = ctr; se.radius = c.Radius();
|
|
||||||
se.p0 = a2; se.p1 = b2;
|
|
||||||
se.start_angle = std::atan2(a2.y() - ctr.y(), a2.x() - ctr.x());
|
|
||||||
se.end_angle = std::atan2(b2.y() - ctr.y(), b2.x() - ctr.x());
|
|
||||||
f.entities.push_back(se);
|
|
||||||
}
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
std::vector<Vec3d> pts = GeometryEngine::sample_edge_world(e);
|
|
||||||
for (size_t i = 1; i < pts.size(); ++i)
|
|
||||||
push_line(to2d(gp_Pnt(pts[i-1].x(), pts[i-1].y(), pts[i-1].z())),
|
|
||||||
to2d(gp_Pnt(pts[i].x(), pts[i].y(), pts[i].z())));
|
|
||||||
} else {
|
|
||||||
std::vector<Vec3d> pts = GeometryEngine::sample_edge_world(e);
|
|
||||||
for (size_t i = 1; i < pts.size(); ++i)
|
|
||||||
push_line(to2d(gp_Pnt(pts[i-1].x(), pts[i-1].y(), pts[i-1].z())),
|
|
||||||
to2d(gp_Pnt(pts[i].x(), pts[i].y(), pts[i].z())));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (f.entities.empty()) throw std::runtime_error("project: produced no entities");
|
if (f.entities.empty()) throw std::runtime_error("project: produced no entities");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -481,6 +481,13 @@ public:
|
|||||||
// entities are (re)derived on every recompute.
|
// entities are (re)derived on every recompute.
|
||||||
int add_project_edges(int source_body, const std::vector<int>& edge_ids, int face,
|
int add_project_edges(int source_body, const std::vector<int>& edge_ids, int face,
|
||||||
const SketchPlane& plane, const std::string& name);
|
const SketchPlane& plane, const std::string& name);
|
||||||
|
// Onshape's "Use" / SolidWorks' "Convert Entities": project a body's edges onto the plane of
|
||||||
|
// an EXISTING sketch feature and append them to that sketch as CONSTRUCTION entities, so new
|
||||||
|
// geometry can be constrained to them. Returns the number of entities appended, or -1 if the
|
||||||
|
// sketch or body reference is invalid. Unlike add_project_edges this creates no feature: the
|
||||||
|
// references become part of the sketch that borrows them.
|
||||||
|
int project_edges_into_sketch(int sketch_feature, int source_body,
|
||||||
|
const std::vector<int>& edge_ids, int face);
|
||||||
// Append a bridging BSpline entity connecting endpoint `end_a` of entity `ent_a` to
|
// Append a bridging BSpline entity connecting endpoint `end_a` of entity `ent_a` to
|
||||||
// endpoint `end_b` of entity `ent_b`, both within sketch feature `sketch_ref`. Returns
|
// endpoint `end_b` of entity `ent_b`, both within sketch feature `sketch_ref`. Returns
|
||||||
// the new entity's index within that sketch's entities vector. Non-parametric: computed
|
// the new entity's index within that sketch's entities vector. Non-parametric: computed
|
||||||
|
|||||||
@@ -3762,6 +3762,153 @@ TEST_CASE("project round-trip serialization", "[CadDocument][project]")
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
TEST_CASE("Use/Convert Entities: box top-face edges land as construction geometry", "[CadDocument][project]")
|
||||||
|
{
|
||||||
|
CadDocument doc;
|
||||||
|
int sk = doc.add_sketch(SketchShape::Rectangle, SketchPlane::XY(), 20, 20, 10, "Box");
|
||||||
|
doc.add_extrude(sk, 10.0, false, BooleanMode::New, "Ext");
|
||||||
|
REQUIRE(doc.recompute());
|
||||||
|
REQUIRE(doc.bodies.size() == 1);
|
||||||
|
|
||||||
|
int n_faces = GeometryEngine::face_count(doc.bodies[0].shape);
|
||||||
|
int top_face = -1;
|
||||||
|
for (int i = 0; i < n_faces; ++i) {
|
||||||
|
TopoDS_Face fc = GeometryEngine::face_by_index(doc.bodies[0].shape, i);
|
||||||
|
if (GeometryEngine::face_normal_world(fc).z() > 0.9) { top_face = i; break; }
|
||||||
|
}
|
||||||
|
REQUIRE(top_face >= 0);
|
||||||
|
|
||||||
|
int target = doc.add_sketch_entities({}, SketchPlane::XY(), "Use");
|
||||||
|
REQUIRE(target >= 0);
|
||||||
|
|
||||||
|
int appended = doc.project_edges_into_sketch(target, 0, {}, top_face);
|
||||||
|
REQUIRE(appended == 4);
|
||||||
|
|
||||||
|
const auto& ents = doc.features[target].entities;
|
||||||
|
REQUIRE(ents.size() == 4);
|
||||||
|
for (const auto& e : ents) {
|
||||||
|
REQUIRE(e.type == SketchEntity::Type::Line);
|
||||||
|
REQUIRE(e.construction == true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_CASE("Use/Convert Entities: construction references never become solid", "[CadDocument][project]")
|
||||||
|
{
|
||||||
|
using Catch::Matchers::WithinRel;
|
||||||
|
|
||||||
|
CadDocument doc;
|
||||||
|
int sk = doc.add_sketch(SketchShape::Rectangle, SketchPlane::XY(), 20, 20, 10, "Box");
|
||||||
|
doc.add_extrude(sk, 10.0, false, BooleanMode::New, "Ext");
|
||||||
|
REQUIRE(doc.recompute());
|
||||||
|
REQUIRE(doc.bodies.size() == 1);
|
||||||
|
|
||||||
|
int n_faces = GeometryEngine::face_count(doc.bodies[0].shape);
|
||||||
|
int top_face = -1;
|
||||||
|
for (int i = 0; i < n_faces; ++i) {
|
||||||
|
TopoDS_Face fc = GeometryEngine::face_by_index(doc.bodies[0].shape, i);
|
||||||
|
if (GeometryEngine::face_normal_world(fc).z() > 0.9) { top_face = i; break; }
|
||||||
|
}
|
||||||
|
REQUIRE(top_face >= 0);
|
||||||
|
|
||||||
|
const size_t body_count_before = doc.bodies.size();
|
||||||
|
const double volume_before = doc.body_mass_properties(0).volume;
|
||||||
|
|
||||||
|
int target = doc.add_sketch_entities({}, SketchPlane::XY(), "Use");
|
||||||
|
int appended = doc.project_edges_into_sketch(target, 0, {}, top_face);
|
||||||
|
REQUIRE(appended == 4);
|
||||||
|
|
||||||
|
REQUIRE(doc.recompute());
|
||||||
|
REQUIRE(doc.error.empty());
|
||||||
|
REQUIRE(doc.bodies.size() == body_count_before);
|
||||||
|
REQUIRE_THAT(doc.body_mass_properties(0).volume, WithinRel(volume_before, 1e-9));
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_CASE("Use/Convert Entities: bad references return -1 and change nothing", "[CadDocument][project]")
|
||||||
|
{
|
||||||
|
CadDocument doc;
|
||||||
|
int sk = doc.add_sketch(SketchShape::Rectangle, SketchPlane::XY(), 20, 20, 10, "Box");
|
||||||
|
doc.add_extrude(sk, 10.0, false, BooleanMode::New, "Ext");
|
||||||
|
REQUIRE(doc.recompute());
|
||||||
|
REQUIRE(doc.bodies.size() == 1);
|
||||||
|
|
||||||
|
int target = doc.add_sketch_entities({}, SketchPlane::XY(), "Use");
|
||||||
|
REQUIRE(target >= 0);
|
||||||
|
|
||||||
|
REQUIRE(doc.project_edges_into_sketch(-1, 0, {}, -1) == -1);
|
||||||
|
REQUIRE(doc.project_edges_into_sketch(999, 0, {}, -1) == -1);
|
||||||
|
REQUIRE(doc.project_edges_into_sketch(target, -1, {}, -1) == -1);
|
||||||
|
REQUIRE(doc.project_edges_into_sketch(target, 999, {}, -1) == -1);
|
||||||
|
REQUIRE(doc.features[target].entities.empty());
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_CASE("Use/Convert Entities: cylinder edge arrives as a Circle, not a polyline", "[CadDocument][project]")
|
||||||
|
{
|
||||||
|
using Catch::Matchers::WithinRel;
|
||||||
|
|
||||||
|
CadDocument doc;
|
||||||
|
SketchEntity c;
|
||||||
|
c.type = SketchEntity::Type::Circle;
|
||||||
|
c.center = Vec2d(0, 0);
|
||||||
|
c.radius = 6.0;
|
||||||
|
int sk = doc.add_sketch_entities({c}, SketchPlane::XY(), "Circ");
|
||||||
|
doc.add_extrude(sk, 10.0, false, BooleanMode::New, "Cyl");
|
||||||
|
REQUIRE(doc.recompute());
|
||||||
|
REQUIRE(doc.bodies.size() == 1);
|
||||||
|
|
||||||
|
int n_edges = GeometryEngine::edge_count(doc.bodies[0].shape);
|
||||||
|
int top_edge = -1;
|
||||||
|
for (int i = 0; i < n_edges; ++i) {
|
||||||
|
TopoDS_Edge e = GeometryEngine::edge_by_index(doc.bodies[0].shape, i);
|
||||||
|
auto pts = GeometryEngine::sample_edge_world(e);
|
||||||
|
if (pts.empty()) continue;
|
||||||
|
Vec3d mid = Vec3d::Zero();
|
||||||
|
for (const auto& p : pts) mid += p;
|
||||||
|
mid /= double(pts.size());
|
||||||
|
if (mid.z() > 9.0) {
|
||||||
|
BRepAdaptor_Curve ac(e);
|
||||||
|
if (ac.GetType() == GeomAbs_Circle) { top_edge = i; break; }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
REQUIRE(top_edge >= 0);
|
||||||
|
|
||||||
|
int target = doc.add_sketch_entities({}, SketchPlane::XY(), "Use");
|
||||||
|
int appended = doc.project_edges_into_sketch(target, 0, {top_edge}, -1);
|
||||||
|
REQUIRE(appended == 1);
|
||||||
|
|
||||||
|
const auto& ents = doc.features[target].entities;
|
||||||
|
REQUIRE(ents.size() == 1);
|
||||||
|
REQUIRE(ents[0].type == SketchEntity::Type::Circle);
|
||||||
|
REQUIRE(ents[0].construction == true);
|
||||||
|
REQUIRE_THAT(ents[0].radius, WithinRel(6.0, 1e-6));
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_CASE("Project feature still emits non-construction entities (regression guard)", "[CadDocument][project]")
|
||||||
|
{
|
||||||
|
CadDocument doc;
|
||||||
|
int sk = doc.add_sketch(SketchShape::Rectangle, SketchPlane::XY(), 20, 20, 10, "Box");
|
||||||
|
doc.add_extrude(sk, 10.0, false, BooleanMode::New, "Ext");
|
||||||
|
REQUIRE(doc.recompute());
|
||||||
|
REQUIRE(doc.bodies.size() == 1);
|
||||||
|
|
||||||
|
int n_faces = GeometryEngine::face_count(doc.bodies[0].shape);
|
||||||
|
int top_face = -1;
|
||||||
|
for (int i = 0; i < n_faces; ++i) {
|
||||||
|
TopoDS_Face fc = GeometryEngine::face_by_index(doc.bodies[0].shape, i);
|
||||||
|
if (GeometryEngine::face_normal_world(fc).z() > 0.9) { top_face = i; break; }
|
||||||
|
}
|
||||||
|
REQUIRE(top_face >= 0);
|
||||||
|
|
||||||
|
int proj = doc.add_project_edges(0, {}, top_face, SketchPlane::XY(), "ProjTop");
|
||||||
|
REQUIRE(proj >= 0);
|
||||||
|
REQUIRE(doc.recompute());
|
||||||
|
REQUIRE(doc.error.empty());
|
||||||
|
|
||||||
|
const auto& pf = doc.features[proj];
|
||||||
|
REQUIRE(pf.entities.size() == 4);
|
||||||
|
for (const auto& e : pf.entities)
|
||||||
|
REQUIRE(e.construction == false);
|
||||||
|
}
|
||||||
|
|
||||||
// --- Golden recipe fixture (v1 format tripwire) ---
|
// --- Golden recipe fixture (v1 format tripwire) ---
|
||||||
|
|
||||||
static CadDocument make_golden_doc_v1()
|
static CadDocument make_golden_doc_v1()
|
||||||
|
|||||||
Reference in New Issue
Block a user