Merge pull request #13196 from bgbsww/bgbsww-toponamingCompSolidPyImp

Toponaming/Part move in PyImps
This commit is contained in:
Chris Hennes
2024-04-01 11:30:44 -05:00
committed by GitHub
8 changed files with 1275 additions and 313 deletions

View File

@@ -122,26 +122,35 @@ extern const char* BRepBuilderAPI_FaceErrorText(BRepBuilderAPI_FaceError fe);
namespace Part {
PartExport void getPyShapes(PyObject *obj, std::vector<TopoShape> &shapes) {
if(!obj)
PartExport void getPyShapes(PyObject* obj, std::vector<TopoShape>& shapes)
{
if (!obj) {
return;
if(PyObject_TypeCheck(obj,&Part::TopoShapePy::Type))
}
if (PyObject_TypeCheck(obj, &Part::TopoShapePy::Type)) {
shapes.push_back(*static_cast<TopoShapePy*>(obj)->getTopoShapePtr());
else if (PyObject_TypeCheck(obj, &GeometryPy::Type))
}
else if (PyObject_TypeCheck(obj, &GeometryPy::Type)) {
shapes.emplace_back(static_cast<GeometryPy*>(obj)->getGeometryPtr()->toShape());
else if(PySequence_Check(obj)) {
}
else if (PySequence_Check(obj)) {
Py::Sequence list(obj);
for (Py::Sequence::iterator it = list.begin(); it != list.end(); ++it) {
if (PyObject_TypeCheck((*it).ptr(), &(Part::TopoShapePy::Type)))
if (PyObject_TypeCheck((*it).ptr(), &(Part::TopoShapePy::Type))) {
shapes.push_back(*static_cast<TopoShapePy*>((*it).ptr())->getTopoShapePtr());
else if (PyObject_TypeCheck((*it).ptr(), &GeometryPy::Type))
shapes.emplace_back(static_cast<GeometryPy*>(
(*it).ptr())->getGeometryPtr()->toShape());
else
}
else if (PyObject_TypeCheck((*it).ptr(), &GeometryPy::Type)) {
shapes.emplace_back(
static_cast<GeometryPy*>((*it).ptr())->getGeometryPtr()->toShape());
}
else {
throw Py::TypeError("expect shape in sequence");
}
}
}else
}
else {
throw Py::TypeError("expect shape or sequence of shapes");
}
}
PartExport std::vector<TopoShape> getPyShapes(PyObject *obj) {
@@ -952,8 +961,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 +998,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 +1053,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 +1203,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 +1313,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 +1391,8 @@ Py::Object makeFilledFace(const Py::Tuple& args)
return shape2pyshape(
TopoShape().makeElementSolid(*static_cast<TopoShapePy*>(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 +2026,8 @@ Py::Object makeSolid(const Py::Tuple& args)
shapes.push_back(*static_cast<TopoShapePy*>(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 +2077,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();
@@ -2162,6 +2171,8 @@ Py::Object makeShellFromWires(const Py::Tuple& args)
nullptr,
tolerance));
#else
if (tolerance == 0.0)
tolerance=0.001;
const TopoDS_Shape& path_shape = static_cast<TopoShapePy*>(path)->getTopoShapePtr()->getShape();
const TopoDS_Shape& prof_shape = static_cast<TopoShapePy*>(profile)->getTopoShapePtr()->getShape();
@@ -2182,6 +2193,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<const char*, 7> kwd_list =
{"shapes", "solid", "ruled", "closed", "max_degree", "op", nullptr};
@@ -2212,13 +2224,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,

View File

@@ -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<TopoShapePy*>((*it).ptr())->
getTopoShapePtr()->getShape();
if (!sh.IsNull())
const TopoDS_Shape& sh =
static_cast<TopoShapePy*>((*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<TopoShapePy*>(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

View File

@@ -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<FaceMaker> 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)

View File

@@ -511,6 +511,11 @@ Returns: result of offsetting (wire or face or compound of those). Compounding
structure follows that of source shape.</UserDocu>
</Documentation>
</Methode>
<Methode Name="makeEvolved" Const="true" Keyword="true">
<Documentation>
<UserDocu>Profile along the spine</UserDocu>
</Documentation>
</Methode>
<Methode Name="makeWires" Const="true">
<Documentation>
<UserDocu>make wire(s) using the edges of this shape
@@ -820,6 +825,45 @@ countElement(type) -> int
</UserDocu>
</Documentation>
</Methode>
<Methode Name="mapSubElement">
<Documentation>
<UserDocu>
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
</UserDocu>
</Documentation>
</Methode>
<Methode Name="mapShapes">
<Documentation>
<UserDocu>
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
</UserDocu>
</Documentation>
</Methode>
<Methode Name="getElementHistory" Const="true">
<Documentation>
<UserDocu>
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.
</UserDocu>
</Documentation>
</Methode>
<Methode Name="getTolerance" Const="true">
<Documentation>
<UserDocu>Determines a tolerance from the ones stored in a shape
@@ -906,6 +950,55 @@ optimalBoundingBox([useTriangulation = True, useShapeTolerance = False]) -> boun
</UserDocu>
</Documentation>
</Methode>
<Methode Name="clearCache" Const="true">
<Documentation>
<UserDocu>Clear internal sub-shape cache</UserDocu>
</Documentation>
</Methode>
<Methode Name="findSubShape" Const="true">
<Documentation>
<UserDocu>
findSubShape(shape) -> (type_name, index)
Find sub shape and return the shape type name and index. If not found,
then return (None, 0)
</UserDocu>
</Documentation>
</Methode>
<Methode Name="findSubShapesWithSharedVertex" Const="true" Keyword="true">
<Documentation>
<UserDocu>
findSubShapesWithSharedVertex(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.
</UserDocu>
</Documentation>
</Methode>
<Methode Name="getChildShapes" Const="true">
<Documentation>
<UserDocu>
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
</UserDocu>
</Documentation>
</Methode>
<!--
<Attribute Name="Location" ReadOnly="false">
<Documentation>

View File

@@ -95,6 +95,7 @@
#include "OCCError.h"
#include "PartPyCXX.h"
#include "ShapeMapHasher.h"
#include "TopoShapeMapper.h"
using namespace Part;
@@ -108,20 +109,26 @@ using namespace Part;
#endif
#ifdef FC_USE_TNP_FIX
static Py_hash_t _TopoShapeHash(PyObject *self) {
static Py_hash_t _TopoShapeHash(PyObject* self)
{
if (!self) {
PyErr_SetString(PyExc_TypeError, "descriptor 'hash' of 'Part.TopoShape' object needs an argument");
PyErr_SetString(PyExc_TypeError,
"descriptor 'hash' of 'Part.TopoShape' object needs an argument");
return 0;
}
if (!static_cast<Base::PyObjectBase*>(self)->isValid()) {
PyErr_SetString(PyExc_ReferenceError, "This object is already deleted most likely through closing a document. This reference is no longer valid!");
PyErr_SetString(PyExc_ReferenceError,
"This object is already deleted most likely through closing a document. "
"This reference is no longer valid!");
return 0;
}
return static_cast<TopoShapePy*>(self)->getTopoShapePtr()->getShape().HashCode(INT_MAX);
}
struct TopoShapePyInit {
TopoShapePyInit() {
struct TopoShapePyInit
{
TopoShapePyInit()
{
TopoShapePy::Type.tp_hash = _TopoShapeHash;
}
} _TopoShapePyInit;
@@ -192,6 +199,7 @@ int TopoShapePy::PyInit(PyObject* args, PyObject* keywds)
}
_PY_CATCH_OCC(return (-1))
#else
(void) keywds;
PyObject* pcObj = nullptr;
if (!PyArg_ParseTuple(args, "|O", &pcObj)) {
return -1;
@@ -236,6 +244,35 @@ 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,
&copyGeom,
&PyBool_Type,
&copyMesh)) {
PyErr_Clear();
if (!PyArg_ParseTuple(args, "|O!O!", &PyBool_Type, &copyGeom, &PyBool_Type, &copyMesh)) {
return 0;
}
}
if (op && !op[0]) {
op = nullptr;
}
App::StringHasherRef hasher;
if (pyHasher) {
hasher = static_cast<App::StringHasherPy*>(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, &copyGeom, &PyBool_Type, &copyMesh))
return nullptr;
@@ -255,12 +292,21 @@ PyObject* TopoShapePy::copy(PyObject *args)
static_cast<TopoShapePy*>(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 +326,7 @@ PyObject* TopoShapePy::cleaned(PyObject *args)
static_cast<TopoShapePy*>(cpy)->getTopoShapePtr()->setShape(c.Shape());
}
return cpy;
#endif
}
PyObject* TopoShapePy::replaceShape(PyObject *args)
@@ -289,6 +336,18 @@ PyObject* TopoShapePy::replaceShape(PyObject *args)
return nullptr;
try {
#ifdef FC_USE_TNP_FIX
Py::Sequence list(l);
std::vector<std::pair<TopoShape, TopoShape>> 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<TopoDS_Shape, TopoDS_Shape> > shapes;
for (Py::Sequence::iterator it = list.begin(); it != list.end(); ++it) {
@@ -305,6 +364,7 @@ PyObject* TopoShapePy::replaceShape(PyObject *args)
static_cast<TopoShapePy*>(inst)->getTopoShapePtr()->setShape
(this->getTopoShapePtr()->replaceShape(shapes));
return inst;
#endif
}
catch (const Py::Exception&) {
return nullptr;
@@ -322,6 +382,10 @@ 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<TopoDS_Shape> shapes;
for (Py::Sequence::iterator it = list.begin(); it != list.end(); ++it) {
@@ -333,6 +397,7 @@ PyObject* TopoShapePy::removeShape(PyObject *args)
static_cast<TopoShapePy*>(inst)->getTopoShapePtr()->setShape
(this->getTopoShapePtr()->removeShape(shapes));
return inst;
#endif
}
catch (...) {
PyErr_SetString(PartExceptionOCCError, "failed to remove shape");
@@ -681,6 +746,10 @@ PyObject* TopoShapePy::extrude(PyObject *args)
try {
Base::Vector3d vec = static_cast<Base::VectorPy*>(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 +777,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 +791,14 @@ 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<Base::VectorPy*>(pPos)->value();
Base::Vector3d dir = static_cast<Base::VectorPy*>(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 +817,6 @@ PyObject* TopoShapePy::revolve(PyObject *args)
return nullptr;
}
Base::Vector3d pos = static_cast<Base::VectorPy*>(pPos)->value();
Base::Vector3d dir = static_cast<Base::VectorPy*>(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 +846,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 +871,26 @@ PyObject* TopoShapePy::check(PyObject *args)
Py_Return;
}
#ifdef FC_USE_TNP_FIX
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<TopoShape> shapes;
shapes.push_back(shape);
getPyShapes(pcObj,shapes);
return Py::new_reference_to(shape2pyshape(TopoShape().makeElementBoolean(op,shapes,0,tol)));
} PY_CATCH_OCC
}
#endif
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<TopoShapePy*>(pcObj)->getTopoShapePtr()->getShape();
@@ -867,10 +960,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 +997,7 @@ PyObject* TopoShapePy::multiFuse(PyObject *args)
PyErr_SetString(PartExceptionOCCError, e.what());
return nullptr;
}
#endif
}
PyObject* TopoShapePy::oldFuse(PyObject *args)
@@ -926,6 +1024,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<TopoShapePy*>(pcObj)->getTopoShapePtr()->getShape();
@@ -993,10 +1094,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 +1170,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 +1180,16 @@ 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<TopoDS_Wire> slice = this->getTopoShapePtr()->slice(vec, d);
Py::List wire;
for (const auto & it : slice) {
@@ -1083,6 +1197,7 @@ PyObject* TopoShapePy::slice(PyObject *args)
}
return Py::new_reference_to(wire);
#endif
}
catch (Standard_Failure& e) {
@@ -1108,8 +1223,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));
#ifdef FC_USE_TNP_FIX
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 +1242,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<TopoShapePy*>(pcObj)->getTopoShapePtr()->getShape();
@@ -1190,6 +1312,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 +1322,29 @@ PyObject* TopoShapePy::generalFuse(PyObject *args)
if (!PyArg_ParseTuple(args, "O|d", &pcObj, &tolerance))
return nullptr;
#ifdef FC_USE_TNP_FIX
std::vector<std::vector<TopoShape>> modifies;
std::vector<TopoShape> 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<TopoDS_Shape> shapeVec;
Py::Sequence shapeSeq(pcObj);
for (Py::Sequence::iterator it = shapeSeq.begin(); it != shapeSeq.end(); ++it) {
@@ -1236,6 +1382,7 @@ PyObject* TopoShapePy::generalFuse(PyObject *args)
PyErr_SetString(PartExceptionOCCError, e.what());
return nullptr;
}
#endif
}
PyObject* TopoShapePy::sewShape(PyObject *args)
@@ -1261,6 +1408,24 @@ 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 +1481,7 @@ PyObject* TopoShapePy::childShapes(PyObject *args)
PyErr_SetString(PartExceptionOCCError, e.GetMessageString());
return nullptr;
}
#endif
}
namespace Part {
@@ -1423,8 +1589,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 +1745,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 +1780,25 @@ 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 +1820,7 @@ PyObject* TopoShapePy::makeFillet(PyObject *args)
return nullptr;
}
}
#endif
PyErr_Clear();
// use one radius for all edges
double radius;
@@ -1663,6 +1857,26 @@ 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 +1903,7 @@ PyObject* TopoShapePy::makeChamfer(PyObject *args)
return nullptr;
}
}
#endif
PyErr_Clear();
// use one radius for all edges
double radius;
@@ -1738,6 +1952,16 @@ PyObject* TopoShapePy::makeThickness(PyObject *args)
return nullptr;
try {
#ifdef FC_USE_TNP_FIX
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<JoinType>(join))));
#else
TopTools_ListOfShape facesToRemove;
Py::Sequence list(obj);
for (Py::Sequence::iterator it = list.begin(); it != list.end(); ++it) {
@@ -1750,6 +1974,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 +1998,22 @@ 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<JoinType>(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 +2036,18 @@ PyObject* TopoShapePy::makeOffset2D(PyObject *args, PyObject *keywds)
}
try {
#ifdef FC_USE_TNP_FIX
return Py::new_reference_to(shape2pyshape(getTopoShapePtr()->makeElementOffset2D(
offset,
static_cast<JoinType>(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;
}
@@ -2217,6 +2462,32 @@ PyObject* TopoShapePy::makeShapeFromMesh(PyObject *args)
PY_CATCH_OCC
}
PyObject* TopoShapePy::makeEvolved(PyObject *args, PyObject *kwds)
{
PyObject* Profile;
PyObject* AxeProf = Py_True;
PyObject* Solid = Py_False;
PyObject* ProfOnSpine = Py_False;
auto JoinType = JoinType::arc;
double Tolerance = 0.0000001;
static char* kwds_evolve[] = {"Profile", "Join", "AxeProf", "Solid", "ProfOnSpine", "Tolerance", nullptr};
if (!PyArg_ParseTupleAndKeywords(args, kwds, "O!|iO!O!O!d", kwds_evolve,
&TopoShapePy::Type, &Profile, &JoinType,
&PyBool_Type, &AxeProf, &PyBool_Type, &Solid,
&PyBool_Type, &ProfOnSpine, &Tolerance))
return nullptr;
try {
return Py::new_reference_to(shape2pyshape(getTopoShapePtr()->makeElementEvolve(
*static_cast<TopoShapePy*>(Profile)->getTopoShapePtr(), JoinType,
PyObject_IsTrue(AxeProf) ? CoordinateSystem::global : CoordinateSystem::relativeToSpine,
PyObject_IsTrue(Solid) ? MakeSolid::makeSolid : MakeSolid::noSolid,
PyObject_IsTrue(ProfOnSpine) ? Spine::on : Spine::notOn,
Tolerance)));
} PY_CATCH_OCC
}
PyObject* TopoShapePy::makeWires(PyObject *args) {
const char *op = nullptr;
if (!PyArg_ParseTuple(args, "s", &op))
@@ -2304,9 +2575,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());
@@ -2779,6 +3054,15 @@ PyObject* TopoShapePy::optimalBoundingBox(PyObject *args)
}
}
PyObject* TopoShapePy::clearCache(PyObject* args)
{
if (!PyArg_ParseTuple(args, "")) {
return 0;
}
getTopoShapePtr()->initCache(1);
return IncRef();
}
PyObject* TopoShapePy::defeaturing(PyObject *args)
{
PyObject *l;
@@ -2806,6 +3090,88 @@ PyObject* TopoShapePy::defeaturing(PyObject *args)
}
}
PyObject* TopoShapePy::findSubShape(PyObject* args)
{
PyObject* pyobj;
if (!PyArg_ParseTuple(args, "O", &pyobj)) {
return nullptr;
}
PY_TRY
{
Py::List res;
for (auto& s : getPyShapes(pyobj)) {
int index = getTopoShapePtr()->findShape(s.getShape());
if (index > 0) {
res.append(Py::TupleN(Py::String(s.shapeName()), Py::Int(index)));
}
else {
res.append(Py::TupleN(Py::Object(), Py::Int(0)));
}
}
if (PySequence_Check(pyobj)) {
return Py::new_reference_to(res);
}
return Py::new_reference_to(Py::Object(res[0].ptr()));
}
PY_CATCH_OCC
}
PyObject* TopoShapePy::findSubShapesWithSharedVertex(PyObject* args, PyObject* keywds)
{
static char* kwlist[] = {"shape", "needName", "checkGeometry", "tol", "atol", nullptr};
PyObject* pyobj;
PyObject* needName = Py_False;
PyObject* checkGeometry = Py_True;
double tol = 1e-7;
double atol = 1e-12;
if (!PyArg_ParseTupleAndKeywords(args,
keywds,
"O!|OOdd",
kwlist,
&Type,
&pyobj,
&needName,
&checkGeometry,
&tol,
&atol)) {
return nullptr;
}
PY_TRY
{
Py::List res;
const TopoShape& shape = *static_cast<TopoShapePy*>(pyobj)->getTopoShapePtr();
if (PyObject_IsTrue(needName)) {
std::vector<std::string> names;
auto shapes = getTopoShapePtr()->findSubShapesWithSharedVertex(
shape,
&names,
PyObject_IsTrue(checkGeometry) ? CheckGeometry::checkGeometry
: CheckGeometry::ignoreGeometry,
tol,
atol);
for (std::size_t i = 0; i < shapes.size(); ++i) {
res.append(Py::TupleN(Py::String(names[i]), shape2pyshape(shapes[i])));
}
}
else {
for (auto& s : getTopoShapePtr()->findSubShapesWithSharedVertex(
shape,
nullptr,
PyObject_IsTrue(checkGeometry) ? CheckGeometry::checkGeometry
: CheckGeometry::ignoreGeometry,
tol,
atol)) {
res.append(shape2pyshape(s));
}
}
return Py::new_reference_to(res);
}
PY_CATCH_OCC
}
// End of Methods, Start of Attributes
Py::String TopoShapePy::getShapeType() const
{
@@ -2912,23 +3278,23 @@ getElements(const TopoShape& sh, TopAbs_ShapeEnum type, TopAbs_ShapeEnum avoid =
return ret;
}
// Todo: Toponaming Project March, 2024: This code is defined, appears to be correct, but
// is not clearly in use, thus commented out:
//
//PyObject *TopoShapePy::getChildShapes(PyObject *args)
//{
// const char *type;
// const char *avoid = nullptr;
// if (!PyArg_ParseTuple(args, "s|s", &type, &avoid))
// return nullptr;
//
// PY_TRY {
// return Py::new_reference_to(
// getElements(*getTopoShapePtr(),
// TopoShape::shapeType(type),
// avoid && avoid[0] ? TopoShape::shapeType(avoid) : TopAbs_SHAPE));
// }PY_CATCH_OCC;
//}
PyObject* TopoShapePy::getChildShapes(PyObject* args)
{
const char* type;
const char* avoid = nullptr;
if (!PyArg_ParseTuple(args, "s|s", &type, &avoid)) {
return nullptr;
}
PY_TRY
{
return Py::new_reference_to(
getElements(*getTopoShapePtr(),
TopoShape::shapeType(type),
avoid && avoid[0] ? TopoShape::shapeType(avoid) : TopAbs_SHAPE));
}
PY_CATCH_OCC;
}
Py::List TopoShapePy::getSubShapes() const
{
@@ -3005,14 +3371,121 @@ Py::Float TopoShapePy::getVolume() const
return Py::Float(props.Mass());
}
PyObject *TopoShapePy::getCustomAttributes(const char* attr) const
PyObject* TopoShapePy::getElementHistory(PyObject* args)
{
if (!attr)
const char* pyname;
if (!PyArg_ParseTuple(args, "s", &pyname)) {
return 0;
}
Data::MappedName name(pyname);
PY_TRY
{
Data::MappedName original;
std::vector<Data::MappedName> history;
long tag = getTopoShapePtr()->getElementHistory(name, &original, &history);
if (!tag) {
Py_Return;
}
Py::Tuple ret(3);
ret.setItem(0, Py::Int(tag));
std::string tmp;
ret.setItem(1, Py::String(original.appendToBuffer(tmp)));
Py::List pyHistory;
for (auto& h : history) {
tmp.clear();
pyHistory.append(Py::String(h.appendToBuffer(tmp)));
}
ret.setItem(2, pyHistory);
return Py::new_reference_to(ret);
}
PY_CATCH_OCC
}
struct PyShapeMapper: Part::ShapeMapper
{
bool populate(MappingStatus status, PyObject* pyobj)
{
if (!pyobj || pyobj == Py_None) {
return true;
}
try {
Py::Sequence seq(pyobj);
for (size_t i = 0, count = seq.size(); i < count; ++i) {
Py::Sequence item(seq[i].ptr());
if (item.size() != 2) {
return false;
}
Part::ShapeMapper::populate(status,
getPyShapes(item[0].ptr()),
getPyShapes(item[1].ptr()));
}
}
catch (Py::Exception&) {
PyErr_Clear();
return false;
}
return true;
}
void init(PyObject* g, PyObject* m)
{
const char* msg =
"Expect input mapping to be a list of tuple(srcShape|shapeList, dstShape|shapeList)";
if (!populate(MappingStatus::Generated, g) || !populate(MappingStatus::Modified, m)) {
throw Py::TypeError(msg);
}
}
};
PyObject* TopoShapePy::mapShapes(PyObject* args)
{
PyObject* generated;
PyObject* modified;
const char* op = nullptr;
if (!PyArg_ParseTuple(args, "OO|s", &generated, &modified, &op)) {
return 0;
}
PY_TRY
{
PyShapeMapper mapper;
mapper.init(generated, modified);
TopoShape& self = *getTopoShapePtr();
TopoShape s(self.Tag, self.Hasher);
s.makeShapeWithElementMap(self.getShape(), mapper, mapper.shapes, op);
self = s;
return IncRef();
}
PY_CATCH_OCC
}
PyObject* TopoShapePy::mapSubElement(PyObject* args)
{
const char* op = nullptr;
PyObject* sh;
if (!PyArg_ParseTuple(args, "O|s", &sh, &op)) {
return 0;
}
PY_TRY
{
getTopoShapePtr()->mapSubElement(getPyShapes(sh), op);
return IncRef();
}
PY_CATCH_OCC
}
PyObject* TopoShapePy::getCustomAttributes(const char* attr) const
{
if (!attr) {
return nullptr;
PY_TRY {
TopoDS_Shape res = getTopoShapePtr()->getSubShape(attr,true);
if (!res.IsNull())
}
PY_TRY
{
TopoDS_Shape res = getTopoShapePtr()->getSubShape(attr, true);
if (!res.IsNull()) {
return Py::new_reference_to(shape2pyshape(res));
}
}
PY_CATCH_OCC
return nullptr;

View File

@@ -38,6 +38,7 @@
#include <Base/VectorPy.h>
#include "OCCError.h"
#include "PartPyCXX.h"
#include "Tools.h"
#include "TopoShapeCompoundPy.h"
#include "TopoShapeCompoundPy.h"
@@ -45,7 +46,7 @@
#include "TopoShapeShellPy.h"
#include "TopoShapeShellPy.cpp"
#include "TopoShapeSolidPy.h"
#include "TopoShapeOpCode.h"
using namespace Part;
@@ -85,6 +86,12 @@ int TopoShapeShellPy::PyInit(PyObject* args, PyObject* /*kwd*/)
if (!PyArg_ParseTuple(args, "O", &obj))
return -1;
#ifdef FC_USE_TNP_FIX
try {
getTopoShapePtr()->makeElementBoolean(Part::OpCodes::Shell, getPyShapes(obj));
}
_PY_CATCH_OCC(return (-1))
#else
BRep_Builder builder;
TopoDS_Shape shape;
TopoDS_Shell shell;
@@ -121,6 +128,7 @@ int TopoShapeShellPy::PyInit(PyObject* args, PyObject* /*kwd*/)
}
getTopoShapePtr()->setShape(shape);
#endif
return 0;
}
@@ -134,11 +142,14 @@ PyObject* TopoShapeShellPy::add(PyObject *args)
TopoDS_Shape shell = getTopoShapePtr()->getShape();
try {
const TopoDS_Shape& sh = static_cast<TopoShapeFacePy*>(obj)->
getTopoShapePtr()->getShape();
const TopoShape& shape = *static_cast<TopoShapeFacePy*>(obj)->getTopoShapePtr();
const auto &sh = shape.getShape();
if (!sh.IsNull()) {
builder.Add(shell, sh);
BRepCheck_Analyzer check(shell);
#ifdef FC_USE_TNP_FIX
getTopoShapePtr()->mapSubElement(shape);
#endif
if (!check.IsValid()) {
ShapeUpgrade_ShellSewing sewShell;
getTopoShapePtr()->setShape(sewShell.ApplySewing(shell));
@@ -167,7 +178,14 @@ PyObject* TopoShapeShellPy::getFreeEdges(PyObject *args)
as.CheckOrientedShells(getTopoShapePtr()->getShape(), Standard_True, Standard_True);
TopoDS_Compound comp = as.FreeEdges();
#ifdef FC_USE_TNP_FIX
TopoShape res;
res.setShape(comp);
res.mapSubElement(*getTopoShapePtr());
return Py::new_reference_to(shape2pyshape(res));
#else
return new TopoShapeCompoundPy(new TopoShape(comp));
#endif
}
PyObject* TopoShapeShellPy::getBadEdges(PyObject *args)
@@ -179,7 +197,14 @@ PyObject* TopoShapeShellPy::getBadEdges(PyObject *args)
as.CheckOrientedShells(getTopoShapePtr()->getShape(), Standard_True, Standard_True);
TopoDS_Compound comp = as.BadEdges();
#ifdef FC_USE_TNP_FIX
TopoShape res;
res.setShape(comp);
res.mapSubElement(*getTopoShapePtr());
return Py::new_reference_to(shape2pyshape(res));
#else
return new TopoShapeCompoundPy(new TopoShape(comp));
#endif
}
PyObject* TopoShapeShellPy::makeHalfSpace(PyObject *args)

View File

@@ -47,6 +47,7 @@
#include <Base/VectorPy.h>
#include "OCCError.h"
#include "PartPyCXX.h"
#include "Tools.h"
// inclusion of the generated files (generated out of TopoShapeSolidPy.xml)
@@ -87,6 +88,9 @@ int TopoShapeSolidPy::PyInit(PyObject* args, PyObject* /*kwd*/)
return -1;
try {
#ifdef FC_USE_TNP_FIX
getTopoShapePtr()->makeElementSolid(*static_cast<TopoShapePy*>(obj)->getTopoShapePtr());
#else
const TopoDS_Shape& shape = static_cast<TopoShapePy*>(obj)
->getTopoShapePtr()->getShape();
//first, if we were given a compsolid, try making a solid out of it
@@ -122,7 +126,7 @@ int TopoShapeSolidPy::PyInit(PyObject* args, PyObject* /*kwd*/)
} else /*if (count > 1)*/ {
Standard_Failure::Raise("Only one compsolid can be accepted. Provided shape has more than one compsolid.");
}
#endif
}
catch (Standard_Failure& err) {
std::stringstream errmsg;
@@ -216,7 +220,14 @@ Py::Object TopoShapeSolidPy::getOuterShell() const
const TopoDS_Shape& shape = getTopoShapePtr()->getShape();
if (!shape.IsNull() && shape.ShapeType() == TopAbs_SOLID)
shell = BRepClass3d::OuterShell(TopoDS::Solid(shape));
#ifdef FC_USE_TNP_FIX
TopoShape res;
res.setShape(shell);
res.mapSubElement(*getTopoShapePtr());
return shape2pyshape(res);
#else
return Py::Object(new TopoShapeShellPy(new TopoShape(shell)),true);
#endif
}
PyObject* TopoShapeSolidPy::getMomentOfInertia(PyObject *args)
@@ -316,7 +327,13 @@ PyObject* TopoShapeSolidPy::offsetFaces(PyObject *args)
try {
builder.MakeOffsetShape();
const TopoDS_Shape& offsetshape = builder.Shape();
#ifdef FC_USE_TNP_FIX
TopoShape res;
res.setShape(offsetshape);
return Py::new_reference_to(shape2pyshape(res));
#else
return new TopoShapeSolidPy(new TopoShape(offsetshape));
#endif
}
catch (Standard_Failure& e) {

View File

@@ -3,6 +3,7 @@ import Part
import unittest
class TopoShapeAssertions:
def assertAttrEqual(self, toposhape, attr_value_list, msg=None):
@@ -42,6 +43,19 @@ 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,290 +175,583 @@ class TopoShapeTest(unittest.TestCase, TopoShapeAssertions):
]
self.doc.recompute()
compound2 = self.doc.Compound.Shape
# Assert
# This is a flag value to indicate that ElementMaps are supported under the current C++ build:
# Assert elementMap
# This flag indicates 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
# Todo: This should contain something as soon as the Python interface for Part.Compound TNP exists
# self.assertEqual(len(compound1.ElementMap), 52, "ElementMap is Incorrect: {0}".format(compound1.ElementMap))
# Todo: This should contain something as soon as the Python interface
# for Part.Compound TNP exists
# self.assertEqual(len(compound1.ElementMap), 52,
# "ElementMap is Incorrect: {0}".format(compound1.ElementMap))
self.assertEqual(
compound2.ElementMapSize,
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,
[
"Edge1",
"Edge2",
"Edge3",
"Edge4",
"Edge5",
"Edge6",
"Edge7",
"Edge8",
"Edge9",
"Edge10",
"Edge11",
"Edge12",
"Face1",
"Face2",
"Face3",
"Face4",
"Face5",
"Face6",
"Vertex1",
"Vertex2",
"Vertex3",
"Vertex4",
"Vertex5",
"Vertex6",
"Vertex7",
"Vertex8",
],
)
common1 = self.doc.Common.Shape
# Assert elementMap
if common1.ElementMapVersion != "": # Should be '4' as of Mar 2023.
self.assertKeysInMap(common1.ElementReverseMap,
[
"Edge1",
"Edge2",
"Edge3",
"Edge4",
"Edge5",
"Edge6",
"Edge7",
"Edge8",
"Edge9",
"Edge10",
"Edge11",
"Edge12",
"Face1",
"Face2",
"Face3",
"Face4",
"Face5",
"Face6",
"Vertex1",
"Vertex2",
"Vertex3",
"Vertex4",
"Vertex5",
"Vertex6",
"Vertex7",
"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,
[
"Edge1",
"Edge2",
"Edge3",
"Edge4",
"Edge5",
"Edge6",
"Edge7",
"Edge8",
"Edge9",
"Edge10",
"Edge11",
"Edge12",
"Face1",
"Face2",
"Face3",
"Face4",
"Face5",
"Face6",
"Vertex1",
"Vertex2",
"Vertex3",
"Vertex4",
"Vertex5",
"Vertex6",
"Vertex7",
"Vertex8",
],
)
cut1 = self.doc.Cut.Shape
# Assert elementMap
if cut1.ElementMapVersion != "": # Should be '4' as of Mar 2023.
self.assertKeysInMap(cut1.ElementReverseMap,
[
"Edge1",
"Edge2",
"Edge3",
"Edge4",
"Edge5",
"Edge6",
"Edge7",
"Edge8",
"Edge9",
"Edge10",
"Edge11",
"Edge12",
"Face1",
"Face2",
"Face3",
"Face4",
"Face5",
"Face6",
"Vertex1",
"Vertex2",
"Vertex3",
"Vertex4",
"Vertex5",
"Vertex6",
"Vertex7",
"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):
face1 = Part.makeFace(self.doc.Box1.Shape.Faces[0],"Part::FaceMakerCheese")
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):
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.
shell1 = Part.makeShellFromWires([wire1,wire2])
def testAppPartMakeShellFromWires(self):
# Arrange
wire1 = self.doc.Box1.Shape.Wires[0] # .copy() Todo: prints 2 gen/mod warn because
wire2 = self.doc.Box1.Shape.Wires[1] # Todo: copy() isn't TNP 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), precision=3)
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.
edge1 = self.doc.Box1.Shape.Faces[0].Edges[0].translated(App.Vector(0,0.5,0))
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]
solids1 = Part.makeSplitShape(face1,[(edge1,face1)])
# 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):
# Arrange
self.doc.addObject("Part::Compound", "Compound")
self.doc.Compound.Links = [
App.activeDocument().Box1,
App.activeDocument().Box2,
]
self.doc.recompute()
compound = self.doc.Compound.Shape
# Act
new_toposhape = Part.Shape(compound)
new_empty_toposhape = Part.Shape()
# Assert elementMap
if compound.ElementMapVersion != "": # Should be '4' as of Mar 2023.
self.assertEqual(compound.ElementMapSize, 52)
self.assertEqual(new_toposhape.ElementMapSize, 52)
def testTopoShapeCopy(self):
# Arrange
self.doc.addObject("Part::Compound", "Compound")
self.doc.Compound.Links = [
App.activeDocument().Box1,
App.activeDocument().Box2,
]
self.doc.recompute()
compound = self.doc.Compound.Shape
# Act
compound_copy = compound.copy()
# Assert elementMap
if compound.ElementMapVersion != "": # Should be '4' as of Mar 2023.
self.assertEqual(compound.ElementMapSize, 52)
self.assertEqual(compound_copy.ElementMapSize, 52)
def testTopoShapeCleaned(self):
# Arrange
self.doc.addObject("Part::Compound", "Compound")
self.doc.Compound.Links = [
App.activeDocument().Box1,
App.activeDocument().Box2,
]
self.doc.recompute()
compound = self.doc.Compound.Shape
# Act
compound_cleaned = compound.cleaned()
# Assert elementMap
if compound.ElementMapVersion != "": # Should be '4' as of Mar 2023.
self.assertEqual(compound.ElementMapSize, 52)
self.assertEqual(compound_cleaned.ElementMapSize, 52)
def testTopoShapeReplaceShape(self):
# Arrange
self.doc.addObject("Part::Compound", "Compound")
self.doc.Compound.Links = [
App.activeDocument().Box1,
App.activeDocument().Box2,
]
self.doc.recompute()
compound = self.doc.Compound.Shape
# Act
compound_replaced = compound.replaceShape([(App.activeDocument().Box2.Shape,
App.activeDocument().Box1.Shape)])
# Assert elementMap
if compound.ElementMapVersion != "": # Should be '4' as of Mar 2023.
self.assertEqual(compound.ElementMapSize, 52)
self.assertEqual(compound_replaced.ElementMapSize, 52)
def testTopoShapeRemoveShape(self):
# Arrange
self.doc.addObject("Part::Compound", "Compound")
self.doc.Compound.Links = [
App.activeDocument().Box1,
App.activeDocument().Box2,
]
self.doc.recompute()
compound = self.doc.Compound.Shape
# Act
compound_removed = compound.removeShape([App.ActiveDocument.Box2.Shape])
# Assert elementMap
if compound.ElementMapVersion != "": # Should be '4' as of Mar 2023.
self.assertEqual(compound.ElementMapSize, 52)
self.assertEqual(compound_removed.ElementMapSize, 52)
def testTopoShapeExtrude(self):
# Arrange
face = self.doc.Box1.Shape.Faces[0]
# Act
extrude = face.extrude(App.Vector(2, 0, 0))
self.doc.recompute()
# Assert elementMap
if extrude.ElementMapVersion != "": # Should be '4' as of Mar 2023.
self.assertEqual(extrude.ElementMapSize, 26)
def testTopoShapeRevolve(self):
# Arrange
face = self.doc.Box1.Shape.Faces[0]
# Act
face.revolve(App.Vector(), App.Vector(1, 0, 0), 45)
self.doc.recompute()
# Assert elementMap
if face.ElementMapVersion != "": # Should be '4' as of Mar 2023.
self.assertEqual(face.ElementMapSize, 9)
def testTopoShapeFuse(self):
# Act
fused = self.doc.Box1.Shape.fuse(self.doc.Box2.Shape)
self.doc.recompute()
# Assert elementMap
if fused.ElementMapVersion != "": # Should be '4' as of Mar 2023.
self.assertEqual(fused.ElementMapSize, 58)
def testTopoShapeMultiFuse(self):
# Act
fused = self.doc.Box1.Shape.multiFuse([self.doc.Box2.Shape])
self.doc.recompute()
# Assert elementMap
if fused.ElementMapVersion != "": # Should be '4' as of Mar 2023.
self.assertEqual(fused.ElementMapSize, 58)
def testTopoShapeCommon(self):
# Act
common = self.doc.Box1.Shape.common(self.doc.Box2.Shape)
self.doc.recompute()
# Assert elementMap
if common.ElementMapVersion != "": # Should be '4' as of Mar 2023.
self.assertEqual(common.ElementMapSize, 26)
def testTopoShapeSection(self):
# Act
section = self.doc.Box1.Shape.Faces[0].section(self.doc.Box2.Shape.Faces[3])
self.doc.recompute()
# Assert elementMap
if section.ElementMapVersion != "": # Should be '4' as of Mar 2023.
self.assertEqual(section.ElementMapSize, 3)
def testTopoShapeSlice(self):
# Act
slice = self.doc.Box1.Shape.slice(App.Vector(10, 10, 0), 1)
self.doc.recompute()
# Assert elementMap
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):
# Act
slices = self.doc.Box1.Shape.Faces[0].slices(App.Vector(10, 10, 0), [1, 2])
self.doc.recompute()
# Assert elementMap
if slices.ElementMapVersion != "": # Should be '4' as of Mar 2023.
self.assertEqual(slices.ElementMapSize, 6)
def testTopoShapeCut(self):
# Act
cut = self.doc.Box1.Shape.cut(self.doc.Box2.Shape)
self.doc.recompute()
# Assert elementMap
if cut.ElementMapVersion != "": # Should be '4' as of Mar 2023.
self.assertEqual(cut.ElementMapSize, 26)
def testTopoShapeGeneralFuse(self):
# Act
fuse = self.doc.Box1.Shape.generalFuse([self.doc.Box2.Shape])
self.doc.recompute()
# Assert elementMap
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):
# Act
childShapes = self.doc.Box1.Shape.childShapes()
self.doc.recompute()
# Assert elementMap
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):
# Act
mirror = self.doc.Box1.Shape.mirror(App.Vector(), App.Vector(1, 0, 0))
self.doc.recompute()
# Assert elementMap
if mirror.ElementMapVersion != "": # Should be '4' as of Mar 2023.
self.assertEqual(mirror.ElementMapSize, 26)
def testTopoShapeScale(self):
# Act
scale = self.doc.Box1.Shape.scaled(2)
self.doc.recompute()
# Assert elementMap
if scale.ElementMapVersion != "": # Should be '4' as of Mar 2023.
self.assertEqual(scale.ElementMapSize, 26)
def testTopoShapeMakeFillet(self):
# Act
fillet = self.doc.Box1.Shape.makeFillet(0.1, self.doc.Box1.Shape.Faces[0].Edges)
self.doc.recompute()
# Assert elementMap
if fillet.ElementMapVersion != "": # Should be '4' as of Mar 2023.
self.assertEqual(fillet.ElementMapSize, 42)
def testTopoShapeMakeChamfer(self):
# Act
chamfer = self.doc.Box1.Shape.makeChamfer(0.1, self.doc.Box1.Shape.Faces[0].Edges)
self.doc.recompute()
# Assert elementMap
if chamfer.ElementMapVersion != "": # Should be '4' as of Mar 2023.
self.assertEqual(chamfer.ElementMapSize, 42)
def testTopoShapeMakeThickness(self):
# Act
thickness = self.doc.Box1.Shape.makeThickness(self.doc.Box1.Shape.Faces[0:2], 0.1, 0.0001)
self.doc.recompute()
# Assert elementMap
if thickness.ElementMapVersion != "": # Should be '4' as of Mar 2023.
self.assertEqual(thickness.ElementMapSize, 74)
def testTopoShapeMakeOffsetShape(self):
# Act
offset = self.doc.Box1.Shape.Faces[0].makeOffset(1)
self.doc.recompute()
# Assert elementMap
if offset.ElementMapVersion != "": # Should be '4' as of Mar 2023.
self.assertEqual(offset.ElementMapSize, 17)
def testTopoShapeOffset2D(self):
# Act
offset = self.doc.Box1.Shape.Faces[0].makeOffset2D(1)
self.doc.recompute()
# Assert elementMap
if offset.ElementMapVersion != "": # Should be '4' as of Mar 2023.
self.assertEqual(offset.ElementMapSize, 17)
def testTopoShapeRemoveSplitter(self):
# Act
fused = self.doc.Box1.Shape.fuse(self.doc.Box2.Shape)
removed = fused.removeSplitter()
self.doc.recompute()
# Assert elementMap
if removed.ElementMapVersion != "": # Should be '4' as of Mar 2023.
self.assertEqual(removed.ElementMapSize, 38)
def testTopoShapeCompSolid(self):
# Act
compSolid = Part.CompSolid([self.doc.Box1.Shape, self.doc.Box2.Shape]) # list of subobjects
box1ts = self.doc.Box1.Shape
compSolid.add(box1ts.Solids[0])
# Assert elementMap
if compSolid.ElementMapVersion != "": # Should be '4' as of Mar 2023.
self.assertEqual(compSolid.ElementMapSize, 78)
def testTopoShapeFaceOffset(self):
# Arrange
box_toposhape = self.doc.Box1.Shape
# Act
offset = box_toposhape.Faces[0].makeOffset(2.0)
# Assert elementMap
if box_toposhape.Faces[0].ElementMapVersion != "": # Should be '4' as of Mar 2023.
self.assertEqual(box_toposhape.Faces[0].ElementMapSize, 9) # 1 Face, 4 Edges, 4 Vertexes
self.assertEqual(offset.ElementMapSize, 17) # 1 Face, 8 Edges, 8 Vertexes
# Todo: makeEvolved doesn't work right, probably due to missing c++ code.
# def testTopoShapeFaceEvolve(self):
# # Arrange
# box_toposhape = self.doc.Box1.Shape
# # Act
# evolved = box_toposhape.Faces[0].makeEvolved(self.doc.Box1.Shape.Wires[1]) # 2,3,4,5 bad
# # Assert elementMap
# if box_toposhape.Faces[0].ElementMapVersion != "": # Should be '4' as of Mar 2023.
# self.assertEqual(box_toposhape.Faces[0].ElementMapSize, 9) # 1 Face, 4 Edges, 4 Vertexes
# self.assertEqual(evolved.ElementMapSize, 0) # Todo: This can't be correct.
def testTopoShapePart(self):
# Arrange
box1ts = self.doc.Box1.Shape
face1 = box1ts.Faces[0]
box1ts2 = box1ts.copy()
# Act
face2 = box1ts.getElement("Face2")
indexed_name = box1ts.findSubShape(face1)
faces1 = box1ts.findSubShapesWithSharedVertex(face2)
subshapes1 = box1ts.getChildShapes("Solid1")
# box1ts.clearCache() # Todo: no apparent way to see a result at this level
# Assert
self.assertTrue(face2.isSame(box1ts.Faces[1]))
self.assertEqual(indexed_name[0], "Face")
self.assertEqual(indexed_name[1], 1)
self.assertEqual(len(faces1), 1)
self.assertTrue(faces1[0].isSame(box1ts.Faces[1]))
self.assertEqual(len(subshapes1), 1)
self.assertTrue(subshapes1[0].isSame(box1ts.Solids[0]))
def testTopoShapeMapSubElement(self):
# Arrange
box = Part.makeBox(1,2,3)
# face = box.Faces[0] # Do not do this. Need the subelement call each usage.
# Assert everything empty
self.assertEqual(box.ElementMapSize,0)
self.assertEqual(box.Faces[0].ElementMapSize,0)
# Act
box.mapSubElement(box.Faces[0])
# Assert elementMaps created
if box.ElementMapVersion != "": # Should be '4' as of Mar 2023.
self.assertEqual(box.ElementMapSize,9) # 1 Face, 4 Edges, 4 Vertexes
self.assertEqual(box.Faces[0].ElementMapSize,9)
def testTopoShapeGetElementHistory(self):
self.doc.addObject("Part::Fuse", "Fuse")
self.doc.Fuse.Base = self.doc.Box1
self.doc.Fuse.Tool = self.doc.Box2
# Act
self.doc.recompute()
fuse1 = self.doc.Fuse.Shape
if fuse1.ElementMapVersion != "": # Should be '4' as of Mar 2023.
history1 = fuse1.getElementHistory(fuse1.ElementReverseMap["Vertex1"])
# Assert
self.assertEqual(len(history1),3) # Just the Fuse operation
# Todo: Still broken, still can't find parms that consistently work to test this.
# However, the results with an empty elementMap are consistent with making the
# same calls on LS3. So what this method is supposed to do remains a mystery;
# So far, it just wipes out the elementMap and returns the Toposhape.
# def testTopoShapeMapShapes(self):
# self.doc.addObject("Part::Fuse", "Fuse")
# self.doc.Fuse.Base = self.doc.Box1
# self.doc.Fuse.Tool = self.doc.Box2
# # Act
# self.doc.recompute()
# fuse1 = self.doc.Fuse.Shape
# res = fuse1.copy() # Make it mutable
# self.assertEqual(res.ElementMapSize,58)
# result = res.mapShapes([(fuse1, fuse1.Faces[0])], []) #[(res, res.Vertexes[0])])
# self.assertEqual(res.ElementMapSize,9)
# # result2 = fuse1.copy().mapShapes([],[(fuse1, fuse1.Edges[0]),(fuse1, fuse1.Edges[1])])
# self.assertEqual(fuse1.ElementMapSize,58) #
# self.assertEqual(fuse1.Faces[0].ElementMapSize,9)
# self.assertEqual(result.ElementMapSize,9)
# self.assertEqual(result.Faces[0].ElementMapSize,0)
# self.assertEqual(result2.ElementMapSize,9)
# self.assertEqual(result2.Faces[0].ElementMapSize,0)
# 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'