// SPDX-License-Identifier: LGPL-2.1-or-later
/****************************************************************************
 *   Copyright (c) 2017 Zheng Lei (realthunder) <realthunder.dev@gmail.com> *
 *                                                                          *
 *   This file is part of the FreeCAD CAx development system.               *
 *                                                                          *
 *   This library is free software; you can redistribute it and/or          *
 *   modify it under the terms of the GNU Library General Public            *
 *   License as published by the Free Software Foundation; either           *
 *   version 2 of the License, or (at your option) any later version.       *
 *                                                                          *
 *   This library  is distributed in the hope that it will be useful,       *
 *   but WITHOUT ANY WARRANTY; without even the implied warranty of         *
 *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the          *
 *   GNU Library General Public License for more details.                   *
 *                                                                          *
 *   You should have received a copy of the GNU Library General Public      *
 *   License along with this library; see the file COPYING.LIB. If not,     *
 *   write to the Free Software Foundation, Inc., 59 Temple Place,          *
 *   Suite 330, Boston, MA  02111-1307, USA                                 *
 *                                                                          *
 ****************************************************************************/


#include <Base/GeometryPyCXX.h>
#include <Base/PyWrapParseTupleAndKeywords.h>
#include <Mod/Part/App/OCCError.h>
#include <Mod/Part/App/TopoShapePy.h>

// inclusion of the generated files (generated out of AreaPy.xml)
#include "PathPy.h"
#include "AreaPy.h"
#include "AreaPy.cpp"


static PyObject* areaAbort(PyObject*, PyObject* args, PyObject* kwd)
{
    static const std::array<const char*, 2> kwlist {"aborting", nullptr};
    PyObject* pObj = Py_True;
    if (!Base::Wrapped_ParseTupleAndKeywords(args, kwd, "|O!", kwlist, &PyBool_Type, &pObj)) {
        return nullptr;
    }

    Area::abort(Base::asBoolean(pObj));

    Py_Return;
}

static PyObject* areaSetParams(PyObject*, PyObject* args, PyObject* kwd)
{

    static const std::array<const char*, 43> kwlist {
        PARAM_FIELD_STRINGS(NAME, AREA_PARAMS_STATIC_CONF),
        nullptr
    };

    if (args && PySequence_Size(args) > 0) {
        PyErr_SetString(PyExc_ValueError, "Non-keyword argument is not supported");
    }

    // Declare variables defined in the NAME field of the CONF parameter list
    PARAM_PY_DECLARE(PARAM_FNAME, AREA_PARAMS_STATIC_CONF);

    AreaStaticParams params = Area::getDefaultParams();

#define AREA_SET(_param) \
    PARAM_FNAME(_param) = PARAM_TYPED(PARAM_PY_CAST_, _param)(params.PARAM_FNAME(_param));
    // populate the CONF variables with params
    PARAM_FOREACH(AREA_SET, AREA_PARAMS_STATIC_CONF)

    // Parse arguments to overwrite CONF variables
    if (!Base::Wrapped_ParseTupleAndKeywords(
            args,
            kwd,
            "|" PARAM_PY_KWDS(AREA_PARAMS_STATIC_CONF),
            kwlist,
            PARAM_REF(PARAM_FNAME, AREA_PARAMS_STATIC_CONF)
        )) {
        return nullptr;
    }

#define AREA_GET(_param) \
    params.PARAM_FNAME(_param) = PARAM_TYPED(PARAM_CAST_PY_, _param)(PARAM_FNAME(_param));
    // populate 'params' with the CONF variables
    PARAM_FOREACH(AREA_GET, AREA_PARAMS_STATIC_CONF)

    Area::setDefaultParams(params);
    Py_INCREF(Py_None);
    return Py_None;
}

static PyObject* areaGetParams(PyObject*, PyObject* args)
{
    if (!PyArg_ParseTuple(args, "")) {
        return nullptr;
    }

    const AreaStaticParams& params = Area::getDefaultParams();

    PyObject* dict = PyDict_New();
#define AREA_SRC(_param) params.PARAM_FNAME(_param)
    PARAM_PY_DICT_SET_VALUE(dict, NAME, AREA_SRC, AREA_PARAMS_STATIC_CONF)
    return dict;
}

static PyObject* areaGetParamsDesc(PyObject*, PyObject* args, PyObject* kwd)
{
    PyObject* pcObj = Py_False;
    static const std::array<const char*, 2> kwlist {"as_string", nullptr};
    if (!Base::Wrapped_ParseTupleAndKeywords(args, kwd, "|O!", kwlist, &PyBool_Type, &pcObj)) {
        return nullptr;
    }

    if (Base::asBoolean(pcObj)) {
        return PyUnicode_FromString(PARAM_PY_DOC(NAME, AREA_PARAMS_STATIC_CONF));
    }

    PyObject* dict = PyDict_New();
    PARAM_PY_DICT_SET_DOC(dict, NAME, AREA_PARAMS_STATIC_CONF)
    return dict;
}

static const PyMethodDef areaOverrides[] = {
    {"setParams",
     nullptr,
     0,
     "setParam(key=value...): Set algorithm parameters. You can call getParamsDesc() to \n"
     "get a list of supported parameters and their descriptions.\n" PARAM_PY_DOC(NAME, AREA_PARAMS_CONF)},
    {
        "add",
        nullptr,
        0,
        "add((shape...)," PARAM_PY_ARGS_DOC(
            ARG,
            AREA_PARAMS_OPCODE
        ) "):\n"
          "Add TopoShape(s) with given operation code\n" PARAM_PY_DOC(
              ARG,
              AREA_PARAMS_OPCODE
          ) "\nThe first shape's wires will be unioned "
            "together regardless of the op code given\n"
            "(except for 'Compound'). Subsequent "
            "shape's wire will be combined using the "
            "op code.\n"
            "All shape wires shall be coplanar, and "
            "are used to determine a working plane for "
            "face\n"
            "making and offsetting. You can call "
            "setPlane() to supply a reference shape to "
            "determine\n"
            "the workplane in case the added shapes "
            "are all colinear lines.\n",
    },

    {
        "makeOffset",
        nullptr,
        0,
        "makeOffset(index=-1, " PARAM_PY_ARGS_DOC(
            ARG,
            AREA_PARAMS_OFFSET
        ) "):\n"
          "Make an 2D offset of the shape.\n"
          "\n* index (-1): the index of the section. -1 means all sections. "
          "No effect on planar shape.\n" PARAM_PY_DOC(ARG, AREA_PARAMS_OFFSET),
    },
    {
        "makePocket",
        nullptr,
        0,
        "makePocket(index=-1, " PARAM_PY_ARGS_DOC(
            ARG,
            AREA_PARAMS_POCKET
        ) "):\n"
          "Generate pocket toolpath of the shape.\n"
          "\n* index (-1): the index of the section. -1 means all sections. "
          "No effect on planar shape.\n" PARAM_PY_DOC(ARG, AREA_PARAMS_POCKET),
    },
    {
        "makeSections",
        nullptr,
        0,
        "makeSections(" PARAM_PY_ARGS_DOC(
            ARG,
            AREA_PARAMS_SECTION_EXTRA
        ) ", heights=[], plane=None):\n"
          "Make a list of area holding the sectioned children shapes "
          "on given heights\n" PARAM_PY_DOC(
              ARG,
              AREA_PARAMS_SECTION_EXTRA
          ) "\n* heights ([]): a list of "
            "section heights, the "
            "meaning of the value is "
            "determined by 'mode'.\n"
            "If not specified, the "
            "current SectionCount, and "
            "SectionOffset of this Area "
            "is used.\n"
            "\n* plane (None): optional "
            "shape to specify a section "
            "plane. If not give, the "
            "current workplane\n"
            "of this Area is used if "
            "section mode is "
            "'Workplane'.",
    },
    {
        "getClearedArea",
        nullptr,
        0,
        "getClearedArea(path, diameter, zmax, bbox):\n"
        "Gets the area cleared when a tool of the specified diameter follows the gcode represented "
        "in the path, ignoring cleared space above zmax and path segments that don't affect space "
        "within the x/y space of bbox.\n",
    },
    {
        "getRestArea",
        nullptr,
        0,
        "getRestArea(clearedAreas, diameter):\n"
        "Rest machining: gets the area left to be machined, assuming some of this area has already "
        "been cleared previous tool paths.\n"
        "clearedAreas: the regions already cleared.\n"
        "diameter: the tool diameter that finishes clearing this area.\n",
    },
    {"toTopoShape", nullptr, 0, "toTopoShape():\n"},
    {"setDefaultParams",
     reinterpret_cast<PyCFunction>(reinterpret_cast<void (*)()>(areaSetParams)),
     METH_VARARGS | METH_KEYWORDS | METH_STATIC,
     "setDefaultParams(key=value...):\n"
     "Static method to set the default parameters of all following Path.Area, plus the following\n"
     "additional parameters.\n"},
    {"getDefaultParams",
     (PyCFunction)areaGetParams,
     METH_VARARGS | METH_STATIC,
     "getDefaultParams(): Static method to return the current default parameters."},
    {
        "abort",
        reinterpret_cast<PyCFunction>(reinterpret_cast<void (*)()>(areaAbort)),
        METH_VARARGS | METH_KEYWORDS | METH_STATIC,
        "abort(aborting=True): Static method to abort any ongoing operation\n"
        "\nTo ensure no stray abortion is left in the previous operation, it is advised to "
        "manually clear\n"
        "the aborting flag by calling abort(False) before starting a new operation.",
    },
    {"getParamsDesc",
     reinterpret_cast<PyCFunction>(reinterpret_cast<void (*)()>(areaGetParamsDesc)),
     METH_VARARGS | METH_KEYWORDS | METH_STATIC,
     "getParamsDesc(as_string=False): Returns a list of supported parameters and their "
     "descriptions.\n"
     "\n* as_string: if False, then return a dictionary of documents of all supported parameters."},
};

struct AreaPyModifier
{
    AreaPyModifier()
    {
        for (auto& method : Path::AreaPy::Methods) {
            if (!method.ml_name) {
                continue;
            }
            for (auto& entry : areaOverrides) {
                if (std::strcmp(method.ml_name, entry.ml_name) == 0) {
                    if (entry.ml_doc) {
                        method.ml_doc = entry.ml_doc;
                    }
                    if (entry.ml_meth) {
                        method.ml_meth = entry.ml_meth;
                    }
                    if (entry.ml_flags) {
                        method.ml_flags = entry.ml_flags;
                    }
                    break;
                }
            }
        }
    }
};

static AreaPyModifier mod;

using namespace Path;

// returns a string which represents the object e.g. when printed in python
std::string AreaPy::representation() const
{
    std::stringstream str;
    str << "<Area object at " << getAreaPtr() << ">";
    return str.str();
}

PyObject* AreaPy::PyMake(struct _typeobject*, PyObject* args, PyObject* kwd)  // Python wrapper
{
    AreaPy* ret = new AreaPy(new Area);
    if (!ret->setParams(args, kwd)) {
        Py_DecRef(ret);
        return nullptr;
    }

    // If setParams() was successful it increments the ref counter.
    // So, it must be decremented again.
    Py_DecRef(ret);
    return ret;
}

// constructor method
int AreaPy::PyInit(PyObject*, PyObject*)
{
    return 0;
}

PyObject* AreaPy::setPlane(PyObject* args)
{
    PyObject* pcObj;
    if (!PyArg_ParseTuple(args, "O!", &(Part::TopoShapePy::Type), &pcObj)) {
        return nullptr;
    }

#define GET_TOPOSHAPE(_p) static_cast<Part::TopoShapePy*>(_p)->getTopoShapePtr()->getShape()
    getAreaPtr()->setPlane(GET_TOPOSHAPE(pcObj));
    Py_INCREF(this);
    return this;
}

PyObject* AreaPy::getShape(PyObject* args, PyObject* keywds)
{
    PyObject* pcObj = Py_False;
    short index = -1;
    static const std::array<const char*, 3> kwlist {"index", "rebuild", nullptr};
    if (!Base::Wrapped_ParseTupleAndKeywords(args, keywds, "|hO!", kwlist, &index, &PyBool_Type, &pcObj)) {
        return nullptr;
    }

    PY_TRY
    {
        if (Base::asBoolean(pcObj)) {
            getAreaPtr()->clean();
        }
        return Py::new_reference_to(Part::shape2pyshape(getAreaPtr()->getShape(index)));
    }
    PY_CATCH_OCC
}

PyObject* AreaPy::add(PyObject* args, PyObject* keywds)
{
    PARAM_PY_DECLARE_INIT(PARAM_FARG, AREA_PARAMS_OPCODE)
    PyObject* pcObj;

    // Strangely, PyArg_ParseTupleAndKeywords requires all arguments to be keyword based,
    // even non-optional ones? That doesn't make sense in python. Seems only in python 3
    // they added '$' to address that issue.
    static const std::array<const char*, 3> kwlist {
        "shape",
        PARAM_FIELD_STRINGS(ARG, AREA_PARAMS_OPCODE),
        nullptr
    };

    if (!Base::Wrapped_ParseTupleAndKeywords(
            args,
            keywds,
            "O|" PARAM_PY_KWDS(AREA_PARAMS_OPCODE),
            kwlist,
            &pcObj,
            PARAM_REF(PARAM_FARG, AREA_PARAMS_OPCODE)
        )) {
        return nullptr;
    }

    PY_TRY
    {
        if (PyObject_TypeCheck(pcObj, &(Part::TopoShapePy::Type))) {
            getAreaPtr()->add(GET_TOPOSHAPE(pcObj), op);
            Py_INCREF(this);
            return this;
        }
        else if (PyObject_TypeCheck(pcObj, &(PyList_Type))
                 || PyObject_TypeCheck(pcObj, &(PyTuple_Type))) {
            Py::Sequence shapeSeq(pcObj);
            for (Py::Sequence::iterator it = shapeSeq.begin(); it != shapeSeq.end(); ++it) {
                PyObject* item = (*it).ptr();
                if (!PyObject_TypeCheck(item, &(Part::TopoShapePy::Type))) {
                    PyErr_SetString(PyExc_TypeError, "non-shape object in sequence");
                    return nullptr;
                }
            }
            for (Py::Sequence::iterator it = shapeSeq.begin(); it != shapeSeq.end(); ++it) {
                PyObject* item = (*it).ptr();
                getAreaPtr()->add(GET_TOPOSHAPE(item), PARAM_PY_FIELDS(PARAM_FARG, AREA_PARAMS_OPCODE));
            }
            Py_INCREF(this);
            return this;
        }
    }
    PY_CATCH_OCC

    PyErr_SetString(PyExc_TypeError, "shape must be 'TopoShape' or list of 'TopoShape'");
    return nullptr;
}

PyObject* AreaPy::makeOffset(PyObject* args, PyObject* keywds)
{
    // Generate a keyword string defined in the ARG field of OFFSET parameter list
    static const std::array<const char*, 6> kwlist {
        "index",
        PARAM_FIELD_STRINGS(ARG, AREA_PARAMS_OFFSET),
        nullptr
    };
    short index = -1;

    // Declare variables defined in the ARG field of the OFFSET parameter list with
    // initialization to defaults
    PARAM_PY_DECLARE_INIT(PARAM_FARG, AREA_PARAMS_OFFSET)

    // Parse arguments to overwrite the defaults
    if (!Base::Wrapped_ParseTupleAndKeywords(
            args,
            keywds,
            "|h" PARAM_PY_KWDS(AREA_PARAMS_OFFSET),
            kwlist,
            &index,
            PARAM_REF(PARAM_FARG, AREA_PARAMS_OFFSET)
        )) {
        return nullptr;
    }

    PY_TRY
    {
        // Expand the variable as function call arguments
        TopoDS_Shape resultShape
            = getAreaPtr()->makeOffset(index, PARAM_PY_FIELDS(PARAM_FARG, AREA_PARAMS_OFFSET));
        return Py::new_reference_to(Part::shape2pyshape(resultShape));
    }
    PY_CATCH_OCC
}

PyObject* AreaPy::makePocket(PyObject* args, PyObject* keywds)
{
    static const std::array<const char*, 11> kwlist {
        "index",
        PARAM_FIELD_STRINGS(ARG, AREA_PARAMS_POCKET),
        nullptr
    };
    short index = -1;

    PARAM_PY_DECLARE_INIT(PARAM_FARG, AREA_PARAMS_POCKET)
    // Override pocket mode default
    mode = Area::PocketModeZigZagOffset;

    if (!Base::Wrapped_ParseTupleAndKeywords(
            args,
            keywds,
            "|h" PARAM_PY_KWDS(AREA_PARAMS_POCKET),
            kwlist,
            &index,
            PARAM_REF(PARAM_FARG, AREA_PARAMS_POCKET)
        )) {
        return nullptr;
    }

    PY_TRY
    {
        TopoDS_Shape resultShape
            = getAreaPtr()->makePocket(index, PARAM_PY_FIELDS(PARAM_FARG, AREA_PARAMS_POCKET));
        return Py::new_reference_to(Part::shape2pyshape(resultShape));
    }
    PY_CATCH_OCC
}

PyObject* AreaPy::makeSections(PyObject* args, PyObject* keywds)
{
    static const std::array<const char*, 5> kwlist {
        PARAM_FIELD_STRINGS(ARG, AREA_PARAMS_SECTION_EXTRA),
        "heights",
        "plane",
        nullptr
    };
    PyObject* heights = nullptr;
    PyObject* plane = nullptr;

    PARAM_PY_DECLARE_INIT(PARAM_FARG, AREA_PARAMS_SECTION_EXTRA)

    if (!Base::Wrapped_ParseTupleAndKeywords(
            args,
            keywds,
            "|" PARAM_PY_KWDS(AREA_PARAMS_SECTION_EXTRA) "OO!",
            kwlist,
            PARAM_REF(PARAM_FARG, AREA_PARAMS_SECTION_EXTRA),
            &heights,
            &(Part::TopoShapePy::Type),
            &plane
        )) {
        return nullptr;
    }

    PY_TRY
    {
        std::vector<double> h;
        if (heights) {
            if (PyObject_TypeCheck(heights, &(PyFloat_Type))) {
                h.push_back(PyFloat_AsDouble(heights));
            }
            else if (PyObject_TypeCheck(heights, &(PyList_Type))
                     || PyObject_TypeCheck(heights, &(PyTuple_Type))) {
                Py::Sequence shapeSeq(heights);
                h.reserve(shapeSeq.size());
                for (Py::Sequence::iterator it = shapeSeq.begin(); it != shapeSeq.end(); ++it) {
                    PyObject* item = (*it).ptr();
                    if (!PyObject_TypeCheck(item, &(PyFloat_Type))) {
                        PyErr_SetString(PyExc_TypeError, "heights must only contain float type");
                        return nullptr;
                    }
                    h.push_back(PyFloat_AsDouble(item));
                }
            }
            else {
                PyErr_SetString(PyExc_TypeError, "heights must be of type float or list/tuple of float");
                return nullptr;
            }
        }

        std::vector<std::shared_ptr<Area>> sections = getAreaPtr()->makeSections(
            PARAM_PY_FIELDS(PARAM_FARG, AREA_PARAMS_SECTION_EXTRA),
            h,
            plane ? GET_TOPOSHAPE(plane) : TopoDS_Shape()
        );

        Py::List ret;
        for (auto& area : sections) {
            ret.append(Py::asObject(new AreaPy(new Area(*area, true))));
        }
        return Py::new_reference_to(ret);
    }
    PY_CATCH_OCC
}

PyObject* AreaPy::getClearedArea(PyObject* args) {PY_TRY {PyObject * pyPath, *pyBbox;
double diameter, zmax;
if (!PyArg_ParseTuple(args, "OddO", &pyPath, &diameter, &zmax, &pyBbox)) {
    return nullptr;
}
if (!PyObject_TypeCheck(pyPath, &(PathPy::Type))) {
    PyErr_SetString(PyExc_TypeError, "path must be of type PathPy");
    return nullptr;
}
if (!PyObject_TypeCheck(pyBbox, &(Base::BoundBoxPy::Type))) {
    PyErr_SetString(PyExc_TypeError, "bbox must be of type BoundBoxPy");
    return nullptr;
}
const PathPy* path = static_cast<PathPy*>(pyPath);
const Py::BoundingBox bbox(pyBbox, false);
std::shared_ptr<Area> clearedArea
    = getAreaPtr()->getClearedArea(path->getToolpathPtr(), diameter, zmax, bbox.getValue());
auto pyClearedArea = Py::asObject(new AreaPy(new Area(*clearedArea, true)));
return Py::new_reference_to(pyClearedArea);
}
PY_CATCH_OCC
}

PyObject* AreaPy::getRestArea(PyObject* args) {PY_TRY {PyObject * pyClearedAreas;
std::vector<std::shared_ptr<Area>> clearedAreas;
double diameter;
if (!PyArg_ParseTuple(args, "Od", &pyClearedAreas, &diameter)) {
    return nullptr;
}
if (pyClearedAreas && PyObject_TypeCheck(pyClearedAreas, &PyList_Type)) {
    Py::Sequence clearedAreasSeq(pyClearedAreas);
    clearedAreas.reserve(clearedAreasSeq.size());
    for (Py::Sequence::iterator it = clearedAreasSeq.begin(); it != clearedAreasSeq.end(); ++it) {
        PyObject* item = (*it).ptr();
        if (!PyObject_TypeCheck(item, &(AreaPy::Type))) {
            PyErr_SetString(PyExc_TypeError, "cleared areas must only contain AreaPy type");
            return nullptr;
        }
        clearedAreas.push_back(std::make_shared<Area>(*static_cast<AreaPy*>(item)->getAreaPtr(), true));
    }
}
else {
    PyErr_SetString(PyExc_TypeError, "clearedAreas must be of type list of AreaPy");
    return nullptr;
}

std::shared_ptr<Area> restArea = getAreaPtr()->getRestArea(clearedAreas, diameter);
if (!restArea) {
    return Py_None;
}
auto pyRestArea = Py::asObject(new AreaPy(new Area(*restArea, true)));
return Py::new_reference_to(pyRestArea);
}
PY_CATCH_OCC
}

PyObject* AreaPy::toTopoShape(PyObject* args) {PY_TRY {if (!PyArg_ParseTuple(args, "")) return nullptr;
return Py::new_reference_to(Part::shape2pyshape(getAreaPtr()->toTopoShape()));
}
PY_CATCH_OCC
}

PyObject* AreaPy::setDefaultParams(PyObject*, PyObject*)
{
    return nullptr;
}

PyObject* AreaPy::setParams(PyObject* args, PyObject* keywds)
{
    static const std::array<const char*, 43> kwlist {
        PARAM_FIELD_STRINGS(NAME, AREA_PARAMS_CONF),
        nullptr
    };

    // Declare variables defined in the NAME field of the CONF parameter list
    PARAM_PY_DECLARE(PARAM_FNAME, AREA_PARAMS_CONF);

    AreaParams params = getAreaPtr()->getParams();

    // populate the CONF variables with params
    PARAM_FOREACH(AREA_SET, AREA_PARAMS_CONF)

    // Parse arguments to overwrite CONF variables
    if (!Base::Wrapped_ParseTupleAndKeywords(
            args,
            keywds,
            "|" PARAM_PY_KWDS(AREA_PARAMS_CONF),
            kwlist,
            PARAM_REF(PARAM_FNAME, AREA_PARAMS_CONF)
        )) {
        return nullptr;
    }

    PY_TRY
    {
        // populate 'params' with the CONF variables
        PARAM_FOREACH(AREA_GET, AREA_PARAMS_CONF)

        getAreaPtr()->setParams(params);
        Py_INCREF(this);
        return this;
    }
    PY_CATCH_OCC
}

PyObject* AreaPy::getParams(PyObject* args)
{
    if (!PyArg_ParseTuple(args, "")) {
        return nullptr;
    }

    const AreaParams& params = getAreaPtr()->getParams();

    PyObject* dict = PyDict_New();
    PARAM_PY_DICT_SET_VALUE(dict, NAME, AREA_SRC, AREA_PARAMS_CONF)
    return dict;
}

PyObject* AreaPy::getDefaultParams(PyObject*)
{
    return nullptr;
}

PyObject* AreaPy::abort(PyObject*, PyObject*)
{
    return nullptr;
}

PyObject* AreaPy::getParamsDesc(PyObject*, PyObject*)
{
    return nullptr;
}

Py::List AreaPy::getSections() const
{
    Py::List ret;
    Area* area = getAreaPtr();
    for (size_t i = 0, count = area->getSectionCount(); i < count; ++i) {
        ret.append(Part::shape2pyshape(getAreaPtr()->getShape(i)));
    }
    return ret;
}

Py::List AreaPy::getShapes() const
{
    Py::List ret;
    Area* area = getAreaPtr();
    const std::list<Area::Shape>& shapes = area->getChildren();
    for (auto& s : shapes) {
        ret.append(Py::TupleN(Part::shape2pyshape(s.shape), Py::Long(s.op)));
    }
    return ret;
}

Py::Object AreaPy::getWorkplane() const
{
    return Part::shape2pyshape(getAreaPtr()->getPlane());
}

void AreaPy::setWorkplane(Py::Object obj)
{
    PyObject* p = obj.ptr();
    if (!PyObject_TypeCheck(p, &(Part::TopoShapePy::Type))) {
        std::string error = std::string("type must be 'TopoShape', not ");
        error += p->ob_type->tp_name;
        throw Py::TypeError(error);
    }
    getAreaPtr()->setPlane(GET_TOPOSHAPE(p));
}

// custom attributes get/set

PyObject* AreaPy::getCustomAttributes(const char* /*attr*/) const
{
    return nullptr;
}

int AreaPy::setCustomAttributes(const char* /*attr*/, PyObject* /*obj*/)
{
    return 0;
}
