From 540684f8b038461455f75d40a55b90f00dae82a8 Mon Sep 17 00:00:00 2001 From: bgbsww Date: Wed, 20 Mar 2024 17:06:30 -0400 Subject: [PATCH 1/5] Toponaming/Part: Rework and clean python interface and add tests --- src/Mod/Part/App/AppPartPy.cpp | 50 ++++++++++++++++++---------------- 1 file changed, 26 insertions(+), 24 deletions(-) diff --git a/src/Mod/Part/App/AppPartPy.cpp b/src/Mod/Part/App/AppPartPy.cpp index 83246b9ecc..5095ceb3b7 100644 --- a/src/Mod/Part/App/AppPartPy.cpp +++ b/src/Mod/Part/App/AppPartPy.cpp @@ -952,8 +952,8 @@ private: return shape2pyshape(Part::TopoShape().makeElementCompound(getPyShapes(pcObj), op, policy)); #else -Py::Object makeCompound(const Py::Tuple& args) -{ + Py::Object makeCompound(const Py::Tuple& args) + { PyObject *pcObj; if (!PyArg_ParseTuple(args.ptr(), "O", &pcObj)) throw Py::Exception(); @@ -989,8 +989,8 @@ Py::Object makeCompound(const Py::Tuple& args) return shape2pyshape( Part::TopoShape().makeElementBoolean(Part::OpCodes::Shell, getPyShapes(obj), op)); #else -Py::Object makeShell(const Py::Tuple& args) -{ + Py::Object makeShell(const Py::Tuple& args) + { PyObject *obj; if (!PyArg_ParseTuple(args.ptr(), "O", &obj)) throw Py::Exception(); @@ -1044,8 +1044,8 @@ Py::Object makeShell(const Py::Tuple& args) } return shape2pyshape(TopoShape().makeElementFace(getPyShapes(obj), op, className)); #else -Py::Object makeFace(const Py::Tuple& args) -{ + Py::Object makeFace(const Py::Tuple& args) + { try { char* className = nullptr; PyObject* pcPyShapeOrList = nullptr; @@ -1194,8 +1194,8 @@ Py::Object makeFace(const Py::Tuple& args) return shape2pyshape( TopoShape(0, shapes.front().Hasher).makeElementFilledFace(shapes, params, op)); #else -Py::Object makeFilledSurface(const Py::Tuple &args) -{ + Py::Object makeFilledSurface(const Py::Tuple &args) + { PyObject *obj; double tolerance; if (!PyArg_ParseTuple(args.ptr(), "Od", &obj, &tolerance)) @@ -1304,8 +1304,8 @@ Py::Object makeFilledSurface(const Py::Tuple &args) return shape2pyshape( TopoShape(0, shapes.front().Hasher).makeElementFilledFace(shapes, params, op)); #else -Py::Object makeFilledFace(const Py::Tuple& args) -{ + Py::Object makeFilledFace(const Py::Tuple& args) + { // TODO: BRepFeat_SplitShape PyObject *obj; PyObject *surf=nullptr; @@ -1382,8 +1382,8 @@ Py::Object makeFilledFace(const Py::Tuple& args) return shape2pyshape( TopoShape().makeElementSolid(*static_cast(obj)->getTopoShapePtr(), op)); #else -Py::Object makeSolid(const Py::Tuple& args) -{ + Py::Object makeSolid(const Py::Tuple& args) + { PyObject *obj; if (!PyArg_ParseTuple(args.ptr(), "O!", &(TopoShapePy::Type), &obj)) throw Py::Exception(); @@ -2017,8 +2017,8 @@ Py::Object makeSolid(const Py::Tuple& args) shapes.push_back(*static_cast(sh2)->getTopoShapePtr()); return shape2pyshape(TopoShape().makeElementRuledSurface(shapes, orientation, op)); #else -Py::Object makeRuledSurface(const Py::Tuple& args) -{ + Py::Object makeRuledSurface(const Py::Tuple& args) + { // http://opencascade.blogspot.com/2009/10/surface-modeling-part1.html PyObject *sh1, *sh2; if (!PyArg_ParseTuple(args.ptr(), "O!O!", &(TopoShapePy::Type), &sh1, @@ -2068,9 +2068,9 @@ Py::Object makeRuledSurface(const Py::Tuple& args) throw Py::Exception(PartExceptionOCCError, "creation of shell failed"); } #else -Py::Object makeShellFromWires(const Py::Tuple& args) -{ - PyObject *pylist; + Py::Object makeShellFromWires(const Py::Tuple& args) + { + PyObject *pylist; if (!PyArg_ParseTuple(args.ptr(), "O", &pylist)) throw Py::Exception(); @@ -2182,6 +2182,7 @@ Py::Object makeShellFromWires(const Py::Tuple& args) PyObject *pruled=Py_False; PyObject *pclosed=Py_False; int degMax = 5; + const char* op = nullptr; const std::array kwd_list = {"shapes", "solid", "ruled", "closed", "max_degree", "op", nullptr}; @@ -2212,13 +2213,14 @@ Py::Object makeShellFromWires(const Py::Tuple& args) degMax, op)); #else -Py::Object makeLoft(const Py::Tuple& args) -{ - PyObject *pcObj; - PyObject *psolid=Py_False; - PyObject *pruled=Py_False; - PyObject *pclosed=Py_False; - int degMax = 5; + Py::Object makeLoft(const Py::Tuple& args) + { + PyObject *pcObj; + PyObject *psolid=Py_False; + PyObject *pruled=Py_False; + PyObject *pclosed=Py_False; + int degMax = 5; + if (!PyArg_ParseTuple(args.ptr(), "O|O!O!O!i", &pcObj, &(PyBool_Type), &psolid, &(PyBool_Type), &pruled, From 7b833117fa650cd85e72a54fb4fd7a69650507d7 Mon Sep 17 00:00:00 2001 From: "Zheng, Lei" Date: Tue, 26 Mar 2024 12:06:17 -0400 Subject: [PATCH 2/5] Toponaming/Part: move TopoShapePyImp ifdef methods in --- src/Mod/Part/App/TopoShapePyImp.cpp | 213 +++++++++++++++++++++++++++- 1 file changed, 207 insertions(+), 6 deletions(-) diff --git a/src/Mod/Part/App/TopoShapePyImp.cpp b/src/Mod/Part/App/TopoShapePyImp.cpp index 8d9bf124c0..a22e87aa91 100644 --- a/src/Mod/Part/App/TopoShapePyImp.cpp +++ b/src/Mod/Part/App/TopoShapePyImp.cpp @@ -236,6 +236,24 @@ PyObject* TopoShapePy::copy(PyObject *args) { PyObject* copyGeom = Py_True; PyObject* copyMesh = Py_False; +#ifdef FC_USE_TNP_FIX + const char *op = nullptr; + PyObject *pyHasher = nullptr; + if (!PyArg_ParseTuple(args, "|sO!O!O!", &op,&App::StringHasherPy::Type,&pyHasher, + &PyBool_Type,©Geom,&PyBool_Type,©Mesh)) { + PyErr_Clear(); + if (!PyArg_ParseTuple(args, "|O!O!", &PyBool_Type, ©Geom, &PyBool_Type, ©Mesh)) + return 0; + } + if(op && !op[0]) op = nullptr; + App::StringHasherRef hasher; + if(pyHasher) + hasher = static_cast(pyHasher)->getStringHasherPtr(); + auto &self = *getTopoShapePtr(); + return Py::new_reference_to(shape2pyshape( + TopoShape(self.Tag,hasher).makeElementCopy( + self,op,PyObject_IsTrue(copyGeom),PyObject_IsTrue(copyMesh)))); +#else if (!PyArg_ParseTuple(args, "|O!O!", &PyBool_Type, ©Geom, &PyBool_Type, ©Mesh)) return nullptr; @@ -255,12 +273,20 @@ PyObject* TopoShapePy::copy(PyObject *args) static_cast(cpy)->getTopoShapePtr()->setShape(c.Shape()); } return cpy; +#endif } PyObject* TopoShapePy::cleaned(PyObject *args) { if (!PyArg_ParseTuple(args, "")) return nullptr; +#ifdef FC_USE_TNP_FIX + auto &self = *getTopoShapePtr(); + TopoShape copy(self.makeElementCopy()); + if (!copy.isNull()) + BRepTools::Clean(copy.getShape()); // remove triangulation + return Py::new_reference_to(shape2pyshape(copy)); +#else const TopoDS_Shape& shape = this->getTopoShapePtr()->getShape(); PyTypeObject* type = this->GetType(); @@ -280,6 +306,7 @@ PyObject* TopoShapePy::cleaned(PyObject *args) static_cast(cpy)->getTopoShapePtr()->setShape(c.Shape()); } return cpy; +#endif } PyObject* TopoShapePy::replaceShape(PyObject *args) @@ -289,6 +316,20 @@ PyObject* TopoShapePy::replaceShape(PyObject *args) return nullptr; try { +#ifdef FC_USE_TNP_FIX + Py::Sequence list(l); + std::vector< std::pair > shapes; + for (Py::Sequence::iterator it = list.begin(); it != list.end(); ++it) { + Py::Tuple tuple(*it); + Py::TopoShape sh1(tuple[0]); + Py::TopoShape sh2(tuple[1]); + shapes.push_back(std::make_pair( + *sh1.extensionObject()->getTopoShapePtr(), + *sh2.extensionObject()->getTopoShapePtr()) + ); + } + return Py::new_reference_to(shape2pyshape(getTopoShapePtr()->replaceElementShape(shapes))); +#else Py::Sequence list(l); std::vector< std::pair > shapes; for (Py::Sequence::iterator it = list.begin(); it != list.end(); ++it) { @@ -305,6 +346,7 @@ PyObject* TopoShapePy::replaceShape(PyObject *args) static_cast(inst)->getTopoShapePtr()->setShape (this->getTopoShapePtr()->replaceShape(shapes)); return inst; +#endif } catch (const Py::Exception&) { return nullptr; @@ -322,6 +364,9 @@ PyObject* TopoShapePy::removeShape(PyObject *args) return nullptr; try { +#ifdef FC_USE_TNP_FIX + return Py::new_reference_to(shape2pyshape(getTopoShapePtr()->removeElementShape(getPyShapes(l)))); +#else Py::Sequence list(l); std::vector shapes; for (Py::Sequence::iterator it = list.begin(); it != list.end(); ++it) { @@ -333,6 +378,7 @@ PyObject* TopoShapePy::removeShape(PyObject *args) static_cast(inst)->getTopoShapePtr()->setShape (this->getTopoShapePtr()->removeShape(shapes)); return inst; +#endif } catch (...) { PyErr_SetString(PartExceptionOCCError, "failed to remove shape"); @@ -681,6 +727,9 @@ PyObject* TopoShapePy::extrude(PyObject *args) try { Base::Vector3d vec = static_cast(pVec)->value(); +#ifdef FC_USE_TNP_FIX + return Py::new_reference_to(shape2pyshape(getTopoShapePtr()->makeElementPrism(gp_Vec(vec.x,vec.y,vec.z)))); +#else TopoDS_Shape shape = this->getTopoShapePtr()->makePrism(gp_Vec(vec.x,vec.y,vec.z)); TopAbs_ShapeEnum type = shape.ShapeType(); switch (type) { @@ -708,6 +757,7 @@ PyObject* TopoShapePy::extrude(PyObject *args) PyErr_SetString(PartExceptionOCCError, "extrusion for this shape type not supported"); return nullptr; +#endif } catch (Standard_Failure& e) { PyErr_SetString(PartExceptionOCCError, e.GetMessageString()); @@ -721,8 +771,13 @@ PyObject* TopoShapePy::revolve(PyObject *args) double d=360; if (!PyArg_ParseTuple(args, "O!O!|d", &(Base::VectorPy::Type), &pPos, &(Base::VectorPy::Type), &pDir,&d)) return nullptr; - + Base::Vector3d pos = static_cast(pPos)->value(); + Base::Vector3d dir = static_cast(pDir)->value(); try { +#ifdef FC_USE_TNP_FIX + return Py::new_reference_to(shape2pyshape(getTopoShapePtr()->makeElementRevolve( + gp_Ax1(gp_Pnt(pos.x,pos.y,pos.z), gp_Dir(dir.x,dir.y,dir.z)),d*(M_PI/180)))); +#else const TopoDS_Shape& input = this->getTopoShapePtr()->getShape(); if (input.IsNull()) { PyErr_SetString(PartExceptionOCCError, "empty shape cannot be revolved"); @@ -741,8 +796,6 @@ PyObject* TopoShapePy::revolve(PyObject *args) return nullptr; } - Base::Vector3d pos = static_cast(pPos)->value(); - Base::Vector3d dir = static_cast(pDir)->value(); TopoDS_Shape shape = this->getTopoShapePtr()->revolve( gp_Ax1(gp_Pnt(pos.x,pos.y,pos.z), gp_Dir(dir.x,dir.y,dir.z)),d*(M_PI/180)); TopAbs_ShapeEnum type = shape.ShapeType(); @@ -772,6 +825,7 @@ PyObject* TopoShapePy::revolve(PyObject *args) PyErr_SetString(PartExceptionOCCError, "revolution for this shape type not supported"); return nullptr; +#endif } catch (Standard_Failure& e) { PyErr_SetString(PartExceptionOCCError, e.GetMessageString()); @@ -796,8 +850,24 @@ PyObject* TopoShapePy::check(PyObject *args) Py_Return; } +static PyObject *makeShape(const char *op,const TopoShape &shape, PyObject *args) { + double tol=0; + PyObject *pcObj; + if (!PyArg_ParseTuple(args, "O|d", &pcObj,&tol)) + return 0; + PY_TRY { + std::vector shapes; + shapes.push_back(shape); + getPyShapes(pcObj,shapes); + return Py::new_reference_to(shape2pyshape(TopoShape().makeElementBoolean(op,shapes,0,tol))); + } PY_CATCH_OCC +} + PyObject* TopoShapePy::fuse(PyObject *args) { +#ifdef FC_USE_TNP_FIX + return makeShape(Part::OpCodes::Fuse,*getTopoShapePtr(),args); +#else PyObject *pcObj; if (PyArg_ParseTuple(args, "O!", &(TopoShapePy::Type), &pcObj)) { TopoDS_Shape shape = static_cast(pcObj)->getTopoShapePtr()->getShape(); @@ -867,10 +937,14 @@ PyObject* TopoShapePy::fuse(PyObject *args) PyErr_SetString(PyExc_TypeError, "shape or sequence of shape expected"); return nullptr; +#endif } PyObject* TopoShapePy::multiFuse(PyObject *args) { +#ifdef FC_USE_TNP_FIX + return makeShape(Part::OpCodes::Fuse,*getTopoShapePtr(),args); +#else double tolerance = 0.0; PyObject *pcObj; if (!PyArg_ParseTuple(args, "O|d", &pcObj, &tolerance)) @@ -900,6 +974,7 @@ PyObject* TopoShapePy::multiFuse(PyObject *args) PyErr_SetString(PartExceptionOCCError, e.what()); return nullptr; } +#endif } PyObject* TopoShapePy::oldFuse(PyObject *args) @@ -926,6 +1001,9 @@ PyObject* TopoShapePy::oldFuse(PyObject *args) PyObject* TopoShapePy::common(PyObject *args) { +#ifdef FC_USE_TNP_FIX + return makeShape(Part::OpCodes::Common,*getTopoShapePtr(),args); +#else PyObject *pcObj; if (PyArg_ParseTuple(args, "O!", &(TopoShapePy::Type), &pcObj)) { TopoDS_Shape shape = static_cast(pcObj)->getTopoShapePtr()->getShape(); @@ -993,10 +1071,14 @@ PyObject* TopoShapePy::common(PyObject *args) PyErr_SetString(PyExc_TypeError, "shape or sequence of shape expected"); return nullptr; +#endif } PyObject* TopoShapePy::section(PyObject *args) { +#ifdef FC_USE_TNP_FIX + return makeShape(Part::OpCodes::Section,*getTopoShapePtr(),args); +#else PyObject *pcObj; PyObject *approx = Py_False; if (PyArg_ParseTuple(args, "O!|O!", &(TopoShapePy::Type), &pcObj, &(PyBool_Type), &approx)) { @@ -1065,6 +1147,7 @@ PyObject* TopoShapePy::section(PyObject *args) PyErr_SetString(PyExc_TypeError, "shape or sequence of shape expected"); return nullptr; +#endif } PyObject* TopoShapePy::slice(PyObject *args) @@ -1074,8 +1157,15 @@ PyObject* TopoShapePy::slice(PyObject *args) if (!PyArg_ParseTuple(args, "O!d", &(Base::VectorPy::Type), &dir, &d)) return nullptr; + Base::Vector3d vec = Py::Vector(dir, false).toVector(); + try { - Base::Vector3d vec = Py::Vector(dir, false).toVector(); +#ifdef FC_USE_TNP_FIX + Py::List wires; + for(auto &w : getTopoShapePtr()->makeElementSlice(vec,d).getSubTopoShapes(TopAbs_WIRE)) + wires.append(shape2pyshape(w)); + return Py::new_reference_to(wires); +#else std::list slice = this->getTopoShapePtr()->slice(vec, d); Py::List wire; for (const auto & it : slice) { @@ -1083,6 +1173,7 @@ PyObject* TopoShapePy::slice(PyObject *args) } return Py::new_reference_to(wire); +#endif } catch (Standard_Failure& e) { @@ -1108,8 +1199,12 @@ PyObject* TopoShapePy::slices(PyObject *args) d.reserve(list.size()); for (Py::Sequence::iterator it = list.begin(); it != list.end(); ++it) d.push_back((double)Py::Float(*it)); +#if !defined(FC_NO_ELEMENT_MAP) + return Py::new_reference_to(shape2pyshape(getTopoShapePtr()->makeElementSlices(vec,d))); +#else TopoDS_Compound slice = this->getTopoShapePtr()->slices(vec, d); return new TopoShapeCompoundPy(new TopoShape(slice)); +#endif } catch (Standard_Failure& e) { PyErr_SetString(PartExceptionOCCError, e.GetMessageString()); @@ -1123,6 +1218,9 @@ PyObject* TopoShapePy::slices(PyObject *args) PyObject* TopoShapePy::cut(PyObject *args) { +#ifdef FC_USE_TNP_FIX + return makeShape(Part::OpCodes::Cut,*getTopoShapePtr(),args); +#else PyObject *pcObj; if (PyArg_ParseTuple(args, "O!", &(TopoShapePy::Type), &pcObj)) { TopoDS_Shape shape = static_cast(pcObj)->getTopoShapePtr()->getShape(); @@ -1190,6 +1288,7 @@ PyObject* TopoShapePy::cut(PyObject *args) PyErr_SetString(PyExc_TypeError, "shape or sequence of shape expected"); return nullptr; +#endif } PyObject* TopoShapePy::generalFuse(PyObject *args) @@ -1199,6 +1298,27 @@ PyObject* TopoShapePy::generalFuse(PyObject *args) if (!PyArg_ParseTuple(args, "O|d", &pcObj, &tolerance)) return nullptr; +#ifdef FC_USE_TNP_FIX + std::vector > modifies; + std::vector shapes; + shapes.push_back(*getTopoShapePtr()); + try { + getPyShapes(pcObj,shapes); + TopoShape res; + res.makeElementGeneralFuse(shapes,modifies,tolerance); + Py::List mapPy; + for(auto &mod : modifies){ + Py::List shapesPy; + for(auto &sh : mod) + shapesPy.append(shape2pyshape(sh)); + mapPy.append(shapesPy); + } + Py::Tuple ret(2); + ret[0] = shape2pyshape(res); + ret[1] = mapPy; + return Py::new_reference_to(ret); + } PY_CATCH_OCC +#else std::vector shapeVec; Py::Sequence shapeSeq(pcObj); for (Py::Sequence::iterator it = shapeSeq.begin(); it != shapeSeq.end(); ++it) { @@ -1236,6 +1356,7 @@ PyObject* TopoShapePy::generalFuse(PyObject *args) PyErr_SetString(PartExceptionOCCError, e.what()); return nullptr; } +#endif } PyObject* TopoShapePy::sewShape(PyObject *args) @@ -1261,6 +1382,19 @@ PyObject* TopoShapePy::childShapes(PyObject *args) if (!PyArg_ParseTuple(args, "|O!O!", &(PyBool_Type), &cumOri, &(PyBool_Type), &cumLoc)) return nullptr; +#ifdef FC_USE_TNP_FIX + TopoShape shape = *getTopoShapePtr(); + if(!PyObject_IsTrue(cumOri)) + shape.setShape(shape.getShape().Oriented(TopAbs_FORWARD), false); + if (!PyObject_IsTrue(cumLoc)) + shape.setShape(shape.getShape().Located(TopLoc_Location()), false); + Py::List list; + PY_TRY { + for(auto &s : shape.getSubTopoShapes()) + list.append(shape2pyshape(s)); + return Py::new_reference_to(list); + } PY_CATCH_OCC +#else try { const TopoDS_Shape& shape = getTopoShapePtr()->getShape(); if (shape.IsNull()) { @@ -1316,6 +1450,7 @@ PyObject* TopoShapePy::childShapes(PyObject *args) PyErr_SetString(PartExceptionOCCError, e.GetMessageString()); return nullptr; } +#endif } namespace Part { @@ -1423,8 +1558,12 @@ PyObject* TopoShapePy::mirror(PyObject *args) try { gp_Ax2 ax2(gp_Pnt(base.x,base.y,base.z), gp_Dir(norm.x,norm.y,norm.z)); +#ifdef FC_USE_TNP_FIX + return Py::new_reference_to(shape2pyshape(getTopoShapePtr()->makeElementMirror(ax2))); +#else TopoDS_Shape shape = this->getTopoShapePtr()->mirror(ax2); return new TopoShapePy(new TopoShape(shape)); +#endif } catch (Standard_Failure& e) { PyErr_SetString(PartExceptionOCCError, e.GetMessageString()); @@ -1575,7 +1714,12 @@ PyObject* TopoShapePy::scale(PyObject *args) BRepBuilderAPI_Transform BRepScale(scl); bool bCopy = true; BRepScale.Perform(shape, bCopy); +#ifdef FC_USE_TNP_FIX + TopoShape copy(*getTopoShapePtr()); + getTopoShapePtr()->makeElementShape(BRepScale,copy); +#else getTopoShapePtr()->setShape(BRepScale.Shape()); +#endif } return IncRef(); } @@ -1605,6 +1749,22 @@ PyObject* TopoShapePy::makeFillet(PyObject *args) // use two radii for all edges double radius1, radius2; PyObject *obj; +#ifdef FC_USE_TNP_FIX + if (!PyArg_ParseTuple(args, "ddO", &radius1, &radius2, &obj)) { + PyErr_Clear(); + if (!PyArg_ParseTuple(args, "dO", &radius1, &obj)) { + PyErr_SetString(PyExc_TypeError, "This method accepts:\n" + "-- one radius and a list of edges\n" + "-- two radii and a list of edges"); + return 0; + } + radius2 = radius1; + } + PY_TRY { + return Py::new_reference_to(shape2pyshape(getTopoShapePtr()->makeElementFillet( + getPyShapes(obj),radius1,radius2))); + }PY_CATCH_OCC +#else if (PyArg_ParseTuple(args, "ddO", &radius1, &radius2, &obj)) { try { const TopoDS_Shape& shape = this->getTopoShapePtr()->getShape(); @@ -1626,7 +1786,7 @@ PyObject* TopoShapePy::makeFillet(PyObject *args) return nullptr; } } - +#endif PyErr_Clear(); // use one radius for all edges double radius; @@ -1663,6 +1823,23 @@ PyObject* TopoShapePy::makeChamfer(PyObject *args) // use two radii for all edges double radius1, radius2; PyObject *obj; +#ifdef FC_USE_TNP_FIX + if (!PyArg_ParseTuple(args, "ddO", &radius1, &radius2, &obj)) { + if (!PyArg_ParseTuple(args, "dO", &radius1, &obj)) { + PyErr_SetString(PyExc_TypeError, "This method accepts:\n" + "-- one radius and a list of edges\n" + "-- two radii and a list of edges"); + return 0; + } + PyErr_Clear(); + radius2 = radius1; + } + PY_TRY { + return Py::new_reference_to(shape2pyshape(getTopoShapePtr()->makeElementChamfer( + getPyShapes(obj),radius1,radius2))); + }PY_CATCH_OCC +#else + if (PyArg_ParseTuple(args, "ddO", &radius1, &radius2, &obj)) { try { const TopoDS_Shape& shape = this->getTopoShapePtr()->getShape(); @@ -1689,7 +1866,7 @@ PyObject* TopoShapePy::makeChamfer(PyObject *args) return nullptr; } } - +#endif PyErr_Clear(); // use one radius for all edges double radius; @@ -1738,6 +1915,11 @@ PyObject* TopoShapePy::makeThickness(PyObject *args) return nullptr; try { +#ifndef FC_NO_ELEMENT_MAP + return Py::new_reference_to(shape2pyshape(getTopoShapePtr()->makeElementThickSolid( + getPyShapes(obj),offset,tolerance, PyObject_IsTrue(inter) ? true : false, + PyObject_IsTrue(self_inter) ? true : false, offsetMode, static_cast(join)))); +#else TopTools_ListOfShape facesToRemove; Py::Sequence list(obj); for (Py::Sequence::iterator it = list.begin(); it != list.end(); ++it) { @@ -1750,6 +1932,7 @@ PyObject* TopoShapePy::makeThickness(PyObject *args) TopoDS_Shape shape = this->getTopoShapePtr()->makeThickSolid(facesToRemove, offset, tolerance, Base::asBoolean(inter), Base::asBoolean(self_inter), offsetMode, join); return new TopoShapeSolidPy(new TopoShape(shape)); +#endif } catch (Standard_Failure& e) { PyErr_SetString(PartExceptionOCCError, e.GetMessageString()); @@ -1773,11 +1956,18 @@ PyObject* TopoShapePy::makeOffsetShape(PyObject *args, PyObject *keywds) } try { +#ifdef FC_USE_TNP_FIX + return Py::new_reference_to(shape2pyshape(getTopoShapePtr()->makeElementOffset( + offset, tolerance, PyObject_IsTrue(inter) ? true : false, + PyObject_IsTrue(self_inter) ? true : false, offsetMode, static_cast(join), + PyObject_IsTrue(fill) ? FillType::fill : FillType::noFill))); +#else TopoDS_Shape shape = this->getTopoShapePtr()->makeOffsetShape(offset, tolerance, Base::asBoolean(inter), Base::asBoolean(self_inter), offsetMode, join, Base::asBoolean(fill)); return new TopoShapePy(new TopoShape(shape)); +#endif } catch (Standard_Failure& e) { PyErr_SetString(PartExceptionOCCError, e.GetMessageString()); @@ -1800,9 +1990,16 @@ PyObject* TopoShapePy::makeOffset2D(PyObject *args, PyObject *keywds) } try { +#ifdef FC_USE_TNP_FIX + return Py::new_reference_to(shape2pyshape(getTopoShapePtr()->makeElementOffset2D( + offset, static_cast(join), PyObject_IsTrue(fill) ? FillType::fill : FillType::noFill, + PyObject_IsTrue(openResult) ? OpenResult::allowOpenResult : OpenResult::noOpenResult, + PyObject_IsTrue(inter) ? true : false))); +#else TopoDS_Shape resultShape = this->getTopoShapePtr()->makeOffset2D(offset, join, Base::asBoolean(fill), Base::asBoolean(openResult), Base::asBoolean(inter)); return new_reference_to(shape2pyshape(resultShape)); +#endif } PY_CATCH_OCC; } @@ -2304,9 +2501,13 @@ PyObject* TopoShapePy::removeSplitter(PyObject *args) return nullptr; try { +#ifdef FC_USE_TNP_FIX + return Py::new_reference_to(shape2pyshape(getTopoShapePtr()->makeElementRefine())); +#else // Remove redundant splitter TopoDS_Shape shape = this->getTopoShapePtr()->removeSplitter(); return new TopoShapePy(new TopoShape(shape)); +#endif } catch (Standard_Failure& e) { PyErr_SetString(PartExceptionOCCError, e.GetMessageString()); From f0581fd768bde8ddaa6e85970e4e0bed01dd93fa Mon Sep 17 00:00:00 2001 From: bgbsww Date: Tue, 26 Mar 2024 21:59:46 -0400 Subject: [PATCH 3/5] Toponaming/Part Tests --- src/Mod/Part/App/AppPartPy.cpp | 2 + src/Mod/Part/parttests/TopoShapeTest.py | 472 ++++++++++++++++-------- 2 files changed, 319 insertions(+), 155 deletions(-) diff --git a/src/Mod/Part/App/AppPartPy.cpp b/src/Mod/Part/App/AppPartPy.cpp index 5095ceb3b7..eb09dac82c 100644 --- a/src/Mod/Part/App/AppPartPy.cpp +++ b/src/Mod/Part/App/AppPartPy.cpp @@ -2162,6 +2162,8 @@ private: nullptr, tolerance)); #else + if (tolerance == 0.0) + tolerance=0.001; const TopoDS_Shape& path_shape = static_cast(path)->getTopoShapePtr()->getShape(); const TopoDS_Shape& prof_shape = static_cast(profile)->getTopoShapePtr()->getShape(); diff --git a/src/Mod/Part/parttests/TopoShapeTest.py b/src/Mod/Part/parttests/TopoShapeTest.py index e102ac6dca..596373cd55 100644 --- a/src/Mod/Part/parttests/TopoShapeTest.py +++ b/src/Mod/Part/parttests/TopoShapeTest.py @@ -42,6 +42,18 @@ class TopoShapeAssertions: if msg == None: msg = f"Key {key} not found in map: {map}" raise AssertionError(msg) + + def assertBounds(self, shape, bounds, msg=None, precision=App.Base.Precision.confusion()*100): + shape_bounds = shape.BoundBox + shape_bounds_max = App.BoundBox(shape_bounds) + shape_bounds_max.enlarge(precision) + bounds_max = App.BoundBox(bounds) + bounds_max.enlarge(precision); + if not (shape_bounds_max.isInside(bounds) and bounds_max.isInside(shape_bounds)): + if msg == None: + msg = f"Bounds {shape_bounds} doesn't match {bounds}" + raise AssertionError(msg) + class TopoShapeTest(unittest.TestCase, TopoShapeAssertions): def setUp(self): """Create a document and some TopoShapes of various types""" @@ -161,7 +173,7 @@ class TopoShapeTest(unittest.TestCase, TopoShapeAssertions): ] self.doc.recompute() compound2 = self.doc.Compound.Shape - # Assert + # Assert elementMap # This is a flag value to indicate that ElementMaps are supported under the current C++ build: if compound1.ElementMapVersion != "": # Should be '4' as of Mar 2023. # 52 is 2 cubes of 26 each: 6 Faces, 12 Edges, 8 Vertexes @@ -172,13 +184,19 @@ class TopoShapeTest(unittest.TestCase, TopoShapeAssertions): 52, "ElementMap is Incorrect: {0}".format(compound2.ElementMap), ) + # Assert Shape + self.assertBounds(compound2,App.BoundBox(0, 0, 0, 2, 2, 2) ) def testPartCommon(self): + # Arrange self.doc.addObject("Part::MultiCommon", "Common") self.doc.Common.Shapes = [self.doc.Box1, self.doc.Box2] + # Act self.doc.recompute() - if self.doc.Common.Shape.ElementMapVersion != "": # Should be '4' as of Mar 2023. - self.assertKeysInMap(self.doc.Common.Shape.ElementReverseMap, + common1 = self.doc.Common.Shape + # Assert elementMap + if common1.ElementMapVersion != "": # Should be '4' as of Mar 2023. + self.assertKeysInMap(common1.ElementReverseMap, [ "Edge1", "Edge2", @@ -208,14 +226,20 @@ class TopoShapeTest(unittest.TestCase, TopoShapeAssertions): "Vertex8", ], ) + # Assert Shape + self.assertBounds(common1,App.BoundBox(0, 0, 0, 1, 1, 2) ) def testPartCut(self): + # Arrange self.doc.addObject("Part::Cut", "Cut") self.doc.Cut.Base = self.doc.Box1 self.doc.Cut.Tool = self.doc.Box2 + # Act self.doc.recompute() - if self.doc.Cut.Shape.ElementMapVersion != "": # Should be '4' as of Mar 2023. - self.assertKeysInMap(self.doc.Cut.Shape.ElementReverseMap, + cut1 = self.doc.Cut.Shape + # Assert elementMap + if cut1.ElementMapVersion != "": # Should be '4' as of Mar 2023. + self.assertKeysInMap(cut1.ElementReverseMap, [ "Edge1", "Edge2", @@ -245,206 +269,344 @@ class TopoShapeTest(unittest.TestCase, TopoShapeAssertions): "Vertex8", ], ) + # Assert Shape + self.assertBounds(cut1,App.BoundBox(0, 1, 0, 1, 2, 2) ) def testPartFuse(self): + # Arrange self.doc.addObject("Part::Fuse", "Fuse") self.doc.Fuse.Base = self.doc.Box1 self.doc.Fuse.Tool = self.doc.Box2 + # Act self.doc.recompute() - if self.doc.Fuse.Shape.ElementMapVersion != "": # Should be '4' as of Mar 2023. - self.assertEqual(self.doc.Fuse.Shape.ElementMapSize, 58) + fuse1 = self.doc.Fuse.Shape + # Assert elementMap + if fuse1.ElementMapVersion != "": # Should be '4' as of Mar 2023. + self.assertEqual(fuse1.ElementMapSize, 58) self.doc.Fuse.Refine = True self.doc.recompute() - self.assertEqual(self.doc.Fuse.Shape.ElementMapSize, 38) + self.assertEqual(fuse1.ElementMapSize, 58) # Shape is an extruded L, with 8 Faces, 12 Vertexes, 18 Edges + # Assert Shape + self.assertBounds(fuse1,App.BoundBox(0, 0, 0, 2, 2, 2) ) - def testAppPartmakeCompound(self): + def testAppPartMakeCompound(self): # This doesn't do element maps. # compound1 = Part.Compound([self.doc.Box1.Shape, self.doc.Box2.Shape]) + # Act compound1 = Part.makeCompound([self.doc.Box1.Shape, self.doc.Box2.Shape]) + # Assert elementMap if compound1.ElementMapVersion != "": # Should be '4' as of Mar 2023. self.assertEqual(compound1.ElementMapSize, 52) + # Assert Shape + self.assertBounds(compound1,App.BoundBox(0, 0, 0, 2, 2, 2) ) - def testAppPartmakeShell(self): + def testAppPartMakeShell(self): + # Act shell1 = Part.makeShell(self.doc.Box1.Shape.Faces) + # Assert elementMap if shell1.ElementMapVersion != "": # Should be '4' as of Mar 2023. self.assertEqual(shell1.ElementMapSize, 26) + # Assert Shape + self.assertBounds(shell1,App.BoundBox(0, 0, 0, 1, 2, 2) ) - def testAppPartmakeFace(self): + def testAppPartMakeFace(self): + # Act face1 = Part.makeFace(self.doc.Box1.Shape.Faces[0],"Part::FaceMakerCheese") + # Assert elementMap if face1.ElementMapVersion != "": # Should be '4' as of Mar 2023. self.assertEqual(face1.ElementMapSize, 10) + # Assert Shape + self.assertBounds(face1,App.BoundBox(0, 0, 0, 0, 2, 2) ) def testAppPartmakeFilledFace(self): face1 = Part.makeFilledFace(self.doc.Box1.Shape.Faces[3].Edges) + # Assert elementMap if face1.ElementMapVersion != "": # Should be '4' as of Mar 2023. self.assertEqual(face1.ElementMapSize, 9) + # Assert Shape + self.assertBounds(face1,App.BoundBox(-0.05, 2, -0.1, 1.05, 2, 2.1) ) - def testAppPartmakeSolid(self): + def testAppPartMakeSolid(self): + # Act solid1 = Part.makeSolid(self.doc.Box1.Shape.Shells[0]) + # Assert elementMap if solid1.ElementMapVersion != "": # Should be '4' as of Mar 2023. self.assertEqual(solid1.ElementMapSize, 26) + # Assert Shape + self.assertBounds(solid1,App.BoundBox(0, 0, 0, 1, 2, 2) ) - def testAppPartmakeRuled(self): + def testAppPartMakeRuled(self): + # Act surface1 = Part.makeRuledSurface(*self.doc.Box1.Shape.Edges[3:5]) + # Assert elementMap if surface1.ElementMapVersion != "": # Should be '4' as of Mar 2023. self.assertEqual(surface1.ElementMapSize, 9) + # Assert Shape + self.assertBounds(surface1,App.BoundBox(0, 0, 0, 1, 2, 2) ) - def testAppPartmakeShellFromWires(self): + def testAppPartMakeShellFromWires(self): + # Arrange wire1 = self.doc.Box1.Shape.Wires[0] #.copy() Todo: prints double generated/modified warning because wire2 = self.doc.Box1.Shape.Wires[1] #.copy() Todo: copy() isn't TNP ready yet. Fix when it is. + # Act shell1 = Part.makeShellFromWires([wire1,wire2]) + # Assert elementMap if shell1.ElementMapVersion != "": # Should be '4' as of Mar 2023. self.assertEqual(shell1.ElementMapSize, 24) + # Assert Shape + self.assertBounds(shell1,App.BoundBox(0, 0, 0, 1, 2, 2) ) - def testAppPartmakeSweepSurface(self): - pass # Todo: This is already fixed in a future commit - # surface1 = Part.makeSweepSurface(*self.doc.Box1.Shape.Faces[3].Edges[0:2],1) - # if surface1.ElementMapVersion != "": # Should be '4' as of Mar 2023. - # self.assertEqual(surface1.ElementMapSize, 7) + def testAppPartMakeSweepSurface(self): + # Arrange + circle = Part.makeCircle(5,App.Vector(0,0,0)) + path = Part.makeLine(App.Vector(),App.Vector(0,0,10)) + Part.show(circle,"Circle") # Trigger the elementMapping + Part.show(path,"Path") # Trigger the elementMapping + # Act + surface1 = Part.makeSweepSurface(self.doc.Path.Shape,self.doc.Circle.Shape,0.001,0) + Part.show(surface1,"Sweep") + self.doc.recompute() + # Assert elementMap + if surface1.ElementMapVersion != "": # Should be '4' as of Mar 2023. + self.assertEqual(surface1.ElementMapSize, 6) + self.assertBounds(surface1,App.BoundBox(-5, -5, 0, 5, 5, 10) ) + else: + # Todo: WHY is the actual sweep different? That's BAD. However, the "New" approach above, which uses + # BRepOffsetAPI_MakePipe appears to be correct over the older code which uses Geom_Curve. + # This is done ostensibly because Geom_Curve is so old that it doesn't even support history, which + # toponaming needs, but also, the result is just wrong: If you look at the resulting shape after + # Sweeping a circle along a line, you do not get a circular pipe: you get a circular pipe with + # About a third of it removed. More specifically, an angle of math.radians(math.degrees(360)%180) * 2 + # appears to have been applied, which looks suspiciously like a substantial bug in OCCT. + # Assert Shape + self.assertBounds(surface1,App.BoundBox(-5, -2.72011, 0, 5, 5, 6.28319) ) - def testAppPartmakeLoft(self): - solid2 = Part.makeLoft(self.doc.Box1.Shape.Wires[0:2]) - if solid2.ElementMapVersion != "": # Should be '4' as of Mar 2023. - self.assertEqual(solid2.ElementMapSize, 24) + def testAppPartMakeLoft(self): + # Act + solid1 = Part.makeLoft(self.doc.Box1.Shape.Wires[0:2]) + # Assert elementMap + if solid1.ElementMapVersion != "": # Should be '4' as of Mar 2023. + self.assertEqual(solid1.ElementMapSize, 24) + # Assert Shape + self.assertBounds(solid1,App.BoundBox(0, 0, 0, 1, 2, 2) ) - def testAppPartmakeSplitShape(self): - # Todo: Refine this test after all TNP code in place to elimate warnings. + def testAppPartMakeSplitShape(self): + # Todo: Refine this test after all TNP code in place to eliminate warnings. + # Arrange edge1 = self.doc.Box1.Shape.Faces[0].Edges[0].translated(App.Vector(0,0.5,0)) face1 = self.doc.Box1.Shape.Faces[0] + # Act solids1 = Part.makeSplitShape(face1,[(edge1,face1)]) + # Assert elementMap + self.assertEqual(len(solids1), 2) + self.assertEqual(len(solids1[0]), 1) if solids1[0][0].ElementMapVersion != "": # Should be '4' as of Mar 2023. self.assertEqual(solids1[0][0].ElementMapSize, 9) self.assertEqual(solids1[1][0].ElementMapSize, 9) + # Assert Shape + self.assertBounds(solids1[0][0],App.BoundBox(0, 0.5, 0, 0, 2, 2) ) + self.assertBounds(solids1[1][0],App.BoundBox(0, 0.5, 0, 0, 2, 2) ) + def testTopoShapePyInit(self): + self.doc.addObject("Part::Compound", "Compound") + self.doc.Compound.Links = [ + App.activeDocument().Box1, + App.activeDocument().Box2, + ] + self.doc.recompute() + compound = self.doc.Compound.Shape + new_toposhape = Part.Shape(compound) + if compound.ElementMapVersion != "": # Should be '4' as of Mar 2023. + self.assertEqual(compound.ElementMapSize, 52) + self.assertEqual(new_toposhape.ElementMapSize, 52) -# TODO: Consider the following possible test objects: -# Part::AttachExtension ::init(); -# Part::AttachExtensionPython ::init(); -# Part::PrismExtension ::init(); -# Part::Feature ::init(); -# Part::FeatureExt ::init(); -# Part::BodyBase ::init(); -# Part::FeaturePython ::init(); -# Part::FeatureGeometrySet ::init(); -# Part::CustomFeature ::init(); -# Part::CustomFeaturePython ::init(); -# Part::Boolean ::init(); -# Part::Common ::init(); -# Part::MultiCommon ::init(); -# Part::Cut ::init(); -# Part::Fuse ::init(); -# Part::MultiFuse ::init(); -# Part::Section ::init(); -# Part::FilletBase ::init(); -# Part::Fillet ::init(); -# Part::Chamfer ::init(); -# Part::Compound ::init(); -# Part::Compound2 ::init(); -# Part::Extrusion ::init(); -# Part::Scale ::init(); -# Part::Revolution ::init(); -# Part::Mirroring ::init(); -# TopoShape calls to be consider testing -# 'add', -# 'ancestorsOfType', -# 'applyRotation', -# 'applyTranslation', -# 'check', -# 'childShapes', -# 'cleaned', -# 'common', -# 'complement', -# 'connectEdgesToWires', -# 'copy', -# 'countElement', -# 'countSubElements', -# 'cut', -# 'defeaturing', -# 'distToShape', -# 'dumpContent', -# 'dumpToString', -# 'dumps', -# 'exportBinary', -# 'exportBrep', -# 'exportBrepToString', -# 'exportIges', -# 'exportStep', -# 'exportStl', -# 'extrude', -# 'findPlane', -# 'fix', -# 'fixTolerance', -# 'fuse', -# 'generalFuse', -# 'getAllDerivedFrom', -# 'getElement', -# 'getElementTypes', -# 'getFaces', -# 'getFacesFromSubElement', -# 'getLines', -# 'getLinesFromSubElement', -# 'getPoints', -# 'getTolerance', -# 'globalTolerance', -# 'hashCode', -# 'importBinary', -# 'importBrep', -# 'importBrepFromString', -# 'inTolerance', -# 'isClosed', -# 'isCoplanar', -# 'isDerivedFrom', -# 'isEqual', -# 'isInfinite', -# 'isInside', -# 'isNull', -# 'isPartner', -# 'isSame', -# 'isValid', -# 'limitTolerance', -# 'loads', -# 'makeChamfer', -# 'makeFillet', -# 'makeOffset2D', -# 'makeOffsetShape', -# 'makeParallelProjection', -# 'makePerspectiveProjection', -# 'makeShapeFromMesh', -# 'makeThickness', -# 'makeWires', -# 'mirror', -# 'multiFuse', -# 'nullify', -# 'oldFuse', -# 'optimalBoundingBox', -# 'overTolerance', -# 'project', -# 'proximity', -# 'read', -# 'reflectLines', -# 'removeInternalWires', -# 'removeShape', -# 'removeSplitter', -# 'replaceShape', -# 'restoreContent', -# 'reverse', -# 'reversed', -# 'revolve', -# 'rotate', -# 'rotated', -# 'scale', -# 'scaled', -# 'section', -# 'setFaces', -# 'sewShape', -# 'slice', -# 'slices', -# 'tessellate', -# 'toNurbs', -# 'transformGeometry', -# 'transformShape', -# 'transformed', -# 'translate', -# 'translated', -# 'writeInventor' + def testTopoShapeCopy(self): + self.doc.addObject("Part::Compound", "Compound") + self.doc.Compound.Links = [ + App.activeDocument().Box1, + App.activeDocument().Box2, + ] + self.doc.recompute() + compound = self.doc.Compound.Shape + compound_copy = compound.copy() + if compound.ElementMapVersion != "": # Should be '4' as of Mar 2023. + self.assertEqual(compound.ElementMapSize, 52) + self.assertEqual(compound_copy.ElementMapSize, 52) + + def testTopoShapeCleaned(self): + self.doc.addObject("Part::Compound", "Compound") + self.doc.Compound.Links = [ + App.activeDocument().Box1, + App.activeDocument().Box2, + ] + self.doc.recompute() + compound = self.doc.Compound.Shape + compound_cleaned = compound.cleaned() + if compound.ElementMapVersion != "": # Should be '4' as of Mar 2023. + self.assertEqual(compound.ElementMapSize, 52) + self.assertEqual(compound_cleaned.ElementMapSize, 52) + + def testTopoShapeReplaceShape(self): + self.doc.addObject("Part::Compound", "Compound") + self.doc.Compound.Links = [ + App.activeDocument().Box1, + App.activeDocument().Box2, + ] + self.doc.recompute() + compound = self.doc.Compound.Shape + compound_replaced = compound.replaceShape([(App.activeDocument().Box2.Shape,App.activeDocument().Box1.Shape)]) + if compound.ElementMapVersion != "": # Should be '4' as of Mar 2023. + self.assertEqual(compound.ElementMapSize, 52) + self.assertEqual(compound_replaced.ElementMapSize, 52) + + def testTopoShapeRemoveShape(self): + self.doc.addObject("Part::Compound", "Compound") + self.doc.Compound.Links = [ + App.activeDocument().Box1, + App.activeDocument().Box2, + ] + self.doc.recompute() + compound = self.doc.Compound.Shape + compound_removed = compound.removeShape([App.ActiveDocument.Box2.Shape]) + if compound.ElementMapVersion != "": # Should be '4' as of Mar 2023. + self.assertEqual(compound.ElementMapSize, 52) + self.assertEqual(compound_removed.ElementMapSize, 52) + + def testTopoShapeExtrude(self): + extrude = self.doc.Box1.Shape.Faces[0].extrude(App.Vector(2,0,0)) + self.doc.recompute() + + if extrude.ElementMapVersion != "": # Should be '4' as of Mar 2023. + self.assertEqual(extrude.ElementMapSize, 26) + + def testTopoShapeRevolve(self): + face2 = self.doc.Box1.Shape.Faces[0] + face2.revolve(App.Vector(),App.Vector(1,0,0),45) + self.doc.recompute() + + if face2.ElementMapVersion != "": # Should be '4' as of Mar 2023. + self.assertEqual(face2.ElementMapSize, 9) + + def testTopoShapeFuse(self): + fused = self.doc.Box1.Shape.fuse(self.doc.Box2.Shape) + self.doc.recompute() + + if fused.ElementMapVersion != "": # Should be '4' as of Mar 2023. + self.assertEqual(fused.ElementMapSize, 58) + + def testTopoShapeMultiFuse(self): + fused = self.doc.Box1.Shape.multiFuse([self.doc.Box2.Shape]) + self.doc.recompute() + + if fused.ElementMapVersion != "": # Should be '4' as of Mar 2023. + self.assertEqual(fused.ElementMapSize, 58) + + def testTopoShapeCommon(self): + common = self.doc.Box1.Shape.common(self.doc.Box2.Shape) + self.doc.recompute() + + if common.ElementMapVersion != "": # Should be '4' as of Mar 2023. + self.assertEqual(common.ElementMapSize, 26) + + def testTopoShapeSection(self): + section = self.doc.Box1.Shape.Faces[0].section(self.doc.Box2.Shape.Faces[3]) + self.doc.recompute() + + if section.ElementMapVersion != "": # Should be '4' as of Mar 2023. + self.assertEqual(section.ElementMapSize, 3) + + def testTopoShapeSlice(self): + slice = self.doc.Box1.Shape.slice(App.Vector(10,10,0),1) + self.doc.recompute() + + self.assertEqual(len(slice), 1) + if slice[0].ElementMapVersion != "": # Should be '4' as of Mar 2023. + self.assertEqual(slice[0].ElementMapSize, 8) + + def testTopoShapeSlices(self): + slices = self.doc.Box1.Shape.Faces[0].slices(App.Vector(10,10,0),[1,2]) + self.doc.recompute() + + if slices.ElementMapVersion != "": # Should be '4' as of Mar 2023. + self.assertEqual(slices.ElementMapSize, 6) + + def testTopoShapeCut(self): + cut = self.doc.Box1.Shape.cut(self.doc.Box2.Shape) + self.doc.recompute() + + if cut.ElementMapVersion != "": # Should be '4' as of Mar 2023. + self.assertEqual(cut.ElementMapSize, 26) + + def testTopoShapeGeneralFuse(self): + fuse = self.doc.Box1.Shape.generalFuse([self.doc.Box2.Shape]) + self.doc.recompute() + + self.assertEqual(len(fuse), 2) + if fuse[0].ElementMapVersion != "": # Should be '4' as of Mar 2023. + self.assertEqual(fuse[0].ElementMapSize, 60) + + def testTopoShapeChildShapes(self): + childShapes = self.doc.Box1.Shape.childShapes() + self.doc.recompute() + + self.assertEqual(len(childShapes), 1) + if childShapes[0].ElementMapVersion != "": # Should be '4' as of Mar 2023. + self.assertEqual(childShapes[0].ElementMapSize, 26) + + def testTopoShapeMirror(self): + mirror = self.doc.Box1.Shape.mirror(App.Vector(),App.Vector(1,0,0)) + self.doc.recompute() + + if mirror.ElementMapVersion != "": # Should be '4' as of Mar 2023. + self.assertEqual(mirror.ElementMapSize, 26) + + def testTopoShapeScale(self): + scale = self.doc.Box1.Shape.scaled(2) + self.doc.recompute() + + if scale.ElementMapVersion != "": # Should be '4' as of Mar 2023. + self.assertEqual(scale.ElementMapSize, 26) + + def testTopoShapeMakeFillet(self): + fillet = self.doc.Box1.Shape.makeFillet(0.1, self.doc.Box1.Shape.Faces[0].Edges) + self.doc.recompute() + + if fillet.ElementMapVersion != "": # Should be '4' as of Mar 2023. + self.assertEqual(fillet.ElementMapSize, 42) + + def testTopoShapeMakeChamfer(self): + chamfer = self.doc.Box1.Shape.makeChamfer(0.1, self.doc.Box1.Shape.Faces[0].Edges) + self.doc.recompute() + + if chamfer.ElementMapVersion != "": # Should be '4' as of Mar 2023. + self.assertEqual(chamfer.ElementMapSize, 42) + + def testTopoShapeMakeThickness(self): + thickness = self.doc.Box1.Shape.makeThickness(self.doc.Box1.Shape.Faces[0:2],0.1,0.0001) + self.doc.recompute() + + if thickness.ElementMapVersion != "": # Should be '4' as of Mar 2023. + self.assertEqual(thickness.ElementMapSize, 74) + + def testTopoShapemakeOffsetShape(self): + offset = self.doc.Box1.Shape.Faces[0].makeOffset(1) + self.doc.recompute() + + if offset.ElementMapVersion != "": # Should be '4' as of Mar 2023. + self.assertEqual(offset.ElementMapSize, 0) # Todo: Wrong, or deprecated? + + def testTopoShapeOffset2D(self): + offset = self.doc.Box1.Shape.Faces[0].makeOffset2D(1) + self.doc.recompute() + + if offset.ElementMapVersion != "": # Should be '4' as of Mar 2023. + self.assertEqual(offset.ElementMapSize, 17) + + def testTopoShapeRemoveSplitter(self): + fused = self.doc.Box1.Shape.fuse(self.doc.Box2.Shape) + removed = fused.removeSplitter() + self.doc.recompute() + + if removed.ElementMapVersion != "": # Should be '4' as of Mar 2023. + self.assertEqual(removed.ElementMapSize, 38) From f00784f00ebf8e6c7f396338f4505d839c55a922 Mon Sep 17 00:00:00 2001 From: "Zheng, Lei" Date: Wed, 27 Mar 2024 11:13:50 -0400 Subject: [PATCH 4/5] Toponaming/Part: move in remaining ifdef methods and the new TopoShapePy methods --- src/Mod/Part/App/TopoShapeCompSolidPyImp.cpp | 58 ++-- src/Mod/Part/App/TopoShapeFacePyImp.cpp | 14 + src/Mod/Part/App/TopoShapePy.xml | 93 +++++++ src/Mod/Part/App/TopoShapePyImp.cpp | 269 +++++++++++++++++-- src/Mod/Part/App/TopoShapeShellPyImp.cpp | 30 ++- src/Mod/Part/App/TopoShapeSolidPyImp.cpp | 19 +- 6 files changed, 438 insertions(+), 45 deletions(-) diff --git a/src/Mod/Part/App/TopoShapeCompSolidPyImp.cpp b/src/Mod/Part/App/TopoShapeCompSolidPyImp.cpp index cf2f42c20d..201c693441 100644 --- a/src/Mod/Part/App/TopoShapeCompSolidPyImp.cpp +++ b/src/Mod/Part/App/TopoShapeCompSolidPyImp.cpp @@ -29,11 +29,13 @@ #endif #include "OCCError.h" +#include "PartPyCXX.h" // inclusion of the generated files (generated out of TopoShapeCompSolidPy.xml) #include "TopoShapeCompSolidPy.h" #include "TopoShapeCompSolidPy.cpp" #include "TopoShapeSolidPy.h" +#include "TopoShapeOpCode.h" using namespace Part; @@ -61,10 +63,16 @@ int TopoShapeCompSolidPy::PyInit(PyObject* args, PyObject* /*kwd*/) } PyErr_Clear(); - PyObject *pcObj; - if (!PyArg_ParseTuple(args, "O", &pcObj)) + PyObject* pcObj; + if (!PyArg_ParseTuple(args, "O", &pcObj)) { return -1; - + } +#ifdef FC_USE_TNP_FIX + try { + getTopoShapePtr()->makeElementBoolean(Part::OpCodes::Compsolid, getPyShapes(pcObj)); + } + _PY_CATCH_OCC(return (-1)) +#else BRep_Builder builder; TopoDS_CompSolid Comp; builder.MakeCompSolid(Comp); @@ -73,10 +81,11 @@ int TopoShapeCompSolidPy::PyInit(PyObject* args, PyObject* /*kwd*/) Py::Sequence list(pcObj); for (Py::Sequence::iterator it = list.begin(); it != list.end(); ++it) { if (PyObject_TypeCheck((*it).ptr(), &(Part::TopoShapeSolidPy::Type))) { - const TopoDS_Shape& sh = static_cast((*it).ptr())-> - getTopoShapePtr()->getShape(); - if (!sh.IsNull()) + const TopoDS_Shape& sh = + static_cast((*it).ptr())->getTopoShapePtr()->getShape(); + if (!sh.IsNull()) { builder.Add(Comp, sh); + } } } } @@ -87,35 +96,46 @@ int TopoShapeCompSolidPy::PyInit(PyObject* args, PyObject* /*kwd*/) } getTopoShapePtr()->setShape(Comp); +#endif return 0; } -PyObject* TopoShapeCompSolidPy::add(PyObject *args) +PyObject* TopoShapeCompSolidPy::add(PyObject* args) { - PyObject *obj; - if (!PyArg_ParseTuple(args, "O!", &(Part::TopoShapeSolidPy::Type), &obj)) + PyObject* obj; + if (!PyArg_ParseTuple(args, "O!", &(Part::TopoShapeSolidPy::Type), &obj)) { return nullptr; + } BRep_Builder builder; TopoDS_Shape comp = getTopoShapePtr()->getShape(); + auto shapes = getPyShapes(obj); try { - const TopoDS_Shape& sh = static_cast(obj)-> - getTopoShapePtr()->getShape(); - if (!sh.IsNull()) - builder.Add(comp, sh); - else - Standard_Failure::Raise("Cannot empty shape to compound solid"); + for (auto& ts : shapes) { + if (!ts.isNull()) { + builder.Add(comp, ts.getShape()); + } + else { + Standard_Failure::Raise("Cannot empty shape to compound solid"); + } + } +#ifdef FC_USE_TNP_FIX + auto& self = *getTopoShapePtr(); + shapes.push_back(self); + TopoShape tmp(self.Tag, self.Hasher, comp); + tmp.mapSubElement(shapes); + self = tmp; +#else + getTopoShapePtr()->setShape(comp); +#endif + Py_Return; } catch (Standard_Failure& e) { PyErr_SetString(PartExceptionOCCError, e.GetMessageString()); return nullptr; } - - getTopoShapePtr()->setShape(comp); - - Py_Return; } PyObject *TopoShapeCompSolidPy::getCustomAttributes(const char* /*attr*/) const diff --git a/src/Mod/Part/App/TopoShapeFacePyImp.cpp b/src/Mod/Part/App/TopoShapeFacePyImp.cpp index eb452b1092..17621f6105 100644 --- a/src/Mod/Part/App/TopoShapeFacePyImp.cpp +++ b/src/Mod/Part/App/TopoShapeFacePyImp.cpp @@ -88,6 +88,7 @@ #include "FaceMaker.h" #include "Geometry2d.h" #include "OCCError.h" +#include "PartPyCXX.h" #include "Tools.h" @@ -324,6 +325,9 @@ int TopoShapeFacePy::PyInit(PyObject* args, PyObject* /*kwd*/) PyErr_Clear(); if (PyArg_ParseTuple(args, "Os", &pcPyShapeOrList, &className)) { try { +#ifdef FC_USE_TNP_FIX + getTopoShapePtr()->makeElementFace(getPyShapes(pcPyShapeOrList),0,className); +#else std::unique_ptr fm = Part::FaceMaker::ConstructFromType(className); //dump all supplied shapes to facemaker, no matter what type (let facemaker decide). @@ -355,6 +359,7 @@ int TopoShapeFacePy::PyInit(PyObject* args, PyObject* /*kwd*/) fm->Build(); getTopoShapePtr()->setShape(fm->Face()); +#endif return 0; } catch (Base::Exception &e) { e.setPyException(); @@ -420,6 +425,10 @@ PyObject* TopoShapeFacePy::addWire(PyObject *args) PyObject* TopoShapeFacePy::makeOffset(PyObject *args) { +#ifdef FC_USE_TNP_FIX + Py::Dict dict; + return TopoShapePy::makeOffset2D(args, dict.ptr()); +#else double dist; if (!PyArg_ParseTuple(args, "d",&dist)) return nullptr; @@ -434,6 +443,7 @@ PyObject* TopoShapeFacePy::makeOffset(PyObject *args) mkOffset.Perform(dist); return new TopoShapePy(new TopoShape(mkOffset.Shape())); +#endif } /* @@ -445,6 +455,9 @@ evolve = spine.makeEvolved(Profile=profile, Join=PartEnums.JoinType.Arc) */ PyObject* TopoShapeFacePy::makeEvolved(PyObject *args, PyObject *kwds) { +#ifdef FC_USE_TNP_FIX + return TopoShapePy::makeEvolved(args, kwds); +#else PyObject* Profile; PyObject* AxeProf = Py_True; PyObject* Solid = Py_False; @@ -496,6 +509,7 @@ PyObject* TopoShapeFacePy::makeEvolved(PyObject *args, PyObject *kwds) PyErr_SetString(PartExceptionOCCError, e.GetMessageString()); return nullptr; } +#endif } PyObject* TopoShapeFacePy::valueAt(PyObject *args) diff --git a/src/Mod/Part/App/TopoShapePy.xml b/src/Mod/Part/App/TopoShapePy.xml index 07d9b67d38..ac9485ba69 100644 --- a/src/Mod/Part/App/TopoShapePy.xml +++ b/src/Mod/Part/App/TopoShapePy.xml @@ -511,6 +511,11 @@ Returns: result of offsetting (wire or face or compound of those). Compounding structure follows that of source shape. + + + Profile along the spine + + make wire(s) using the edges of this shape @@ -820,6 +825,45 @@ countElement(type) -> int + + + + mapSubElement(shape|[shape...], op='') - maps the sub element of other shape + + shape: other shape or sequence of shapes to map the sub-elements + op: optional string prefix to append before the mapped sub element names + + + + + + + mapShapes(generated, modified, op='') + + generate element names with user defined mapping + + generated: a list of tuple(src, dst) that indicating src shape or shapes + generates dst shape or shapes. Note that the dst shape or shapes + must be sub-shapes of this shape. + modified: a list of tuple(src, dst) that indicating src shape or shapes + modifies into dst shape or shapes. Note that the dst shape or + shapes must be sub-shapes of this shape. + op: optional string prefix to append before the mapped sub element names + + + + + + + getElementHistory(name) - returns the element mapped name history + + name: mapped element name belonging to this shape + + Returns tuple(sourceShapeTag, sourceName, [intermediateNames...]), + or None if no history. + + + Determines a tolerance from the ones stored in a shape @@ -906,6 +950,55 @@ optimalBoundingBox([useTriangulation = True, useShapeTolerance = False]) -> boun + + + Clear internal sub-shape cache + + + + + + findSubShape(shape) -> (type_name, index) + + Find sub shape and return the shape type name and index. If not found, + then return (None, 0) + + + + + + + searchSubShape(shape, needName=False, checkGeometry=True, tol=1e-7, atol=1e-12) -> Shape + + shape: input elementary shape, currently only support Face, Edge, or Vertex + + needName: if True, return a list of tuple(name, shape), or else return a list + of shapes. + + checkGeometry: whether to compare geometry + + tol: distance tolerance + + atol: angular tolerance + + Search sub shape by checking vertex coordinates and comparing the underlying + geometries, This can find shapes that are copied. It currently only works with + elementary shapes, Face, Edge, Vertex. + + + + + + + getChildShapes(shapetype, avoidtype='') -> list(Shape) + + Return a list of child sub-shapes of given type. + + shapetype: the type of requesting sub shapes + avoidtype: optional shape type to skip when exploring + + +